@monster-orange/den 0.4.18 → 0.4.19
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 +134 -0
- package/README.md +14 -13
- package/docs/distributions.md +99 -24
- package/docs/reference.md +1 -1
- package/package.json +2 -1
- package/skills/install-den/SKILL.md +35 -75
- package/src/shared/agents/den-claude.ts +40 -6
- package/src/shared/agents/guardrails/stop-hook.ts +30 -4
- package/src/shared/agents/guardrails.ts +40 -4
- package/src/shared/agents/handback.ts +22 -4
- package/src/shared/agents/personas.ts +32 -5
- package/src/shared/agents/runtime.ts +8 -0
- package/src/shared/agents/server-agent.ts +57 -0
- package/src/shared/agents/server-pack/server.md +45 -0
- package/src/shared/environment-manager/workspaces.ts +4 -0
- package/src/ui/cli/agent-ops.ts +41 -6
- package/src/ui/cli/create.ts +2 -2
- package/src/ui/cli/index.ts +1 -1
- package/src/ui/cli/repo-command.ts +8 -1
- package/src/ui/cli/repo-detect.ts +58 -0
- package/src/ui/cli/server-agent-ops.ts +411 -0
- package/src/ui/mcp/server.ts +29 -12
- package/src/ui/mcp/tools.ts +4 -2
- package/src/ui/server/agent-routes.ts +1 -1
- package/src/ui/server/held-actions.ts +35 -7
- package/src/ui/server/index.ts +20 -5
- package/src/ui/server/public/agents-page.js +10 -1
- package/src/ui/server/public/error-page.js +39 -1
- package/src/ui/server/public/graph.css +21 -0
- package/src/ui/server/public/graph.js +49 -6
- package/src/ui/server/public/header.js +29 -7
- package/src/ui/server/public/i18n/es.js +37 -0
- package/src/ui/server/public/i18n/ja.js +39 -0
- package/src/ui/server/public/local-dev.html +22 -1
- package/src/ui/server/public/persona-ui.css +6 -0
- package/src/ui/server/public/server-agent-page.js +415 -0
- package/src/ui/server/public/server-agent.html +222 -0
- package/src/ui/server/public/shell.js +66 -6
- package/src/ui/server/public/svc-panel.js +27 -14
- package/src/ui/server/public/topbar.js +13 -1
- package/src/ui/server/route-classes.ts +21 -0
- package/src/ui/server/server-agent-routes.ts +176 -0
- package/src/ui/server/session-routes.ts +12 -1
- package/src/ui/server/ui-assets.ts +2 -0
- package/src/ui/server/workspace-routes.ts +3 -0
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The server agent's page (ON-6): this Den's own agent in a terminal pane,
|
|
3
|
+
* framed as the page, with "Open in Agents" as the way out. A fresh Den's
|
|
4
|
+
* first page (the server serves it at /). Its status is GET
|
|
5
|
+
* /api/server-agent (server-agent-routes.ts).
|
|
6
|
+
*
|
|
7
|
+
* - Fresh, with a runtime that can run it: it starts by itself. It only
|
|
8
|
+
* reads until the person allows a write (each write is held for the
|
|
9
|
+
* browser's Allow, ON-3).
|
|
10
|
+
* - No runtime: the manual path, and how to install a runtime.
|
|
11
|
+
* - Running on Claude on a fresh Den: a line saying Claude asks to trust
|
|
12
|
+
* its folder (choose Yes), and one saying to log in to Claude in the
|
|
13
|
+
* terminal if it asks (Den can't tell whether it is logged in). Which
|
|
14
|
+
* runtime it runs on is its record's (runningRuntime), never the
|
|
15
|
+
* picker's: the two differ after the picker changes, or when the runtime
|
|
16
|
+
* is not found again on a later read.
|
|
17
|
+
* - Started by itself, but not running yet when the page reads it back:
|
|
18
|
+
* the page reads again, a few times, until it runs (so the terminal and
|
|
19
|
+
* its lines come up without a reload).
|
|
20
|
+
* - Not fresh and not running: a Start button (never started by itself).
|
|
21
|
+
* - The runtime picker saves the person's choice (agents.server_runtime),
|
|
22
|
+
* and the model picker beside it (agents.server_model), with only the
|
|
23
|
+
* runtime's own models.
|
|
24
|
+
*
|
|
25
|
+
* serverAgentView(status) is pure (HTML from the status JSON), so tests
|
|
26
|
+
* need no server.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
const t = (key, vars) => globalThis.window?.denShell?.t?.(key, vars) ?? key;
|
|
30
|
+
const esc = (s) =>
|
|
31
|
+
String(s ?? "").replace(
|
|
32
|
+
/[&<>"']/g,
|
|
33
|
+
(c) =>
|
|
34
|
+
({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[
|
|
35
|
+
c
|
|
36
|
+
],
|
|
37
|
+
);
|
|
38
|
+
const CLI = "__DEN_CLI__";
|
|
39
|
+
|
|
40
|
+
const RUNNING = new Set(["running", "stopping"]);
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* What the page shows for `status`: its header line, its body, whether
|
|
44
|
+
* the agent starts by itself, and whether a terminal opens.
|
|
45
|
+
*
|
|
46
|
+
* { sub, body, autoStart, terminal }
|
|
47
|
+
*/
|
|
48
|
+
export function serverAgentView(s) {
|
|
49
|
+
const running = RUNNING.has(s.state) && !!s.sessionId;
|
|
50
|
+
const hasRuntime = !!s.runtime;
|
|
51
|
+
const sub = s.fresh ? t("sa.sub.fresh") : t("sa.sub");
|
|
52
|
+
const readsOnly = `<p class="note sa-reads" data-note="reads-only">${esc(t("sa.readsOnly"))}</p>`;
|
|
53
|
+
// Den can't tell whether Claude is logged in (ON-6). On a fresh Den's
|
|
54
|
+
// first run, one line above the terminal says a login prompt is
|
|
55
|
+
// expected, so Claude's own login screen never reads as a crash.
|
|
56
|
+
// Claude Code asks once whether to trust the agent's folder, which Den
|
|
57
|
+
// made for it; Den doesn't answer for the person (that is Claude's own
|
|
58
|
+
// file, ~/.claude.json, with no documented way to pre-accept).
|
|
59
|
+
const onClaude =
|
|
60
|
+
s.fresh &&
|
|
61
|
+
(running ? (s.runningRuntime ?? s.runtime?.id) : s.runtime?.id) ===
|
|
62
|
+
"den-claude";
|
|
63
|
+
const login = onClaude
|
|
64
|
+
? `<p class="note sa-trust" data-note="trust">${esc(t("sa.trust"))}</p>` +
|
|
65
|
+
`<p class="note sa-login" data-note="login">${esc(t("sa.login", { name: s.name }))}</p>`
|
|
66
|
+
: "";
|
|
67
|
+
if (running)
|
|
68
|
+
return {
|
|
69
|
+
sub,
|
|
70
|
+
body: login + readsOnly,
|
|
71
|
+
autoStart: false,
|
|
72
|
+
terminal: s.sessionId,
|
|
73
|
+
};
|
|
74
|
+
if (!hasRuntime)
|
|
75
|
+
return {
|
|
76
|
+
sub,
|
|
77
|
+
body: noRuntimeHtml(s),
|
|
78
|
+
autoStart: false,
|
|
79
|
+
terminal: null,
|
|
80
|
+
};
|
|
81
|
+
// Fresh and never started (or stopped): it starts by itself.
|
|
82
|
+
if (s.fresh)
|
|
83
|
+
return {
|
|
84
|
+
sub,
|
|
85
|
+
body: `<p class="note" data-note="starting">${esc(t("sa.starting"))}</p>${readsOnly}`,
|
|
86
|
+
autoStart: true,
|
|
87
|
+
terminal: null,
|
|
88
|
+
};
|
|
89
|
+
return {
|
|
90
|
+
sub,
|
|
91
|
+
body:
|
|
92
|
+
`<div class="sa-idle" data-note="idle"><p>${esc(t("sa.idle", { name: s.name }))}</p>` +
|
|
93
|
+
`<button type="button" class="den-btn primary" data-act="start">${esc(t("sa.start"))}</button></div>` +
|
|
94
|
+
readsOnly,
|
|
95
|
+
autoStart: false,
|
|
96
|
+
terminal: null,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** No runtime can run it: how to install one, and the manual path. */
|
|
101
|
+
function noRuntimeHtml(s) {
|
|
102
|
+
const installs = (s.runtimes ?? [])
|
|
103
|
+
.filter((r) => !r.usable)
|
|
104
|
+
.map(
|
|
105
|
+
(r) =>
|
|
106
|
+
`<li data-runtime="${esc(r.id)}"><b>${esc(r.label ?? r.id)}</b>` +
|
|
107
|
+
(r.available && !r.denTools
|
|
108
|
+
? ` · ${esc(t("sa.noTools"))}`
|
|
109
|
+
: r.install
|
|
110
|
+
? ` · <code>${esc(r.install)}</code>`
|
|
111
|
+
: "") +
|
|
112
|
+
`</li>`,
|
|
113
|
+
)
|
|
114
|
+
.join("");
|
|
115
|
+
const manual = (s.manual ?? [])
|
|
116
|
+
.map((c) => `<li><code>${esc(c)}</code></li>`)
|
|
117
|
+
.join("");
|
|
118
|
+
return (
|
|
119
|
+
`<section class="sa-none" data-note="no-runtime"><h2 class="sec">${esc(t("sa.none.h"))}</h2>` +
|
|
120
|
+
`<p>${esc(t("sa.none"))}</p>` +
|
|
121
|
+
(installs ? `<ul class="sa-install">${installs}</ul>` : "") +
|
|
122
|
+
`</section>` +
|
|
123
|
+
`<section class="sa-manual" data-note="manual"><h2 class="sec">${esc(t("sa.manual.h"))}</h2>` +
|
|
124
|
+
`<p>${esc(t("sa.manual"))}</p><ol class="sa-steps">${manual}</ol></section>`
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** The model picker's options: the runtime's own models, the chosen one selected. */
|
|
129
|
+
export function modelOptionsHtml(s) {
|
|
130
|
+
const models = s.models ?? [];
|
|
131
|
+
if (!models.length) return "";
|
|
132
|
+
return models
|
|
133
|
+
.map(
|
|
134
|
+
(m) =>
|
|
135
|
+
`<option value="${esc(m.id)}"${s.model === m.id ? " selected" : ""}>${esc(m.label ?? m.id)}</option>`,
|
|
136
|
+
)
|
|
137
|
+
.join("");
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** The runtime picker's options: the usable ones, the chosen one first. */
|
|
141
|
+
export function runtimeOptionsHtml(s) {
|
|
142
|
+
const usable = (s.runtimes ?? []).filter((r) => r.usable);
|
|
143
|
+
if (!usable.length) return "";
|
|
144
|
+
return usable
|
|
145
|
+
.map(
|
|
146
|
+
(r) =>
|
|
147
|
+
`<option value="${esc(r.id)}"${s.runtime?.id === r.id ? " selected" : ""}>${esc(r.label ?? r.id)}</option>`,
|
|
148
|
+
)
|
|
149
|
+
.join("");
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// ── the page ────────────────────────────────────────────────────────────
|
|
153
|
+
|
|
154
|
+
const st = {
|
|
155
|
+
root: null,
|
|
156
|
+
status: null,
|
|
157
|
+
pane: null,
|
|
158
|
+
starting: false,
|
|
159
|
+
autoTried: false,
|
|
160
|
+
/** Reads after a start that came back not running yet. */
|
|
161
|
+
rereads: 0,
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
/** How many times, a second apart, a start is read back until it runs. */
|
|
165
|
+
const REREADS = 15;
|
|
166
|
+
|
|
167
|
+
async function readStatus() {
|
|
168
|
+
try {
|
|
169
|
+
const r = await fetch("/api/server-agent", { headers: { accept: "json" } });
|
|
170
|
+
return r.ok ? await r.json() : null;
|
|
171
|
+
} catch {
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function paint() {
|
|
177
|
+
const s = st.status;
|
|
178
|
+
const sub = st.root.querySelector("#sa-sub");
|
|
179
|
+
const body = st.root.querySelector("#sa-body");
|
|
180
|
+
const open = st.root.querySelector("#sa-open");
|
|
181
|
+
const pick = st.root.querySelector("#sa-runtime");
|
|
182
|
+
const mpick = st.root.querySelector("#sa-model");
|
|
183
|
+
body.removeAttribute("aria-busy");
|
|
184
|
+
if (!s) {
|
|
185
|
+
sub.textContent = t("sa.unread");
|
|
186
|
+
body.innerHTML = "";
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
const v = serverAgentView(s);
|
|
190
|
+
sub.textContent = v.sub;
|
|
191
|
+
body.innerHTML = v.body;
|
|
192
|
+
const opts = runtimeOptionsHtml(s);
|
|
193
|
+
pick.hidden = !opts;
|
|
194
|
+
if (opts) pick.querySelector("select").innerHTML = opts;
|
|
195
|
+
const mopts = modelOptionsHtml(s);
|
|
196
|
+
if (mpick) {
|
|
197
|
+
mpick.hidden = !mopts;
|
|
198
|
+
if (mopts) mpick.querySelector("select").innerHTML = mopts;
|
|
199
|
+
}
|
|
200
|
+
open.hidden = !v.terminal;
|
|
201
|
+
if (v.terminal)
|
|
202
|
+
open.setAttribute("href", `/agents?open=${encodeURIComponent(v.terminal)}`);
|
|
203
|
+
return v;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
async function refresh() {
|
|
207
|
+
st.status = await readStatus();
|
|
208
|
+
const v = paint();
|
|
209
|
+
if (!v) return;
|
|
210
|
+
if (v.terminal) void openTerminal(v.terminal);
|
|
211
|
+
else closeTerminal();
|
|
212
|
+
// Once per page: a start that ends at once is never retried in a loop.
|
|
213
|
+
if (v.autoStart && !st.autoTried) {
|
|
214
|
+
st.autoTried = true;
|
|
215
|
+
void start();
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
// Started, but not running yet: read again until it is.
|
|
219
|
+
if (st.autoTried && !v.terminal && v.autoStart && st.rereads < REREADS) {
|
|
220
|
+
st.rereads++;
|
|
221
|
+
setTimeout(() => void refresh(), 1000);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
async function start() {
|
|
226
|
+
if (st.starting) return;
|
|
227
|
+
st.starting = true;
|
|
228
|
+
try {
|
|
229
|
+
await window.denShell.api("POST", "/api/server-agent/start", {});
|
|
230
|
+
} catch (err) {
|
|
231
|
+
st.starting = false;
|
|
232
|
+
const body = st.root.querySelector("#sa-body");
|
|
233
|
+
const why = err?.message ?? String(err);
|
|
234
|
+
body.innerHTML = `<p class="note bad" data-note="failed" role="alert">${esc(t("sa.failed", { why, cli: CLI }))}</p>`;
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
st.starting = false;
|
|
238
|
+
await refresh();
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
async function saveModel(id) {
|
|
242
|
+
try {
|
|
243
|
+
await window.denShell.api("POST", "/api/server-agent/model", {
|
|
244
|
+
model: id,
|
|
245
|
+
});
|
|
246
|
+
window.denShell?.toast?.(t("sa.saved"));
|
|
247
|
+
} catch (err) {
|
|
248
|
+
window.denShell?.errorToast?.(err);
|
|
249
|
+
}
|
|
250
|
+
await refresh();
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
async function saveRuntime(id) {
|
|
254
|
+
try {
|
|
255
|
+
await window.denShell.api("POST", "/api/server-agent/runtime", {
|
|
256
|
+
runtime: id,
|
|
257
|
+
});
|
|
258
|
+
window.denShell?.toast?.(t("sa.saved"));
|
|
259
|
+
} catch (err) {
|
|
260
|
+
window.denShell?.errorToast?.(err);
|
|
261
|
+
}
|
|
262
|
+
await refresh();
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// ── the terminal ────────────────────────────────────────────────────────
|
|
266
|
+
|
|
267
|
+
function cssVar(name) {
|
|
268
|
+
const v = getComputedStyle(document.documentElement)
|
|
269
|
+
.getPropertyValue(name)
|
|
270
|
+
.trim();
|
|
271
|
+
return v || undefined;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
async function csrf() {
|
|
275
|
+
try {
|
|
276
|
+
const r = await fetch("/api/session", { credentials: "same-origin" });
|
|
277
|
+
const b = r.ok ? await r.json() : null;
|
|
278
|
+
return typeof b?.csrf === "string" ? b.csrf : null;
|
|
279
|
+
} catch {
|
|
280
|
+
return null;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function closeTerminal() {
|
|
285
|
+
const p = st.pane;
|
|
286
|
+
if (!p) return;
|
|
287
|
+
st.pane = null;
|
|
288
|
+
p.closed = true;
|
|
289
|
+
p.sock?.close();
|
|
290
|
+
p.term?.dispose();
|
|
291
|
+
p.observer?.disconnect();
|
|
292
|
+
p.el.remove();
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
async function openTerminal(id) {
|
|
296
|
+
if (st.pane?.id === id) return;
|
|
297
|
+
closeTerminal();
|
|
298
|
+
const el = document.createElement("div");
|
|
299
|
+
el.className = "sa-term";
|
|
300
|
+
el.dataset.id = id;
|
|
301
|
+
el.innerHTML = `<div class="tbox"></div>`;
|
|
302
|
+
st.root.querySelector("#sa-pane").appendChild(el);
|
|
303
|
+
const p = { id, el, term: null, fit: null, sock: null, closed: false };
|
|
304
|
+
st.pane = p;
|
|
305
|
+
let lib;
|
|
306
|
+
try {
|
|
307
|
+
const [x, f] = await Promise.all([
|
|
308
|
+
import("/vendor/xterm.mjs"),
|
|
309
|
+
import("/vendor/addon-fit.mjs"),
|
|
310
|
+
]);
|
|
311
|
+
lib = { Terminal: x.Terminal, FitAddon: f.FitAddon };
|
|
312
|
+
} catch (err) {
|
|
313
|
+
el.innerHTML = `<p class="note">${esc(err?.message ?? String(err))}</p>`;
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
if (p.closed) return;
|
|
317
|
+
const bg = cssVar("--sunken");
|
|
318
|
+
const term = new lib.Terminal({
|
|
319
|
+
fontFamily: cssVar("--mono") ?? "monospace",
|
|
320
|
+
fontSize: 12,
|
|
321
|
+
lineHeight: 1.25,
|
|
322
|
+
scrollback: 5000,
|
|
323
|
+
theme: { background: bg, foreground: cssVar("--text"), cursorAccent: bg },
|
|
324
|
+
});
|
|
325
|
+
const fit = new lib.FitAddon();
|
|
326
|
+
term.loadAddon(fit);
|
|
327
|
+
term.open(el.querySelector(".tbox"));
|
|
328
|
+
p.term = term;
|
|
329
|
+
p.fit = fit;
|
|
330
|
+
const refit = () => {
|
|
331
|
+
try {
|
|
332
|
+
fit.fit();
|
|
333
|
+
} catch {
|
|
334
|
+
/* not laid out yet */
|
|
335
|
+
}
|
|
336
|
+
};
|
|
337
|
+
refit();
|
|
338
|
+
p.observer = new ResizeObserver(refit);
|
|
339
|
+
p.observer.observe(el);
|
|
340
|
+
const enc = new TextEncoder();
|
|
341
|
+
term.onData((d) => {
|
|
342
|
+
if (p.sock?.readyState === WebSocket.OPEN) p.sock.send(enc.encode(d));
|
|
343
|
+
});
|
|
344
|
+
const size = () => {
|
|
345
|
+
if (p.sock?.readyState === WebSocket.OPEN && term.cols > 1 && term.rows > 1)
|
|
346
|
+
p.sock.send(
|
|
347
|
+
JSON.stringify({ type: "resize", cols: term.cols, rows: term.rows }),
|
|
348
|
+
);
|
|
349
|
+
};
|
|
350
|
+
term.onResize(size);
|
|
351
|
+
const token = await csrf();
|
|
352
|
+
if (p.closed) return;
|
|
353
|
+
const proto = location.protocol === "https:" ? "wss:" : "ws:";
|
|
354
|
+
const sock = new WebSocket(
|
|
355
|
+
`${proto}//${location.host}/api/sessions/${encodeURIComponent(id)}/io`,
|
|
356
|
+
["den.io", `den.csrf.${token ?? ""}`],
|
|
357
|
+
);
|
|
358
|
+
sock.binaryType = "arraybuffer";
|
|
359
|
+
p.sock = sock;
|
|
360
|
+
sock.onopen = () => {
|
|
361
|
+
refit();
|
|
362
|
+
size();
|
|
363
|
+
term.focus();
|
|
364
|
+
};
|
|
365
|
+
sock.onmessage = (e) => {
|
|
366
|
+
if (typeof e.data === "string") {
|
|
367
|
+
let msg = null;
|
|
368
|
+
try {
|
|
369
|
+
msg = JSON.parse(e.data);
|
|
370
|
+
} catch {
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
if (msg?.type === "exit") {
|
|
374
|
+
term.write(`\r\n${t("ag.exitedLine", { code: "" })}\r\n`);
|
|
375
|
+
setTimeout(() => void refresh(), 1000);
|
|
376
|
+
}
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
term.write(new Uint8Array(e.data));
|
|
380
|
+
};
|
|
381
|
+
sock.onclose = (e) => {
|
|
382
|
+
if (p.closed || p.sock !== sock) return;
|
|
383
|
+
p.sock = null;
|
|
384
|
+
term.write(`\r\n${t("ag.lost", { why: e.reason || String(e.code) })}\r\n`);
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
export async function mountServerAgentPage(root) {
|
|
389
|
+
if (!root) return;
|
|
390
|
+
st.root = root;
|
|
391
|
+
for (const [sel, key] of [
|
|
392
|
+
["#sa-title", "n.serverAgent"],
|
|
393
|
+
["#sa-runtime > span", "sa.runsOn"],
|
|
394
|
+
["#sa-model > span", "sa.model"],
|
|
395
|
+
["#sa-open", "sa.openAgents"],
|
|
396
|
+
]) {
|
|
397
|
+
const el = root.querySelector(sel);
|
|
398
|
+
if (el) el.textContent = t(key);
|
|
399
|
+
}
|
|
400
|
+
if (window.denShell?.apiOff?.()) return;
|
|
401
|
+
root.addEventListener("click", (e) => {
|
|
402
|
+
const b = e.target.closest("button[data-act]");
|
|
403
|
+
if (b?.dataset.act === "start") {
|
|
404
|
+
b.disabled = true;
|
|
405
|
+
void start();
|
|
406
|
+
}
|
|
407
|
+
});
|
|
408
|
+
root.querySelector("#sa-runtime select")?.addEventListener("change", (e) => {
|
|
409
|
+
void saveRuntime(e.target.value);
|
|
410
|
+
});
|
|
411
|
+
root.querySelector("#sa-model select")?.addEventListener("change", (e) => {
|
|
412
|
+
void saveModel(e.target.value);
|
|
413
|
+
});
|
|
414
|
+
await refresh();
|
|
415
|
+
}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<script>
|
|
7
|
+
// Mode before first paint: yours (the server fills it in below),
|
|
8
|
+
// else the system's, else the theme's default, else dark.
|
|
9
|
+
(function () {
|
|
10
|
+
var m = "@@DEN_MODE@@";
|
|
11
|
+
if (m !== "dark" && m !== "light") {
|
|
12
|
+
var q = function (s) {
|
|
13
|
+
return (
|
|
14
|
+
window.matchMedia &&
|
|
15
|
+
matchMedia("(prefers-color-scheme: " + s + ")").matches
|
|
16
|
+
);
|
|
17
|
+
};
|
|
18
|
+
m = q("light")
|
|
19
|
+
? "light"
|
|
20
|
+
: q("dark")
|
|
21
|
+
? "dark"
|
|
22
|
+
: "@@DEN_DEFAULT_MODE@@";
|
|
23
|
+
}
|
|
24
|
+
document.documentElement.dataset.mode =
|
|
25
|
+
m === "light" ? "light" : "dark";
|
|
26
|
+
})();
|
|
27
|
+
</script>
|
|
28
|
+
<link rel="stylesheet" href="/theme.css" id="theme-css" />
|
|
29
|
+
<link rel="stylesheet" href="/vendor/xterm.css" />
|
|
30
|
+
<title>__DEN_TITLE__ · Server agent</title>
|
|
31
|
+
<style>
|
|
32
|
+
/* The server agent's page (ON-6): the prototype's page header (.ph,
|
|
33
|
+
R20), then the agent's terminal as the page, the Agents tab's pane
|
|
34
|
+
look (.pane .term) without its list. --r is --radius. */
|
|
35
|
+
*,
|
|
36
|
+
*::before,
|
|
37
|
+
*::after {
|
|
38
|
+
box-sizing: border-box;
|
|
39
|
+
}
|
|
40
|
+
html,
|
|
41
|
+
body {
|
|
42
|
+
height: 100%;
|
|
43
|
+
margin: 0;
|
|
44
|
+
background: var(--bg);
|
|
45
|
+
color: var(--text);
|
|
46
|
+
}
|
|
47
|
+
body {
|
|
48
|
+
display: flex;
|
|
49
|
+
flex-direction: column;
|
|
50
|
+
overflow: hidden;
|
|
51
|
+
}
|
|
52
|
+
html body.den-has-shell > #den-content {
|
|
53
|
+
display: flex;
|
|
54
|
+
flex-direction: column;
|
|
55
|
+
min-width: 0;
|
|
56
|
+
min-height: 0;
|
|
57
|
+
overflow: auto;
|
|
58
|
+
padding: 20px 24px 14px 24px;
|
|
59
|
+
font: 14px/1.45 var(--sans);
|
|
60
|
+
color: var(--text);
|
|
61
|
+
}
|
|
62
|
+
#den-content :focus-visible {
|
|
63
|
+
outline: 2px solid var(--accent);
|
|
64
|
+
outline-offset: 2px;
|
|
65
|
+
}
|
|
66
|
+
a {
|
|
67
|
+
color: var(--accent-text);
|
|
68
|
+
}
|
|
69
|
+
.ph {
|
|
70
|
+
display: flex;
|
|
71
|
+
align-items: flex-end;
|
|
72
|
+
gap: 16px;
|
|
73
|
+
flex-wrap: wrap;
|
|
74
|
+
margin-bottom: 12px;
|
|
75
|
+
}
|
|
76
|
+
.ph h1 {
|
|
77
|
+
font: 600 22px/1.2 var(--display);
|
|
78
|
+
margin: 0;
|
|
79
|
+
text-wrap: balance;
|
|
80
|
+
}
|
|
81
|
+
.ph .sub {
|
|
82
|
+
color: var(--muted);
|
|
83
|
+
font-size: 13px;
|
|
84
|
+
margin-top: 4px;
|
|
85
|
+
}
|
|
86
|
+
.ph .acts {
|
|
87
|
+
margin-left: auto;
|
|
88
|
+
display: flex;
|
|
89
|
+
align-items: center;
|
|
90
|
+
gap: 10px;
|
|
91
|
+
flex-wrap: wrap;
|
|
92
|
+
}
|
|
93
|
+
.ph label {
|
|
94
|
+
font-size: 13px;
|
|
95
|
+
color: var(--muted);
|
|
96
|
+
display: flex;
|
|
97
|
+
align-items: center;
|
|
98
|
+
gap: 6px;
|
|
99
|
+
}
|
|
100
|
+
.ph select {
|
|
101
|
+
font: inherit;
|
|
102
|
+
color: var(--text);
|
|
103
|
+
background: var(--surface);
|
|
104
|
+
border: 1px solid var(--border);
|
|
105
|
+
border-radius: var(--radius-button);
|
|
106
|
+
padding: 4px 8px;
|
|
107
|
+
}
|
|
108
|
+
.note {
|
|
109
|
+
color: var(--muted);
|
|
110
|
+
font-size: 13px;
|
|
111
|
+
margin: 0 0 10px;
|
|
112
|
+
}
|
|
113
|
+
.note.bad {
|
|
114
|
+
color: var(--bad);
|
|
115
|
+
}
|
|
116
|
+
h2.sec {
|
|
117
|
+
font: 600 13px var(--mono);
|
|
118
|
+
text-transform: uppercase;
|
|
119
|
+
letter-spacing: 0.07em;
|
|
120
|
+
color: var(--muted);
|
|
121
|
+
margin: 0 0 10px;
|
|
122
|
+
}
|
|
123
|
+
section {
|
|
124
|
+
margin-bottom: 22px;
|
|
125
|
+
}
|
|
126
|
+
code {
|
|
127
|
+
font: 12.5px var(--mono);
|
|
128
|
+
}
|
|
129
|
+
.sa-steps li,
|
|
130
|
+
.sa-install li {
|
|
131
|
+
margin-bottom: 6px;
|
|
132
|
+
}
|
|
133
|
+
.sa-idle {
|
|
134
|
+
display: flex;
|
|
135
|
+
align-items: center;
|
|
136
|
+
gap: 12px;
|
|
137
|
+
margin-bottom: 10px;
|
|
138
|
+
}
|
|
139
|
+
.sa-idle p {
|
|
140
|
+
margin: 0;
|
|
141
|
+
}
|
|
142
|
+
/* The terminal is the page: it takes what is left. */
|
|
143
|
+
#sa-pane {
|
|
144
|
+
flex: 1 1 auto;
|
|
145
|
+
min-height: 0;
|
|
146
|
+
display: flex;
|
|
147
|
+
}
|
|
148
|
+
#sa-pane:empty {
|
|
149
|
+
display: none;
|
|
150
|
+
}
|
|
151
|
+
.sa-term {
|
|
152
|
+
flex: 1 1 auto;
|
|
153
|
+
min-height: 240px;
|
|
154
|
+
min-width: 0;
|
|
155
|
+
display: grid;
|
|
156
|
+
grid-template: minmax(0, 1fr) / minmax(0, 1fr);
|
|
157
|
+
padding: 6px 4px 4px 8px;
|
|
158
|
+
background: var(--sunken);
|
|
159
|
+
border: 1px solid var(--border);
|
|
160
|
+
border-radius: var(--radius);
|
|
161
|
+
overflow: hidden;
|
|
162
|
+
}
|
|
163
|
+
.sa-term .tbox {
|
|
164
|
+
min-height: 0;
|
|
165
|
+
min-width: 0;
|
|
166
|
+
overflow: hidden;
|
|
167
|
+
}
|
|
168
|
+
.sa-term .xterm {
|
|
169
|
+
height: 100%;
|
|
170
|
+
}
|
|
171
|
+
.sa-term .xterm .xterm-viewport {
|
|
172
|
+
background-color: var(--sunken);
|
|
173
|
+
}
|
|
174
|
+
</style>
|
|
175
|
+
</head>
|
|
176
|
+
<body>
|
|
177
|
+
<!-- The shell: top bar and rail, the same on every page (shell.js). -->
|
|
178
|
+
<div id="den-shell" data-page="server-agent"></div>
|
|
179
|
+
<script src="/shell.js"></script>
|
|
180
|
+
|
|
181
|
+
<!-- The page's one main landmark: everything after the shell. -->
|
|
182
|
+
<main id="den-content">
|
|
183
|
+
<div class="ph">
|
|
184
|
+
<div>
|
|
185
|
+
<h1 id="sa-title">__DEN_TITLE__ agent</h1>
|
|
186
|
+
<div class="sub" id="sa-sub"></div>
|
|
187
|
+
</div>
|
|
188
|
+
<div class="acts">
|
|
189
|
+
<label id="sa-runtime" hidden
|
|
190
|
+
><span>Runs on</span> <select></select
|
|
191
|
+
></label>
|
|
192
|
+
<label id="sa-model" hidden
|
|
193
|
+
><span>Model</span> <select></select
|
|
194
|
+
></label>
|
|
195
|
+
<a class="den-btn" id="sa-open" href="/agents" hidden
|
|
196
|
+
>Open in Agents</a
|
|
197
|
+
>
|
|
198
|
+
</div>
|
|
199
|
+
</div>
|
|
200
|
+
<div id="sa-body" aria-busy="true" aria-live="polite"></div>
|
|
201
|
+
<div id="sa-pane"></div>
|
|
202
|
+
|
|
203
|
+
<script type="module">
|
|
204
|
+
import {
|
|
205
|
+
applyDenConfig,
|
|
206
|
+
initChangelog,
|
|
207
|
+
initHealth,
|
|
208
|
+
initLogBadge,
|
|
209
|
+
initWorkspacePill,
|
|
210
|
+
} from "/header.js";
|
|
211
|
+
import { mountServerAgentPage } from "/server-agent-page.js";
|
|
212
|
+
void Promise.all([
|
|
213
|
+
initHealth(),
|
|
214
|
+
initWorkspacePill(),
|
|
215
|
+
applyDenConfig(),
|
|
216
|
+
initChangelog(),
|
|
217
|
+
]).then(initLogBadge);
|
|
218
|
+
void mountServerAgentPage(document.getElementById("den-content"));
|
|
219
|
+
</script>
|
|
220
|
+
</main>
|
|
221
|
+
</body>
|
|
222
|
+
</html>
|