@songsid/agend 2.1.0-beta.26 โ†’ 2.1.0-beta.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.
@@ -11,7 +11,7 @@
11
11
  --accent:#2563eb; --accent-hover:#1d4ed8; --green:#059669; --red:#dc2626; --yellow:#b45309;
12
12
  --radius:8px; --radius-sm:6px;
13
13
  }
14
- body { background: var(--bg); color: var(--fg); font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; font-size: 14px; line-height: 1.5; padding-bottom: 60px; }
14
+ body { background: var(--bg); color: var(--fg); font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; font-size: 14px; line-height: 1.5; padding-bottom: 88px; }
15
15
  header { display: flex; align-items: center; gap: 12px; padding: 16px 24px; border-bottom: 1px solid var(--border); position: sticky; top: 0; background: var(--bg); z-index: 20; }
16
16
  header h1 { font-size: 18px; font-weight: 700; }
17
17
  .grow { flex: 1; }
@@ -78,6 +78,18 @@
78
78
  .inherit-row { display:flex; gap:8px; align-items:center; margin-top:6px; color:var(--muted); font-size:12px; }
79
79
  .adv-body { display: none; padding: 14px 16px; }
80
80
  .adv-body.open { display: block; }
81
+ .advanced-only { display:none !important; }
82
+ body.show-advanced .advanced-only { display:block !important; }
83
+ .more-menu { position:relative; }
84
+ .more-menu summary { list-style:none; cursor:pointer; padding:4px 9px; color:var(--muted); }
85
+ .more-menu summary::-webkit-details-marker { display:none; }
86
+ .more-menu .menu { position:absolute; right:0; top:30px; z-index:10; background:#fff; border:1px solid var(--border); border-radius:var(--radius-sm); box-shadow:0 8px 24px rgba(0,0,0,.12); padding:5px; min-width:130px; }
87
+ .more-menu .menu button { width:100%; text-align:left; border:0; }
88
+ .pending-bar { position:fixed; left:0; right:0; bottom:0; display:none; align-items:center; gap:12px; padding:12px max(16px, calc((100vw - 860px) / 2 + 24px)); background:#111827; color:#fff; z-index:35; box-shadow:0 -4px 18px rgba(0,0,0,.18); }
89
+ .pending-bar.open { display:flex; }
90
+ .pending-bar .grow { flex:1; }
91
+ .pending-bar button.primary { background:#fff; color:#111827; border-color:#fff; }
92
+ .level-label { font-size:11px; color:var(--muted); text-transform:uppercase; letter-spacing:.4px; margin-bottom:8px; }
81
93
  .toolbar { display: flex; gap: 6px; align-items: center; margin-bottom: 8px; flex-wrap: wrap; }
82
94
  .seg { display: inline-flex; border: 1px solid var(--border); border-radius: var(--radius-sm); overflow: hidden; }
83
95
  .seg button { border: none; border-radius: 0; }
@@ -106,6 +118,9 @@
106
118
  .sub { margin-left:0; } .field { padding:10px 12px; }
107
119
  #agentSearch { width:130px !important; margin-right:0 !important; }
108
120
  button { min-height:36px; } button.sm { min-height:32px; }
121
+ .pending-bar { padding:10px 12px; flex-wrap:wrap; }
122
+ .pending-bar .grow { display:none; }
123
+ .pending-bar button { flex:1; }
109
124
  }
110
125
  </style>
111
126
  </head>
@@ -116,7 +131,7 @@
116
131
  <div class="grow"></div>
117
132
  <button class="help-btn" id="langBtn" title="Language">๐ŸŒ</button>
118
133
  <button class="help-btn" id="helpBtn" title="Help">โ„น๏ธ</button>
119
- <button id="reloadBtn" class="primary" data-i18n="reloadFleet" title="Apply saved config to running instances (SIGHUP)">โ†ป Reload Fleet</button>
134
+ <button id="advancedToggle" title="Show additional controls">Show advanced</button>
120
135
  </header>
121
136
  <div class="help-backdrop" id="helpBackdrop"></div>
122
137
  <aside class="help-panel" id="helpPanel">
@@ -137,7 +152,7 @@
137
152
  </section>
138
153
 
139
154
  <section>
140
- <div class="sec-head"><h2 data-i18n="bots">๐Ÿ”Œ Bots</h2><div class="grow"></div><button class="sm" id="newBotBtn" data-i18n="add">+ Add</button></div>
155
+ <div class="sec-head"><h2>๐Ÿ”Œ Connections &amp; Bots</h2><div class="grow"></div><button class="sm advanced-only" id="newBotBtn" data-i18n="add">+ Add</button></div>
141
156
  <div id="newBot"></div>
142
157
  <div class="card" id="bots"></div>
143
158
  </section>
@@ -148,15 +163,18 @@
148
163
  </section>
149
164
 
150
165
  <section>
151
- <div class="sec-head"><h2 data-i18n="whatsNew">๐Ÿ†• What's New</h2><div class="grow"></div><button class="sm" id="wnToggle" data-i18n="expand">โ–ถ Expand</button></div>
152
- <div class="card"><div class="adv-body" id="wnBody"></div></div>
153
- </section>
154
-
155
- <section>
156
- <div class="sec-head"><h2 data-i18n="advanced">๐Ÿ“‹ Advanced</h2><div class="grow"></div><button class="sm" id="advToggle" data-i18n="expand">โ–ถ Expand</button></div>
166
+ <div class="level-label">Developer ยท Level 3</div>
167
+ <div class="sec-head"><h2>๐Ÿ“‹ Developer YAML</h2><div class="grow"></div><button class="sm" id="advToggle" data-i18n="expand">โ–ถ Expand</button></div>
157
168
  <div class="card"><div class="adv-body" id="advBody"></div></div>
158
169
  </section>
159
170
  </main>
171
+ <div class="pending-bar" id="pendingBar">
172
+ <strong id="pendingCount">0 changes not applied</strong>
173
+ <span id="pendingImpact">โšก Immediately ยท ๐Ÿ”„ Restart this Agent ยท ๐Ÿ”„๐Ÿ”„ Restart AgEnD</span>
174
+ <span class="grow"></span>
175
+ <button id="discardChanges">Discard</button>
176
+ <button class="primary" id="applyChanges">Apply changes</button>
177
+ </div>
160
178
  <script>
161
179
  (() => {
162
180
  "use strict";
@@ -228,7 +246,7 @@
228
246
  document.querySelectorAll("[data-i18n]").forEach(e => { e.textContent = t(e.getAttribute("data-i18n")); });
229
247
  document.querySelectorAll("[data-i18n-ph]").forEach(e => { e.setAttribute("placeholder", t(e.getAttribute("data-i18n-ph"))); });
230
248
  // toggle labels that carry expand/collapse state
231
- for (const [id, body] of [["advToggle", "advBody"], ["wnToggle", "wnBody"]]) {
249
+ for (const [id, body] of [["advToggle", "advBody"]]) {
232
250
  const b = $(body); if (b) $(id).textContent = b.classList.contains("open") ? t("collapse") : t("expand");
233
251
  }
234
252
  renderHelp();
@@ -288,7 +306,7 @@
288
306
  }
289
307
 
290
308
  // โ”€โ”€ State โ”€โ”€
291
- const state = { fleet: {}, classic: {}, classicStatus: {}, live: {}, fleetUp: false, advFmt: "yaml", advEdit: false, collapsedGroups: new Set(), version: "", newAgentOpen: false, newBotOpen: false };
309
+ const state = { fleet: {}, classic: {}, classicStatus: {}, live: {}, fleetUp: false, advFmt: "yaml", advEdit: false, collapsedGroups: new Set(), version: "", newAgentOpen: false, newBotOpen: false, pending: new Map() };
292
310
  const channels = () => state.fleet.channels || (state.fleet.channel ? [state.fleet.channel] : []);
293
311
  const channelIds = () => channels().map(c => c.id || c.type).filter(Boolean);
294
312
  const chLabel = (i) => i === 0 ? "primary" : "persona";
@@ -304,7 +322,36 @@
304
322
  redraw(); return box;
305
323
  }
306
324
  function select(value, options) { const s = el("select"); for (const o of options) { const val = typeof o === "string" ? o : o.value; const label = typeof o === "string" ? o : o.label; const opt = el("option", {}, label); opt.value = val; if (val === value) opt.selected = true; s.append(opt); } return s; }
307
- const impact = (kind) => el("span", { class: "impact" }, kind === "now" ? "โšก immediate" : kind === "fleet" ? "๐Ÿ”„๐Ÿ”„ restart fleet" : "๐Ÿ”„ restart instance");
325
+ const impact = (kind) => el("span", { class: "impact" }, kind === "now" ? "โšก Immediately" : kind === "fleet" ? "๐Ÿ”„๐Ÿ”„ Restart AgEnD" : "๐Ÿ”„ Restart this Agent");
326
+ function updatePendingBar() {
327
+ const n = state.pending.size;
328
+ $("pendingBar").classList.toggle("open", n > 0);
329
+ $("pendingCount").textContent = `${n} change${n === 1 ? "" : "s"} not applied`;
330
+ const kinds = new Set([...state.pending.values()].map(change => change.impact));
331
+ $("pendingImpact").textContent = [...kinds].map(kind => kind === "now" ? "โšก Immediately" : kind === "fleet" ? "๐Ÿ”„๐Ÿ”„ Restart AgEnD" : "๐Ÿ”„ Restart this Agent").join(" ยท ");
332
+ }
333
+ function stageChange(key, change) {
334
+ state.pending.set(key, change);
335
+ updatePendingBar();
336
+ showBanner(`Queued: ${change.label}`);
337
+ }
338
+ async function applyPendingChanges() {
339
+ const button = $("applyChanges"); button.disabled = true;
340
+ for (const [key, change] of [...state.pending]) {
341
+ let result;
342
+ try { result = await change.apply(); }
343
+ catch (error) { result = { ok: false, body: { error: error.message } }; }
344
+ if (!result?.ok) {
345
+ showBanner(`${change.label}: ${result?.body?.error || "Apply failed"}`, true);
346
+ button.disabled = false; return;
347
+ }
348
+ state.pending.delete(key); updatePendingBar();
349
+ }
350
+ await api("/api/settings/reload", { method: "POST" });
351
+ await reload();
352
+ showBanner("โœ“ Changes applied. AgEnD is reconciling affected Agents.");
353
+ button.disabled = false;
354
+ }
308
355
  const hasOwn = (obj, key) => Object.prototype.hasOwnProperty.call(obj || {}, key);
309
356
  function overrideControl(inst, key, input, inheritedValue, impactKind = "instance") {
310
357
  const inherited = !hasOwn(inst, key);
@@ -357,7 +404,9 @@
357
404
  running ? el("button", { class: "sm", onclick: () => pauseWakeAgent(name, "pause") }, t("pause")) : null,
358
405
  paused ? el("button", { class: "sm", onclick: () => pauseWakeAgent(name, "wake") }, t("wake")) : null,
359
406
  !paused ? el("button", { class: "sm", onclick: () => toggleAgent(name, running) }, running ? t("stop") : t("start")) : null,
360
- el("button", { class: "sm danger del-btn", title: t("close"), onclick: () => delAgent(name) }, "๐Ÿ—‘๏ธ"),
407
+ el("details", { class: "more-menu" },
408
+ el("summary", { title: "More actions" }, "โ‹ฏ"),
409
+ el("div", { class: "menu" }, el("button", { class: "sm danger", onclick: () => delAgent(name) }, "Delete Agent"))),
361
410
  ));
362
411
  if (editing === name) item.append(agentEditForm(name, inst));
363
412
  return item;
@@ -443,27 +492,42 @@
443
492
  model_failover: failover.toggle.checked ? null : (failover.input.value.trim() ? [failover.input.value.trim()] : []),
444
493
  display_name: displayName.toggle.checked ? null : displayName.input.value.trim(),
445
494
  };
446
- const res = await api(`/api/settings/fleet/instances/${encodeURIComponent(name)}`, { method: "PATCH", body: JSON.stringify(patch) });
447
- resultMsg(msg, res); if (res.ok) { await reload(); showBanner(`โœ“ ${t("affected")}: ${name} โ€” ๐Ÿ”„ restart instance`); }
495
+ state.fleet.instances[name] = { ...inst, ...patch };
496
+ for (const [key, value] of Object.entries(patch)) if (value === null) delete state.fleet.instances[name][key];
497
+ if (patch.hang_detector?.timeout_minutes === null) {
498
+ state.fleet.instances[name].hang_detector = { ...(inst.hang_detector || {}) };
499
+ delete state.fleet.instances[name].hang_detector.timeout_minutes;
500
+ }
501
+ stageChange(`agent:${name}`, {
502
+ label: `Update ${shortName(name, state.fleet.instances[name])}`,
503
+ impact: "instance",
504
+ apply: () => api(`/api/settings/fleet/instances/${encodeURIComponent(name)}`, { method: "PATCH", body: JSON.stringify(patch) }),
505
+ });
506
+ editing = null; renderAgents();
448
507
  };
449
508
  box.append(
450
509
  el("div", { class: "sub", style: "margin:0 0 8px" }, "ID: " + name),
451
- el("div", { class: "grid2" }, el("div", {}, el("label", {}, t("backend"), impact("instance")), fBackend), el("div", {}, el("label", {}, t("model"), impact("instance")), fModel)),
510
+ el("div", { class: "grid2" },
511
+ el("div", {}, el("label", {}, t("displayName"), impact("now")), displayName.row),
512
+ el("div", {}, el("label", {}, t("description"), impact("now")), fDesc),
513
+ el("div", {}, el("label", {}, t("backend"), impact("instance")), fBackend),
514
+ el("div", {}, el("label", {}, t("model"), impact("instance")), fModel)),
452
515
  el("label", {}, t("workingDir"), impact("instance")), fWd,
453
- el("div", { class: "grid2" }, el("div", {}, el("label", {}, t("channelBinding"), impact("fleet")), fChan), el("div", {}, el("label", {}, t("description"), impact("now")), fDesc)),
454
- el("label", {}, t("systemPrompt"), impact("instance")), fSys,
455
- el("label", {}, t("tags"), impact("now")), chipList(tags, null, "tag"),
456
- el("label", { style: "display:flex;align-items:center;gap:8px;cursor:pointer" }, fGeneral, el("span", {}, t("generalTopicField")), impact("fleet")),
457
- el("div", { class: "sub", style: "margin:14px 0 6px;font-weight:600" }, t("runtimeResources")),
458
516
  el("div", { class: "grid2" },
459
- el("div", {}, el("label", {}, `${t("autoPause")} (${t("minutes")})`), auto.row, autoFeedback),
517
+ el("div", {}, el("label", {}, `${t("autoPause")} (${t("minutes")})`, impact("instance")), auto.row, autoFeedback)),
518
+ el("div", { class: "advanced-only" },
519
+ el("div", { class: "sub", style: "margin:14px 0 6px;font-weight:600" }, "Advanced Agent settings"),
520
+ el("div", { class: "grid2" },
521
+ el("div", {}, el("label", {}, t("channelBinding"), impact("fleet")), fChan),
460
522
  el("div", {}, el("label", {}, `${t("hangTimeout")} (${t("minutes")})`), hang.row, hangFeedback),
461
523
  el("div", {}, el("label", {}, t("agentMode")), agentMode.row),
462
524
  el("div", {}, el("label", {}, t("toolSet")), toolSet.row),
463
525
  el("div", {}, el("label", {}, t("logLevel")), logLevel.row),
464
526
  el("div", {}, el("label", {}, t("lightweight")), lightweight.row),
465
- el("div", {}, el("label", {}, t("modelFailover")), failover.row, failoverFeedback),
466
- el("div", {}, el("label", {}, t("displayName")), displayName.row)),
527
+ el("div", {}, el("label", {}, t("modelFailover")), failover.row, failoverFeedback)),
528
+ el("label", {}, t("systemPrompt"), impact("instance")), fSys,
529
+ el("label", {}, t("tags"), impact("now")), chipList(tags, null, "tag"),
530
+ el("label", { style: "display:flex;align-items:center;gap:8px;cursor:pointer" }, fGeneral, el("span", {}, t("generalTopicField")), impact("fleet"))),
467
531
  el("div", { class: "actions" },
468
532
  saveBtn,
469
533
  el("button", { class: "sm ghost", onclick: () => { editing = null; renderAgents(); } }, t("cancel")),
@@ -483,9 +547,13 @@
483
547
  await reloadLive();
484
548
  }
485
549
  async function delAgent(name) {
486
- if (!confirm(`Delete agent "${name}"? (config removed; running daemon stops on next Reload)`)) return;
487
- const res = await api(`/api/settings/fleet/instances/${encodeURIComponent(name)}`, { method: "DELETE" });
488
- if (res.ok) await reload(); else alert((res.body && res.body.error) || "Delete failed");
550
+ if (!confirm(`Delete agent "${name}"? This will be applied only after you confirm the pending changes.`)) return;
551
+ delete state.fleet.instances[name];
552
+ stageChange(`agent:${name}`, {
553
+ label: `Delete ${name}`, impact: "fleet",
554
+ apply: () => api(`/api/settings/fleet/instances/${encodeURIComponent(name)}`, { method: "DELETE" }),
555
+ });
556
+ renderAgents();
489
557
  }
490
558
  function renderNewAgent(open) {
491
559
  const host = $("newAgent"); host.innerHTML = "";
@@ -520,12 +588,12 @@
520
588
  const type = ch.type || "?";
521
589
  item.append(el("div", { class: "item-row" },
522
590
  el("span", { class: "name" }, type === "telegram" ? "Telegram" : "Discord"),
523
- el("span", { class: "tag" + (i === 0 ? "" : " persona") + (type === "telegram" ? " tg" : "") }, chLabel(i)),
524
- el("span", { class: "sub", style: "margin:0" }, ch.bot_token_env || "(no token env)"),
591
+ el("span", { class: "tag" + (i === 0 ? "" : " persona") + (type === "telegram" ? " tg" : "") }, ch.id || chLabel(i)),
592
+ el("span", { class: "sub advanced-only", style: "margin:0" }, ch.bot_token_env || "(no token env)"),
525
593
  el("span", { class: "spacer" }),
526
- el("span", { style: "color:var(--green);font-size:13px" }, state.fleetUp ? t("online") : "โ€”"),
527
- el("button", { class: "sm ghost", onclick: () => { editingBot = editingBot === i ? null : i; renderBots(); } }, editingBot === i ? t("close") : t("edit")),
528
- el("button", { class: "sm danger", onclick: () => delBot(i) }, "๐Ÿ—‘๏ธ"),
594
+ el("span", { style: `color:${state.fleetUp ? "var(--green)" : "var(--red)"};font-size:13px` }, state.fleetUp ? "Connected" : "Problem"),
595
+ el("button", { class: "sm ghost advanced-only", onclick: () => { editingBot = editingBot === i ? null : i; renderBots(); } }, editingBot === i ? t("close") : t("edit")),
596
+ el("button", { class: "sm danger advanced-only", onclick: () => delBot(i) }, "Delete"),
529
597
  ));
530
598
  if (editingBot === i) item.append(botEditForm(ch, i));
531
599
  host.append(item);
@@ -559,10 +627,10 @@
559
627
  return box;
560
628
  }
561
629
 
562
- // โ”€โ”€ ClassicBot Channels (read-only; created dynamically via /start /stop) โ”€โ”€
630
+ // โ”€โ”€ ClassicBot Channels โ”€โ”€
563
631
  function renderClassic() {
564
632
  const host = $("classicList"); if (!host) return; host.innerHTML = "";
565
- const chans = Object.values(state.classic?.channels || {});
633
+ const chans = Object.entries(state.classic?.channels || {}).map(([key, channel]) => ({ key, ...channel }));
566
634
  if (!chans.length) { host.className = "card empty"; host.textContent = t("noClassic"); return; }
567
635
  const filter = ($("agentSearch")?.value || "").trim().toLowerCase();
568
636
  let list = chans;
@@ -576,10 +644,20 @@
576
644
  for (const c of list) {
577
645
  const st = state.classicStatus[c.instanceName] || "stopped";
578
646
  const adapter = c.adapterId || "discord";
647
+ const backend = select(c.backend || state.classic?.defaults?.backend || "claude-code", BACKENDS.filter(value => value !== "gemini-cli"));
648
+ backend.style.width = "150px";
649
+ backend.onchange = () => {
650
+ const value = backend.value;
651
+ state.classic.channels[c.key].backend = value;
652
+ stageChange(`classic:${c.key}`, {
653
+ label: `Change ${c.name || c.instanceName} backend`, impact: "instance",
654
+ apply: () => api(`/api/settings/classic/channels/${encodeURIComponent(c.key)}`, { method: "PATCH", body: JSON.stringify({ backend: value }) }),
655
+ });
656
+ };
579
657
  host.append(el("div", { class: "item" }, el("div", { class: "item-row" },
580
658
  el("span", { class: "dot " + st, title: STATUS_TEXT[st] || st }),
581
659
  el("span", { class: "name", title: c.instanceName || "" }, c.name || c.instanceName || "?"),
582
- el("span", { class: "sub", style: "margin:0" }, c.backend || state.classic?.defaults?.backend || "claude-code"),
660
+ backend,
583
661
  el("span", { class: "tag" + (adapter !== "discord" ? " persona" : "") }, adapter),
584
662
  c.collab ? el("span", { class: "tag", style: "background:#ecfdf5;color:#065f46" }, "collab") : null,
585
663
  )));
@@ -635,6 +713,7 @@
635
713
  c.admin_users = c.admin_users || []; c.allowed_guilds = c.allowed_guilds || [];
636
714
 
637
715
  const fBackend = select(d.backend || "claude-code", BACKENDS);
716
+ const fClassicBackend = select(c.backend || d.backend || "claude-code", BACKENDS.filter(value => value !== "gemini-cli"));
638
717
  const fModel = el("input", { type: "text", value: d.model || "", placeholder: "e.g. claude-opus-4-8" });
639
718
  const fAgentMode = select(d.agent_mode || "mcp", ["mcp", "cli"]);
640
719
  const fToolSet = select(d.tool_set || "full", ["full", "standard", "minimal"]);
@@ -665,7 +744,8 @@
665
744
  for (const input of [fAutoPause, fHangTimeout, fConcurrency, fStagger]) {
666
745
  input.addEventListener("input", validate); input.addEventListener("change", validate);
667
746
  }
668
- saveBtn.onclick = async () => {
747
+ saveBtn.textContent = "Review changes";
748
+ saveBtn.onclick = () => {
669
749
  if (!validate()) return;
670
750
  const defaultsPatch = {
671
751
  backend: fBackend.value,
@@ -678,17 +758,13 @@
678
758
  hang_detector: { enabled: fHangEnabled.checked, timeout_minutes: Number(fHangTimeout.value) },
679
759
  startup: { concurrency: Number(fConcurrency.value), stagger_delay_ms: Number(fStagger.value) },
680
760
  };
681
- const r1 = await api("/api/settings/fleet/defaults", { method: "PUT", body: JSON.stringify(defaultsPatch) });
682
- let r2 = { ok: true, body: {} };
683
- if (primary) r2 = await saveChannels();
684
- const r3 = await api("/api/settings/classic/defaults", { method: "PUT", body: JSON.stringify({ backend: c.backend, admin_users: c.admin_users, allowed_guilds: c.allowed_guilds }) });
685
- const bad = [r1, r2, r3].find(r => !r.ok);
686
- resultMsg(msg, bad || { ok: true, body: { warnings: [...(r1.body?.warnings || []), ...(r3.body?.warnings || [])] } });
687
- if (!bad) {
688
- const affected = Object.keys(state.fleet.instances || {});
689
- await reload();
690
- showBanner(`โœ“ ${t("affected")}: ${affected.length ? affected.join(", ") : "none"} โ€” ๐Ÿ”„ restart instances; startup changes ๐Ÿ”„๐Ÿ”„ restart fleet`);
691
- }
761
+ const classicPatch = { backend: fClassicBackend.value, admin_users: c.admin_users, allowed_guilds: c.allowed_guilds };
762
+ state.fleet.defaults = { ...d, ...defaultsPatch };
763
+ state.classic.defaults = { ...c, ...classicPatch };
764
+ stageChange("defaults:fleet", { label: "Update Agent defaults", impact: "instance", apply: () => api("/api/settings/fleet/defaults", { method: "PUT", body: JSON.stringify(defaultsPatch) }) });
765
+ if (primary) stageChange("connections:primary", { label: "Update primary connection access", impact: "fleet", apply: () => api("/api/settings/fleet/channels", { method: "PUT", body: JSON.stringify(channels()) }) });
766
+ stageChange("defaults:classic", { label: "Update ClassicBot defaults and access", impact: "fleet", apply: () => api("/api/settings/classic/defaults", { method: "PUT", body: JSON.stringify(classicPatch) }) });
767
+ msg.innerHTML = `<span class="s">Ready to apply</span>`;
692
768
  };
693
769
 
694
770
  const section = (title, open, ...children) => {
@@ -697,15 +773,24 @@
697
773
  return details;
698
774
  };
699
775
  host.append(
700
- section(t("runtimeResources"), true,
701
- el("div", { class: "grid2" },
702
- el("div", { class: "field" }, el("label", {}, `${t("autoPause")} (${t("minutes")})`, impact("instance")), fAutoPause, autoFeedback),
703
- el("div", { class: "field" }, el("label", {}, t("toolSet"), impact("instance")), fToolSet),
704
- el("div", { class: "field" }, el("label", {}, t("logLevel"), impact("instance")), fLogLevel))),
705
- section(t("backendSection"), true,
776
+ section("Defaults", true,
706
777
  el("div", { class: "grid2" },
778
+ el("div", { class: "field" }, el("label", {}, t("language"), impact("now")), fLocale),
707
779
  el("div", { class: "field" }, el("label", {}, t("defaultBackend"), impact("instance")), fBackend),
708
780
  el("div", { class: "field" }, el("label", {}, t("defaultModel"), impact("instance")), fModel),
781
+ el("div", { class: "field" }, el("label", {}, `${t("autoPause")} (${t("minutes")})`, impact("instance")), fAutoPause, autoFeedback))),
782
+ section("ClassicBot", true,
783
+ el("div", { class: "grid2" },
784
+ el("div", { class: "field" }, el("label", {}, "Classic default backend", impact("instance")), fClassicBackend)),
785
+ el("div", { class: "field" }, el("strong", {}, "Who can use ClassicBot"),
786
+ el("div", { class: "grid2" },
787
+ el("div", {}, el("label", {}, t("classicAdmins"), impact("fleet")), chipList(c.admin_users, null, "user id")),
788
+ el("div", {}, el("label", {}, t("allowedGuilds"), impact("fleet")), chipList(c.allowed_guilds, null, "guild id"))))),
789
+ el("div", { class: "advanced-only" },
790
+ section(t("runtimeResources"), false,
791
+ el("div", { class: "grid2" },
792
+ el("div", { class: "field" }, el("label", {}, t("toolSet"), impact("instance")), fToolSet),
793
+ el("div", { class: "field" }, el("label", {}, t("logLevel"), impact("instance")), fLogLevel),
709
794
  el("div", { class: "field" }, el("label", {}, t("agentMode"), impact("instance")), fAgentMode))),
710
795
  section(t("detection"), false,
711
796
  el("div", { class: "grid2" },
@@ -715,11 +800,8 @@
715
800
  el("div", { class: "grid2" },
716
801
  el("div", { class: "field" }, el("label", {}, t("concurrency"), impact("fleet")), fConcurrency, concurrencyFeedback),
717
802
  el("div", { class: "field" }, el("label", {}, `${t("staggerDelay")} (ms)`, impact("fleet")), fStagger, staggerFeedback))),
718
- section(t("advancedSection"), false,
719
- el("div", { class: "field" }, el("label", {}, t("language"), impact("now")), fLocale),
720
- el("div", { class: "field" }, el("label", {}, t("allowedUsers"), impact("fleet")), chipList(allowedUsers, null, "user id")),
721
- el("div", { class: "field" }, el("label", {}, t("classicAdmins"), impact("fleet")), chipList(c.admin_users, null, "user id")),
722
- el("div", { class: "field" }, el("label", {}, t("allowedGuilds"), impact("fleet")), chipList(c.allowed_guilds, null, "guild id"))),
803
+ section("Advanced & Access", false,
804
+ el("div", { class: "field" }, el("label", {}, t("allowedUsers"), impact("fleet")), chipList(allowedUsers, null, "user id")))),
723
805
  el("div", { class: "field" }, el("div", { class: "actions" }, saveBtn, msg)),
724
806
  );
725
807
  validate();
@@ -786,7 +868,7 @@
786
868
  }
787
869
 
788
870
  // โ”€โ”€ Load / render โ”€โ”€
789
- function renderAll() { renderAgents(); renderClassic(); renderBots(); renderGeneral(); renderWhatsNew(); renderAdvanced(); applyStaticI18n(); }
871
+ function renderAll() { renderAgents(); renderClassic(); renderBots(); renderGeneral(); renderAdvanced(); applyStaticI18n(); }
790
872
  async function reloadLive() {
791
873
  const f = await api("/api/fleet");
792
874
  state.fleetUp = f.ok;
@@ -813,13 +895,22 @@
813
895
  $("helpClose").onclick = () => setHelp(false);
814
896
  $("helpBackdrop").onclick = () => setHelp(false);
815
897
  $("langBtn").onclick = () => { lang = lang === "en" ? "zh-TW" : "en"; localStorage.setItem("agend_lang", lang); renderAll(); };
816
- $("verBadge").onclick = () => { const b = $("wnBody"); b.classList.add("open"); $("wnToggle").textContent = t("collapse"); b.scrollIntoView({ behavior: "smooth" }); };
898
+ $("verBadge").onclick = () => window.open("https://github.com/songsid/AgEnD/releases/latest", "_blank", "noopener");
817
899
  $("agentSearch").oninput = () => { renderAgents(); renderClassic(); };
818
900
  $("newAgentBtn").onclick = () => { state.newAgentOpen = !state.newAgentOpen; $("newAgentBtn").textContent = state.newAgentOpen ? t("cancel") : t("new"); renderNewAgent(state.newAgentOpen); };
819
901
  $("newBotBtn").onclick = () => { state.newBotOpen = !state.newBotOpen; $("newBotBtn").textContent = state.newBotOpen ? t("cancel") : t("add"); renderNewBot(state.newBotOpen); };
820
902
  $("advToggle").onclick = () => { const b = $("advBody"); const open = !b.classList.contains("open"); b.classList.toggle("open", open); $("advToggle").textContent = open ? t("collapse") : t("expand"); };
821
- $("wnToggle").onclick = () => { const b = $("wnBody"); const open = !b.classList.contains("open"); b.classList.toggle("open", open); $("wnToggle").textContent = open ? t("collapse") : t("expand"); };
822
- $("reloadBtn").onclick = async () => { const r = await api("/api/settings/reload", { method: "POST" }); const b = $("banner"); b.style.display = "block"; b.style.background = "#ecfdf5"; b.style.color = "var(--green)"; b.textContent = r.ok ? "โœ“ Reload signal sent โ€” instances reconciling." : "Reload failed."; setTimeout(() => b.style.display = "none", 4000); setTimeout(reloadLive, 1500); };
903
+ const advancedEnabled = localStorage.getItem("agend_settings_advanced") === "true";
904
+ document.body.classList.toggle("show-advanced", advancedEnabled);
905
+ $("advancedToggle").textContent = advancedEnabled ? "Hide advanced" : "Show advanced";
906
+ $("advancedToggle").onclick = () => {
907
+ const enabled = !document.body.classList.contains("show-advanced");
908
+ document.body.classList.toggle("show-advanced", enabled);
909
+ localStorage.setItem("agend_settings_advanced", String(enabled));
910
+ $("advancedToggle").textContent = enabled ? "Hide advanced" : "Show advanced";
911
+ };
912
+ $("applyChanges").onclick = applyPendingChanges;
913
+ $("discardChanges").onclick = async () => { state.pending.clear(); updatePendingBar(); await reload(); };
823
914
 
824
915
  reload();
825
916
  })();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@songsid/agend",
3
- "version": "2.1.0-beta.26",
3
+ "version": "2.1.0-beta.28",
4
4
  "description": "Multi-agent fleet daemon โ€” run any coding CLI (Claude, Gemini, Codex, OpenCode) from Telegram",
5
5
  "type": "module",
6
6
  "bin": {