@songsid/agend 2.2.0 → 2.3.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-cli.js +248 -2
- package/dist/agent-cli.js.map +1 -1
- package/dist/agent-endpoint.d.ts +2 -0
- package/dist/agent-endpoint.js +25 -0
- package/dist/agent-endpoint.js.map +1 -1
- package/dist/agent-file-read.d.ts +35 -0
- package/dist/agent-file-read.js +144 -0
- package/dist/agent-file-read.js.map +1 -0
- package/dist/agent-whats-new.js +8 -0
- package/dist/agent-whats-new.js.map +1 -1
- package/dist/atomic-write.js +9 -1
- package/dist/atomic-write.js.map +1 -1
- package/dist/auth-api.js +17 -0
- package/dist/auth-api.js.map +1 -1
- package/dist/backend/antigravity.js +3 -3
- package/dist/backend/antigravity.js.map +1 -1
- package/dist/backend/claude-code.js +5 -1
- package/dist/backend/claude-code.js.map +1 -1
- package/dist/backend/launch-effort.js +1 -1
- package/dist/backend/launch-effort.js.map +1 -1
- package/dist/backend/muse.js +10 -2
- package/dist/backend/muse.js.map +1 -1
- package/dist/backend/types.d.ts +8 -0
- package/dist/backend/types.js +10 -0
- package/dist/backend/types.js.map +1 -1
- package/dist/cache-analysis.d.ts +47 -1
- package/dist/cache-analysis.js +68 -8
- package/dist/cache-analysis.js.map +1 -1
- package/dist/cache-report-tool.d.ts +17 -0
- package/dist/cache-report-tool.js +40 -0
- package/dist/cache-report-tool.js.map +1 -0
- package/dist/cache-service.d.ts +15 -0
- package/dist/cache-service.js +26 -1
- package/dist/cache-service.js.map +1 -1
- package/dist/channel/adapters/discord.d.ts +2 -0
- package/dist/channel/adapters/discord.js +17 -2
- package/dist/channel/adapters/discord.js.map +1 -1
- package/dist/channel/adapters/telegram.d.ts +8 -1
- package/dist/channel/adapters/telegram.js +74 -4
- package/dist/channel/adapters/telegram.js.map +1 -1
- package/dist/channel/mcp-tools.js +36 -0
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/channel/types.d.ts +15 -0
- package/dist/cli.js +7 -3
- package/dist/cli.js.map +1 -1
- package/dist/config-validator.js +22 -2
- package/dist/config-validator.js.map +1 -1
- package/dist/daemon.js +244 -20
- package/dist/daemon.js.map +1 -1
- package/dist/fleet-context.d.ts +5 -0
- package/dist/fleet-manager.d.ts +92 -0
- package/dist/fleet-manager.js +410 -29
- package/dist/fleet-manager.js.map +1 -1
- package/dist/general-knowledge/skills/credential-profiles/SKILL.md +7 -0
- package/dist/general-knowledge/skills/web-appearance/SKILL.md +367 -0
- package/dist/instance-avatar.js +3 -0
- package/dist/instance-avatar.js.map +1 -1
- package/dist/instance-config-impact.js +3 -0
- package/dist/instance-config-impact.js.map +1 -1
- package/dist/instance-lifecycle.js +9 -4
- package/dist/instance-lifecycle.js.map +1 -1
- package/dist/needs-you-hub.d.ts +4 -0
- package/dist/needs-you-hub.js +31 -6
- package/dist/needs-you-hub.js.map +1 -1
- package/dist/needs-you.d.ts +1 -13
- package/dist/needs-you.js +3 -37
- package/dist/needs-you.js.map +1 -1
- package/dist/outbound-handlers.d.ts +7 -0
- package/dist/outbound-handlers.js +65 -10
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.d.ts +74 -0
- package/dist/outbound-schemas.js +56 -0
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/public-web-gateway.js +14 -0
- package/dist/public-web-gateway.js.map +1 -1
- package/dist/repo-worktree-guard.d.ts +36 -0
- package/dist/repo-worktree-guard.js +143 -0
- package/dist/repo-worktree-guard.js.map +1 -0
- package/dist/scheduler/scheduler.d.ts +70 -1
- package/dist/scheduler/scheduler.js +256 -17
- package/dist/scheduler/scheduler.js.map +1 -1
- package/dist/service-installer.d.ts +2 -1
- package/dist/service-installer.js +18 -3
- package/dist/service-installer.js.map +1 -1
- package/dist/settings-api.js +74 -1
- package/dist/settings-api.js.map +1 -1
- package/dist/settings-change.js +8 -0
- package/dist/settings-change.js.map +1 -1
- package/dist/settings-effect.js +16 -2
- package/dist/settings-effect.js.map +1 -1
- package/dist/settings-http-confirmation.js +5 -0
- package/dist/settings-http-confirmation.js.map +1 -1
- package/dist/tool-permissions.d.ts +5 -0
- package/dist/tool-permissions.js +61 -2
- package/dist/tool-permissions.js.map +1 -1
- package/dist/topic-commands.d.ts +10 -6
- package/dist/topic-commands.js +21 -17
- package/dist/topic-commands.js.map +1 -1
- package/dist/topic-link.d.ts +128 -0
- package/dist/topic-link.js +269 -0
- package/dist/topic-link.js.map +1 -0
- package/dist/types.d.ts +13 -0
- package/dist/ui/app-effects.js +187 -0
- package/dist/ui/chat-effect-posters.js +42 -0
- package/dist/ui/chat-effects.js +72 -0
- package/dist/ui/chat-live-replies.js +58 -0
- package/dist/ui/chat-render.js +4 -4
- package/dist/ui/chat-store.js +9 -3
- package/dist/ui/chat-strings.js +15 -0
- package/dist/ui/chat-thread.js +47 -21
- package/dist/ui/chat-tour.js +1 -1
- package/dist/ui/effect-trial-controller.js +119 -0
- package/dist/ui/effect-trial-offer.js +31 -0
- package/dist/ui/effect-trial-panel.js +37 -0
- package/dist/ui/fleet-cache.js +23 -1
- package/dist/ui/image-lightbox.js +1 -1
- package/dist/ui/panel-chat.js +85 -17
- package/dist/ui/panel-details.js +18 -5
- package/dist/ui/panel-fleet.js +3 -3
- package/dist/ui/panel-log.js +6 -6
- package/dist/ui/panel-needs.js +3 -3
- package/dist/ui/panel-settings.js +8 -5
- package/dist/ui/preview-panel.js +25 -9
- package/dist/ui/settings-appearance.js +325 -0
- package/dist/ui/settings-effects.js +201 -0
- package/dist/ui/settings-mods.js +257 -0
- package/dist/ui/settings-owner-effects.js +80 -0
- package/dist/ui/settings-strings.js +245 -0
- package/dist/ui/shared/agend-auth.js +27 -3
- package/dist/ui/shared/agent-widget.js +248 -0
- package/dist/ui/shared/app-i18n.js +2 -0
- package/dist/ui/shared/app-prefs.js +30 -10
- package/dist/ui/shared/app-route.js +23 -1
- package/dist/ui/shared/app-session.js +1 -1
- package/dist/ui/shared/app-shell.js +29 -16
- package/dist/ui/shared/app-store.js +2 -0
- package/dist/ui/shared/app-stream.js +12 -7
- package/dist/ui/shared/app-themes.js +188 -0
- package/dist/ui/shared/app.css +225 -3
- package/dist/ui/shared/app.js +6 -0
- package/dist/ui/shared/card-strings.js +28 -0
- package/dist/ui/shared/effect-app-runtime.js +248 -0
- package/dist/ui/shared/effect-baseline.js +64 -0
- package/dist/ui/shared/effect-chat-runtime.js +154 -0
- package/dist/ui/shared/effect-cursor.js +125 -0
- package/dist/ui/shared/effect-frame.js +127 -0
- package/dist/ui/shared/effect-geometry.js +122 -0
- package/dist/ui/shared/effect-host-style.js +10 -0
- package/dist/ui/shared/effect-owner-regions.js +74 -0
- package/dist/ui/shared/effect-owner-source.js +115 -0
- package/dist/ui/shared/effect-policy.js +137 -0
- package/dist/ui/shared/effect-poster-reader.js +98 -0
- package/dist/ui/shared/effect-posters.js +117 -0
- package/dist/ui/shared/effect-profiler.js +162 -0
- package/dist/ui/shared/effect-profiles.js +65 -0
- package/dist/ui/shared/effect-registry.js +496 -0
- package/dist/ui/shared/effect-runtime-loader.js +234 -0
- package/dist/ui/shared/effect-safety.js +153 -0
- package/dist/ui/shared/effect-stock-snapshot.js +34 -0
- package/dist/ui/shared/effect-stock-trust.js +165 -0
- package/dist/ui/shared/effect-trial-source.js +103 -0
- package/dist/ui/shared/effect-trial-stage.js +147 -0
- package/dist/ui/shared/header-tools.js +1 -1
- package/dist/ui/shared/identity-card.js +202 -0
- package/dist/ui/shared/identity-color.js +261 -0
- package/dist/ui/shared/instance-nav.js +37 -19
- package/dist/ui/shared/owner-css.js +221 -0
- package/dist/ui/shared/owner-effect-prefs.js +117 -0
- package/dist/ui/shared/panel-view.js +81 -8
- package/dist/ui/shared/signin.js +2 -0
- package/dist/ui/shared/ui-dialog.js +1 -1
- package/dist/ui/shared/ui-icons.js +2 -0
- package/dist/ui/shared/ui-menu.js +1 -1
- package/dist/ui/shared/ui-toast.js +1 -1
- package/dist/ui/shared/usage-dialog.js +14 -2
- package/dist/ui/shared/view-strings.js +36 -2
- package/dist/ui/signin.html +1 -0
- package/dist/usage/providers.d.ts +3 -0
- package/dist/usage/providers.js +4 -3
- package/dist/usage/providers.js.map +1 -1
- package/dist/view-api.d.ts +29 -0
- package/dist/view-api.js +188 -8
- package/dist/view-api.js.map +1 -1
- package/dist/web-api.d.ts +42 -0
- package/dist/web-api.js +898 -36
- package/dist/web-api.js.map +1 -1
- package/dist/web-appearance-limits.d.ts +82 -0
- package/dist/web-appearance-limits.js +83 -0
- package/dist/web-appearance-limits.js.map +1 -0
- package/dist/web-appearance-policy.d.ts +66 -0
- package/dist/web-appearance-policy.js +94 -0
- package/dist/web-appearance-policy.js.map +1 -0
- package/dist/web-appearance-tools.d.ts +50 -0
- package/dist/web-appearance-tools.js +105 -0
- package/dist/web-appearance-tools.js.map +1 -0
- package/dist/web-appearance-view.d.ts +99 -0
- package/dist/web-appearance-view.js +163 -0
- package/dist/web-appearance-view.js.map +1 -0
- package/dist/web-appearance.d.ts +79 -0
- package/dist/web-appearance.js +298 -0
- package/dist/web-appearance.js.map +1 -0
- package/dist/web-asset-parts.d.ts +93 -0
- package/dist/web-asset-parts.js +133 -0
- package/dist/web-asset-parts.js.map +1 -0
- package/dist/web-auth.js +17 -1
- package/dist/web-auth.js.map +1 -1
- package/dist/web-card.d.ts +96 -0
- package/dist/web-card.js +232 -0
- package/dist/web-card.js.map +1 -0
- package/dist/web-chat-history.d.ts +13 -0
- package/dist/web-chat-history.js +23 -2
- package/dist/web-chat-history.js.map +1 -1
- package/dist/web-effect-bundle.d.ts +28 -0
- package/dist/web-effect-bundle.js +126 -0
- package/dist/web-effect-bundle.js.map +1 -0
- package/dist/web-effect-canonical.d.ts +8 -0
- package/dist/web-effect-canonical.js +27 -0
- package/dist/web-effect-canonical.js.map +1 -0
- package/dist/web-effect-library.d.ts +114 -0
- package/dist/web-effect-library.js +530 -0
- package/dist/web-effect-library.js.map +1 -0
- package/dist/web-effect-poster.d.ts +8 -0
- package/dist/web-effect-poster.js +52 -0
- package/dist/web-effect-poster.js.map +1 -0
- package/dist/web-effect-profiles.d.ts +118 -0
- package/dist/web-effect-profiles.js +180 -0
- package/dist/web-effect-profiles.js.map +1 -0
- package/dist/web-effect-service.d.ts +86 -0
- package/dist/web-effect-service.js +314 -0
- package/dist/web-effect-service.js.map +1 -0
- package/dist/web-effect-tools.d.ts +47 -0
- package/dist/web-effect-tools.js +129 -0
- package/dist/web-effect-tools.js.map +1 -0
- package/dist/web-effect-trials.d.ts +86 -0
- package/dist/web-effect-trials.js +204 -0
- package/dist/web-effect-trials.js.map +1 -0
- package/dist/web-effect-upload.d.ts +27 -0
- package/dist/web-effect-upload.js +185 -0
- package/dist/web-effect-upload.js.map +1 -0
- package/dist/web-effect-worker.d.ts +8 -0
- package/dist/web-effect-worker.js +170 -0
- package/dist/web-effect-worker.js.map +1 -0
- package/dist/web-effect.d.ts +103 -0
- package/dist/web-effect.js +153 -0
- package/dist/web-effect.js.map +1 -0
- package/dist/web-identity-color.d.ts +87 -0
- package/dist/web-identity-color.js +209 -0
- package/dist/web-identity-color.js.map +1 -0
- package/dist/web-identity-palette.d.ts +113 -0
- package/dist/web-identity-palette.js +84 -0
- package/dist/web-identity-palette.js.map +1 -0
- package/dist/web-image-meta.d.ts +25 -0
- package/dist/web-image-meta.js +200 -0
- package/dist/web-image-meta.js.map +1 -0
- package/dist/web-mod-agent.d.ts +50 -0
- package/dist/web-mod-agent.js +205 -0
- package/dist/web-mod-agent.js.map +1 -0
- package/dist/web-mod-bundle.d.ts +149 -0
- package/dist/web-mod-bundle.js +470 -0
- package/dist/web-mod-bundle.js.map +1 -0
- package/dist/web-mod-export.d.ts +59 -0
- package/dist/web-mod-export.js +79 -0
- package/dist/web-mod-export.js.map +1 -0
- package/dist/web-mod-store.d.ts +283 -0
- package/dist/web-mod-store.js +625 -0
- package/dist/web-mod-store.js.map +1 -0
- package/dist/web-org.d.ts +3 -3
- package/dist/web-org.js +11 -17
- package/dist/web-org.js.map +1 -1
- package/dist/web-owner-css-store.d.ts +180 -0
- package/dist/web-owner-css-store.js +384 -0
- package/dist/web-owner-css-store.js.map +1 -0
- package/dist/web-owner-css.d.ts +58 -0
- package/dist/web-owner-css.js +552 -0
- package/dist/web-owner-css.js.map +1 -0
- package/dist/web-prefs.d.ts +48 -0
- package/dist/web-prefs.js +209 -33
- package/dist/web-prefs.js.map +1 -1
- package/dist/web-preview.d.ts +9 -1
- package/dist/web-preview.js +24 -7
- package/dist/web-preview.js.map +1 -1
- package/dist/web-settings-patch.d.ts +18 -0
- package/dist/web-settings-patch.js +51 -0
- package/dist/web-settings-patch.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 +218 -0
- package/dist/web-theme-store.js +516 -0
- package/dist/web-theme-store.js.map +1 -0
- package/dist/web-theme.d.ts +121 -0
- package/dist/web-theme.js +407 -0
- package/dist/web-theme.js.map +1 -0
- package/dist/web-widget.d.ts +72 -0
- package/dist/web-widget.js +245 -0
- package/dist/web-widget.js.map +1 -0
- package/package.json +1 -1
- package/templates/systemd.service.ejs +8 -0
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* #1990: links to another topic. One place builds "a link to instance X's place (or a world's General) on surface S"
|
|
3
|
+
* from what the adapters already know — Discord guild/channel ids, the Telegram supergroup id, its public @username
|
|
4
|
+
* when it has one, and topic ids — and messages name such a place through a token that each surface renders in its own
|
|
5
|
+
* form, or as plain text when the reader could not open the target.
|
|
6
|
+
*
|
|
7
|
+
* Discord `<#channel>` (clickable in place); in a DM, `https://discord.com/channels/<guild>/<channel>`
|
|
8
|
+
* Telegram `https://t.me/<username>/<topic>` (public group) or `https://t.me/c/<id without -100>/<topic>`, carried by
|
|
9
|
+
* a `text_link` entity on plain text — no parse mode, so nothing in the text needs escaping
|
|
10
|
+
* plain the label alone (a suffix token disappears)
|
|
11
|
+
*
|
|
12
|
+
* The forms are the documented ones; no real client was driven (#1990: the user checks them on the 2.3 beta).
|
|
13
|
+
*/
|
|
14
|
+
/** A world's place, for links: its platform, its group and (Telegram) the group's public @username once read. */
|
|
15
|
+
export function worldPlaceOf(world) {
|
|
16
|
+
const username = world.groupId ? world.adapter?.groupUsername?.(world.groupId) : undefined;
|
|
17
|
+
return { type: world.type, ...(world.groupId ? { groupId: world.groupId } : {}), ...(username ? { username } : {}) };
|
|
18
|
+
}
|
|
19
|
+
export const SNOWFLAKE = /^\d{5,25}$/;
|
|
20
|
+
/** A Telegram @username as the platform allows it: 4–32 of [A-Za-z0-9_], starting with a letter. */
|
|
21
|
+
const TELEGRAM_USERNAME = /^[A-Za-z][A-Za-z0-9_]{3,31}$/;
|
|
22
|
+
/** t.me/c/ links take a supergroup's id without its -100 prefix; anything else has no such link. */
|
|
23
|
+
export function telegramInternalId(chatId) {
|
|
24
|
+
const m = /^-100(\d+)$/.exec(chatId);
|
|
25
|
+
return m ? m[1] : undefined;
|
|
26
|
+
}
|
|
27
|
+
/** The t.me path prefix of a group: `<username>` for a public one, `c/<id>` for a private supergroup. */
|
|
28
|
+
function telegramGroupPath(place, chatId = place.groupId ?? "") {
|
|
29
|
+
if (place.username && TELEGRAM_USERNAME.test(place.username) && chatId === place.groupId)
|
|
30
|
+
return place.username;
|
|
31
|
+
const internal = telegramInternalId(chatId);
|
|
32
|
+
return internal ? `c/${internal}` : undefined;
|
|
33
|
+
}
|
|
34
|
+
/** A link to a message (a prompt's own buttons), or undefined when none can be built for this platform/place. */
|
|
35
|
+
export function messageLink(place, at) {
|
|
36
|
+
if (!at.messageId)
|
|
37
|
+
return undefined;
|
|
38
|
+
if (place.type === "discord") {
|
|
39
|
+
const channel = at.threadId ?? at.chatId;
|
|
40
|
+
if (!place.groupId || !SNOWFLAKE.test(place.groupId) || !SNOWFLAKE.test(channel) || !SNOWFLAKE.test(at.messageId))
|
|
41
|
+
return undefined;
|
|
42
|
+
return `https://discord.com/channels/${place.groupId}/${channel}/${at.messageId}`;
|
|
43
|
+
}
|
|
44
|
+
if (place.type === "telegram") {
|
|
45
|
+
const path = telegramGroupPath(place, at.chatId);
|
|
46
|
+
if (!path || !/^\d+$/.test(at.messageId))
|
|
47
|
+
return undefined;
|
|
48
|
+
const topic = at.threadId && at.threadId !== "1" && /^\d+$/.test(at.threadId) ? `${at.threadId}/` : "";
|
|
49
|
+
return `https://t.me/${path}/${topic}${at.messageId}`;
|
|
50
|
+
}
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* A browser URL to a topic or channel. Telegram's General topic (1) gets one only when `general` is set: an
|
|
55
|
+
* instance's link never points at General.
|
|
56
|
+
*/
|
|
57
|
+
export function topicUrl(place, topicId, opts = {}) {
|
|
58
|
+
if (!place || !topicId)
|
|
59
|
+
return undefined;
|
|
60
|
+
if (place.type === "discord") {
|
|
61
|
+
if (!place.groupId || !SNOWFLAKE.test(place.groupId) || !SNOWFLAKE.test(topicId))
|
|
62
|
+
return undefined;
|
|
63
|
+
return `https://discord.com/channels/${place.groupId}/${topicId}`;
|
|
64
|
+
}
|
|
65
|
+
if (place.type === "telegram") {
|
|
66
|
+
const path = telegramGroupPath(place);
|
|
67
|
+
if (!path || !/^\d+$/.test(topicId) || (topicId === "1" && !opts.general))
|
|
68
|
+
return undefined;
|
|
69
|
+
return `https://t.me/${path}/${topicId}`;
|
|
70
|
+
}
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
/** A way to the instance's own thread: Discord's channel mention (clickable in place), Telegram's topic link. */
|
|
74
|
+
export function instanceLink(place, topicId) {
|
|
75
|
+
if (!topicId)
|
|
76
|
+
return undefined;
|
|
77
|
+
if (place.type === "discord")
|
|
78
|
+
return SNOWFLAKE.test(topicId) ? `<#${topicId}>` : undefined;
|
|
79
|
+
return topicUrl(place, topicId);
|
|
80
|
+
}
|
|
81
|
+
// Private-use code points: no normal text carries them, and a stray one is never taken for a token.
|
|
82
|
+
const OPEN = "";
|
|
83
|
+
const MID = "";
|
|
84
|
+
const CLOSE = "";
|
|
85
|
+
const TOKEN = /([^-]*)([^-]*)/g;
|
|
86
|
+
const MARKERS = /[-]/g;
|
|
87
|
+
function encodeTarget(target) {
|
|
88
|
+
return target.kind === "general" ? "g" : `i:${encodeURIComponent(target.name)}`;
|
|
89
|
+
}
|
|
90
|
+
function decodeTarget(payload) {
|
|
91
|
+
if (payload === "g")
|
|
92
|
+
return { kind: "general" };
|
|
93
|
+
if (!payload.startsWith("i:"))
|
|
94
|
+
return undefined;
|
|
95
|
+
try {
|
|
96
|
+
const name = decodeURIComponent(payload.slice(2));
|
|
97
|
+
return name ? { kind: "instance", name } : undefined;
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
return undefined;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/** Name a place in a message: rendered as `label` linked to the target, or as `label` alone. */
|
|
104
|
+
export function placeRef(target, label) {
|
|
105
|
+
return `${OPEN}${encodeTarget(target)}${MID}${label.replace(MARKERS, "")}${CLOSE}`;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* A link appended to a message (" → General"): the message's own words stay as they are, and when the place cannot
|
|
109
|
+
* be linked the suffix disappears with its leading space.
|
|
110
|
+
*/
|
|
111
|
+
export function placeSuffix(target, label) {
|
|
112
|
+
return ` ${OPEN}s|${encodeTarget(target)}${MID}${label.replace(MARKERS, "")}${CLOSE}`;
|
|
113
|
+
}
|
|
114
|
+
export function hasPlaceRefs(text) {
|
|
115
|
+
return text.includes(OPEN);
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Render every token for one surface. `resolve` says how this reader reaches a target (undefined: plain text). The
|
|
119
|
+
* text outside tokens is copied unchanged; `textLinks` is non-empty only on Telegram.
|
|
120
|
+
*/
|
|
121
|
+
export function renderPlaceRefs(text, surface, resolve) {
|
|
122
|
+
if (!hasPlaceRefs(text))
|
|
123
|
+
return { text, textLinks: [] };
|
|
124
|
+
const textLinks = [];
|
|
125
|
+
let out = "";
|
|
126
|
+
let last = 0;
|
|
127
|
+
for (const m of text.matchAll(TOKEN)) {
|
|
128
|
+
let before = text.slice(last, m.index);
|
|
129
|
+
last = m.index + m[0].length;
|
|
130
|
+
const suffix = m[1].startsWith("s|");
|
|
131
|
+
const target = decodeTarget(suffix ? m[1].slice(2) : m[1]);
|
|
132
|
+
const label = m[2];
|
|
133
|
+
const link = target && label && surface !== "plain" ? resolve(target) : undefined;
|
|
134
|
+
if (!link) {
|
|
135
|
+
// A suffix is all link: without one it goes, with the space placeSuffix put before it.
|
|
136
|
+
if (suffix)
|
|
137
|
+
before = before.endsWith(" ") ? before.slice(0, -1) : before;
|
|
138
|
+
out += before + (suffix ? "" : label);
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
out += before;
|
|
142
|
+
const arrow = suffix ? "→ " : "";
|
|
143
|
+
if (surface === "discord") {
|
|
144
|
+
// A mention is clickable in place; a URL (a DM) in angle brackets so Discord adds no embed.
|
|
145
|
+
const ref = "mention" in link ? link.mention : `<${link.url}>`;
|
|
146
|
+
out += suffix ? `${arrow}${ref}` : `${label} (${ref})`;
|
|
147
|
+
}
|
|
148
|
+
else if ("url" in link) {
|
|
149
|
+
out += arrow;
|
|
150
|
+
textLinks.push({ offset: out.length, length: label.length, url: link.url });
|
|
151
|
+
out += label;
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
out += arrow + label;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
out += text.slice(last);
|
|
158
|
+
return { text: out, textLinks };
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* #1990: an in-app route of the web app for an instance — its chat, or its terminal View — as the web client parses it
|
|
162
|
+
* (src/ui/shared/app-route.js chatPath / viewPath). Undefined for a name no route takes.
|
|
163
|
+
*/
|
|
164
|
+
export function webRoute(panel, name) {
|
|
165
|
+
if (!name || name.length > 128 || /[/\\\u0000-\u001f\u007f]/.test(name) || name === "." || name.includes(".."))
|
|
166
|
+
return undefined;
|
|
167
|
+
return panel === "chat" ? `/ui/chat/${encodeURIComponent(name)}` : `/view/${encodeURIComponent(name)}`;
|
|
168
|
+
}
|
|
169
|
+
const WEB_ROUTE = /^\/(?:ui\/chat|view)\/[^/?#\s]+$/;
|
|
170
|
+
export const WEB_TEXT_LINKS_MAX = 16;
|
|
171
|
+
/**
|
|
172
|
+
* #1990: links as the web keeps them — on a status line or a prompt, ranges of its text that open an instance's chat
|
|
173
|
+
* or View in the app. Whatever is not exactly that (out of the text, overlapping, not such a route) is dropped; an
|
|
174
|
+
* empty result is undefined.
|
|
175
|
+
*/
|
|
176
|
+
export function webTextLinks(raw, text) {
|
|
177
|
+
if (!Array.isArray(raw))
|
|
178
|
+
return undefined;
|
|
179
|
+
const out = [];
|
|
180
|
+
let end = 0;
|
|
181
|
+
for (const l of raw.slice(0, WEB_TEXT_LINKS_MAX)) {
|
|
182
|
+
if (!l || typeof l !== "object")
|
|
183
|
+
continue;
|
|
184
|
+
const { offset, length, url } = l;
|
|
185
|
+
if (!Number.isSafeInteger(offset) || !Number.isSafeInteger(length) || typeof url !== "string")
|
|
186
|
+
continue;
|
|
187
|
+
const o = offset, n = length;
|
|
188
|
+
if (o < end || n < 1 || o + n > text.length || !WEB_ROUTE.test(url))
|
|
189
|
+
continue;
|
|
190
|
+
try {
|
|
191
|
+
decodeURIComponent(url);
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
out.push({ offset: o, length: n, url });
|
|
197
|
+
end = o + n;
|
|
198
|
+
}
|
|
199
|
+
return out.length ? out : undefined;
|
|
200
|
+
}
|
|
201
|
+
/** Every token as its label, suffixes gone: what a surface that renders no links shows. */
|
|
202
|
+
export function stripPlaceRefs(text) {
|
|
203
|
+
return renderPlaceRefs(text, "plain", () => undefined).text;
|
|
204
|
+
}
|
|
205
|
+
/** Text links that lie wholly inside [start, end), re-based to start; one that crosses the boundary is dropped. */
|
|
206
|
+
export function textLinksWithin(links, start, end) {
|
|
207
|
+
return links.filter(l => l.offset >= start && l.offset + l.length <= end).map(l => ({ ...l, offset: l.offset - start }));
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Whether a DM recipient may get links into a group: only a confirmed member (#1990 — never link to what the reader
|
|
211
|
+
* cannot open). One read per (group, user) per `ttlMs`, bounded by `deadlineMs`; a non-member, an error, a timeout or
|
|
212
|
+
* an adapter that cannot tell all mean "no" (plain text). Answers are cached, failures included, so a refusing
|
|
213
|
+
* platform is asked again only after the TTL; concurrent asks share one read.
|
|
214
|
+
*/
|
|
215
|
+
export class GroupMembership {
|
|
216
|
+
opts;
|
|
217
|
+
answers = new Map();
|
|
218
|
+
reads = new Map();
|
|
219
|
+
constructor(opts = { now: () => performance.now() }) {
|
|
220
|
+
this.opts = opts;
|
|
221
|
+
}
|
|
222
|
+
async isMember(source, groupId, userId) {
|
|
223
|
+
if (!source.isGroupMember || !groupId || !userId)
|
|
224
|
+
return false;
|
|
225
|
+
const key = `${groupId}\u0000${userId}`;
|
|
226
|
+
const ttl = this.opts.ttlMs ?? 10 * 60_000;
|
|
227
|
+
const cached = this.answers.get(key);
|
|
228
|
+
if (cached && this.opts.now() - cached.at < ttl)
|
|
229
|
+
return cached.member;
|
|
230
|
+
const pending = this.reads.get(key);
|
|
231
|
+
if (pending)
|
|
232
|
+
return pending;
|
|
233
|
+
const read = this.read(source, groupId, userId).then(member => {
|
|
234
|
+
this.answers.delete(key); // re-insert: the map's order is its age, for the bound below
|
|
235
|
+
this.answers.set(key, { member, at: this.opts.now() });
|
|
236
|
+
const max = this.opts.maxEntries ?? 1000;
|
|
237
|
+
while (this.answers.size > max)
|
|
238
|
+
this.answers.delete(this.answers.keys().next().value);
|
|
239
|
+
return member;
|
|
240
|
+
}).finally(() => this.reads.delete(key));
|
|
241
|
+
this.reads.set(key, read);
|
|
242
|
+
return read;
|
|
243
|
+
}
|
|
244
|
+
read(source, groupId, userId) {
|
|
245
|
+
const deadlineMs = this.opts.deadlineMs ?? 5_000;
|
|
246
|
+
return new Promise(resolve => {
|
|
247
|
+
const timer = setTimeout(() => resolve(false), deadlineMs);
|
|
248
|
+
timer.unref?.();
|
|
249
|
+
let asked;
|
|
250
|
+
try {
|
|
251
|
+
asked = source.isGroupMember(groupId, userId);
|
|
252
|
+
}
|
|
253
|
+
catch {
|
|
254
|
+
asked = Promise.resolve(false);
|
|
255
|
+
}
|
|
256
|
+
asked.then(member => { clearTimeout(timer); resolve(member === true); }, () => { clearTimeout(timer); resolve(false); });
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
/** #1990: refusals that send the person to General — their reply carries a link to it. */
|
|
261
|
+
export const GENERAL_POINTING_REFUSALS = new Set(["cmd.use_in_general", "profile.general_only"]);
|
|
262
|
+
/**
|
|
263
|
+
* Text from outside (an agent's message, a pane line, a dialog's description) as a place-token-free argument: a
|
|
264
|
+
* message that interpolates it must not let it name a place of its own.
|
|
265
|
+
*/
|
|
266
|
+
export function noPlaceMarkers(text) {
|
|
267
|
+
return text.replace(MARKERS, "");
|
|
268
|
+
}
|
|
269
|
+
//# sourceMappingURL=topic-link.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"topic-link.js","sourceRoot":"","sources":["../src/topic-link.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
CHANGED
|
@@ -404,6 +404,19 @@ export interface WebConfig {
|
|
|
404
404
|
* instead of it; web.preview: false turns previews off everywhere.
|
|
405
405
|
*/
|
|
406
406
|
preview_on_public_link?: boolean;
|
|
407
|
+
/**
|
|
408
|
+
* #1629: agents' own web themes and effects. `agent_edit` (default true): false refuses every agent appearance write,
|
|
409
|
+
* fleet-wide; what is stored stays, and people can still reset and lock.
|
|
410
|
+
*/
|
|
411
|
+
themes?: {
|
|
412
|
+
agent_edit?: boolean;
|
|
413
|
+
};
|
|
414
|
+
/**
|
|
415
|
+
* #1629 r4.1 Q3 (b): the identity card's upload limits and each agent's picture quota. Defaults: portrait
|
|
416
|
+
* 1024×1536 / 1536 KiB, banner 1600×600 / 768 KiB, quota 8 files / 4 MiB. A value outside its hard bound is clamped
|
|
417
|
+
* (and warned about); a change applies to new uploads only.
|
|
418
|
+
*/
|
|
419
|
+
appearance?: import("./web-appearance-limits.js").AppearanceLimitsConfig;
|
|
407
420
|
}
|
|
408
421
|
/** `login:` section — remote /login behaviour. */
|
|
409
422
|
export interface LoginConfig {
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/** #1629: one session-only app effect owner. Importing this module starts nothing. */
|
|
2
|
+
import { appStore } from "/assets/app-store.js";
|
|
3
|
+
import { navStore } from "/assets/app-nav.js";
|
|
4
|
+
import { onPrefsChanged, prefInForce } from "/assets/app-prefs.js";
|
|
5
|
+
import { resolvedMode, themesStore } from "/assets/app-themes.js";
|
|
6
|
+
import { ownerCssLifecycle, safeMode } from "/assets/owner-css.js";
|
|
7
|
+
import { constrainedDevice } from "/assets/effect-policy.js";
|
|
8
|
+
import { createAppEffectRuntime } from "/assets/effect-app-runtime.js";
|
|
9
|
+
import { createEffectCursor } from "/assets/effect-cursor.js";
|
|
10
|
+
import { createOwnerEffectRegions } from "/assets/effect-owner-regions.js";
|
|
11
|
+
import { chatEffectIdentity } from "/assets/effect-chat-runtime.js";
|
|
12
|
+
import { createEffectTrialController } from "./effect-trial-controller.js";
|
|
13
|
+
import { effectTrialMessage } from "./effect-trial-offer.js";
|
|
14
|
+
import { claimPanel, panelVersion, panelStore, openTrialPanel, closePanel } from "./preview-panel.js";
|
|
15
|
+
const owners = new WeakMap();
|
|
16
|
+
export function getAppEffects(host = window) { const value = owners.get(host); return value && !value.dead && value.api?.ok ? value : null; }
|
|
17
|
+
|
|
18
|
+
/** Boot once with the page's Chat store, including when the first visible panel is Fleet. */
|
|
19
|
+
export function startAppEffects({ store, window: host = window, factories = {} } = {}) {
|
|
20
|
+
const existing = owners.get(host);
|
|
21
|
+
if (existing) { if (!existing.dead) existing.bindStore(store); return getAppEffects(host); }
|
|
22
|
+
if (!chatEffectIdentity(host) || typeof host.AgendAuth?.onSessionEnd !== "function" || typeof host.AgendAuth?.sessionEnded !== "function") return null;
|
|
23
|
+
const doc = host.document, releases = [], ended = new Set(), updates = new Set(), trialUpdates = new Set(); let replyOff = null, replyStore = null, regions = null, cursor = null, cursorStamp = null, trialControl = null, trialIntent = null, trialHint = false, trialSerial = 0, trialStamp = null, chatBinding = null;
|
|
24
|
+
const safe = fn => { try { return fn(); } catch { return null; } };
|
|
25
|
+
const hold = off => { if (entry.dead) safe(() => off?.()); else releases.push(off); };
|
|
26
|
+
const entry = { dead: false, api: null,
|
|
27
|
+
cursorSnapshot: () => cursor?.snapshot() ?? { status: "unavailable", id: null },
|
|
28
|
+
gates: () => context(),
|
|
29
|
+
subscribe(fn) { if (typeof fn !== "function" || entry.dead) return () => {}; updates.add(fn); return () => updates.delete(fn); },
|
|
30
|
+
subscribeTrial(fn) { if (typeof fn !== "function" || entry.dead) return () => {}; trialUpdates.add(fn); return () => trialUpdates.delete(fn); },
|
|
31
|
+
onEnd(fn) { if (typeof fn !== "function") return () => {}; if (entry.dead) { safe(fn); return () => {}; } ended.add(fn); return () => ended.delete(fn); },
|
|
32
|
+
bindStore(value) {
|
|
33
|
+
if (entry.dead || value === replyStore) return;
|
|
34
|
+
safe(() => replyOff?.()); replyOff = null; replyStore = value;
|
|
35
|
+
trialControl?.stop(); chatBinding = null; entry.api?.detachChat();
|
|
36
|
+
if (typeof value?.onAgentReply === "function") {
|
|
37
|
+
const off = value.onAgentReply(event => { if (!entry.dead && replyStore === value) entry.api?.reply(event); });
|
|
38
|
+
if (entry.dead || replyStore !== value) safe(() => off?.()); else replyOff = off;
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
canTry(message) {
|
|
42
|
+
const offer = safe(() => effectTrialMessage(message)), nav = navStore.get();
|
|
43
|
+
return !entry.dead && !!chatBinding && safe(() => chatBinding.current()) === true && trialHint && trialControl?.ok === true && !!offer && message.instance === nav.route?.instance && nav.route?.panel === "chat"
|
|
44
|
+
&& trialControl.snapshot().phase === "idle" && !trialControl.snapshot().busy && trialAllowed();
|
|
45
|
+
},
|
|
46
|
+
startTrial(message) {
|
|
47
|
+
if (!entry.canTry(message)) return Promise.resolve(false);
|
|
48
|
+
const nav = navStore.get(); trialIntent = { name: message.instance, seq: nav.seq, claim: claimPanel(), handle: null, chat: chatBinding };
|
|
49
|
+
return trialControl.start(message);
|
|
50
|
+
},
|
|
51
|
+
attachChat(options) {
|
|
52
|
+
const nav = navStore.get(), name = options?.name;
|
|
53
|
+
if (entry.dead || !entry.api?.ok || nav.route?.panel !== "chat" || nav.route.instance !== name || typeof options.current !== "function") return null;
|
|
54
|
+
const binding = { current: () => !entry.dead && host.document === doc && navStore.get().seq === nav.seq && navStore.get().route?.panel === "chat" && navStore.get().route?.instance === name && options.current() === true };
|
|
55
|
+
chatBinding = binding; trialControl?.stop();
|
|
56
|
+
if (entry.dead || chatBinding !== binding) return null;
|
|
57
|
+
const handle = entry.api.attachChat({ ...options, current: () => chatBinding === binding && binding.current() });
|
|
58
|
+
if (entry.dead || chatBinding !== binding) { safe(() => handle?.dispose()); return null; }
|
|
59
|
+
if (!handle) chatBinding = null;
|
|
60
|
+
return handle;
|
|
61
|
+
},
|
|
62
|
+
dispose() {
|
|
63
|
+
if (entry.dead) return; entry.dead = true;
|
|
64
|
+
safe(() => trialControl?.dispose()); safe(() => entry.api?.dispose()); safe(() => replyOff?.()); replyOff = null; replyStore = null;
|
|
65
|
+
for (const off of releases.splice(0)) safe(off);
|
|
66
|
+
updates.clear(); trialUpdates.clear(); const listeners = [...ended]; ended.clear(); for (const fn of listeners) safe(fn);
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
owners.set(host, entry); // Reentrant setup never constructs another owner.
|
|
70
|
+
let media, coarse, preview;
|
|
71
|
+
function context() {
|
|
72
|
+
let ready = false; try { ready = preview?.availability().ok === true; } catch { /* unavailable */ }
|
|
73
|
+
return { safe: safeMode(), publicLink: doc.body.dataset.publicLink === "1", reducedMotion: media?.matches !== false, saveData: host.navigator?.connection?.saveData === true,
|
|
74
|
+
hidden: doc.visibilityState !== "visible", enabled: typeof host.OffscreenCanvas === "function" && typeof host.Worker === "function" && typeof host.MutationObserver === "function" && typeof host.ResizeObserver === "function",
|
|
75
|
+
show: prefInForce("fx.show")?.value !== false, previewReady: ready,
|
|
76
|
+
optIn: prefInForce("preview.optIn")?.value === true && preview?.optedIn() === true && preview?.never() !== true,
|
|
77
|
+
constrained: constrainedDevice({ memory: host.navigator?.deviceMemory, cores: host.navigator?.hardwareConcurrency, coarse: coarse?.matches === true, width: host.innerWidth }),
|
|
78
|
+
modal: !!doc.querySelector("dialog[open],.menu-list,.pop,[role=alertdialog],.oc-keep,.shell.sb-open"), openInstance: null };
|
|
79
|
+
}
|
|
80
|
+
function trialCurrent(name) {
|
|
81
|
+
const intent = trialIntent, nav = navStore.get();
|
|
82
|
+
return !entry.dead && host.document === doc && !!intent && !!intent.chat && chatBinding === intent.chat && intent.chat.current() === true && intent.name === name && nav.seq === intent.seq && nav.route?.panel === "chat" && nav.route.instance === name
|
|
83
|
+
&& (panelVersion() === intent.claim || intent.handle !== null && panelStore.get().open?.handle === intent.handle);
|
|
84
|
+
}
|
|
85
|
+
function trialAllowed() {
|
|
86
|
+
const c = context();
|
|
87
|
+
return !entry.dead && doc.visibilityState === "visible" && doc.hasFocus?.() === true && ownerCssLifecycle.get().state === "off"
|
|
88
|
+
&& c.enabled && c.show && c.previewReady && c.optIn && !c.safe && !c.publicLink && !c.reducedMotion && !c.saveData && !c.hidden && !c.modal;
|
|
89
|
+
}
|
|
90
|
+
function mountTrial(handle) {
|
|
91
|
+
const intent = trialIntent; if (!intent || !trialCurrent(handle.instance) || !handle.current() || !Number.isSafeInteger(++trialSerial)) throw Error("trial_unavailable");
|
|
92
|
+
intent.handle = handle;
|
|
93
|
+
const binding = Object.freeze({ key: `trial:${trialSerial}:${handle.reply.trial}`, id: handle.reply.offer.id, run: handle.reply.trial, instance: handle.instance, generation: trialSerial });
|
|
94
|
+
let stage = null, source = null;
|
|
95
|
+
const cleanup = () => {
|
|
96
|
+
safe(() => source?.stop()); safe(() => stage?.dispose());
|
|
97
|
+
if (panelStore.get().open?.handle === handle) closePanel();
|
|
98
|
+
};
|
|
99
|
+
try {
|
|
100
|
+
openTrialPanel({ key: binding.key, instance: handle.instance, binding, handle, ownerCss: () => ownerCssLifecycle.get().state,
|
|
101
|
+
onStage(value) {
|
|
102
|
+
stage = value;
|
|
103
|
+
if (!handle.current()) { cleanup(); handle.stop(); return; }
|
|
104
|
+
source = entry.api.attachTrial({ reply: handle.reply, offer: handle.reply.offer.offer, startedAt: handle.startedAt, stage,
|
|
105
|
+
current: handle.current, onStop: handle.stop });
|
|
106
|
+
if (!source) { cleanup(); handle.stop(); }
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
} catch (error) { cleanup(); throw error; }
|
|
110
|
+
return cleanup;
|
|
111
|
+
}
|
|
112
|
+
function notifyTrial() {
|
|
113
|
+
if (entry.dead) return;
|
|
114
|
+
const nav = navStore.get(), next = JSON.stringify([trialHint, safe(() => chatBinding?.current()) === true, trialAllowed(), nav.route?.panel, nav.route?.instance, trialControl?.snapshot()]);
|
|
115
|
+
if (next === trialStamp) return; trialStamp = next;
|
|
116
|
+
for (const fn of [...trialUpdates]) safe(fn);
|
|
117
|
+
}
|
|
118
|
+
const notify = () => { if (!entry.dead) { trialControl?.sync(); notifyTrial(); for (const fn of [...updates]) safe(fn); } };
|
|
119
|
+
const cursorDragging = () => doc.body.classList?.contains("side-resizing") === true || !!doc.querySelector(".chat-split.resizing");
|
|
120
|
+
function syncCursor() {
|
|
121
|
+
if (entry.dead || !cursor) return;
|
|
122
|
+
const c = context();
|
|
123
|
+
cursor.update(entry.api?.cursorSelection?.() ?? null, c.enabled && c.show && c.previewReady && c.optIn && !c.safe && !c.publicLink && !c.reducedMotion && !c.saveData && !c.hidden && !c.modal && !cursorDragging());
|
|
124
|
+
}
|
|
125
|
+
try {
|
|
126
|
+
if (appStore.get().connection === "ended") { entry.dispose(); return null; }
|
|
127
|
+
preview = host.AgendPreview;
|
|
128
|
+
media = host.matchMedia?.("(prefers-reduced-motion: reduce)") ?? { matches: true };
|
|
129
|
+
coarse = host.matchMedia?.("(pointer: coarse)") ?? { matches: false };
|
|
130
|
+
entry.api = (factories.runtime ?? createAppEffectRuntime)({ window: host, current: () => !entry.dead && host.document === doc,
|
|
131
|
+
observe: () => ({ context: context(), ownerCss: ownerCssLifecycle.get().state, mode: resolvedMode() }), onDispose: () => entry.dispose(),
|
|
132
|
+
resolveOwnerTarget: (slot, manifest, proof) => !entry.dead ? regions?.resolve?.(slot, manifest.box, proof) ?? null : null,
|
|
133
|
+
onChange: () => { syncCursor(); notify(); } });
|
|
134
|
+
if (entry.dead || !entry.api.ok) { entry.dispose(); return null; }
|
|
135
|
+
trialControl = (factories.trialController ?? createEffectTrialController)({ window: host, current: trialCurrent, allowed: trialAllowed, onStart: mountTrial, onChange: notify });
|
|
136
|
+
hold(() => trialControl?.dispose());
|
|
137
|
+
if (typeof host.AgendAuth.trialSession === "function") Promise.resolve().then(() => host.AgendAuth.trialSession()).then(value => {
|
|
138
|
+
if (entry.dead) return; trialHint = value?.localAdmin === true && value.epoch === chatEffectIdentity(host)?.epoch; notify();
|
|
139
|
+
}).catch(() => {});
|
|
140
|
+
hold(panelStore.subscribe(() => { trialControl?.sync(); notifyTrial(); }));
|
|
141
|
+
const ownRegions = (factories.regions ?? createOwnerEffectRegions)({ window: host, current: () => !entry.dead && host.document === doc });
|
|
142
|
+
regions = ownRegions?.ok ? ownRegions : null; hold(() => ownRegions?.dispose?.());
|
|
143
|
+
if (entry.dead) return null;
|
|
144
|
+
const ownCursor = safe(() => (factories.cursor ?? createEffectCursor)({ window: host, current: () => !entry.dead && host.document === doc,
|
|
145
|
+
readImage: (selection, signal) => entry.api?.cursorImageSettled?.(selection, signal) ?? Promise.resolve(null),
|
|
146
|
+
onChange(value) { const next = JSON.stringify(value); if (next !== cursorStamp) { cursorStamp = next; notify(); } },
|
|
147
|
+
}));
|
|
148
|
+
cursor = ownCursor?.ok ? ownCursor : null; hold(() => ownCursor?.dispose?.());
|
|
149
|
+
if (entry.dead) return null;
|
|
150
|
+
const sync = () => { if (!entry.dead) { trialControl?.sync(); entry.api.sync(); syncCursor(); notifyTrial(); } };
|
|
151
|
+
let revision = appStore.get().themesRev, prefsRevision = appStore.get().prefsRev, navSeq = navStore.get().seq;
|
|
152
|
+
hold(navStore.subscribe(value => { if (entry.dead || value.seq === navSeq) return; navSeq = value.seq; trialControl?.stop(); chatBinding = null; entry.api.detachChat(); syncCursor(); }));
|
|
153
|
+
hold(appStore.subscribe(value => {
|
|
154
|
+
if (entry.dead) return;
|
|
155
|
+
if (value.connection === "ended") { entry.dispose(); return; }
|
|
156
|
+
const chatChanged = value.themesRev !== revision, ownerChanged = value.prefsRev !== prefsRevision;
|
|
157
|
+
revision = value.themesRev; prefsRevision = value.prefsRev;
|
|
158
|
+
if (chatChanged) entry.api.changedChat();
|
|
159
|
+
if (ownerChanged) entry.api.changedOwner();
|
|
160
|
+
if (!chatChanged && !ownerChanged) sync();
|
|
161
|
+
}));
|
|
162
|
+
hold(ownerCssLifecycle.subscribe(sync)); hold(themesStore.subscribe(sync)); hold(onPrefsChanged(sync));
|
|
163
|
+
if (typeof preview?.onChange === "function") hold(preview.onChange(sync));
|
|
164
|
+
if (typeof host.AgendTheme?.onChange === "function") hold(host.AgendTheme.onChange(sync));
|
|
165
|
+
for (const [target, type] of [[media, "change"], [coarse, "change"], [doc, "visibilitychange"], [host, "resize"], [host.navigator?.connection, "change"]]) {
|
|
166
|
+
if (target?.addEventListener) { target.addEventListener(type, sync); hold(() => target.removeEventListener(type, sync)); }
|
|
167
|
+
}
|
|
168
|
+
if (entry.dead) return null;
|
|
169
|
+
let modal = context().modal, dragging = cursorDragging(), queuedSurface = false;
|
|
170
|
+
// Panel commits can happen after navigation and contain no Chat lease. Reproject fixed owner targets
|
|
171
|
+
// once after stock DOM changes. Body-level effect roots/frames/rules do not schedule themselves.
|
|
172
|
+
const surfaceChanged = () => {
|
|
173
|
+
if (entry.dead || queuedSurface) return; queuedSurface = true;
|
|
174
|
+
Promise.resolve().then(() => { queuedSurface = false; sync(); });
|
|
175
|
+
};
|
|
176
|
+
const barriers = typeof host.MutationObserver === "function" ? new host.MutationObserver(records => {
|
|
177
|
+
if (entry.dead) return;
|
|
178
|
+
const value = context().modal, drag = cursorDragging(); if (value !== modal || drag !== dragging) { modal = value; dragging = drag; sync(); }
|
|
179
|
+
if (Array.isArray(records) && records.some(r => r.target?.closest?.("#app") || r.target === doc.getElementById?.("app"))) surfaceChanged();
|
|
180
|
+
}) : null;
|
|
181
|
+
hold(() => barriers?.disconnect());
|
|
182
|
+
if (entry.dead) return null;
|
|
183
|
+
barriers?.observe(doc.documentElement, { subtree: true, childList: true, attributes: true, attributeFilter: ["class", "open", "hidden", "role"] });
|
|
184
|
+
entry.bindStore(store); sync();
|
|
185
|
+
return getAppEffects(host);
|
|
186
|
+
} catch { entry.dispose(); return null; }
|
|
187
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/** #1629: static Chat fallback. It requires no preview origin, device consent, Worker or personal identity. */
|
|
2
|
+
import { useLayoutEffect } from "/assets/app-html.js";
|
|
3
|
+
import { appStore } from "/assets/app-store.js";
|
|
4
|
+
import { onPrefsChanged, prefInForce } from "/assets/app-prefs.js";
|
|
5
|
+
import { themesShown, themesStore } from "/assets/app-themes.js";
|
|
6
|
+
import { ownerCssLifecycle } from "/assets/owner-css.js";
|
|
7
|
+
import { chatEffectIdentity } from "/assets/effect-chat-runtime.js";
|
|
8
|
+
import { createChatPosters } from "/assets/effect-posters.js";
|
|
9
|
+
|
|
10
|
+
export function ChatEffectPosters({ name, lease, element }) {
|
|
11
|
+
useLayoutEffect(() => {
|
|
12
|
+
let live = true, api = null;
|
|
13
|
+
const releases = [], host = window;
|
|
14
|
+
const current = () => live && lease.current();
|
|
15
|
+
const cleanup = () => { if (!live) return; live = false; try { api?.dispose?.(); } catch { /* optional */ } for (const off of releases) { try { off?.(); } catch { /* release all */ } } };
|
|
16
|
+
lease.hold(cleanup);
|
|
17
|
+
try {
|
|
18
|
+
const preview = host.AgendPreview, motion = host.matchMedia?.("(prefers-reduced-motion: reduce)") ?? { matches: true };
|
|
19
|
+
const observe = () => {
|
|
20
|
+
let previewReady = false; try { previewReady = preview?.availability().ok === true; } catch { /* fallback */ }
|
|
21
|
+
// This is a surface fallback, not a replacement for an explicit Pause, queued effect or failed live read.
|
|
22
|
+
const playback = chatEffectIdentity(host) && typeof host.Worker === "function" && typeof host.OffscreenCanvas === "function" && previewReady &&
|
|
23
|
+
prefInForce("preview.optIn")?.value === true && preview?.optedIn() === true && preview?.never() !== true && !motion.matches && host.navigator?.connection?.saveData !== true;
|
|
24
|
+
return { shown: themesShown(), fallback: !playback, ownerCss: ownerCssLifecycle.get().state };
|
|
25
|
+
};
|
|
26
|
+
api = createChatPosters({ window: host, name, element: element.current, current, observe });
|
|
27
|
+
if (!api.ok) return cleanup;
|
|
28
|
+
const sync = () => { if (current()) api.sync(); };
|
|
29
|
+
let revision = appStore.get().themesRev;
|
|
30
|
+
releases.push(appStore.subscribe(s => {
|
|
31
|
+
if (!current()) return;
|
|
32
|
+
if (s.connection === "ended") { api.dispose(); return; }
|
|
33
|
+
if (s.themesRev !== revision) { revision = s.themesRev; api.changed(); } else sync();
|
|
34
|
+
}));
|
|
35
|
+
releases.push(ownerCssLifecycle.subscribe(sync)); releases.push(themesStore.subscribe(sync)); releases.push(onPrefsChanged(sync));
|
|
36
|
+
if (typeof preview?.onChange === "function") releases.push(preview.onChange(sync));
|
|
37
|
+
for (const target of [motion, host.navigator?.connection]) if (target?.addEventListener) { target.addEventListener("change", sync); releases.push(() => target.removeEventListener("change", sync)); }
|
|
38
|
+
sync(); return cleanup;
|
|
39
|
+
} catch { cleanup(); return cleanup; }
|
|
40
|
+
}, [name, lease]);
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// #1629: Chat contributes a committed lease to the one app owner; no private pool or server polling.
|
|
2
|
+
import { html, useLayoutEffect, useRef, useState } from "/assets/app-html.js";
|
|
3
|
+
import { register, t } from "/assets/app-i18n.js";
|
|
4
|
+
import { appStore, useStore } from "/assets/app-store.js";
|
|
5
|
+
import { ownerCssLifecycle } from "/assets/owner-css.js";
|
|
6
|
+
import { chatEffectState, chatEffectIdentity } from "/assets/effect-chat-runtime.js";
|
|
7
|
+
import { Dialog } from "/assets/ui-dialog.js";
|
|
8
|
+
import { navigate } from "/assets/app-nav.js";
|
|
9
|
+
import { toast } from "/assets/ui-toast.js";
|
|
10
|
+
import { getAppEffects } from "./app-effects.js";
|
|
11
|
+
|
|
12
|
+
register("chatFx", {
|
|
13
|
+
en: { title: "Effects", loading: "Checking effects…", empty: "No effects in this chat.", failed: "Effects stopped because their resources could not be verified. Reload the page to try again.", reload: "Reload page", pause: "Pause", resume: "Resume", remove: "Remove from chat", removeFailed: "Could not remove this effect. Reload the page to check its state.", css: "Agent foreground effects are paused while your custom CSS is on or cannot be verified.", setting: "Open Web appearance settings", running: "Running", starting: "Starting", off: "Stopped", resumeFailed: "This effect cannot resume: the page or its safety history is not ready. Reload the page or choose another effect.", queued: "Waiting for a slot", unknown: "Playback is off or unavailable here.", budget: "Paused to protect page responsiveness. Resume keeps the three-pauses safety limit.", paused: "Paused", low: "Reduced quality" },
|
|
14
|
+
"zh-TW": { title: "效果", loading: "正在確認效果…", empty: "這個聊天沒有放置效果。", failed: "無法確認效果資源,已停止播放。重新載入頁面後再試。", reload: "重新載入頁面", pause: "暫停", resume: "恢復", remove: "從聊天移除", removeFailed: "無法移除效果。請重新載入頁面確認狀態。", css: "自訂 CSS 開啟或無法確認時,代理的前景效果會暫停。", setting: "開啟網頁外觀設定", running: "播放中", starting: "啟動中", off: "已停止", resumeFailed: "頁面或安全紀錄尚未就緒,無法恢復此效果。請重新載入頁面,或選擇其他效果。", queued: "等待空位", unknown: "這裡已關閉播放,或無法播放效果。", budget: "為維持頁面順暢,已暫停效果。恢復仍保留三次暫停的安全上限。", paused: "已暫停", low: "降低畫質" },
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
/** Local read/admin sessions keep controls; public, View, poll and header-only pages have no runtime identity. */
|
|
18
|
+
export function chatEffectsMenuAvailable() { return chatEffectIdentity(window) !== null; }
|
|
19
|
+
|
|
20
|
+
export function ChatEffects({ name, lease, element, store, open, onClose }) {
|
|
21
|
+
const cssLife = useStore(ownerCssLifecycle);
|
|
22
|
+
const runtime = useRef(null), [state, setState] = useState(null), [canWrite, setCanWrite] = useState(false), [busy, setBusy] = useState(false);
|
|
23
|
+
useLayoutEffect(() => {
|
|
24
|
+
let live = true, api = null;
|
|
25
|
+
const releases = [];
|
|
26
|
+
// Effects are optional: a failed setup must release its partial ownership without breaking Chat.
|
|
27
|
+
const cleanup = () => {
|
|
28
|
+
if (!live) return;
|
|
29
|
+
live = false;
|
|
30
|
+
try { api?.dispose?.(); } catch { /* continue releasing partial setup */ }
|
|
31
|
+
for (const off of releases) { try { if (typeof off === "function") off(); } catch { /* release the rest */ } }
|
|
32
|
+
if (runtime.current === api) runtime.current = null;
|
|
33
|
+
};
|
|
34
|
+
lease.hold(cleanup);
|
|
35
|
+
try {
|
|
36
|
+
setBusy(false); onClose(); setCanWrite(false); setState(null);
|
|
37
|
+
const host = window, owner = getAppEffects(host);
|
|
38
|
+
const current = () => live && lease.current();
|
|
39
|
+
if (!owner) { setState({ error: null, rows: [], effects: [], unavailable: true }); return cleanup; }
|
|
40
|
+
releases.push(owner.onEnd(() => { if (current()) setState({ error: "resource_unavailable", rows: [], effects: [], unavailable: true }); }));
|
|
41
|
+
const changed = value => {
|
|
42
|
+
if (!current()) return;
|
|
43
|
+
const g = owner.gates();
|
|
44
|
+
setState({ ...value, unavailable: !g.enabled || !g.show || !g.previewReady || !g.optIn || g.safe || g.publicLink || g.reducedMotion || g.saveData });
|
|
45
|
+
};
|
|
46
|
+
api = owner.attachChat({ name, element: element.current, current,
|
|
47
|
+
observe: () => ({ state: chatEffectState(appStore.get(), name) }), onChange: changed });
|
|
48
|
+
runtime.current = api;
|
|
49
|
+
if (!api?.ok) { setState({ error: null, rows: [], effects: [], unavailable: true }); return cleanup; }
|
|
50
|
+
host.AgendAuth?.canWrite?.().then(value => { if (current()) setCanWrite(value === true); }).catch(() => {});
|
|
51
|
+
api.sync();
|
|
52
|
+
return cleanup;
|
|
53
|
+
} catch {
|
|
54
|
+
cleanup();
|
|
55
|
+
if (lease.current()) setState({ error: "runtime_unavailable", rows: [], effects: [] });
|
|
56
|
+
return cleanup;
|
|
57
|
+
}
|
|
58
|
+
}, [name, lease]);
|
|
59
|
+
const rows = state?.rows ?? [], effects = state?.effects ?? [];
|
|
60
|
+
const css = cssLife.state !== "off" && effects.some(p => p.by !== "human" && p.manifest.layer === "front");
|
|
61
|
+
const remove = async id => { if (busy) return; setBusy(true); const api = runtime.current; const ok = await api?.remove?.(id); if (!lease.current() || runtime.current !== api) return; setBusy(false); if (!ok) toast(t("chatFx.removeFailed"), false); };
|
|
62
|
+
return html`${open ? html`<${Dialog} title=${t("chatFx.title")} onClose=${onClose} busy=${busy}>
|
|
63
|
+
${state?.error ? html`<p>${t("chatFx.failed")}</p><button type="button" class="btn" onClick=${() => location.reload()}>${t("chatFx.reload")}</button>` : null}
|
|
64
|
+
${css ? html`<p>${t("chatFx.css")}</p><button type="button" class="btn" onClick=${() => { onClose(); navigate("/settings/appearance"); }}>${t("chatFx.setting")}</button>` : null}
|
|
65
|
+
${!effects.length && !state?.error ? html`<p>${t(state?.unavailable ? "chatFx.unknown" : state?.loading || !state ? "chatFx.loading" : "chatFx.empty")}</p>` : null}
|
|
66
|
+
${effects.map(p => { const row = rows.find(r => r.id === p.id), phase = row?.phase; return html`<section key=${p.id}><h3>${p.manifest.title}</h3><p>${t(row?.reason === "paused" || row?.reason === "host_budget" ? "chatFx.paused" : phase === "running" ? "chatFx.running" : phase ? "chatFx.starting" : row?.verdict === "queued" ? "chatFx.queued" : "chatFx.off")}${row?.quality === "low" ? ` · ${t("chatFx.low")}` : ""}</p>
|
|
67
|
+
${row?.reason === "host_budget" ? html`<p>${t("chatFx.budget")}</p>` : null}
|
|
68
|
+
<button type="button" class="btn" disabled=${busy} onClick=${() => runtime.current?.pause?.(p.id)}>${t("chatFx.pause")}</button>
|
|
69
|
+
<button type="button" class="btn" disabled=${busy} onClick=${() => { if (!runtime.current?.resume?.(p.id)) toast(t("chatFx.resumeFailed"), false); }}>${t("chatFx.resume")}</button>
|
|
70
|
+
${canWrite ? html`<button type="button" class="btn" disabled=${busy} onClick=${() => remove(p.id)}>${t("chatFx.remove")}</button>` : null}</section>`; })}
|
|
71
|
+
</${Dialog}>` : null}`;
|
|
72
|
+
}
|