@monster-orange/den 0.4.26 → 0.4.28

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.
@@ -0,0 +1,311 @@
1
+ /**
2
+ * D32: the helpers an agent starts in its own session (Claude Code's Agent
3
+ * and Workflow subagents), read-only. Den shows them; they belong to the
4
+ * agent's session, so nothing here stops or steers one (and the views say
5
+ * so).
6
+ *
7
+ * helperRowsHtml(row): under an agent's row on the Agents page, its
8
+ * recent helpers (the row's `helpers.recent`: running, or ended a short
9
+ * while ago) and "All helpers (n)".
10
+ * openHelpers({ ws, agent, name, opener }): every helper, this
11
+ * conversation's first and earlier ones folded under "Earlier (n)";
12
+ * each opens its transcript (transcript-view.js).
13
+ * openHelperTranscript({ ws, agent, helper, opener }).
14
+ */
15
+ import { focusBack, openTranscript, refindKey } from "/transcript-view.js";
16
+
17
+ const t = (key, vars) => window.denShell?.t?.(key, vars) ?? key;
18
+ const esc = (s) =>
19
+ String(s ?? "").replace(
20
+ /[&<>"']/g,
21
+ (c) =>
22
+ ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[
23
+ c
24
+ ],
25
+ );
26
+ const api = (method, url) => window.denShell.api(method, url);
27
+ const base = (ws, agent) =>
28
+ `/api/workspaces/${encodeURIComponent(ws)}/agents/${encodeURIComponent(agent)}/helpers`;
29
+
30
+ /** A dot's tone for a helper's status (the shell's .den-dot tones). */
31
+ export function helperTone(status) {
32
+ return status === "running"
33
+ ? "ok"
34
+ : status === "failed"
35
+ ? "bad"
36
+ : status === "idle" || status === "stopped"
37
+ ? "warn"
38
+ : "off";
39
+ }
40
+
41
+ /** 65 s → "1m 5s". */
42
+ export function dur(ms) {
43
+ if (typeof ms !== "number" || ms < 0) return "";
44
+ const s = Math.round(ms / 1000);
45
+ if (s < 60) return `${s}s`;
46
+ const m = Math.floor(s / 60);
47
+ if (m < 60) return `${m}m ${s % 60}s`;
48
+ return `${Math.floor(m / 60)}h ${m % 60}m`;
49
+ }
50
+
51
+ const label = (h) => h.description || h.workflow?.label || h.id;
52
+
53
+ /** One helper as a button row (opens its transcript). */
54
+ /**
55
+ * How far each helper of `list` (in sortHelpers' order) is indented: one
56
+ * step under a parent shown just above it, none when its parent isn't in
57
+ * the list (folded away, or another conversation's).
58
+ */
59
+ export function shownDepths(list) {
60
+ const at = new Map();
61
+ for (const h of list || [])
62
+ at.set(h.id, h.parentId && at.has(h.parentId) ? at.get(h.parentId) + 1 : 0);
63
+ return at;
64
+ }
65
+
66
+ /** A list of helpers as rows, each indented under a parent it shows. */
67
+ export function helperListHtml(list, ctx) {
68
+ const d = shownDepths(list);
69
+ return (list || [])
70
+ .map((h) => helperHtml(h, { ...ctx, depth: d.get(h.id) ?? 0 }))
71
+ .join("");
72
+ }
73
+
74
+ export function helperHtml(h, { ws, agent, depth }) {
75
+ const meta = [
76
+ h.workflow
77
+ ? t("hp.workflow", { name: h.workflow.name || h.workflow.runId })
78
+ : h.type,
79
+ h.model,
80
+ h.branch,
81
+ ]
82
+ .filter(Boolean)
83
+ .join(" · ");
84
+ const st = t(`hp.st.${h.status}`);
85
+ const when = [
86
+ h.durationMs !== undefined ? dur(h.durationMs) : "",
87
+ h.lastTool && h.status === "running" ? h.lastTool : "",
88
+ ]
89
+ .filter(Boolean)
90
+ .join(" · ");
91
+ return (
92
+ `<button type="button" class="agh" data-helper="${esc(h.id)}" data-agent="${esc(agent)}" data-ws="${esc(ws)}" data-status="${esc(h.status)}"` +
93
+ ` style="--hp-depth:${Math.max(0, Math.min(8, depth ?? (h.depth || 1) - 1))}"` +
94
+ ` aria-label="${esc(t("hp.open", { what: label(h), status: st }))}" title="${esc(h.statusFrom ? `${st}: ${h.statusFrom}` : st)}">` +
95
+ `<span class="den-dot ${helperTone(h.status)}" aria-hidden="true"></span>` +
96
+ `<span class="agh-n">${esc(label(h))}</span>` +
97
+ `<span class="agh-s">${esc(st)}${when ? ` · ${esc(when)}` : ""}</span>` +
98
+ (meta ? `<span class="agh-m mono">${esc(meta)}</span>` : "") +
99
+ `</button>`
100
+ );
101
+ }
102
+
103
+ /** Under an agent's row: its recent helpers and "All helpers (n)". */
104
+ export function helperRowsHtml(r) {
105
+ const h = r?.helpers;
106
+ if (!h || !h.total) return "";
107
+ const ctx = { ws: r.workspace, agent: r.agent };
108
+ return (
109
+ `<div class="aghs" data-helpers-of="${esc(r.agent)}" data-ws="${esc(r.workspace)}" role="group" aria-label="${esc(t("hp.groupOf", { name: r.displayName || r.agent }))}">` +
110
+ helperListHtml(h.recent, ctx) +
111
+ `<button type="button" class="lnk aghs-all">` +
112
+ esc(t("hp.all", { n: h.total })) +
113
+ (h.running
114
+ ? ` <span class="hpbdg" aria-label="${esc(t("hp.runningN", { n: h.running }))}">${esc(h.running)}</span>`
115
+ : "") +
116
+ `</button></div>`
117
+ );
118
+ }
119
+
120
+ /** "Helpers (n)" for a pane's bar: a count badge while any run. */
121
+ export function helperBarButtonHtml(r) {
122
+ const h = r?.helpers;
123
+ if (!h || !h.total) return "";
124
+ return (
125
+ `<button type="button" class="den-btn sm" data-act="helpers" aria-haspopup="dialog"` +
126
+ ` aria-label="${esc(t(h.running ? "hp.barRunning" : "hp.bar", { n: h.total, r: h.running }))}">` +
127
+ esc(t("hp.title", { n: h.total })) +
128
+ (h.running
129
+ ? ` <span class="hpbdg" aria-hidden="true">${esc(h.running)}</span>`
130
+ : "") +
131
+ `</button>`
132
+ );
133
+ }
134
+
135
+ const CSS = `
136
+ .hp-dlg{font-family:var(--sans);width:min(760px,calc(100vw - 32px));max-height:calc(100vh - 48px);padding:0;border:1px solid var(--border-strong);border-radius:10px;background:var(--raised, var(--surface));color:var(--text);box-shadow:var(--shadow)}
137
+ .hp-dlg::backdrop{background:rgba(0,0,0,.35)}
138
+ .hp-hd{display:flex;gap:8px;align-items:baseline;padding:12px 16px;border-bottom:1px solid var(--border)}
139
+ .hp-hd h2{font-size:15px;margin:0;outline:none;min-width:0;overflow-wrap:anywhere}
140
+ .hp-hd .x{margin-left:auto;flex:none}
141
+ .hp-body{padding:8px 12px;overflow:auto;max-height:calc(100vh - 200px)}
142
+ .hp-note{margin:0;padding:8px 16px 12px;color:var(--muted);font-size:12px;border-top:1px solid var(--border)}
143
+ .hp-none{color:var(--muted);font-size:13px;padding:8px 4px}
144
+ .hp-earlier{margin-top:8px}
145
+ .hp-earlier summary{cursor:pointer;color:var(--muted);font-size:13px;padding:4px}
146
+ .aghs{display:flex;flex-direction:column;gap:2px;margin:2px 0 6px 18px;padding-left:8px;border-left:2px solid var(--border)}
147
+ .agh{all:unset;box-sizing:border-box;display:grid;grid-template-columns:auto 1fr auto;gap:2px 8px;align-items:center;width:100%;padding:4px 6px 4px calc(6px + var(--hp-depth,0) * 14px);border-radius:var(--radius-button,6px);cursor:pointer;font-size:12px;color:var(--text)}
148
+ .agh:hover{background:var(--sunken)}
149
+ .agh:focus-visible{outline:2px solid var(--accent);outline-offset:1px}
150
+ .agh-n{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
151
+ .agh-s{color:var(--muted);white-space:nowrap}
152
+ .agh-m{grid-column:2 / 4;color:var(--faint);font-size:11px;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
153
+ .agh[data-status="running"] .agh-s{color:var(--text)}
154
+ .aghs-all{align-self:flex-start;font-size:12px;margin:2px 6px}
155
+ .hpbdg{display:inline-block;min-width:16px;padding:0 5px;border-radius:8px;background:var(--accent);color:var(--accent-text);font-size:11px;line-height:16px;text-align:center}
156
+ `;
157
+
158
+ let dlg = null;
159
+ let opener = null;
160
+ let openerKey = "";
161
+
162
+ function ensureCss() {
163
+ if (document.getElementById("hp-css")) return;
164
+ const s = document.createElement("style");
165
+ s.id = "hp-css";
166
+ s.textContent = CSS;
167
+ document.head.append(s);
168
+ }
169
+
170
+ function dialog() {
171
+ ensureCss();
172
+ if (dlg && dlg.isConnected) return dlg;
173
+ dlg = document.createElement("dialog");
174
+ dlg.className = "hp-dlg";
175
+ dlg.id = "hp-dlg";
176
+ dlg.setAttribute("aria-labelledby", "hp-h");
177
+ dlg.innerHTML =
178
+ `<div class="hp-hd"><h2 id="hp-h" tabindex="-1"></h2>` +
179
+ `<button type="button" class="den-btn sm x" data-hp-close></button></div>` +
180
+ `<div class="hp-body" id="hp-body" aria-live="polite"></div>` +
181
+ `<p class="hp-note" id="hp-note"></p>`;
182
+ dlg.querySelector("[data-hp-close]").addEventListener("click", () => {
183
+ if (dlg.open && typeof dlg.close === "function") dlg.close();
184
+ else dlg.removeAttribute("open");
185
+ });
186
+ dlg.addEventListener("close", () => {
187
+ const o = opener;
188
+ const k = openerKey;
189
+ opener = null;
190
+ openerKey = "";
191
+ // The Agents page's poll may have replaced it meanwhile: find it again.
192
+ focusBack(o, k);
193
+ });
194
+ // A helper opens its transcript over this list.
195
+ dlg.addEventListener("click", (e) => {
196
+ const b = e.target.closest("button.agh");
197
+ if (b)
198
+ void openHelperTranscript({
199
+ ws: b.dataset.ws,
200
+ agent: b.dataset.agent,
201
+ helper: b.dataset.helper,
202
+ opener: b,
203
+ });
204
+ });
205
+ document.body.append(dlg);
206
+ return dlg;
207
+ }
208
+
209
+ /** Every helper of agent `agent` (see the file's comment). */
210
+ export async function openHelpers({ ws, agent, name, opener: from = null }) {
211
+ const d = dialog();
212
+ opener = from ?? document.activeElement;
213
+ openerKey = refindKey(opener);
214
+ d.querySelector("#hp-h").textContent = t("hp.dlg", { name: name || agent });
215
+ d.querySelector("[data-hp-close]").textContent = t("tx.close");
216
+ d.querySelector("#hp-note").textContent = t("hp.note");
217
+ const body = d.querySelector("#hp-body");
218
+ body.setAttribute("aria-busy", "true");
219
+ body.innerHTML = `<p class="hp-none">${esc(t("tx.loading"))}</p>`;
220
+ if (!d.open) {
221
+ if (typeof d.showModal === "function") d.showModal();
222
+ else d.setAttribute("open", "");
223
+ }
224
+ d.querySelector("#hp-h").focus();
225
+ try {
226
+ const r = await api("GET", base(ws, agent));
227
+ const all = Array.isArray(r?.helpers) ? r.helpers : [];
228
+ const now = all.filter((h) => !h.earlier);
229
+ const before = all.filter((h) => h.earlier);
230
+ const ctx = { ws, agent };
231
+ body.innerHTML =
232
+ (r?.supported === false
233
+ ? `<p class="hp-none">${esc(t("hp.unsupported"))}</p>`
234
+ : now.length
235
+ ? helperListHtml(now, ctx)
236
+ : `<p class="hp-none">${esc(t("hp.none"))}</p>`) +
237
+ (before.length
238
+ ? `<details class="hp-earlier"><summary>${esc(t("hp.earlier", { n: before.length }))}</summary>` +
239
+ helperListHtml(before, ctx) +
240
+ `</details>`
241
+ : "");
242
+ } catch (e) {
243
+ body.innerHTML = `<p class="hp-none" role="alert">${esc(t("tx.failed", { err: e?.message || String(e) }))}</p>`;
244
+ } finally {
245
+ body.removeAttribute("aria-busy");
246
+ }
247
+ }
248
+
249
+ /** One helper's transcript, in the shared transcript view. */
250
+ export function openHelperTranscript({ ws, agent, helper, opener: from }) {
251
+ let head = null;
252
+ return openTranscript({
253
+ title: t("tx.loading"),
254
+ note: t("hp.note"),
255
+ opener: from,
256
+ load: async () => {
257
+ const r = await api(
258
+ "GET",
259
+ `${base(ws, agent)}/${encodeURIComponent(helper)}`,
260
+ );
261
+ head = r?.helper;
262
+ const h2 = document.getElementById("tx-h");
263
+ const sub = document.getElementById("tx-sub");
264
+ if (h2 && head) h2.textContent = label(head);
265
+ if (sub && head)
266
+ sub.textContent = [
267
+ t(`hp.st.${head.status}`),
268
+ head.workflow
269
+ ? t("hp.workflow", {
270
+ name: head.workflow.name || head.workflow.runId,
271
+ })
272
+ : head.type,
273
+ head.model,
274
+ head.branch,
275
+ head.durationMs !== undefined ? dur(head.durationMs) : "",
276
+ ]
277
+ .filter(Boolean)
278
+ .join(" · ");
279
+ return { items: r?.items ?? [], omitted: r?.omitted ?? 0 };
280
+ },
281
+ });
282
+ }
283
+
284
+ /** Wire helper rows and "All helpers" inside `root` (a list's container). */
285
+ export function wireHelperRows(root, nameOf = (a) => a) {
286
+ ensureCss();
287
+ root.addEventListener("click", (e) => {
288
+ const row = e.target.closest("button.agh");
289
+ if (row && root.contains(row)) {
290
+ e.stopPropagation();
291
+ void openHelperTranscript({
292
+ ws: row.dataset.ws,
293
+ agent: row.dataset.agent,
294
+ helper: row.dataset.helper,
295
+ opener: row,
296
+ });
297
+ return;
298
+ }
299
+ const all = e.target.closest(".aghs-all");
300
+ if (all && root.contains(all)) {
301
+ e.stopPropagation();
302
+ const g = all.closest(".aghs");
303
+ void openHelpers({
304
+ ws: g.dataset.ws,
305
+ agent: g.dataset.helpersOf,
306
+ name: nameOf(g.dataset.helpersOf, g.dataset.ws),
307
+ opener: all,
308
+ });
309
+ }
310
+ });
311
+ }
@@ -84,6 +84,13 @@
84
84
  * as a tag.
85
85
  */
86
86
  import { readiness, runtimeName, runtimeOptions } from "/agent-start.js";
87
+ import { focusBack, refindKey } from "/transcript-view.js";
88
+ import {
89
+ helperBarButtonHtml,
90
+ helperRowsHtml,
91
+ openHelpers,
92
+ wireHelperRows,
93
+ } from "/agent-helpers.js";
87
94
  import {
88
95
  commandProblem,
89
96
  filterBarHtml,
@@ -496,7 +503,8 @@ function stoppedHtml(r) {
496
503
  `<span class="why">${esc(stopReason(r))}</span>` +
497
504
  `</button>` +
498
505
  `<div class="agacts">${callout}${cant}${resume}${fresh}${rename}${remove}</div>` +
499
- `</div>`
506
+ `</div>` +
507
+ helperRowsHtml(r)
500
508
  );
501
509
  }
502
510
 
@@ -540,15 +548,49 @@ function rowHtml(r) {
540
548
  // D28: a running agent whose rotation is due: its callout, with buttons
541
549
  // (before, only a tag: nothing to press).
542
550
  const callout = r.kind === "agent" ? rotCalloutHtml(r) : "";
543
- if (!callout) return button;
551
+ // D32: the helpers it started in its own session, under it.
552
+ const helpers = r.kind === "agent" ? helperRowsHtml(r) : "";
553
+ if (!callout) return button + helpers;
544
554
  return (
545
555
  `<div class="agst" data-agent="${esc(r.agent)}" data-state="${esc(r.state)}" data-ws="${esc(r.workspace)}" data-rotation="${rot}">` +
546
556
  button +
547
557
  `<div class="agacts">${callout}</div>` +
548
- `</div>`
558
+ `</div>` +
559
+ helpers
549
560
  );
550
561
  }
551
562
 
563
+ /** Where focus was in the list, as a selector that finds it again once
564
+ * the list is redrawn: a helper row or button, an agent's row, a link. */
565
+ function listFocusKey(el) {
566
+ const key = refindKey(el);
567
+ if (key) return key;
568
+ const q = (v) => CSS.escape(String(v ?? ""));
569
+ if (el.matches("button.agr[data-id]"))
570
+ return `#agl button.agr[data-id="${q(el.dataset.id)}"]`;
571
+ if (el.matches("[data-act]")) {
572
+ const row = el.closest(".agst[data-agent]");
573
+ const act = `[data-act="${q(el.dataset.act)}"]`;
574
+ return row
575
+ ? `#agl .agst[data-agent="${q(row.dataset.agent)}"][data-ws="${q(row.dataset.ws)}"] ${act}`
576
+ : `#agl ${act}`;
577
+ }
578
+ return "";
579
+ }
580
+
581
+ /** The list's HTML, written only when it changed; focus inside it is kept
582
+ * (the refresh poll redraws it every few seconds). */
583
+ function setList(el, html) {
584
+ if (el.denHtml === html) return;
585
+ el.denHtml = html;
586
+ const had = el.contains(document.activeElement)
587
+ ? document.activeElement
588
+ : null;
589
+ const key = had ? listFocusKey(had) : "";
590
+ el.innerHTML = html;
591
+ if (had) focusBack(had, key);
592
+ }
593
+
552
594
  function paintList() {
553
595
  const el = st.root.querySelector("#agl");
554
596
  el.removeAttribute("aria-busy");
@@ -562,7 +604,7 @@ function paintList() {
562
604
  `<div class="agnote" data-note="backend">${esc(t("ag.noBackend", { cli: CLI }))}</div>`,
563
605
  );
564
606
  if (!st.data) {
565
- el.innerHTML = notes.join("");
607
+ setList(el, notes.join(""));
566
608
  return;
567
609
  }
568
610
  // ON-6: the server agent's group, "Server", first, whatever the view.
@@ -615,10 +657,12 @@ function paintList() {
615
657
  )
616
658
  .join("")
617
659
  : `<div class="none">${esc(t("ag.none.scope", { scope: st.scopeLabel }))}</div>`;
618
- el.innerHTML =
660
+ setList(
661
+ el,
619
662
  notes.join("") +
620
- body +
621
- `<div class="aghint" id="agl-hint">${esc(t(`ag.hint.${shownLayout()}`))} ${esc(t("ag.hint.keys"))}</div>`;
663
+ body +
664
+ `<div class="aghint" id="agl-hint">${esc(t(`ag.hint.${shownLayout()}`))} ${esc(t("ag.hint.keys"))}</div>`,
665
+ );
622
666
  const clear = st.root.querySelector("#ag-clear");
623
667
  if (clear) clear.hidden = !visibleRows().some(isStopped);
624
668
  // No such workspace: nothing to start an agent in.
@@ -697,6 +741,13 @@ async function refresh() {
697
741
  /* the last list stays; the next ask may answer */
698
742
  }
699
743
  followAgents();
744
+ // D32: the rail's Agents badge: helpers running now.
745
+ window.denShell?.setState?.({
746
+ helpersRunning: (st.data?.sessions ?? []).reduce(
747
+ (n, r) => n + (r.helpers?.running ?? 0),
748
+ 0,
749
+ ),
750
+ });
700
751
  st.scopeLabel = scopeLabelFor(viewWorkspaces());
701
752
  paintList();
702
753
  paintSub();
@@ -1098,7 +1149,7 @@ function paintBar(p) {
1098
1149
  (r?.kind === "agent" && rotActionsHtml(r)
1099
1150
  ? `<span class="agrot" data-rot-bar title="${esc(rotMessage(r))}"><span class="tagx warn" data-rotation="${rotationOf(r)}">${esc(t(rotationOf(r) === "ready" ? "ag.rot.ready" : "ag.rot.due", { why: rotationWhy(r.rotation) }))}</span>${rotActionsHtml(r)}</span>`
1100
1151
  : "") +
1101
- `<span class="x">${reconnect}${stop}${resume}${more}<button type="button" class="den-btn sm" data-act="close" aria-label="${esc(t("ag.detachName", { id: name }))}" title="${esc(t("ag.detach"))}">×</button></span>`;
1152
+ `<span class="x">${r?.kind === "agent" ? helperBarButtonHtml(r) : ""}${reconnect}${stop}${resume}${more}<button type="button" class="den-btn sm" data-act="close" aria-label="${esc(t("ag.detachName", { id: name }))}" title="${esc(t("ag.detach"))}">×</button></span>`;
1102
1153
  // Tabs: the tab's × closes it; the pane's bar has none.
1103
1154
  const x = bar.querySelector('[data-act="close"]');
1104
1155
  if (x) x.hidden = shownLayout() === "tabs";
@@ -2947,6 +2998,13 @@ export async function mountAgentsPage(root) {
2947
2998
  startPrefill = { ws: q.get("ws") || "", folder: q.get("folder") || "" };
2948
2999
  }
2949
3000
 
3001
+ // D32: helper rows open their transcript; "All helpers" the full list.
3002
+ wireHelperRows(root.querySelector("#agl"), (agent, ws) => {
3003
+ const r = visibleRows().find(
3004
+ (x) => x.agent === agent && x.workspace === ws,
3005
+ );
3006
+ return r ? displayName(r) : agent;
3007
+ });
2950
3008
  root.querySelector("#agl").addEventListener("click", (e) => {
2951
3009
  const act = e.target.closest("button[data-act]");
2952
3010
  if (act) {
@@ -3015,7 +3073,16 @@ export async function mountAgentsPage(root) {
3015
3073
  const r = rowOf(id);
3016
3074
  if (r) void restartRow(r, "resume", b);
3017
3075
  } else if (b.dataset.act === "reconnect") void connect(p);
3018
- else if (b.dataset.act === "more") {
3076
+ else if (b.dataset.act === "helpers") {
3077
+ const r = rowOf(id);
3078
+ if (r?.kind === "agent")
3079
+ void openHelpers({
3080
+ ws: r.workspace,
3081
+ agent: r.agent,
3082
+ name: paneName(r),
3083
+ opener: b,
3084
+ });
3085
+ } else if (b.dataset.act === "more") {
3019
3086
  const r = rowOf(id);
3020
3087
  if (!r) return;
3021
3088
  window.denShell?.openMenu?.(b, {
@@ -197,6 +197,8 @@
197
197
  "lock.note":
198
198
  "Den la usa solo para sudo. Los agentes no pueden introducirla.",
199
199
  "lock.saved": "Contraseña de administrador guardada para esta sesión",
200
+ "lock.savedTo":
201
+ "Contraseña de administrador guardada para esta sesión y en {s}",
200
202
  "lock.saveFailed":
201
203
  "Guardada para esta sesión, pero no se pudo guardar en {s}: {err}",
202
204
  "lock.wrong": "Contraseña incorrecta; inténtalo de nuevo",
@@ -756,6 +758,45 @@
756
758
  "ag.sub.one": "{n} sesión · {w} en espera · {scope}",
757
759
  "ag.all": "todos los espacios",
758
760
  "ag.waiting": "en espera",
761
+ "tx.asked": "Pedido",
762
+ "tx.result": "Resultado",
763
+ "tx.error": "Error",
764
+ "tx.omitted": "{n} líneas anteriores no se muestran",
765
+ "tx.omitted.one": "{n} línea anterior no se muestra",
766
+ "tx.empty": "Nada todavía.",
767
+ "tx.close": "Cerrar",
768
+ "tx.loading": "Cargando…",
769
+ "tx.failed": "No se pudo leer: {err}",
770
+ "hp.title": "Ayudantes ({n})",
771
+ "hp.bar": "Ayudantes: {n}",
772
+ "hp.barRunning": "Ayudantes: {n}, {r} en marcha",
773
+ "hp.all": "Todos los ayudantes ({n})",
774
+ "hp.runningN": "{n} en marcha",
775
+ "hp.groupOf": "Ayudantes de {name}",
776
+ "hp.open": "{what}, {status}: abrir su transcripción",
777
+ "hp.dlg": "Ayudantes de {name}",
778
+ "hp.earlier": "Conversaciones anteriores ({n})",
779
+ "hp.none": "Sin ayudantes en esta conversación.",
780
+ "hp.unsupported":
781
+ "El runtime de este agente no puede decir qué ayudantes inició.",
782
+ "hp.workflow": "flujo {name}",
783
+ "hp.note":
784
+ "Los ayudantes corren dentro de la sesión del propio agente: Den los muestra, pero no puede detenerlos ni dirigirlos. Para cambiar lo que hacen, díselo al agente.",
785
+ "hp.st.running": "en marcha",
786
+ "hp.st.idle": "inactivo",
787
+ "hp.st.done": "terminado",
788
+ "hp.st.failed": "falló",
789
+ "hp.st.stopped": "detenido",
790
+ "b.helpers": "{n} ayudantes en marcha",
791
+ "b.helpers.one": "{n} ayudante en marcha",
792
+ "hub.rail": "Hub",
793
+ "hub.railTip": "{team} en Hub (se abre en otra pestaña)",
794
+ "hub.railTipPlain": "Hub (se abre en otra pestaña)",
795
+ "hub.railUnread": "{n} esperan tu respuesta",
796
+ "hub.railRejected":
797
+ "Hub no acepta el token de esta instalación. Vuelve a conectar en Ajustes › Hub.",
798
+ "hub.railForbidden":
799
+ "Hub no permite esta instalación. Pídeselo a un administrador de Hub (Ajustes › Hub).",
759
800
  "ag.session": "sesión",
760
801
  "ag.empty":
761
802
  "Elige una sesión a la izquierda para abrir su terminal. Abre más para verlas en mosaico (hasta 4).",
@@ -186,6 +186,8 @@
186
186
  "lock.note":
187
187
  "Den はこれを sudo にだけ使います。エージェントは入力できません。",
188
188
  "lock.saved": "このセッションの間、管理者パスワードを保持します",
189
+ "lock.savedTo":
190
+ "このセッションの間、管理者パスワードを保持し、{s} に保存しました",
189
191
  "lock.saveFailed":
190
192
  "このセッションの間は保持しますが、{s} への保存に失敗しました: {err}",
191
193
  "lock.wrong": "パスワードが違います。もう一度お試しください",
@@ -737,6 +739,43 @@
737
739
  "ag.sub": "{n} セッション · 承認待ち {w} · {scope}",
738
740
  "ag.all": "すべてのワークスペース",
739
741
  "ag.waiting": "承認待ち",
742
+ "tx.asked": "依頼",
743
+ "tx.result": "結果",
744
+ "tx.error": "エラー",
745
+ "tx.omitted": "以前の {n} 行は表示していません",
746
+ "tx.empty": "まだ何もありません。",
747
+ "tx.close": "閉じる",
748
+ "tx.loading": "読み込み中…",
749
+ "tx.failed": "読み取れませんでした: {err}",
750
+ "hp.title": "ヘルパー ({n})",
751
+ "hp.bar": "ヘルパー: {n}",
752
+ "hp.barRunning": "ヘルパー: {n}、実行中 {r}",
753
+ "hp.all": "すべてのヘルパー ({n})",
754
+ "hp.runningN": "実行中 {n}",
755
+ "hp.groupOf": "{name} のヘルパー",
756
+ "hp.open": "{what}、{status}: トランスクリプトを開く",
757
+ "hp.dlg": "{name} のヘルパー",
758
+ "hp.earlier": "以前の会話 ({n})",
759
+ "hp.none": "この会話にヘルパーはいません。",
760
+ "hp.unsupported":
761
+ "このエージェントのランタイムは、起動したヘルパーを示せません。",
762
+ "hp.workflow": "ワークフロー {name}",
763
+ "hp.note":
764
+ "ヘルパーはこのエージェント自身のセッションで動くため、Den は表示できますが、停止や指示はできません。変えたいときはエージェントに伝えてください。",
765
+ "hp.st.running": "実行中",
766
+ "hp.st.idle": "待機",
767
+ "hp.st.done": "完了",
768
+ "hp.st.failed": "失敗",
769
+ "hp.st.stopped": "停止",
770
+ "b.helpers": "実行中のヘルパー {n}",
771
+ "hub.rail": "Hub",
772
+ "hub.railTip": "Hub の {team}(新しいタブで開く)",
773
+ "hub.railTipPlain": "Hub(新しいタブで開く)",
774
+ "hub.railUnread": "対応待ち {n} 件",
775
+ "hub.railRejected":
776
+ "Hub がこのインストールのトークンを受け付けません。設定 › Hub から再接続してください。",
777
+ "hub.railForbidden":
778
+ "Hub がこのインストールを許可していません。Hub の管理者に依頼してください(設定 › Hub)。",
740
779
  "ag.session": "セッション",
741
780
  "ag.empty":
742
781
  "左のセッションを選ぶとターミナルが開きます。複数開くと並べて表示します(最大 4)。",