@songsid/agend 2.3.3-beta.3 → 2.3.3-beta.5
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 +13 -112
- package/dist/agent-cli.js.map +1 -1
- package/dist/agent-endpoint.d.ts +0 -1
- package/dist/agent-endpoint.js +1 -9
- package/dist/agent-endpoint.js.map +1 -1
- package/dist/agent-whats-new.d.ts +3 -6
- package/dist/agent-whats-new.js +7 -18
- package/dist/agent-whats-new.js.map +1 -1
- package/dist/auth-api.d.ts +0 -7
- package/dist/auth-api.js +1 -15
- package/dist/auth-api.js.map +1 -1
- package/dist/backend/codex.d.ts +1 -0
- package/dist/backend/codex.js +106 -15
- package/dist/backend/codex.js.map +1 -1
- package/dist/backend/types.d.ts +17 -0
- package/dist/backend/types.js.map +1 -1
- package/dist/bundled-skills.d.ts +4 -6
- package/dist/bundled-skills.js +10 -14
- package/dist/bundled-skills.js.map +1 -1
- package/dist/channel/mcp-server.js +5 -5
- package/dist/channel/mcp-server.js.map +1 -1
- package/dist/channel/mcp-tools.d.ts +1 -3
- package/dist/channel/mcp-tools.js +4 -58
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/cli.js +3 -1
- package/dist/cli.js.map +1 -1
- package/dist/config-validator.js +6 -3
- package/dist/config-validator.js.map +1 -1
- package/dist/daemon.d.ts +11 -3
- package/dist/daemon.js +59 -107
- package/dist/daemon.js.map +1 -1
- package/dist/dashboard-live.d.ts +1 -1
- package/dist/dashboard-live.js +3 -3
- package/dist/dashboard-live.js.map +1 -1
- package/dist/dashboard-permissions.d.ts +1 -1
- package/dist/dashboard-permissions.js +1 -3
- package/dist/dashboard-permissions.js.map +1 -1
- package/dist/fleet-manager.d.ts +26 -44
- package/dist/fleet-manager.js +70 -226
- package/dist/fleet-manager.js.map +1 -1
- package/dist/general-knowledge/skills/credential-profiles/SKILL.md +11 -1
- package/dist/general-knowledge/skills/session-management/SKILL.md +6 -0
- package/dist/general-knowledge/skills/web-appearance/SKILL.md +13 -204
- package/dist/general-knowledge/skills/web-dashboard/SKILL.md +26 -63
- package/dist/instance-lifecycle.js +1 -1
- package/dist/instance-lifecycle.js.map +1 -1
- package/dist/locale.js +2 -0
- package/dist/locale.js.map +1 -1
- package/dist/outbound-handlers.js +19 -7
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.d.ts +0 -62
- package/dist/outbound-schemas.js +2 -41
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/public-web-gateway.js +0 -9
- package/dist/public-web-gateway.js.map +1 -1
- package/dist/public-web-link.d.ts +2 -0
- package/dist/public-web-link.js +5 -0
- package/dist/public-web-link.js.map +1 -1
- package/dist/search/search-docs.d.ts +1 -1
- package/dist/search/search-docs.js +2 -2
- package/dist/search/search-docs.js.map +1 -1
- package/dist/settings-api.d.ts +6 -0
- package/dist/settings-api.js +14 -2
- package/dist/settings-api.js.map +1 -1
- package/dist/settings-change.js +3 -1
- package/dist/settings-change.js.map +1 -1
- package/dist/task-board-waker.d.ts +4 -1
- package/dist/task-board-waker.js +13 -2
- package/dist/task-board-waker.js.map +1 -1
- package/dist/tool-permissions.js +3 -10
- package/dist/tool-permissions.js.map +1 -1
- package/dist/topic-commands.d.ts +6 -2
- package/dist/topic-commands.js +2 -0
- package/dist/topic-commands.js.map +1 -1
- package/dist/types.d.ts +2 -1
- package/dist/ui/chat-ambient.js +4 -4
- package/dist/ui/chat-commands.js +3 -2
- package/dist/ui/chat-store.js +4 -1
- package/dist/ui/chat-strings.js +6 -42
- package/dist/ui/chat-thread.js +6 -44
- package/dist/ui/panel-chat.js +21 -50
- package/dist/ui/preview-panel.js +3 -15
- package/dist/ui/search-dialog.js +30 -14
- package/dist/ui/settings-appearance.js +1 -3
- package/dist/ui/settings-guests.js +13 -4
- package/dist/ui/settings-learn.js +1 -1
- package/dist/ui/settings-mods.js +5 -12
- package/dist/ui/settings-registry.js +0 -1
- package/dist/ui/settings-strings.js +6 -6
- package/dist/ui/shared/app-prefs.js +17 -17
- package/dist/ui/shared/app-shell.js +12 -8
- package/dist/ui/shared/app-store.js +6 -2
- package/dist/ui/shared/app.css +4 -36
- package/dist/ui/shared/app.js +5 -4
- package/dist/ui/shared/instance-nav.js +9 -9
- package/dist/ui/shared/instance-switch.js +8 -2
- package/dist/ui/shared/panel-view.js +22 -8
- package/dist/ui/shared/view-strings.js +6 -0
- package/dist/usage/usage-api.d.ts +3 -0
- package/dist/usage/usage-api.js +5 -1
- package/dist/usage/usage-api.js.map +1 -1
- package/dist/view-api.d.ts +2 -0
- package/dist/view-api.js +64 -0
- package/dist/view-api.js.map +1 -1
- package/dist/web-api.d.ts +9 -17
- package/dist/web-api.js +255 -371
- package/dist/web-api.js.map +1 -1
- package/dist/web-appearance-tools.d.ts +0 -2
- package/dist/web-appearance-tools.js +2 -5
- package/dist/web-appearance-tools.js.map +1 -1
- package/dist/web-appearance-view.d.ts +0 -6
- package/dist/web-appearance-view.js +0 -1
- package/dist/web-appearance-view.js.map +1 -1
- package/dist/web-appearance.d.ts +14 -7
- package/dist/web-appearance.js +5 -6
- package/dist/web-appearance.js.map +1 -1
- package/dist/web-asset-parts.d.ts +1 -17
- package/dist/web-asset-parts.js +1 -15
- package/dist/web-asset-parts.js.map +1 -1
- package/dist/web-auth.js +1 -7
- package/dist/web-auth.js.map +1 -1
- package/dist/web-chat-history.d.ts +3 -7
- package/dist/web-chat-history.js +10 -17
- package/dist/web-chat-history.js.map +1 -1
- package/dist/web-commands.d.ts +9 -1
- package/dist/web-commands.js +13 -0
- package/dist/web-commands.js.map +1 -1
- package/dist/web-experimental.d.ts +2 -11
- package/dist/web-experimental.js +8 -36
- package/dist/web-experimental.js.map +1 -1
- package/dist/web-guest-access.d.ts +40 -2
- package/dist/web-guest-access.js +50 -14
- package/dist/web-guest-access.js.map +1 -1
- package/dist/web-mod-bundle.d.ts +2 -45
- package/dist/web-mod-bundle.js +15 -63
- package/dist/web-mod-bundle.js.map +1 -1
- package/dist/web-mod-store.d.ts +3 -48
- package/dist/web-mod-store.js +30 -97
- package/dist/web-mod-store.js.map +1 -1
- package/dist/web-prefs.d.ts +13 -0
- package/dist/web-prefs.js +38 -0
- package/dist/web-prefs.js.map +1 -1
- package/dist/web-preview.d.ts +1 -11
- package/dist/web-preview.js +6 -20
- package/dist/web-preview.js.map +1 -1
- package/dist/web-session.d.ts +11 -3
- package/dist/web-session.js +22 -11
- package/dist/web-session.js.map +1 -1
- package/dist/web-settings-patch.d.ts +1 -0
- package/dist/web-settings-patch.js +6 -3
- package/dist/web-settings-patch.js.map +1 -1
- package/dist/web-settings-tools.d.ts +0 -13
- package/dist/web-settings-tools.js +12 -84
- package/dist/web-settings-tools.js.map +1 -1
- package/launcher/fleet-service-owner.cjs +4 -1
- package/package.json +1 -1
- package/dist/general-knowledge/skills/web-appearance/SKILL.canvas-worker-off.md +0 -376
- package/dist/general-knowledge/skills/web-dashboard/SKILL.canvas-worker-off.md +0 -672
- package/dist/ui/app-effects.js +0 -251
- package/dist/ui/chat-effect-posters.js +0 -42
- package/dist/ui/chat-effects.js +0 -167
- package/dist/ui/effect-trial-controller.js +0 -124
- package/dist/ui/effect-trial-offer.js +0 -2
- package/dist/ui/effect-trial-panel.js +0 -40
- package/dist/ui/effects-waker.js +0 -2
- package/dist/ui/settings-effects.js +0 -202
- package/dist/ui/settings-owner-effects.js +0 -82
- package/dist/ui/shared/effect-app-runtime.js +0 -299
- package/dist/ui/shared/effect-baseline.js +0 -64
- package/dist/ui/shared/effect-chat-runtime.js +0 -162
- package/dist/ui/shared/effect-cursor.js +0 -126
- package/dist/ui/shared/effect-display-metadata.js +0 -39
- package/dist/ui/shared/effect-frame.js +0 -146
- package/dist/ui/shared/effect-geometry.js +0 -137
- package/dist/ui/shared/effect-host-style.js +0 -10
- package/dist/ui/shared/effect-owner-regions.js +0 -74
- package/dist/ui/shared/effect-owner-source.js +0 -122
- package/dist/ui/shared/effect-policy.js +0 -151
- package/dist/ui/shared/effect-poster-reader.js +0 -98
- package/dist/ui/shared/effect-posters.js +0 -117
- package/dist/ui/shared/effect-profiler.js +0 -184
- package/dist/ui/shared/effect-profiles.js +0 -65
- package/dist/ui/shared/effect-registry.js +0 -545
- package/dist/ui/shared/effect-runtime-loader.js +0 -236
- package/dist/ui/shared/effect-safety.js +0 -153
- package/dist/ui/shared/effect-stock-snapshot.js +0 -43
- package/dist/ui/shared/effect-stock-trust.js +0 -217
- package/dist/ui/shared/effect-trial-shadow.js +0 -62
- package/dist/ui/shared/effect-trial-source.js +0 -103
- package/dist/ui/shared/effect-trial-stage.js +0 -170
- package/dist/ui/shared/effect-trial.css +0 -40
- package/dist/ui/shared/feature-flags.js +0 -127
- package/dist/ui/shared/owner-effect-prefs.js +0 -117
- package/dist/web-effect-bootstrap.d.ts +0 -12
- package/dist/web-effect-bootstrap.js +0 -49
- package/dist/web-effect-bootstrap.js.map +0 -1
- package/dist/web-effect-bundle.d.ts +0 -28
- package/dist/web-effect-bundle.js +0 -171
- package/dist/web-effect-bundle.js.map +0 -1
- package/dist/web-effect-canonical.d.ts +0 -8
- package/dist/web-effect-canonical.js +0 -27
- package/dist/web-effect-canonical.js.map +0 -1
- package/dist/web-effect-library.d.ts +0 -114
- package/dist/web-effect-library.js +0 -530
- package/dist/web-effect-library.js.map +0 -1
- package/dist/web-effect-playback.d.ts +0 -30
- package/dist/web-effect-playback.js +0 -71
- package/dist/web-effect-playback.js.map +0 -1
- package/dist/web-effect-poster.d.ts +0 -8
- package/dist/web-effect-poster.js +0 -52
- package/dist/web-effect-poster.js.map +0 -1
- package/dist/web-effect-profiles.d.ts +0 -118
- package/dist/web-effect-profiles.js +0 -181
- package/dist/web-effect-profiles.js.map +0 -1
- package/dist/web-effect-service.d.ts +0 -93
- package/dist/web-effect-service.js +0 -335
- package/dist/web-effect-service.js.map +0 -1
- package/dist/web-effect-tools.d.ts +0 -48
- package/dist/web-effect-tools.js +0 -129
- package/dist/web-effect-tools.js.map +0 -1
- package/dist/web-effect-trials.d.ts +0 -86
- package/dist/web-effect-trials.js +0 -204
- package/dist/web-effect-trials.js.map +0 -1
- package/dist/web-effect-upload.d.ts +0 -27
- package/dist/web-effect-upload.js +0 -185
- package/dist/web-effect-upload.js.map +0 -1
- package/dist/web-effect-worker.d.ts +0 -8
- package/dist/web-effect-worker.js +0 -170
- package/dist/web-effect-worker.js.map +0 -1
- package/dist/web-effect.d.ts +0 -103
- package/dist/web-effect.js +0 -153
- package/dist/web-effect.js.map +0 -1
- package/dist/web-experimental-http.d.ts +0 -2
- package/dist/web-experimental-http.js +0 -12
- package/dist/web-experimental-http.js.map +0 -1
- package/dist/web-mod-agent.d.ts +0 -50
- package/dist/web-mod-agent.js +0 -207
- package/dist/web-mod-agent.js.map +0 -1
package/dist/ui/search-dialog.js
CHANGED
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
// ↑↓ and Enter, Esc or a click outside to close; filters folded under one toggle. A chat row still in the agent's
|
|
4
4
|
// history opens there, at the message (#m=); anything else — a chat row that has left the history, a message between
|
|
5
5
|
// agents, a task — opens here as the index's copy (GET /ui/search/context). Text only: no innerHTML, no style attribute.
|
|
6
|
+
// #2855: a trusted guest's — the chats it can see only (the server's rule): no settings, teams, sources or "only mine".
|
|
6
7
|
import { html, useEffect, useLayoutEffect, useMemo, useRef, useState } from "/assets/app-html.js";
|
|
7
8
|
import { t, register } from "/assets/app-i18n.js";
|
|
8
9
|
import { recordEdit } from "/assets/text-undo.js";
|
|
9
|
-
import { appStore, useStore } from "/assets/app-store.js";
|
|
10
|
+
import { appStore, guestPage, useStore } from "/assets/app-store.js";
|
|
10
11
|
import { navigate } from "/assets/app-nav.js";
|
|
11
12
|
import { chatPath, fleetPath } from "/assets/app-route.js";
|
|
12
13
|
import { namesOf, namesStore } from "/assets/instance-nav.js";
|
|
@@ -28,6 +29,8 @@ register("search", {
|
|
|
28
29
|
building: "Still indexing — some older results may be missing.",
|
|
29
30
|
limited: "Words of 1–2 characters alone search the newest 5,000 entries only — add a longer word to search everything.",
|
|
30
31
|
unavailable: "Search is off or not available (web.search in fleet.yaml).", busy: "Search is busy — try again in a moment.",
|
|
32
|
+
guestPlaceholder: "Search your chats…", guestHint: "Find something said in the chats you can see: a word or two. Short words (1–2 characters) work too.",
|
|
33
|
+
tooMany: "Too many searches. Try again in {0} seconds.",
|
|
31
34
|
signedOut: "Signed out — sign in again to search.", failed: "Search failed — try again.",
|
|
32
35
|
keys: "↑↓ move · Enter open · Esc close", results: "Results", back: "Results",
|
|
33
36
|
gone: "This message is no longer in the chat's history — this is the copy kept by search.",
|
|
@@ -47,6 +50,8 @@ register("search", {
|
|
|
47
50
|
building: "索引仍在建立中,較舊的結果可能還沒出現。",
|
|
48
51
|
limited: "只有 1–2 個字的短詞時,只會搜尋最新的 5,000 筆;加上較長的詞即可搜尋全部。",
|
|
49
52
|
unavailable: "搜尋已關閉或無法使用(fleet.yaml 的 web.search)。", busy: "搜尋忙碌中,請稍後再試。",
|
|
53
|
+
guestPlaceholder: "搜尋你的聊天…", guestHint: "找你看得到的聊天裡說過的事:輸入一兩個詞。1–2 個字的短詞也可以。",
|
|
54
|
+
tooMany: "搜尋太多次了,請 {0} 秒後再試。",
|
|
50
55
|
signedOut: "已登出,請重新登入後再搜尋。", failed: "搜尋失敗,請再試一次。",
|
|
51
56
|
keys: "↑↓ 移動 · Enter 開啟 · Esc 關閉", results: "結果", back: "結果",
|
|
52
57
|
gone: "這則訊息已不在聊天紀錄中,以下是搜尋保留的副本。",
|
|
@@ -144,7 +149,10 @@ export function jumpToMessage(instance, key, w = window) {
|
|
|
144
149
|
w.dispatchEvent(new Event("hashchange"));
|
|
145
150
|
}
|
|
146
151
|
|
|
147
|
-
const errorText = (status) => (status === 404 ? t("search.unavailable") : status === 503 ? t("search.busy") : status === 401 ? t("search.signedOut")
|
|
152
|
+
const errorText = (status, retryS) => (status === 404 ? t("search.unavailable") : status === 503 ? t("search.busy") : status === 401 ? t("search.signedOut")
|
|
153
|
+
: status === 429 ? t("search.tooMany", retryS || 60) : t("search.failed"));
|
|
154
|
+
/** #2855: a refused guest search's "try again in" (seconds), from its answer. */
|
|
155
|
+
const retryOf = async (r) => { try { const b = await r.json(); return { error: b && b.error, s: Number(b && b.retry_after_s) || 0 }; } catch { return { error: null, s: 0 }; } };
|
|
148
156
|
|
|
149
157
|
/** #2347 §6: how many settings the dialog lists above the messages (the Settings page's own search lists them all). */
|
|
150
158
|
const SETTINGS_SHOWN = 5;
|
|
@@ -154,7 +162,8 @@ export function SearchDialog({ preset = {}, onClose }) {
|
|
|
154
162
|
const app = useStore(appStore);
|
|
155
163
|
const names = useStore(namesStore);
|
|
156
164
|
const [q, setQ] = useState(preset.q || "");
|
|
157
|
-
const
|
|
165
|
+
const guest = guestPage();
|
|
166
|
+
const [f, setF] = useState({ instance: preset.instance || "", team: "", since: "any", sources: guest ? ["chat"] : [...SOURCES], mine: false });
|
|
158
167
|
const [showFilters, setShowFilters] = useState(!!preset.instance);
|
|
159
168
|
const [teams, setTeams] = useState(null);
|
|
160
169
|
const [res, setRes] = useState({ status: "idle" });
|
|
@@ -193,8 +202,15 @@ export function SearchDialog({ preset = {}, onClose }) {
|
|
|
193
202
|
const ac = typeof AbortController === "function" ? new AbortController() : null;
|
|
194
203
|
const timer = setTimeout(async () => {
|
|
195
204
|
try {
|
|
196
|
-
const
|
|
205
|
+
const ask = () => fetch(url, { headers: { Accept: "application/json" }, ...(ac ? { signal: ac.signal } : {}) });
|
|
206
|
+
let r = await ask();
|
|
197
207
|
if (mine !== seq.current) return;
|
|
208
|
+
if (r.status === 429) {
|
|
209
|
+
// #2855: a guest's searches run one at a time — the one before this may still be finishing: once more, shortly.
|
|
210
|
+
const why = await retryOf(r);
|
|
211
|
+
if (why.error === "guest_search_running") { await new Promise((ok) => setTimeout(ok, 400)); if (mine !== seq.current) return; r = await ask(); if (mine !== seq.current) return; }
|
|
212
|
+
if (r.status === 429) { const again = why.error === "guest_search_running" ? await retryOf(r) : why; setRes({ status: "error", error: errorText(429, again.s) }); return; }
|
|
213
|
+
}
|
|
198
214
|
if (!r.ok) { setRes({ status: "error", error: errorText(r.status) }); return; }
|
|
199
215
|
const body = await r.json();
|
|
200
216
|
if (mine !== seq.current) return;
|
|
@@ -209,7 +225,7 @@ export function SearchDialog({ preset = {}, onClose }) {
|
|
|
209
225
|
|
|
210
226
|
// Teams, once the filters are first shown.
|
|
211
227
|
useEffect(() => {
|
|
212
|
-
if (!showFilters || teams) return;
|
|
228
|
+
if (guest || !showFilters || teams) return;
|
|
213
229
|
fetch("/ui/teams", { headers: { Accept: "application/json" } }).then(r => (r.ok ? r.json() : { teams: {} })).then(
|
|
214
230
|
(b) => setTeams(Object.keys((b && b.teams) || {}).sort()), () => setTeams([]));
|
|
215
231
|
}, [showFilters, teams]);
|
|
@@ -219,9 +235,9 @@ export function SearchDialog({ preset = {}, onClose }) {
|
|
|
219
235
|
// While a new query runs, the last answer stays (dimmed) rather than flashing empty.
|
|
220
236
|
const groups = useMemo(() => groupHits(res.hits || []), [res]);
|
|
221
237
|
// #2347 §6: the settings matching the query, from the page's own registry (no request), above the messages.
|
|
222
|
-
const settings = useMemo(() => (q.trim() ? searchSettings(q).slice(0, SETTINGS_SHOWN) : []), [q]);
|
|
238
|
+
const settings = useMemo(() => (q.trim() && !guest ? searchSettings(q).slice(0, SETTINGS_SHOWN) : []), [q]);
|
|
223
239
|
const flat = useMemo(() => [...settings.map((entry) => ({ setting: entry })), ...flatHits(groups)], [settings, groups]);
|
|
224
|
-
const activeFilters = (f.instance ? 1 : 0) + (f.team ? 1 : 0) + (f.since !== "any" ? 1 : 0) + (f.sources.length < SOURCES.length ? 1 : 0) + (f.mine ? 1 : 0);
|
|
240
|
+
const activeFilters = (f.instance ? 1 : 0) + (f.team ? 1 : 0) + (f.since !== "any" ? 1 : 0) + (!guest && f.sources.length < SOURCES.length ? 1 : 0) + (f.mine ? 1 : 0);
|
|
225
241
|
|
|
226
242
|
// The active row stays in view.
|
|
227
243
|
useEffect(() => {
|
|
@@ -239,7 +255,7 @@ export function SearchDialog({ preset = {}, onClose }) {
|
|
|
239
255
|
// Any failure — the request, or a body that is not JSON — ends as an error, never "Loading…" for good.
|
|
240
256
|
fetch(`/ui/search/context?key=${encodeURIComponent(hit.key)}`, { headers: { Accept: "application/json" } })
|
|
241
257
|
.then(async (r) => {
|
|
242
|
-
if (!r.ok) { setDetail(d => (d && d.hit === hit ? { hit, status: "error", error: r.status === 404 ? t("search.notFound") : errorText(r.status) } : d)); return; }
|
|
258
|
+
if (!r.ok) { const s = r.status === 429 ? (await retryOf(r)).s : 0; setDetail(d => (d && d.hit === hit ? { hit, status: "error", error: r.status === 404 ? t("search.notFound") : errorText(r.status, s) } : d)); return; }
|
|
243
259
|
const body = await r.json();
|
|
244
260
|
setDetail(d => (d && d.hit === hit ? { hit, status: "ok", ctx: body } : d));
|
|
245
261
|
})
|
|
@@ -266,7 +282,7 @@ export function SearchDialog({ preset = {}, onClose }) {
|
|
|
266
282
|
<div class="search-box">
|
|
267
283
|
<div class="search-head">
|
|
268
284
|
<${Icon} name="search" />
|
|
269
|
-
<input ref=${input} class="search-input" type="search" value=${q} placeholder=${t("search.placeholder")} aria-label=${t("search.label")}
|
|
285
|
+
<input ref=${input} class="search-input" type="search" value=${q} placeholder=${t(guest ? "search.guestPlaceholder" : "search.placeholder")} aria-label=${t("search.label")}
|
|
270
286
|
role="combobox" aria-expanded=${flat.length && !detail ? "true" : "false"} aria-controls="search-results" aria-autocomplete="list"
|
|
271
287
|
aria-activedescendant=${activeId} autocomplete="off" spellcheck=${false} enterkeyhint="search"
|
|
272
288
|
onInput=${(e) => { setQ(e.target.value); setDetail(null); }} />
|
|
@@ -288,24 +304,24 @@ export function SearchDialog({ preset = {}, onClose }) {
|
|
|
288
304
|
${["any", "day", "week", "month"].map(k => html`<button type="button" class=${`chip-btn${f.since === k ? " on" : ""}`} aria-pressed=${f.since === k ? "true" : "false"}
|
|
289
305
|
onClick=${() => setFilter({ since: k })}>${k === "any" ? t("search.anyTime") : t(`search.${k}`)}</button>`)}
|
|
290
306
|
</div>
|
|
291
|
-
|
|
307
|
+
${guest ? null : html`<div class="search-chips" role="group" aria-label=${t("search.sources")}>
|
|
292
308
|
${SOURCES.map(s => html`<button type="button" class=${`chip-btn${f.sources.includes(s) ? " on" : ""}`} aria-pressed=${f.sources.includes(s) ? "true" : "false"}
|
|
293
309
|
onClick=${() => toggleSource(s)}>${t(`search.src_${s}`)}</button>`)}
|
|
294
310
|
<label class="search-mine"><input type="checkbox" checked=${f.mine} onChange=${(e) => setFilter({ mine: e.target.checked })} />${t("search.mine")}</label>
|
|
295
|
-
</div
|
|
311
|
+
</div>`}
|
|
296
312
|
</div>` : null}
|
|
297
313
|
<div class="search-body" ref=${list}>
|
|
298
314
|
${detail ? html`<${Detail} detail=${detail} q=${q} label=${label} onBack=${() => { setDetail(null); input.current && input.current.focus(); }} onClose=${onClose} />`
|
|
299
|
-
: html`<${Results} q=${q} res=${res} groups=${groups} settings=${settings} active=${active} setActive=${setActive} open=${open} label=${label} filtered=${activeFilters > 0}
|
|
315
|
+
: html`<${Results} guest=${guest} q=${q} res=${res} groups=${groups} settings=${settings} active=${active} setActive=${setActive} open=${open} label=${label} filtered=${activeFilters > 0}
|
|
300
316
|
recent=${recent} pick=${(s) => { recordEdit(input.current); setQ(s); input.current && input.current.focus(); }} clearRecent=${() => { forgetRecent(); setRecent([]); }} />`}
|
|
301
317
|
</div>
|
|
302
318
|
<div class="search-foot" aria-hidden="true">${t("search.keys")}</div>
|
|
303
319
|
</div></dialog>`;
|
|
304
320
|
}
|
|
305
321
|
|
|
306
|
-
function Results({ q, res, groups, settings = [], active, setActive, open, label, filtered, recent, pick, clearRecent }) {
|
|
322
|
+
function Results({ guest = false, q, res, groups, settings = [], active, setActive, open, label, filtered, recent, pick, clearRecent }) {
|
|
307
323
|
if (!q.trim()) {
|
|
308
|
-
return html`<div class="search-empty"><p class="search-hint">${t("search.hint")}</p>
|
|
324
|
+
return html`<div class="search-empty"><p class="search-hint">${t(guest ? "search.guestHint" : "search.hint")}</p>
|
|
309
325
|
${recent.length ? html`<div class="search-recent"><div class="search-recent-head"><span>${t("search.recent")}</span>
|
|
310
326
|
<button type="button" class="chip-btn" onClick=${clearRecent}>${t("search.clearRecent")}</button></div>
|
|
311
327
|
<ul>${recent.map(s => html`<li><button type="button" class="search-recent-item" onClick=${() => pick(s)}><${Icon} name="clock" size=${14} /><span>${s}</span></button></li>`)}</ul></div>` : null}</div>`;
|
|
@@ -6,8 +6,6 @@ import { register, t } from "/assets/app-i18n.js";
|
|
|
6
6
|
import { recordEdit } from "/assets/text-undo.js";
|
|
7
7
|
import { appStore, useStore } from "/assets/app-store.js";
|
|
8
8
|
import { ModsCard } from "./settings-mods.js";
|
|
9
|
-
import { experimentalComponent } from "/assets/feature-flags.js";
|
|
10
|
-
const EffectsCard = experimentalComponent(() => import("./settings-effects.js"), m => m.EffectsCard);
|
|
11
9
|
import { Icon } from "/assets/ui-icons.js";
|
|
12
10
|
import { toast } from "/assets/ui-toast.js";
|
|
13
11
|
import { ownerCssOffHere, ownerCssTrial, safeMode, setOwnerCssActive, setOwnerCssOffHere, startOwnerCssTrial } from "/assets/owner-css.js";
|
|
@@ -71,7 +69,7 @@ export function AppearanceSection(props = {}) {
|
|
|
71
69
|
return html`${group("groupLook")}<${ThemesCard} ...${props} /><${IdentityColorCard} /><${CardSizeCard} />
|
|
72
70
|
${group("groupLayout")}<${SidebarWidgetCard} />
|
|
73
71
|
${group("groupCss")}<${OwnerCssCard} />
|
|
74
|
-
${group("groupMods")}<${ModsCard}
|
|
72
|
+
${group("groupMods")}<${ModsCard} />
|
|
75
73
|
<${RestoreDefaultsCard} ...${props} />`;
|
|
76
74
|
}
|
|
77
75
|
|
|
@@ -22,13 +22,16 @@ const PLATFORMS = { discord: "Discord", telegram: "Telegram" };
|
|
|
22
22
|
const own = (o, k) => !!o && Object.prototype.hasOwnProperty.call(o, k);
|
|
23
23
|
const sameList = (a, b) => (a || []).length === (b || []).length && (a || []).every((n) => (b || []).includes(n));
|
|
24
24
|
const accountKey = (g) => `${g.adapterId}:${g.userId}`;
|
|
25
|
-
|
|
25
|
+
// #2855: a grant's level — "trusted", or chat (the default, so never written).
|
|
26
|
+
const grantOf = (v) => ({ ...(v.name ? { name: v.name } : {}), instances: [...v.instances], ...(v.level === "trusted" ? { level: "trusted" } : {}) });
|
|
27
|
+
const copyGrants = (m) => { const out = {}; for (const [k, v] of Object.entries(m || {})) if (v && Array.isArray(v.instances)) out[k] = grantOf(v); return out; };
|
|
26
28
|
/** The saved state, from the fleet's config: `grants` null while the fleet still uses #1920's derived access. */
|
|
27
29
|
export function savedGuests(fleet) {
|
|
28
30
|
const g = (fleet && fleet.web && fleet.web.guests) || {};
|
|
29
31
|
return { enabled: g.enabled === true, chat: g.chat !== false, grants: g.grants ? copyGrants(g.grants) : null };
|
|
30
32
|
}
|
|
31
|
-
const
|
|
33
|
+
const levelOf = (g) => (g && g.level === "trusted" ? "trusted" : "chat");
|
|
34
|
+
const sameGrant = (a, b) => !!a && !!b && (a.name || "") === (b.name || "") && sameList(a.instances, b.instances) && levelOf(a) === levelOf(b);
|
|
32
35
|
|
|
33
36
|
/**
|
|
34
37
|
* What changed against the saved state, as the PUT /api/settings/fleet/web body's `guests` (null: nothing), and how
|
|
@@ -44,7 +47,7 @@ export function guestsBody(original, f) {
|
|
|
44
47
|
for (const k of new Set([...Object.keys(was), ...Object.keys(f.grants)])) {
|
|
45
48
|
const a = own(was, k) ? was[k] : undefined, b = own(f.grants, k) ? f.grants[k] : undefined;
|
|
46
49
|
if (a && b && sameGrant(a, b)) continue;
|
|
47
|
-
diff[k] = b ?
|
|
50
|
+
diff[k] = b ? grantOf(b) : null;
|
|
48
51
|
count++;
|
|
49
52
|
}
|
|
50
53
|
// #2365 review: turning grants on is a change by itself, even with no one in them yet (default deny): `grants: {}`.
|
|
@@ -235,6 +238,12 @@ export function GuestsSection({ ctx, staged }) {
|
|
|
235
238
|
${removed ? html`<span class="g-state">${tn("grantsWillBeRemoved")}</span>` : added ? html`<span class="g-state">${tn("grantsNew")}</span>` : pill(person)}</div>
|
|
236
239
|
<div class="g-chips">${((draft || saved).instances || []).map((n) => html`<span key=${n} class="g-chip" title=${shownName(rooms, n) !== n ? n : undefined}>${shownName(rooms, n)}</span>`)}</div>
|
|
237
240
|
${draft && !draft.instances.length ? html`<p class="note g-error" role="alert">${tn("grantsNoAgents")}</p>` : null}
|
|
241
|
+
${draft ? html`<div class=${`g-level${levelOf(draft) !== levelOf(saved) && saved ? " s-dirty" : ""}`}>
|
|
242
|
+
<span class="note" id=${`level-${k}`}>${tn("grantsLevel", person.name)}</span>
|
|
243
|
+
<div class="seg-inline" role="group" aria-labelledby=${`level-${k}`}>${["chat", "trusted"].map((l) => html`<button key=${l} type="button"
|
|
244
|
+
class=${`btn btn-sm${levelOf(draft) === l ? " btn-primary" : ""}`} aria-pressed=${levelOf(draft) === l ? "true" : "false"} data-level=${l}
|
|
245
|
+
onClick=${() => setGrant(k, { ...draft, level: l === "trusted" ? "trusted" : undefined })}>${tn(l === "trusted" ? "grantsLevelTrusted" : "grantsLevelChat")}</button>`)}</div>
|
|
246
|
+
${levelOf(draft) === "trusted" ? html`<p class="note">${tn("grantsLevelTrustedHint")}</p>` : null}</div>` : null}
|
|
238
247
|
${editing === k && draft ? html`<div class="g-edit"><p class="note">${tn("grantsMaySee", person.name)}</p>
|
|
239
248
|
<${Names} id=${`grant-${k}`} all=${instances} rooms=${rooms} value=${draft.instances} wrote=${person.wrote} onChange=${(list) => setGrant(k, { ...draft, instances: list })} /></div>` : null}
|
|
240
249
|
${readOnly ? null : html`<div class="guest-actions">
|
|
@@ -260,7 +269,7 @@ export function GuestsSection({ ctx, staged }) {
|
|
|
260
269
|
${overview && overview.audit ? html`<h4>${tn("grantsRecent")}</h4>${!overview.audit.length ? html`<p class="note" id="grantsRecentNone">${tn("grantsRecentNone")}</p>`
|
|
261
270
|
: html`<ul class="g-audit" id="grantsRecentList">${overview.audit.map((e, i) => html`<li key=${`${e.at}-${i}`} class="g-audit-row" data-kind=${e.kind}>
|
|
262
271
|
<span class="g-when">${new Date(e.at).toLocaleString()}</span> <strong>${e.name}</strong>
|
|
263
|
-
${e.kind === "grant" ? html`${e.added.map((n) => html` <span key=${`+${n}`} class="g-wrote g-add">+${n}</span>`)}${e.removed.map((n) => html` <span key=${`-${n}`} class="g-wrote g-del">−${n}</span>`)}
|
|
272
|
+
${e.kind === "grant" ? html`${e.added.map((n) => html` <span key=${`+${n}`} class="g-wrote g-add">+${n}</span>`)}${e.removed.map((n) => html` <span key=${`-${n}`} class="g-wrote g-del">−${n}</span>`)}${e.level ? html` <span class="g-wrote g-level-to">${tn("grantsAuditLevel", tn(e.level === "trusted" ? "grantsLevelTrusted" : "grantsLevelChat"))}</span>` : null}
|
|
264
273
|
<span class="note"> · <span class="g-wrote">${tn(e.via === "confirmed" ? "grantsAuditConfirmed" : "grantsAuditDirect")}</span>${e.endedSessions ? html` · <span class="g-wrote">${tn("grantsAuditEnded", e.endedSessions)}</span>` : null}</span>`
|
|
265
274
|
: html` <span class="note">${e.outcome === "sent" ? tn("grantsAuditCodeSent") : tn("grantsAuditCodeNot", auditOutcome(e.outcome))}</span>`}</li>`)}</ul>`}` : null}
|
|
266
275
|
${status && status.kind === "waiting" ? html`<p class="note" role="status">${tn("grantsWaiting")}</p>` : null}
|
|
@@ -12,7 +12,7 @@ const DOCS = { en: "https://github.com/songsid/AgEnD/blob/main/docs/web-dashboar
|
|
|
12
12
|
export const LEARN_ANCHORS = {
|
|
13
13
|
colors: ["identity-colours", "識別色"], css: ["custom-css-settings--look", "自訂-css設定--外觀"], themes: ["agents-themes", "agent-的主題"],
|
|
14
14
|
cards: ["identity-cards", "身分卡"], limits: ["card-upload-limits", "身分卡上傳限制"], widgets: ["widgets", "小工具"], mods: ["mods", "mod"],
|
|
15
|
-
|
|
15
|
+
restore: ["restore-defaults", "還原預設值"],
|
|
16
16
|
"app-icon": ["app-icon", "app-圖示"],
|
|
17
17
|
};
|
|
18
18
|
export function learnHref(topic, language = lang()) {
|
package/dist/ui/settings-mods.js
CHANGED
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
// parts the person ticks are applied, all or nothing. Custom CSS from a file is never ticked by default. Installed
|
|
5
5
|
// mods can be downloaded again or uninstalled (what they applied stays: Reset and the CSS controls work on it).
|
|
6
6
|
// Export builds a file from what is in force now.
|
|
7
|
-
import { canvasWorkerEnabled, useCanvasWorker } from "/assets/feature-flags.js";
|
|
8
7
|
import { html, useEffect, useLayoutEffect, useRef, useState } from "/assets/app-html.js";
|
|
9
8
|
import { t } from "/assets/app-i18n.js";
|
|
10
9
|
import { appStore, useStore } from "/assets/app-store.js";
|
|
@@ -48,7 +47,6 @@ export function defaultChoice(plan) {
|
|
|
48
47
|
for (const it of plan.items) {
|
|
49
48
|
if (it.part === "theme" || it.part === "identity" || it.part === "ambient") c[it.part] = true;
|
|
50
49
|
else if (it.part === "widget") c.widgets[it.slot] = true;
|
|
51
|
-
else if (it.part === "effect" && canvasWorkerEnabled()) c.effects = true;
|
|
52
50
|
// ownerCss stays false: "read it before turning it on" (r4 §4).
|
|
53
51
|
}
|
|
54
52
|
return c;
|
|
@@ -62,7 +60,7 @@ function Swatch({ color }) {
|
|
|
62
60
|
return html`<span class="s-mod-sw"><span class="s-mod-sw-chip" ref=${ref} aria-hidden="true"></span><code>${color}</code></span>`;
|
|
63
61
|
}
|
|
64
62
|
/** Every field of the checked theme, as it will be applied — nothing left unseen (#1897 review). */
|
|
65
|
-
export function ThemeDetail({ theme, pictures
|
|
63
|
+
export function ThemeDetail({ theme, pictures }) {
|
|
66
64
|
const th = theme || {};
|
|
67
65
|
const row = (k, v) => html`<div class="s-mod-kv"><span class="k">${k}</span><span class="v">${v}</span></div>`;
|
|
68
66
|
const pal = (mode) => {
|
|
@@ -83,7 +81,6 @@ export function ThemeDetail({ theme, pictures, effects }) {
|
|
|
83
81
|
${th.background ? pic(th.background.image, tn("modBackground")) : null}
|
|
84
82
|
${th.background ? row(tn("modBackgroundFit"), `${th.background.fit} · ${Math.round(th.background.opacity * 100)}%`) : null}
|
|
85
83
|
${th.avatar ? pic(th.avatar, tn("modAvatar")) : null}
|
|
86
|
-
${(canvasWorkerEnabled() ? effects || [] : []).map((e, i) => html`<div key=${i}>${row(tn("modPlacesEffect"), html`<span><code>${e.effect}</code>${e.id ? html` <span class="muted">${e.id.slice(0, 8)}</span>` : null}${Object.keys(e.params || {}).length ? html` · <code>${JSON.stringify(e.params)}</code>` : null}</span>`)}</div>`)}
|
|
87
84
|
<details class="s-mod-raw"><summary>${tn("modAllFields")}</summary><pre class="s-mod-css-text">${JSON.stringify(th, null, 2)}</pre></details>
|
|
88
85
|
</div>`;
|
|
89
86
|
}
|
|
@@ -92,7 +89,7 @@ function PlanItem({ it, choice, setChoice, binds, fleet }) {
|
|
|
92
89
|
const tick = (label, on, set) => html`<label class="check s-mod-tick"><input type="checkbox" checked=${on} onChange=${(e) => set(e.target.checked)} /><span>${label}</span></label>`;
|
|
93
90
|
if (it.part === "theme") {
|
|
94
91
|
return html`<li class="s-mod-item">${tick(tn("modPartTheme"), choice.theme, (v) => setChoice({ ...choice, theme: v }))}
|
|
95
|
-
<div class="s-mod-detail"><${ThemeDetail} theme=${it.theme} pictures=${it.pictures}
|
|
92
|
+
<div class="s-mod-detail"><${ThemeDetail} theme=${it.theme} pictures=${it.pictures} /></div></li>`;
|
|
96
93
|
}
|
|
97
94
|
if (it.part === "identity") {
|
|
98
95
|
return html`<li class="s-mod-item">${tick(tn("modPartIdentity"), choice.identity, (v) => setChoice({ ...choice, identity: v }))}
|
|
@@ -116,11 +113,8 @@ function PlanItem({ it, choice, setChoice, binds, fleet }) {
|
|
|
116
113
|
<div class="s-mod-detail">${one("loop")}${one("click")}
|
|
117
114
|
<details class="s-mod-raw"><summary>${tn("modAllFields")}</summary><pre class="s-mod-css-text">${JSON.stringify({ loop: it.loop, click: it.click }, null, 2)}</pre></details></div></li>`;
|
|
118
115
|
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
return html`<li class="s-mod-item">${tick(tn("modPartEffect", it.title), choice.effects, (v) => setChoice({ ...choice, effects: v }))}
|
|
122
|
-
<div class="s-mod-detail"><span>${kib(it.bytes)} · ${tn("modRegions", it.regions.join(", "))}</span>${it.wants.length ? html`<span>${tn("modWants", it.wants.join(", "))}</span>` : null}</div></li>`;
|
|
123
|
-
}
|
|
116
|
+
// #2799: a Canvas Worker effect entry (the server refuses the file that carries one) is not offered.
|
|
117
|
+
if (it.part === "effect") return null;
|
|
124
118
|
if (it.part === "ownerCss") {
|
|
125
119
|
return html`<li class="s-mod-item s-mod-css">${tick(tn("modPartCss"), choice.ownerCss, (v) => setChoice({ ...choice, ownerCss: v }))}
|
|
126
120
|
<p class="s-mod-warn">${tn("modCssWarn")}</p>
|
|
@@ -141,7 +135,7 @@ function Plan({ staged, onDone }) {
|
|
|
141
135
|
const names = (app.instances || []).map((i) => i.name);
|
|
142
136
|
const install = async () => {
|
|
143
137
|
setBusy(true); setError("");
|
|
144
|
-
const r = await postJson("/ui/mods/commit", { stage: staged.stage, plan_hash: staged.plan_hash, include: { ...choice, effects:
|
|
138
|
+
const r = await postJson("/ui/mods/commit", { stage: staged.stage, plan_hash: staged.plan_hash, include: { ...choice, effects: false }, ...(toInstance ? { target } : {}) });
|
|
145
139
|
setBusy(false);
|
|
146
140
|
// The stage this plan shows is gone or no longer this plan (replaced on another device, expired): read again —
|
|
147
141
|
// the page then shows whatever is staged now, with its own, fresh choices (keyed by stage).
|
|
@@ -209,7 +203,6 @@ function ExportForm() {
|
|
|
209
203
|
}
|
|
210
204
|
|
|
211
205
|
export function ModsCard() {
|
|
212
|
-
useCanvasWorker();
|
|
213
206
|
const [state, setState] = useState(null);
|
|
214
207
|
const [busy, setBusy] = useState(false);
|
|
215
208
|
const [error, setError] = useState("");
|
|
@@ -28,7 +28,6 @@ const ROWS = [
|
|
|
28
28
|
["look.widget", "look", ".s-widget", "settings.widgetTitle", "widget.sidebar", ["sidebar widget", "widget", "json"], ["側邊欄小工具", "小工具"]],
|
|
29
29
|
["look.css", "look", ".s-owner-css", "settings.cssTitle", null, ["custom css", "stylesheet", "style", "css"], ["自訂 CSS", "樣式表", "樣式"]],
|
|
30
30
|
["look.mods", "look", ".s-mods", "settings.modsTitle", null, ["mod", "mods", "import", "add-on", "agendmod"], ["mod", "匯入", "擴充"]],
|
|
31
|
-
["look.effects", "look", ".s-effects", null, null, ["effects", "canvas", "ambient", "petals", "click stars"], ["特效", "效果", "花瓣", "氛圍"], L("Effects", "效果")],
|
|
32
31
|
["look.restore", "look", ".s-restore", "settings.restoreTitle", null, ["restore defaults", "reset", "default"], ["還原預設值", "重設", "預設"]],
|
|
33
32
|
// Fleet · Agents
|
|
34
33
|
["agents.new", "agents", "#newAgentBtn", "settings.newAgent", null, ["new agent", "create", "add agent", "instance"], ["新增 agent", "建立", "新 agent"]],
|
|
@@ -393,16 +393,14 @@ register("settings", {
|
|
|
393
393
|
modPartWidget_chat_header: "Chat header widget",
|
|
394
394
|
modPartWidget_details: "Details page widget",
|
|
395
395
|
modPartWidget_sidebar: "Your sidebar widget",
|
|
396
|
-
modPartEffect: "Effect: {0}",
|
|
397
396
|
modPartCss: "Custom CSS (on your screens only)",
|
|
398
397
|
modCssWarn: "Custom CSS from this file — read it before turning it on.",
|
|
399
398
|
modCssAssets: "{0} files for it ({1})",
|
|
400
399
|
modAccent: "Accent", modFont: "Font", modPictures: "{0} pictures ({1})", modPlaces: "places {0} effects",
|
|
401
400
|
modColorsLight: "Light colours", modColorsDark: "Dark colours", modBubble: "Bubbles", modWorkingLabel: "Working label",
|
|
402
401
|
modBackground: "Background picture", modBackgroundFit: "Background fit and opacity", modAvatar: "Picture (face)",
|
|
403
|
-
|
|
402
|
+
modAllFields: "Every field, as it will be applied",
|
|
404
403
|
modStageGone: "That import was replaced or ran out of time. Showing what is staged now.",
|
|
405
|
-
modRegions: "draws on: {0}", modWants: "hears: {0}",
|
|
406
404
|
modTarget: "Apply to",
|
|
407
405
|
modTargetPick: "Choose an agent…",
|
|
408
406
|
modTargetNeeded: "Choose the agent this look goes to.",
|
|
@@ -541,6 +539,8 @@ register("settings", {
|
|
|
541
539
|
grantsChoose: "Choose", grantsChosen: "Chosen", grantsMaySee: "{0} may see:", grantsClassicRooms: "ClassicBot rooms", grantsWroteHere: "wrote here", grantsWroteTo: "Wrote to: {0}",
|
|
542
540
|
grantsLastSeen: "last seen {0}", grantsRecent: "Recent changes", grantsRecentNone: "No change to anyone's access yet.", grantsAuditDirect: "applied at once", grantsAuditConfirmed: "confirmed in General", grantsAuditEnded: "{0} session(s) ended", grantsAuditCodeSent: "sign-in code sent", grantsAuditCodeNot: "sign-in code not sent ({0})", grantsAuditDmFailed: "the DM did not go through", grantsAuditWait: "asked again within a minute", grantsAuditAdminLimit: "the admin's limit of codes was reached",
|
|
543
541
|
grantsEditAgents: "Edit agents", grantsRemove: "Remove access", grantsUndoRemove: "Keep access",
|
|
542
|
+
grantsLevel: "What {0} may do", grantsLevelChat: "Chat", grantsLevelTrusted: "Trusted",
|
|
543
|
+
grantsLevelTrustedHint: "Trusted adds watching these agents' screens (read-only) and searching their chats. The screen shows everything the agent's terminal does: other people's messages to it, commands and their output, which can include secrets. Chat and search stay limited to what this person may see.", grantsAuditLevel: "→ {0}",
|
|
544
544
|
grantsWillBeRemoved: "Access ends when you save.", grantsNew: "New — granted when you save", grantsNoAgents: "Tick at least one agent, or remove this person.",
|
|
545
545
|
grantsSend: "Send sign-in code", grantsSendAgain: "Send again in {0}", grantsSendSaveFirst: "Save first: a code goes only to saved access.",
|
|
546
546
|
grantsSent: "Code sent {0} · expires {1}", grantsSentPublic: "It works on the open public link too.", grantsSending: "Sending…",
|
|
@@ -1047,16 +1047,14 @@ register("settings", {
|
|
|
1047
1047
|
modPartWidget_chat_header: "聊天標頭小工具",
|
|
1048
1048
|
modPartWidget_details: "詳細資料頁小工具",
|
|
1049
1049
|
modPartWidget_sidebar: "你的側邊欄小工具",
|
|
1050
|
-
modPartEffect: "效果:{0}",
|
|
1051
1050
|
modPartCss: "自訂 CSS(只套用在你的畫面)",
|
|
1052
1051
|
modCssWarn: "這是檔案裡的自訂 CSS——開啟前請先讀過。",
|
|
1053
1052
|
modCssAssets: "附帶 {0} 個檔案({1})",
|
|
1054
1053
|
modAccent: "強調色", modFont: "字型", modPictures: "{0} 張圖片({1})", modPlaces: "放置 {0} 個效果",
|
|
1055
1054
|
modColorsLight: "淺色模式顏色", modColorsDark: "深色模式顏色", modBubble: "對話泡泡", modWorkingLabel: "工作中文字",
|
|
1056
1055
|
modBackground: "背景圖片", modBackgroundFit: "背景填滿方式與不透明度", modAvatar: "圖片(頭像)",
|
|
1057
|
-
|
|
1056
|
+
modAllFields: "所有欄位(套用時的樣子)",
|
|
1058
1057
|
modStageGone: "那次匯入已被取代或已逾時。以下顯示目前暫存的內容。",
|
|
1059
|
-
modRegions: "繪製於:{0}", modWants: "接收:{0}",
|
|
1060
1058
|
modTarget: "套用到",
|
|
1061
1059
|
modTargetPick: "選擇一個 agent…",
|
|
1062
1060
|
modTargetNeeded: "請選擇這套外觀要套用到哪個 agent。",
|
|
@@ -1194,6 +1192,8 @@ register("settings", {
|
|
|
1194
1192
|
grantsChoose: "選擇", grantsChosen: "已選擇", grantsMaySee: "{0} 可以看到:", grantsClassicRooms: "ClassicBot 聊天室", grantsWroteHere: "傳過訊息", grantsWroteTo: "傳過訊息給:{0}",
|
|
1195
1193
|
grantsLastSeen: "最後出現 {0}", grantsRecent: "最近的變更", grantsRecentNone: "還沒有人的存取權變更過。", grantsAuditDirect: "立即生效", grantsAuditConfirmed: "在 General 確認後生效", grantsAuditEnded: "結束了 {0} 個登入", grantsAuditCodeSent: "已寄出登入碼", grantsAuditCodeNot: "登入碼未寄出({0})", grantsAuditDmFailed: "私訊沒有送達", grantsAuditWait: "一分鐘內重複要求", grantsAuditAdminLimit: "已達管理員的登入碼上限",
|
|
1196
1194
|
grantsEditAgents: "編輯 agent", grantsRemove: "移除存取權", grantsUndoRemove: "保留存取權",
|
|
1195
|
+
grantsLevel: "{0} 可以做什麼", grantsLevelChat: "聊天", grantsLevelTrusted: "信任",
|
|
1196
|
+
grantsLevelTrustedHint: "信任多了:觀看這些 agent 的畫面(唯讀),以及搜尋它們的聊天。畫面會顯示 agent 終端上的一切:別人傳給它的訊息、指令和它們的輸出,其中可能有機密。聊天和搜尋仍只限這個人看得到的內容。", grantsAuditLevel: "→ {0}",
|
|
1197
1197
|
grantsWillBeRemoved: "儲存後存取權就會結束。", grantsNew: "新增 —— 儲存後授權", grantsNoAgents: "至少勾選一個 agent,或移除這個人。",
|
|
1198
1198
|
grantsSend: "傳送登入碼", grantsSendAgain: "{0} 後可再傳", grantsSendSaveFirst: "請先儲存:登入碼只會傳給已儲存的授權。",
|
|
1199
1199
|
grantsSent: "{0} 已傳送 · {1} 到期", grantsSentPublic: "也可以用在目前開啟的公開連結上。", grantsSending: "傳送中…",
|
|
@@ -91,15 +91,6 @@ let holding = 0;
|
|
|
91
91
|
const held = new Map(); // key → value chosen in Settings, shown here, not saved yet
|
|
92
92
|
const heldFrom = new Map(); // key → its cached form before it was held (a choice back to it is no change)
|
|
93
93
|
const heldListeners = new Set();
|
|
94
|
-
// #2193: whether the signed-in person has a personal effect slot chosen (their fx.* keys: in the page's data-prefs and
|
|
95
|
-
// in every host read, never in this host cache). The effects runtime is loaded only when there is something to play.
|
|
96
|
-
const ownerEffectsIn = (values) => !!values && OWNER_EFFECT_PREF_KEYS.some((k) => own(values, k) && values[k] != null && values[k] !== "");
|
|
97
|
-
let ownerFx = initial ? ownerEffectsIn(initial.values) : false;
|
|
98
|
-
const ownerFxListeners = new Set();
|
|
99
|
-
/** Whether the person has any personal effect (fx.page/header/sidebar/box/cursor) chosen, as this page last read. */
|
|
100
|
-
export function ownerEffectsChosen() { return ownerFx; }
|
|
101
|
-
/** Called when that changes. Returns the unsubscribe. */
|
|
102
|
-
export function onOwnerEffectsChanged(fn) { ownerFxListeners.add(fn); return () => ownerFxListeners.delete(fn); }
|
|
103
94
|
|
|
104
95
|
// The host's values into the cache, now — before the sidebar, the tour or the first-run card read their keys. A key
|
|
105
96
|
// the host does not have: the default, on a device in step with the host; this device's own (to upload) otherwise.
|
|
@@ -111,9 +102,20 @@ if (host) {
|
|
|
111
102
|
}
|
|
112
103
|
}
|
|
113
104
|
|
|
114
|
-
/**
|
|
115
|
-
|
|
116
|
-
|
|
105
|
+
/**
|
|
106
|
+
* #2855: the preferences a guest keeps as its own, on the host under its account (the same list as web-prefs.ts
|
|
107
|
+
* GUEST_OWN_PREF_KEYS); the rest of a guest's page follows the host's look and stays on this browser.
|
|
108
|
+
*/
|
|
109
|
+
export const GUEST_OWN_PREF_KEYS = Object.freeze(["theme", "lang", "nav.favorites", "nav.sort", "nav.filter"]);
|
|
110
|
+
const GUEST_OWN = new Set(GUEST_OWN_PREF_KEYS);
|
|
111
|
+
/**
|
|
112
|
+
* This page may change a preference on the host: the signed-in app, not over the public link (#1920). A guest's page
|
|
113
|
+
* (#2855): only its own keys — over the public link too, since only its own bucket is written; no key named: none.
|
|
114
|
+
*/
|
|
115
|
+
export function prefsWritable(key) {
|
|
116
|
+
if (!initial) return false;
|
|
117
|
+
if (guestPage()) return key !== undefined && GUEST_OWN.has(key);
|
|
118
|
+
return !(typeof document !== "undefined" && document.body && document.body.dataset.publicLink === "1");
|
|
117
119
|
}
|
|
118
120
|
/**
|
|
119
121
|
* The value in force for a key, in memory — never only in this browser's storage (a full storage must not bring back
|
|
@@ -140,8 +142,6 @@ function take(body) {
|
|
|
140
142
|
if (!isView(body) || (rev !== null && body.rev < rev)) return false;
|
|
141
143
|
rev = body.rev;
|
|
142
144
|
host = hostValues(body.values);
|
|
143
|
-
const fx = ownerEffectsIn(body.values);
|
|
144
|
-
if (fx !== ownerFx) { ownerFx = fx; for (const fn of [...ownerFxListeners]) { try { fn(fx); } catch { /* the rest still hear */ } } }
|
|
145
145
|
return true;
|
|
146
146
|
}
|
|
147
147
|
|
|
@@ -153,7 +153,7 @@ export function savePref(key, value) {
|
|
|
153
153
|
if (!hostKey(key)) return;
|
|
154
154
|
if (holding && HELD_KEYS.has(key)) { hold(key, value); return; }
|
|
155
155
|
cache(key, value);
|
|
156
|
-
if (!prefsWritable()) return;
|
|
156
|
+
if (!prefsWritable(key)) return;
|
|
157
157
|
pending.set(key, value);
|
|
158
158
|
mine.set(key, value);
|
|
159
159
|
clearTimeout(flushTimer);
|
|
@@ -328,9 +328,9 @@ function apply(changed) {
|
|
|
328
328
|
* refused session or an unreadable store leaves the device unmarked, so the next load tries again.
|
|
329
329
|
*/
|
|
330
330
|
export async function migratePrefs() {
|
|
331
|
-
if (!
|
|
331
|
+
if (!initial || inStep() || !HOST_KEYS.some((key) => prefsWritable(key))) return false;
|
|
332
332
|
for (const key of HOST_KEYS) {
|
|
333
|
-
if (own(host, key)) continue;
|
|
333
|
+
if (own(host, key) || !prefsWritable(key)) continue;
|
|
334
334
|
const local = SYNCED[key].fromLocal(readLocal(SYNCED[key].ls));
|
|
335
335
|
if (local === undefined) continue;
|
|
336
336
|
let r;
|
|
@@ -7,7 +7,7 @@ import { Toasts } from "./ui-toast.js";
|
|
|
7
7
|
import { ConfirmHost, confirmDialog } from "./ui-confirm.js";
|
|
8
8
|
import { ColorDialogHost } from "./identity-color.js";
|
|
9
9
|
import { t, lang, setLang, onLang } from "./app-i18n.js";
|
|
10
|
-
import { appStore, createStore, guestBadge, guestPage, useStore } from "./app-store.js";
|
|
10
|
+
import { appStore, createStore, guestBadge, guestPage, guestTrusted, useStore } from "./app-store.js";
|
|
11
11
|
import { navStore } from "./app-nav.js";
|
|
12
12
|
import { toast } from "./ui-toast.js";
|
|
13
13
|
import { chatPath, viewPath, settingsPath, routeKey, NEEDS_PATH, fleetEntryPath } from "./app-route.js";
|
|
@@ -78,7 +78,8 @@ export const searchShortcut = () => (isMacLike() ? "⌘K" : "Ctrl K");
|
|
|
78
78
|
export function searchOffered(admin) {
|
|
79
79
|
const body = typeof document !== "undefined" ? document.body : null;
|
|
80
80
|
const offLink = !!body && body.dataset.publicLink === "1" && body.dataset.searchLink === "0";
|
|
81
|
-
|
|
81
|
+
// #2855: a trusted guest searches too (only what it can see: the server's rule).
|
|
82
|
+
return (guestPage() ? guestTrusted() : !!admin) && !offLink;
|
|
82
83
|
}
|
|
83
84
|
let searchModule = null;
|
|
84
85
|
/** Open search; `preset.instance`: narrowed to that agent (Ctrl+F in its chat). */
|
|
@@ -107,6 +108,10 @@ export function handleSearchKey(e) {
|
|
|
107
108
|
openSearch(key === "f" ? { instance: route.instance } : {});
|
|
108
109
|
}
|
|
109
110
|
|
|
111
|
+
/** The sidebar's Search row (Ctrl+K / ⌘K). */
|
|
112
|
+
const searchRow = () => html`<button type="button" class="side-row side-search" onClick=${() => openSearch()} title=${t("app.searchTitle", searchShortcut())}
|
|
113
|
+
aria-keyshortcuts=${isMacLike() ? "Meta+K" : "Control+K"}><${Icon} name="search" /><span class="grow">${t("app.search")}</span><kbd class="kbd-hint" aria-hidden="true">${searchShortcut()}</kbd></button>`;
|
|
114
|
+
|
|
110
115
|
function readCollapsed() { try { return localStorage.getItem("agend_sidebar") === "hidden"; } catch { return false; } }
|
|
111
116
|
const narrow = () => typeof matchMedia === "function" && matchMedia("(max-width: 899px)").matches;
|
|
112
117
|
|
|
@@ -350,6 +355,7 @@ function Sidebar({ route, onNewInstance, viewOnly }) {
|
|
|
350
355
|
<a class="brand" href="/ui" onClick=${closeDrawer}>${t("app.brand")}</a>
|
|
351
356
|
<button type="button" class="icon-btn side-collapse" onClick=${toggleSidebar} aria-label=${narrow() ? t("app.closeMenu") : t("app.collapse")} title=${narrow() ? t("app.closeMenu") : t("app.collapse")} aria-controls="sidebar"><${Icon} name="sidebar" /></button>
|
|
352
357
|
</div>
|
|
358
|
+
${searchOffered(false) ? html`<nav data-trust="navigation" class="side-nav" aria-label=${t("app.menu")}>${searchRow()}</nav>` : null}
|
|
353
359
|
<${InstanceNav} route=${route} items=${app.instances} loaded=${app.ready !== false} exec=${app.exec} awaiting=${app.awaiting} onPick=${closeDrawer} />
|
|
354
360
|
<div class="side-foot" data-trust="navigation">
|
|
355
361
|
<${GuestChip} />
|
|
@@ -372,8 +378,7 @@ function Sidebar({ route, onNewInstance, viewOnly }) {
|
|
|
372
378
|
<nav data-trust="navigation" class="side-nav" aria-label=${t("app.menu")}>
|
|
373
379
|
${overview}
|
|
374
380
|
${navLink("needs", NEEDS_PATH, "inbox", t("app.needsNav"), needsCount)}
|
|
375
|
-
${searchOffered(canCreate) ?
|
|
376
|
-
aria-keyshortcuts=${isMacLike() ? "Meta+K" : "Control+K"}><${Icon} name="search" /><span class="grow">${t("app.search")}</span><kbd class="kbd-hint" aria-hidden="true">${searchShortcut()}</kbd></button>` : null}
|
|
381
|
+
${searchOffered(canCreate) ? searchRow() : null}
|
|
377
382
|
${canCreate && onNewInstance ? html`<button type="button" class="side-row side-new" onClick=${onNewInstance}><${Icon} name="plus" /><span class="grow">${t("app.newAgent")}</span></button>` : null}
|
|
378
383
|
</nav>
|
|
379
384
|
${shell.side ? html`<${shell.side.Component} />` : html`<${InstanceNav} route=${route} items=${app.instances} loaded=${app.ready !== false}
|
|
@@ -393,8 +398,7 @@ function GuestChip() {
|
|
|
393
398
|
const { name, platform } = guestBadge();
|
|
394
399
|
const where = PLATFORM_NAMES[platform] || platform;
|
|
395
400
|
const label = name ? (where ? t("app.guestAsOn", name, where) : t("app.guestAs", name)) : t("app.guestChip");
|
|
396
|
-
// Inside .side-foot (a marked
|
|
397
|
-
// refuses any element it does not know, which would hide a guest's static effect posters (#2101 review).
|
|
401
|
+
// Inside .side-foot (a marked element): no trust marker of its own (#2101 review).
|
|
398
402
|
return html`<p class="guest-chip" title=${t("app.guestHint")}><${Icon} name="user" size=${14} /><span class="grow">${label}</span></p>`;
|
|
399
403
|
}
|
|
400
404
|
|
|
@@ -781,7 +785,7 @@ export function Shell({ panels, onNewInstance, viewOnly = false }) {
|
|
|
781
785
|
document.removeEventListener("compositionstart", start, true); document.removeEventListener("compositionend", end, true);
|
|
782
786
|
};
|
|
783
787
|
}, []);
|
|
784
|
-
const guest = guestPage(); // #1920: a guest's page — no operations,
|
|
788
|
+
const guest = guestPage(); // #1920: a guest's page — no operations, pending changes or owner CSS (#2855: usage, yes)
|
|
785
789
|
const cls = ["shell", shell.collapsed ? "sb-collapsed" : "", shell.drawer ? "sb-open" : "", shell.keyboard ? "kb-open" : "", guest ? "guest" : ""].filter(Boolean).join(" ");
|
|
786
790
|
return html`<div ref=${root} class=${cls}>
|
|
787
791
|
<a data-trust="navigation" class="skip" href="#main">${t("app.skip")}</a>
|
|
@@ -795,7 +799,7 @@ export function Shell({ panels, onNewInstance, viewOnly = false }) {
|
|
|
795
799
|
${viewOnly || guest || !shell.banner ? null : html`<${shell.banner} />`}
|
|
796
800
|
<${Outlet} route=${nav.route} seq=${nav.seq} panels=${panels} />
|
|
797
801
|
</main>
|
|
798
|
-
|
|
802
|
+
<${RightRail} route=${nav.route} />
|
|
799
803
|
<${BottomTabs} route=${nav.route} viewOnly=${viewOnly} />
|
|
800
804
|
${shell.dialog ? html`<${shell.dialog.Component} ...${shell.dialog.props} onClose=${closeDialog} />` : null}
|
|
801
805
|
${viewOnly || guest || (nav.route && nav.route.panel === "settings") ? null : html`<${PendingChanges} />`}
|
|
@@ -24,7 +24,7 @@ export function createStore(initial) {
|
|
|
24
24
|
* (app-stream.js catchUp) — "catching" / "retrying" / "failed" / "ok"; `retryHydration` asks for it again.
|
|
25
25
|
* `connections`: how many chat connections the fleet has (#1519 P7: 0 is web-only — the first-run card); null until known. */
|
|
26
26
|
export const appStore = createStore({ ready: false, instances: [], uptime: 0, exec: {}, awaiting: {}, connection: "connecting", needs: [], connections: null,
|
|
27
|
-
hydration: "none", retryHydration: null
|
|
27
|
+
hydration: "none", retryHydration: null });
|
|
28
28
|
|
|
29
29
|
/**
|
|
30
30
|
* #1920 phase 3: this page is a guest's — the server says so on <body> (data-tier="guest"). A guest sees only its own
|
|
@@ -34,6 +34,10 @@ export const appStore = createStore({ ready: false, instances: [], uptime: 0, ex
|
|
|
34
34
|
export function guestPage() {
|
|
35
35
|
return typeof document !== "undefined" && !!document.body && document.body.dataset.tier === "guest";
|
|
36
36
|
}
|
|
37
|
+
/** #2855: this guest's access is Trusted — its page offers View (read-only) and search (the server's, on <body>). */
|
|
38
|
+
export function guestTrusted() {
|
|
39
|
+
return guestPage() && document.body.dataset.guestLevel === "trusted";
|
|
40
|
+
}
|
|
37
41
|
/** Who a guest's page is signed in as: its web name and its platform (the server's, on <body>). */
|
|
38
42
|
export function guestBadge() {
|
|
39
43
|
const d = typeof document !== "undefined" && document.body ? document.body.dataset : {};
|
|
@@ -49,7 +53,7 @@ export function applyStatus(d) {
|
|
|
49
53
|
exec[i.name] = (i.execution_state !== undefined ? i.execution_state : i.state) ?? null;
|
|
50
54
|
if (i.state === "awaiting_input") awaiting[i.name] = typeof i.interaction_summary === "string" ? i.interaction_summary : "";
|
|
51
55
|
}
|
|
52
|
-
appStore.set({
|
|
56
|
+
appStore.set({ ready: true, instances: d.instances, uptime: d.uptime ?? 0, exec, awaiting,
|
|
53
57
|
startup: d.startup?.phase === "starting" ? d.startup : null,
|
|
54
58
|
connections: typeof d.connections === "number" ? d.connections : null,
|
|
55
59
|
// #1665: the host preferences' revision (app-prefs.js re-reads them when it moves).
|