@songsid/agend 2.3.0-beta.6 → 2.3.0-beta.8
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-instructions.md +1 -1
- package/dist/agent-cli.js +21 -2
- package/dist/agent-cli.js.map +1 -1
- package/dist/agent-endpoint.js +4 -0
- package/dist/agent-endpoint.js.map +1 -1
- package/dist/agent-file-read.js +68 -8
- package/dist/agent-file-read.js.map +1 -1
- package/dist/agent-whats-new.js +4 -0
- package/dist/agent-whats-new.js.map +1 -1
- package/dist/auth-api.js +3 -1
- package/dist/auth-api.js.map +1 -1
- package/dist/authority/authority-state.d.ts +38 -0
- package/dist/authority/authority-state.js +114 -0
- package/dist/authority/authority-state.js.map +1 -0
- package/dist/authority/session-authorizations.d.ts +43 -0
- package/dist/authority/session-authorizations.js +102 -0
- package/dist/authority/session-authorizations.js.map +1 -0
- package/dist/authority/web-authority.d.ts +31 -11
- package/dist/authority/web-authority.js +39 -12
- package/dist/authority/web-authority.js.map +1 -1
- package/dist/cache-ledger.d.ts +16 -3
- package/dist/cache-ledger.js +33 -13
- package/dist/cache-ledger.js.map +1 -1
- package/dist/cache-service.d.ts +55 -3
- package/dist/cache-service.js +152 -16
- package/dist/cache-service.js.map +1 -1
- package/dist/channel/mcp-tools.js +13 -7
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/classic-channel-manager.d.ts +2 -2
- package/dist/classic-channel-manager.js +2 -6
- package/dist/classic-channel-manager.js.map +1 -1
- package/dist/config-validator.js +10 -4
- package/dist/config-validator.js.map +1 -1
- package/dist/daemon.d.ts +2 -0
- package/dist/daemon.js +31 -9
- package/dist/daemon.js.map +1 -1
- package/dist/delivery-outbox.d.ts +8 -0
- package/dist/delivery-outbox.js +7 -1
- package/dist/delivery-outbox.js.map +1 -1
- package/dist/fleet-manager.d.ts +109 -2
- package/dist/fleet-manager.js +280 -12
- package/dist/fleet-manager.js.map +1 -1
- package/dist/general-knowledge/skills/cross-instance-messaging/SKILL.md +2 -1
- package/dist/general-knowledge/skills/multi-channel/SKILL.md +2 -1
- package/dist/general-knowledge/skills/web-appearance/SKILL.md +89 -30
- package/dist/general-knowledge/skills/web-dashboard/SKILL.md +62 -8
- package/dist/general-knowledge/skills/worker-collaboration/SKILL.md +1 -1
- package/dist/human-delivery-receipts.d.ts +14 -0
- package/dist/human-delivery-receipts.js +42 -6
- package/dist/human-delivery-receipts.js.map +1 -1
- package/dist/instance-config-impact.js +1 -0
- package/dist/instance-config-impact.js.map +1 -1
- package/dist/instance-names.d.ts +4 -0
- package/dist/instance-names.js +12 -0
- package/dist/instance-names.js.map +1 -0
- package/dist/instructions.d.ts +8 -1
- package/dist/instructions.js +9 -2
- package/dist/instructions.js.map +1 -1
- package/dist/locale.js +52 -0
- package/dist/locale.js.map +1 -1
- package/dist/outbound-handlers.d.ts +6 -0
- package/dist/outbound-handlers.js +49 -2
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.d.ts +57 -1
- package/dist/outbound-schemas.js +34 -2
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/public-web-gateway.js +8 -2
- package/dist/public-web-gateway.js.map +1 -1
- package/dist/rollout-index.d.ts +5 -0
- package/dist/rollout-index.js +26 -5
- package/dist/rollout-index.js.map +1 -1
- package/dist/settings-api.d.ts +6 -0
- package/dist/settings-api.js +54 -1
- package/dist/settings-api.js.map +1 -1
- package/dist/settings-change.js +31 -0
- package/dist/settings-change.js.map +1 -1
- package/dist/settings-effect.js +10 -1
- package/dist/settings-effect.js.map +1 -1
- package/dist/tool-permissions.d.ts +0 -7
- package/dist/tool-permissions.js +73 -5
- package/dist/tool-permissions.js.map +1 -1
- package/dist/topic-commands.d.ts +2 -1
- package/dist/topic-commands.js +2 -4
- package/dist/topic-commands.js.map +1 -1
- package/dist/types.d.ts +13 -2
- package/dist/ui/app-effects.js +46 -28
- package/dist/ui/chat-ambient.js +307 -0
- package/dist/ui/chat-effect-posters.js +8 -8
- package/dist/ui/chat-effects.js +51 -22
- package/dist/ui/chat-strings.js +21 -6
- package/dist/ui/chat-thread.js +1 -1
- package/dist/ui/fleet-usage.js +401 -0
- package/dist/ui/panel-chat.js +22 -9
- package/dist/ui/panel-fleet.js +6 -5
- package/dist/ui/panel-settings.js +10 -2
- package/dist/ui/settings-appearance.js +8 -0
- package/dist/ui/settings-mods.js +19 -6
- package/dist/ui/settings-owner-effects.js +3 -3
- package/dist/ui/settings-strings.js +30 -0
- package/dist/ui/settings-web-authority.js +91 -0
- package/dist/ui/shared/app-route.js +1 -1
- package/dist/ui/shared/app-session.js +2 -0
- package/dist/ui/shared/app-shell.js +2 -1
- package/dist/ui/shared/app-themes.js +24 -6
- package/dist/ui/shared/app.css +95 -0
- package/dist/ui/shared/effect-app-runtime.js +68 -19
- package/dist/ui/shared/effect-chat-runtime.js +16 -3
- package/dist/ui/shared/effect-frame.js +17 -3
- package/dist/ui/shared/effect-policy.js +18 -5
- package/dist/ui/shared/effect-profiler.js +31 -9
- package/dist/ui/shared/effect-registry.js +17 -41
- package/dist/ui/shared/effect-stock-trust.js +4 -4
- package/dist/ui/shared/effect-trial-stage.js +18 -7
- package/dist/ui/shared/fx-motion.js +67 -0
- package/dist/ui/shared/usage-dialog.js +8 -0
- package/dist/ui/shared/view-strings.js +4 -0
- package/dist/ui/shared/web-authorize.js +151 -0
- package/dist/usage-report.d.ts +158 -0
- package/dist/usage-report.js +226 -0
- package/dist/usage-report.js.map +1 -0
- package/dist/usage-rollup.d.ts +51 -0
- package/dist/usage-rollup.js +168 -0
- package/dist/usage-rollup.js.map +1 -0
- package/dist/web-ambient.d.ts +72 -0
- package/dist/web-ambient.js +154 -0
- package/dist/web-ambient.js.map +1 -0
- package/dist/web-api.d.ts +33 -0
- package/dist/web-api.js +148 -18
- package/dist/web-api.js.map +1 -1
- package/dist/web-appearance-tools.d.ts +3 -0
- package/dist/web-appearance-tools.js +18 -3
- package/dist/web-appearance-tools.js.map +1 -1
- package/dist/web-appearance-view.d.ts +14 -2
- package/dist/web-appearance-view.js +32 -5
- package/dist/web-appearance-view.js.map +1 -1
- package/dist/web-appearance.d.ts +15 -1
- package/dist/web-appearance.js +77 -1
- package/dist/web-appearance.js.map +1 -1
- package/dist/web-asset-parts.d.ts +2 -2
- package/dist/web-asset-parts.js +3 -3
- package/dist/web-asset-parts.js.map +1 -1
- package/dist/web-auth.js +3 -1
- package/dist/web-auth.js.map +1 -1
- package/dist/web-effect-bootstrap.d.ts +12 -0
- package/dist/web-effect-bootstrap.js +49 -0
- package/dist/web-effect-bootstrap.js.map +1 -0
- package/dist/web-effect-bundle.js +46 -1
- package/dist/web-effect-bundle.js.map +1 -1
- package/dist/web-effect-playback.d.ts +30 -0
- package/dist/web-effect-playback.js +71 -0
- package/dist/web-effect-playback.js.map +1 -0
- package/dist/web-effect-profiles.d.ts +4 -4
- package/dist/web-effect-service.d.ts +11 -4
- package/dist/web-effect-service.js +25 -4
- package/dist/web-effect-service.js.map +1 -1
- package/dist/web-effect-tools.d.ts +1 -0
- package/dist/web-effect-tools.js +2 -1
- package/dist/web-effect-tools.js.map +1 -1
- package/dist/web-effect-worker.d.ts +1 -1
- package/dist/web-effect-worker.js +2 -2
- package/dist/web-effect-worker.js.map +1 -1
- package/dist/web-guest-access.js +3 -1
- package/dist/web-guest-access.js.map +1 -1
- package/dist/web-mod-agent.js +5 -1
- package/dist/web-mod-agent.js.map +1 -1
- package/dist/web-mod-bundle.d.ts +9 -1
- package/dist/web-mod-bundle.js +62 -3
- package/dist/web-mod-bundle.js.map +1 -1
- package/dist/web-mod-export.d.ts +9 -0
- package/dist/web-mod-export.js +21 -0
- package/dist/web-mod-export.js.map +1 -1
- package/dist/web-mod-store.d.ts +2 -1
- package/dist/web-mod-store.js +13 -6
- package/dist/web-mod-store.js.map +1 -1
- package/dist/web-session-authorization.d.ts +132 -0
- package/dist/web-session-authorization.js +193 -0
- package/dist/web-session-authorization.js.map +1 -0
- package/dist/web-session.d.ts +5 -0
- package/dist/web-session.js +16 -0
- package/dist/web-session.js.map +1 -1
- package/dist/web-settings-patch.d.ts +26 -2
- package/dist/web-settings-patch.js +50 -1
- package/dist/web-settings-patch.js.map +1 -1
- package/dist/web-settings-tools.d.ts +8 -0
- package/dist/web-settings-tools.js +14 -1
- package/dist/web-settings-tools.js.map +1 -1
- package/dist/web-shell-routes.d.ts +1 -1
- package/dist/web-shell-routes.js +1 -1
- package/dist/web-shell-routes.js.map +1 -1
- package/dist/web-theme-store.d.ts +39 -0
- package/dist/web-theme-store.js +122 -11
- package/dist/web-theme-store.js.map +1 -1
- package/package.json +1 -1
|
@@ -44,6 +44,7 @@ import { startTour } from "./chat-tour.js";
|
|
|
44
44
|
import "./chat-strings.js";
|
|
45
45
|
import { AppearanceSection } from "./settings-appearance.js";
|
|
46
46
|
import { GuestsCard } from "./settings-guests.js";
|
|
47
|
+
import { WebAuthorityCard } from "./settings-web-authority.js";
|
|
47
48
|
|
|
48
49
|
const tn = (k, ...v) => t(`settings.${k}`, ...v);
|
|
49
50
|
const ICONS = { device: "user", appearance: "palette", agents: "bot", bots: "plug", classic: "room", general: "sliders", web: "globe", advanced: "code" };
|
|
@@ -772,17 +773,20 @@ export const WEB_READ_ONLY = [
|
|
|
772
773
|
function WebSection({ ctx, staged }) {
|
|
773
774
|
const { schema } = ctx;
|
|
774
775
|
const web = ctx.fleet.web || {}, local = (web.session && web.session.local) || {}, term = ctx.fleet.web_terminal || {};
|
|
776
|
+
// Over the public link the fields read only; the Guests and Web authority cards are not offered (the server never
|
|
777
|
+
// serves their reads there).
|
|
775
778
|
const overPublicLink = globalThis.document?.body?.dataset?.publicLink === "1";
|
|
776
779
|
const original = useMemo(() => ({
|
|
777
780
|
echo: web.echo_to_channel ?? true,
|
|
778
781
|
log: web.log_tab === true, // #1218: off by default
|
|
782
|
+
usagePub: web.usage_report_on_public_link === true, // #2122: off by default
|
|
779
783
|
pub: { allow_public: (web.public_link && web.public_link.allow_public) ?? true, ttl_minutes: (web.public_link && web.public_link.ttl_minutes) ?? 120, protocol: (web.public_link && web.public_link.protocol) ?? "http2" },
|
|
780
784
|
session: { absolute_hours: local.absolute_hours ?? null, idle_hours: local.idle_hours ?? null },
|
|
781
785
|
terminal: { enabled: term.enabled ?? null, ttl_minutes: term.ttl_minutes ?? null },
|
|
782
786
|
}), []);
|
|
783
787
|
const text = (v) => (v === null || v === undefined ? "" : String(v));
|
|
784
788
|
const [f, setF] = useState(() => ({
|
|
785
|
-
echo: original.echo, log: original.log, pub: original.pub.allow_public, ttl: String(original.pub.ttl_minutes), protocol: original.pub.protocol,
|
|
789
|
+
echo: original.echo, log: original.log, usagePub: original.usagePub, pub: original.pub.allow_public, ttl: String(original.pub.ttl_minutes), protocol: original.pub.protocol,
|
|
786
790
|
abs: text(original.session.absolute_hours), idle: text(original.session.idle_hours),
|
|
787
791
|
termOn: original.terminal.enabled !== false, termTtl: text(original.terminal.ttl_minutes),
|
|
788
792
|
}));
|
|
@@ -811,6 +815,7 @@ function WebSection({ ctx, staged }) {
|
|
|
811
815
|
const body = {
|
|
812
816
|
...(f.echo !== original.echo ? { echo_to_channel: f.echo } : {}),
|
|
813
817
|
...(f.log !== original.log ? { log_tab: f.log } : {}),
|
|
818
|
+
...(f.usagePub !== original.usagePub ? { usage_report_on_public_link: f.usagePub } : {}),
|
|
814
819
|
...(Object.keys(publicPatch).length ? { public_link: publicPatch } : {}),
|
|
815
820
|
...(Object.keys(sessionPatch).length ? { session: { local: sessionPatch } } : {}),
|
|
816
821
|
...(Object.keys(terminalPatch).length ? { web_terminal: terminalPatch } : {}),
|
|
@@ -818,6 +823,7 @@ function WebSection({ ctx, staged }) {
|
|
|
818
823
|
const fields = [
|
|
819
824
|
...(body.echo_to_channel !== undefined ? ["web.echo_to_channel"] : []),
|
|
820
825
|
...(body.log_tab !== undefined ? ["web.log_tab"] : []),
|
|
826
|
+
...(body.usage_report_on_public_link !== undefined ? ["web.usage_report_on_public_link"] : []),
|
|
821
827
|
...Object.keys(publicPatch).map((k) => `web.public_link.${k}`),
|
|
822
828
|
...Object.keys(sessionPatch).map((k) => `web.session.local.${k}`),
|
|
823
829
|
...Object.keys(terminalPatch).map((k) => `web_terminal.${k}`),
|
|
@@ -859,6 +865,8 @@ function WebSection({ ctx, staged }) {
|
|
|
859
865
|
<p class="note">${tn("webEchoHint")}</p>
|
|
860
866
|
<label class="check"><input id="webLogTab" type="checkbox" checked=${f.log} onChange=${(e) => set("log")(e.target.checked)} /><span>${tn("webLogTab")}</span><${Impact} schema=${schema} field="web.log_tab" /></label>
|
|
861
867
|
<p class="note">${tn("webLogTabHint")}</p>
|
|
868
|
+
<label class="check"><input id="webUsagePublic" type="checkbox" checked=${f.usagePub} onChange=${(e) => set("usagePub")(e.target.checked)} /><span>${tn("webUsagePublic")}</span><${Impact} schema=${schema} field="web.usage_report_on_public_link" /></label>
|
|
869
|
+
<p class="note">${tn("webUsagePublicHint")}</p>
|
|
862
870
|
<label class="check"><input id="publicWebLink" type="checkbox" checked=${f.pub} onChange=${(e) => set("pub")(e.target.checked)} /><span>${tn("publicLink")}</span><${Impact} schema=${schema} field="web.public_link.allow_public" /></label>
|
|
863
871
|
<p class="note">${tn("publicLinkHint")}</p>
|
|
864
872
|
<div class="grid2">
|
|
@@ -867,7 +875,7 @@ function WebSection({ ctx, staged }) {
|
|
|
867
875
|
</div>
|
|
868
876
|
</section>
|
|
869
877
|
</fieldset>
|
|
870
|
-
|
|
878
|
+
${overPublicLink ? null : html`<${GuestsCard} ctx=${ctx} staged=${staged} /><${WebAuthorityCard} ctx=${ctx} staged=${staged} />`}
|
|
871
879
|
<section class="card s-web-readonly"><h3>${tn("webReadOnlyTitle")}</h3>
|
|
872
880
|
<p class="note">${tn("webReadOnlyLead")}</p>
|
|
873
881
|
<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>
|
|
@@ -10,6 +10,7 @@ import { Icon } from "/assets/ui-icons.js";
|
|
|
10
10
|
import { toast } from "/assets/ui-toast.js";
|
|
11
11
|
import { ownerCssOffHere, ownerCssTrial, safeMode, setOwnerCssActive, setOwnerCssOffHere, startOwnerCssTrial } from "/assets/owner-css.js";
|
|
12
12
|
import { setThemesShown, themesStore } from "/assets/app-themes.js";
|
|
13
|
+
import { followsReducedMotion, onMotionChange, setFollowsReducedMotion } from "/assets/fx-motion.js";
|
|
13
14
|
import { CARD_SIZES, cardSizeDefault, cardSizeStore, setCardSizeDefault } from "/assets/identity-card.js";
|
|
14
15
|
import { AgentWidget, checkWidget, fleetBinds, setSidebarWidget, sidebarWidgetRaw, sidebarWidgetStore, widgetProblem } from "/assets/agent-widget.js";
|
|
15
16
|
import { confirmedWrite } from "./settings-confirm.js";
|
|
@@ -87,6 +88,7 @@ function IdentityColorCard() {
|
|
|
87
88
|
*/
|
|
88
89
|
export async function restoreAppearanceDefaults() {
|
|
89
90
|
setThemesShown(true);
|
|
91
|
+
setFollowsReducedMotion(false);
|
|
90
92
|
setCardSizeDefault("m");
|
|
91
93
|
savePref("card.sizeByInstance", null);
|
|
92
94
|
setSidebarWidget(null);
|
|
@@ -187,10 +189,16 @@ function CardLimitsCard() {
|
|
|
187
189
|
/** #1629 r2 (d): whether this person's screens show agents' themes (and, as they come, effects) — synced. */
|
|
188
190
|
function ThemesCard() {
|
|
189
191
|
const s = useStore(themesStore);
|
|
192
|
+
// #2117: this device's own switch (not synced): effects play unless it follows the system's reduced-motion setting.
|
|
193
|
+
const [follow, setFollow] = useState(followsReducedMotion);
|
|
194
|
+
useEffect(() => onMotionChange(() => setFollow(followsReducedMotion())), []);
|
|
190
195
|
return html`<section class="card s-appearance s-synced"><${CardTitle} title=${tn("themesTitle")} topic="themes" />
|
|
191
196
|
<label class="check"><input type="checkbox" checked=${s.shown} disabled=${safeMode()} onChange=${(e) => setThemesShown(e.target.checked)} />
|
|
192
197
|
<span>${tn("themesShow")}</span></label>
|
|
193
198
|
<p class="sub">${safeMode() ? tn("themesSafeMode") : tn("themesShowLead")}</p>
|
|
199
|
+
<label class="check"><input type="checkbox" id="fx-follow-reduced-motion" checked=${follow} onChange=${(e) => setFollowsReducedMotion(e.target.checked)} />
|
|
200
|
+
<span>${tn("themesFollowMotion")}</span></label>
|
|
201
|
+
<p class="sub">${tn("themesFollowMotionLead")}</p>
|
|
194
202
|
</section>`;
|
|
195
203
|
}
|
|
196
204
|
|
package/dist/ui/settings-mods.js
CHANGED
|
@@ -41,9 +41,9 @@ function why(body) {
|
|
|
41
41
|
|
|
42
42
|
/** The parts a plan offers, each with its default: everything but custom CSS. */
|
|
43
43
|
export function defaultChoice(plan) {
|
|
44
|
-
const c = { theme: false, identity: false, effects: false, ownerCss: false, widgets: {}, replace: false };
|
|
44
|
+
const c = { theme: false, identity: false, effects: false, ownerCss: false, ambient: false, widgets: {}, replace: false };
|
|
45
45
|
for (const it of plan.items) {
|
|
46
|
-
if (it.part === "theme" || it.part === "identity") c[it.part] = true;
|
|
46
|
+
if (it.part === "theme" || it.part === "identity" || it.part === "ambient") c[it.part] = true;
|
|
47
47
|
else if (it.part === "widget") c.widgets[it.slot] = true;
|
|
48
48
|
else if (it.part === "effect") c.effects = true;
|
|
49
49
|
// ownerCss stays false: "read it before turning it on" (r4 §4).
|
|
@@ -100,6 +100,19 @@ function PlanItem({ it, choice, setChoice, binds, fleet }) {
|
|
|
100
100
|
return html`<li class="s-mod-item">${tick(tn(`modPartWidget_${it.slot}`), !!choice.widgets[it.slot], (v) => setChoice({ ...choice, widgets: { ...choice.widgets, [it.slot]: v } }))}
|
|
101
101
|
<div class="s-mod-detail s-mod-widget">${w ? html`<${AgentWidget} slot=${it.slot} widget=${w} binds=${it.slot === "sidebar" ? fleet : binds} />` : null}</div></li>`;
|
|
102
102
|
}
|
|
103
|
+
if (it.part === "ambient") {
|
|
104
|
+
// #2117: each ambient as it will be drawn: its preset, glyphs (pictures shown) and colours; every field below.
|
|
105
|
+
const glyph = (g, i) => typeof g === "string" ? html`<span key=${i} class="s-mod-text">${g}</span>`
|
|
106
|
+
: html`<img key=${i} class="s-mod-glyph" src=${`/ui/mods/staged/${g.asset}`} alt="" loading="lazy" />`;
|
|
107
|
+
const one = (kind) => {
|
|
108
|
+
const a = it[kind];
|
|
109
|
+
if (!a || !Array.isArray(a.glyphs)) return null;
|
|
110
|
+
return html`<div key=${kind} class="s-mod-kv"><span class="k">${a.preset}</span><span class="v s-mod-sws">${a.glyphs.map(glyph)}${(a.colors || []).map((c) => html`<${Swatch} key=${c} color=${c} />`)}</span></div>`;
|
|
111
|
+
};
|
|
112
|
+
return html`<li class="s-mod-item">${tick(tn("modPartAmbient"), !!choice.ambient, (v) => setChoice({ ...choice, ambient: v }))}
|
|
113
|
+
<div class="s-mod-detail">${one("loop")}${one("click")}
|
|
114
|
+
<details class="s-mod-raw"><summary>${tn("modAllFields")}</summary><pre class="s-mod-css-text">${JSON.stringify({ loop: it.loop, click: it.click }, null, 2)}</pre></details></div></li>`;
|
|
115
|
+
}
|
|
103
116
|
if (it.part === "effect") {
|
|
104
117
|
return html`<li class="s-mod-item">${tick(tn("modPartEffect", it.title), choice.effects, (v) => setChoice({ ...choice, effects: v }))}
|
|
105
118
|
<div class="s-mod-detail"><span>${kib(it.bytes)} · ${tn("modRegions", it.regions.join(", "))}</span>${it.wants.length ? html`<span>${tn("modWants", it.wants.join(", "))}</span>` : null}</div></li>`;
|
|
@@ -120,7 +133,7 @@ function Plan({ staged, onDone }) {
|
|
|
120
133
|
const [target, setTarget] = useState("");
|
|
121
134
|
const [busy, setBusy] = useState(false);
|
|
122
135
|
const [error, setError] = useState("");
|
|
123
|
-
const toInstance = choice.theme || choice.identity || !!choice.widgets.chat_header || !!choice.widgets.details;
|
|
136
|
+
const toInstance = choice.theme || choice.identity || choice.ambient || !!choice.widgets.chat_header || !!choice.widgets.details;
|
|
124
137
|
const names = (app.instances || []).map((i) => i.name);
|
|
125
138
|
const install = async () => {
|
|
126
139
|
setBusy(true); setError("");
|
|
@@ -162,13 +175,13 @@ function Plan({ staged, onDone }) {
|
|
|
162
175
|
|
|
163
176
|
function ExportForm() {
|
|
164
177
|
const app = useStore(appStore);
|
|
165
|
-
const [f, setF] = useState({ instance: "", name: "", version: "1.0.0", title: "", theme: true, identity: true, header: true, details: true, sidebar: false, ownerCss: false });
|
|
178
|
+
const [f, setF] = useState({ instance: "", name: "", version: "1.0.0", title: "", theme: true, identity: true, header: true, details: true, ambient: true, sidebar: false, ownerCss: false });
|
|
166
179
|
const [error, setError] = useState("");
|
|
167
180
|
const set = (k) => (e) => setF({ ...f, [k]: e.target.type === "checkbox" ? e.target.checked : e.target.value });
|
|
168
181
|
const go = async () => {
|
|
169
182
|
setError("");
|
|
170
183
|
const body = { instance: f.instance || undefined, name: f.name, version: f.version, title: f.title,
|
|
171
|
-
include: { theme: f.theme, identity: f.identity, ownerCss: f.ownerCss, widgets: { chat_header: f.header, details: f.details, sidebar: f.sidebar } } };
|
|
184
|
+
include: { theme: f.theme, identity: f.identity, ambient: f.ambient, ownerCss: f.ownerCss, widgets: { chat_header: f.header, details: f.details, sidebar: f.sidebar } } };
|
|
172
185
|
let res;
|
|
173
186
|
try { res = await fetch("/ui/mods/export", { method: "POST", credentials: "same-origin", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body) }); }
|
|
174
187
|
catch { setError(tn("modFailed", "network")); return; }
|
|
@@ -182,7 +195,7 @@ function ExportForm() {
|
|
|
182
195
|
return html`<details class="s-mod-export"><summary>${tn("modExport")}</summary>
|
|
183
196
|
<p class="sub">${tn("modExportLead")}</p>
|
|
184
197
|
<label class="field"><span>${tn("modExportInstance")}</span><select value=${f.instance} onChange=${set("instance")}><option value="">—</option>${(app.instances || []).map((i) => html`<option key=${i.name} value=${i.name}>${i.name}</option>`)}</select></label>
|
|
185
|
-
<div class="s-mod-boxes">${box("theme", tn("modPartTheme"))}${box("identity", tn("modPartIdentity"))}${box("header", tn("modPartWidget_chat_header"))}${box("details", tn("modPartWidget_details"))}${box("sidebar", tn("modPartWidget_sidebar"))}${box("ownerCss", tn("modPartCss"))}</div>
|
|
198
|
+
<div class="s-mod-boxes">${box("theme", tn("modPartTheme"))}${box("identity", tn("modPartIdentity"))}${box("header", tn("modPartWidget_chat_header"))}${box("details", tn("modPartWidget_details"))}${box("ambient", tn("modPartAmbient"))}${box("sidebar", tn("modPartWidget_sidebar"))}${box("ownerCss", tn("modPartCss"))}</div>
|
|
186
199
|
<label class="field"><span>${tn("modExportName")}</span><input value=${f.name} placeholder="my-look" onInput=${set("name")} /></label>
|
|
187
200
|
<label class="field"><span>${tn("modExportVersion")}</span><input value=${f.version} onInput=${set("version")} /></label>
|
|
188
201
|
<label class="field"><span>${tn("modExportTitle")}</span><input value=${f.title} onInput=${set("title")} /></label>
|
|
@@ -13,7 +13,7 @@ register("ownerEffects", {
|
|
|
13
13
|
unknown: "Choices are unavailable. Reload to try again.", changed: "This page can no longer access these choices. Reload and sign in again.",
|
|
14
14
|
unavailable: "No longer available", clear: "Clear", reload: "Reload choices", saved: "Saved.", failed: "Couldn't save. Reload choices and try again.",
|
|
15
15
|
savedRefresh: "Saved, but the choices couldn't be refreshed. Reload to check.", readOnly: "Sign in as an admin to change these choices.",
|
|
16
|
-
playback: "
|
|
16
|
+
playback: "Effects don't require HTML-preview permission.",
|
|
17
17
|
pause: "Pause", resume: "Resume", running: "Running", cursorFailed: "The cursor could not be applied here. Clear or reload the choice to try again.", starting: "Starting", queued: "Waiting for a slot", paused: "Paused", off: "Playback is off or unavailable here.", budget: "Paused to keep the page responsive.", resumeFailed: "This effect cannot resume: the page or its safety history is not ready. Reload the page or choose another effect.",
|
|
18
18
|
},
|
|
19
19
|
"zh-TW": {
|
|
@@ -23,7 +23,7 @@ register("ownerEffects", {
|
|
|
23
23
|
unknown: "目前無法取得選擇,請重新載入再試。", changed: "這個頁面已無法存取選擇,請重新載入並登入。",
|
|
24
24
|
unavailable: "已無法使用", clear: "清除", reload: "重新載入選擇", saved: "已儲存。", failed: "無法儲存,請重新載入選擇再試。",
|
|
25
25
|
savedRefresh: "已儲存,但無法更新畫面;請重新載入確認。", readOnly: "請以管理員身分登入才能修改選擇。",
|
|
26
|
-
playback: "
|
|
26
|
+
playback: "效果不需要 HTML 預覽權限。",
|
|
27
27
|
pause: "暫停", resume: "恢復", running: "播放中", cursorFailed: "這裡無法套用游標。可清除或重新載入選擇後再試。", starting: "啟動中", queued: "等待空位", paused: "已暫停", off: "這裡已關閉播放,或無法播放效果。", budget: "為維持頁面順暢,已暫停。", resumeFailed: "頁面或安全紀錄尚未就緒,無法恢復此效果。請重新載入頁面,或選擇其他效果。",
|
|
28
28
|
},
|
|
29
29
|
});
|
|
@@ -39,7 +39,7 @@ export function OwnerEffects({ catalog, busy = false }) {
|
|
|
39
39
|
useEffect(() => { life.current.alive = true; const stop = watchOwnerEffects(); return () => { life.current.alive = false; life.current.seq++; stop(); }; }, []);
|
|
40
40
|
useEffect(() => {
|
|
41
41
|
let live = true; const entry = getAppEffects();
|
|
42
|
-
const pull = () => { if (!live) return; const gates = entry?.gates(); setRuntime({ entry, snapshot: entry?.api?.ownerSnapshot?.(), cursor: entry?.cursorSnapshot?.(), available: !!entry && !entry.dead && gates.enabled && gates.show && gates.
|
|
42
|
+
const pull = () => { if (!live) return; const gates = entry?.gates(); setRuntime({ entry, snapshot: entry?.api?.ownerSnapshot?.(), cursor: entry?.cursorSnapshot?.(), available: !!entry && !entry.dead && gates.enabled && gates.show && gates.transportReady && !gates.safe && !gates.publicLink && !gates.reducedMotion }); };
|
|
43
43
|
const off = entry?.subscribe(pull), end = entry?.onEnd(pull); pull();
|
|
44
44
|
return () => { live = false; off?.(); end?.(); };
|
|
45
45
|
}, []);
|
|
@@ -15,6 +15,7 @@ register("settings", {
|
|
|
15
15
|
publicProtocol: "Tunnel protocol",
|
|
16
16
|
webEcho: "Web echo",
|
|
17
17
|
webEchoHint: "Copies web messages to the bound Telegram or Discord topic. ClassicBot rooms follow it unless a room sets its own.",
|
|
18
|
+
webUsagePublic: "Usage report over the public link", webUsagePublicHint: "Fleet → Usage (up to 400 days of each agent's tokens, requests, messages and API-equivalent amounts) is offered over the temporary public link too. Off by default; guests never see it. Applies at once.",
|
|
18
19
|
webLogTab: "Log tab: each agent's steps", webLogTabHint: "Shows each instance's prompts, replies, tool calls and results, and fleet events in its Log tab, read from its own transcript only while the tab is open. Secret values are masked. Also over the public link. Turning it on waits for a fleet admin's confirmation.",
|
|
19
20
|
general: "General Settings",
|
|
20
21
|
advanced: "Advanced",
|
|
@@ -316,6 +317,8 @@ register("settings", {
|
|
|
316
317
|
themesTitle: "Agents' themes",
|
|
317
318
|
themesShow: "Show agents' themes, effects and widgets",
|
|
318
319
|
themesShowLead: "When off, every chat looks the same.",
|
|
320
|
+
themesFollowMotion: "Effects: follow the system's reduced-motion setting",
|
|
321
|
+
themesFollowMotionLead: "This device only: effects stay still while the system reduces motion.",
|
|
319
322
|
themesSafeMode: "This page was opened with ?safe=1: agents' themes are not shown on it.",
|
|
320
323
|
widgetTitle: "Your sidebar widget",
|
|
321
324
|
widgetLead: "A small panel under your agent list, written as JSON.",
|
|
@@ -337,6 +340,7 @@ register("settings", {
|
|
|
337
340
|
modReplace: "Replace the installed version {0}",
|
|
338
341
|
modPartTheme: "Theme (colours, font, pictures)",
|
|
339
342
|
modPartIdentity: "Card text (tagline and bio)",
|
|
343
|
+
modPartAmbient: "Ambient effect (falling, rising, twinkling or click glyphs)",
|
|
340
344
|
modPartWidget_chat_header: "Chat header widget",
|
|
341
345
|
modPartWidget_details: "Details page widget",
|
|
342
346
|
modPartWidget_sidebar: "Your sidebar widget",
|
|
@@ -439,6 +443,17 @@ register("settings", {
|
|
|
439
443
|
guestNarrow: "Narrow", guestNotReachable: "(no longer reachable)", guestReachesNothing: "This account reaches no agent from chat right now, so there is nothing to narrow.", guestNarrowLift: "Remove narrowing", guestNarrowHint: "Tick what this guest may still reach. Narrowing only takes away (it applies at once); widening or removing it waits for a fleet admin's confirmation.",
|
|
440
444
|
guestSignOut: "Sign out", guestSignOutTitle: "Sign out this guest?", guestSignOutConfirm: "{0}'s guest sessions end now, their sign-in code stops working and their browsers are forgotten. They can sign in again with /web while guest sign-in is on.",
|
|
441
445
|
guestSignedOut: "{0} is signed out.", guestSignedOutNotDurable: "{0} is signed out for now, but it could not be saved: a restart may bring their sessions back.", guestSignOutFailed: "That did not work — try again.",
|
|
446
|
+
vaTitle: "Web authority", vaReview: "Review web authority changes",
|
|
447
|
+
vaHint: "Whether a signed-in fleet admin's web messages carry their own account's authority, recorded in each message's delivery receipt. A session signed in with a code sent privately to that account, on the dashboard reached directly, is authorized once from Discord or Telegram; the public link and guests never are. Agents don't act on it yet: they still treat web messages as recognition only.",
|
|
448
|
+
vaLoadFailed: "Web authority could not be loaded.",
|
|
449
|
+
vaEnabled: "Web messages carry authority", vaEnabledHint: "On by default: a session carries authority only once its account confirms it from Discord or Telegram. Off: no web message carries authority, and any already given reads as none at once; authority given before it was turned off never comes back.",
|
|
450
|
+
vaStuckOff: "Web authority is off for now: its state could not be saved on this computer (see the fleet log). It comes back on by itself once it can be.",
|
|
451
|
+
vaLoopbackOnly: "Only this computer can authorize", vaLoopbackOnlyHint: "On: only a browser on this computer qualifies, not one on your network or tailnet, and authority already given from elsewhere reads as none. Turning it off again doesn't bring that back.",
|
|
452
|
+
vaLanWarning: "The dashboard also answers to {0} (web.allowed_hosts). Anyone who reaches it there with a stolen sign-in could give authority in your name. Turn on \"Only this computer can authorize\" unless you trust everyone who can reach it.",
|
|
453
|
+
vaHours: "An authorization lasts (hours, {0}–{1})", vaHoursRange: "Must be a number from 1 to 48",
|
|
454
|
+
vaSessionsTitle: "Authorized sessions", vaSessionsNone: "No session is authorized now.", vaSessionUntil: "Authorized {0} · until {1}",
|
|
455
|
+
vaRevoke: "Revoke web authority", vaRevokeTitle: "Revoke all web authority?", vaRevokeConfirm: "Every authority given from the web so far reads as none from now on, and every session's authorization ends. Nobody is signed out: a session carries authority again once it is authorized again.",
|
|
456
|
+
vaRevoked: "Web authority given so far is revoked.", vaRevokedNotDurable: "Revoked for now, but it could not be saved: web authority stays off until it can be, and a restart before then undoes the revocation.", vaRevokeFailed: "That did not work — try again.",
|
|
442
457
|
webReadOnlyLead: "Read-only here: these decide what the dashboard listens on and exposes. Edit fleet.yaml, then run agend reload (web.preview_port and web.preview_origin take a restart of AgEnD).",
|
|
443
458
|
notSet: "not set (default)",
|
|
444
459
|
defaultsTitle: "Defaults",
|
|
@@ -548,6 +563,7 @@ register("settings", {
|
|
|
548
563
|
publicProtocol: "Tunnel 協定",
|
|
549
564
|
webEcho: "Web 同步",
|
|
550
565
|
webEchoHint: "將網頁訊息複製到綁定的 Telegram 或 Discord 主題。ClassicBot 聊天室也照這個設定,除非該聊天室另外設定。",
|
|
566
|
+
webUsagePublic: "透過公開連結提供用量報告", webUsagePublicHint: "Fleet → 用量(最多 400 天、每個 agent 的 token、請求、訊息與依牌價換算的 API 等值)也會透過臨時公開連結提供。預設關閉;訪客一律看不到。變更立即生效。",
|
|
551
567
|
webLogTab: "紀錄分頁:每個 agent 的步驟", webLogTabHint: "在每個 instance 的「紀錄」分頁顯示提示、回覆、工具呼叫與結果,以及 fleet 事件;只在分頁開著時從它自己的對話紀錄讀取。機密值會遮蔽。公開連結也看得到。開啟需 fleet admin 確認。",
|
|
552
568
|
general: "一般設定",
|
|
553
569
|
advanced: "進階",
|
|
@@ -823,6 +839,8 @@ register("settings", {
|
|
|
823
839
|
themesTitle: "Agent 的主題",
|
|
824
840
|
themesShow: "顯示 agent 的主題、效果與小工具",
|
|
825
841
|
themesShowLead: "關閉時每個聊天看起來都一樣。",
|
|
842
|
+
themesFollowMotion: "效果:跟隨系統的「減少動態效果」設定",
|
|
843
|
+
themesFollowMotionLead: "只套用於這台裝置:系統減少動態效果時,效果保持靜止。",
|
|
826
844
|
themesSafeMode: "這個頁面是用 ?safe=1 開啟的:不顯示 agent 的主題。",
|
|
827
845
|
widgetTitle: "你的側邊欄小工具",
|
|
828
846
|
widgetLead: "agent 清單下方的小面板,用 JSON 撰寫。",
|
|
@@ -844,6 +862,7 @@ register("settings", {
|
|
|
844
862
|
modReplace: "取代已安裝的 {0} 版",
|
|
845
863
|
modPartTheme: "主題(顏色、字型、圖片)",
|
|
846
864
|
modPartIdentity: "身分卡文字(標語與簡介)",
|
|
865
|
+
modPartAmbient: "環境效果(飄落、上升、閃爍或點擊符號)",
|
|
847
866
|
modPartWidget_chat_header: "聊天標頭小工具",
|
|
848
867
|
modPartWidget_details: "詳細資料頁小工具",
|
|
849
868
|
modPartWidget_sidebar: "你的側邊欄小工具",
|
|
@@ -946,6 +965,17 @@ register("settings", {
|
|
|
946
965
|
guestNarrow: "縮小範圍", guestNotReachable: "(已無法使用)", guestReachesNothing: "這個帳號目前在聊天中沒有能用的 agent,沒有可以縮小的範圍。", guestNarrowLift: "取消縮小", guestNarrowHint: "勾選這位訪客仍可使用的 agent。縮小只會拿走權限(立即生效);放寬或取消縮小需 fleet admin 確認。",
|
|
947
966
|
guestSignOut: "登出", guestSignOutTitle: "登出這位訪客?", guestSignOutConfirm: "{0} 的訪客 session 會立即結束,登入碼失效,記住的瀏覽器也會被忘記。訪客登入開啟期間,他可以再傳 /web 登入。",
|
|
948
967
|
guestSignedOut: "已登出 {0}。", guestSignedOutNotDurable: "已暫時登出 {0},但無法保存:重新啟動後他的 session 可能會恢復。", guestSignOutFailed: "沒有成功,請再試一次。",
|
|
968
|
+
vaTitle: "網頁授權", vaReview: "檢視網頁授權變更",
|
|
969
|
+
vaHint: "已登入的 fleet 管理者從網頁送出的訊息,是否帶有他本人帳號的授權,記錄在每則訊息的送達收據中。用私下傳給該帳號的登入碼、透過直接連線的 dashboard 登入的 session,要在 Discord 或 Telegram 上授權一次;公開連結與訪客一律不行。目前 agent 還不會依它行動:仍只把網頁訊息當成辨識。",
|
|
970
|
+
vaLoadFailed: "無法載入網頁授權。",
|
|
971
|
+
vaEnabled: "網頁訊息帶有授權", vaEnabledHint: "預設開啟:session 要等它的帳號在 Discord 或 Telegram 上確認後才帶有授權。關閉:網頁訊息都不帶授權,已給出的授權立即視為無;關閉前給出的授權不會恢復。",
|
|
972
|
+
vaStuckOff: "網頁授權目前是關閉的:它的狀態無法存到這台電腦上(請看 fleet 紀錄)。可以存入後會自動恢復。",
|
|
973
|
+
vaLoopbackOnly: "只有這台電腦可以授權", vaLoopbackOnlyHint: "開啟:只有這台電腦上的瀏覽器符合,網路或 tailnet 上的不算,之前從其他地方給出的授權視為無。再關掉也不會讓那些授權恢復。",
|
|
974
|
+
vaLanWarning: "dashboard 也接受 {0} 的連線(web.allowed_hosts)。任何人只要從那裡連上、又拿到被盜的登入,就能以你的名義給出授權。除非你信任所有能連到那裡的人,否則請開啟「只有這台電腦可以授權」。",
|
|
975
|
+
vaHours: "一次授權的有效期(小時,{0}–{1})", vaHoursRange: "必須是 1 到 48 之間的數字",
|
|
976
|
+
vaSessionsTitle: "已授權的 session", vaSessionsNone: "目前沒有已授權的 session。", vaSessionUntil: "{0} 授權 · 到 {1}",
|
|
977
|
+
vaRevoke: "撤銷網頁授權", vaRevokeTitle: "撤銷所有網頁授權?", vaRevokeConfirm: "到目前為止從網頁給出的授權,從現在起都視為無,所有 session 的授權也會結束。不會登出任何人:session 再授權一次後,送出的訊息會再帶有授權。",
|
|
978
|
+
vaRevoked: "已撤銷到目前為止的網頁授權。", vaRevokedNotDurable: "已暫時撤銷,但無法保存:在可以保存之前網頁授權會保持關閉,而在那之前重新啟動會讓這次撤銷失效。", vaRevokeFailed: "沒有成功,請再試一次。",
|
|
949
979
|
webReadOnlyLead: "這裡僅供檢視:這些設定決定儀表板監聽和公開的內容。請編輯 fleet.yaml 後執行 agend reload(web.preview_port 與 web.preview_origin 需要重新啟動 AgEnD)。",
|
|
950
980
|
notSet: "未設定(預設)",
|
|
951
981
|
defaultsTitle: "預設值",
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// #1939 S2a: Settings → Web → Web authority — whether a signed-in admin's web messages carry their account's authority
|
|
2
|
+
// in delivery receipts (docs/design/1939-verified-web-sessions.md §0, §0.1, §3, §10): the server switch (on by default),
|
|
3
|
+
// "Only this computer can authorize", how long a session's authorization lasts, the sessions authorized now, and "Revoke
|
|
4
|
+
// web authority". Changes go through Review changes → Apply like every Settings edit, and apply at once (§0.1: none of
|
|
5
|
+
// them grants anything by itself — a session still needs its account's Confirm in Discord or Telegram).
|
|
6
|
+
import { html, useEffect, useState } from "/assets/app-html.js";
|
|
7
|
+
import { t } from "/assets/app-i18n.js";
|
|
8
|
+
import { toast } from "/assets/ui-toast.js";
|
|
9
|
+
import { confirmDialog } from "/assets/ui-confirm.js";
|
|
10
|
+
import { api } from "./settings-confirm.js";
|
|
11
|
+
import { Feedback } from "./settings-dialogs.js";
|
|
12
|
+
import { resetWebAuthorization } from "/assets/web-authorize.js";
|
|
13
|
+
import "./settings-strings.js";
|
|
14
|
+
|
|
15
|
+
const tn = (k, ...v) => t(`settings.${k}`, ...v);
|
|
16
|
+
|
|
17
|
+
/** What changed against what fleet.yaml says, as the PUT /api/settings/fleet/web body's `verified_authority` (null: nothing). */
|
|
18
|
+
export function verifiedAuthorityBody(original, f) {
|
|
19
|
+
const out = {};
|
|
20
|
+
if (f.enabled !== original.enabled) out.enabled = f.enabled ? null : false; // §0.1: on is the default
|
|
21
|
+
if (f.loopbackOnly !== original.loopbackOnly) out.loopback_only = f.loopbackOnly ? true : null;
|
|
22
|
+
if (f.hours !== original.hours) out.max_session_hours = f.hours === null ? null : f.hours;
|
|
23
|
+
return Object.keys(out).length ? out : null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function WebAuthorityCard({ ctx, staged, disabled = false }) {
|
|
27
|
+
const [overview, setOverview] = useState(null), [failed, setFailed] = useState(false);
|
|
28
|
+
const [original, setOriginal] = useState(null), [f, setF] = useState(null), [msg, setMsg] = useState(null);
|
|
29
|
+
const load = async () => {
|
|
30
|
+
// What changes here (a revoke, the switch) changes this browser's own line: the chat reads it again when it shows.
|
|
31
|
+
resetWebAuthorization();
|
|
32
|
+
let r = null;
|
|
33
|
+
try { r = await api("/api/settings/web-authority"); } catch { r = null; }
|
|
34
|
+
// Anything but the overview's shape (an older server, an error page) is "could not be loaded", never a crash.
|
|
35
|
+
const o = r && r.ok ? r.body : null;
|
|
36
|
+
if (!o || typeof o !== "object" || !o.state || !o.hours || !Array.isArray(o.lanHosts) || !Array.isArray(o.authorized)) { setFailed(true); return; }
|
|
37
|
+
const c = o.configured || {};
|
|
38
|
+
const orig = { enabled: c.enabled !== false, loopbackOnly: c.loopback_only === true, hours: typeof c.max_session_hours === "number" ? c.max_session_hours : null };
|
|
39
|
+
setOverview(o); setFailed(false);
|
|
40
|
+
setOriginal((prev) => prev || orig);
|
|
41
|
+
setF((prev) => prev || { ...orig });
|
|
42
|
+
};
|
|
43
|
+
useEffect(() => { load(); }, []);
|
|
44
|
+
const card = (body) => html`<section class="card s-web-authority"><h3>${tn("vaTitle")}</h3><p class="note">${tn("vaHint")}</p>${body}</section>`;
|
|
45
|
+
if (failed) return card(html`<p class="note" role="status">${tn("vaLoadFailed")} <button type="button" class="btn btn-sm" onClick=${load}>${t("app.retry")}</button></p>`);
|
|
46
|
+
if (!overview || !f) return card(html`<p class="note">${t("app.loading")}</p>`);
|
|
47
|
+
|
|
48
|
+
const set = (patch) => setF((x) => ({ ...x, ...patch }));
|
|
49
|
+
const hoursText = f.hours === null ? "" : String(f.hours);
|
|
50
|
+
const hoursError = f.hours !== null && (!Number.isFinite(f.hours) || f.hours < overview.hours.min || f.hours > overview.hours.max) ? "vaHoursRange" : "";
|
|
51
|
+
const stuckOff = f.enabled && original.enabled && !overview.state.enabled;
|
|
52
|
+
const warnHosts = !f.loopbackOnly && overview.lanHosts.length > 0;
|
|
53
|
+
const when = (ms) => new Date(ms).toLocaleString();
|
|
54
|
+
|
|
55
|
+
const review = () => {
|
|
56
|
+
if (hoursError) return;
|
|
57
|
+
const body = verifiedAuthorityBody(original, f);
|
|
58
|
+
if (body) ctx.stage("verified_authority", { label: tn("vaTitle"), impact: "now", request: { method: "PUT", url: "/api/settings/fleet/web", body: { verified_authority: body } } });
|
|
59
|
+
else if (staged.has("verified_authority")) ctx.unstage("verified_authority");
|
|
60
|
+
setMsg(tn("readyApply"));
|
|
61
|
+
};
|
|
62
|
+
const revoke = async () => {
|
|
63
|
+
const yes = await confirmDialog({ title: tn("vaRevokeTitle"), message: tn("vaRevokeConfirm"), confirmLabel: tn("vaRevoke"), danger: true });
|
|
64
|
+
if (!yes) return;
|
|
65
|
+
let r = null;
|
|
66
|
+
try { r = await api("/api/settings/web-authority/revoke", { method: "POST", body: "{}" }); } catch { r = null; }
|
|
67
|
+
if (r && r.ok) { toast(tn(r.body && r.body.durable === false ? "vaRevokedNotDurable" : "vaRevoked")); load(); }
|
|
68
|
+
else toast(tn("vaRevokeFailed"), false);
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
return card(html`<fieldset class="s-va-fields" disabled=${disabled}>
|
|
72
|
+
${stuckOff ? html`<p class="note s-va-warn" role="status" id="vaStuckOff">${tn("vaStuckOff")}</p>` : null}
|
|
73
|
+
<label class="check"><input id="vaEnabled" type="checkbox" checked=${f.enabled} onChange=${(e) => set({ enabled: e.target.checked })} /><span>${tn("vaEnabled")}</span></label>
|
|
74
|
+
<p class="note">${tn("vaEnabledHint")}</p>
|
|
75
|
+
<label class="check"><input id="vaLoopbackOnly" type="checkbox" checked=${f.loopbackOnly} onChange=${(e) => set({ loopbackOnly: e.target.checked })} /><span>${tn("vaLoopbackOnly")}</span></label>
|
|
76
|
+
<p class="note">${tn("vaLoopbackOnlyHint")}</p>
|
|
77
|
+
${warnHosts ? html`<p class="note s-va-warn" role="status" id="vaLanWarning">${tn("vaLanWarning", overview.lanHosts.join(", "))}</p>` : null}
|
|
78
|
+
<div class="field"><label for="vaHours">${tn("vaHours", overview.hours.min, overview.hours.max)}</label>
|
|
79
|
+
<input id="vaHours" type="number" min=${overview.hours.min} max=${overview.hours.max} step="any" placeholder=${tn("defaultHours", overview.hours.default)}
|
|
80
|
+
class=${hoursError ? "invalid" : ""} value=${hoursText} onInput=${(e) => set({ hours: e.target.value.trim() === "" ? null : Number(e.target.value) })} />
|
|
81
|
+
<${Feedback} error=${hoursError} /></div>
|
|
82
|
+
<h4>${tn("vaSessionsTitle")}</h4>
|
|
83
|
+
${overview.authorized.length
|
|
84
|
+
? html`<ul class="s-va-sessions" id="vaSessions">${overview.authorized.map((a) => html`<li key=${a.handle} data-handle=${a.handle}>
|
|
85
|
+
<strong>${a.label}</strong> <span class="note">${a.account}</span><br /><span class="note">${tn("vaSessionUntil", when(a.authorizedAt), when(a.until))}</span></li>`)}</ul>`
|
|
86
|
+
: html`<p class="note" id="vaSessionsNone">${tn("vaSessionsNone")}</p>`}
|
|
87
|
+
</fieldset>
|
|
88
|
+
<div class="save-row"><button type="button" class="btn btn-primary" id="vaReview" disabled=${disabled || !!hoursError} onClick=${review}>${tn("vaReview")}</button>
|
|
89
|
+
<button type="button" class="btn btn-danger" id="vaRevoke" disabled=${disabled} onClick=${revoke}>${tn("vaRevoke")}</button>
|
|
90
|
+
${msg ? html`<span class="note" role="status">${msg}</span>` : null}</div>`);
|
|
91
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// #1408 §3: the app's routes on the client. The same table as the server's classifier (src/web-shell-routes.ts;
|
|
2
2
|
// a test runs both over one list of cases). Pure: no DOM, no history.
|
|
3
3
|
|
|
4
|
-
export const FLEET_TABS = ["tasks", "schedules", "teams", "org", "cache", "config"];
|
|
4
|
+
export const FLEET_TABS = ["tasks", "schedules", "teams", "org", "cache", "usage", "config"];
|
|
5
5
|
export const SETTINGS_SECTIONS = ["device", "appearance", "agents", "bots", "classic", "general", "web", "advanced"]; // #1604: device first
|
|
6
6
|
const MAX_INSTANCE_NAME = 128;
|
|
7
7
|
const PATH_SEPARATOR_OR_CONTROL = /[/\\\u0000-\u001f\u007f]/;
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { html, useEffect, useRef, useState } from "./app-html.js";
|
|
5
5
|
import { Icon } from "./ui-icons.js";
|
|
6
6
|
import { t, lang } from "./app-i18n.js";
|
|
7
|
+
import { AuthorizeChip } from "./web-authorize.js";
|
|
7
8
|
|
|
8
9
|
async function getJson(url) {
|
|
9
10
|
try {
|
|
@@ -53,6 +54,7 @@ export function SessionMenu() {
|
|
|
53
54
|
${!me ? html`<h3>${t("app.signedOut")}</h3><a class="btn" href=${`/signin?next=${encodeURIComponent(location.pathname)}`}>${t("app.signIn")}</a>` : html`
|
|
54
55
|
<h3>${t("app.signedIn")} · ${me.label}</h3>
|
|
55
56
|
<p class="sub">${t("app.expires", clock(me.expiresAt), span(me.idleMs))}</p>
|
|
57
|
+
<${AuthorizeChip} />
|
|
56
58
|
${rows.length > 1 ? html`<h3>${t("app.devices")}</h3><ul class="devices">${rows.map(s => html`<li key=${s.handle}>
|
|
57
59
|
<span class="grow"><span class="name">${s.label}${s.current ? ` · ${t("app.thisDevice")}` : ""}</span><span class="when">${t("app.lastActive", ago(s.lastSeen))}</span></span>
|
|
58
60
|
${s.current ? null : html`<button type="button" class="btn btn-sm danger" onClick=${() => act("DELETE", `/auth/sessions/${encodeURIComponent(s.handle)}`, refresh)}>${t("app.signOut")}</button>`}</li>`)}</ul>` : null}
|
|
@@ -394,7 +394,7 @@ function PrefsPopover() {
|
|
|
394
394
|
* The header every panel shows: ☰ (phone, or a collapsed sidebar), its title and status, then its actions. `nav`
|
|
395
395
|
* (#1523 N2): an instance page's view switch — beside the title on a wide screen, a row of tabs under it on a phone.
|
|
396
396
|
*/
|
|
397
|
-
export function PanelHeader({ title, sub, children, headingRef, nav }) {
|
|
397
|
+
export function PanelHeader({ title, sub, children, headingRef, nav, notice }) {
|
|
398
398
|
const shell = useStore(shellStore);
|
|
399
399
|
const canCreate = useCanCreate() && !!newInstanceOpener;
|
|
400
400
|
return html`<header data-trust="header" class=${`panel-head${shell.collapsed ? " collapsed" : ""}`}>
|
|
@@ -404,6 +404,7 @@ export function PanelHeader({ title, sub, children, headingRef, nav }) {
|
|
|
404
404
|
<div class="panel-title"><h1 ref=${headingRef} tabindex="-1">${title}</h1>${sub ? html`<div class="panel-sub">${sub}</div>` : null}</div>
|
|
405
405
|
${nav || null}
|
|
406
406
|
<div class="panel-actions">${children}</div>
|
|
407
|
+
${notice || null}
|
|
407
408
|
</header>`;
|
|
408
409
|
}
|
|
409
410
|
|
|
@@ -10,8 +10,11 @@ import { t } from "./app-i18n.js";
|
|
|
10
10
|
import { toast } from "./ui-toast.js";
|
|
11
11
|
import { confirmDialog } from "./ui-confirm.js";
|
|
12
12
|
|
|
13
|
-
/**
|
|
14
|
-
|
|
13
|
+
/**
|
|
14
|
+
* { rev, themes: { <name>: theme }, locked: [<name>], widgets: { <name>: { <slot>: widget } }, ambient: { <name>: { loop?, click? } } }
|
|
15
|
+
* as GET /ui/themes said (rev -1: not read yet).
|
|
16
|
+
*/
|
|
17
|
+
export const themesStore = createStore({ rev: -1, themes: {}, locked: [], widgets: {}, ambient: {}, shown: true });
|
|
15
18
|
const own = (o, k) => !!o && Object.prototype.hasOwnProperty.call(o, k);
|
|
16
19
|
|
|
17
20
|
const COLOR = /^#[0-9a-f]{6}$/;
|
|
@@ -30,14 +33,15 @@ function labelOk(s) {
|
|
|
30
33
|
return n >= 1 && n <= 24;
|
|
31
34
|
}
|
|
32
35
|
|
|
33
|
-
// Public sessions cannot write host prefs. Keep their explicit switch apart from boot-cached
|
|
36
|
+
// Public/guest sessions cannot write host prefs. Keep their explicit browser switch apart from boot-cached defaults.
|
|
37
|
+
// Keep the existing public key for compatibility; both read-only viewer surfaces share this device choice.
|
|
34
38
|
const PUBLIC_FX_SHOW = "agend_public_fx_show";
|
|
35
39
|
let publicFxChoice = (() => { try { const v = localStorage.getItem(PUBLIC_FX_SHOW); return v === "on" ? true : v === "off" ? false : null; } catch { return null; } })();
|
|
36
40
|
|
|
37
41
|
/** Whether this page shows agents' themes now: the viewer's switch (default on), and never in safe mode. */
|
|
38
42
|
export function themesShown() {
|
|
39
43
|
if (safeMode()) return false;
|
|
40
|
-
if (
|
|
44
|
+
if (localAppearanceViewer() && publicFxChoice !== null) return publicFxChoice;
|
|
41
45
|
try {
|
|
42
46
|
const f = prefInForce("fx.show"); // this page's choice, else the host's, else the default
|
|
43
47
|
if (f) return f.value !== false;
|
|
@@ -46,7 +50,7 @@ export function themesShown() {
|
|
|
46
50
|
}
|
|
47
51
|
/** The viewer's switch (Settings → Appearance): kept with the person's preferences, and in force on this page at once. */
|
|
48
52
|
export function setThemesShown(on) {
|
|
49
|
-
if (
|
|
53
|
+
if (localAppearanceViewer()) {
|
|
50
54
|
publicFxChoice = !!on;
|
|
51
55
|
try { localStorage.setItem(PUBLIC_FX_SHOW, on ? "on" : "off"); } catch { /* the in-memory choice still applies */ }
|
|
52
56
|
} else savePref("fx.show", on ? null : false);
|
|
@@ -122,7 +126,8 @@ async function read(rev) {
|
|
|
122
126
|
if (!body || typeof body !== "object") break;
|
|
123
127
|
themesStore.set({ rev: typeof body.rev === "number" ? body.rev : want, themes: body.themes && typeof body.themes === "object" ? body.themes : {},
|
|
124
128
|
locked: Array.isArray(body.locked) ? body.locked.filter((n) => typeof n === "string") : [],
|
|
125
|
-
widgets: body.widgets && typeof body.widgets === "object" && !Array.isArray(body.widgets) ? body.widgets : {}
|
|
129
|
+
widgets: body.widgets && typeof body.widgets === "object" && !Array.isArray(body.widgets) ? body.widgets : {},
|
|
130
|
+
ambient: body.ambient && typeof body.ambient === "object" && !Array.isArray(body.ambient) ? body.ambient : {} });
|
|
126
131
|
if (want === wanted) break;
|
|
127
132
|
}
|
|
128
133
|
} finally { reading = false; }
|
|
@@ -144,6 +149,7 @@ export function startThemes() {
|
|
|
144
149
|
}
|
|
145
150
|
|
|
146
151
|
const publicLink = () => typeof document !== "undefined" && !!document.body && document.body.dataset.publicLink === "1";
|
|
152
|
+
const localAppearanceViewer = () => publicLink() || typeof document !== "undefined" && document.body?.dataset.tier === "guest";
|
|
147
153
|
|
|
148
154
|
/** A person's Reset or Lock of an agent's theme (an admin session; never over the public link). */
|
|
149
155
|
async function themeAction(name, what, body) {
|
|
@@ -169,6 +175,18 @@ async function removeWidget(name, slot) {
|
|
|
169
175
|
else toast(t("chat.widgetRemoved", name));
|
|
170
176
|
}
|
|
171
177
|
|
|
178
|
+
/** #2117: a person's Clear of an agent's ambient, one kind ("loop" or "click"). */
|
|
179
|
+
export async function clearAmbient(name, which) {
|
|
180
|
+
let r;
|
|
181
|
+
try {
|
|
182
|
+
const res = await fetch(`/ui/themes/${encodeURIComponent(name)}/ambient`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ which }) });
|
|
183
|
+
r = await res.json().catch(() => ({}));
|
|
184
|
+
if (!res.ok) r = { error: r && r.error ? r.error : `HTTP ${res.status}` };
|
|
185
|
+
} catch (err) { r = { error: err && err.message ? err.message : "network" }; }
|
|
186
|
+
if (r && r.error) toast(t("chat.themeFailed", r.error), false);
|
|
187
|
+
else toast(t("chat.ambientCleared", name));
|
|
188
|
+
}
|
|
189
|
+
|
|
172
190
|
/** The chat's and Details' ⋯ entries for an agent's theme: Lock/Unlock, Reset… and its widgets' Remove — none over the public link. */
|
|
173
191
|
export function themeMenuItems(name) {
|
|
174
192
|
if (publicLink()) return [];
|