@songsid/agend 2.3.0-beta.22 → 2.3.0-beta.24
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 +2 -2
- package/README.zh-TW.md +2 -2
- package/dist/agent-cli.js +4 -2
- package/dist/agent-cli.js.map +1 -1
- package/dist/agent-endpoint.d.ts +1 -0
- package/dist/agent-endpoint.js +4 -0
- package/dist/agent-endpoint.js.map +1 -1
- package/dist/agent-whats-new.d.ts +5 -0
- package/dist/agent-whats-new.js +16 -1
- package/dist/agent-whats-new.js.map +1 -1
- package/dist/auth-api.d.ts +7 -0
- package/dist/auth-api.js +54 -21
- package/dist/auth-api.js.map +1 -1
- package/dist/backend/codex-package.d.ts +30 -0
- package/dist/backend/codex-package.js +133 -0
- package/dist/backend/codex-package.js.map +1 -0
- package/dist/backend/codex.d.ts +2 -0
- package/dist/backend/codex.js +9 -2
- package/dist/backend/codex.js.map +1 -1
- package/dist/backend/grok.js +3 -1
- package/dist/backend/grok.js.map +1 -1
- package/dist/backend/kiro.d.ts +9 -1
- package/dist/backend/kiro.js +102 -1
- package/dist/backend/kiro.js.map +1 -1
- package/dist/backend/opencode-permission.js +1 -1
- package/dist/backend/opencode-permission.js.map +1 -1
- package/dist/backend/opencode.js +2 -1
- package/dist/backend/opencode.js.map +1 -1
- package/dist/binary-probe.js +1 -1
- package/dist/bug-report-cli.js +11 -10
- package/dist/bug-report-cli.js.map +1 -1
- package/dist/bug-report.js +11 -10
- package/dist/bug-report.js.map +1 -1
- package/dist/bundled-skills.d.ts +7 -3
- package/dist/bundled-skills.js +16 -7
- package/dist/bundled-skills.js.map +1 -1
- package/dist/channel/adapters/discord.js +3 -3
- package/dist/channel/adapters/discord.js.map +1 -1
- package/dist/channel/mcp-server.js +12 -4
- package/dist/channel/mcp-server.js.map +1 -1
- package/dist/channel/mcp-tools.d.ts +3 -1
- package/dist/channel/mcp-tools.js +36 -0
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/child-process.d.ts +21 -0
- package/dist/child-process.js +54 -0
- package/dist/child-process.js.map +1 -0
- package/dist/cli.js +188 -204
- package/dist/cli.js.map +1 -1
- package/dist/command-table.d.ts +1 -1
- package/dist/command-table.js +1 -1
- package/dist/command-table.js.map +1 -1
- package/dist/config-validator.js +20 -1
- package/dist/config-validator.js.map +1 -1
- package/dist/config.js +3 -1
- package/dist/config.js.map +1 -1
- package/dist/daemon.d.ts +8 -0
- package/dist/daemon.js +31 -8
- package/dist/daemon.js.map +1 -1
- package/dist/darwin-memory.js +1 -1
- package/dist/doctor.d.ts +5 -0
- package/dist/doctor.js +42 -0
- package/dist/doctor.js.map +1 -1
- package/dist/export-import.js +4 -3
- package/dist/export-import.js.map +1 -1
- package/dist/fleet-level-config.d.ts +1 -1
- package/dist/fleet-level-config.js +2 -0
- package/dist/fleet-level-config.js.map +1 -1
- package/dist/fleet-manager.d.ts +45 -0
- package/dist/fleet-manager.js +324 -36
- package/dist/fleet-manager.js.map +1 -1
- package/dist/full-restart.d.ts +5 -5
- package/dist/full-restart.js +5 -5
- package/dist/full-restart.js.map +1 -1
- package/dist/general-knowledge/skills/credential-profiles/SKILL.md +48 -10
- package/dist/general-knowledge/skills/fleet-config/SKILL.md +1 -1
- package/dist/general-knowledge/skills/fleet-restart/SKILL.md +7 -8
- package/dist/general-knowledge/skills/multi-channel/SKILL.md +1 -1
- package/dist/general-knowledge/skills/web-appearance/SKILL.canvas-worker-off.md +319 -0
- package/dist/general-knowledge/skills/web-dashboard/SKILL.canvas-worker-off.md +529 -0
- package/dist/general-knowledge/skills/web-dashboard/SKILL.md +17 -4
- package/dist/general-knowledge/steering/core-rules.md +1 -1
- package/dist/instance-config-impact.js +10 -3
- package/dist/instance-config-impact.js.map +1 -1
- package/dist/instance-lifecycle.js +4 -4
- package/dist/locale.js +296 -4
- package/dist/locale.js.map +1 -1
- package/dist/login-flows.js +1 -1
- package/dist/login-shell.js +3 -1
- package/dist/login-shell.js.map +1 -1
- package/dist/ls-rows.js +1 -1
- package/dist/needs-you-hub.d.ts +98 -1
- package/dist/needs-you-hub.js +313 -44
- package/dist/needs-you-hub.js.map +1 -1
- package/dist/needs-you-live.d.ts +5 -2
- package/dist/needs-you-live.js +6 -3
- package/dist/needs-you-live.js.map +1 -1
- package/dist/needs-you.d.ts +58 -3
- package/dist/needs-you.js +36 -3
- package/dist/needs-you.js.map +1 -1
- package/dist/outbound-schemas.d.ts +3 -3
- package/dist/public-link-progress.d.ts +12 -1
- package/dist/public-link-progress.js +5 -10
- package/dist/public-link-progress.js.map +1 -1
- package/dist/public-tunnel-config.d.ts +5 -0
- package/dist/public-tunnel-config.js +45 -1
- package/dist/public-tunnel-config.js.map +1 -1
- package/dist/public-web-link.js +6 -5
- package/dist/public-web-link.js.map +1 -1
- package/dist/quickstart.js +15 -9
- package/dist/quickstart.js.map +1 -1
- package/dist/resource-report.js +1 -1
- package/dist/scheduler/task-board.d.ts +41 -1
- package/dist/scheduler/task-board.js +152 -5
- package/dist/scheduler/task-board.js.map +1 -1
- package/dist/sd-notify.js +2 -1
- package/dist/sd-notify.js.map +1 -1
- package/dist/service-installer.d.ts +69 -0
- package/dist/service-installer.js +182 -0
- package/dist/service-installer.js.map +1 -1
- package/dist/service-owner.d.ts +44 -0
- package/dist/service-owner.js +48 -0
- package/dist/service-owner.js.map +1 -0
- package/dist/service-plan.d.ts +8 -0
- package/dist/service-plan.js +13 -0
- package/dist/service-plan.js.map +1 -1
- package/dist/settings-api.d.ts +4 -0
- package/dist/settings-api.js +21 -3
- package/dist/settings-api.js.map +1 -1
- package/dist/settings-change.js +88 -4
- package/dist/settings-change.js.map +1 -1
- package/dist/settings-confirmation.d.ts +2 -0
- package/dist/settings-confirmation.js +14 -8
- package/dist/settings-confirmation.js.map +1 -1
- package/dist/settings-control.js +2 -1
- package/dist/settings-control.js.map +1 -1
- package/dist/settings-effect.js +2 -0
- package/dist/settings-effect.js.map +1 -1
- package/dist/settings-http-confirmation.d.ts +2 -0
- package/dist/settings-http-confirmation.js +3 -2
- package/dist/settings-http-confirmation.js.map +1 -1
- package/dist/settings-request-capability.d.ts +4 -1
- package/dist/settings-request-capability.js +11 -3
- package/dist/settings-request-capability.js.map +1 -1
- package/dist/setup-auth.d.ts +1 -0
- package/dist/setup-auth.js +2 -1
- package/dist/setup-auth.js.map +1 -1
- package/dist/setup-host.d.ts +1 -0
- package/dist/setup-host.js +7 -3
- package/dist/setup-host.js.map +1 -1
- package/dist/setup-wizard.js +1 -1
- package/dist/setup-wizard.js.map +1 -1
- package/dist/task-board-summary.d.ts +101 -0
- package/dist/task-board-summary.js +177 -0
- package/dist/task-board-summary.js.map +1 -0
- package/dist/task-board-waker.d.ts +8 -1
- package/dist/task-board-waker.js +15 -6
- package/dist/task-board-waker.js.map +1 -1
- package/dist/tmux-control.js +1 -1
- package/dist/tmux-control.js.map +1 -1
- package/dist/tmux-exec-helper.d.ts +13 -3
- package/dist/tmux-exec-helper.js +34 -29
- package/dist/tmux-exec-helper.js.map +1 -1
- package/dist/tmux-manager.js +1 -1
- package/dist/tmux-read.js +1 -1
- package/dist/tmux-read.js.map +1 -1
- package/dist/topic-commands.d.ts +3 -0
- package/dist/topic-commands.js +29 -18
- package/dist/topic-commands.js.map +1 -1
- package/dist/transcript-monitor.js +5 -0
- package/dist/transcript-monitor.js.map +1 -1
- package/dist/transcript-sources.d.ts +2 -0
- package/dist/transcript-sources.js +4 -0
- package/dist/transcript-sources.js.map +1 -1
- package/dist/tunnel/cloudflared-install.js +3 -1
- package/dist/tunnel/cloudflared-install.js.map +1 -1
- package/dist/tunnel/cloudflared.d.ts +1 -1
- package/dist/tunnel/cloudflared.js +51 -10
- package/dist/tunnel/cloudflared.js.map +1 -1
- package/dist/tunnel/named-diagnostics.d.ts +13 -0
- package/dist/tunnel/named-diagnostics.js +52 -0
- package/dist/tunnel/named-diagnostics.js.map +1 -0
- package/dist/tunnel/types.d.ts +2 -1
- package/dist/tunnel/types.js.map +1 -1
- package/dist/types.d.ts +9 -0
- package/dist/ui/app-effects.js +10 -4
- package/dist/ui/chat-strings.js +3 -0
- package/dist/ui/chat-thread.js +14 -7
- package/dist/ui/effect-trial-offer.js +2 -31
- package/dist/ui/effects-waker.js +2 -70
- package/dist/ui/fleet-task-card.js +5 -5
- package/dist/ui/fleet-task-common.js +43 -9
- package/dist/ui/fleet-task-work.js +107 -0
- package/dist/ui/fleet-tasks.js +46 -12
- package/dist/ui/image-lightbox.js +39 -1
- package/dist/ui/image-zoom.js +242 -0
- package/dist/ui/panel-chat.js +24 -13
- package/dist/ui/panel-details.js +2 -0
- package/dist/ui/panel-fleet.js +6 -2
- package/dist/ui/panel-needs.js +21 -6
- package/dist/ui/panel-settings.js +35 -13
- package/dist/ui/preview-panel.js +2 -1
- package/dist/ui/search-dialog.js +2 -1
- package/dist/ui/settings-appearance.js +44 -10
- package/dist/ui/settings-dialogs.js +33 -17
- package/dist/ui/settings-discord.js +6 -2
- package/dist/ui/settings-guests.js +2 -1
- package/dist/ui/settings-model.js +2 -11
- package/dist/ui/settings-mods.js +8 -3
- package/dist/ui/settings-strings.js +32 -0
- package/dist/ui/settings-token.js +3 -1
- package/dist/ui/settings-web-authority.js +6 -3
- package/dist/ui/settings-wizard.js +13 -7
- package/dist/ui/shared/app-i18n.js +2 -2
- package/dist/ui/shared/app-needs.js +2 -2
- package/dist/ui/shared/app-store.js +2 -2
- package/dist/ui/shared/app.css +93 -32
- package/dist/ui/shared/app.js +5 -1
- package/dist/ui/shared/effect-app-runtime.js +7 -5
- package/dist/ui/shared/effect-chat-runtime.js +2 -7
- package/dist/ui/shared/effect-cursor.js +3 -2
- package/dist/ui/shared/effect-display-metadata.js +39 -0
- package/dist/ui/shared/effect-registry.js +4 -2
- package/dist/ui/shared/effect-runtime-loader.js +3 -1
- package/dist/ui/shared/effect-stock-trust.js +5 -5
- package/dist/ui/shared/feature-flags.js +127 -0
- package/dist/ui/shared/identity-color.js +6 -3
- package/dist/ui/shared/instance-nav.js +3 -2
- package/dist/ui/shared/panel-view.js +14 -29
- package/dist/ui/shared/text-undo.js +210 -0
- package/dist/ui/shared/ui-dialog.js +4 -3
- package/dist/ui/shared/ui-icons.js +2 -0
- package/dist/ui/shared/usage-dialog.js +29 -8
- package/dist/ui/shared/view-strings.js +2 -4
- package/dist/ui/web-terminal/terminal.html +10 -10
- package/dist/ui/web-terminal/terminal.js +58 -12
- package/dist/update-dispatch.js +1 -1
- package/dist/update-launch.d.ts +1 -1
- package/dist/update-launch.js +1 -2
- package/dist/update-launch.js.map +1 -1
- package/dist/update-preflight.js +1 -1
- package/dist/usage/providers.js +1 -1
- package/dist/usage/providers.js.map +1 -1
- package/dist/view-api.js +1 -1
- package/dist/web-api.d.ts +6 -0
- package/dist/web-api.js +163 -21
- package/dist/web-api.js.map +1 -1
- package/dist/web-app-icon-worker.js +2 -1
- package/dist/web-app-icon-worker.js.map +1 -1
- package/dist/web-app-icon.d.ts +10 -8
- package/dist/web-app-icon.js +14 -13
- package/dist/web-app-icon.js.map +1 -1
- package/dist/web-appearance-tools.d.ts +1 -0
- package/dist/web-appearance-tools.js +7 -3
- package/dist/web-appearance-tools.js.map +1 -1
- package/dist/web-appearance.d.ts +4 -10
- package/dist/web-appearance.js +4 -3
- package/dist/web-appearance.js.map +1 -1
- package/dist/web-auth.d.ts +8 -4
- package/dist/web-auth.js +28 -17
- package/dist/web-auth.js.map +1 -1
- package/dist/web-chat-history.d.ts +14 -0
- package/dist/web-chat-history.js +26 -1
- package/dist/web-chat-history.js.map +1 -1
- package/dist/web-cookie-names.d.ts +5 -0
- package/dist/web-cookie-names.js +12 -0
- package/dist/web-cookie-names.js.map +1 -0
- package/dist/web-experimental-http.d.ts +2 -0
- package/dist/web-experimental-http.js +12 -0
- package/dist/web-experimental-http.js.map +1 -0
- package/dist/web-experimental.d.ts +11 -0
- package/dist/web-experimental.js +49 -0
- package/dist/web-experimental.js.map +1 -0
- package/dist/web-guest-access.d.ts +1 -0
- package/dist/web-guest-access.js +4 -2
- package/dist/web-guest-access.js.map +1 -1
- package/dist/web-mod-store.d.ts +2 -0
- package/dist/web-mod-store.js +10 -0
- package/dist/web-mod-store.js.map +1 -1
- package/dist/web-png.d.ts +4 -0
- package/dist/web-png.js +10 -2
- package/dist/web-png.js.map +1 -1
- package/dist/web-preview.d.ts +2 -0
- package/dist/web-preview.js +2 -0
- package/dist/web-preview.js.map +1 -1
- package/dist/web-returning-device.d.ts +4 -2
- package/dist/web-returning-device.js +12 -5
- package/dist/web-returning-device.js.map +1 -1
- package/dist/web-settings-patch.d.ts +2 -1
- package/dist/web-settings-patch.js +5 -1
- package/dist/web-settings-patch.js.map +1 -1
- package/dist/web-settings-tools.d.ts +1 -0
- package/dist/web-settings-tools.js +14 -10
- package/dist/web-settings-tools.js.map +1 -1
- package/dist/web-terminal.js +1 -1
- package/dist/web-theme-store.d.ts +2 -2
- package/dist/web-theme-store.js +6 -3
- package/dist/web-theme-store.js.map +1 -1
- package/package.json +11 -11
package/dist/ui/panel-chat.js
CHANGED
|
@@ -21,6 +21,7 @@ import { Menu } from "/assets/ui-menu.js";
|
|
|
21
21
|
import { Dialog } from "/assets/ui-dialog.js";
|
|
22
22
|
import { Empty, Skeleton } from "/assets/ui-states.js";
|
|
23
23
|
import { Icon } from "/assets/ui-icons.js";
|
|
24
|
+
import { recordEdit } from "/assets/text-undo.js";
|
|
24
25
|
import { toast } from "/assets/ui-toast.js";
|
|
25
26
|
import { confirmDialog } from "/assets/ui-confirm.js";
|
|
26
27
|
import { savePref } from "/assets/app-prefs.js";
|
|
@@ -74,11 +75,14 @@ import { PreviewPanel, claimPanel, openPanel, panelClaims, panelStore, panelVers
|
|
|
74
75
|
import { needsArgument, paletteFor, parseCommandLine } from "./chat-commands.js";
|
|
75
76
|
import { installTour, refreshTourSpot } from "./chat-tour.js";
|
|
76
77
|
import { LightboxHost, openLightbox } from "./image-lightbox.js";
|
|
77
|
-
import {
|
|
78
|
-
|
|
78
|
+
import { canvasWorkerMenuAvailable, experimentalComponent, useCanvasWorker } from "/assets/feature-flags.js";
|
|
79
|
+
const ChatEffectPosters = experimentalComponent(() => import("./chat-effect-posters.js"), m => m.ChatEffectPosters);
|
|
80
|
+
const ChatEffects = experimentalComponent(() => import("./chat-effects.js"), m => m.ChatEffects);
|
|
79
81
|
import { setFollowsReducedMotion } from "/assets/fx-motion.js";
|
|
80
82
|
import { AmbientMotionNote, ChatAmbient, ambientMenuItems } from "./chat-ambient.js";
|
|
81
|
-
import { startEffectsWaker, getAppEffects, wakeEffects, effectsLoaded, onEffectsAwake, chatHasEffects } from "
|
|
83
|
+
import { startEffectsWaker, getAppEffects, wakeEffects, effectsLoaded, onEffectsAwake, chatHasEffects } from "/assets/feature-flags.js";
|
|
84
|
+
/** The composer's textarea (#2342: text put back into it is a step Ctrl+Z goes back from). */
|
|
85
|
+
const composerField = () => (typeof document !== "undefined" ? document.getElementById("msgIn") : null);
|
|
82
86
|
|
|
83
87
|
const R = () => globalThis.AgendChatRender;
|
|
84
88
|
const P = () => globalThis.AgendPreview;
|
|
@@ -297,7 +301,8 @@ function ChatView({ name, inst, lease, navKey, exec, awaiting }) {
|
|
|
297
301
|
else toast(t(verb === "start" ? "chat.started" : verb === "stop" ? "chat.stoppedInst" : "chat.restarted", name));
|
|
298
302
|
};
|
|
299
303
|
// #2193: this chat's Canvas Worker placements (its status row's effect_chat_v): none, no effects code runs for it.
|
|
300
|
-
const
|
|
304
|
+
const cwEnabled = useCanvasWorker();
|
|
305
|
+
const fxHere = cwEnabled && chatHasEffects(name);
|
|
301
306
|
const items = [
|
|
302
307
|
// #1523 N2: Details is a page now (the Fleet side of this instance), not a dialog.
|
|
303
308
|
{ key: "details", label: t("chat.details"), icon: "info", onSelect: () => navigate(detailsPath(name)) },
|
|
@@ -310,7 +315,7 @@ function ChatView({ name, inst, lease, navKey, exec, awaiting }) {
|
|
|
310
315
|
...themeMenuItems(name),
|
|
311
316
|
// #2117: its ambient — why it is not showing here (with the way to change that), and a person's Clear.
|
|
312
317
|
...ambientMenuItems(name, { publicLink: document.body.dataset.publicLink === "1", clear: clearAmbient }),
|
|
313
|
-
|
|
318
|
+
cwEnabled && canvasWorkerMenuAvailable(window) ? { key: "effects", label: t("cwControls.title"), onSelect: () => setDialog("effects") } : null,
|
|
314
319
|
{ key: "delete", label: t("chat.delete"), icon: "trash", danger: true, onSelect: () => setDialog("delete") },
|
|
315
320
|
];
|
|
316
321
|
const cls = statusClass(inst, exec, awaiting);
|
|
@@ -451,11 +456,16 @@ function follow(name, thread) {
|
|
|
451
456
|
}));
|
|
452
457
|
offs.push(P() ? P().onChange(() => thread.refreshCards()) : () => {});
|
|
453
458
|
// #2193: the runtime may start later (an offer shown wakes it): its trial updates are followed from then.
|
|
454
|
-
let trialOff = null;
|
|
455
|
-
const watchTrial = () => {
|
|
459
|
+
let trialOff = null, trialOwner = null;
|
|
460
|
+
const watchTrial = () => {
|
|
461
|
+
const next = getAppEffects();
|
|
462
|
+
if (next === trialOwner) return;
|
|
463
|
+
trialOff?.(); trialOff = null; trialOwner = next;
|
|
464
|
+
trialOff = next?.subscribeTrial(() => thread.render(store.state.msgs[name] || [])) ?? null;
|
|
465
|
+
};
|
|
456
466
|
watchTrial();
|
|
457
|
-
|
|
458
|
-
offs.push(() => trialOff?.());
|
|
467
|
+
offs.push(onEffectsAwake(() => { watchTrial(); thread.render(store.state.msgs[name] || []); }));
|
|
468
|
+
offs.push(() => { trialOff?.(); trialOff = null; trialOwner = null; });
|
|
459
469
|
offs.push(panelStore.subscribe(() => thread.refreshCards())); // a card shows whether the panel has its block
|
|
460
470
|
return () => { for (const off of offs) off(); };
|
|
461
471
|
}
|
|
@@ -537,7 +547,7 @@ function Dock({ name, inst, lease, exec, awaiting, workingLabel }) {
|
|
|
537
547
|
<${CommandCard} name=${name} lease=${lease} />
|
|
538
548
|
<${QuickActions} name=${name} inst=${inst} lease=${lease} />
|
|
539
549
|
${s.failedSends[name] ? html`<div class="failed-send" role="alert"><span>${t("chat.notSent")}</span><span class="txt">${s.failedSends[name]}</span>
|
|
540
|
-
<button type="button" class="btn btn-primary btn-sm" onClick=${() => store.putBack(name)}>${t("chat.putBack")}</button>
|
|
550
|
+
<button type="button" class="btn btn-primary btn-sm" onClick=${() => { recordEdit(composerField()); store.putBack(name); }}>${t("chat.putBack")}</button>
|
|
541
551
|
<button type="button" class="btn btn-sm" onClick=${() => store.discardFailed(name)}>${t("chat.discard")}</button></div>` : null}
|
|
542
552
|
${s.waking[name] ? html`<p class="send-waking" role="status">${t("chat.waking", name)}</p>` : null}
|
|
543
553
|
${s.sendFailures[name] ? html`<div class="failed-send send-failure" role="alert"><span>${s.sendFailures[name].error}</span>
|
|
@@ -654,7 +664,8 @@ function Composer({ name, lease, busy }) {
|
|
|
654
664
|
setValue("");
|
|
655
665
|
store.runCommand(name, cmd.command, cmd.args, { alive: () => lease.current() });
|
|
656
666
|
};
|
|
657
|
-
|
|
667
|
+
// #2342: a completion is a step Ctrl+Z goes back from (to what was typed); a command that runs consumes the line.
|
|
668
|
+
const complete = (c) => { recordEdit(input.current); setValue(`/${c.name}${c.arg ? " " : ""}`); };
|
|
658
669
|
const submit = () => {
|
|
659
670
|
if (pick) {
|
|
660
671
|
if (value === `/${pick.name}` && !needsArgument(pick.name)) runLine({ command: pick.name, args: "" });
|
|
@@ -664,7 +675,7 @@ function Composer({ name, lease, busy }) {
|
|
|
664
675
|
// #1920: a guest's "/…" would be refused as a command — said here, the draft kept to edit.
|
|
665
676
|
if (guest && /^\s*\//.test(value)) { toast(t("chat.guestNoCommands"), false); return; }
|
|
666
677
|
const cmd = files.length ? null : parseCommandLine(value);
|
|
667
|
-
if (cmd && needsArgument(cmd.command) && !cmd.args) { setValue(`/${cmd.command} `); return; }
|
|
678
|
+
if (cmd && needsArgument(cmd.command) && !cmd.args) { recordEdit(input.current); setValue(`/${cmd.command} `); return; }
|
|
668
679
|
if (cmd) { runLine(cmd); return; }
|
|
669
680
|
store.send(name);
|
|
670
681
|
};
|
|
@@ -728,7 +739,7 @@ function FileChip({ f, i, name }) {
|
|
|
728
739
|
return html`<span class="file-chip">
|
|
729
740
|
${isImage ? html`<img ref=${img} alt="" />` : html`<span class="ic"><${Icon} name="file" size=${16} /></span>`}
|
|
730
741
|
<span class="nm">${f.name}</span><span class="sz">${R().formatSize(f.size)}</span>
|
|
731
|
-
${store.isPasted(f) ? html`<button type="button" class="as-text" onClick=${() => store.fileBackAsText(name, i)}>${t("chat.asText")}</button>` : null}
|
|
742
|
+
${store.isPasted(f) ? html`<button type="button" class="as-text" onClick=${() => { recordEdit(composerField()); store.fileBackAsText(name, i); }}>${t("chat.asText")}</button>` : null}
|
|
732
743
|
<button type="button" class="chip-x" title=${t("chat.removeFile")} aria-label=${`${t("chat.removeFile")} ${f.name}`} onClick=${() => store.removeFile(name, i)}><${Icon} name="close" size=${14} /></button></span>`;
|
|
733
744
|
}
|
|
734
745
|
|
package/dist/ui/panel-details.js
CHANGED
|
@@ -25,6 +25,7 @@ import { Empty, ErrorState, Skeleton } from "/assets/ui-states.js";
|
|
|
25
25
|
import { toast } from "/assets/ui-toast.js";
|
|
26
26
|
import "./chat-strings.js";
|
|
27
27
|
import { api } from "./settings-confirm.js";
|
|
28
|
+
import { InstanceBoardWork } from "./fleet-task-work.js";
|
|
28
29
|
import { requestAgentSettings } from "./settings-request.js";
|
|
29
30
|
|
|
30
31
|
export function DetailsPanel({ route, navKey }) {
|
|
@@ -150,6 +151,7 @@ function DetailsView({ name, inst, navKey, exec, awaiting }) {
|
|
|
150
151
|
${row(t("chat.dContext"), html`<span class="pct">${ctx != null ? `${Math.round(ctx)}%` : "--"}</span>${ctx != null ? bar(ctx, ctx > 80 ? "warn" : "") : null}`)}
|
|
151
152
|
${row(t("chat.dRate5"), html`<span class="pct">${Math.round(rl5)}%</span>${bar(rl5, rl5 > 90 ? "error" : rl5 > 70 ? "warn" : "")}`)}
|
|
152
153
|
${row(t("chat.dRate7"), html`<span class="pct">${Math.round(rl7)}%</span>${bar(rl7, rl7 > 90 ? "error" : rl7 > 70 ? "warn" : "")}`)}</section>
|
|
154
|
+
<${InstanceBoardWork} name=${name} onOpen=${(id) => navigate(`/ui/fleet?card=${encodeURIComponent(id)}`)} />
|
|
153
155
|
<section class="card"><h3>${t("chat.dActivity")}</h3>${d.recent_activity?.length ? d.recent_activity.map((a, i) => html`<div key=${i} class="activity">
|
|
154
156
|
<span class="mono muted">${a.timestamp ? new Date(`${a.timestamp.replace(" ", "T")}Z`).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }) : ""}</span>
|
|
155
157
|
<span class="ev">${a.event}</span> ${a.summary || ""}</div>`) : html`<p class="note">${t("chat.dNoActivity")}</p>`}</section>`;
|
package/dist/ui/panel-fleet.js
CHANGED
|
@@ -276,6 +276,10 @@ function Config({ lease }) {
|
|
|
276
276
|
// Each save owns the form's busy state only while it is the latest: an older save that is decided later (it waited
|
|
277
277
|
// for an admin's confirmation) never unlocks the form under a newer one still on its way (#1453 review).
|
|
278
278
|
const saves = useRef(0);
|
|
279
|
+
// #2342: each project root's row keeps its own key — keyed by position, removing one shifted the texts below into
|
|
280
|
+
// the boxes above (each box's text rewritten, its undo history lost).
|
|
281
|
+
const rootKeys = useRef({ n: 0, list: [] });
|
|
282
|
+
const rootKey = (i) => { const k = rootKeys.current; while (k.list.length <= i) k.list.push(++k.n); return k.list[i]; };
|
|
279
283
|
useEffect(() => {
|
|
280
284
|
if (!state || !state.data) return;
|
|
281
285
|
const c = state.data, ch = c.channel || {}, acc = ch.access || {};
|
|
@@ -314,9 +318,9 @@ function Config({ lease }) {
|
|
|
314
318
|
<section class="card"><h3>${t("fleet.defaults")}</h3>
|
|
315
319
|
<div class="field"><label for="cfg-be">${t("fleet.backend")}</label><${BackendSelect} id="cfg-be" value=${form.backend} onChange=${(v) => setForm({ ...form, backend: v })} /></div></section>
|
|
316
320
|
<section class="card"><h3>${t("fleet.roots")}</h3>
|
|
317
|
-
${form.roots.map((r, i) => html`<div key=${i} class="field-row"><input value=${r} placeholder="/path/to/projects" aria-label=${t("fleet.roots")}
|
|
321
|
+
${form.roots.map((r, i) => html`<div key=${rootKey(i)} class="field-row"><input value=${r} placeholder="/path/to/projects" aria-label=${t("fleet.roots")}
|
|
318
322
|
onInput=${(e) => { const roots = [...form.roots]; roots[i] = e.target.value; setForm({ ...form, roots }); }} />
|
|
319
|
-
<button type="button" class="icon-btn" aria-label=${t("fleet.removeRoot")} title=${t("fleet.removeRoot")} onClick=${() => setForm({ ...form, roots: form.roots.filter((_, j) => j !== i) })}><${Icon} name="close" size=${16} /></button></div>`)}
|
|
323
|
+
<button type="button" class="icon-btn" aria-label=${t("fleet.removeRoot")} title=${t("fleet.removeRoot")} onClick=${() => { rootKeys.current.list.splice(i, 1); setForm({ ...form, roots: form.roots.filter((_, j) => j !== i) }); }}><${Icon} name="close" size=${16} /></button></div>`)}
|
|
320
324
|
<button type="button" class="btn btn-ghost" onClick=${() => setForm({ ...form, roots: [...form.roots, ""] })}><${Icon} name="plus" size=${16} />${t("fleet.addRoot")}</button></section>
|
|
321
325
|
<div class="save-row"><button type="button" class="btn btn-primary" disabled=${saving} onClick=${save}>${t("fleet.save")}</button><span class="note">${t("fleet.saveNote")}</span></div>`}</${Loaded}>`;
|
|
322
326
|
}
|
package/dist/ui/panel-needs.js
CHANGED
|
@@ -25,7 +25,8 @@ import { openSignIn, signInOffered } from "./backend-signin.js";
|
|
|
25
25
|
register("needs", {
|
|
26
26
|
en: {
|
|
27
27
|
title: "Needs you", empty: "Nothing needs you right now", emptyHint: "When an agent waits for you, it shows here — and in your chat app's General.", emptyHintWeb: "When an agent waits for you, it shows here.",
|
|
28
|
-
open: "Open chat",
|
|
28
|
+
open: "Open chat", openCard: "Open the card", noAssignee: "Task board (nobody assigned)", signIn: "Sign in",
|
|
29
|
+
gap: "not wrapped up: {0}", gapUnchecked: "{0} checklist item(s) unchecked", gapNoResult: "no result", gapJoin: ", ", acknowledge: "Acknowledge", acknowledging: "Acknowledging…", answering: "Answering…",
|
|
29
30
|
ageNow: "just now", ageMin: "{0} min", ageHour: "{0} h", ageDay: "{0} d",
|
|
30
31
|
notifyTitle: "Notify me on this device", notifyOn: "Notifications are on for this browser while a tab is open.",
|
|
31
32
|
notifyOff: "Get a desktop notification when something new needs you while this tab is in the background.",
|
|
@@ -37,7 +38,8 @@ register("needs", {
|
|
|
37
38
|
},
|
|
38
39
|
"zh-TW": {
|
|
39
40
|
title: "等你處理", empty: "目前沒有需要你處理的事", emptyHint: "agent 在等你時,會出現在這裡——也會出現在聊天 app 的 General。", emptyHintWeb: "agent 在等你時,會出現在這裡。",
|
|
40
|
-
open: "開啟聊天",
|
|
41
|
+
open: "開啟聊天", openCard: "打開卡片", noAssignee: "任務看板(沒有負責人)", signIn: "登入",
|
|
42
|
+
gap: "還沒補齊:{0}", gapUnchecked: "還有 {0} 項沒勾", gapNoResult: "沒有產出總結", gapJoin: "、", acknowledge: "確認", acknowledging: "確認中…", answering: "回覆中…",
|
|
41
43
|
ageNow: "剛剛", ageMin: "{0} 分鐘", ageHour: "{0} 小時", ageDay: "{0} 天",
|
|
42
44
|
notifyTitle: "在這台裝置通知我", notifyOn: "這個瀏覽器的通知已開啟(需要開著分頁)。",
|
|
43
45
|
notifyOff: "分頁在背景時,有新的事等你處理就跳出桌面通知。",
|
|
@@ -84,9 +86,21 @@ const setClaim = (kind, key, value) => claims.set((c) => {
|
|
|
84
86
|
return { ...c, [kind]: next };
|
|
85
87
|
});
|
|
86
88
|
|
|
87
|
-
const ICON = { prompt: "chat", awaiting_input: "clock", instance: "alert", delivery: "send" };
|
|
89
|
+
const ICON = { prompt: "chat", awaiting_input: "clock", instance: "alert", delivery: "send", task: "tasks" };
|
|
88
90
|
const BAD = new Set(["crashed", "delivery_failed", "hang", "exited"]);
|
|
89
91
|
|
|
92
|
+
/**
|
|
93
|
+
* An item's line. A task board card's is said here, in the page's language (#2246 step 3): its number and title, then
|
|
94
|
+
* its question or what it lacks; anything else is the server's own words.
|
|
95
|
+
*/
|
|
96
|
+
function detailOf(item) {
|
|
97
|
+
const c = item.type === "task" ? item.card : null;
|
|
98
|
+
if (!c || !c.title) return item.detail;
|
|
99
|
+
const lacks = c.gap ? [c.gap.unchecked ? tn("gapUnchecked", c.gap.unchecked) : "", c.gap.noResult ? tn("gapNoResult") : ""].filter(Boolean).join(tn("gapJoin")) : "";
|
|
100
|
+
const tail = c.question || (lacks ? tn("gap", lacks) : "");
|
|
101
|
+
return `${c.code} ${c.title}${tail ? ` — ${tail}` : ""}`;
|
|
102
|
+
}
|
|
103
|
+
|
|
90
104
|
/** "3 min", "2 h": the age shown beside an item (refreshed by a display timer, never by a read). */
|
|
91
105
|
export function age(since, nowMs) {
|
|
92
106
|
const minutes = Math.max(0, Math.floor((nowMs - since) / 60_000));
|
|
@@ -129,7 +143,7 @@ export function NeedsPanel({ navKey }) {
|
|
|
129
143
|
<div data-trust="panel-content" class="panel-body"><div class="col">
|
|
130
144
|
${!items.length ? html`<${Empty} icon="check" title=${tn("empty")} hint=${tn(chatApp ? "emptyHint" : "emptyHintWeb")} />`
|
|
131
145
|
: groups.map((g) => html`<section key=${g.instance} class="n-group" aria-label=${g.instance}>
|
|
132
|
-
<h2 class="n-group-head"
|
|
146
|
+
<h2 class="n-group-head">${g.instance ? html`<a href=${chatPath(g.instance)} title=${g.instance}>${nameFor(g.instance)}</a>` : tn("noAssignee")}</h2>
|
|
133
147
|
${g.items.map((item) => html`<${NeedsItem} key=${item.id} item=${item} nowMs=${nowMs} owner=${owner} />`)}
|
|
134
148
|
</section>`)}
|
|
135
149
|
<${NotifyToggle} />
|
|
@@ -180,14 +194,15 @@ function NeedsItem({ item, nowMs, owner }) {
|
|
|
180
194
|
<span class="n-icon"><${Icon} name=${ICON[item.type] || "alert"} size=${18} /></span>
|
|
181
195
|
<div class="n-body">
|
|
182
196
|
<div class="n-title"><strong>${reasonText(item)}</strong><span class="n-age">${age(item.since, nowMs)}</span></div>
|
|
183
|
-
${item
|
|
197
|
+
${detailOf(item) ? html`<p class="n-detail">${detailOf(item)}</p>` : null}
|
|
184
198
|
<div class="n-actions">
|
|
185
199
|
${item.type === "prompt" && Array.isArray(item.actions) ? item.actions.map((a) => html`<button key=${a.id} type="button" class="btn btn-sm"
|
|
186
200
|
disabled=${promptBusy} onClick=${() => answer(a.id)}>${(p ? p.busy : fallback === "busy") ? tn("answering") : a.label}</button>`) : null}
|
|
187
201
|
${item.type === "delivery" ? html`<button type="button" class="btn btn-sm" disabled=${!!ack} onClick=${acknowledge}>
|
|
188
202
|
<${Icon} name="check" size=${14} />${ack === "busy" ? tn("acknowledging") : tn("acknowledge")}</button>` : null}
|
|
189
203
|
${item.reason === "auth_paused" && item.signIn && signInOffered() ? html`<button type="button" class="btn btn-sm btn-primary" onClick=${() => openSignIn(item.signIn)}>${tn("signIn")}</button>` : null}
|
|
190
|
-
|
|
204
|
+
${item.type === "task" && item.card ? html`<a class="btn btn-sm" href=${`/ui/fleet?card=${encodeURIComponent(item.card.code)}`}>${tn("openCard")}</a>` : null}
|
|
205
|
+
${item.instance ? html`<a class="btn btn-sm btn-ghost" href=${chatPath(item.instance)}>${tn("open")}</a>` : null}
|
|
191
206
|
</div>
|
|
192
207
|
</div>
|
|
193
208
|
</article>`;
|
|
@@ -175,7 +175,7 @@ export function SettingsPanel({ route, navKey }) {
|
|
|
175
175
|
const applied = (pendingChanges || []).filter((p) => p.state === "applied").map((p) => p.id).join(",");
|
|
176
176
|
useEffect(() => { if (applied) reload(); }, [applied]);
|
|
177
177
|
|
|
178
|
-
const stage = (key, change) => {
|
|
178
|
+
const stage = (key, change, quiet = false) => {
|
|
179
179
|
setStaged((s) => {
|
|
180
180
|
const m = new Map(s);
|
|
181
181
|
const prev = m.get(key);
|
|
@@ -186,7 +186,7 @@ export function SettingsPanel({ route, navKey }) {
|
|
|
186
186
|
m.set(key, { ...change, stageKey: key });
|
|
187
187
|
return m;
|
|
188
188
|
});
|
|
189
|
-
toast(tn("queued", change.label));
|
|
189
|
+
if (!quiet) toast(tn("queued", change.label));
|
|
190
190
|
};
|
|
191
191
|
const unstage = (key) => setStaged((s) => { if (!s.has(key)) return s; const m = new Map(s); m.delete(key); return m; });
|
|
192
192
|
const discard = () => { dropSecrets(staged); setStaged(new Map()); reload(); };
|
|
@@ -818,11 +818,21 @@ function WebSection({ ctx, staged }) {
|
|
|
818
818
|
terminal: { enabled: term.enabled ?? null, ttl_minutes: term.ttl_minutes ?? null },
|
|
819
819
|
}), []);
|
|
820
820
|
const text = (v) => (v === null || v === undefined ? "" : String(v));
|
|
821
|
-
const [f, setF] = useState(() =>
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
821
|
+
const [f, setF] = useState(() => {
|
|
822
|
+
// Settings sections share one draft. Reopening Web must show it as well as preserve it in the Apply bar, so a
|
|
823
|
+
// subsequent field edit keeps earlier edits. The original disk values remain the diff/confirmation baseline.
|
|
824
|
+
const request = staged.get("web")?.request;
|
|
825
|
+
const pending = request?.method === "PUT" && request.url === "/api/settings/fleet/web" ? request.body || {} : {};
|
|
826
|
+
const pub = pending.public_link || {}, session = pending.session?.local || {}, terminal = pending.web_terminal || {};
|
|
827
|
+
const pick = (obj, key, fallback) => Object.prototype.hasOwnProperty.call(obj, key) ? obj[key] : fallback;
|
|
828
|
+
return {
|
|
829
|
+
echo: pick(pending, "echo_to_channel", original.echo), log: pick(pending, "log_tab", original.log), usagePub: pick(pending, "usage_report_on_public_link", original.usagePub),
|
|
830
|
+
pub: pick(pub, "allow_public", original.pub.allow_public), ttl: String(pick(pub, "ttl_minutes", original.pub.ttl_minutes)), protocol: pick(pub, "protocol", original.pub.protocol),
|
|
831
|
+
hostname: pick(pub, "hostname", original.pub.hostname), originPort: String(pick(pub, "origin_port", original.pub.origin_port)),
|
|
832
|
+
abs: text(pick(session, "absolute_hours", original.session.absolute_hours)), idle: text(pick(session, "idle_hours", original.session.idle_hours)),
|
|
833
|
+
termOn: pick(terminal, "enabled", original.terminal.enabled) !== false, termTtl: text(pick(terminal, "ttl_minutes", original.terminal.ttl_minutes)),
|
|
834
|
+
};
|
|
835
|
+
});
|
|
826
836
|
const [msg, setMsg] = useState(null);
|
|
827
837
|
const set = (k) => (v) => setF((x) => ({ ...x, [k]: v }));
|
|
828
838
|
// Empty = the default (null in the write: the key is removed from fleet.yaml).
|
|
@@ -840,9 +850,14 @@ function WebSection({ ctx, staged }) {
|
|
|
840
850
|
termTtl: value(f.termTtl) !== null && (!Number.isInteger(value(f.termTtl)) || value(f.termTtl) < 1 || value(f.termTtl) > 20) ? "webTerminalTtlRange" : "",
|
|
841
851
|
};
|
|
842
852
|
const ready = !overPublicLink && !Object.values(errors).some(Boolean);
|
|
843
|
-
const review = () => {
|
|
844
|
-
|
|
845
|
-
|
|
853
|
+
const review = (quiet = false) => {
|
|
854
|
+
// Invalid intermediate text must not leave an older valid draft available to Apply.
|
|
855
|
+
if (!ready) { if (staged.has("web")) ctx.unstage("web"); setMsg(null); return; }
|
|
856
|
+
// Keep an existing secret reference, including a custom one. Only a newly supplied hostname without any
|
|
857
|
+
// reference adopts the standard env key; mounting or editing an unrelated field never rewrites credentials.
|
|
858
|
+
const tokenEnv = f.hostname === original.pub.hostname ? original.pub.tunnel_token_env
|
|
859
|
+
: f.hostname ? original.pub.tunnel_token_env || "AGEND_CLOUDFLARE_TUNNEL_TOKEN" : "";
|
|
860
|
+
const publicPatch = changedFields({ allow_public: f.pub, ttl_minutes: Number(f.ttl), protocol: f.protocol, hostname: f.hostname, tunnel_token_env: tokenEnv, origin_port: Number(f.originPort) }, original.pub);
|
|
846
861
|
const sessionPatch = changedFields({ absolute_hours: value(f.abs), idle_hours: value(f.idle) }, original.session);
|
|
847
862
|
// The checkbox shows the default (on) for an unset key: only a real toggle is written; on again = back to the default.
|
|
848
863
|
const terminalPatch = changedFields({ enabled: f.termOn === (original.terminal.enabled !== false) ? original.terminal.enabled : f.termOn ? null : false,
|
|
@@ -865,10 +880,16 @@ function WebSection({ ctx, staged }) {
|
|
|
865
880
|
];
|
|
866
881
|
if (fields.length) {
|
|
867
882
|
ctx.stage("web", { label: tn("sec_web"), impact: batchImpact(schema, fields, (k) => impactOf(schema, k)),
|
|
868
|
-
request: { method: "PUT", url: "/api/settings/fleet/web", body } });
|
|
883
|
+
request: { method: "PUT", url: "/api/settings/fleet/web", body } }, quiet);
|
|
869
884
|
} else if (staged.has("web")) ctx.unstage("web");
|
|
870
|
-
setMsg(tn("readyApply"));
|
|
885
|
+
setMsg(fields.length ? tn("readyApply") : null);
|
|
871
886
|
};
|
|
887
|
+
// This form already has validation and a single shared Web draft. Valid edits join that draft immediately;
|
|
888
|
+
// Apply, its confirmations and the leave guard still own all HTTP writes. Quiet staging avoids a toast per key.
|
|
889
|
+
// Returning from another Settings section remounts this form: its unchanged initial fields must not erase the
|
|
890
|
+
// parent's already-staged Web draft. Only an actual field edit replaces or removes that draft.
|
|
891
|
+
const formMounted = useRef(false);
|
|
892
|
+
useEffect(() => { if (!formMounted.current) { formMounted.current = true; return; } review(true); }, [f]);
|
|
872
893
|
const shown = (path) => {
|
|
873
894
|
const [top, key] = path.split(".");
|
|
874
895
|
const v = (top === "web" ? web : term)[key];
|
|
@@ -920,7 +941,7 @@ function WebSection({ ctx, staged }) {
|
|
|
920
941
|
<p class="note">${tn("webReadOnlyLead")}</p>
|
|
921
942
|
<dl class="s-web-ro">${WEB_READ_ONLY.map((k) => html`<div key=${k}><dt><code>${k}</code></dt><dd>${shown(k)}</dd></div>`)}</dl>
|
|
922
943
|
</section>
|
|
923
|
-
<div class="save-row"><button type="button" class="btn btn-primary" disabled=${!ready} onClick=${review}>${tn("reviewChanges")}</button>
|
|
944
|
+
<div class="save-row"><button type="button" class="btn btn-primary" disabled=${!ready} onClick=${() => review()}>${tn("reviewChanges")}</button>
|
|
924
945
|
${msg ? html`<span class="note" role="status">${msg}</span>` : null}</div>
|
|
925
946
|
</div>`;
|
|
926
947
|
}
|
|
@@ -1062,6 +1083,7 @@ function PublicTunnelToken() {
|
|
|
1062
1083
|
try {
|
|
1063
1084
|
const prepared = await api(`${base}/verify`, { method: "POST", headers: { "Idempotency-Key": key }, body: JSON.stringify(clear ? { clear: true } : { secret: value }) });
|
|
1064
1085
|
if (!lease.current()) return;
|
|
1086
|
+
if (!prepared.ok && prepared.body?.error_code === "invalid_tunnel_token") { setMessage(tn("namedTokenInvalid")); return; }
|
|
1065
1087
|
if (!prepared.ok) throw Error("refused");
|
|
1066
1088
|
const applied = await confirmedWrite(`${base}/apply`, { method: "POST", key, label: tn(clear ? "namedTokenClear" : "namedTokenSet"), body: { verification_id: prepared.body?.verification_id, idempotency_key: key } });
|
|
1067
1089
|
let body = applied.body || {};
|
package/dist/ui/preview-panel.js
CHANGED
|
@@ -16,7 +16,8 @@ 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 {
|
|
19
|
+
import { experimentalComponent, useCanvasWorker } from "/assets/feature-flags.js";
|
|
20
|
+
const EffectTrialPanel = experimentalComponent(() => import("./effect-trial-panel.js"), m => m.EffectTrialPanel);
|
|
20
21
|
import { chatTime, onDayChange } from "./chat-time.js";
|
|
21
22
|
|
|
22
23
|
const R = () => globalThis.AgendChatRender;
|
package/dist/ui/search-dialog.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
// agents, a task — opens here as the index's copy (GET /ui/search/context). Text only: no innerHTML, no style attribute.
|
|
6
6
|
import { html, useEffect, useLayoutEffect, useMemo, useRef, useState } from "/assets/app-html.js";
|
|
7
7
|
import { t, register } from "/assets/app-i18n.js";
|
|
8
|
+
import { recordEdit } from "/assets/text-undo.js";
|
|
8
9
|
import { appStore, useStore } from "/assets/app-store.js";
|
|
9
10
|
import { navigate } from "/assets/app-nav.js";
|
|
10
11
|
import { chatPath, fleetPath } from "/assets/app-route.js";
|
|
@@ -286,7 +287,7 @@ export function SearchDialog({ preset = {}, onClose }) {
|
|
|
286
287
|
<div class="search-body" ref=${list}>
|
|
287
288
|
${detail ? html`<${Detail} detail=${detail} q=${q} label=${label} onBack=${() => { setDetail(null); input.current && input.current.focus(); }} onClose=${onClose} />`
|
|
288
289
|
: html`<${Results} q=${q} res=${res} groups=${groups} active=${active} setActive=${setActive} open=${open} label=${label} filtered=${activeFilters > 0}
|
|
289
|
-
recent=${recent} pick=${(s) => { setQ(s); input.current && input.current.focus(); }} clearRecent=${() => { forgetRecent(); setRecent([]); }} />`}
|
|
290
|
+
recent=${recent} pick=${(s) => { recordEdit(input.current); setQ(s); input.current && input.current.focus(); }} clearRecent=${() => { forgetRecent(); setRecent([]); }} />`}
|
|
290
291
|
</div>
|
|
291
292
|
<div class="search-foot" aria-hidden="true">${t("search.keys")}</div>
|
|
292
293
|
</div></dialog>`;
|
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
// versions, the pictures and fonts it may name, and this device's off switch. Settings itself is always drawn stock
|
|
3
3
|
// (owner-css.js removes the link here), so a stylesheet that hides things can always be fixed from this page.
|
|
4
4
|
import { html, useEffect, useRef, useState } from "/assets/app-html.js";
|
|
5
|
-
import { t } from "/assets/app-i18n.js";
|
|
5
|
+
import { register, t } from "/assets/app-i18n.js";
|
|
6
|
+
import { recordEdit } from "/assets/text-undo.js";
|
|
6
7
|
import { appStore, useStore } from "/assets/app-store.js";
|
|
7
8
|
import { ModsCard } from "./settings-mods.js";
|
|
8
|
-
import {
|
|
9
|
+
import { experimentalComponent, useCanvasWorker } from "/assets/feature-flags.js";
|
|
10
|
+
const EffectsCard = experimentalComponent(() => import("./settings-effects.js"), m => m.EffectsCard);
|
|
9
11
|
import { Icon } from "/assets/ui-icons.js";
|
|
10
12
|
import { toast } from "/assets/ui-toast.js";
|
|
11
13
|
import { ownerCssOffHere, ownerCssTrial, safeMode, setOwnerCssActive, setOwnerCssOffHere, startOwnerCssTrial } from "/assets/owner-css.js";
|
|
@@ -21,6 +23,15 @@ import { CardTitle } from "./settings-learn.js";
|
|
|
21
23
|
import "./settings-strings.js";
|
|
22
24
|
|
|
23
25
|
const tn = (k, ...v) => t(`settings.${k}`, ...v);
|
|
26
|
+
/** #2390: a refusal's detail in the page's language when the host named it (detail_key), else its English as sent. */
|
|
27
|
+
export function appIconDetail(body) {
|
|
28
|
+
const key = body && typeof body.detail_key === "string" ? `appIconDetail_${body.detail_key}` : null;
|
|
29
|
+
const english = (body && body.detail) || "";
|
|
30
|
+
if (!key) return english;
|
|
31
|
+
const args = Array.isArray(body.detail_args) ? body.detail_args : [];
|
|
32
|
+
const said = tn(key, ...(body.detail_key === "corrupt" ? [english] : args));
|
|
33
|
+
return said === `settings.${key}` ? english : said;
|
|
34
|
+
}
|
|
24
35
|
const short = (sha) => (sha ? sha.slice(0, 8) : "");
|
|
25
36
|
const kib = (n) => `${Math.max(1, Math.round(n / 1024))} KiB`;
|
|
26
37
|
|
|
@@ -49,7 +60,26 @@ export function AppearanceSection() {
|
|
|
49
60
|
${group("groupLayout")}<${SidebarWidgetCard} />
|
|
50
61
|
${group("groupCss")}<${OwnerCssCard} /><${DeviceCard} />
|
|
51
62
|
${group("groupMods")}<${ModsCard} /><${EffectsCard} />
|
|
52
|
-
${group("groupAdvanced")}<${CardLimitsCard} /><${RestoreDefaultsCard} />`;
|
|
63
|
+
${group("groupAdvanced")}<${ExperimentalEffectsCard} /><${CardLimitsCard} /><${RestoreDefaultsCard} />`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
register("experimentalEffects", {
|
|
67
|
+
en: { title: "Experimental: Canvas Worker effects", help: "Enable uploaded Canvas Worker effects. Ambient effects are always available. Saved effects stay intact when this is off.", failed: "Could not change this setting. Try again." },
|
|
68
|
+
"zh-TW": { title: "實驗功能:Canvas Worker 特效", help: "啟用上傳的 Canvas Worker 特效。氛圍特效不受影響;關閉時保留已儲存的特效。", failed: "無法變更此設定,請重試。" },
|
|
69
|
+
});
|
|
70
|
+
function ExperimentalEffectsCard() {
|
|
71
|
+
const enabled = useCanvasWorker(), [writable, setWritable] = useState(false), [busy, setBusy] = useState(false), [failed, setFailed] = useState(false);
|
|
72
|
+
useEffect(() => { let live = true; Promise.resolve(globalThis.AgendAuth?.trialSession?.()).then(value => { if (live) setWritable(value?.localAdmin === true); }, () => {}); return () => { live = false; }; }, []);
|
|
73
|
+
if (!writable) return null;
|
|
74
|
+
const change = async value => {
|
|
75
|
+
setBusy(true); setFailed(false);
|
|
76
|
+
try {
|
|
77
|
+
const result = await confirmedWrite("/api/settings/fleet/web", { method: "PUT", body: { experimental: { canvas_worker: value } }, label: t("experimentalEffects.title") });
|
|
78
|
+
if (result.ok) appStore.set({ canvasWorker: value }); else setFailed(true);
|
|
79
|
+
} catch { setFailed(true); }
|
|
80
|
+
finally { setBusy(false); }
|
|
81
|
+
};
|
|
82
|
+
return html`<section class="card s-appearance s-experimental-effects"><label class="check"><input type="checkbox" checked=${enabled} disabled=${busy} onChange=${event => change(event.target.checked)} /><span>${t("experimentalEffects.title")}</span></label><p class="sub">${t("experimentalEffects.help")}</p>${failed ? html`<p role="alert">${t("experimentalEffects.failed")}</p>` : null}</section>`;
|
|
53
83
|
}
|
|
54
84
|
|
|
55
85
|
/** #2056 B: a sample sidebar row in the host's default colour places, so the choice is seen rather than described. */
|
|
@@ -210,8 +240,8 @@ function AppIconCard() {
|
|
|
210
240
|
const answered = (r, done) => {
|
|
211
241
|
setBusy(false);
|
|
212
242
|
if (!r.ok) {
|
|
213
|
-
const known = ["app_icon_too_large", "app_icon_not_png", "app_icon_unreadable", "app_icon_admin_only", "app_icon_needs_a_signed_in_person", "app_icon_busy", "app_icon_superseded", "app_icon_not_reset"];
|
|
214
|
-
setError(known.includes(r.body && r.body.error) ? tn(`appIconError_${r.body.error}`, (r.body
|
|
243
|
+
const known = ["app_icon_too_large", "app_icon_not_png", "app_icon_unreadable", "app_icon_admin_only", "app_icon_needs_a_signed_in_person", "app_icon_busy", "app_icon_superseded", "app_icon_not_reset", "app_icon_not_kept", "app_icon_failed"];
|
|
244
|
+
setError(known.includes(r.body && r.body.error) ? tn(`appIconError_${r.body.error}`, appIconDetail(r.body)) : tn("appIconError_other", (r.body && r.body.error) || String(r.status)));
|
|
215
245
|
return;
|
|
216
246
|
}
|
|
217
247
|
setState(r.body);
|
|
@@ -347,6 +377,7 @@ function SidebarWidgetCard() {
|
|
|
347
377
|
const app = useStore(appStore);
|
|
348
378
|
const saved = sidebarWidgetRaw();
|
|
349
379
|
const [text, setText] = useState(() => (saved ? JSON.stringify(saved, null, 2) : ""));
|
|
380
|
+
const area = useRef(null);
|
|
350
381
|
const [error, setError] = useState("");
|
|
351
382
|
let parsed = null, problem = "";
|
|
352
383
|
if (text.trim()) {
|
|
@@ -359,17 +390,17 @@ function SidebarWidgetCard() {
|
|
|
359
390
|
setSidebarWidget(parsed);
|
|
360
391
|
toast(tn("widgetSaved"));
|
|
361
392
|
};
|
|
362
|
-
const remove = () => { setSidebarWidget(null); setText(""); setError(""); toast(tn("widgetRemoved")); };
|
|
393
|
+
const remove = () => { setSidebarWidget(null); recordEdit(area.current); setText(""); setError(""); toast(tn("widgetRemoved")); };
|
|
363
394
|
return html`<section class="card s-appearance s-synced s-widget"><${CardTitle} title=${tn("widgetTitle")} topic="widgets" />
|
|
364
395
|
<p class="sub">${tn("widgetLead")}</p>
|
|
365
396
|
<label class="field"><span class="sr-only">${tn("widgetTitle")}</span>
|
|
366
|
-
<textarea class="s-widget-text" rows="10" spellcheck=${false} autocapitalize="off" autocomplete="off" value=${text}
|
|
397
|
+
<textarea ref=${area} class="s-widget-text" rows="10" spellcheck=${false} autocapitalize="off" autocomplete="off" value=${text}
|
|
367
398
|
onInput=${(e) => { setText(e.target.value); setError(""); }}></textarea></label>
|
|
368
399
|
${error || problem ? html`<p class="err" role="alert">${error || problem}</p>` : null}
|
|
369
400
|
${preview ? html`<div class="s-widget-preview"><${AgentWidget} slot="sidebar" widget=${preview} binds=${fleetBinds(app.instances, app.exec, app.awaiting)} /></div>` : null}
|
|
370
401
|
<div class="s-widget-actions">
|
|
371
402
|
<button type="button" class="btn btn-primary" disabled=${!parsed || !!problem} onClick=${save}><${Icon} name="check" size=${16} />${tn("widgetSave")}</button>
|
|
372
|
-
${!text.trim() ? html`<button type="button" class="btn" onClick=${() => setText(JSON.stringify(SIDEBAR_WIDGET_EXAMPLE, null, 2))}>${tn("widgetExample")}</button>` : null}
|
|
403
|
+
${!text.trim() ? html`<button type="button" class="btn" onClick=${() => { recordEdit(area.current); setText(JSON.stringify(SIDEBAR_WIDGET_EXAMPLE, null, 2)); }}>${tn("widgetExample")}</button>` : null}
|
|
373
404
|
${saved ? html`<button type="button" class="btn" onClick=${remove}>${tn("widgetRemove")}</button>` : null}
|
|
374
405
|
</div>
|
|
375
406
|
</section>`;
|
|
@@ -378,6 +409,7 @@ function SidebarWidgetCard() {
|
|
|
378
409
|
function OwnerCssCard() {
|
|
379
410
|
const [state, setState] = useState(null);
|
|
380
411
|
const [text, setText] = useState("");
|
|
412
|
+
const area = useRef(null);
|
|
381
413
|
const [busy, setBusy] = useState(false);
|
|
382
414
|
const [error, setError] = useState("");
|
|
383
415
|
const trial = useStore(ownerCssTrial);
|
|
@@ -386,6 +418,8 @@ function OwnerCssCard() {
|
|
|
386
418
|
const r = await call("/ui/owner-css/state");
|
|
387
419
|
if (!r.ok || !r.body) { setState({ failed: true }); return; }
|
|
388
420
|
setState(r.body);
|
|
421
|
+
// #2342: a restored version replaces the editor's text as a step Ctrl+Z goes back from.
|
|
422
|
+
if (opts.undoable) recordEdit(area.current);
|
|
389
423
|
if (opts.text !== false) setText(r.body.text || "");
|
|
390
424
|
};
|
|
391
425
|
useEffect(() => { load(); }, []);
|
|
@@ -409,7 +443,7 @@ function OwnerCssCard() {
|
|
|
409
443
|
setBusy(false);
|
|
410
444
|
if (!r.ok) { setError(refusal(r.body)); return; }
|
|
411
445
|
setOwnerCssActive(r.body.active);
|
|
412
|
-
load();
|
|
446
|
+
load({ undoable: true });
|
|
413
447
|
};
|
|
414
448
|
const addAsset = async () => {
|
|
415
449
|
const f = file.current && file.current.files && file.current.files[0];
|
|
@@ -440,7 +474,7 @@ function OwnerCssCard() {
|
|
|
440
474
|
<p class="sub">${tn("cssLead")}</p>
|
|
441
475
|
<p class="s-css-status" role="status">${inForce}</p>
|
|
442
476
|
<label class="field"><span class="sr-only">${tn("cssTitle")}</span>
|
|
443
|
-
<textarea class="s-css-text" rows="14" spellcheck=${false} autocapitalize="off" autocomplete="off" value=${text} disabled=${busy}
|
|
477
|
+
<textarea ref=${area} class="s-css-text" rows="14" spellcheck=${false} autocapitalize="off" autocomplete="off" value=${text} disabled=${busy}
|
|
444
478
|
onInput=${(e) => setText(e.target.value)}></textarea></label>
|
|
445
479
|
${error ? html`<p class="err" role="alert">${error}</p>` : null}
|
|
446
480
|
<div class="s-css-actions">
|