@songsid/agend 2.3.2-beta.1 → 2.3.2-beta.11

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 (298) hide show
  1. package/THIRD_PARTY_NOTICES.md +40 -0
  2. package/dist/agent-cli.js +69 -2
  3. package/dist/agent-cli.js.map +1 -1
  4. package/dist/agent-endpoint.js +11 -0
  5. package/dist/agent-endpoint.js.map +1 -1
  6. package/dist/agent-whats-new.js +3 -0
  7. package/dist/agent-whats-new.js.map +1 -1
  8. package/dist/antigravity-usage-scan.d.ts +58 -0
  9. package/dist/antigravity-usage-scan.js +257 -0
  10. package/dist/antigravity-usage-scan.js.map +1 -0
  11. package/dist/antigravity-usage-worker.d.ts +1 -0
  12. package/dist/antigravity-usage-worker.js +10 -0
  13. package/dist/antigravity-usage-worker.js.map +1 -0
  14. package/dist/auth-api.js +4 -0
  15. package/dist/auth-api.js.map +1 -1
  16. package/dist/backend/kiro-classic-archive.d.ts +52 -0
  17. package/dist/backend/kiro-classic-archive.js +444 -0
  18. package/dist/backend/kiro-classic-archive.js.map +1 -0
  19. package/dist/backend/kiro-identity.d.ts +83 -0
  20. package/dist/backend/kiro-identity.js +144 -0
  21. package/dist/backend/kiro-identity.js.map +1 -1
  22. package/dist/backend/kiro-migration-verify.d.ts +10 -0
  23. package/dist/backend/kiro-migration-verify.js +52 -0
  24. package/dist/backend/kiro-migration-verify.js.map +1 -0
  25. package/dist/backend/kiro-retired-model.d.ts +62 -0
  26. package/dist/backend/kiro-retired-model.js +160 -0
  27. package/dist/backend/kiro-retired-model.js.map +1 -0
  28. package/dist/backend/kiro-v2-store-worker.js +21 -1
  29. package/dist/backend/kiro-v2-store-worker.js.map +1 -1
  30. package/dist/backend/kiro-v2-store.d.ts +8 -0
  31. package/dist/backend/kiro-v2-store.js.map +1 -1
  32. package/dist/backend/kiro.d.ts +169 -2
  33. package/dist/backend/kiro.js +703 -28
  34. package/dist/backend/kiro.js.map +1 -1
  35. package/dist/backend/types.d.ts +108 -0
  36. package/dist/backend/types.js +9 -0
  37. package/dist/backend/types.js.map +1 -1
  38. package/dist/backend-signin-guide.d.ts +1 -0
  39. package/dist/backend-signin-guide.js +9 -0
  40. package/dist/backend-signin-guide.js.map +1 -0
  41. package/dist/backend-signin-pending.d.ts +10 -0
  42. package/dist/backend-signin-pending.js +44 -0
  43. package/dist/backend-signin-pending.js.map +1 -0
  44. package/dist/browser-backend-signins.d.ts +56 -0
  45. package/dist/browser-backend-signins.js +189 -0
  46. package/dist/browser-backend-signins.js.map +1 -0
  47. package/dist/cache-ledger.d.ts +38 -2
  48. package/dist/cache-ledger.js +133 -8
  49. package/dist/cache-ledger.js.map +1 -1
  50. package/dist/cache-prices.d.ts +2 -1
  51. package/dist/cache-prices.js +5 -2
  52. package/dist/cache-prices.js.map +1 -1
  53. package/dist/cache-service.d.ts +68 -0
  54. package/dist/cache-service.js +355 -13
  55. package/dist/cache-service.js.map +1 -1
  56. package/dist/channel/adapters/discord.d.ts +11 -0
  57. package/dist/channel/adapters/discord.js +71 -14
  58. package/dist/channel/adapters/discord.js.map +1 -1
  59. package/dist/channel/ipc-timeouts.js +3 -0
  60. package/dist/channel/ipc-timeouts.js.map +1 -1
  61. package/dist/channel/mcp-tools.js +41 -2
  62. package/dist/channel/mcp-tools.js.map +1 -1
  63. package/dist/channel/tool-router.js +2 -1
  64. package/dist/channel/tool-router.js.map +1 -1
  65. package/dist/classic-channel-manager.d.ts +11 -0
  66. package/dist/classic-channel-manager.js +39 -1
  67. package/dist/classic-channel-manager.js.map +1 -1
  68. package/dist/cli.js +54 -2
  69. package/dist/cli.js.map +1 -1
  70. package/dist/config-validator.d.ts +7 -1
  71. package/dist/config-validator.js +55 -10
  72. package/dist/config-validator.js.map +1 -1
  73. package/dist/config.d.ts +19 -0
  74. package/dist/config.js +33 -1
  75. package/dist/config.js.map +1 -1
  76. package/dist/cpu-quota.d.ts +23 -0
  77. package/dist/cpu-quota.js +86 -0
  78. package/dist/cpu-quota.js.map +1 -0
  79. package/dist/daemon.d.ts +68 -0
  80. package/dist/daemon.js +448 -26
  81. package/dist/daemon.js.map +1 -1
  82. package/dist/dashboard-permissions.d.ts +31 -6
  83. package/dist/dashboard-permissions.js +84 -9
  84. package/dist/dashboard-permissions.js.map +1 -1
  85. package/dist/doctor.js +2 -1
  86. package/dist/doctor.js.map +1 -1
  87. package/dist/fleet-control-audit.d.ts +1 -1
  88. package/dist/fleet-control-audit.js +3 -0
  89. package/dist/fleet-control-audit.js.map +1 -1
  90. package/dist/fleet-manager.d.ts +133 -2
  91. package/dist/fleet-manager.js +905 -94
  92. package/dist/fleet-manager.js.map +1 -1
  93. package/dist/forged-envelope-scan.d.ts +9 -0
  94. package/dist/forged-envelope-scan.js +30 -5
  95. package/dist/forged-envelope-scan.js.map +1 -1
  96. package/dist/general-knowledge/skills/credential-profiles/SKILL.md +5 -2
  97. package/dist/general-knowledge/skills/kiro-engine-migration/SKILL.md +24 -1
  98. package/dist/general-knowledge/skills/web-appearance/SKILL.canvas-worker-off.md +49 -1
  99. package/dist/general-knowledge/skills/web-appearance/SKILL.md +49 -1
  100. package/dist/general-knowledge/skills/web-dashboard/SKILL.canvas-worker-off.md +47 -9
  101. package/dist/general-knowledge/skills/web-dashboard/SKILL.md +54 -11
  102. package/dist/instance-config-impact.js +10 -5
  103. package/dist/instance-config-impact.js.map +1 -1
  104. package/dist/instance-lifecycle.d.ts +23 -0
  105. package/dist/instance-lifecycle.js +62 -12
  106. package/dist/instance-lifecycle.js.map +1 -1
  107. package/dist/kiro-db-reader.d.ts +5 -0
  108. package/dist/kiro-db-reader.js +18 -0
  109. package/dist/kiro-db-reader.js.map +1 -1
  110. package/dist/kiro-engine-status.d.ts +2 -0
  111. package/dist/kiro-engine-status.js +9 -0
  112. package/dist/kiro-engine-status.js.map +1 -1
  113. package/dist/kiro-migrate.d.ts +60 -0
  114. package/dist/kiro-migrate.js +160 -0
  115. package/dist/kiro-migrate.js.map +1 -0
  116. package/dist/kiro-session-proof.d.ts +41 -0
  117. package/dist/kiro-session-proof.js +89 -0
  118. package/dist/kiro-session-proof.js.map +1 -0
  119. package/dist/kiro-transcript-lane.d.ts +2 -0
  120. package/dist/kiro-transcript-lane.js.map +1 -1
  121. package/dist/kiro-transcript-worker.js +1 -0
  122. package/dist/kiro-transcript-worker.js.map +1 -1
  123. package/dist/kiro-tui-forensic.d.ts +12 -0
  124. package/dist/kiro-tui-forensic.js +110 -0
  125. package/dist/kiro-tui-forensic.js.map +1 -0
  126. package/dist/kiro-usage-run.d.ts +7 -0
  127. package/dist/kiro-usage-run.js +39 -0
  128. package/dist/kiro-usage-run.js.map +1 -0
  129. package/dist/kiro-usage-scan.d.ts +49 -0
  130. package/dist/kiro-usage-scan.js +223 -0
  131. package/dist/kiro-usage-scan.js.map +1 -0
  132. package/dist/kiro-usage-worker.d.ts +1 -0
  133. package/dist/kiro-usage-worker.js +10 -0
  134. package/dist/kiro-usage-worker.js.map +1 -0
  135. package/dist/kiro-usage.d.ts +58 -0
  136. package/dist/kiro-usage.js +310 -0
  137. package/dist/kiro-usage.js.map +1 -0
  138. package/dist/locale.js +230 -8
  139. package/dist/locale.js.map +1 -1
  140. package/dist/log-service.js +1 -1
  141. package/dist/log-service.js.map +1 -1
  142. package/dist/login-url.d.ts +4 -0
  143. package/dist/login-url.js +22 -0
  144. package/dist/login-url.js.map +1 -0
  145. package/dist/needs-you-hub.d.ts +2 -1
  146. package/dist/needs-you-hub.js +1 -1
  147. package/dist/needs-you-hub.js.map +1 -1
  148. package/dist/needs-you.d.ts +10 -2
  149. package/dist/needs-you.js +10 -2
  150. package/dist/needs-you.js.map +1 -1
  151. package/dist/notice-delivery.d.ts +16 -0
  152. package/dist/notice-delivery.js +35 -0
  153. package/dist/notice-delivery.js.map +1 -0
  154. package/dist/office-share-page.d.ts +16 -0
  155. package/dist/office-share-page.js +85 -0
  156. package/dist/office-share-page.js.map +1 -0
  157. package/dist/office-share-service.d.ts +91 -0
  158. package/dist/office-share-service.js +176 -0
  159. package/dist/office-share-service.js.map +1 -0
  160. package/dist/office-share.d.ts +268 -0
  161. package/dist/office-share.js +574 -0
  162. package/dist/office-share.js.map +1 -0
  163. package/dist/opencode-usage-scan.d.ts +28 -0
  164. package/dist/opencode-usage-scan.js +92 -0
  165. package/dist/opencode-usage-scan.js.map +1 -0
  166. package/dist/opencode-usage-worker.d.ts +1 -0
  167. package/dist/opencode-usage-worker.js +10 -0
  168. package/dist/opencode-usage-worker.js.map +1 -0
  169. package/dist/outbound-handlers.js +22 -2
  170. package/dist/outbound-handlers.js.map +1 -1
  171. package/dist/outbound-schemas.d.ts +65 -2
  172. package/dist/outbound-schemas.js +56 -2
  173. package/dist/outbound-schemas.js.map +1 -1
  174. package/dist/public-web-gateway.js +7 -1
  175. package/dist/public-web-gateway.js.map +1 -1
  176. package/dist/quickstart-api.js +13 -2
  177. package/dist/quickstart-api.js.map +1 -1
  178. package/dist/quickstart.js +2 -1
  179. package/dist/quickstart.js.map +1 -1
  180. package/dist/remote-card-answer.d.ts +83 -0
  181. package/dist/remote-card-answer.js +208 -0
  182. package/dist/remote-card-answer.js.map +1 -0
  183. package/dist/remote-card-prompt.d.ts +38 -0
  184. package/dist/remote-card-prompt.js +86 -0
  185. package/dist/remote-card-prompt.js.map +1 -0
  186. package/dist/reply-triggers.d.ts +2 -0
  187. package/dist/reply-triggers.js +3 -0
  188. package/dist/reply-triggers.js.map +1 -0
  189. package/dist/settings-api.d.ts +2 -0
  190. package/dist/settings-api.js +41 -16
  191. package/dist/settings-api.js.map +1 -1
  192. package/dist/settings-change.js +20 -14
  193. package/dist/settings-change.js.map +1 -1
  194. package/dist/settings-effect.js +7 -1
  195. package/dist/settings-effect.js.map +1 -1
  196. package/dist/settings-instance-patch.js +4 -1
  197. package/dist/settings-instance-patch.js.map +1 -1
  198. package/dist/setup-form.d.ts +1 -1
  199. package/dist/setup-form.js +55 -6
  200. package/dist/setup-form.js.map +1 -1
  201. package/dist/setup-host.d.ts +9 -0
  202. package/dist/setup-host.js +127 -6
  203. package/dist/setup-host.js.map +1 -1
  204. package/dist/setup-signin-assets.d.ts +2 -0
  205. package/dist/setup-signin-assets.js +6 -0
  206. package/dist/setup-signin-assets.js.map +1 -0
  207. package/dist/setup-wizard.js +2 -0
  208. package/dist/setup-wizard.js.map +1 -1
  209. package/dist/structured-signin-transport.d.ts +8 -0
  210. package/dist/structured-signin-transport.js +79 -0
  211. package/dist/structured-signin-transport.js.map +1 -0
  212. package/dist/structured-signin.d.ts +131 -0
  213. package/dist/structured-signin.js +388 -0
  214. package/dist/structured-signin.js.map +1 -0
  215. package/dist/tool-permissions.js +29 -0
  216. package/dist/tool-permissions.js.map +1 -1
  217. package/dist/topic-commands.js +6 -2
  218. package/dist/topic-commands.js.map +1 -1
  219. package/dist/transcript-sources.d.ts +27 -2
  220. package/dist/transcript-sources.js +104 -40
  221. package/dist/transcript-sources.js.map +1 -1
  222. package/dist/turn-reply-guard.d.ts +1 -1
  223. package/dist/turn-reply-guard.js +1 -1
  224. package/dist/turn-reply-guard.js.map +1 -1
  225. package/dist/types.d.ts +33 -8
  226. package/dist/ui/backend-signin.js +63 -10
  227. package/dist/ui/chat-store.js +3 -2
  228. package/dist/ui/chat-strings.js +4 -4
  229. package/dist/ui/fleet-office-editor.js +68 -11
  230. package/dist/ui/fleet-office-layout.js +110 -35
  231. package/dist/ui/fleet-office-model.js +6 -3
  232. package/dist/ui/fleet-office-overview.js +167 -0
  233. package/dist/ui/fleet-office-pack.d.ts +13 -3
  234. package/dist/ui/fleet-office-pack.js +214 -24
  235. package/dist/ui/fleet-office-skin.js +98 -23
  236. package/dist/ui/fleet-office-store.js +82 -5
  237. package/dist/ui/fleet-office-view.js +5 -4
  238. package/dist/ui/fleet-office-zones.js +232 -21
  239. package/dist/ui/fleet-office.js +549 -186
  240. package/dist/ui/fleet-usage.js +19 -15
  241. package/dist/ui/office-share-dialog.js +145 -0
  242. package/dist/ui/office-share-page.js +164 -0
  243. package/dist/ui/panel-chat.js +22 -3
  244. package/dist/ui/panel-log.js +4 -4
  245. package/dist/ui/panel-needs.js +5 -5
  246. package/dist/ui/panel-settings.js +38 -12
  247. package/dist/ui/settings-dialogs.js +8 -1
  248. package/dist/ui/settings-guests.js +17 -7
  249. package/dist/ui/settings-model.js +22 -6
  250. package/dist/ui/settings-registry.js +4 -0
  251. package/dist/ui/settings-strings.js +18 -2
  252. package/dist/ui/shared/app-i18n.js +4 -2
  253. package/dist/ui/shared/app-shell.js +14 -6
  254. package/dist/ui/shared/app-stream.js +2 -1
  255. package/dist/ui/shared/app.css +85 -4
  256. package/dist/ui/shared/office/cozy.png +0 -0
  257. package/dist/ui/shared/side-panel.js +95 -0
  258. package/dist/ui/structured-signin.js +115 -0
  259. package/dist/usage-report.d.ts +20 -0
  260. package/dist/usage-report.js +54 -17
  261. package/dist/usage-report.js.map +1 -1
  262. package/dist/usage-rollup.d.ts +9 -1
  263. package/dist/usage-rollup.js +20 -4
  264. package/dist/usage-rollup.js.map +1 -1
  265. package/dist/web-api.d.ts +23 -1
  266. package/dist/web-api.js +292 -3
  267. package/dist/web-api.js.map +1 -1
  268. package/dist/web-auth.js +1 -1
  269. package/dist/web-auth.js.map +1 -1
  270. package/dist/web-backend-login.d.ts +1 -7
  271. package/dist/web-backend-login.js +2 -24
  272. package/dist/web-backend-login.js.map +1 -1
  273. package/dist/web-office-acts.d.ts +173 -0
  274. package/dist/web-office-acts.js +285 -0
  275. package/dist/web-office-acts.js.map +1 -0
  276. package/dist/web-office-pack-read.d.ts +35 -0
  277. package/dist/web-office-pack-read.js +252 -0
  278. package/dist/web-office-pack-read.js.map +1 -0
  279. package/dist/web-office-pack-tools.d.ts +232 -0
  280. package/dist/web-office-pack-tools.js +754 -0
  281. package/dist/web-office-pack-tools.js.map +1 -0
  282. package/dist/web-office-skins.d.ts +71 -6
  283. package/dist/web-office-skins.js +321 -35
  284. package/dist/web-office-skins.js.map +1 -1
  285. package/dist/web-office-store.d.ts +5 -2
  286. package/dist/web-office-store.js +69 -10
  287. package/dist/web-office-store.js.map +1 -1
  288. package/dist/web-settings-patch.d.ts +2 -1
  289. package/dist/web-settings-patch.js +4 -1
  290. package/dist/web-settings-patch.js.map +1 -1
  291. package/dist/working-recovery.d.ts +2 -2
  292. package/dist/working-recovery.js +43 -8
  293. package/dist/working-recovery.js.map +1 -1
  294. package/dist/working-state-journal.d.ts +2 -0
  295. package/dist/working-state-journal.js +16 -1
  296. package/dist/working-state-journal.js.map +1 -1
  297. package/package.json +3 -2
  298. package/templates/windows-shortcuts.ps1 +155 -140
@@ -5,12 +5,13 @@
5
5
  // session that started a sign-in gets its terminal link, access code and device code; every other admin page only that
6
6
  // one runs, on which device, and may cancel it. The stream's `backend_login` event carries nothing — it only says to
7
7
  // read again. Starting is two steps, as in chat: check (the server keeps what it found) and start (with its id).
8
- import { html, useEffect, useState } from "/assets/app-html.js";
8
+ import { html, useEffect, useState, useRef } from "/assets/app-html.js";
9
9
  import { t, register } from "/assets/app-i18n.js";
10
10
  import { appStore, createStore, useStore } from "/assets/app-store.js";
11
11
  import { closeDialog, shellStore, showDialog } from "/assets/app-shell.js";
12
12
  import { Dialog } from "/assets/ui-dialog.js";
13
13
  import { toast } from "/assets/ui-toast.js";
14
+ import { mountStructuredSignIns } from "./structured-signin.js";
14
15
 
15
16
  register("signin", {
16
17
  en: {
@@ -49,6 +50,8 @@ register("signin", {
49
50
  notOffered: "A public link is not offered for this sign-in.",
50
51
  failed: "That did not work.",
51
52
  listTitle: "Sign-ins",
53
+ browserRunning: "Signing in to {0} on this machine",
54
+ consoleFallback: "Use console fallback",
52
55
  listHint: "Each backend's login on this machine, and each credential profile an agent uses.",
53
56
  stUsable: "signed in",
54
57
  stNotSignedIn: "not signed in",
@@ -98,6 +101,8 @@ register("signin", {
98
101
  notOffered: "這次登入不提供公開連結。",
99
102
  failed: "沒有成功。",
100
103
  listTitle: "登入",
104
+ browserRunning: "正在這台機器登入 {0}",
105
+ consoleFallback: "改用主控台備援",
101
106
  listHint: "這台機器上每個 backend 的登入,以及代理使用的每個憑證設定檔。",
102
107
  stUsable: "已登入",
103
108
  stNotSignedIn: "未登入",
@@ -116,7 +121,7 @@ const tn = (k, ...v) => t(`signin.${k}`, ...v);
116
121
  const shownOf = (backend, profile) => (profile ? tn("profile", backend, profile) : backend);
117
122
 
118
123
  /** What this page knows: the current sign-in as this session may see it, and Settings' list. */
119
- export const signInStore = createStore({ loaded: false, login: null, targets: [] });
124
+ export const signInStore = createStore({ loaded: false, login: null, targets: [], structured: null });
120
125
 
121
126
  let reading = null, again = false;
122
127
  /**
@@ -125,9 +130,13 @@ let reading = null, again = false;
125
130
  */
126
131
  export function refreshSignIn() {
127
132
  if (reading) { again = true; return reading; }
128
- reading = fetch("/ui/backend-login", { cache: "no-store" })
129
- .then((r) => (r.ok ? r.json() : null))
130
- .then((b) => { if (b) signInStore.set({ loaded: true, login: b.login || null, targets: Array.isArray(b.targets) ? b.targets : [] }); return b; })
133
+ reading = Promise.all([
134
+ fetch("/ui/backend-login", { cache: "no-store" }).then(r => r.ok ? r.json() : null).catch(() => null),
135
+ fetch("/ui/browser-backend-signins", { cache: "no-store" }).then(r => r.ok ? r.json() : null).catch(() => null),
136
+ ]).then(([b, structured]) => {
137
+ if (b) signInStore.set({ loaded: true, login: b.login || null, targets: Array.isArray(b.targets) ? b.targets : [] });
138
+ signInStore.set({ structured: structured && Array.isArray(structured.selected) && Array.isArray(structured.pending) ? structured : null }); return b;
139
+ })
131
140
  .catch(() => null)
132
141
  .finally(() => { reading = null; if (again) { again = false; void refreshSignIn(); } });
133
142
  return reading;
@@ -140,13 +149,25 @@ export function installSignIn() {
140
149
  installed = true;
141
150
  const on = appStore.get().streamOn;
142
151
  if (typeof on === "function") on("backend_login", () => { void refreshSignIn(); });
143
- void refreshSignIn();
152
+ void refreshSignIn().then(() => {
153
+ // A navigation hint from the installer, never authority to start a CLI or cancel another writer.
154
+ const backends = installerSignInBackends(typeof location === "undefined" ? "" : location.search);
155
+ if (backends && signInStore.get().structured) showDialog(BrowserSignInDialog, { backends });
156
+ });
144
157
  shellStore.set({ banner: SignInLine });
145
158
  }
146
159
 
160
+ export function installerSignInBackends(search) {
161
+ const csv = new URLSearchParams(search).get("backend_signins");
162
+ if (!csv || csv.length > 100) return null;
163
+ const ids = csv.split(",");
164
+ return ids.length <= 4 && ids.every(b => ["kiro-cli", "claude-code", "codex", "antigravity"].includes(b)) ? [...new Set(ids)] : null;
165
+ }
166
+
147
167
  /** Open the dialog for one login (a backend's default one, or a credential profile). */
148
168
  export function openSignIn({ backend, profile }) {
149
- showDialog(SignInDialog, { backend, ...(profile ? { profile } : {}) });
169
+ if (!profile && ["kiro-cli", "claude-code", "codex"].includes(backend)) showDialog(BrowserSignInDialog, { backend });
170
+ else showDialog(SignInDialog, { backend, ...(profile ? { profile } : {}) });
150
171
  }
151
172
 
152
173
  /** Open the dialog on the running sign-in (its starter's view, or the cancel-only one). */
@@ -191,6 +212,32 @@ function Running({ login }) {
191
212
  </div>`;
192
213
  }
193
214
 
215
+ /** The same closed controls as first setup. Profile-specific logins retain their existing flow. */
216
+ export function BrowserSignInDialog({ backend, backends, onClose }) {
217
+ const root = useRef(null), close = onClose || closeDialog;
218
+ useEffect(() => {
219
+ let cards, last = "";
220
+ cards = mountStructuredSignIns(root.current, { endpoint: "/ui/browser-backend-signins", changed: next => {
221
+ if (backends) { if (backends.some(b => !next.selected.includes(b))) void cards.addBackends(backends); }
222
+ else if (!next.selected.includes(backend)) void cards.addBackend(backend);
223
+ const signature = JSON.stringify([next.login, next.pending]);
224
+ if (signature !== last) { last = signature; void refreshSignIn(); }
225
+ } });
226
+ return () => cards.dispose();
227
+ }, [backend, backends]);
228
+ const fallback = async (chosen = backend) => {
229
+ const result = await post("/ui/browser-backend-signins", { operation: "cancel" });
230
+ if (!result.ok) { toast(tn("failed")); return; }
231
+ showDialog(SignInDialog, { backend: chosen });
232
+ };
233
+ return html`<${Dialog} title=${backends ? tn("listTitle") : tn("title", backend)} onClose=${close}>
234
+ <div ref=${root}></div>
235
+ ${backends ? backends.filter(b => b !== "antigravity").map(b => html`<button type="button" class="btn" onClick=${() => fallback(b)}>${b} · ${tn("consoleFallback")}</button>`) : null}
236
+ <div class="s-actions">${backend ? html`<button type="button" class="btn" onClick=${() => fallback(backend)}>${tn("consoleFallback")}</button>` : null}
237
+ <button type="button" class="btn" onClick=${close}>${tn("close")}</button></div>
238
+ </${Dialog}>`;
239
+ }
240
+
194
241
  export function SignInDialog({ backend, profile, resume = false, onClose }) {
195
242
  const s = useStore(signInStore);
196
243
  const [step, setStep] = useState(resume ? { kind: "login" } : { kind: "checking" });
@@ -265,7 +312,12 @@ export function SignInDialog({ backend, profile, resume = false, onClose }) {
265
312
 
266
313
  /** The line every admin page shows while a sign-in runs (the shell's banner slot). */
267
314
  export function SignInLine() {
268
- const { login } = useStore(signInStore);
315
+ const { login, structured } = useStore(signInStore);
316
+ const active = structured?.login;
317
+ if ((!login || login.phase !== "running") && active && active.phase !== "done" && !(active.phase === "failed" && active.reason)) {
318
+ return html`<div data-trust="status" class="conn op-line signin-line" role="status"><span>${tn("browserRunning", active.backend)}</span>
319
+ <button type="button" class="btn btn-sm" onClick=${() => openSignIn({ backend: active.backend })}>${tn("open")}</button></div>`;
320
+ }
269
321
  if (!login || login.phase !== "running") return null;
270
322
  return html`<div data-trust="status" class="conn op-line signin-line" role="status">
271
323
  <span>${tn("running", shownOf(login.backend, login.profile), login.startedOn)}</span>
@@ -276,13 +328,14 @@ const STATE_KEY = { usable: "stUsable", not_signed_in: "stNotSignedIn", not_inst
276
328
 
277
329
  /** Settings → Agents: each login on this machine, with Sign in where the dashboard can do it. */
278
330
  export function SignInList() {
279
- const { targets, loaded, login } = useStore(signInStore);
331
+ const { targets, loaded, login, structured } = useStore(signInStore);
280
332
  // Not over the temporary public link: only the dashboard reached directly signs a backend in, and the link does not
281
333
  // serve this read (it was a 404 in the console there, found by smoke-1939-authorize-defaults).
282
334
  const offered = signInOffered();
283
335
  useEffect(() => { if (offered) void refreshSignIn(); }, []);
284
336
  if (!offered || !loaded || !targets.length) return null;
285
- const running = login && login.phase === "running";
337
+ const active = structured?.login;
338
+ const running = (login && login.phase === "running") || (active && active.phase !== "done" && !(active.phase === "failed" && active.reason));
286
339
  return html`<section class="s-group signin-list" aria-label=${tn("listTitle")}>
287
340
  <div class="list-head"><span>${tn("listTitle")}</span></div>
288
341
  <p class="sub">${tn("listHint")}</p>
@@ -208,11 +208,12 @@ export function createChatStore(deps) {
208
208
  deps.setTimeout(() => { if (s.prompts[d.nonce] === p) { delete s.prompts[d.nonce]; changed(p.instance); } }, 60000);
209
209
  changed(p.instance);
210
210
  }
211
- async function answerPrompt(p, action) {
211
+ /** `text`: #2675 — the typed answer, for an action the prompt shows a text box for. */
212
+ async function answerPrompt(p, action, text) {
212
213
  if (p.busy || p.resolved) return;
213
214
  p.busy = true; changed(p.instance);
214
215
  let r;
215
- try { r = await api("POST", "/ui/prompt", { instance: p.instance, nonce: p.nonce, action }); }
216
+ try { r = await api("POST", "/ui/prompt", { instance: p.instance, nonce: p.nonce, action, ...(text !== undefined ? { text } : {}) }); }
216
217
  catch (err) { r = { error: err && err.message ? err.message : t("chat.disconnected") }; }
217
218
  if (r && r.answered) return; // the outcome arrives as prompt_resolved
218
219
  const latest = s.prompts[p.nonce];
@@ -111,12 +111,12 @@ register("chat", {
111
111
  clicked: "Clicked:", clickShow: "Show the message these buttons were on",
112
112
  copy: "Copy", copyMessage: "Copy this reply", copied: "Copied", copyFailed: "Copy failed", wrap: "Wrap", showAll: "Show all {0} lines", showMore: "Show more", showLess: "Show less", jumpLatest: "Latest", newBelow: "{0} new",
113
113
  disconnected: "disconnected", tick_queued: "Waiting behind another message", tick_processing: "Handed to the agent", tick_delivered: "The agent has it", tick_failed: "Not delivered", tick_cancelled: "Dropped by Stop", reactedBy: "{0} reacted",
114
- promptAnswering: "Answering…", promptGone: "This prompt is no longer open", promptFor: "{0}: {1}",
114
+ promptAnswering: "Answering…", promptGone: "This prompt is no longer open", promptTextPlaceholder: "Your answer", promptTextSend: "Send answer", promptFor: "{0}: {1}",
115
115
  working: "{0} is working…", stuck: "{0} looks stuck", stopReply: "Stop reply", stopReplyTitle: "Stop the current reply (like Esc) and drop the messages still waiting. The instance keeps running.", stopSent: "Stopped",
116
116
  file_empty: "empty file", file_too_large: "over {0} MB", file_too_large_public: "over {0} MB — the most the public link carries in one upload", file_too_many: "at most {0} files per message", file_too_much: "the files together would be over {0} MB", removeFile: "Remove", putBack: "Put back",
117
117
  noMessages: "Type a task below.",
118
118
  pick: "Choose an agent to start chatting.", noAgents: "Create an agent to start chatting.", newAgent: "New agent", notFound: "No instance called “{0}”", notFoundHint: "It may have been deleted or renamed.", backToList: "Choose an agent",
119
- details: "Instance details", start: "Start instance", stopInstance: "Stop instance", restart: "Restart instance", delete: "Delete instance…",
119
+ details: "Instance details", fullWidth: "Full width", start: "Start instance", stopInstance: "Stop instance", restart: "Restart instance", delete: "Delete instance…",
120
120
  started: "{0} started", stoppedInst: "{0} stopped", restarted: "{0} restarted", instanceDeleted: "{0} was deleted",
121
121
  deleteTitle: "Delete {0}?", deleteHelp: "This removes the instance from the fleet and stops it. Its working directory is kept. Type “delete {0}” to confirm.", deleteGo: "Delete", cancel: "Cancel",
122
122
  loadFailed: "Failed to load", tour: "Chat tour", tourBtnTitle: "Show the chat tour on an agent's chat", tourNoAgent: "Choose an agent before opening the chat tour.",
@@ -250,12 +250,12 @@ register("chat", {
250
250
  clicked: "點選了:", clickShow: "顯示這些按鈕所在的訊息",
251
251
  copy: "複製", copyMessage: "複製這則回覆", copied: "已複製", copyFailed: "複製失敗", wrap: "換行", showAll: "展開全部 {0} 行", showMore: "顯示更多", showLess: "收合", jumpLatest: "最新", newBelow: "{0} 則新訊息",
252
252
  disconnected: "已斷線", tick_queued: "排在前一則訊息之後", tick_processing: "已交給 agent", tick_delivered: "agent 已收到", tick_failed: "未送達", tick_cancelled: "已被中止取消", reactedBy: "{0} 的回應",
253
- promptAnswering: "回覆中…", promptGone: "這個提示已經結束", promptFor: "{0}:{1}",
253
+ promptAnswering: "回覆中…", promptGone: "這個提示已經結束", promptTextPlaceholder: "你的回答", promptTextSend: "送出回答", promptFor: "{0}:{1}",
254
254
  working: "{0} 處理中…", stuck: "{0} 似乎卡住了", stopReply: "中止回覆", stopReplyTitle: "中止目前的回覆(同 Esc),並取消還在排隊的訊息。instance 會繼續運行。", stopSent: "已中止",
255
255
  file_empty: "空檔案", file_too_large: "超過 {0} MB", file_too_large_public: "超過 {0} MB,這是公開連結單次上傳的上限", file_too_many: "每則訊息最多 {0} 個檔案", file_too_much: "檔案合計會超過 {0} MB", removeFile: "移除", putBack: "放回輸入框",
256
256
  noMessages: "直接在下方輸入任務。",
257
257
  pick: "選一個 Agent 開始聊天。", noAgents: "先建立一個 Agent,再開始聊天。", newAgent: "新增 Agent", notFound: "沒有叫「{0}」的 instance", notFoundHint: "它可能已被刪除或改名。", backToList: "選擇 Agent",
258
- details: "Instance 詳細資料", start: "啟動 instance", stopInstance: "停止 instance", restart: "重新啟動 instance", delete: "刪除 instance…",
258
+ details: "Instance 詳細資料", fullWidth: "全寬", start: "啟動 instance", stopInstance: "停止 instance", restart: "重新啟動 instance", delete: "刪除 instance…",
259
259
  started: "{0} 已啟動", stoppedInst: "{0} 已停止", restarted: "{0} 已重新啟動", instanceDeleted: "{0} 已刪除",
260
260
  deleteTitle: "要刪除 {0} 嗎?", deleteHelp: "這會把 instance 從 fleet 移除並停止它,工作目錄會保留。請輸入「delete {0}」確認。", deleteGo: "刪除", cancel: "取消",
261
261
  loadFailed: "載入失敗", tour: "聊天導覽", tourBtnTitle: "在 Agent 的聊天上開啟聊天導覽", tourNoAgent: "請先選一個 Agent,再開啟聊天導覽。",
@@ -4,13 +4,15 @@
4
4
  // (PUT /ui/offices; a change made meanwhile is a 409: read again, nothing overwritten). A stored office's layout (seats,
5
5
  // paths) and its layout overrides (Edit layout, fleet-office-layout.js) are kept as they are. Skin packs (#2513): the
6
6
  // packs the host keeps are styles to choose; an admin uploads one here (its manifest.json and its PNGs, picked together
7
- // or as a folder — sent at once, checked by the host, which keeps it whole) or deletes one. Never offered to a guest,
7
+ // or as a folder — sent at once, checked by the host, which keeps it whole) or deletes one; #2692: which agents may change
8
+ // packs, whether their changes need the admin's yes, those waiting for it, and the newest log entries. Never offered to a guest,
8
9
  // the public link or a read-only session (the host refuses them too).
9
- import { html, useMemo, useState } from "/assets/app-html.js";
10
+ import { html, useEffect, useMemo, useState } from "/assets/app-html.js";
10
11
  import { t, register } from "/assets/app-i18n.js";
11
- import { Dialog } from "/assets/ui-dialog.js";
12
+ import { SidePanel } from "/assets/side-panel.js";
12
13
  import { toast } from "/assets/ui-toast.js";
13
14
  import { nodeText } from "./fleet-org.js";
15
+ import { BUILT_IN_SKINS } from "./fleet-office-skin.js";
14
16
 
15
17
  register("orge", {
16
18
  en: {
@@ -28,6 +30,11 @@ register("orge", {
28
30
  packsNone: "No skin packs yet.", upload: "Upload a skin pack", uploadFolder: "Upload a folder", uploading: "Uploading…", uploaded: "Skin pack “{0}” uploaded.",
29
31
  uploadFailed: "The skin pack was not uploaded: {0}", noManifest: "no manifest.json among the files", badManifest: "manifest.json is not JSON",
30
32
  removePack: "Delete {0}", removedPack: "Skin pack “{0}” deleted.", removeFailed: "The skin pack was not deleted ({0}).",
33
+ agentsTitle: "Agents changing packs", agentsLead: "Agents with Office skin packs switched on (Settings → agent) may check, upload, replace, delete and restore packs. Each change is logged and told to you in General.",
34
+ agentsNone: "No agent may change packs.", agentsAllowed: "May change packs: {0}", approval: "Their changes",
35
+ approvalApply: "Apply at once", approvalApprove: "Need my yes", pendingNone: "Nothing waits for your yes.",
36
+ approve: "Approve", reject: "Reject", decided: "Done: {0}", decideFailed: "Not done: {0}", recent: "Recent changes", refresh: "Refresh",
37
+ act_upload: "upload", act_delete: "delete", act_restore: "restore", act_style: "office style",
31
38
  },
32
39
  "zh-TW": {
33
40
  edit: "編輯辦公室", title: "辦公室", lead: "這個 fleet 的每個人在辦公室檢視中都會看到這些辦公室。沒有選任何成員的辦公室會顯示全部。",
@@ -44,11 +51,16 @@ register("orge", {
44
51
  packsNone: "還沒有外觀包。", upload: "上傳外觀包", uploadFolder: "上傳資料夾", uploading: "上傳中…", uploaded: "已上傳外觀包「{0}」。",
45
52
  uploadFailed: "外觀包沒有上傳:{0}", noManifest: "檔案中沒有 manifest.json", badManifest: "manifest.json 不是 JSON",
46
53
  removePack: "刪除 {0}", removedPack: "已刪除外觀包「{0}」。", removeFailed: "外觀包沒有刪除({0})。",
54
+ agentsTitle: "會改外觀包的 agent", agentsLead: "開啟「辦公室外觀包」的 agent(設定 → agent)可以檢查、上傳、取代、刪除與還原外觀包。每次變更都會記錄,並在 General 通知你。",
55
+ agentsNone: "沒有 agent 能改外觀包。", agentsAllowed: "可以改外觀包:{0}", approval: "它們的變更",
56
+ approvalApply: "直接套用", approvalApprove: "要我核准", pendingNone: "沒有等你核准的變更。",
57
+ approve: "核准", reject: "拒絕", decided: "完成:{0}", decideFailed: "沒有完成:{0}", recent: "最近的變更", refresh: "重新整理",
58
+ act_upload: "上傳", act_delete: "刪除", act_restore: "還原", act_style: "辦公室風格",
47
59
  },
48
60
  });
49
61
  const te = (k, ...v) => t(`orge.${k}`, ...v);
50
62
  export const MAX_OFFICES = 20;
51
- const STYLES = ["pixel", "studio"];
63
+ const STYLES = BUILT_IN_SKINS;
52
64
 
53
65
  /** A file's bytes as base64 (in slices: a large picture never goes through one huge string call). */
54
66
  export function base64Of(bytes) {
@@ -113,6 +125,46 @@ function Packs({ packs, store }) {
113
125
  </fieldset>`;
114
126
  }
115
127
 
128
+ /**
129
+ * #2692: the agents that may change packs, the fleet's approval mode, the changes waiting for an admin's yes (approve /
130
+ * reject; judged again by the host on a yes) and the newest log entries. Read on opening, after each action, and when
131
+ * the packs change.
132
+ */
133
+ function PackAgents({ store, packs }) {
134
+ const [d, setD] = useState(null);
135
+ const [busy, setBusy] = useState(false);
136
+ const [msg, setMsg] = useState("");
137
+ const load = async () => { const c = await store.packChanges(); if (c) setD(c); };
138
+ useEffect(() => { void load(); }, [packs.map((p) => `${p.id}:${p.sha}`).join(",")]);
139
+ if (!d) return null;
140
+ const mode = async (approval) => {
141
+ setBusy(true); setMsg("");
142
+ try { const r = await store.setPackApproval(approval); if (r.ok && r.changes) setD(r.changes); else setMsg(te("decideFailed", r.error)); } finally { setBusy(false); }
143
+ };
144
+ const decide = async (id, decision) => {
145
+ setBusy(true); setMsg("");
146
+ try { const r = await store.decidePackChange(id, decision); setMsg(r.ok ? te("decided", r.text) : te("decideFailed", r.detail || r.error)); await load(); } finally { setBusy(false); }
147
+ };
148
+ const what = (v) => `${te(`act_${v.action}`)} ${v.action === "style" ? `${v.office} → ${v.style}` : v.packs.map((p) => p.name !== p.id ? `${p.name} (${p.id})` : p.id).join(", ")}`;
149
+ return html`<fieldset class="oe-packs oe-form oe-pack-agents" disabled=${busy}>
150
+ <legend class="oe-label">${te("agentsTitle")}</legend>
151
+ <p class="note">${te("agentsLead")}</p>
152
+ <p class="note" id="oePackAgents">${d.agents.length ? te("agentsAllowed", d.agents.join(", ")) : te("agentsNone")}</p>
153
+ <label class="oe-field"><span class="oe-label">${te("approval")}</span>
154
+ <select class="tb-select oe-pack-approval" value=${d.approval} onChange=${(e) => mode(e.target.value)}>
155
+ <option value="approve">${te("approvalApprove")}</option><option value="apply">${te("approvalApply")}</option></select></label>
156
+ ${d.pending.length ? html`<ul class="oe-pack-list">${d.pending.map((v) => html`<li key=${v.id} class="oe-pack oe-pack-pending" data-change=${v.id}>
157
+ <span>${v.instance}: ${what(v)}</span>
158
+ <button type="button" class="btn btn-sm btn-primary oe-pack-approve" onClick=${() => decide(v.id, "approve")}>${te("approve")}</button>
159
+ <button type="button" class="btn btn-sm oe-pack-reject" onClick=${() => decide(v.id, "reject")}>${te("reject")}</button></li>`)}</ul>`
160
+ : html`<p class="note">${te("pendingNone")}</p>`}
161
+ ${d.log.length ? html`<details class="oe-pack-log"><summary>${te("recent")}</summary><ul class="oe-pack-list">${d.log.slice(0, 10).map((e, i) => html`<li key=${i} class="oe-pack muted">
162
+ <span class="mono">${e.at.slice(0, 16).replace("T", " ")}</span> <span>${e.by}: ${e.action} ${e.pack}${e.outcome ? ` — ${e.outcome}` : ""}${e.approved_by ? ` (${e.approved_by})` : ""}</span></li>`)}</ul></details>` : null}
163
+ <button type="button" class="btn btn-sm oe-pack-refresh" onClick=${() => { void load(); }}>${te("refresh")}</button>
164
+ ${msg ? html`<p class="note oe-pack-msg" role="status">${msg}</p>` : null}
165
+ </fieldset>`;
166
+ }
167
+
116
168
  /** A new office's id: unique among `taken`, the store's pattern ([A-Za-z0-9_-], never "auto"). */
117
169
  export function newOfficeId(taken, rand = Math.random) {
118
170
  for (;;) {
@@ -147,8 +199,11 @@ export function OfficeEditor({ offices, rev, view, store, packs = [], ask = () =
147
199
  // the rev under an older draft (a save would then overwrite what it never showed). After a 409 the person chooses
148
200
  // (#2431): keep their edits (on the newer rev: Save then replaces what was saved meanwhile, knowingly) or load the
149
201
  // newer list — never their draft thrown away unasked.
150
- const [base, setBase] = useState(() => ({ rev, draft: offices.map((o) => ({ ...o, members: [...o.members] })) }));
202
+ // `saved`: the list the draft is measured against (as read, or the newer one loaded) — the draft holds changes when
203
+ // it differs (#2585: leaving the page beside the docked editor then asks first).
204
+ const [base, setBase] = useState(() => ({ rev, saved: JSON.stringify(offices), draft: offices.map((o) => ({ ...o, members: [...o.members] })) }));
151
205
  const draft = base.draft;
206
+ const dirty = JSON.stringify(draft) !== base.saved;
152
207
  const setDraft = (next) => setBase((b) => ({ ...b, draft: typeof next === "function" ? next(b.draft) : next }));
153
208
  const [open, setOpen] = useState(null); // the office whose members are shown
154
209
  const [busy, setBusy] = useState(false);
@@ -157,14 +212,14 @@ export function OfficeEditor({ offices, rev, view, store, packs = [], ask = () =
157
212
  const copy = (list) => list.map((o) => ({ ...o, members: [...o.members] }));
158
213
  // The choice takes the snapshot in force when it is made: the stream may have read a newer one since the 409 (#2449).
159
214
  const inForce = () => current() || conflict;
160
- const keepMine = () => { const now = inForce(); setBase((b) => ({ rev: now.rev, draft: b.draft })); setConflict(null); setMsg(te("keptMine")); };
161
- const loadTheirs = () => { const now = inForce(); setBase({ rev: now.rev, draft: copy(now.offices) }); setOpen(null); setConflict(null); setMsg(""); };
215
+ const keepMine = () => { const now = inForce(); setBase((b) => ({ rev: now.rev, saved: JSON.stringify(now.offices), draft: b.draft })); setConflict(null); setMsg(te("keptMine")); };
216
+ const loadTheirs = () => { const now = inForce(); setBase({ rev: now.rev, saved: JSON.stringify(now.offices), draft: copy(now.offices) }); setOpen(null); setConflict(null); setMsg(""); };
162
217
  const everyone = useMemo(() => { const m = new Map(); for (const n of [...view.top, ...view.teams.flatMap((tm) => tm.nodes), ...view.rest.nodes]) if (!m.has(n.name)) m.set(n.name, n); return [...m.values()]; }, [view]);
163
218
  const set = (id, patch) => setDraft((d) => d.map((o) => (o.id === id ? { ...o, ...patch } : o)));
164
219
  const add = () => {
165
220
  if (draft.length >= MAX_OFFICES) return;
166
221
  const id = newOfficeId(new Set(draft.map((o) => o.id)));
167
- setDraft((d) => [...d, { id, name: te("newName"), skin: "pixel", members: [], layout: null }]);
222
+ setDraft((d) => [...d, { id, name: te("newName"), skin: "cozy", members: [], layout: null }]); // #2631: a new office is cozy
168
223
  setOpen(id);
169
224
  };
170
225
  // Everything is frozen from Save until its answer (and a 409's re-read) is in (#2424 review): no edit made meanwhile
@@ -191,7 +246,8 @@ export function OfficeEditor({ offices, rev, view, store, packs = [], ask = () =
191
246
  };
192
247
  const actions = html`<button type="button" class="btn" onClick=${onClose} disabled=${busy}>${te("cancel")}</button>
193
248
  <button type="button" class="btn btn-primary" id="oeSave" onClick=${save} disabled=${busy || !!conflict}>${te("save")}</button>`;
194
- return html`<${Dialog} title=${te("title")} onClose=${onClose} actions=${actions} busy=${busy} wide=${true}>
249
+ // #2585: docked on the right on a wide desk (the page beside it in use); the centred dialog elsewhere.
250
+ return html`<${SidePanel} title=${te("title")} onClose=${onClose} actions=${actions} busy=${busy} dirty=${dirty}>
195
251
  <p class="note">${te("lead")}</p>
196
252
  <fieldset class="oe-form" disabled=${busy}>
197
253
  ${draft.length ? null : html`<p class="note" id="oeNone">${te("none")}</p>`}
@@ -201,7 +257,7 @@ export function OfficeEditor({ offices, rev, view, store, packs = [], ask = () =
201
257
  <input type="text" class="oe-name" maxlength="60" value=${o.name} onInput=${(e) => set(o.id, { name: e.target.value })} /></label>
202
258
  <label class="oe-field"><span class="oe-label">${te("styleLabel")}</span>
203
259
  <select class="tb-select oe-style" value=${o.skin} onChange=${(e) => set(o.id, { skin: e.target.value })}>
204
- ${STYLES.map((s) => html`<option key=${s} value=${s}>${t(`orgo.style_${s}`)}</option>`)}
260
+ ${STYLES.filter((s) => !packs.some((pk) => pk.id === s)).map((s) => html`<option key=${s} value=${s}>${t(`orgo.style_${s}`)}</option>`)}
205
261
  ${packs.map((pk) => html`<option key=${`pack:${pk.id}`} value=${pk.id}>${pk.name}</option>`)}
206
262
  ${STYLES.includes(o.skin) || packs.some((pk) => pk.id === o.skin) ? null : html`<option key=${o.skin} value=${o.skin}>${te("styleUnknown", o.skin)}</option>`}</select></label>
207
263
  <button type="button" class="btn btn-sm oe-members-btn" aria-expanded=${open === o.id ? "true" : "false"} onClick=${() => setOpen(open === o.id ? null : o.id)}>
@@ -216,9 +272,10 @@ export function OfficeEditor({ offices, rev, view, store, packs = [], ask = () =
216
272
  </fieldset>
217
273
  ${msg ? html`<p class="note oe-msg" role="status">${msg}</p>` : null}
218
274
  ${store.uploadSkin ? html`<${Packs} packs=${packs} store=${store} />` : null}
275
+ ${store.packChanges ? html`<${PackAgents} packs=${packs} store=${store} />` : null}
219
276
  ${conflict ? html`<div class="oe-conflict">
220
277
  <button type="button" class="btn btn-sm" id="oeKeepMine" onClick=${keepMine} disabled=${busy}>${te("keepMine")}</button>
221
278
  <button type="button" class="btn btn-sm" id="oeLoadTheirs" onClick=${loadTheirs} disabled=${busy}>${te("loadTheirs")}</button>
222
279
  </div>` : null}
223
- </${Dialog}>`;
280
+ </${SidePanel}>`;
224
281
  }