@songsid/agend 2.2.0-beta.9 → 2.3.0-beta.1

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 (422) hide show
  1. package/README.md +40 -24
  2. package/README.zh-TW.md +40 -24
  3. package/dist/agent-cli.js +248 -2
  4. package/dist/agent-cli.js.map +1 -1
  5. package/dist/agent-endpoint.d.ts +2 -0
  6. package/dist/agent-endpoint.js +28 -0
  7. package/dist/agent-endpoint.js.map +1 -1
  8. package/dist/agent-file-read.d.ts +30 -0
  9. package/dist/agent-file-read.js +129 -0
  10. package/dist/agent-file-read.js.map +1 -0
  11. package/dist/agent-whats-new.js +20 -1
  12. package/dist/agent-whats-new.js.map +1 -1
  13. package/dist/atomic-write.js +9 -1
  14. package/dist/atomic-write.js.map +1 -1
  15. package/dist/auth-api.d.ts +3 -1
  16. package/dist/auth-api.js +32 -1
  17. package/dist/auth-api.js.map +1 -1
  18. package/dist/backend/antigravity.d.ts +33 -0
  19. package/dist/backend/antigravity.js +129 -21
  20. package/dist/backend/antigravity.js.map +1 -1
  21. package/dist/backend/claude-code.d.ts +3 -0
  22. package/dist/backend/claude-code.js +9 -0
  23. package/dist/backend/claude-code.js.map +1 -1
  24. package/dist/backend/codex-metadata.d.ts +33 -0
  25. package/dist/backend/codex-metadata.js +122 -18
  26. package/dist/backend/codex-metadata.js.map +1 -1
  27. package/dist/backend/codex.d.ts +3 -0
  28. package/dist/backend/codex.js +17 -1
  29. package/dist/backend/codex.js.map +1 -1
  30. package/dist/backend/effort-metadata.d.ts +37 -1
  31. package/dist/backend/effort-metadata.js +86 -15
  32. package/dist/backend/effort-metadata.js.map +1 -1
  33. package/dist/backend/grok.d.ts +3 -0
  34. package/dist/backend/grok.js +9 -0
  35. package/dist/backend/grok.js.map +1 -1
  36. package/dist/backend/kiro.d.ts +3 -1
  37. package/dist/backend/kiro.js +7 -3
  38. package/dist/backend/kiro.js.map +1 -1
  39. package/dist/backend/launch-effort.d.ts +61 -0
  40. package/dist/backend/launch-effort.js +170 -0
  41. package/dist/backend/launch-effort.js.map +1 -0
  42. package/dist/backend/muse.js +10 -2
  43. package/dist/backend/muse.js.map +1 -1
  44. package/dist/backend/types.d.ts +24 -0
  45. package/dist/backend/types.js +12 -1
  46. package/dist/backend/types.js.map +1 -1
  47. package/dist/backend-command.d.ts +91 -0
  48. package/dist/backend-command.js +122 -0
  49. package/dist/backend-command.js.map +1 -0
  50. package/dist/channel/adapters/discord.d.ts +14 -0
  51. package/dist/channel/adapters/discord.js +66 -4
  52. package/dist/channel/adapters/discord.js.map +1 -1
  53. package/dist/channel/adapters/telegram.d.ts +26 -1
  54. package/dist/channel/adapters/telegram.js +98 -7
  55. package/dist/channel/adapters/telegram.js.map +1 -1
  56. package/dist/channel/mcp-tools.js +36 -0
  57. package/dist/channel/mcp-tools.js.map +1 -1
  58. package/dist/channel/tool-router.d.ts +2 -0
  59. package/dist/channel/tool-router.js +1 -1
  60. package/dist/channel/tool-router.js.map +1 -1
  61. package/dist/channel/types.d.ts +31 -0
  62. package/dist/cli.js +124 -24
  63. package/dist/cli.js.map +1 -1
  64. package/dist/command-table.d.ts +2 -2
  65. package/dist/command-table.js +5 -2
  66. package/dist/command-table.js.map +1 -1
  67. package/dist/compact-watch.d.ts +86 -0
  68. package/dist/compact-watch.js +288 -0
  69. package/dist/compact-watch.js.map +1 -0
  70. package/dist/config-validator.js +55 -0
  71. package/dist/config-validator.js.map +1 -1
  72. package/dist/config.d.ts +1 -0
  73. package/dist/config.js +2 -1
  74. package/dist/config.js.map +1 -1
  75. package/dist/daemon.d.ts +9 -0
  76. package/dist/daemon.js +394 -29
  77. package/dist/daemon.js.map +1 -1
  78. package/dist/delivery-outbox.d.ts +8 -1
  79. package/dist/delivery-outbox.js +6 -2
  80. package/dist/delivery-outbox.js.map +1 -1
  81. package/dist/doctor.js +7 -0
  82. package/dist/doctor.js.map +1 -1
  83. package/dist/fleet-context.d.ts +12 -0
  84. package/dist/fleet-manager.d.ts +247 -9
  85. package/dist/fleet-manager.js +1227 -142
  86. package/dist/fleet-manager.js.map +1 -1
  87. package/dist/forged-envelope-scan.d.ts +4 -3
  88. package/dist/forged-envelope-scan.js +4 -3
  89. package/dist/forged-envelope-scan.js.map +1 -1
  90. package/dist/general-knowledge/skills/model-discovery/SKILL.md +4 -1
  91. package/dist/general-knowledge/skills/web-appearance/SKILL.md +258 -0
  92. package/dist/install-lock.d.ts +18 -1
  93. package/dist/install-lock.js +30 -3
  94. package/dist/install-lock.js.map +1 -1
  95. package/dist/instance-avatar.d.ts +5 -4
  96. package/dist/instance-avatar.js +15 -8
  97. package/dist/instance-avatar.js.map +1 -1
  98. package/dist/instance-config-impact.js +3 -0
  99. package/dist/instance-config-impact.js.map +1 -1
  100. package/dist/instance-lifecycle.js +9 -4
  101. package/dist/instance-lifecycle.js.map +1 -1
  102. package/dist/instructions.js +2 -2
  103. package/dist/instructions.js.map +1 -1
  104. package/dist/kiro-compact-pane.d.ts +38 -0
  105. package/dist/kiro-compact-pane.js +57 -0
  106. package/dist/kiro-compact-pane.js.map +1 -0
  107. package/dist/launchd-handoff.d.ts +43 -0
  108. package/dist/launchd-handoff.js +56 -0
  109. package/dist/launchd-handoff.js.map +1 -0
  110. package/dist/locale.js +202 -30
  111. package/dist/locale.js.map +1 -1
  112. package/dist/log-service.js +10 -2
  113. package/dist/log-service.js.map +1 -1
  114. package/dist/login-shell.d.ts +21 -0
  115. package/dist/login-shell.js +98 -0
  116. package/dist/login-shell.js.map +1 -0
  117. package/dist/needs-you-hub.d.ts +4 -0
  118. package/dist/needs-you-hub.js +31 -6
  119. package/dist/needs-you-hub.js.map +1 -1
  120. package/dist/needs-you.d.ts +1 -13
  121. package/dist/needs-you.js +3 -37
  122. package/dist/needs-you.js.map +1 -1
  123. package/dist/outbound-handlers.d.ts +32 -0
  124. package/dist/outbound-handlers.js +150 -45
  125. package/dist/outbound-handlers.js.map +1 -1
  126. package/dist/outbound-schemas.d.ts +76 -0
  127. package/dist/outbound-schemas.js +65 -4
  128. package/dist/outbound-schemas.js.map +1 -1
  129. package/dist/provider-probe.d.ts +29 -0
  130. package/dist/provider-probe.js +198 -2
  131. package/dist/provider-probe.js.map +1 -1
  132. package/dist/public-web-gateway.d.ts +2 -0
  133. package/dist/public-web-gateway.js +25 -1
  134. package/dist/public-web-gateway.js.map +1 -1
  135. package/dist/quickstart-api.d.ts +1 -1
  136. package/dist/quickstart-api.js +8 -6
  137. package/dist/quickstart-api.js.map +1 -1
  138. package/dist/quickstart.d.ts +12 -0
  139. package/dist/quickstart.js +36 -6
  140. package/dist/quickstart.js.map +1 -1
  141. package/dist/reply-buttons-controller.d.ts +86 -5
  142. package/dist/reply-buttons-controller.js +271 -7
  143. package/dist/reply-buttons-controller.js.map +1 -1
  144. package/dist/reply-buttons.d.ts +48 -3
  145. package/dist/reply-buttons.js +105 -10
  146. package/dist/reply-buttons.js.map +1 -1
  147. package/dist/reply-polls.d.ts +85 -0
  148. package/dist/reply-polls.js +154 -0
  149. package/dist/reply-polls.js.map +1 -0
  150. package/dist/repo-worktree-guard.d.ts +36 -0
  151. package/dist/repo-worktree-guard.js +143 -0
  152. package/dist/repo-worktree-guard.js.map +1 -0
  153. package/dist/restart-guard.d.ts +6 -3
  154. package/dist/restart-guard.js +6 -3
  155. package/dist/restart-guard.js.map +1 -1
  156. package/dist/restart-progress.d.ts +0 -2
  157. package/dist/restart-progress.js +0 -2
  158. package/dist/restart-progress.js.map +1 -1
  159. package/dist/scheduler/scheduler.d.ts +58 -1
  160. package/dist/scheduler/scheduler.js +176 -10
  161. package/dist/scheduler/scheduler.js.map +1 -1
  162. package/dist/sd-notify.d.ts +22 -1
  163. package/dist/sd-notify.js +63 -10
  164. package/dist/sd-notify.js.map +1 -1
  165. package/dist/service-activation.d.ts +15 -7
  166. package/dist/service-activation.js +42 -26
  167. package/dist/service-activation.js.map +1 -1
  168. package/dist/service-installer.d.ts +24 -0
  169. package/dist/service-installer.js +49 -1
  170. package/dist/service-installer.js.map +1 -1
  171. package/dist/service-plan.d.ts +11 -0
  172. package/dist/service-plan.js +23 -0
  173. package/dist/service-plan.js.map +1 -1
  174. package/dist/settings-api.js +74 -1
  175. package/dist/settings-api.js.map +1 -1
  176. package/dist/settings-change.js +8 -0
  177. package/dist/settings-change.js.map +1 -1
  178. package/dist/settings-effect.js +16 -2
  179. package/dist/settings-effect.js.map +1 -1
  180. package/dist/settings-http-confirmation.js +5 -0
  181. package/dist/settings-http-confirmation.js.map +1 -1
  182. package/dist/systemd-peer-bus.d.ts +95 -0
  183. package/dist/systemd-peer-bus.js +430 -0
  184. package/dist/systemd-peer-bus.js.map +1 -0
  185. package/dist/systemd-runtime.d.ts +27 -1
  186. package/dist/systemd-runtime.js +69 -7
  187. package/dist/systemd-runtime.js.map +1 -1
  188. package/dist/tool-permissions.js +53 -3
  189. package/dist/tool-permissions.js.map +1 -1
  190. package/dist/topic-commands.d.ts +72 -2
  191. package/dist/topic-commands.js +156 -38
  192. package/dist/topic-commands.js.map +1 -1
  193. package/dist/topic-link.d.ts +128 -0
  194. package/dist/topic-link.js +269 -0
  195. package/dist/topic-link.js.map +1 -0
  196. package/dist/transcript-jsonl.d.ts +7 -2
  197. package/dist/transcript-jsonl.js +13 -7
  198. package/dist/transcript-jsonl.js.map +1 -1
  199. package/dist/transcript-sources.js +6 -1
  200. package/dist/transcript-sources.js.map +1 -1
  201. package/dist/types.d.ts +21 -0
  202. package/dist/ui/app-effects.js +187 -0
  203. package/dist/ui/chat-commands.js +1 -0
  204. package/dist/ui/chat-effect-posters.js +42 -0
  205. package/dist/ui/chat-effects.js +72 -0
  206. package/dist/ui/chat-live-replies.js +58 -0
  207. package/dist/ui/chat-render.js +59 -14
  208. package/dist/ui/chat-store.js +78 -8
  209. package/dist/ui/chat-strings.js +43 -16
  210. package/dist/ui/chat-thread.js +175 -31
  211. package/dist/ui/chat-tour.js +18 -15
  212. package/dist/ui/effect-trial-controller.js +119 -0
  213. package/dist/ui/effect-trial-offer.js +31 -0
  214. package/dist/ui/effect-trial-panel.js +37 -0
  215. package/dist/ui/image-lightbox.js +1 -1
  216. package/dist/ui/panel-chat.js +101 -27
  217. package/dist/ui/panel-details.js +18 -5
  218. package/dist/ui/panel-fleet.js +18 -5
  219. package/dist/ui/panel-log.js +10 -10
  220. package/dist/ui/panel-needs.js +13 -10
  221. package/dist/ui/panel-settings.js +66 -12
  222. package/dist/ui/preview-panel.js +25 -9
  223. package/dist/ui/settings-appearance.js +325 -0
  224. package/dist/ui/settings-dialogs.js +11 -6
  225. package/dist/ui/settings-effects.js +201 -0
  226. package/dist/ui/settings-model.js +3 -1
  227. package/dist/ui/settings-mods.js +257 -0
  228. package/dist/ui/settings-owner-effects.js +80 -0
  229. package/dist/ui/settings-strings.js +269 -10
  230. package/dist/ui/settings-token.js +7 -0
  231. package/dist/ui/settings-wizard.js +5 -3
  232. package/dist/ui/shared/agend-auth.js +27 -3
  233. package/dist/ui/shared/agent-widget.js +248 -0
  234. package/dist/ui/shared/app-i18n.js +3 -1
  235. package/dist/ui/shared/app-prefs.js +30 -10
  236. package/dist/ui/shared/app-route.js +23 -1
  237. package/dist/ui/shared/app-session.js +1 -1
  238. package/dist/ui/shared/app-shell.js +29 -16
  239. package/dist/ui/shared/app-store.js +2 -0
  240. package/dist/ui/shared/app-stream.js +15 -7
  241. package/dist/ui/shared/app-themes.js +188 -0
  242. package/dist/ui/shared/app.css +266 -11
  243. package/dist/ui/shared/app.js +6 -0
  244. package/dist/ui/shared/card-strings.js +28 -0
  245. package/dist/ui/shared/effect-app-runtime.js +248 -0
  246. package/dist/ui/shared/effect-baseline.js +64 -0
  247. package/dist/ui/shared/effect-chat-runtime.js +154 -0
  248. package/dist/ui/shared/effect-cursor.js +125 -0
  249. package/dist/ui/shared/effect-frame.js +127 -0
  250. package/dist/ui/shared/effect-geometry.js +122 -0
  251. package/dist/ui/shared/effect-host-style.js +10 -0
  252. package/dist/ui/shared/effect-owner-regions.js +74 -0
  253. package/dist/ui/shared/effect-owner-source.js +115 -0
  254. package/dist/ui/shared/effect-policy.js +137 -0
  255. package/dist/ui/shared/effect-poster-reader.js +98 -0
  256. package/dist/ui/shared/effect-posters.js +117 -0
  257. package/dist/ui/shared/effect-profiler.js +162 -0
  258. package/dist/ui/shared/effect-profiles.js +65 -0
  259. package/dist/ui/shared/effect-registry.js +496 -0
  260. package/dist/ui/shared/effect-runtime-loader.js +234 -0
  261. package/dist/ui/shared/effect-safety.js +153 -0
  262. package/dist/ui/shared/effect-stock-snapshot.js +34 -0
  263. package/dist/ui/shared/effect-stock-trust.js +165 -0
  264. package/dist/ui/shared/effect-trial-source.js +103 -0
  265. package/dist/ui/shared/effect-trial-stage.js +147 -0
  266. package/dist/ui/shared/header-tools.js +35 -15
  267. package/dist/ui/shared/identity-card.js +202 -0
  268. package/dist/ui/shared/identity-color.js +261 -0
  269. package/dist/ui/shared/instance-nav.js +22 -14
  270. package/dist/ui/shared/instance-switch.js +5 -2
  271. package/dist/ui/shared/owner-css.js +221 -0
  272. package/dist/ui/shared/owner-effect-prefs.js +117 -0
  273. package/dist/ui/shared/panel-view.js +96 -12
  274. package/dist/ui/shared/signin.js +17 -9
  275. package/dist/ui/shared/ui-dialog.js +1 -1
  276. package/dist/ui/shared/ui-icons.js +3 -0
  277. package/dist/ui/shared/ui-menu.js +1 -1
  278. package/dist/ui/shared/ui-toast.js +1 -1
  279. package/dist/ui/shared/view-strings.js +40 -2
  280. package/dist/ui/signin.html +9 -3
  281. package/dist/update-check.d.ts +31 -0
  282. package/dist/update-check.js +47 -0
  283. package/dist/update-check.js.map +1 -1
  284. package/dist/update-launch.d.ts +48 -1
  285. package/dist/update-launch.js +159 -5
  286. package/dist/update-launch.js.map +1 -1
  287. package/dist/usage/providers.d.ts +28 -8
  288. package/dist/usage/providers.js +96 -41
  289. package/dist/usage/providers.js.map +1 -1
  290. package/dist/view-api.d.ts +29 -0
  291. package/dist/view-api.js +214 -17
  292. package/dist/view-api.js.map +1 -1
  293. package/dist/web-api.d.ts +86 -2
  294. package/dist/web-api.js +1160 -92
  295. package/dist/web-api.js.map +1 -1
  296. package/dist/web-appearance-limits.d.ts +82 -0
  297. package/dist/web-appearance-limits.js +83 -0
  298. package/dist/web-appearance-limits.js.map +1 -0
  299. package/dist/web-appearance-policy.d.ts +66 -0
  300. package/dist/web-appearance-policy.js +94 -0
  301. package/dist/web-appearance-policy.js.map +1 -0
  302. package/dist/web-appearance-tools.d.ts +50 -0
  303. package/dist/web-appearance-tools.js +105 -0
  304. package/dist/web-appearance-tools.js.map +1 -0
  305. package/dist/web-appearance-view.d.ts +99 -0
  306. package/dist/web-appearance-view.js +163 -0
  307. package/dist/web-appearance-view.js.map +1 -0
  308. package/dist/web-appearance.d.ts +79 -0
  309. package/dist/web-appearance.js +298 -0
  310. package/dist/web-appearance.js.map +1 -0
  311. package/dist/web-asset-parts.d.ts +98 -0
  312. package/dist/web-asset-parts.js +133 -0
  313. package/dist/web-asset-parts.js.map +1 -0
  314. package/dist/web-auth.js +17 -1
  315. package/dist/web-auth.js.map +1 -1
  316. package/dist/web-card.d.ts +96 -0
  317. package/dist/web-card.js +232 -0
  318. package/dist/web-card.js.map +1 -0
  319. package/dist/web-chat-history.d.ts +39 -1
  320. package/dist/web-chat-history.js +69 -9
  321. package/dist/web-chat-history.js.map +1 -1
  322. package/dist/web-commands.d.ts +39 -3
  323. package/dist/web-commands.js +22 -5
  324. package/dist/web-commands.js.map +1 -1
  325. package/dist/web-effect-bundle.d.ts +28 -0
  326. package/dist/web-effect-bundle.js +126 -0
  327. package/dist/web-effect-bundle.js.map +1 -0
  328. package/dist/web-effect-canonical.d.ts +8 -0
  329. package/dist/web-effect-canonical.js +27 -0
  330. package/dist/web-effect-canonical.js.map +1 -0
  331. package/dist/web-effect-library.d.ts +114 -0
  332. package/dist/web-effect-library.js +530 -0
  333. package/dist/web-effect-library.js.map +1 -0
  334. package/dist/web-effect-poster.d.ts +8 -0
  335. package/dist/web-effect-poster.js +52 -0
  336. package/dist/web-effect-poster.js.map +1 -0
  337. package/dist/web-effect-profiles.d.ts +118 -0
  338. package/dist/web-effect-profiles.js +180 -0
  339. package/dist/web-effect-profiles.js.map +1 -0
  340. package/dist/web-effect-service.d.ts +86 -0
  341. package/dist/web-effect-service.js +312 -0
  342. package/dist/web-effect-service.js.map +1 -0
  343. package/dist/web-effect-tools.d.ts +47 -0
  344. package/dist/web-effect-tools.js +126 -0
  345. package/dist/web-effect-tools.js.map +1 -0
  346. package/dist/web-effect-trials.d.ts +86 -0
  347. package/dist/web-effect-trials.js +204 -0
  348. package/dist/web-effect-trials.js.map +1 -0
  349. package/dist/web-effect-upload.d.ts +29 -0
  350. package/dist/web-effect-upload.js +190 -0
  351. package/dist/web-effect-upload.js.map +1 -0
  352. package/dist/web-effect-worker.d.ts +8 -0
  353. package/dist/web-effect-worker.js +170 -0
  354. package/dist/web-effect-worker.js.map +1 -0
  355. package/dist/web-effect.d.ts +89 -0
  356. package/dist/web-effect.js +132 -0
  357. package/dist/web-effect.js.map +1 -0
  358. package/dist/web-identity-color.d.ts +87 -0
  359. package/dist/web-identity-color.js +209 -0
  360. package/dist/web-identity-color.js.map +1 -0
  361. package/dist/web-identity-palette.d.ts +113 -0
  362. package/dist/web-identity-palette.js +84 -0
  363. package/dist/web-identity-palette.js.map +1 -0
  364. package/dist/web-image-meta.d.ts +25 -0
  365. package/dist/web-image-meta.js +200 -0
  366. package/dist/web-image-meta.js.map +1 -0
  367. package/dist/web-login.d.ts +14 -0
  368. package/dist/web-login.js +4 -2
  369. package/dist/web-login.js.map +1 -1
  370. package/dist/web-mod-agent.d.ts +50 -0
  371. package/dist/web-mod-agent.js +203 -0
  372. package/dist/web-mod-agent.js.map +1 -0
  373. package/dist/web-mod-bundle.d.ts +144 -0
  374. package/dist/web-mod-bundle.js +464 -0
  375. package/dist/web-mod-bundle.js.map +1 -0
  376. package/dist/web-mod-export.d.ts +59 -0
  377. package/dist/web-mod-export.js +79 -0
  378. package/dist/web-mod-export.js.map +1 -0
  379. package/dist/web-mod-store.d.ts +283 -0
  380. package/dist/web-mod-store.js +624 -0
  381. package/dist/web-mod-store.js.map +1 -0
  382. package/dist/web-org.d.ts +3 -3
  383. package/dist/web-org.js +11 -17
  384. package/dist/web-org.js.map +1 -1
  385. package/dist/web-owner-css-store.d.ts +180 -0
  386. package/dist/web-owner-css-store.js +384 -0
  387. package/dist/web-owner-css-store.js.map +1 -0
  388. package/dist/web-owner-css.d.ts +58 -0
  389. package/dist/web-owner-css.js +552 -0
  390. package/dist/web-owner-css.js.map +1 -0
  391. package/dist/web-prefs.d.ts +66 -0
  392. package/dist/web-prefs.js +243 -35
  393. package/dist/web-prefs.js.map +1 -1
  394. package/dist/web-preview.d.ts +9 -1
  395. package/dist/web-preview.js +24 -7
  396. package/dist/web-preview.js.map +1 -1
  397. package/dist/web-profile.d.ts +32 -0
  398. package/dist/web-profile.js +132 -0
  399. package/dist/web-profile.js.map +1 -0
  400. package/dist/web-session.d.ts +14 -0
  401. package/dist/web-session.js +10 -0
  402. package/dist/web-session.js.map +1 -1
  403. package/dist/web-settings-patch.d.ts +18 -0
  404. package/dist/web-settings-patch.js +51 -0
  405. package/dist/web-settings-patch.js.map +1 -1
  406. package/dist/web-shell-routes.d.ts +1 -1
  407. package/dist/web-shell-routes.js +1 -1
  408. package/dist/web-shell-routes.js.map +1 -1
  409. package/dist/web-theme-store.d.ts +218 -0
  410. package/dist/web-theme-store.js +516 -0
  411. package/dist/web-theme-store.js.map +1 -0
  412. package/dist/web-theme.d.ts +121 -0
  413. package/dist/web-theme.js +407 -0
  414. package/dist/web-theme.js.map +1 -0
  415. package/dist/web-upload.d.ts +119 -4
  416. package/dist/web-upload.js +252 -39
  417. package/dist/web-upload.js.map +1 -1
  418. package/dist/web-widget.d.ts +72 -0
  419. package/dist/web-widget.js +245 -0
  420. package/dist/web-widget.js.map +1 -0
  421. package/launcher/install-admission.cjs +61 -6
  422. package/package.json +3 -3
@@ -2,6 +2,8 @@
2
2
  // Ported from settings.html with their reasons: every rule here was a fix (the review or issue named beside it).
3
3
  // No DOM, no fetch; the panel (panel-settings.js) and the tests call these directly.
4
4
 
5
+ /** #1854: the stuck timeout when none is set — the daemon's DEFAULT_STUCK_TIMEOUT_MS (src/daemon.ts), in minutes. */
6
+ export const HANG_TIMEOUT_DEFAULT_MINUTES = 10;
5
7
  export const BACKENDS = ["claude-code", "kiro-cli", "codex", "opencode", "antigravity", "grok", "muse"];
6
8
  export const CH_TYPES = ["discord", "telegram"];
7
9
  export const LOCALES = [["en", "English"], ["zh-TW", "繁體中文 (zh-TW)"]];
@@ -166,7 +168,7 @@ export function agentForm(inst, defaults) {
166
168
  description: inst.description || "", systemPrompt: inst.systemPrompt || "", tags: [...(inst.tags || [])],
167
169
  general_topic: !!inst.general_topic,
168
170
  auto_pause_after: String(inst.auto_pause_after ?? defaults.auto_pause_after ?? 0),
169
- hang: String(hang.timeout_minutes ?? (defaults.hang_detector && defaults.hang_detector.timeout_minutes) ?? 15),
171
+ hang: String(hang.timeout_minutes ?? (defaults.hang_detector && defaults.hang_detector.timeout_minutes) ?? HANG_TIMEOUT_DEFAULT_MINUTES),
170
172
  agent_mode: inst.agent_mode || defaults.agent_mode || "mcp",
171
173
  tool_set: inst.tool_set || defaults.tool_set || "full",
172
174
  tool_progress: inst.tool_progress ?? defaults.tool_progress ?? "off",
@@ -0,0 +1,257 @@
1
+ // #1629 r4 §4 (r4-5): Settings → Appearance → Mods. A mod is one file that carries a look: an agent's theme with its
2
+ // pictures, its card text, widgets, effects, and a person's own CSS. Importing is a person's action in two steps:
3
+ // the file is checked whole and its plan shown in full (every part, the custom CSS's whole text) — then only the
4
+ // parts the person ticks are applied, all or nothing. Custom CSS from a file is never ticked by default. Installed
5
+ // mods can be downloaded again or uninstalled (what they applied stays: Reset and the CSS controls work on it).
6
+ // Export builds a file from what is in force now.
7
+ import { html, useEffect, useLayoutEffect, useRef, useState } from "/assets/app-html.js";
8
+ import { t } from "/assets/app-i18n.js";
9
+ import { appStore, useStore } from "/assets/app-store.js";
10
+ import { Icon } from "/assets/ui-icons.js";
11
+ import { toast } from "/assets/ui-toast.js";
12
+ import { confirmDialog } from "/assets/ui-confirm.js";
13
+ import { startOwnerCssTrial } from "/assets/owner-css.js";
14
+ import { AgentWidget, checkWidget, fleetBinds, instanceBinds } from "/assets/agent-widget.js";
15
+ import "./settings-strings.js";
16
+
17
+ const tn = (k, ...v) => t(`settings.${k}`, ...v);
18
+ const kib = (n) => (n >= 1024 * 1024 ? `${(n / 1024 / 1024).toFixed(1)} MiB` : `${Math.max(1, Math.round(n / 1024))} KiB`);
19
+
20
+ async function call(path, init) {
21
+ let res;
22
+ try { res = await fetch(path, { credentials: "same-origin", ...init }); } catch { return { ok: false, status: 0, body: null }; }
23
+ let body = null;
24
+ try { body = await res.json(); } catch { /* not JSON */ }
25
+ return { ok: res.ok, status: res.status, body, res };
26
+ }
27
+ const postJson = (path, body) => call(path, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body) });
28
+ function download(text, name) {
29
+ const url = URL.createObjectURL(new Blob([text], { type: "application/octet-stream" }));
30
+ const a = document.createElement("a"); a.href = url; a.download = name; a.className = "offscreen";
31
+ document.body.appendChild(a); a.click(); a.remove();
32
+ URL.revokeObjectURL(url);
33
+ }
34
+ /** Why the host refused, in words (the first problems of a bundle, each with where it is). */
35
+ function why(body) {
36
+ if (!body) return tn("modFailed", "?");
37
+ if (Array.isArray(body.problems) && body.problems.length) return body.problems.slice(0, 5).map((p) => `${p.path || "file"}: ${p.problem}`).join("\n");
38
+ return tn("modFailed", body.detail ? `${body.error}: ${body.detail}` : body.error || "?");
39
+ }
40
+
41
+ /** The parts a plan offers, each with its default: everything but custom CSS. */
42
+ export function defaultChoice(plan) {
43
+ const c = { theme: false, identity: false, effects: false, ownerCss: false, widgets: {}, replace: false };
44
+ for (const it of plan.items) {
45
+ if (it.part === "theme" || it.part === "identity") c[it.part] = true;
46
+ else if (it.part === "widget") c.widgets[it.slot] = true;
47
+ else if (it.part === "effect") c.effects = true;
48
+ // ownerCss stays false: "read it before turning it on" (r4 §4).
49
+ }
50
+ return c;
51
+ }
52
+
53
+ const COLOR = /^#[0-9a-f]{6}$/;
54
+ /** A colour as a swatch (its fill set through the CSSOM — never a style attribute) and its hex code. */
55
+ function Swatch({ color }) {
56
+ const ref = useRef(null);
57
+ useLayoutEffect(() => { if (ref.current && ref.current.style && COLOR.test(color)) ref.current.style.setProperty("--sw", color); }, [color]);
58
+ return html`<span class="s-mod-sw"><span class="s-mod-sw-chip" ref=${ref} aria-hidden="true"></span><code>${color}</code></span>`;
59
+ }
60
+ /** Every field of the checked theme, as it will be applied — nothing left unseen (#1897 review). */
61
+ export function ThemeDetail({ theme, pictures, effects }) {
62
+ const th = theme || {};
63
+ const row = (k, v) => html`<div class="s-mod-kv"><span class="k">${k}</span><span class="v">${v}</span></div>`;
64
+ const pal = (mode) => {
65
+ const p = th.colors && th.colors[mode];
66
+ return p && Object.keys(p).length ? row(tn(mode === "dark" ? "modColorsDark" : "modColorsLight"), html`<span class="s-mod-sws">${Object.keys(p).map((k) => html`<span key=${k} class="s-mod-sw-item">${k} <${Swatch} color=${p[k]} /></span>`)}</span>`) : null;
67
+ };
68
+ const picOf = (id) => (pictures || []).find((p) => p.id === id);
69
+ const pic = (id, label) => {
70
+ const p = picOf(id);
71
+ return p ? row(label, html`<span class="s-mod-pic"><img src=${`/ui/mods/staged/${p.id}`} alt=${label} loading="lazy" /><span class="muted">${p.path} · ${kib(p.bytes)}</span></span>`) : null;
72
+ };
73
+ return html`<div class="s-mod-theme">
74
+ ${th.accent ? row(tn("modAccent"), html`<${Swatch} color=${th.accent} />`) : null}
75
+ ${pal("light")}${pal("dark")}
76
+ ${th.bubble ? row(tn("modBubble"), `${th.bubble.radius !== undefined ? `${th.bubble.radius}px` : ""}${th.bubble.radius !== undefined && th.bubble.border ? " · " : ""}${th.bubble.border || ""}`) : null}
77
+ ${th.font ? row(tn("modFont"), th.font) : null}
78
+ ${th.workingLabel ? row(tn("modWorkingLabel"), html`<span class="s-mod-text">${th.workingLabel}</span>`) : null}
79
+ ${th.background ? pic(th.background.image, tn("modBackground")) : null}
80
+ ${th.background ? row(tn("modBackgroundFit"), `${th.background.fit} · ${Math.round(th.background.opacity * 100)}%`) : null}
81
+ ${th.avatar ? pic(th.avatar, tn("modAvatar")) : null}
82
+ ${(effects || []).map((e, i) => html`<div key=${i}>${row(tn("modPlacesEffect"), html`<span><code>${e.effect}</code>${e.id ? html` <span class="muted">${e.id.slice(0, 8)}</span>` : null}${Object.keys(e.params || {}).length ? html` · <code>${JSON.stringify(e.params)}</code>` : null}</span>`)}</div>`)}
83
+ <details class="s-mod-raw"><summary>${tn("modAllFields")}</summary><pre class="s-mod-css-text">${JSON.stringify(th, null, 2)}</pre></details>
84
+ </div>`;
85
+ }
86
+
87
+ function PlanItem({ it, choice, setChoice, binds, fleet }) {
88
+ const tick = (label, on, set) => html`<label class="check s-mod-tick"><input type="checkbox" checked=${on} onChange=${(e) => set(e.target.checked)} /><span>${label}</span></label>`;
89
+ if (it.part === "theme") {
90
+ return html`<li class="s-mod-item">${tick(tn("modPartTheme"), choice.theme, (v) => setChoice({ ...choice, theme: v }))}
91
+ <div class="s-mod-detail"><${ThemeDetail} theme=${it.theme} pictures=${it.pictures} effects=${it.effects} /></div></li>`;
92
+ }
93
+ if (it.part === "identity") {
94
+ return html`<li class="s-mod-item">${tick(tn("modPartIdentity"), choice.identity, (v) => setChoice({ ...choice, identity: v }))}
95
+ <div class="s-mod-detail">${it.tagline ? html`<span class="s-mod-text">${it.tagline}</span>` : null}${it.bio ? html`<span class="s-mod-text">${it.bio}</span>` : null}</div></li>`;
96
+ }
97
+ if (it.part === "widget") {
98
+ const w = checkWidget(it.slot, { spec: it.spec, data: it.data });
99
+ return html`<li class="s-mod-item">${tick(tn(`modPartWidget_${it.slot}`), !!choice.widgets[it.slot], (v) => setChoice({ ...choice, widgets: { ...choice.widgets, [it.slot]: v } }))}
100
+ <div class="s-mod-detail s-mod-widget">${w ? html`<${AgentWidget} slot=${it.slot} widget=${w} binds=${it.slot === "sidebar" ? fleet : binds} />` : null}</div></li>`;
101
+ }
102
+ if (it.part === "effect") {
103
+ return html`<li class="s-mod-item">${tick(tn("modPartEffect", it.title), choice.effects, (v) => setChoice({ ...choice, effects: v }))}
104
+ <div class="s-mod-detail"><span>${kib(it.bytes)} · ${tn("modRegions", it.regions.join(", "))}</span>${it.wants.length ? html`<span>${tn("modWants", it.wants.join(", "))}</span>` : null}</div></li>`;
105
+ }
106
+ if (it.part === "ownerCss") {
107
+ return html`<li class="s-mod-item s-mod-css">${tick(tn("modPartCss"), choice.ownerCss, (v) => setChoice({ ...choice, ownerCss: v }))}
108
+ <p class="s-mod-warn">${tn("modCssWarn")}</p>
109
+ <pre class="s-mod-css-text">${it.text}</pre>
110
+ ${it.assets.length ? html`<p class="sub">${tn("modCssAssets", it.assets.length, kib(it.assets.reduce((n, a) => n + a.bytes, 0)))}</p>` : null}</li>`;
111
+ }
112
+ return null;
113
+ }
114
+
115
+ function Plan({ staged, onDone }) {
116
+ const app = useStore(appStore);
117
+ const plan = staged.plan;
118
+ const [choice, setChoice] = useState(() => defaultChoice(plan));
119
+ const [target, setTarget] = useState("");
120
+ const [busy, setBusy] = useState(false);
121
+ const [error, setError] = useState("");
122
+ const toInstance = choice.theme || choice.identity || !!choice.widgets.chat_header || !!choice.widgets.details;
123
+ const names = (app.instances || []).map((i) => i.name);
124
+ const install = async () => {
125
+ setBusy(true); setError("");
126
+ const r = await postJson("/ui/mods/commit", { stage: staged.stage, plan_hash: staged.plan_hash, include: choice, ...(toInstance ? { target } : {}) });
127
+ setBusy(false);
128
+ // The stage this plan shows is gone or no longer this plan (replaced on another device, expired): read again —
129
+ // the page then shows whatever is staged now, with its own, fresh choices (keyed by stage).
130
+ if (!r.ok) { setError(why(r.body)); if (r.status === 404 || (r.body && r.body.error === "plan_changed")) onDone(); return; }
131
+ if (r.body.css) { startOwnerCssTrial(r.body.css.trial, r.body.css.sha, r.body.css.previous); toast(tn("cssTrying")); }
132
+ toast(tn("modInstalled", plan.mod.title));
133
+ onDone();
134
+ };
135
+ const cancel = async () => {
136
+ const r = await postJson("/ui/mods/abort", { stage: staged.stage });
137
+ // Not this stage any more (another device staged a file meanwhile): say so, then show what is staged now.
138
+ if (!r.ok) toast(r.status === 404 ? tn("modStageGone") : why(r.body), false);
139
+ onDone();
140
+ };
141
+ const inst = (app.instances || []).find((i) => i.name === target) || null;
142
+ const binds = inst ? instanceBinds(inst, app.exec && app.exec[inst.name], app.awaiting && Object.prototype.hasOwnProperty.call(app.awaiting, inst.name) ? app.awaiting[inst.name] : null) : {};
143
+ const m = plan.mod;
144
+ return html`<div class="s-mod-plan" role="group" aria-label=${tn("modPlan")}>
145
+ <h4>${m.title} <span class="muted">${m.name} ${m.version}</span></h4>
146
+ ${m.author ? html`<p class="sub">${tn("modAuthor", m.author)}</p>` : null}
147
+ ${m.description ? html`<p class="sub s-mod-text">${m.description}</p>` : null}
148
+ <p class="sub">${tn("modSizeId", kib(m.bytes), m.id.slice(0, 8))}${m.installed ? ` · ${tn("modAlready")}` : ""}</p>
149
+ ${m.replaces ? html`<label class="check"><input type="checkbox" checked=${choice.replace} onChange=${(e) => setChoice({ ...choice, replace: e.target.checked })} /><span>${tn("modReplace", m.replaces.version)}</span></label>` : null}
150
+ <ul class="s-mod-items">${plan.items.map((it, i) => html`<${PlanItem} key=${i} it=${it} choice=${choice} setChoice=${setChoice} binds=${binds} fleet=${fleetBinds(app.instances, app.exec, app.awaiting)} />`)}</ul>
151
+ ${toInstance ? html`<label class="field"><span>${tn("modTarget")}</span>
152
+ <select value=${target} onChange=${(e) => setTarget(e.target.value)}><option value="">${tn("modTargetPick")}</option>${names.map((n) => html`<option key=${n} value=${n}>${n}</option>`)}</select></label>` : null}
153
+ ${toInstance && !target ? html`<p class="sub">${tn("modTargetNeeded")}</p>` : null}
154
+ ${error ? html`<p class="err s-mod-err" role="alert">${error}</p>` : null}
155
+ <div class="s-widget-actions">
156
+ <button type="button" class="btn btn-primary" disabled=${busy || (toInstance && !target) || (m.replaces && !choice.replace)} onClick=${install}><${Icon} name="check" size=${16} />${tn("modInstall")}</button>
157
+ <button type="button" class="btn" disabled=${busy} onClick=${cancel}>${tn("modCancel")}</button>
158
+ </div>
159
+ </div>`;
160
+ }
161
+
162
+ function ExportForm() {
163
+ const app = useStore(appStore);
164
+ const [f, setF] = useState({ instance: "", name: "", version: "1.0.0", title: "", theme: true, identity: true, header: true, details: true, sidebar: false, ownerCss: false });
165
+ const [error, setError] = useState("");
166
+ const set = (k) => (e) => setF({ ...f, [k]: e.target.type === "checkbox" ? e.target.checked : e.target.value });
167
+ const go = async () => {
168
+ setError("");
169
+ const body = { instance: f.instance || undefined, name: f.name, version: f.version, title: f.title,
170
+ include: { theme: f.theme, identity: f.identity, ownerCss: f.ownerCss, widgets: { chat_header: f.header, details: f.details, sidebar: f.sidebar } } };
171
+ let res;
172
+ try { res = await fetch("/ui/mods/export", { method: "POST", credentials: "same-origin", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body) }); }
173
+ catch { setError(tn("modFailed", "network")); return; }
174
+ if (!res.ok) { let b = null; try { b = await res.json(); } catch { /* none */ } setError(why(b)); return; }
175
+ download(await res.text(), `${f.name}-${f.version}.agendmod.json`);
176
+ // Effects that aren't this person's to carry stay out of the file (their own claims and shared ones travel).
177
+ const omitted = Number(res.headers && res.headers.get ? res.headers.get("X-AgEnD-Effects-Omitted") : 0);
178
+ if (omitted > 0) toast(tn("modExportOmitted", omitted), false);
179
+ };
180
+ const box = (k, label) => html`<label class="check"><input type="checkbox" checked=${f[k]} onChange=${set(k)} /><span>${label}</span></label>`;
181
+ return html`<details class="s-mod-export"><summary>${tn("modExport")}</summary>
182
+ <p class="sub">${tn("modExportLead")}</p>
183
+ <label class="field"><span>${tn("modExportInstance")}</span><select value=${f.instance} onChange=${set("instance")}><option value="">—</option>${(app.instances || []).map((i) => html`<option key=${i.name} value=${i.name}>${i.name}</option>`)}</select></label>
184
+ <div class="s-mod-boxes">${box("theme", tn("modPartTheme"))}${box("identity", tn("modPartIdentity"))}${box("header", tn("modPartWidget_chat_header"))}${box("details", tn("modPartWidget_details"))}${box("sidebar", tn("modPartWidget_sidebar"))}${box("ownerCss", tn("modPartCss"))}</div>
185
+ <label class="field"><span>${tn("modExportName")}</span><input value=${f.name} placeholder="my-look" onInput=${set("name")} /></label>
186
+ <label class="field"><span>${tn("modExportVersion")}</span><input value=${f.version} onInput=${set("version")} /></label>
187
+ <label class="field"><span>${tn("modExportTitle")}</span><input value=${f.title} onInput=${set("title")} /></label>
188
+ ${error ? html`<p class="err s-mod-err" role="alert">${error}</p>` : null}
189
+ <div class="s-widget-actions"><button type="button" class="btn" disabled=${!f.name || !f.title} onClick=${go}><${Icon} name="download" size=${16} />${tn("modExportGo")}</button></div>
190
+ </details>`;
191
+ }
192
+
193
+ export function ModsCard() {
194
+ const [state, setState] = useState(null);
195
+ const [busy, setBusy] = useState(false);
196
+ const [error, setError] = useState("");
197
+ const file = useRef(null);
198
+ // Only an answer of the expected shape is shown (#1889's lesson): anything else is "couldn't load", never a crash.
199
+ const load = async () => {
200
+ const r = await call("/ui/mods");
201
+ const b = r.ok && r.body && typeof r.body === "object" ? r.body : null;
202
+ const staged = b && b.staged && typeof b.staged === "object" && typeof b.staged.stage === "string" && b.staged.plan && Array.isArray(b.staged.plan.items) ? b.staged : null;
203
+ const proposals = b && Array.isArray(b.proposals) ? b.proposals.filter((p) => p && typeof p.instance === "string" && typeof p.title === "string") : [];
204
+ setState(b && Array.isArray(b.mods) ? { mods: b.mods.filter((m) => m && typeof m.id === "string"), staged, proposals } : { failed: true });
205
+ };
206
+ useEffect(() => { load(); }, []);
207
+ if (!state) return html`<section class="card s-appearance s-mods"><h3>${tn("modsTitle")}</h3><p class="sub">…</p></section>`;
208
+ if (state.failed) return html`<section class="card s-appearance s-mods"><h3>${tn("modsTitle")}</h3><p class="err">${tn("modsLoadFailed")}</p></section>`;
209
+ const stage = async () => {
210
+ const f = file.current && file.current.files && file.current.files[0];
211
+ if (!f) return;
212
+ setBusy(true); setError("");
213
+ const r = await call("/ui/mods/stage", { method: "POST", headers: { "Content-Type": "application/json" }, body: f });
214
+ setBusy(false);
215
+ if (file.current) file.current.value = "";
216
+ if (!r.ok) { setError(why(r.body)); return; }
217
+ setState({ ...state, staged: r.body });
218
+ };
219
+ const review = async (p) => {
220
+ const r = await postJson("/ui/mods/proposal/stage", { instance: p.instance });
221
+ if (!r.ok) { setError(why(r.body)); return; }
222
+ setState({ ...state, staged: r.body });
223
+ };
224
+ const dismiss = async (p) => {
225
+ const r = await postJson("/ui/mods/proposal/dismiss", { instance: p.instance });
226
+ if (!r.ok) { toast(why(r.body), false); return; }
227
+ load();
228
+ };
229
+ const uninstall = async (m) => {
230
+ if (!(await confirmDialog({ message: tn("modUninstallConfirm", m.title), confirmLabel: tn("modUninstall"), danger: true }))) return;
231
+ const r = await postJson("/ui/mods/uninstall", { id: m.id });
232
+ if (!r.ok) { toast(why(r.body), false); return; }
233
+ toast(tn("modUninstalled", m.title));
234
+ load();
235
+ };
236
+ const fetchFile = async (m) => {
237
+ const r = await fetch(`/ui/mods/file/${m.id}`, { credentials: "same-origin" }).catch(() => null);
238
+ if (!r || !r.ok) { toast(tn("modFailed", r ? r.status : "network"), false); return; }
239
+ download(await r.text(), `${m.name}-${m.version}.agendmod.json`);
240
+ };
241
+ return html`<section class="card s-appearance s-mods"><h3>${tn("modsTitle")}</h3>
242
+ <p class="sub">${tn("modsLead")}</p>
243
+ ${state.staged ? html`<${Plan} key=${state.staged.stage} staged=${state.staged} onDone=${load} />` : html`<div class="s-mod-import">
244
+ <label class="btn"><${Icon} name="attach" size=${16} /><span>${tn("modChoose")}</span>
245
+ <input ref=${file} type="file" accept=".json,application/json" class="sr-only" disabled=${busy} onChange=${stage} /></label></div>`}
246
+ ${error ? html`<p class="err s-mod-err" role="alert">${error}</p>` : null}
247
+ ${!state.staged && (state.proposals || []).length ? html`<div class="s-mod-proposals"><h4>${tn("modProposals")}</h4><ul class="s-mod-list">${state.proposals.map((p) => html`<li key=${p.instance}>
248
+ <span class="grow">${tn("modProposedBy", p.instance)} <strong>${p.title}</strong> <span class="muted">${p.name} ${p.version}</span></span>
249
+ <button type="button" class="btn btn-sm" onClick=${() => review(p)}>${tn("modReview")}</button>
250
+ <button type="button" class="btn btn-sm" onClick=${() => dismiss(p)}>${tn("modDismiss")}</button></li>`)}</ul></div>` : null}
251
+ ${state.mods.length ? html`<ul class="s-mod-list">${state.mods.map((m) => html`<li key=${m.id}>
252
+ <span class="grow"><strong>${m.title}</strong> <span class="muted">${m.name} ${m.version}${m.target ? ` · ${tn("modAppliedTo", m.target)}` : ""}</span></span>
253
+ <button type="button" class="btn btn-sm" onClick=${() => fetchFile(m)}>${tn("modDownload")}</button>
254
+ <button type="button" class="btn btn-sm danger" onClick=${() => uninstall(m)}>${tn("modUninstall")}</button></li>`)}</ul>` : html`<p class="sub">${tn("modsNone")}</p>`}
255
+ <${ExportForm} />
256
+ </section>`;
257
+ }
@@ -0,0 +1,80 @@
1
+ // #1629: authoritative selectors and local slot controls; the app owns all playback and resource reads.
2
+ import { html, useEffect, useRef, useState } from "/assets/app-html.js";
3
+ import { register, t } from "/assets/app-i18n.js";
4
+ import { useStore } from "/assets/app-store.js";
5
+ import { OWNER_EFFECT_PREF_KEYS } from "/assets/app-prefs.js";
6
+ import { getAppEffects } from "./app-effects.js";
7
+ import { ownerEffectsStore, watchOwnerEffects, refreshOwnerEffects, saveOwnerEffects } from "/assets/owner-effect-prefs.js";
8
+ register("ownerEffects", {
9
+ en: {
10
+ title: "Your effect choices", operatorTitle: "Host-wide effect choices", person: "These choices follow this platform sign-in. Discord and Telegram accounts stay separate.",
11
+ operator: "Host-wide choices: everyone signed in as the host operator shares these slots.",
12
+ sidebar: "Sidebar", header: "Header", page: "Page", box: "Box", cursor: "Cursor", none: "None", savedChoice: "Saved choice",
13
+ unknown: "Choices are unavailable. Reload to try again.", changed: "This page can no longer access these choices. Reload and sign in again.",
14
+ unavailable: "No longer available", clear: "Clear", reload: "Reload choices", saved: "Saved.", failed: "Couldn't save. Reload choices and try again.",
15
+ savedRefresh: "Saved, but the choices couldn't be refreshed. Reload to check.", readOnly: "Sign in as an admin to change these choices.",
16
+ playback: "Playback requires Effects and preview consent. The cursor follows the same consent and falls back to stock cursors when unavailable.",
17
+ pause: "Pause", resume: "Resume", running: "Running", cursorFailed: "The cursor could not be applied here. Clear or reload the choice to try again.", starting: "Starting", queued: "Waiting for a slot", paused: "Paused", off: "Playback is off or unavailable here.", budget: "Paused to protect page responsiveness. Resume keeps the three-pauses safety limit.", resumeFailed: "This effect cannot resume: the page or its safety history is not ready. Reload the page or choose another effect.",
18
+ },
19
+ "zh-TW": {
20
+ title: "你的效果選擇", operatorTitle: "主機共用效果選擇", person: "選擇會跟隨這個平台的登入身分;Discord 與 Telegram 帳號各自獨立。",
21
+ operator: "主機共用選擇:所有以主機操作者身分登入的人,都會共用這些欄位。",
22
+ sidebar: "側邊欄", header: "頁首", page: "頁面", box: "區塊", cursor: "游標", none: "無", savedChoice: "已儲存的選擇",
23
+ unknown: "目前無法取得選擇,請重新載入再試。", changed: "這個頁面已無法存取選擇,請重新載入並登入。",
24
+ unavailable: "已無法使用", clear: "清除", reload: "重新載入選擇", saved: "已儲存。", failed: "無法儲存,請重新載入選擇再試。",
25
+ savedRefresh: "已儲存,但無法更新畫面;請重新載入確認。", readOnly: "請以管理員身分登入才能修改選擇。",
26
+ playback: "開啟效果並允許預覽後才會播放;游標也遵循相同的同意設定;無法使用時會還原一般游標。",
27
+ pause: "暫停", resume: "恢復", running: "播放中", cursorFailed: "這裡無法套用游標。可清除或重新載入選擇後再試。", starting: "啟動中", queued: "等待空位", paused: "已暫停", off: "這裡已關閉播放,或無法播放效果。", budget: "為維持頁面順暢,已暫停效果。恢復仍保留三次暫停的安全上限。", resumeFailed: "頁面或安全紀錄尚未就緒,無法恢復此效果。請重新載入頁面,或選擇其他效果。",
28
+ },
29
+ });
30
+ const text = key => t(`ownerEffects.${key}`);
31
+ export function ownerEffectFits(manifest, key) {
32
+ if (!manifest || typeof manifest !== "object" || !OWNER_EFFECT_PREF_KEYS.includes(key)) return false;
33
+ if (key === "fx.cursor") return !!manifest.cursor;
34
+ const region = key.slice(3);
35
+ return Array.isArray(manifest.regions) && manifest.regions.includes(region) && (region !== "box" || !!manifest.box);
36
+ }
37
+ export function OwnerEffects({ catalog, busy = false }) {
38
+ const state = useStore(ownerEffectsStore), [message, setMessage] = useState(null), life = useRef({ alive: false, seq: 0 }), [runtime, setRuntime] = useState(null);
39
+ useEffect(() => { life.current.alive = true; const stop = watchOwnerEffects(); return () => { life.current.alive = false; life.current.seq++; stop(); }; }, []);
40
+ useEffect(() => {
41
+ let live = true; const entry = getAppEffects();
42
+ const pull = () => { if (!live) return; const gates = entry?.gates(); setRuntime({ entry, snapshot: entry?.api?.ownerSnapshot?.(), cursor: entry?.cursorSnapshot?.(), available: !!entry && !entry.dead && gates.enabled && gates.show && gates.previewReady && gates.optIn && !gates.safe && !gates.publicLink && !gates.reducedMotion && !gates.saveData }); };
43
+ const off = entry?.subscribe(pull), end = entry?.onEnd(pull); pull();
44
+ return () => { live = false; off?.(); end?.(); };
45
+ }, []);
46
+ const reload = () => { getAppEffects()?.api?.changedOwner?.(); return refreshOwnerEffects(); };
47
+ // Library reloads/own share changes can alter availability without changing the preference revision.
48
+ useEffect(() => { if (catalog) reload(); }, [catalog]);
49
+ if (state.scope === "none") return null;
50
+ const disabled = busy || state.busy || state.status !== "known" || state.revoked || !catalog?.canWrite;
51
+ const save = async (key, id) => {
52
+ if (disabled || !life.current.alive) return;
53
+ if (id !== null && !(catalog.effects || []).some(e => e.id === id && ownerEffectFits(e.manifest, key))) return;
54
+ const seq = ++life.current.seq; setMessage(null);
55
+ const result = await saveOwnerEffects({ [key]: id });
56
+ if (!life.current.alive || seq !== life.current.seq) return;
57
+ if (result.ok || result.saved) getAppEffects()?.api?.changedOwner?.();
58
+ setMessage(text(result.ok ? "saved" : result.saved ? "savedRefresh" : "failed"));
59
+ };
60
+ return html`<section class="s-fx-owner" aria-label=${text(state.scope === "operator" ? "operatorTitle" : "title")}><h4>${text(state.scope === "operator" ? "operatorTitle" : "title")}</h4><p class="sub">${text(state.scope === "operator" ? "operator" : "person")}</p>
61
+ <p class="sub">${text("playback")}</p>${catalog && !catalog.canWrite ? html`<p class="sub">${text("readOnly")}</p>` : null}
62
+ <button class="btn" type="button" disabled=${busy || state.busy || state.revoked} onClick=${reload}>${text("reload")}</button>
63
+ ${state.status !== "known" ? html`<p class="sub" role="status">${text(state.revoked ? "changed" : "unknown")}</p>` : OWNER_EFFECT_PREF_KEYS.map(key => {
64
+ const row = state.slots[key], options = (catalog?.effects || []).filter(e => ownerEffectFits(e.manifest, key));
65
+ const absent = !!row.id && !options.some(e => e.id === row.id);
66
+ const play = runtime?.snapshot?.rows?.find(r => r.slot === key && r.id === row.id);
67
+ const unavailable = row.status === "unavailable" || runtime?.snapshot?.slots?.[key]?.id === row.id && runtime.snapshot.slots[key].status === "unavailable";
68
+ const phase = !runtime?.available ? "off" : play?.reason === "paused" || play?.reason === "host_budget" ? "paused" : play?.phase === "running" ? "running" : play?.phase ? "starting" : play?.verdict === "queued" ? "queued" : "off";
69
+ return html`<div class="s-fx-owner-row" key=${key}><label>${text(key.slice(3))}<select class="s-fx-target" data-owner-slot=${key} disabled=${disabled} value=${row.id || ""} onChange=${e => save(key, e.target.value || null)}>
70
+ <option value="">${text("none")}</option>${absent ? html`<option value=${row.id} disabled>${text(row.status === "unavailable" ? "unavailable" : "savedChoice")}</option>` : null}
71
+ ${options.map(e => html`<option key=${e.id} value=${e.id}>${e.manifest.title}</option>`)}</select></label>
72
+ ${unavailable ? html`<span class="sub" role="status">${text("unavailable")}</span>` : null}
73
+ ${key === "fx.cursor" && row.status === "available" && row.id && !unavailable ? html`<span class="sub" role="status" data-owner-runtime=${key}>${text(!runtime?.available ? "off" : runtime.cursor?.id === row.id && runtime.cursor.status === "running" ? "running" : runtime.cursor?.id === row.id && runtime.cursor.status === "loading" ? "starting" : runtime.cursor?.id === row.id && runtime.cursor.status === "unavailable" ? "cursorFailed" : "off")}</span>` : null}
74
+ ${key !== "fx.cursor" && row.status === "available" && row.id && !unavailable ? html`<span class="sub" role="status" data-owner-runtime=${key}>${text(phase)}</span>
75
+ ${play?.reason === "host_budget" ? html`<span class="sub">${text("budget")}</span>` : null}
76
+ <button class="btn" type="button" data-owner-pause=${key} disabled=${!runtime?.available || !play} onClick=${() => runtime?.entry?.api?.pauseOwner?.(key)}>${text("pause")}</button>
77
+ <button class="btn" type="button" data-owner-resume=${key} disabled=${!runtime?.available || !play} onClick=${() => { if (!runtime?.entry?.api?.resumeOwner?.(key)) setMessage(text("resumeFailed")); }}>${text("resume")}</button>` : null}
78
+ ${row.id ? html`<button class="btn" type="button" data-owner-clear=${key} disabled=${disabled} onClick=${() => save(key, null)}>${text("clear")}</button>` : null}</div>`;
79
+ })}${message ? html`<p class="sub" role="status">${message}</p>` : null}</section>`;
80
+ }