@songsid/agend 2.3.0-beta.25 → 2.3.0-beta.27
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 +7 -5
- package/README.zh-TW.md +7 -5
- package/dist/adapter-world.js.map +1 -1
- package/dist/agend-home.d.ts +5 -0
- package/dist/agend-home.js +7 -0
- package/dist/agend-home.js.map +1 -0
- package/dist/agent-cli.js.map +1 -1
- package/dist/agent-endpoint.js +8 -0
- package/dist/agent-endpoint.js.map +1 -1
- package/dist/agent-file-read.js +1 -1
- package/dist/agent-file-read.js.map +1 -1
- package/dist/agent-whats-new.js +12 -2
- package/dist/agent-whats-new.js.map +1 -1
- package/dist/apply-job.js.map +1 -1
- package/dist/atomic-write.js.map +1 -1
- package/dist/auth-api.d.ts +5 -0
- package/dist/auth-api.js +21 -0
- package/dist/auth-api.js.map +1 -1
- package/dist/authority/authority-state.d.ts +4 -0
- package/dist/authority/authority-state.js +17 -8
- package/dist/authority/authority-state.js.map +1 -1
- package/dist/authority/session-authorizations.js.map +1 -1
- package/dist/authority/web-authority.d.ts +14 -6
- package/dist/authority/web-authority.js +19 -5
- package/dist/authority/web-authority.js.map +1 -1
- package/dist/backend/antigravity.d.ts +2 -2
- package/dist/backend/antigravity.js +2 -1
- package/dist/backend/antigravity.js.map +1 -1
- package/dist/backend/claude-code.js.map +1 -1
- package/dist/backend/codex-package.js.map +1 -1
- package/dist/backend/codex-session-lookup.js.map +1 -1
- package/dist/backend/codex.js +1 -1
- package/dist/backend/codex.js.map +1 -1
- package/dist/backend/effort-metadata.js.map +1 -1
- package/dist/backend/grok.js.map +1 -1
- package/dist/backend/kiro-engine-ledger.js.map +1 -1
- package/dist/backend/kiro-kas-store.js.map +1 -1
- package/dist/backend/kiro-v2-store.js.map +1 -1
- package/dist/backend/kiro-v3-identity.js.map +1 -1
- package/dist/backend/kiro.js.map +1 -1
- package/dist/backend/launch-effort.js.map +1 -1
- package/dist/backend/mock.js.map +1 -1
- package/dist/backend/muse.js.map +1 -1
- package/dist/backend/opencode-permission.js.map +1 -1
- package/dist/backend/opencode.js.map +1 -1
- package/dist/backend/session-signals.js.map +1 -1
- package/dist/backend-availability.js.map +1 -1
- package/dist/backend-command.js.map +1 -1
- package/dist/backend-outage.js.map +1 -1
- package/dist/binary-probe.js.map +1 -1
- package/dist/bug-report.js.map +1 -1
- package/dist/bundled-skills.js.map +1 -1
- package/dist/cache-service.js +1 -1
- package/dist/cache-service.js.map +1 -1
- package/dist/channel/adapters/discord.d.ts +2 -0
- package/dist/channel/adapters/discord.js +40 -5
- package/dist/channel/adapters/discord.js.map +1 -1
- package/dist/channel/adapters/telegram.d.ts +6 -0
- package/dist/channel/adapters/telegram.js +54 -14
- package/dist/channel/adapters/telegram.js.map +1 -1
- package/dist/channel/mcp-server.js +10 -1
- package/dist/channel/mcp-server.js.map +1 -1
- package/dist/channel/mcp-tools.js +2 -2
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/channel/message-queue.js.map +1 -1
- package/dist/channel/sticker-download.js.map +1 -1
- package/dist/channel/tool-router.js.map +1 -1
- package/dist/classic-channel-manager.js +3 -2
- package/dist/classic-channel-manager.js.map +1 -1
- package/dist/cli-entry.js.map +1 -1
- package/dist/cli-flags.js.map +1 -1
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +44 -9
- package/dist/cli.js.map +1 -1
- package/dist/command-table.js.map +1 -1
- package/dist/compact-watch.js.map +1 -1
- package/dist/completion-install.js.map +1 -1
- package/dist/completion.js.map +1 -1
- package/dist/config-validator.js +22 -6
- package/dist/config-validator.js.map +1 -1
- package/dist/config.js +8 -2
- package/dist/config.js.map +1 -1
- package/dist/connection-secrets.js.map +1 -1
- package/dist/context-guardian.js.map +1 -1
- package/dist/coredump-filter.js.map +1 -1
- package/dist/cpu-profile.js.map +1 -1
- package/dist/cross-instance-envelope.js.map +1 -1
- package/dist/daemon.d.ts +3 -0
- package/dist/daemon.js +76 -4
- package/dist/daemon.js.map +1 -1
- package/dist/daily-summary.js.map +1 -1
- package/dist/darwin-memory.js.map +1 -1
- package/dist/dashboard-live.d.ts +8 -0
- package/dist/dashboard-live.js +35 -0
- package/dist/dashboard-live.js.map +1 -0
- package/dist/dashboard-permissions.d.ts +69 -0
- package/dist/dashboard-permissions.js +138 -0
- package/dist/dashboard-permissions.js.map +1 -0
- package/dist/decision-list.js.map +1 -1
- package/dist/delivery-consumed-watch.js.map +1 -1
- package/dist/delivery-outbox.js.map +1 -1
- package/dist/delivery-outbox.test.js.map +1 -1
- package/dist/delivery-reconciliation.test.js.map +1 -1
- package/dist/diagnostic-redaction.js.map +1 -1
- package/dist/doctor.d.ts +3 -0
- package/dist/doctor.js +10 -0
- package/dist/doctor.js.map +1 -1
- package/dist/e2e-isolation.js.map +1 -1
- package/dist/event-log.js.map +1 -1
- package/dist/export-import.js +3 -2
- package/dist/export-import.js.map +1 -1
- package/dist/fleet-control-audit.js.map +1 -1
- package/dist/fleet-label.js.map +1 -1
- package/dist/fleet-lock.js +3 -2
- package/dist/fleet-lock.js.map +1 -1
- package/dist/fleet-manager.d.ts +84 -7
- package/dist/fleet-manager.js +562 -158
- package/dist/fleet-manager.js.map +1 -1
- package/dist/fleet-process-handlers.js +3 -2
- package/dist/fleet-process-handlers.js.map +1 -1
- package/dist/fleet-startup.d.ts +14 -0
- package/dist/fleet-startup.js +30 -0
- package/dist/fleet-startup.js.map +1 -0
- package/dist/fleet-traffic.js.map +1 -1
- package/dist/forged-envelope-scan.d.ts +0 -20
- package/dist/forged-envelope-scan.js +3 -2
- package/dist/forged-envelope-scan.js.map +1 -1
- package/dist/full-restart.js.map +1 -1
- package/dist/general-knowledge/skills/web-appearance/SKILL.canvas-worker-off.md +2 -0
- package/dist/general-knowledge/skills/web-appearance/SKILL.md +2 -0
- package/dist/general-knowledge/skills/web-dashboard/SKILL.canvas-worker-off.md +43 -21
- package/dist/general-knowledge/skills/web-dashboard/SKILL.md +52 -23
- package/dist/host-memory.js.map +1 -1
- package/dist/idle-gate-log.d.ts +19 -0
- package/dist/idle-gate-log.js +15 -0
- package/dist/idle-gate-log.js.map +1 -0
- package/dist/instance-config-impact.js +6 -0
- package/dist/instance-config-impact.js.map +1 -1
- package/dist/instance-lifecycle.js +1 -1
- package/dist/instance-lifecycle.js.map +1 -1
- package/dist/instructions.d.ts +2 -0
- package/dist/instructions.js +6 -2
- package/dist/instructions.js.map +1 -1
- package/dist/kiro-compact-pane.js.map +1 -1
- package/dist/kiro-db-reader.js.map +1 -1
- package/dist/kiro-engine-status.js.map +1 -1
- package/dist/kiro-log-gate.js.map +1 -1
- package/dist/kiro-transcript-lane.js.map +1 -1
- package/dist/locale.js +40 -6
- package/dist/locale.js.map +1 -1
- package/dist/log-service.js.map +1 -1
- package/dist/log-steps.js.map +1 -1
- package/dist/logger.js +3 -2
- package/dist/logger.js.map +1 -1
- package/dist/login-controller.js.map +1 -1
- package/dist/login-manager.js.map +1 -1
- package/dist/login-shell.js.map +1 -1
- package/dist/memory-pressure.js.map +1 -1
- package/dist/muse-usage-relay.js.map +1 -1
- package/dist/needs-you-hub.js.map +1 -1
- package/dist/needs-you-live.js.map +1 -1
- package/dist/network-family.js.map +1 -1
- package/dist/operator-control-paths.js +3 -4
- package/dist/operator-control-paths.js.map +1 -1
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.d.ts +68 -68
- package/dist/outbound-schemas.js +1 -1
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/outbox-open-error.js.map +1 -1
- package/dist/pane-context-cache.js.map +1 -1
- package/dist/paths.js.map +1 -1
- package/dist/private-dir.js.map +1 -1
- package/dist/probe-worker-pool.js.map +1 -1
- package/dist/profile-control.js.map +1 -1
- package/dist/prompt-file-ref.js.map +1 -1
- package/dist/provider-probe.js.map +1 -1
- package/dist/provider-secret-registry.js.map +1 -1
- package/dist/public-link-auto-open.d.ts +32 -0
- package/dist/public-link-auto-open.js +86 -0
- package/dist/public-link-auto-open.js.map +1 -0
- package/dist/public-link-owner.d.ts +10 -0
- package/dist/public-link-owner.js +36 -0
- package/dist/public-link-owner.js.map +1 -0
- package/dist/public-link-progress.js.map +1 -1
- package/dist/public-preview.js.map +1 -1
- package/dist/public-web-gateway.js +7 -2
- package/dist/public-web-gateway.js.map +1 -1
- package/dist/public-web-link.d.ts +35 -1
- package/dist/public-web-link.js +142 -43
- package/dist/public-web-link.js.map +1 -1
- package/dist/quickstart-api.js.map +1 -1
- package/dist/quickstart.d.ts +5 -0
- package/dist/quickstart.js +25 -16
- package/dist/quickstart.js.map +1 -1
- package/dist/reply-buttons-controller.js.map +1 -1
- package/dist/reply-dedup.js.map +1 -1
- package/dist/resource-report.js.map +1 -1
- package/dist/restart-guard.js.map +1 -1
- package/dist/restart-progress.js.map +1 -1
- package/dist/rollout-index.js.map +1 -1
- package/dist/runtime-cpu-profile.js.map +1 -1
- package/dist/scheduler/db.js.map +1 -1
- package/dist/scheduler/scheduler.js.map +1 -1
- package/dist/scheduler/task-board-text.js +16 -11
- package/dist/scheduler/task-board-text.js.map +1 -1
- package/dist/scheduler/task-board.d.ts +39 -11
- package/dist/scheduler/task-board.js +123 -45
- package/dist/scheduler/task-board.js.map +1 -1
- package/dist/scheduler/types.d.ts +1 -1
- package/dist/sd-notify.js.map +1 -1
- package/dist/search/search-index.js.map +1 -1
- package/dist/secret-file.js.map +1 -1
- package/dist/secret-store.js.map +1 -1
- package/dist/service-installer.d.ts +7 -1
- package/dist/service-installer.js +13 -1
- package/dist/service-installer.js.map +1 -1
- package/dist/service-plan.d.ts +33 -4
- package/dist/service-plan.js +177 -8
- package/dist/service-plan.js.map +1 -1
- package/dist/session-oom.d.ts +21 -0
- package/dist/session-oom.js +139 -0
- package/dist/session-oom.js.map +1 -0
- package/dist/settings-api.js +3 -1
- package/dist/settings-api.js.map +1 -1
- package/dist/settings-baseline.js.map +1 -1
- package/dist/settings-change.d.ts +5 -0
- package/dist/settings-change.js +71 -1
- package/dist/settings-change.js.map +1 -1
- package/dist/settings-confirmation.d.ts +4 -0
- package/dist/settings-confirmation.js +2 -0
- package/dist/settings-confirmation.js.map +1 -1
- package/dist/settings-control.js.map +1 -1
- package/dist/settings-effect.js +4 -1
- package/dist/settings-effect.js.map +1 -1
- package/dist/settings-http-confirmation.js +1 -0
- package/dist/settings-http-confirmation.js.map +1 -1
- package/dist/settings-instance-patch.js +13 -1
- package/dist/settings-instance-patch.js.map +1 -1
- package/dist/settings-transaction.js.map +1 -1
- package/dist/setup-auth.js.map +1 -1
- package/dist/setup-host.d.ts +5 -1
- package/dist/setup-host.js +8 -1
- package/dist/setup-host.js.map +1 -1
- package/dist/setup-wizard.js +5 -2
- package/dist/setup-wizard.js.map +1 -1
- package/dist/spawn-gate.d.ts +4 -0
- package/dist/spawn-gate.js +15 -5
- package/dist/spawn-gate.js.map +1 -1
- package/dist/start-attempt.d.ts +1 -0
- package/dist/start-attempt.js +18 -0
- package/dist/start-attempt.js.map +1 -0
- package/dist/start-failure-notice.d.ts +4 -0
- package/dist/start-failure-notice.js +20 -0
- package/dist/start-failure-notice.js.map +1 -0
- package/dist/start-failure.d.ts +147 -0
- package/dist/start-failure.js +281 -0
- package/dist/start-failure.js.map +1 -0
- package/dist/startup-memory-deadline.d.ts +23 -0
- package/dist/startup-memory-deadline.js +55 -0
- package/dist/startup-memory-deadline.js.map +1 -0
- package/dist/statusline-watcher.js.map +1 -1
- package/dist/storm-window.js.map +1 -1
- package/dist/systemd-peer-bus.js.map +1 -1
- package/dist/target-queue-worker.js.map +1 -1
- package/dist/task-board-summary.js.map +1 -1
- package/dist/task-board-tool.js +3 -2
- package/dist/task-board-tool.js.map +1 -1
- package/dist/task-board-waker.js +8 -4
- package/dist/task-board-waker.js.map +1 -1
- package/dist/tips.js.map +1 -1
- package/dist/tmux-control.js.map +1 -1
- package/dist/tmux-exec-helper.js.map +1 -1
- package/dist/tmux-manager.d.ts +1 -0
- package/dist/tmux-manager.js +2 -1
- package/dist/tmux-manager.js.map +1 -1
- package/dist/tmux-read.js +1 -1
- package/dist/tmux-read.js.map +1 -1
- package/dist/tool-permissions.js.map +1 -1
- package/dist/tool-progress.js.map +1 -1
- package/dist/topic-archiver.js.map +1 -1
- package/dist/topic-commands.js.map +1 -1
- package/dist/topic-link.js.map +1 -1
- package/dist/transcript-delta-reader.js.map +1 -1
- package/dist/transcript-jsonl.js.map +1 -1
- package/dist/transcript-monitor.js.map +1 -1
- package/dist/transcript-sources.js.map +1 -1
- package/dist/tunnel/cloudflared-install.js.map +1 -1
- package/dist/tunnel/cloudflared.js.map +1 -1
- package/dist/tunnel/lease.js +3 -2
- package/dist/tunnel/lease.js.map +1 -1
- package/dist/tunnel/manager.js.map +1 -1
- package/dist/tunnel/public-fetch.js.map +1 -1
- package/dist/tunnel/purpose-lane.js.map +1 -1
- package/dist/tunnel/types.js.map +1 -1
- package/dist/types.d.ts +22 -1
- package/dist/ui/chat-render.js +7 -17
- package/dist/ui/chat-store.js +171 -61
- package/dist/ui/chat-strings.js +9 -4
- package/dist/ui/chat-thread.js +48 -1
- package/dist/ui/fleet-canvas-layout.js +3 -0
- package/dist/ui/fleet-canvas.js +23 -20
- package/dist/ui/fleet-office-editor.js +149 -0
- package/dist/ui/fleet-office-model.js +358 -0
- package/dist/ui/fleet-office-pack.js +199 -0
- package/dist/ui/fleet-office-skin.js +474 -0
- package/dist/ui/fleet-office-store.js +49 -0
- package/dist/ui/fleet-office-zones.js +177 -0
- package/dist/ui/fleet-office.js +434 -0
- package/dist/ui/fleet-org.js +18 -5
- package/dist/ui/guest-refused.html +41 -0
- package/dist/ui/image-lightbox.js +9 -3
- package/dist/ui/panel-chat.js +35 -22
- package/dist/ui/panel-settings.js +24 -7
- package/dist/ui/settings-dialogs.js +11 -2
- package/dist/ui/settings-model.js +20 -0
- package/dist/ui/settings-strings.js +36 -0
- package/dist/ui/settings-web-authority.js +7 -2
- package/dist/ui/shared/agend-auth.js +2 -0
- package/dist/ui/shared/app-i18n.js +2 -2
- package/dist/ui/shared/app-prefs.js +2 -0
- package/dist/ui/shared/app-shell.js +62 -9
- package/dist/ui/shared/app-store.js +1 -0
- package/dist/ui/shared/app-stream.js +2 -1
- package/dist/ui/shared/app.css +164 -15
- package/dist/ui/shared/guest-refused.js +35 -0
- package/dist/ui/shared/header-tools.js +2 -2
- package/dist/ui/shared/instance-nav.js +25 -8
- package/dist/ui/shared/tokens.css +4 -0
- package/dist/ui/shared/ui-dialog.js +2 -2
- package/dist/ui/shared/usage-dialog.js +152 -6
- package/dist/ui/shared/view-strings.js +20 -0
- package/dist/ui/shared/web-authorize.js +3 -1
- package/dist/update-check.js.map +1 -1
- package/dist/update-dispatch.js.map +1 -1
- package/dist/update-install.js.map +1 -1
- package/dist/update-launch.js.map +1 -1
- package/dist/update-marker.js.map +1 -1
- package/dist/update-preflight.js.map +1 -1
- package/dist/update-progress.d.ts +3 -1
- package/dist/update-progress.js +3 -1
- package/dist/update-progress.js.map +1 -1
- package/dist/usage/format-rich.js.map +1 -1
- package/dist/usage/providers.d.ts +8 -0
- package/dist/usage/providers.js +6 -2
- package/dist/usage/providers.js.map +1 -1
- package/dist/usage/statusline-usage.js.map +1 -1
- package/dist/usage/usage-api.js +2 -1
- package/dist/usage/usage-api.js.map +1 -1
- package/dist/usage-report.js.map +1 -1
- package/dist/usage-rollup.js +1 -1
- package/dist/usage-rollup.js.map +1 -1
- package/dist/user-systemd.js.map +1 -1
- package/dist/view-api.js +1 -1
- package/dist/view-api.js.map +1 -1
- package/dist/wake-coordinator.js.map +1 -1
- package/dist/web-ambient.js.map +1 -1
- package/dist/web-api.d.ts +19 -2
- package/dist/web-api.js +180 -28
- package/dist/web-api.js.map +1 -1
- package/dist/web-app-icon.js.map +1 -1
- package/dist/web-appearance-policy.js.map +1 -1
- package/dist/web-appearance-tools.js.map +1 -1
- package/dist/web-appearance-view.js.map +1 -1
- package/dist/web-appearance.js.map +1 -1
- package/dist/web-auth.js +5 -4
- package/dist/web-auth.js.map +1 -1
- package/dist/web-backend-login.js.map +1 -1
- package/dist/web-card.js.map +1 -1
- package/dist/web-channel-echo.js.map +1 -1
- package/dist/web-chat-history.d.ts +41 -11
- package/dist/web-chat-history.js +93 -22
- package/dist/web-chat-history.js.map +1 -1
- package/dist/web-commands.js.map +1 -1
- package/dist/web-effect-bundle.d.ts +2 -2
- package/dist/web-effect-library.js.map +1 -1
- package/dist/web-effect-playback.js.map +1 -1
- package/dist/web-effect-profiles.d.ts +16 -16
- package/dist/web-effect-profiles.js +3 -2
- package/dist/web-effect-profiles.js.map +1 -1
- package/dist/web-effect-service.js.map +1 -1
- package/dist/web-effect-tools.js.map +1 -1
- package/dist/web-effect-trials.js.map +1 -1
- package/dist/web-effect-upload.js.map +1 -1
- package/dist/web-effect.d.ts +7 -7
- package/dist/web-effect.js.map +1 -1
- package/dist/web-experimental.js.map +1 -1
- package/dist/web-guest-access.js +1 -1
- package/dist/web-guest-access.js.map +1 -1
- package/dist/web-guest-rooms.js.map +1 -1
- package/dist/web-host-guard.js.map +1 -1
- package/dist/web-icons.js.map +1 -1
- package/dist/web-identity-color.js.map +1 -1
- package/dist/web-images.js.map +1 -1
- package/dist/web-libavoid.js.map +1 -1
- package/dist/web-login.js.map +1 -1
- package/dist/web-mentions.js.map +1 -1
- package/dist/web-mod-agent.js.map +1 -1
- package/dist/web-mod-store.js.map +1 -1
- package/dist/web-office-store.d.ts +104 -0
- package/dist/web-office-store.js +236 -0
- package/dist/web-office-store.js.map +1 -0
- package/dist/web-owner-css-store.js.map +1 -1
- package/dist/web-owner-css.js.map +1 -1
- package/dist/web-png.js.map +1 -1
- package/dist/web-prefs.js +2 -0
- package/dist/web-prefs.js.map +1 -1
- package/dist/web-profile.js.map +1 -1
- package/dist/web-reactions.d.ts +2 -1
- package/dist/web-request-context.d.ts +5 -0
- package/dist/web-request-context.js.map +1 -1
- package/dist/web-returning-device.js +1 -1
- package/dist/web-returning-device.js.map +1 -1
- package/dist/web-schedules.js.map +1 -1
- package/dist/web-send-dedupe.d.ts +36 -0
- package/dist/web-send-dedupe.js +69 -0
- package/dist/web-send-dedupe.js.map +1 -0
- package/dist/web-session-authorization.js.map +1 -1
- package/dist/web-session.js +1 -1
- package/dist/web-session.js.map +1 -1
- package/dist/web-settings-patch.d.ts +1 -0
- package/dist/web-settings-patch.js +6 -4
- package/dist/web-settings-patch.js.map +1 -1
- package/dist/web-settings-tools.js.map +1 -1
- package/dist/web-terminal-http.js.map +1 -1
- package/dist/web-terminal.js.map +1 -1
- package/dist/web-theme-store.js.map +1 -1
- package/dist/web-upload.d.ts +5 -1
- package/dist/web-upload.js +6 -4
- package/dist/web-upload.js.map +1 -1
- package/dist/web-widget.js.map +1 -1
- package/dist/webhook-emitter.js.map +1 -1
- package/dist/windows-shortcuts.js +1 -1
- package/dist/windows-shortcuts.js.map +1 -1
- package/dist/workflow-templates/default.md +1 -1
- package/dist/ws-server.js.map +1 -1
- package/dist/yaml-shared-nodes.js.map +1 -1
- package/launcher/agend-install.cjs +281 -6
- package/launcher/private-home.cjs +43 -0
- package/package.json +3 -2
- package/templates/windows-shortcuts.ps1 +1 -1
package/dist/ui/chat-render.js
CHANGED
|
@@ -509,27 +509,17 @@
|
|
|
509
509
|
/** Whether the chat shows "working" and its Stop button for an instance in this execution state. */
|
|
510
510
|
function isBusy(state) { return state === "working" || state === "stuck"; }
|
|
511
511
|
|
|
512
|
-
/**
|
|
513
|
-
|
|
512
|
+
/**
|
|
513
|
+
* What a key in the composer does: Enter sends, Shift+Enter is a new line, nothing happens mid-IME-composition.
|
|
514
|
+
* #2478 (the user's request): on a touch screen (a coarse pointer) Return is a new line — the Send button sends.
|
|
515
|
+
*/
|
|
516
|
+
function composerKey(ev, opts) {
|
|
514
517
|
if (!ev || ev.key !== "Enter" || ev.isComposing || ev.keyCode === 229) return "none";
|
|
518
|
+
if (opts && opts.touch) return "newline";
|
|
515
519
|
if (ev.shiftKey || ev.altKey || ev.ctrlKey || ev.metaKey) return "newline";
|
|
516
520
|
return "send";
|
|
517
521
|
}
|
|
518
522
|
|
|
519
|
-
/**
|
|
520
|
-
* A send that failed: where does its text go? Back into the composer only if that composer is the same
|
|
521
|
-
* target's, still on screen and empty; otherwise it is kept aside for that target (never put into another
|
|
522
|
-
* target's composer, never over a draft someone is typing) and offered back when that chat is open.
|
|
523
|
-
*/
|
|
524
|
-
function settleFailedSend(target, current, composerPresent, composerValue) {
|
|
525
|
-
return target === current && composerPresent && !composerValue ? "restore" : "keep";
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
/** "Put back" for a kept message: in front of whatever is being typed now, never replacing it. */
|
|
529
|
-
function putBack(failedText, draft) {
|
|
530
|
-
return draft ? failedText + "\n" + draft : failedText;
|
|
531
|
-
}
|
|
532
|
-
|
|
533
523
|
/** The defaults of the upload limits the server enforces (web-upload.ts UPLOAD_LIMITS), for a page that was given none. */
|
|
534
524
|
var FILE_LIMITS = { maxFileBytes: 50 * 1024 * 1024, maxFiles: 5, maxTotalBytes: 100 * 1024 * 1024 };
|
|
535
525
|
/**
|
|
@@ -666,7 +656,7 @@
|
|
|
666
656
|
return {
|
|
667
657
|
renderMarkdown: renderMarkdown, escapeHtml: escapeHtml, mergeMessages: mergeMessages, applyReplyButtons: applyReplyButtons, composerKey: composerKey,
|
|
668
658
|
pollRemaining: pollRemaining,
|
|
669
|
-
|
|
659
|
+
|
|
670
660
|
FILE_LIMITS: FILE_LIMITS, fileLimits: fileLimits, fileRejection: fileRejection, formatSize: formatSize, checkFiles: checkFiles, attachmentsHtml: attachmentsHtml, isHtmlAttachment: isHtmlAttachment,
|
|
671
661
|
nextDeliveryState: nextDeliveryState, applyDelivery: applyDelivery, deliveryHtml: deliveryHtml, isBusy: isBusy,
|
|
672
662
|
applyReactions: applyReactions, reactionsHtml: reactionsHtml,
|
package/dist/ui/chat-store.js
CHANGED
|
@@ -42,7 +42,7 @@ const COMPACT_PAGE_LIMIT_MS = 11 * 60_000;
|
|
|
42
42
|
/** Build a store. `deps` (fetch, toast, announce, t, now) are injected so tests can drive every path. */
|
|
43
43
|
export function createChatStore(deps) {
|
|
44
44
|
const s = {
|
|
45
|
-
msgs: {}, drafts: {}, replies: {},
|
|
45
|
+
msgs: {}, drafts: {}, replies: {}, pendingFiles: {}, sending: {}, outgoing: {}, instanceStatus: {}, waking: {},
|
|
46
46
|
prompts: {}, workingSince: {}, stopping: {}, cancelling: {}, scrollMemo: {}, historyRead: new Set(), rbBusy: new Set(),
|
|
47
47
|
pollMine: readPollMine(),
|
|
48
48
|
// #1823 (#1828 review): the server's clock minus this device's. A poll's deadline is the server's; a page whose clock
|
|
@@ -108,6 +108,8 @@ export function createChatStore(deps) {
|
|
|
108
108
|
liveReplies.message(m, extra);
|
|
109
109
|
const before = s.msgs[m.instance];
|
|
110
110
|
s.msgs[m.instance] = R().mergeMessages(before, [m], 500);
|
|
111
|
+
// The live copy of a message this page sent carries its clientId: its pending bubble goes in this same redraw.
|
|
112
|
+
if (settleOutgoing(m.instance, m.role === "user" ? m.clientId : undefined) && s.msgs[m.instance] === before) changed(m.instance, "msgs");
|
|
111
113
|
if (m.instance === s.current && s.msgs[m.instance] !== before && m.role !== "status" && !isUser(m, m.instance) && (s.msgs[m.instance] || []).length > (before || []).length) deps.announce(tf("chat.a11yReplied", m.sender));
|
|
112
114
|
if (s.msgs[m.instance] !== before) changed(m.instance, "msgs");
|
|
113
115
|
}
|
|
@@ -166,6 +168,7 @@ export function createChatStore(deps) {
|
|
|
166
168
|
// Kept even when the person has moved on: the store is page-lived, and the answer belongs to `name`.
|
|
167
169
|
for (const m of r.messages) liveReplies.message(m);
|
|
168
170
|
s.msgs[name] = R().mergeMessages(s.msgs[name], r.messages, 500);
|
|
171
|
+
settleOutgoing(name);
|
|
169
172
|
changed(name, "msgs");
|
|
170
173
|
} catch { s.historyRead.delete(name); /* the live stream still works; the next visit tries again */ }
|
|
171
174
|
void lease;
|
|
@@ -242,71 +245,186 @@ export function createChatStore(deps) {
|
|
|
242
245
|
}
|
|
243
246
|
const promptsFor = (instance) => Object.values(s.prompts).filter(p => p.instance === instance);
|
|
244
247
|
|
|
245
|
-
// ── Sending
|
|
246
|
-
//
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
248
|
+
// ── Sending (Discord-like, the user's request): a message shows at once as its own bubble, dimmed while it goes; its
|
|
249
|
+
// files show their upload progress. The server's copy replaces it when it arrives — matched by the page's clientId
|
|
250
|
+
// (on the live copy) or the messageId the send answered with — so it is never shown twice. A send that fails stays,
|
|
251
|
+
// marked, with Retry and Delete: nothing is lost, and nothing is put back into the composer behind the person's back.
|
|
252
|
+
// Sends of one chat go out in order, one at a time; a later one waits behind an earlier one still going.
|
|
253
|
+
let outSeq = 0;
|
|
254
|
+
const objectUrl = (f) => { try { return globalThis.URL && typeof URL.createObjectURL === "function" && /^image\/(png|jpeg|gif|webp)$/.test(f.type || "") ? URL.createObjectURL(f) : null; } catch { return null; } };
|
|
255
|
+
const freeUrl = (u) => { try { if (u) URL.revokeObjectURL(u); } catch { /* gone */ } };
|
|
256
|
+
const newClientId = () => {
|
|
257
|
+
const bytes = new Uint8Array(12);
|
|
258
|
+
try { globalThis.crypto.getRandomValues(bytes); } catch { for (let i = 0; i < bytes.length; i++) bytes[i] = Math.floor(Math.random() * 256); }
|
|
259
|
+
return "c" + [...bytes].map((x) => x.toString(16).padStart(2, "0")).join("");
|
|
260
|
+
};
|
|
261
|
+
/** Pending bubbles the server's copy now stands for go (their previews freed). Returns whether any went. */
|
|
262
|
+
function settleOutgoing(name, clientId) {
|
|
263
|
+
const list = s.outgoing[name];
|
|
264
|
+
if (!list || !list.length) return false;
|
|
265
|
+
const ids = new Set((s.msgs[name] || []).map((m) => m.messageId).filter(Boolean)), clients = new Set(clientId ? [clientId] : []);
|
|
266
|
+
const kept = list.filter((e) => !((e.messageId && ids.has(e.messageId)) || clients.has(e.clientId)));
|
|
267
|
+
if (kept.length === list.length) return false;
|
|
268
|
+
for (const e of list) if (!kept.includes(e)) for (const f of e.files) freeUrl(f.url);
|
|
269
|
+
s.outgoing[name] = kept;
|
|
270
|
+
return true;
|
|
271
|
+
}
|
|
272
|
+
/** The thread as drawn: the server's messages, then this page's pending ones. */
|
|
273
|
+
function threadRows(name) {
|
|
274
|
+
const out = s.outgoing[name];
|
|
275
|
+
return out && out.length ? (s.msgs[name] || []).concat(out) : (s.msgs[name] || []);
|
|
276
|
+
}
|
|
277
|
+
/** Upload one file, its progress told as it goes (XHR: fetch cannot report an upload's progress). */
|
|
278
|
+
async function uploadFile(target, file, onProgress) {
|
|
279
|
+
const url = `/ui/upload?instance=${encodeURIComponent(target)}`;
|
|
280
|
+
const X = deps.XMLHttpRequest;
|
|
281
|
+
if (!X) { // no XHR given (a test): the whole file at once
|
|
282
|
+
const r = await deps.fetch(url, { method: "POST", headers: { "Content-Type": file.type || "application/octet-stream", "X-Agend-Filename": encodeURIComponent(file.name || "file") }, body: file });
|
|
283
|
+
let body = {};
|
|
284
|
+
try { body = await r.json(); } catch { /* not JSON */ }
|
|
285
|
+
if (!r.ok || !body || typeof body.id !== "string") throw new Error(body && body.error ? `${file.name}: ${body.error}` : `${file.name}: upload failed (${r.status})`);
|
|
286
|
+
onProgress?.(file.size || 0, file.size || 0);
|
|
287
|
+
return body;
|
|
288
|
+
}
|
|
289
|
+
// The CSRF header the page's fetch adds (agend-auth.js), added here too; a 401 ends the session as fetch's would.
|
|
290
|
+
const csrf = globalThis.AgendAuth && typeof globalThis.AgendAuth.csrf === "function" ? await globalThis.AgendAuth.csrf().catch(() => null) : null;
|
|
291
|
+
return new Promise((resolve, reject) => {
|
|
292
|
+
const xhr = new X();
|
|
293
|
+
xhr.open("POST", url);
|
|
294
|
+
xhr.setRequestHeader("Content-Type", file.type || "application/octet-stream");
|
|
295
|
+
xhr.setRequestHeader("X-Agend-Filename", encodeURIComponent(file.name || "file"));
|
|
296
|
+
if (csrf) xhr.setRequestHeader("X-Agend-CSRF", csrf);
|
|
297
|
+
if (xhr.upload && onProgress) xhr.upload.onprogress = (ev) => { if (ev.lengthComputable) onProgress(ev.loaded, ev.total); };
|
|
298
|
+
xhr.onload = () => {
|
|
299
|
+
let body = {};
|
|
300
|
+
try { body = JSON.parse(xhr.responseText || "{}"); } catch { /* not JSON */ }
|
|
301
|
+
if (xhr.status === 401) globalThis.AgendAuth?.unauthorized?.();
|
|
302
|
+
if (xhr.status >= 200 && xhr.status < 300 && body && typeof body.id === "string") resolve(body);
|
|
303
|
+
else reject(new Error(body && body.error ? `${file.name}: ${body.error}` : `${file.name}: upload failed (${xhr.status})`));
|
|
304
|
+
};
|
|
305
|
+
xhr.onerror = () => reject(new Error(`${file.name}: ${t("chat.uploadNoConnection")}`));
|
|
306
|
+
xhr.onabort = () => reject(new Error(`${file.name}: ${t("chat.uploadNoConnection")}`));
|
|
307
|
+
xhr.send(file);
|
|
252
308
|
});
|
|
253
|
-
let body = {};
|
|
254
|
-
try { body = await r.json(); } catch { /* not JSON */ }
|
|
255
|
-
if (!r.ok || !body || typeof body.id !== "string") throw new Error(body && body.error ? `${file.name}: ${body.error}` : `${file.name}: upload failed (${r.status})`);
|
|
256
|
-
return body;
|
|
257
309
|
}
|
|
258
|
-
|
|
310
|
+
/** Send what the composer holds: its bubble at once, then out in this chat's order. */
|
|
311
|
+
function send(target) {
|
|
259
312
|
const txt = (s.drafts[target] || "").trim();
|
|
260
|
-
const quote = s.replies[target]; // #2272: the message this one replies to (its row; the server reads the rest)
|
|
261
313
|
const files = (s.pendingFiles[target] || []).slice();
|
|
262
|
-
if ((!txt && !files.length) || !target
|
|
263
|
-
s.
|
|
264
|
-
|
|
265
|
-
|
|
314
|
+
if ((!txt && !files.length) || !target) return;
|
|
315
|
+
const quote = s.replies[target];
|
|
316
|
+
const e = {
|
|
317
|
+
// "You" until the server's copy (with the person's name) replaces it: no request is made for a name.
|
|
318
|
+
boot: "out", id: ++outSeq, instance: target, role: "user", sender: t("chat.you"), text: txt,
|
|
319
|
+
ts: new Date().toISOString(), clientId: newClientId(), replyTo: quote ? { boot: quote.boot, id: quote.id, sender: quote.sender, text: quote.text, media: quote.media } : undefined,
|
|
320
|
+
quote, outgoing: { state: "queued" },
|
|
321
|
+
files: files.map((f) => ({ file: f, name: f.name || "file", size: f.size || 0, type: f.type || "", url: objectUrl(f), loaded: 0, total: f.size || 0, state: "waiting", id: null })),
|
|
322
|
+
};
|
|
323
|
+
(s.outgoing[target] = s.outgoing[target] || []).push(e);
|
|
266
324
|
s.drafts[target] = "";
|
|
267
|
-
s.pendingFiles[target] = [];
|
|
325
|
+
s.pendingFiles[target] = [];
|
|
326
|
+
if (quote) delete s.replies[target];
|
|
327
|
+
changed(target, "msgs");
|
|
268
328
|
changed(target);
|
|
269
329
|
changed(target, "sent"); // your own message: the thread goes to the bottom
|
|
330
|
+
return pump(target);
|
|
331
|
+
}
|
|
332
|
+
/** Take this chat's waiting sends, one after another. */
|
|
333
|
+
async function pump(target) {
|
|
334
|
+
if (s.sending[target]) return;
|
|
335
|
+
s.sending[target] = true;
|
|
336
|
+
try {
|
|
337
|
+
for (let e = nextQueued(target); e; e = nextQueued(target)) await deliver(target, e);
|
|
338
|
+
} finally { s.sending[target] = false; delete s.waking[target]; changed(target); }
|
|
339
|
+
}
|
|
340
|
+
const nextQueued = (target) => (s.outgoing[target] || []).find((x) => x.outgoing.state === "queued");
|
|
341
|
+
/** One send: its files (each with its own progress), then the message. */
|
|
342
|
+
async function deliver(target, e) {
|
|
343
|
+
e.outgoing = { state: "sending" };
|
|
344
|
+
s.waking[target] = s.instanceStatus[target] === "paused";
|
|
345
|
+
changed(target, "msgs"); changed(target);
|
|
270
346
|
let r;
|
|
271
347
|
try {
|
|
272
|
-
const
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
348
|
+
for (const f of e.files) {
|
|
349
|
+
if (f.id) continue; // uploaded by an earlier try
|
|
350
|
+
f.state = "uploading"; f.loaded = 0; changed(target, "progress");
|
|
351
|
+
let last = 0;
|
|
352
|
+
const body = await uploadFile(target, f.file, (loaded, total) => {
|
|
353
|
+
f.loaded = loaded; f.total = total || f.total;
|
|
354
|
+
const at = deps.now ? deps.now() : Date.now();
|
|
355
|
+
if (at - last > 60 || loaded >= f.total) { last = at; changed(target, "progress"); }
|
|
356
|
+
});
|
|
357
|
+
f.id = body.id; f.state = "done"; f.loaded = f.total; changed(target, "progress");
|
|
358
|
+
}
|
|
359
|
+
const body = { instance: target, message: e.text, clientId: e.clientId };
|
|
360
|
+
const ids = e.files.map((f) => f.id);
|
|
361
|
+
if (ids.length) body.attachments = ids;
|
|
362
|
+
if (e.quote) body.replyTo = { boot: e.quote.boot, id: e.quote.id };
|
|
276
363
|
r = await api("POST", "/ui/send", body);
|
|
277
|
-
} catch (err) {
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
if (r && r.reason === "reply_gone" && s.replies[target] === quote) delete s.replies[target];
|
|
284
|
-
if (files.length) s.pendingFiles[target] = R().checkFiles(files, s.pendingFiles[target] || []).kept;
|
|
285
|
-
if (txt) {
|
|
286
|
-
const shown = s.current === target;
|
|
287
|
-
if (R().settleFailedSend(target, s.current, shown, shown ? (s.drafts[target] || "") : "") === "restore") s.drafts[target] = txt;
|
|
288
|
-
else s.failedSends[target] = s.failedSends[target] ? `${s.failedSends[target]}\n${txt}` : txt;
|
|
364
|
+
} catch (err) {
|
|
365
|
+
const f = e.files.find((x) => x.state === "uploading");
|
|
366
|
+
if (f) f.state = "failed";
|
|
367
|
+
// The browser's own words for a request that never reached the server (Chrome, Firefox, Safari): the page's.
|
|
368
|
+
const offline = err && err.name === "TypeError" && /failed to fetch|networkerror|load failed/i.test(err.message || "");
|
|
369
|
+
r = { error: offline ? t("chat.sendNoConnection") : err && err.message ? err.message : t("chat.disconnected") };
|
|
289
370
|
}
|
|
371
|
+
if (!(s.outgoing[target] || []).includes(e)) return; // deleted meanwhile
|
|
372
|
+
if (r && !r.error && r.sent !== false) {
|
|
373
|
+
e.outgoing = { state: "sent" };
|
|
374
|
+
if (typeof r.messageId === "string") e.messageId = r.messageId;
|
|
375
|
+
settleOutgoing(target);
|
|
376
|
+
changed(target, "msgs"); changed(target);
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
// Files the server no longer holds (they are released when a delivery fails) are uploaded again on Retry.
|
|
380
|
+
if (r && typeof r.error === "string" && /files? (are|is) not available|no such file|already sent/i.test(r.error)) for (const f of e.files) { f.id = null; f.state = "waiting"; }
|
|
381
|
+
// #2272: the quoted message is gone: Retry sends it without the quote.
|
|
382
|
+
if (r && r.reason === "reply_gone") { e.quote = undefined; e.replyTo = undefined; }
|
|
290
383
|
const why = r && r.reason;
|
|
291
384
|
const key = typeof why === "string" ? `chat.sendFailure_${why}` : "";
|
|
292
385
|
const localized = key ? t(key) : "";
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
386
|
+
e.outgoing = { state: "failed", error: localized && localized !== key ? localized : (r && r.error ? r.error : t("chat.disconnected")) };
|
|
387
|
+
changed(target, "msgs"); changed(target);
|
|
388
|
+
deps.toast(e.outgoing.error, false);
|
|
389
|
+
}
|
|
390
|
+
/** A failed send, sent again (the same bubble; files already uploaded are not uploaded again). */
|
|
391
|
+
function retryOutgoing(target, clientId) {
|
|
392
|
+
const e = (s.outgoing[target] || []).find((x) => x.clientId === clientId);
|
|
393
|
+
if (!e || e.outgoing.state !== "failed") return;
|
|
394
|
+
e.outgoing = { state: "queued" };
|
|
395
|
+
for (const f of e.files) if (f.state === "failed") f.state = "waiting";
|
|
396
|
+
changed(target, "msgs");
|
|
397
|
+
return pump(target);
|
|
398
|
+
}
|
|
399
|
+
/** A failed send, let go (its previews freed). */
|
|
400
|
+
function deleteOutgoing(target, clientId) {
|
|
401
|
+
const list = s.outgoing[target] || [];
|
|
402
|
+
const e = list.find((x) => x.clientId === clientId);
|
|
403
|
+
if (!e || e.outgoing.state !== "failed") return;
|
|
404
|
+
for (const f of e.files) freeUrl(f.url);
|
|
405
|
+
s.outgoing[target] = list.filter((x) => x !== e);
|
|
406
|
+
changed(target, "msgs"); changed(target);
|
|
297
407
|
}
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
408
|
+
/**
|
|
409
|
+
* A failed send, back in the composer to change it: its text before what is typed there now, its files (within the
|
|
410
|
+
* limits) and its reply, if none is started; the bubble goes. The page records the composer's undo step first.
|
|
411
|
+
*/
|
|
412
|
+
function putBackOutgoing(target, clientId) {
|
|
413
|
+
const list = s.outgoing[target] || [];
|
|
414
|
+
const e = list.find((x) => x.clientId === clientId);
|
|
415
|
+
if (!e || e.outgoing.state !== "failed") return;
|
|
416
|
+
const draft = s.drafts[target] || "";
|
|
417
|
+
s.drafts[target] = e.text ? (draft ? e.text + "\n" + draft : e.text) : draft;
|
|
418
|
+
for (const f of e.files) freeUrl(f.url);
|
|
419
|
+
s.outgoing[target] = list.filter((x) => x !== e);
|
|
420
|
+
if (e.quote && !s.replies[target]) s.replies[target] = e.quote;
|
|
421
|
+
changed(target, "msgs");
|
|
422
|
+
if (e.files.length) addFiles(target, e.files.map((f) => f.file)); else changed(target);
|
|
303
423
|
}
|
|
304
|
-
function retrySend(target) { if (canRetrySend(target)) return send(target); }
|
|
305
424
|
function addFiles(target, list) {
|
|
306
425
|
if (!target) return;
|
|
307
|
-
const
|
|
308
|
-
|
|
309
|
-
s.pendingFiles[target] = res.kept.slice(inFlight.length);
|
|
426
|
+
const res = R().checkFiles(s.pendingFiles[target] || [], [...(list || [])]);
|
|
427
|
+
s.pendingFiles[target] = res.kept;
|
|
310
428
|
for (const x of res.rejected) { const why = R().fileRejection(x.reason); deps.toast(`${x.name}: ${t(why.key, ...why.values)}`, false); }
|
|
311
429
|
changed(target);
|
|
312
430
|
}
|
|
@@ -316,7 +434,7 @@ export function createChatStore(deps) {
|
|
|
316
434
|
if (!target) return false;
|
|
317
435
|
const stamp = new Date().toISOString().slice(0, 19).replace(/[-:T]/g, "");
|
|
318
436
|
const f = new File([text], `pasted-${stamp}.txt`, { type: "text/plain" });
|
|
319
|
-
const taken =
|
|
437
|
+
const taken = s.pendingFiles[target] || [];
|
|
320
438
|
if (R().checkFiles(taken, [f]).rejected.length) { deps.toast(t("chat.pasteKeptAsText")); return false; }
|
|
321
439
|
pastedTexts.set(f, text);
|
|
322
440
|
addFiles(target, [f]);
|
|
@@ -333,16 +451,8 @@ export function createChatStore(deps) {
|
|
|
333
451
|
changed(target);
|
|
334
452
|
}
|
|
335
453
|
const isPasted = (f) => pastedTexts.has(f);
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
if (!kept) return;
|
|
339
|
-
s.drafts[target] = R().putBack(kept, s.drafts[target] || "");
|
|
340
|
-
delete s.failedSends[target];
|
|
341
|
-
changed(target);
|
|
342
|
-
}
|
|
343
|
-
function discardFailed(target) { delete s.failedSends[target]; changed(target); }
|
|
344
|
-
/** Typing stays local, except a failed send's Retry button must follow edits too. */
|
|
345
|
-
function setDraft(target, value) { s.drafts[target] = value; if (s.sendFailures[target]) changed(target); }
|
|
454
|
+
/** Typing stays local. */
|
|
455
|
+
function setDraft(target, value) { s.drafts[target] = value; }
|
|
346
456
|
/**
|
|
347
457
|
* #2272: reply to a message of this chat — what the bar above the composer shows (its sender and first words); only
|
|
348
458
|
* its row is sent. One at a time per chat: a new one replaces it.
|
|
@@ -363,7 +473,7 @@ export function createChatStore(deps) {
|
|
|
363
473
|
if (has()) return true;
|
|
364
474
|
try {
|
|
365
475
|
const r = await api("GET", `/ui/history?instance=${encodeURIComponent(name)}&limit=500`);
|
|
366
|
-
if (r && Array.isArray(r.messages)) { s.msgs[name] = R().mergeMessages(s.msgs[name], r.messages, 500); changed(name, "msgs"); }
|
|
476
|
+
if (r && Array.isArray(r.messages)) { s.msgs[name] = R().mergeMessages(s.msgs[name], r.messages, 500); settleOutgoing(name); changed(name, "msgs"); }
|
|
367
477
|
} catch { /* the jump says it is not there */ }
|
|
368
478
|
return has();
|
|
369
479
|
}
|
|
@@ -461,7 +571,7 @@ export function createChatStore(deps) {
|
|
|
461
571
|
attach, applyStatus, applyActivity, ingest, applyDeliveries, openHistory,
|
|
462
572
|
onPrompt, applyPrompts, resolvePrompt, answerPrompt, answerByNonce, holdPrompt, releasePrompt, promptsFor,
|
|
463
573
|
applyReplyButtons, clickReplyButton,
|
|
464
|
-
uploadFile, send,
|
|
574
|
+
uploadFile, send, retryOutgoing, deleteOutgoing, putBackOutgoing, threadRows, addFiles, removeFile, attachPastedText, fileBackAsText, isPasted, setDraft,
|
|
465
575
|
startReply, clearReply, findQuoted,
|
|
466
576
|
cancelReply, isUser, runCommand, dismissCommand, commandFree,
|
|
467
577
|
};
|
package/dist/ui/chat-strings.js
CHANGED
|
@@ -4,6 +4,9 @@ import { register } from "/assets/app-i18n.js";
|
|
|
4
4
|
register("chat", {
|
|
5
5
|
en: {
|
|
6
6
|
waking: "Waking {0}…", retrySend: "Retry send",
|
|
7
|
+
// A message being sent (its own bubble at once) and its files' uploads.
|
|
8
|
+
you: "You", sending: "Sending…", uploadingFile: "Uploading {0}", uploadWaiting: "waiting", uploadFailed: "failed",
|
|
9
|
+
uploadNoConnection: "the upload was cut off — check the connection", sendNoConnection: "it did not reach AgEnD — check the connection", notSentBecause: "Not sent: {0}", deleteUnsent: "Delete",
|
|
7
10
|
sendFailure_wake_capacity: "No slot is free to wake this instance. Retry when another instance is idle.",
|
|
8
11
|
sendFailure_wake_credentials: "The instance could not sign in. Check its account on the host, then retry.",
|
|
9
12
|
sendFailure_wake_timeout: "The instance did not become ready in time. Retry, or check its status on the host.",
|
|
@@ -53,7 +56,7 @@ register("chat", {
|
|
|
53
56
|
a11yWorking: "{0} started working", a11yIdle: "{0} finished", a11yReplied: "{0} replied", conversationWith: "Conversation with {0}",
|
|
54
57
|
stopping: "Stopping {0}…", awaiting: "{0} is waiting for your input", approxNote: "Read from the terminal, so approximate", elapsedTitle: "Time since this page saw it start (approximate)",
|
|
55
58
|
dropHere: "Drop files to attach them", asText: "As text", pasteAttached: "A long paste ({0} characters) was attached as a text file", pasteKeptAsText: "No room for another attachment: the long paste stays as text",
|
|
56
|
-
send: "Send", composerPlaceholder: "Message {0}…", composerHint: "Enter to send · Shift+Enter for a new line", composerMore: "/ for commands · Esc stops the reply", guestMore: "Esc stops the reply",
|
|
59
|
+
send: "Send", composerPlaceholder: "Message {0}…", composerHint: "Enter to send · Shift+Enter for a new line", composerHintTouch: "Return adds a line · tap Send to send", composerMore: "/ for commands · Esc stops the reply", guestMore: "Esc stops the reply",
|
|
57
60
|
guestNote: "You're a guest: the agent sees your messages as a guest's (recognition only).", guestNoCommands: "Guests can't run commands — send it without the leading /.", attachTitle: "Attach files (any type — or paste / drop them)",
|
|
58
61
|
pvPreview: "Preview", pvStop: "Stop", pvDownload: "Download", pvMenu: "Preview options", pvHtml: "HTML",
|
|
59
62
|
pvAllowButton: "Allow previews", pvAllow: "Allow HTML previews — they may be able to send data out", pvDisallow: "Stop allowing HTML previews",
|
|
@@ -110,7 +113,7 @@ register("chat", {
|
|
|
110
113
|
disconnected: "disconnected", tick_queued: "Waiting behind another message", tick_processing: "Handed to the agent", tick_delivered: "The agent has it", tick_failed: "Not delivered", tick_cancelled: "Dropped by Stop", reactedBy: "{0} reacted",
|
|
111
114
|
promptAnswering: "Answering…", promptGone: "This prompt is no longer open", promptFor: "{0}: {1}",
|
|
112
115
|
working: "{0} is working…", stuck: "{0} looks stuck", stopReply: "Stop reply", stopReplyTitle: "Stop the current reply (like Esc) and drop the messages still waiting. The instance keeps running.", stopSent: "Stopped",
|
|
113
|
-
file_empty: "empty file", file_too_large: "over {0} MB", file_too_large_public: "over {0} MB — the most the public link carries in one upload", file_too_many: "at most {0} files per message", file_too_much: "the files together would be over {0} MB", removeFile: "Remove",
|
|
116
|
+
file_empty: "empty file", file_too_large: "over {0} MB", file_too_large_public: "over {0} MB — the most the public link carries in one upload", file_too_many: "at most {0} files per message", file_too_much: "the files together would be over {0} MB", removeFile: "Remove", putBack: "Put back",
|
|
114
117
|
noMessages: "Type a task below.",
|
|
115
118
|
pick: "Choose an agent to start chatting.", noAgents: "Create an agent to start chatting.", newAgent: "New agent", notFound: "No instance called “{0}”", notFoundHint: "It may have been deleted or renamed.", backToList: "Choose an agent",
|
|
116
119
|
details: "Instance details", start: "Start instance", stopInstance: "Stop instance", restart: "Restart instance", delete: "Delete instance…",
|
|
@@ -143,6 +146,8 @@ register("chat", {
|
|
|
143
146
|
},
|
|
144
147
|
"zh-TW": {
|
|
145
148
|
waking: "正在喚醒 {0}…", retrySend: "重試傳送",
|
|
149
|
+
you: "你", sending: "傳送中…", uploadingFile: "正在上傳 {0}", uploadWaiting: "等待中", uploadFailed: "失敗",
|
|
150
|
+
uploadNoConnection: "上傳中斷了,請檢查網路連線", sendNoConnection: "沒有送到 AgEnD,請檢查網路連線", notSentBecause: "沒有送出:{0}", deleteUnsent: "刪除",
|
|
146
151
|
sendFailure_wake_capacity: "目前沒有空位可喚醒這個實例。請等其他實例閒置後重試。",
|
|
147
152
|
sendFailure_wake_credentials: "實例無法登入。請先在主機檢查它的帳號,再重試。",
|
|
148
153
|
sendFailure_wake_timeout: "實例未能及時就緒。請重試,或在主機檢查它的狀態。",
|
|
@@ -192,7 +197,7 @@ register("chat", {
|
|
|
192
197
|
a11yWorking: "{0} 開始工作", a11yIdle: "{0} 已完成", a11yReplied: "{0} 已回覆", conversationWith: "與 {0} 的對話",
|
|
193
198
|
stopping: "正在中止 {0}…", awaiting: "{0} 正在等你回應", approxNote: "從終端畫面判讀,僅供參考", elapsedTitle: "從這個頁面看到它開始工作起算(約略)",
|
|
194
199
|
dropHere: "放開即可附加檔案", asText: "改為文字", pasteAttached: "貼上的長文字({0} 個字元)已改為文字檔附加", pasteKeptAsText: "附件已滿,貼上的長文字保留為一般文字",
|
|
195
|
-
send: "送出", composerPlaceholder: "傳訊息給 {0}…", composerHint: "Enter 送出 · Shift+Enter 換行", composerMore: "/ 叫出指令 · Esc 中止回覆", guestMore: "Esc 中止回覆",
|
|
200
|
+
send: "送出", composerPlaceholder: "傳訊息給 {0}…", composerHint: "Enter 送出 · Shift+Enter 換行", composerHintTouch: "Return 換行 · 按「送出」傳送", composerMore: "/ 叫出指令 · Esc 中止回覆", guestMore: "Esc 中止回覆",
|
|
196
201
|
guestNote: "你是訪客:agent 會把你的訊息視為訪客的訊息(僅供辨識)。", guestNoCommands: "訪客不能執行指令,請去掉開頭的 / 再送出。", attachTitle: "附加檔案(任何類型,也可以貼上或拖曳)",
|
|
197
202
|
pvPreview: "預覽", pvStop: "停止", pvDownload: "下載", pvMenu: "預覽選項", pvHtml: "HTML",
|
|
198
203
|
pvAllowButton: "允許預覽", pvAllow: "允許預覽 HTML(預覽可能把資料傳出去)", pvDisallow: "不再允許預覽 HTML",
|
|
@@ -247,7 +252,7 @@ register("chat", {
|
|
|
247
252
|
disconnected: "已斷線", tick_queued: "排在前一則訊息之後", tick_processing: "已交給 agent", tick_delivered: "agent 已收到", tick_failed: "未送達", tick_cancelled: "已被中止取消", reactedBy: "{0} 的回應",
|
|
248
253
|
promptAnswering: "回覆中…", promptGone: "這個提示已經結束", promptFor: "{0}:{1}",
|
|
249
254
|
working: "{0} 處理中…", stuck: "{0} 似乎卡住了", stopReply: "中止回覆", stopReplyTitle: "中止目前的回覆(同 Esc),並取消還在排隊的訊息。instance 會繼續運行。", stopSent: "已中止",
|
|
250
|
-
file_empty: "空檔案", file_too_large: "超過 {0} MB", file_too_large_public: "超過 {0} MB,這是公開連結單次上傳的上限", file_too_many: "每則訊息最多 {0} 個檔案", file_too_much: "檔案合計會超過 {0} MB", removeFile: "移除",
|
|
255
|
+
file_empty: "空檔案", file_too_large: "超過 {0} MB", file_too_large_public: "超過 {0} MB,這是公開連結單次上傳的上限", file_too_many: "每則訊息最多 {0} 個檔案", file_too_much: "檔案合計會超過 {0} MB", removeFile: "移除", putBack: "放回輸入框",
|
|
251
256
|
noMessages: "直接在下方輸入任務。",
|
|
252
257
|
pick: "選一個 Agent 開始聊天。", noAgents: "先建立一個 Agent,再開始聊天。", newAgent: "新增 Agent", notFound: "沒有叫「{0}」的 instance", notFoundHint: "它可能已被刪除或改名。", backToList: "選擇 Agent",
|
|
253
258
|
details: "Instance 詳細資料", start: "啟動 instance", stopInstance: "停止 instance", restart: "重新啟動 instance", delete: "刪除 instance…",
|
package/dist/ui/chat-thread.js
CHANGED
|
@@ -132,6 +132,7 @@ export function createThread(list, scroller, opts) {
|
|
|
132
132
|
// #1770: the label ("17:38", "Yesterday 17:38", "10/11 17:38") depends on today, so it is not part of the html a node
|
|
133
133
|
// is kept by: it is written into the node (timeLabels) — crossing midnight relabels in place and reloads no preview.
|
|
134
134
|
const time = x.ts ? `<span class="time" data-ts="${escAttr(x.ts)}" title="${escAttr(chatTimeFull(x.ts))}"></span>` : `<span class="time"></span>`;
|
|
135
|
+
if (x.outgoing) return outgoingHtml(x);
|
|
135
136
|
const trial = readCanvasTrialMessage(x);
|
|
136
137
|
if (trial) return canvasWorkerEnabled() ? trialHtml(trial, time, x) : "";
|
|
137
138
|
if (x.role === "status") return statusHtml(x, time);
|
|
@@ -147,6 +148,48 @@ export function createThread(list, scroller, opts) {
|
|
|
147
148
|
const act = opts.canReply?.() === false ? "" : `<div data-trust="thread-control" class="msg-actions"><button type="button" class="chip-btn icon-only" data-act="reply" data-arg="${escAttr(key)}" title="${escAttr(tr("chat.reply"))}" aria-label="${escAttr(tr("chat.reply"))}">${REPLY}</button></div>`;
|
|
148
149
|
return `<div class="msg ${u ? "user" : "agent"}" data-key="${escAttr(key)}" tabindex="-1">${act}<div class="meta"><span class="sender">${escAttr(x.sender)}</span>${time}${ticks}</div>${quoteHtml(x.replyTo)}<div class="body">${clicked ? clickHtml(x.click) : `<div class="md">${renderedMarkdown(x.text, x.role === "agent", opts.emojis, opts.mentions)}</div>`}${R().attachmentsHtml(x.attachments, { htmlCards: x.role === "agent", goneTitle: tr("chat.attGone"), goneLabel: tr("chat.attUnavailable"), inlinePreviews: !PUBLIC_LINK() })}${buttons}</div>${R().reactionsHtml(x.reactions, tr("chat.reactedBy", x.instance || ""))}${tools}</div>`;
|
|
149
150
|
}
|
|
151
|
+
/**
|
|
152
|
+
* A message this page is sending (chat-store.js): its own bubble at once, dimmed with "Sending…" until the server
|
|
153
|
+
* takes it, then as sent until the server's copy replaces it; failed: marked, with Retry and Delete. Each file shows
|
|
154
|
+
* its upload — a picture as itself (a local preview), anything else by name — with its own bar, moved in place by
|
|
155
|
+
* progress() (the html does not hold the numbers, so a moving bar never redraws the bubble). No data-key: it is no
|
|
156
|
+
* message yet (nothing replies to it, links to it or copies it).
|
|
157
|
+
*/
|
|
158
|
+
function outgoingHtml(x) {
|
|
159
|
+
const st = x.outgoing.state, going = st === "queued" || st === "sending";
|
|
160
|
+
const files = (x.files || []).map((f, i) => {
|
|
161
|
+
const pic = f.url ? `<img class="out-pic" src="${escAttr(f.url)}" alt="${escAttr(f.name)}">` : `<span class="out-doc" aria-hidden="true"></span>`;
|
|
162
|
+
return `<div class="out-file" data-i="${escAttr(i)}">${pic}<div class="out-file-info"><span class="out-name">${escAttr(f.name)}</span>`
|
|
163
|
+
+ `<span class="out-bar" role="progressbar" aria-label="${escAttr(trf("chat.uploadingFile", f.name))}" aria-valuemin="0" aria-valuemax="100"><span class="out-fill"></span></span>`
|
|
164
|
+
+ `<span class="out-pct"></span></div></div>`;
|
|
165
|
+
}).join("");
|
|
166
|
+
const failed = st === "failed" ? `<div data-trust="thread-control" class="out-note lbar" role="alert"><span class="out-error">${escAttr(trf("chat.notSentBecause", x.outgoing.error || ""))}</span>`
|
|
167
|
+
+ `<span class="out-actions"><button type="button" class="btn btn-sm btn-primary" data-act="retryOut" data-arg="${escAttr(x.clientId)}">${escAttr(tr("chat.retrySend"))}</button>`
|
|
168
|
+
+ `<button type="button" class="btn btn-sm" data-act="putBackOut" data-arg="${escAttr(x.clientId)}">${escAttr(tr("chat.putBack"))}</button>`
|
|
169
|
+
+ `<button type="button" class="btn btn-sm" data-act="deleteOut" data-arg="${escAttr(x.clientId)}">${escAttr(tr("chat.deleteUnsent"))}</button></span></div>` : "";
|
|
170
|
+
const state = going ? `<span class="out-state" role="status"><span class="out-spin" aria-hidden="true"></span>${escAttr(tr("chat.sending"))}</span>` : "";
|
|
171
|
+
return `<div class="msg user out out-${st}" data-out="${escAttr(x.clientId)}"${going ? ' aria-busy="true"' : ""} tabindex="-1"><div class="meta"><span class="sender">${escAttr(x.sender)}</span>${state}</div>`
|
|
172
|
+
+ `${quoteHtml(x.replyTo)}<div class="body">${x.text ? `<div class="md">${renderedMarkdown(x.text, false, opts.emojis, opts.mentions)}</div>` : ""}`
|
|
173
|
+
+ `${files ? `<div class="out-files">${files}</div>` : ""}</div>${failed}</div>`;
|
|
174
|
+
}
|
|
175
|
+
/** The pending bubbles' upload bars and percentages, as their files stand now (in place: CSSOM, no redraw). */
|
|
176
|
+
function progress() {
|
|
177
|
+
for (const v of nodes.values()) {
|
|
178
|
+
const x = v.x;
|
|
179
|
+
if (!x || !x.outgoing || !x.files || !x.files.length) continue;
|
|
180
|
+
for (const el of v.node.querySelectorAll(".out-file[data-i]")) {
|
|
181
|
+
const f = x.files[Number(el.dataset.i)];
|
|
182
|
+
if (!f) continue;
|
|
183
|
+
const pct = f.state === "done" ? 100 : f.total > 0 ? Math.min(100, Math.floor((f.loaded / f.total) * 100)) : 0;
|
|
184
|
+
el.dataset.state = f.state;
|
|
185
|
+
const bar = el.querySelector(".out-bar"), fill = el.querySelector(".out-fill"), label = el.querySelector(".out-pct");
|
|
186
|
+
if (bar) bar.setAttribute("aria-valuenow", String(pct));
|
|
187
|
+
if (fill) fill.style.transform = `scaleX(${pct / 100})`;
|
|
188
|
+
if (label) { const text = f.state === "failed" ? tr("chat.uploadFailed") : f.state === "waiting" ? tr("chat.uploadWaiting") : `${pct}%`; if (label.textContent !== text) label.textContent = text; }
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
150
193
|
/**
|
|
151
194
|
* #2272: what a reply quotes, above its body — the quoted sender and first words (or that it had files), as the server
|
|
152
195
|
* read them when it was sent; a button that shows that message. A guest that may not see it gets only "an earlier
|
|
@@ -678,6 +721,7 @@ export function createThread(list, scroller, opts) {
|
|
|
678
721
|
armDay();
|
|
679
722
|
armPolls();
|
|
680
723
|
rove();
|
|
724
|
+
progress();
|
|
681
725
|
return added;
|
|
682
726
|
}
|
|
683
727
|
/**
|
|
@@ -777,6 +821,9 @@ export function createThread(list, scroller, opts) {
|
|
|
777
821
|
} else if (act === "showButtons") { const x = msgs.find(y => y.buttons && y.buttons.id === el.dataset.arg); if (x) reveal(msgKey(x)); }
|
|
778
822
|
else if (act === "reply") { const x = msgs.find(y => msgKey(y) === el.dataset.arg); if (x) opts.startReply?.(x); }
|
|
779
823
|
else if (act === "jumpQuote") void jumpTo(el.dataset.arg);
|
|
824
|
+
else if (act === "retryOut") opts.retryOutgoing?.(el.dataset.arg); // a failed send, again
|
|
825
|
+
else if (act === "putBackOut") opts.putBackOutgoing?.(el.dataset.arg); // back in the composer, to change it
|
|
826
|
+
else if (act === "deleteOut") opts.deleteOutgoing?.(el.dataset.arg);
|
|
780
827
|
}
|
|
781
828
|
/**
|
|
782
829
|
* #2272: show the message a reply quotes, flashed — drawn now, or after the chat reads back the longest history the
|
|
@@ -834,5 +881,5 @@ export function createThread(list, scroller, opts) {
|
|
|
834
881
|
armPolls();
|
|
835
882
|
}
|
|
836
883
|
|
|
837
|
-
return { render, onScroll, onClick, jumpLatest, reveal, jumpTo, messageOf, rove, refreshCards, park, attach, dispose, nodes, get unseen() { return unseen; } };
|
|
884
|
+
return { render, progress, onScroll, onClick, jumpLatest, reveal, jumpTo, messageOf, rove, refreshCards, park, attach, dispose, nodes, get unseen() { return unseen; } };
|
|
838
885
|
}
|
|
@@ -10,6 +10,9 @@
|
|
|
10
10
|
*/
|
|
11
11
|
export const OC = { NW: 184, NH: 56, GAP: 28, PAD: 32, GAP_NARROW: 18, PAD_NARROW: 24, HEAD: 34, TEAM_GAP: 72, ROW_GAP: 96, ROW_MAX: 1400, ROW_MAX_NARROW: 460, LW: 232, LH: 68, TOP_Y: 20, BUS: 44, FOLDED: 64, TRUNK: -30 };
|
|
12
12
|
const colsFor = (n, narrow) => (n <= 1 ? 1 : n <= 4 || narrow ? 2 : 3);
|
|
13
|
+
/** A search's needle, and whether a node matches it (its name or nickname): the canvas's Find, the Office view's too. */
|
|
14
|
+
export const needle = (q) => String(q || "").trim().toLowerCase();
|
|
15
|
+
export const hits = (node, q) => !!q && (node.name.toLowerCase().includes(q) || (!!node.i && typeof node.i.display_name === "string" && node.i.display_name.toLowerCase().includes(q)));
|
|
13
16
|
/** The key a team folds by (per device): its name; the instances in no team have one of their own no team name can be. */
|
|
14
17
|
export const REST_KEY = "\u0000rest";
|
|
15
18
|
|