dsh-agy-link 0.4.43 → 0.4.46

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/dist/client.js CHANGED
@@ -39,6 +39,25 @@ window.__ModuleLoader__.load({
39
39
  "section.label": "Antigravity",
40
40
  "code.title": "代码",
41
41
  "code.inspect": "检查",
42
+ "tv.command": "命令",
43
+ "tv.write": "写入",
44
+ "tv.edit": "编辑",
45
+ "tv.read": "读取",
46
+ "tv.search": "搜索",
47
+ "tv.webSearch": "网页搜索",
48
+ "tv.list": "列表",
49
+ "tv.delete": "删除",
50
+ "tv.tool": "工具",
51
+ "tv.noOutput": "(无输出)",
52
+ "tv.noMatches": "(无匹配)",
53
+ "tv.noChanges": "(无文本变更)",
54
+ "tv.emptyDir": "(空目录)",
55
+ "tv.noDetail": "(无详情)",
56
+ "tv.hint.expandInspect": "点击展开 · 右键检查",
57
+ "tv.hint.collapseInspect": "点击收起 · 右键检查",
58
+ "tv.hint.expand": "点击展开",
59
+ "tv.hint.collapse": "点击收起",
60
+ "tv.diffMore": "… 其余 {count} 行未渲染",
42
61
  "code.copy": "复制",
43
62
  "code.copied": "已复制",
44
63
  "console.title": "Antigravity 管理控制台",
@@ -74,6 +93,7 @@ window.__ModuleLoader__.load({
74
93
  "quota.weekly": "周额度",
75
94
  "quota.refreshed": "额度已刷新",
76
95
  "proxy.button": "代理",
96
+ "pool.winIsolationWarn": "Windows 提示:agy 的登录凭据存在系统级 Credential Manager(单槽位),多账号池无法隔离凭据——轮巡只切换工作目录,实际仍消耗系统登录账号(issue #45)。",
77
97
  "proxy.placeholder": "专属代理 URL(如:http://127.0.0.1:7890;留空则使用全局)",
78
98
  "proxy.save": "保存",
79
99
  "proxy.saved": "代理已保存",
@@ -121,6 +141,9 @@ window.__ModuleLoader__.load({
121
141
  "pool.label": "号池调度:",
122
142
  "pool.sequential": "顺次耗尽",
123
143
  "pool.roundRobin": "轮询均衡",
144
+ "models.label": "模型显示:",
145
+ "models.empty": "模型目录还没有可用模型,发一次对话或点“刷新额度”后再来管理",
146
+ "models.hint": "点击切换显示/隐藏;关闭的模型不在模型选择器中列出,进行中的会话不受影响。",
124
147
  "header.refreshQuota": "刷新额度",
125
148
  "header.refreshing": "刷新中",
126
149
  "console.close": "关闭(Esc)",
@@ -131,6 +154,25 @@ window.__ModuleLoader__.load({
131
154
  "console.title": "Antigravity Management Console",
132
155
  "code.title": "Code",
133
156
  "code.inspect": "Inspect",
157
+ "tv.command": "Command",
158
+ "tv.write": "Write",
159
+ "tv.edit": "Edit",
160
+ "tv.read": "Read",
161
+ "tv.search": "Search",
162
+ "tv.webSearch": "Web Search",
163
+ "tv.list": "List",
164
+ "tv.delete": "Delete",
165
+ "tv.tool": "Tool",
166
+ "tv.noOutput": "(no output)",
167
+ "tv.noMatches": "(no matches)",
168
+ "tv.noChanges": "(no textual changes)",
169
+ "tv.emptyDir": "(empty directory)",
170
+ "tv.noDetail": "(no detail)",
171
+ "tv.hint.expandInspect": "Click to expand · right-click to inspect",
172
+ "tv.hint.collapseInspect": "Click to collapse · right-click to inspect",
173
+ "tv.hint.expand": "Click to expand",
174
+ "tv.hint.collapse": "Click to collapse",
175
+ "tv.diffMore": "… {count} more lines not rendered",
134
176
  "code.copy": "Copy",
135
177
  "code.copied": "Copied",
136
178
  "status.ready": "Ready",
@@ -165,6 +207,7 @@ window.__ModuleLoader__.load({
165
207
  "quota.weekly": "Weekly quota",
166
208
  "quota.refreshed": "Quota refreshed",
167
209
  "proxy.button": "Proxy",
210
+ "pool.winIsolationWarn": "Windows: agy keeps its login in the system-wide Credential Manager (single slot), so the account pool cannot isolate credentials — rotation switches workspaces only, and generation still bills the system login (issue #45).",
168
211
  "proxy.placeholder": "Dedicated proxy URL (for example: http://127.0.0.1:7890; leave blank to use the global proxy)",
169
212
  "proxy.save": "Save",
170
213
  "proxy.saved": "Proxy saved",
@@ -212,6 +255,9 @@ window.__ModuleLoader__.load({
212
255
  "pool.label": "Account-pool scheduling:",
213
256
  "pool.sequential": "Use sequentially",
214
257
  "pool.roundRobin": "Round-robin",
258
+ "models.label": "Model visibility:",
259
+ "models.empty": "No models in the catalog yet — send a message or click \"Refresh quota\", then come back to manage them",
260
+ "models.hint": "Click to show/hide. Hidden models are not listed in the model picker; ongoing sessions are unaffected.",
215
261
  "header.refreshQuota": "Refresh quota",
216
262
  "header.refreshing": "Refreshing...",
217
263
  "console.close": "Close (Esc)",
@@ -222,6 +268,25 @@ window.__ModuleLoader__.load({
222
268
  "console.title": "Console de gerenciamento do Antigravity",
223
269
  "code.title": "Código",
224
270
  "code.inspect": "Inspecionar",
271
+ "tv.command": "Comando",
272
+ "tv.write": "Escrever",
273
+ "tv.edit": "Editar",
274
+ "tv.read": "Ler",
275
+ "tv.search": "Buscar",
276
+ "tv.webSearch": "Busca na web",
277
+ "tv.list": "Listar",
278
+ "tv.delete": "Excluir",
279
+ "tv.tool": "Ferramenta",
280
+ "tv.noOutput": "(sem saída)",
281
+ "tv.noMatches": "(nenhuma correspondência)",
282
+ "tv.noChanges": "(sem alterações de texto)",
283
+ "tv.emptyDir": "(diretório vazio)",
284
+ "tv.noDetail": "(sem detalhes)",
285
+ "tv.hint.expandInspect": "Clique para expandir · clique direito para inspecionar",
286
+ "tv.hint.collapseInspect": "Clique para recolher · clique direito para inspecionar",
287
+ "tv.hint.expand": "Clique para expandir",
288
+ "tv.hint.collapse": "Clique para recolher",
289
+ "tv.diffMore": "… {count} linhas restantes não renderizadas",
225
290
  "code.copy": "Copiar",
226
291
  "code.copied": "Copiado",
227
292
  "status.ready": "Pronto",
@@ -256,6 +321,7 @@ window.__ModuleLoader__.load({
256
321
  "quota.weekly": "Cota semanal",
257
322
  "quota.refreshed": "Cota atualizada",
258
323
  "proxy.button": "Proxy",
324
+ "pool.winIsolationWarn": "Windows: o agy guarda o login no Credential Manager do sistema (um único slot), então o pool de contas não isola credenciais — a rotação troca apenas os diretórios e o consumo continua na conta do sistema (issue #45).",
259
325
  "proxy.placeholder": "URL do proxy exclusivo (por exemplo: http://127.0.0.1:7890; deixe vazio para usar o proxy global)",
260
326
  "proxy.save": "Salvar",
261
327
  "proxy.saved": "Proxy salvo",
@@ -303,6 +369,9 @@ window.__ModuleLoader__.load({
303
369
  "pool.label": "Agendamento do pool de contas:",
304
370
  "pool.sequential": "Usar em sequência",
305
371
  "pool.roundRobin": "Rodízio",
372
+ "models.label": "Visibilidade dos modelos:",
373
+ "models.empty": "Ainda não há modelos no catálogo — envie uma mensagem ou clique em \"Atualizar cota\" e volte para gerenciá-los",
374
+ "models.hint": "Clique para mostrar/ocultar. Modelos ocultos não aparecem no seletor de modelos; as sessões em andamento não são afetadas.",
306
375
  "header.refreshQuota": "Atualizar cota",
307
376
  "header.refreshing": "Atualizando...",
308
377
  "console.close": "Fechar (Esc)",
@@ -313,6 +382,25 @@ window.__ModuleLoader__.load({
313
382
  "console.title": "Consola de administración de Antigravity",
314
383
  "code.title": "Código",
315
384
  "code.inspect": "Inspeccionar",
385
+ "tv.command": "Comando",
386
+ "tv.write": "Escribir",
387
+ "tv.edit": "Editar",
388
+ "tv.read": "Leer",
389
+ "tv.search": "Buscar",
390
+ "tv.webSearch": "Búsqueda web",
391
+ "tv.list": "Listar",
392
+ "tv.delete": "Eliminar",
393
+ "tv.tool": "Herramienta",
394
+ "tv.noOutput": "(sin salida)",
395
+ "tv.noMatches": "(sin coincidencias)",
396
+ "tv.noChanges": "(sin cambios de texto)",
397
+ "tv.emptyDir": "(directorio vacío)",
398
+ "tv.noDetail": "(sin detalles)",
399
+ "tv.hint.expandInspect": "Clic para expandir · clic derecho para inspeccionar",
400
+ "tv.hint.collapseInspect": "Clic para contraer · clic derecho para inspeccionar",
401
+ "tv.hint.expand": "Clic para expandir",
402
+ "tv.hint.collapse": "Clic para contraer",
403
+ "tv.diffMore": "… {count} líneas restantes no renderizadas",
316
404
  "code.copy": "Copiar",
317
405
  "code.copied": "Copiado",
318
406
  "status.ready": "Listo",
@@ -347,6 +435,7 @@ window.__ModuleLoader__.load({
347
435
  "quota.weekly": "Cuota semanal",
348
436
  "quota.refreshed": "Cuota actualizada",
349
437
  "proxy.button": "Proxy",
438
+ "pool.winIsolationWarn": "Windows: agy guarda el inicio de sesión en el Credential Manager del sistema (un único espacio), por lo que el pool de cuentas no puede aislar credenciales — la rotación solo cambia de directorio y el consumo sigue en la cuenta del sistema (issue #45).",
350
439
  "proxy.placeholder": "URL de proxy exclusivo (por ejemplo: http://127.0.0.1:7890; déjelo vacío para usar el proxy global)",
351
440
  "proxy.save": "Guardar",
352
441
  "proxy.saved": "Proxy guardado",
@@ -394,6 +483,9 @@ window.__ModuleLoader__.load({
394
483
  "pool.label": "Programación del pool de cuentas:",
395
484
  "pool.sequential": "Usar en secuencia",
396
485
  "pool.roundRobin": "Turno rotativo",
486
+ "models.label": "Visibilidad de modelos:",
487
+ "models.empty": "Aún no hay modelos en el catálogo: envía un mensaje o haz clic en \"Actualizar cuota\" y vuelve para gestionarlos",
488
+ "models.hint": "Haz clic para mostrar/ocultar. Los modelos ocultos no aparecen en el selector de modelos; las sesiones en curso no se ven afectadas.",
397
489
  "header.refreshQuota": "Actualizar cuota",
398
490
  "header.refreshing": "Actualizando...",
399
491
  "console.close": "Cerrar (Esc)",
@@ -693,7 +785,9 @@ window.__ModuleLoader__.load({
693
785
  "grep_search",
694
786
  "search",
695
787
  "search_file_content",
696
- "grep"
788
+ "grep",
789
+ "search_web",
790
+ "web_search"
697
791
  ]);
698
792
  const LIST_TOOLS = /* @__PURE__ */ new Set(["list_dir", "ls"]);
699
793
  const DELETE_TOOLS = /* @__PURE__ */ new Set([
@@ -821,15 +915,30 @@ window.__ModuleLoader__.load({
821
915
  function cls(...names) {
822
916
  return names.filter((n) => typeof n === "string" && n !== "").join(" ");
823
917
  }
824
- function displayTitle(model) {
918
+ /**
919
+ * Locale lookup with an English fallback: the mirror card renders through
920
+ * host-provided `t` when the registration declares the namespace, but tests
921
+ * and hosts without the seat call it bare. The old literals were mixed
922
+ * 中文 and English as BOTH fallback and primary, so non-Chinese users saw
923
+ * Chinese buttons on mirror cards.
924
+ */
925
+ function tr(t, key, fallback, params) {
926
+ if (typeof t === "function") try {
927
+ const v = t(key, params);
928
+ if (typeof v === "string" && v !== "" && v !== key) return v;
929
+ } catch {}
930
+ if (params !== void 0 && params.count !== void 0 && fallback.includes("{count}")) return fallback.replace("{count}", String(params.count));
931
+ return fallback;
932
+ }
933
+ function displayTitle(model, t) {
825
934
  switch (model.kind) {
826
- case "terminal": return "Command";
827
- case "diff": return model.tool === "write_to_file" || model.tool === "write_file" || model.tool === "create_file" ? "Write" : "Edit";
828
- case "read": return "Read";
829
- case "search": return "Search";
830
- case "list": return "List";
831
- case "delete": return "Delete";
832
- default: return model.tool ? model.tool.replace(/_/g, " ") : "Tool";
935
+ case "terminal": return tr(t, "tv.command", "Command");
936
+ case "diff": return model.tool === "write_to_file" || model.tool === "write_file" || model.tool === "create_file" ? tr(t, "tv.write", "Write") : tr(t, "tv.edit", "Edit");
937
+ case "read": return tr(t, "tv.read", "Read");
938
+ case "search": return model.tool === "search_web" || model.tool === "web_search" ? tr(t, "tv.webSearch", "Web Search") : tr(t, "tv.search", "Search");
939
+ case "list": return tr(t, "tv.list", "List");
940
+ case "delete": return tr(t, "tv.delete", "Delete");
941
+ default: return model.tool ? model.tool.replace(/_/g, " ") : tr(t, "tv.tool", "Tool");
833
942
  }
834
943
  }
835
944
  function displaySummary(model, cwd, home) {
@@ -839,7 +948,7 @@ window.__ModuleLoader__.load({
839
948
  const rawPath = model.path ?? "";
840
949
  return rawPath !== "" ? relativize(rawPath, cwd, home) : model.title;
841
950
  }
842
- if (model.kind === "search") return model.title.replace(/^Search\s*/i, "") || model.title;
951
+ if (model.kind === "search") return model.title.replace(/^(Web search|Search)\s*/i, "") || model.title;
843
952
  if (model.kind === "list") return model.title.replace(/^List\s*(directory\s*)?/i, "") || model.title;
844
953
  return model.title;
845
954
  }
@@ -853,16 +962,18 @@ window.__ModuleLoader__.load({
853
962
  if (oldLines === 0) return `+${newLines}`;
854
963
  return `+${newLines} -${oldLines}`;
855
964
  }
856
- function CopyButton({ text, labels }) {
965
+ function CopyButton({ text, labels, t }) {
857
966
  const [copied, setCopied] = react().useState(false);
858
- const copyLabel = labels?.[0] ?? "复制";
859
- const copiedLabel = labels?.[1] ?? "已复制";
967
+ const copyLabel = labels?.[0] ?? tr(t, "code.copy", "Copy");
968
+ const copiedLabel = labels?.[1] ?? tr(t, "code.copied", "Copied");
860
969
  const onCopy = (e) => {
861
970
  e.stopPropagation();
862
971
  if (typeof navigator !== "undefined" && navigator.clipboard) navigator.clipboard.writeText(text).then(() => {
863
972
  setCopied(true);
864
973
  setTimeout(() => setCopied(false), 1500);
865
- }).catch(() => {});
974
+ }).catch(() => {
975
+ setCopied(false);
976
+ });
866
977
  };
867
978
  return hx("button", {
868
979
  type: "button",
@@ -871,39 +982,51 @@ window.__ModuleLoader__.load({
871
982
  title: copied ? copiedLabel : copyLabel
872
983
  }, copied ? IconCheckOutline14(11) : IconCopyOutline16(11), hx("span", null, copied ? copiedLabel : copyLabel));
873
984
  }
874
- function DiffBody({ model }) {
985
+ /** Per-side render cap: a 5000-line write_to_file used to build 10k DOM rows
986
+ * inside a max-height:280px pane (99% of them invisible). */
987
+ const DIFF_MAX_LINES = 400;
988
+ function DiffBody({ model, t }) {
875
989
  const h = hx;
876
990
  const oldText = model.oldText ?? "";
877
991
  const newText = model.newText ?? "";
878
992
  const rows = [];
993
+ const moreRow = (count, key) => h("div", {
994
+ key,
995
+ className: "agy-tv-diff-line",
996
+ style: { color: "var(--dsw-alias-label-tertiary)" }
997
+ }, tr(t, "tv.diffMore", "… {count} more lines not rendered", { count }));
879
998
  if (oldText !== "") {
880
999
  const lines = oldText.split("\n");
881
- for (let i = 0; i < lines.length; i++) {
1000
+ const shown = Math.min(lines.length, DIFF_MAX_LINES);
1001
+ for (let i = 0; i < shown; i++) {
882
1002
  const line = lines[i];
883
1003
  rows.push(h("div", {
884
1004
  key: "del-" + i,
885
1005
  className: "agy-tv-diff-line agy-tv-diff-del"
886
1006
  }, h("span", { className: "agy-tv-diff-marker" }, "-"), h("span", { style: { flex: 1 } }, line === "" ? " " : line)));
887
1007
  }
1008
+ if (lines.length > shown) rows.push(moreRow(lines.length - shown, "del-more"));
888
1009
  }
889
1010
  if (newText !== "") {
890
1011
  const lines = newText.split("\n");
891
- for (let i = 0; i < lines.length; i++) {
1012
+ const shown = Math.min(lines.length, DIFF_MAX_LINES);
1013
+ for (let i = 0; i < shown; i++) {
892
1014
  const line = lines[i];
893
1015
  rows.push(h("div", {
894
1016
  key: "add-" + i,
895
1017
  className: "agy-tv-diff-line agy-tv-diff-add"
896
1018
  }, h("span", { className: "agy-tv-diff-marker" }, "+"), h("span", { style: { flex: 1 } }, line === "" ? " " : line)));
897
1019
  }
1020
+ if (lines.length > shown) rows.push(moreRow(lines.length - shown, "add-more"));
898
1021
  }
899
1022
  if (rows.length === 0) rows.push(h("div", {
900
1023
  key: "empty",
901
1024
  className: "agy-tv-diff-line",
902
1025
  style: { color: "var(--dsw-alias-label-tertiary)" }
903
- }, "(no textual changes)"));
1026
+ }, tr(t, "tv.noChanges", "(no textual changes)")));
904
1027
  return h("div", { className: "agy-tv-diff-list" }, ...rows);
905
1028
  }
906
- function CardBody({ model, openFile, cwd, home, inspect }) {
1029
+ function CardBody({ model, openFile, cwd, home, inspect, t }) {
907
1030
  const h = hx;
908
1031
  const inspectNode = inspect !== void 0 ? h("div", {
909
1032
  key: "ins",
@@ -912,10 +1035,10 @@ window.__ModuleLoader__.load({
912
1035
  type: "button",
913
1036
  className: "agy-tv-inspect-btn",
914
1037
  onClick: inspect
915
- }, IconInspectOutline12(12), "检查")) : null;
1038
+ }, IconInspectOutline12(12), tr(t, "code.inspect", "Inspect"))) : null;
916
1039
  switch (model.kind) {
917
1040
  case "terminal": {
918
- const rawBody = model.output !== void 0 && model.output !== "" ? trimTrailingBlankLines(model.output) : "(no output)";
1041
+ const rawBody = model.output !== void 0 && model.output !== "" ? trimTrailingBlankLines(model.output) : tr(t, "tv.noOutput", "(no output)");
919
1042
  const copyText = model.output !== void 0 && model.output !== "" ? model.output : model.command ?? "";
920
1043
  return h("div", { className: "agy-tv-card" }, h("div", { className: "agy-tv-card-header" }, h("div", { className: "agy-tv-card-header-left" }, h("span", { className: "agy-tv-prompt-glyph" }, "❯"), ...model.cwd ? [h("span", {
921
1044
  key: "cwd",
@@ -923,7 +1046,10 @@ window.__ModuleLoader__.load({
923
1046
  }, model.cwd)] : [], h("span", {
924
1047
  className: "agy-tv-card-cmd",
925
1048
  title: model.command
926
- }, model.command ?? "")), h(CopyButton, { text: copyText })), h("pre", { className: "agy-tv-card-content" }, clip(rawBody, 16e3)), inspectNode);
1049
+ }, model.command ?? "")), h(CopyButton, {
1050
+ text: copyText,
1051
+ t
1052
+ })), h("pre", { className: "agy-tv-card-content" }, clip(rawBody, 16e3)), inspectNode);
927
1053
  }
928
1054
  case "diff": {
929
1055
  const pathLabel = relativize(model.path ?? "file", cwd, home);
@@ -936,38 +1062,51 @@ window.__ModuleLoader__.load({
936
1062
  model,
937
1063
  openFile,
938
1064
  cwd,
939
- home
1065
+ home,
1066
+ t
940
1067
  }), inspectNode);
941
1068
  }
942
1069
  case "read": {
943
1070
  const pathLabel = relativize(model.path ?? "file", cwd, home);
944
- const rawBody = model.output !== void 0 && model.output !== "" ? trimTrailingBlankLines(model.output) : "(no output)";
945
- return h("div", { className: "agy-tv-card" }, h("div", { className: "agy-tv-card-header" }, h("div", { className: "agy-tv-card-header-left" }, IconBrowseOutline16(13), h("span", { className: "agy-tv-card-path" }, pathLabel)), h(CopyButton, { text: model.output ?? "" })), h("pre", { className: "agy-tv-card-content" }, clip(rawBody, 16e3)), inspectNode);
1071
+ const rawBody = model.output !== void 0 && model.output !== "" ? trimTrailingBlankLines(model.output) : tr(t, "tv.noOutput", "(no output)");
1072
+ return h("div", { className: "agy-tv-card" }, h("div", { className: "agy-tv-card-header" }, h("div", { className: "agy-tv-card-header-left" }, IconBrowseOutline16(13), h("span", { className: "agy-tv-card-path" }, pathLabel)), h(CopyButton, {
1073
+ text: model.output ?? "",
1074
+ t
1075
+ })), h("pre", { className: "agy-tv-card-content" }, clip(rawBody, 16e3)), inspectNode);
946
1076
  }
947
1077
  case "search": {
948
- const rawBody = model.output !== void 0 && model.output !== "" ? trimTrailingBlankLines(model.output) : "(no matches)";
949
- return h("div", { className: "agy-tv-card" }, h("div", { className: "agy-tv-card-header" }, h("div", { className: "agy-tv-card-header-left" }, IconSearchOutline16(13), h("span", { className: "agy-tv-card-path" }, model.title)), h(CopyButton, { text: model.output ?? "" })), h("pre", { className: "agy-tv-card-content" }, clip(rawBody, 16e3)), inspectNode);
1078
+ const rawBody = model.output !== void 0 && model.output !== "" ? trimTrailingBlankLines(model.output) : tr(t, "tv.noMatches", "(no matches)");
1079
+ return h("div", { className: "agy-tv-card" }, h("div", { className: "agy-tv-card-header" }, h("div", { className: "agy-tv-card-header-left" }, IconSearchOutline16(13), h("span", { className: "agy-tv-card-path" }, model.title)), h(CopyButton, {
1080
+ text: model.output ?? "",
1081
+ t
1082
+ })), h("pre", { className: "agy-tv-card-content" }, clip(rawBody, 16e3)), inspectNode);
950
1083
  }
951
1084
  case "list": {
952
- const rawBody = model.output !== void 0 && model.output !== "" ? trimTrailingBlankLines(model.output) : "(empty directory)";
953
- return h("div", { className: "agy-tv-card" }, h("div", { className: "agy-tv-card-header" }, h("div", { className: "agy-tv-card-header-left" }, IconBrowseOutline16(13), h("span", { className: "agy-tv-card-path" }, model.title)), h(CopyButton, { text: model.output ?? "" })), h("pre", { className: "agy-tv-card-content" }, clip(rawBody, 16e3)), inspectNode);
1085
+ const rawBody = model.output !== void 0 && model.output !== "" ? trimTrailingBlankLines(model.output) : tr(t, "tv.emptyDir", "(empty directory)");
1086
+ return h("div", { className: "agy-tv-card" }, h("div", { className: "agy-tv-card-header" }, h("div", { className: "agy-tv-card-header-left" }, IconBrowseOutline16(13), h("span", { className: "agy-tv-card-path" }, model.title)), h(CopyButton, {
1087
+ text: model.output ?? "",
1088
+ t
1089
+ })), h("pre", { className: "agy-tv-card-content" }, clip(rawBody, 16e3)), inspectNode);
954
1090
  }
955
1091
  default: {
956
1092
  const copyText = model.output !== void 0 && model.output !== "" ? model.output : model.raw ?? "";
957
- const rawBody = model.output !== void 0 && model.output !== "" ? trimTrailingBlankLines(model.output) : model.raw !== void 0 && model.raw !== "" ? clip(model.raw, 4e3) : "(no detail)";
958
- return h("div", { className: "agy-tv-card" }, h("div", { className: "agy-tv-card-header" }, h("div", { className: "agy-tv-card-header-left" }, IconCodeOutline16(13), h("span", { className: "agy-tv-card-path" }, model.tool || "Tool")), h(CopyButton, { text: copyText })), h("pre", { className: "agy-tv-card-content" }, clip(rawBody, 16e3)), inspectNode);
1093
+ const rawBody = model.output !== void 0 && model.output !== "" ? trimTrailingBlankLines(model.output) : model.raw !== void 0 && model.raw !== "" ? clip(model.raw, 4e3) : tr(t, "tv.noDetail", "(no detail)");
1094
+ return h("div", { className: "agy-tv-card" }, h("div", { className: "agy-tv-card-header" }, h("div", { className: "agy-tv-card-header-left" }, IconCodeOutline16(13), h("span", { className: "agy-tv-card-path" }, model.tool || "Tool")), h(CopyButton, {
1095
+ text: copyText,
1096
+ t
1097
+ })), h("pre", { className: "agy-tv-card-content" }, clip(rawBody, 16e3)), inspectNode);
959
1098
  }
960
1099
  }
961
1100
  }
962
1101
  function AgyMirrorToolView(props) {
963
1102
  const [open, setOpen] = useToggle(false);
964
1103
  const h = hx;
965
- const { block, cwd, home, openFile, inspect } = props;
1104
+ const { block, cwd, home, openFile, inspect, t } = props;
966
1105
  const settle = isSettled(block);
967
1106
  const err = isError(block);
968
1107
  const model = mirrorCardModel(block, cwd, home);
969
1108
  const state = !settle ? "run" : err ? "err" : "ok";
970
- const title = displayTitle(model);
1109
+ const title = displayTitle(model, t);
971
1110
  const summary = displaySummary(model, cwd, home);
972
1111
  const stat = diffStat(model);
973
1112
  const preview = !open && model.kind !== "diff" ? previewLine(model, cwd, home) : void 0;
@@ -1004,7 +1143,7 @@ window.__ModuleLoader__.load({
1004
1143
  setOpen();
1005
1144
  }
1006
1145
  },
1007
- title: inspect !== void 0 ? open ? "Click to collapse · right-click to inspect" : "Click to expand · right-click to inspect" : open ? "Click to collapse" : "Click to expand",
1146
+ title: inspect !== void 0 ? open ? tr(t, "tv.hint.collapseInspect", "Click to collapse · right-click to inspect") : tr(t, "tv.hint.expandInspect", "Click to expand · right-click to inspect") : open ? tr(t, "tv.hint.collapse", "Click to collapse") : tr(t, "tv.hint.expand", "Click to expand"),
1008
1147
  onContextMenu: inspect !== void 0 ? (e) => {
1009
1148
  e.preventDefault();
1010
1149
  inspect();
@@ -1032,7 +1171,8 @@ window.__ModuleLoader__.load({
1032
1171
  openFile,
1033
1172
  cwd,
1034
1173
  home,
1035
- inspect
1174
+ inspect,
1175
+ t
1036
1176
  }))] : []);
1037
1177
  }
1038
1178
  /** Extract the agy mirror cursor (and tool name) from a run_code program. */
@@ -1104,7 +1244,7 @@ window.__ModuleLoader__.load({
1104
1244
  setOpen();
1105
1245
  }
1106
1246
  },
1107
- title: inspect !== void 0 ? open ? "Click to collapse · right-click to inspect" : "Click to expand · right-click to inspect" : open ? "Click to collapse" : "Click to expand",
1247
+ title: inspect !== void 0 ? open ? tr(t, "tv.hint.collapseInspect", "Click to collapse · right-click to inspect") : tr(t, "tv.hint.expandInspect", "Click to expand · right-click to inspect") : open ? tr(t, "tv.hint.collapse", "Click to collapse") : tr(t, "tv.hint.expand", "Click to expand"),
1108
1248
  onContextMenu: inspect !== void 0 ? (e) => {
1109
1249
  e.preventDefault();
1110
1250
  inspect();
@@ -1147,18 +1287,21 @@ window.__ModuleLoader__.load({
1147
1287
  const block = typed?.block;
1148
1288
  const mirror = parseAgyMirrorFromCode(block !== void 0 ? parsedArgsRaw(block) : "{}");
1149
1289
  if (mirror === null) return AgyCodeToolView(typed);
1150
- return AgyMirrorToolView({ block: {
1151
- ...block,
1152
- call: {
1153
- ...block?.call ?? {},
1154
- name: "agy_tool",
1155
- argsRaw: JSON.stringify({
1156
- run: mirror.run,
1157
- step: mirror.step,
1158
- ...mirror.tool !== void 0 ? { tool: mirror.tool } : {}
1159
- })
1160
- }
1161
- } });
1290
+ return AgyMirrorToolView({
1291
+ block: {
1292
+ ...block,
1293
+ call: {
1294
+ ...block?.call ?? {},
1295
+ name: "agy_tool",
1296
+ argsRaw: JSON.stringify({
1297
+ run: mirror.run,
1298
+ step: mirror.step,
1299
+ ...mirror.tool !== void 0 ? { tool: mirror.tool } : {}
1300
+ })
1301
+ }
1302
+ },
1303
+ t: typed?.t
1304
+ });
1162
1305
  }
1163
1306
  function installAgyToolView(ctx) {
1164
1307
  ctx.slots.inject("tool.call.toolview", () => {
@@ -1166,7 +1309,8 @@ window.__ModuleLoader__.load({
1166
1309
  name: "tool.call.toolview",
1167
1310
  key: "agy_tool",
1168
1311
  id: "agy-tool-view",
1169
- label: "Antigravity tool"
1312
+ label: "Antigravity tool",
1313
+ locale: NS
1170
1314
  }, AgyMirrorToolView);
1171
1315
  const d2 = ctx.slots.register({
1172
1316
  name: "tool.call.toolview",
@@ -1589,12 +1733,25 @@ div[data-agy-mermaid-svg][data-agy-mermaid-error] {
1589
1733
  white-space: pre-wrap;
1590
1734
  padding: 10px 12px;
1591
1735
  }
1736
+ /* Prevent Mermaid's internal error DOM or leaked temporary nodes from escaping into document.body.
1737
+ The generic .error-icon/.error-text classes are scoped to OUR mounts and the
1738
+ leaked-body id shapes only — unqualified, they hid host UI using the same
1739
+ class names. */
1740
+ div[data-agy-mermaid-svg] .error-icon,
1741
+ div[data-agy-mermaid-svg] .error-text,
1742
+ body > [id^="dagy-mmd"] .error-icon,
1743
+ body > [id^="iagy-mmd"] .error-icon {
1744
+ display: none !important;
1745
+ }
1592
1746
  `;
1593
- const CDN_URLS = [
1594
- "/plugins/agy-link/mermaid.min.js",
1595
- "https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js",
1596
- "https://unpkg.com/mermaid@11/dist/mermaid.min.js"
1597
- ];
1747
+ /**
1748
+ * The bundle ships mermaid.min.js and serves it from the plugin route. There
1749
+ * is deliberately NO public-CDN fallback: a floating-version, unsigned
1750
+ * script executing inside the DSH web origin (with reach into every local
1751
+ * HTTP API) is a supply-chain hazard that outweighs offline convenience.
1752
+ * When the local asset is missing the source block simply stays visible.
1753
+ */
1754
+ const CDN_URLS = ["/plugins/agy-link/mermaid.min.js"];
1598
1755
  let loadPromise = null;
1599
1756
  let loadFailedAt = 0;
1600
1757
  const LOAD_RETRY_MS = 6e4;
@@ -1612,9 +1769,13 @@ div[data-agy-mermaid-svg][data-agy-mermaid-error] {
1612
1769
  const s = document.createElement("script");
1613
1770
  s.src = src;
1614
1771
  s.async = true;
1772
+ let settled = false;
1615
1773
  const done = (ok) => {
1774
+ if (settled) return;
1775
+ settled = true;
1616
1776
  s.removeEventListener("load", onLoad);
1617
1777
  s.removeEventListener("error", onError);
1778
+ if (!ok) s.remove();
1618
1779
  resolve(ok);
1619
1780
  };
1620
1781
  const onLoad = () => done(true);
@@ -1637,7 +1798,8 @@ div[data-agy-mermaid-svg][data-agy-mermaid-error] {
1637
1798
  startOnLoad: false,
1638
1799
  securityLevel: "strict",
1639
1800
  theme: "neutral",
1640
- fontFamily: "inherit"
1801
+ fontFamily: "inherit",
1802
+ suppressErrorRendering: true
1641
1803
  });
1642
1804
  } catch {}
1643
1805
  return window.mermaid;
@@ -1719,6 +1881,16 @@ div[data-agy-mermaid-svg][data-agy-mermaid-error] {
1719
1881
  function readSource(block) {
1720
1882
  return ((block.querySelector("[data-code-block-content] code") ?? block.querySelector("[data-code-block-content] pre") ?? block.querySelector("[data-code-block-content]"))?.textContent ?? "").trim();
1721
1883
  }
1884
+ function cleanupTempElements(id) {
1885
+ if (typeof document === "undefined") return;
1886
+ if (id) {
1887
+ document.getElementById("d" + id)?.remove();
1888
+ document.getElementById("i" + id)?.remove();
1889
+ document.getElementById(id)?.remove();
1890
+ }
1891
+ const stray = document.querySelectorAll("body > [id^=\"dagy-mmd\"], body > [id^=\"iagy-mmd\"], body > [id^=\"d_agy-mmd\"], body > [id^=\"i_agy-mmd\"], body > [id^=\"agy-mmd-\"]");
1892
+ for (let i = 0; i < stray.length; i++) stray[i]?.remove();
1893
+ }
1722
1894
  async function renderInto(block, source) {
1723
1895
  block.querySelector("[data-agy-mermaid-svg]")?.remove();
1724
1896
  const mount = document.createElement("div");
@@ -1731,28 +1903,32 @@ div[data-agy-mermaid-svg][data-agy-mermaid-error] {
1731
1903
  mount.textContent = "Mermaid renderer unavailable (offline). Diagram source stays below.";
1732
1904
  return;
1733
1905
  }
1906
+ renderSeq += 1;
1907
+ const id = "agy-mmd-" + renderSeq;
1734
1908
  try {
1735
- renderSeq += 1;
1736
- const id = "agy-mmd-" + renderSeq;
1737
- const { svg } = await api.render(id, source);
1909
+ if (typeof api.parse === "function") await api.parse(source);
1910
+ const { svg } = await api.render(id, source, mount);
1738
1911
  mount.innerHTML = svg;
1739
1912
  block.setAttribute("data-agy-mermaid", "done");
1740
1913
  } catch (e) {
1741
1914
  block.setAttribute("data-agy-mermaid", "error");
1742
1915
  mount.setAttribute("data-agy-mermaid-error", "1");
1743
1916
  mount.textContent = "Mermaid render failed: " + (e instanceof Error ? e.message : String(e));
1917
+ } finally {
1918
+ cleanupTempElements(id);
1744
1919
  }
1745
1920
  }
1746
- let scanTimer = null;
1747
- function scheduleScan() {
1748
- if (scanTimer !== null) return;
1749
- scanTimer = setTimeout(() => {
1750
- scanTimer = null;
1921
+ let scanTimer$1 = null;
1922
+ function scheduleScan$1() {
1923
+ if (scanTimer$1 !== null) return;
1924
+ scanTimer$1 = setTimeout(() => {
1925
+ scanTimer$1 = null;
1751
1926
  scan();
1752
1927
  }, 150);
1753
1928
  }
1754
1929
  function scan() {
1755
1930
  if (typeof document === "undefined") return;
1931
+ cleanupTempElements();
1756
1932
  const blocks = document.querySelectorAll(".md-code-block");
1757
1933
  for (let i = 0; i < blocks.length; i++) {
1758
1934
  const block = blocks[i];
@@ -1767,14 +1943,25 @@ div[data-agy-mermaid-svg][data-agy-mermaid-error] {
1767
1943
  renderInto(block, source);
1768
1944
  }
1769
1945
  }
1946
+ let activeObserver$1 = null;
1947
+ let domContentLoadedHandler$1 = null;
1948
+ /**
1949
+ * Install the mermaid overlay. Returns a disposer that disconnects the
1950
+ * MutationObserver and cancels pending setup — wrap it in ctx.effect so a
1951
+ * plugin reload cannot stack observers over the same document.
1952
+ */
1770
1953
  function installMermaidRendering() {
1771
- if (typeof window === "undefined" || typeof document === "undefined") return;
1954
+ if (typeof window === "undefined" || typeof document === "undefined") return () => {};
1955
+ disposeMermaidRendering();
1772
1956
  injectStyles$1();
1957
+ cleanupTempElements();
1773
1958
  const observer = new MutationObserver(() => {
1774
- scheduleScan();
1959
+ scheduleScan$1();
1775
1960
  });
1961
+ activeObserver$1 = observer;
1776
1962
  const setup = () => {
1777
1963
  if (document.body) {
1964
+ cleanupTempElements();
1778
1965
  observer.observe(document.body, {
1779
1966
  childList: true,
1780
1967
  subtree: true
@@ -1782,8 +1969,26 @@ div[data-agy-mermaid-svg][data-agy-mermaid-error] {
1782
1969
  scan();
1783
1970
  }
1784
1971
  };
1785
- if (document.readyState === "loading") window.addEventListener("DOMContentLoaded", setup, { once: true });
1786
- else setup();
1972
+ if (document.readyState === "loading") {
1973
+ domContentLoadedHandler$1 = setup;
1974
+ window.addEventListener("DOMContentLoaded", setup, { once: true });
1975
+ } else setup();
1976
+ return disposeMermaidRendering;
1977
+ }
1978
+ /** Disconnect the observer and cancel pending setup work. */
1979
+ function disposeMermaidRendering() {
1980
+ if (activeObserver$1 !== null) {
1981
+ activeObserver$1.disconnect();
1982
+ activeObserver$1 = null;
1983
+ }
1984
+ if (domContentLoadedHandler$1 !== null) {
1985
+ window.removeEventListener("DOMContentLoaded", domContentLoadedHandler$1);
1986
+ domContentLoadedHandler$1 = null;
1987
+ }
1988
+ if (scanTimer$1 !== null) {
1989
+ clearTimeout(scanTimer$1);
1990
+ scanTimer$1 = null;
1991
+ }
1787
1992
  }
1788
1993
  //#endregion
1789
1994
  //#region src/client/reasoning.ts
@@ -1845,9 +2050,16 @@ div[data-variant="think"] [class*="thinkBody"] {
1845
2050
  * Try to expand a thinking row once when its body contains real prose
1846
2051
  * (not a bare [agy thinking turn · N tokens] chip). Uses the host's own
1847
2052
  * disclosure control when present — never mutates thinkBody children.
2053
+ *
2054
+ * A row the user manually COLLAPSED is recorded in a WeakSet keyed by the
2055
+ * row element; React keeps the same element identity across its own
2056
+ * re-renders, so the scanner stops re-expanding it (the old data attribute
2057
+ * survived only until React rebuilt the node).
1848
2058
  */
2059
+ const userCollapsed = typeof WeakSet !== "undefined" ? /* @__PURE__ */ new WeakSet() : null;
1849
2060
  function tryExpandThinkRow(root) {
1850
2061
  if (root.dataset.agyExpanded === "true") return;
2062
+ if (userCollapsed?.has(root) === true) return;
1851
2063
  const body = root.querySelector("[class*=\"thinkBody\"]");
1852
2064
  if (!body) return;
1853
2065
  const text = (body.textContent ?? "").trim();
@@ -1859,10 +2071,15 @@ div[data-variant="think"] [class*="thinkBody"] {
1859
2071
  return;
1860
2072
  }
1861
2073
  const toggle = root.querySelector("[data-disclosure-row]") ?? root.querySelector("[class*=\"disclosure\"]") ?? root.querySelector("button");
1862
- if (toggle) try {
1863
- toggle.click();
1864
- root.dataset.agyExpanded = "true";
1865
- } catch {}
2074
+ if (toggle) {
2075
+ userCollapsed?.add(root);
2076
+ try {
2077
+ toggle.click();
2078
+ root.dataset.agyExpanded = "true";
2079
+ } catch {
2080
+ userCollapsed?.delete(root);
2081
+ }
2082
+ }
1866
2083
  }
1867
2084
  function scanThinkingRows() {
1868
2085
  if (typeof document === "undefined") return;
@@ -1872,12 +2089,29 @@ div[data-variant="think"] [class*="thinkBody"] {
1872
2089
  if (row) tryExpandThinkRow(row);
1873
2090
  }
1874
2091
  }
2092
+ let scanTimer = null;
2093
+ function scheduleScan() {
2094
+ if (scanTimer !== null) return;
2095
+ scanTimer = setTimeout(() => {
2096
+ scanTimer = null;
2097
+ scanThinkingRows();
2098
+ }, 200);
2099
+ }
2100
+ let activeObserver = null;
2101
+ let domContentLoadedHandler = null;
2102
+ /**
2103
+ * Install the auto-expand reasoning overlay. Returns a disposer that
2104
+ * disconnects the MutationObserver — wrap it in ctx.effect so a plugin
2105
+ * reload cannot stack observers.
2106
+ */
1875
2107
  function installAutoExpandReasoning() {
1876
- if (typeof window === "undefined" || typeof document === "undefined") return;
2108
+ if (typeof window === "undefined" || typeof document === "undefined") return () => {};
2109
+ disposeAutoExpandReasoning();
1877
2110
  injectStyles();
1878
2111
  const observer = new MutationObserver(() => {
1879
- scanThinkingRows();
2112
+ scheduleScan();
1880
2113
  });
2114
+ activeObserver = observer;
1881
2115
  const setupObserver = () => {
1882
2116
  if (document.body) {
1883
2117
  observer.observe(document.body, {
@@ -1887,8 +2121,26 @@ div[data-variant="think"] [class*="thinkBody"] {
1887
2121
  scanThinkingRows();
1888
2122
  }
1889
2123
  };
1890
- if (document.readyState === "loading") window.addEventListener("DOMContentLoaded", setupObserver, { once: true });
1891
- else setupObserver();
2124
+ if (document.readyState === "loading") {
2125
+ domContentLoadedHandler = setupObserver;
2126
+ window.addEventListener("DOMContentLoaded", setupObserver, { once: true });
2127
+ } else setupObserver();
2128
+ return disposeAutoExpandReasoning;
2129
+ }
2130
+ /** Disconnect the observer and cancel pending work. */
2131
+ function disposeAutoExpandReasoning() {
2132
+ if (activeObserver !== null) {
2133
+ activeObserver.disconnect();
2134
+ activeObserver = null;
2135
+ }
2136
+ if (domContentLoadedHandler !== null) {
2137
+ window.removeEventListener("DOMContentLoaded", domContentLoadedHandler);
2138
+ domContentLoadedHandler = null;
2139
+ }
2140
+ if (scanTimer !== null) {
2141
+ clearTimeout(scanTimer);
2142
+ scanTimer = null;
2143
+ }
1892
2144
  }
1893
2145
  //#endregion
1894
2146
  //#region src/client/index.ts
@@ -1906,7 +2158,9 @@ div[data-variant="think"] [class*="thinkBody"] {
1906
2158
  let statusCache = null;
1907
2159
  async function getStatus() {
1908
2160
  try {
1909
- const res = await win.fetch?.(base + "/plugins/agy-link/status");
2161
+ const init = {};
2162
+ if (typeof AbortSignal !== "undefined" && typeof AbortSignal.timeout === "function") init.signal = AbortSignal.timeout(5e3);
2163
+ const res = await win.fetch?.(base + "/plugins/agy-link/status", init);
1910
2164
  if (!res || !res.ok) return null;
1911
2165
  const payload = await res.json();
1912
2166
  statusCache = payload;
@@ -1924,7 +2178,12 @@ div[data-variant="think"] [class*="thinkBody"] {
1924
2178
  });
1925
2179
  if (!res) return null;
1926
2180
  try {
1927
- return await res.json();
2181
+ const payload = await res.json();
2182
+ if (!res.ok && payload !== null && typeof payload === "object") return {
2183
+ ok: false,
2184
+ ...payload
2185
+ };
2186
+ return payload;
1928
2187
  } catch {
1929
2188
  return { ok: res.ok };
1930
2189
  }
@@ -1932,6 +2191,58 @@ div[data-variant="think"] [class*="thinkBody"] {
1932
2191
  return null;
1933
2192
  }
1934
2193
  }
2194
+ const statusListeners = /* @__PURE__ */ new Set();
2195
+ let statusPollTimer = null;
2196
+ let statusFetchSeq = 0;
2197
+ function notifyStatus(st) {
2198
+ statusCache = st;
2199
+ for (const fn of [...statusListeners]) try {
2200
+ fn(st);
2201
+ } catch {}
2202
+ }
2203
+ async function statusPollTick() {
2204
+ const seq = ++statusFetchSeq;
2205
+ const st = await getStatus();
2206
+ if (st !== null && seq === statusFetchSeq) notifyStatus(st);
2207
+ }
2208
+ function ensureStatusPolling() {
2209
+ if (statusPollTimer !== null) return;
2210
+ statusPollTick();
2211
+ statusPollTimer = setInterval(() => {
2212
+ statusPollTick();
2213
+ }, 3e3);
2214
+ }
2215
+ function stopStatusPollingIfIdle() {
2216
+ if (statusListeners.size === 0 && statusPollTimer !== null) {
2217
+ clearInterval(statusPollTimer);
2218
+ statusPollTimer = null;
2219
+ }
2220
+ }
2221
+ /**
2222
+ * Subscribe a component to the shared status poll. Returns the latest
2223
+ * snapshot plus a manual refresh that keeps the last good snapshot on a
2224
+ * transient failure (a failed refresh used to flip the whole panel back to
2225
+ * its loading state).
2226
+ */
2227
+ function useAgyStatus() {
2228
+ const [status, setStatus] = useState(statusCache);
2229
+ useEffect(() => {
2230
+ statusListeners.add(setStatus);
2231
+ ensureStatusPolling();
2232
+ return () => {
2233
+ statusListeners.delete(setStatus);
2234
+ stopStatusPollingIfIdle();
2235
+ };
2236
+ }, []);
2237
+ const refresh = async () => {
2238
+ const st = await getStatus();
2239
+ if (st !== null) notifyStatus(st);
2240
+ };
2241
+ return {
2242
+ status,
2243
+ refresh
2244
+ };
2245
+ }
1935
2246
  /** Global modal state for opening Antigravity console dialog from footer shortcut or header status */
1936
2247
  const agyModalStore = {
1937
2248
  open: false,
@@ -2642,7 +2953,7 @@ body.dark,
2642
2953
  };
2643
2954
  const AgySettingsSection = (props) => {
2644
2955
  const t = useAgyTranslation();
2645
- const [status, setStatus] = useState(statusCache);
2956
+ const { status, refresh } = useAgyStatus();
2646
2957
  const [aliasInput, setAliasInput] = useState("");
2647
2958
  const [proxyInputs, setProxyInputs] = useState({});
2648
2959
  const [editingProxyId, setEditingProxyId] = useState(null);
@@ -2651,24 +2962,10 @@ body.dark,
2651
2962
  const [loadingAction, setLoadingAction] = useState(null);
2652
2963
  const [toast, setToast] = useState(null);
2653
2964
  const [expandedModels, setExpandedModels] = useState({});
2965
+ const [, setTimeTick] = useState(0);
2654
2966
  useEffect(() => {
2655
- let alive = true;
2656
- let lastJson = "";
2657
- const tick = async () => {
2658
- const st = await getStatus();
2659
- if (!alive || !st) return;
2660
- const json = JSON.stringify(st);
2661
- if (json !== lastJson) {
2662
- lastJson = json;
2663
- setStatus(st);
2664
- }
2665
- };
2666
- tick();
2667
- const timer = setInterval(tick, 3e3);
2668
- return () => {
2669
- alive = false;
2670
- clearInterval(timer);
2671
- };
2967
+ const timer = setInterval(() => setTimeTick((n) => n + 1), 3e4);
2968
+ return () => clearInterval(timer);
2672
2969
  }, []);
2673
2970
  const flowStartedRef = useRef(false);
2674
2971
  const safeMessage = (code) => {
@@ -2694,18 +2991,24 @@ body.dark,
2694
2991
  status?.poolAuth?.phase,
2695
2992
  status?.poolAuth?.code
2696
2993
  ]);
2994
+ const toastTimerRef = useRef(null);
2995
+ useEffect(() => () => {
2996
+ if (toastTimerRef.current !== null) clearTimeout(toastTimerRef.current);
2997
+ }, []);
2697
2998
  const showToast = (text, type = "info") => {
2698
2999
  setToast({
2699
3000
  text,
2700
3001
  type,
2701
3002
  id: Date.now()
2702
3003
  });
2703
- setTimeout(() => {
2704
- setToast((prev) => prev?.text === text ? null : prev);
3004
+ if (toastTimerRef.current !== null) clearTimeout(toastTimerRef.current);
3005
+ toastTimerRef.current = setTimeout(() => {
3006
+ toastTimerRef.current = null;
3007
+ setToast((prev) => prev !== null && prev.text === text ? null : prev);
2705
3008
  }, 6e3);
2706
3009
  };
2707
- const refresh = async () => {
2708
- setStatus(await getStatus());
3010
+ const actionFailed = () => {
3011
+ showToast(t("api.genericError"), "error");
2709
3012
  };
2710
3013
  const handleBeginAddAccount = async () => {
2711
3014
  setLoadingAction("pool:beginAdd");
@@ -2743,30 +3046,62 @@ body.dark,
2743
3046
  };
2744
3047
  const setCfg = async (key, value) => {
2745
3048
  setLoadingAction(`config:${key}`);
2746
- await postJson("/plugins/agy-link/config", {
3049
+ const res = await postJson("/plugins/agy-link/config", {
2747
3050
  key,
2748
3051
  value
2749
3052
  });
3053
+ if (res === null || res?.ok === false) {
3054
+ setLoadingAction(null);
3055
+ actionFailed();
3056
+ return;
3057
+ }
2750
3058
  await refresh();
2751
3059
  setLoadingAction(null);
2752
3060
  };
3061
+ const hiddenPendingRef = useRef(null);
3062
+ const toggleModelHidden = async (id, hide) => {
3063
+ const next = new Set(hiddenPendingRef.current ?? status?.hiddenModels ?? []);
3064
+ if (hide) next.add(id);
3065
+ else next.delete(id);
3066
+ hiddenPendingRef.current = next;
3067
+ try {
3068
+ await setCfg("hiddenModels", Array.from(next));
3069
+ } finally {
3070
+ if (hiddenPendingRef.current === next) hiddenPendingRef.current = null;
3071
+ }
3072
+ };
2753
3073
  const setPrimary = async (id) => {
2754
3074
  setLoadingAction(`primary:${id}`);
2755
- await postJson("/plugins/agy-link/pool/primary", { id });
3075
+ const res = await postJson("/plugins/agy-link/pool/primary", { id });
3076
+ if (res === null || res?.ok === false) {
3077
+ setLoadingAction(null);
3078
+ actionFailed();
3079
+ return;
3080
+ }
2756
3081
  await refresh();
2757
3082
  setLoadingAction(null);
2758
3083
  showToast(t("account.setPrimary"), "success");
2759
3084
  };
2760
3085
  const removeAccount = async (id, alias) => {
2761
3086
  setLoadingAction(`remove:${id}`);
2762
- await postJson("/plugins/agy-link/pool/remove", { id });
3087
+ const res = await postJson("/plugins/agy-link/pool/remove", { id });
3088
+ if (res === null || res?.ok === false) {
3089
+ setLoadingAction(null);
3090
+ actionFailed();
3091
+ return;
3092
+ }
2763
3093
  await refresh();
2764
3094
  setLoadingAction(null);
2765
3095
  showToast(t("account.removed", { alias }), "info");
2766
3096
  };
2767
3097
  const refreshQuota = async (id) => {
2768
3098
  setLoadingAction(id ? `refresh:${id}` : "refresh:all");
2769
- await postJson("/plugins/agy-link/pool/refresh-quota", { id });
3099
+ const res = await postJson("/plugins/agy-link/pool/refresh-quota", { id });
3100
+ if (res === null || res?.ok === false) {
3101
+ setLoadingAction(null);
3102
+ actionFailed();
3103
+ return;
3104
+ }
2770
3105
  await refresh();
2771
3106
  setLoadingAction(null);
2772
3107
  showToast(t("quota.refreshed"), "success");
@@ -2774,10 +3109,15 @@ body.dark,
2774
3109
  const saveProxy = async (id) => {
2775
3110
  setLoadingAction(`proxy:${id}`);
2776
3111
  const proxyUrl = proxyInputs[id];
2777
- await postJson("/plugins/agy-link/pool/proxy", {
3112
+ const res = await postJson("/plugins/agy-link/pool/proxy", {
2778
3113
  id,
2779
3114
  proxyUrl
2780
3115
  });
3116
+ if (res === null || res?.ok === false) {
3117
+ setLoadingAction(null);
3118
+ actionFailed();
3119
+ return;
3120
+ }
2781
3121
  setEditingProxyId(null);
2782
3122
  await refresh();
2783
3123
  setLoadingAction(null);
@@ -2785,13 +3125,23 @@ body.dark,
2785
3125
  };
2786
3126
  const setMode = async (mode) => {
2787
3127
  setLoadingAction(`mode:${mode}`);
2788
- await postJson("/plugins/agy-link/pool/mode", { mode });
3128
+ const res = await postJson("/plugins/agy-link/pool/mode", { mode });
3129
+ if (res === null || res?.ok === false) {
3130
+ setLoadingAction(null);
3131
+ actionFailed();
3132
+ return;
3133
+ }
2789
3134
  await refresh();
2790
3135
  setLoadingAction(null);
2791
3136
  };
2792
3137
  const clearCooldown = async (id) => {
2793
3138
  setLoadingAction(id ? `clearCooldown:${id}` : "clearCooldown:all");
2794
- await postJson("/plugins/agy-link/pool/clear-cooldown", { id });
3139
+ const res = await postJson("/plugins/agy-link/pool/clear-cooldown", { id });
3140
+ if (res === null || res?.ok === false) {
3141
+ setLoadingAction(null);
3142
+ actionFailed();
3143
+ return;
3144
+ }
2795
3145
  await refresh();
2796
3146
  setLoadingAction(null);
2797
3147
  showToast(t("account.clearCooldown"), "success");
@@ -2804,6 +3154,8 @@ body.dark,
2804
3154
  };
2805
3155
  const authPhase = status?.auth?.phase ?? "unknown";
2806
3156
  const pool = status?.pool;
3157
+ const catalogModels = status?.catalogModels ?? [];
3158
+ const hiddenModelSet = new Set(status?.hiddenModels ?? []);
2807
3159
  const accounts = pool?.accounts ?? [];
2808
3160
  const isAuthed = authPhase === "ok" || accounts.length > 0;
2809
3161
  const isBusy = loadingAction !== null;
@@ -3313,7 +3665,21 @@ body.dark,
3313
3665
  },
3314
3666
  disabled: isBusy,
3315
3667
  onClick: () => void refreshQuota()
3316
- }, loadingAction === "refresh:all" ? [renderSpinner(), t("header.refreshing")] : [uiIcon("refresh", 12, "var(--agy-text-btn)"), ` ${t("header.refreshQuota")}`]))), renderToastBanner(), addAccountSection, renderedAccountCards, h("div", { style: {
3668
+ }, loadingAction === "refresh:all" ? [renderSpinner(), t("header.refreshing")] : [uiIcon("refresh", 12, "var(--agy-text-btn)"), ` ${t("header.refreshQuota")}`]))), renderToastBanner(), status?.credentialIsolationLimited === true ? h("div", {
3669
+ key: "win-isolation",
3670
+ style: {
3671
+ display: "flex",
3672
+ alignItems: "flex-start",
3673
+ gap: "6px",
3674
+ padding: "8px 10px",
3675
+ marginBottom: "8px",
3676
+ background: "var(--agy-amber-soft, rgba(245, 158, 11, 0.12))",
3677
+ border: "1px solid rgba(245, 158, 11, 0.45)",
3678
+ borderRadius: "7px",
3679
+ fontSize: "11.5px",
3680
+ color: "var(--agy-text-primary)"
3681
+ }
3682
+ }, h("span", { style: { flexShrink: 0 } }, "⚠️"), h("span", null, t("pool.winIsolationWarn"))) : null, addAccountSection, renderedAccountCards, h("div", { style: {
3317
3683
  marginTop: "16px",
3318
3684
  paddingTop: "12px",
3319
3685
  borderTop: "1px solid var(--agy-border-divider)"
@@ -3386,7 +3752,33 @@ body.dark,
3386
3752
  type: "button",
3387
3753
  style: pool?.mode === "round-robin" ? S.segBtnActive : S.segBtn,
3388
3754
  onClick: () => void setMode("round-robin")
3389
- }, t("pool.roundRobin")))))));
3755
+ }, t("pool.roundRobin")))), h("div", { style: {
3756
+ display: "flex",
3757
+ flexDirection: "column",
3758
+ gap: "6px"
3759
+ } }, h("span", { style: {
3760
+ color: "var(--agy-text-primary)",
3761
+ fontWeight: 600,
3762
+ fontSize: "12.5px"
3763
+ } }, t("models.label")), catalogModels.length === 0 ? h("span", { style: S.muted }, t("models.empty")) : h("div", { style: {
3764
+ display: "flex",
3765
+ flexWrap: "wrap",
3766
+ gap: "6px"
3767
+ } }, catalogModels.map((m) => {
3768
+ const isHidden = hiddenModelSet.has(m.id);
3769
+ return h("button", {
3770
+ type: "button",
3771
+ key: m.id,
3772
+ title: m.id,
3773
+ disabled: loadingAction === "config:hiddenModels",
3774
+ style: isHidden ? {
3775
+ ...S.segBtn,
3776
+ opacity: .45,
3777
+ textDecoration: "line-through"
3778
+ } : S.segBtnActive,
3779
+ onClick: () => void toggleModelHidden(m.id, !isHidden)
3780
+ }, m.name);
3781
+ })), h("span", { style: S.muted }, t("models.hint"))))));
3390
3782
  };
3391
3783
  /** Modal dialog container rendered into document.body */
3392
3784
  const AgyModalDialog = () => {
@@ -3466,37 +3858,24 @@ body.dark,
3466
3858
  /** Session Header badge in chat toolbar */
3467
3859
  const AgySessionStatus = () => {
3468
3860
  const t = useAgyTranslation();
3469
- const [status, setStatus] = useState(statusCache);
3861
+ const { status } = useAgyStatus();
3862
+ const [, setTimeTick] = useState(0);
3470
3863
  useEffect(() => {
3471
- let alive = true;
3472
- let lastJson = "";
3473
- const tick = async () => {
3474
- const st = await getStatus();
3475
- if (!alive || !st) return;
3476
- const json = JSON.stringify(st);
3477
- if (json !== lastJson) {
3478
- lastJson = json;
3479
- setStatus(st);
3480
- }
3481
- };
3482
- tick();
3483
- const timer = setInterval(tick, 3e3);
3484
- return () => {
3485
- alive = false;
3486
- clearInterval(timer);
3487
- };
3864
+ const timer = setInterval(() => setTimeTick((n) => n + 1), 3e4);
3865
+ return () => clearInterval(timer);
3488
3866
  }, []);
3489
3867
  const pool = status?.pool;
3490
3868
  const accounts = pool?.accounts ?? [];
3491
3869
  const rawActiveId = pool?.activeAccountId ?? pool?.lastActiveAccountId ?? pool?.activeAccountIds?.google ?? pool?.primaryAccountId;
3492
3870
  const activeAcc = accounts.find((a) => a.id === rawActiveId && a.enabled && !a.authRequired) ?? accounts.find((a) => a.enabled && !a.authRequired) ?? accounts[0];
3493
- const activeHasCooldown = activeAcc ? Object.entries(activeAcc.cooldowns).some(([, cd]) => cd && cd.cooldownUntil > Date.now()) : false;
3494
- const allInCooldown = accounts.length > 0 && accounts.every((a) => !a.enabled || a.authRequired || Object.entries(a.cooldowns).some(([, cd]) => cd && cd.cooldownUntil > Date.now()));
3871
+ const activeHasCooldown = activeAcc ? Object.entries(activeAcc.cooldowns ?? {}).some(([, cd]) => cd && cd.cooldownUntil > Date.now()) : false;
3872
+ const allInCooldown = accounts.length > 0 && accounts.every((a) => !a.enabled || a.authRequired || Object.entries(a.cooldowns ?? {}).some(([, cd]) => cd && cd.cooldownUntil > Date.now()));
3495
3873
  const isAuthed = status?.auth?.phase === "ok" || accounts.length > 0;
3496
3874
  const color = status === null ? "#64748b" : status.dormantReason ? "#f59e0b" : allInCooldown ? "#f59e0b" : activeHasCooldown ? "#f59e0b" : isAuthed ? "#10b981" : "#f59e0b";
3497
3875
  const legacyDefault = activeAcc && activeAcc.defaultAlias === void 0 && activeAcc.systemHome && activeAcc.alias === "主账号 (系统登录)";
3498
3876
  const displayAlias = activeAcc ? activeAcc.defaultAlias || legacyDefault ? activeAcc.systemHome ? t("account.defaultAlias") : t("account.aliasDefault", { number: accounts.indexOf(activeAcc) + 1 }) : activeAcc.alias : "";
3499
- const activeDetail = activeAcc ? ` · ${t("status.inUse")}: ${displayAlias}${activeAcc.email ? ` (${activeAcc.email})` : ""}` : "";
3877
+ const activeEligible = activeAcc !== void 0 && activeAcc.enabled && !activeAcc.authRequired;
3878
+ const activeDetail = activeAcc !== void 0 && activeEligible ? ` · ${t("status.inUse")}: ${displayAlias}${activeAcc.email ? ` (${activeAcc.email})` : ""}` : "";
3500
3879
  const badgeTitle = `${t("header.badgeTitle", { count: accounts.length })}${activeDetail}`;
3501
3880
  const buttonLabel = displayAlias ? `AGY · ${displayAlias}` : `AGY (${accounts.length})`;
3502
3881
  const badge = h("button", {
@@ -3547,8 +3926,8 @@ body.dark,
3547
3926
  }, AgySettingsSection);
3548
3927
  });
3549
3928
  installAgyToolView(ctx);
3550
- installAutoExpandReasoning();
3551
- installMermaidRendering();
3929
+ ctx.effect(() => installAutoExpandReasoning());
3930
+ ctx.effect(() => installMermaidRendering());
3552
3931
  }
3553
3932
  //#endregion
3554
3933
  exports.apply = apply;