@songsid/agend 2.3.0-beta.24 → 2.3.0-beta.26
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/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.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.js +29 -4
- 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.js +15 -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 +20 -6
- package/dist/config-validator.js.map +1 -1
- package/dist/config.js +11 -3
- 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 +73 -3
- 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.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 +77 -6
- package/dist/fleet-manager.js +488 -147
- 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 +36 -12
- package/dist/general-knowledge/skills/web-dashboard/SKILL.md +45 -14
- package/dist/host-memory.js.map +1 -1
- 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 +22 -0
- 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 +4 -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 +50 -10
- package/dist/scheduler/task-board.js +138 -44
- 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 +6 -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 +6 -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/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.d.ts +4 -4
- package/dist/task-board-summary.js +4 -4
- 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 +26 -2
- package/dist/ui/chat-render.js +1 -15
- package/dist/ui/chat-store.js +171 -61
- package/dist/ui/chat-strings.js +7 -2
- 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 +14 -13
- package/dist/ui/panel-settings.js +30 -12
- package/dist/ui/settings-dialogs.js +15 -6
- package/dist/ui/settings-model.js +40 -3
- package/dist/ui/settings-strings.js +38 -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 +12 -0
- package/dist/ui/shared/app-store.js +1 -0
- package/dist/ui/shared/app-stream.js +2 -1
- package/dist/ui/shared/app.css +132 -3
- 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/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/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 +15 -2
- package/dist/web-api.js +174 -25
- 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/types.d.ts
CHANGED
|
@@ -157,7 +157,10 @@ export interface InstanceConfig {
|
|
|
157
157
|
* default UI; "v3" opts into the experimental next-generation agent.
|
|
158
158
|
*/
|
|
159
159
|
kiro_ui?: "legacy" | "tui" | "v3";
|
|
160
|
-
/**
|
|
160
|
+
/**
|
|
161
|
+
* MCP tool profile (src/tool-permissions.ts): "worker", "coordinator", "standard", "minimal" or "full". Unset: the
|
|
162
|
+
* fleet default's, else "general" for a General, else "worker".
|
|
163
|
+
*/
|
|
161
164
|
tool_set?: string;
|
|
162
165
|
/**
|
|
163
166
|
* Tool-progress detail shown in the channel processing bubble.
|
|
@@ -178,6 +181,14 @@ export interface InstanceConfig {
|
|
|
178
181
|
* reads it at each claim; the daemon only keeps it current.
|
|
179
182
|
*/
|
|
180
183
|
board_cards_at_once?: number;
|
|
184
|
+
/**
|
|
185
|
+
* Which dashboard features this instance may use, on top of its `tool_set` (dashboard-permissions.ts): `appearance`
|
|
186
|
+
* (its own look: theme, pictures, widgets, ambient, effects, mod proposals), `task_board` (the `task` tool) and
|
|
187
|
+
* `web_settings` (Settings → Web appearance for the person). `false` switches one off; unset allows. `defaults.dashboard`
|
|
188
|
+
* sets it for every instance and merges key by key with an instance's own. The fleet reads it at each tool call; the
|
|
189
|
+
* daemon keeps it current, and an agent's tool menu follows from its next start.
|
|
190
|
+
*/
|
|
191
|
+
dashboard?: import("./dashboard-permissions.js").DashboardAccess;
|
|
181
192
|
/**
|
|
182
193
|
* Detect a human-channel turn that ended without a delivered reply and ask
|
|
183
194
|
* the agent to send one bounded recovery reply. Only capable backends use
|
|
@@ -350,7 +361,11 @@ export interface WebConfig {
|
|
|
350
361
|
experimental?: {
|
|
351
362
|
canvas_worker?: boolean;
|
|
352
363
|
};
|
|
353
|
-
/**
|
|
364
|
+
/**
|
|
365
|
+
* Only menu availability; every exposure requires explicit admin consent. `ttl_minutes`: 1..480 for a quick tunnel;
|
|
366
|
+
* with a fixed `hostname` (named tunnel) 1..10080, or 0 = no expiry. `auto_open` (named only, default false): reopen
|
|
367
|
+
* the link after the fleet starts, for the admin who last opened it — no sign-in code is sent by that.
|
|
368
|
+
*/
|
|
354
369
|
public_link?: {
|
|
355
370
|
allow_public?: boolean;
|
|
356
371
|
ttl_minutes?: number;
|
|
@@ -358,6 +373,7 @@ export interface WebConfig {
|
|
|
358
373
|
hostname?: string;
|
|
359
374
|
tunnel_token_env?: string;
|
|
360
375
|
origin_port?: number;
|
|
376
|
+
auto_open?: boolean;
|
|
361
377
|
};
|
|
362
378
|
/** Echo web chat to the owning fleet topic (default true); never ClassicBot rooms. */
|
|
363
379
|
echo_to_channel?: boolean;
|
|
@@ -472,6 +488,12 @@ export interface WebVerifiedAuthorityConfig {
|
|
|
472
488
|
loopback_only?: boolean;
|
|
473
489
|
/** A session's authorization lasts this many hours after it was given, 1–48 (default 48). */
|
|
474
490
|
max_session_hours?: number;
|
|
491
|
+
/**
|
|
492
|
+
* #2453 (§0.2): sessions over the admin's own named tunnel (`web.public_link.hostname`) may be authorized too — off by
|
|
493
|
+
* default. Turning it on is confirmed in a General or on the host (never by the asking session); off applies at once
|
|
494
|
+
* and every receipt from a named-link session reads as none. A quick tunnel never qualifies.
|
|
495
|
+
*/
|
|
496
|
+
named_link?: boolean;
|
|
475
497
|
}
|
|
476
498
|
/** #1920: fleet.yaml `web.guests`. */
|
|
477
499
|
export interface WebGuestsConfig {
|
|
@@ -541,9 +563,11 @@ export interface FleetConfig {
|
|
|
541
563
|
health_port?: number;
|
|
542
564
|
web?: WebConfig;
|
|
543
565
|
/** #1386 "Needs you": the live list in each world's General (default on) and DMs to that world's admins (default off). */
|
|
566
|
+
/** `task_board` (default false, #2425): the task board's cards that wait for the person are Needs-you items too. */
|
|
544
567
|
needs_you?: {
|
|
545
568
|
live_message?: boolean;
|
|
546
569
|
dm?: boolean;
|
|
570
|
+
task_board?: boolean;
|
|
547
571
|
};
|
|
548
572
|
/** Host name used when building URLs handed to users (/dashboard, web terminal). Default "localhost". */
|
|
549
573
|
hostname?: string;
|
package/dist/ui/chat-render.js
CHANGED
|
@@ -516,20 +516,6 @@
|
|
|
516
516
|
return "send";
|
|
517
517
|
}
|
|
518
518
|
|
|
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
519
|
/** The defaults of the upload limits the server enforces (web-upload.ts UPLOAD_LIMITS), for a page that was given none. */
|
|
534
520
|
var FILE_LIMITS = { maxFileBytes: 50 * 1024 * 1024, maxFiles: 5, maxTotalBytes: 100 * 1024 * 1024 };
|
|
535
521
|
/**
|
|
@@ -666,7 +652,7 @@
|
|
|
666
652
|
return {
|
|
667
653
|
renderMarkdown: renderMarkdown, escapeHtml: escapeHtml, mergeMessages: mergeMessages, applyReplyButtons: applyReplyButtons, composerKey: composerKey,
|
|
668
654
|
pollRemaining: pollRemaining,
|
|
669
|
-
|
|
655
|
+
|
|
670
656
|
FILE_LIMITS: FILE_LIMITS, fileLimits: fileLimits, fileRejection: fileRejection, formatSize: formatSize, checkFiles: checkFiles, attachmentsHtml: attachmentsHtml, isHtmlAttachment: isHtmlAttachment,
|
|
671
657
|
nextDeliveryState: nextDeliveryState, applyDelivery: applyDelivery, deliveryHtml: deliveryHtml, isBusy: isBusy,
|
|
672
658
|
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.",
|
|
@@ -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: "實例未能及時就緒。請重試,或在主機檢查它的狀態。",
|
|
@@ -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
|
|