@songsid/agend 2.3.3 → 2.3.4-beta.10
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 +3 -3
- package/README.zh-TW.md +3 -3
- package/THIRD_PARTY_NOTICES.md +37 -0
- package/dist/agend-home.d.ts +13 -0
- package/dist/agend-home.js +42 -0
- package/dist/agend-home.js.map +1 -1
- package/dist/agent-cli-instructions.md +2 -2
- package/dist/agent-credentials.d.ts +24 -0
- package/dist/agent-credentials.js +66 -0
- package/dist/agent-credentials.js.map +1 -0
- package/dist/agent-endpoint.d.ts +3 -1
- package/dist/agent-endpoint.js +13 -0
- package/dist/agent-endpoint.js.map +1 -1
- package/dist/auth-api.d.ts +16 -3
- package/dist/auth-api.js +32 -8
- package/dist/auth-api.js.map +1 -1
- package/dist/authority/session-authorizations.d.ts +28 -4
- package/dist/authority/session-authorizations.js +63 -17
- package/dist/authority/session-authorizations.js.map +1 -1
- package/dist/authority/web-authority.d.ts +15 -1
- package/dist/authority/web-authority.js +27 -10
- package/dist/authority/web-authority.js.map +1 -1
- package/dist/backend/antigravity.js +3 -1
- package/dist/backend/antigravity.js.map +1 -1
- package/dist/backend/claude-code.d.ts +3 -0
- package/dist/backend/claude-code.js +100 -5
- package/dist/backend/claude-code.js.map +1 -1
- package/dist/backend/codex-footer-hints.d.ts +4 -0
- package/dist/backend/codex-footer-hints.js +27 -0
- package/dist/backend/codex-footer-hints.js.map +1 -0
- package/dist/backend/codex-session-lookup.d.ts +35 -0
- package/dist/backend/codex-session-lookup.js +93 -0
- package/dist/backend/codex-session-lookup.js.map +1 -1
- package/dist/backend/codex.d.ts +27 -0
- package/dist/backend/codex.js +136 -23
- package/dist/backend/codex.js.map +1 -1
- package/dist/backend/kiro-identity.d.ts +2 -0
- package/dist/backend/kiro-identity.js +13 -6
- package/dist/backend/kiro-identity.js.map +1 -1
- package/dist/backend/kiro.d.ts +3 -0
- package/dist/backend/kiro.js +68 -5
- package/dist/backend/kiro.js.map +1 -1
- package/dist/backend/muse.js +5 -3
- package/dist/backend/muse.js.map +1 -1
- package/dist/backend/types.d.ts +13 -1
- package/dist/backend/types.js +3 -0
- package/dist/backend/types.js.map +1 -1
- package/dist/backend-availability.d.ts +6 -0
- package/dist/backend-availability.js +13 -5
- package/dist/backend-availability.js.map +1 -1
- package/dist/cache-ledger.d.ts +85 -0
- package/dist/cache-ledger.js +236 -1
- package/dist/cache-ledger.js.map +1 -1
- package/dist/cache-service.d.ts +5 -0
- package/dist/cache-service.js +16 -4
- package/dist/cache-service.js.map +1 -1
- package/dist/channel/adapters/discord.js +27 -4
- package/dist/channel/adapters/discord.js.map +1 -1
- package/dist/channel/adapters/telegram.d.ts +51 -0
- package/dist/channel/adapters/telegram.js +238 -18
- package/dist/channel/adapters/telegram.js.map +1 -1
- package/dist/channel/inbox-temp-name.d.ts +9 -0
- package/dist/channel/inbox-temp-name.js +13 -0
- package/dist/channel/inbox-temp-name.js.map +1 -0
- package/dist/channel/mcp-tools.js +3 -3
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/channel/types.d.ts +2 -0
- package/dist/chat-log-ids.d.ts +48 -0
- package/dist/chat-log-ids.js +182 -0
- package/dist/chat-log-ids.js.map +1 -0
- package/dist/classic-channel-manager.d.ts +8 -1
- package/dist/classic-channel-manager.js +24 -2
- package/dist/classic-channel-manager.js.map +1 -1
- package/dist/cli.js +4 -6
- package/dist/cli.js.map +1 -1
- package/dist/config-validator.js +18 -1
- package/dist/config-validator.js.map +1 -1
- package/dist/cross-instance-notice.d.ts +10 -0
- package/dist/cross-instance-notice.js +67 -5
- package/dist/cross-instance-notice.js.map +1 -1
- package/dist/daemon.d.ts +52 -0
- package/dist/daemon.js +538 -35
- package/dist/daemon.js.map +1 -1
- package/dist/delivery-consumed-watch.d.ts +23 -1
- package/dist/delivery-consumed-watch.js +30 -3
- package/dist/delivery-consumed-watch.js.map +1 -1
- package/dist/delivery-reconciliation.d.ts +7 -0
- package/dist/delivery-reconciliation.js +9 -1
- package/dist/delivery-reconciliation.js.map +1 -1
- package/dist/dialog-screen-text.d.ts +16 -0
- package/dist/dialog-screen-text.js +51 -0
- package/dist/dialog-screen-text.js.map +1 -0
- package/dist/env-file.d.ts +2 -0
- package/dist/env-file.js +13 -8
- package/dist/env-file.js.map +1 -1
- package/dist/export-import.d.ts +7 -0
- package/dist/export-import.js +44 -10
- package/dist/export-import.js.map +1 -1
- package/dist/fleet-context.d.ts +2 -0
- package/dist/fleet-manager.d.ts +172 -12
- package/dist/fleet-manager.js +897 -139
- package/dist/fleet-manager.js.map +1 -1
- package/dist/fleet-yaml-patch.d.ts +3 -0
- package/dist/fleet-yaml-patch.js +103 -0
- package/dist/fleet-yaml-patch.js.map +1 -0
- package/dist/general-knowledge/skills/concepts/SKILL.md +393 -0
- package/dist/general-knowledge/skills/cross-instance-messaging/SKILL.md +2 -1
- package/dist/general-knowledge/skills/instance-lifecycle/SKILL.md +3 -1
- package/dist/general-knowledge/skills/kiro-engine-migration/SKILL.md +41 -4
- package/dist/general-knowledge/skills/model-discovery/SKILL.md +2 -0
- package/dist/general-knowledge/skills/web-appearance/SKILL.md +1 -1
- package/dist/general-knowledge/skills/web-dashboard/SKILL.md +14 -4
- package/dist/general-knowledge/skills/worker-collaboration/SKILL.md +1 -1
- package/dist/install-stream.d.ts +53 -0
- package/dist/install-stream.js +134 -0
- package/dist/install-stream.js.map +1 -0
- package/dist/instance-config-impact.js +1 -0
- package/dist/instance-config-impact.js.map +1 -1
- package/dist/instance-lifecycle.d.ts +22 -2
- package/dist/instance-lifecycle.js +103 -19
- package/dist/instance-lifecycle.js.map +1 -1
- package/dist/instance-topic-delete.d.ts +77 -0
- package/dist/instance-topic-delete.js +123 -0
- package/dist/instance-topic-delete.js.map +1 -0
- package/dist/instructions.d.ts +2 -2
- package/dist/instructions.js +2 -2
- package/dist/instructions.js.map +1 -1
- package/dist/interaction-observation.d.ts +1 -0
- package/dist/interaction-observation.js +1 -1
- package/dist/interaction-observation.js.map +1 -1
- package/dist/kiro-engine-status.d.ts +11 -0
- package/dist/kiro-engine-status.js +6 -0
- package/dist/kiro-engine-status.js.map +1 -1
- package/dist/kiro-resume-summary.d.ts +37 -0
- package/dist/kiro-resume-summary.js +85 -0
- package/dist/kiro-resume-summary.js.map +1 -0
- package/dist/locale.js +130 -6
- package/dist/locale.js.map +1 -1
- package/dist/log-service.d.ts +30 -0
- package/dist/log-service.js +36 -3
- package/dist/log-service.js.map +1 -1
- package/dist/log-steps.d.ts +4 -3
- package/dist/log-steps.js +32 -7
- package/dist/log-steps.js.map +1 -1
- package/dist/logger.d.ts +12 -0
- package/dist/logger.js +43 -1
- package/dist/logger.js.map +1 -1
- package/dist/login-controller.js +9 -1
- package/dist/login-controller.js.map +1 -1
- package/dist/login-flows.d.ts +2 -0
- package/dist/login-flows.js +5 -2
- package/dist/login-flows.js.map +1 -1
- package/dist/login-manager.d.ts +6 -0
- package/dist/login-manager.js +10 -1
- package/dist/login-manager.js.map +1 -1
- package/dist/missed-offline.d.ts +25 -0
- package/dist/missed-offline.js +79 -0
- package/dist/missed-offline.js.map +1 -0
- package/dist/needs-you-hub.d.ts +6 -1
- package/dist/needs-you-hub.js +31 -8
- package/dist/needs-you-hub.js.map +1 -1
- package/dist/needs-you.d.ts +22 -2
- package/dist/needs-you.js +17 -3
- package/dist/needs-you.js.map +1 -1
- package/dist/next-steps.d.ts +12 -2
- package/dist/next-steps.js +19 -3
- package/dist/next-steps.js.map +1 -1
- package/dist/office-share-page.d.ts +1 -1
- package/dist/office-share-page.js +6 -2
- package/dist/office-share-page.js.map +1 -1
- package/dist/office-share-service.d.ts +57 -0
- package/dist/office-share-service.js +76 -11
- package/dist/office-share-service.js.map +1 -1
- package/dist/office-share.d.ts +8 -0
- package/dist/office-share.js +34 -0
- package/dist/office-share.js.map +1 -1
- package/dist/outbound-handlers.js +59 -3
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.d.ts +1 -0
- package/dist/outbound-schemas.js +10 -4
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/package-preimage.js +27 -2
- package/dist/package-preimage.js.map +1 -1
- package/dist/page-share.d.ts +122 -0
- package/dist/page-share.js +199 -0
- package/dist/page-share.js.map +1 -0
- package/dist/public-preview.d.ts +4 -0
- package/dist/public-preview.js +5 -4
- package/dist/public-preview.js.map +1 -1
- package/dist/public-web-gateway.js +5 -0
- package/dist/public-web-gateway.js.map +1 -1
- package/dist/queued-handoffs.d.ts +33 -0
- package/dist/queued-handoffs.js +75 -0
- package/dist/queued-handoffs.js.map +1 -0
- package/dist/quickstart-api.d.ts +5 -0
- package/dist/quickstart-api.js +1 -1
- package/dist/quickstart-api.js.map +1 -1
- package/dist/quickstart.d.ts +40 -0
- package/dist/quickstart.js +174 -28
- package/dist/quickstart.js.map +1 -1
- package/dist/remote-card-answer.js +2 -0
- package/dist/remote-card-answer.js.map +1 -1
- package/dist/remote-card-prompt.js +4 -2
- package/dist/remote-card-prompt.js.map +1 -1
- package/dist/reply-buttons-controller.js +10 -0
- package/dist/reply-buttons-controller.js.map +1 -1
- package/dist/scheduler/db.d.ts +2 -1
- package/dist/scheduler/db.js +23 -9
- package/dist/scheduler/db.js.map +1 -1
- package/dist/selection-card.d.ts +20 -0
- package/dist/selection-card.js +202 -0
- package/dist/selection-card.js.map +1 -0
- package/dist/settings-api.d.ts +13 -0
- package/dist/settings-api.js +16 -1
- package/dist/settings-api.js.map +1 -1
- package/dist/settings-change.js +5 -0
- package/dist/settings-change.js.map +1 -1
- package/dist/settings-effect.js +2 -0
- package/dist/settings-effect.js.map +1 -1
- package/dist/setup-host.js +2 -2
- package/dist/setup-host.js.map +1 -1
- package/dist/setup-wizard.js +2 -2
- package/dist/setup-wizard.js.map +1 -1
- package/dist/stall-reporter.d.ts +21 -0
- package/dist/stall-reporter.js +82 -0
- package/dist/stall-reporter.js.map +1 -0
- package/dist/tmux-control.d.ts +2 -2
- package/dist/tmux-control.js +4 -2
- package/dist/tmux-control.js.map +1 -1
- package/dist/tmux-exec-helper.d.ts +6 -3
- package/dist/tmux-exec-helper.js +115 -53
- package/dist/tmux-exec-helper.js.map +1 -1
- package/dist/tmux-manager.d.ts +6 -0
- package/dist/tmux-manager.js +17 -0
- package/dist/tmux-manager.js.map +1 -1
- package/dist/tool-permissions.d.ts +5 -2
- package/dist/tool-permissions.js +6 -5
- package/dist/tool-permissions.js.map +1 -1
- package/dist/tool-progress.d.ts +2 -0
- package/dist/tool-progress.js +19 -3
- package/dist/tool-progress.js.map +1 -1
- package/dist/topic-commands.js +4 -0
- package/dist/topic-commands.js.map +1 -1
- package/dist/tunnel/cloudflared-install.d.ts +4 -0
- package/dist/tunnel/cloudflared-install.js +20 -1
- package/dist/tunnel/cloudflared-install.js.map +1 -1
- package/dist/tunnel/cloudflared.d.ts +8 -1
- package/dist/tunnel/cloudflared.js +34 -6
- package/dist/tunnel/cloudflared.js.map +1 -1
- package/dist/tunnel/lease.d.ts +3 -3
- package/dist/tunnel/lease.js +23 -19
- package/dist/tunnel/lease.js.map +1 -1
- package/dist/tunnel/manager.js +6 -7
- package/dist/tunnel/manager.js.map +1 -1
- package/dist/types.d.ts +10 -0
- package/dist/ui/chat-store.js +28 -4
- package/dist/ui/chat-strings.js +40 -4
- package/dist/ui/chat-thread.js +52 -10
- package/dist/ui/fleet-canvas.js +4 -3
- package/dist/ui/fleet-office-model.js +55 -22
- package/dist/ui/fleet-office-overview.js +6 -38
- package/dist/ui/fleet-office-pack.js +4 -3
- package/dist/ui/fleet-office-skin.js +114 -21
- package/dist/ui/fleet-office-store.js +5 -0
- package/dist/ui/fleet-office-zones.js +2 -2
- package/dist/ui/fleet-office.js +41 -9
- package/dist/ui/fleet-usage.js +90 -27
- package/dist/ui/page-share-dialog.js +191 -0
- package/dist/ui/panel-chat.js +16 -0
- package/dist/ui/panel-log.js +7 -1
- package/dist/ui/panel-needs.js +17 -8
- package/dist/ui/panel-notices.js +98 -0
- package/dist/ui/panel-settings.js +9 -2
- package/dist/ui/preview-pages.js +41 -0
- package/dist/ui/preview-panel-actions.js +161 -0
- package/dist/ui/preview-panel.js +209 -28
- package/dist/ui/preview-versions.js +155 -0
- package/dist/ui/preview.js +88 -3
- package/dist/ui/settings-registry.js +1 -0
- package/dist/ui/settings-strings.js +6 -2
- package/dist/ui/shared/app-i18n.js +6 -6
- package/dist/ui/shared/app-needs.js +1 -1
- package/dist/ui/shared/app-route.js +4 -0
- package/dist/ui/shared/app-shell.js +9 -5
- package/dist/ui/shared/app-store.js +71 -1
- package/dist/ui/shared/app-stream.js +27 -1
- package/dist/ui/shared/app.css +94 -4
- package/dist/ui/shared/app.js +22 -1
- package/dist/ui/shared/office/guild.png +0 -0
- package/dist/ui/shared/office/school.png +0 -0
- package/dist/ui/shared/panel-view.js +339 -17
- package/dist/ui/shared/signin.js +12 -1
- package/dist/ui/shared/ui-icons.js +3 -0
- package/dist/ui/shared/usage-dialog.js +18 -1
- package/dist/ui/shared/view-history.css +17 -0
- package/dist/ui/shared/view-history.js +438 -0
- package/dist/ui/shared/view-strings.js +42 -0
- package/dist/ui/shared/web-authorize.js +3 -1
- package/dist/unattended-install.d.ts +6 -0
- package/dist/unattended-install.js +79 -0
- package/dist/unattended-install.js.map +1 -0
- package/dist/usage/capacity.d.ts +69 -0
- package/dist/usage/capacity.js +119 -0
- package/dist/usage/capacity.js.map +1 -0
- package/dist/usage/i18n-keys.d.ts +1 -1
- package/dist/usage/i18n-keys.js +3 -0
- package/dist/usage/i18n-keys.js.map +1 -1
- package/dist/usage/profile-timeline.d.ts +16 -0
- package/dist/usage/profile-timeline.js +58 -0
- package/dist/usage/profile-timeline.js.map +1 -0
- package/dist/usage/quota-history.d.ts +43 -0
- package/dist/usage/quota-history.js +168 -0
- package/dist/usage/quota-history.js.map +1 -0
- package/dist/usage/usage-api.d.ts +31 -0
- package/dist/usage/usage-api.js +80 -1
- package/dist/usage/usage-api.js.map +1 -1
- package/dist/usage-report.d.ts +25 -1
- package/dist/usage-report.js +55 -4
- package/dist/usage-report.js.map +1 -1
- package/dist/usage-rollup.d.ts +4 -1
- package/dist/usage-rollup.js +12 -3
- package/dist/usage-rollup.js.map +1 -1
- package/dist/view-api.d.ts +31 -0
- package/dist/view-api.js +186 -41
- package/dist/view-api.js.map +1 -1
- package/dist/view-history.d.ts +84 -0
- package/dist/view-history.js +116 -0
- package/dist/view-history.js.map +1 -0
- package/dist/web-api.d.ts +32 -0
- package/dist/web-api.js +195 -5
- package/dist/web-api.js.map +1 -1
- package/dist/web-auth.js +7 -1
- package/dist/web-auth.js.map +1 -1
- package/dist/web-chat-history.d.ts +20 -0
- package/dist/web-chat-history.js +21 -0
- package/dist/web-chat-history.js.map +1 -1
- package/dist/web-guest-access.js +5 -1
- package/dist/web-guest-access.js.map +1 -1
- package/dist/web-login.d.ts +6 -3
- package/dist/web-login.js +26 -15
- package/dist/web-login.js.map +1 -1
- package/dist/web-notices.d.ts +51 -0
- package/dist/web-notices.js +118 -0
- package/dist/web-notices.js.map +1 -0
- package/dist/web-office-pack-tools.d.ts +1 -1
- package/dist/web-office-pack-tools.js +1 -1
- package/dist/web-office-pack-tools.js.map +1 -1
- package/dist/web-only-fleet.d.ts +22 -0
- package/dist/web-only-fleet.js +13 -0
- package/dist/web-only-fleet.js.map +1 -0
- package/dist/web-preview.d.ts +32 -3
- package/dist/web-preview.js +52 -19
- package/dist/web-preview.js.map +1 -1
- package/dist/web-session-authorization.d.ts +13 -2
- package/dist/web-session-authorization.js +10 -3
- package/dist/web-session-authorization.js.map +1 -1
- package/dist/web-session.d.ts +12 -0
- package/dist/web-session.js +22 -8
- package/dist/web-session.js.map +1 -1
- package/dist/web-settings-patch.d.ts +3 -1
- package/dist/web-settings-patch.js +4 -1
- package/dist/web-settings-patch.js.map +1 -1
- package/dist/web-shell-routes.d.ts +2 -0
- package/dist/web-shell-routes.js +3 -0
- package/dist/web-shell-routes.js.map +1 -1
- package/launcher/agend-install.cjs +16 -2
- package/launcher/private-home.cjs +1 -1
- package/package.json +1 -1
package/dist/ui/chat-store.js
CHANGED
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
// navigation — it counts as use, as the old click did). Coming back to it reads nothing; the stream keeps it current.
|
|
10
10
|
import "./chat-render.js";
|
|
11
11
|
import { createLiveAgentReplies } from "./chat-live-replies.js";
|
|
12
|
+
/** A message's identity on this page (ids restart with each fleet process; the boot makes them unique). */
|
|
13
|
+
const msgKeyOf = (m) => `${m.boot}-${m.id}`;
|
|
12
14
|
|
|
13
15
|
const R = () => globalThis.AgendChatRender;
|
|
14
16
|
|
|
@@ -43,7 +45,7 @@ const COMPACT_PAGE_LIMIT_MS = 11 * 60_000;
|
|
|
43
45
|
export function createChatStore(deps) {
|
|
44
46
|
const s = {
|
|
45
47
|
msgs: {}, drafts: {}, replies: {}, pendingFiles: {}, sending: {}, outgoing: {}, instanceStatus: {}, waking: {},
|
|
46
|
-
prompts: {}, workingSince: {}, stopping: {}, cancelling: {}, scrollMemo: {}, historyRead: new Set(), allHidden: new Set(), rbBusy: new Set(),
|
|
48
|
+
prompts: {}, workingSince: {}, stopping: {}, cancelling: {}, scrollMemo: {}, historyRead: new Set(), gapped: new Set(), allHidden: new Set(), rbBusy: new Set(),
|
|
47
49
|
pollMine: readPollMine(),
|
|
48
50
|
// #1823 (#1828 review): the server's clock minus this device's. A poll's deadline is the server's; a page whose clock
|
|
49
51
|
// is a few seconds off would count a 10 s poll wrong. Only the server's explicit `clock` sets it (#1830 review): a
|
|
@@ -161,6 +163,9 @@ export function createChatStore(deps) {
|
|
|
161
163
|
async function openHistory(name, lease, signal) {
|
|
162
164
|
if (s.historyRead.has(name) || signal?.aborted) return false;
|
|
163
165
|
s.historyRead.add(name);
|
|
166
|
+
// M-2260: after a gap, what this chat held when the read began may sit across a hole: the answer replaces it, and
|
|
167
|
+
// only what arrived while the read was out (the live stream) is kept beside it — exactly what a reload would show.
|
|
168
|
+
const stale = s.gapped.has(name) ? new Set((s.msgs[name] || []).map(msgKeyOf)) : null;
|
|
164
169
|
try {
|
|
165
170
|
const r = await api("GET", `/ui/history?instance=${encodeURIComponent(name)}&limit=200`, undefined, signal);
|
|
166
171
|
if (signal?.aborted) { s.historyRead.delete(name); return false; }
|
|
@@ -169,7 +174,10 @@ export function createChatStore(deps) {
|
|
|
169
174
|
// #2848: a guest's chat whose every message is hidden from it (between agents): its empty chat says so.
|
|
170
175
|
if (r.allHidden === true) s.allHidden.add(name); else s.allHidden.delete(name);
|
|
171
176
|
for (const m of r.messages) liveReplies.message(m);
|
|
172
|
-
|
|
177
|
+
if (stale && s.gapped.has(name)) {
|
|
178
|
+
s.gapped.delete(name);
|
|
179
|
+
s.msgs[name] = R().mergeMessages((s.msgs[name] || []).filter((m) => !stale.has(msgKeyOf(m))), r.messages, 500);
|
|
180
|
+
} else s.msgs[name] = R().mergeMessages(s.msgs[name], r.messages, 500);
|
|
173
181
|
settleOutgoing(name);
|
|
174
182
|
changed(name, "msgs");
|
|
175
183
|
} catch { s.historyRead.delete(name); /* the live stream still works; the next visit tries again */ }
|
|
@@ -456,6 +464,13 @@ export function createChatStore(deps) {
|
|
|
456
464
|
const isPasted = (f) => pastedTexts.has(f);
|
|
457
465
|
/** Typing stays local. */
|
|
458
466
|
function setDraft(target, value) { s.drafts[target] = value; }
|
|
467
|
+
/** #2911: a line added to the composer for the person to edit (after what is typed, on its own line; never twice). */
|
|
468
|
+
function addToDraft(target, line) {
|
|
469
|
+
const d = s.drafts[target] || "";
|
|
470
|
+
if (!target || !line || d.split("\n").includes(line)) return;
|
|
471
|
+
s.drafts[target] = d ? `${d.replace(/\n+$/, "")}\n${line}` : line;
|
|
472
|
+
changed(target);
|
|
473
|
+
}
|
|
459
474
|
/**
|
|
460
475
|
* #2272: reply to a message of this chat — what the bar above the composer shows (its sender and first words); only
|
|
461
476
|
* its row is sent. One at a time per chat: a new one replaces it.
|
|
@@ -550,8 +565,17 @@ export function createChatStore(deps) {
|
|
|
550
565
|
/** Whether a command can start for `target` now (none is out): checked before a typed command line is consumed. */
|
|
551
566
|
const commandFree = (target) => !(s.commands[target] && s.commands[target].busy);
|
|
552
567
|
|
|
568
|
+
/**
|
|
569
|
+
* M-2260: the catch-up after a long absence brought only the newest messages ("gap"): every chat this page holds may
|
|
570
|
+
* have a hole. Each reads its newest history again — the one on screen now, the others when they are next opened.
|
|
571
|
+
*/
|
|
572
|
+
function onGap() {
|
|
573
|
+
for (const name of new Set([...Object.keys(s.msgs), ...s.historyRead])) { s.historyRead.delete(name); s.gapped.add(name); }
|
|
574
|
+
if (s.current) void openHistory(s.current);
|
|
575
|
+
}
|
|
553
576
|
/** Wire the store to the app's stream, for the life of the page. */
|
|
554
577
|
function attach(stream) {
|
|
578
|
+
stream.on("gap", onGap);
|
|
555
579
|
stream.on("status", applyStatus);
|
|
556
580
|
stream.on("activity", applyActivity);
|
|
557
581
|
stream.on("message", ingest);
|
|
@@ -571,11 +595,11 @@ export function createChatStore(deps) {
|
|
|
571
595
|
subscribe(fn) { subs.add(fn); return () => subs.delete(fn); },
|
|
572
596
|
setCurrent(name) { s.current = name; },
|
|
573
597
|
onAgentReply: liveReplies.subscribe,
|
|
574
|
-
attach, applyStatus, applyActivity, ingest, applyDeliveries, openHistory,
|
|
598
|
+
attach, applyStatus, applyActivity, ingest, applyDeliveries, openHistory, onGap,
|
|
575
599
|
onPrompt, applyPrompts, resolvePrompt, answerPrompt, answerByNonce, holdPrompt, releasePrompt, promptsFor,
|
|
576
600
|
applyReplyButtons, clickReplyButton,
|
|
577
601
|
uploadFile, send, retryOutgoing, deleteOutgoing, putBackOutgoing, threadRows, addFiles, removeFile, attachPastedText, fileBackAsText, isPasted, setDraft,
|
|
578
|
-
startReply, clearReply, findQuoted,
|
|
602
|
+
startReply, clearReply, findQuoted, addToDraft,
|
|
579
603
|
cancelReply, isUser, runCommand, dismissCommand, commandFree,
|
|
580
604
|
allHidden: (name) => s.allHidden.has(name),
|
|
581
605
|
};
|
package/dist/ui/chat-strings.js
CHANGED
|
@@ -56,6 +56,25 @@ register("chat", {
|
|
|
56
56
|
pvOpenPanel: "Open in panel", pvInPanel: "Shown in the panel.", pvPanel: "HTML preview", pvSource: "From {0}, {1} — show the message",
|
|
57
57
|
pvPage: "Page", pvCode: "Code", pvReload: "Reload", pvClose: "Close the panel", pvBack: "Back to the chat", pvResize: "Resize the preview panel",
|
|
58
58
|
pvNewer: "A newer version is in the reply from {0}.", pvNewerShow: "Show it", pvPanelIdle: "Click Preview to run it here.",
|
|
59
|
+
// #2911 (M-2216): the panel's versions and their changes.
|
|
60
|
+
pvVersions: "Versions", pvVer: "v{0}/{1}", pvVerLong: "Version {0} of {1}", pvVerPrev: "Previous version", pvVerNext: "Next version",
|
|
61
|
+
pvChanges: "Changes", pvChangesFrom: "Changes from v{0}", pvChangesNone: "No changes from v{0}.",
|
|
62
|
+
pvPagesCount: "Pages ({0})", pvPagesTitle: "Every page in this conversation", pvPagesUntitled: "Untitled page", pvPagesVersions: "{0} versions",
|
|
63
|
+
pvWidth: "Width the page is shown at", pvWidthFull: "Full width", pvWidthTablet: "Tablet · 768", pvWidthPhone: "Phone · 390",
|
|
64
|
+
pvFs: "Full screen", pvFsExit: "Leave full screen (Esc)",
|
|
65
|
+
pvChangesBig: "These versions are too large or too different to compare here. Download both to compare them.", pvChangesCut: "showing the first {0} lines",
|
|
66
|
+
// #2911 (M-2217): the preview's feedback actions.
|
|
67
|
+
pvFbReply: "Reply about this page", pvFbReplyTitle: "Reply to the message this page came in, naming the page — you write what to change",
|
|
68
|
+
pvFbAboutNamed: "About the page “{0}”: ", pvFbAboutThis: "About this page: ",
|
|
69
|
+
pvFbErrOne: "error", pvFbErrMany: "errors", pvFbErrTitle: "The page reported errors: put them in the message box, quoted, for you to check and send. Their text is the page's",
|
|
70
|
+
pvFbErrHeadOne: "The preview reported 1 error (text from the page):", pvFbErrHead: "The preview reported {0} errors (text from the page):",
|
|
71
|
+
pvFbErrRejection: "Unhandled rejection: ", pvFbErrAt: "line {0}:{1}", pvFbErrMore: "…and {0} more.",
|
|
72
|
+
// #2911 (7, M-2216): CDN libraries in previews.
|
|
73
|
+
pvCdnBlocked: "Blocked by the preview: ",
|
|
74
|
+
pvCdnNote: "This page loads a library from {0}, which previews block unless Settings → Web dashboard → “HTML previews may load libraries from cdnjs / jsDelivr” is on.",
|
|
75
|
+
pvFbPick: "Point at it", pvFbPickTitle: "Click an element in the page to say what to change about it",
|
|
76
|
+
pvFbPickCancel: "Cancel pointing", pvFbPickCancelTitle: "Stop pointing — the page takes clicks again",
|
|
77
|
+
pvFbPickWords: "On `{0}` (“{1}”, text from the page): ", pvFbPickAt: "On `{0}` (from the page): ",
|
|
59
78
|
lbLabel: "Image: {0}", lbLabelNoName: "Image", lbClose: "Close", lbOpenOriginal: "Open original", lbPrev: "Previous image", lbNext: "Next image", lbCount: "{0} / {1}",
|
|
60
79
|
// #2333: zoom in the lightbox.
|
|
61
80
|
lbZoomIn: "Zoom in (+)", lbZoomOut: "Zoom out (−)", lbZoomLevel: "{0}% — click for fit or actual size (0 / 1)", lbFitWidth: "Fit width (W)",
|
|
@@ -91,7 +110,7 @@ register("chat", {
|
|
|
91
110
|
pvServer_notListening: "Previews are not available on this fleet right now.",
|
|
92
111
|
previews: "Allow HTML previews",
|
|
93
112
|
clicked: "Clicked:", clickShow: "Show the message these buttons were on",
|
|
94
|
-
copy: "Copy", copyMessage: "Copy this reply", copied: "Copied", copyFailed: "Copy failed", wrap: "Wrap", showAll: "Show all {0} lines", showMore: "Show more", showLess: "Show less", jumpLatest: "Latest", newBelow: "{0} new",
|
|
113
|
+
copy: "Copy", copyMessage: "Copy this reply", copied: "Copied", copyFailed: "Copy failed", wrap: "Wrap", showAll: "Show all {0} lines", showMore: "Show more", showLess: "Show less", showAllShort: "All {0} lines", showMoreShort: "More", jumpLatest: "Latest", newBelow: "{0} new",
|
|
95
114
|
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",
|
|
96
115
|
promptAnswering: "Answering…", promptGone: "This prompt is no longer open", promptTextPlaceholder: "Your answer", promptTextSend: "Send answer", promptFor: "{0}: {1}",
|
|
97
116
|
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",
|
|
@@ -101,7 +120,7 @@ register("chat", {
|
|
|
101
120
|
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",
|
|
102
121
|
details: "Instance details", fullWidth: "Full width", start: "Start instance", stopInstance: "Stop instance", restart: "Restart instance", delete: "Delete instance…",
|
|
103
122
|
started: "{0} started", stoppedInst: "{0} stopped", restarted: "{0} restarted", instanceDeleted: "{0} was deleted",
|
|
104
|
-
deleteTitle: "Delete {0}?", deleteHelp: "This removes the instance from the fleet and stops it. Its working directory is kept. Type “delete {0}” to confirm.", deleteGo: "Delete", cancel: "Cancel",
|
|
123
|
+
deleteTitle: "Delete {0}?", deleteHelp: "This removes the instance from the fleet and stops it, and deletes its chat topic (on Discord, its channel and history). Its working directory is kept. Type “delete {0}” to confirm.", deleteGo: "Delete", cancel: "Cancel",
|
|
105
124
|
loadFailed: "Failed to load", tour: "Chat tour", tourBtnTitle: "Show the chat tour on an agent's chat", tourNoAgent: "Choose an agent before opening the chat tour.",
|
|
106
125
|
dName: "Name", dStatus: "Status", dDisplay: "Display name", dDescription: "Description", dDirectory: "Directory", dBackend: "Backend", dModel: "Model", dEffort: "Effort",
|
|
107
126
|
dCost: "Cost", dCostNote: "(session accumulated)", dContext: "Context", dRate5: "Rate 5h", dRate7: "Rate 7d", dActivity: "Recent activity", dInstance: "Instance", dRuntime: "Runtime",
|
|
@@ -180,6 +199,23 @@ register("chat", {
|
|
|
180
199
|
pvOpenPanel: "在面板中開啟", pvInPanel: "已在面板中顯示。", pvPanel: "HTML 預覽", pvSource: "來自 {0},{1}——顯示該訊息",
|
|
181
200
|
pvPage: "頁面", pvCode: "程式碼", pvReload: "重新載入", pvClose: "關閉面板", pvBack: "回到對話", pvResize: "調整預覽面板寬度",
|
|
182
201
|
pvNewer: "{0} 的回覆裡有較新的版本。", pvNewerShow: "顯示", pvPanelIdle: "按「預覽」在這裡執行。",
|
|
202
|
+
pvVersions: "版本", pvVer: "v{0}/{1}", pvVerLong: "第 {0} 版,共 {1} 版", pvVerPrev: "上一版", pvVerNext: "下一版",
|
|
203
|
+
pvChanges: "變更", pvChangesFrom: "與第 {0} 版相比的變更", pvChangesNone: "與第 {0} 版相比沒有變更。",
|
|
204
|
+
pvPagesCount: "頁面({0})", pvPagesTitle: "這段對話中的所有頁面", pvPagesUntitled: "未命名頁面", pvPagesVersions: "{0} 個版本",
|
|
205
|
+
pvWidth: "頁面顯示寬度", pvWidthFull: "完整寬度", pvWidthTablet: "平板 · 768", pvWidthPhone: "手機 · 390",
|
|
206
|
+
pvFs: "全螢幕", pvFsExit: "離開全螢幕(Esc)",
|
|
207
|
+
pvChangesBig: "這兩版太大或差異太多,無法在這裡比較。請下載兩版後再比較。", pvChangesCut: "只顯示前 {0} 行",
|
|
208
|
+
// #2911 (M-2217): 預覽的回饋動作。
|
|
209
|
+
pvFbReply: "回覆這個頁面", pvFbReplyTitle: "回覆這個頁面所在的訊息並註明頁面——要改什麼由你來寫",
|
|
210
|
+
pvFbAboutNamed: "關於頁面「{0}」:", pvFbAboutThis: "關於這個頁面:",
|
|
211
|
+
pvFbErrOne: "個錯誤", pvFbErrMany: "個錯誤", pvFbErrTitle: "頁面回報了錯誤:以引用的方式放進輸入框,由你確認後送出。錯誤文字來自頁面本身",
|
|
212
|
+
pvFbErrHeadOne: "預覽回報了 1 個錯誤(文字來自頁面):", pvFbErrHead: "預覽回報了 {0} 個錯誤(文字來自頁面):",
|
|
213
|
+
pvFbErrRejection: "未處理的 rejection:", pvFbErrAt: "第 {0} 行第 {1} 欄", pvFbErrMore: "……還有 {0} 個。",
|
|
214
|
+
pvCdnBlocked: "被預覽封鎖:",
|
|
215
|
+
pvCdnNote: "這個頁面要從 {0} 載入函式庫,但預覽會封鎖它,除非開啟 設定 → 網頁儀表板 中的「HTML 預覽可從 cdnjs / jsDelivr 載入函式庫」。",
|
|
216
|
+
pvFbPick: "指出位置", pvFbPickTitle: "在頁面上點一個元素,說明要怎麼改",
|
|
217
|
+
pvFbPickCancel: "取消指出", pvFbPickCancelTitle: "停止指出——頁面恢復接收點擊",
|
|
218
|
+
pvFbPickWords: "在 `{0}`(「{1}」,文字來自頁面):", pvFbPickAt: "在 `{0}`(來自頁面):",
|
|
183
219
|
lbLabel: "圖片:{0}", lbLabelNoName: "圖片", lbClose: "關閉", lbOpenOriginal: "開啟原圖", lbPrev: "上一張", lbNext: "下一張", lbCount: "{0} / {1}",
|
|
184
220
|
lbZoomIn: "放大(+)", lbZoomOut: "縮小(−)", lbZoomLevel: "{0}%:點一下切換「符合視窗」與「實際大小」(0 / 1)", lbFitWidth: "符合寬度(W)",
|
|
185
221
|
pvWhyOptin: "還沒允許 HTML 預覽。可從這張卡片的 ⋯ 選單或「設定 → 偏好設定」允許。",
|
|
@@ -213,7 +249,7 @@ register("chat", {
|
|
|
213
249
|
pvServer_notListening: "這個 fleet 目前無法預覽。",
|
|
214
250
|
previews: "允許預覽 HTML",
|
|
215
251
|
clicked: "點選了:", clickShow: "顯示這些按鈕所在的訊息",
|
|
216
|
-
copy: "複製", copyMessage: "複製這則回覆", copied: "已複製", copyFailed: "複製失敗", wrap: "換行", showAll: "展開全部 {0} 行", showMore: "顯示更多", showLess: "收合", jumpLatest: "最新", newBelow: "{0} 則新訊息",
|
|
252
|
+
copy: "複製", copyMessage: "複製這則回覆", copied: "已複製", copyFailed: "複製失敗", wrap: "換行", showAll: "展開全部 {0} 行", showMore: "顯示更多", showLess: "收合", showAllShort: "全部 {0} 行", showMoreShort: "更多", jumpLatest: "最新", newBelow: "{0} 則新訊息",
|
|
217
253
|
disconnected: "已斷線", tick_queued: "排在前一則訊息之後", tick_processing: "已交給 agent", tick_delivered: "agent 已收到", tick_failed: "未送達", tick_cancelled: "已被中止取消", reactedBy: "{0} 的回應",
|
|
218
254
|
promptAnswering: "回覆中…", promptGone: "這個提示已經結束", promptTextPlaceholder: "你的回答", promptTextSend: "送出回答", promptFor: "{0}:{1}",
|
|
219
255
|
working: "{0} 處理中…", stuck: "{0} 似乎卡住了", stopReply: "中止回覆", stopReplyTitle: "中止目前的回覆(同 Esc),並取消還在排隊的訊息。instance 會繼續運行。", stopSent: "已中止",
|
|
@@ -223,7 +259,7 @@ register("chat", {
|
|
|
223
259
|
pick: "選一個 Agent 開始聊天。", noAgents: "先建立一個 Agent,再開始聊天。", newAgent: "新增 Agent", notFound: "沒有叫「{0}」的 instance", notFoundHint: "它可能已被刪除或改名。", backToList: "選擇 Agent",
|
|
224
260
|
details: "Instance 詳細資料", fullWidth: "全寬", start: "啟動 instance", stopInstance: "停止 instance", restart: "重新啟動 instance", delete: "刪除 instance…",
|
|
225
261
|
started: "{0} 已啟動", stoppedInst: "{0} 已停止", restarted: "{0} 已重新啟動", instanceDeleted: "{0} 已刪除",
|
|
226
|
-
deleteTitle: "要刪除 {0} 嗎?", deleteHelp: "這會把 instance 從 fleet
|
|
262
|
+
deleteTitle: "要刪除 {0} 嗎?", deleteHelp: "這會把 instance 從 fleet 移除並停止它,並刪除它的聊天 topic(在 Discord 上是它的頻道與訊息記錄)。工作目錄會保留。請輸入「delete {0}」確認。", deleteGo: "刪除", cancel: "取消",
|
|
227
263
|
loadFailed: "載入失敗", tour: "聊天導覽", tourBtnTitle: "在 Agent 的聊天上開啟聊天導覽", tourNoAgent: "請先選一個 Agent,再開啟聊天導覽。",
|
|
228
264
|
dName: "名稱", dStatus: "狀態", dDisplay: "顯示名稱", dDescription: "說明", dDirectory: "目錄", dBackend: "Backend", dModel: "模型", dEffort: "Effort",
|
|
229
265
|
dCost: "花費", dCostNote: "(本次 session 累計)", dContext: "Context", dRate5: "5 小時額度", dRate7: "7 天額度", dActivity: "最近活動", dInstance: "Instance", dRuntime: "執行環境",
|
package/dist/ui/chat-thread.js
CHANGED
|
@@ -31,8 +31,14 @@ export const TEXT_PREVIEW_COLUMNS = 50;
|
|
|
31
31
|
export const TEXT_CARD_MAX = 1024 * 1024;
|
|
32
32
|
const P = () => globalThis.AgendPreview;
|
|
33
33
|
/** #1672: a status line longer than this (lines, or characters) is folded until it is opened. */
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
/**
|
|
35
|
+
* #1672 / the user (hmsg-d6ac6dfb): a status line shows two lines — its header ("A → B:") and the first line of the
|
|
36
|
+
* message, each one row (clipped with an ellipsis). Its "open" control shows when that hides something: more lines than
|
|
37
|
+
* two, or a row clipped at the width it is drawn at (measured after each draw and resize: fitStatus). Where nothing is
|
|
38
|
+
* measured (no layout), a line longer than STATUS_LINE_CHARS stands for "clipped".
|
|
39
|
+
*/
|
|
40
|
+
export const STATUS_FOLD_LINES = 2;
|
|
41
|
+
export const STATUS_LINE_CHARS = 100;
|
|
36
42
|
const REPLY = '<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"><path d="M9 14 4 9l5-5"/><path d="M4 9h10.5a5.5 5.5 0 0 1 0 11H11"/></svg>';
|
|
37
43
|
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>';
|
|
38
44
|
|
|
@@ -224,15 +230,19 @@ export function createThread(list, scroller, opts) {
|
|
|
224
230
|
*/
|
|
225
231
|
function statusHtml(x, time) {
|
|
226
232
|
const text = String(x.text == null ? "" : x.text);
|
|
227
|
-
|
|
233
|
+
// Rows split on any line break (a CRLF is one break, never a row and a stray CR); the text itself is not changed.
|
|
234
|
+
const lines = R().lineCount(text.replace(/\r\n?/g, "\n"));
|
|
228
235
|
const manyLines = lines > STATUS_FOLD_LINES;
|
|
229
|
-
const
|
|
230
|
-
|
|
231
|
-
|
|
236
|
+
const overflows = manyLines || text.split(/\r\n?|\n/).some(l => l.length > STATUS_LINE_CHARS);
|
|
237
|
+
// Its control: a short label at the end of line 2; its full name (and whether it is open) for a screen reader. Shown
|
|
238
|
+
// only while the line overflows (class `overflows`, kept by fitStatus at the width it is drawn at).
|
|
239
|
+
const more = `<button data-trust="thread-control" type="button" class="st-more" data-act="toggleFold" aria-expanded="false"${manyLines ? ` data-lines="${escAttr(lines)}"` : ""} aria-label="${escAttr(manyLines ? trf("chat.showAll", lines) : tr("chat.showMore"))}">${escAttr(manyLines ? trf("chat.showAllShort", lines) : tr("chat.showMoreShort"))}</button>`;
|
|
232
240
|
const tools = `<div data-trust="thread-control" 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>`;
|
|
233
241
|
// #1990: the other instance's name opens its chat (an in-app link the router takes: no target).
|
|
234
|
-
|
|
235
|
-
|
|
242
|
+
// Each line its own row (folded: the first two, each clipped to one row); the line breaks stay in the text.
|
|
243
|
+
const body = textLinkSegments(text, x.links).map(sg => sg.href ? `<a data-trust="link" class="st-link" href="${escAttr(sg.href)}">${escAttr(sg.text)}</a>` : escAttr(sg.text)).join("")
|
|
244
|
+
.split(/\r\n?|\n/).map(l => `<span class="st-ln">${l}</span>`).join("\n");
|
|
245
|
+
return `<div class="msg status-line folded${overflows ? " overflows" : ""}"><div class="meta">${time}</div><div class="body"><div class="st-text">${body}</div>${more}</div>${tools}</div>`;
|
|
236
246
|
}
|
|
237
247
|
/**
|
|
238
248
|
* #1266: an agent reply's buttons. Labels are text (escaped, never Markdown); the click names the set and the index,
|
|
@@ -610,7 +620,25 @@ export function createThread(list, scroller, opts) {
|
|
|
610
620
|
}
|
|
611
621
|
// Sizes change without a scroll event (layout after the render, the dock under the thread): while pinned, the view
|
|
612
622
|
// stays flush with the bottom; a reader who scrolled up is never moved.
|
|
613
|
-
const resized = () => { if (pinned && !atBottom()) toBottom(); reportJump(); };
|
|
623
|
+
const resized = () => { fitStatus(); if (pinned && !atBottom()) toBottom(); reportJump(); };
|
|
624
|
+
/**
|
|
625
|
+
* The user (hmsg-d6ac6dfb): a folded status line whose first two rows fit is plain two lines, no control; one with a
|
|
626
|
+
* row clipped at the width it is drawn at gets its "open" control (`overflows`). Measured after each draw and resize;
|
|
627
|
+
* a line with more than two rows always overflows (data-lines). Nothing laid out (no width): the guess it was drawn with.
|
|
628
|
+
*/
|
|
629
|
+
function fitStatus() {
|
|
630
|
+
for (const m of list.querySelectorAll(".msg.status-line.folded")) {
|
|
631
|
+
const btn = m.querySelector(".st-more");
|
|
632
|
+
if (!btn || btn.dataset.lines) continue;
|
|
633
|
+
let measured = false, over = false;
|
|
634
|
+
for (const row of [...m.querySelectorAll(".st-ln")].slice(0, STATUS_FOLD_LINES)) {
|
|
635
|
+
if (!row.clientWidth) continue;
|
|
636
|
+
measured = true;
|
|
637
|
+
if (row.scrollWidth > row.clientWidth + 1) over = true;
|
|
638
|
+
}
|
|
639
|
+
if (measured && m.classList.contains("overflows") !== over) m.classList.toggle("overflows", over);
|
|
640
|
+
}
|
|
641
|
+
}
|
|
614
642
|
let ro = typeof ResizeObserver === "function" ? new ResizeObserver(resized) : null;
|
|
615
643
|
if (ro) { ro.observe(list); ro.observe(scroller); }
|
|
616
644
|
// #2158: the reader's own hand on the thread — a wheel, a touch, the scrollbar (a pointer held), a scrolling key
|
|
@@ -725,6 +753,7 @@ export function createThread(list, scroller, opts) {
|
|
|
725
753
|
armPolls();
|
|
726
754
|
rove();
|
|
727
755
|
progress();
|
|
756
|
+
fitStatus();
|
|
728
757
|
return added;
|
|
729
758
|
}
|
|
730
759
|
/**
|
|
@@ -802,13 +831,26 @@ export function createThread(list, scroller, opts) {
|
|
|
802
831
|
function onClick(e) {
|
|
803
832
|
if (onImageClick(e)) return;
|
|
804
833
|
const el = e.target && e.target.closest ? e.target.closest("[data-act]") : null;
|
|
805
|
-
if (!el || !list.contains(el))
|
|
834
|
+
if (!el || !list.contains(el)) {
|
|
835
|
+
// A folded status line opens by a click on its text too (not on a link in it); its button is the keyboard's way.
|
|
836
|
+
const text = e.target && e.target.closest ? e.target.closest(".msg.status-line.folded.overflows .st-text") : null;
|
|
837
|
+
const btn = text && !e.target.closest("a") ? text.parentNode.querySelector(".st-more") : null;
|
|
838
|
+
if (btn && list.contains(btn)) onClick({ target: btn });
|
|
839
|
+
return;
|
|
840
|
+
}
|
|
806
841
|
const act = el.dataset.act;
|
|
807
842
|
if (act === "copyMsg") { const x = msgs.find(y => msgKey(y) === el.dataset.arg); if (x) copyFrom(el, x.text); }
|
|
808
843
|
else if (act === "copyCode") { const pre = el.closest(".codeblock")?.querySelector("pre"); if (pre) copyFrom(el, pre.textContent); }
|
|
809
844
|
else if (act === "toggleFold") {
|
|
810
845
|
const box = el.closest(".codeblock, .msg.status-line"); if (!box) return;
|
|
811
846
|
const folded = box.classList.toggle("folded");
|
|
847
|
+
if (box.classList.contains("status-line")) {
|
|
848
|
+
// A status line's own label: short at the end of line 2 while folded, its full name for a screen reader.
|
|
849
|
+
el.setAttribute("aria-expanded", folded ? "false" : "true");
|
|
850
|
+
el.setAttribute("aria-label", !folded ? tr("chat.showLess") : el.dataset.lines ? trf("chat.showAll", el.dataset.lines) : tr("chat.showMore"));
|
|
851
|
+
el.textContent = !folded ? tr("chat.showLess") : el.dataset.lines ? trf("chat.showAllShort", el.dataset.lines) : tr("chat.showMoreShort");
|
|
852
|
+
return;
|
|
853
|
+
}
|
|
812
854
|
el.textContent = !folded ? tr("chat.showLess") : el.dataset.lines ? trf("chat.showAll", el.dataset.lines) : tr("chat.showMore");
|
|
813
855
|
} else if (act === "toggleWrap") opts.toggleWrap();
|
|
814
856
|
else if (act === "replyButton") {
|
package/dist/ui/fleet-canvas.js
CHANGED
|
@@ -207,7 +207,8 @@ const HEAD_REF_X = 10 - (10 * HEAD.BACK) / HEAD.LEN;
|
|
|
207
207
|
/**
|
|
208
208
|
* The key to the lines (#2052): a **Key** toggle in the toolbar; open, its rows are a strip between the toolbar and the
|
|
209
209
|
* canvas — never over the canvas, so it covers no card (it used to sit in the canvas's bottom-left corner, over the
|
|
210
|
-
* team there at the opening view). Folded until the person opens it; kept per device.
|
|
210
|
+
* team there at the opening view). Folded until the person opens it; kept per device. The lines' key: the Office has its
|
|
211
|
+
* own, for the items it carries (fleet-office.js OfficeKey, #2946).
|
|
211
212
|
*/
|
|
212
213
|
function readKeyOpen() { try { return localStorage.getItem(KEY_KEY) === "1"; } catch { return false; } }
|
|
213
214
|
function KeyToggle({ open, onToggle }) {
|
|
@@ -490,13 +491,13 @@ export function OrgCanvas({ org, app, lease }) {
|
|
|
490
491
|
${mode !== "list" ? html`<label class="s-search oc-find"><${Icon} name="search" size=${14} /><span class="sr-only">${tc("find")}</span>
|
|
491
492
|
<input type="search" value=${query} placeholder=${tc("findShort")} autocomplete="off" onInput=${(e) => setQuery(e.target.value)} onKeyDown=${onFind} />
|
|
492
493
|
${q ? html`<span class="oc-found" role="status">${found ? (found === 1 ? tc("foundOne") : tc("found", found)) : tc("noMatch")}</span>` : null}</label>` : null}
|
|
493
|
-
${mode
|
|
494
|
+
${mode === "canvas" && live ? html`<${KeyToggle} open=${keyOpen} onToggle=${toggleKey} />` : null}
|
|
494
495
|
<button type="button" class=${`btn btn-sm oc-live${live ? " on" : ""}`} aria-pressed=${live ? "true" : "false"} title=${live ? tc("liveOn") : tc("liveOff")}
|
|
495
496
|
onClick=${() => setLive((v) => !v)}><span class="oc-live-dot" aria-hidden="true"></span>${tc("live")}</button>
|
|
496
497
|
<div class="seg-inline" role="group" aria-label=${tc("view")}>${modeBtn("canvas")}${modeBtn("list")}${modeBtn("office")}</div>
|
|
497
498
|
</div>
|
|
498
499
|
</div>
|
|
499
|
-
${mode
|
|
500
|
+
${mode === "canvas" && live && keyOpen ? html`<${Legend} />` : null}
|
|
500
501
|
${mode === "list" ? html`<${OrgChart} org=${org} app=${app} bare=${true} sending=${sending} />`
|
|
501
502
|
: mode === "office" ? html`<${Office} view=${view} traffic=${live ? traffic : null} q=${q} lease=${lease} onOpen=${openChat} />`
|
|
502
503
|
: html`<${Canvas} view=${view} folded=${folded} onFold=${onFold} q=${q} jump=${jump} traffic=${live ? traffic : null} />`}
|
|
@@ -322,47 +322,80 @@ export function along(pts, t) {
|
|
|
322
322
|
// ── Traffic: an item carried from desk to desk ──
|
|
323
323
|
|
|
324
324
|
/** px per ms along the path, the shortest and longest trip, the stagger between items that arrive together. */
|
|
325
|
-
|
|
325
|
+
/**
|
|
326
|
+
* The items' flight: SPEED px/ms along the corridors, at least MIN_MS (#2946: under a second read as a glitch) and at
|
|
327
|
+
* most MAX_MS in the air; a burst's trips start STAGGER_MS apart. At most MAX are in the air or waiting to start: a
|
|
328
|
+
* burst over that flies its newest and drops the rest (they are on the Traffic view), so the room never lags behind a
|
|
329
|
+
* backlog of old events, and a trip under way is never cut short to make room.
|
|
330
|
+
*/
|
|
331
|
+
export const FLIGHT = { SPEED: 0.35, MIN_MS: 2500, MAX_MS: 4500, STAGGER_MS: 200, MAX: 12 };
|
|
326
332
|
const MAX_SEEN = 2000;
|
|
333
|
+
/** A trip's time in the air for a path `len` px long. */
|
|
334
|
+
export const flightMs = (len) => Math.min(FLIGHT.MAX_MS, Math.max(FLIGHT.MIN_MS, len / FLIGHT.SPEED));
|
|
335
|
+
/** How many new items may wait for their first frame (a frame not drawn for a while: the newest kept). */
|
|
336
|
+
const MAX_WAITING = 200;
|
|
327
337
|
/**
|
|
328
|
-
* The
|
|
329
|
-
*
|
|
330
|
-
*
|
|
331
|
-
*
|
|
332
|
-
*
|
|
338
|
+
* The trips' lifecycle, on whatever `pathOf(on, from, to)` routes them through (`on`: what `frame` is given — a layout,
|
|
339
|
+
* or All offices' tiles; null: not drawn there). `sync(items, now, still)`: each new item is seen once (`seen`,
|
|
340
|
+
* bounded: never flown again) and waits for the next frame — none when `still` (reduced motion) or for a stale
|
|
341
|
+
* delegation. `frame(now, on)`: first the waiting are admitted, #2946 — only those with a path here (all controllers
|
|
342
|
+
* get the whole fleet's traffic: the rest are not this one's to fly, and never take a place), and of those the newest
|
|
343
|
+
* that fit in `max` beside the trips still up (landed ones and those with no path any more pruned first), each batch
|
|
344
|
+
* staggered from when it came, one already past its flight time never flown late; then the trips under way
|
|
345
|
+
* { id, hue, kind, from, x, y, t }. A trip under way
|
|
346
|
+
* is never cut short to make room. `size()`: trips up or waiting.
|
|
333
347
|
*/
|
|
334
|
-
export function
|
|
348
|
+
export function createTrips(pathOf, { max = FLIGHT.MAX } = {}) {
|
|
335
349
|
const seen = new Set();
|
|
336
|
-
let trips = [];
|
|
350
|
+
let trips = [], waiting = [];
|
|
351
|
+
const route = (f, on) => { f.on = on; f.pts = pathOf(on, f.from, f.to); f.ms = f.pts ? flightMs(pathLength(f.pts)) : 0; };
|
|
337
352
|
return {
|
|
338
353
|
sync(items, now, still = false) {
|
|
339
|
-
let
|
|
354
|
+
let fresh = 0;
|
|
340
355
|
for (const it of items || []) {
|
|
341
356
|
if (seen.has(it.id)) continue;
|
|
342
357
|
seen.add(it.id);
|
|
343
358
|
if (seen.size > MAX_SEEN) seen.delete(seen.values().next().value);
|
|
344
359
|
if (still || it.phase === "stale") continue;
|
|
345
|
-
|
|
346
|
-
|
|
360
|
+
waiting.push({ id: it.id, from: it.from, to: it.to, kind: it.kind, hue: hue(it.kind), at: now });
|
|
361
|
+
fresh++;
|
|
347
362
|
}
|
|
348
|
-
if (
|
|
349
|
-
return
|
|
363
|
+
if (waiting.length > MAX_WAITING) waiting = waiting.slice(-MAX_WAITING);
|
|
364
|
+
return fresh;
|
|
350
365
|
},
|
|
351
|
-
frame(now,
|
|
352
|
-
|
|
366
|
+
frame(now, on) {
|
|
367
|
+
// First the trips up: routed again on a new layout, the ones with no path or landed dropped — so the room below is
|
|
368
|
+
// what is really free (#2977 review: after a gap with no frame, a hidden tab, landed trips held every place).
|
|
353
369
|
trips = trips.filter((f) => {
|
|
354
|
-
if (f.
|
|
355
|
-
|
|
356
|
-
const t = (now - f.start) / f.ms;
|
|
357
|
-
if (t >= 1) return false;
|
|
358
|
-
if (t >= 0) { const p = along(f.pts, t); out.push({ id: f.id, hue: f.hue, kind: f.kind, from: f.from, x: p.x, y: p.y, t }); }
|
|
359
|
-
return true;
|
|
370
|
+
if (f.on !== on) route(f, on);
|
|
371
|
+
return !!f.pts && (now - f.start) / f.ms < 1;
|
|
360
372
|
});
|
|
373
|
+
if (waiting.length) {
|
|
374
|
+
// Then the waiting with a path here and still due (one whose own flight time has passed since it came is stale:
|
|
375
|
+
// never flown late), the newest that fit, each batch staggered from when it came.
|
|
376
|
+
const ok = [];
|
|
377
|
+
for (const w of waiting) { const f = { ...w, start: w.at }; route(f, on); if (f.pts && now - w.at < f.ms) ok.push(f); }
|
|
378
|
+
waiting = [];
|
|
379
|
+
const room = max - trips.length, add = room > 0 ? ok.slice(-room) : []; // never slice(-0): that is all of them
|
|
380
|
+
const k = new Map();
|
|
381
|
+
for (const f of add) { const n = k.get(f.at) || 0; k.set(f.at, n + 1); f.start = f.at + n * FLIGHT.STAGGER_MS; trips.push(f); }
|
|
382
|
+
}
|
|
383
|
+
const out = [];
|
|
384
|
+
for (const f of trips) {
|
|
385
|
+
const t = (now - f.start) / f.ms;
|
|
386
|
+
if (t >= 0 && t < 1) { const p = along(f.pts, t); out.push({ id: f.id, hue: f.hue, kind: f.kind, from: f.from, x: p.x, y: p.y, t }); }
|
|
387
|
+
}
|
|
361
388
|
return out;
|
|
362
389
|
},
|
|
363
|
-
size: () => trips.length,
|
|
390
|
+
size: () => trips.length + waiting.length,
|
|
364
391
|
};
|
|
365
392
|
}
|
|
393
|
+
/**
|
|
394
|
+
* The items in the air in one office: each new one (a message, a delegation's work, its report coming back) carried from
|
|
395
|
+
* its sender's desk to its receiver's along layout L's corridors (createTrips; `frame(now, L)`; `from`: who sent it — a
|
|
396
|
+
* pack can draw it as theirs). A trip whose desks are not drawn on L is not this office's.
|
|
397
|
+
*/
|
|
398
|
+
export const createFlights = (opts = {}) => createTrips((L, from, to) => (L ? walkPath(L, from, to) : null), opts);
|
|
366
399
|
|
|
367
400
|
/** How many delegations each instance is working on now (the folders on its desk). */
|
|
368
401
|
export function workOn(items) {
|
|
@@ -13,8 +13,7 @@
|
|
|
13
13
|
// contain); a tie keeps the narrower. The same input always gives the same picture.
|
|
14
14
|
// - Traffic between people in different tiles is a straight flight between their seats in overview coordinates
|
|
15
15
|
// (createCrossFlights); between two people in the same tile it is that tile's own (createFlights, along its corridors).
|
|
16
|
-
import { FLIGHT,
|
|
17
|
-
import { hue } from "./fleet-traffic.js";
|
|
16
|
+
import { FLIGHT, createTrips, desksOf, membersOf } from "./fleet-office-model.js";
|
|
18
17
|
|
|
19
18
|
/** The switcher's "All offices" value: '*' is never in a stored office's id (fleet-office-model.js officeOk). */
|
|
20
19
|
export const ALL_ID = "*all";
|
|
@@ -127,41 +126,10 @@ export function crossPath(parts, from, to) {
|
|
|
127
126
|
return a && z ? [a, z] : null;
|
|
128
127
|
}
|
|
129
128
|
|
|
130
|
-
const MAX_SEEN = 2000;
|
|
131
129
|
/**
|
|
132
|
-
* The items in the air between tiles — fleet-office-model.js
|
|
133
|
-
*
|
|
134
|
-
* t } in overview units;
|
|
130
|
+
* The items in the air between tiles — fleet-office-model.js createTrips (each new item once; none for a stale delegation
|
|
131
|
+
* or with reduced motion; at most `max` at once, newest first, of those with a crossing), on crossPath's straight line:
|
|
132
|
+
* `frame(now, parts)` → { id, hue, kind, from, x, y, t } in overview units; one with no crossing (both in one tile, or
|
|
133
|
+
* either gone) is not this one's.
|
|
135
134
|
*/
|
|
136
|
-
export
|
|
137
|
-
const seen = new Set();
|
|
138
|
-
let trips = [];
|
|
139
|
-
return {
|
|
140
|
-
sync(items, now, still = false) {
|
|
141
|
-
let k = 0, started = 0;
|
|
142
|
-
for (const it of items || []) {
|
|
143
|
-
if (seen.has(it.id)) continue;
|
|
144
|
-
seen.add(it.id);
|
|
145
|
-
if (seen.size > MAX_SEEN) seen.delete(seen.values().next().value);
|
|
146
|
-
if (still || it.phase === "stale") continue;
|
|
147
|
-
trips.push({ id: it.id, from: it.from, to: it.to, kind: it.kind, hue: hue(it.kind), start: now + k++ * FLIGHT.STAGGER_MS, parts: null, pts: null, ms: 0 });
|
|
148
|
-
started++;
|
|
149
|
-
}
|
|
150
|
-
if (trips.length > max) trips = trips.slice(trips.length - max);
|
|
151
|
-
return started;
|
|
152
|
-
},
|
|
153
|
-
frame(now, parts) {
|
|
154
|
-
const out = [];
|
|
155
|
-
trips = trips.filter((f) => {
|
|
156
|
-
if (f.parts !== parts) { f.parts = parts; f.pts = crossPath(parts, f.from, f.to); f.ms = f.pts ? Math.min(FLIGHT.MAX_MS, Math.max(FLIGHT.MIN_MS, pathLength(f.pts) / FLIGHT.SPEED)) : 0; }
|
|
157
|
-
if (!f.pts) return false;
|
|
158
|
-
const t = (now - f.start) / f.ms;
|
|
159
|
-
if (t >= 1) return false;
|
|
160
|
-
if (t >= 0) { const p = along(f.pts, t); out.push({ id: f.id, hue: f.hue, kind: f.kind, from: f.from, x: p.x, y: p.y, t }); }
|
|
161
|
-
return true;
|
|
162
|
-
});
|
|
163
|
-
return out;
|
|
164
|
-
},
|
|
165
|
-
size: () => trips.length,
|
|
166
|
-
};
|
|
167
|
-
}
|
|
135
|
+
export const createCrossFlights = ({ max = FLIGHT.MAX } = {}) => createTrips((parts, from, to) => (parts ? crossPath(parts, from, to) : null), { max });
|
|
@@ -154,10 +154,11 @@ const SET_STRIPS = [...POSES, "prop"];
|
|
|
154
154
|
const WALKS = ["walk", "walkDown", "walkUp", "walkSide"];
|
|
155
155
|
const BUILT_IN = new Set(["pixel", "studio"]);
|
|
156
156
|
/**
|
|
157
|
-
* Ids built in after packs could be uploaded under them (#2631: "cozy"): a new upload is
|
|
158
|
-
* kept under one before (`kept`) is still read back, listed and deletable, and wins the id
|
|
157
|
+
* Ids built in after packs could be uploaded under them (#2631: "cozy"; #2947: "guild", "school"): a new upload is
|
|
158
|
+
* refused, but a pack the host kept under one before (`kept`) is still read back, listed and deletable, and wins the id
|
|
159
|
+
* for the offices that name it.
|
|
159
160
|
*/
|
|
160
|
-
const RESERVED = new Set(["cozy"]);
|
|
161
|
+
const RESERVED = new Set(["cozy", "guild", "school"]);
|
|
161
162
|
const ID = /^[A-Za-z0-9_-]{1,40}$/;
|
|
162
163
|
const SRC = /^[A-Za-z0-9._/-]{1,200}$/;
|
|
163
164
|
const HEX = /^#[0-9a-f]{6}$/i;
|