@songsid/agend 2.3.5-alpha.3 → 2.3.5-alpha.4

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.
Files changed (101) hide show
  1. package/dist/agent-runtime-api.d.ts +4 -0
  2. package/dist/agent-runtime-api.js +59 -0
  3. package/dist/agent-runtime-api.js.map +1 -0
  4. package/dist/agent-runtime-change.d.ts +74 -0
  5. package/dist/agent-runtime-change.js +102 -0
  6. package/dist/agent-runtime-change.js.map +1 -0
  7. package/dist/auth-api.js +4 -0
  8. package/dist/auth-api.js.map +1 -1
  9. package/dist/backend/codex-footer-hints.d.ts +4 -0
  10. package/dist/backend/codex-footer-hints.js +27 -0
  11. package/dist/backend/codex-footer-hints.js.map +1 -0
  12. package/dist/backend/codex.js +8 -10
  13. package/dist/backend/codex.js.map +1 -1
  14. package/dist/fleet-manager.d.ts +3 -0
  15. package/dist/fleet-manager.js +157 -1
  16. package/dist/fleet-manager.js.map +1 -1
  17. package/dist/fleet-windows-runtime.d.ts +6 -1
  18. package/dist/fleet-windows-runtime.js +70 -1
  19. package/dist/fleet-windows-runtime.js.map +1 -1
  20. package/dist/office-share-page.js +1 -1
  21. package/dist/office-share-page.js.map +1 -1
  22. package/dist/public-web-link.d.ts +6 -0
  23. package/dist/public-web-link.js +9 -0
  24. package/dist/public-web-link.js.map +1 -1
  25. package/dist/runtime-api.js +84 -9
  26. package/dist/runtime-api.js.map +1 -1
  27. package/dist/runtime-build-info.d.ts +9 -0
  28. package/dist/runtime-build-info.js +57 -0
  29. package/dist/runtime-build-info.js.map +1 -0
  30. package/dist/runtime-bundle.d.ts +3 -1
  31. package/dist/runtime-bundle.js +14 -8
  32. package/dist/runtime-bundle.js.map +1 -1
  33. package/dist/runtime-cli.d.ts +6 -1
  34. package/dist/runtime-cli.js +46 -11
  35. package/dist/runtime-cli.js.map +1 -1
  36. package/dist/runtime-client-store.d.ts +20 -1
  37. package/dist/runtime-client-store.js +56 -4
  38. package/dist/runtime-client-store.js.map +1 -1
  39. package/dist/runtime-client.d.ts +12 -0
  40. package/dist/runtime-client.js +43 -7
  41. package/dist/runtime-client.js.map +1 -1
  42. package/dist/runtime-connection.d.ts +22 -0
  43. package/dist/runtime-connection.js +93 -0
  44. package/dist/runtime-connection.js.map +1 -0
  45. package/dist/runtime-coordinator.d.ts +9 -0
  46. package/dist/runtime-coordinator.js +81 -34
  47. package/dist/runtime-coordinator.js.map +1 -1
  48. package/dist/runtime-pane-boot.d.ts +7 -0
  49. package/dist/runtime-pane-boot.js +23 -0
  50. package/dist/runtime-pane-boot.js.map +1 -1
  51. package/dist/runtime-protocol.d.ts +13 -0
  52. package/dist/runtime-protocol.js +18 -0
  53. package/dist/runtime-protocol.js.map +1 -1
  54. package/dist/settings-effect.js +1 -1
  55. package/dist/settings-effect.js.map +1 -1
  56. package/dist/settings-http-confirmation.d.ts +3 -0
  57. package/dist/settings-http-confirmation.js +16 -1
  58. package/dist/settings-http-confirmation.js.map +1 -1
  59. package/dist/ui/agent-runtime-settings.js +67 -0
  60. package/dist/ui/agent-runtime.js +20 -2
  61. package/dist/ui/backend-signin.js +36 -21
  62. package/dist/ui/native-instance-settings.js +5 -8
  63. package/dist/ui/panel-chat.js +3 -2
  64. package/dist/ui/panel-fleet.js +4 -2
  65. package/dist/ui/panel-runtimes.js +136 -33
  66. package/dist/ui/panel-settings.js +224 -139
  67. package/dist/ui/panel-windows-cli.js +77 -41
  68. package/dist/ui/settings-agent-filters.js +94 -0
  69. package/dist/ui/settings-appearance.js +122 -23
  70. package/dist/ui/settings-dialogs.js +5 -5
  71. package/dist/ui/settings-guests.js +25 -11
  72. package/dist/ui/settings-layout.js +245 -0
  73. package/dist/ui/settings-model.js +8 -0
  74. package/dist/ui/settings-registry.js +5 -3
  75. package/dist/ui/settings-shortcuts.js +65 -0
  76. package/dist/ui/settings-strings.js +93 -0
  77. package/dist/ui/settings-web-authority.js +14 -11
  78. package/dist/ui/shared/app-shell.js +17 -7
  79. package/dist/ui/shared/app.css +1022 -12
  80. package/dist/ui/shared/instance-nav.js +3 -2
  81. package/dist/ui/shared/machine-name.js +13 -0
  82. package/dist/ui/shared/settings-width.js +31 -0
  83. package/dist/ui/shared/tokens.css +1 -0
  84. package/dist/ui/shared/ui-dialog.js +2 -2
  85. package/dist/ui/shared/ui-menu.js +3 -3
  86. package/dist/ui/windows-cli-catalog.js +41 -12
  87. package/dist/web-api.d.ts +1 -0
  88. package/dist/web-api.js +3 -0
  89. package/dist/web-api.js.map +1 -1
  90. package/dist/windows-agents.d.ts +3 -1
  91. package/dist/windows-agents.js +13 -10
  92. package/dist/windows-agents.js.map +1 -1
  93. package/dist/windows-cli-api.js +2 -1
  94. package/dist/windows-cli-api.js.map +1 -1
  95. package/dist/windows-cli-worker.d.ts +1 -0
  96. package/dist/windows-cli-worker.js +2 -2
  97. package/dist/windows-cli-worker.js.map +1 -1
  98. package/dist/windows-cli.d.ts +1 -0
  99. package/dist/windows-cli.js +7 -2
  100. package/dist/windows-cli.js.map +1 -1
  101. package/package.json +1 -1
@@ -16,6 +16,7 @@ import { confirmDialog } from "/assets/ui-confirm.js";
16
16
  import { api } from "./settings-confirm.js";
17
17
  import { DEFAULT_SCHEMA, impactOf } from "./settings-model.js";
18
18
  import "./settings-strings.js";
19
+ import { layoutText } from "./settings-layout.js";
19
20
 
20
21
  const tn = (k, ...v) => t(`settings.${k}`, ...v);
21
22
  const PLATFORMS = { discord: "Discord", telegram: "Telegram" };
@@ -93,6 +94,7 @@ export function GuestsSection({ ctx, staged }) {
93
94
  const [overview, setOverview] = useState(null), [failed, setFailed] = useState(false);
94
95
  const [people, setPeople] = useState(null), [query, setQuery] = useState(""), [editing, setEditing] = useState(null);
95
96
  const [sendState, setSendState] = useState({}), [now, setNow] = useState(() => Date.now());
97
+ const [viewedGuest, setViewedGuest] = useState(null);
96
98
  const [status, setStatus] = useState(null); // { kind: "waiting" } | { kind: "failed", error }
97
99
  const load = async () => {
98
100
  let r = null;
@@ -211,6 +213,7 @@ export function GuestsSection({ ctx, staged }) {
211
213
  };
212
214
 
213
215
  const rows = f.grants ? [...new Set([...Object.keys(f.grants), ...Object.keys(original.grants || {})])] : [];
216
+ const activeGuest = rows.includes(editing) ? editing : rows.includes(viewedGuest) ? viewedGuest : rows[0];
214
217
  const q = query.trim().toLowerCase();
215
218
  const candidates = (people || []).filter((p) => !(f.grants && own(f.grants, accountKey(p))))
216
219
  .filter((p) => !q || [p.name, p.username, p.userId].some((x) => typeof x === "string" && x.toLowerCase().includes(q))).slice(0, 8);
@@ -218,21 +221,32 @@ export function GuestsSection({ ctx, staged }) {
218
221
  return html`<section class="card s-guests" id="guests"><h3>${tn("guestsTitle")}</h3>
219
222
  <p class="note">${tn("grantsHint")}</p>
220
223
  <fieldset class="s-guest-fields" disabled=${readOnly}>
221
- <label class=${`check${f.enabled !== original.enabled ? " s-dirty" : ""}`}><input id="guestsEnabled" type="checkbox" checked=${f.enabled} onChange=${(e) => set({ enabled: e.target.checked })} /><span>${tn("guestsEnabled")}</span></label>
224
+ <div class="s-guest-policy"><div> <label class=${`check${f.enabled !== original.enabled ? " s-dirty" : ""}`}><input class="s-ui-toggle" id="guestsEnabled" type="checkbox" checked=${f.enabled} onChange=${(e) => set({ enabled: e.target.checked })} /><span>${tn("guestsEnabled")}</span></label>
222
225
  <p class="note">${tn("guestsEnabledHint")}</p>
223
- <label class=${`check${f.chat !== original.chat ? " s-dirty" : ""}`}><input id="guestsChat" type="checkbox" checked=${f.chat} onChange=${(e) => set({ chat: e.target.checked })} /><span>${tn("guestsChat")}</span></label>
226
+ </div><div> <label class=${`check${f.chat !== original.chat ? " s-dirty" : ""}`}><input class="s-ui-toggle" id="guestsChat" type="checkbox" checked=${f.chat} onChange=${(e) => set({ chat: e.target.checked })} /><span>${tn("guestsChat")}</span></label>
224
227
  <p class="note">${tn("guestsChatHint")}</p>
228
+ </div></div>
225
229
  ${failed ? html`<p class="note" role="status">${tn("guestsLoadFailed")} <button type="button" class="btn btn-sm" onClick=${load}>${t("app.retry")}</button></p>`
226
230
  : !overview ? html`<p class="note">${t("app.loading")}</p>` : null}
227
231
  ${!f.grants && overview ? html`<div class="g-legacy"><p class="note">${tn("grantsLegacy")}</p>
228
232
  ${readOnly ? null : html`<button type="button" class="btn btn-sm" id="grantsConvert" onClick=${convert}>${tn("grantsConvert")}</button>`}</div>` : null}
229
- <h4>${tn("grantsWithAccess")}</h4>
230
- ${!rows.length ? html`<p class="note" id="grantsNone">${tn("grantsNoneYet")}</p>` : html`<ul class="guest-list">${rows.map((k) => {
233
+ <div class="s-guest-workbench"><section class="s-guest-list-card"><h4>${layoutText("revGuestManagement")}</h4>
234
+ <div class="s-guest-columns" aria-hidden="true"><span>${tn("guestsTitle")}</span><span>${layoutText("revVisibleAgents")}</span><span>${layoutText("revGuestLevel")}</span><span>${tn("settingsButton")}</span></div>
235
+ ${!rows.length ? html`<p class="note" id="grantsNone">${tn("grantsNoneYet")}</p>` : html`<ul class="guest-list">${rows.map(k => {
236
+ const person=who(k), grant=f.grants?.[k] || original.grants?.[k];
237
+ return html`<li key=${k} class=${`guest-summary${activeGuest === k ? " is-selected" : ""}`}>
238
+ <span class="g-summary-name"><strong>${person.name}</strong><small>${PLATFORMS[person.platform] || person.platform}</small></span>
239
+ <span class="g-summary-agents">${(grant?.instances || []).map(n => html`<span key=${n}>${shownName(rooms,n)}</span>`)}</span>
240
+ <span>${tn(levelOf(grant) === "trusted" ? "grantsLevelTrusted" : "grantsLevelChat")}</span>
241
+ <button type="button" class="btn btn-sm" aria-pressed=${activeGuest === k ? "true" : "false"} aria-controls=${`guest-detail-${encodeURIComponent(k)}`} onClick=${() => {setViewedGuest(k);setEditing(k);}}>${tn("settingsButton")}</button>
242
+ </li>`;
243
+ })}</ul>`}</section>
244
+ <aside class="s-guest-detail-card" aria-label=${layoutText("revGuestDetail")}>${rows.map((k) => {
231
245
  const person = who(k), draft = f.grants && f.grants[k], saved = original.grants && original.grants[k];
232
246
  const removed = !draft, added = !saved, dirty = removed || added || !sameGrant(draft, saved);
233
247
  const applied = !!saved && saved.instances.length > 0 && !dirty;
234
248
  const ss = sendState[k] || {}, cooling = ss.until && ss.until > now;
235
- return html`<li key=${k} class=${`guest-row${dirty ? " s-dirty" : ""}${removed ? " g-removed" : ""}`} data-account=${k}>
249
+ return html`<section key=${k} hidden=${!readOnly && activeGuest !== k} id=${`guest-detail-${encodeURIComponent(k)}`} class=${`guest-row${dirty ? " s-dirty" : ""}${removed ? " g-removed" : ""}`} data-account=${k}>
236
250
  <div class="g-head"><span class="g-av" aria-hidden="true">${(person.name || "?").slice(0, 1).toUpperCase()}</span>
237
251
  <div class="g-id"><strong>${person.name}</strong> <span class="note">${PLATFORMS[person.platform] || person.platform}${person.username ? ` · @${person.username}` : ""}</span></div>
238
252
  ${removed ? html`<span class="g-state">${tn("grantsWillBeRemoved")}</span>` : added ? html`<span class="g-state">${tn("grantsNew")}</span>` : pill(person)}</div>
@@ -255,23 +269,23 @@ export function GuestsSection({ ctx, staged }) {
255
269
  : html`<button type="button" class="btn btn-sm" onClick=${() => setGrant(k, { ...saved })}>${tn("grantsUndoRemove")}</button>`}
256
270
  ${person.guest && (person.guest.sessions || person.guest.devices) ? html`<button type="button" class="btn btn-sm" onClick=${() => signOut(person)}>${tn("guestSignOut")}</button>` : null}
257
271
  </div>`}
258
- </li>`;
259
- })}</ul>`}
260
- ${readOnly ? null : html`<h4>${tn("grantsAdd")}</h4>
272
+ </section>`;
273
+ })}${!rows.length ? html`<p class="note">${layoutText("revSelectGuest")}</p>` : null}</aside></div>
274
+ ${readOnly ? null : html`<details class="s-guest-add"><summary>${tn("grantsAdd")}</summary><div>
261
275
  <input type="search" class="input g-search" id="grantsSearch" value=${query} aria-label=${tn("grantsSearch")} placeholder=${tn("grantsSearch")} onInput=${(e) => setQuery(e.target.value)} />
262
276
  ${people === null ? html`<p class="note">${t("app.loading")}</p>` : !candidates.length ? html`<p class="note" id="grantsNoPeople">${tn("grantsNoPeople")}</p>`
263
277
  : html`<ul class="g-pick">${candidates.map((p) => html`<li key=${accountKey(p)} class="g-cand" data-account=${accountKey(p)}>
264
278
  <span class="g-av" aria-hidden="true">${(p.name || "?").slice(0, 1).toUpperCase()}</span>
265
279
  <div class="g-id"><strong>${p.name}</strong> <span class="note">${PLATFORMS[p.platform] || p.platform}${p.username ? ` · @${p.username}` : ""} · <span class="g-when">${tn("grantsLastSeen", new Date(p.lastSeen).toLocaleString())}</span></span>
266
280
  ${p.instances && p.instances.length ? html`<div class="note">${wroteTo(p.instances)}</div>` : null}</div>
267
- <button type="button" class="btn btn-sm" onClick=${() => { const k = accountKey(p); setGrant(k, { ...(p.name ? { name: p.name } : {}), instances: choosable.filter((n) => (p.instances || []).includes(n)) }); setEditing(k); }}>${tn("grantsChoose")}</button></li>`)}</ul>`}`}
281
+ <button type="button" class="btn btn-sm" onClick=${() => { const k = accountKey(p); setGrant(k, { ...(p.name ? { name: p.name } : {}), instances: choosable.filter((n) => (p.instances || []).includes(n)) }); setEditing(k); }}>${tn("grantsChoose")}</button></li>`)}</ul>`}</div></details>`}
268
282
  </fieldset>
269
- ${overview && overview.audit ? html`<h4>${tn("grantsRecent")}</h4>${!overview.audit.length ? html`<p class="note" id="grantsRecentNone">${tn("grantsRecentNone")}</p>`
283
+ ${overview && overview.audit ? html`<details class="s-guest-audit"><summary>${tn("grantsRecent")}</summary>${!overview.audit.length ? html`<p class="note" id="grantsRecentNone">${tn("grantsRecentNone")}</p>`
270
284
  : html`<ul class="g-audit" id="grantsRecentList">${overview.audit.map((e, i) => html`<li key=${`${e.at}-${i}`} class="g-audit-row" data-kind=${e.kind}>
271
285
  <span class="g-when">${new Date(e.at).toLocaleString()}</span> <strong>${e.name}</strong>
272
286
  ${e.kind === "grant" ? html`${e.added.map((n) => html` <span key=${`+${n}`} class="g-wrote g-add">+${n}</span>`)}${e.removed.map((n) => html` <span key=${`-${n}`} class="g-wrote g-del">−${n}</span>`)}${e.level ? html` <span class="g-wrote g-level-to">${tn("grantsAuditLevel", tn(e.level === "trusted" ? "grantsLevelTrusted" : "grantsLevelChat"))}</span>` : null}
273
287
  <span class="note"> · <span class="g-wrote">${tn(e.via === "confirmed" ? "grantsAuditConfirmed" : "grantsAuditDirect")}</span>${e.endedSessions ? html` · <span class="g-wrote">${tn("grantsAuditEnded", e.endedSessions)}</span>` : null}</span>`
274
- : html` <span class="note">${e.outcome === "sent" ? tn("grantsAuditCodeSent") : tn("grantsAuditCodeNot", auditOutcome(e.outcome))}</span>`}</li>`)}</ul>`}` : null}
288
+ : html` <span class="note">${e.outcome === "sent" ? tn("grantsAuditCodeSent") : tn("grantsAuditCodeNot", auditOutcome(e.outcome))}</span>`}</li>`)}</ul>`}</details>` : null}
275
289
  ${status && status.kind === "waiting" ? html`<p class="note" role="status">${tn("grantsWaiting")}</p>` : null}
276
290
  ${status && status.kind === "failed" ? html`<p class="note g-error" role="alert">${tn("grantsSaveFailed", status.error)}</p>` : null}
277
291
  </section>`;
@@ -0,0 +1,245 @@
1
+ // Shared presentation for Settings. Tab panels stay mounted so switching views never drops a form's draft.
2
+ import { html, useEffect, useRef, useState } from "/assets/app-html.js";
3
+ import { register, t } from "/assets/app-i18n.js";
4
+
5
+ register("settingsLayout", {
6
+ en: {
7
+ account: "Your account", personal: "Your preferences", fleet: "Whole fleet",
8
+ lead_preferences: "Your identity, language and content preferences.",
9
+ lead_look: "Choose how AgEnD looks and keep your workspace comfortable.",
10
+ lead_agents: "Manage agents across runtimes, with their CLI, model and status in one place.",
11
+ "lead_agent-defaults": "Defaults for new agents. Each agent can override the settings it supports.",
12
+ lead_chat: "Connect chat services and choose how messages are received and delivered.",
13
+ "lead_web-dashboard": "Manage fleet-wide dashboard features, branding and resource limits.",
14
+ lead_access: "Manage sign-in, web authority, guests and sharing.",
15
+ lead_advanced: "Inspect and edit fleet configuration, with effective values kept separate.",
16
+ editor: "Configuration editor", effective: "Effective settings (read only)",
17
+ aiAccounts: "AI accounts and keys", aiAccountsHint: "Sign-ins and supported provider keys on the AgEnD host. Windows CLI status is shown under Runtime.",
18
+ classicDefaults: "ClassicBot defaults and access",
19
+ },
20
+ "zh-TW": {
21
+ account: "你的帳號", personal: "你的偏好", fleet: "整個 Fleet",
22
+ lead_preferences: "你的身分、語言與內容偏好。",
23
+ lead_look: "調整顯示方式,保留你的工作習慣。",
24
+ lead_agents: "集中管理各個 Runtime 的 Agent、CLI、模型與狀態。",
25
+ "lead_agent-defaults": "新 Agent 的預設行為;個別 Agent 可覆寫支援的設定。",
26
+ lead_chat: "連接聊天服務,設定訊息如何進入與送出。",
27
+ "lead_web-dashboard": "管理整個 Fleet 的網頁功能、品牌與資源限制。",
28
+ lead_access: "管理登入、網頁授權、訪客與對外分享。",
29
+ lead_advanced: "檢視與編輯 Fleet 設定,並區分目前生效的唯讀內容。",
30
+ editor: "設定編輯器", effective: "生效設定(唯讀)",
31
+ aiAccounts: "AI 帳號與金鑰", aiAccountsHint: "管理 AgEnD 主機的登入與支援的供應商金鑰;Windows CLI 狀態請到 Runtime 查看。",
32
+ classicDefaults: "ClassicBot 預設行為與存取",
33
+ },
34
+ });
35
+
36
+ register("settingsLayout", {
37
+ en: {
38
+ aiAccounts: "AI sign-ins and API keys", aiAccountsSummary: "Host CLI sign-ins and provider API keys, managed separately.",
39
+ aiHostScope: "Credentials on the AgEnD host", aiAccountsHint: "These sign-ins and keys belong to the host running AgEnD.", aiWindowsLink: "Manage Windows computers and their CLIs in Runtime",
40
+ apiKeysTitle: "Provider API keys", apiKeysHint: "API credentials for supported provider integrations.", apiKeysUnavailableState: "Not offered", apiKeysUnavailable: "Provider API key management is not offered in this session.",
41
+ apiNewPlaceholder: "Enter an API key to verify", apiReplacePlaceholder: "Enter a replacement API key",
42
+ },
43
+ "zh-TW": {
44
+ aiAccounts: "AI 登入與 API 金鑰", aiAccountsSummary: "分開管理主機 CLI 登入與供應商 API 金鑰。",
45
+ aiHostScope: "AgEnD 主機上的憑證", aiAccountsHint: "這裡的登入與金鑰屬於執行 AgEnD 的主機。", aiWindowsLink: "前往 Runtime 管理 Windows 電腦與 CLI",
46
+ apiKeysTitle: "供應商 API 金鑰", apiKeysHint: "供支援的供應商整合功能使用的 API 憑證。", apiKeysUnavailableState: "目前未提供", apiKeysUnavailable: "此工作階段未提供供應商 API 金鑰管理。",
47
+ apiNewPlaceholder: "輸入要驗證的 API 金鑰", apiReplacePlaceholder: "輸入替換用的 API 金鑰",
48
+ },
49
+ });
50
+
51
+ export const layoutText = (key, ...values) => t(`settingsLayout.${key}`, ...values);
52
+
53
+ register("settingsLayout", {
54
+ "en": {
55
+ "accessLogin": "Sign-in & authority",
56
+ "accessGuests": "Guests",
57
+ "accessSharing": "Public & sharing",
58
+ "accessSharingList": "Shared links",
59
+ "accessPublicCredentials": "Cloudflare connector credentials",
60
+ "accessPolicyHint": "How access changes take effect",
61
+ "dashboardFeatures": "Features",
62
+ "developerReadOnly": "Read-only mode",
63
+ "developerEditing": "Editing draft",
64
+ "effectiveKey": "Setting key",
65
+ "effectiveValue": "Value in effect",
66
+ "effectiveSource": "Source",
67
+ "effectiveSourceConfig": "fleet.yaml",
68
+ "effectiveSourceDefault": "Default"
69
+ },
70
+ "zh-TW": {
71
+ "accessLogin": "登入與授權",
72
+ "accessGuests": "訪客",
73
+ "accessSharing": "公開與分享",
74
+ "accessSharingList": "分享連結",
75
+ "accessPublicCredentials": "Cloudflare 連接器憑證",
76
+ "accessPolicyHint": "存取變更如何生效",
77
+ "dashboardFeatures": "功能",
78
+ "developerReadOnly": "唯讀模式",
79
+ "developerEditing": "編輯草稿中",
80
+ "effectiveKey": "設定鍵",
81
+ "effectiveValue": "目前生效值",
82
+ "effectiveSource": "來源",
83
+ "effectiveSourceConfig": "fleet.yaml",
84
+ "effectiveSourceDefault": "預設"
85
+ }
86
+ });
87
+
88
+ export function SettingsPageHead({ section, children }) {
89
+ const scope = section === "preferences" ? "account" : section === "look" ? "personal" : "fleet";
90
+ return html`<header class="s-page-head"><div><div class="s-page-title"><h2>${t(`settings.sec_${section}`)}</h2><span class="s-scope">${layoutText(scope)}</span></div><p class="s-page-lead">${layoutText(`lead_${section}`)}</p></div>${children ? html`<div class="s-page-actions">${children}</div>` : null}</header>`;
91
+ }
92
+
93
+ // Settings search, validation links and legacy anchors reveal their owning tab before focusing a control.
94
+ const revealers = new Set();
95
+ export function useSettingsTabs(group, initial) {
96
+ const [value, setValue] = useState(initial);
97
+ const current = useRef(value);
98
+ current.current = value;
99
+ useEffect(() => {
100
+ const reveal = target => {
101
+ const panel = target?.closest?.("[data-settings-tabs]");
102
+ if (!panel || panel.getAttribute("data-settings-tabs") !== group) return false;
103
+ const next = panel.getAttribute("data-settings-tab");
104
+ if (!next || next === current.current) return false;
105
+ current.current = next;
106
+ setValue(next);
107
+ return true;
108
+ };
109
+ revealers.add(reveal);
110
+ return () => revealers.delete(reveal);
111
+ }, [group]);
112
+ return [value, setValue];
113
+ }
114
+
115
+ export function revealSettingsTarget(target) {
116
+ let changed = false;
117
+ for (const reveal of revealers) changed = reveal(target) || changed;
118
+ return changed;
119
+ }
120
+
121
+ export function SettingsTabs({ id, value, onChange, items }) {
122
+ const choose = (key, focus = false) => {
123
+ onChange(key);
124
+ if (focus) document.getElementById(`${id}-tab-${key}`)?.focus();
125
+ };
126
+ const keydown = (event, index) => {
127
+ let next;
128
+ if (event.key === "ArrowRight") next = (index + 1) % items.length;
129
+ else if (event.key === "ArrowLeft") next = (index - 1 + items.length) % items.length;
130
+ else if (event.key === "Home") next = 0;
131
+ else if (event.key === "End") next = items.length - 1;
132
+ else return;
133
+ event.preventDefault();
134
+ choose(items[next].id, true);
135
+ };
136
+ return html`<div class="s-page-tabs" role="tablist" aria-label=${t("settings.title")}>${items.map((item, index) => html`<button key=${item.id} id=${`${id}-tab-${item.id}`} type="button" role="tab" class=${value === item.id ? "on" : ""} aria-selected=${value === item.id ? "true" : "false"} aria-controls=${`${id}-panel-${item.id}`} tabindex=${value === item.id ? 0 : -1} onClick=${() => choose(item.id)} onKeyDown=${event => keydown(event, index)}>${item.label}</button>`)}</div>`;
137
+ }
138
+
139
+ export function SettingsTabPanel({ group, id, active, children }) {
140
+ return html`<div id=${`${group}-panel-${id}`} class="s-tab-panel" role="tabpanel" aria-labelledby=${`${group}-tab-${id}`} data-settings-tabs=${group} data-settings-tab=${id} hidden=${!active}>${children}</div>`;
141
+ }
142
+
143
+ register("settingsLayout", {
144
+ "en": {
145
+ "profileIdentityDetails": "How agents recognise you",
146
+ "previewsPersonalHint": "Only affects your preview preference.",
147
+ "chatConnections": "Chat connections",
148
+ "messageBehavior": "Message behavior",
149
+ "pageContents": "On this page",
150
+ "defaultsAccessWorkspace": "Permissions and workspace",
151
+ "replyRulesHelp": "Reply rules",
152
+ "replySupportHelp": "CLI support"
153
+ },
154
+ "zh-TW": {
155
+ "profileIdentityDetails": "Agent 如何辨識你的身分",
156
+ "previewsPersonalHint": "只影響你的預覽偏好。",
157
+ "chatConnections": "聊天連線",
158
+ "messageBehavior": "訊息行為",
159
+ "pageContents": "本頁內容",
160
+ "defaultsAccessWorkspace": "權限與工作區",
161
+ "replyRulesHelp": "回覆規則說明",
162
+ "replySupportHelp": "CLI 支援情況"
163
+ }
164
+ });
165
+
166
+ register("settingsLayout", {
167
+ "en": {
168
+ "revDashboard_log": "Agent log tab",
169
+ "revDashboardHint_log": "Show prompts, replies, tool steps and fleet events. Secrets are masked; public links can also see it. Enabling needs confirmation.",
170
+ "revDashboard_usagePub": "Usage over the public link",
171
+ "revDashboardHint_usagePub": "Offer the fleet usage report over the temporary public link. Guests never see it.",
172
+ "revDashboard_levels": "Office levels",
173
+ "revDashboardHint_levels": "Show each agent's lifetime work level on its Office name tag and profile.",
174
+ "revDashboard_cdn": "CDN libraries in HTML previews",
175
+ "revDashboardHint_cdn": "Allow cdnjs and jsDelivr. Requests leave this computer, are visible to the CDN and could send page data out.",
176
+ "revDetails": "Details",
177
+ "revAccessPolicy": "Loosening access needs fleet administrator confirmation. Tightening applies at once.",
178
+ "revLoginSessions": "Sign-in sessions",
179
+ "revPublicSessionLimit": "Public-link sessions are capped at 8 hours, end after 30 minutes idle and never outlive the link.",
180
+ "revSessionDetails": "Session rules and notifications",
181
+ "revRelatedAccess": "Other access settings",
182
+ "revRelatedGuests": "Choose who can sign in and which agents they see.",
183
+ "revRelatedSharing": "Manage the public entry and read-only shared links.",
184
+ "revPublicEntry": "Fleet public entry",
185
+ "revReadOnlySharing": "Time-limited read-only sharing",
186
+ "revSharingOrigin": "Create new shares from the Office or an HTML preview. Manage existing links below.",
187
+ "revEffectiveHint": "These are the values in effect. This view is read only; edit supported values in the configuration editor.",
188
+ "revGoEditor": "Open configuration editor",
189
+ "revEditorContext": "Fleet configuration · use the form pages for individual settings",
190
+ "revUpdateCredential": "Update connector credential",
191
+ "revSetCredential": "Set connector credential",
192
+ "revAuthorityBrief": "Only signed-in fleet administrators can confirm their account's authority in Discord or Telegram. Guests cannot. Publishing, destructive actions, spending, secrets and passing authority require it.",
193
+ "revAuthorityDetails": "Authority rules",
194
+ "revAuthorityEnabled": "Allow web authority",
195
+ "revAuthorityEnabledHint": "Turning this off invalidates existing authority at once. Turning it back on does not restore it.",
196
+ "revAuthorityLocal": "Only this computer",
197
+ "revAuthorityLocalHint": "Allow only this computer's browser. Authority already given over a LAN or tailnet becomes invalid and is not restored by switching this off.",
198
+ "revAuthorityPublicWarning": "Public links are reached from the internet and Cloudflare sees their traffic. Enabling their authority needs confirmation; re-enabling temporary-link authority ends all earlier authorizations.",
199
+ "revAuthorityLinks": "Authority over public links",
200
+ "revGuestManagement": "Guest management",
201
+ "revVisibleAgents": "Visible agents",
202
+ "revGuestLevel": "Level",
203
+ "revGuestDetail": "Selected guest",
204
+ "revSelectGuest": "Add or choose a guest to manage their access."
205
+ },
206
+ "zh-TW": {
207
+ "revDashboard_log": "Agent 紀錄分頁",
208
+ "revDashboardHint_log": "顯示提示、回覆、工具步驟與 Fleet 事件;機密值會遮蔽,公開連結也看得到。開啟需確認。",
209
+ "revDashboard_usagePub": "公開連結提供用量報告",
210
+ "revDashboardHint_usagePub": "透過臨時公開連結提供 Fleet 用量報告;訪客一律看不到。",
211
+ "revDashboard_levels": "辦公室等級",
212
+ "revDashboardHint_levels": "在辦公室名牌與名片上顯示 Agent 依終身工作量得到的等級。",
213
+ "revDashboard_cdn": "HTML 預覽載入 CDN 函式庫",
214
+ "revDashboardHint_cdn": "允許 cdnjs 與 jsDelivr。請求會離開本機,CDN 能看到,也可能被頁面用來傳出資料。",
215
+ "revDetails": "詳細說明",
216
+ "revAccessPolicy": "放寬存取需要 Fleet 管理員確認;收緊的變更立即生效。",
217
+ "revLoginSessions": "登入工作階段",
218
+ "revPublicSessionLimit": "公開連結的工作階段最長 8 小時、閒置 30 分鐘登出,也不會超過連結期限。",
219
+ "revSessionDetails": "工作階段與通知規則",
220
+ "revRelatedAccess": "其他存取設定",
221
+ "revRelatedGuests": "管理可登入的訪客與可見 Agent。",
222
+ "revRelatedSharing": "管理公開入口與唯讀分享連結。",
223
+ "revPublicEntry": "Fleet 公開入口",
224
+ "revReadOnlySharing": "限時唯讀分享",
225
+ "revSharingOrigin": "新分享請從辦公室或 HTML 預覽建立;在下方管理既有連結。",
226
+ "revEffectiveHint": "下方為目前生效的設定,僅供檢視;需要修改時,請切換至設定編輯器。",
227
+ "revGoEditor": "前往設定編輯器",
228
+ "revEditorContext": "Fleet 設定檔・個別設定可使用對應表單調整",
229
+ "revUpdateCredential": "更新連接器憑證",
230
+ "revSetCredential": "設定連接器憑證",
231
+ "revAuthorityBrief": "僅限已登入的 Fleet 管理者,並須在 Discord 或 Telegram 確認本人帳號授權;訪客不適用。發布、破壞性操作、花費、密鑰與轉交授權都需要它。",
232
+ "revAuthorityDetails": "授權規則說明",
233
+ "revAuthorityEnabled": "允許網頁授權",
234
+ "revAuthorityEnabledHint": "關閉會立即使現有授權失效;重新開啟不會恢復。",
235
+ "revAuthorityLocal": "僅限本機",
236
+ "revAuthorityLocalHint": "只接受本機瀏覽器;區域網路與 tailnet 上已給出的授權會失效,關掉此限制也不會恢復。",
237
+ "revAuthorityPublicWarning": "公開連結可從網際網路存取,Cloudflare 看得到流量。開啟其授權須經確認;重新開啟臨時連結授權會結束之前給出的全部授權。",
238
+ "revAuthorityLinks": "公開連結的授權範圍",
239
+ "revGuestManagement": "訪客管理",
240
+ "revVisibleAgents": "可見 Agent",
241
+ "revGuestLevel": "等級",
242
+ "revGuestDetail": "選取的訪客",
243
+ "revSelectGuest": "新增或選擇訪客後,管理其存取權限。"
244
+ }
245
+ });
@@ -75,6 +75,14 @@ export const visibilityDefault = (defaults) => (VISIBILITY_MODES.includes(defaul
75
75
  export const replyGuardSupported = (backend, mode, kiroUi = "legacy") =>
76
76
  mode === "mcp" && (["claude-code", "codex", "muse"].includes(backend) || (backend === "kiro-cli" && ["legacy", "tui"].includes(kiroUi || "legacy")));
77
77
 
78
+ /**
79
+ * Backends AgEnD no longer runs, with the one to use instead (#1280). The browser cannot import src/backend/removed.ts;
80
+ * tests/settings-removed-backend-1280.test.ts keeps this list equal to it.
81
+ */
82
+ export const REMOVED_BACKEND_REPLACEMENTS = Object.freeze({ "gemini-cli": "antigravity" });
83
+ export const removedBackendReplacement = (backend) =>
84
+ typeof backend === "string" && Object.prototype.hasOwnProperty.call(REMOVED_BACKEND_REPLACEMENTS, backend) ? REMOVED_BACKEND_REPLACEMENTS[backend] : null;
85
+
78
86
  /**
79
87
  * The backend picker's options. A current value it does not offer (the removed gemini-cli, #1280 review) stays first
80
88
  * and selected: otherwise the select falls to its first option and an unrelated edit switches the CLI and account.
@@ -15,14 +15,16 @@ const ROWS = [
15
15
  ["general.location", "agent-defaults", "#general-location", null, null, ["general", "windows", "paired PC", "host", "location"], ["General", "Windows", "電腦", "主機", "執行位置"], L("Run General on", "General 執行位置")],
16
16
  // You · Preferences
17
17
  ["profile.name", "preferences", "#profile-name", "settings.profileTitle", null, ["name", "display name", "who agents see", "profile"], ["名字", "名稱", "顯示名稱", "個人資料"]],
18
- ["prefs.theme", "preferences", "#pref-theme", "app.theme", null, ["theme", "dark", "light", "dark mode", "colour scheme"], ["主題", "深色", "淺色", "外觀"]],
18
+ ["prefs.theme", "look", "#pref-theme", "app.theme", null, ["theme", "dark", "light", "dark mode", "colour scheme"], ["主題", "深色", "淺色", "外觀"]],
19
19
  ["prefs.lang", "preferences", "#pref-lang", "app.language", null, ["language", "dashboard language", "english", "chinese", "locale"], ["語言", "介面語言", "中文", "英文"]],
20
20
  ["prefs.names", "preferences", "#pref-names", "app.names", null, ["names", "nickname", "real name", "instance name", "display"], ["名稱", "暱稱", "實名"]],
21
21
  ["prefs.previews", "preferences", "#pref-previews", "chat.previews", null, ["html preview", "preview", "iframe"], ["預覽", "HTML 預覽"]],
22
22
  ["prefs.tour", "preferences", "#tourBtn", "chat.tour", null, ["tour", "help", "walkthrough", "guide"], ["導覽", "說明", "教學"]],
23
- ["device.motion", "preferences", "#fx-follow-reduced-motion", "settings.themesFollowMotion", null, ["reduced motion", "animation", "effects", "accessibility"], ["減少動態", "動畫", "特效", "無障礙"]],
24
- ["device.css-off", "preferences", "#css-off-here", "settings.cssDeviceOff", null, ["custom css", "css off", "this device", "safe"], ["自訂 CSS", "關閉 CSS", "這個裝置"]],
23
+ ["prefs.shortcuts", "preferences", "#keyboardShortcutsBtn", null, null, ["keyboard shortcuts", "hotkeys", "keys", "help", "ctrl", "command"], ["快捷鍵", "快速鍵", "鍵盤", "說明"], L("Keyboard shortcuts", "快捷鍵")],
24
+ ["device.motion", "look", "#fx-follow-reduced-motion", "settings.themesFollowMotion", null, ["reduced motion", "animation", "effects", "accessibility"], ["減少動態", "動畫", "特效", "無障礙"]],
25
+ ["device.css-off", "look", "#css-off-here", "settings.cssDeviceOff", null, ["custom css", "css off", "this device", "safe"], ["自訂 CSS", "關閉 CSS", "這個裝置"]],
25
26
  // You · Look
27
+ ["device.settings-width", "look", "#settings-content-width", null, null, ["settings width", "content width", "layout", "wide", "compact", "this device"], ["設定寬度", "內容寬度", "版型", "寬版", "窄版", "此裝置"], L("Settings content width", "設定頁內容寬度")],
26
28
  ["look.themes", "look", "#fx-show-themes", "settings.themesShow", "fx.show", ["agent themes", "themes", "effects", "widgets"], ["主題", "agent 的主題", "特效", "小工具"]],
27
29
  ["look.colours", "look", ".s-colors", "settings.colorTitle", "color.show", ["identity colour", "color", "colour", "turn off colours"], ["識別色", "顏色", "關閉顏色"]],
28
30
  ["look.cards", "look", ".s-card-size", "settings.cardSizeTitle", "card.size", ["identity card", "card size", "portrait", "banner"], ["身分卡", "卡片大小"]],
@@ -0,0 +1,65 @@
1
+ // Help for existing handlers only. Keyboard behavior stays with app-shell.js, panel-chat.js,
2
+ // chat-render.js (composerKey) and panel-settings.js; this module registers no keyboard listener.
3
+ import { html } from "/assets/app-html.js";
4
+ import { register, t } from "/assets/app-i18n.js";
5
+ import { isMacLike, showDialog } from "/assets/app-shell.js";
6
+ import { Dialog } from "/assets/ui-dialog.js";
7
+ import { Icon } from "/assets/ui-icons.js";
8
+
9
+ register("settingsShortcuts", {
10
+ en: {
11
+ title: "Keyboard shortcuts", button: "View shortcuts",
12
+ hint: "Find the existing shortcuts for search, chat and settings.",
13
+ intro: "Shortcuts work in the contexts below. Search availability depends on your account and the fleet's settings.",
14
+ globalTitle: "Search across the fleet", globalAction: "Open search",
15
+ globalScope: "Works in text fields too, on pages that offer search. Does not run while another modal dialog is open or while an input method is composing. Do not hold Alt or Shift.",
16
+ chatSearchTitle: "Search the current chat", chatSearchAction: "Search within this agent",
17
+ chatSearchScope: "On an agent's chat page, when search is available. Other pages keep the browser's Find command. The same dialog and input-method restrictions apply.",
18
+ composerTitle: "Chat input", send: "Send the message or run the typed command", newline: "Add a new line",
19
+ composerScope: "In the chat composer with a physical keyboard. Enter does not send during input-method composition. On a touch screen, Enter adds a line; use Send to send. Alt, Ctrl or Command with Enter also adds a line.",
20
+ settingsTitle: "Settings search", settingsAction: "Focus Search settings",
21
+ settingsScope: "Only on Settings pages, outside inputs, textareas, selects and editable content. Does not run during input-method composition or with Ctrl, Command or Alt held.",
22
+ closeHint: "Closes this help dialog and returns focus to View shortcuts.",
23
+ },
24
+ "zh-TW": {
25
+ title: "快捷鍵", button: "查看快捷鍵",
26
+ hint: "查看搜尋、聊天與設定目前支援的快捷鍵。",
27
+ intro: "快捷鍵依下列情境使用;搜尋是否提供,取決於你的帳號權限與 Fleet 設定。",
28
+ globalTitle: "全域搜尋", globalAction: "開啟搜尋",
29
+ globalScope: "提供搜尋的頁面中,輸入欄內也可使用。其他對話框開啟時或輸入法組字時不觸發;請勿同時按住 Alt 或 Shift。",
30
+ chatSearchTitle: "搜尋目前聊天", chatSearchAction: "搜尋目前 Agent 的對話",
31
+ chatSearchScope: "在提供搜尋功能的 Agent 聊天頁使用;其他頁面保留瀏覽器的尋找功能。對話框與輸入法的限制同上。",
32
+ composerTitle: "聊天輸入", send: "送出訊息或執行輸入的指令", newline: "新增一行",
33
+ composerScope: "適用於聊天輸入框與實體鍵盤;輸入法組字時,Enter 不會送出。觸控螢幕的 Enter 會換行,請按「送出」傳送。Alt、Ctrl 或 Command 加 Enter 也會換行。",
34
+ settingsTitle: "設定搜尋", settingsAction: "聚焦「搜尋設定」",
35
+ settingsScope: "僅限設定頁面,且焦點不在輸入欄、文字區、選單或可編輯內容內。輸入法組字時,或同時按住 Ctrl、Command、Alt 時不觸發。",
36
+ closeHint: "關閉此說明,焦點會回到「查看快捷鍵」。",
37
+ },
38
+ });
39
+
40
+ export const shortcutsText = (key) => t(`settingsShortcuts.${key}`);
41
+
42
+ /** The Settings preference row is intentionally independent of saved preferences. */
43
+ export function ShortcutsHelpRow() {
44
+ return html`<div class="s-pref-help-row s-pref-shortcuts-row"><div><strong>${shortcutsText("title")}</strong><p class="sub">${shortcutsText("hint")}</p></div>
45
+ <button type="button" id="keyboardShortcutsBtn" class="btn" aria-haspopup="dialog" onClick=${openShortcuts}><${Icon} name="info" size=${16} />${shortcutsText("button")}</button></div>`;
46
+ }
47
+
48
+ export function openShortcuts() { showDialog(ShortcutsDialog); }
49
+
50
+ /** Reuse the shared modal's native Escape, backdrop and focus-restoration behavior. */
51
+ export function ShortcutsDialog({ onClose }) {
52
+ const modifier = isMacLike() ? "⌘" : "Ctrl";
53
+ const keys = (parts) => html`<span class="settings-shortcut-keys">${parts.map((key, i) => html`${i ? html`<span aria-hidden="true">+</span>` : null}<kbd key=${key}>${key}</kbd>`)}</span>`;
54
+ const row = (parts, action) => html`<div class="settings-shortcut-row">${keys(parts)}<span>${shortcutsText(action)}</span></div>`;
55
+ return html`<${Dialog} title=${shortcutsText("title")} onClose=${onClose} wide=${true}
56
+ actions=${html`<button type="button" class="btn btn-primary" onClick=${onClose}>${t("app.close")}</button>`}>
57
+ <div class="settings-shortcuts"><p class="settings-shortcuts-intro">${shortcutsText("intro")}</p>
58
+ <section><h3>${shortcutsText("globalTitle")}</h3>${row([modifier, "K"], "globalAction")}<p>${shortcutsText("globalScope")}</p></section>
59
+ <section><h3>${shortcutsText("chatSearchTitle")}</h3>${row([modifier, "F"], "chatSearchAction")}<p>${shortcutsText("chatSearchScope")}</p></section>
60
+ <section><h3>${shortcutsText("composerTitle")}</h3>${row(["Enter"], "send")}${row(["Shift", "Enter"], "newline")}<p>${shortcutsText("composerScope")}</p></section>
61
+ <section><h3>${shortcutsText("settingsTitle")}</h3>${row(["/"], "settingsAction")}<p>${shortcutsText("settingsScope")}</p></section>
62
+ <p class="settings-shortcuts-close"><kbd>Escape</kbd> ${shortcutsText("closeHint")}</p>
63
+ </div>
64
+ </${Dialog}>`;
65
+ }
@@ -1333,3 +1333,96 @@ register("settings", {
1333
1333
  help6: "存取、密鑰或連線的變更,要等 fleet 管理員在聊天中確認後才會套用。",
1334
1334
  },
1335
1335
  });
1336
+
1337
+ // Settings UX presentation labels.
1338
+ register("settings", {
1339
+ "en": {
1340
+ "prefsProfileGroup": "Personal details",
1341
+ "prefsContentGroup": "Language and content",
1342
+ "prefsAccountScope": "Your account",
1343
+ "prefsHelpGroup": "Help and guidance",
1344
+ "prefsTourHint": "Walk through the controls in an Agent conversation.",
1345
+ "prefsReplayTour": "Replay tour",
1346
+ "prefsLookHint": "Theme, appearance and effects are in Look",
1347
+ "prefsNameImmediate": "Save name applies this change immediately, separately from the other preferences.",
1348
+ "prefsSaveName": "Save name",
1349
+ "lookDisplayGroup": "Display and layout",
1350
+ "lookThemeHint": "Choose the theme used on your signed-in devices.",
1351
+ "lookIdentityGroup": "Agent identity",
1352
+ "lookMotionGroup": "Themes and motion",
1353
+ "lookPreview": "Preview",
1354
+ "lookPreviewHint": "A sample of your current colours and default card size."
1355
+ },
1356
+ "zh-TW": {
1357
+ "prefsProfileGroup": "個人資料",
1358
+ "prefsContentGroup": "語言與內容",
1359
+ "prefsAccountScope": "你的帳號",
1360
+ "prefsHelpGroup": "使用說明",
1361
+ "prefsTourHint": "重新了解 Agent 對話中的常用操作。",
1362
+ "prefsReplayTour": "重新播放導覽",
1363
+ "prefsLookHint": "主題、外觀與動態效果請前往「外觀」",
1364
+ "prefsNameImmediate": "「儲存名字」會立即套用,與其他偏好的儲存分開。",
1365
+ "prefsSaveName": "儲存名字",
1366
+ "lookDisplayGroup": "顯示與版面",
1367
+ "lookThemeHint": "選擇你登入的裝置所使用的主題。",
1368
+ "lookIdentityGroup": "Agent 識別",
1369
+ "lookMotionGroup": "主題與動態效果",
1370
+ "lookPreview": "預覽",
1371
+ "lookPreviewHint": "目前識別色與預設身分卡大小的示意。"
1372
+ }
1373
+ }
1374
+ );
1375
+
1376
+ // Settings UX presentation labels.
1377
+ register("settings", {
1378
+ "en": {
1379
+ "uiAgentColumnName": "Name",
1380
+ "uiAgentColumnCli": "CLI & model",
1381
+ "uiListColumnState": "Status",
1382
+ "uiListColumnActions": "Actions",
1383
+ "uiRuntimeWindowsLocal": "Local Windows",
1384
+ "uiRuntimeWindowsRemote": "Windows runtime",
1385
+ "uiConnectionColumnService": "Service & connection",
1386
+ "uiConnectionColumnInfo": "Connection details",
1387
+ "uiClassicColumnRoom": "Room",
1388
+ "uiProviderKeyActionHint": "Verify and apply each key separately. These actions do not wait for the page Save button.",
1389
+ "uiDefaultsModelsTitle": "Models & language",
1390
+ "uiDefaultsWorkTitle": "Work behavior",
1391
+ "uiDefaultsCollaborationTitle": "Collaboration & replies",
1392
+ "uiDefaultsCapabilitiesTitle": "Agent capabilities & permissions",
1393
+ "uiDefaultsWorkspaceTitle": "Workspace rules",
1394
+ "uiDefaultsAdvancedTitle": "Advanced execution policies"
1395
+ },
1396
+ "zh-TW": {
1397
+ "uiAgentColumnName": "名稱",
1398
+ "uiAgentColumnCli": "CLI 與模型",
1399
+ "uiListColumnState": "狀態",
1400
+ "uiListColumnActions": "操作",
1401
+ "uiRuntimeWindowsLocal": "Windows 本機",
1402
+ "uiRuntimeWindowsRemote": "Windows Runtime",
1403
+ "uiConnectionColumnService": "服務與連線",
1404
+ "uiConnectionColumnInfo": "連線資訊",
1405
+ "uiClassicColumnRoom": "房間",
1406
+ "uiProviderKeyActionHint": "每組金鑰會分別驗證並套用,不需等待頁面底部的儲存。",
1407
+ "uiDefaultsModelsTitle": "模型與語言",
1408
+ "uiDefaultsWorkTitle": "工作行為",
1409
+ "uiDefaultsCollaborationTitle": "協作與回覆",
1410
+ "uiDefaultsCapabilitiesTitle": "Agent 能力與權限",
1411
+ "uiDefaultsWorkspaceTitle": "工作區規則",
1412
+ "uiDefaultsAdvancedTitle": "進階執行策略"
1413
+ }
1414
+ }
1415
+ );
1416
+
1417
+ register("settings", {
1418
+ "en": {
1419
+ "lookBrandGroup": "Brand",
1420
+ "lookIconMore": "Icon update help",
1421
+ "prefsNameImmediate": "Name changes save immediately."
1422
+ },
1423
+ "zh-TW": {
1424
+ "lookBrandGroup": "品牌",
1425
+ "lookIconMore": "圖示更新說明",
1426
+ "prefsNameImmediate": "名字會立即儲存。"
1427
+ }
1428
+ });