dsh-agy-link 0.4.26 → 0.4.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/client.js CHANGED
@@ -31,6 +31,806 @@ window.__ModuleLoader__.load({
31
31
  alert: "M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0zM12 9v4M12 17h.01"
32
32
  };
33
33
  //#endregion
34
+ //#region src/client/toolview.ts
35
+ function react() {
36
+ const mod = require("react");
37
+ if (mod === void 0 || mod === null) throw new Error("dsh-agy-link toolview requires react at render time (browser host)");
38
+ return mod;
39
+ }
40
+ /**
41
+ * Wrap a raw useState setter into a toggle: every call flips the previous
42
+ * value. The raw setter itself must NEVER be called with no arguments —
43
+ * `setState(undefined)` pins the state to a falsy value, so a second click
44
+ * could never expand a collapsed card again (the regression this fixes).
45
+ * Exported separately so the toggling contract is unit-testable in Node.
46
+ */
47
+ function makeToggle(setValue) {
48
+ return () => setValue((prev) => !prev);
49
+ }
50
+ function useToggle(initial = false) {
51
+ const [value, setValue] = react().useState(initial);
52
+ return [value, makeToggle(setValue)];
53
+ }
54
+ /** React.createElement bound helper (call inside render functions). */
55
+ function hx(type, props, ...children) {
56
+ return react().createElement(type, props, ...children);
57
+ }
58
+ function parsedArgsRaw(block) {
59
+ const settled = block;
60
+ if ("call" in settled && settled.call !== null && typeof settled.call.argsRaw === "string") return settled.call.argsRaw;
61
+ const running = block;
62
+ if (typeof running.argsRaw === "string") return running.argsRaw;
63
+ return "";
64
+ }
65
+ function parseArgs(block) {
66
+ const raw = parsedArgsRaw(block);
67
+ if (raw === "") return null;
68
+ try {
69
+ const v = JSON.parse(raw);
70
+ return typeof v === "object" && v !== null && !Array.isArray(v) ? v : null;
71
+ } catch {
72
+ return null;
73
+ }
74
+ }
75
+ /** agy tool name + PascalCase or camelCase input object from the mirror args. */
76
+ function mirrorInfo(block) {
77
+ const a = parseArgs(block);
78
+ if (a === null) return null;
79
+ const tool = typeof a.tool === "string" ? a.tool : "";
80
+ if (tool === "") return null;
81
+ let input = {};
82
+ const raw = a.input;
83
+ if (typeof raw === "object" && raw !== null && !Array.isArray(raw)) input = raw;
84
+ else if (typeof raw === "string") try {
85
+ const p = JSON.parse(raw);
86
+ if (typeof p === "object" && p !== null && !Array.isArray(p)) input = p;
87
+ else input = { value: raw };
88
+ } catch {
89
+ input = { value: raw };
90
+ }
91
+ return {
92
+ tool,
93
+ input
94
+ };
95
+ }
96
+ function pick(input, ...keys) {
97
+ for (const k of keys) {
98
+ const v = input[k];
99
+ if (typeof v === "string" && v !== "") return v;
100
+ }
101
+ }
102
+ function num(input, ...keys) {
103
+ for (const k of keys) {
104
+ const v = input[k];
105
+ if (typeof v === "number" && Number.isFinite(v)) return v;
106
+ }
107
+ }
108
+ function resultText(block) {
109
+ const settled = block;
110
+ if (!Array.isArray(settled.content)) return "";
111
+ const parts = [];
112
+ for (const b of settled.content) if (b && typeof b === "object" && b.type === "text" && typeof b.text === "string") parts.push(b.text);
113
+ return parts.join("\n");
114
+ }
115
+ function isSettled(block) {
116
+ return block.kind === "tool-result" || "content" in block;
117
+ }
118
+ function isError(block) {
119
+ const settled = block;
120
+ return settled.isError === true || settled.error !== void 0 && settled.error !== null;
121
+ }
122
+ const TERMINAL_TOOLS = /* @__PURE__ */ new Set([
123
+ "run_command",
124
+ "bash",
125
+ "execute_command"
126
+ ]);
127
+ const DIFF_TOOLS = /* @__PURE__ */ new Set([
128
+ "replace_file_content",
129
+ "edit_file",
130
+ "replace_in_file",
131
+ "edit",
132
+ "write_to_file",
133
+ "write_file",
134
+ "create_file"
135
+ ]);
136
+ const READ_TOOLS = /* @__PURE__ */ new Set([
137
+ "read_file",
138
+ "view_file",
139
+ "read",
140
+ "open_file"
141
+ ]);
142
+ const SEARCH_TOOLS = /* @__PURE__ */ new Set([
143
+ "find_by_name",
144
+ "glob",
145
+ "search_files",
146
+ "grep_search",
147
+ "search",
148
+ "search_file_content",
149
+ "grep"
150
+ ]);
151
+ const LIST_TOOLS = /* @__PURE__ */ new Set(["list_dir", "ls"]);
152
+ const DELETE_TOOLS = /* @__PURE__ */ new Set([
153
+ "delete_file",
154
+ "remove_file",
155
+ "rm"
156
+ ]);
157
+ /** Build the display model for one agy_tool block (pending or settled). */
158
+ function mirrorCardModel(block, cwd, home) {
159
+ const info = mirrorInfo(block);
160
+ const out = resultText(block);
161
+ if (info === null) return {
162
+ kind: "generic",
163
+ title: "agy_tool",
164
+ output: out,
165
+ raw: parsedArgsRaw(block)
166
+ };
167
+ const { tool, input } = info;
168
+ const desc = pick(input, "Description", "description", "toolAction", "toolSummary", "Instruction", "instruction");
169
+ if (TERMINAL_TOOLS.has(tool)) {
170
+ const command = pick(input, "CommandLine", "command_line", "command", "cmd", "Cmd") ?? JSON.stringify(input);
171
+ const cwdVal = pick(input, "Cwd", "cwd", "WorkingDirectory", "working_directory") ?? cwd;
172
+ return {
173
+ kind: "terminal",
174
+ tool,
175
+ title: desc !== void 0 ? desc + " · " + command : command,
176
+ command,
177
+ ...cwdVal !== void 0 ? { cwd: cwdVal } : {},
178
+ output: out
179
+ };
180
+ }
181
+ if (DIFF_TOOLS.has(tool)) {
182
+ const rawPath = pick(input, "TargetFile", "target_file", "path", "file_path", "Path", "FilePath", "AbsolutePath", "targetFile", "filename", "FileName") ?? "file";
183
+ const path = relativize(rawPath, cwd, home);
184
+ const oldText = pick(input, "TargetContent", "target_content", "old_string", "oldText", "OldString", "OldText", "targetContent") ?? null;
185
+ const newText = pick(input, "ReplacementContent", "replacement_content", "new_string", "newText", "content", "NewString", "NewText", "Content", "replacementContent", "CodeContent", "code_content", "contents", "FileContents", "codeContent") ?? "";
186
+ const action = tool === "write_to_file" || tool === "write_file" || tool === "create_file" ? "Write" : "Edit";
187
+ const isGenericDesc = desc !== void 0 && /^(file edit|editing file|write file|writing file|file write|edit file|editing|writing|file create|creating file)$/i.test(desc.trim());
188
+ return {
189
+ kind: "diff",
190
+ tool,
191
+ title: desc !== void 0 && !isGenericDesc && !desc.includes(path) ? `${action} ${path} · ${desc}` : `${action} ${path}`,
192
+ path: rawPath,
193
+ oldText,
194
+ newText,
195
+ output: out
196
+ };
197
+ }
198
+ if (READ_TOOLS.has(tool)) {
199
+ const path = pick(input, "AbsolutePath", "absolute_path", "TargetFile", "target_file", "path", "file_path", "filename", "Path", "FilePath", "FileName", "targetFile") ?? "";
200
+ const offset = num(input, "offset", "Offset") ?? num(input, "StartLine", "start_line");
201
+ const line = offset !== void 0 ? offset > 0 && (tool === "view_file" || tool === "read_file") ? offset : offset + 1 : void 0;
202
+ return {
203
+ kind: "read",
204
+ tool,
205
+ title: desc ? `${desc} · ${path}` : path !== "" ? "Read " + path : "Read",
206
+ path,
207
+ output: out,
208
+ ...path !== "" ? { location: {
209
+ path,
210
+ ...line !== void 0 ? { line } : {}
211
+ } } : {}
212
+ };
213
+ }
214
+ if (SEARCH_TOOLS.has(tool)) {
215
+ const q = pick(input, "query", "Query", "pattern", "Pattern", "regex", "Regex", "QueryString") ?? "";
216
+ return {
217
+ kind: "search",
218
+ tool,
219
+ title: desc ?? (q !== "" ? "Search " + q : "Search"),
220
+ output: out
221
+ };
222
+ }
223
+ if (LIST_TOOLS.has(tool)) {
224
+ const path = pick(input, "DirectoryPath", "directory_path", "path", "directory", "Path", "Directory", "SearchDirectory", "search_directory", "AbsolutePath") ?? "";
225
+ return {
226
+ kind: "list",
227
+ tool,
228
+ title: desc ?? (path !== "" ? "List " + path : "List directory"),
229
+ output: out
230
+ };
231
+ }
232
+ if (DELETE_TOOLS.has(tool)) {
233
+ const path = pick(input, "TargetFile", "target_file", "path", "file_path", "Path", "FilePath", "AbsolutePath") ?? "";
234
+ return {
235
+ kind: "delete",
236
+ tool,
237
+ title: desc ?? "Delete " + path,
238
+ output: out
239
+ };
240
+ }
241
+ return {
242
+ kind: "generic",
243
+ tool,
244
+ title: desc ?? (tool !== "" ? tool : "agy_tool"),
245
+ output: out,
246
+ raw: parsedArgsRaw(block)
247
+ };
248
+ }
249
+ function cls(...names) {
250
+ return names.filter((n) => typeof n === "string" && n !== "").join(" ");
251
+ }
252
+ const ROW_CSS = `
253
+ .agy-tv-row{box-sizing:border-box;border:.5px solid var(--dsw-alias-border-l1,#e2e8f0);border-radius:10px;background:var(--dsw-alias-bg-layer-2,var(--dsw-alias-bg-base,#fff));margin:4px 0;overflow:hidden;font:var(--dsw-font-13,13px/1.5 -apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'PingFang SC','Microsoft YaHei',sans-serif)}
254
+ .agy-tv-head{display:flex;align-items:center;gap:8px;padding:6px 10px;cursor:pointer;user-select:none;background:transparent;border:none;width:100%;text-align:left;color:var(--dsw-alias-label-primary,#0f172a)}
255
+ .agy-tv-head:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(0,0,0,.04))}
256
+ .agy-tv-icon{flex:none;display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;border-radius:5px;background:var(--dsw-alias-bg-layer-3,#f1f5f9);color:var(--dsw-alias-label-secondary,#334155)}
257
+ .agy-tv-title{min-width:0;flex:1;font-size:12.5px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
258
+ .agy-tv-kind{flex:none;font-size:10px;font-weight:700;letter-spacing:.02em;text-transform:capitalize;padding:1px 6px;border-radius:5px;color:var(--dsw-alias-state-business-primary,#2563eb);background:color-mix(in srgb,var(--dsw-alias-state-business-primary,#2563eb) 12%,transparent)}
259
+ .agy-tv-tool{flex:none;font-size:10px;font-weight:600;font-family:var(--dsw-font-family-code,ui-monospace,SFMono-Regular,Menlo,Consolas,monospace);color:var(--dsw-alias-label-tertiary,#64748b);background:var(--dsw-alias-bg-layer-3,#f1f5f9);border:.5px solid var(--dsw-alias-border-l1,#e2e8f0);border-radius:5px;padding:1px 6px;max-width:28%;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
260
+ .agy-tv-label{font-size:10.5px;font-weight:700;letter-spacing:.03em;text-transform:uppercase;color:var(--dsw-alias-label-tertiary,#64748b);margin:0 0 3px}
261
+ .agy-tv-preview{flex:none;max-width:32%;font-size:11px;color:var(--dsw-alias-label-caption,#94a3b8);font-family:var(--dsw-font-family-code,ui-monospace,SFMono-Regular,Menlo,Consolas,monospace);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;background:var(--dsw-alias-bg-layer-3,#f1f5f9);border-radius:5px;padding:1px 6px}
262
+ .agy-tv-state{flex:none;font-size:10.5px;font-weight:700;padding:1px 7px;border-radius:999px}
263
+ .agy-tv-state-ok{color:var(--dsw-alias-state-success-primary,#059669);background:color-mix(in srgb,var(--dsw-alias-state-success-primary,#059669) 12%,transparent)}
264
+ .agy-tv-state-err{color:var(--dsw-alias-state-error-primary,#dc2626);background:color-mix(in srgb,var(--dsw-alias-state-error-primary,#dc2626) 12%,transparent)}
265
+ .agy-tv-state-run{color:var(--dsw-alias-state-business-primary,#2563eb);background:color-mix(in srgb,var(--dsw-alias-state-business-primary,#2563eb) 12%,transparent)}
266
+ .agy-tv-body{display:none;padding:8px 10px;border-top:.5px solid var(--dsw-alias-border-l1,#e2e8f0)}
267
+ .agy-tv-open .agy-tv-body{display:block}
268
+ .agy-tv-term{font-family:var(--dsw-font-family-code,ui-monospace,SFMono-Regular,Menlo,Consolas,monospace);font-size:12px;line-height:1.55;white-space:pre-wrap;word-break:break-word;background:var(--dsw-alias-bg-layer-1,#f8fafc);border:.5px solid var(--dsw-alias-border-l1,#e2e8f0);border-radius:7px;padding:8px 10px;color:var(--dsw-alias-label-primary,#0f172a)}
269
+ .agy-tv-term-cmd{color:var(--dsw-alias-label-tertiary,#64748b);font-size:11px;margin:0 0 4px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
270
+ .agy-tv-term-cwd{color:var(--dsw-alias-label-caption,#94a3b8);font-size:11px;font-family:var(--dsw-font-family-code,ui-monospace,SFMono-Regular,Menlo,Consolas,monospace);padding:2px 8px;border-top-left-radius:7px;border-top-right-radius:7px;background:var(--dsw-alias-bg-layer-3,#f1f5f9);border:.5px solid var(--dsw-alias-border-l1,#e2e8f0);border-bottom:none}
271
+ .agy-tv-diff{font-family:var(--dsw-font-family-code,ui-monospace,SFMono-Regular,Menlo,Consolas,monospace);font-size:12px;line-height:1.5;border:.5px solid var(--dsw-alias-border-l1,#e2e8f0);border-radius:7px;overflow:hidden}
272
+ .agy-tv-diff-path{font-size:11px;padding:4px 10px;background:var(--dsw-alias-bg-layer-3,#f1f5f9);color:var(--dsw-alias-label-secondary,#334155);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
273
+ .agy-tv-line{display:flex;gap:8px;padding:0 10px;min-height:20px;white-space:pre-wrap;word-break:break-word}
274
+ .agy-tv-line-del{background:color-mix(in srgb,var(--dsw-alias-state-error-primary,#dc2626) 12%,transparent);color:var(--dsw-alias-label-primary,#0f172a)}
275
+ .agy-tv-line-del .agy-tv-line-marker{color:var(--dsw-alias-state-error-primary,#dc2626);font-weight:700}
276
+ .agy-tv-line-add{background:color-mix(in srgb,var(--dsw-alias-state-success-primary,#059669) 12%,transparent);color:var(--dsw-alias-label-primary,#0f172a)}
277
+ .agy-tv-line-add .agy-tv-line-marker{color:var(--dsw-alias-state-success-primary,#059669);font-weight:700}
278
+ .agy-tv-line-marker{flex:none;width:14px;color:var(--dsw-alias-label-caption,#94a3b8);user-select:none}
279
+ .agy-tv-path{font-family:var(--dsw-font-family-code,ui-monospace,SFMono-Regular,Menlo,Consolas,monospace);font-size:12px;color:var(--dsw-alias-state-business-primary,#2563eb);cursor:pointer;text-decoration:underline}
280
+ .agy-tv-raw{font-family:var(--dsw-font-family-code,ui-monospace,SFMono-Regular,Menlo,Consolas,monospace);font-size:11.5px;white-space:pre-wrap;word-break:break-word;color:var(--dsw-alias-label-secondary,#334155);max-height:200px;overflow:auto}
281
+ `;
282
+ const stateLabel = (kind) => kind === "err" ? "Error" : kind === "run" ? "Running" : "Done";
283
+ function ChevronDown() {
284
+ const h = hx;
285
+ return h("svg", {
286
+ viewBox: "0 0 16 16",
287
+ width: 12,
288
+ height: 12,
289
+ style: {
290
+ flex: "none",
291
+ color: "var(--dsw-alias-label-tertiary,#64748b)"
292
+ }
293
+ }, h("path", {
294
+ d: "M4 6l4 4 4-4",
295
+ fill: "none",
296
+ stroke: "currentColor",
297
+ "stroke-width": 1.5,
298
+ "stroke-linecap": "round",
299
+ "stroke-linejoin": "round"
300
+ }));
301
+ }
302
+ function StatusIcon({ kind }) {
303
+ const h = hx;
304
+ return h("svg", {
305
+ viewBox: "0 0 16 16",
306
+ width: 14,
307
+ height: 14,
308
+ style: {
309
+ color: kind === "diff" ? "var(--dsw-alias-state-success-primary,#059669)" : kind === "delete" ? "var(--dsw-alias-state-error-primary,#dc2626)" : "var(--dsw-alias-label-secondary,#334155)",
310
+ display: "block"
311
+ }
312
+ }, h("path", {
313
+ d: kind === "terminal" ? "M4 2l8 6-8 6z" : kind === "diff" ? "M4 8h8M8 4v8M3 3h6a2 2 0 012 2v6a2 2 0 01-2 2H3a2 2 0 01-2-2V5a2 2 0 012-2z" : kind === "read" ? "M4 3h8a1 1 0 011 1v9l-3-2-3 2-3-2V4a1 1 0 011-1z" : kind === "search" ? "M7 3a4 4 0 100 8 4 4 0 000-8zm4 4a4 4 0 01-.5 2L14 12.5 12.5 14 9 10.5" : kind === "list" ? "M3 5h10M3 8h10M3 11h6" : kind === "delete" ? "M3 5h10M6 5V3h4v2M5 5l.5 9h5L11 5" : "M8 2a6 6 0 100 12A6 6 0 008 2zm0 2v4l3 2",
314
+ fill: "none",
315
+ stroke: "currentColor",
316
+ "stroke-width": 1.3,
317
+ "stroke-linecap": "round",
318
+ "stroke-linejoin": "round"
319
+ }));
320
+ }
321
+ /** Render one line-diff hunk (oldText -> newText) with +/- markers. */
322
+ function DiffBody({ model, openFile, cwd, home }) {
323
+ const h = hx;
324
+ const path = model.path ?? "file";
325
+ const oldText = model.oldText ?? "";
326
+ const newText = model.newText ?? "";
327
+ const pathLabel = relativize(path, cwd, home);
328
+ const rows = [];
329
+ if (oldText !== "") for (const line of oldText.split("\n")) rows.push(h("div", {
330
+ key: "o" + rows.length,
331
+ className: "agy-tv-line agy-tv-line-del"
332
+ }, h("span", { className: "agy-tv-line-marker" }, "-"), h("span", { style: { flex: 1 } }, line === "" ? " " : line)));
333
+ if (newText !== "") for (const line of newText.split("\n")) rows.push(h("div", {
334
+ key: "n" + rows.length,
335
+ className: "agy-tv-line agy-tv-line-add"
336
+ }, h("span", { className: "agy-tv-line-marker" }, "+"), h("span", { style: { flex: 1 } }, line === "" ? " " : line)));
337
+ if (rows.length === 0) rows.push(h("div", {
338
+ key: "empty",
339
+ className: "agy-tv-line"
340
+ }, h("span", { className: "agy-tv-line-marker" }, " "), h("span", { style: {
341
+ flex: 1,
342
+ color: "var(--dsw-alias-label-tertiary,#64748b)"
343
+ } }, "(no textual change)")));
344
+ return h("div", { className: "agy-tv-diff" }, h("div", { className: "agy-tv-diff-path" }, openFile !== void 0 ? h("span", {
345
+ className: "agy-tv-path",
346
+ onClick: () => openFile(path)
347
+ }, pathLabel) : h("span", { style: { cursor: "default" } }, pathLabel)), ...rows);
348
+ }
349
+ function relativize(p, cwd, home) {
350
+ if (cwd && p.startsWith(cwd)) {
351
+ const rel = p.slice(cwd.length).replace(/^[/\\]+/, "");
352
+ return rel !== "" ? rel : p;
353
+ }
354
+ if (home && p.startsWith(home)) return "~" + p.slice(home.length);
355
+ return p;
356
+ }
357
+ function clip(s, max) {
358
+ return s.length > max ? s.slice(0, max) + "\n… (+" + (s.length - max) + " chars)" : s;
359
+ }
360
+ /**
361
+ * One-line preview of a card's body for the collapsed header: the first
362
+ * non-empty output line, trimmed to a single compact row. Gives a collapsed
363
+ * card a content cue (e.g. `pwd` -> `/Users/.../agy-spaces`) instead of a bare
364
+ * title + state.
365
+ */
366
+ function previewLine(model, cwd, home) {
367
+ if (model.kind === "diff") {
368
+ const rel = relativize(model.path ?? "file", cwd, home);
369
+ const display = rel.length > 40 ? "…" + rel.slice(-40) : rel;
370
+ return model.oldText != null ? `± ${display}` : `+ ${display}`;
371
+ }
372
+ const out = model.output;
373
+ if (out !== void 0 && out !== "") {
374
+ const first = out.split("\n").find((l) => l.trim() !== "");
375
+ if (first !== void 0 && first.trim() !== "") return first.trim().slice(0, 80);
376
+ }
377
+ }
378
+ /** Small "OUTPUT" caption above a card's result content. */
379
+ function OutputLabel() {
380
+ return hx("div", { className: "agy-tv-label" }, "输出");
381
+ }
382
+ function CardBody({ model, openFile, cwd, home }) {
383
+ const h = hx;
384
+ switch (model.kind) {
385
+ case "terminal": {
386
+ const body = model.output !== void 0 && model.output !== "" ? trimTrailingBlankLines(model.output) : "(no output)";
387
+ return h("div", null, ...model.command !== void 0 && model.command !== "" && model.command !== model.title ? [h("div", {
388
+ key: "cmd",
389
+ className: "agy-tv-term-cmd"
390
+ }, model.command)] : [], ...model.cwd !== void 0 && model.cwd !== "" ? [h("div", {
391
+ key: "cwd",
392
+ className: "agy-tv-term-cwd"
393
+ }, "❯ " + model.cwd)] : [], h(OutputLabel, { key: "ol" }), h("pre", { className: "agy-tv-term" }, clip(body, 8e3)));
394
+ }
395
+ case "diff": return h("div", null, h(OutputLabel, { key: "ol" }), h(DiffBody, {
396
+ model,
397
+ openFile,
398
+ cwd,
399
+ home
400
+ }));
401
+ case "read":
402
+ case "search":
403
+ case "list":
404
+ case "delete": {
405
+ const body = model.output !== void 0 && model.output !== "" ? trimTrailingBlankLines(model.output) : "(no output)";
406
+ return h("div", null, h(OutputLabel, { key: "ol" }), h("pre", { className: "agy-tv-term" }, clip(body, 8e3)));
407
+ }
408
+ default: return h("div", null, h(OutputLabel, { key: "ol" }), h("pre", { className: "agy-tv-raw" }, model.raw !== void 0 && model.raw !== "" ? clip(model.raw, 4e3) : "(no detail)"));
409
+ }
410
+ }
411
+ function trimTrailingBlankLines(s) {
412
+ return s.replace(/[\t ]*\r?\n[\t ]*(\r?\n[\t ]*)*$/, "\n");
413
+ }
414
+ /**
415
+ * The keyed `tool.call.toolview` component for `agy_tool`. Renders the
416
+ * recorded agy tool activity as a native-looking DSH tool card.
417
+ * Receives the ToolCallOwnerProps: { callId, toolName, block, cwd, home,
418
+ * openFile, loadImage, inspect } (locale/inject optional).
419
+ */
420
+ function AgyMirrorToolView(props) {
421
+ const [open, setOpen] = useToggle(false);
422
+ const h = hx;
423
+ const { block, cwd, home, openFile, inspect } = props;
424
+ const settle = isSettled(block);
425
+ const err = isError(block);
426
+ const model = mirrorCardModel(block, cwd, home);
427
+ const state = !settle ? "run" : err ? "err" : "ok";
428
+ const title = model.title !== "" ? model.title : "agy tool";
429
+ const preview = !open ? previewLine(model, cwd, home) : void 0;
430
+ const header = h("button", {
431
+ type: "button",
432
+ className: "agy-tv-head",
433
+ onClick: (e) => {
434
+ e.stopPropagation();
435
+ setOpen();
436
+ },
437
+ onDoubleClick: (e) => e.stopPropagation(),
438
+ 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",
439
+ onContextMenu: inspect !== void 0 ? (e) => {
440
+ e.preventDefault();
441
+ inspect();
442
+ } : void 0
443
+ }, h("span", { className: "agy-tv-icon" }, h(StatusIcon, { kind: model.kind })), h("span", { className: "agy-tv-kind" }, model.kind), ...model.tool !== void 0 && model.tool !== "" ? [h("span", {
444
+ className: "agy-tv-tool",
445
+ title: model.tool
446
+ }, model.tool)] : [], h("span", {
447
+ className: "agy-tv-title",
448
+ title
449
+ }, title), ...preview !== void 0 ? [h("span", {
450
+ className: "agy-tv-preview",
451
+ title: preview
452
+ }, preview)] : [], h("span", { className: cls("agy-tv-state", state === "ok" ? "agy-tv-state-ok" : state === "err" ? "agy-tv-state-err" : "agy-tv-state-run") }, stateLabel(state)), h("span", { style: {
453
+ display: "inline-flex",
454
+ transition: "transform .15s ease",
455
+ transform: open ? "rotate(0deg)" : "rotate(-90deg)"
456
+ } }, h(ChevronDown, null)));
457
+ return h("div", { className: cls("agy-tv-row", open && "agy-tv-open") }, header, h("div", { className: "agy-tv-body" }, h(CardBody, {
458
+ model,
459
+ openFile,
460
+ cwd,
461
+ home
462
+ })));
463
+ }
464
+ /** Install the native tool-card toolview (call once from apply). */
465
+ function installAgyToolView(ctx) {
466
+ ctx.slots.inject("tool.call.toolview", () => ctx.slots.register({
467
+ name: "tool.call.toolview",
468
+ key: "agy_tool",
469
+ id: "agy-tool-view",
470
+ label: "Antigravity tool"
471
+ }, AgyMirrorToolView));
472
+ }
473
+ const gDoc = globalThis.document;
474
+ if (gDoc !== void 0 && gDoc !== null) {
475
+ const styleId = "dsh-agy-link-toolview-css";
476
+ if (gDoc.getElementById(styleId) === null) {
477
+ const st = gDoc.createElement("style");
478
+ st.id = styleId;
479
+ st.textContent = ROW_CSS;
480
+ const host = gDoc.head ?? gDoc.documentElement;
481
+ if (host) host.appendChild(st);
482
+ }
483
+ }
484
+ //#endregion
485
+ //#region src/client/locales.ts
486
+ /** Native DSH locale dictionaries for the Antigravity UI. */
487
+ const NS = "agy-link";
488
+ /** Simplified Chinese is preserved as the original UI language and key source. */
489
+ const zh = {
490
+ "section.label": "Antigravity",
491
+ "console.title": "Antigravity 管理控制台",
492
+ "status.ready": "就绪",
493
+ "status.needsAuth": "待认证",
494
+ "status.loading": "正在加载 Antigravity 状态...",
495
+ "status.authRequired": "需重新登录",
496
+ "status.primary": "主用",
497
+ "status.proxy": "代理",
498
+ "account.add": "添加 Google 账号",
499
+ "account.aliasDefault": "备用 Google 账号 {number}",
500
+ "account.defaultAlias": "主账号(系统登录)",
501
+ "account.aliasPlaceholder": "账号别名(例如:备用账号 2)",
502
+ "account.removeTitle": "移除此账号",
503
+ "aria.aliasInput": "账号别名",
504
+ "aria.proxyInput": "专属代理 URL",
505
+ "aria.authCodeInput": "授权码或回调 URL",
506
+ "aria.closeToast": "关闭提示",
507
+ "account.setPrimary": "设为主用",
508
+ "account.settingPrimary": "设置中",
509
+ "account.details": "明细",
510
+ "account.collapse": "收起",
511
+ "account.sync": "同步",
512
+ "account.syncing": "同步中",
513
+ "account.syncTitle": "刷新此账号(换号后立即同步 email / 额度 / 模型)",
514
+ "account.models": "单模型明细:",
515
+ "account.cooldown": "部分模型限流中,已自动切换账号",
516
+ "account.clearCooldown": "清除冷却",
517
+ "account.localCooldown": " · 本地冷却中",
518
+ "account.removed": "已移除账号:{alias}",
519
+ "quota.fiveHour": "5 小时额度",
520
+ "quota.weekly": "周额度",
521
+ "quota.refreshed": "额度已刷新",
522
+ "proxy.button": "代理",
523
+ "proxy.placeholder": "专属代理 URL(如:http://127.0.0.1:7890;留空则使用全局)",
524
+ "proxy.save": "保存",
525
+ "proxy.saved": "代理已保存",
526
+ "cancel": "取消",
527
+ "auth.openBrowser": "打开浏览器登录",
528
+ "auth.openingBrowser": "正在打开浏览器...",
529
+ "auth.browserOpened": "浏览器已打开 Google 授权页,完成授权后将自动同步",
530
+ "auth.browserManual": "无法自动打开浏览器,请点击下方链接手动完成授权",
531
+ "auth.startFailed": "无法启动 Google 授权。请检查网络或代理配置。",
532
+ "auth.networkHint": "请检查网络或代理配置",
533
+ "auth.activated": "账号已激活入池",
534
+ "auth.failed": "授权失败",
535
+ "auth.added": "成功添加并激活 Google 账号",
536
+ "auth.codeFailed": "授权码验证失败",
537
+ "auth.exchanging": "正在验证授权并激活账号,请稍候...",
538
+ "auth.waiting": "等待浏览器中完成 Google 授权,成功后将自动激活。",
539
+ "auth.openManual": "若浏览器未打开,请点击此处手动打开 Google 登录页",
540
+ "auth.pasteHint": "未完成自动回调时,可粘贴授权码或回调 URL:",
541
+ "auth.codePlaceholder": "授权码或 http://localhost:51121/oauth-callback?code=... 完整链接",
542
+ "auth.activate": "手动激活",
543
+ "auth.activating": "验证激活中...",
544
+ "auth.code.browser_manual": "无法自动打开浏览器,请使用下方链接手动授权。",
545
+ "auth.code.callback_failed": "授权回调失败。请重新开始授权。",
546
+ "auth.code.no_active_flow": "当前没有进行中的授权流程。",
547
+ "auth.code.invalid_code": "无法识别授权码。请粘贴授权码或完整回调 URL。",
548
+ "auth.code.invalid_state": "授权链接不属于当前流程或已过期。请重新开始授权。",
549
+ "auth.code.exchange_failed": "授权验证失败。请重新开始授权。",
550
+ "auth.code.primary_activated": "主账号已登录。",
551
+ "auth.code.account_activated": "账号已添加并激活。",
552
+ "auth.code.missing_code": "请输入授权码。",
553
+ "auth.code.request_failed": "请求未完成。请重试。",
554
+ "api.genericError": "操作未完成。请重试。",
555
+ "duration.day": "天",
556
+ "duration.hour": "小时",
557
+ "duration.minute": "分钟",
558
+ "permission.label": "权限模式:",
559
+ "permission.plan": "plan(只读)",
560
+ "permission.acceptEdits": "accept-edits(改代码)",
561
+ "permission.skip": "skip(自动批准;AGY 内部保护仍生效)",
562
+ "effort.label": "思考强度:",
563
+ "effort.auto": "auto",
564
+ "effort.low": "low",
565
+ "effort.medium": "medium",
566
+ "effort.high": "high",
567
+ "pool.label": "号池调度:",
568
+ "pool.sequential": "顺次耗尽",
569
+ "pool.roundRobin": "轮询均衡",
570
+ "header.refreshQuota": "刷新额度",
571
+ "header.refreshing": "刷新中",
572
+ "console.close": "关闭(Esc)",
573
+ "header.badgeTitle": "Antigravity:{count} 个账号已就绪 · 点击打开控制台"
574
+ };
575
+ const en = {
576
+ "section.label": "Antigravity",
577
+ "console.title": "Antigravity Management Console",
578
+ "status.ready": "Ready",
579
+ "status.needsAuth": "Authentication required",
580
+ "status.loading": "Loading Antigravity status...",
581
+ "status.authRequired": "Sign in again required",
582
+ "status.primary": "Primary",
583
+ "status.proxy": "Proxy",
584
+ "account.add": "Add Google account",
585
+ "account.aliasDefault": "Backup Google account {number}",
586
+ "account.defaultAlias": "Primary account (system sign-in)",
587
+ "account.aliasPlaceholder": "Account alias (for example: Backup account 2)",
588
+ "account.removeTitle": "Remove this account",
589
+ "aria.aliasInput": "Account alias",
590
+ "aria.proxyInput": "Dedicated proxy URL",
591
+ "aria.authCodeInput": "Authorization code or callback URL",
592
+ "aria.closeToast": "Dismiss notification",
593
+ "account.setPrimary": "Set as primary",
594
+ "account.settingPrimary": "Setting...",
595
+ "account.details": "Details",
596
+ "account.collapse": "Collapse",
597
+ "account.sync": "Sync",
598
+ "account.syncing": "Syncing...",
599
+ "account.syncTitle": "Refresh this account (sync email, quota, and models after switching accounts)",
600
+ "account.models": "Per-model details:",
601
+ "account.cooldown": "Some models are rate-limited; the account was switched automatically",
602
+ "account.clearCooldown": "Clear cooldown",
603
+ "account.localCooldown": " · local cooldown active",
604
+ "account.removed": "Account removed: {alias}",
605
+ "quota.fiveHour": "5-hour quota",
606
+ "quota.weekly": "Weekly quota",
607
+ "quota.refreshed": "Quota refreshed",
608
+ "proxy.button": "Proxy",
609
+ "proxy.placeholder": "Dedicated proxy URL (for example: http://127.0.0.1:7890; leave blank to use the global proxy)",
610
+ "proxy.save": "Save",
611
+ "proxy.saved": "Proxy saved",
612
+ "cancel": "Cancel",
613
+ "auth.openBrowser": "Open browser to sign in",
614
+ "auth.openingBrowser": "Opening browser...",
615
+ "auth.browserOpened": "The Google authorization page opened in your browser and will sync automatically after authorization",
616
+ "auth.browserManual": "The browser could not be opened automatically; use the link below to authorize manually",
617
+ "auth.startFailed": "Could not start Google authorization. Check your network or proxy configuration.",
618
+ "auth.networkHint": "Check your network or proxy configuration",
619
+ "auth.activated": "Account activated in the pool",
620
+ "auth.failed": "Authorization failed",
621
+ "auth.added": "Google account added and activated",
622
+ "auth.codeFailed": "Authorization code validation failed",
623
+ "auth.exchanging": "Verifying authorization and activating the account; please wait...",
624
+ "auth.waiting": "Complete Google authorization in the browser. The account will activate automatically.",
625
+ "auth.openManual": "If the browser did not open, click here to open the Google sign-in page manually",
626
+ "auth.pasteHint": "If the automatic callback did not complete, paste the authorization code or callback URL:",
627
+ "auth.codePlaceholder": "Authorization code or full http://localhost:51121/oauth-callback?code=... URL",
628
+ "auth.activate": "Activate manually",
629
+ "auth.activating": "Verifying and activating...",
630
+ "auth.code.browser_manual": "The browser could not be opened automatically; use the link below to authorize manually.",
631
+ "auth.code.callback_failed": "The authorization callback failed. Start authorization again.",
632
+ "auth.code.no_active_flow": "There is no active authorization flow.",
633
+ "auth.code.invalid_code": "The authorization code was not recognized. Paste the code or full callback URL.",
634
+ "auth.code.invalid_state": "The authorization link does not belong to this flow or has expired. Start authorization again.",
635
+ "auth.code.exchange_failed": "Authorization verification failed. Start authorization again.",
636
+ "auth.code.primary_activated": "Primary account signed in.",
637
+ "auth.code.account_activated": "Account added and activated.",
638
+ "auth.code.missing_code": "Enter an authorization code.",
639
+ "auth.code.request_failed": "The request did not complete. Try again.",
640
+ "api.genericError": "The operation did not complete. Try again.",
641
+ "duration.day": "d",
642
+ "duration.hour": "h",
643
+ "duration.minute": "m",
644
+ "permission.label": "Permission mode:",
645
+ "permission.plan": "plan (read-only)",
646
+ "permission.acceptEdits": "accept-edits (edit code)",
647
+ "permission.skip": "skip (auto-approve; AGY internal protections remain)",
648
+ "effort.label": "Thinking effort:",
649
+ "effort.auto": "auto",
650
+ "effort.low": "low",
651
+ "effort.medium": "medium",
652
+ "effort.high": "high",
653
+ "pool.label": "Account-pool scheduling:",
654
+ "pool.sequential": "Use sequentially",
655
+ "pool.roundRobin": "Round-robin",
656
+ "header.refreshQuota": "Refresh quota",
657
+ "header.refreshing": "Refreshing...",
658
+ "console.close": "Close (Esc)",
659
+ "header.badgeTitle": "Antigravity: {count} accounts ready · click to open the console"
660
+ };
661
+ const ptBR = {
662
+ "section.label": "Antigravity",
663
+ "console.title": "Console de gerenciamento do Antigravity",
664
+ "status.ready": "Pronto",
665
+ "status.needsAuth": "Autenticação necessária",
666
+ "status.loading": "Carregando o status do Antigravity...",
667
+ "status.authRequired": "É necessário entrar novamente",
668
+ "status.primary": "Principal",
669
+ "status.proxy": "Proxy",
670
+ "account.add": "Adicionar conta Google",
671
+ "account.aliasDefault": "Conta Google reserva {number}",
672
+ "account.defaultAlias": "Conta principal (login do sistema)",
673
+ "account.aliasPlaceholder": "Apelido da conta (por exemplo: Conta reserva 2)",
674
+ "account.removeTitle": "Remover esta conta",
675
+ "aria.aliasInput": "Apelido da conta",
676
+ "aria.proxyInput": "URL do proxy exclusivo",
677
+ "aria.authCodeInput": "Código de autorização ou URL de retorno",
678
+ "aria.closeToast": "Fechar notificação",
679
+ "account.setPrimary": "Definir como principal",
680
+ "account.settingPrimary": "Definindo...",
681
+ "account.details": "Detalhes",
682
+ "account.collapse": "Recolher",
683
+ "account.sync": "Sincronizar",
684
+ "account.syncing": "Sincronizando...",
685
+ "account.syncTitle": "Atualizar esta conta (sincroniza e-mail, cota e modelos depois de trocar de conta)",
686
+ "account.models": "Detalhes por modelo:",
687
+ "account.cooldown": "Alguns modelos estão limitados; a conta foi alternada automaticamente",
688
+ "account.clearCooldown": "Limpar espera",
689
+ "account.localCooldown": " · espera local ativa",
690
+ "account.removed": "Conta removida: {alias}",
691
+ "quota.fiveHour": "Cota de 5 horas",
692
+ "quota.weekly": "Cota semanal",
693
+ "quota.refreshed": "Cota atualizada",
694
+ "proxy.button": "Proxy",
695
+ "proxy.placeholder": "URL do proxy exclusivo (por exemplo: http://127.0.0.1:7890; deixe vazio para usar o proxy global)",
696
+ "proxy.save": "Salvar",
697
+ "proxy.saved": "Proxy salvo",
698
+ "cancel": "Cancelar",
699
+ "auth.openBrowser": "Abrir navegador para entrar",
700
+ "auth.openingBrowser": "Abrindo navegador...",
701
+ "auth.browserOpened": "A página de autorização do Google foi aberta no navegador e será sincronizada após a autorização",
702
+ "auth.browserManual": "Não foi possível abrir o navegador automaticamente; use o link abaixo para autorizar manualmente",
703
+ "auth.startFailed": "Não foi possível iniciar a autorização do Google. Verifique a configuração de rede ou proxy.",
704
+ "auth.networkHint": "Verifique a configuração de rede ou proxy",
705
+ "auth.activated": "Conta ativada no pool",
706
+ "auth.failed": "Falha na autorização",
707
+ "auth.added": "Conta Google adicionada e ativada",
708
+ "auth.codeFailed": "Falha ao validar o código de autorização",
709
+ "auth.exchanging": "Verificando a autorização e ativando a conta; aguarde...",
710
+ "auth.waiting": "Conclua a autorização do Google no navegador. A conta será ativada automaticamente.",
711
+ "auth.openManual": "Se o navegador não abriu, clique aqui para abrir manualmente a página de login do Google",
712
+ "auth.pasteHint": "Se o retorno automático não foi concluído, cole o código de autorização ou a URL de retorno:",
713
+ "auth.codePlaceholder": "Código de autorização ou URL completa http://localhost:51121/oauth-callback?code=...",
714
+ "auth.activate": "Ativar manualmente",
715
+ "auth.activating": "Verificando e ativando...",
716
+ "auth.code.browser_manual": "Não foi possível abrir o navegador automaticamente; use o link abaixo para autorizar manualmente.",
717
+ "auth.code.callback_failed": "O retorno da autorização falhou. Inicie a autorização novamente.",
718
+ "auth.code.no_active_flow": "Não há um fluxo de autorização em andamento.",
719
+ "auth.code.invalid_code": "O código de autorização não foi reconhecido. Cole o código ou a URL completa de retorno.",
720
+ "auth.code.invalid_state": "O link de autorização não pertence a este fluxo ou expirou. Inicie a autorização novamente.",
721
+ "auth.code.exchange_failed": "A validação da autorização falhou. Inicie a autorização novamente.",
722
+ "auth.code.primary_activated": "Conta principal conectada.",
723
+ "auth.code.account_activated": "Conta adicionada e ativada.",
724
+ "auth.code.missing_code": "Informe um código de autorização.",
725
+ "auth.code.request_failed": "A solicitação não foi concluída. Tente novamente.",
726
+ "api.genericError": "A operação não foi concluída. Tente novamente.",
727
+ "duration.day": "d",
728
+ "duration.hour": "h",
729
+ "duration.minute": "min",
730
+ "permission.label": "Modo de permissão:",
731
+ "permission.plan": "plan (somente leitura)",
732
+ "permission.acceptEdits": "accept-edits (editar código)",
733
+ "permission.skip": "skip (aprovação automática; proteções internas do AGY permanecem)",
734
+ "effort.label": "Intensidade de raciocínio:",
735
+ "effort.auto": "auto",
736
+ "effort.low": "low",
737
+ "effort.medium": "medium",
738
+ "effort.high": "high",
739
+ "pool.label": "Agendamento do pool de contas:",
740
+ "pool.sequential": "Usar em sequência",
741
+ "pool.roundRobin": "Rodízio",
742
+ "header.refreshQuota": "Atualizar cota",
743
+ "header.refreshing": "Atualizando...",
744
+ "console.close": "Fechar (Esc)",
745
+ "header.badgeTitle": "Antigravity: {count} contas prontas · clique para abrir o console"
746
+ };
747
+ const es = {
748
+ "section.label": "Antigravity",
749
+ "console.title": "Consola de administración de Antigravity",
750
+ "status.ready": "Listo",
751
+ "status.needsAuth": "Se requiere autenticación",
752
+ "status.loading": "Cargando el estado de Antigravity...",
753
+ "status.authRequired": "Debe iniciar sesión de nuevo",
754
+ "status.primary": "Principal",
755
+ "status.proxy": "Proxy",
756
+ "account.add": "Añadir cuenta de Google",
757
+ "account.aliasDefault": "Cuenta de Google de respaldo {number}",
758
+ "account.defaultAlias": "Cuenta principal (inicio de sesión del sistema)",
759
+ "account.aliasPlaceholder": "Alias de la cuenta (por ejemplo: Cuenta de respaldo 2)",
760
+ "account.removeTitle": "Eliminar esta cuenta",
761
+ "aria.aliasInput": "Alias de la cuenta",
762
+ "aria.proxyInput": "URL de proxy exclusivo",
763
+ "aria.authCodeInput": "Código de autorización o URL de devolución",
764
+ "aria.closeToast": "Cerrar notificación",
765
+ "account.setPrimary": "Establecer como principal",
766
+ "account.settingPrimary": "Configurando...",
767
+ "account.details": "Detalles",
768
+ "account.collapse": "Contraer",
769
+ "account.sync": "Sincronizar",
770
+ "account.syncing": "Sincronizando...",
771
+ "account.syncTitle": "Actualizar esta cuenta (sincroniza correo, cuota y modelos tras cambiar de cuenta)",
772
+ "account.models": "Detalles por modelo:",
773
+ "account.cooldown": "Algunos modelos tienen límite de uso; la cuenta se cambió automáticamente",
774
+ "account.clearCooldown": "Borrar espera",
775
+ "account.localCooldown": " · espera local activa",
776
+ "account.removed": "Cuenta eliminada: {alias}",
777
+ "quota.fiveHour": "Cuota de 5 horas",
778
+ "quota.weekly": "Cuota semanal",
779
+ "quota.refreshed": "Cuota actualizada",
780
+ "proxy.button": "Proxy",
781
+ "proxy.placeholder": "URL de proxy exclusivo (por ejemplo: http://127.0.0.1:7890; déjelo vacío para usar el proxy global)",
782
+ "proxy.save": "Guardar",
783
+ "proxy.saved": "Proxy guardado",
784
+ "cancel": "Cancelar",
785
+ "auth.openBrowser": "Abrir navegador para iniciar sesión",
786
+ "auth.openingBrowser": "Abriendo navegador...",
787
+ "auth.browserOpened": "La página de autorización de Google se abrió en el navegador y se sincronizará después de autorizar",
788
+ "auth.browserManual": "No se pudo abrir el navegador automáticamente; use el enlace de abajo para autorizar manualmente",
789
+ "auth.startFailed": "No se pudo iniciar la autorización de Google. Revise la configuración de red o proxy.",
790
+ "auth.networkHint": "Revise la configuración de red o proxy",
791
+ "auth.activated": "Cuenta activada en el pool",
792
+ "auth.failed": "Falló la autorización",
793
+ "auth.added": "Cuenta de Google añadida y activada",
794
+ "auth.codeFailed": "Falló la validación del código de autorización",
795
+ "auth.exchanging": "Verificando la autorización y activando la cuenta; espere...",
796
+ "auth.waiting": "Complete la autorización de Google en el navegador. La cuenta se activará automáticamente.",
797
+ "auth.openManual": "Si el navegador no se abrió, haga clic aquí para abrir manualmente la página de inicio de sesión de Google",
798
+ "auth.pasteHint": "Si la devolución automática no se completó, pegue el código de autorización o la URL de devolución:",
799
+ "auth.codePlaceholder": "Código de autorización o URL completa http://localhost:51121/oauth-callback?code=...",
800
+ "auth.activate": "Activar manualmente",
801
+ "auth.activating": "Verificando y activando...",
802
+ "auth.code.browser_manual": "No se pudo abrir el navegador automáticamente; use el enlace de abajo para autorizar manualmente.",
803
+ "auth.code.callback_failed": "La devolución de la autorización falló. Inicie la autorización de nuevo.",
804
+ "auth.code.no_active_flow": "No hay un flujo de autorización en curso.",
805
+ "auth.code.invalid_code": "No se reconoció el código de autorización. Pegue el código o la URL completa de devolución.",
806
+ "auth.code.invalid_state": "El enlace de autorización no pertenece a este flujo o caducó. Inicie la autorización de nuevo.",
807
+ "auth.code.exchange_failed": "Falló la validación de la autorización. Inicie la autorización de nuevo.",
808
+ "auth.code.primary_activated": "Cuenta principal conectada.",
809
+ "auth.code.account_activated": "Cuenta añadida y activada.",
810
+ "auth.code.missing_code": "Introduzca un código de autorización.",
811
+ "auth.code.request_failed": "La solicitud no se completó. Inténtelo de nuevo.",
812
+ "api.genericError": "La operación no se completó. Inténtelo de nuevo.",
813
+ "duration.day": "d",
814
+ "duration.hour": "h",
815
+ "duration.minute": "min",
816
+ "permission.label": "Modo de permisos:",
817
+ "permission.plan": "plan (solo lectura)",
818
+ "permission.acceptEdits": "accept-edits (editar código)",
819
+ "permission.skip": "skip (aprobación automática; se mantienen las protecciones internas de AGY)",
820
+ "effort.label": "Intensidad de razonamiento:",
821
+ "effort.auto": "auto",
822
+ "effort.low": "low",
823
+ "effort.medium": "medium",
824
+ "effort.high": "high",
825
+ "pool.label": "Programación del pool de cuentas:",
826
+ "pool.sequential": "Usar en secuencia",
827
+ "pool.roundRobin": "Turno rotativo",
828
+ "header.refreshQuota": "Actualizar cuota",
829
+ "header.refreshing": "Actualizando...",
830
+ "console.close": "Cerrar (Esc)",
831
+ "header.badgeTitle": "Antigravity: {count} cuentas listas · haga clic para abrir la consola"
832
+ };
833
+ //#endregion
34
834
  //#region src/client/index.ts
35
835
  const { createElement: h, useState, useEffect, useRef } = require("react");
36
836
  const reactDom = require("react-dom");
@@ -90,7 +890,7 @@ window.__ModuleLoader__.load({
90
890
  /**
91
891
  * Format reset timestamp into readable date/time + relative countdown.
92
892
  */
93
- function formatQuotaWindow(resetTimeStr) {
893
+ function formatQuotaWindow(t, resetTimeStr) {
94
894
  if (!resetTimeStr) return { resetText: "" };
95
895
  try {
96
896
  const d = new Date(resetTimeStr);
@@ -101,7 +901,7 @@ window.__ModuleLoader__.load({
101
901
  const hours = Math.floor(totalMins % 1440 / 60);
102
902
  const mins = totalMins % 60;
103
903
  const isWeekly = days >= 1;
104
- const countdown = days > 0 ? `${days}d${hours}h` : hours > 0 ? `${hours}h${mins}m` : `${mins}m`;
904
+ const countdown = days > 0 ? `${days}${t("duration.day")} ${hours}${t("duration.hour")}` : hours > 0 ? `${hours}${t("duration.hour")} ${mins}${t("duration.minute")}` : `${mins}${t("duration.minute")}`;
105
905
  const hh = d.getHours().toString().padStart(2, "0");
106
906
  const mm = d.getMinutes().toString().padStart(2, "0");
107
907
  return { resetText: isWeekly ? `${d.getMonth() + 1}/${d.getDate()} ${hh}:${mm} (${countdown})` : `${hh}:${mm} (${countdown})` };
@@ -750,7 +1550,39 @@ body.dark,
750
1550
  }
751
1551
  };
752
1552
  function apply(ctx) {
1553
+ const locale = ctx.locale;
1554
+ if (locale) ctx.effect(() => {
1555
+ const disposePtBR = locale.addLanguage({
1556
+ id: "pt-BR",
1557
+ label: "Português (Brasil)",
1558
+ fallback: "en"
1559
+ });
1560
+ const disposeEs = locale.addLanguage({
1561
+ id: "es",
1562
+ label: "Español",
1563
+ fallback: "en"
1564
+ });
1565
+ const disposeZh = locale.register(NS, "zh", zh);
1566
+ const disposeEn = locale.register(NS, "en", en);
1567
+ const disposePtBRDictionary = locale.register(NS, "pt-BR", ptBR);
1568
+ const disposeEsDictionary = locale.register(NS, "es", es);
1569
+ return () => {
1570
+ disposeEsDictionary();
1571
+ disposePtBRDictionary();
1572
+ disposeEn();
1573
+ disposeZh();
1574
+ disposeEs();
1575
+ disposePtBR();
1576
+ };
1577
+ }, "agy-link: locale dictionaries");
1578
+ const tFallback = ((key) => zh[key]);
1579
+ const useAgyTranslation = () => {
1580
+ const [, rerender] = useState(0);
1581
+ useEffect(() => locale?.subscribe(() => rerender((revision) => revision + 1)) ?? (() => {}), []);
1582
+ return locale?.bind("agy-link") ?? tFallback;
1583
+ };
753
1584
  const AgySettingsSection = (props) => {
1585
+ const t = useAgyTranslation();
754
1586
  const [status, setStatus] = useState(statusCache);
755
1587
  const [aliasInput, setAliasInput] = useState("");
756
1588
  const [proxyInputs, setProxyInputs] = useState({});
@@ -780,6 +1612,10 @@ body.dark,
780
1612
  };
781
1613
  }, []);
782
1614
  const flowStartedRef = useRef(false);
1615
+ const safeMessage = (code) => {
1616
+ const key = `auth.code.${code ?? ""}`;
1617
+ return code && key in en ? t(key) : t("api.genericError");
1618
+ };
783
1619
  useEffect(() => {
784
1620
  if (!addingAccount || !flowStartedRef.current) return;
785
1621
  const pa = status?.poolAuth;
@@ -789,15 +1625,15 @@ body.dark,
789
1625
  setAddingAccount(false);
790
1626
  setAuthCodeInput("");
791
1627
  setAliasInput("");
792
- showToast(pa.message || "账号已激活入池", "success");
1628
+ showToast(safeMessage(pa.code), "success");
793
1629
  } else if (pa.phase === "failed") {
794
1630
  flowStartedRef.current = false;
795
- showToast(pa.message || "授权失败", "error");
1631
+ showToast(safeMessage(pa.code), "error");
796
1632
  }
797
1633
  }, [
798
1634
  addingAccount,
799
1635
  status?.poolAuth?.phase,
800
- status?.poolAuth?.message
1636
+ status?.poolAuth?.code
801
1637
  ]);
802
1638
  const showToast = (text, type = "info") => {
803
1639
  setToast({
@@ -814,14 +1650,15 @@ body.dark,
814
1650
  };
815
1651
  const handleBeginAddAccount = async () => {
816
1652
  setLoadingAction("pool:beginAdd");
817
- const res = await postJson("/plugins/agy-link/pool/begin-add", { alias: aliasInput.trim() || `备用 Google 账号 ${(status?.pool?.accounts?.length ?? 1) + 1}` });
1653
+ const alias = aliasInput.trim();
1654
+ const res = await postJson("/plugins/agy-link/pool/begin-add", alias ? { alias } : {});
818
1655
  setLoadingAction(null);
819
1656
  if (res && res.ok) {
820
1657
  flowStartedRef.current = true;
821
1658
  await refresh();
822
- if (res.browserOpened) showToast("浏览器已打开 Google 授权页,完成授权后将自动同步", "info");
823
- else showToast("无法自动打开浏览器,请点击下方链接手动完成授权", "warn");
824
- } else showToast(`启动 Google 授权失败: ${res?.message || "请检查网络或代理配置"}`, "error");
1659
+ if (res.browserOpened) showToast(t("auth.browserOpened"), "info");
1660
+ else showToast(t("auth.browserManual"), "warn");
1661
+ } else showToast(res?.code ? safeMessage(res.code) : t("auth.startFailed"), "error");
825
1662
  };
826
1663
  const handleCompleteAddAccount = async () => {
827
1664
  if (!authCodeInput.trim()) return;
@@ -834,8 +1671,8 @@ body.dark,
834
1671
  setAliasInput("");
835
1672
  setAddingAccount(false);
836
1673
  await refresh();
837
- showToast(res.message || "成功添加并激活 Google 账号", "success");
838
- } else showToast(res?.message || res?.error || "授权码验证失败", "error");
1674
+ showToast(safeMessage(res.code), "success");
1675
+ } else showToast(safeMessage(res?.code), "error");
839
1676
  };
840
1677
  const handleCancelAddAccount = async () => {
841
1678
  flowStartedRef.current = false;
@@ -859,21 +1696,21 @@ body.dark,
859
1696
  await postJson("/plugins/agy-link/pool/primary", { id });
860
1697
  await refresh();
861
1698
  setLoadingAction(null);
862
- showToast("已设为主用账号", "success");
1699
+ showToast(t("account.setPrimary"), "success");
863
1700
  };
864
1701
  const removeAccount = async (id, alias) => {
865
1702
  setLoadingAction(`remove:${id}`);
866
1703
  await postJson("/plugins/agy-link/pool/remove", { id });
867
1704
  await refresh();
868
1705
  setLoadingAction(null);
869
- showToast(`已移除账号: ${alias}`, "info");
1706
+ showToast(t("account.removed", { alias }), "info");
870
1707
  };
871
1708
  const refreshQuota = async (id) => {
872
1709
  setLoadingAction(id ? `refresh:${id}` : "refresh:all");
873
1710
  await postJson("/plugins/agy-link/pool/refresh-quota", { id });
874
1711
  await refresh();
875
1712
  setLoadingAction(null);
876
- showToast("额度已刷新", "success");
1713
+ showToast(t("quota.refreshed"), "success");
877
1714
  };
878
1715
  const saveProxy = async (id) => {
879
1716
  setLoadingAction(`proxy:${id}`);
@@ -885,7 +1722,7 @@ body.dark,
885
1722
  setEditingProxyId(null);
886
1723
  await refresh();
887
1724
  setLoadingAction(null);
888
- showToast("代理已保存", "success");
1725
+ showToast(t("proxy.saved"), "success");
889
1726
  };
890
1727
  const setMode = async (mode) => {
891
1728
  setLoadingAction(`mode:${mode}`);
@@ -898,7 +1735,7 @@ body.dark,
898
1735
  await postJson("/plugins/agy-link/pool/clear-cooldown", { id });
899
1736
  await refresh();
900
1737
  setLoadingAction(null);
901
- showToast("已清除冷却", "success");
1738
+ showToast(t("account.clearCooldown"), "success");
902
1739
  };
903
1740
  const toggleExpand = (accId) => {
904
1741
  setExpandedModels((prev) => ({
@@ -956,7 +1793,8 @@ body.dark,
956
1793
  display: "inline-flex",
957
1794
  alignItems: "center"
958
1795
  },
959
- onClick: () => setToast(null)
1796
+ onClick: () => setToast(null),
1797
+ "aria-label": t("aria.closeToast")
960
1798
  }, uiIcon("x", 12)));
961
1799
  };
962
1800
  const renderQuotaBar = (label, familyKey, acc) => {
@@ -964,10 +1802,10 @@ body.dark,
964
1802
  const cd = acc.cooldowns[familyKey];
965
1803
  const inCooldown = cd && cd.cooldownUntil > Date.now();
966
1804
  const pct5h = typeof info?.remainingFraction === "number" && Number.isFinite(info.remainingFraction) ? Math.max(0, Math.min(100, Math.round(info.remainingFraction * 100))) : -1;
967
- const cdNote = inCooldown ? " · 本地冷却中" : "";
968
- const w5h = formatQuotaWindow(info?.resetTime);
1805
+ const cdNote = inCooldown ? t("account.localCooldown") : "";
1806
+ const w5h = formatQuotaWindow(t, info?.resetTime);
969
1807
  const pctWeekly = typeof info?.weeklyFraction === "number" && Number.isFinite(info.weeklyFraction) ? Math.max(0, Math.min(100, Math.round(info.weeklyFraction * 100))) : -1;
970
- const wWeekly = formatQuotaWindow(info?.weeklyResetTime);
1808
+ const wWeekly = formatQuotaWindow(t, info?.weeklyResetTime);
971
1809
  const getColors = (pct) => {
972
1810
  if (pct < 0) return {
973
1811
  bar: "var(--agy-quota-none-border)",
@@ -1052,7 +1890,7 @@ body.dark,
1052
1890
  fontSize: "12.5px",
1053
1891
  marginBottom: "5px",
1054
1892
  color: "var(--agy-text-primary)"
1055
- } }, brandIcon(FAMILY_BRAND[familyKey], 14), h("span", null, label)), renderLine("5h 额度", pct5h, c5h, w5h.resetText ? w5h.resetText + cdNote : cdNote.replace(/^ · /, "")), renderLine("周额度", pctWeekly, cWeekly, wWeekly.resetText));
1893
+ } }, brandIcon(FAMILY_BRAND[familyKey], 14), h("span", null, label)), renderLine(t("quota.fiveHour"), pct5h, c5h, w5h.resetText ? w5h.resetText + cdNote : cdNote.replace(/^ · /, "")), renderLine(t("quota.weekly"), pctWeekly, cWeekly, wWeekly.resetText));
1056
1894
  };
1057
1895
  const renderedAccountCards = accounts.map((acc) => {
1058
1896
  const isPrimary = acc.id === pool?.primaryAccountId;
@@ -1062,6 +1900,8 @@ body.dark,
1062
1900
  const isEditingProxy = editingProxyId === acc.id;
1063
1901
  const isExpanded = expandedModels[acc.id] ?? false;
1064
1902
  const cardStyle = isPrimary ? { ...S.cardPrimary } : { ...S.card };
1903
+ const legacyDefault = acc.defaultAlias === void 0 && acc.systemHome && acc.alias === "主账号 (系统登录)";
1904
+ const displayAlias = acc.defaultAlias || legacyDefault ? acc.systemHome ? t("account.defaultAlias") : t("account.aliasDefault", { number: accounts.indexOf(acc) + 1 }) : acc.alias;
1065
1905
  const googleModels = acc.quotas.google?.models ?? [];
1066
1906
  const anthropicModels = acc.quotas.anthropic?.models ?? [];
1067
1907
  const openaiModels = acc.quotas.openai?.models ?? [];
@@ -1103,14 +1943,14 @@ body.dark,
1103
1943
  fontWeight: 700,
1104
1944
  fontSize: "13.5px",
1105
1945
  color: "var(--agy-text-primary)"
1106
- } }, acc.alias), isAuthRequired ? h("span", { style: {
1946
+ } }, displayAlias), isAuthRequired ? h("span", { style: {
1107
1947
  ...S.badgeTag,
1108
1948
  background: "var(--agy-badge-email-bg)",
1109
1949
  color: "#ef4444",
1110
1950
  borderColor: "#fca5a5",
1111
1951
  gap: "4px",
1112
1952
  fontWeight: 700
1113
- } }, uiIcon("alert", 11, "#ef4444"), "需重新登录") : null, acc.email ? h("span", { style: {
1953
+ } }, uiIcon("alert", 11, "#ef4444"), t("status.authRequired")) : null, acc.email ? h("span", { style: {
1114
1954
  ...S.badgeTag,
1115
1955
  background: "var(--agy-badge-email-bg)",
1116
1956
  color: "var(--agy-badge-email-text)",
@@ -1119,13 +1959,13 @@ body.dark,
1119
1959
  } }, uiIcon("mail", 11, "var(--agy-badge-email-text)"), acc.email) : null, isPrimary ? h("span", { style: {
1120
1960
  ...S.badgePrimary,
1121
1961
  gap: "4px"
1122
- } }, uiIcon("star", 10, "var(--agy-badge-primary-text)"), "主用") : null, acc.proxyUrl ? h("span", { style: {
1962
+ } }, uiIcon("star", 10, "var(--agy-badge-primary-text)"), t("status.primary")) : null, acc.proxyUrl ? h("span", { style: {
1123
1963
  ...S.badgeTag,
1124
1964
  background: "var(--agy-badge-proxy-bg)",
1125
1965
  color: "var(--agy-badge-proxy-text)",
1126
1966
  borderColor: "var(--agy-badge-proxy-border)",
1127
1967
  gap: "5px"
1128
- } }, uiIcon("globe", 11, "var(--agy-badge-proxy-text)"), "代理") : null), h("div", { style: {
1968
+ } }, uiIcon("globe", 11, "var(--agy-badge-proxy-text)"), t("status.proxy")) : null), h("div", { style: {
1129
1969
  display: "flex",
1130
1970
  gap: "5px",
1131
1971
  alignItems: "center"
@@ -1140,17 +1980,17 @@ body.dark,
1140
1980
  gap: "3px"
1141
1981
  },
1142
1982
  onClick: () => toggleExpand(acc.id)
1143
- }, isExpanded ? [uiIcon("chevronUp", 11), " 收起"] : [uiIcon("chevronDown", 11), " 明细"]) : null, h("button", {
1983
+ }, isExpanded ? [uiIcon("chevronUp", 11), ` ${t("account.collapse")}`] : [uiIcon("chevronDown", 11), ` ${t("account.details")}`]) : null, h("button", {
1144
1984
  type: "button",
1145
1985
  className: "agy-btn",
1146
1986
  style: {
1147
1987
  ...S.btnSm,
1148
1988
  gap: "4px"
1149
1989
  },
1150
- title: "刷新此账号(换号后点这里立即同步 email / 额度 / 模型)",
1990
+ title: t("account.syncTitle"),
1151
1991
  disabled: isBusy,
1152
1992
  onClick: () => void refreshQuota(acc.id)
1153
- }, loadingAction === `refresh:${acc.id}` ? [renderSpinner(), "同步中"] : [uiIcon("refresh", 11), " 同步"]), !isPrimary ? h("button", {
1993
+ }, loadingAction === `refresh:${acc.id}` ? [renderSpinner(), t("account.syncing")] : [uiIcon("refresh", 11), ` ${t("account.sync")}`]), !isPrimary ? h("button", {
1154
1994
  type: "button",
1155
1995
  className: "agy-btn",
1156
1996
  style: {
@@ -1159,7 +1999,7 @@ body.dark,
1159
1999
  },
1160
2000
  disabled: isBusy,
1161
2001
  onClick: () => void setPrimary(acc.id)
1162
- }, loadingAction === `primary:${acc.id}` ? [renderSpinner(), "设置中"] : [uiIcon("star", 11), " 设为主用"]) : null, h("button", {
2002
+ }, loadingAction === `primary:${acc.id}` ? [renderSpinner(), t("account.settingPrimary")] : [uiIcon("star", 11), ` ${t("account.setPrimary")}`]) : null, h("button", {
1163
2003
  type: "button",
1164
2004
  className: "agy-btn",
1165
2005
  style: isEditingProxy ? {
@@ -1171,16 +2011,17 @@ body.dark,
1171
2011
  },
1172
2012
  disabled: isBusy,
1173
2013
  onClick: () => setEditingProxyId(isEditingProxy ? null : acc.id)
1174
- }, [uiIcon("globe", 11), " 代理"]), accounts.length > 1 ? h("button", {
2014
+ }, [uiIcon("globe", 11), ` ${t("proxy.button")}`]), accounts.length > 1 ? h("button", {
1175
2015
  type: "button",
1176
2016
  className: "agy-btn",
1177
2017
  style: {
1178
2018
  ...S.btnDanger,
1179
2019
  padding: "3px 7px"
1180
2020
  },
1181
- title: "移除此账号",
2021
+ title: t("account.removeTitle"),
2022
+ "aria-label": t("account.removeTitle"),
1182
2023
  disabled: isBusy,
1183
- onClick: () => void removeAccount(acc.id, acc.alias)
2024
+ onClick: () => void removeAccount(acc.id, displayAlias)
1184
2025
  }, loadingAction === `remove:${acc.id}` ? renderSpinner() : uiIcon("trash", 12, "var(--agy-danger-text)")) : null)), h("div", { style: S.quotaBox }, renderQuotaBar("Gemini", "google", acc), renderQuotaBar("Claude", "anthropic", acc), renderQuotaBar("GPT-OSS", "openai", acc), isExpanded && allChildModels.length > 0 ? h("div", { style: {
1185
2026
  marginTop: "8px",
1186
2027
  paddingTop: "8px",
@@ -1190,10 +2031,10 @@ body.dark,
1190
2031
  marginBottom: "6px",
1191
2032
  fontWeight: 600,
1192
2033
  fontSize: "11px"
1193
- } }, "单模型明细:"), allChildModels.map(({ family, model }) => {
2034
+ } }, t("account.models")), allChildModels.map(({ family, model }) => {
1194
2035
  const frac = model.remainingFraction ?? 1;
1195
2036
  const pct = Math.round(frac * 100);
1196
- const w = formatQuotaWindow(model.resetTime);
2037
+ const w = formatQuotaWindow(t, model.resetTime);
1197
2038
  const pColor = pct <= 20 ? "var(--agy-quota-low-text)" : pct <= 50 ? "var(--agy-quota-med-text)" : "var(--agy-quota-high-text)";
1198
2039
  return h("div", {
1199
2040
  key: model.modelId,
@@ -1228,7 +2069,7 @@ body.dark,
1228
2069
  display: "flex",
1229
2070
  alignItems: "center",
1230
2071
  gap: "6px"
1231
- } }, uiIcon("alert", 13, "var(--agy-warn-text)"), h("span", null, "部分模型限流中,已自动切换账号")), h("button", {
2072
+ } }, uiIcon("alert", 13, "var(--agy-warn-text)"), h("span", null, t("account.cooldown"))), h("button", {
1232
2073
  type: "button",
1233
2074
  className: "agy-btn",
1234
2075
  style: {
@@ -1240,7 +2081,7 @@ body.dark,
1240
2081
  },
1241
2082
  disabled: isBusy,
1242
2083
  onClick: () => void clearCooldown(acc.id)
1243
- }, loadingAction === `clearCooldown:${acc.id}` ? [renderSpinner(), ""] : [uiIcon("zap", 11, "var(--agy-warn-btn-text)"), " 清除冷却"])) : null, isEditingProxy ? h("div", { style: {
2084
+ }, loadingAction === `clearCooldown:${acc.id}` ? [renderSpinner(), ""] : [uiIcon("zap", 11, "var(--agy-warn-btn-text)"), ` ${t("account.clearCooldown")}`])) : null, isEditingProxy ? h("div", { style: {
1244
2085
  marginTop: "8px",
1245
2086
  padding: "10px 12px",
1246
2087
  background: "var(--agy-bg-subbox)",
@@ -1252,7 +2093,8 @@ body.dark,
1252
2093
  } }, h("input", {
1253
2094
  style: S.input,
1254
2095
  value: proxyInputs[acc.id] !== void 0 ? proxyInputs[acc.id] : acc.proxyUrl ?? "",
1255
- placeholder: "专属代理 URL (如: http://127.0.0.1:7890,留空则使用全局)",
2096
+ placeholder: t("proxy.placeholder"),
2097
+ "aria-label": t("aria.proxyInput"),
1256
2098
  onChange: (e) => setProxyInputs({
1257
2099
  ...proxyInputs,
1258
2100
  [acc.id]: e.target.value
@@ -1263,17 +2105,21 @@ body.dark,
1263
2105
  style: S.btnPrimary,
1264
2106
  disabled: isBusy,
1265
2107
  onClick: () => void saveProxy(acc.id)
1266
- }, loadingAction === `proxy:${acc.id}` ? [renderSpinner(), "保存"] : "保存"), h("button", {
2108
+ }, loadingAction === `proxy:${acc.id}` ? [renderSpinner(), t("proxy.save")] : t("proxy.save")), h("button", {
1267
2109
  type: "button",
1268
2110
  className: "agy-btn",
1269
2111
  style: S.btn,
1270
2112
  onClick: () => setEditingProxyId(null)
1271
- }, "取消"))) : null);
2113
+ }, t("cancel")))) : null);
1272
2114
  });
1273
2115
  const poolAuth = status?.poolAuth;
1274
2116
  const flowPhase = poolAuth?.phase ?? "idle";
1275
2117
  const flowActive = flowPhase === "waiting" || flowPhase === "exchanging";
1276
- const addAccountSection = addingAccount ? h("div", { style: S.authModal }, h("div", { style: {
2118
+ const addAccountSection = addingAccount ? h("div", {
2119
+ style: S.authModal,
2120
+ role: "group",
2121
+ "aria-label": t("account.add")
2122
+ }, h("div", { style: {
1277
2123
  fontWeight: 700,
1278
2124
  fontSize: "13.5px",
1279
2125
  marginBottom: "10px",
@@ -1281,13 +2127,14 @@ body.dark,
1281
2127
  alignItems: "center",
1282
2128
  gap: "6px",
1283
2129
  color: "var(--agy-text-primary)"
1284
- } }, uiIcon("plus", 13, "var(--agy-btn-primary-bg)"), "添加 Google 账号"), !flowActive ? h("div", null, h("div", { style: {
2130
+ } }, uiIcon("plus", 13, "var(--agy-btn-primary-bg)"), t("account.add")), !flowActive ? h("div", null, h("div", { style: {
1285
2131
  display: "flex",
1286
2132
  gap: "8px"
1287
2133
  } }, h("input", {
1288
2134
  style: S.input,
1289
2135
  value: aliasInput,
1290
- placeholder: "账号别名 (例如: 备用账号 2)",
2136
+ placeholder: t("account.aliasPlaceholder"),
2137
+ "aria-label": t("aria.aliasInput"),
1291
2138
  onChange: (e) => setAliasInput(e.target.value)
1292
2139
  }), h("button", {
1293
2140
  type: "button",
@@ -1298,12 +2145,12 @@ body.dark,
1298
2145
  },
1299
2146
  disabled: isBusy,
1300
2147
  onClick: () => void handleBeginAddAccount()
1301
- }, loadingAction === "pool:beginAdd" ? [renderSpinner(), "正在打开浏览器..."] : [uiIcon("externalLink", 12, "#ffffff"), " 打开浏览器登录"]), h("button", {
2148
+ }, loadingAction === "pool:beginAdd" ? [renderSpinner(), t("auth.openingBrowser")] : [uiIcon("externalLink", 12, "#ffffff"), ` ${t("auth.openBrowser")}`]), h("button", {
1302
2149
  type: "button",
1303
2150
  className: "agy-btn",
1304
2151
  style: S.btn,
1305
2152
  onClick: () => handleCancelAddAccount()
1306
- }, "取消")), flowPhase === "failed" && poolAuth?.message ? h("div", { style: {
2153
+ }, t("cancel"))), flowPhase === "failed" ? h("div", { style: {
1307
2154
  ...S.muted,
1308
2155
  color: "var(--agy-danger-text)",
1309
2156
  marginTop: "8px",
@@ -1311,14 +2158,14 @@ body.dark,
1311
2158
  alignItems: "center",
1312
2159
  gap: "5px",
1313
2160
  fontWeight: 600
1314
- } }, uiIcon("alert", 12, "var(--agy-danger-text)"), poolAuth.message) : null) : h("div", null, h("div", { style: {
2161
+ } }, uiIcon("alert", 12, "var(--agy-danger-text)"), safeMessage(poolAuth?.code)) : null) : h("div", null, h("div", { style: {
1315
2162
  display: "flex",
1316
2163
  alignItems: "center",
1317
2164
  color: "var(--agy-text-secondary)",
1318
2165
  marginBottom: "8px",
1319
2166
  lineHeight: 1.5,
1320
2167
  fontSize: "12px"
1321
- } }, renderSpinner(), flowPhase === "exchanging" ? "正在验证授权并激活账号,请稍候..." : "等待浏览器中完成 Google 授权,成功后将自动激活。"), poolAuth?.url ? h("div", { style: { marginBottom: "8px" } }, h("a", {
2168
+ } }, renderSpinner(), flowPhase === "exchanging" ? t("auth.exchanging") : t("auth.waiting")), poolAuth?.url ? h("div", { style: { marginBottom: "8px" } }, h("a", {
1322
2169
  href: poolAuth.url,
1323
2170
  target: "_blank",
1324
2171
  style: {
@@ -1330,18 +2177,19 @@ body.dark,
1330
2177
  alignItems: "center",
1331
2178
  gap: "4px"
1332
2179
  }
1333
- }, uiIcon("externalLink", 12, "currentColor"), "若浏览器未打开,请点击此处手动打开 Google 登录页")) : null, h("div", { style: {
2180
+ }, uiIcon("externalLink", 12, "currentColor"), t("auth.openManual"))) : null, h("div", { style: {
1334
2181
  color: "var(--agy-text-tertiary)",
1335
2182
  marginBottom: "6px",
1336
2183
  fontSize: "11px",
1337
2184
  fontWeight: 500
1338
- } }, "未完成自动回调时,可粘贴授权码或回调 URL:"), h("div", { style: {
2185
+ } }, t("auth.pasteHint")), h("div", { style: {
1339
2186
  display: "flex",
1340
2187
  gap: "8px"
1341
2188
  } }, h("input", {
1342
2189
  style: S.input,
1343
2190
  value: authCodeInput,
1344
- placeholder: "授权码 或 http://localhost:51121/oauth-callback?code=... 完整链接",
2191
+ placeholder: t("auth.codePlaceholder"),
2192
+ "aria-label": t("aria.authCodeInput"),
1345
2193
  onChange: (e) => setAuthCodeInput(e.target.value)
1346
2194
  }), h("button", {
1347
2195
  type: "button",
@@ -1352,19 +2200,19 @@ body.dark,
1352
2200
  },
1353
2201
  disabled: isBusy || !authCodeInput.trim(),
1354
2202
  onClick: () => void handleCompleteAddAccount()
1355
- }, loadingAction === "pool:completeAdd" ? [renderSpinner(), "验证激活中..."] : [uiIcon("check", 12, "#ffffff"), " 手动激活"]), h("button", {
2203
+ }, loadingAction === "pool:completeAdd" ? [renderSpinner(), t("auth.activating")] : [uiIcon("check", 12, "#ffffff"), ` ${t("auth.activate")}`]), h("button", {
1356
2204
  type: "button",
1357
2205
  className: "agy-btn",
1358
2206
  style: S.btn,
1359
2207
  onClick: () => handleCancelAddAccount()
1360
- }, "取消")))) : null;
2208
+ }, t("cancel"))))) : null;
1361
2209
  if (status === null) return h("div", { style: {
1362
2210
  ...S.container,
1363
2211
  minHeight: "120px",
1364
2212
  display: "flex",
1365
2213
  alignItems: "center",
1366
2214
  justifyContent: "center"
1367
- } }, h("style", null, GLOBAL_CSS), h("span", { style: S.muted }, [renderSpinner(), "正在加载 Antigravity 状态..."]));
2215
+ } }, h("style", null, GLOBAL_CSS), h("span", { style: S.muted }, [renderSpinner(), t("status.loading")]));
1368
2216
  return h("div", { style: S.container }, h("style", null, GLOBAL_CSS), h("div", { style: S.headerCard }, h("div", { style: {
1369
2217
  display: "flex",
1370
2218
  alignItems: "center",
@@ -1373,7 +2221,7 @@ body.dark,
1373
2221
  fontWeight: 700,
1374
2222
  fontSize: "14px",
1375
2223
  color: "var(--agy-text-primary)"
1376
- } }, "Antigravity"), h("span", { style: isAuthed ? S.badgeReady : S.badgeUnready }, isAuthed ? "就绪" : "待认证")), h("div", { style: {
2224
+ } }, "Antigravity"), h("span", { style: isAuthed ? S.badgeReady : S.badgeUnready }, isAuthed ? t("status.ready") : t("status.needsAuth"))), h("div", { style: {
1377
2225
  display: "flex",
1378
2226
  gap: "6px",
1379
2227
  alignItems: "center"
@@ -1385,7 +2233,7 @@ body.dark,
1385
2233
  gap: "4px"
1386
2234
  },
1387
2235
  onClick: () => setAddingAccount(true)
1388
- }, [uiIcon("plus", 12, "#ffffff"), " 添加账号"]) : null, h("button", {
2236
+ }, [uiIcon("plus", 12, "#ffffff"), ` ${t("account.add")}`]) : null, h("button", {
1389
2237
  type: "button",
1390
2238
  className: "agy-btn",
1391
2239
  style: {
@@ -1394,7 +2242,7 @@ body.dark,
1394
2242
  },
1395
2243
  disabled: isBusy,
1396
2244
  onClick: () => void refreshQuota()
1397
- }, loadingAction === "refresh:all" ? [renderSpinner(), "刷新中"] : [uiIcon("refresh", 12, "var(--agy-text-btn)"), " 刷新额度"]))), renderToastBanner(), addAccountSection, renderedAccountCards, h("div", { style: {
2245
+ }, loadingAction === "refresh:all" ? [renderSpinner(), t("header.refreshing")] : [uiIcon("refresh", 12, "var(--agy-text-btn)"), ` ${t("header.refreshQuota")}`]))), renderToastBanner(), addAccountSection, renderedAccountCards, h("div", { style: {
1398
2246
  marginTop: "16px",
1399
2247
  paddingTop: "12px",
1400
2248
  borderTop: "1px solid var(--agy-border-divider)"
@@ -1410,15 +2258,15 @@ body.dark,
1410
2258
  color: "var(--agy-text-primary)",
1411
2259
  fontWeight: 600,
1412
2260
  fontSize: "12.5px"
1413
- } }, "权限模式:"), h("div", { style: S.segGroup }, h("button", {
2261
+ } }, t("permission.label")), h("div", { style: S.segGroup }, h("button", {
1414
2262
  type: "button",
1415
2263
  style: status?.permissionMode === "plan" ? S.segBtnActive : S.segBtn,
1416
2264
  onClick: () => void setCfg("permissionMode", "plan")
1417
- }, "plan (只读)"), h("button", {
2265
+ }, t("permission.plan")), h("button", {
1418
2266
  type: "button",
1419
2267
  style: status?.permissionMode === "accept-edits" ? S.segBtnActive : S.segBtn,
1420
2268
  onClick: () => void setCfg("permissionMode", "accept-edits")
1421
- }, "accept-edits (改代码)"), h("button", {
2269
+ }, t("permission.acceptEdits")), h("button", {
1422
2270
  type: "button",
1423
2271
  style: status?.permissionMode === "skip" ? {
1424
2272
  ...S.segBtnActive,
@@ -1427,7 +2275,7 @@ body.dark,
1427
2275
  border: "1px solid var(--agy-seg-danger-border)"
1428
2276
  } : S.segBtn,
1429
2277
  onClick: () => void setCfg("permissionMode", "skip")
1430
- }, "skip (全自动免确认)"))), h("div", { style: {
2278
+ }, t("permission.skip")))), h("div", { style: {
1431
2279
  display: "flex",
1432
2280
  alignItems: "center",
1433
2281
  justifyContent: "space-between"
@@ -1435,23 +2283,23 @@ body.dark,
1435
2283
  color: "var(--agy-text-primary)",
1436
2284
  fontWeight: 600,
1437
2285
  fontSize: "12.5px"
1438
- } }, "思考强度:"), h("div", { style: S.segGroup }, h("button", {
2286
+ } }, t("effort.label")), h("div", { style: S.segGroup }, h("button", {
1439
2287
  type: "button",
1440
2288
  style: status?.defaultEffort === "" ? S.segBtnActive : S.segBtn,
1441
2289
  onClick: () => void setCfg("defaultEffort", "")
1442
- }, "auto"), h("button", {
2290
+ }, t("effort.auto")), h("button", {
1443
2291
  type: "button",
1444
2292
  style: status?.defaultEffort === "low" ? S.segBtnActive : S.segBtn,
1445
2293
  onClick: () => void setCfg("defaultEffort", "low")
1446
- }, "low"), h("button", {
2294
+ }, t("effort.low")), h("button", {
1447
2295
  type: "button",
1448
2296
  style: status?.defaultEffort === "medium" ? S.segBtnActive : S.segBtn,
1449
2297
  onClick: () => void setCfg("defaultEffort", "medium")
1450
- }, "medium"), h("button", {
2298
+ }, t("effort.medium")), h("button", {
1451
2299
  type: "button",
1452
2300
  style: status?.defaultEffort === "high" ? S.segBtnActive : S.segBtn,
1453
2301
  onClick: () => void setCfg("defaultEffort", "high")
1454
- }, "high"))), h("div", { style: {
2302
+ }, t("effort.high")))), h("div", { style: {
1455
2303
  display: "flex",
1456
2304
  alignItems: "center",
1457
2305
  justifyContent: "space-between"
@@ -1459,18 +2307,19 @@ body.dark,
1459
2307
  color: "var(--agy-text-primary)",
1460
2308
  fontWeight: 600,
1461
2309
  fontSize: "12.5px"
1462
- } }, "号池调度:"), h("div", { style: S.segGroup }, h("button", {
2310
+ } }, t("pool.label")), h("div", { style: S.segGroup }, h("button", {
1463
2311
  type: "button",
1464
2312
  style: pool?.mode === "sequential" || !pool?.mode ? S.segBtnActive : S.segBtn,
1465
2313
  onClick: () => void setMode("sequential")
1466
- }, "顺次耗尽"), h("button", {
2314
+ }, t("pool.sequential")), h("button", {
1467
2315
  type: "button",
1468
2316
  style: pool?.mode === "round-robin" ? S.segBtnActive : S.segBtn,
1469
2317
  onClick: () => void setMode("round-robin")
1470
- }, "轮询均衡"))))));
2318
+ }, t("pool.roundRobin")))))));
1471
2319
  };
1472
2320
  /** Modal dialog container rendered into document.body */
1473
2321
  const AgyModalDialog = () => {
2322
+ const t = useAgyTranslation();
1474
2323
  const [isOpen, setIsOpen] = useState(agyModalStore.open);
1475
2324
  useEffect(() => {
1476
2325
  return agyModalStore.subscribe(() => {
@@ -1497,7 +2346,8 @@ body.dark,
1497
2346
  }, h("div", {
1498
2347
  className: "agy-modal-panel",
1499
2348
  role: "dialog",
1500
- "aria-modal": true
2349
+ "aria-modal": true,
2350
+ "aria-labelledby": "agy-console-title"
1501
2351
  }, h("div", { style: {
1502
2352
  display: "flex",
1503
2353
  alignItems: "center",
@@ -1510,13 +2360,17 @@ body.dark,
1510
2360
  display: "flex",
1511
2361
  alignItems: "center",
1512
2362
  gap: "8px"
1513
- } }, agyIcon(18), h("strong", { style: {
1514
- fontSize: "14.5px",
1515
- fontWeight: 700,
1516
- color: "var(--agy-text-primary)"
1517
- } }, "Antigravity 管理控制台")), h("button", {
2363
+ } }, agyIcon(18), h("strong", {
2364
+ id: "agy-console-title",
2365
+ style: {
2366
+ fontSize: "14.5px",
2367
+ fontWeight: 700,
2368
+ color: "var(--agy-text-primary)"
2369
+ }
2370
+ }, t("console.title"))), h("button", {
1518
2371
  type: "button",
1519
- title: "关闭 (Esc)",
2372
+ title: t("console.close"),
2373
+ "aria-label": t("console.close"),
1520
2374
  style: {
1521
2375
  background: "var(--agy-bg-btn)",
1522
2376
  border: "1px solid var(--agy-border-btn)",
@@ -1540,6 +2394,7 @@ body.dark,
1540
2394
  };
1541
2395
  /** Session Header badge in chat toolbar */
1542
2396
  const AgySessionStatus = () => {
2397
+ const t = useAgyTranslation();
1543
2398
  const [status, setStatus] = useState(statusCache);
1544
2399
  useEffect(() => {
1545
2400
  let alive = true;
@@ -1566,7 +2421,7 @@ body.dark,
1566
2421
  const color = status === null ? "#64748b" : status.dormantReason ? "#f59e0b" : hasCooldown ? "#f59e0b" : isAuthed ? "#10b981" : "#f59e0b";
1567
2422
  const badge = h("button", {
1568
2423
  type: "button",
1569
- title: `Antigravity: ${accounts.length} accounts ready · 点击打开控制台`,
2424
+ title: t("header.badgeTitle", { count: accounts.length }),
1570
2425
  className: "agy-btn",
1571
2426
  onClick: () => agyModalStore.setOpen(true),
1572
2427
  style: {
@@ -1598,7 +2453,8 @@ body.dark,
1598
2453
  name: "conversation.session.header.actions",
1599
2454
  id: "agy-link-status",
1600
2455
  order: 10,
1601
- label: "Antigravity"
2456
+ label: () => locale ? locale.bind(NS)("section.label") : zh["section.label"],
2457
+ locale: NS
1602
2458
  }, AgySessionStatus);
1603
2459
  });
1604
2460
  ctx.slots.inject("settings.section", () => {
@@ -1606,9 +2462,11 @@ body.dark,
1606
2462
  name: "settings.section",
1607
2463
  id: "agy-link",
1608
2464
  order: 20,
1609
- label: "Antigravity"
2465
+ label: () => locale ? locale.bind(NS)("section.label") : zh["section.label"],
2466
+ locale: NS
1610
2467
  }, AgySettingsSection);
1611
2468
  });
2469
+ installAgyToolView(ctx);
1612
2470
  }
1613
2471
  //#endregion
1614
2472
  exports.apply = apply;