@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.
- package/README.md +40 -24
- package/README.zh-TW.md +40 -24
- package/dist/agent-cli.js +248 -2
- package/dist/agent-cli.js.map +1 -1
- package/dist/agent-endpoint.d.ts +2 -0
- package/dist/agent-endpoint.js +28 -0
- package/dist/agent-endpoint.js.map +1 -1
- package/dist/agent-file-read.d.ts +30 -0
- package/dist/agent-file-read.js +129 -0
- package/dist/agent-file-read.js.map +1 -0
- package/dist/agent-whats-new.js +20 -1
- package/dist/agent-whats-new.js.map +1 -1
- package/dist/atomic-write.js +9 -1
- package/dist/atomic-write.js.map +1 -1
- package/dist/auth-api.d.ts +3 -1
- package/dist/auth-api.js +32 -1
- package/dist/auth-api.js.map +1 -1
- package/dist/backend/antigravity.d.ts +33 -0
- package/dist/backend/antigravity.js +129 -21
- package/dist/backend/antigravity.js.map +1 -1
- package/dist/backend/claude-code.d.ts +3 -0
- package/dist/backend/claude-code.js +9 -0
- package/dist/backend/claude-code.js.map +1 -1
- package/dist/backend/codex-metadata.d.ts +33 -0
- package/dist/backend/codex-metadata.js +122 -18
- package/dist/backend/codex-metadata.js.map +1 -1
- package/dist/backend/codex.d.ts +3 -0
- package/dist/backend/codex.js +17 -1
- package/dist/backend/codex.js.map +1 -1
- package/dist/backend/effort-metadata.d.ts +37 -1
- package/dist/backend/effort-metadata.js +86 -15
- package/dist/backend/effort-metadata.js.map +1 -1
- package/dist/backend/grok.d.ts +3 -0
- package/dist/backend/grok.js +9 -0
- package/dist/backend/grok.js.map +1 -1
- package/dist/backend/kiro.d.ts +3 -1
- package/dist/backend/kiro.js +7 -3
- package/dist/backend/kiro.js.map +1 -1
- package/dist/backend/launch-effort.d.ts +61 -0
- package/dist/backend/launch-effort.js +170 -0
- package/dist/backend/launch-effort.js.map +1 -0
- package/dist/backend/muse.js +10 -2
- package/dist/backend/muse.js.map +1 -1
- package/dist/backend/types.d.ts +24 -0
- package/dist/backend/types.js +12 -1
- package/dist/backend/types.js.map +1 -1
- package/dist/backend-command.d.ts +91 -0
- package/dist/backend-command.js +122 -0
- package/dist/backend-command.js.map +1 -0
- package/dist/channel/adapters/discord.d.ts +14 -0
- package/dist/channel/adapters/discord.js +66 -4
- package/dist/channel/adapters/discord.js.map +1 -1
- package/dist/channel/adapters/telegram.d.ts +26 -1
- package/dist/channel/adapters/telegram.js +98 -7
- package/dist/channel/adapters/telegram.js.map +1 -1
- package/dist/channel/mcp-tools.js +36 -0
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/channel/tool-router.d.ts +2 -0
- package/dist/channel/tool-router.js +1 -1
- package/dist/channel/tool-router.js.map +1 -1
- package/dist/channel/types.d.ts +31 -0
- package/dist/cli.js +124 -24
- package/dist/cli.js.map +1 -1
- package/dist/command-table.d.ts +2 -2
- package/dist/command-table.js +5 -2
- package/dist/command-table.js.map +1 -1
- package/dist/compact-watch.d.ts +86 -0
- package/dist/compact-watch.js +288 -0
- package/dist/compact-watch.js.map +1 -0
- package/dist/config-validator.js +55 -0
- package/dist/config-validator.js.map +1 -1
- package/dist/config.d.ts +1 -0
- package/dist/config.js +2 -1
- package/dist/config.js.map +1 -1
- package/dist/daemon.d.ts +9 -0
- package/dist/daemon.js +394 -29
- package/dist/daemon.js.map +1 -1
- package/dist/delivery-outbox.d.ts +8 -1
- package/dist/delivery-outbox.js +6 -2
- package/dist/delivery-outbox.js.map +1 -1
- package/dist/doctor.js +7 -0
- package/dist/doctor.js.map +1 -1
- package/dist/fleet-context.d.ts +12 -0
- package/dist/fleet-manager.d.ts +247 -9
- package/dist/fleet-manager.js +1227 -142
- package/dist/fleet-manager.js.map +1 -1
- package/dist/forged-envelope-scan.d.ts +4 -3
- package/dist/forged-envelope-scan.js +4 -3
- package/dist/forged-envelope-scan.js.map +1 -1
- package/dist/general-knowledge/skills/model-discovery/SKILL.md +4 -1
- package/dist/general-knowledge/skills/web-appearance/SKILL.md +258 -0
- package/dist/install-lock.d.ts +18 -1
- package/dist/install-lock.js +30 -3
- package/dist/install-lock.js.map +1 -1
- package/dist/instance-avatar.d.ts +5 -4
- package/dist/instance-avatar.js +15 -8
- package/dist/instance-avatar.js.map +1 -1
- package/dist/instance-config-impact.js +3 -0
- package/dist/instance-config-impact.js.map +1 -1
- package/dist/instance-lifecycle.js +9 -4
- package/dist/instance-lifecycle.js.map +1 -1
- package/dist/instructions.js +2 -2
- package/dist/instructions.js.map +1 -1
- package/dist/kiro-compact-pane.d.ts +38 -0
- package/dist/kiro-compact-pane.js +57 -0
- package/dist/kiro-compact-pane.js.map +1 -0
- package/dist/launchd-handoff.d.ts +43 -0
- package/dist/launchd-handoff.js +56 -0
- package/dist/launchd-handoff.js.map +1 -0
- package/dist/locale.js +202 -30
- package/dist/locale.js.map +1 -1
- package/dist/log-service.js +10 -2
- package/dist/log-service.js.map +1 -1
- package/dist/login-shell.d.ts +21 -0
- package/dist/login-shell.js +98 -0
- package/dist/login-shell.js.map +1 -0
- package/dist/needs-you-hub.d.ts +4 -0
- package/dist/needs-you-hub.js +31 -6
- package/dist/needs-you-hub.js.map +1 -1
- package/dist/needs-you.d.ts +1 -13
- package/dist/needs-you.js +3 -37
- package/dist/needs-you.js.map +1 -1
- package/dist/outbound-handlers.d.ts +32 -0
- package/dist/outbound-handlers.js +150 -45
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.d.ts +76 -0
- package/dist/outbound-schemas.js +65 -4
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/provider-probe.d.ts +29 -0
- package/dist/provider-probe.js +198 -2
- package/dist/provider-probe.js.map +1 -1
- package/dist/public-web-gateway.d.ts +2 -0
- package/dist/public-web-gateway.js +25 -1
- package/dist/public-web-gateway.js.map +1 -1
- package/dist/quickstart-api.d.ts +1 -1
- package/dist/quickstart-api.js +8 -6
- package/dist/quickstart-api.js.map +1 -1
- package/dist/quickstart.d.ts +12 -0
- package/dist/quickstart.js +36 -6
- package/dist/quickstart.js.map +1 -1
- package/dist/reply-buttons-controller.d.ts +86 -5
- package/dist/reply-buttons-controller.js +271 -7
- package/dist/reply-buttons-controller.js.map +1 -1
- package/dist/reply-buttons.d.ts +48 -3
- package/dist/reply-buttons.js +105 -10
- package/dist/reply-buttons.js.map +1 -1
- package/dist/reply-polls.d.ts +85 -0
- package/dist/reply-polls.js +154 -0
- package/dist/reply-polls.js.map +1 -0
- package/dist/repo-worktree-guard.d.ts +36 -0
- package/dist/repo-worktree-guard.js +143 -0
- package/dist/repo-worktree-guard.js.map +1 -0
- package/dist/restart-guard.d.ts +6 -3
- package/dist/restart-guard.js +6 -3
- package/dist/restart-guard.js.map +1 -1
- package/dist/restart-progress.d.ts +0 -2
- package/dist/restart-progress.js +0 -2
- package/dist/restart-progress.js.map +1 -1
- package/dist/scheduler/scheduler.d.ts +58 -1
- package/dist/scheduler/scheduler.js +176 -10
- package/dist/scheduler/scheduler.js.map +1 -1
- package/dist/sd-notify.d.ts +22 -1
- package/dist/sd-notify.js +63 -10
- package/dist/sd-notify.js.map +1 -1
- package/dist/service-activation.d.ts +15 -7
- package/dist/service-activation.js +42 -26
- package/dist/service-activation.js.map +1 -1
- package/dist/service-installer.d.ts +24 -0
- package/dist/service-installer.js +49 -1
- package/dist/service-installer.js.map +1 -1
- package/dist/service-plan.d.ts +11 -0
- package/dist/service-plan.js +23 -0
- package/dist/service-plan.js.map +1 -1
- package/dist/settings-api.js +74 -1
- package/dist/settings-api.js.map +1 -1
- package/dist/settings-change.js +8 -0
- package/dist/settings-change.js.map +1 -1
- package/dist/settings-effect.js +16 -2
- package/dist/settings-effect.js.map +1 -1
- package/dist/settings-http-confirmation.js +5 -0
- package/dist/settings-http-confirmation.js.map +1 -1
- package/dist/systemd-peer-bus.d.ts +95 -0
- package/dist/systemd-peer-bus.js +430 -0
- package/dist/systemd-peer-bus.js.map +1 -0
- package/dist/systemd-runtime.d.ts +27 -1
- package/dist/systemd-runtime.js +69 -7
- package/dist/systemd-runtime.js.map +1 -1
- package/dist/tool-permissions.js +53 -3
- package/dist/tool-permissions.js.map +1 -1
- package/dist/topic-commands.d.ts +72 -2
- package/dist/topic-commands.js +156 -38
- package/dist/topic-commands.js.map +1 -1
- package/dist/topic-link.d.ts +128 -0
- package/dist/topic-link.js +269 -0
- package/dist/topic-link.js.map +1 -0
- package/dist/transcript-jsonl.d.ts +7 -2
- package/dist/transcript-jsonl.js +13 -7
- package/dist/transcript-jsonl.js.map +1 -1
- package/dist/transcript-sources.js +6 -1
- package/dist/transcript-sources.js.map +1 -1
- package/dist/types.d.ts +21 -0
- package/dist/ui/app-effects.js +187 -0
- package/dist/ui/chat-commands.js +1 -0
- package/dist/ui/chat-effect-posters.js +42 -0
- package/dist/ui/chat-effects.js +72 -0
- package/dist/ui/chat-live-replies.js +58 -0
- package/dist/ui/chat-render.js +59 -14
- package/dist/ui/chat-store.js +78 -8
- package/dist/ui/chat-strings.js +43 -16
- package/dist/ui/chat-thread.js +175 -31
- package/dist/ui/chat-tour.js +18 -15
- package/dist/ui/effect-trial-controller.js +119 -0
- package/dist/ui/effect-trial-offer.js +31 -0
- package/dist/ui/effect-trial-panel.js +37 -0
- package/dist/ui/image-lightbox.js +1 -1
- package/dist/ui/panel-chat.js +101 -27
- package/dist/ui/panel-details.js +18 -5
- package/dist/ui/panel-fleet.js +18 -5
- package/dist/ui/panel-log.js +10 -10
- package/dist/ui/panel-needs.js +13 -10
- package/dist/ui/panel-settings.js +66 -12
- package/dist/ui/preview-panel.js +25 -9
- package/dist/ui/settings-appearance.js +325 -0
- package/dist/ui/settings-dialogs.js +11 -6
- package/dist/ui/settings-effects.js +201 -0
- package/dist/ui/settings-model.js +3 -1
- package/dist/ui/settings-mods.js +257 -0
- package/dist/ui/settings-owner-effects.js +80 -0
- package/dist/ui/settings-strings.js +269 -10
- package/dist/ui/settings-token.js +7 -0
- package/dist/ui/settings-wizard.js +5 -3
- package/dist/ui/shared/agend-auth.js +27 -3
- package/dist/ui/shared/agent-widget.js +248 -0
- package/dist/ui/shared/app-i18n.js +3 -1
- package/dist/ui/shared/app-prefs.js +30 -10
- package/dist/ui/shared/app-route.js +23 -1
- package/dist/ui/shared/app-session.js +1 -1
- package/dist/ui/shared/app-shell.js +29 -16
- package/dist/ui/shared/app-store.js +2 -0
- package/dist/ui/shared/app-stream.js +15 -7
- package/dist/ui/shared/app-themes.js +188 -0
- package/dist/ui/shared/app.css +266 -11
- package/dist/ui/shared/app.js +6 -0
- package/dist/ui/shared/card-strings.js +28 -0
- package/dist/ui/shared/effect-app-runtime.js +248 -0
- package/dist/ui/shared/effect-baseline.js +64 -0
- package/dist/ui/shared/effect-chat-runtime.js +154 -0
- package/dist/ui/shared/effect-cursor.js +125 -0
- package/dist/ui/shared/effect-frame.js +127 -0
- package/dist/ui/shared/effect-geometry.js +122 -0
- package/dist/ui/shared/effect-host-style.js +10 -0
- package/dist/ui/shared/effect-owner-regions.js +74 -0
- package/dist/ui/shared/effect-owner-source.js +115 -0
- package/dist/ui/shared/effect-policy.js +137 -0
- package/dist/ui/shared/effect-poster-reader.js +98 -0
- package/dist/ui/shared/effect-posters.js +117 -0
- package/dist/ui/shared/effect-profiler.js +162 -0
- package/dist/ui/shared/effect-profiles.js +65 -0
- package/dist/ui/shared/effect-registry.js +496 -0
- package/dist/ui/shared/effect-runtime-loader.js +234 -0
- package/dist/ui/shared/effect-safety.js +153 -0
- package/dist/ui/shared/effect-stock-snapshot.js +34 -0
- package/dist/ui/shared/effect-stock-trust.js +165 -0
- package/dist/ui/shared/effect-trial-source.js +103 -0
- package/dist/ui/shared/effect-trial-stage.js +147 -0
- package/dist/ui/shared/header-tools.js +35 -15
- package/dist/ui/shared/identity-card.js +202 -0
- package/dist/ui/shared/identity-color.js +261 -0
- package/dist/ui/shared/instance-nav.js +22 -14
- package/dist/ui/shared/instance-switch.js +5 -2
- package/dist/ui/shared/owner-css.js +221 -0
- package/dist/ui/shared/owner-effect-prefs.js +117 -0
- package/dist/ui/shared/panel-view.js +96 -12
- package/dist/ui/shared/signin.js +17 -9
- package/dist/ui/shared/ui-dialog.js +1 -1
- package/dist/ui/shared/ui-icons.js +3 -0
- package/dist/ui/shared/ui-menu.js +1 -1
- package/dist/ui/shared/ui-toast.js +1 -1
- package/dist/ui/shared/view-strings.js +40 -2
- package/dist/ui/signin.html +9 -3
- package/dist/update-check.d.ts +31 -0
- package/dist/update-check.js +47 -0
- package/dist/update-check.js.map +1 -1
- package/dist/update-launch.d.ts +48 -1
- package/dist/update-launch.js +159 -5
- package/dist/update-launch.js.map +1 -1
- package/dist/usage/providers.d.ts +28 -8
- package/dist/usage/providers.js +96 -41
- package/dist/usage/providers.js.map +1 -1
- package/dist/view-api.d.ts +29 -0
- package/dist/view-api.js +214 -17
- package/dist/view-api.js.map +1 -1
- package/dist/web-api.d.ts +86 -2
- package/dist/web-api.js +1160 -92
- package/dist/web-api.js.map +1 -1
- package/dist/web-appearance-limits.d.ts +82 -0
- package/dist/web-appearance-limits.js +83 -0
- package/dist/web-appearance-limits.js.map +1 -0
- package/dist/web-appearance-policy.d.ts +66 -0
- package/dist/web-appearance-policy.js +94 -0
- package/dist/web-appearance-policy.js.map +1 -0
- package/dist/web-appearance-tools.d.ts +50 -0
- package/dist/web-appearance-tools.js +105 -0
- package/dist/web-appearance-tools.js.map +1 -0
- package/dist/web-appearance-view.d.ts +99 -0
- package/dist/web-appearance-view.js +163 -0
- package/dist/web-appearance-view.js.map +1 -0
- package/dist/web-appearance.d.ts +79 -0
- package/dist/web-appearance.js +298 -0
- package/dist/web-appearance.js.map +1 -0
- package/dist/web-asset-parts.d.ts +98 -0
- package/dist/web-asset-parts.js +133 -0
- package/dist/web-asset-parts.js.map +1 -0
- package/dist/web-auth.js +17 -1
- package/dist/web-auth.js.map +1 -1
- package/dist/web-card.d.ts +96 -0
- package/dist/web-card.js +232 -0
- package/dist/web-card.js.map +1 -0
- package/dist/web-chat-history.d.ts +39 -1
- package/dist/web-chat-history.js +69 -9
- package/dist/web-chat-history.js.map +1 -1
- package/dist/web-commands.d.ts +39 -3
- package/dist/web-commands.js +22 -5
- package/dist/web-commands.js.map +1 -1
- package/dist/web-effect-bundle.d.ts +28 -0
- package/dist/web-effect-bundle.js +126 -0
- package/dist/web-effect-bundle.js.map +1 -0
- package/dist/web-effect-canonical.d.ts +8 -0
- package/dist/web-effect-canonical.js +27 -0
- package/dist/web-effect-canonical.js.map +1 -0
- package/dist/web-effect-library.d.ts +114 -0
- package/dist/web-effect-library.js +530 -0
- package/dist/web-effect-library.js.map +1 -0
- package/dist/web-effect-poster.d.ts +8 -0
- package/dist/web-effect-poster.js +52 -0
- package/dist/web-effect-poster.js.map +1 -0
- package/dist/web-effect-profiles.d.ts +118 -0
- package/dist/web-effect-profiles.js +180 -0
- package/dist/web-effect-profiles.js.map +1 -0
- package/dist/web-effect-service.d.ts +86 -0
- package/dist/web-effect-service.js +312 -0
- package/dist/web-effect-service.js.map +1 -0
- package/dist/web-effect-tools.d.ts +47 -0
- package/dist/web-effect-tools.js +126 -0
- package/dist/web-effect-tools.js.map +1 -0
- package/dist/web-effect-trials.d.ts +86 -0
- package/dist/web-effect-trials.js +204 -0
- package/dist/web-effect-trials.js.map +1 -0
- package/dist/web-effect-upload.d.ts +29 -0
- package/dist/web-effect-upload.js +190 -0
- package/dist/web-effect-upload.js.map +1 -0
- package/dist/web-effect-worker.d.ts +8 -0
- package/dist/web-effect-worker.js +170 -0
- package/dist/web-effect-worker.js.map +1 -0
- package/dist/web-effect.d.ts +89 -0
- package/dist/web-effect.js +132 -0
- package/dist/web-effect.js.map +1 -0
- package/dist/web-identity-color.d.ts +87 -0
- package/dist/web-identity-color.js +209 -0
- package/dist/web-identity-color.js.map +1 -0
- package/dist/web-identity-palette.d.ts +113 -0
- package/dist/web-identity-palette.js +84 -0
- package/dist/web-identity-palette.js.map +1 -0
- package/dist/web-image-meta.d.ts +25 -0
- package/dist/web-image-meta.js +200 -0
- package/dist/web-image-meta.js.map +1 -0
- package/dist/web-login.d.ts +14 -0
- package/dist/web-login.js +4 -2
- package/dist/web-login.js.map +1 -1
- package/dist/web-mod-agent.d.ts +50 -0
- package/dist/web-mod-agent.js +203 -0
- package/dist/web-mod-agent.js.map +1 -0
- package/dist/web-mod-bundle.d.ts +144 -0
- package/dist/web-mod-bundle.js +464 -0
- package/dist/web-mod-bundle.js.map +1 -0
- package/dist/web-mod-export.d.ts +59 -0
- package/dist/web-mod-export.js +79 -0
- package/dist/web-mod-export.js.map +1 -0
- package/dist/web-mod-store.d.ts +283 -0
- package/dist/web-mod-store.js +624 -0
- package/dist/web-mod-store.js.map +1 -0
- package/dist/web-org.d.ts +3 -3
- package/dist/web-org.js +11 -17
- package/dist/web-org.js.map +1 -1
- package/dist/web-owner-css-store.d.ts +180 -0
- package/dist/web-owner-css-store.js +384 -0
- package/dist/web-owner-css-store.js.map +1 -0
- package/dist/web-owner-css.d.ts +58 -0
- package/dist/web-owner-css.js +552 -0
- package/dist/web-owner-css.js.map +1 -0
- package/dist/web-prefs.d.ts +66 -0
- package/dist/web-prefs.js +243 -35
- package/dist/web-prefs.js.map +1 -1
- package/dist/web-preview.d.ts +9 -1
- package/dist/web-preview.js +24 -7
- package/dist/web-preview.js.map +1 -1
- package/dist/web-profile.d.ts +32 -0
- package/dist/web-profile.js +132 -0
- package/dist/web-profile.js.map +1 -0
- package/dist/web-session.d.ts +14 -0
- package/dist/web-session.js +10 -0
- package/dist/web-session.js.map +1 -1
- package/dist/web-settings-patch.d.ts +18 -0
- package/dist/web-settings-patch.js +51 -0
- package/dist/web-settings-patch.js.map +1 -1
- package/dist/web-shell-routes.d.ts +1 -1
- package/dist/web-shell-routes.js +1 -1
- package/dist/web-shell-routes.js.map +1 -1
- package/dist/web-theme-store.d.ts +218 -0
- package/dist/web-theme-store.js +516 -0
- package/dist/web-theme-store.js.map +1 -0
- package/dist/web-theme.d.ts +121 -0
- package/dist/web-theme.js +407 -0
- package/dist/web-theme.js.map +1 -0
- package/dist/web-upload.d.ts +119 -4
- package/dist/web-upload.js +252 -39
- package/dist/web-upload.js.map +1 -1
- package/dist/web-widget.d.ts +72 -0
- package/dist/web-widget.js +245 -0
- package/dist/web-widget.js.map +1 -0
- package/launcher/install-admission.cjs +61 -6
- package/package.json +3 -3
package/dist/types.d.ts
CHANGED
|
@@ -345,6 +345,14 @@ export interface WebConfig {
|
|
|
345
345
|
echo_to_channel?: boolean;
|
|
346
346
|
/** Show the AI subscription usage panel on /view and serve /api/ai-usage (default true). */
|
|
347
347
|
usage_panel?: boolean;
|
|
348
|
+
/**
|
|
349
|
+
* #1937: the largest file the web chat takes, in MB (whole number, 1–500; default 50). Never more than
|
|
350
|
+
* upload_max_total_mb; over the public link a file is at most 95 MB whatever this says (the quick tunnel's limit).
|
|
351
|
+
* Read on each upload: `agend reload` applies a change; an open page uses the new limits after it reloads.
|
|
352
|
+
*/
|
|
353
|
+
upload_max_file_mb?: number;
|
|
354
|
+
/** #1937: all files on one web chat message together, in MB (whole number, 1–500; default 100). At most 5 files. */
|
|
355
|
+
upload_max_total_mb?: number;
|
|
348
356
|
/**
|
|
349
357
|
* #1218: the instance Log tab (紀錄) — each agent's steps (prompts, replies, tool calls and results, errors) read on
|
|
350
358
|
* demand from its own transcript, secrets masked. Default false: off, it does no work at all. Turning it on is an
|
|
@@ -396,6 +404,19 @@ export interface WebConfig {
|
|
|
396
404
|
* instead of it; web.preview: false turns previews off everywhere.
|
|
397
405
|
*/
|
|
398
406
|
preview_on_public_link?: boolean;
|
|
407
|
+
/**
|
|
408
|
+
* #1629: agents' own web themes and effects. `agent_edit` (default true): false refuses every agent appearance write,
|
|
409
|
+
* fleet-wide; what is stored stays, and people can still reset and lock.
|
|
410
|
+
*/
|
|
411
|
+
themes?: {
|
|
412
|
+
agent_edit?: boolean;
|
|
413
|
+
};
|
|
414
|
+
/**
|
|
415
|
+
* #1629 r4.1 Q3 (b): the identity card's upload limits and each agent's picture quota. Defaults: portrait
|
|
416
|
+
* 1024×1536 / 1536 KiB, banner 1600×600 / 768 KiB, quota 8 files / 4 MiB. A value outside its hard bound is clamped
|
|
417
|
+
* (and warned about); a change applies to new uploads only.
|
|
418
|
+
*/
|
|
419
|
+
appearance?: import("./web-appearance-limits.js").AppearanceLimitsConfig;
|
|
399
420
|
}
|
|
400
421
|
/** `login:` section — remote /login behaviour. */
|
|
401
422
|
export interface LoginConfig {
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/** #1629: one session-only app effect owner. Importing this module starts nothing. */
|
|
2
|
+
import { appStore } from "/assets/app-store.js";
|
|
3
|
+
import { navStore } from "/assets/app-nav.js";
|
|
4
|
+
import { onPrefsChanged, prefInForce } from "/assets/app-prefs.js";
|
|
5
|
+
import { resolvedMode, themesStore } from "/assets/app-themes.js";
|
|
6
|
+
import { ownerCssLifecycle, safeMode } from "/assets/owner-css.js";
|
|
7
|
+
import { constrainedDevice } from "/assets/effect-policy.js";
|
|
8
|
+
import { createAppEffectRuntime } from "/assets/effect-app-runtime.js";
|
|
9
|
+
import { createEffectCursor } from "/assets/effect-cursor.js";
|
|
10
|
+
import { createOwnerEffectRegions } from "/assets/effect-owner-regions.js";
|
|
11
|
+
import { chatEffectIdentity } from "/assets/effect-chat-runtime.js";
|
|
12
|
+
import { createEffectTrialController } from "./effect-trial-controller.js";
|
|
13
|
+
import { effectTrialMessage } from "./effect-trial-offer.js";
|
|
14
|
+
import { claimPanel, panelVersion, panelStore, openTrialPanel, closePanel } from "./preview-panel.js";
|
|
15
|
+
const owners = new WeakMap();
|
|
16
|
+
export function getAppEffects(host = window) { const value = owners.get(host); return value && !value.dead && value.api?.ok ? value : null; }
|
|
17
|
+
|
|
18
|
+
/** Boot once with the page's Chat store, including when the first visible panel is Fleet. */
|
|
19
|
+
export function startAppEffects({ store, window: host = window, factories = {} } = {}) {
|
|
20
|
+
const existing = owners.get(host);
|
|
21
|
+
if (existing) { if (!existing.dead) existing.bindStore(store); return getAppEffects(host); }
|
|
22
|
+
if (!chatEffectIdentity(host) || typeof host.AgendAuth?.onSessionEnd !== "function" || typeof host.AgendAuth?.sessionEnded !== "function") return null;
|
|
23
|
+
const doc = host.document, releases = [], ended = new Set(), updates = new Set(), trialUpdates = new Set(); let replyOff = null, replyStore = null, regions = null, cursor = null, cursorStamp = null, trialControl = null, trialIntent = null, trialHint = false, trialSerial = 0, trialStamp = null, chatBinding = null;
|
|
24
|
+
const safe = fn => { try { return fn(); } catch { return null; } };
|
|
25
|
+
const hold = off => { if (entry.dead) safe(() => off?.()); else releases.push(off); };
|
|
26
|
+
const entry = { dead: false, api: null,
|
|
27
|
+
cursorSnapshot: () => cursor?.snapshot() ?? { status: "unavailable", id: null },
|
|
28
|
+
gates: () => context(),
|
|
29
|
+
subscribe(fn) { if (typeof fn !== "function" || entry.dead) return () => {}; updates.add(fn); return () => updates.delete(fn); },
|
|
30
|
+
subscribeTrial(fn) { if (typeof fn !== "function" || entry.dead) return () => {}; trialUpdates.add(fn); return () => trialUpdates.delete(fn); },
|
|
31
|
+
onEnd(fn) { if (typeof fn !== "function") return () => {}; if (entry.dead) { safe(fn); return () => {}; } ended.add(fn); return () => ended.delete(fn); },
|
|
32
|
+
bindStore(value) {
|
|
33
|
+
if (entry.dead || value === replyStore) return;
|
|
34
|
+
safe(() => replyOff?.()); replyOff = null; replyStore = value;
|
|
35
|
+
trialControl?.stop(); chatBinding = null; entry.api?.detachChat();
|
|
36
|
+
if (typeof value?.onAgentReply === "function") {
|
|
37
|
+
const off = value.onAgentReply(event => { if (!entry.dead && replyStore === value) entry.api?.reply(event); });
|
|
38
|
+
if (entry.dead || replyStore !== value) safe(() => off?.()); else replyOff = off;
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
canTry(message) {
|
|
42
|
+
const offer = safe(() => effectTrialMessage(message)), nav = navStore.get();
|
|
43
|
+
return !entry.dead && !!chatBinding && safe(() => chatBinding.current()) === true && trialHint && trialControl?.ok === true && !!offer && message.instance === nav.route?.instance && nav.route?.panel === "chat"
|
|
44
|
+
&& trialControl.snapshot().phase === "idle" && !trialControl.snapshot().busy && trialAllowed();
|
|
45
|
+
},
|
|
46
|
+
startTrial(message) {
|
|
47
|
+
if (!entry.canTry(message)) return Promise.resolve(false);
|
|
48
|
+
const nav = navStore.get(); trialIntent = { name: message.instance, seq: nav.seq, claim: claimPanel(), handle: null, chat: chatBinding };
|
|
49
|
+
return trialControl.start(message);
|
|
50
|
+
},
|
|
51
|
+
attachChat(options) {
|
|
52
|
+
const nav = navStore.get(), name = options?.name;
|
|
53
|
+
if (entry.dead || !entry.api?.ok || nav.route?.panel !== "chat" || nav.route.instance !== name || typeof options.current !== "function") return null;
|
|
54
|
+
const binding = { current: () => !entry.dead && host.document === doc && navStore.get().seq === nav.seq && navStore.get().route?.panel === "chat" && navStore.get().route?.instance === name && options.current() === true };
|
|
55
|
+
chatBinding = binding; trialControl?.stop();
|
|
56
|
+
if (entry.dead || chatBinding !== binding) return null;
|
|
57
|
+
const handle = entry.api.attachChat({ ...options, current: () => chatBinding === binding && binding.current() });
|
|
58
|
+
if (entry.dead || chatBinding !== binding) { safe(() => handle?.dispose()); return null; }
|
|
59
|
+
if (!handle) chatBinding = null;
|
|
60
|
+
return handle;
|
|
61
|
+
},
|
|
62
|
+
dispose() {
|
|
63
|
+
if (entry.dead) return; entry.dead = true;
|
|
64
|
+
safe(() => trialControl?.dispose()); safe(() => entry.api?.dispose()); safe(() => replyOff?.()); replyOff = null; replyStore = null;
|
|
65
|
+
for (const off of releases.splice(0)) safe(off);
|
|
66
|
+
updates.clear(); trialUpdates.clear(); const listeners = [...ended]; ended.clear(); for (const fn of listeners) safe(fn);
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
owners.set(host, entry); // Reentrant setup never constructs another owner.
|
|
70
|
+
let media, coarse, preview;
|
|
71
|
+
function context() {
|
|
72
|
+
let ready = false; try { ready = preview?.availability().ok === true; } catch { /* unavailable */ }
|
|
73
|
+
return { safe: safeMode(), publicLink: doc.body.dataset.publicLink === "1", reducedMotion: media?.matches !== false, saveData: host.navigator?.connection?.saveData === true,
|
|
74
|
+
hidden: doc.visibilityState !== "visible", enabled: typeof host.OffscreenCanvas === "function" && typeof host.Worker === "function" && typeof host.MutationObserver === "function" && typeof host.ResizeObserver === "function",
|
|
75
|
+
show: prefInForce("fx.show")?.value !== false, previewReady: ready,
|
|
76
|
+
optIn: prefInForce("preview.optIn")?.value === true && preview?.optedIn() === true && preview?.never() !== true,
|
|
77
|
+
constrained: constrainedDevice({ memory: host.navigator?.deviceMemory, cores: host.navigator?.hardwareConcurrency, coarse: coarse?.matches === true, width: host.innerWidth }),
|
|
78
|
+
modal: !!doc.querySelector("dialog[open],.menu-list,.pop,[role=alertdialog],.oc-keep,.shell.sb-open"), openInstance: null };
|
|
79
|
+
}
|
|
80
|
+
function trialCurrent(name) {
|
|
81
|
+
const intent = trialIntent, nav = navStore.get();
|
|
82
|
+
return !entry.dead && host.document === doc && !!intent && !!intent.chat && chatBinding === intent.chat && intent.chat.current() === true && intent.name === name && nav.seq === intent.seq && nav.route?.panel === "chat" && nav.route.instance === name
|
|
83
|
+
&& (panelVersion() === intent.claim || intent.handle !== null && panelStore.get().open?.handle === intent.handle);
|
|
84
|
+
}
|
|
85
|
+
function trialAllowed() {
|
|
86
|
+
const c = context();
|
|
87
|
+
return !entry.dead && doc.visibilityState === "visible" && doc.hasFocus?.() === true && ownerCssLifecycle.get().state === "off"
|
|
88
|
+
&& c.enabled && c.show && c.previewReady && c.optIn && !c.safe && !c.publicLink && !c.reducedMotion && !c.saveData && !c.hidden && !c.modal;
|
|
89
|
+
}
|
|
90
|
+
function mountTrial(handle) {
|
|
91
|
+
const intent = trialIntent; if (!intent || !trialCurrent(handle.instance) || !handle.current() || !Number.isSafeInteger(++trialSerial)) throw Error("trial_unavailable");
|
|
92
|
+
intent.handle = handle;
|
|
93
|
+
const binding = Object.freeze({ key: `trial:${trialSerial}:${handle.reply.trial}`, id: handle.reply.offer.id, run: handle.reply.trial, instance: handle.instance, generation: trialSerial });
|
|
94
|
+
let stage = null, source = null;
|
|
95
|
+
const cleanup = () => {
|
|
96
|
+
safe(() => source?.stop()); safe(() => stage?.dispose());
|
|
97
|
+
if (panelStore.get().open?.handle === handle) closePanel();
|
|
98
|
+
};
|
|
99
|
+
try {
|
|
100
|
+
openTrialPanel({ key: binding.key, instance: handle.instance, binding, handle, ownerCss: () => ownerCssLifecycle.get().state,
|
|
101
|
+
onStage(value) {
|
|
102
|
+
stage = value;
|
|
103
|
+
if (!handle.current()) { cleanup(); handle.stop(); return; }
|
|
104
|
+
source = entry.api.attachTrial({ reply: handle.reply, offer: handle.reply.offer.offer, startedAt: handle.startedAt, stage,
|
|
105
|
+
current: handle.current, onStop: handle.stop });
|
|
106
|
+
if (!source) { cleanup(); handle.stop(); }
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
} catch (error) { cleanup(); throw error; }
|
|
110
|
+
return cleanup;
|
|
111
|
+
}
|
|
112
|
+
function notifyTrial() {
|
|
113
|
+
if (entry.dead) return;
|
|
114
|
+
const nav = navStore.get(), next = JSON.stringify([trialHint, safe(() => chatBinding?.current()) === true, trialAllowed(), nav.route?.panel, nav.route?.instance, trialControl?.snapshot()]);
|
|
115
|
+
if (next === trialStamp) return; trialStamp = next;
|
|
116
|
+
for (const fn of [...trialUpdates]) safe(fn);
|
|
117
|
+
}
|
|
118
|
+
const notify = () => { if (!entry.dead) { trialControl?.sync(); notifyTrial(); for (const fn of [...updates]) safe(fn); } };
|
|
119
|
+
const cursorDragging = () => doc.body.classList?.contains("side-resizing") === true || !!doc.querySelector(".chat-split.resizing");
|
|
120
|
+
function syncCursor() {
|
|
121
|
+
if (entry.dead || !cursor) return;
|
|
122
|
+
const c = context();
|
|
123
|
+
cursor.update(entry.api?.cursorSelection?.() ?? null, c.enabled && c.show && c.previewReady && c.optIn && !c.safe && !c.publicLink && !c.reducedMotion && !c.saveData && !c.hidden && !c.modal && !cursorDragging());
|
|
124
|
+
}
|
|
125
|
+
try {
|
|
126
|
+
if (appStore.get().connection === "ended") { entry.dispose(); return null; }
|
|
127
|
+
preview = host.AgendPreview;
|
|
128
|
+
media = host.matchMedia?.("(prefers-reduced-motion: reduce)") ?? { matches: true };
|
|
129
|
+
coarse = host.matchMedia?.("(pointer: coarse)") ?? { matches: false };
|
|
130
|
+
entry.api = (factories.runtime ?? createAppEffectRuntime)({ window: host, current: () => !entry.dead && host.document === doc,
|
|
131
|
+
observe: () => ({ context: context(), ownerCss: ownerCssLifecycle.get().state, mode: resolvedMode() }), onDispose: () => entry.dispose(),
|
|
132
|
+
resolveOwnerTarget: (slot, manifest, proof) => !entry.dead ? regions?.resolve?.(slot, manifest.box, proof) ?? null : null,
|
|
133
|
+
onChange: () => { syncCursor(); notify(); } });
|
|
134
|
+
if (entry.dead || !entry.api.ok) { entry.dispose(); return null; }
|
|
135
|
+
trialControl = (factories.trialController ?? createEffectTrialController)({ window: host, current: trialCurrent, allowed: trialAllowed, onStart: mountTrial, onChange: notify });
|
|
136
|
+
hold(() => trialControl?.dispose());
|
|
137
|
+
if (typeof host.AgendAuth.trialSession === "function") Promise.resolve().then(() => host.AgendAuth.trialSession()).then(value => {
|
|
138
|
+
if (entry.dead) return; trialHint = value?.localAdmin === true && value.epoch === chatEffectIdentity(host)?.epoch; notify();
|
|
139
|
+
}).catch(() => {});
|
|
140
|
+
hold(panelStore.subscribe(() => { trialControl?.sync(); notifyTrial(); }));
|
|
141
|
+
const ownRegions = (factories.regions ?? createOwnerEffectRegions)({ window: host, current: () => !entry.dead && host.document === doc });
|
|
142
|
+
regions = ownRegions?.ok ? ownRegions : null; hold(() => ownRegions?.dispose?.());
|
|
143
|
+
if (entry.dead) return null;
|
|
144
|
+
const ownCursor = safe(() => (factories.cursor ?? createEffectCursor)({ window: host, current: () => !entry.dead && host.document === doc,
|
|
145
|
+
readImage: (selection, signal) => entry.api?.cursorImageSettled?.(selection, signal) ?? Promise.resolve(null),
|
|
146
|
+
onChange(value) { const next = JSON.stringify(value); if (next !== cursorStamp) { cursorStamp = next; notify(); } },
|
|
147
|
+
}));
|
|
148
|
+
cursor = ownCursor?.ok ? ownCursor : null; hold(() => ownCursor?.dispose?.());
|
|
149
|
+
if (entry.dead) return null;
|
|
150
|
+
const sync = () => { if (!entry.dead) { trialControl?.sync(); entry.api.sync(); syncCursor(); notifyTrial(); } };
|
|
151
|
+
let revision = appStore.get().themesRev, prefsRevision = appStore.get().prefsRev, navSeq = navStore.get().seq;
|
|
152
|
+
hold(navStore.subscribe(value => { if (entry.dead || value.seq === navSeq) return; navSeq = value.seq; trialControl?.stop(); chatBinding = null; entry.api.detachChat(); syncCursor(); }));
|
|
153
|
+
hold(appStore.subscribe(value => {
|
|
154
|
+
if (entry.dead) return;
|
|
155
|
+
if (value.connection === "ended") { entry.dispose(); return; }
|
|
156
|
+
const chatChanged = value.themesRev !== revision, ownerChanged = value.prefsRev !== prefsRevision;
|
|
157
|
+
revision = value.themesRev; prefsRevision = value.prefsRev;
|
|
158
|
+
if (chatChanged) entry.api.changedChat();
|
|
159
|
+
if (ownerChanged) entry.api.changedOwner();
|
|
160
|
+
if (!chatChanged && !ownerChanged) sync();
|
|
161
|
+
}));
|
|
162
|
+
hold(ownerCssLifecycle.subscribe(sync)); hold(themesStore.subscribe(sync)); hold(onPrefsChanged(sync));
|
|
163
|
+
if (typeof preview?.onChange === "function") hold(preview.onChange(sync));
|
|
164
|
+
if (typeof host.AgendTheme?.onChange === "function") hold(host.AgendTheme.onChange(sync));
|
|
165
|
+
for (const [target, type] of [[media, "change"], [coarse, "change"], [doc, "visibilitychange"], [host, "resize"], [host.navigator?.connection, "change"]]) {
|
|
166
|
+
if (target?.addEventListener) { target.addEventListener(type, sync); hold(() => target.removeEventListener(type, sync)); }
|
|
167
|
+
}
|
|
168
|
+
if (entry.dead) return null;
|
|
169
|
+
let modal = context().modal, dragging = cursorDragging(), queuedSurface = false;
|
|
170
|
+
// Panel commits can happen after navigation and contain no Chat lease. Reproject fixed owner targets
|
|
171
|
+
// once after stock DOM changes. Body-level effect roots/frames/rules do not schedule themselves.
|
|
172
|
+
const surfaceChanged = () => {
|
|
173
|
+
if (entry.dead || queuedSurface) return; queuedSurface = true;
|
|
174
|
+
Promise.resolve().then(() => { queuedSurface = false; sync(); });
|
|
175
|
+
};
|
|
176
|
+
const barriers = typeof host.MutationObserver === "function" ? new host.MutationObserver(records => {
|
|
177
|
+
if (entry.dead) return;
|
|
178
|
+
const value = context().modal, drag = cursorDragging(); if (value !== modal || drag !== dragging) { modal = value; dragging = drag; sync(); }
|
|
179
|
+
if (Array.isArray(records) && records.some(r => r.target?.closest?.("#app") || r.target === doc.getElementById?.("app"))) surfaceChanged();
|
|
180
|
+
}) : null;
|
|
181
|
+
hold(() => barriers?.disconnect());
|
|
182
|
+
if (entry.dead) return null;
|
|
183
|
+
barriers?.observe(doc.documentElement, { subtree: true, childList: true, attributes: true, attributeFilter: ["class", "open", "hidden", "role"] });
|
|
184
|
+
entry.bindStore(store); sync();
|
|
185
|
+
return getAppEffects(host);
|
|
186
|
+
} catch { entry.dispose(); return null; }
|
|
187
|
+
}
|
package/dist/ui/chat-commands.js
CHANGED
|
@@ -9,6 +9,7 @@ export const CHAT_COMMANDS = [
|
|
|
9
9
|
{ name: "compact", arg: "[instructions]" },
|
|
10
10
|
{ name: "clear", arg: "" },
|
|
11
11
|
{ name: "model", arg: "[name]" },
|
|
12
|
+
{ name: "backend", arg: "[backend] [model] [effort]" },
|
|
12
13
|
{ name: "effort", arg: "[level]" },
|
|
13
14
|
{ name: "cancel", arg: "" },
|
|
14
15
|
{ name: "btw", arg: "<question>" },
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/** #1629: static Chat fallback. It requires no preview origin, device consent, Worker or personal identity. */
|
|
2
|
+
import { useLayoutEffect } from "/assets/app-html.js";
|
|
3
|
+
import { appStore } from "/assets/app-store.js";
|
|
4
|
+
import { onPrefsChanged, prefInForce } from "/assets/app-prefs.js";
|
|
5
|
+
import { themesShown, themesStore } from "/assets/app-themes.js";
|
|
6
|
+
import { ownerCssLifecycle } from "/assets/owner-css.js";
|
|
7
|
+
import { chatEffectIdentity } from "/assets/effect-chat-runtime.js";
|
|
8
|
+
import { createChatPosters } from "/assets/effect-posters.js";
|
|
9
|
+
|
|
10
|
+
export function ChatEffectPosters({ name, lease, element }) {
|
|
11
|
+
useLayoutEffect(() => {
|
|
12
|
+
let live = true, api = null;
|
|
13
|
+
const releases = [], host = window;
|
|
14
|
+
const current = () => live && lease.current();
|
|
15
|
+
const cleanup = () => { if (!live) return; live = false; try { api?.dispose?.(); } catch { /* optional */ } for (const off of releases) { try { off?.(); } catch { /* release all */ } } };
|
|
16
|
+
lease.hold(cleanup);
|
|
17
|
+
try {
|
|
18
|
+
const preview = host.AgendPreview, motion = host.matchMedia?.("(prefers-reduced-motion: reduce)") ?? { matches: true };
|
|
19
|
+
const observe = () => {
|
|
20
|
+
let previewReady = false; try { previewReady = preview?.availability().ok === true; } catch { /* fallback */ }
|
|
21
|
+
// This is a surface fallback, not a replacement for an explicit Pause, queued effect or failed live read.
|
|
22
|
+
const playback = chatEffectIdentity(host) && typeof host.Worker === "function" && typeof host.OffscreenCanvas === "function" && previewReady &&
|
|
23
|
+
prefInForce("preview.optIn")?.value === true && preview?.optedIn() === true && preview?.never() !== true && !motion.matches && host.navigator?.connection?.saveData !== true;
|
|
24
|
+
return { shown: themesShown(), fallback: !playback, ownerCss: ownerCssLifecycle.get().state };
|
|
25
|
+
};
|
|
26
|
+
api = createChatPosters({ window: host, name, element: element.current, current, observe });
|
|
27
|
+
if (!api.ok) return cleanup;
|
|
28
|
+
const sync = () => { if (current()) api.sync(); };
|
|
29
|
+
let revision = appStore.get().themesRev;
|
|
30
|
+
releases.push(appStore.subscribe(s => {
|
|
31
|
+
if (!current()) return;
|
|
32
|
+
if (s.connection === "ended") { api.dispose(); return; }
|
|
33
|
+
if (s.themesRev !== revision) { revision = s.themesRev; api.changed(); } else sync();
|
|
34
|
+
}));
|
|
35
|
+
releases.push(ownerCssLifecycle.subscribe(sync)); releases.push(themesStore.subscribe(sync)); releases.push(onPrefsChanged(sync));
|
|
36
|
+
if (typeof preview?.onChange === "function") releases.push(preview.onChange(sync));
|
|
37
|
+
for (const target of [motion, host.navigator?.connection]) if (target?.addEventListener) { target.addEventListener("change", sync); releases.push(() => target.removeEventListener("change", sync)); }
|
|
38
|
+
sync(); return cleanup;
|
|
39
|
+
} catch { cleanup(); return cleanup; }
|
|
40
|
+
}, [name, lease]);
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// #1629: Chat contributes a committed lease to the one app owner; no private pool or server polling.
|
|
2
|
+
import { html, useLayoutEffect, useRef, useState } from "/assets/app-html.js";
|
|
3
|
+
import { register, t } from "/assets/app-i18n.js";
|
|
4
|
+
import { appStore, useStore } from "/assets/app-store.js";
|
|
5
|
+
import { ownerCssLifecycle } from "/assets/owner-css.js";
|
|
6
|
+
import { chatEffectState, chatEffectIdentity } from "/assets/effect-chat-runtime.js";
|
|
7
|
+
import { Dialog } from "/assets/ui-dialog.js";
|
|
8
|
+
import { navigate } from "/assets/app-nav.js";
|
|
9
|
+
import { toast } from "/assets/ui-toast.js";
|
|
10
|
+
import { getAppEffects } from "./app-effects.js";
|
|
11
|
+
|
|
12
|
+
register("chatFx", {
|
|
13
|
+
en: { title: "Effects", loading: "Checking effects…", empty: "No effects in this chat.", failed: "Effects stopped because their resources could not be verified. Reload the page to try again.", reload: "Reload page", pause: "Pause", resume: "Resume", remove: "Remove from chat", removeFailed: "Could not remove this effect. Reload the page to check its state.", css: "Agent foreground effects are paused while your custom CSS is on or cannot be verified.", setting: "Open Appearance settings", running: "Running", starting: "Starting", off: "Stopped", resumeFailed: "This effect cannot resume: the page or its safety history is not ready. Reload the page or choose another effect.", queued: "Waiting for a slot", unknown: "Playback is off or unavailable here.", budget: "Paused to protect page responsiveness. Resume keeps the three-pauses safety limit.", paused: "Paused", low: "Reduced quality" },
|
|
14
|
+
"zh-TW": { title: "效果", loading: "正在確認效果…", empty: "這個聊天沒有放置效果。", failed: "無法確認效果資源,已停止播放。重新載入頁面後再試。", reload: "重新載入頁面", pause: "暫停", resume: "恢復", remove: "從聊天移除", removeFailed: "無法移除效果。請重新載入頁面確認狀態。", css: "自訂 CSS 開啟或無法確認時,代理的前景效果會暫停。", setting: "開啟外觀設定", running: "播放中", starting: "啟動中", off: "已停止", resumeFailed: "頁面或安全紀錄尚未就緒,無法恢復此效果。請重新載入頁面,或選擇其他效果。", queued: "等待空位", unknown: "這裡已關閉播放,或無法播放效果。", budget: "為維持頁面順暢,已暫停效果。恢復仍保留三次暫停的安全上限。", paused: "已暫停", low: "降低畫質" },
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
/** Local read/admin sessions keep controls; public, View, poll and header-only pages have no runtime identity. */
|
|
18
|
+
export function chatEffectsMenuAvailable() { return chatEffectIdentity(window) !== null; }
|
|
19
|
+
|
|
20
|
+
export function ChatEffects({ name, lease, element, store, open, onClose }) {
|
|
21
|
+
const cssLife = useStore(ownerCssLifecycle);
|
|
22
|
+
const runtime = useRef(null), [state, setState] = useState(null), [canWrite, setCanWrite] = useState(false), [busy, setBusy] = useState(false);
|
|
23
|
+
useLayoutEffect(() => {
|
|
24
|
+
let live = true, api = null;
|
|
25
|
+
const releases = [];
|
|
26
|
+
// Effects are optional: a failed setup must release its partial ownership without breaking Chat.
|
|
27
|
+
const cleanup = () => {
|
|
28
|
+
if (!live) return;
|
|
29
|
+
live = false;
|
|
30
|
+
try { api?.dispose?.(); } catch { /* continue releasing partial setup */ }
|
|
31
|
+
for (const off of releases) { try { if (typeof off === "function") off(); } catch { /* release the rest */ } }
|
|
32
|
+
if (runtime.current === api) runtime.current = null;
|
|
33
|
+
};
|
|
34
|
+
lease.hold(cleanup);
|
|
35
|
+
try {
|
|
36
|
+
setBusy(false); onClose(); setCanWrite(false); setState(null);
|
|
37
|
+
const host = window, owner = getAppEffects(host);
|
|
38
|
+
const current = () => live && lease.current();
|
|
39
|
+
if (!owner) { setState({ error: null, rows: [], effects: [], unavailable: true }); return cleanup; }
|
|
40
|
+
releases.push(owner.onEnd(() => { if (current()) setState({ error: "resource_unavailable", rows: [], effects: [], unavailable: true }); }));
|
|
41
|
+
const changed = value => {
|
|
42
|
+
if (!current()) return;
|
|
43
|
+
const g = owner.gates();
|
|
44
|
+
setState({ ...value, unavailable: !g.enabled || !g.show || !g.previewReady || !g.optIn || g.safe || g.publicLink || g.reducedMotion || g.saveData });
|
|
45
|
+
};
|
|
46
|
+
api = owner.attachChat({ name, element: element.current, current,
|
|
47
|
+
observe: () => ({ state: chatEffectState(appStore.get(), name) }), onChange: changed });
|
|
48
|
+
runtime.current = api;
|
|
49
|
+
if (!api?.ok) { setState({ error: null, rows: [], effects: [], unavailable: true }); return cleanup; }
|
|
50
|
+
host.AgendAuth?.canWrite?.().then(value => { if (current()) setCanWrite(value === true); }).catch(() => {});
|
|
51
|
+
api.sync();
|
|
52
|
+
return cleanup;
|
|
53
|
+
} catch {
|
|
54
|
+
cleanup();
|
|
55
|
+
if (lease.current()) setState({ error: "runtime_unavailable", rows: [], effects: [] });
|
|
56
|
+
return cleanup;
|
|
57
|
+
}
|
|
58
|
+
}, [name, lease]);
|
|
59
|
+
const rows = state?.rows ?? [], effects = state?.effects ?? [];
|
|
60
|
+
const css = cssLife.state !== "off" && effects.some(p => p.by !== "human" && p.manifest.layer === "front");
|
|
61
|
+
const remove = async id => { if (busy) return; setBusy(true); const api = runtime.current; const ok = await api?.remove?.(id); if (!lease.current() || runtime.current !== api) return; setBusy(false); if (!ok) toast(t("chatFx.removeFailed"), false); };
|
|
62
|
+
return html`${open ? html`<${Dialog} title=${t("chatFx.title")} onClose=${onClose} busy=${busy}>
|
|
63
|
+
${state?.error ? html`<p>${t("chatFx.failed")}</p><button type="button" class="btn" onClick=${() => location.reload()}>${t("chatFx.reload")}</button>` : null}
|
|
64
|
+
${css ? html`<p>${t("chatFx.css")}</p><button type="button" class="btn" onClick=${() => { onClose(); navigate("/settings/appearance"); }}>${t("chatFx.setting")}</button>` : null}
|
|
65
|
+
${!effects.length && !state?.error ? html`<p>${t(state?.unavailable ? "chatFx.unknown" : state?.loading || !state ? "chatFx.loading" : "chatFx.empty")}</p>` : null}
|
|
66
|
+
${effects.map(p => { const row = rows.find(r => r.id === p.id), phase = row?.phase; return html`<section key=${p.id}><h3>${p.manifest.title}</h3><p>${t(row?.reason === "paused" || row?.reason === "host_budget" ? "chatFx.paused" : phase === "running" ? "chatFx.running" : phase ? "chatFx.starting" : row?.verdict === "queued" ? "chatFx.queued" : "chatFx.off")}${row?.quality === "low" ? ` · ${t("chatFx.low")}` : ""}</p>
|
|
67
|
+
${row?.reason === "host_budget" ? html`<p>${t("chatFx.budget")}</p>` : null}
|
|
68
|
+
<button type="button" class="btn" disabled=${busy} onClick=${() => runtime.current?.pause?.(p.id)}>${t("chatFx.pause")}</button>
|
|
69
|
+
<button type="button" class="btn" disabled=${busy} onClick=${() => { if (!runtime.current?.resume?.(p.id)) toast(t("chatFx.resumeFailed"), false); }}>${t("chatFx.resume")}</button>
|
|
70
|
+
${canWrite ? html`<button type="button" class="btn" disabled=${busy} onClick=${() => remove(p.id)}>${t("chatFx.remove")}</button>` : null}</section>`; })}
|
|
71
|
+
</${Dialog}>` : null}`;
|
|
72
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// #1629: transient host notifications, never message content or replay/history authority.
|
|
2
|
+
const own = (o, k) => Object.prototype.hasOwnProperty.call(o, k);
|
|
3
|
+
function fields(value, keys, closed = false) {
|
|
4
|
+
try {
|
|
5
|
+
if (!value || typeof value !== "object" || ![Object.prototype, null].includes(Object.getPrototypeOf(value))) return null;
|
|
6
|
+
if (closed && Reflect.ownKeys(value).length !== keys.length) return null;
|
|
7
|
+
const out = {};
|
|
8
|
+
for (const k of keys) { const d = Object.getOwnPropertyDescriptor(value, k); if (!d || !own(d, "value")) return null; out[k] = d.value; }
|
|
9
|
+
return out;
|
|
10
|
+
} catch { return null; }
|
|
11
|
+
}
|
|
12
|
+
const identity = x => !!x && typeof x.boot === "string" && /^[0-9a-f]{1,32}$/.test(x.boot)
|
|
13
|
+
&& Number.isSafeInteger(x.id) && x.id > 0 && typeof x.instance === "string" && x.instance.length >= 1 && x.instance.length <= 128;
|
|
14
|
+
const MAX_AGE = 5000, MAX_BOOTS = 64, MAX_PENDING = 64;
|
|
15
|
+
export function createLiveAgentReplies(now) {
|
|
16
|
+
const seen = new Map(), pending = new Map(), listeners = new Set();
|
|
17
|
+
let last = null, revoked = false;
|
|
18
|
+
function time() {
|
|
19
|
+
if (revoked) return null;
|
|
20
|
+
let t; try { t = now(); } catch { t = null; }
|
|
21
|
+
if (typeof t !== "number" || !Number.isFinite(t) || t < 0 || last !== null && t < last) { revoked = true; pending.clear(); return null; }
|
|
22
|
+
last = t;
|
|
23
|
+
for (const [key, item] of pending) if (t - item.at > MAX_AGE) pending.delete(key);
|
|
24
|
+
return t;
|
|
25
|
+
}
|
|
26
|
+
function ingress(extra, t) {
|
|
27
|
+
const e = fields(extra, ["source", "receivedAt"], true);
|
|
28
|
+
return e && e.source === "sse" && typeof e.receivedAt === "number" && Number.isFinite(e.receivedAt) && e.receivedAt >= 0 && e.receivedAt <= t && t - e.receivedAt <= MAX_AGE ? e.receivedAt : null;
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
// Record every accepted ingress, including history/poll, before its message-array merge/eviction.
|
|
32
|
+
message(value, extra) {
|
|
33
|
+
const m = fields(value, ["boot", "id", "instance"]); if (!identity(m)) return;
|
|
34
|
+
const role = fields(value, ["role"])?.role;
|
|
35
|
+
const t = time(); if (t === null) return;
|
|
36
|
+
const key = `${m.boot}:${m.id}`, previous = seen.get(m.boot) || 0;
|
|
37
|
+
pending.delete(key);
|
|
38
|
+
if (!seen.has(m.boot) && seen.size >= MAX_BOOTS) { revoked = true; pending.clear(); return; }
|
|
39
|
+
seen.set(m.boot, Math.max(previous, m.id));
|
|
40
|
+
const at = ingress(extra, t);
|
|
41
|
+
if (m.id <= previous || role !== "agent" || at === null) return;
|
|
42
|
+
// Reconnect replay has no receipts: discard old candidates, never the durable-in-page watermark.
|
|
43
|
+
if (pending.size >= MAX_PENDING) pending.delete(pending.keys().next().value);
|
|
44
|
+
pending.set(key, { instance: m.instance, at });
|
|
45
|
+
},
|
|
46
|
+
receipt(value, extra) {
|
|
47
|
+
const r = fields(value, ["v", "boot", "id", "instance"], true); if (!identity(r) || r.v !== 1) return false;
|
|
48
|
+
const t = time(); if (t === null) return false;
|
|
49
|
+
const at = ingress(extra, t); if (at === null) return false;
|
|
50
|
+
const key = `${r.boot}:${r.id}`, candidate = pending.get(key); pending.delete(key);
|
|
51
|
+
if (!candidate || candidate.instance !== r.instance || at < candidate.at || t - candidate.at > MAX_AGE) return false;
|
|
52
|
+
const event = Object.freeze({ v: 1, boot: r.boot, id: r.id, instance: r.instance });
|
|
53
|
+
for (const fn of [...listeners]) { try { fn(event); } catch { /* one host observer cannot suppress another */ } }
|
|
54
|
+
return true;
|
|
55
|
+
},
|
|
56
|
+
subscribe(fn) { if (typeof fn !== "function") return () => {}; listeners.add(fn); return () => listeners.delete(fn); },
|
|
57
|
+
};
|
|
58
|
+
}
|
package/dist/ui/chat-render.js
CHANGED
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
function link(href, label) {
|
|
39
|
-
return '<a href="' + href + '" target="_blank" rel="noopener noreferrer">' + label + "</a>";
|
|
39
|
+
return '<a href="' + href + '" target="_blank" rel="noopener noreferrer" data-trust="link">' + label + "</a>";
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
// Placeholders survive the later passes untouched (they contain no Markdown characters).
|
|
@@ -78,7 +78,9 @@
|
|
|
78
78
|
function inline(escaped, store) {
|
|
79
79
|
var s = escaped;
|
|
80
80
|
// `code` first: nothing inside it is Markdown.
|
|
81
|
-
|
|
81
|
+
// #1812: in a table cell a path may wrap after its separators — the cell keeps words whole, so without these a long
|
|
82
|
+
// path held its whole column wide.
|
|
83
|
+
s = s.replace(/`([^`\n]+)`/g, function (_m, code) { return stash(store, "<code>" + (store.inCell ? code.replace(/\//g, "/<wbr>") : code) + "</code>"); });
|
|
82
84
|
var known = store.emojis;
|
|
83
85
|
if (known) s = s.replace(EMOJI_RE, function (m, _a, name, id) { return known(id) ? stash(store, emojiImg(id, name)) : m; });
|
|
84
86
|
var names = store.mentions;
|
|
@@ -180,7 +182,8 @@
|
|
|
180
182
|
function renderTable(header, aligns, rows, store) {
|
|
181
183
|
function cell(tag, text, i) {
|
|
182
184
|
var a = aligns[i] ? ' class="al-' + aligns[i] + '"' : "";
|
|
183
|
-
|
|
185
|
+
store.inCell = true;
|
|
186
|
+
try { return "<" + tag + a + ">" + inline(text, store) + "</" + tag + ">"; } finally { store.inCell = false; }
|
|
184
187
|
}
|
|
185
188
|
function row(tag, cells) {
|
|
186
189
|
var out = "";
|
|
@@ -338,6 +341,22 @@
|
|
|
338
341
|
* stream — and must show once.
|
|
339
342
|
*/
|
|
340
343
|
function buttonsRank(b) { return b && b.state !== "open" ? 1 : 0; }
|
|
344
|
+
/**
|
|
345
|
+
* Whether `next` is an older view than `had` of the same buttons: they only go forward (open → ended), and a poll's
|
|
346
|
+
* views (#1823), which change while it is open, by their `rev`.
|
|
347
|
+
*/
|
|
348
|
+
function olderButtons(next, had) {
|
|
349
|
+
var d = buttonsRank(next) - buttonsRank(had);
|
|
350
|
+
if (d) return d < 0;
|
|
351
|
+
return !!(next && had && next.poll && had.poll && next.poll.rev < had.poll.rev);
|
|
352
|
+
}
|
|
353
|
+
/** #1823: a poll's time left, counted down by the page every second: m:ss, or h:mm:ss. */
|
|
354
|
+
function pollRemaining(ms) {
|
|
355
|
+
var s = Math.max(0, Math.ceil(ms / 1000));
|
|
356
|
+
var h = Math.floor(s / 3600), m = Math.floor((s % 3600) / 60), r = s % 60;
|
|
357
|
+
var mm = h ? String(m).padStart(2, "0") : String(m);
|
|
358
|
+
return (h ? h + ":" : "") + mm + ":" + String(r).padStart(2, "0");
|
|
359
|
+
}
|
|
341
360
|
/**
|
|
342
361
|
* #1266: a reply's buttons changed (one was chosen, or they expired): the message carrying them, by its set id.
|
|
343
362
|
* Returns the list unchanged when no message carries that set.
|
|
@@ -346,7 +365,7 @@
|
|
|
346
365
|
if (!Array.isArray(list) || !buttons || typeof buttons.id !== "string") return list;
|
|
347
366
|
var i = -1;
|
|
348
367
|
for (var j = list.length - 1; j >= 0; j--) if (list[j] && list[j].buttons && list[j].buttons.id === buttons.id) { i = j; break; }
|
|
349
|
-
if (i < 0 ||
|
|
368
|
+
if (i < 0 || olderButtons(buttons, list[i].buttons)) return list;
|
|
350
369
|
var out = list.slice();
|
|
351
370
|
out[i] = Object.assign({}, list[i], { buttons: buttons });
|
|
352
371
|
return out;
|
|
@@ -366,7 +385,7 @@
|
|
|
366
385
|
var next = nextDeliveryState(had.delivery, m.delivery);
|
|
367
386
|
if (next !== had.delivery) out[seen[k]] = had = Object.assign({}, had, { delivery: next });
|
|
368
387
|
// #1266: …and the copy whose buttons got further (open → chosen / expired) shows them.
|
|
369
|
-
if (m.buttons && (!had.buttons || buttonsRank(m.buttons) > buttonsRank(had.buttons))) out[seen[k]] = Object.assign({}, had, { buttons: m.buttons });
|
|
388
|
+
if (m.buttons && (!had.buttons || buttonsRank(m.buttons) > buttonsRank(had.buttons) || (buttonsRank(m.buttons) === buttonsRank(had.buttons) && m.buttons.poll && had.buttons.poll && m.buttons.poll.rev > had.buttons.poll.rev))) out[seen[k]] = Object.assign({}, had, { buttons: m.buttons });
|
|
370
389
|
return;
|
|
371
390
|
}
|
|
372
391
|
seen[k] = out.length;
|
|
@@ -470,8 +489,32 @@
|
|
|
470
489
|
return draft ? failedText + "\n" + draft : failedText;
|
|
471
490
|
}
|
|
472
491
|
|
|
473
|
-
/** The upload limits the server enforces (web-upload.ts UPLOAD_LIMITS)
|
|
474
|
-
var FILE_LIMITS = { maxFileBytes:
|
|
492
|
+
/** The defaults of the upload limits the server enforces (web-upload.ts UPLOAD_LIMITS), for a page that was given none. */
|
|
493
|
+
var FILE_LIMITS = { maxFileBytes: 50 * 1024 * 1024, maxFiles: 5, maxTotalBytes: 100 * 1024 * 1024 };
|
|
494
|
+
/**
|
|
495
|
+
* #1937: the limits in force for this page — the server's (web.upload_max_file_mb / _total_mb, and the public link's
|
|
496
|
+
* own cap), on <body data-upload-limits>. Checked here first only to say so early: the server checks every upload.
|
|
497
|
+
*/
|
|
498
|
+
function fileLimits() {
|
|
499
|
+
try {
|
|
500
|
+
var raw = JSON.parse(document.body.dataset.uploadLimits || "null");
|
|
501
|
+
var ok = function (n) { return typeof n === "number" && isFinite(n) && n > 0; };
|
|
502
|
+
if (raw && ok(raw.maxFileBytes) && ok(raw.maxFiles) && ok(raw.maxTotalBytes)) {
|
|
503
|
+
return { maxFileBytes: raw.maxFileBytes, maxFiles: raw.maxFiles, maxTotalBytes: raw.maxTotalBytes, publicLinkCapped: raw.publicLinkCapped === true };
|
|
504
|
+
}
|
|
505
|
+
} catch (e) { /* no page, or no limits on it */ }
|
|
506
|
+
return { maxFileBytes: FILE_LIMITS.maxFileBytes, maxFiles: FILE_LIMITS.maxFiles, maxTotalBytes: FILE_LIMITS.maxTotalBytes, publicLinkCapped: false };
|
|
507
|
+
}
|
|
508
|
+
/** A size in MB as the limits are worded ("50", "2.5") — web-upload.ts mbText. */
|
|
509
|
+
function mbText(bytes) { return String(Math.round((bytes / 1024 / 1024) * 10) / 10); }
|
|
510
|
+
/** #1937: the words for a file checkFiles() refused — a chat string key and the numbers it says, from fileLimits(). */
|
|
511
|
+
function fileRejection(reason) {
|
|
512
|
+
var l = fileLimits();
|
|
513
|
+
if (reason === "too-large") return { key: l.publicLinkCapped ? "chat.file_too_large_public" : "chat.file_too_large", values: [mbText(l.maxFileBytes)] };
|
|
514
|
+
if (reason === "too-many") return { key: "chat.file_too_many", values: [l.maxFiles] };
|
|
515
|
+
if (reason === "too-much") return { key: "chat.file_too_much", values: [mbText(l.maxTotalBytes)] };
|
|
516
|
+
return { key: "chat.file_" + String(reason).replace(/-/g, "_"), values: [] };
|
|
517
|
+
}
|
|
475
518
|
|
|
476
519
|
function formatSize(n) {
|
|
477
520
|
var b = Number(n) || 0;
|
|
@@ -485,6 +528,7 @@
|
|
|
485
528
|
* Returns the files kept (pending first, then the accepted new ones) and, for each refused one, why.
|
|
486
529
|
*/
|
|
487
530
|
function checkFiles(pending, adding) {
|
|
531
|
+
var limits = fileLimits();
|
|
488
532
|
var kept = (pending || []).slice();
|
|
489
533
|
var total = 0;
|
|
490
534
|
kept.forEach(function (f) { total += Number(f.size) || 0; });
|
|
@@ -492,9 +536,9 @@
|
|
|
492
536
|
(adding || []).forEach(function (f) {
|
|
493
537
|
var size = Number(f.size) || 0;
|
|
494
538
|
if (size === 0) rejected.push({ name: f.name, reason: "empty" });
|
|
495
|
-
else if (size >
|
|
496
|
-
else if (kept.length >=
|
|
497
|
-
else if (total + size >
|
|
539
|
+
else if (size > limits.maxFileBytes) rejected.push({ name: f.name, reason: "too-large" });
|
|
540
|
+
else if (kept.length >= limits.maxFiles) rejected.push({ name: f.name, reason: "too-many" });
|
|
541
|
+
else if (total + size > limits.maxTotalBytes) rejected.push({ name: f.name, reason: "too-much" });
|
|
498
542
|
else { kept.push(f); total += size; }
|
|
499
543
|
});
|
|
500
544
|
return { kept: kept, rejected: rejected };
|
|
@@ -550,8 +594,8 @@
|
|
|
550
594
|
if (cards && isHtmlAttachment(a)) after.push('<div class="html-card" data-att="' + a.id + '"></div>');
|
|
551
595
|
var url = "/ui/file/" + a.id;
|
|
552
596
|
var kind = inline ? attachmentPreviewType(a) : null;
|
|
553
|
-
if (kind === "video") after.push('<div class="att-media"><video class="att-video" controls preload="metadata" src="' + url + '"></video></div>');
|
|
554
|
-
else if (kind === "audio") after.push('<div class="att-media"><audio class="att-audio" controls preload="metadata" src="' + url + '"></audio></div>');
|
|
597
|
+
if (kind === "video") after.push('<div data-trust="media" class="att-media"><video class="att-video" controls preload="metadata" src="' + url + '"></video></div>');
|
|
598
|
+
else if (kind === "audio") after.push('<div data-trust="media" class="att-media"><audio class="att-audio" controls preload="metadata" src="' + url + '"></audio></div>');
|
|
555
599
|
else if (kind) after.push('<div class="text-card" data-att="' + a.id + '" data-type="' + kind + '"></div>');
|
|
556
600
|
var name = escapeHtml(a.name || "file");
|
|
557
601
|
if (a.kind === "photo") {
|
|
@@ -560,13 +604,14 @@
|
|
|
560
604
|
out.push('<a class="att-file" href="' + url + '" download="' + name + '">📎 ' + name + ' <span class="att-size">' + escapeHtml(formatSize(a.size)) + "</span></a>");
|
|
561
605
|
}
|
|
562
606
|
});
|
|
563
|
-
return (out.length ? '<div class="atts">' + out.join("") + "</div>" : "") + after.join("");
|
|
607
|
+
return (out.length ? '<div data-trust="attachment" class="atts">' + out.join("") + "</div>" : "") + after.join("");
|
|
564
608
|
}
|
|
565
609
|
|
|
566
610
|
return {
|
|
567
611
|
renderMarkdown: renderMarkdown, escapeHtml: escapeHtml, mergeMessages: mergeMessages, applyReplyButtons: applyReplyButtons, composerKey: composerKey,
|
|
612
|
+
pollRemaining: pollRemaining,
|
|
568
613
|
settleFailedSend: settleFailedSend, putBack: putBack,
|
|
569
|
-
FILE_LIMITS: FILE_LIMITS, formatSize: formatSize, checkFiles: checkFiles, attachmentsHtml: attachmentsHtml, isHtmlAttachment: isHtmlAttachment,
|
|
614
|
+
FILE_LIMITS: FILE_LIMITS, fileLimits: fileLimits, fileRejection: fileRejection, formatSize: formatSize, checkFiles: checkFiles, attachmentsHtml: attachmentsHtml, isHtmlAttachment: isHtmlAttachment,
|
|
570
615
|
nextDeliveryState: nextDeliveryState, applyDelivery: applyDelivery, deliveryHtml: deliveryHtml, isBusy: isBusy,
|
|
571
616
|
isNearBottom: isNearBottom, CODE_FOLD_LINES: CODE_FOLD_LINES, lineCount: lineCount,
|
|
572
617
|
LONG_PASTE_CHARS: LONG_PASTE_CHARS, isLongPaste: isLongPaste, formatElapsed: formatElapsed,
|