@songsid/agend 2.3.3-beta.4 → 2.3.3-beta.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-cli.js +13 -112
- package/dist/agent-cli.js.map +1 -1
- package/dist/agent-endpoint.d.ts +0 -1
- package/dist/agent-endpoint.js +1 -9
- package/dist/agent-endpoint.js.map +1 -1
- package/dist/agent-whats-new.d.ts +3 -6
- package/dist/agent-whats-new.js +7 -18
- package/dist/agent-whats-new.js.map +1 -1
- package/dist/auth-api.d.ts +0 -7
- package/dist/auth-api.js +1 -15
- package/dist/auth-api.js.map +1 -1
- package/dist/backend/claude-code.js +4 -2
- package/dist/backend/claude-code.js.map +1 -1
- package/dist/backend/codex.d.ts +1 -0
- package/dist/backend/codex.js +106 -15
- package/dist/backend/codex.js.map +1 -1
- package/dist/backend/grok.js +57 -11
- package/dist/backend/grok.js.map +1 -1
- package/dist/backend/types.d.ts +35 -0
- package/dist/backend/types.js.map +1 -1
- package/dist/bundled-skills.d.ts +4 -6
- package/dist/bundled-skills.js +10 -14
- package/dist/bundled-skills.js.map +1 -1
- package/dist/channel/mcp-server.js +5 -5
- package/dist/channel/mcp-server.js.map +1 -1
- package/dist/channel/mcp-tools.d.ts +1 -3
- package/dist/channel/mcp-tools.js +4 -58
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/cli.js +3 -1
- package/dist/cli.js.map +1 -1
- package/dist/config-validator.js +6 -3
- package/dist/config-validator.js.map +1 -1
- package/dist/daemon.d.ts +17 -3
- package/dist/daemon.js +76 -107
- package/dist/daemon.js.map +1 -1
- package/dist/dashboard-live.d.ts +1 -1
- package/dist/dashboard-live.js +3 -3
- package/dist/dashboard-live.js.map +1 -1
- package/dist/dashboard-permissions.d.ts +1 -1
- package/dist/dashboard-permissions.js +1 -3
- package/dist/dashboard-permissions.js.map +1 -1
- package/dist/fleet-manager.d.ts +31 -44
- package/dist/fleet-manager.js +94 -227
- package/dist/fleet-manager.js.map +1 -1
- package/dist/general-knowledge/skills/credential-profiles/SKILL.md +11 -1
- package/dist/general-knowledge/skills/session-management/SKILL.md +6 -0
- package/dist/general-knowledge/skills/web-appearance/SKILL.md +13 -204
- package/dist/general-knowledge/skills/web-dashboard/SKILL.md +26 -63
- package/dist/instance-lifecycle.d.ts +6 -0
- package/dist/instance-lifecycle.js +20 -4
- package/dist/instance-lifecycle.js.map +1 -1
- package/dist/locale.js +14 -4
- package/dist/locale.js.map +1 -1
- package/dist/outbound-handlers.js +19 -7
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.d.ts +0 -62
- package/dist/outbound-schemas.js +3 -42
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/public-web-gateway.js +0 -9
- package/dist/public-web-gateway.js.map +1 -1
- package/dist/public-web-link.d.ts +2 -0
- package/dist/public-web-link.js +5 -0
- package/dist/public-web-link.js.map +1 -1
- package/dist/search/search-docs.d.ts +1 -1
- package/dist/search/search-docs.js +2 -2
- package/dist/search/search-docs.js.map +1 -1
- package/dist/settings-api.d.ts +6 -0
- package/dist/settings-api.js +14 -2
- package/dist/settings-api.js.map +1 -1
- package/dist/settings-change.js +3 -1
- package/dist/settings-change.js.map +1 -1
- package/dist/settings-confirmation.d.ts +17 -3
- package/dist/settings-confirmation.js +8 -5
- package/dist/settings-confirmation.js.map +1 -1
- package/dist/settings-http-confirmation.js +31 -1
- package/dist/settings-http-confirmation.js.map +1 -1
- package/dist/tool-permissions.js +3 -10
- package/dist/tool-permissions.js.map +1 -1
- package/dist/topic-commands.d.ts +27 -7
- package/dist/topic-commands.js +94 -36
- package/dist/topic-commands.js.map +1 -1
- package/dist/types.d.ts +2 -1
- package/dist/ui/chat-ambient.js +4 -4
- package/dist/ui/chat-commands.js +3 -2
- package/dist/ui/chat-store.js +4 -1
- package/dist/ui/chat-strings.js +12 -46
- package/dist/ui/chat-thread.js +51 -59
- package/dist/ui/fleet-office-skin.js +37 -14
- package/dist/ui/fleet-office.js +31 -7
- package/dist/ui/html-attachment.js +22 -8
- package/dist/ui/panel-chat.js +21 -50
- package/dist/ui/preview-panel.js +12 -21
- package/dist/ui/preview.js +10 -3
- package/dist/ui/search-dialog.js +30 -14
- package/dist/ui/settings-appearance.js +1 -3
- package/dist/ui/settings-confirm.js +18 -0
- package/dist/ui/settings-guests.js +13 -4
- package/dist/ui/settings-learn.js +1 -1
- package/dist/ui/settings-mods.js +5 -12
- package/dist/ui/settings-registry.js +0 -1
- package/dist/ui/settings-strings.js +6 -6
- package/dist/ui/shared/app-i18n.js +13 -2
- package/dist/ui/shared/app-prefs.js +17 -17
- package/dist/ui/shared/app-shell.js +47 -9
- package/dist/ui/shared/app-store.js +6 -2
- package/dist/ui/shared/app.css +19 -36
- package/dist/ui/shared/app.js +5 -4
- package/dist/ui/shared/instance-nav.js +9 -9
- package/dist/ui/shared/instance-switch.js +8 -2
- package/dist/ui/shared/panel-view.js +22 -8
- package/dist/ui/shared/view-strings.js +6 -0
- package/dist/usage/usage-api.d.ts +3 -0
- package/dist/usage/usage-api.js +5 -1
- package/dist/usage/usage-api.js.map +1 -1
- package/dist/view-api.d.ts +2 -0
- package/dist/view-api.js +64 -0
- package/dist/view-api.js.map +1 -1
- package/dist/web-api.d.ts +9 -17
- package/dist/web-api.js +255 -371
- package/dist/web-api.js.map +1 -1
- package/dist/web-appearance-tools.d.ts +0 -2
- package/dist/web-appearance-tools.js +2 -5
- package/dist/web-appearance-tools.js.map +1 -1
- package/dist/web-appearance-view.d.ts +0 -6
- package/dist/web-appearance-view.js +0 -1
- package/dist/web-appearance-view.js.map +1 -1
- package/dist/web-appearance.d.ts +14 -7
- package/dist/web-appearance.js +5 -6
- package/dist/web-appearance.js.map +1 -1
- package/dist/web-asset-parts.d.ts +1 -17
- package/dist/web-asset-parts.js +1 -15
- package/dist/web-asset-parts.js.map +1 -1
- package/dist/web-auth.js +1 -7
- package/dist/web-auth.js.map +1 -1
- package/dist/web-chat-history.d.ts +3 -7
- package/dist/web-chat-history.js +10 -17
- package/dist/web-chat-history.js.map +1 -1
- package/dist/web-commands.d.ts +9 -1
- package/dist/web-commands.js +13 -0
- package/dist/web-commands.js.map +1 -1
- package/dist/web-experimental.d.ts +2 -11
- package/dist/web-experimental.js +8 -36
- package/dist/web-experimental.js.map +1 -1
- package/dist/web-guest-access.d.ts +40 -2
- package/dist/web-guest-access.js +50 -14
- package/dist/web-guest-access.js.map +1 -1
- package/dist/web-mod-bundle.d.ts +2 -45
- package/dist/web-mod-bundle.js +15 -63
- package/dist/web-mod-bundle.js.map +1 -1
- package/dist/web-mod-store.d.ts +3 -48
- package/dist/web-mod-store.js +30 -97
- package/dist/web-mod-store.js.map +1 -1
- package/dist/web-prefs.d.ts +13 -0
- package/dist/web-prefs.js +38 -0
- package/dist/web-prefs.js.map +1 -1
- package/dist/web-preview.d.ts +6 -13
- package/dist/web-preview.js +12 -23
- package/dist/web-preview.js.map +1 -1
- package/dist/web-session.d.ts +11 -3
- package/dist/web-session.js +22 -11
- package/dist/web-session.js.map +1 -1
- package/dist/web-settings-patch.d.ts +1 -0
- package/dist/web-settings-patch.js +6 -3
- package/dist/web-settings-patch.js.map +1 -1
- package/dist/web-settings-tools.d.ts +0 -13
- package/dist/web-settings-tools.js +12 -84
- package/dist/web-settings-tools.js.map +1 -1
- package/package.json +1 -1
- package/dist/general-knowledge/skills/web-appearance/SKILL.canvas-worker-off.md +0 -376
- package/dist/general-knowledge/skills/web-dashboard/SKILL.canvas-worker-off.md +0 -672
- package/dist/ui/app-effects.js +0 -251
- package/dist/ui/chat-effect-posters.js +0 -42
- package/dist/ui/chat-effects.js +0 -167
- package/dist/ui/effect-trial-controller.js +0 -124
- package/dist/ui/effect-trial-offer.js +0 -2
- package/dist/ui/effect-trial-panel.js +0 -40
- package/dist/ui/effects-waker.js +0 -2
- package/dist/ui/settings-effects.js +0 -202
- package/dist/ui/settings-owner-effects.js +0 -82
- package/dist/ui/shared/effect-app-runtime.js +0 -299
- package/dist/ui/shared/effect-baseline.js +0 -64
- package/dist/ui/shared/effect-chat-runtime.js +0 -162
- package/dist/ui/shared/effect-cursor.js +0 -126
- package/dist/ui/shared/effect-display-metadata.js +0 -39
- package/dist/ui/shared/effect-frame.js +0 -146
- package/dist/ui/shared/effect-geometry.js +0 -137
- package/dist/ui/shared/effect-host-style.js +0 -10
- package/dist/ui/shared/effect-owner-regions.js +0 -74
- package/dist/ui/shared/effect-owner-source.js +0 -122
- package/dist/ui/shared/effect-policy.js +0 -151
- package/dist/ui/shared/effect-poster-reader.js +0 -98
- package/dist/ui/shared/effect-posters.js +0 -117
- package/dist/ui/shared/effect-profiler.js +0 -184
- package/dist/ui/shared/effect-profiles.js +0 -65
- package/dist/ui/shared/effect-registry.js +0 -545
- package/dist/ui/shared/effect-runtime-loader.js +0 -236
- package/dist/ui/shared/effect-safety.js +0 -153
- package/dist/ui/shared/effect-stock-snapshot.js +0 -43
- package/dist/ui/shared/effect-stock-trust.js +0 -217
- package/dist/ui/shared/effect-trial-shadow.js +0 -62
- package/dist/ui/shared/effect-trial-source.js +0 -103
- package/dist/ui/shared/effect-trial-stage.js +0 -170
- package/dist/ui/shared/effect-trial.css +0 -40
- package/dist/ui/shared/feature-flags.js +0 -127
- package/dist/ui/shared/owner-effect-prefs.js +0 -117
- package/dist/web-effect-bootstrap.d.ts +0 -12
- package/dist/web-effect-bootstrap.js +0 -49
- package/dist/web-effect-bootstrap.js.map +0 -1
- package/dist/web-effect-bundle.d.ts +0 -28
- package/dist/web-effect-bundle.js +0 -171
- package/dist/web-effect-bundle.js.map +0 -1
- package/dist/web-effect-canonical.d.ts +0 -8
- package/dist/web-effect-canonical.js +0 -27
- package/dist/web-effect-canonical.js.map +0 -1
- package/dist/web-effect-library.d.ts +0 -114
- package/dist/web-effect-library.js +0 -530
- package/dist/web-effect-library.js.map +0 -1
- package/dist/web-effect-playback.d.ts +0 -30
- package/dist/web-effect-playback.js +0 -71
- package/dist/web-effect-playback.js.map +0 -1
- package/dist/web-effect-poster.d.ts +0 -8
- package/dist/web-effect-poster.js +0 -52
- package/dist/web-effect-poster.js.map +0 -1
- package/dist/web-effect-profiles.d.ts +0 -118
- package/dist/web-effect-profiles.js +0 -181
- package/dist/web-effect-profiles.js.map +0 -1
- package/dist/web-effect-service.d.ts +0 -93
- package/dist/web-effect-service.js +0 -335
- package/dist/web-effect-service.js.map +0 -1
- package/dist/web-effect-tools.d.ts +0 -48
- package/dist/web-effect-tools.js +0 -129
- package/dist/web-effect-tools.js.map +0 -1
- package/dist/web-effect-trials.d.ts +0 -86
- package/dist/web-effect-trials.js +0 -204
- package/dist/web-effect-trials.js.map +0 -1
- package/dist/web-effect-upload.d.ts +0 -27
- package/dist/web-effect-upload.js +0 -185
- package/dist/web-effect-upload.js.map +0 -1
- package/dist/web-effect-worker.d.ts +0 -8
- package/dist/web-effect-worker.js +0 -170
- package/dist/web-effect-worker.js.map +0 -1
- package/dist/web-effect.d.ts +0 -103
- package/dist/web-effect.js +0 -153
- package/dist/web-effect.js.map +0 -1
- package/dist/web-experimental-http.d.ts +0 -2
- package/dist/web-experimental-http.js +0 -12
- package/dist/web-experimental-http.js.map +0 -1
- package/dist/web-mod-agent.d.ts +0 -50
- package/dist/web-mod-agent.js +0 -207
- package/dist/web-mod-agent.js.map +0 -1
package/dist/ui/panel-chat.js
CHANGED
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
// right, the agent's at full width, and one rounded composer pinned below with attach, Stop reply and Send inside it.
|
|
7
7
|
// The instance's own actions (details, start, stop, restart, delete) are in the header's ⋯ menu, and only the ones
|
|
8
8
|
// that apply to its state are offered (#1408 rough edges 4 and 7).
|
|
9
|
-
import { html,
|
|
9
|
+
import { html, useEffect, useLayoutEffect, useRef, useState } from "/assets/app-html.js";
|
|
10
10
|
import { lang, t } from "/assets/app-i18n.js";
|
|
11
|
-
import { appStore, createStore, guestPage, useStore } from "/assets/app-store.js";
|
|
11
|
+
import { appStore, createStore, guestPage, guestTrusted, useStore } from "/assets/app-store.js";
|
|
12
12
|
import { ComposerAuth } from "/assets/web-authorize.js";
|
|
13
13
|
import { useLease } from "/assets/app-ctx.js";
|
|
14
14
|
import { modelText, namesOf, namesStore, navStore as instanceNavStore } from "/assets/instance-nav.js";
|
|
@@ -80,12 +80,7 @@ import { PreviewPanel, claimPanel, openPanel, panelClaims, panelStore, panelVers
|
|
|
80
80
|
import { needsArgument, paletteFor, parseCommandLine } from "./chat-commands.js";
|
|
81
81
|
import { installTour, refreshTourSpot } from "./chat-tour.js";
|
|
82
82
|
import { LightboxHost, openLightbox } from "./image-lightbox.js";
|
|
83
|
-
import { canvasWorkerMenuAvailable, experimentalComponent, useCanvasWorker } from "/assets/feature-flags.js";
|
|
84
|
-
const ChatEffectPosters = experimentalComponent(() => import("./chat-effect-posters.js"), m => m.ChatEffectPosters);
|
|
85
|
-
const ChatEffects = experimentalComponent(() => import("./chat-effects.js"), m => m.ChatEffects);
|
|
86
|
-
import { setFollowsReducedMotion } from "/assets/fx-motion.js";
|
|
87
83
|
import { AmbientMotionNote, ChatAmbient, ambientMenuItems } from "./chat-ambient.js";
|
|
88
|
-
import { startEffectsWaker, getAppEffects, wakeEffects, effectsLoaded, onEffectsAwake, chatHasEffects } from "/assets/feature-flags.js";
|
|
89
84
|
/** The composer's textarea (#2342: text put back into it is a step Ctrl+Z goes back from). */
|
|
90
85
|
const composerField = () => (typeof document !== "undefined" ? document.getElementById("msgIn") : null);
|
|
91
86
|
|
|
@@ -149,9 +144,6 @@ export function boot({ stream, boot: bootData, deps = {} }) {
|
|
|
149
144
|
}
|
|
150
145
|
// #1604: the preview opt-in and Tour live in Settings → This device now (and on each preview card's menu).
|
|
151
146
|
installTour();
|
|
152
|
-
// #2118: viewing current in-scope Chat effects needs no management or personal-slot authority. #2193: the runtime
|
|
153
|
-
// itself loads only when there is something to play (effects-waker.js).
|
|
154
|
-
startEffectsWaker({ store });
|
|
155
147
|
return store;
|
|
156
148
|
}
|
|
157
149
|
|
|
@@ -261,9 +253,6 @@ function ChatView({ name, inst, lease, navKey, exec, awaiting }) {
|
|
|
261
253
|
const themeKey = JSON.stringify(theme);
|
|
262
254
|
const card = useCard(name, inst.card_v);
|
|
263
255
|
const [dialog, setDialog] = useState(null); // "delete" | null
|
|
264
|
-
const [canvasPlayback, setCanvasPlayback] = useState(null);
|
|
265
|
-
const publishCanvasPlayback = useCallback(value => setCanvasPlayback(previous =>
|
|
266
|
-
!previous && !value.playing || previous?.name === value.name && previous.playing === value.playing ? previous : value), []);
|
|
267
256
|
const [wrap, setWrap] = useState(codeWrap);
|
|
268
257
|
const wide = useStore(chatWide).on;
|
|
269
258
|
const view = useRef(null), split = useRef(null), thread = useRef(null), bg = useRef(null), effectSurface = useRef(null);
|
|
@@ -312,9 +301,6 @@ function ChatView({ name, inst, lease, navKey, exec, awaiting }) {
|
|
|
312
301
|
else if (r && r.held) toast(r.message);
|
|
313
302
|
else toast(t(verb === "start" ? "chat.started" : verb === "stop" ? "chat.stoppedInst" : "chat.restarted", name));
|
|
314
303
|
};
|
|
315
|
-
// #2193: this chat's Canvas Worker placements (its status row's effect_chat_v): none, no effects code runs for it.
|
|
316
|
-
const cwEnabled = useCanvasWorker();
|
|
317
|
-
const fxHere = cwEnabled && chatHasEffects(name);
|
|
318
304
|
const items = [
|
|
319
305
|
// #1523 N2: Details is a page now (the Fleet side of this instance), not a dialog.
|
|
320
306
|
{ key: "details", label: t("chat.details"), icon: "info", onSelect: () => navigate(detailsPath(name)) },
|
|
@@ -328,7 +314,6 @@ function ChatView({ name, inst, lease, navKey, exec, awaiting }) {
|
|
|
328
314
|
// #2117: its ambient — why it is not showing here (with the way to change that), and a person's Clear.
|
|
329
315
|
...ambientMenuItems(name, { publicLink: document.body.dataset.publicLink === "1", clear: clearAmbient }),
|
|
330
316
|
narrowScreen() ? null : { key: "fullWidth", checkbox: true, checked: wide, label: t("chat.fullWidth"), onSelect: () => setChatWide(!wide) },
|
|
331
|
-
cwEnabled && canvasWorkerMenuAvailable(window) ? { key: "effects", label: t("cwControls.title"), onSelect: () => setDialog("effects") } : null,
|
|
332
317
|
{ key: "delete", label: t("chat.delete"), icon: "trash", danger: true, onSelect: () => setDialog("delete") },
|
|
333
318
|
];
|
|
334
319
|
const cls = statusClass(inst, exec, awaiting);
|
|
@@ -341,14 +326,14 @@ function ChatView({ name, inst, lease, navKey, exec, awaiting }) {
|
|
|
341
326
|
${inst.model ? html`<button type="button" class="hd-chip" title=${t("chat.chipModel", modelText(inst))} aria-label=${t("chat.chipModel", modelText(inst))} onClick=${() => pick("model")}>${modelText(inst)}</button>` : null}
|
|
342
327
|
${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}`;
|
|
343
328
|
return html`<div class=${`panel p-chat${wrap ? " wrap-code" : ""}`} data-width=${wide ? "full" : undefined} ref=${view}>
|
|
344
|
-
<${PanelHeader} title=${headerName(namesOf(inst || { name }, names.primary))} sub=${sub} nav=${guest ?
|
|
329
|
+
<${PanelHeader} title=${headerName(namesOf(inst || { name }, names.primary))} sub=${sub} nav=${guest ? (guestTrusted() ? html`<${InstanceSwitch} name=${name} current="chat" guest=${true} />` : null) : html`<${InstanceSwitch} name=${name} current="chat" />`}><${CardMenu} card=${card} /><${HeaderTools} navKey=${navKey} />${guest ? null : html`<${Menu} items=${items} label=${t("app.more")} />`}</${PanelHeader}>
|
|
345
330
|
<${ChatCard} key=${name} name=${name} card=${card} />
|
|
346
331
|
<${AgentWidget} slot="chat_header" widget=${widgetFor(name, "chat_header")} binds=${instanceBinds(inst, exec, awaiting)} />
|
|
347
332
|
<div class="chat-split" ref=${split}>
|
|
348
333
|
<div ref=${effectSurface} class=${`chat-main${theme && theme.background ? " has-th-bg" : ""}`}>
|
|
349
334
|
${theme && theme.background ? html`<img ref=${bg} class=${`th-bg th-bg-${theme.background.fit}`} src=${themeImageUrl(name, theme.background.image)} alt="" aria-hidden="true" decoding="async" />` : null}
|
|
350
335
|
<${ChatAmbient} name=${name} surface=${effectSurface} part="layer" />
|
|
351
|
-
<${AmbientMotionNote} name=${name}
|
|
336
|
+
<${AmbientMotionNote} name=${name} />
|
|
352
337
|
<${Thread} name=${name} th=${thread} />
|
|
353
338
|
<${Dock} name=${name} inst=${inst} lease=${lease} exec=${exec} awaiting=${awaiting} workingLabel=${theme ? theme.workingLabel : undefined} />
|
|
354
339
|
<${ChatAmbient} name=${name} surface=${effectSurface} part="front" />
|
|
@@ -423,20 +408,11 @@ function threadEntry(name, doc) {
|
|
|
423
408
|
list.setAttribute("aria-label", t("chat.conversationWith", name));
|
|
424
409
|
const e = { list, sink: null, lang: lang(), thread: null };
|
|
425
410
|
const thread = createThread(list, doc.createElement("div"), {
|
|
426
|
-
t, tf: t, isUser: (x) => store.isUser(x, name), onJump: (j) => e.sink?.setJump(j),
|
|
411
|
+
t, tf: t, isUser: (x) => store.isUser(x, name), onJump: (j) => e.sink?.setJump(j),
|
|
412
|
+
// #2848: which empty — "hidden" (a guest's chat whose every message is hidden from it) differs from a plain empty
|
|
413
|
+
// chat, so the explanation shows when the history answer comes after the first (empty) draw.
|
|
414
|
+
onEmpty: (v) => e.sink?.setEmpty(v && store.allHidden(name) ? "hidden" : v),
|
|
427
415
|
setPreviewOptIn, copyText, download: downloadHtml, toggleWrap,
|
|
428
|
-
// #2193: an offer shown while the effects runtime sleeps wakes it; until it answers, the offer is being checked.
|
|
429
|
-
effectTrialStatus: x => { const fx = getAppEffects(); if (fx) return fx.trialStatus(x); if (!effectsLoaded()) { void wakeEffects(); return "checking"; } return "localAdmin"; },
|
|
430
|
-
effectTrialFailure: x => getAppEffects()?.trialFailure(x),
|
|
431
|
-
fixEffectTrial: (x, reason) => {
|
|
432
|
-
if (reason === "reducedMotion") setFollowsReducedMotion(false);
|
|
433
|
-
else if (reason === "busy") getAppEffects()?.stopTrial();
|
|
434
|
-
else if (reason === "transport") location.reload();
|
|
435
|
-
else if (reason === "localAdmin") location.href = "/signin?next=" + encodeURIComponent(location.pathname + location.search);
|
|
436
|
-
else navigate("/settings/look");
|
|
437
|
-
},
|
|
438
|
-
canTryEffect: x => getAppEffects()?.canTry(x) === true,
|
|
439
|
-
startEffectTrial: x => { const seq = navStore.get().seq; void getAppEffects()?.startTrial(x).then(ok => { if (!ok && e.sink && navStore.get().seq === seq && document.hasFocus?.() === true && document.visibilityState === "visible") toast(t("chat.effectTrialUnavailable")); }); },
|
|
440
416
|
clickReplyButton: (id, index) => store.clickReplyButton(name, id, index), // #1266
|
|
441
417
|
retryOutgoing: (clientId) => store.retryOutgoing(name, clientId), // a failed send's Retry and Delete
|
|
442
418
|
deleteOutgoing: (clientId) => store.deleteOutgoing(name, clientId),
|
|
@@ -472,17 +448,6 @@ function follow(name, thread) {
|
|
|
472
448
|
else if (kind === "sent") thread.jumpLatest();
|
|
473
449
|
}));
|
|
474
450
|
offs.push(P() ? P().onChange(() => thread.refreshCards()) : () => {});
|
|
475
|
-
// #2193: the runtime may start later (an offer shown wakes it): its trial updates are followed from then.
|
|
476
|
-
let trialOff = null, trialOwner = null;
|
|
477
|
-
const watchTrial = () => {
|
|
478
|
-
const next = getAppEffects();
|
|
479
|
-
if (next === trialOwner) return;
|
|
480
|
-
trialOff?.(); trialOff = null; trialOwner = next;
|
|
481
|
-
trialOff = next?.subscribeTrial(() => thread.render(store.threadRows(name))) ?? null;
|
|
482
|
-
};
|
|
483
|
-
watchTrial();
|
|
484
|
-
offs.push(onEffectsAwake(() => { watchTrial(); thread.render(store.threadRows(name)); }));
|
|
485
|
-
offs.push(() => { trialOff?.(); trialOff = null; trialOwner = null; });
|
|
486
451
|
offs.push(panelStore.subscribe(() => thread.refreshCards())); // a card shows whether the panel has its block
|
|
487
452
|
return () => { for (const off of offs) off(); };
|
|
488
453
|
}
|
|
@@ -548,7 +513,8 @@ function Thread({ name, th }) {
|
|
|
548
513
|
}, [name]);
|
|
549
514
|
return html`<div class="scroller" ref=${scroller}>
|
|
550
515
|
<div class="thread-host" ref=${host}></div>
|
|
551
|
-
${empty ? html`<${Empty} icon="chat" title=${t("chat.
|
|
516
|
+
${empty === "hidden" ? html`<${Empty} icon="chat" title=${t("chat.guestAllHidden")} hint=${t("chat.guestAllHiddenHint")} />`
|
|
517
|
+
: empty ? html`<${Empty} icon="chat" title=${t("chat.noMessages")} />` : null}
|
|
552
518
|
<div class="jump-dock">${jump.show ? html`<button data-trust="thread-control" type="button" class="jump-latest" onClick=${() => th.current && th.current.jumpLatest()}>
|
|
553
519
|
<${Icon} name="down" size=${16} />${jump.unseen > 0 ? t("chat.newBelow", jump.unseen) : t("chat.jumpLatest")}</button>` : null}</div>
|
|
554
520
|
</div>`;
|
|
@@ -677,10 +643,12 @@ function Composer({ name, label = name, lease, busy }) {
|
|
|
677
643
|
const files = s.pendingFiles[name] || [];
|
|
678
644
|
const reply = s.replies[name]; // #2272: the message this one will reply to
|
|
679
645
|
const content = !!value.trim() || files.length > 0;
|
|
680
|
-
// A file waiting: what goes is a message with its file, never a command (no palette, no command line).
|
|
681
|
-
//
|
|
646
|
+
// A file waiting: what goes is a message with its file, never a command (no palette, no command line). #2855: a guest's
|
|
647
|
+
// palette offers only what it may run on this agent — the table's "anyone" commands (its status frame's `commands`).
|
|
682
648
|
const guest = guestPage();
|
|
683
|
-
const
|
|
649
|
+
const app = useStore(appStore);
|
|
650
|
+
const guestAllowed = guest ? (app.instances.find((i) => i.name === name)?.commands ?? []) : null;
|
|
651
|
+
const palette = files.length || closedFor === value || (guest && !guestAllowed.length) ? null : paletteFor(value, guestAllowed || undefined);
|
|
684
652
|
const pick = palette && palette.length ? palette[Math.min(sel, palette.length - 1)] : null;
|
|
685
653
|
// Grow with the text, up to a limit: set through the CSSOM, never a style attribute (#1300). Empty, it is one line
|
|
686
654
|
// (#2478 — a long name's placeholder made the box two lines tall).
|
|
@@ -707,9 +675,12 @@ function Composer({ name, label = name, lease, busy }) {
|
|
|
707
675
|
else complete(pick);
|
|
708
676
|
return;
|
|
709
677
|
}
|
|
710
|
-
// #1920: a guest's "/…" would be refused as a command — said here, the draft kept to edit.
|
|
711
|
-
if (guest && /^\s*\//.test(value)) { toast(t("chat.guestNoCommands"), false); return; }
|
|
712
678
|
const cmd = files.length ? null : parseCommandLine(value);
|
|
679
|
+
// #1920/#2855: a guest's "/…" that is not one it may run here is refused here, why said, the draft kept to edit — a
|
|
680
|
+
// known command: only the channel's admins run it; anything else: it would not go as a message either.
|
|
681
|
+
if (guest && /^\s*\//.test(value) && !(cmd && guestAllowed.includes(cmd.command))) {
|
|
682
|
+
toast(cmd ? t("chat.guestCommandNotAllowed", `/${cmd.command}`) : t("chat.guestNoCommands"), false); return;
|
|
683
|
+
}
|
|
713
684
|
if (cmd && needsArgument(cmd.command) && !cmd.args) { recordEdit(input.current); setValue(`/${cmd.command} `); return; }
|
|
714
685
|
if (cmd) { runLine(cmd); return; }
|
|
715
686
|
store.send(name);
|
|
@@ -743,7 +714,7 @@ function Composer({ name, label = name, lease, busy }) {
|
|
|
743
714
|
${palette.length ? palette.map((c) => html`<li key=${c.name} id=${optId(c)} role="option" aria-selected=${c === pick ? "true" : "false"}
|
|
744
715
|
class=${c === pick ? "on" : ""} onMouseDown=${(e) => { e.preventDefault(); complete(c); }}>
|
|
745
716
|
<span class="cmd-name mono">/${c.name}${c.arg ? html` <span class="cmd-arg">${c.arg}</span>` : null}</span><span class="cmd-desc">${t(`chat.cmd_${c.name}`)}</span></li>`)
|
|
746
|
-
: html`<li class="cmd-none" role="option" aria-selected="false">${t("chat.cmdNotCommand")}</li>`}
|
|
717
|
+
: html`<li class="cmd-none" role="option" aria-selected="false">${t(guest ? "chat.cmdNotGuests" : "chat.cmdNotCommand")}</li>`}
|
|
747
718
|
</ul>` : null}
|
|
748
719
|
${reply ? html`<div class="reply-bar" data-trust="composer" role="status">
|
|
749
720
|
<span class="reply-mark" aria-hidden="true"><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"><path d="M9 14 4 9l5-5"/><path d="M4 9h10.5a5.5 5.5 0 0 1 0 11H11"/></svg></span><span class="reply-label">${t("chat.replyingTo", reply.sender)}</span>
|
package/dist/ui/preview-panel.js
CHANGED
|
@@ -16,8 +16,6 @@ import { Icon } from "/assets/ui-icons.js";
|
|
|
16
16
|
import "./chat-render.js";
|
|
17
17
|
import "./preview.js";
|
|
18
18
|
import { loadHtmlAttachment } from "./html-attachment.js";
|
|
19
|
-
import { experimentalComponent, useCanvasWorker } from "/assets/feature-flags.js";
|
|
20
|
-
const EffectTrialPanel = experimentalComponent(() => import("./effect-trial-panel.js"), m => m.EffectTrialPanel);
|
|
21
19
|
import { chatTime, onDayChange } from "./chat-time.js";
|
|
22
20
|
|
|
23
21
|
const R = () => globalThis.AgendChatRender;
|
|
@@ -47,19 +45,13 @@ export const frameKey = (key) => `panel:${key}`;
|
|
|
47
45
|
/** Show `spec` in the panel; `run`: start it there (the card's "Open in panel" click). The block shown before stops. */
|
|
48
46
|
function retire(open) {
|
|
49
47
|
if (!open) return;
|
|
50
|
-
if (open.
|
|
51
|
-
else if (P()) P().stop(frameKey(open.key), "stopped", "");
|
|
48
|
+
if (P()) P().stop(frameKey(open.key), "stopped", "");
|
|
52
49
|
}
|
|
53
50
|
export function openPanel(spec, { run = false } = {}) {
|
|
54
51
|
const old = state.open;
|
|
55
52
|
set({ open: { ...spec, kind: "html" }, run: run ? ++runs : 0 });
|
|
56
53
|
retire(old); // State changes first: cleanup reentry cannot close a newer panel.
|
|
57
54
|
}
|
|
58
|
-
/** A checked controller handle enters the existing component slot; no HTML run token is issued. */
|
|
59
|
-
export function openTrialPanel(spec) {
|
|
60
|
-
const old = state.open;
|
|
61
|
-
set({ open: { ...spec, kind: "trial" }, run: 0 }); retire(old);
|
|
62
|
-
}
|
|
63
55
|
/** Close publishes the empty slot before old-owner callbacks can reenter. */
|
|
64
56
|
export function closePanel() {
|
|
65
57
|
const old = state.open; if (!old) return;
|
|
@@ -80,7 +72,7 @@ export function htmlTitle(code) {
|
|
|
80
72
|
* without one, the newest block does. `msgs` in order; `keyOf` makes a message's key.
|
|
81
73
|
*/
|
|
82
74
|
export function newerVersion(open, msgs, keyOf) {
|
|
83
|
-
if (!open ||
|
|
75
|
+
if (!open || !Array.isArray(msgs)) return null;
|
|
84
76
|
const at = msgs.findIndex(x => keyOf(x) === open.msgKey);
|
|
85
77
|
if (at < 0) return null;
|
|
86
78
|
// An attached file: a later agent reply attaching a file of the same name is its newer version. Its HTML is read
|
|
@@ -145,8 +137,7 @@ export function PreviewPanel({ name, split, msgs, subscribe, keyOf, reveal, down
|
|
|
145
137
|
const pending = useRef(0); // #1704: the card's click, waiting for this device's Allow (the st.run it came with)
|
|
146
138
|
const ownAllow = useRef(0); // while this panel's own Allow commits its choice: the click it answers (not cancelled by it)
|
|
147
139
|
const holder = useRef(null), divider = useRef(null), panel = useRef(null);
|
|
148
|
-
const
|
|
149
|
-
const open = st.open?.kind !== "trial" && st.open?.instance === name ? st.open : null;
|
|
140
|
+
const open = st.open?.instance === name ? st.open : null;
|
|
150
141
|
|
|
151
142
|
useEffect(() => panelStore.subscribe(setSt), []);
|
|
152
143
|
// This device's choice (here or in another tab) decides whether Preview is offered: re-read it when it changes.
|
|
@@ -161,11 +152,12 @@ export function PreviewPanel({ name, split, msgs, subscribe, keyOf, reveal, down
|
|
|
161
152
|
const [, dayTick] = useState(0);
|
|
162
153
|
useEffect(() => (open ? onDayChange(() => dayTick(n => n + 1)) : undefined), [!!open]);
|
|
163
154
|
|
|
164
|
-
|
|
165
|
-
const
|
|
155
|
+
// #2863: "large" — over 8 MB, nothing runs until "Preview anyway" (or the card's, which opened it with `anyway`).
|
|
156
|
+
const ui = { state: (s, reason) => setRun({ state: s === "starting" || s === "running" || s === "large" ? s : "idle", reason: reason || "" }) };
|
|
157
|
+
const start = (anyway) => {
|
|
166
158
|
if (!open || !holder.current) return;
|
|
167
159
|
setView("preview");
|
|
168
|
-
P().start(frameKey(open.key), holder.current, open.code, ui, { fill: true });
|
|
160
|
+
P().start(frameKey(open.key), holder.current, open.code, ui, anyway === true || open.anyway ? { fill: true, anyway: true } : { fill: true });
|
|
169
161
|
};
|
|
170
162
|
const stop = () => { if (open) P().stop(frameKey(open.key), "stopped", ""); };
|
|
171
163
|
// A new block in the panel: what ran before is gone (openPanel stopped it); the card's click starts it here — or,
|
|
@@ -233,7 +225,8 @@ export function PreviewPanel({ name, split, msgs, subscribe, keyOf, reveal, down
|
|
|
233
225
|
if (newer.code != null) { openPanel(newer); return; }
|
|
234
226
|
const was = open && open.key, mine = reading.current = newer;
|
|
235
227
|
setNewerNote(t("chat.pvLoading"));
|
|
236
|
-
|
|
228
|
+
// A click on the newer version reads it whatever its size (up to the 50 MB ceiling); over 8 MB the panel then asks.
|
|
229
|
+
const r = await loadHtmlAttachment(newer.att, undefined, { anyway: true });
|
|
237
230
|
if (reading.current !== mine) return;
|
|
238
231
|
reading.current = null;
|
|
239
232
|
const now = panelStore.get().open;
|
|
@@ -245,9 +238,6 @@ export function PreviewPanel({ name, split, msgs, subscribe, keyOf, reveal, down
|
|
|
245
238
|
// Esc in the panel closes it (and is not the chat's "stop the reply").
|
|
246
239
|
const onPanelKey = (e) => { if (e.key === "Escape" && !e.isComposing) { e.preventDefault(); closePanel(); } };
|
|
247
240
|
|
|
248
|
-
if (trial) return html`<${EffectTrialPanel} binding=${trial.binding} title=${trial.handle.reply.offer.title}
|
|
249
|
-
current=${trial.handle.current} ownerCss=${trial.ownerCss} onStage=${trial.onStage}
|
|
250
|
-
onStop=${trial.handle.stop} onClose=${() => { if (panelStore.get().open === trial) closePanel(); }} />`;
|
|
251
241
|
if (!open) return null;
|
|
252
242
|
const going = run.state === "starting" || run.state === "running";
|
|
253
243
|
const a = P().availability();
|
|
@@ -282,9 +272,10 @@ export function PreviewPanel({ name, split, msgs, subscribe, keyOf, reveal, down
|
|
|
282
272
|
<button type="button" class=${`btn btn-sm${view === "code" ? " on" : ""}`} aria-pressed=${view === "code" ? "true" : "false"} onClick=${() => setView("code")}><${Icon} name="code" size=${14} />${t("chat.pvCode")}</button>
|
|
283
273
|
</div>
|
|
284
274
|
<span class="grow"></span>
|
|
285
|
-
${going ? html`<button type="button" class="btn btn-sm pv-panel-reload" title=${t("chat.pvReload")} onClick=${() => { stop(); start(); }}><${Icon} name="restart" size=${14} /><span class="lbl">${t("chat.pvReload")}</span></button>
|
|
275
|
+
${going ? html`<button type="button" class="btn btn-sm pv-panel-reload" title=${t("chat.pvReload")} onClick=${() => { stop(); start(true); }}><${Icon} name="restart" size=${14} /><span class="lbl">${t("chat.pvReload")}</span></button>
|
|
286
276
|
<button type="button" class="btn btn-sm pv-panel-stop" title=${t("chat.pvStop")} onClick=${stop}><${Icon} name="stop" size=${14} /><span class="lbl">${t("chat.pvStop")}</span></button>`
|
|
287
|
-
: html`<button type="button" class="btn btn-sm btn-primary pv-panel-
|
|
277
|
+
: run.state === "large" ? html`<button type="button" class="btn btn-sm btn-primary pv-panel-anyway" title=${t("chat.pvAnyway")} disabled=${!a.ok} onClick=${() => start(true)}><${Icon} name="play" size=${14} /><span class="lbl">${t("chat.pvAnyway")}</span></button>`
|
|
278
|
+
: html`<button type="button" class="btn btn-sm btn-primary pv-panel-run" title=${t("chat.pvPreview")} disabled=${!a.ok} onClick=${() => start()}><${Icon} name="play" size=${14} /><span class="lbl">${t("chat.pvPreview")}</span></button>`}
|
|
288
279
|
<button type="button" class="btn btn-sm pv-panel-dl" title=${t("chat.pvDownload")} onClick=${() => download(open.code, fileName)}><${Icon} name="download" size=${14} /><span class="lbl">${t("chat.pvDownload")}</span></button>
|
|
289
280
|
</div>
|
|
290
281
|
${newer && newer.key !== open.key ? html`<div class="pv-newer" role="status"><span>${t("chat.pvNewer", timeOf(newer.ts))}</span>
|
package/dist/ui/preview.js
CHANGED
|
@@ -20,7 +20,9 @@
|
|
|
20
20
|
})(typeof globalThis !== "undefined" ? globalThis : this, function (root) {
|
|
21
21
|
"use strict";
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
// #2863: askBytes — above it the person is asked first ("Preview anyway?"); maxBytes — above it only a download (the
|
|
24
|
+
// frame's own ceiling, web-preview.ts PREVIEW_HTML_MAX_BYTES; html-attachment.js reads with the same two).
|
|
25
|
+
var LIMITS = { askBytes: 8 * 1024 * 1024, maxBytes: 50 * 1024 * 1024, minHeight: 40, maxHeight: 4000, readyMs: 3000, watchdogMs: 10000, resizePerSec: 10, minDelta: 2, growthFreeze: 5, growthWindowMs: 2000 };
|
|
24
26
|
var OPT_IN_KEY = "agend_html_preview"; // localStorage: "on" — this device allows previews
|
|
25
27
|
var NEVER_KEY = "agend_html_preview_never"; // sessionStorage: "1" — never preview on this device (this session)
|
|
26
28
|
// #1568: the banner was acknowledged ("I understand"). localStorage: the sign-in's session handle — hidden while the
|
|
@@ -54,6 +56,8 @@
|
|
|
54
56
|
function now() { return root.performance && typeof root.performance.now === "function" ? root.performance.now() : Date.now(); }
|
|
55
57
|
function store(kind) { try { return root[kind] || null; } catch (e) { return null; } }
|
|
56
58
|
function utf8Bytes(s) { try { return new root.TextEncoder().encode(s).length; } catch (e) { return String(s).length * 3; } }
|
|
59
|
+
/** #2863: "12 MB" / "8.4 MB" (1 MB = 1024 × 1024 bytes, as the caps are; html-attachment.js sizeLabel). */
|
|
60
|
+
function sizeLabel(bytes) { var mb = bytes / (1024 * 1024); return (mb >= 10 ? Math.round(mb) : Math.round(mb * 10) / 10) + " MB"; }
|
|
57
61
|
function randomCh() {
|
|
58
62
|
var b = new Uint8Array(16);
|
|
59
63
|
root.crypto.getRandomValues(b);
|
|
@@ -229,7 +233,8 @@
|
|
|
229
233
|
|
|
230
234
|
/**
|
|
231
235
|
* Run `html` in `holder` (a node of the card) for `key`. `ui` is told what happened: ui.state(name, reason) with
|
|
232
|
-
* "starting" | "running" | "stopped" | "unavailable"
|
|
236
|
+
* "starting" | "running" | "stopped" | "unavailable" | "large" (#2863: over LIMITS.askBytes — nothing runs until it is
|
|
237
|
+
* asked again with opts.anyway, the person's "Preview anyway"). One preview at a time: any other running one stops first.
|
|
233
238
|
* opts.fill (#1481, the side panel): the frame fills its holder — the same frame from mountPreview, only sized by the
|
|
234
239
|
* panel instead of by the frame's height messages, which it then ignores.
|
|
235
240
|
*/
|
|
@@ -239,7 +244,9 @@
|
|
|
239
244
|
function start(key, holder, html, ui, opts) {
|
|
240
245
|
var a = availability();
|
|
241
246
|
if (!a.ok) { ui.state("unavailable", a.reason); return false; }
|
|
242
|
-
|
|
247
|
+
var bytes = typeof html === "string" ? utf8Bytes(html) : Infinity;
|
|
248
|
+
if (bytes > LIMITS.maxBytes) { ui.state("unavailable", say("pvHtmlOver", "This HTML is over 50 MB; it is not previewed. Download it to open it.")); return false; }
|
|
249
|
+
if (bytes > LIMITS.askBytes && !(opts && opts.anyway)) { ui.state("large", say("pvLarge", "This file is large ({0}). Preview anyway?", sizeLabel(bytes))); return false; }
|
|
243
250
|
stopAll("another");
|
|
244
251
|
starts++;
|
|
245
252
|
var doc = holder.ownerDocument;
|
package/dist/ui/search-dialog.js
CHANGED
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
// ↑↓ and Enter, Esc or a click outside to close; filters folded under one toggle. A chat row still in the agent's
|
|
4
4
|
// history opens there, at the message (#m=); anything else — a chat row that has left the history, a message between
|
|
5
5
|
// agents, a task — opens here as the index's copy (GET /ui/search/context). Text only: no innerHTML, no style attribute.
|
|
6
|
+
// #2855: a trusted guest's — the chats it can see only (the server's rule): no settings, teams, sources or "only mine".
|
|
6
7
|
import { html, useEffect, useLayoutEffect, useMemo, useRef, useState } from "/assets/app-html.js";
|
|
7
8
|
import { t, register } from "/assets/app-i18n.js";
|
|
8
9
|
import { recordEdit } from "/assets/text-undo.js";
|
|
9
|
-
import { appStore, useStore } from "/assets/app-store.js";
|
|
10
|
+
import { appStore, guestPage, useStore } from "/assets/app-store.js";
|
|
10
11
|
import { navigate } from "/assets/app-nav.js";
|
|
11
12
|
import { chatPath, fleetPath } from "/assets/app-route.js";
|
|
12
13
|
import { namesOf, namesStore } from "/assets/instance-nav.js";
|
|
@@ -28,6 +29,8 @@ register("search", {
|
|
|
28
29
|
building: "Still indexing — some older results may be missing.",
|
|
29
30
|
limited: "Words of 1–2 characters alone search the newest 5,000 entries only — add a longer word to search everything.",
|
|
30
31
|
unavailable: "Search is off or not available (web.search in fleet.yaml).", busy: "Search is busy — try again in a moment.",
|
|
32
|
+
guestPlaceholder: "Search your chats…", guestHint: "Find something said in the chats you can see: a word or two. Short words (1–2 characters) work too.",
|
|
33
|
+
tooMany: "Too many searches. Try again in {0} seconds.",
|
|
31
34
|
signedOut: "Signed out — sign in again to search.", failed: "Search failed — try again.",
|
|
32
35
|
keys: "↑↓ move · Enter open · Esc close", results: "Results", back: "Results",
|
|
33
36
|
gone: "This message is no longer in the chat's history — this is the copy kept by search.",
|
|
@@ -47,6 +50,8 @@ register("search", {
|
|
|
47
50
|
building: "索引仍在建立中,較舊的結果可能還沒出現。",
|
|
48
51
|
limited: "只有 1–2 個字的短詞時,只會搜尋最新的 5,000 筆;加上較長的詞即可搜尋全部。",
|
|
49
52
|
unavailable: "搜尋已關閉或無法使用(fleet.yaml 的 web.search)。", busy: "搜尋忙碌中,請稍後再試。",
|
|
53
|
+
guestPlaceholder: "搜尋你的聊天…", guestHint: "找你看得到的聊天裡說過的事:輸入一兩個詞。1–2 個字的短詞也可以。",
|
|
54
|
+
tooMany: "搜尋太多次了,請 {0} 秒後再試。",
|
|
50
55
|
signedOut: "已登出,請重新登入後再搜尋。", failed: "搜尋失敗,請再試一次。",
|
|
51
56
|
keys: "↑↓ 移動 · Enter 開啟 · Esc 關閉", results: "結果", back: "結果",
|
|
52
57
|
gone: "這則訊息已不在聊天紀錄中,以下是搜尋保留的副本。",
|
|
@@ -144,7 +149,10 @@ export function jumpToMessage(instance, key, w = window) {
|
|
|
144
149
|
w.dispatchEvent(new Event("hashchange"));
|
|
145
150
|
}
|
|
146
151
|
|
|
147
|
-
const errorText = (status) => (status === 404 ? t("search.unavailable") : status === 503 ? t("search.busy") : status === 401 ? t("search.signedOut")
|
|
152
|
+
const errorText = (status, retryS) => (status === 404 ? t("search.unavailable") : status === 503 ? t("search.busy") : status === 401 ? t("search.signedOut")
|
|
153
|
+
: status === 429 ? t("search.tooMany", retryS || 60) : t("search.failed"));
|
|
154
|
+
/** #2855: a refused guest search's "try again in" (seconds), from its answer. */
|
|
155
|
+
const retryOf = async (r) => { try { const b = await r.json(); return { error: b && b.error, s: Number(b && b.retry_after_s) || 0 }; } catch { return { error: null, s: 0 }; } };
|
|
148
156
|
|
|
149
157
|
/** #2347 §6: how many settings the dialog lists above the messages (the Settings page's own search lists them all). */
|
|
150
158
|
const SETTINGS_SHOWN = 5;
|
|
@@ -154,7 +162,8 @@ export function SearchDialog({ preset = {}, onClose }) {
|
|
|
154
162
|
const app = useStore(appStore);
|
|
155
163
|
const names = useStore(namesStore);
|
|
156
164
|
const [q, setQ] = useState(preset.q || "");
|
|
157
|
-
const
|
|
165
|
+
const guest = guestPage();
|
|
166
|
+
const [f, setF] = useState({ instance: preset.instance || "", team: "", since: "any", sources: guest ? ["chat"] : [...SOURCES], mine: false });
|
|
158
167
|
const [showFilters, setShowFilters] = useState(!!preset.instance);
|
|
159
168
|
const [teams, setTeams] = useState(null);
|
|
160
169
|
const [res, setRes] = useState({ status: "idle" });
|
|
@@ -193,8 +202,15 @@ export function SearchDialog({ preset = {}, onClose }) {
|
|
|
193
202
|
const ac = typeof AbortController === "function" ? new AbortController() : null;
|
|
194
203
|
const timer = setTimeout(async () => {
|
|
195
204
|
try {
|
|
196
|
-
const
|
|
205
|
+
const ask = () => fetch(url, { headers: { Accept: "application/json" }, ...(ac ? { signal: ac.signal } : {}) });
|
|
206
|
+
let r = await ask();
|
|
197
207
|
if (mine !== seq.current) return;
|
|
208
|
+
if (r.status === 429) {
|
|
209
|
+
// #2855: a guest's searches run one at a time — the one before this may still be finishing: once more, shortly.
|
|
210
|
+
const why = await retryOf(r);
|
|
211
|
+
if (why.error === "guest_search_running") { await new Promise((ok) => setTimeout(ok, 400)); if (mine !== seq.current) return; r = await ask(); if (mine !== seq.current) return; }
|
|
212
|
+
if (r.status === 429) { const again = why.error === "guest_search_running" ? await retryOf(r) : why; setRes({ status: "error", error: errorText(429, again.s) }); return; }
|
|
213
|
+
}
|
|
198
214
|
if (!r.ok) { setRes({ status: "error", error: errorText(r.status) }); return; }
|
|
199
215
|
const body = await r.json();
|
|
200
216
|
if (mine !== seq.current) return;
|
|
@@ -209,7 +225,7 @@ export function SearchDialog({ preset = {}, onClose }) {
|
|
|
209
225
|
|
|
210
226
|
// Teams, once the filters are first shown.
|
|
211
227
|
useEffect(() => {
|
|
212
|
-
if (!showFilters || teams) return;
|
|
228
|
+
if (guest || !showFilters || teams) return;
|
|
213
229
|
fetch("/ui/teams", { headers: { Accept: "application/json" } }).then(r => (r.ok ? r.json() : { teams: {} })).then(
|
|
214
230
|
(b) => setTeams(Object.keys((b && b.teams) || {}).sort()), () => setTeams([]));
|
|
215
231
|
}, [showFilters, teams]);
|
|
@@ -219,9 +235,9 @@ export function SearchDialog({ preset = {}, onClose }) {
|
|
|
219
235
|
// While a new query runs, the last answer stays (dimmed) rather than flashing empty.
|
|
220
236
|
const groups = useMemo(() => groupHits(res.hits || []), [res]);
|
|
221
237
|
// #2347 §6: the settings matching the query, from the page's own registry (no request), above the messages.
|
|
222
|
-
const settings = useMemo(() => (q.trim() ? searchSettings(q).slice(0, SETTINGS_SHOWN) : []), [q]);
|
|
238
|
+
const settings = useMemo(() => (q.trim() && !guest ? searchSettings(q).slice(0, SETTINGS_SHOWN) : []), [q]);
|
|
223
239
|
const flat = useMemo(() => [...settings.map((entry) => ({ setting: entry })), ...flatHits(groups)], [settings, groups]);
|
|
224
|
-
const activeFilters = (f.instance ? 1 : 0) + (f.team ? 1 : 0) + (f.since !== "any" ? 1 : 0) + (f.sources.length < SOURCES.length ? 1 : 0) + (f.mine ? 1 : 0);
|
|
240
|
+
const activeFilters = (f.instance ? 1 : 0) + (f.team ? 1 : 0) + (f.since !== "any" ? 1 : 0) + (!guest && f.sources.length < SOURCES.length ? 1 : 0) + (f.mine ? 1 : 0);
|
|
225
241
|
|
|
226
242
|
// The active row stays in view.
|
|
227
243
|
useEffect(() => {
|
|
@@ -239,7 +255,7 @@ export function SearchDialog({ preset = {}, onClose }) {
|
|
|
239
255
|
// Any failure — the request, or a body that is not JSON — ends as an error, never "Loading…" for good.
|
|
240
256
|
fetch(`/ui/search/context?key=${encodeURIComponent(hit.key)}`, { headers: { Accept: "application/json" } })
|
|
241
257
|
.then(async (r) => {
|
|
242
|
-
if (!r.ok) { setDetail(d => (d && d.hit === hit ? { hit, status: "error", error: r.status === 404 ? t("search.notFound") : errorText(r.status) } : d)); return; }
|
|
258
|
+
if (!r.ok) { const s = r.status === 429 ? (await retryOf(r)).s : 0; setDetail(d => (d && d.hit === hit ? { hit, status: "error", error: r.status === 404 ? t("search.notFound") : errorText(r.status, s) } : d)); return; }
|
|
243
259
|
const body = await r.json();
|
|
244
260
|
setDetail(d => (d && d.hit === hit ? { hit, status: "ok", ctx: body } : d));
|
|
245
261
|
})
|
|
@@ -266,7 +282,7 @@ export function SearchDialog({ preset = {}, onClose }) {
|
|
|
266
282
|
<div class="search-box">
|
|
267
283
|
<div class="search-head">
|
|
268
284
|
<${Icon} name="search" />
|
|
269
|
-
<input ref=${input} class="search-input" type="search" value=${q} placeholder=${t("search.placeholder")} aria-label=${t("search.label")}
|
|
285
|
+
<input ref=${input} class="search-input" type="search" value=${q} placeholder=${t(guest ? "search.guestPlaceholder" : "search.placeholder")} aria-label=${t("search.label")}
|
|
270
286
|
role="combobox" aria-expanded=${flat.length && !detail ? "true" : "false"} aria-controls="search-results" aria-autocomplete="list"
|
|
271
287
|
aria-activedescendant=${activeId} autocomplete="off" spellcheck=${false} enterkeyhint="search"
|
|
272
288
|
onInput=${(e) => { setQ(e.target.value); setDetail(null); }} />
|
|
@@ -288,24 +304,24 @@ export function SearchDialog({ preset = {}, onClose }) {
|
|
|
288
304
|
${["any", "day", "week", "month"].map(k => html`<button type="button" class=${`chip-btn${f.since === k ? " on" : ""}`} aria-pressed=${f.since === k ? "true" : "false"}
|
|
289
305
|
onClick=${() => setFilter({ since: k })}>${k === "any" ? t("search.anyTime") : t(`search.${k}`)}</button>`)}
|
|
290
306
|
</div>
|
|
291
|
-
|
|
307
|
+
${guest ? null : html`<div class="search-chips" role="group" aria-label=${t("search.sources")}>
|
|
292
308
|
${SOURCES.map(s => html`<button type="button" class=${`chip-btn${f.sources.includes(s) ? " on" : ""}`} aria-pressed=${f.sources.includes(s) ? "true" : "false"}
|
|
293
309
|
onClick=${() => toggleSource(s)}>${t(`search.src_${s}`)}</button>`)}
|
|
294
310
|
<label class="search-mine"><input type="checkbox" checked=${f.mine} onChange=${(e) => setFilter({ mine: e.target.checked })} />${t("search.mine")}</label>
|
|
295
|
-
</div
|
|
311
|
+
</div>`}
|
|
296
312
|
</div>` : null}
|
|
297
313
|
<div class="search-body" ref=${list}>
|
|
298
314
|
${detail ? html`<${Detail} detail=${detail} q=${q} label=${label} onBack=${() => { setDetail(null); input.current && input.current.focus(); }} onClose=${onClose} />`
|
|
299
|
-
: html`<${Results} q=${q} res=${res} groups=${groups} settings=${settings} active=${active} setActive=${setActive} open=${open} label=${label} filtered=${activeFilters > 0}
|
|
315
|
+
: html`<${Results} guest=${guest} q=${q} res=${res} groups=${groups} settings=${settings} active=${active} setActive=${setActive} open=${open} label=${label} filtered=${activeFilters > 0}
|
|
300
316
|
recent=${recent} pick=${(s) => { recordEdit(input.current); setQ(s); input.current && input.current.focus(); }} clearRecent=${() => { forgetRecent(); setRecent([]); }} />`}
|
|
301
317
|
</div>
|
|
302
318
|
<div class="search-foot" aria-hidden="true">${t("search.keys")}</div>
|
|
303
319
|
</div></dialog>`;
|
|
304
320
|
}
|
|
305
321
|
|
|
306
|
-
function Results({ q, res, groups, settings = [], active, setActive, open, label, filtered, recent, pick, clearRecent }) {
|
|
322
|
+
function Results({ guest = false, q, res, groups, settings = [], active, setActive, open, label, filtered, recent, pick, clearRecent }) {
|
|
307
323
|
if (!q.trim()) {
|
|
308
|
-
return html`<div class="search-empty"><p class="search-hint">${t("search.hint")}</p>
|
|
324
|
+
return html`<div class="search-empty"><p class="search-hint">${t(guest ? "search.guestHint" : "search.hint")}</p>
|
|
309
325
|
${recent.length ? html`<div class="search-recent"><div class="search-recent-head"><span>${t("search.recent")}</span>
|
|
310
326
|
<button type="button" class="chip-btn" onClick=${clearRecent}>${t("search.clearRecent")}</button></div>
|
|
311
327
|
<ul>${recent.map(s => html`<li><button type="button" class="search-recent-item" onClick=${() => pick(s)}><${Icon} name="clock" size=${14} /><span>${s}</span></button></li>`)}</ul></div>` : null}</div>`;
|
|
@@ -6,8 +6,6 @@ import { register, t } from "/assets/app-i18n.js";
|
|
|
6
6
|
import { recordEdit } from "/assets/text-undo.js";
|
|
7
7
|
import { appStore, useStore } from "/assets/app-store.js";
|
|
8
8
|
import { ModsCard } from "./settings-mods.js";
|
|
9
|
-
import { experimentalComponent } from "/assets/feature-flags.js";
|
|
10
|
-
const EffectsCard = experimentalComponent(() => import("./settings-effects.js"), m => m.EffectsCard);
|
|
11
9
|
import { Icon } from "/assets/ui-icons.js";
|
|
12
10
|
import { toast } from "/assets/ui-toast.js";
|
|
13
11
|
import { ownerCssOffHere, ownerCssTrial, safeMode, setOwnerCssActive, setOwnerCssOffHere, startOwnerCssTrial } from "/assets/owner-css.js";
|
|
@@ -71,7 +69,7 @@ export function AppearanceSection(props = {}) {
|
|
|
71
69
|
return html`${group("groupLook")}<${ThemesCard} ...${props} /><${IdentityColorCard} /><${CardSizeCard} />
|
|
72
70
|
${group("groupLayout")}<${SidebarWidgetCard} />
|
|
73
71
|
${group("groupCss")}<${OwnerCssCard} />
|
|
74
|
-
${group("groupMods")}<${ModsCard}
|
|
72
|
+
${group("groupMods")}<${ModsCard} />
|
|
75
73
|
<${RestoreDefaultsCard} ...${props} />`;
|
|
76
74
|
}
|
|
77
75
|
|
|
@@ -65,6 +65,8 @@ function publish() {
|
|
|
65
65
|
// Monotonic: when the server's countdown reaches zero on this page's clock.
|
|
66
66
|
deadline: at + Math.max(0, Number(view.remaining_ms) || 0),
|
|
67
67
|
withdraw: view.can_withdraw && view.state === "pending" ? () => withdraw(view.id) : null,
|
|
68
|
+
// #2868: this browser may confirm it itself now (the server's read says so; it checks again on the click).
|
|
69
|
+
applyNow: view.can_self_confirm && view.state === "pending" ? () => applyNow(view.id) : null,
|
|
68
70
|
dismiss: TERMINAL.has(view.state) ? () => forget(view.id) : null,
|
|
69
71
|
})),
|
|
70
72
|
});
|
|
@@ -150,6 +152,22 @@ export async function withdraw(id) {
|
|
|
150
152
|
return false;
|
|
151
153
|
}
|
|
152
154
|
|
|
155
|
+
/**
|
|
156
|
+
* #2868: "Apply now" — confirm this session's own pending request from this browser (#2170's rule, asked again by the
|
|
157
|
+
* server). Its answer is followed like any other; a refusal (no longer allowed here) leaves it waiting and is { ok: false }.
|
|
158
|
+
*/
|
|
159
|
+
export async function applyNow(id) {
|
|
160
|
+
const e = tracked.get(id);
|
|
161
|
+
if (!e) return { ok: false };
|
|
162
|
+
const token = ticket(e);
|
|
163
|
+
let res;
|
|
164
|
+
try { res = await api(`/api/settings/pending/${encodeURIComponent(id)}/confirm`, { method: "POST", body: "{}" }); } catch { return { ok: false }; }
|
|
165
|
+
if (isPendingResponse(res) && res.body.pending_change.id === id) { update(res.body.pending_change, token); schedule(); return { ok: true }; }
|
|
166
|
+
// Refused (403: not allowed here any more): read it again, so "Apply now" goes until the server offers it again.
|
|
167
|
+
if (e.view.can_self_confirm) update({ ...e.view, can_self_confirm: false }, token);
|
|
168
|
+
return { ok: false, status: res.status };
|
|
169
|
+
}
|
|
170
|
+
|
|
153
171
|
/** This session's requests still open on the server (after a reload): followed like new ones. */
|
|
154
172
|
export async function attach() {
|
|
155
173
|
let res;
|
|
@@ -22,13 +22,16 @@ const PLATFORMS = { discord: "Discord", telegram: "Telegram" };
|
|
|
22
22
|
const own = (o, k) => !!o && Object.prototype.hasOwnProperty.call(o, k);
|
|
23
23
|
const sameList = (a, b) => (a || []).length === (b || []).length && (a || []).every((n) => (b || []).includes(n));
|
|
24
24
|
const accountKey = (g) => `${g.adapterId}:${g.userId}`;
|
|
25
|
-
|
|
25
|
+
// #2855: a grant's level — "trusted", or chat (the default, so never written).
|
|
26
|
+
const grantOf = (v) => ({ ...(v.name ? { name: v.name } : {}), instances: [...v.instances], ...(v.level === "trusted" ? { level: "trusted" } : {}) });
|
|
27
|
+
const copyGrants = (m) => { const out = {}; for (const [k, v] of Object.entries(m || {})) if (v && Array.isArray(v.instances)) out[k] = grantOf(v); return out; };
|
|
26
28
|
/** The saved state, from the fleet's config: `grants` null while the fleet still uses #1920's derived access. */
|
|
27
29
|
export function savedGuests(fleet) {
|
|
28
30
|
const g = (fleet && fleet.web && fleet.web.guests) || {};
|
|
29
31
|
return { enabled: g.enabled === true, chat: g.chat !== false, grants: g.grants ? copyGrants(g.grants) : null };
|
|
30
32
|
}
|
|
31
|
-
const
|
|
33
|
+
const levelOf = (g) => (g && g.level === "trusted" ? "trusted" : "chat");
|
|
34
|
+
const sameGrant = (a, b) => !!a && !!b && (a.name || "") === (b.name || "") && sameList(a.instances, b.instances) && levelOf(a) === levelOf(b);
|
|
32
35
|
|
|
33
36
|
/**
|
|
34
37
|
* What changed against the saved state, as the PUT /api/settings/fleet/web body's `guests` (null: nothing), and how
|
|
@@ -44,7 +47,7 @@ export function guestsBody(original, f) {
|
|
|
44
47
|
for (const k of new Set([...Object.keys(was), ...Object.keys(f.grants)])) {
|
|
45
48
|
const a = own(was, k) ? was[k] : undefined, b = own(f.grants, k) ? f.grants[k] : undefined;
|
|
46
49
|
if (a && b && sameGrant(a, b)) continue;
|
|
47
|
-
diff[k] = b ?
|
|
50
|
+
diff[k] = b ? grantOf(b) : null;
|
|
48
51
|
count++;
|
|
49
52
|
}
|
|
50
53
|
// #2365 review: turning grants on is a change by itself, even with no one in them yet (default deny): `grants: {}`.
|
|
@@ -235,6 +238,12 @@ export function GuestsSection({ ctx, staged }) {
|
|
|
235
238
|
${removed ? html`<span class="g-state">${tn("grantsWillBeRemoved")}</span>` : added ? html`<span class="g-state">${tn("grantsNew")}</span>` : pill(person)}</div>
|
|
236
239
|
<div class="g-chips">${((draft || saved).instances || []).map((n) => html`<span key=${n} class="g-chip" title=${shownName(rooms, n) !== n ? n : undefined}>${shownName(rooms, n)}</span>`)}</div>
|
|
237
240
|
${draft && !draft.instances.length ? html`<p class="note g-error" role="alert">${tn("grantsNoAgents")}</p>` : null}
|
|
241
|
+
${draft ? html`<div class=${`g-level${levelOf(draft) !== levelOf(saved) && saved ? " s-dirty" : ""}`}>
|
|
242
|
+
<span class="note" id=${`level-${k}`}>${tn("grantsLevel", person.name)}</span>
|
|
243
|
+
<div class="seg-inline" role="group" aria-labelledby=${`level-${k}`}>${["chat", "trusted"].map((l) => html`<button key=${l} type="button"
|
|
244
|
+
class=${`btn btn-sm${levelOf(draft) === l ? " btn-primary" : ""}`} aria-pressed=${levelOf(draft) === l ? "true" : "false"} data-level=${l}
|
|
245
|
+
onClick=${() => setGrant(k, { ...draft, level: l === "trusted" ? "trusted" : undefined })}>${tn(l === "trusted" ? "grantsLevelTrusted" : "grantsLevelChat")}</button>`)}</div>
|
|
246
|
+
${levelOf(draft) === "trusted" ? html`<p class="note">${tn("grantsLevelTrustedHint")}</p>` : null}</div>` : null}
|
|
238
247
|
${editing === k && draft ? html`<div class="g-edit"><p class="note">${tn("grantsMaySee", person.name)}</p>
|
|
239
248
|
<${Names} id=${`grant-${k}`} all=${instances} rooms=${rooms} value=${draft.instances} wrote=${person.wrote} onChange=${(list) => setGrant(k, { ...draft, instances: list })} /></div>` : null}
|
|
240
249
|
${readOnly ? null : html`<div class="guest-actions">
|
|
@@ -260,7 +269,7 @@ export function GuestsSection({ ctx, staged }) {
|
|
|
260
269
|
${overview && overview.audit ? html`<h4>${tn("grantsRecent")}</h4>${!overview.audit.length ? html`<p class="note" id="grantsRecentNone">${tn("grantsRecentNone")}</p>`
|
|
261
270
|
: html`<ul class="g-audit" id="grantsRecentList">${overview.audit.map((e, i) => html`<li key=${`${e.at}-${i}`} class="g-audit-row" data-kind=${e.kind}>
|
|
262
271
|
<span class="g-when">${new Date(e.at).toLocaleString()}</span> <strong>${e.name}</strong>
|
|
263
|
-
${e.kind === "grant" ? html`${e.added.map((n) => html` <span key=${`+${n}`} class="g-wrote g-add">+${n}</span>`)}${e.removed.map((n) => html` <span key=${`-${n}`} class="g-wrote g-del">−${n}</span>`)}
|
|
272
|
+
${e.kind === "grant" ? html`${e.added.map((n) => html` <span key=${`+${n}`} class="g-wrote g-add">+${n}</span>`)}${e.removed.map((n) => html` <span key=${`-${n}`} class="g-wrote g-del">−${n}</span>`)}${e.level ? html` <span class="g-wrote g-level-to">${tn("grantsAuditLevel", tn(e.level === "trusted" ? "grantsLevelTrusted" : "grantsLevelChat"))}</span>` : null}
|
|
264
273
|
<span class="note"> · <span class="g-wrote">${tn(e.via === "confirmed" ? "grantsAuditConfirmed" : "grantsAuditDirect")}</span>${e.endedSessions ? html` · <span class="g-wrote">${tn("grantsAuditEnded", e.endedSessions)}</span>` : null}</span>`
|
|
265
274
|
: html` <span class="note">${e.outcome === "sent" ? tn("grantsAuditCodeSent") : tn("grantsAuditCodeNot", auditOutcome(e.outcome))}</span>`}</li>`)}</ul>`}` : null}
|
|
266
275
|
${status && status.kind === "waiting" ? html`<p class="note" role="status">${tn("grantsWaiting")}</p>` : null}
|
|
@@ -12,7 +12,7 @@ const DOCS = { en: "https://github.com/songsid/AgEnD/blob/main/docs/web-dashboar
|
|
|
12
12
|
export const LEARN_ANCHORS = {
|
|
13
13
|
colors: ["identity-colours", "識別色"], css: ["custom-css-settings--look", "自訂-css設定--外觀"], themes: ["agents-themes", "agent-的主題"],
|
|
14
14
|
cards: ["identity-cards", "身分卡"], limits: ["card-upload-limits", "身分卡上傳限制"], widgets: ["widgets", "小工具"], mods: ["mods", "mod"],
|
|
15
|
-
|
|
15
|
+
restore: ["restore-defaults", "還原預設值"],
|
|
16
16
|
"app-icon": ["app-icon", "app-圖示"],
|
|
17
17
|
};
|
|
18
18
|
export function learnHref(topic, language = lang()) {
|