@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
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
export declare const AMBIENT_PRESETS: readonly ["fall", "rise", "twinkle", "burst-on-click"];
|
|
2
|
+
export type AmbientPreset = typeof AMBIENT_PRESETS[number];
|
|
3
|
+
/** Where a preset is kept: a chat has one of each. */
|
|
4
|
+
export declare const AMBIENT_KINDS: readonly ["loop", "click"];
|
|
5
|
+
export type AmbientKind = typeof AMBIENT_KINDS[number];
|
|
6
|
+
export declare const ambientKind: (preset: AmbientPreset) => AmbientKind;
|
|
7
|
+
export declare const AMBIENT_GLYPHS_MAX = 4;
|
|
8
|
+
export declare const AMBIENT_GLYPH_GRAPHEMES = 2;
|
|
9
|
+
export declare const AMBIENT_COLORS_MAX = 5;
|
|
10
|
+
export declare const AMBIENT_SPEC_MAX_BYTES = 1024;
|
|
11
|
+
/** The page's caps (chat-ambient.js keeps the same numbers): 24 looping particles, 3 bursts of 16 at once. */
|
|
12
|
+
export declare const AMBIENT_LOOP_MAX = 24;
|
|
13
|
+
export declare const AMBIENT_BURST_MAX = 16;
|
|
14
|
+
export declare const AMBIENT_BURSTS_LIVE = 3;
|
|
15
|
+
type Range = readonly [number, number];
|
|
16
|
+
/** Each preset's bounds and defaults. A count is particles (loops) or particles per click (burst). */
|
|
17
|
+
export declare const AMBIENT_RULES: Record<AmbientPreset, {
|
|
18
|
+
count: Range;
|
|
19
|
+
countDefault: number;
|
|
20
|
+
duration: Range;
|
|
21
|
+
durationDefault: Range;
|
|
22
|
+
}>;
|
|
23
|
+
export declare const AMBIENT_SIZE: Range;
|
|
24
|
+
export declare const AMBIENT_SIZE_DEFAULT: Range;
|
|
25
|
+
export declare const AMBIENT_SWAY_MAX = 80;
|
|
26
|
+
export declare const AMBIENT_SWAY_DEFAULT = 40;
|
|
27
|
+
export declare const AMBIENT_SPREAD: Range;
|
|
28
|
+
export declare const AMBIENT_SPREAD_DEFAULT: Range;
|
|
29
|
+
export declare const AMBIENT_ROTATE_MAX = 720;
|
|
30
|
+
export declare const AMBIENT_ROTATE_DEFAULT = 300;
|
|
31
|
+
export declare const AMBIENT_OPACITY_DEFAULT: readonly [0, 0.85, 0.15];
|
|
32
|
+
export type AmbientGlyph = string | {
|
|
33
|
+
asset: string;
|
|
34
|
+
};
|
|
35
|
+
/** Canonical: every field present, so the page and a preview draw the same thing. */
|
|
36
|
+
export interface AmbientSpec {
|
|
37
|
+
preset: AmbientPreset;
|
|
38
|
+
glyphs: AmbientGlyph[];
|
|
39
|
+
/** Empty: the theme's accent. */
|
|
40
|
+
colors: string[];
|
|
41
|
+
count: number;
|
|
42
|
+
duration: [number, number];
|
|
43
|
+
size: [number, number];
|
|
44
|
+
sway: number;
|
|
45
|
+
spread: [number, number];
|
|
46
|
+
rotate: number;
|
|
47
|
+
opacity: [number, number, number];
|
|
48
|
+
layer: "behind";
|
|
49
|
+
}
|
|
50
|
+
export interface AmbientProblem {
|
|
51
|
+
path: string;
|
|
52
|
+
problem: string;
|
|
53
|
+
}
|
|
54
|
+
export type Checked<T> = {
|
|
55
|
+
ok: true;
|
|
56
|
+
value: T;
|
|
57
|
+
} | {
|
|
58
|
+
ok: false;
|
|
59
|
+
errors: AmbientProblem[];
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* A spec, closed and canonical: unknown keys, a value out of its preset's bounds or a glyph that is not 1..2 visible
|
|
63
|
+
* graphemes (or one of this instance's `glyph` pictures, when `glyphAsset` is given) are refused.
|
|
64
|
+
*/
|
|
65
|
+
export declare function checkAmbient(raw: unknown, opts?: {
|
|
66
|
+
glyphAsset?: (id: string) => boolean;
|
|
67
|
+
}): Checked<AmbientSpec>;
|
|
68
|
+
/** The glyph pictures a spec names. */
|
|
69
|
+
export declare function ambientAssets(spec: AmbientSpec): string[];
|
|
70
|
+
/** One line per problem, for a tool's refusal. */
|
|
71
|
+
export declare function ambientProblems(errors: AmbientProblem[]): string;
|
|
72
|
+
export {};
|
|
@@ -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
|
@@ -49,9 +49,11 @@ export interface WebApiContext {
|
|
|
49
49
|
group_id?: number | string;
|
|
50
50
|
mode?: string;
|
|
51
51
|
};
|
|
52
|
+
health_port?: number;
|
|
52
53
|
web?: {
|
|
53
54
|
echo_to_channel?: boolean;
|
|
54
55
|
usage_panel?: boolean;
|
|
56
|
+
usage_report_on_public_link?: boolean;
|
|
55
57
|
upload_max_file_mb?: number;
|
|
56
58
|
upload_max_total_mb?: number;
|
|
57
59
|
};
|
|
@@ -130,6 +132,7 @@ export interface WebApiContext {
|
|
|
130
132
|
/** Current owned pane reader shared with status; absent means context is unavailable. */
|
|
131
133
|
getPaneContextSource?(name: string): PaneContextSource | null;
|
|
132
134
|
/** #1306: which preview origin this /ui load may frame (Host and the TLS signal only); absent: previews off. */
|
|
135
|
+
readonly effectBoot?: string;
|
|
133
136
|
previewForUi?(hostHeader: string | undefined, secure: boolean): PreviewAvailability & {
|
|
134
137
|
boot: string | null;
|
|
135
138
|
};
|
|
@@ -196,6 +199,11 @@ export interface WebApiContext {
|
|
|
196
199
|
logSteps?(name: string, q: import("./log-service.js").LogQuery): Promise<unknown>;
|
|
197
200
|
/** #1468: the prompt-cache expiry analysis for a window ("24h" | "7d" | "30d"); absent: not offered. */
|
|
198
201
|
cacheReport?(window: string): Promise<unknown>;
|
|
202
|
+
/** #2122: the usage report (Fleet → Usage); absent: not offered. */
|
|
203
|
+
usageReport?(params: {
|
|
204
|
+
range: import("./usage-report.js").UsageRange;
|
|
205
|
+
group: import("./usage-report.js").UsageGroup;
|
|
206
|
+
}): Promise<unknown>;
|
|
199
207
|
/** #1269: an instance's chat command from the web chat (web-commands.ts); absent: not offered. */
|
|
200
208
|
webCommand?(input: {
|
|
201
209
|
instance: string;
|
|
@@ -210,6 +218,29 @@ export interface WebApiContext {
|
|
|
210
218
|
status: number;
|
|
211
219
|
body: unknown;
|
|
212
220
|
}>;
|
|
221
|
+
/** #1939 S2b: "Authorize this browser" — the Confirm card in the account's DM; and what the app shows for this browser. */
|
|
222
|
+
requestSessionAuthorization?(input: {
|
|
223
|
+
session: SessionRecord | null;
|
|
224
|
+
gateway: boolean;
|
|
225
|
+
peer: WebPeer;
|
|
226
|
+
}): Promise<{
|
|
227
|
+
ok: true;
|
|
228
|
+
expiresInMs: number;
|
|
229
|
+
} | {
|
|
230
|
+
ok: false;
|
|
231
|
+
refusal: string;
|
|
232
|
+
retryAfterMs?: number;
|
|
233
|
+
}>;
|
|
234
|
+
webSessionAuthorization?(input: {
|
|
235
|
+
session: SessionRecord | null;
|
|
236
|
+
gateway: boolean;
|
|
237
|
+
peer: WebPeer;
|
|
238
|
+
}): {
|
|
239
|
+
state: string;
|
|
240
|
+
until?: number;
|
|
241
|
+
expiresInMs?: number;
|
|
242
|
+
reason?: string;
|
|
243
|
+
};
|
|
213
244
|
/** #1939 S1: the design's §3 for one request (`instance` once the body names it); absent: web carries no authority. */
|
|
214
245
|
webAuthority?(input: {
|
|
215
246
|
session: SessionRecord | null;
|
|
@@ -350,6 +381,8 @@ export type PublicPreviewForUi = PreviewAvailability & {
|
|
|
350
381
|
detail?: string;
|
|
351
382
|
};
|
|
352
383
|
export interface AppShellContext {
|
|
384
|
+
/** #2118: independent trusted opaque effect document, never HTML-preview consent or listener state. */
|
|
385
|
+
readonly effectBoot?: string;
|
|
353
386
|
previewForUi?(hostHeader: string | undefined, secure: boolean): (PreviewAvailability & {
|
|
354
387
|
boot: string | null;
|
|
355
388
|
}) | 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,47 @@ 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
|
+
// Over the public link, where to turn it on: Settings → Web on the dashboard reached directly (this computer's own
|
|
2345
|
+
// address — the public link's Settings only reads). Never the public origin's own Settings, which cannot change it.
|
|
2346
|
+
const answer = ([status, error]) => json(res, status, {
|
|
2347
|
+
error, ...(status === 403 ? { settings: `http://localhost:${ctx.fleetConfig?.health_port ?? 19280}/settings/web` } : {})
|
|
2348
|
+
});
|
|
2349
|
+
const first = refusal();
|
|
2350
|
+
if (first) {
|
|
2351
|
+
answer(first);
|
|
2352
|
+
return true;
|
|
2353
|
+
}
|
|
2354
|
+
const admitted = admissionOf(req, url, ctx);
|
|
2355
|
+
if (!admitted) {
|
|
2356
|
+
json(res, 401, { error: WEB_TOKEN_INVALID_MESSAGE });
|
|
2357
|
+
return true;
|
|
2358
|
+
}
|
|
2359
|
+
void import("./usage-report.js").then(({ usageRange, usageGroup }) => ctx.usageReport({
|
|
2360
|
+
range: usageRange(url.searchParams.get("range"), url.searchParams.get("from") ?? undefined, url.searchParams.get("to") ?? undefined),
|
|
2361
|
+
group: usageGroup(url.searchParams.get("group")),
|
|
2362
|
+
})).then((report) => {
|
|
2363
|
+
const now = refusal();
|
|
2364
|
+
if (now)
|
|
2365
|
+
return answer(now);
|
|
2366
|
+
if (admissionOf(req, url, ctx) !== admitted)
|
|
2367
|
+
return json(res, 401, { error: WEB_TOKEN_INVALID_MESSAGE });
|
|
2368
|
+
json(res, 200, report);
|
|
2369
|
+
}, (err) => { ctx.logger.error({ err }, "usage report failed"); json(res, 500, { error: "Usage report failed" }); });
|
|
2370
|
+
return true;
|
|
2371
|
+
}
|
|
2255
2372
|
// #1468: the prompt-cache expiry analysis. Opening it is a person's navigation, like any Fleet tab (it counts as
|
|
2256
2373
|
// use); the page never re-reads it on a timer. Answered from the ledgers as they are; a catch-up runs behind.
|
|
2257
2374
|
if (method === "GET" && path === "/ui/cache") {
|
|
@@ -2873,23 +2990,33 @@ function shellHtmlTag(ctx, mode, viewer) {
|
|
|
2873
2990
|
* the public link, the anonymous View reader, the CLI's header token or the sign-in and setup pages (other panels).
|
|
2874
2991
|
*/
|
|
2875
2992
|
function shellOwnerCss(ctx, mode, viewer) {
|
|
2876
|
-
return mode === "full" && viewer?.person !== null && viewer?.person !== undefined && ctx.ownerCss ? ctx.ownerCss.active(viewer.person) : null;
|
|
2993
|
+
return mode === "full" && !viewer?.guest && viewer?.person !== null && viewer?.person !== undefined && ctx.ownerCss ? ctx.ownerCss.active(viewer.person) : null;
|
|
2877
2994
|
}
|
|
2878
2995
|
function guestBodyAttrs(ctx, owner, attr) {
|
|
2879
2996
|
const badge = ctx.guestBadge?.(owner);
|
|
2880
2997
|
return ` data-tier="guest"${badge ? ` data-guest-name="${attr(badge.name)}" data-guest-platform="${attr(badge.platform)}"` : ""}`;
|
|
2881
2998
|
}
|
|
2999
|
+
/** A separate session fence for Chat playback: public/guest viewers acquire no owner-pref identity. */
|
|
3000
|
+
function shellEffect(req, ctx, mode) {
|
|
3001
|
+
if (mode !== "full" || typeof ctx.effectBoot !== "string" || !/^[0-9a-f]{32}$/.test(ctx.effectBoot))
|
|
3002
|
+
return null;
|
|
3003
|
+
const v = evaluateWebRequest(req, new URL(req.url ?? "/", "http://localhost"), ctx.webToken ?? null, ctx.webSessions, { touch: false });
|
|
3004
|
+
if (v.kind !== "allow" || v.via !== "session" || !v.session || !/^[a-f0-9]{16}$/.test(v.session.handle))
|
|
3005
|
+
return null;
|
|
3006
|
+
return { boot: ctx.effectBoot, epoch: v.session.handle, scope: gatewayRequestContext(req) || v.session.tier === "guest" ? "chat" : "owner" };
|
|
3007
|
+
}
|
|
2882
3008
|
function shellBodyTag(req, ctx, mode, viewer) {
|
|
2883
3009
|
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) {
|
|
3010
|
+
const ownerCss = shellOwnerCss(ctx, mode, viewer), effect = shellEffect(req, ctx, mode);
|
|
3011
|
+
let ownerCssState = mode === "full" && (gatewayRequestContext(req) || viewer?.guest) ? "off" : "unknown";
|
|
3012
|
+
if (mode === "full" && !viewer?.guest && viewer?.person != null && ctx.ownerCss) {
|
|
2887
3013
|
ownerCssState = ctx.ownerCss.effectState(viewer.person);
|
|
2888
3014
|
if (ownerCssState === "off" && ownerCss !== null || ownerCssState === "on" && ownerCss === null)
|
|
2889
3015
|
ownerCssState = "unknown";
|
|
2890
3016
|
}
|
|
2891
3017
|
const attr = (v) => String(v ?? "").replace(/[&<>"']/g, c => `&#${c.charCodeAt(0)};`);
|
|
2892
3018
|
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)}"`
|
|
3019
|
+
+ `${effect ? ` data-effect-boot="${effect.boot}" data-effect-epoch="${effect.epoch}" data-effect-scope="${effect.scope}"` : ""}`
|
|
2893
3020
|
+ ` data-preview-boot="${attr(p?.previewOrigin ? p.boot : "")}" data-preview-reason="${attr(p ? p.reason : "Previews are not available on this fleet.")}"`
|
|
2894
3021
|
// #1554: no availability asked (the public link, or not the full app) still says why, in the page's language.
|
|
2895
3022
|
+ ` data-preview-reason-code="${attr(p ? p.code ?? "" : gatewayRequestContext(req) ? "publicLink" : "notOffered")}"`
|
|
@@ -2925,8 +3052,11 @@ export function serveAppShell(req, res, ctx, mode) {
|
|
|
2925
3052
|
const mediaSrc = own ? `${own}/ui/file/` : "'none'";
|
|
2926
3053
|
// A public-link page frames what its exposure's preview names (the quick-tunnel wildcard: its host is not known when
|
|
2927
3054
|
// the page may load); every other page frames exactly the origin this load chose (#1723).
|
|
2928
|
-
const
|
|
2929
|
-
|
|
3055
|
+
const previewFrames = p && "frameSrc" in p ? p.frameSrc : p?.previewOrigin ? `${p.previewOrigin}/frame` : null;
|
|
3056
|
+
// This fixed trusted document is independent of HTML preview. Its own response confines all Worker execution.
|
|
3057
|
+
const effectOrigin = gatewayRequestContext(req)?.expectedOrigin ?? dashboardOriginFor(typeof req.headers.host === "string" ? req.headers.host : undefined, isSecureRequest(req));
|
|
3058
|
+
const effectFrame = shellEffect(req, ctx, mode) && effectOrigin ? `${effectOrigin}/effect-bootstrap` : null;
|
|
3059
|
+
const frameSrc = [previewFrames, effectFrame].filter(Boolean).join(" ");
|
|
2930
3060
|
sendPanelHtml(res, html.replace('<html lang="en">', shellHtmlTag(ctx, mode, viewer)).replace("<body>", shellBodyTag(req, ctx, mode, viewer)), 200, {}, { ...(frameSrc ? { frameSrc } : {}), mediaSrc });
|
|
2931
3061
|
}
|
|
2932
3062
|
catch {
|