@songsid/agend 2.2.0-beta.1 → 2.2.0-beta.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/README.zh-TW.md +1 -1
- package/dist/agent-cli-instructions.md +16 -9
- package/dist/agent-cli.js +15 -7
- package/dist/agent-cli.js.map +1 -1
- package/dist/agent-endpoint.js +24 -9
- package/dist/agent-endpoint.js.map +1 -1
- package/dist/agent-whats-new.d.ts +47 -0
- package/dist/agent-whats-new.js +147 -0
- package/dist/agent-whats-new.js.map +1 -0
- package/dist/auth-api.d.ts +5 -3
- package/dist/auth-api.js +19 -3
- package/dist/auth-api.js.map +1 -1
- package/dist/backend/antigravity.d.ts +8 -0
- package/dist/backend/antigravity.js +32 -9
- package/dist/backend/antigravity.js.map +1 -1
- package/dist/backend/claude-carry.d.ts +22 -0
- package/dist/backend/claude-carry.js +170 -0
- package/dist/backend/claude-carry.js.map +1 -0
- package/dist/backend/claude-code.d.ts +65 -2
- package/dist/backend/claude-code.js +226 -33
- package/dist/backend/claude-code.js.map +1 -1
- package/dist/backend/codex-metadata.d.ts +33 -0
- package/dist/backend/codex-metadata.js +122 -18
- package/dist/backend/codex-metadata.js.map +1 -1
- package/dist/backend/codex.d.ts +3 -0
- package/dist/backend/codex.js +17 -1
- package/dist/backend/codex.js.map +1 -1
- package/dist/backend/credential-profile.d.ts +40 -3
- package/dist/backend/credential-profile.js +113 -5
- package/dist/backend/credential-profile.js.map +1 -1
- package/dist/backend/effort-metadata.d.ts +37 -1
- package/dist/backend/effort-metadata.js +86 -15
- package/dist/backend/effort-metadata.js.map +1 -1
- package/dist/backend/grok.d.ts +4 -0
- package/dist/backend/grok.js +14 -0
- package/dist/backend/grok.js.map +1 -1
- package/dist/backend/kiro-engine-ledger.d.ts +5 -0
- package/dist/backend/kiro-engine-ledger.js +20 -0
- package/dist/backend/kiro-engine-ledger.js.map +1 -1
- package/dist/backend/kiro-identity.d.ts +6 -0
- package/dist/backend/kiro-identity.js +19 -5
- package/dist/backend/kiro-identity.js.map +1 -1
- package/dist/backend/kiro.d.ts +39 -3
- package/dist/backend/kiro.js +143 -19
- package/dist/backend/kiro.js.map +1 -1
- package/dist/backend/launch-effort.d.ts +61 -0
- package/dist/backend/launch-effort.js +170 -0
- package/dist/backend/launch-effort.js.map +1 -0
- package/dist/backend/muse.d.ts +84 -0
- package/dist/backend/muse.js +449 -53
- package/dist/backend/muse.js.map +1 -1
- package/dist/backend/types.d.ts +47 -4
- package/dist/backend/types.js +12 -1
- package/dist/backend/types.js.map +1 -1
- package/dist/backend-command.d.ts +91 -0
- package/dist/backend-command.js +122 -0
- package/dist/backend-command.js.map +1 -0
- package/dist/background-work-ended.d.ts +18 -0
- package/dist/background-work-ended.js +30 -0
- package/dist/background-work-ended.js.map +1 -0
- package/dist/cache-service.d.ts +2 -0
- package/dist/cache-service.js +2 -1
- package/dist/cache-service.js.map +1 -1
- package/dist/channel/adapters/discord.d.ts +36 -1
- package/dist/channel/adapters/discord.js +126 -4
- package/dist/channel/adapters/discord.js.map +1 -1
- package/dist/channel/adapters/telegram.d.ts +42 -2
- package/dist/channel/adapters/telegram.js +142 -23
- package/dist/channel/adapters/telegram.js.map +1 -1
- package/dist/channel/mcp-server.js +9 -1
- package/dist/channel/mcp-server.js.map +1 -1
- package/dist/channel/mcp-tools.d.ts +11 -0
- package/dist/channel/mcp-tools.js +26 -13
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/channel/message-edit-errors.d.ts +5 -0
- package/dist/channel/message-edit-errors.js +24 -0
- package/dist/channel/message-edit-errors.js.map +1 -0
- package/dist/channel/telegram-markdown.d.ts +6 -0
- package/dist/channel/telegram-markdown.js +104 -0
- package/dist/channel/telegram-markdown.js.map +1 -0
- package/dist/channel/tool-router.d.ts +2 -0
- package/dist/channel/tool-router.js +10 -4
- package/dist/channel/tool-router.js.map +1 -1
- package/dist/channel/types.d.ts +55 -3
- package/dist/cli.js +167 -39
- package/dist/cli.js.map +1 -1
- package/dist/command-table.d.ts +2 -2
- package/dist/command-table.js +6 -3
- package/dist/command-table.js.map +1 -1
- package/dist/config-validator.js +59 -0
- package/dist/config-validator.js.map +1 -1
- package/dist/cross-instance-envelope.d.ts +15 -2
- package/dist/cross-instance-envelope.js +29 -5
- package/dist/cross-instance-envelope.js.map +1 -1
- package/dist/daemon.d.ts +156 -1
- package/dist/daemon.js +773 -89
- package/dist/daemon.js.map +1 -1
- package/dist/delivery-outbox.d.ts +70 -8
- package/dist/delivery-outbox.js +315 -24
- package/dist/delivery-outbox.js.map +1 -1
- package/dist/delivery-reconciliation.d.ts +18 -1
- package/dist/delivery-reconciliation.js +67 -2
- package/dist/delivery-reconciliation.js.map +1 -1
- package/dist/fleet-context.d.ts +10 -1
- package/dist/fleet-manager.d.ts +404 -13
- package/dist/fleet-manager.js +2039 -210
- package/dist/fleet-manager.js.map +1 -1
- package/dist/general-knowledge/skills/backend-providers/SKILL.md +1 -1
- package/dist/general-knowledge/skills/credential-profiles/SKILL.md +92 -21
- package/dist/general-knowledge/skills/cross-instance-messaging/SKILL.md +27 -3
- package/dist/general-knowledge/skills/delegation-playbook/SKILL.md +8 -8
- package/dist/general-knowledge/skills/fleet-config/SKILL.md +5 -5
- package/dist/general-knowledge/skills/fleet-health/SKILL.md +4 -4
- package/dist/general-knowledge/skills/fleet-restart/SKILL.md +13 -13
- package/dist/general-knowledge/skills/instance-lifecycle/SKILL.md +3 -2
- package/dist/general-knowledge/skills/kiro-engine-migration/SKILL.md +6 -0
- package/dist/general-knowledge/skills/model-discovery/SKILL.md +12 -9
- package/dist/general-knowledge/skills/multi-channel/SKILL.md +8 -2
- package/dist/general-knowledge/skills/scheduling/SKILL.md +5 -3
- package/dist/general-knowledge/skills/session-management/SKILL.md +6 -5
- package/dist/general-knowledge/skills/tui-effort/SKILL.md +2 -2
- package/dist/general-knowledge/skills/worker-collaboration/SKILL.md +8 -3
- package/dist/general-knowledge/steering/core-rules.md +8 -7
- package/dist/host-memory.d.ts +10 -0
- package/dist/host-memory.js +33 -1
- package/dist/host-memory.js.map +1 -1
- package/dist/human-delivery-receipts.d.ts +83 -0
- package/dist/human-delivery-receipts.js +194 -0
- package/dist/human-delivery-receipts.js.map +1 -0
- package/dist/instance-avatar.d.ts +36 -0
- package/dist/instance-avatar.js +57 -0
- package/dist/instance-avatar.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.d.ts +29 -6
- package/dist/instance-lifecycle.js +79 -28
- package/dist/instance-lifecycle.js.map +1 -1
- package/dist/instructions.d.ts +3 -1
- package/dist/instructions.js +11 -8
- package/dist/instructions.js.map +1 -1
- package/dist/kiro-log-gate.d.ts +42 -0
- package/dist/kiro-log-gate.js +39 -0
- package/dist/kiro-log-gate.js.map +1 -0
- package/dist/locale.js +226 -44
- package/dist/locale.js.map +1 -1
- package/dist/log-service.d.ts +99 -0
- package/dist/log-service.js +243 -0
- package/dist/log-service.js.map +1 -0
- package/dist/log-steps.d.ts +169 -0
- package/dist/log-steps.js +858 -0
- package/dist/log-steps.js.map +1 -0
- package/dist/login-controller.d.ts +20 -3
- package/dist/login-controller.js +71 -19
- package/dist/login-controller.js.map +1 -1
- package/dist/login-flows.d.ts +5 -2
- package/dist/login-flows.js +5 -4
- package/dist/login-flows.js.map +1 -1
- package/dist/memory-pressure.d.ts +13 -1
- package/dist/memory-pressure.js +50 -10
- package/dist/memory-pressure.js.map +1 -1
- package/dist/needs-you-hub.js +2 -0
- package/dist/needs-you-hub.js.map +1 -1
- package/dist/needs-you-live.d.ts +6 -0
- package/dist/needs-you-live.js +23 -3
- package/dist/needs-you-live.js.map +1 -1
- package/dist/next-steps.js +1 -1
- package/dist/next-steps.js.map +1 -1
- package/dist/outbound-handlers.d.ts +100 -0
- package/dist/outbound-handlers.js +284 -97
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.d.ts +38 -15
- package/dist/outbound-schemas.js +36 -8
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/public-link-progress.d.ts +1 -1
- package/dist/public-link-progress.js +8 -2
- package/dist/public-link-progress.js.map +1 -1
- package/dist/public-preview.d.ts +101 -0
- package/dist/public-preview.js +236 -0
- package/dist/public-preview.js.map +1 -0
- package/dist/public-web-gateway.js +14 -4
- package/dist/public-web-gateway.js.map +1 -1
- package/dist/public-web-link.d.ts +24 -1
- package/dist/public-web-link.js +63 -9
- package/dist/public-web-link.js.map +1 -1
- package/dist/reply-buttons-controller.d.ts +89 -5
- package/dist/reply-buttons-controller.js +273 -8
- package/dist/reply-buttons-controller.js.map +1 -1
- package/dist/reply-buttons.d.ts +48 -3
- package/dist/reply-buttons.js +105 -10
- package/dist/reply-buttons.js.map +1 -1
- package/dist/reply-polls.d.ts +85 -0
- package/dist/reply-polls.js +154 -0
- package/dist/reply-polls.js.map +1 -0
- package/dist/restart-guard.d.ts +6 -3
- package/dist/restart-guard.js +6 -3
- package/dist/restart-guard.js.map +1 -1
- package/dist/restart-progress.d.ts +33 -0
- package/dist/restart-progress.js +30 -0
- package/dist/restart-progress.js.map +1 -1
- package/dist/service-activation.d.ts +15 -7
- package/dist/service-activation.js +46 -29
- package/dist/service-activation.js.map +1 -1
- package/dist/service-installer.d.ts +31 -0
- package/dist/service-installer.js +41 -2
- package/dist/service-installer.js.map +1 -1
- package/dist/settings-api.js +85 -25
- package/dist/settings-api.js.map +1 -1
- package/dist/settings-change.js +72 -1
- package/dist/settings-change.js.map +1 -1
- package/dist/settings-effect.js +28 -4
- package/dist/settings-effect.js.map +1 -1
- package/dist/settings-http-confirmation.js +7 -0
- package/dist/settings-http-confirmation.js.map +1 -1
- package/dist/setup-form.d.ts +1 -1
- package/dist/setup-form.js +1 -1
- package/dist/spawn-gate.js +5 -1
- package/dist/spawn-gate.js.map +1 -1
- package/dist/spawn-hold-reply.d.ts +12 -0
- package/dist/spawn-hold-reply.js +39 -0
- package/dist/spawn-hold-reply.js.map +1 -0
- package/dist/status-emojis.d.ts +10 -3
- package/dist/status-emojis.js +11 -2
- package/dist/status-emojis.js.map +1 -1
- package/dist/systemd-peer-bus.d.ts +95 -0
- package/dist/systemd-peer-bus.js +430 -0
- package/dist/systemd-peer-bus.js.map +1 -0
- package/dist/systemd-runtime.d.ts +27 -1
- package/dist/systemd-runtime.js +69 -7
- package/dist/systemd-runtime.js.map +1 -1
- package/dist/tips.js +4 -4
- package/dist/tips.js.map +1 -1
- package/dist/tool-permissions.js +3 -3
- package/dist/tool-permissions.js.map +1 -1
- package/dist/tool-progress.js +3 -2
- package/dist/tool-progress.js.map +1 -1
- package/dist/topic-commands.d.ts +42 -2
- package/dist/topic-commands.js +101 -11
- package/dist/topic-commands.js.map +1 -1
- package/dist/transcript-delta-reader.d.ts +3 -3
- package/dist/transcript-delta-reader.js +8 -8
- package/dist/transcript-delta-reader.js.map +1 -1
- package/dist/transcript-jsonl.d.ts +80 -3
- package/dist/transcript-jsonl.js +147 -10
- package/dist/transcript-jsonl.js.map +1 -1
- package/dist/transcript-monitor.d.ts +19 -0
- package/dist/transcript-monitor.js +87 -15
- package/dist/transcript-monitor.js.map +1 -1
- package/dist/transcript-sources.d.ts +78 -0
- package/dist/transcript-sources.js +375 -64
- package/dist/transcript-sources.js.map +1 -1
- package/dist/transcript-turns.d.ts +15 -2
- package/dist/transcript-turns.js +15 -2
- package/dist/transcript-turns.js.map +1 -1
- package/dist/tunnel/cloudflared.d.ts +7 -15
- package/dist/tunnel/cloudflared.js +36 -1
- package/dist/tunnel/cloudflared.js.map +1 -1
- package/dist/tunnel/purpose-lane.d.ts +5 -1
- package/dist/tunnel/purpose-lane.js +14 -7
- package/dist/tunnel/purpose-lane.js.map +1 -1
- package/dist/tunnel/types.d.ts +5 -1
- package/dist/tunnel/types.js.map +1 -1
- package/dist/turn-resume.d.ts +2 -0
- package/dist/turn-resume.js +1 -1
- package/dist/turn-resume.js.map +1 -1
- package/dist/types.d.ts +25 -0
- package/dist/ui/chat-commands.js +1 -0
- package/dist/ui/chat-emojis.js +33 -0
- package/dist/ui/chat-mentions.js +63 -0
- package/dist/ui/chat-render.js +61 -4
- package/dist/ui/chat-store.js +37 -3
- package/dist/ui/chat-strings.js +40 -20
- package/dist/ui/chat-thread.js +111 -12
- package/dist/ui/chat-time.js +80 -0
- package/dist/ui/panel-chat.js +41 -10
- package/dist/ui/panel-details.js +2 -0
- package/dist/ui/panel-fleet.js +199 -57
- package/dist/ui/panel-log.js +428 -0
- package/dist/ui/panel-needs.js +6 -2
- package/dist/ui/panel-settings.js +184 -32
- package/dist/ui/preview-panel.js +55 -15
- package/dist/ui/preview-status.js +28 -0
- package/dist/ui/preview.js +44 -3
- package/dist/ui/settings-dialogs.js +7 -2
- package/dist/ui/settings-model.js +2 -1
- package/dist/ui/settings-strings.js +56 -4
- package/dist/ui/settings-token.js +1 -1
- package/dist/ui/shared/app-i18n.js +43 -17
- package/dist/ui/shared/app-prefs.js +250 -0
- package/dist/ui/shared/app-route.js +7 -2
- package/dist/ui/shared/app-shell.js +165 -8
- package/dist/ui/shared/app-store.js +5 -1
- package/dist/ui/shared/app-stream.js +26 -5
- package/dist/ui/shared/app.css +163 -22
- package/dist/ui/shared/app.js +11 -1
- package/dist/ui/shared/fonts/README.md +1 -1
- package/dist/ui/shared/header-tools.js +55 -5
- package/dist/ui/shared/instance-nav.js +241 -45
- package/dist/ui/shared/instance-switch.js +4 -4
- package/dist/ui/shared/panel-view.js +6 -0
- package/dist/ui/shared/signin.js +7 -7
- package/dist/ui/shared/theme.js +39 -7
- package/dist/ui/shared/tokens.css +1 -1
- package/dist/ui/shared/ui-icons.js +1 -0
- package/dist/ui/shared/usage-dialog.js +23 -5
- package/dist/ui/shared/view-strings.js +32 -4
- package/dist/ui/signin.html +1 -1
- package/dist/unrecognised-prompt.d.ts +23 -0
- package/dist/unrecognised-prompt.js +74 -0
- package/dist/unrecognised-prompt.js.map +1 -0
- package/dist/update-check.d.ts +31 -0
- package/dist/update-check.js +47 -0
- package/dist/update-check.js.map +1 -1
- package/dist/update-install.d.ts +28 -2
- package/dist/update-install.js +22 -4
- package/dist/update-install.js.map +1 -1
- package/dist/update-launch.d.ts +46 -1
- package/dist/update-launch.js +146 -3
- package/dist/update-launch.js.map +1 -1
- package/dist/update-marker.d.ts +51 -0
- package/dist/update-marker.js +61 -0
- package/dist/update-marker.js.map +1 -1
- package/dist/update-preflight.d.ts +100 -0
- package/dist/update-preflight.js +264 -0
- package/dist/update-preflight.js.map +1 -0
- package/dist/updater-caller.d.ts +31 -0
- package/dist/updater-caller.js +109 -0
- package/dist/updater-caller.js.map +1 -0
- package/dist/usage/i18n-keys.d.ts +1 -1
- package/dist/usage/i18n-keys.js +1 -1
- package/dist/usage/i18n-keys.js.map +1 -1
- package/dist/usage/providers.d.ts +34 -5
- package/dist/usage/providers.js +160 -27
- package/dist/usage/providers.js.map +1 -1
- package/dist/usage/statusline-usage.d.ts +6 -2
- package/dist/usage/statusline-usage.js +10 -4
- package/dist/usage/statusline-usage.js.map +1 -1
- package/dist/view-api.d.ts +1 -1
- package/dist/view-api.js +38 -8
- package/dist/view-api.js.map +1 -1
- package/dist/wake-coordinator.d.ts +11 -0
- package/dist/wake-coordinator.js +38 -4
- package/dist/wake-coordinator.js.map +1 -1
- package/dist/web-api.d.ts +89 -4
- package/dist/web-api.js +551 -45
- package/dist/web-api.js.map +1 -1
- package/dist/web-auth.d.ts +2 -2
- package/dist/web-auth.js +9 -4
- package/dist/web-auth.js.map +1 -1
- package/dist/web-chat-history.d.ts +12 -1
- package/dist/web-chat-history.js +29 -7
- package/dist/web-chat-history.js.map +1 -1
- package/dist/web-commands.d.ts +27 -4
- package/dist/web-commands.js +19 -4
- package/dist/web-commands.js.map +1 -1
- package/dist/web-host-guard.d.ts +3 -11
- package/dist/web-host-guard.js +3 -12
- package/dist/web-host-guard.js.map +1 -1
- package/dist/web-images.d.ts +47 -0
- package/dist/web-images.js +169 -0
- package/dist/web-images.js.map +1 -0
- package/dist/web-login.d.ts +14 -0
- package/dist/web-login.js +4 -2
- package/dist/web-login.js.map +1 -1
- package/dist/web-mentions.d.ts +44 -0
- package/dist/web-mentions.js +75 -0
- package/dist/web-mentions.js.map +1 -0
- package/dist/web-prefs.d.ts +81 -0
- package/dist/web-prefs.js +297 -0
- package/dist/web-prefs.js.map +1 -0
- package/dist/web-preview.d.ts +14 -1
- package/dist/web-preview.js +35 -13
- package/dist/web-preview.js.map +1 -1
- package/dist/web-profile.d.ts +32 -0
- package/dist/web-profile.js +132 -0
- package/dist/web-profile.js.map +1 -0
- package/dist/web-reply-files.d.ts +45 -0
- package/dist/web-reply-files.js +223 -0
- package/dist/web-reply-files.js.map +1 -0
- package/dist/web-schedules.d.ts +12 -0
- package/dist/web-schedules.js +34 -0
- package/dist/web-schedules.js.map +1 -0
- package/dist/web-server-drain.d.ts +10 -0
- package/dist/web-server-drain.js +50 -0
- package/dist/web-server-drain.js.map +1 -0
- package/dist/web-session.d.ts +50 -1
- package/dist/web-session.js +63 -6
- package/dist/web-session.js.map +1 -1
- package/dist/web-settings-patch.d.ts +23 -0
- package/dist/web-settings-patch.js +77 -0
- package/dist/web-settings-patch.js.map +1 -0
- package/dist/web-shell-routes.d.ts +5 -1
- package/dist/web-shell-routes.js +10 -1
- package/dist/web-shell-routes.js.map +1 -1
- package/dist/web-upload.d.ts +17 -4
- package/dist/web-upload.js +64 -13
- package/dist/web-upload.js.map +1 -1
- package/dist/workflow-templates/default.md +1 -1
- package/package.json +1 -1
- package/templates/launchd.plist.ejs +3 -1
- package/templates/systemd.service.ejs +2 -1
package/dist/ui/chat-thread.js
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import "./chat-render.js";
|
|
10
10
|
import "./preview.js";
|
|
11
11
|
import { loadHtmlAttachment } from "./html-attachment.js";
|
|
12
|
+
import { chatTime, chatTimeFull, onDayChange } from "./chat-time.js";
|
|
12
13
|
|
|
13
14
|
const R = () => globalThis.AgendChatRender;
|
|
14
15
|
/** #1589: the public link shows attachments as downloads only (its page says so: <body data-public-link="1">). */
|
|
@@ -22,6 +23,9 @@ export const TEXT_PREVIEW_NODES = 4000;
|
|
|
22
23
|
/** A csv/tsv wider than this is shown as text, as Markdown tables are capped at 20 columns. */
|
|
23
24
|
export const TEXT_PREVIEW_COLUMNS = 50;
|
|
24
25
|
const P = () => globalThis.AgendPreview;
|
|
26
|
+
/** #1672: a status line longer than this (lines, or characters) is folded until it is opened. */
|
|
27
|
+
export const STATUS_FOLD_LINES = 6;
|
|
28
|
+
export const STATUS_FOLD_CHARS = 600;
|
|
25
29
|
const COPY = '<svg class="icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>';
|
|
26
30
|
|
|
27
31
|
/** Escapes & " ' < >: safe for text and inside a quoted attribute. */
|
|
@@ -31,7 +35,7 @@ export const msgKey = (x) => `${x.boot}-${x.id}`;
|
|
|
31
35
|
/**
|
|
32
36
|
* Bind a renderer to `list` (the thread element) inside `scroller`. opts: { t, tf, isUser(x), onJump({show, unseen}),
|
|
33
37
|
* setPreviewOptIn(on), copyText(text) → Promise<boolean>, download(code), panel? }. panel (#1481): { shown() → the card
|
|
34
|
-
* key the side panel shows, or null; open(spec, { run }) } — without it a card has no "Open in panel".
|
|
38
|
+
* key the side panel shows, or null; open(spec, { run }); version(); claim(); claims() } — without it a card has no "Open in panel".
|
|
35
39
|
*/
|
|
36
40
|
export function createThread(list, scroller, opts) {
|
|
37
41
|
const nodes = new Map(); // key → { html, node }
|
|
@@ -46,17 +50,48 @@ export function createThread(list, scroller, opts) {
|
|
|
46
50
|
const cardRefresh = new WeakMap(); // card node → re-read availability (weak: a card that left takes its source with it)
|
|
47
51
|
const tr = opts.t, trf = opts.tf;
|
|
48
52
|
const stopIn = (node) => (P() ? P().stopIn(node) : []);
|
|
53
|
+
/** #1770: write a message node's time label for now; unchanged text is not touched. */
|
|
54
|
+
function timeLabel(node) {
|
|
55
|
+
const el = node.querySelector(".meta .time[data-ts]");
|
|
56
|
+
if (!el) return;
|
|
57
|
+
const v = chatTime(el.dataset.ts, { yesterday: (hm) => trf("chat.yesterdayAt", hm) });
|
|
58
|
+
if (el.textContent !== v) el.textContent = v;
|
|
59
|
+
}
|
|
60
|
+
// #1770: "today" ends at the fleet's midnight; the labels are rewritten then (and on every render), while there are any.
|
|
61
|
+
let offDay = null;
|
|
62
|
+
function armDay() {
|
|
63
|
+
if (nodes.size && !offDay) offDay = onDayChange(() => { for (const v of nodes.values()) timeLabel(v.node); });
|
|
64
|
+
else if (!nodes.size && offDay) { offDay(); offDay = null; }
|
|
65
|
+
}
|
|
49
66
|
|
|
50
67
|
const TICKS = () => ({ queued: tr("chat.tick_queued"), processing: tr("chat.tick_processing"), delivered: tr("chat.tick_delivered"), failed: tr("chat.tick_failed"), cancelled: tr("chat.tick_cancelled") });
|
|
51
68
|
|
|
52
69
|
function msgHtml(x) {
|
|
70
|
+
// #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
|
|
71
|
+
// is kept by: it is written into the node (timeLabels) — crossing midnight relabels in place and reloads no preview.
|
|
72
|
+
const time = x.ts ? `<span class="time" data-ts="${escAttr(x.ts)}" title="${escAttr(chatTimeFull(x.ts))}"></span>` : `<span class="time"></span>`;
|
|
73
|
+
if (x.role === "status") return statusHtml(x, time);
|
|
53
74
|
const u = opts.isUser(x);
|
|
54
|
-
const time = x.ts ? new Date(x.ts).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }) : "";
|
|
55
75
|
// renderMarkdown escapes the whole text before it adds any tag of its own (chat-render.js).
|
|
56
76
|
const ticks = u && x.delivery ? R().deliveryHtml(x.delivery, TICKS()) : "";
|
|
57
77
|
const tools = u ? "" : `<div class="msg-tools"><button type="button" class="chip-btn icon-only" data-act="copyMsg" data-arg="${escAttr(msgKey(x))}" title="${escAttr(tr("chat.copyMessage"))}" aria-label="${escAttr(tr("chat.copyMessage"))}">${COPY}</button></div>`;
|
|
58
78
|
const buttons = x.role === "agent" ? replyButtonsHtml(x.buttons) : "";
|
|
59
|
-
return `<div class="msg ${u ? "user" : "agent"}"><div class="meta"><span class="sender">${escAttr(x.sender)}</span
|
|
79
|
+
return `<div class="msg ${u ? "user" : "agent"}"><div class="meta"><span class="sender">${escAttr(x.sender)}</span>${time}${ticks}</div><div class="body"><div class="md">${R().renderMarkdown(x.text, { htmlCards: x.role === "agent", emojis: opts.emojis, mentions: opts.mentions })}</div>${R().attachmentsHtml(x.attachments, { htmlCards: x.role === "agent", goneTitle: tr("chat.attGone"), goneLabel: tr("chat.attUnavailable"), inlinePreviews: !PUBLIC_LINK() })}${buttons}</div>${tools}</div>`;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* #1672: a status line (another instance's message, a daemon notice) is muted, read-only plain text — never Markdown,
|
|
83
|
+
* a preview card, a button or a person's bubble. Checked before isUser, which reads the sender: a fleet name need not
|
|
84
|
+
* start with "agend". A long one is folded until it is opened.
|
|
85
|
+
*/
|
|
86
|
+
function statusHtml(x, time) {
|
|
87
|
+
const text = String(x.text == null ? "" : x.text);
|
|
88
|
+
const lines = R().lineCount(text);
|
|
89
|
+
const manyLines = lines > STATUS_FOLD_LINES;
|
|
90
|
+
const more = manyLines || text.length > STATUS_FOLD_CHARS
|
|
91
|
+
? `<button type="button" class="st-more" data-act="toggleFold"${manyLines ? ` data-lines="${escAttr(lines)}"` : ""}>${escAttr(manyLines ? trf("chat.showAll", lines) : tr("chat.showMore"))}</button>`
|
|
92
|
+
: "";
|
|
93
|
+
const tools = `<div class="msg-tools"><button type="button" class="chip-btn icon-only" data-act="copyMsg" data-arg="${escAttr(msgKey(x))}" title="${escAttr(tr("chat.copyMessage"))}" aria-label="${escAttr(tr("chat.copyMessage"))}">${COPY}</button></div>`;
|
|
94
|
+
return `<div class="msg status-line${more ? " folded" : ""}"><div class="meta">${time}</div><div class="body"><div class="st-text">${escAttr(text)}</div>${more}</div>${tools}</div>`;
|
|
60
95
|
}
|
|
61
96
|
/**
|
|
62
97
|
* #1266: an agent reply's buttons. Labels are text (escaped, never Markdown); the click names the set and the index,
|
|
@@ -64,6 +99,7 @@ export function createThread(list, scroller, opts) {
|
|
|
64
99
|
*/
|
|
65
100
|
function replyButtonsHtml(b) {
|
|
66
101
|
if (!b || typeof b.id !== "string" || !Array.isArray(b.labels) || !b.labels.length) return "";
|
|
102
|
+
if (b.poll && typeof b.poll === "object") return pollHtml(b);
|
|
67
103
|
const busy = !!(opts.replyButtonBusy && opts.replyButtonBusy(b.id));
|
|
68
104
|
const off = b.state !== "open" || busy;
|
|
69
105
|
const items = b.labels.map((label, i) => {
|
|
@@ -73,11 +109,51 @@ export function createThread(list, scroller, opts) {
|
|
|
73
109
|
const note = b.state === "chosen" ? trf("chat.rbChosen", b.by || "") : b.state === "expired" ? tr("chat.rbExpired") : "";
|
|
74
110
|
return `<div class="rb-row" role="group" aria-label="${escAttr(tr("chat.rbGroup"))}">${items}${note ? `<span class="rb-note">${escAttr(note)}</span>` : ""}</div>`;
|
|
75
111
|
}
|
|
112
|
+
/**
|
|
113
|
+
* #1823: a poll. Anyone here may click while it is open; the page marks its own vote (opts.pollMine), which no one else
|
|
114
|
+
* sees. Counts only when the server sends them (revealed: live, or closed); names only at close with show_voters. The
|
|
115
|
+
* time left is not in the html (a node is kept by its html): pollLeft writes it, every second.
|
|
116
|
+
*/
|
|
117
|
+
function pollHtml(b) {
|
|
118
|
+
const p = b.poll, closed = b.state === "closed";
|
|
119
|
+
const busy = !!(opts.replyButtonBusy && opts.replyButtonBusy(b.id));
|
|
120
|
+
const mine = (opts.pollMine && opts.pollMine(b.id)) || [];
|
|
121
|
+
const counts = Array.isArray(p.counts) && p.counts.length === b.labels.length ? p.counts : null;
|
|
122
|
+
const items = b.labels.map((label, i) => {
|
|
123
|
+
const me = !closed && mine.includes(i);
|
|
124
|
+
const tally = counts ? ` · ${counts[i]}${closed && p.answered ? ` (${Math.round((counts[i] / p.answered) * 100)}%)` : ""}` : "";
|
|
125
|
+
return `<button type="button" class="btn btn-sm rb-btn${me ? " chosen" : ""}" data-act="replyButton" data-arg="${escAttr(`${b.id}:${i}`)}" aria-pressed="${me ? "true" : "false"}"${closed || busy ? " disabled" : ""}>${escAttr(`${me ? "✓ " : ""}${label}${tally}`)}</button>`;
|
|
126
|
+
}).join("");
|
|
127
|
+
const status = closed
|
|
128
|
+
? escAttr(trf("chat.pollClosed", p.answered))
|
|
129
|
+
: `${escAttr(trf("chat.pollAnswered", p.answered))} · ⏳ <span class="poll-left" data-deadline="${escAttr(p.deadline)}" title="${escAttr(tr("chat.pollLeft"))}"></span>`;
|
|
130
|
+
const voters = closed && Array.isArray(p.voters)
|
|
131
|
+
? `<ul class="poll-voters" aria-label="${escAttr(tr("chat.pollVoters"))}">${b.labels.map((label, i) => `<li>${escAttr(`${label}: ${(p.voters[i] || []).join(", ") || "—"}`)}</li>`).join("")}</ul>`
|
|
132
|
+
: "";
|
|
133
|
+
return `<div class="rb-row poll" role="group" aria-label="${escAttr(tr("chat.pollGroup"))}">${items}<span class="rb-note">${status}</span></div>${voters}`;
|
|
134
|
+
}
|
|
135
|
+
/** #1823: the time left on this node's open polls, now. */
|
|
136
|
+
function pollLeft(node) {
|
|
137
|
+
for (const el of node.querySelectorAll(".poll-left[data-deadline]")) {
|
|
138
|
+
const v = R().pollRemaining(Number(el.dataset.deadline) - (opts.serverNow ? opts.serverNow() : Date.now()));
|
|
139
|
+
if (el.textContent !== v) el.textContent = v;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
// #1823: while an open poll is on screen, its time left is rewritten every second.
|
|
143
|
+
let pollTick = null;
|
|
144
|
+
function armPolls() {
|
|
145
|
+
const open = nodes.size > 0 && !!list.querySelector(".poll-left[data-deadline]");
|
|
146
|
+
if (open && !pollTick) pollTick = setInterval(() => pollLeft(list), 1000);
|
|
147
|
+
else if (!open && pollTick) { clearInterval(pollTick); pollTick = null; }
|
|
148
|
+
}
|
|
149
|
+
|
|
76
150
|
function msgNode(html, x, stoppedKeys) {
|
|
77
151
|
const tpl = list.ownerDocument.createElement("template");
|
|
78
152
|
tpl.innerHTML = html;
|
|
79
153
|
const node = tpl.content.firstElementChild;
|
|
80
154
|
decorateCode(node);
|
|
155
|
+
timeLabel(node);
|
|
156
|
+
pollLeft(node);
|
|
81
157
|
if (x) decorateHtmlCards(node, x, stoppedKeys || []);
|
|
82
158
|
if (x) decorateTextCards(node, x);
|
|
83
159
|
return node;
|
|
@@ -299,14 +375,32 @@ export function createThread(list, scroller, opts) {
|
|
|
299
375
|
};
|
|
300
376
|
const ui = { state: (name, reason) => { state = name === "starting" || name === "running" ? name : "idle"; refresh(reason || null); } };
|
|
301
377
|
// Nothing is read for Preview on a device that does not allow previews: the button is not even shown there.
|
|
302
|
-
|
|
378
|
+
// A Preview click is newer than any Open in panel still reading (#1704): it claims the panel's turn too — and, its
|
|
379
|
+
// own HTML read, starts only if no newer click claimed meanwhile and no preview was started since (the panel's own
|
|
380
|
+
// Preview): a late read never undoes a newer choice. A close is neither, so it does not drop this click.
|
|
381
|
+
run.onclick = () => {
|
|
382
|
+
if (!P().availability().ok || reading) return; // this card already reading: that click stays the one
|
|
383
|
+
const mine = panel ? (panel.claim(), panel.claims()) : 0, started = P().startCount();
|
|
384
|
+
withCode((c) => {
|
|
385
|
+
if ((panel && panel.claims() !== mine) || P().startCount() !== started) { refresh(); return; }
|
|
386
|
+
P().start(key, holder, c, ui);
|
|
387
|
+
});
|
|
388
|
+
};
|
|
303
389
|
stopB.onclick = () => { if (reading) { reading = 0; refresh(); } else P().stop(key, "stopped", ""); };
|
|
304
|
-
//
|
|
305
|
-
//
|
|
390
|
+
// #1704: this click runs it in the panel, as Preview there would — a running preview moves (it stops here and
|
|
391
|
+
// starts there), an idle one starts there. Whether it can run is the panel's to say: a device that has not allowed
|
|
392
|
+
// previews is offered Allow first (and allowing runs it); where previews are off, nothing runs.
|
|
393
|
+
// An attachment's HTML is read first. The click claims the panel at once, so a newer click (even one still reading)
|
|
394
|
+
// wins: if the panel changed (another click, block, close) or any preview started meanwhile, this one is dropped —
|
|
395
|
+
// a late read never undoes a newer click.
|
|
306
396
|
if (toPanel) toPanel.onclick = () => {
|
|
307
|
-
|
|
308
|
-
if (
|
|
309
|
-
|
|
397
|
+
if (reading) return; // this card already reading: that click stays the one
|
|
398
|
+
if (P().running(key)) P().stop(key, "stopped", "");
|
|
399
|
+
const at = panel.claim(), started = P().startCount();
|
|
400
|
+
withCode((c) => {
|
|
401
|
+
if (panel.version() !== at || P().startCount() !== started) { refresh(); return; }
|
|
402
|
+
panel.open({ key, instance: x.instance, msgKey: msgKey(x), ...(att ? { att } : { n: source.n }), code: c, sender: x.sender, ts: x.ts }, { run: true });
|
|
403
|
+
});
|
|
310
404
|
};
|
|
311
405
|
if (dl) dl.onclick = () => opts.download(code);
|
|
312
406
|
ack.onclick = () => P().ackBanner(); // every card and the panel hear it (onChange)
|
|
@@ -341,6 +435,7 @@ export function createThread(list, scroller, opts) {
|
|
|
341
435
|
if (!msgs.length) {
|
|
342
436
|
for (const v of nodes.values()) stopIn(v.node);
|
|
343
437
|
nodes.clear();
|
|
438
|
+
armDay();
|
|
344
439
|
list.textContent = "";
|
|
345
440
|
opts.onEmpty(true);
|
|
346
441
|
return;
|
|
@@ -357,12 +452,14 @@ export function createThread(list, scroller, opts) {
|
|
|
357
452
|
const node = msgNode(html, x, stopped);
|
|
358
453
|
if (have) have.node.replaceWith(node); else added++;
|
|
359
454
|
have = { html, node }; nodes.set(key, have);
|
|
360
|
-
}
|
|
455
|
+
} else timeLabel(have.node);
|
|
361
456
|
const want = prev ? prev.nextSibling : list.firstChild;
|
|
362
457
|
// A node already in the list is being moved: its preview stops first. A new node (still in its template) has none.
|
|
363
458
|
if (have.node !== want) { if (have.node.parentNode === list) stopIn(have.node); list.insertBefore(have.node, want); }
|
|
364
459
|
prev = have.node;
|
|
365
460
|
}
|
|
461
|
+
armDay();
|
|
462
|
+
armPolls();
|
|
366
463
|
if ("restore" in renderOpts) {
|
|
367
464
|
unseen = 0;
|
|
368
465
|
if (renderOpts.restore == null) toBottom();
|
|
@@ -426,9 +523,9 @@ export function createThread(list, scroller, opts) {
|
|
|
426
523
|
if (act === "copyMsg") { const x = msgs.find(y => msgKey(y) === el.dataset.arg); if (x) copyFrom(el, x.text); }
|
|
427
524
|
else if (act === "copyCode") { const pre = el.closest(".codeblock")?.querySelector("pre"); if (pre) copyFrom(el, pre.textContent); }
|
|
428
525
|
else if (act === "toggleFold") {
|
|
429
|
-
const box = el.closest(".codeblock"); if (!box) return;
|
|
526
|
+
const box = el.closest(".codeblock, .msg.status-line"); if (!box) return;
|
|
430
527
|
const folded = box.classList.toggle("folded");
|
|
431
|
-
el.textContent = folded ? trf("chat.showAll", el.dataset.lines) : tr("chat.
|
|
528
|
+
el.textContent = !folded ? tr("chat.showLess") : el.dataset.lines ? trf("chat.showAll", el.dataset.lines) : tr("chat.showMore");
|
|
432
529
|
} else if (act === "toggleWrap") opts.toggleWrap();
|
|
433
530
|
else if (act === "replyButton") {
|
|
434
531
|
const m = /^([0-9a-f]{32}):(\d{1,2})$/.exec(el.dataset.arg || "");
|
|
@@ -441,6 +538,8 @@ export function createThread(list, scroller, opts) {
|
|
|
441
538
|
if (ro) ro.disconnect();
|
|
442
539
|
if (P()) P().stopAll("leave");
|
|
443
540
|
nodes.clear();
|
|
541
|
+
armDay();
|
|
542
|
+
armPolls();
|
|
444
543
|
}
|
|
445
544
|
|
|
446
545
|
return { render, onScroll, onClick, jumpLatest, reveal, refreshCards, dispose, nodes, get unseen() { return unseen; } };
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// #1770: the time a chat line shows, read in the fleet's zone (the one chat-logs, fleet.log and the #1586 DMs use) —
|
|
2
|
+
// "17:38" today, "Yesterday 17:38", "10/11 17:38" earlier this year, "2025/10/11 17:38" before. The hour and minute keep
|
|
3
|
+
// the browser's own format (12- or 24-hour), as they always had. No imports: the thread, the preview panel and the
|
|
4
|
+
// tests read it as it is.
|
|
5
|
+
|
|
6
|
+
const cache = new Map();
|
|
7
|
+
/** One formatter per (kind, zone, locale): a thread may label 500 lines on every render. */
|
|
8
|
+
function fmt(kind, tz, locale) {
|
|
9
|
+
const key = `${kind}|${tz || ""}|${locale || ""}`;
|
|
10
|
+
let f = cache.get(key);
|
|
11
|
+
if (!f) {
|
|
12
|
+
const o = kind === "hm" ? { hour: "2-digit", minute: "2-digit" }
|
|
13
|
+
: kind === "ymd" ? { year: "numeric", month: "numeric", day: "numeric", hour: "numeric", minute: "numeric", second: "numeric", hourCycle: "h23" }
|
|
14
|
+
: { year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", timeZoneName: "short" };
|
|
15
|
+
f = new Intl.DateTimeFormat(kind === "ymd" ? "en-US" : (locale || []), { ...o, timeZone: tz || undefined });
|
|
16
|
+
cache.set(key, f);
|
|
17
|
+
}
|
|
18
|
+
return f;
|
|
19
|
+
}
|
|
20
|
+
/** `tz` when this browser knows it, else undefined (the browser's own zone): a label must still be drawn. */
|
|
21
|
+
function known(tz) {
|
|
22
|
+
if (!tz) return undefined;
|
|
23
|
+
try { fmt("ymd", tz); return tz; } catch { return undefined; }
|
|
24
|
+
}
|
|
25
|
+
/** The fleet's zone, as the page was served it (<body data-fleet-tz>); undefined when absent or unknown here. */
|
|
26
|
+
export function fleetTz() { return known(globalThis.document?.body?.dataset?.fleetTz); }
|
|
27
|
+
|
|
28
|
+
const toMs = (ts) => (typeof ts === "number" ? ts : Date.parse(String(ts || "")));
|
|
29
|
+
function wall(ms, tz) {
|
|
30
|
+
const p = {};
|
|
31
|
+
for (const x of fmt("ymd", tz).formatToParts(ms)) p[x.type] = Number(x.value);
|
|
32
|
+
return p;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* The label for `ts` (ISO string or epoch ms), "" when it cannot be read. opts: tz (default the fleet's), now (epoch ms),
|
|
37
|
+
* yesterday(hm) → the word for "yesterday" around hm (default: hm alone), locale (tests).
|
|
38
|
+
*/
|
|
39
|
+
export function chatTime(ts, opts = {}) {
|
|
40
|
+
const ms = toMs(ts);
|
|
41
|
+
if (!Number.isFinite(ms)) return "";
|
|
42
|
+
const tz = "tz" in opts ? known(opts.tz) : fleetTz();
|
|
43
|
+
const hm = fmt("hm", tz, opts.locale).format(ms);
|
|
44
|
+
const d = wall(ms, tz), today = wall(opts.now ?? Date.now(), tz);
|
|
45
|
+
if (d.year === today.year && d.month === today.month && d.day === today.day) return hm;
|
|
46
|
+
const y = new Date(Date.UTC(today.year, today.month - 1, today.day - 1));
|
|
47
|
+
if (d.year === y.getUTCFullYear() && d.month === y.getUTCMonth() + 1 && d.day === y.getUTCDate()) return opts.yesterday ? opts.yesterday(hm) : hm;
|
|
48
|
+
return d.year === today.year ? `${d.month}/${d.day} ${hm}` : `${d.year}/${d.month}/${d.day} ${hm}`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** The full date, time and zone of `ts`, for a tooltip; "" when it cannot be read. */
|
|
52
|
+
export function chatTimeFull(ts, opts = {}) {
|
|
53
|
+
const ms = toMs(ts);
|
|
54
|
+
if (!Number.isFinite(ms)) return "";
|
|
55
|
+
return fmt("full", "tz" in opts ? known(opts.tz) : fleetTz(), opts.locale).format(ms);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Milliseconds until the fleet's next midnight (when "today" labels change), at most an hour (a DST day self-corrects). */
|
|
59
|
+
export function msToNextDay(now = Date.now(), tz = fleetTz()) {
|
|
60
|
+
const w = wall(now, known(tz));
|
|
61
|
+
const into = ((w.hour * 60 + w.minute) * 60 + w.second) * 1000 + (now % 1000);
|
|
62
|
+
return Math.max(1000, Math.min(3_600_000, 86_400_000 - into + 1000));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Call `fn` when the fleet's day may have changed — at its next midnight, then again for each one after (an hour at
|
|
67
|
+
* most between looks, so a DST day or a late timer corrects itself). Each subscriber holds its own timer, and no
|
|
68
|
+
* module-wide state: the unsubscribe it returns leaves nothing armed.
|
|
69
|
+
*/
|
|
70
|
+
export function onDayChange(fn) {
|
|
71
|
+
let timer = null, live = true;
|
|
72
|
+
const arm = () => {
|
|
73
|
+
timer = setTimeout(() => {
|
|
74
|
+
timer = null;
|
|
75
|
+
try { fn(); } finally { if (live) arm(); }
|
|
76
|
+
}, msToNextDay());
|
|
77
|
+
};
|
|
78
|
+
arm();
|
|
79
|
+
return () => { live = false; if (timer !== null) { clearTimeout(timer); timer = null; } };
|
|
80
|
+
}
|
package/dist/ui/panel-chat.js
CHANGED
|
@@ -10,6 +10,7 @@ import { html, useEffect, useLayoutEffect, useRef, useState } from "/assets/app-
|
|
|
10
10
|
import { t } from "/assets/app-i18n.js";
|
|
11
11
|
import { appStore, useStore } from "/assets/app-store.js";
|
|
12
12
|
import { useLease } from "/assets/app-ctx.js";
|
|
13
|
+
import { namesOf, namesStore } from "/assets/instance-nav.js";
|
|
13
14
|
import { PanelHeader, onPanelKey, setTitle, statusClass, statusLabel, openDrawer, requestNewInstance } from "/assets/app-shell.js";
|
|
14
15
|
import { detailsPath } from "/assets/app-route.js";
|
|
15
16
|
import { InstanceSwitch } from "/assets/instance-switch.js";
|
|
@@ -21,11 +22,15 @@ import { Empty, Skeleton } from "/assets/ui-states.js";
|
|
|
21
22
|
import { Icon } from "/assets/ui-icons.js";
|
|
22
23
|
import { toast } from "/assets/ui-toast.js";
|
|
23
24
|
import { confirmDialog } from "/assets/ui-confirm.js";
|
|
25
|
+
import { savePref } from "/assets/app-prefs.js";
|
|
24
26
|
import "./chat-strings.js";
|
|
25
27
|
import { confirmedWrite } from "./settings-confirm.js";
|
|
26
28
|
import { createChatStore } from "./chat-store.js";
|
|
27
29
|
import { createThread, msgKey } from "./chat-thread.js";
|
|
28
|
-
import {
|
|
30
|
+
import { createEmojiIndex } from "./chat-emojis.js";
|
|
31
|
+
import { pollPublicPreview } from "./preview-status.js";
|
|
32
|
+
import { createMentionIndex } from "./chat-mentions.js";
|
|
33
|
+
import { PreviewPanel, claimPanel, openPanel, panelClaims, panelStore, panelVersion, shownKey } from "./preview-panel.js";
|
|
29
34
|
import { needsArgument, paletteFor, parseCommandLine } from "./chat-commands.js";
|
|
30
35
|
import { installTour, refreshTourSpot } from "./chat-tour.js";
|
|
31
36
|
import { FirstRunCard } from "./first-run.js";
|
|
@@ -40,6 +45,9 @@ export let store = null;
|
|
|
40
45
|
// ── Announcements: one polite, visually hidden line for the coarse events only (a reply arrived, the agent started,
|
|
41
46
|
// finished, or waits on you). Messages themselves are not read out as they arrive; the list is a log to browse.
|
|
42
47
|
let announceTimer = null;
|
|
48
|
+
/** #1630: which Discord server emoji this page may draw (created with the store, in boot). */
|
|
49
|
+
let emojiIndex = null;
|
|
50
|
+
let mentionIndex = null; // #1682: the names of the mentions shown
|
|
43
51
|
function announce(text) {
|
|
44
52
|
let el = document.getElementById("announcer");
|
|
45
53
|
if (!el) { el = document.createElement("div"); el.id = "announcer"; el.className = "sr-only"; el.setAttribute("role", "status"); el.setAttribute("aria-live", "polite"); el.setAttribute("aria-atomic", "true"); document.body.appendChild(el); }
|
|
@@ -51,11 +59,14 @@ function announce(text) {
|
|
|
51
59
|
|
|
52
60
|
/**
|
|
53
61
|
* This device's preview opt-in: asks once (the app's dialog) and says what it means; turning it off stops every
|
|
54
|
-
* running preview. Resolves to the choice in force.
|
|
62
|
+
* running preview. Resolves to the choice in force. commit(apply): runs `apply` (the change itself) — the panel's Allow
|
|
63
|
+
* wraps it to know which change of the choice is its own (#1704).
|
|
55
64
|
*/
|
|
56
|
-
export async function setPreviewOptIn(on) {
|
|
65
|
+
export async function setPreviewOptIn(on, commit = (apply) => apply()) {
|
|
57
66
|
if (on && !P().optedIn() && !(await confirmDialog({ message: t("chat.pvConfirm"), confirmLabel: t("chat.pvAllowButton") }))) on = false;
|
|
58
|
-
P().setOptIn(on);
|
|
67
|
+
commit(() => P().setOptIn(on));
|
|
68
|
+
// #1665: kept on the host for every device (only a signed-in page writes it; never the public link).
|
|
69
|
+
savePref("preview.optIn", P().optedIn());
|
|
59
70
|
return P().optedIn();
|
|
60
71
|
}
|
|
61
72
|
|
|
@@ -65,11 +76,15 @@ export function boot({ stream, boot: bootData, deps = {} }) {
|
|
|
65
76
|
store = createChatStore({
|
|
66
77
|
fetch: (...a) => fetch(...a), toast, announce, t, setTimeout: (fn, ms) => setTimeout(fn, ms), confirm: confirmDialog, ...deps,
|
|
67
78
|
});
|
|
79
|
+
emojiIndex = createEmojiIndex((url) => (deps.fetch || fetch)(url, { credentials: "same-origin" }));
|
|
80
|
+
mentionIndex = createMentionIndex((url) => (deps.fetch || fetch)(url, { credentials: "same-origin" }));
|
|
68
81
|
store.attach(stream);
|
|
69
82
|
// #1306: what the server said about previews for this load. The page-wide message and storage listeners this adds
|
|
70
83
|
// belong to the app, once (they are never per mount).
|
|
71
84
|
// #1554: its words in the page's language (chat.pv*); a key the dictionary does not have keeps preview.js's English.
|
|
72
85
|
if (P() && bootData) P().init(bootData, { text: (key, ...v) => { const k = `chat.${key}`, s = t(k, ...v); return s === k ? null : s; } });
|
|
86
|
+
// #1723: the public link's preview was still starting when this page loaded: ask until it is up (or failed).
|
|
87
|
+
if (P() && bootData) pollPublicPreview(P());
|
|
73
88
|
// #1568: the banner's "I understand" lasts until the next sign-in — preview.js is told which sign-in this page is
|
|
74
89
|
// (the session handle, from the one /auth/session read); none (no session, or no AgendAuth): this tab only.
|
|
75
90
|
if (P() && typeof P().setBannerScope === "function") {
|
|
@@ -119,6 +134,9 @@ function useChatTick(name, kinds) {
|
|
|
119
134
|
|
|
120
135
|
// ── The panel ──
|
|
121
136
|
|
|
137
|
+
/** #1722: the chat's title — the name that leads, the other on hover. */
|
|
138
|
+
const headerName = ({ primary, secondary }) => (secondary ? html`<span title=${secondary}>${primary}</span>` : primary);
|
|
139
|
+
|
|
122
140
|
export function ChatPanel({ route, navKey }) {
|
|
123
141
|
const lease = useLease(navKey);
|
|
124
142
|
const app = useStore(appStore);
|
|
@@ -154,6 +172,7 @@ function NotFound({ name }) {
|
|
|
154
172
|
}
|
|
155
173
|
|
|
156
174
|
function ChatView({ name, inst, lease, navKey, exec, awaiting }) {
|
|
175
|
+
const names = useStore(namesStore); // #1722: which name leads (the title)
|
|
157
176
|
const [dialog, setDialog] = useState(null); // "delete" | null
|
|
158
177
|
const [wrap, setWrap] = useState(codeWrap);
|
|
159
178
|
const view = useRef(null), split = useRef(null), thread = useRef(null);
|
|
@@ -189,6 +208,7 @@ function ChatView({ name, inst, lease, navKey, exec, awaiting }) {
|
|
|
189
208
|
try { const res = await fetch(`/ui/${verb}/${encodeURIComponent(name)}`, { method: "POST", headers: { "Content-Type": "application/json" } }); r = await res.json(); }
|
|
190
209
|
catch (err) { r = { error: err && err.message ? err.message : t("chat.disconnected") }; }
|
|
191
210
|
if (r && r.error) toast(r.error, false);
|
|
211
|
+
else if (r && r.held) toast(r.message);
|
|
192
212
|
else toast(t(verb === "start" ? "chat.started" : verb === "stop" ? "chat.stoppedInst" : "chat.restarted", name));
|
|
193
213
|
};
|
|
194
214
|
const items = [
|
|
@@ -206,14 +226,14 @@ function ChatView({ name, inst, lease, navKey, exec, awaiting }) {
|
|
|
206
226
|
${inst.model ? html`<button type="button" class="hd-chip" title=${t("chat.chipModel", inst.model)} aria-label=${t("chat.chipModel", inst.model)} onClick=${() => pick("model")}>${inst.model}</button>` : null}
|
|
207
227
|
${inst.effort ? html`<button type="button" class="hd-chip" title=${t("chat.chipEffort", inst.effort)} aria-label=${t("chat.chipEffort", inst.effort)} onClick=${() => pick("effort")}>${inst.effort}</button>` : null}`;
|
|
208
228
|
return html`<div class=${`panel p-chat${wrap ? " wrap-code" : ""}`} ref=${view}>
|
|
209
|
-
<${PanelHeader} title=${name} sub=${sub} nav=${html`<${InstanceSwitch} name=${name} current="chat" />`}><${HeaderTools} navKey=${navKey} /><${Menu} items=${items} label=${t("app.more")} /></${PanelHeader}>
|
|
229
|
+
<${PanelHeader} title=${headerName(namesOf(inst || { name }, names.primary))} sub=${sub} nav=${html`<${InstanceSwitch} name=${name} current="chat" />`}><${HeaderTools} navKey=${navKey} /><${Menu} items=${items} label=${t("app.more")} /></${PanelHeader}>
|
|
210
230
|
<div class="chat-split" ref=${split}>
|
|
211
231
|
<div class="chat-main">
|
|
212
232
|
<${Thread} name=${name} th=${thread} />
|
|
213
233
|
<${Dock} name=${name} inst=${inst} lease=${lease} exec=${exec} awaiting=${awaiting} />
|
|
214
234
|
</div>
|
|
215
235
|
<${PreviewPanel} name=${name} split=${split} msgs=${() => store.state.msgs[name] || []} subscribe=${(fn) => store.subscribe(fn)}
|
|
216
|
-
keyOf=${msgKey} reveal=${(k) => thread.current && thread.current.reveal(k)} download=${downloadHtml} allow=${() => setPreviewOptIn(true)} />
|
|
236
|
+
keyOf=${msgKey} reveal=${(k) => thread.current && thread.current.reveal(k)} download=${downloadHtml} allow=${(commit) => setPreviewOptIn(true, commit)} />
|
|
217
237
|
</div>
|
|
218
238
|
<${LightboxHost} />
|
|
219
239
|
<div class="drop-overlay" aria-hidden="true"><div class="drop-card"><${Icon} name="attach" size=${32} /><span>${t("chat.dropHere")}</span></div></div>
|
|
@@ -232,14 +252,24 @@ function Thread({ name, th }) {
|
|
|
232
252
|
setPreviewOptIn, copyText, download: downloadHtml, toggleWrap,
|
|
233
253
|
clickReplyButton: (id, index) => store.clickReplyButton(name, id, index), // #1266
|
|
234
254
|
replyButtonBusy: (id) => store.state.rbBusy.has(id),
|
|
235
|
-
|
|
255
|
+
pollMine: (id) => store.state.pollMine[id], // #1823: this device's own vote
|
|
256
|
+
serverNow: () => Date.now() + store.state.clockSkew, // #1823: the deadline is the server's
|
|
257
|
+
panel: { shown: shownKey, open: (spec, o) => openPanel(spec, o), version: panelVersion, claim: claimPanel, claims: panelClaims }, // #1481, #1704
|
|
236
258
|
openImage: openLightbox, // an image, full size, in the page
|
|
259
|
+
emojis: (id) => !!emojiIndex && emojiIndex.known(id), // #1630: the fleet's server emoji
|
|
260
|
+
mentions: (kind, id) => (mentionIndex ? mentionIndex.name(kind, id) : null), // #1682: mention names
|
|
237
261
|
});
|
|
238
262
|
th.current = thread;
|
|
263
|
+
// #1630: the server-emoji list is read once a message shows a tag; when it arrives, those messages are drawn again.
|
|
264
|
+
// #1682: the same for mention names — asked for the ids the messages carry; when names arrive, drawn again.
|
|
265
|
+
const wantEmojis = (msgs) => { if (emojiIndex && emojiIndex.wants(msgs)) emojiIndex.ensure(); if (mentionIndex) mentionIndex.ensure(msgs); };
|
|
266
|
+
const offEmojis = emojiIndex ? emojiIndex.subscribe(() => thread.render(store.state.msgs[name] || [])) : () => {};
|
|
267
|
+
const offMentions = mentionIndex ? mentionIndex.subscribe(() => thread.render(store.state.msgs[name] || [])) : () => {};
|
|
239
268
|
thread.render(store.state.msgs[name] || [], { restore: store.state.scrollMemo[name] ?? null });
|
|
269
|
+
wantEmojis(store.state.msgs[name]);
|
|
240
270
|
const off = store.subscribe((instance, kind) => {
|
|
241
271
|
if (instance !== name) return;
|
|
242
|
-
if (kind === "msgs") thread.render(store.state.msgs[name] || []);
|
|
272
|
+
if (kind === "msgs") { thread.render(store.state.msgs[name] || []); wantEmojis(store.state.msgs[name]); }
|
|
243
273
|
else if (kind === "sent") thread.jumpLatest();
|
|
244
274
|
});
|
|
245
275
|
const offPv = P() ? P().onChange(() => thread.refreshCards()) : () => {};
|
|
@@ -249,7 +279,7 @@ function Thread({ name, th }) {
|
|
|
249
279
|
sc.addEventListener("scroll", onScroll, { passive: true });
|
|
250
280
|
ls.addEventListener("click", thread.onClick);
|
|
251
281
|
return () => {
|
|
252
|
-
off(); offPv(); offPanel();
|
|
282
|
+
off(); offPv(); offPanel(); offEmojis(); offMentions();
|
|
253
283
|
sc.removeEventListener("scroll", onScroll);
|
|
254
284
|
ls.removeEventListener("click", thread.onClick);
|
|
255
285
|
thread.dispose(); // the whole thread goes: every preview in it stops first
|
|
@@ -292,6 +322,7 @@ function CommandCard({ name, lease }) {
|
|
|
292
322
|
aria-label=${t("chat.cmdDismiss")} onClick=${() => store.dismissCommand(name)}><${Icon} name="close" size=${14} /></button>`}</div>
|
|
293
323
|
${c.error ? html`<p class="cmd-text">${c.error}</p>` : null}
|
|
294
324
|
${c.text ? html`<pre class="cmd-text">${c.text}</pre>` : null}
|
|
325
|
+
${c.choices && c.choices.prompt ? html`<pre class="cmd-text">${c.choices.prompt}</pre>` : null}
|
|
295
326
|
${c.choices ? html`<div class="cmd-choices" role="group" aria-label=${t("chat.cmdChoose")}>${c.choices.options.map((o) => html`<button key=${o.id} type="button"
|
|
296
327
|
class=${`btn btn-sm${o.id === c.choices.current ? " current" : ""}`} aria-current=${o.id === c.choices.current ? "true" : undefined} onClick=${() => run(o.id)}>${o.label}</button>`)}</div>` : null}
|
|
297
328
|
</div>`;
|
|
@@ -466,7 +497,7 @@ function DeleteDialog({ name, onClose }) {
|
|
|
466
497
|
<button type="button" class="btn btn-danger" disabled=${typed !== want || busy} onClick=${go}>${t("chat.deleteGo")}</button>`}>
|
|
467
498
|
<p>${t("chat.deleteHelp", name)}</p>
|
|
468
499
|
<label class="field"><span class="sr-only">${t("chat.deleteHelp", name)}</span>
|
|
469
|
-
<input type="text" value=${typed} autocomplete="off" spellcheck
|
|
500
|
+
<input type="text" value=${typed} autocomplete="off" spellcheck=${false} placeholder=${want} onInput=${(e) => setTyped(e.target.value)}
|
|
470
501
|
onKeyDown=${(e) => { if (e.key === "Enter") go(); }} /></label>
|
|
471
502
|
</${Dialog}>`;
|
|
472
503
|
}
|
package/dist/ui/panel-details.js
CHANGED
|
@@ -73,11 +73,13 @@ function DetailsView({ name, inst, navKey, exec, awaiting }) {
|
|
|
73
73
|
try { const res = await fetch(`/ui/${verb}/${encodeURIComponent(name)}`, { method: "POST", headers: { "Content-Type": "application/json" } }); r = await res.json(); }
|
|
74
74
|
catch (err) { r = { error: err && err.message ? err.message : t("chat.disconnected") }; }
|
|
75
75
|
if (r && r.error) toast(r.error, false);
|
|
76
|
+
else if (r && r.held) toast(r.message);
|
|
76
77
|
else toast(t(verb === "start" ? "chat.started" : verb === "stop" ? "chat.stoppedInst" : "chat.restarted", name));
|
|
77
78
|
});
|
|
78
79
|
const pauseWake = (verb) => act(verb, async () => {
|
|
79
80
|
const res = await api(`/api/settings/instances/${encodeURIComponent(name)}/${verb}`, { method: "POST" }).catch(() => ({ ok: false, body: {} }));
|
|
80
81
|
if (!res.ok) toast((res.body && res.body.error) || t("chat.dActionFailed"), false);
|
|
82
|
+
else if (res.body && res.body.held) toast(res.body.message);
|
|
81
83
|
else toast(t(verb === "pause" ? "chat.dPaused" : "chat.dWoken", name));
|
|
82
84
|
});
|
|
83
85
|
// A ClassicBot room (classicBot.yaml) keeps Classic's split: its own Settings dialog, and only the actions Settings'
|