dsh-agy-link 0.4.49 → 0.4.53

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/CHANGELOG.md CHANGED
@@ -1,5 +1,65 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.53 (2026-10-15)
4
+
5
+ ### English
6
+
7
+ - **Fix: a NUL byte in a tool result can no longer kill the turn before agy starts.** Tool output that quoted binary content (an agent listing or reading a `.git` directory, a packed object, an image) carried U+0000 into the assembled prompt; because the prompt travels as an `-p` argv element, `spawn()` rejected it with `TypeError [ERR_INVALID_ARG_VALUE]: The argument 'args[16]' must be a string without null bytes` and the turn failed as "failed to spawn agy" before agy was ever launched — the retry re-sent the same history and failed identically, so the session could not make progress. `stripNulBytes()` now removes U+0000 from the rolling digest (per message text), from the final prompt on its single path into `buildArgs` — covering both the argv transport and the `--input-format stream-json` stdin transport — and from the one-shot/compaction prompt after file inlining. Every other control character is legal in argv and is left untouched.
8
+
9
+ ### 中文 (Chinese)
10
+
11
+ - **修复:工具结果里的 NUL 字节不再让整个轮次在 agy 启动前就崩溃。** 当工具输出引用了二进制内容(agent 列出或读取 `.git` 目录、packed object、图片等)时,U+0000 会被带进拼装好的提示词;提示词以 `-p` 参数进入 argv,`spawn()` 因此抛 `TypeError [ERR_INVALID_ARG_VALUE]: The argument 'args[16]' must be a string without null bytes`,整轮以「failed to spawn agy」失败——重试会重发同一段历史,于是同样失败,会话彻底卡死。现在 `stripNulBytes()` 会从三处移除 U+0000:滚动摘要中每条消息的文本、进入 `buildArgs` 前的最终提示词(同时覆盖 argv 与 `--input-format stream-json` 的 stdin 两种传输)、以及一次性/压缩任务内联文件后的提示词。其余控制字符在 argv 中合法,保持原样。
12
+
13
+ ## 0.4.52 (2026-10-15)
14
+
15
+ ### English
16
+
17
+ - **Fix: DSH `/goal` infinite continuation loop resolved.** In DSH, `/goal` runs a multi-round state machine (up to 256 rounds) that keeps injecting `<goal_round>` until the agent explicitly invokes `update_goal(action='complete')`. Because `agy` CLI uses its own native tools and terminates after finishing, DSH treated the goal as perpetually active (running up to 93+ rounds). The bridge now intercepts the completion event in goal mode and emits a synthetic `update_goal(complete)` tool-call, properly closing the goal state machine.
18
+ - **Feature: DSH `/plan` mode support.** When running in plan mode (prompt containing "You are in plan mode"), the bridge automatically emits the `exit_plan_mode` tool-call carrying the generated plan markdown for user interactive review and approval.
19
+ - **Feature: /agy goal command family.** Added `/agy goal [status|complete|pause|resume|clear]` allowing users to inspect the current goal state (ID, revision, objective, round count) or manually control/complete/clear the active goal.
20
+ - **Feature: /agy session command family.** Added `/agy session [info|clear]` for inspecting session workspace CWD, bound agy conversation ID, event counts, and safely dropping session bindings without guessing. `/agy clear` is also upgraded to prioritize the current session.
21
+ - **Feature: Intelligent quota-expiry-first rotation (Weekly & 5h).** Refactored pool candidate selection: when switching accounts, the pool prioritizes accounts whose weekly quota is expiring soonest (Priority 1: preventing irreversible weekly quota loss), followed by accounts whose 5-hour quota is expiring soonest (Priority 2). Execution maintains seamless sticky sequential drain, continuing on the current account until its quota is exhausted before cleanly advancing to the next optimal candidate.
22
+ - **UI: Simplified thinking turn banner.** Replaced verbose `[agy thinking turn · N thinking tokens]` chip with concise `[agy · N tokens]` (or `[agy]` when no count is present), avoiding long headers crowding out the collapsed reasoning summary in DSH.
23
+ - **UI: Removed redundant "Set as primary" action.** Dropped the confusing "Set as primary" button from the account cards across all locales. Manual account activation is cleanly handled via the existing "Set as in use" action.
24
+
25
+ ### 中文 (Chinese)
26
+
27
+ - **修复:彻底解决 DSH `/goal` 目标空转死循环问题。** DSH 的 `/goal` 状态机在模型未调用宿主原生 `update_goal(action='complete')` 前,会在每轮结束后自动注入 `<goal_round>` 启动下一轮(最多 256 轮,曾导致 93+ 轮空转)。由于 agy 是自闭环 Agent 引擎,完成任务后正常退出但不会直接调用 DSH 宿主工具。桥接层新增 `dsh-goal` 协议转换模块,在检测到目标轮次执行完毕后,自动向 DSH 发射 `update_goal(complete)` 工具调用块,使 DSH 目标状态机正常流转至完成态并停止重试。
28
+ - **功能:支持 DSH `/plan` 规划模式适配。** 当检测到会话处于规划模式(上下文携带 "You are in plan mode")时,桥接层在模型生成规划后自动发射 `exit_plan_mode` 工具调用块,将规划 Markdown 提交给 DSH 宿主渲染审核弹窗供用户审批。
29
+ - **功能:新增 `/agy goal` 目标管理命令。** 支持 `/agy goal [status|complete|pause|resume|clear]`,可实时查看当前会话的目标状态(目标 ID、版本号、目标描述、已执行轮次/上限),或手动完成、暂停、恢复、清除卡住的目标。
30
+ - **功能:新增 `/agy session` 会话管理命令。** 支持 `/agy session [info|clear]`,可精确查看当前 DSH 会话绑定的工作区 CWD、agy 会话 ID、事件总数等信息,或一键清除当前会话的 agy 绑定;同时增强 `/agy clear` 优先清理当前活动会话的绑定。
31
+ - **功能:按配额临期优先度智能轮换账号(周额度优先,5小时次优)。** 重构账号轮换调度算法:在切号时,第一优先级优先选取周额度剩余时间最少(最先到期)的账号(避免周硬额度清零浪费),第二优先级选取 5 小时窗口最先到期的账号;同时严格遵循无感黏性消耗原则(只要当前账号的 5 小时或周额度未耗尽绝不切号,耗尽后自动无缝切换下一个最优账号),最大化利用全部账号配额。
32
+ - **体验:精简思考过程标签(thinking banner)。** 将冗长的 `[agy thinking turn · N thinking tokens]` 简化为精炼的 `[agy · N tokens]`(无计数时为 `[agy]`),大幅缩减括号内字符宽度,让 DSH 折叠思考栏能够更完整展现具体的思维链与工具摘要。
33
+ - **体验:移除冗余的“设为主用”按钮。** 从前端控制台卡片操作栏和各语言包中彻底移除“设为主用”按钮,手动切换账号统一由“设为当前使用”负责,简化界面操作。
34
+
35
+ ## 0.4.51 (2026-10-15)
36
+
37
+ ### English
38
+
39
+ - **Feature: Per-account Enable / Disable toggles.** Added dedicated Enable / Disable buttons and status badges to each account card in the WebUI console and CLI (`/agy enable-account <id>`, `/agy disable-account <id>`). Disabled accounts are dimmed with a gray "Disabled" badge.
40
+ - **Fix: Account rotation and selection strictly skips disabled accounts.** Rotation algorithms (`selectAccount`, `advanceAccount`, sticky sequential drain, round-robin, and fallback model degradation) skip disabled accounts completely.
41
+ - **Fix: Cooldown & quota reset fallback never returns a disabled account.** `getActiveAccountId` previously fell back to `primaryAccountId` or the first account without checking `enabled`, which could surface a disabled slot as active; it now only falls back to eligible enabled accounts (or `undefined` if all accounts are disabled).
42
+ - **Fix: Background auth reverification skips disabled accounts.** `needsAuthReverify` now checks `account.enabled`, preventing the background quota poller from probing disabled slots.
43
+ - **Fix: Detailed error diagnostics when accounts are disabled.** When all or remaining accounts cannot serve a model family, the error message explicitly enumerates disabled slots and informs the user to re-enable them in the console or CLI rather than reporting misleading 0 cooldown/auth counts.
44
+
45
+ ### 中文 (Chinese)
46
+
47
+ - **功能:每个账号增加启用 / 停用按钮。** 在 WebUI 控制台的每个账号卡片上以及 CLI 命令中增加了启用/停用按钮与操作(`/agy enable-account <id>`、`/agy disable-account <id>`)。停用账号卡片以半透明呈现并标记灰色“已停用”徽章。
48
+ - **修复:切号与调度严格跳过停用账号。** 顺次耗尽(sequential)、轮询(round-robin)、主动切号(advanceAccount)、跨家族自动降级(autoFallbackModel)等调度算法在选号时全部严格跳过已停用账号。
49
+ - **修复:号池活动指针降级不再回退到已停用账号。** 修复了 `getActiveAccountId` 在全部账号限流时无条件 fallback 到主账号或首个账号导致停用账号被当作当前在用账号的缺陷;现在严格仅回退到已启用的账号(若全部账号均被停用则返回 `undefined`)。
50
+ - **修复:后台静默身份复验跳过已停用账号。** `needsAuthReverify` 增加了 `account.enabled` 检查,彻底杜绝后台轮询向 Google 请求复验已停用账号。
51
+ - **修复:账号不可用时清晰提示停用状态。** 当全部或部分账号不可用时,错误提示中会明确指出有几个账号处于“已停用”状态,并提示用户在控制台或通过 CLI 重新启用,不再报出误导性的 0 冷却/0 登录错误。
52
+
53
+ ## 0.4.50 (2026-10-14)
54
+
55
+ ### English
56
+
57
+ - **Fix: the header badge now names the same account as the console.** The session header applied an eligibility fallback chain (any enabled account, else `accounts[0]`) when the server-scheduled slot was cooling down or auth-flagged, so it displayed a DIFFERENT account than the console modal's "in use" badge. The header is a readout, not a picker: it now displays exactly `pool.activeAccountId`, with the dot turning red/amber for auth-required/cooldown states.
58
+
59
+ ### 中文 (Chinese)
60
+
61
+ - **修复:顶栏徽章与控制台显示同一个账号。** 当服务端调度的账号处于冷却或被标记时,会话顶栏会走降级链(任选其他合格账号,再不行取第 1 个),导致与控制台弹窗的"当前使用"徽章显示不同的账号。顶栏是状态显示器而不是选择器:现在严格显示 `pool.activeAccountId`,账号需重新登录/冷却中时圆点相应变红/变黄。
62
+
3
63
  ## 0.4.49 (2026-10-14)
4
64
 
5
65
  ### English
package/dist/client.js CHANGED
@@ -35,7 +35,8 @@ window.__ModuleLoader__.load({
35
35
  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",
36
36
  move: "M9 5h.01M9 12h.01M9 19h.01M15 5h.01M15 12h.01M15 19h.01",
37
37
  arrowUp: "M12 19V5M5 12l7-7 7 7",
38
- arrowDown: "M12 5v14M19 12l-7 7-7-7"
38
+ arrowDown: "M12 5v14M19 12l-7 7-7-7",
39
+ power: "M18.36 6.64a9 9 0 1 1-12.73 0M12 2v10"
39
40
  };
40
41
  //#endregion
41
42
  //#region src/client/locales.ts
@@ -72,7 +73,10 @@ window.__ModuleLoader__.load({
72
73
  "status.needsAuth": "待认证",
73
74
  "status.loading": "正在加载 Antigravity 状态...",
74
75
  "status.authRequired": "需重新登录",
75
- "status.primary": "主用",
76
+ "account.setActive": "设为当前使用",
77
+ "account.settingActive": "切换中",
78
+ "account.activeSet": "已切换当前使用账号",
79
+ "account.activeSetFailed": "该账号当前不可用(冷却或需重新登录)",
76
80
  "status.proxy": "代理",
77
81
  "status.inUse": "当前使用",
78
82
  "account.add": "添加 Google 账号",
@@ -85,8 +89,6 @@ window.__ModuleLoader__.load({
85
89
  "aria.proxyInput": "专属代理 URL",
86
90
  "aria.authCodeInput": "授权码或回调 URL",
87
91
  "aria.closeToast": "关闭提示",
88
- "account.setPrimary": "设为主用",
89
- "account.settingPrimary": "设置中",
90
92
  "account.details": "明细",
91
93
  "account.collapse": "收起",
92
94
  "account.expandOthers": "展开查看 Claude & GPT-OSS 模型额度",
@@ -171,7 +173,14 @@ window.__ModuleLoader__.load({
171
173
  "account.cardExpand": "展开详情",
172
174
  "account.draggingTarget": "放置至此成为优先级 #{num}",
173
175
  "account.dragBanner": "正在调整优先级:将【{alias}】移动至 #{num}",
174
- "account.dragHint": "拖拽卡片可实时调整调度优先级"
176
+ "account.dragHint": "拖拽卡片可实时调整调度优先级",
177
+ "account.enable": "启用",
178
+ "account.disable": "停用",
179
+ "account.enableTitle": "启用此账号(加入自动切号与调度)",
180
+ "account.disableTitle": "停用此账号(切号与调度时跳过)",
181
+ "account.enabledToast": "已启用账号:{alias}",
182
+ "account.disabledToast": "已停用账号:{alias}",
183
+ "status.disabled": "已停用"
175
184
  };
176
185
  const en = {
177
186
  "section.label": "Antigravity",
@@ -203,7 +212,10 @@ window.__ModuleLoader__.load({
203
212
  "status.needsAuth": "Authentication required",
204
213
  "status.loading": "Loading Antigravity status...",
205
214
  "status.authRequired": "Sign in again required",
206
- "status.primary": "Primary",
215
+ "account.setActive": "Set as in use",
216
+ "account.settingActive": "Switching...",
217
+ "account.activeSet": "Active account switched",
218
+ "account.activeSetFailed": "Account not currently eligible (cooldown or auth required)",
207
219
  "status.proxy": "Proxy",
208
220
  "status.inUse": "In use",
209
221
  "account.add": "Add Google account",
@@ -216,8 +228,6 @@ window.__ModuleLoader__.load({
216
228
  "aria.proxyInput": "Dedicated proxy URL",
217
229
  "aria.authCodeInput": "Authorization code or callback URL",
218
230
  "aria.closeToast": "Dismiss notification",
219
- "account.setPrimary": "Set as primary",
220
- "account.settingPrimary": "Setting...",
221
231
  "account.details": "Details",
222
232
  "account.collapse": "Collapse",
223
233
  "account.expandOthers": "Expand Claude & GPT-OSS quotas",
@@ -302,7 +312,14 @@ window.__ModuleLoader__.load({
302
312
  "account.cardExpand": "Expand details",
303
313
  "account.draggingTarget": "Drop here to set priority #{num}",
304
314
  "account.dragBanner": "Reordering: moving [{alias}] to priority #{num}",
305
- "account.dragHint": "Drag cards to reorder scheduling priority in real time"
315
+ "account.dragHint": "Drag cards to reorder scheduling priority in real time",
316
+ "account.enable": "Enable",
317
+ "account.disable": "Disable",
318
+ "account.enableTitle": "Enable this account (include in rotation)",
319
+ "account.disableTitle": "Disable this account (skip in rotation)",
320
+ "account.enabledToast": "Account enabled: {alias}",
321
+ "account.disabledToast": "Account disabled: {alias}",
322
+ "status.disabled": "Disabled"
306
323
  };
307
324
  const ptBR = {
308
325
  "section.label": "Antigravity",
@@ -334,7 +351,10 @@ window.__ModuleLoader__.load({
334
351
  "status.needsAuth": "Autenticação necessária",
335
352
  "status.loading": "Carregando o status do Antigravity...",
336
353
  "status.authRequired": "É necessário entrar novamente",
337
- "status.primary": "Principal",
354
+ "account.setActive": "Definir como em uso",
355
+ "account.settingActive": "Alternando...",
356
+ "account.activeSet": "Conta em uso alterada",
357
+ "account.activeSetFailed": "Conta não elegível no momento (cooldown ou login necessário)",
338
358
  "status.proxy": "Proxy",
339
359
  "status.inUse": "Em uso",
340
360
  "account.add": "Adicionar conta Google",
@@ -347,8 +367,6 @@ window.__ModuleLoader__.load({
347
367
  "aria.proxyInput": "URL do proxy exclusivo",
348
368
  "aria.authCodeInput": "Código de autorização ou URL de retorno",
349
369
  "aria.closeToast": "Fechar notificação",
350
- "account.setPrimary": "Definir como principal",
351
- "account.settingPrimary": "Definindo...",
352
370
  "account.details": "Detalhes",
353
371
  "account.collapse": "Recolher",
354
372
  "account.expandOthers": "Expandir cotas Claude e GPT-OSS",
@@ -433,7 +451,14 @@ window.__ModuleLoader__.load({
433
451
  "account.cardExpand": "Expandir detalhes",
434
452
  "account.draggingTarget": "Soltar aqui para prioridade #{num}",
435
453
  "account.dragBanner": "Reordenando: movendo [{alias}] para #{num}",
436
- "account.dragHint": "Arraste os cartões para reordenar a prioridade em tempo real"
454
+ "account.dragHint": "Arraste os cartões para reordenar a prioridade em tempo real",
455
+ "account.enable": "Ativar",
456
+ "account.disable": "Desativar",
457
+ "account.enableTitle": "Ativar esta conta (incluir na rotação)",
458
+ "account.disableTitle": "Desativar esta conta (ignorar na rotação)",
459
+ "account.enabledToast": "Conta ativada: {alias}",
460
+ "account.disabledToast": "Conta desativada: {alias}",
461
+ "status.disabled": "Desativado"
437
462
  };
438
463
  const es = {
439
464
  "section.label": "Antigravity",
@@ -465,7 +490,10 @@ window.__ModuleLoader__.load({
465
490
  "status.needsAuth": "Se requiere autenticación",
466
491
  "status.loading": "Cargando el estado de Antigravity...",
467
492
  "status.authRequired": "Debe iniciar sesión de nuevo",
468
- "status.primary": "Principal",
493
+ "account.setActive": "Establecer como en uso",
494
+ "account.settingActive": "Cambiando...",
495
+ "account.activeSet": "Cuenta en uso cambiada",
496
+ "account.activeSetFailed": "Cuenta no elegible ahora (enfriamiento o requiere login)",
469
497
  "status.proxy": "Proxy",
470
498
  "status.inUse": "En uso",
471
499
  "account.add": "Añadir cuenta de Google",
@@ -478,8 +506,6 @@ window.__ModuleLoader__.load({
478
506
  "aria.proxyInput": "URL de proxy exclusivo",
479
507
  "aria.authCodeInput": "Código de autorización o URL de devolución",
480
508
  "aria.closeToast": "Cerrar notificación",
481
- "account.setPrimary": "Establecer como principal",
482
- "account.settingPrimary": "Configurando...",
483
509
  "account.details": "Detalles",
484
510
  "account.collapse": "Contraer",
485
511
  "account.expandOthers": "Expandir cuotas Claude y GPT-OSS",
@@ -564,7 +590,14 @@ window.__ModuleLoader__.load({
564
590
  "account.cardExpand": "Expandir detalles",
565
591
  "account.draggingTarget": "Soltar aquí para prioridad #{num}",
566
592
  "account.dragBanner": "Reordenando: moviendo [{alias}] a #{num}",
567
- "account.dragHint": "Arrastre las tarjetas para reordenar la prioridad en tiempo real"
593
+ "account.dragHint": "Arrastre las tarjetas para reordenar la prioridad en tiempo real",
594
+ "account.enable": "Habilitar",
595
+ "account.disable": "Deshabilitar",
596
+ "account.enableTitle": "Habilitar esta cuenta (incluir en rotación)",
597
+ "account.disableTitle": "Deshabilitar esta cuenta (ignorar en rotación)",
598
+ "account.enabledToast": "Cuenta habilitada: {alias}",
599
+ "account.disabledToast": "Cuenta deshabilitada: {alias}",
600
+ "status.disabled": "Deshabilitado"
568
601
  };
569
602
  //#endregion
570
603
  //#region src/client/decisions.ts
@@ -3174,7 +3207,7 @@ div[data-variant="think"] [class*="thinkBody"] {
3174
3207
  if (!body) return;
3175
3208
  const text = (body.textContent ?? "").trim();
3176
3209
  if (text === "") return;
3177
- if (/^\[agy thinking turn(?: · \d+ thinking tokens)?\]\s*$/.test(text)) return;
3210
+ if (/^\[agy(?: thinking turn)?(?: · \d+ (?:thinking )?tokens)?\]\s*$/.test(text)) return;
3178
3211
  const style = typeof getComputedStyle === "function" ? getComputedStyle(body) : null;
3179
3212
  if (style && style.display !== "none" && body.clientHeight > 8) {
3180
3213
  root.dataset.agyExpanded = "true";
@@ -3283,9 +3316,7 @@ div[data-variant="think"] [class*="thinkBody"] {
3283
3316
  if (typeof AbortSignal !== "undefined" && typeof AbortSignal.timeout === "function") init.signal = AbortSignal.timeout(5e3);
3284
3317
  const res = await win.fetch?.(base + "/plugins/agy-link/status", init);
3285
3318
  if (!res || !res.ok) return null;
3286
- const payload = await res.json();
3287
- statusCache = payload;
3288
- return payload;
3319
+ return await res.json();
3289
3320
  } catch {
3290
3321
  return null;
3291
3322
  }
@@ -3394,20 +3425,35 @@ div[data-variant="think"] [class*="thinkBody"] {
3394
3425
  }
3395
3426
  };
3396
3427
  /**
3397
- * Singleton modal instance manager: ensures at most one modal dialog is
3398
- * rendered into document.body at any time even when multiple session headers mount.
3428
+ * Global modal host manager: ensures exactly one mounted session header
3429
+ * hosts the modal dialog portal reactively. If the primary header unmounts,
3430
+ * the next available header is immediately notified and seamlessly takes over.
3399
3431
  */
3400
- let primaryModalInstanceId = null;
3401
- const modalInstanceIds = /* @__PURE__ */ new Set();
3402
- function registerModalInstance(id) {
3403
- modalInstanceIds.add(id);
3404
- if (primaryModalInstanceId === null || !modalInstanceIds.has(primaryModalInstanceId)) primaryModalInstanceId = id;
3405
- return primaryModalInstanceId === id;
3406
- }
3407
- function unregisterModalInstance(id) {
3408
- modalInstanceIds.delete(id);
3409
- if (primaryModalInstanceId === id) primaryModalInstanceId = modalInstanceIds.values().next().value ?? null;
3410
- }
3432
+ const modalHostManager = {
3433
+ listeners: /* @__PURE__ */ new Map(),
3434
+ activeId: null,
3435
+ register(id, onChange) {
3436
+ modalHostManager.listeners.set(id, onChange);
3437
+ if (modalHostManager.activeId === null) modalHostManager.activeId = id;
3438
+ onChange(modalHostManager.activeId === id);
3439
+ return () => {
3440
+ modalHostManager.listeners.delete(id);
3441
+ if (modalHostManager.activeId === id) {
3442
+ const next = modalHostManager.listeners.keys().next().value ?? null;
3443
+ modalHostManager.activeId = next;
3444
+ if (next) modalHostManager.listeners.get(next)?.(true);
3445
+ }
3446
+ };
3447
+ },
3448
+ claim(id) {
3449
+ if (modalHostManager.activeId !== id) {
3450
+ const prev = modalHostManager.activeId;
3451
+ modalHostManager.activeId = id;
3452
+ if (prev) modalHostManager.listeners.get(prev)?.(false);
3453
+ modalHostManager.listeners.get(id)?.(true);
3454
+ }
3455
+ }
3456
+ };
3411
3457
  /**
3412
3458
  * Format reset timestamp into readable date/time + relative countdown.
3413
3459
  */
@@ -3475,10 +3521,9 @@ div[data-variant="think"] [class*="thinkBody"] {
3475
3521
  anthropic: "claude",
3476
3522
  openai: "openai"
3477
3523
  };
3478
- let agyIconSeq = 0;
3479
3524
  /** Antigravity mark: gradient orb with an orbit ring (product has no public simple-icon). */
3480
3525
  function agyIcon(size = 14) {
3481
- const gid = `agy-g${++agyIconSeq}`;
3526
+ const gid = "agy-icon-linear-gradient";
3482
3527
  return h("svg", {
3483
3528
  viewBox: "0 0 24 24",
3484
3529
  width: size,
@@ -3929,7 +3974,7 @@ body.dark,
3929
3974
  marginBottom: "8px",
3930
3975
  color: "var(--agy-text-primary)",
3931
3976
  boxShadow: "var(--agy-shadow-card)",
3932
- transition: "all 0.18s cubic-bezier(0.16, 1, 0.3, 1)"
3977
+ transition: "border-color 0.18s ease, box-shadow 0.18s ease, opacity 0.18s ease"
3933
3978
  },
3934
3979
  cardPrimary: {
3935
3980
  background: "var(--agy-bg-card)",
@@ -3939,7 +3984,7 @@ body.dark,
3939
3984
  marginBottom: "8px",
3940
3985
  color: "var(--agy-text-primary)",
3941
3986
  boxShadow: "var(--agy-shadow-card)",
3942
- transition: "all 0.18s cubic-bezier(0.16, 1, 0.3, 1)"
3987
+ transition: "border-color 0.18s ease, box-shadow 0.18s ease, opacity 0.18s ease"
3943
3988
  },
3944
3989
  quotaBox: {
3945
3990
  background: "var(--agy-bg-box)",
@@ -4085,6 +4130,7 @@ body.dark,
4085
4130
  }
4086
4131
  };
4087
4132
  function apply(ctx) {
4133
+ injectGlobalStyles();
4088
4134
  const locale = ctx.locale;
4089
4135
  ctx.effect(() => {
4090
4136
  const disposePtBR = locale.addLanguage({
@@ -4116,7 +4162,6 @@ body.dark,
4116
4162
  return locale.bind(NS);
4117
4163
  };
4118
4164
  const AgySettingsSection = (props) => {
4119
- injectGlobalStyles();
4120
4165
  const t = useAgyTranslation();
4121
4166
  const { status, refresh } = useAgyStatus();
4122
4167
  const [aliasInput, setAliasInput] = useState("");
@@ -4262,9 +4307,18 @@ body.dark,
4262
4307
  if (hiddenPendingRef.current === next) hiddenPendingRef.current = null;
4263
4308
  }
4264
4309
  };
4265
- const setPrimary = async (id) => {
4266
- setLoadingAction(`primary:${id}`);
4267
- const res = await postJson("/plugins/agy-link/pool/primary", { id });
4310
+ const setActiveAccount = async (id) => {
4311
+ setLoadingAction(`active:${id}`);
4312
+ const res = await postJson("/plugins/agy-link/pool/set-active", { id });
4313
+ setLoadingAction(null);
4314
+ if (res && res.ok) {
4315
+ await refresh();
4316
+ showToast(t("account.activeSet"), "success");
4317
+ } else showToast(res?.error || t("account.activeSetFailed"), "error");
4318
+ };
4319
+ const removeAccount = async (id, alias) => {
4320
+ setLoadingAction(`remove:${id}`);
4321
+ const res = await postJson("/plugins/agy-link/pool/remove", { id });
4268
4322
  if (res === null || res?.ok === false) {
4269
4323
  setLoadingAction(null);
4270
4324
  actionFailed();
@@ -4272,11 +4326,15 @@ body.dark,
4272
4326
  }
4273
4327
  await refresh();
4274
4328
  setLoadingAction(null);
4275
- showToast(t("account.setPrimary"), "success");
4329
+ showToast(t("account.removed", { alias }), "info");
4276
4330
  };
4277
- const removeAccount = async (id, alias) => {
4278
- setLoadingAction(`remove:${id}`);
4279
- const res = await postJson("/plugins/agy-link/pool/remove", { id });
4331
+ const toggleAccountEnabled = async (acc) => {
4332
+ const nextState = !acc.enabled;
4333
+ setLoadingAction(`enable:${acc.id}`);
4334
+ const res = await postJson("/plugins/agy-link/pool/enable", {
4335
+ id: acc.id,
4336
+ enabled: nextState
4337
+ });
4280
4338
  if (res === null || res?.ok === false) {
4281
4339
  setLoadingAction(null);
4282
4340
  actionFailed();
@@ -4284,7 +4342,9 @@ body.dark,
4284
4342
  }
4285
4343
  await refresh();
4286
4344
  setLoadingAction(null);
4287
- showToast(t("account.removed", { alias }), "info");
4345
+ const legacyDefault = acc.defaultAlias === void 0 && acc.systemHome && acc.alias === "主账号 (系统登录)";
4346
+ const displayAlias = acc.defaultAlias || legacyDefault ? acc.systemHome ? t("account.defaultAlias") : acc.alias : acc.alias;
4347
+ showToast(t(nextState ? "account.enabledToast" : "account.disabledToast", { alias: displayAlias }), "success");
4288
4348
  };
4289
4349
  const refreshQuota = async (id) => {
4290
4350
  setLoadingAction(id ? `refresh:${id}` : "refresh:all");
@@ -4509,7 +4569,8 @@ body.dark,
4509
4569
  ...S.card,
4510
4570
  cursor: "default",
4511
4571
  padding: "10px 14px",
4512
- marginBottom: "8px"
4572
+ marginBottom: "8px",
4573
+ ...!acc.enabled ? { opacity: .72 } : {}
4513
4574
  };
4514
4575
  const legacyDefault = acc.defaultAlias === void 0 && acc.systemHome && acc.alias === "主账号 (系统登录)";
4515
4576
  const displayAlias = acc.defaultAlias || legacyDefault ? acc.systemHome ? t("account.defaultAlias") : t("account.aliasDefault", { number: accIndex + 1 }) : acc.alias;
@@ -4640,12 +4701,7 @@ body.dark,
4640
4701
  whiteSpace: "nowrap"
4641
4702
  },
4642
4703
  title: acc.email
4643
- }, uiIcon("mail", 10, "var(--agy-badge-email-text)"), acc.email) : null, isPrimary ? h("span", { style: {
4644
- ...S.badgePrimary,
4645
- gap: "3px",
4646
- fontSize: "10.5px",
4647
- padding: "1px 5px"
4648
- } }, uiIcon("star", 10, "var(--agy-badge-primary-text)"), t("status.primary")) : null, isInUse ? h("span", {
4704
+ }, uiIcon("mail", 10, "var(--agy-badge-email-text)"), acc.email) : null, isInUse ? h("span", {
4649
4705
  style: {
4650
4706
  ...S.badgeTag,
4651
4707
  background: "rgba(16, 185, 129, 0.12)",
@@ -4665,7 +4721,19 @@ body.dark,
4665
4721
  gap: "4px",
4666
4722
  fontSize: "10px",
4667
4723
  padding: "1px 5px"
4668
- } }, uiIcon("globe", 10, "var(--agy-badge-proxy-text)"), t("status.proxy")) : null), h("div", { style: {
4724
+ } }, uiIcon("globe", 10, "var(--agy-badge-proxy-text)"), t("status.proxy")) : null, !acc.enabled ? h("span", {
4725
+ style: {
4726
+ ...S.badgeTag,
4727
+ background: "var(--agy-bg-subbox)",
4728
+ color: "var(--agy-text-secondary)",
4729
+ borderColor: "var(--agy-border-subbox)",
4730
+ gap: "3px",
4731
+ fontSize: "10px",
4732
+ padding: "1px 5px",
4733
+ fontWeight: 700
4734
+ },
4735
+ title: t("status.disabled")
4736
+ }, uiIcon("power", 10, "#64748b"), t("status.disabled")) : null), h("div", { style: {
4669
4737
  display: "flex",
4670
4738
  gap: "4px",
4671
4739
  alignItems: "center",
@@ -4673,19 +4741,21 @@ body.dark,
4673
4741
  } }, h("button", {
4674
4742
  type: "button",
4675
4743
  className: "agy-btn",
4676
- style: isExpanded ? {
4677
- ...S.btnSmPrimary,
4744
+ style: acc.enabled ? {
4745
+ ...S.btnSm,
4678
4746
  gap: "3px",
4679
4747
  fontSize: "11px",
4680
4748
  padding: "2px 6px"
4681
4749
  } : {
4682
- ...S.btnSm,
4750
+ ...S.btnSmPrimary,
4683
4751
  gap: "3px",
4684
4752
  fontSize: "11px",
4685
4753
  padding: "2px 6px"
4686
4754
  },
4687
- onClick: () => toggleExpand(acc.id)
4688
- }, isExpanded ? [uiIcon("chevronUp", 10), ` ${t("account.collapse")}`] : [uiIcon("chevronDown", 10), ` ${t("account.otherModels")}`]), h("button", {
4755
+ title: acc.enabled ? t("account.disableTitle") : t("account.enableTitle"),
4756
+ disabled: isBusy,
4757
+ onClick: () => void toggleAccountEnabled(acc)
4758
+ }, loadingAction === `enable:${acc.id}` ? [renderSpinner(), acc.enabled ? t("account.disable") : t("account.enable")] : [uiIcon("power", 10, acc.enabled ? "var(--agy-text-secondary)" : "#ffffff"), ` ${acc.enabled ? t("account.disable") : t("account.enable")}`]), h("button", {
4689
4759
  type: "button",
4690
4760
  className: "agy-btn",
4691
4761
  style: {
@@ -4697,7 +4767,7 @@ body.dark,
4697
4767
  title: t("account.syncTitle"),
4698
4768
  disabled: isBusy,
4699
4769
  onClick: () => void refreshQuota(acc.id)
4700
- }, loadingAction === `refresh:${acc.id}` ? [renderSpinner(), t("account.syncing")] : [uiIcon("refresh", 10), ` ${t("account.sync")}`]), !isPrimary ? h("button", {
4770
+ }, loadingAction === `refresh:${acc.id}` ? [renderSpinner(), t("account.syncing")] : [uiIcon("refresh", 10), ` ${t("account.sync")}`]), acc.enabled && !isInUse ? h("button", {
4701
4771
  type: "button",
4702
4772
  className: "agy-btn",
4703
4773
  style: {
@@ -4706,9 +4776,10 @@ body.dark,
4706
4776
  fontSize: "11px",
4707
4777
  padding: "2px 6px"
4708
4778
  },
4779
+ title: t("account.setActive"),
4709
4780
  disabled: isBusy,
4710
- onClick: () => void setPrimary(acc.id)
4711
- }, loadingAction === `primary:${acc.id}` ? [renderSpinner(), t("account.settingPrimary")] : [uiIcon("star", 10), ` ${t("account.setPrimary")}`]) : null, h("button", {
4781
+ onClick: () => void setActiveAccount(acc.id)
4782
+ }, loadingAction === `active:${acc.id}` ? [renderSpinner(), t("account.settingActive")] : [uiIcon("zap", 10), ` ${t("account.setActive")}`]) : null, h("button", {
4712
4783
  type: "button",
4713
4784
  className: "agy-btn",
4714
4785
  style: isEditingProxy ? {
@@ -5123,25 +5194,8 @@ body.dark,
5123
5194
  };
5124
5195
  /** Modal dialog container rendered into document.body */
5125
5196
  const AgyModalDialog = () => {
5126
- const instanceIdRef = useRef(null);
5127
- if (instanceIdRef.current === null) instanceIdRef.current = "modal_" + Math.random().toString(36).slice(2);
5128
- const isPrimary = registerModalInstance(instanceIdRef.current);
5129
- useEffect(() => {
5130
- const id = instanceIdRef.current;
5131
- return () => {
5132
- unregisterModalInstance(id);
5133
- };
5134
- }, []);
5135
5197
  const t = useAgyTranslation();
5136
- const [isOpen, setIsOpen] = useState(agyModalStore.open);
5137
5198
  useEffect(() => {
5138
- injectGlobalStyles();
5139
- return agyModalStore.subscribe(() => {
5140
- setIsOpen(agyModalStore.open);
5141
- });
5142
- }, []);
5143
- useEffect(() => {
5144
- if (!isOpen) return;
5145
5199
  const onKey = (e) => {
5146
5200
  if (e.key === "Escape") {
5147
5201
  e.stopPropagation();
@@ -5150,8 +5204,7 @@ body.dark,
5150
5204
  };
5151
5205
  win.addEventListener?.("keydown", onKey, true);
5152
5206
  return () => win.removeEventListener?.("keydown", onKey, true);
5153
- }, [isOpen]);
5154
- if (!isOpen || !isPrimary) return null;
5207
+ }, []);
5155
5208
  return h("div", {
5156
5209
  className: "agy-modal-backdrop",
5157
5210
  onClick: (e) => {
@@ -5208,6 +5261,19 @@ body.dark,
5208
5261
  };
5209
5262
  /** Session Header badge in chat toolbar */
5210
5263
  const AgySessionStatus = () => {
5264
+ const hostIdRef = useRef(null);
5265
+ if (hostIdRef.current === null) hostIdRef.current = "host_" + Math.random().toString(36).slice(2);
5266
+ const hostId = hostIdRef.current;
5267
+ const [isPrimaryHost, setIsPrimaryHost] = useState(false);
5268
+ useEffect(() => {
5269
+ return modalHostManager.register(hostId, setIsPrimaryHost);
5270
+ }, [hostId]);
5271
+ const [isModalOpen, setIsModalOpen] = useState(agyModalStore.open);
5272
+ useEffect(() => {
5273
+ return agyModalStore.subscribe(() => {
5274
+ setIsModalOpen(agyModalStore.open);
5275
+ });
5276
+ }, []);
5211
5277
  const t = useAgyTranslation();
5212
5278
  const { status } = useAgyStatus();
5213
5279
  const [, setTimeTick] = useState(0);
@@ -5218,11 +5284,12 @@ body.dark,
5218
5284
  const pool = status?.pool;
5219
5285
  const accounts = pool?.accounts ?? [];
5220
5286
  const rawActiveId = pool?.activeAccountId ?? pool?.lastActiveAccountId ?? pool?.activeAccountIds?.google ?? pool?.primaryAccountId;
5221
- const activeAcc = accounts.find((a) => a.id === rawActiveId && a.enabled && !a.authRequired) ?? accounts.find((a) => a.enabled && !a.authRequired) ?? accounts[0];
5287
+ const activeAcc = rawActiveId !== void 0 ? accounts.find((a) => a.id === rawActiveId) : void 0;
5288
+ const activeAuthRequired = activeAcc?.authRequired === true;
5222
5289
  const activeHasCooldown = activeAcc ? Object.entries(activeAcc.cooldowns ?? {}).some(([, cd]) => cd && cd.cooldownUntil > Date.now()) : false;
5223
5290
  const allInCooldown = accounts.length > 0 && accounts.every((a) => !a.enabled || a.authRequired || Object.entries(a.cooldowns ?? {}).some(([, cd]) => cd && cd.cooldownUntil > Date.now()));
5224
5291
  const isAuthed = status?.auth?.phase === "ok" || accounts.length > 0;
5225
- const color = status === null ? "#64748b" : status.dormantReason ? "#f59e0b" : allInCooldown ? "#f59e0b" : activeHasCooldown ? "#f59e0b" : isAuthed ? "#10b981" : "#f59e0b";
5292
+ const color = status === null ? "#64748b" : status.dormantReason ? "#f59e0b" : allInCooldown ? "#f59e0b" : activeAuthRequired ? "#ef4444" : activeHasCooldown ? "#f59e0b" : isAuthed ? "#10b981" : "#f59e0b";
5226
5293
  const legacyDefault = activeAcc && activeAcc.defaultAlias === void 0 && activeAcc.systemHome && activeAcc.alias === "主账号 (系统登录)";
5227
5294
  const displayAlias = activeAcc ? activeAcc.defaultAlias || legacyDefault ? activeAcc.systemHome ? t("account.defaultAlias") : t("account.aliasDefault", { number: accounts.indexOf(activeAcc) + 1 }) : activeAcc.alias : "";
5228
5295
  const activeEligible = activeAcc !== void 0 && activeAcc.enabled && !activeAcc.authRequired;
@@ -5233,7 +5300,10 @@ body.dark,
5233
5300
  type: "button",
5234
5301
  title: badgeTitle,
5235
5302
  className: "agy-btn",
5236
- onClick: () => agyModalStore.setOpen(true),
5303
+ onClick: () => {
5304
+ modalHostManager.claim(hostId);
5305
+ agyModalStore.setOpen(true);
5306
+ },
5237
5307
  style: {
5238
5308
  background: "var(--agy-bg-header)",
5239
5309
  border: "1px solid var(--agy-border-card)",
@@ -5256,7 +5326,7 @@ body.dark,
5256
5326
  borderRadius: "50%",
5257
5327
  background: color
5258
5328
  } }), buttonLabel);
5259
- return h("div", { style: { display: "inline-block" } }, badge, portalToBody(h(AgyModalDialog, null)));
5329
+ return h("div", { style: { display: "inline-block" } }, badge, isPrimaryHost && isModalOpen ? portalToBody(h(AgyModalDialog, null)) : null);
5260
5330
  };
5261
5331
  ctx.slots.inject("conversation.session.header.actions", () => {
5262
5332
  return ctx.slots.register({