@songsid/agend 2.1.5-beta.21 → 2.1.5-beta.23

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.
@@ -194,7 +194,7 @@
194
194
  backend: "Backend", model: "Model", workingDir: "Working directory", channelBinding: "Channel binding",
195
195
  description: "Description", systemPrompt: "System prompt", tags: "Tags", generalTopicField: "General topic (fleet dispatcher)",
196
196
  runtimeResources: "Runtime & Resources", autoPause: "Auto-pause after", minutes: "minutes", inheritDefault: "Inherit from defaults",
197
- detection: "Detection", startup: "Startup", backendSection: "Backend", advancedSection: "Advanced & Access", hangEnabled: "Hang detector enabled", hangTimeout: "Stuck timeout", agentMode: "Agent mode", toolSet: "Tool set", toolProgress: "Tool progress", logLevel: "Log level", concurrency: "Startup concurrency", staggerDelay: "Stagger delay", lightweight: "Lightweight mode", modelFailover: "Fallback model", displayName: "Display name", pause: "Pause", wake: "Wake", accessMode: "Access mode", affected: "Affected instances",
197
+ detection: "Detection", startup: "Startup", backendSection: "Backend", advancedSection: "Advanced & Access", hangEnabled: "Hang detector enabled", hangTimeout: "Stuck timeout", agentMode: "Agent mode", toolSet: "Tool set", toolProgress: "Tool progress", replyCompletionGuard: "Require a delivered reply", replyGuardSupported: "Active for Claude Code in MCP mode.", replyGuardUnsupported: "Stored but inactive: {0} in {1} mode does not support reply-drop recovery.", logLevel: "Log level", concurrency: "Startup concurrency", staggerDelay: "Stagger delay", lightweight: "Lightweight mode", modelFailover: "Fallback model", displayName: "Display name", pause: "Pause", wake: "Wake", accessMode: "Access mode", affected: "Affected instances",
198
198
  language: "Language (bot replies)", defaultBackend: "Default backend", defaultModel: "Default model", allowedUsers: "Allowed users (primary bot)",
199
199
  classicAdmins: "ClassicBot admins", allowedGuilds: "Allowed guilds", type: "Type", adapterId: "Adapter id",
200
200
  tokenEnv: "Bot token env var", groupId: "Group / guild id", nameField: "Name",
@@ -216,7 +216,7 @@
216
216
  backend: "後端", model: "模型", workingDir: "工作目錄", channelBinding: "頻道綁定",
217
217
  description: "描述", systemPrompt: "System prompt", tags: "標籤", generalTopicField: "General topic(fleet 分派器)",
218
218
  runtimeResources: "執行環境與資源", autoPause: "閒置後自動暫停", minutes: "分鐘", inheritDefault: "沿用全域預設值",
219
- detection: "偵測", startup: "啟動", backendSection: "後端", advancedSection: "進階與存取", hangEnabled: "啟用卡住偵測", hangTimeout: "卡住逾時", agentMode: "Agent 模式", toolSet: "工具集", toolProgress: "工具進度", logLevel: "Log 等級", concurrency: "啟動併發數", staggerDelay: "錯開延遲", lightweight: "輕量模式", modelFailover: "備援模型", displayName: "顯示名稱", pause: "暫停", wake: "喚醒", accessMode: "存取模式", affected: "受影響 instances",
219
+ detection: "偵測", startup: "啟動", backendSection: "後端", advancedSection: "進階與存取", hangEnabled: "啟用卡住偵測", hangTimeout: "卡住逾時", agentMode: "Agent 模式", toolSet: "工具集", toolProgress: "工具進度", replyCompletionGuard: "要求送出回覆", replyGuardSupported: "Claude Code 的 MCP 模式可用。", replyGuardUnsupported: "設定會保留,但不會生效:{0} 的 {1} 模式不支援回覆遺失補救。", logLevel: "Log 等級", concurrency: "啟動併發數", staggerDelay: "錯開延遲", lightweight: "輕量模式", modelFailover: "備援模型", displayName: "顯示名稱", pause: "暫停", wake: "喚醒", accessMode: "存取模式", affected: "受影響 instances",
220
220
  language: "語言(bot 回覆)", defaultBackend: "預設後端", defaultModel: "預設模型", allowedUsers: "允許使用者(主 bot)",
221
221
  classicAdmins: "ClassicBot 管理員", allowedGuilds: "允許的伺服器", type: "類型", adapterId: "Adapter id",
222
222
  tokenEnv: "Bot token 環境變數", groupId: "群組 / guild id", nameField: "名稱",
@@ -373,7 +373,7 @@
373
373
  button.disabled = false;
374
374
  }
375
375
  const hasOwn = (obj, key) => Object.prototype.hasOwnProperty.call(obj || {}, key);
376
- const HOT_FIELDS = new Set(["tool_progress", "mcp_proxy_reply", "auto_pause_after", "warm_cap", "display_name", "description", "tags", "log_level"]);
376
+ const HOT_FIELDS = new Set(["tool_progress", "reply_completion_guard", "mcp_proxy_reply", "auto_pause_after", "warm_cap", "display_name", "description", "tags", "log_level"]);
377
377
  const sameValue = (a, b) => JSON.stringify(a) === JSON.stringify(b);
378
378
  function changedFields(next, baseline) {
379
379
  return Object.fromEntries(Object.entries(next).filter(([key, value]) => !sameValue(value, baseline[key])));
@@ -389,6 +389,12 @@
389
389
  toggle.onchange = () => { input.disabled = toggle.checked; };
390
390
  return { input, toggle, row: el("div", {}, input, el("label", { class: "inherit-row" }, toggle, el("span", {}, t("inheritDefault")), impact(impactKind))) };
391
391
  }
392
+ const replyGuardSupported = (backend, mode) => backend === "claude-code" && mode === "mcp";
393
+ const replyGuardHint = (backend, mode) => el(
394
+ "div",
395
+ { class: "hint" },
396
+ replyGuardSupported(backend, mode) ? t("replyGuardSupported") : tf("replyGuardUnsupported", backend, mode),
397
+ );
392
398
  function setValidation(input, feedback, error, warning) {
393
399
  input.classList.toggle("invalid", !!error);
394
400
  feedback.className = "feedback" + (error ? " error" : warning ? " warning" : "");
@@ -501,7 +507,17 @@
501
507
  const hang = overrideControl(hangHolder, "value", el("input", { type: "number", min: "0.1", step: "0.5", value: hangRaw.timeout_minutes ?? defaults.hang_detector?.timeout_minutes ?? 15 }), defaults.hang_detector?.timeout_minutes ?? 15);
502
508
  const agentMode = overrideControl(inst, "agent_mode", select(inst.agent_mode || defaults.agent_mode || "mcp", ["mcp", "cli"]), defaults.agent_mode || "mcp");
503
509
  const toolSet = overrideControl(inst, "tool_set", select(inst.tool_set || defaults.tool_set || "full", ["full", "standard", "minimal"]), defaults.tool_set || "full");
504
- const toolProgress = overrideControl(inst, "tool_progress", select(inst.tool_progress || defaults.tool_progress || "off", ["off", "standard", "verbose"]), defaults.tool_progress || "off", "now");
510
+ const toolProgress = overrideControl(inst, "tool_progress", select(inst.tool_progress ?? defaults.tool_progress ?? "off", ["off", "standard", "verbose"]), defaults.tool_progress ?? "off", "now");
511
+ const fReplyGuard = el("input", { type: "checkbox" });
512
+ fReplyGuard.checked = inst.reply_completion_guard ?? defaults.reply_completion_guard ?? true;
513
+ const replyGuard = overrideControl(inst, "reply_completion_guard", fReplyGuard, defaults.reply_completion_guard ?? true, "now");
514
+ const replyHint = replyGuardHint(fBackend.value, agentMode.input.value);
515
+ const refreshReplyHint = () => {
516
+ const supported = replyGuardSupported(fBackend.value, agentMode.input.value);
517
+ replyHint.textContent = supported ? t("replyGuardSupported") : tf("replyGuardUnsupported", fBackend.value, agentMode.input.value);
518
+ };
519
+ fBackend.addEventListener("change", refreshReplyHint);
520
+ agentMode.input.addEventListener("change", refreshReplyHint);
505
521
  const logLevel = overrideControl(inst, "log_level", select(inst.log_level || defaults.log_level || "info", ["trace", "debug", "info", "warn", "error"]), defaults.log_level || "info");
506
522
  const fLightweight = el("input", { type: "checkbox" }); fLightweight.checked = inst.lightweight ?? defaults.lightweight ?? false;
507
523
  const lightweight = overrideControl(inst, "lightweight", fLightweight, defaults.lightweight ?? false);
@@ -521,7 +537,7 @@
521
537
  saveBtn.disabled = !(validAuto && validHang);
522
538
  return !saveBtn.disabled;
523
539
  };
524
- for (const ctl of [auto, hang, agentMode, toolSet, toolProgress, logLevel, lightweight, failover, displayName]) {
540
+ for (const ctl of [auto, hang, agentMode, toolSet, toolProgress, replyGuard, logLevel, lightweight, failover, displayName]) {
525
541
  ctl.input.addEventListener("input", validate); ctl.input.addEventListener("change", validate); ctl.toggle.addEventListener("change", validate);
526
542
  }
527
543
  saveBtn.onclick = async () => {
@@ -536,6 +552,7 @@
536
552
  agent_mode: agentMode.toggle.checked ? null : agentMode.input.value,
537
553
  tool_set: toolSet.toggle.checked ? null : toolSet.input.value,
538
554
  tool_progress: toolProgress.toggle.checked ? null : toolProgress.input.value,
555
+ reply_completion_guard: replyGuard.toggle.checked ? null : replyGuard.input.checked,
539
556
  log_level: logLevel.toggle.checked ? null : logLevel.input.value,
540
557
  lightweight: lightweight.toggle.checked ? null : lightweight.input.checked,
541
558
  model_failover: failover.toggle.checked ? null : (failover.input.value.trim() ? [failover.input.value.trim()] : []),
@@ -555,6 +572,7 @@
555
572
  agent_mode: hasOwn(inst, "agent_mode") ? inst.agent_mode : null,
556
573
  tool_set: hasOwn(inst, "tool_set") ? inst.tool_set : null,
557
574
  tool_progress: hasOwn(inst, "tool_progress") ? inst.tool_progress : null,
575
+ reply_completion_guard: hasOwn(inst, "reply_completion_guard") ? inst.reply_completion_guard : null,
558
576
  log_level: hasOwn(inst, "log_level") ? inst.log_level : null,
559
577
  lightweight: hasOwn(inst, "lightweight") ? inst.lightweight : null,
560
578
  model_failover: hasOwn(inst, "model_failover") ? inst.model_failover : null,
@@ -591,6 +609,7 @@
591
609
  el("div", {}, el("label", {}, t("agentMode")), agentMode.row),
592
610
  el("div", {}, el("label", {}, t("toolSet")), toolSet.row),
593
611
  el("div", {}, el("label", {}, t("toolProgress"), impact("now")), toolProgress.row),
612
+ el("div", {}, el("label", {}, t("replyCompletionGuard"), impact("now")), replyGuard.row, replyHint),
594
613
  el("div", {}, el("label", {}, t("logLevel"), impact("now")), logLevel.row),
595
614
  el("div", {}, el("label", {}, t("lightweight")), lightweight.row),
596
615
  el("div", {}, el("label", {}, t("modelFailover")), failover.row, failoverFeedback)),
@@ -717,10 +736,22 @@
717
736
  const inheritedBackend = classicDefaults.backend || fleetDefaults.backend || "claude-code";
718
737
  const inheritedModel = classicDefaults.model || fleetDefaults.model || "";
719
738
  const inheritedAutoPause = classicDefaults.auto_pause_after ?? fleetDefaults.auto_pause_after ?? 0;
739
+ const inheritedToolProgress = classicDefaults.tool_progress ?? fleetDefaults.tool_progress ?? "off";
740
+ const inheritedReplyGuard = classicDefaults.reply_completion_guard ?? fleetDefaults.reply_completion_guard ?? true;
720
741
  const runtime = classicRuntime(c.instanceName);
721
742
  const fBackend = select(c.backend || inheritedBackend, BACKENDS.filter(value => value !== "gemini-cli"));
722
743
  const fModel = el("input", { type: "text", value: c.model || "", placeholder: inheritedModel || "(inherit default)" });
723
744
  const auto = overrideControl(c, "auto_pause_after", el("input", { type: "number", min: "0", step: "1", value: c.auto_pause_after ?? inheritedAutoPause, placeholder: "0 = disabled" }), inheritedAutoPause);
745
+ const toolProgress = overrideControl(c, "tool_progress", select(c.tool_progress ?? inheritedToolProgress, ["off", "standard", "verbose"]), inheritedToolProgress, "now");
746
+ const fReplyGuard = el("input", { type: "checkbox" });
747
+ fReplyGuard.checked = c.reply_completion_guard ?? inheritedReplyGuard;
748
+ const replyGuard = overrideControl(c, "reply_completion_guard", fReplyGuard, inheritedReplyGuard, "now");
749
+ const classicMode = fleetDefaults.agent_mode ?? "mcp";
750
+ const replyHint = replyGuardHint(fBackend.value, classicMode);
751
+ fBackend.addEventListener("change", () => {
752
+ const supported = replyGuardSupported(fBackend.value, classicMode);
753
+ replyHint.textContent = supported ? t("replyGuardSupported") : tf("replyGuardUnsupported", fBackend.value, classicMode);
754
+ });
724
755
  const fCollab = el("input", { type: "checkbox" }); fCollab.checked = !!c.collab;
725
756
  const fContext = el("input", { type: "number", min: "0", step: "1", value: c.context_lines ?? classicDefaults.context_lines ?? 5 });
726
757
  const autoFeedback = el("div", { class: "feedback" });
@@ -734,24 +765,39 @@
734
765
  saveBtn.disabled = !(validAuto && validContext);
735
766
  return !saveBtn.disabled;
736
767
  };
737
- for (const input of [fBackend, fModel, auto.input, auto.toggle, fCollab, fContext]) {
768
+ for (const input of [fBackend, fModel, auto.input, auto.toggle, toolProgress.input, toolProgress.toggle, replyGuard.input, replyGuard.toggle, fCollab, fContext]) {
738
769
  input.addEventListener("input", validate); input.addEventListener("change", validate);
739
770
  }
740
771
  saveBtn.onclick = () => {
741
772
  if (!validate()) return;
742
- const patch = {
773
+ const nextPatch = {
743
774
  backend: fBackend.value,
744
775
  model: fModel.value.trim() || null,
745
776
  auto_pause_after: auto.toggle.checked ? null : Number(auto.input.value),
777
+ tool_progress: toolProgress.toggle.checked ? null : toolProgress.input.value,
778
+ reply_completion_guard: replyGuard.toggle.checked ? null : replyGuard.input.checked,
746
779
  collab: fCollab.checked,
747
780
  context_lines: Number(fContext.value),
748
781
  };
782
+ const patch = changedFields(nextPatch, {
783
+ backend: c.backend || inheritedBackend,
784
+ model: c.model?.trim() || null,
785
+ auto_pause_after: hasOwn(c, "auto_pause_after") ? c.auto_pause_after : null,
786
+ tool_progress: hasOwn(c, "tool_progress") ? c.tool_progress : null,
787
+ reply_completion_guard: hasOwn(c, "reply_completion_guard") ? c.reply_completion_guard : null,
788
+ collab: !!c.collab,
789
+ context_lines: c.context_lines ?? classicDefaults.context_lines ?? 5,
790
+ });
791
+ if (Object.keys(patch).length === 0) { editingClassic = null; renderClassic(); return; }
749
792
  state.classic.channels[c.key] = { ...state.classic.channels[c.key], ...patch };
750
793
  if (patch.model === null) delete state.classic.channels[c.key].model;
751
794
  if (patch.auto_pause_after === null) delete state.classic.channels[c.key].auto_pause_after;
795
+ if (patch.tool_progress === null) delete state.classic.channels[c.key].tool_progress;
796
+ if (patch.reply_completion_guard === null) delete state.classic.channels[c.key].reply_completion_guard;
797
+ const hotOnly = Object.keys(patch).every(key => key === "tool_progress" || key === "reply_completion_guard");
752
798
  stageChange(`classic:${c.key}`, {
753
799
  label: `Update ${c.name || c.instanceName}`,
754
- impact: "instance",
800
+ impact: hotOnly ? "now" : "instance",
755
801
  apply: () => api(`/api/settings/classic/channels/${encodeURIComponent(c.key)}`, { method: "PATCH", body: JSON.stringify(patch) }),
756
802
  });
757
803
  editingClassic = null;
@@ -763,6 +809,8 @@
763
809
  el("div", {}, el("label", {}, t("backend"), impact("instance")), fBackend),
764
810
  el("div", {}, el("label", {}, t("model"), impact("instance")), fModel),
765
811
  el("div", {}, el("label", {}, `${t("autoPause")} (${t("minutes")})`, impact("instance")), auto.row, autoFeedback),
812
+ el("div", {}, el("label", {}, t("toolProgress"), impact("now")), toolProgress.row),
813
+ el("div", {}, el("label", {}, t("replyCompletionGuard"), impact("now")), replyGuard.row, replyHint),
766
814
  el("div", {}, el("label", { style: "display:flex;align-items:center;gap:8px;cursor:pointer;margin-top:28px" }, fCollab, el("span", {}, "Collaboration mode"), impact("instance")))),
767
815
  el("div", { class: "advanced-only" },
768
816
  el("label", {}, "Context lines", impact("instance")), fContext, contextFeedback),
@@ -872,7 +920,11 @@
872
920
  const fModel = el("input", { type: "text", value: d.model || "", placeholder: "e.g. claude-opus-4-8" });
873
921
  const fAgentMode = select(d.agent_mode || "mcp", ["mcp", "cli"]);
874
922
  const fToolSet = select(d.tool_set || "full", ["full", "standard", "minimal"]);
875
- const fToolProgress = select(d.tool_progress || "off", ["off", "standard", "verbose"]);
923
+ const fToolProgress = select(d.tool_progress ?? "off", ["off", "standard", "verbose"]);
924
+ const fReplyGuard = el("input", { type: "checkbox" }); fReplyGuard.checked = d.reply_completion_guard ?? true;
925
+ const fClassicToolProgress = overrideControl(c, "tool_progress", select(c.tool_progress ?? d.tool_progress ?? "off", ["off", "standard", "verbose"]), d.tool_progress ?? "off", "now");
926
+ const fClassicReplyInput = el("input", { type: "checkbox" }); fClassicReplyInput.checked = c.reply_completion_guard ?? d.reply_completion_guard ?? true;
927
+ const fClassicReplyGuard = overrideControl(c, "reply_completion_guard", fClassicReplyInput, d.reply_completion_guard ?? true, "now");
876
928
  const fAccessMode = select(originalPrimaryAccess.mode || "locked", ["locked", "open", "pairing"]);
877
929
  const fLogLevel = select(d.log_level || "info", ["trace", "debug", "info", "warn", "error"]);
878
930
  const fLocale = el("select");
@@ -889,6 +941,17 @@
889
941
  const staggerFeedback = el("div", { class: "feedback" });
890
942
  const accessFeedback = el("div", { class: "feedback" });
891
943
  const saveBtn = el("button", { class: "primary" }, t("save"));
944
+ const defaultReplyHint = replyGuardHint(fBackend.value, fAgentMode.value);
945
+ const classicReplyHint = replyGuardHint(fClassicBackend.value, fAgentMode.value);
946
+ const refreshDefaultReplyHints = () => {
947
+ const fleetSupported = replyGuardSupported(fBackend.value, fAgentMode.value);
948
+ defaultReplyHint.textContent = fleetSupported ? t("replyGuardSupported") : tf("replyGuardUnsupported", fBackend.value, fAgentMode.value);
949
+ const classicSupported = replyGuardSupported(fClassicBackend.value, fAgentMode.value);
950
+ classicReplyHint.textContent = classicSupported ? t("replyGuardSupported") : tf("replyGuardUnsupported", fClassicBackend.value, fAgentMode.value);
951
+ };
952
+ fBackend.addEventListener("change", refreshDefaultReplyHints);
953
+ fClassicBackend.addEventListener("change", refreshDefaultReplyHints);
954
+ fAgentMode.addEventListener("change", refreshDefaultReplyHints);
892
955
  const validate = () => {
893
956
  const auto = Number(fAutoPause.value), timeout = Number(fHangTimeout.value);
894
957
  const concurrency = Number(fConcurrency.value), stagger = Number(fStagger.value);
@@ -915,6 +978,7 @@
915
978
  agent_mode: fAgentMode.value,
916
979
  tool_set: fToolSet.value,
917
980
  tool_progress: fToolProgress.value,
981
+ reply_completion_guard: fReplyGuard.checked,
918
982
  log_level: fLogLevel.value,
919
983
  hang_detector: { enabled: fHangEnabled.checked, timeout_minutes: Number(fHangTimeout.value) },
920
984
  startup: { concurrency: Number(fConcurrency.value), stagger_delay_ms: Number(fStagger.value) },
@@ -926,21 +990,31 @@
926
990
  auto_pause_after: d.auto_pause_after ?? 0,
927
991
  agent_mode: d.agent_mode || "mcp",
928
992
  tool_set: d.tool_set || "full",
929
- tool_progress: d.tool_progress || "off",
993
+ tool_progress: d.tool_progress ?? "off",
994
+ reply_completion_guard: d.reply_completion_guard ?? true,
930
995
  log_level: d.log_level || "info",
931
996
  hang_detector: { enabled: hang.enabled !== false, timeout_minutes: hang.timeout_minutes ?? 15 },
932
997
  startup: { concurrency: startup.concurrency ?? 10, stagger_delay_ms: startup.stagger_delay_ms ?? 500 },
933
998
  });
934
999
  const classicPatch = changedFields(
935
- { backend: fClassicBackend.value, admin_users: c.admin_users, allowed_guilds: c.allowed_guilds },
1000
+ {
1001
+ backend: fClassicBackend.value,
1002
+ tool_progress: fClassicToolProgress.toggle.checked ? null : fClassicToolProgress.input.value,
1003
+ reply_completion_guard: fClassicReplyGuard.toggle.checked ? null : fClassicReplyGuard.input.checked,
1004
+ admin_users: c.admin_users,
1005
+ allowed_guilds: c.allowed_guilds,
1006
+ },
936
1007
  {
937
1008
  backend: originalClassicDefaults.backend || d.backend || "claude-code",
1009
+ tool_progress: hasOwn(originalClassicDefaults, "tool_progress") ? originalClassicDefaults.tool_progress : null,
1010
+ reply_completion_guard: hasOwn(originalClassicDefaults, "reply_completion_guard") ? originalClassicDefaults.reply_completion_guard : null,
938
1011
  admin_users: originalClassicDefaults.admin_users || [],
939
1012
  allowed_guilds: originalClassicDefaults.allowed_guilds || [],
940
1013
  },
941
1014
  );
942
1015
  state.fleet.defaults = { ...d, ...defaultsPatch };
943
1016
  state.classic.defaults = { ...c, ...classicPatch };
1017
+ for (const [key, value] of Object.entries(classicPatch)) if (value === null) delete state.classic.defaults[key];
944
1018
  if (Object.keys(defaultsPatch).length > 0) {
945
1019
  stageChange("defaults:fleet", { label: "Update Agent defaults", impact: Object.keys(defaultsPatch).every(key => HOT_FIELDS.has(key)) ? "now" : "instance", apply: () => api("/api/settings/fleet/defaults", { method: "PUT", body: JSON.stringify(defaultsPatch) }) });
946
1020
  }
@@ -962,7 +1036,8 @@
962
1036
  }
963
1037
  }
964
1038
  if (Object.keys(classicPatch).length > 0) {
965
- stageChange("defaults:classic", { label: "Update ClassicBot defaults and access", impact: "fleet", apply: () => api("/api/settings/classic/defaults", { method: "PUT", body: JSON.stringify(classicPatch) }) });
1039
+ const classicHotOnly = Object.keys(classicPatch).every(key => key === "tool_progress" || key === "reply_completion_guard");
1040
+ stageChange("defaults:classic", { label: "Update ClassicBot defaults and access", impact: classicHotOnly ? "now" : "fleet", apply: () => api("/api/settings/classic/defaults", { method: "PUT", body: JSON.stringify(classicPatch) }) });
966
1041
  }
967
1042
  msg.innerHTML = `<span class="s">${esc(t("readyApply"))}</span>`;
968
1043
  };
@@ -978,10 +1053,14 @@
978
1053
  el("div", { class: "field" }, el("label", {}, t("language"), impact("now")), fLocale),
979
1054
  el("div", { class: "field" }, el("label", {}, t("defaultBackend"), impact("instance")), fBackend),
980
1055
  el("div", { class: "field" }, el("label", {}, t("defaultModel"), impact("instance")), fModel),
981
- el("div", { class: "field" }, el("label", {}, `${t("autoPause")} (${t("minutes")})`, impact("now")), fAutoPause, autoFeedback))),
1056
+ el("div", { class: "field" }, el("label", {}, `${t("autoPause")} (${t("minutes")})`, impact("now")), fAutoPause, autoFeedback),
1057
+ el("div", { class: "field" }, el("label", {}, t("toolProgress"), impact("now")), fToolProgress),
1058
+ el("div", { class: "field" }, el("label", { style: "display:flex;align-items:center;gap:8px" }, fReplyGuard, el("span", {}, t("replyCompletionGuard")), impact("now")), defaultReplyHint))),
982
1059
  section("ClassicBot", true,
983
1060
  el("div", { class: "grid2" },
984
- el("div", { class: "field" }, el("label", {}, "Classic default backend", impact("instance")), fClassicBackend)),
1061
+ el("div", { class: "field" }, el("label", {}, "Classic default backend", impact("instance")), fClassicBackend),
1062
+ el("div", { class: "field" }, el("label", {}, t("toolProgress"), impact("now")), fClassicToolProgress.row),
1063
+ el("div", { class: "field" }, el("label", {}, t("replyCompletionGuard"), impact("now")), fClassicReplyGuard.row, classicReplyHint)),
985
1064
  el("div", { class: "field" }, el("strong", {}, "Who can use ClassicBot"),
986
1065
  el("div", { class: "grid2" },
987
1066
  el("div", {}, el("label", {}, t("classicAdmins"), impact("fleet")), chipList(c.admin_users, null, "user id")),
@@ -990,7 +1069,6 @@
990
1069
  section(t("runtimeResources"), false,
991
1070
  el("div", { class: "grid2" },
992
1071
  el("div", { class: "field" }, el("label", {}, t("toolSet"), impact("instance")), fToolSet),
993
- el("div", { class: "field" }, el("label", {}, t("toolProgress"), impact("now")), fToolProgress),
994
1072
  el("div", { class: "field" }, el("label", {}, t("logLevel"), impact("now")), fLogLevel),
995
1073
  el("div", { class: "field" }, el("label", {}, t("agentMode"), impact("instance")), fAgentMode))),
996
1074
  section(t("detection"), false,
package/dist/ui/view.html CHANGED
@@ -26,7 +26,9 @@
26
26
  .inst.active { background: rgba(88,166,255,.14); border-left: 3px solid var(--accent); padding-left: 13px; }
27
27
  .dot { width: 8px; height: 8px; border-radius: 50%; flex: 0 0 8px; }
28
28
  .dot.running { background: #3fb950; } .dot.stopped { background: #6e7681; } .dot.crashed { background: #f85149; }
29
- .inst .nm { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
29
+ .inst .identity { flex: 1; min-width: 0; }
30
+ .inst .nm { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
31
+ .inst .alias { display: block; margin-top: 2px; color: var(--dim); font-size: 11px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
30
32
  .inst .pct { color: var(--dim); font-size: 11px; }
31
33
  .cli-icon { width: 10px; height: 10px; flex: 0 0 10px; border-radius: 50%; border: 1px solid rgba(255,255,255,.24); box-shadow: 0 0 0 1px rgba(0,0,0,.18); }
32
34
  .cli-claude-code { background: #c678dd; }
@@ -416,11 +418,9 @@
416
418
  if (tags.length) return tags[0];
417
419
  return "Other";
418
420
  }
419
- // Short label: display name if set, else raw instance_name.
420
- // M1: No fallback shortening — raw identity must be preserved when no display_name.
421
- // Tooltip still shows full instance_name for discoverability.
422
- function shortName(it) {
423
- return it.display_name || it.instance_name;
421
+ function sidebarAlias(it) {
422
+ const alias = typeof it.display_name === "string" ? it.display_name.trim() : "";
423
+ return alias && alias !== it.instance_name ? alias : "";
424
424
  }
425
425
 
426
426
  const BACKEND_LABELS = {
@@ -536,7 +536,8 @@
536
536
  el.className = "inst" + (name === current ? " active" : "");
537
537
  el.draggable = true;
538
538
  // M2: context_pct != null shows value including 0%; null means unavailable (empty)
539
- el.innerHTML = `<span class="dot ${it.status}"></span><span class="nm">${esc(shortName(it))}</span><span class="pct">${it.context_pct != null ? Math.round(it.context_pct)+"%" : ""}</span>${backendIconHtml(it.backend)}`;
539
+ const alias = sidebarAlias(it);
540
+ el.innerHTML = `<span class="dot ${it.status}"></span><span class="identity"><span class="nm">${esc(it.instance_name)}</span>${alias ? `<span class="alias">${esc(alias)}</span>` : ""}</span><span class="pct">${it.context_pct != null ? Math.round(it.context_pct)+"%" : ""}</span>${backendIconHtml(it.backend)}`;
540
541
  el.title = instanceTooltip(it);
541
542
  el.onclick = () => select(name);
542
543
  wireDrag(el, { type: "instance", name, group: g });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@songsid/agend",
3
- "version": "2.1.5-beta.21",
3
+ "version": "2.1.5-beta.23",
4
4
  "description": "Multi-agent fleet daemon — run any coding CLI (Claude, Gemini, Codex, OpenCode) from Telegram",
5
5
  "type": "module",
6
6
  "bin": {