@songsid/agend 2.3.0-beta.6 → 2.3.0-beta.7
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 +12 -6
- 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.js +2 -0
- 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 +81 -25
- package/dist/general-knowledge/skills/web-dashboard/SKILL.md +55 -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-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 +45 -1
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.d.ts +57 -1
- package/dist/outbound-schemas.js +33 -1
- 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 +50 -1
- package/dist/settings-api.js.map +1 -1
- package/dist/settings-change.js +27 -0
- package/dist/settings-change.js.map +1 -1
- package/dist/settings-effect.js +8 -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 +43 -25
- package/dist/ui/chat-ambient.js +306 -0
- package/dist/ui/chat-effect-posters.js +6 -7
- package/dist/ui/chat-effects.js +38 -15
- package/dist/ui/chat-strings.js +17 -4
- package/dist/ui/panel-chat.js +15 -7
- package/dist/ui/panel-settings.js +4 -1
- 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 +28 -0
- package/dist/ui/settings-web-authority.js +91 -0
- 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 +28 -0
- package/dist/ui/shared/effect-app-runtime.js +67 -18
- 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 -4
- package/dist/ui/shared/effect-registry.js +11 -18
- package/dist/ui/shared/effect-stock-trust.js +2 -2
- package/dist/ui/shared/effect-trial-stage.js +18 -7
- package/dist/ui/shared/fx-motion.js +58 -0
- package/dist/ui/shared/web-authorize.js +151 -0
- package/dist/usage-report.d.ts +147 -0
- package/dist/usage-report.js +195 -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 +32 -0
- package/dist/web-api.js +143 -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 +24 -2
- package/dist/web-settings-patch.js +47 -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-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
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* #2117 (from #2115's proposal): an **ambient** effect is declarative, like a widget. The agent supplies a small closed
|
|
3
|
+
* spec (a preset, its glyphs and a few numbers); the page draws it with its own code (src/ui/chat-ambient.js): plain
|
|
4
|
+
* nodes animated by the Web Animations API, under the chat's content and never taking a click. No agent code runs, so
|
|
5
|
+
* an ambient needs no Worker, preview origin, HTML-preview consent or FPS baseline (Canvas Worker effects do).
|
|
6
|
+
*
|
|
7
|
+
* A chat holds at most one looping ambient (`fall`, `rise`, `twinkle`) and one click ambient (`burst-on-click`): falling
|
|
8
|
+
* petals and click stars together. One module validates and canonicalises the spec; the store keeps only what this
|
|
9
|
+
* returns, and the page checks the bounds again.
|
|
10
|
+
*/
|
|
11
|
+
import { canonicalColor, canonicalText } from "./web-theme.js";
|
|
12
|
+
export const AMBIENT_PRESETS = ["fall", "rise", "twinkle", "burst-on-click"];
|
|
13
|
+
/** Where a preset is kept: a chat has one of each. */
|
|
14
|
+
export const AMBIENT_KINDS = ["loop", "click"];
|
|
15
|
+
export const ambientKind = (preset) => preset === "burst-on-click" ? "click" : "loop";
|
|
16
|
+
export const AMBIENT_GLYPHS_MAX = 4;
|
|
17
|
+
export const AMBIENT_GLYPH_GRAPHEMES = 2;
|
|
18
|
+
export const AMBIENT_COLORS_MAX = 5;
|
|
19
|
+
export const AMBIENT_SPEC_MAX_BYTES = 1024;
|
|
20
|
+
/** The page's caps (chat-ambient.js keeps the same numbers): 24 looping particles, 3 bursts of 16 at once. */
|
|
21
|
+
export const AMBIENT_LOOP_MAX = 24;
|
|
22
|
+
export const AMBIENT_BURST_MAX = 16;
|
|
23
|
+
export const AMBIENT_BURSTS_LIVE = 3;
|
|
24
|
+
/** Each preset's bounds and defaults. A count is particles (loops) or particles per click (burst). */
|
|
25
|
+
export const AMBIENT_RULES = {
|
|
26
|
+
fall: { count: [1, AMBIENT_LOOP_MAX], countDefault: 12, duration: [4, 30], durationDefault: [9, 19] },
|
|
27
|
+
rise: { count: [1, AMBIENT_LOOP_MAX], countDefault: 12, duration: [4, 30], durationDefault: [9, 19] },
|
|
28
|
+
twinkle: { count: [1, AMBIENT_LOOP_MAX], countDefault: 12, duration: [1, 10], durationDefault: [2, 5] },
|
|
29
|
+
"burst-on-click": { count: [4, AMBIENT_BURST_MAX], countDefault: 10, duration: [0.3, 2], durationDefault: [0.5, 0.9] },
|
|
30
|
+
};
|
|
31
|
+
export const AMBIENT_SIZE = [10, 40];
|
|
32
|
+
export const AMBIENT_SIZE_DEFAULT = [14, 22];
|
|
33
|
+
export const AMBIENT_SWAY_MAX = 80;
|
|
34
|
+
export const AMBIENT_SWAY_DEFAULT = 40;
|
|
35
|
+
export const AMBIENT_SPREAD = [20, 120];
|
|
36
|
+
export const AMBIENT_SPREAD_DEFAULT = [34, 90];
|
|
37
|
+
export const AMBIENT_ROTATE_MAX = 720;
|
|
38
|
+
export const AMBIENT_ROTATE_DEFAULT = 300;
|
|
39
|
+
export const AMBIENT_OPACITY_DEFAULT = [0, 0.85, 0.15];
|
|
40
|
+
const KEYS = ["preset", "glyphs", "colors", "count", "duration", "size", "sway", "spread", "rotate", "opacity", "layer"];
|
|
41
|
+
const ASSET_ID = /^[0-9a-f]{32}$/;
|
|
42
|
+
const isObj = (v) => typeof v === "object" && v !== null && !Array.isArray(v) && Object.getPrototypeOf(v) === Object.prototype;
|
|
43
|
+
const isNum = (v) => typeof v === "number" && Number.isFinite(v);
|
|
44
|
+
/** Two decimals are plenty for seconds, pixels and opacity, and keep the stored spec short. */
|
|
45
|
+
const round = (n) => Math.round(n * 100) / 100;
|
|
46
|
+
/**
|
|
47
|
+
* A spec, closed and canonical: unknown keys, a value out of its preset's bounds or a glyph that is not 1..2 visible
|
|
48
|
+
* graphemes (or one of this instance's `glyph` pictures, when `glyphAsset` is given) are refused.
|
|
49
|
+
*/
|
|
50
|
+
export function checkAmbient(raw, opts = {}) {
|
|
51
|
+
const errors = [];
|
|
52
|
+
const bad = (path, problem) => { errors.push({ path, problem }); };
|
|
53
|
+
if (!isObj(raw))
|
|
54
|
+
return { ok: false, errors: [{ path: "ambient", problem: "an object { preset, glyphs, … }" }] };
|
|
55
|
+
for (const k of Object.keys(raw))
|
|
56
|
+
if (!KEYS.includes(k))
|
|
57
|
+
bad(k.slice(0, 40), "unknown key");
|
|
58
|
+
const preset = raw.preset;
|
|
59
|
+
if (!AMBIENT_PRESETS.includes(preset))
|
|
60
|
+
return { ok: false, errors: [...errors, { path: "preset", problem: AMBIENT_PRESETS.join(" | ") }] };
|
|
61
|
+
const rules = AMBIENT_RULES[preset];
|
|
62
|
+
const glyphs = [];
|
|
63
|
+
if (!Array.isArray(raw.glyphs) || raw.glyphs.length < 1 || raw.glyphs.length > AMBIENT_GLYPHS_MAX)
|
|
64
|
+
bad("glyphs", `1..${AMBIENT_GLYPHS_MAX} glyphs`);
|
|
65
|
+
else
|
|
66
|
+
raw.glyphs.forEach((g, i) => {
|
|
67
|
+
if (typeof g === "string") {
|
|
68
|
+
const t = canonicalText(g, 1, AMBIENT_GLYPH_GRAPHEMES);
|
|
69
|
+
if (t === null)
|
|
70
|
+
bad(`glyphs.${i}`, `1..${AMBIENT_GLYPH_GRAPHEMES} visible characters (an emoji, ✦, ❀)`);
|
|
71
|
+
else
|
|
72
|
+
glyphs.push(t);
|
|
73
|
+
}
|
|
74
|
+
else if (isObj(g) && Object.keys(g).length === 1 && typeof g.asset === "string" && ASSET_ID.test(g.asset)) {
|
|
75
|
+
if (opts.glyphAsset && !opts.glyphAsset(g.asset))
|
|
76
|
+
bad(`glyphs.${i}.asset`, "not one of this instance's glyph pictures (set_web_theme_asset slot glyph)");
|
|
77
|
+
else
|
|
78
|
+
glyphs.push({ asset: g.asset });
|
|
79
|
+
}
|
|
80
|
+
else
|
|
81
|
+
bad(`glyphs.${i}`, "a short string, or { asset: <id> }");
|
|
82
|
+
});
|
|
83
|
+
const colors = [];
|
|
84
|
+
if (raw.colors !== undefined) {
|
|
85
|
+
if (!Array.isArray(raw.colors) || raw.colors.length > AMBIENT_COLORS_MAX)
|
|
86
|
+
bad("colors", `0..${AMBIENT_COLORS_MAX} colours`);
|
|
87
|
+
else
|
|
88
|
+
raw.colors.forEach((c, i) => { const v = canonicalColor(c); if (v === null)
|
|
89
|
+
bad(`colors.${i}`, "#rgb or #rrggbb");
|
|
90
|
+
else
|
|
91
|
+
colors.push(v); });
|
|
92
|
+
}
|
|
93
|
+
const int = (key, [lo, hi], dflt) => {
|
|
94
|
+
const v = raw[key];
|
|
95
|
+
if (v === undefined)
|
|
96
|
+
return dflt;
|
|
97
|
+
if (!Number.isSafeInteger(v) || v < lo || v > hi) {
|
|
98
|
+
bad(key, `a whole number ${lo}..${hi}${key === "count" ? ` for ${preset}` : ""}`);
|
|
99
|
+
return dflt;
|
|
100
|
+
}
|
|
101
|
+
return v;
|
|
102
|
+
};
|
|
103
|
+
const num = (key, [lo, hi], dflt) => {
|
|
104
|
+
const v = raw[key];
|
|
105
|
+
if (v === undefined)
|
|
106
|
+
return dflt;
|
|
107
|
+
if (!isNum(v) || v < lo || v > hi) {
|
|
108
|
+
bad(key, `a number ${lo}..${hi}`);
|
|
109
|
+
return dflt;
|
|
110
|
+
}
|
|
111
|
+
return round(v);
|
|
112
|
+
};
|
|
113
|
+
const range = (key, [lo, hi], dflt) => {
|
|
114
|
+
const v = raw[key];
|
|
115
|
+
if (v === undefined)
|
|
116
|
+
return [dflt[0], dflt[1]];
|
|
117
|
+
if (!Array.isArray(v) || v.length !== 2 || !v.every(isNum) || v[0] < lo || v[1] > hi || v[0] > v[1]) {
|
|
118
|
+
bad(key, `[min, max] within ${lo}..${hi}${key === "duration" ? ` seconds for ${preset}` : ""}`);
|
|
119
|
+
return [dflt[0], dflt[1]];
|
|
120
|
+
}
|
|
121
|
+
return [round(v[0]), round(v[1])];
|
|
122
|
+
};
|
|
123
|
+
const count = int("count", rules.count, rules.countDefault);
|
|
124
|
+
const duration = range("duration", rules.duration, rules.durationDefault);
|
|
125
|
+
const size = range("size", AMBIENT_SIZE, AMBIENT_SIZE_DEFAULT);
|
|
126
|
+
const sway = num("sway", [0, AMBIENT_SWAY_MAX], AMBIENT_SWAY_DEFAULT);
|
|
127
|
+
const spread = range("spread", AMBIENT_SPREAD, AMBIENT_SPREAD_DEFAULT);
|
|
128
|
+
const rotate = num("rotate", [0, AMBIENT_ROTATE_MAX], AMBIENT_ROTATE_DEFAULT);
|
|
129
|
+
let opacity = [...AMBIENT_OPACITY_DEFAULT];
|
|
130
|
+
if (raw.opacity !== undefined) {
|
|
131
|
+
const o = raw.opacity;
|
|
132
|
+
if (!Array.isArray(o) || o.length !== 3 || !o.every(x => isNum(x) && x >= 0 && x <= 1))
|
|
133
|
+
bad("opacity", "[start, peak, end], each 0..1");
|
|
134
|
+
else
|
|
135
|
+
opacity = [round(o[0]), round(o[1]), round(o[2])];
|
|
136
|
+
}
|
|
137
|
+
if (raw.layer !== undefined && raw.layer !== "behind")
|
|
138
|
+
bad("layer", "\"behind\" (an ambient never covers the chat)");
|
|
139
|
+
if (errors.length)
|
|
140
|
+
return { ok: false, errors };
|
|
141
|
+
const spec = { preset, glyphs, colors, count, duration, size, sway, spread, rotate, opacity, layer: "behind" };
|
|
142
|
+
if (Buffer.byteLength(JSON.stringify(spec), "utf8") > AMBIENT_SPEC_MAX_BYTES)
|
|
143
|
+
return { ok: false, errors: [{ path: "ambient", problem: `at most ${AMBIENT_SPEC_MAX_BYTES} bytes` }] };
|
|
144
|
+
return { ok: true, value: spec };
|
|
145
|
+
}
|
|
146
|
+
/** The glyph pictures a spec names. */
|
|
147
|
+
export function ambientAssets(spec) {
|
|
148
|
+
return spec.glyphs.flatMap(g => typeof g === "string" ? [] : [g.asset]);
|
|
149
|
+
}
|
|
150
|
+
/** One line per problem, for a tool's refusal. */
|
|
151
|
+
export function ambientProblems(errors) {
|
|
152
|
+
return errors.map(e => `${e.path}: ${e.problem}`).join("; ");
|
|
153
|
+
}
|
|
154
|
+
//# sourceMappingURL=web-ambient.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"web-ambient.js","sourceRoot":"","sources":["../src/web-ambient.ts"],"names":[],"mappings":"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"}
|
package/dist/web-api.d.ts
CHANGED
|
@@ -52,6 +52,7 @@ export interface WebApiContext {
|
|
|
52
52
|
web?: {
|
|
53
53
|
echo_to_channel?: boolean;
|
|
54
54
|
usage_panel?: boolean;
|
|
55
|
+
usage_report_on_public_link?: boolean;
|
|
55
56
|
upload_max_file_mb?: number;
|
|
56
57
|
upload_max_total_mb?: number;
|
|
57
58
|
};
|
|
@@ -130,6 +131,7 @@ export interface WebApiContext {
|
|
|
130
131
|
/** Current owned pane reader shared with status; absent means context is unavailable. */
|
|
131
132
|
getPaneContextSource?(name: string): PaneContextSource | null;
|
|
132
133
|
/** #1306: which preview origin this /ui load may frame (Host and the TLS signal only); absent: previews off. */
|
|
134
|
+
readonly effectBoot?: string;
|
|
133
135
|
previewForUi?(hostHeader: string | undefined, secure: boolean): PreviewAvailability & {
|
|
134
136
|
boot: string | null;
|
|
135
137
|
};
|
|
@@ -196,6 +198,11 @@ export interface WebApiContext {
|
|
|
196
198
|
logSteps?(name: string, q: import("./log-service.js").LogQuery): Promise<unknown>;
|
|
197
199
|
/** #1468: the prompt-cache expiry analysis for a window ("24h" | "7d" | "30d"); absent: not offered. */
|
|
198
200
|
cacheReport?(window: string): Promise<unknown>;
|
|
201
|
+
/** #2122: the usage report (Fleet → Usage); absent: not offered. */
|
|
202
|
+
usageReport?(params: {
|
|
203
|
+
range: import("./usage-report.js").UsageRange;
|
|
204
|
+
group: import("./usage-report.js").UsageGroup;
|
|
205
|
+
}): Promise<unknown>;
|
|
199
206
|
/** #1269: an instance's chat command from the web chat (web-commands.ts); absent: not offered. */
|
|
200
207
|
webCommand?(input: {
|
|
201
208
|
instance: string;
|
|
@@ -210,6 +217,29 @@ export interface WebApiContext {
|
|
|
210
217
|
status: number;
|
|
211
218
|
body: unknown;
|
|
212
219
|
}>;
|
|
220
|
+
/** #1939 S2b: "Authorize this browser" — the Confirm card in the account's DM; and what the app shows for this browser. */
|
|
221
|
+
requestSessionAuthorization?(input: {
|
|
222
|
+
session: SessionRecord | null;
|
|
223
|
+
gateway: boolean;
|
|
224
|
+
peer: WebPeer;
|
|
225
|
+
}): Promise<{
|
|
226
|
+
ok: true;
|
|
227
|
+
expiresInMs: number;
|
|
228
|
+
} | {
|
|
229
|
+
ok: false;
|
|
230
|
+
refusal: string;
|
|
231
|
+
retryAfterMs?: number;
|
|
232
|
+
}>;
|
|
233
|
+
webSessionAuthorization?(input: {
|
|
234
|
+
session: SessionRecord | null;
|
|
235
|
+
gateway: boolean;
|
|
236
|
+
peer: WebPeer;
|
|
237
|
+
}): {
|
|
238
|
+
state: string;
|
|
239
|
+
until?: number;
|
|
240
|
+
expiresInMs?: number;
|
|
241
|
+
reason?: string;
|
|
242
|
+
};
|
|
213
243
|
/** #1939 S1: the design's §3 for one request (`instance` once the body names it); absent: web carries no authority. */
|
|
214
244
|
webAuthority?(input: {
|
|
215
245
|
session: SessionRecord | null;
|
|
@@ -350,6 +380,8 @@ export type PublicPreviewForUi = PreviewAvailability & {
|
|
|
350
380
|
detail?: string;
|
|
351
381
|
};
|
|
352
382
|
export interface AppShellContext {
|
|
383
|
+
/** #2118: independent trusted opaque effect document, never HTML-preview consent or listener state. */
|
|
384
|
+
readonly effectBoot?: string;
|
|
353
385
|
previewForUi?(hostHeader: string | undefined, secure: boolean): (PreviewAvailability & {
|
|
354
386
|
boot: string | null;
|
|
355
387
|
}) | null;
|
package/dist/web-api.js
CHANGED
|
@@ -516,7 +516,12 @@ function handleEffects(req, res, url, ctx, path, method) {
|
|
|
516
516
|
const reading = method === "GET";
|
|
517
517
|
// Reports are bounded untrusted diagnostics, not an appearance or admission write (r2 §6).
|
|
518
518
|
const profilePost = method === "POST" && /^\/ui\/effects\/profile\/[0-9a-f]{32}$/.test(path);
|
|
519
|
-
|
|
519
|
+
const gateway = gatewayRequestContext(req), guest = guestRequestOf(req);
|
|
520
|
+
const instanceQuery = url.searchParams.get("instance"), keys = [...url.searchParams.keys()];
|
|
521
|
+
const chatRead = reading && (path === "/ui/effects/placements" || /^\/ui\/effects\/resource\/[0-9a-f]{32}$/.test(path))
|
|
522
|
+
&& keys.length === 2 && keys.includes("instance") && keys.includes("ownerEpoch") && !!instanceQuery && instanceQuery.length <= 128;
|
|
523
|
+
// Public viewing grants current Chat reads, never ordinary library/owner/Trial access or writes.
|
|
524
|
+
if (gateway && (!chatRead || !gateway.isCurrent())) {
|
|
520
525
|
json(res, reading ? 404 : 403, { error: "effects_not_over_public_link" });
|
|
521
526
|
return true;
|
|
522
527
|
}
|
|
@@ -528,7 +533,9 @@ function handleEffects(req, res, url, ctx, path, method) {
|
|
|
528
533
|
return { status: 403, error: "effects_need_a_signed_in_person" };
|
|
529
534
|
if (!reading && !profilePost && verdict.session.tier !== "admin")
|
|
530
535
|
return { status: 403, error: "effects_read_only" };
|
|
531
|
-
|
|
536
|
+
if (verdict.session.tier === "guest" && (!chatRead || !guest || !guest.current() || !guest.scope().has(instanceQuery)))
|
|
537
|
+
return { status: 404, error: "not_found" };
|
|
538
|
+
return { person: webPersonKey(verdict.session.owner), canWrite: verdict.session.tier === "admin", epoch: verdict.session.handle, guest: verdict.session.tier === "guest" };
|
|
532
539
|
};
|
|
533
540
|
const first = who();
|
|
534
541
|
if ("status" in first) {
|
|
@@ -571,9 +578,9 @@ function handleEffects(req, res, url, ctx, path, method) {
|
|
|
571
578
|
else if (path === "/ui/effects/owner" && instance === undefined && slot === null)
|
|
572
579
|
answer(service.ownerSelections(first.person));
|
|
573
580
|
else if (path === "/ui/effects/placements" && instance !== undefined)
|
|
574
|
-
answer(service.placements(first.person, instance));
|
|
581
|
+
answer(service.placements(first.person, instance, !!gateway || first.guest));
|
|
575
582
|
else if (idPath?.[1] === "resource")
|
|
576
|
-
answer(slot === null ? service.resource(first.person, idPath[2], instance) : service.ownerResource(first.person, idPath[2], slot));
|
|
583
|
+
answer(slot === null ? service.resource(first.person, idPath[2], instance, !!gateway || first.guest) : service.ownerResource(first.person, idPath[2], slot));
|
|
577
584
|
else if (idPath?.[1] === "profile" && instance === undefined)
|
|
578
585
|
answer(service.profile(first.person, idPath[2]));
|
|
579
586
|
else
|
|
@@ -627,8 +634,13 @@ function handleEffects(req, res, url, ctx, path, method) {
|
|
|
627
634
|
answer(service.remove(now.person, body));
|
|
628
635
|
else if (path === "/ui/effects/share")
|
|
629
636
|
answer(service.share(now.person, body));
|
|
630
|
-
else
|
|
631
|
-
|
|
637
|
+
else {
|
|
638
|
+
if (body && typeof body === "object" && body.kind === "playback" && (url.searchParams.getAll("instance").length !== 1 || slot !== null)) {
|
|
639
|
+
json(res, 400, { error: "playback_report" });
|
|
640
|
+
return;
|
|
641
|
+
}
|
|
642
|
+
answer(service.report(now.person, idPath[2], body, instance, now.epoch));
|
|
643
|
+
}
|
|
632
644
|
})().catch(() => { try {
|
|
633
645
|
json(res, 500, { error: "effect_request_failed" });
|
|
634
646
|
}
|
|
@@ -837,7 +849,7 @@ export function exportMod(ctx, person, body) {
|
|
|
837
849
|
return { ok: false, status: 400, error: "title", detail: "1..60 characters on one line" };
|
|
838
850
|
const parts = {};
|
|
839
851
|
let omitted = 0;
|
|
840
|
-
const wantsInstance = inc.theme === true || inc.identity === true || (isObj(inc.widgets) && AGENT_WIDGET_SLOTS.some(s => inc.widgets[s] === true));
|
|
852
|
+
const wantsInstance = inc.theme === true || inc.identity === true || inc.ambient === true || (isObj(inc.widgets) && AGENT_WIDGET_SLOTS.some(s => inc.widgets[s] === true));
|
|
841
853
|
let name = null;
|
|
842
854
|
if (wantsInstance) {
|
|
843
855
|
if (typeof body.instance !== "string" || !ctx.isKnownInstance?.(body.instance) || !ctx.themeStore)
|
|
@@ -864,6 +876,11 @@ export function exportMod(ctx, person, body) {
|
|
|
864
876
|
if (isObj(inc.widgets) && inc.widgets[slot] === true && w)
|
|
865
877
|
(parts.widgets ??= {})[slot] = { spec: w.spec, data: w.data };
|
|
866
878
|
}
|
|
879
|
+
// #2117: its ambients, whoever set them (a person exports what is on the chat).
|
|
880
|
+
if (inc.ambient === true) {
|
|
881
|
+
const specs = Object.fromEntries(Object.entries(rec.placements.ambient).flatMap(([k, a]) => a ? [[k, a.spec]] : []));
|
|
882
|
+
parts.ambient = { specs, picture: id => { const a = rec.assets.find(x => x.id === id); const b = store.asset(name, id); return a && b ? { bytes: b.bytes, kind: a.kind } : null; } };
|
|
883
|
+
}
|
|
867
884
|
}
|
|
868
885
|
if (isObj(inc.widgets) && inc.widgets.sidebar === true && ctx.webPrefs) {
|
|
869
886
|
const v = ctx.webPrefs.read(OPERATOR).values["widget.sidebar"];
|
|
@@ -914,6 +931,19 @@ function webAuthorityOf(req, url, ctx, instance) {
|
|
|
914
931
|
return ctx.webAuthority({ session: v.kind === "allow" && v.via === "session" ? v.session : null, gateway: !!gatewayRequestContext(req),
|
|
915
932
|
peer: classifyWebPeer(req), ...(instance !== undefined ? { instance } : {}) });
|
|
916
933
|
}
|
|
934
|
+
/**
|
|
935
|
+
* #2122: what let this request in, asked now — its session (by handle, under the token it was signed in with) or the
|
|
936
|
+
* header token; null when nothing does any more (the gate also refuses a public link that is no longer the current
|
|
937
|
+
* one). Equal before and after an await: the same admission still holds. Never touches the session.
|
|
938
|
+
*/
|
|
939
|
+
function admissionOf(req, url, ctx) {
|
|
940
|
+
const gateway = gatewayRequestContext(req);
|
|
941
|
+
const v = evaluateWebRequest(req, url, ctx.webToken ?? null, ctx.webSessions, { touch: false });
|
|
942
|
+
if (v.kind !== "allow")
|
|
943
|
+
return null;
|
|
944
|
+
const via = v.via === "session" ? `session:${v.session.handle}:${v.session.tokenEpoch}` : "token";
|
|
945
|
+
return gateway ? `${via}@${gateway.exposureId}` : via;
|
|
946
|
+
}
|
|
917
947
|
/** #1827: the person, or the request is answered 401 here (it is no longer authorized) and null is returned. */
|
|
918
948
|
function webPersonOr401(req, res, url, ctx) {
|
|
919
949
|
const p = webPersonOf(req, url, ctx);
|
|
@@ -1282,9 +1312,10 @@ export function handleWebRequest(req, res, url, ctx) {
|
|
|
1282
1312
|
return true;
|
|
1283
1313
|
}
|
|
1284
1314
|
// ── #1629: agents' themes — read by every page (the public link included: it views them), changed by a person ──
|
|
1285
|
-
// GET /ui/themes: { rev, themes, locked, widgets } when the status frame's themes_rev moves (passive, web-auth.ts).
|
|
1315
|
+
// GET /ui/themes: { rev, themes, locked, widgets, ambient (#2117) } when the status frame's themes_rev moves (passive, web-auth.ts).
|
|
1286
1316
|
// GET /ui/img/theme/<instance>/<id>: a theme's picture (same headers as #1630's images; the id is the content hash).
|
|
1287
|
-
// POST /ui/themes/<instance>/reset | lock | widget (r4-4: { slot } — take an agent's widget out)
|
|
1317
|
+
// POST /ui/themes/<instance>/reset | lock | widget (r4-4: { slot } — take an agent's widget out) | ambient (#2117: { spec } sets one,
|
|
1318
|
+
// { which? } alone clears that kind or both): a person's, from an admin session — never the CLI token, never the public
|
|
1288
1319
|
// link (absent from its manifest, and refused here too).
|
|
1289
1320
|
if (method === "GET" && path === "/ui/themes") {
|
|
1290
1321
|
const store = ctx.themeStore;
|
|
@@ -1292,7 +1323,7 @@ export function handleWebRequest(req, res, url, ctx) {
|
|
|
1292
1323
|
json(res, 404, { error: "Themes are not kept on this fleet" });
|
|
1293
1324
|
return true;
|
|
1294
1325
|
}
|
|
1295
|
-
json(res, 200, { rev: store.rev(), themes: store.themes(), locked: store.lockedNames(), widgets: store.widgets() });
|
|
1326
|
+
json(res, 200, { rev: store.rev(), themes: store.themes(), locked: store.lockedNames(), widgets: store.widgets(), ambient: store.ambients() });
|
|
1296
1327
|
return true;
|
|
1297
1328
|
}
|
|
1298
1329
|
const themeImage = path.match(/^\/ui\/img\/theme\/([^/]+)\/([0-9a-f]{32})$/);
|
|
@@ -1318,7 +1349,7 @@ export function handleWebRequest(req, res, url, ctx) {
|
|
|
1318
1349
|
res.end(image.bytes);
|
|
1319
1350
|
return true;
|
|
1320
1351
|
}
|
|
1321
|
-
const themeWrite = path.match(/^\/ui\/themes\/([^/]+)\/(reset|lock|widget)$/);
|
|
1352
|
+
const themeWrite = path.match(/^\/ui\/themes\/([^/]+)\/(reset|lock|widget|ambient)$/);
|
|
1322
1353
|
if (method === "POST" && themeWrite) {
|
|
1323
1354
|
const service = ctx.webAppearance;
|
|
1324
1355
|
if (!service) {
|
|
@@ -1370,7 +1401,8 @@ export function handleWebRequest(req, res, url, ctx) {
|
|
|
1370
1401
|
}
|
|
1371
1402
|
const r = themeWrite[2] === "reset" ? service.humanReset(name)
|
|
1372
1403
|
: themeWrite[2] === "widget" ? service.humanRemoveWidget(name, body.slot)
|
|
1373
|
-
:
|
|
1404
|
+
: themeWrite[2] === "ambient" ? service.humanSetAmbient(name, body.spec ?? null, body.which)
|
|
1405
|
+
: typeof body.locked === "boolean" ? service.humanLock(name, body.locked) : { error: "locked: true or false", status: 400 };
|
|
1374
1406
|
if (typeof r.error === "string") {
|
|
1375
1407
|
json(res, typeof r.status === "number" ? r.status : 503, { error: r.error });
|
|
1376
1408
|
return;
|
|
@@ -1771,6 +1803,50 @@ export function handleWebRequest(req, res, url, ctx) {
|
|
|
1771
1803
|
// #1269: an instance's chat command (/ctx, /compact, /clear, /model, …) — a session write (CSRF, checked by the
|
|
1772
1804
|
// gate), run through the platforms' own handlers and command table. The public link is told it is one (/save is
|
|
1773
1805
|
// refused there).
|
|
1806
|
+
// ── #1939 S2b: this browser's authorization — what the app shows (GET), and "Authorize this browser" (POST: posts the
|
|
1807
|
+
// Confirm card in the account's DM). Judged on this request (§3), never touching the session.
|
|
1808
|
+
if (path === "/ui/authorization" && (method === "GET" || method === "POST")) {
|
|
1809
|
+
const judge = () => {
|
|
1810
|
+
const v = evaluateWebRequest(req, url, ctx.webToken ?? null, ctx.webSessions, { touch: false });
|
|
1811
|
+
return { session: v.kind === "allow" && v.via === "session" ? v.session : null, gateway: !!gatewayRequestContext(req), peer: classifyWebPeer(req) };
|
|
1812
|
+
};
|
|
1813
|
+
res.setHeader("Cache-Control", "no-store");
|
|
1814
|
+
if (method === "GET") {
|
|
1815
|
+
if (!ctx.webSessionAuthorization) {
|
|
1816
|
+
json(res, 404, { error: "not available" });
|
|
1817
|
+
return true;
|
|
1818
|
+
}
|
|
1819
|
+
json(res, 200, ctx.webSessionAuthorization(judge()));
|
|
1820
|
+
return true;
|
|
1821
|
+
}
|
|
1822
|
+
if (!ctx.requestSessionAuthorization) {
|
|
1823
|
+
json(res, 404, { error: "not available" });
|
|
1824
|
+
return true;
|
|
1825
|
+
}
|
|
1826
|
+
const ask = ctx.requestSessionAuthorization.bind(ctx);
|
|
1827
|
+
(async () => {
|
|
1828
|
+
try {
|
|
1829
|
+
await parseBody(req);
|
|
1830
|
+
}
|
|
1831
|
+
catch {
|
|
1832
|
+
json(res, 400, { error: "Invalid JSON" });
|
|
1833
|
+
return;
|
|
1834
|
+
}
|
|
1835
|
+
if (!permitWebContinuation(req, res, ctx))
|
|
1836
|
+
return;
|
|
1837
|
+
const r = await ask(judge()); // asked again now the body is in
|
|
1838
|
+
// A refusal is an answer the page shows (pending, three this hour, why not), not a failed request: 200 with
|
|
1839
|
+
// ok: false — a 4xx would be logged as an error in the browser's console every time.
|
|
1840
|
+
json(res, 200, r);
|
|
1841
|
+
})().catch(err => {
|
|
1842
|
+
ctx.logger.error({ err: err.message }, "Web authorization request failed");
|
|
1843
|
+
try {
|
|
1844
|
+
json(res, 500, { error: "Request failed" });
|
|
1845
|
+
}
|
|
1846
|
+
catch { /* already answered */ }
|
|
1847
|
+
});
|
|
1848
|
+
return true;
|
|
1849
|
+
}
|
|
1774
1850
|
if (method === "POST" && path === "/ui/command") {
|
|
1775
1851
|
if (!ctx.webCommand) {
|
|
1776
1852
|
json(res, 404, { error: "Not available" });
|
|
@@ -2252,6 +2328,42 @@ export function handleWebRequest(req, res, url, ctx) {
|
|
|
2252
2328
|
}
|
|
2253
2329
|
return true;
|
|
2254
2330
|
}
|
|
2331
|
+
// #2122: the usage report. Signed-in sessions by default (guests never: not in GUEST_ROUTES); hidden with the rest of
|
|
2332
|
+
// usage by web.usage_panel: false; over the public link only when web.usage_report_on_public_link is true. Answered
|
|
2333
|
+
// from the ledgers and rollups as they are; a catch-up runs behind. The page's CSV is built from this same answer.
|
|
2334
|
+
if (method === "GET" && path === "/ui/usage-report") {
|
|
2335
|
+
// Who may have it, asked on arrival and again before it is sent (the report is awaited: a switch turned off, a
|
|
2336
|
+
// session signed out or a public link ended meanwhile gets nothing). The same boundary both times, no new gate.
|
|
2337
|
+
const refusal = () => {
|
|
2338
|
+
if (!ctx.usageReport || ctx.fleetConfig?.web?.usage_panel === false)
|
|
2339
|
+
return [404, "Not available"];
|
|
2340
|
+
if (gatewayRequestContext(req) && ctx.fleetConfig?.web?.usage_report_on_public_link !== true)
|
|
2341
|
+
return [403, "usage_report_not_over_public_link"];
|
|
2342
|
+
return null;
|
|
2343
|
+
};
|
|
2344
|
+
const first = refusal();
|
|
2345
|
+
if (first) {
|
|
2346
|
+
json(res, first[0], { error: first[1] });
|
|
2347
|
+
return true;
|
|
2348
|
+
}
|
|
2349
|
+
const admitted = admissionOf(req, url, ctx);
|
|
2350
|
+
if (!admitted) {
|
|
2351
|
+
json(res, 401, { error: WEB_TOKEN_INVALID_MESSAGE });
|
|
2352
|
+
return true;
|
|
2353
|
+
}
|
|
2354
|
+
void import("./usage-report.js").then(({ usageRange, usageGroup }) => ctx.usageReport({
|
|
2355
|
+
range: usageRange(url.searchParams.get("range"), url.searchParams.get("from") ?? undefined, url.searchParams.get("to") ?? undefined),
|
|
2356
|
+
group: usageGroup(url.searchParams.get("group")),
|
|
2357
|
+
})).then((report) => {
|
|
2358
|
+
const now = refusal();
|
|
2359
|
+
if (now)
|
|
2360
|
+
return json(res, now[0], { error: now[1] });
|
|
2361
|
+
if (admissionOf(req, url, ctx) !== admitted)
|
|
2362
|
+
return json(res, 401, { error: WEB_TOKEN_INVALID_MESSAGE });
|
|
2363
|
+
json(res, 200, report);
|
|
2364
|
+
}, (err) => { ctx.logger.error({ err }, "usage report failed"); json(res, 500, { error: "Usage report failed" }); });
|
|
2365
|
+
return true;
|
|
2366
|
+
}
|
|
2255
2367
|
// #1468: the prompt-cache expiry analysis. Opening it is a person's navigation, like any Fleet tab (it counts as
|
|
2256
2368
|
// use); the page never re-reads it on a timer. Answered from the ledgers as they are; a catch-up runs behind.
|
|
2257
2369
|
if (method === "GET" && path === "/ui/cache") {
|
|
@@ -2873,23 +2985,33 @@ function shellHtmlTag(ctx, mode, viewer) {
|
|
|
2873
2985
|
* the public link, the anonymous View reader, the CLI's header token or the sign-in and setup pages (other panels).
|
|
2874
2986
|
*/
|
|
2875
2987
|
function shellOwnerCss(ctx, mode, viewer) {
|
|
2876
|
-
return mode === "full" && viewer?.person !== null && viewer?.person !== undefined && ctx.ownerCss ? ctx.ownerCss.active(viewer.person) : null;
|
|
2988
|
+
return mode === "full" && !viewer?.guest && viewer?.person !== null && viewer?.person !== undefined && ctx.ownerCss ? ctx.ownerCss.active(viewer.person) : null;
|
|
2877
2989
|
}
|
|
2878
2990
|
function guestBodyAttrs(ctx, owner, attr) {
|
|
2879
2991
|
const badge = ctx.guestBadge?.(owner);
|
|
2880
2992
|
return ` data-tier="guest"${badge ? ` data-guest-name="${attr(badge.name)}" data-guest-platform="${attr(badge.platform)}"` : ""}`;
|
|
2881
2993
|
}
|
|
2994
|
+
/** A separate session fence for Chat playback: public/guest viewers acquire no owner-pref identity. */
|
|
2995
|
+
function shellEffect(req, ctx, mode) {
|
|
2996
|
+
if (mode !== "full" || typeof ctx.effectBoot !== "string" || !/^[0-9a-f]{32}$/.test(ctx.effectBoot))
|
|
2997
|
+
return null;
|
|
2998
|
+
const v = evaluateWebRequest(req, new URL(req.url ?? "/", "http://localhost"), ctx.webToken ?? null, ctx.webSessions, { touch: false });
|
|
2999
|
+
if (v.kind !== "allow" || v.via !== "session" || !v.session || !/^[a-f0-9]{16}$/.test(v.session.handle))
|
|
3000
|
+
return null;
|
|
3001
|
+
return { boot: ctx.effectBoot, epoch: v.session.handle, scope: gatewayRequestContext(req) || v.session.tier === "guest" ? "chat" : "owner" };
|
|
3002
|
+
}
|
|
2882
3003
|
function shellBodyTag(req, ctx, mode, viewer) {
|
|
2883
3004
|
const p = shellPreview(req, ctx, mode);
|
|
2884
|
-
const ownerCss = shellOwnerCss(ctx, mode, viewer);
|
|
2885
|
-
let ownerCssState = "unknown";
|
|
2886
|
-
if (mode === "full" && viewer?.person != null && ctx.ownerCss) {
|
|
3005
|
+
const ownerCss = shellOwnerCss(ctx, mode, viewer), effect = shellEffect(req, ctx, mode);
|
|
3006
|
+
let ownerCssState = mode === "full" && (gatewayRequestContext(req) || viewer?.guest) ? "off" : "unknown";
|
|
3007
|
+
if (mode === "full" && !viewer?.guest && viewer?.person != null && ctx.ownerCss) {
|
|
2887
3008
|
ownerCssState = ctx.ownerCss.effectState(viewer.person);
|
|
2888
3009
|
if (ownerCssState === "off" && ownerCss !== null || ownerCssState === "on" && ownerCss === null)
|
|
2889
3010
|
ownerCssState = "unknown";
|
|
2890
3011
|
}
|
|
2891
3012
|
const attr = (v) => String(v ?? "").replace(/[&<>"']/g, c => `&#${c.charCodeAt(0)};`);
|
|
2892
3013
|
return `<body data-mode="${mode}"${mode === "full" && gatewayRequestContext(req) ? ' data-web-transport="poll"' : ""} data-dashboard-origin="${attr(p?.dashboardOrigin)}" data-preview-origin="${attr(p?.previewOrigin)}"`
|
|
3014
|
+
+ `${effect ? ` data-effect-boot="${effect.boot}" data-effect-epoch="${effect.epoch}" data-effect-scope="${effect.scope}"` : ""}`
|
|
2893
3015
|
+ ` data-preview-boot="${attr(p?.previewOrigin ? p.boot : "")}" data-preview-reason="${attr(p ? p.reason : "Previews are not available on this fleet.")}"`
|
|
2894
3016
|
// #1554: no availability asked (the public link, or not the full app) still says why, in the page's language.
|
|
2895
3017
|
+ ` data-preview-reason-code="${attr(p ? p.code ?? "" : gatewayRequestContext(req) ? "publicLink" : "notOffered")}"`
|
|
@@ -2925,8 +3047,11 @@ export function serveAppShell(req, res, ctx, mode) {
|
|
|
2925
3047
|
const mediaSrc = own ? `${own}/ui/file/` : "'none'";
|
|
2926
3048
|
// A public-link page frames what its exposure's preview names (the quick-tunnel wildcard: its host is not known when
|
|
2927
3049
|
// the page may load); every other page frames exactly the origin this load chose (#1723).
|
|
2928
|
-
const
|
|
2929
|
-
|
|
3050
|
+
const previewFrames = p && "frameSrc" in p ? p.frameSrc : p?.previewOrigin ? `${p.previewOrigin}/frame` : null;
|
|
3051
|
+
// This fixed trusted document is independent of HTML preview. Its own response confines all Worker execution.
|
|
3052
|
+
const effectOrigin = gatewayRequestContext(req)?.expectedOrigin ?? dashboardOriginFor(typeof req.headers.host === "string" ? req.headers.host : undefined, isSecureRequest(req));
|
|
3053
|
+
const effectFrame = shellEffect(req, ctx, mode) && effectOrigin ? `${effectOrigin}/effect-bootstrap` : null;
|
|
3054
|
+
const frameSrc = [previewFrames, effectFrame].filter(Boolean).join(" ");
|
|
2930
3055
|
sendPanelHtml(res, html.replace('<html lang="en">', shellHtmlTag(ctx, mode, viewer)).replace("<body>", shellBodyTag(req, ctx, mode, viewer)), 200, {}, { ...(frameSrc ? { frameSrc } : {}), mediaSrc });
|
|
2931
3056
|
}
|
|
2932
3057
|
catch {
|