@monster-orange/den 0.4.7 → 0.4.8
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 +94 -0
- package/README.md +7 -7
- package/docs/distributions.md +120 -1
- package/package.json +1 -1
- package/schemas/den.plugin.v1.json +3 -2
- package/src/shared/agents/den-claude.ts +19 -0
- package/src/shared/agents/personas.ts +11 -1
- package/src/shared/agents/records.ts +114 -0
- package/src/shared/agents/runtime.ts +12 -0
- package/src/shared/den-config.ts +30 -0
- package/src/shared/plugins/manifest.ts +3 -0
- package/src/shared/usage/paths.ts +16 -0
- package/src/shared/usage/report.ts +283 -0
- package/src/shared/usage/sources.ts +127 -0
- package/src/shared/usage/transcript.ts +262 -0
- package/src/ui/cli/agent-ops.ts +488 -50
- package/src/ui/cli/agents-command.ts +120 -14
- package/src/ui/cli/index.ts +13 -1
- package/src/ui/cli/usage-command.ts +256 -0
- package/src/ui/mcp/api.ts +10 -1
- package/src/ui/mcp/server.ts +34 -0
- package/src/ui/mcp/tools.ts +50 -0
- package/src/ui/server/agent-resume-grant.ts +53 -0
- package/src/ui/server/agent-routes.ts +230 -9
- package/src/ui/server/audit.ts +68 -1
- package/src/ui/server/index.ts +16 -5
- package/src/ui/server/public/agents-page.js +333 -11
- package/src/ui/server/public/agents-section.js +17 -3
- package/src/ui/server/public/agents.html +44 -0
- package/src/ui/server/public/i18n/es.js +28 -0
- package/src/ui/server/public/i18n/ja.js +25 -0
- package/src/ui/server/public/shell.js +66 -0
- package/src/ui/server/request-guard.ts +21 -1
- package/src/ui/server/route-classes.ts +23 -0
- package/src/ui/server/session-routes.ts +52 -4
- package/src/ui/server/sudo-store.ts +36 -7
|
@@ -31,6 +31,15 @@
|
|
|
31
31
|
* can't, before Start (agent-start.js readiness). Starting, stopping and
|
|
32
32
|
* resuming an agent never ask for the admin password: it is for sudo, and
|
|
33
33
|
* no agent action runs sudo (LK-0).
|
|
34
|
+
*
|
|
35
|
+
* Stopped agents (AG-14) stay listed under their workspace, below the
|
|
36
|
+
* running ones, each with why it stopped ("Stopped by you 19:52",
|
|
37
|
+
* "Interrupted: Den restarted", "Exited (code 1)") and its actions:
|
|
38
|
+
* Resume (the same conversation; its pane opens), Start fresh, Remove (one
|
|
39
|
+
* click, and Undo in the toast for 10 s). A runtime that can't resume
|
|
40
|
+
* shows only Start fresh, saying so. Clear stopped removes every stopped
|
|
41
|
+
* agent in view. After a restart, one line says how many were interrupted,
|
|
42
|
+
* with Resume all and Review.
|
|
34
43
|
*/
|
|
35
44
|
import { fillStartForm } from "/agent-start.js";
|
|
36
45
|
|
|
@@ -45,6 +54,9 @@ const esc = (s) =>
|
|
|
45
54
|
);
|
|
46
55
|
const api = (method, url, body) => window.denShell.api(method, url, body);
|
|
47
56
|
const CLI = "__DEN_CLI__";
|
|
57
|
+
const TITLE = "__DEN_TITLE__";
|
|
58
|
+
/** How long Remove's Undo stays offered (AG-14.1b). */
|
|
59
|
+
export const UNDO_MS = 10_000;
|
|
48
60
|
|
|
49
61
|
/** The most panes at once (the prototype's 2x2). */
|
|
50
62
|
export const MAX_PANES = 4;
|
|
@@ -88,11 +100,17 @@ function writeUrl() {
|
|
|
88
100
|
|
|
89
101
|
// ── the list ────────────────────────────────────────────────────────────
|
|
90
102
|
|
|
91
|
-
/**
|
|
103
|
+
/** ?ws=: the one workspace the page was asked for, or null. */
|
|
104
|
+
const urlWorkspace = () => new URLSearchParams(location.search).get("ws");
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* The workspaces to show: ?ws= (always that one, even when it isn't active
|
|
108
|
+
* or isn't known: never All in its place), else the scope bar's view.
|
|
109
|
+
*/
|
|
92
110
|
function viewWorkspaces() {
|
|
93
111
|
const all = st.data?.workspaces ?? [];
|
|
94
|
-
const ws =
|
|
95
|
-
if (ws
|
|
112
|
+
const ws = urlWorkspace();
|
|
113
|
+
if (ws) return [ws];
|
|
96
114
|
if (st.scope && st.scope !== "all" && all.includes(st.scope))
|
|
97
115
|
return [st.scope];
|
|
98
116
|
return all;
|
|
@@ -112,7 +130,9 @@ function stateText(state) {
|
|
|
112
130
|
return m[1] === undefined
|
|
113
131
|
? t("ag.state.exited")
|
|
114
132
|
: t("ag.state.exitedCode", { code: m[1] });
|
|
115
|
-
return ["running", "stopping", "stopped", "ended"].includes(
|
|
133
|
+
return ["running", "stopping", "stopped", "ended", "interrupted"].includes(
|
|
134
|
+
state,
|
|
135
|
+
)
|
|
116
136
|
? t(`ag.state.${state}`)
|
|
117
137
|
: state;
|
|
118
138
|
}
|
|
@@ -127,7 +147,73 @@ function paneName(r) {
|
|
|
127
147
|
return r.kind === "agent" ? r.displayName || r.agent || r.label : r.label;
|
|
128
148
|
}
|
|
129
149
|
|
|
150
|
+
/** An agent that isn't running: stopped, interrupted or exited (AG-14). */
|
|
151
|
+
export function isStopped(r) {
|
|
152
|
+
return r.kind === "agent" && r.state !== "running" && r.state !== "stopping";
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** "19:52" today, else "Nov 6, 19:52" (the browser's own format). */
|
|
156
|
+
function when(iso) {
|
|
157
|
+
const d = new Date(iso);
|
|
158
|
+
if (Number.isNaN(d.getTime())) return "";
|
|
159
|
+
const time = d.toLocaleTimeString([], {
|
|
160
|
+
hour: "2-digit",
|
|
161
|
+
minute: "2-digit",
|
|
162
|
+
hour12: false,
|
|
163
|
+
});
|
|
164
|
+
const today = new Date().toDateString() === d.toDateString();
|
|
165
|
+
return today
|
|
166
|
+
? time
|
|
167
|
+
: `${d.toLocaleDateString([], { month: "short", day: "numeric" })}, ${time}`;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** Why a stopped agent stopped (AG-14.1). */
|
|
171
|
+
export function stopReason(r) {
|
|
172
|
+
if (r.state === "interrupted")
|
|
173
|
+
return t("ag.why.interrupted", { name: TITLE });
|
|
174
|
+
if (r.state === "stopped")
|
|
175
|
+
return t("ag.why.stopped", { time: when(r.stoppedAt ?? "") }).trim();
|
|
176
|
+
const m = /^exited(?: \((-?\d+)\))?$/.exec(r.state);
|
|
177
|
+
if (m)
|
|
178
|
+
return m[1] === undefined
|
|
179
|
+
? t("ag.why.exited")
|
|
180
|
+
: t("ag.why.exitedCode", { code: m[1] });
|
|
181
|
+
return stateText(r.state);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** A stopped agent's row: its button (it opens an ended pane) and actions. */
|
|
185
|
+
function stoppedHtml(r) {
|
|
186
|
+
const open = st.open.includes(r.id);
|
|
187
|
+
const name = displayName(r);
|
|
188
|
+
const nm = `${esc(name)}${r.persona && r.persona !== r.label ? ` <span class="id">${esc(r.label)}</span>` : ""}`;
|
|
189
|
+
const meta = [r.runtime, r.cwd].filter(Boolean).join(" · ");
|
|
190
|
+
const who = esc(r.agent);
|
|
191
|
+
const resume = r.resumable
|
|
192
|
+
? `<button type="button" class="den-btn sm primary" data-act="resume" aria-label="${esc(t("ag.resumeName", { id: r.agent }))}">${esc(t("ag.resume"))}</button>`
|
|
193
|
+
: "";
|
|
194
|
+
const fresh = `<button type="button" class="den-btn sm" data-act="fresh" aria-label="${esc(t("ag.freshName", { id: r.agent }))}">${esc(t("ag.fresh"))}</button>`;
|
|
195
|
+
const remove = `<button type="button" class="den-btn sm" data-act="remove" aria-label="${esc(t("ag.removeName", { id: r.agent }))}">${esc(t("ag.remove"))}</button>`;
|
|
196
|
+
const cant =
|
|
197
|
+
!r.resumable && r.noResume
|
|
198
|
+
? `<span class="agcant" data-no-resume>${esc(r.noResume.startsWith(`${r.runtime} `) ? t("ag.noResume", { runtime: r.runtime }) : r.noResume)}</span>`
|
|
199
|
+
: "";
|
|
200
|
+
return (
|
|
201
|
+
`<div class="agst" data-agent="${who}" data-state="${esc(r.state)}" data-ws="${esc(r.workspace)}">` +
|
|
202
|
+
`<button type="button" class="agr" data-id="${esc(r.id)}" data-kind="agent" data-state="${esc(r.state)}"` +
|
|
203
|
+
` aria-pressed="${open}"${r.attachable ? "" : ' aria-disabled="true"'}` +
|
|
204
|
+
` aria-label="${esc(t("ag.openName", { id: `${r.workspace} / ${name}` }))}">` +
|
|
205
|
+
`<span class="den-dot ${stateTone(r)}" aria-hidden="true"></span>` +
|
|
206
|
+
`<span class="nm">${nm}</span><span class="st">${esc(stateText(r.state))}</span>` +
|
|
207
|
+
(meta ? `<span class="meta">${esc(meta)}</span>` : "") +
|
|
208
|
+
`<span class="why">${esc(stopReason(r))}</span>` +
|
|
209
|
+
`</button>` +
|
|
210
|
+
`<div class="agacts">${cant}${resume}${fresh}${remove}</div>` +
|
|
211
|
+
`</div>`
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
|
|
130
215
|
function rowHtml(r) {
|
|
216
|
+
if (isStopped(r)) return stoppedHtml(r);
|
|
131
217
|
const open = st.open.includes(r.id);
|
|
132
218
|
const nm =
|
|
133
219
|
r.kind === "agent"
|
|
@@ -169,10 +255,40 @@ function paintList() {
|
|
|
169
255
|
return;
|
|
170
256
|
}
|
|
171
257
|
const shown = viewWorkspaces();
|
|
258
|
+
// ?ws= names a workspace that isn't active: say so, and offer the switch.
|
|
259
|
+
const asked = st.data.requested;
|
|
260
|
+
if (asked && !asked.active)
|
|
261
|
+
notes.push(
|
|
262
|
+
`<div class="agnote" data-note="inactive" role="status">` +
|
|
263
|
+
esc(t("ag.inactive", { ws: asked.workspace })) +
|
|
264
|
+
(asked.known
|
|
265
|
+
? ` · <button type="button" class="lnk" data-act="switch-ws">${esc(t("ag.switchWs", { ws: asked.workspace }))}</button>`
|
|
266
|
+
: ` ${esc(t("ag.unknownWs"))}`) +
|
|
267
|
+
`</div>`,
|
|
268
|
+
);
|
|
269
|
+
// AG-14.3: one line after a restart, for the agents it interrupted.
|
|
270
|
+
const hit = visibleRows().filter((r) => r.state === "interrupted");
|
|
271
|
+
if (hit.length)
|
|
272
|
+
notes.push(
|
|
273
|
+
`<div class="agnote" data-note="interrupted" role="status">` +
|
|
274
|
+
`${esc(t(hit.length === 1 ? "ag.interrupted.one" : "ag.interrupted", { n: hit.length, name: TITLE }))}` +
|
|
275
|
+
(hit.some((r) => r.resumable)
|
|
276
|
+
? ` · <button type="button" class="lnk" data-act="resume-all">${esc(t("ag.resumeAll"))}</button>`
|
|
277
|
+
: "") +
|
|
278
|
+
` · <button type="button" class="lnk" data-act="review">${esc(t("ag.review"))}</button></div>`,
|
|
279
|
+
);
|
|
172
280
|
const groups = shown
|
|
173
281
|
.map((ws) => ({
|
|
174
282
|
ws,
|
|
175
|
-
|
|
283
|
+
// Running ones (and plain sessions) first, then the stopped agents.
|
|
284
|
+
rows: st.data.sessions
|
|
285
|
+
.filter((s) => s.workspace === ws)
|
|
286
|
+
.map((s, i) => ({ s, i }))
|
|
287
|
+
.sort(
|
|
288
|
+
(a, b) =>
|
|
289
|
+
Number(isStopped(a.s)) - Number(isStopped(b.s)) || a.i - b.i,
|
|
290
|
+
)
|
|
291
|
+
.map((x) => x.s),
|
|
176
292
|
}))
|
|
177
293
|
.filter((g) => g.rows.length > 0);
|
|
178
294
|
const body = groups.length
|
|
@@ -186,6 +302,11 @@ function paintList() {
|
|
|
186
302
|
.join("")
|
|
187
303
|
: `<div class="none">${esc(t("ag.none.scope", { scope: st.scopeLabel }))}</div>`;
|
|
188
304
|
el.innerHTML = notes.join("") + body;
|
|
305
|
+
const clear = st.root.querySelector("#ag-clear");
|
|
306
|
+
if (clear) clear.hidden = !visibleRows().some(isStopped);
|
|
307
|
+
// No such workspace: nothing to start an agent in.
|
|
308
|
+
const start = st.root.querySelector("#ag-start");
|
|
309
|
+
if (start) start.hidden = st.data.requested?.known === false;
|
|
189
310
|
}
|
|
190
311
|
|
|
191
312
|
function visibleRows() {
|
|
@@ -219,7 +340,7 @@ async function loadScope() {
|
|
|
219
340
|
}
|
|
220
341
|
|
|
221
342
|
function scopeLabelFor(shown) {
|
|
222
|
-
const ws =
|
|
343
|
+
const ws = urlWorkspace();
|
|
223
344
|
if (ws && shown.length === 1 && shown[0] === ws) return ws;
|
|
224
345
|
return st.scope === "all" ? t("ag.all") : st.scope;
|
|
225
346
|
}
|
|
@@ -227,7 +348,11 @@ function scopeLabelFor(shown) {
|
|
|
227
348
|
async function refresh() {
|
|
228
349
|
clearTimeout(st.timer);
|
|
229
350
|
try {
|
|
230
|
-
const
|
|
351
|
+
const ws = urlWorkspace();
|
|
352
|
+
const r = await fetch(
|
|
353
|
+
ws ? `/api/sessions?ws=${encodeURIComponent(ws)}` : "/api/sessions",
|
|
354
|
+
{ headers: { accept: "json" } },
|
|
355
|
+
);
|
|
231
356
|
if (r.status === 401) {
|
|
232
357
|
st.signedOut = true;
|
|
233
358
|
} else if (r.ok) {
|
|
@@ -325,6 +450,11 @@ function paintBar(p) {
|
|
|
325
450
|
r?.kind === "agent" && (r.state === "running" || r.state === "stopping")
|
|
326
451
|
? `<button type="button" class="den-btn sm" data-act="stop" aria-label="${esc(t("ag.stopName", { id: r.agent }))}">${esc(t("ag.stop"))}</button>`
|
|
327
452
|
: "";
|
|
453
|
+
// The mock's pane bar: Resume (primary) on an ended agent that can.
|
|
454
|
+
const resume =
|
|
455
|
+
r && isStopped(r) && r.resumable
|
|
456
|
+
? `<button type="button" class="den-btn sm primary" data-act="resume" aria-label="${esc(t("ag.resumeName", { id: r.agent }))}">${esc(t("ag.resume"))}</button>`
|
|
457
|
+
: "";
|
|
328
458
|
const reconnect = p.lost
|
|
329
459
|
? `<button type="button" class="den-btn sm" data-act="reconnect">${esc(t("ag.reconnect"))}</button>`
|
|
330
460
|
: "";
|
|
@@ -334,7 +464,7 @@ function paintBar(p) {
|
|
|
334
464
|
(r?.tokenRevoked
|
|
335
465
|
? `<span class="tagx warn" title="${esc(t("ag.revokedTip"))}">${esc(t("ag.revoked"))}</span>`
|
|
336
466
|
: "") +
|
|
337
|
-
`<span class="x">${reconnect}${stop}<button type="button" class="den-btn sm" data-act="close" aria-label="${esc(t("ag.detachName", { id: name }))}" title="${esc(t("ag.detach"))}">×</button></span>`;
|
|
467
|
+
`<span class="x">${reconnect}${stop}${resume}<button type="button" class="den-btn sm" data-act="close" aria-label="${esc(t("ag.detachName", { id: name }))}" title="${esc(t("ag.detach"))}">×</button></span>`;
|
|
338
468
|
}
|
|
339
469
|
|
|
340
470
|
function send(p, obj) {
|
|
@@ -610,17 +740,185 @@ async function stopAgent(p, button) {
|
|
|
610
740
|
if (!r?.agent) return;
|
|
611
741
|
button.disabled = true;
|
|
612
742
|
try {
|
|
613
|
-
await api(
|
|
743
|
+
const out = await api(
|
|
614
744
|
"POST",
|
|
615
745
|
`/api/workspaces/${encodeURIComponent(r.workspace)}/agents/${encodeURIComponent(r.agent)}/stop`,
|
|
616
746
|
);
|
|
617
|
-
window.denShell?.toast?.(
|
|
747
|
+
window.denShell?.toast?.(
|
|
748
|
+
t(out?.stillStopping ? "ag.stillStopping" : "ag.stopped", {
|
|
749
|
+
id: r.agent,
|
|
750
|
+
}),
|
|
751
|
+
);
|
|
618
752
|
} catch (err) {
|
|
619
753
|
window.denShell?.errorToast?.(err);
|
|
620
754
|
}
|
|
621
755
|
await refresh();
|
|
622
756
|
}
|
|
623
757
|
|
|
758
|
+
// ── Stopped agents (AG-14) ──────────────────────────────────────────────
|
|
759
|
+
|
|
760
|
+
const agentPath = (r, verb) =>
|
|
761
|
+
`/api/workspaces/${encodeURIComponent(r.workspace)}/agents/${encodeURIComponent(r.agent)}/${verb}`;
|
|
762
|
+
|
|
763
|
+
/** Resume (the same conversation) or Start fresh; its pane opens on it. */
|
|
764
|
+
async function restartRow(r, verb, button) {
|
|
765
|
+
if (button) button.disabled = true;
|
|
766
|
+
try {
|
|
767
|
+
const out = await api("POST", agentPath(r, verb));
|
|
768
|
+
window.denShell?.toast?.(
|
|
769
|
+
t(
|
|
770
|
+
verb !== "resume"
|
|
771
|
+
? "ag.startedFresh"
|
|
772
|
+
: out?.fresh
|
|
773
|
+
? "ag.resumedFresh"
|
|
774
|
+
: "ag.resumed",
|
|
775
|
+
{ id: r.agent },
|
|
776
|
+
),
|
|
777
|
+
);
|
|
778
|
+
if (st.panes.has(r.id)) closePane(r.id, { quiet: true });
|
|
779
|
+
await refresh();
|
|
780
|
+
if (out?.agent?.sessionId) await openPane(out.agent.sessionId);
|
|
781
|
+
} catch (err) {
|
|
782
|
+
window.denShell?.errorToast?.(err);
|
|
783
|
+
await refresh();
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
/** Undo: the removed agents come back (POST /api/agents/restore). */
|
|
788
|
+
async function undoRemove(ids) {
|
|
789
|
+
try {
|
|
790
|
+
const out = await api("POST", "/api/agents/restore", { agents: ids });
|
|
791
|
+
const back = out?.restored ?? [];
|
|
792
|
+
if (back.length)
|
|
793
|
+
window.denShell?.toast?.(t("ag.restored", { ids: back.join(", ") }));
|
|
794
|
+
} catch (err) {
|
|
795
|
+
window.denShell?.errorToast?.(err);
|
|
796
|
+
}
|
|
797
|
+
await refresh();
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
/** The removal's toast, with Undo for UNDO_MS. */
|
|
801
|
+
function removedToast(msg, ids) {
|
|
802
|
+
const shell = window.denShell;
|
|
803
|
+
if (shell?.toastAction)
|
|
804
|
+
shell.toastAction(msg, t("ag.undo"), () => void undoRemove(ids), UNDO_MS);
|
|
805
|
+
else shell?.toast?.(msg);
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
/** Remove: one click, no confirm (AG-14.1b). */
|
|
809
|
+
async function removeRow(r, button) {
|
|
810
|
+
if (button) button.disabled = true;
|
|
811
|
+
try {
|
|
812
|
+
const out = await api("POST", agentPath(r, "remove"));
|
|
813
|
+
if (st.panes.has(r.id)) closePane(r.id, { quiet: true });
|
|
814
|
+
const ids = (out?.removed ?? []).map((x) => x.agent);
|
|
815
|
+
await refresh();
|
|
816
|
+
removedToast(
|
|
817
|
+
t("ag.removed", { id: r.agent }),
|
|
818
|
+
ids.length ? ids : [r.agent],
|
|
819
|
+
);
|
|
820
|
+
} catch (err) {
|
|
821
|
+
window.denShell?.errorToast?.(err);
|
|
822
|
+
await refresh();
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
/** Clear stopped: every stopped agent in view (AG-14.1b). */
|
|
827
|
+
async function clearStopped(button) {
|
|
828
|
+
const workspaces = [
|
|
829
|
+
...new Set(
|
|
830
|
+
visibleRows()
|
|
831
|
+
.filter(isStopped)
|
|
832
|
+
.map((r) => r.workspace),
|
|
833
|
+
),
|
|
834
|
+
];
|
|
835
|
+
if (!workspaces.length) return;
|
|
836
|
+
button.disabled = true;
|
|
837
|
+
try {
|
|
838
|
+
const out = await api("POST", "/api/agents/clear-stopped", { workspaces });
|
|
839
|
+
const ids = (out?.removed ?? []).map((x) => x.agent);
|
|
840
|
+
for (const r of visibleRows())
|
|
841
|
+
if (ids.includes(r.agent) && st.panes.has(r.id))
|
|
842
|
+
closePane(r.id, { quiet: true });
|
|
843
|
+
await refresh();
|
|
844
|
+
if (ids.length)
|
|
845
|
+
removedToast(
|
|
846
|
+
t(ids.length === 1 ? "ag.removedMany.one" : "ag.removedMany", {
|
|
847
|
+
n: ids.length,
|
|
848
|
+
}),
|
|
849
|
+
ids,
|
|
850
|
+
);
|
|
851
|
+
} catch (err) {
|
|
852
|
+
window.denShell?.errorToast?.(err);
|
|
853
|
+
await refresh();
|
|
854
|
+
} finally {
|
|
855
|
+
button.disabled = false;
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
/** Resume all: every interrupted agent in view that can resume (AG-14.3). */
|
|
860
|
+
async function resumeAll(button) {
|
|
861
|
+
const workspaces = [
|
|
862
|
+
...new Set(
|
|
863
|
+
visibleRows()
|
|
864
|
+
.filter((r) => r.state === "interrupted")
|
|
865
|
+
.map((r) => r.workspace),
|
|
866
|
+
),
|
|
867
|
+
];
|
|
868
|
+
if (!workspaces.length) return;
|
|
869
|
+
button.disabled = true;
|
|
870
|
+
try {
|
|
871
|
+
const out = await window.denShell.api(
|
|
872
|
+
"POST",
|
|
873
|
+
"/api/agents/resume-interrupted",
|
|
874
|
+
{ workspaces },
|
|
875
|
+
);
|
|
876
|
+
const n = out?.resumed?.length ?? 0;
|
|
877
|
+
window.denShell?.toast?.(
|
|
878
|
+
t(n === 1 ? "ag.resumedAll.one" : "ag.resumedAll", { n }),
|
|
879
|
+
);
|
|
880
|
+
} catch (err) {
|
|
881
|
+
window.denShell?.errorToast?.(err);
|
|
882
|
+
}
|
|
883
|
+
await refresh();
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
/**
|
|
887
|
+
* Switch to ?ws='s workspace: make it active (as Local Dev's Activate
|
|
888
|
+
* does), then view it (POST /api/scope, the top bar's Workspace switch),
|
|
889
|
+
* then reload, as that switch does.
|
|
890
|
+
*/
|
|
891
|
+
async function switchWorkspace(button) {
|
|
892
|
+
const ws = st.data?.requested?.workspace;
|
|
893
|
+
if (!ws) return;
|
|
894
|
+
button.disabled = true;
|
|
895
|
+
try {
|
|
896
|
+
await api("POST", "/api/workspaces", { name: ws, action: "activate" });
|
|
897
|
+
await api("POST", "/api/scope", { scope: ws });
|
|
898
|
+
} catch (err) {
|
|
899
|
+
window.denShell?.errorToast?.(err);
|
|
900
|
+
button.disabled = false;
|
|
901
|
+
return;
|
|
902
|
+
}
|
|
903
|
+
location.reload();
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
/** Review: the first interrupted agent, scrolled to and focused. */
|
|
907
|
+
function review() {
|
|
908
|
+
const first = st.root.querySelector('#agl .agst[data-state="interrupted"]');
|
|
909
|
+
if (!first) return;
|
|
910
|
+
for (const el of st.root.querySelectorAll("#agl .agst[data-review]"))
|
|
911
|
+
el.removeAttribute("data-review");
|
|
912
|
+
for (const el of st.root.querySelectorAll(
|
|
913
|
+
'#agl .agst[data-state="interrupted"]',
|
|
914
|
+
))
|
|
915
|
+
el.setAttribute("data-review", "");
|
|
916
|
+
first.scrollIntoView({ block: "nearest" });
|
|
917
|
+
(
|
|
918
|
+
first.querySelector('[data-act="resume"]') ?? first.querySelector(".agr")
|
|
919
|
+
)?.focus();
|
|
920
|
+
}
|
|
921
|
+
|
|
624
922
|
// ── Start agent ─────────────────────────────────────────────────────────
|
|
625
923
|
|
|
626
924
|
// Personas and runtimes, per workspace (AG-0.5: a workspace's own runtime
|
|
@@ -761,6 +1059,7 @@ function translateStatic(root) {
|
|
|
761
1059
|
};
|
|
762
1060
|
set("#ag-h", "n.agents");
|
|
763
1061
|
set("#ag-start", "ag.start");
|
|
1062
|
+
set("#ag-clear", "ag.clear");
|
|
764
1063
|
set("#ag-dlg-h", "ag.start");
|
|
765
1064
|
set("#ag-ws-t", "ag.ws");
|
|
766
1065
|
set("#ag-p-t", "ag.persona");
|
|
@@ -782,6 +1081,23 @@ export async function mountAgentsPage(root) {
|
|
|
782
1081
|
const startAsked = new URLSearchParams(location.search).get("start") === "1";
|
|
783
1082
|
|
|
784
1083
|
root.querySelector("#agl").addEventListener("click", (e) => {
|
|
1084
|
+
const act = e.target.closest("button[data-act]");
|
|
1085
|
+
if (act) {
|
|
1086
|
+
const which = act.dataset.act;
|
|
1087
|
+
if (which === "resume-all") return void resumeAll(act);
|
|
1088
|
+
if (which === "review") return review();
|
|
1089
|
+
if (which === "switch-ws") return void switchWorkspace(act);
|
|
1090
|
+
const agent = act.closest(".agst")?.dataset.agent;
|
|
1091
|
+
const ws = act.closest(".agst")?.dataset.ws;
|
|
1092
|
+
const r = visibleRows().find(
|
|
1093
|
+
(x) => x.agent === agent && x.workspace === ws,
|
|
1094
|
+
);
|
|
1095
|
+
if (!r) return;
|
|
1096
|
+
if (which === "resume" || which === "fresh")
|
|
1097
|
+
void restartRow(r, which, act);
|
|
1098
|
+
else if (which === "remove") void removeRow(r, act);
|
|
1099
|
+
return;
|
|
1100
|
+
}
|
|
785
1101
|
const b = e.target.closest(".agr");
|
|
786
1102
|
if (!b) return;
|
|
787
1103
|
if (b.getAttribute("aria-disabled") === "true") return;
|
|
@@ -797,11 +1113,17 @@ export async function mountAgentsPage(root) {
|
|
|
797
1113
|
if (!p) return;
|
|
798
1114
|
if (b.dataset.act === "close") closePane(id);
|
|
799
1115
|
else if (b.dataset.act === "stop") void stopAgent(p, b);
|
|
800
|
-
else if (b.dataset.act === "
|
|
1116
|
+
else if (b.dataset.act === "resume") {
|
|
1117
|
+
const r = rowOf(id);
|
|
1118
|
+
if (r) void restartRow(r, "resume", b);
|
|
1119
|
+
} else if (b.dataset.act === "reconnect") void connect(p);
|
|
801
1120
|
});
|
|
802
1121
|
root.querySelector("#ag-start").addEventListener("click", () => {
|
|
803
1122
|
void openStart();
|
|
804
1123
|
});
|
|
1124
|
+
root.querySelector("#ag-clear")?.addEventListener("click", (e) => {
|
|
1125
|
+
void clearStopped(e.currentTarget);
|
|
1126
|
+
});
|
|
805
1127
|
root.querySelector("#ag-ws").addEventListener("change", (e) => {
|
|
806
1128
|
void fillForWorkspace(e.currentTarget.value);
|
|
807
1129
|
});
|
|
@@ -79,7 +79,9 @@ function stateText(state) {
|
|
|
79
79
|
return m[1] === undefined
|
|
80
80
|
? t("ag.state.exited")
|
|
81
81
|
: t("ag.state.exitedCode", { code: m[1] });
|
|
82
|
-
return ["running", "stopping", "stopped", "ended"].includes(
|
|
82
|
+
return ["running", "stopping", "stopped", "ended", "interrupted"].includes(
|
|
83
|
+
state,
|
|
84
|
+
)
|
|
83
85
|
? t(`ag.state.${state}`)
|
|
84
86
|
: state;
|
|
85
87
|
}
|
|
@@ -219,9 +221,21 @@ async function openForm(sec) {
|
|
|
219
221
|
async function act(ws, verb, id, button) {
|
|
220
222
|
button.disabled = true;
|
|
221
223
|
try {
|
|
222
|
-
await api(
|
|
224
|
+
const out = await api(
|
|
225
|
+
"POST",
|
|
226
|
+
`${base(ws)}/${encodeURIComponent(id)}/${verb}`,
|
|
227
|
+
);
|
|
223
228
|
window.denShell?.toast?.(
|
|
224
|
-
t(
|
|
229
|
+
t(
|
|
230
|
+
verb === "stop"
|
|
231
|
+
? out?.stillStopping
|
|
232
|
+
? "ag.stillStopping"
|
|
233
|
+
: "ag.stopped"
|
|
234
|
+
: out?.fresh
|
|
235
|
+
? "ag.resumedFresh"
|
|
236
|
+
: "ag.resumed",
|
|
237
|
+
{ id },
|
|
238
|
+
),
|
|
225
239
|
);
|
|
226
240
|
} catch (err) {
|
|
227
241
|
window.denShell?.errorToast?.(err);
|
|
@@ -85,6 +85,9 @@
|
|
|
85
85
|
display: flex;
|
|
86
86
|
gap: 8px;
|
|
87
87
|
}
|
|
88
|
+
.ph .act [hidden] {
|
|
89
|
+
display: none;
|
|
90
|
+
}
|
|
88
91
|
.mono {
|
|
89
92
|
font-family: var(--mono);
|
|
90
93
|
}
|
|
@@ -163,6 +166,44 @@
|
|
|
163
166
|
overflow: hidden;
|
|
164
167
|
text-overflow: ellipsis;
|
|
165
168
|
}
|
|
169
|
+
/* AG-14: a stopped agent's row. Its why is the row's .meta line; its
|
|
170
|
+
actions are the mock's .btn.sm (den-btn sm), starting under the name
|
|
171
|
+
(the row's 12px padding, its 2px border, the 8px dot with its 5px
|
|
172
|
+
margins (.den-dot), the 8px gap: 40px). */
|
|
173
|
+
.agr .why {
|
|
174
|
+
grid-column: 2 / 4;
|
|
175
|
+
font: 11.5px var(--mono);
|
|
176
|
+
color: var(--faint);
|
|
177
|
+
white-space: nowrap;
|
|
178
|
+
overflow: hidden;
|
|
179
|
+
text-overflow: ellipsis;
|
|
180
|
+
}
|
|
181
|
+
.agst .agacts {
|
|
182
|
+
display: flex;
|
|
183
|
+
flex-wrap: wrap;
|
|
184
|
+
align-items: center;
|
|
185
|
+
gap: 6px;
|
|
186
|
+
padding: 0 12px 8px 40px;
|
|
187
|
+
}
|
|
188
|
+
.agst .agcant {
|
|
189
|
+
flex-basis: 100%;
|
|
190
|
+
font: 12px/1.4 var(--sans);
|
|
191
|
+
color: var(--faint);
|
|
192
|
+
}
|
|
193
|
+
.agst[data-review] > .agr {
|
|
194
|
+
border-left-color: var(--warn);
|
|
195
|
+
}
|
|
196
|
+
/* The mock's .lnk: a button that reads as a link. */
|
|
197
|
+
.lnk {
|
|
198
|
+
color: var(--accent-text);
|
|
199
|
+
cursor: pointer;
|
|
200
|
+
font: inherit;
|
|
201
|
+
font-size: 12.5px;
|
|
202
|
+
text-decoration: underline;
|
|
203
|
+
background: none;
|
|
204
|
+
border: 0;
|
|
205
|
+
padding: 0;
|
|
206
|
+
}
|
|
166
207
|
.wbdg {
|
|
167
208
|
font: 10.5px var(--mono);
|
|
168
209
|
color: var(--warn);
|
|
@@ -424,6 +465,9 @@
|
|
|
424
465
|
</div>
|
|
425
466
|
</div>
|
|
426
467
|
<div class="act">
|
|
468
|
+
<button type="button" class="den-btn" id="ag-clear" hidden>
|
|
469
|
+
Clear stopped
|
|
470
|
+
</button>
|
|
427
471
|
<button type="button" class="den-btn primary" id="ag-start">
|
|
428
472
|
+ Start agent
|
|
429
473
|
</button>
|
|
@@ -400,7 +400,35 @@
|
|
|
400
400
|
"ag.cancel": "Cancelar",
|
|
401
401
|
"ag.started": "Agente {id} iniciado",
|
|
402
402
|
"ag.stopped": "Agente {id} detenido",
|
|
403
|
+
"ag.stillStopping": "{id} todavía se está deteniendo",
|
|
404
|
+
"ag.resumedFresh": "{id}: aún no hay nada que reanudar; se inició de nuevo",
|
|
405
|
+
"ag.inactive": "El espacio de trabajo {ws} no está activo",
|
|
406
|
+
"ag.switchWs": "Cambiar a {ws}",
|
|
407
|
+
"ag.unknownWs": "(ningún espacio de trabajo tiene ese nombre)",
|
|
403
408
|
"ag.resumed": "Agente {id} reanudado",
|
|
409
|
+
"ag.state.interrupted": "interrumpido",
|
|
410
|
+
"ag.why.stopped": "Lo detuviste a las {time}",
|
|
411
|
+
"ag.why.interrupted": "Interrumpido: {name} se reinició",
|
|
412
|
+
"ag.why.exited": "Salió",
|
|
413
|
+
"ag.why.exitedCode": "Salió (código {code})",
|
|
414
|
+
"ag.fresh": "Empezar de cero",
|
|
415
|
+
"ag.freshName": "Empezar {id} de cero",
|
|
416
|
+
"ag.startedFresh": "{id} empezó de cero",
|
|
417
|
+
"ag.remove": "Quitar",
|
|
418
|
+
"ag.removeName": "Quitar {id}",
|
|
419
|
+
"ag.removed": "{id} quitado",
|
|
420
|
+
"ag.removedMany": "{n} agentes detenidos quitados",
|
|
421
|
+
"ag.removedMany.one": "{n} agente detenido quitado",
|
|
422
|
+
"ag.undo": "Deshacer",
|
|
423
|
+
"ag.restored": "De vuelta en la lista: {ids}",
|
|
424
|
+
"ag.clear": "Quitar detenidos",
|
|
425
|
+
"ag.noResume": "{runtime} no puede reanudar una conversación",
|
|
426
|
+
"ag.interrupted": "{n} agentes se interrumpieron al reiniciarse {name}",
|
|
427
|
+
"ag.interrupted.one": "{n} agente se interrumpió al reiniciarse {name}",
|
|
428
|
+
"ag.resumeAll": "Reanudar todos",
|
|
429
|
+
"ag.review": "Revisar",
|
|
430
|
+
"ag.resumedAll": "{n} agentes reanudados",
|
|
431
|
+
"ag.resumedAll.one": "{n} agente reanudado",
|
|
404
432
|
// The Agents tab (AG-4a-term, agents-page.js).
|
|
405
433
|
"n.agents": "Agentes",
|
|
406
434
|
"kind.agent": "Agente",
|
|
@@ -387,7 +387,32 @@
|
|
|
387
387
|
"ag.cancel": "キャンセル",
|
|
388
388
|
"ag.started": "エージェント {id} を開始しました",
|
|
389
389
|
"ag.stopped": "エージェント {id} を停止しました",
|
|
390
|
+
"ag.stillStopping": "{id} はまだ停止中です",
|
|
391
|
+
"ag.resumedFresh": "{id}: まだ再開するものがないため、新しく開始しました",
|
|
392
|
+
"ag.inactive": "ワークスペース {ws} はアクティブではありません",
|
|
393
|
+
"ag.switchWs": "{ws} に切り替え",
|
|
394
|
+
"ag.unknownWs": "(その名前のワークスペースはありません)",
|
|
390
395
|
"ag.resumed": "エージェント {id} を再開しました",
|
|
396
|
+
"ag.state.interrupted": "中断",
|
|
397
|
+
"ag.why.stopped": "{time} に停止しました",
|
|
398
|
+
"ag.why.interrupted": "中断: {name} が再起動しました",
|
|
399
|
+
"ag.why.exited": "終了しました",
|
|
400
|
+
"ag.why.exitedCode": "終了しました(コード {code})",
|
|
401
|
+
"ag.fresh": "新しく始める",
|
|
402
|
+
"ag.freshName": "{id} を新しく始める",
|
|
403
|
+
"ag.startedFresh": "{id} を新しく始めました",
|
|
404
|
+
"ag.remove": "削除",
|
|
405
|
+
"ag.removeName": "{id} を削除",
|
|
406
|
+
"ag.removed": "{id} を削除しました",
|
|
407
|
+
"ag.removedMany": "停止中のエージェント {n} 件を削除しました",
|
|
408
|
+
"ag.undo": "元に戻す",
|
|
409
|
+
"ag.restored": "一覧に戻しました: {ids}",
|
|
410
|
+
"ag.clear": "停止中を削除",
|
|
411
|
+
"ag.noResume": "{runtime} は会話を再開できません",
|
|
412
|
+
"ag.interrupted": "{name} の再起動でエージェント {n} 件が中断されました",
|
|
413
|
+
"ag.resumeAll": "すべて再開",
|
|
414
|
+
"ag.review": "確認",
|
|
415
|
+
"ag.resumedAll": "エージェント {n} 件を再開しました",
|
|
391
416
|
// The Agents tab (AG-4a-term, agents-page.js).
|
|
392
417
|
"n.agents": "エージェント",
|
|
393
418
|
"kind.agent": "エージェント",
|