@songsid/agend 2.2.0 → 2.3.0-beta.1
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 +30 -0
- package/dist/agent-file-read.js +129 -0
- package/dist/agent-file-read.js.map +1 -0
- package/dist/agent-whats-new.js +13 -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/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/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 +34 -0
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/channel/types.d.ts +15 -0
- package/dist/config-validator.js +17 -1
- 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 +89 -0
- package/dist/fleet-manager.js +401 -25
- package/dist/fleet-manager.js.map +1 -1
- package/dist/general-knowledge/skills/web-appearance/SKILL.md +258 -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 +5 -0
- package/dist/outbound-handlers.js +30 -9
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.d.ts +64 -0
- package/dist/outbound-schemas.js +50 -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 +58 -1
- package/dist/scheduler/scheduler.js +176 -10
- package/dist/scheduler/scheduler.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.js +50 -0
- 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/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 +214 -1
- 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 +22 -14
- 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/view-strings.js +30 -0
- package/dist/ui/signin.html +1 -0
- 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 +896 -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 +98 -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 +312 -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 +126 -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 +29 -0
- package/dist/web-effect-upload.js +190 -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 +89 -0
- package/dist/web-effect.js +132 -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 +203 -0
- package/dist/web-mod-agent.js.map +1 -0
- package/dist/web-mod-bundle.d.ts +144 -0
- package/dist/web-mod-bundle.js +464 -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 +624 -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
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
// #1629: host-authoritative personal choices. No local cache, migration, frame, code fetch or automatic resend.
|
|
2
|
+
import { createStore, appStore } from "./app-store.js";
|
|
3
|
+
import { OWNER_EFFECT_PREF_KEYS } from "./app-prefs.js";
|
|
4
|
+
import { checkEffectOwnerSlots } from "./effect-runtime-loader.js";
|
|
5
|
+
const KEYS = OWNER_EFFECT_PREF_KEYS, ID = /^[a-f0-9]{32}$/, EPOCH = /^[a-f0-9]{16}$/;
|
|
6
|
+
const plain = x => !!x && typeof x === "object" && Object.getPrototypeOf(x) === Object.prototype;
|
|
7
|
+
const own = (x, k) => Object.hasOwn(x, k);
|
|
8
|
+
const unknownSlots = () => Object.fromEntries(KEYS.map(k => [k, { status: "unknown", id: null }]));
|
|
9
|
+
const publicPage = () => typeof document === "undefined" || document.body?.dataset.publicLink === "1" || document.body?.dataset.mode === "view-only";
|
|
10
|
+
function bootScope() {
|
|
11
|
+
try {
|
|
12
|
+
const x = JSON.parse(document.documentElement.getAttribute("data-prefs") || "null");
|
|
13
|
+
return plain(x) && ["person", "operator"].includes(x.ownerScope) && typeof x.ownerEpoch === "string" && EPOCH.test(x.ownerEpoch)
|
|
14
|
+
? Object.freeze({ scope: x.ownerScope, epoch: x.ownerEpoch }) : null;
|
|
15
|
+
} catch { return null; }
|
|
16
|
+
}
|
|
17
|
+
const owner = bootScope();
|
|
18
|
+
export const ownerEffectsStore = createStore({ scope: owner?.scope ?? "none", status: "unknown", revoked: false, busy: false, slots: unknownSlots(), error: null });
|
|
19
|
+
let watchers = 0, generation = 0, active = null, queuedRead = false, unsubscribe = null, rev = -1, seen = null, failedAt = -Infinity;
|
|
20
|
+
const clock = () => { try { const n = performance.now(); return Number.isFinite(n) && n >= 0 ? n : null; } catch { return null; } };
|
|
21
|
+
const eligible = () => !!owner && watchers > 0 && !publicPage() && !ownerEffectsStore.get().revoked;
|
|
22
|
+
const current = op => eligible() && op.generation === generation && active === op;
|
|
23
|
+
function revoke() {
|
|
24
|
+
queuedRead = false;
|
|
25
|
+
ownerEffectsStore.set({ status: "unknown", revoked: true, slots: unknownSlots(), error: "sign_in_changed" });
|
|
26
|
+
}
|
|
27
|
+
function unknown(error) { const n = clock(); failedAt = n === null ? Infinity : n; ownerEffectsStore.set({ status: "unknown", slots: unknownSlots(), error }); }
|
|
28
|
+
function claim() {
|
|
29
|
+
if (!eligible() || active) return null;
|
|
30
|
+
const controller = new AbortController(), op = { controller, generation };
|
|
31
|
+
active = op; ownerEffectsStore.set({ busy: true, error: null }); return op;
|
|
32
|
+
}
|
|
33
|
+
async function call(op, path, init = {}) {
|
|
34
|
+
const started = clock();
|
|
35
|
+
if (started === null || op.controller.signal.aborted) return { ok: false, status: 0, body: null };
|
|
36
|
+
const timer = setTimeout(() => op.controller.abort(), 10000);
|
|
37
|
+
try {
|
|
38
|
+
const res = await fetch(path, { credentials: "same-origin", ...init, signal: op.controller.signal });
|
|
39
|
+
let body = null; try { body = await res.json(); } catch { /* unknown */ }
|
|
40
|
+
const ended = clock();
|
|
41
|
+
if (op.controller.signal.aborted || ended === null || ended < started || ended - started >= 10000) return { ok: false, status: 0, body: null };
|
|
42
|
+
return { ok: res.ok, status: res.status, body };
|
|
43
|
+
} catch { return { ok: false, status: 0, body: null }; }
|
|
44
|
+
finally { clearTimeout(timer); }
|
|
45
|
+
}
|
|
46
|
+
function finish(op) {
|
|
47
|
+
if (active !== op) return;
|
|
48
|
+
active = null; ownerEffectsStore.set({ busy: false });
|
|
49
|
+
const again = queuedRead; queuedRead = false;
|
|
50
|
+
const n = clock();
|
|
51
|
+
if (again && eligible() && n !== null && n - failedAt >= 5000) refreshOwnerEffects();
|
|
52
|
+
}
|
|
53
|
+
/** An owner response is closed and bounded. Unavailable means a stored id, not an empty slot or private manifest. */
|
|
54
|
+
export function checkOwnerSlots(body) { return checkEffectOwnerSlots(body); }
|
|
55
|
+
|
|
56
|
+
async function read(op) {
|
|
57
|
+
const r = await call(op, `/ui/effects/owner?ownerEpoch=${owner.epoch}`);
|
|
58
|
+
if (!current(op)) return false;
|
|
59
|
+
if ([401, 403, 409].includes(r.status)) { revoke(); return false; }
|
|
60
|
+
const data = r.ok ? checkOwnerSlots(r.body) : null;
|
|
61
|
+
if (!data) { unknown("unreadable"); return false; }
|
|
62
|
+
if (data.rev < rev) { unknown("stale"); return false; }
|
|
63
|
+
rev = data.rev; failedAt = -Infinity;
|
|
64
|
+
ownerEffectsStore.set({ status: "known", slots: data.slots, error: null }); return true;
|
|
65
|
+
}
|
|
66
|
+
export async function refreshOwnerEffects() {
|
|
67
|
+
if (!eligible()) return false;
|
|
68
|
+
if (active) { queuedRead = true; return false; }
|
|
69
|
+
const op = claim(); if (!op) return false;
|
|
70
|
+
try { return await read(op); } finally { finish(op); }
|
|
71
|
+
}
|
|
72
|
+
/** One physical operation survives until settlement, including after abort. At most one coalesced read follows. */
|
|
73
|
+
export function watchOwnerEffects() {
|
|
74
|
+
watchers++;
|
|
75
|
+
if (watchers === 1 && eligible()) {
|
|
76
|
+
const check = () => {
|
|
77
|
+
const next = appStore.get().prefsRev, n = clock();
|
|
78
|
+
if (next !== seen || ownerEffectsStore.get().status === "unknown" && n !== null && n - failedAt >= 5000) { seen = next; refreshOwnerEffects(); }
|
|
79
|
+
};
|
|
80
|
+
unsubscribe = appStore.subscribe(check); check();
|
|
81
|
+
}
|
|
82
|
+
let ended = false;
|
|
83
|
+
return () => {
|
|
84
|
+
if (ended) return; ended = true;
|
|
85
|
+
if (--watchers > 0) return;
|
|
86
|
+
generation++; queuedRead = false; unsubscribe?.(); unsubscribe = null; active?.controller.abort();
|
|
87
|
+
ownerEffectsStore.set({ status: "unknown", slots: unknownSlots(), error: null });
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
export async function saveOwnerEffects(set) {
|
|
91
|
+
if (!plain(set) || !Object.keys(set).length || Object.keys(set).some(k => !KEYS.includes(k) || set[k] !== null && (typeof set[k] !== "string" || !ID.test(set[k])))) return { ok: false, error: "invalid" };
|
|
92
|
+
// Snapshot both data and immutable boot context before the first wait. No optimistic runtime selection.
|
|
93
|
+
const values = { ...set }, op = claim(); if (!op) return { ok: false, error: "busy_or_unavailable" };
|
|
94
|
+
let saved = false;
|
|
95
|
+
try {
|
|
96
|
+
const r = await call(op, "/ui/prefs", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ set: values, ownerEpoch: owner.epoch }) });
|
|
97
|
+
if (!current(op)) return { ok: false, error: "superseded" };
|
|
98
|
+
if ([401, 403, 409].includes(r.status)) { revoke(); return { ok: false, error: "sign_in_changed" }; }
|
|
99
|
+
if (r.ok && plain(r.body) && (r.body.ownerEpoch !== owner.epoch || r.body.ownerScope !== owner.scope)) { revoke(); return { ok: false, error: "sign_in_changed" }; }
|
|
100
|
+
if (!r.ok || !plain(r.body) || !plain(r.body.values) || !Number.isSafeInteger(r.body.rev) || r.body.rev < 0 || r.body.rev < rev || r.body.unreadable) { unknown("not_saved"); return { ok: false, error: "not_saved" }; }
|
|
101
|
+
if (!Object.entries(values).every(([k, v]) => v === null ? !own(r.body.values, k) : r.body.values[k] === v)) { unknown("not_saved"); return { ok: false, error: "not_saved" }; }
|
|
102
|
+
saved = true;
|
|
103
|
+
const refreshed = await read(op); return { ok: refreshed, saved, error: refreshed ? null : "refresh_failed" };
|
|
104
|
+
} finally { finish(op); }
|
|
105
|
+
}
|
|
106
|
+
/** Runtime adapter asks only for the exact, currently known selection; a cursor can never request Worker source. */
|
|
107
|
+
export function ownerEffectUrl(key, id, kind = "resource") {
|
|
108
|
+
const row = ownerEffectsStore.get().slots[key];
|
|
109
|
+
if (!eligible() || ownerEffectsStore.get().status !== "known" || !KEYS.includes(key) || !row || row.status !== "available" || row.id !== id || !["resource", "poster", "cursor"].includes(kind) || kind === "resource" && key === "fx.cursor") return null;
|
|
110
|
+
const path = kind === "resource" ? `/ui/effects/resource/${id}` : `/ui/effects/image/${id}/${kind}.png`;
|
|
111
|
+
return `${path}?slot=${encodeURIComponent(key)}&ownerEpoch=${owner.epoch}`;
|
|
112
|
+
}
|
|
113
|
+
export function markOwnerEffectUnavailable(key, id) {
|
|
114
|
+
const s = ownerEffectsStore.get(), row = s.slots[key];
|
|
115
|
+
if (!eligible() || s.status !== "known" || !KEYS.includes(key) || row?.id !== id) return;
|
|
116
|
+
ownerEffectsStore.set({ slots: { ...s.slots, [key]: { status: "unavailable", id } } });
|
|
117
|
+
}
|
|
@@ -27,6 +27,9 @@ import { Dialog } from "./ui-dialog.js";
|
|
|
27
27
|
import { Empty, ErrorState, Skeleton } from "./ui-states.js";
|
|
28
28
|
import { Icon } from "./ui-icons.js";
|
|
29
29
|
import "./view-strings.js";
|
|
30
|
+
import { checkCard, fitPicture, IdentityCard, useCard } from "./identity-card.js";
|
|
31
|
+
import { confirmDialog } from "./ui-confirm.js";
|
|
32
|
+
import { openColorDialog } from "./identity-color.js";
|
|
30
33
|
|
|
31
34
|
/** view.* text with named values: tn("paneFailed", { name, status }). */
|
|
32
35
|
function tn(key, values = {}) {
|
|
@@ -180,15 +183,15 @@ export function ViewPanel({ route, navKey }) {
|
|
|
180
183
|
<button type="button" class="icon-btn" onClick=${() => setDialog("help")} aria-label=${tn("helpButton")} title=${tn("helpButton")}><${Icon} name="info" /></button>`;
|
|
181
184
|
|
|
182
185
|
let body;
|
|
183
|
-
if (v.error && !v.roster.length) body = html`<div class="panel-body center"><${ErrorState} message=${tn("rosterFailedShort")} onRetry=${() => navigate(location.pathname, { replace: true })} /></div>`;
|
|
184
|
-
else if (!v.loaded) body = html`<div class="panel-body"><${Skeleton} lines=${6} /></div>`;
|
|
185
|
-
else if (!v.roster.length) body = html`<div class="panel-body center"><${Empty} icon="view" title=${tn("noInstances")} /></div>`;
|
|
186
|
-
else if (!name) body = html`<div class="panel-body"><${Skeleton} lines=${6} /></div>`;
|
|
187
|
-
else if (!it) body = html`<div class="panel-body center"><${Empty} icon="alert" title=${tn("notFound", { name })} hint=${tn("notFoundHint")} /></div>`;
|
|
186
|
+
if (v.error && !v.roster.length) body = html`<div data-trust="panel-content" class="panel-body center"><${ErrorState} message=${tn("rosterFailedShort")} onRetry=${() => navigate(location.pathname, { replace: true })} /></div>`;
|
|
187
|
+
else if (!v.loaded) body = html`<div data-trust="panel-content" class="panel-body"><${Skeleton} lines=${6} /></div>`;
|
|
188
|
+
else if (!v.roster.length) body = html`<div data-trust="panel-content" class="panel-body center"><${Empty} icon="view" title=${tn("noInstances")} /></div>`;
|
|
189
|
+
else if (!name) body = html`<div data-trust="panel-content" class="panel-body"><${Skeleton} lines=${6} /></div>`;
|
|
190
|
+
else if (!it) body = html`<div data-trust="panel-content" class="panel-body center"><${Empty} icon="alert" title=${tn("notFound", { name })} hint=${tn("notFoundHint")} /></div>`;
|
|
188
191
|
else body = html`<${Terminal} key=${name} name=${name} lease=${lease} density=${density} />
|
|
189
192
|
<${Card} it=${it} viewOnly=${viewOnly} onEdit=${() => setDialog("edit")} />`;
|
|
190
193
|
|
|
191
|
-
return html`<div class="panel p-view">
|
|
194
|
+
return html`<div data-effect-panel="view" class="panel p-view">
|
|
192
195
|
<${PanelHeader} title=${it ? (it.display_name || it.instance_name) : tn("title")}
|
|
193
196
|
sub=${it ? html`<span class="status"><span class=${`dot ${it.status === "running" ? "ok" : it.status === "crashed" ? "bad" : "off"}`} aria-hidden="true"></span>${tn(STATUS_KEYS[it.status] || "statusUnknown")}</span>` : null}
|
|
194
197
|
nav=${it && !viewOnly ? html`<${InstanceSwitch} name=${name} current="view" />` : null}>${actions}</${PanelHeader}>
|
|
@@ -266,7 +269,7 @@ function Terminal({ name, lease, density }) {
|
|
|
266
269
|
lease.hold(() => ro.disconnect());
|
|
267
270
|
}
|
|
268
271
|
}, [lease, name]);
|
|
269
|
-
return html`<div class="v-term" ref=${term}>${problem ? html`<p class="v-term-note" role="status">${problem}</p>` : null}<pre ref=${pre} class="v-pre"></pre></div>`;
|
|
272
|
+
return html`<div data-trust="panel-content" class="v-term" ref=${term}>${problem ? html`<p class="v-term-note" role="status">${problem}</p>` : null}<pre ref=${pre} class="v-pre"></pre></div>`;
|
|
270
273
|
}
|
|
271
274
|
|
|
272
275
|
// The advance of one monospace cell as a fraction of font-size, measured once on this page.
|
|
@@ -296,7 +299,9 @@ function Card({ it, viewOnly, onEdit }) {
|
|
|
296
299
|
const initial = (it.display_name || it.instance_name || "?").trim().charAt(0) || "?";
|
|
297
300
|
const ctx = it.context_pct != null ? ` · ctx ${Math.round(it.context_pct)}%` : "";
|
|
298
301
|
const toggle = () => { const next = !open; setOpen(next); store("agend_view_card_expanded", next ? "1" : "0"); };
|
|
299
|
-
|
|
302
|
+
// #1629 r4 §1: the identity card, above the profile row while it is expanded.
|
|
303
|
+
const card = useCard(it.instance_name, it.card_v);
|
|
304
|
+
return html`${open && card ? html`<div data-trust="panel-content" class="v-id"><${IdentityCard} name=${it.instance_name} card=${card} /></div>` : null}<section data-trust="panel-content" class=${`v-card${open ? " open" : ""}`} aria-label=${it.display_name || it.instance_name}>
|
|
300
305
|
<div class="v-avatar">${it.has_avatar ? html`<img ref=${avatar} alt="" />` : html`<span class="v-initial" aria-hidden="true">${initial}</span>`}</div>
|
|
301
306
|
<div class="v-card-body">
|
|
302
307
|
<div class="v-card-line"><strong>${it.display_name || it.instance_name}</strong>${it.role ? html`<span class="v-role">${it.role}</span>` : null}</div>
|
|
@@ -320,6 +325,28 @@ function EditProfile({ it, onClose }) {
|
|
|
320
325
|
// #1630 follow-up: the uploaded picture is removed on Save (Cancel keeps it); a newly picked file wins over removing.
|
|
321
326
|
const [removePic, setRemovePic] = useState(false);
|
|
322
327
|
const file = useRef(null);
|
|
328
|
+
// #1629 r4 §1 / r4.1: the identity card's own fields — this person's (theirs win over the agent's, field by field).
|
|
329
|
+
// Read when the dialog opens: what is drawn now, whose each field is, and the upload limits in force.
|
|
330
|
+
const [card, setCard] = useState(null);
|
|
331
|
+
const [cardText, setCardText] = useState({ tagline: "", bio: "" });
|
|
332
|
+
const [removeCard, setRemoveCard] = useState({ portrait: false, banner: false });
|
|
333
|
+
const [clearCard, setClearCard] = useState(false);
|
|
334
|
+
const pics = { portrait: useRef(null), banner: useRef(null) };
|
|
335
|
+
useEffect(() => {
|
|
336
|
+
let live = true;
|
|
337
|
+
// The card's fields stay disabled until this read is in (#1889 review: a draft typed meanwhile was overwritten);
|
|
338
|
+
// a failed read lets them be edited, starting empty.
|
|
339
|
+
const none = { mine: { tagline: null, bio: null, portrait: null, banner: null }, limits: null };
|
|
340
|
+
lease.fetch(`/api/card/${encodeURIComponent(it.instance_name)}`).then((r) => (r.ok ? r.json() : null)).then((body) => {
|
|
341
|
+
if (!live || !lease.current()) return;
|
|
342
|
+
if (!body) { setCard(none); return; }
|
|
343
|
+
const resolved = checkCard(it.instance_name, body);
|
|
344
|
+
const mine = (k) => (resolved && resolved[k] && resolved[k].by === "human" ? resolved[k] : null);
|
|
345
|
+
setCard({ mine: { tagline: mine("tagline"), bio: mine("bio"), portrait: mine("portrait"), banner: mine("banner") }, limits: body.limits || null });
|
|
346
|
+
setCardText({ tagline: mine("tagline") ? mine("tagline").text : "", bio: mine("bio") ? mine("bio").text : "" });
|
|
347
|
+
}).catch(() => { if (live && lease.current()) setCard(none); });
|
|
348
|
+
return () => { live = false; };
|
|
349
|
+
}, []);
|
|
323
350
|
async function save() {
|
|
324
351
|
if (busy) return;
|
|
325
352
|
setBusy(true); setMsg(null);
|
|
@@ -329,7 +356,27 @@ function EditProfile({ it, onClose }) {
|
|
|
329
356
|
const draft = { display_name: f.display, role: f.role, description: f.desc };
|
|
330
357
|
const picked = file.current && file.current.files && file.current.files[0];
|
|
331
358
|
const removing = removePic;
|
|
359
|
+
// The card: Clear card wins over everything else in it; otherwise only the fields that changed are sent.
|
|
360
|
+
const clearing = clearCard;
|
|
361
|
+
const before = card && card.mine ? { tagline: card.mine.tagline ? card.mine.tagline.text : "", bio: card.mine.bio ? card.mine.bio.text : "" } : { tagline: "", bio: "" };
|
|
362
|
+
if (!clearing) for (const k of ["tagline", "bio"]) if (cardText[k] !== before[k]) draft[`card_${k}`] = cardText[k];
|
|
363
|
+
const cardPics = {};
|
|
332
364
|
try {
|
|
365
|
+
// A picture too large is fitted first (after asking) — before anything is written, so declining writes nothing.
|
|
366
|
+
if (!clearing) for (const slot of ["portrait", "banner"]) {
|
|
367
|
+
const chosen = pics[slot].current && pics[slot].current.files && pics[slot].current.files[0];
|
|
368
|
+
if (chosen) {
|
|
369
|
+
const limit = card && card.limits && card.limits[slot];
|
|
370
|
+
let blob = chosen;
|
|
371
|
+
if (limit) {
|
|
372
|
+
try { blob = await fitPicture(chosen, limit, (w, h, nw, nh) => confirmDialog({ message: tn("resizeAsk", { w, h, nw, nh }), confirmLabel: tn("resizeGo") })); }
|
|
373
|
+
catch { throw new Error(tn("pictureTooLarge")); }
|
|
374
|
+
}
|
|
375
|
+
if (!lease.current()) return;
|
|
376
|
+
if (!blob) { setBusy(false); return; }
|
|
377
|
+
cardPics[slot] = blob;
|
|
378
|
+
} else if (removeCard[slot]) cardPics[slot] = "remove";
|
|
379
|
+
}
|
|
333
380
|
const r = await fetch(`/api/profile/${name}`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(draft) });
|
|
334
381
|
if (!lease.current()) return;
|
|
335
382
|
if (r.status === 401) throw new Error(tn("signInToSave"));
|
|
@@ -342,6 +389,18 @@ function EditProfile({ it, onClose }) {
|
|
|
342
389
|
if (!dr.ok) { const e = await dr.json().catch(() => ({})); throw new Error(tn("avatarRemoveFailed", { error: e.error || dr.status })); }
|
|
343
390
|
}
|
|
344
391
|
if (!lease.current()) return;
|
|
392
|
+
if (clearing) {
|
|
393
|
+
const cr = await fetch(`/api/card/${name}`, { method: "DELETE" });
|
|
394
|
+
if (!cr.ok) { const e = await cr.json().catch(() => ({})); throw new Error(tn("cardFailed", { error: e.error || cr.status })); }
|
|
395
|
+
}
|
|
396
|
+
for (const [slot, what] of Object.entries(cardPics)) {
|
|
397
|
+
if (!lease.current()) return;
|
|
398
|
+
const pr = what === "remove"
|
|
399
|
+
? await fetch(`/api/card/${name}/${slot}`, { method: "DELETE" })
|
|
400
|
+
: await fetch(`/api/card/${name}/${slot}`, { method: "POST", headers: { "Content-Type": what.type || "application/octet-stream" }, body: what });
|
|
401
|
+
if (!pr.ok) { const e = await pr.json().catch(() => ({})); throw new Error(tn("cardFailed", { error: e.detail || e.error || pr.status })); }
|
|
402
|
+
}
|
|
403
|
+
if (!lease.current()) return;
|
|
345
404
|
setBusy(false);
|
|
346
405
|
onClose();
|
|
347
406
|
} catch (e) { if (lease.current()) { setBusy(false); setMsg(e.message); } }
|
|
@@ -356,6 +415,20 @@ function EditProfile({ it, onClose }) {
|
|
|
356
415
|
<label class="field"><span>${tn("avatar")}</span><input ref=${file} type="file" accept="image/png,image/jpeg,image/gif,image/webp" /></label>
|
|
357
416
|
${it.has_avatar ? html`<div class="field-row"><button type="button" class="btn btn-sm" aria-pressed=${removePic ? "true" : "false"}
|
|
358
417
|
onClick=${() => setRemovePic(!removePic)}>${tn(removePic ? "keepPicture" : "removePicture")}</button>${removePic ? html`<span class="muted">${tn("removePictureNote")}</span>` : null}</div>` : null}
|
|
418
|
+
<div class="field-row"><button type="button" class="btn btn-sm" onClick=${() => openColorDialog(it.instance_name, (appStore.get().instances || []).find((i) => i.name === it.instance_name))}>${t("color.menu")}</button></div>
|
|
419
|
+
<h3 class="form-sub">${tn("cardSection")}</h3>
|
|
420
|
+
<p class="note">${tn("cardLead")}</p>
|
|
421
|
+
<fieldset class="form" disabled=${clearCard || !card} aria-busy=${card ? "false" : "true"}>
|
|
422
|
+
<label class="field"><span>${tn("cardTagline")}</span><input value=${cardText.tagline} onInput=${(e) => setCardText({ ...cardText, tagline: e.target.value })} /></label>
|
|
423
|
+
<label class="field"><span>${tn("cardBio")}</span><textarea rows="4" value=${cardText.bio} onInput=${(e) => setCardText({ ...cardText, bio: e.target.value })}></textarea></label>
|
|
424
|
+
${["portrait", "banner"].map((slot) => html`<div key=${slot} class="form">
|
|
425
|
+
<label class="field"><span>${tn(slot === "portrait" ? "cardPortrait" : "cardBanner")}</span><input ref=${pics[slot]} type="file" accept="image/png,image/jpeg,image/webp" /></label>
|
|
426
|
+
${card && card.mine && card.mine[slot] ? html`<div class="field-row"><button type="button" class="btn btn-sm" aria-pressed=${removeCard[slot] ? "true" : "false"}
|
|
427
|
+
onClick=${() => setRemoveCard({ ...removeCard, [slot]: !removeCard[slot] })}>${tn(removeCard[slot] ? "cardKeep" : "cardRemove")}</button></div>` : null}
|
|
428
|
+
</div>`)}
|
|
429
|
+
</fieldset>
|
|
430
|
+
<div class="field-row"><button type="button" class="btn btn-sm" aria-pressed=${clearCard ? "true" : "false"} onClick=${() => setClearCard(!clearCard)}>${tn(clearCard ? "keepCard" : "clearCard")}</button>
|
|
431
|
+
${clearCard ? html`<span class="muted">${tn("clearCardNote")}</span>` : null}</div>
|
|
359
432
|
</fieldset></form>
|
|
360
433
|
${msg ? html`<p class="err" role="alert">${msg}${msg === tn("signInToSave") ? html` <a class="link" href=${signInHref()}>${t("app.signIn")}</a>` : null}</p>` : null}
|
|
361
434
|
</${Dialog}>`;
|
package/dist/ui/shared/signin.js
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
label: "One-time code", submit: "Sign in", working: "Signing in…",
|
|
16
16
|
hint: "No code? Send /web in General, then press a sign-in button to get one.",
|
|
17
17
|
nobot: "No chat bot?", nobot_body: "Ask the operator to run agend web --code on the AgEnD computer.",
|
|
18
|
+
safe: "Page looks wrong after signing in?", safe_body: "Your custom CSS may be hiding something. Add ?safe=1 to the end of the address to load the page without it, then fix it or turn it off in Settings → Web appearance.",
|
|
18
19
|
refused: "The code is invalid or no longer works. Send /web in General, then press a sign-in button for a new code.",
|
|
19
20
|
unconfirmed: "Sign-in could not be confirmed. Get a new code and try again; if it keeps failing, contact the operator.",
|
|
20
21
|
paused: "Too many wrong codes — sign-in is paused for a few minutes.",
|
|
@@ -28,6 +29,7 @@
|
|
|
28
29
|
label: "一次性登入碼", submit: "登入", working: "登入中…",
|
|
29
30
|
hint: "沒收到?在 General 傳 /web,再按登入按鈕取碼。",
|
|
30
31
|
nobot: "沒有聊天 bot?", nobot_body: "請管理員在 AgEnD 電腦執行 agend web --code。",
|
|
32
|
+
safe: "登入後畫面不對勁?", safe_body: "可能是你的自訂 CSS 把東西藏起來了。在網址最後加上 ?safe=1,就會不套用它地載入;再到「設定 → 網頁外觀」修正或關閉。",
|
|
31
33
|
refused: "登入碼無效或已失效。請在 General 傳 /web,再按登入按鈕取新碼。",
|
|
32
34
|
unconfirmed: "登入暫時無法確認。請重新取碼再試;若仍失敗,請聯絡管理員。",
|
|
33
35
|
paused: "錯誤次數過多,登入暫停幾分鐘。",
|
|
@@ -35,7 +35,7 @@ export function Dialog({ title, onClose, actions, children, busy = false, wide =
|
|
|
35
35
|
d.addEventListener("click", onClick);
|
|
36
36
|
return () => { d.removeEventListener("cancel", onCancel); d.removeEventListener("click", onClick); };
|
|
37
37
|
}, [onClose]);
|
|
38
|
-
return html`<dialog ref=${ref} class=${`dlg${wide ? " dlg-wide" : ""}`} aria-label=${label || title}>
|
|
38
|
+
return html`<dialog data-trust="dialog" ref=${ref} class=${`dlg${wide ? " dlg-wide" : ""}`} aria-label=${label || title}>
|
|
39
39
|
<div class="dlg-box">
|
|
40
40
|
<div class="dlg-head"><h2 class="dlg-title">${title}</h2>
|
|
41
41
|
<button type="button" class="icon-btn dlg-x" aria-label=${t("app.close")} title=${t("app.close")} onClick=${() => { if (!busyRef.current) onClose(); }}><${Icon} name="close" /></button></div>
|
|
@@ -54,6 +54,8 @@ const P = {
|
|
|
54
54
|
panel: () => html`<rect x="3" y="4" width="18" height="16" rx="2"/><path d="M14 4v16"/>`,
|
|
55
55
|
back: () => html`<path d="M19 12H5M12 19l-7-7 7-7"/>`,
|
|
56
56
|
forward: () => html`<path d="M5 12h14M12 5l7 7-7 7"/>`,
|
|
57
|
+
// #1629: Settings → Appearance.
|
|
58
|
+
palette: () => html`<path d="M12 3a9 9 0 1 0 0 18c1.1 0 1.7-.8 1.7-1.7 0-.5-.2-.9-.5-1.2-.3-.3-.5-.7-.5-1.2 0-.9.8-1.7 1.7-1.7H16a5 5 0 0 0 5-5c0-4-4-7.2-9-7.2z"/><circle cx="7.5" cy="10.5" r="1"/><circle cx="10.5" cy="7" r="1"/><circle cx="15" cy="7.5" r="1"/>`,
|
|
57
59
|
book: () => html`<path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20V3H6.5A2.5 2.5 0 0 0 4 5.5z"/><path d="M4 19.5A2.5 2.5 0 0 0 6.5 22H20v-5"/>`,
|
|
58
60
|
external: () => html`<path d="M14 4h6v6M20 4l-9 9"/><path d="M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"/>`,
|
|
59
61
|
};
|
|
@@ -31,7 +31,7 @@ export function Menu({ items, label, icon = "more", cls = "" }) {
|
|
|
31
31
|
return html`<div class=${`menu ${cls}`}>
|
|
32
32
|
<button ref=${btn} type="button" class="icon-btn" aria-haspopup="menu" aria-expanded=${open ? "true" : "false"}
|
|
33
33
|
aria-label=${label || t("app.more")} title=${label || t("app.more")} onClick=${() => setOpen(!open)}><${Icon} name=${icon} /></button>
|
|
34
|
-
${open ? html`<div ref=${list} class="menu-list" role="menu" aria-label=${label || t("app.more")} onKeyDown=${onKey}>
|
|
34
|
+
${open ? html`<div data-trust="menu" ref=${list} class="menu-list" role="menu" aria-label=${label || t("app.more")} onKeyDown=${onKey}>
|
|
35
35
|
${shown.map(item => html`<button key=${item.key} type="button" role="menuitem" class=${`menu-item${item.danger ? " danger" : ""}`}
|
|
36
36
|
disabled=${!!item.disabled} onClick=${() => choose(item)}>${item.icon ? html`<${Icon} name=${item.icon} size=${16} />` : null}<span>${item.label}</span></button>`)}
|
|
37
37
|
</div>` : null}
|
|
@@ -14,5 +14,5 @@ export function toast(text, ok = true) {
|
|
|
14
14
|
export function Toasts() {
|
|
15
15
|
const [list, setList] = useState(toasts.get());
|
|
16
16
|
useEffect(() => toasts.subscribe(setList), []);
|
|
17
|
-
return html`<div class="toasts" role="status" aria-live="polite">${list.map(x => html`<div key=${x.id} class=${`toast ${x.ok ? "ok" : "err"}`}>${x.text}</div>`)}</div>`;
|
|
17
|
+
return html`<div data-trust="toast" class="toasts" role="status" aria-live="polite">${list.map(x => html`<div key=${x.id} class=${`toast ${x.ok ? "ok" : "err"}`}>${x.text}</div>`)}</div>`;
|
|
18
18
|
}
|
|
@@ -156,6 +156,21 @@ register("view", {
|
|
|
156
156
|
"removePicture": "Remove picture",
|
|
157
157
|
"keepPicture": "Keep picture",
|
|
158
158
|
"removePictureNote": "Removed when you save. The bot's avatar, the persona emoji or the first letter shows instead.",
|
|
159
|
+
"cardSection": "Identity card",
|
|
160
|
+
"cardLead": "Yours wins over the agent's, field by field. Leave a field empty to show the agent's.",
|
|
161
|
+
"cardTagline": "Tagline (one line, up to 60 characters)",
|
|
162
|
+
"cardBio": "Bio (up to 280 characters, 3 line breaks)",
|
|
163
|
+
"cardPortrait": "Portrait",
|
|
164
|
+
"cardBanner": "Banner",
|
|
165
|
+
"cardRemove": "Remove yours",
|
|
166
|
+
"cardKeep": "Keep yours",
|
|
167
|
+
"clearCard": "Clear card",
|
|
168
|
+
"keepCard": "Keep card",
|
|
169
|
+
"clearCardNote": "Removed when you save: your tagline, bio, portrait and banner. The agent's own card shows instead.",
|
|
170
|
+
"resizeAsk": "This picture is {w}×{h}; it will be resized to {nw}×{nh}.",
|
|
171
|
+
"resizeGo": "Resize",
|
|
172
|
+
"cardFailed": "Card not saved: {error}",
|
|
173
|
+
"pictureTooLarge": "This picture can't be made small enough to upload.",
|
|
159
174
|
"saved": "Saved",
|
|
160
175
|
"saveFailed": "Save failed: {error}",
|
|
161
176
|
"signInToEdit": "Sign in to edit",
|
|
@@ -336,6 +351,21 @@ register("view", {
|
|
|
336
351
|
"removePicture": "移除圖片",
|
|
337
352
|
"keepPicture": "保留圖片",
|
|
338
353
|
"removePictureNote": "儲存後移除,改顯示 Bot 的頭像、persona emoji 或名稱第一個字。",
|
|
354
|
+
"cardSection": "身分卡",
|
|
355
|
+
"cardLead": "你設定的欄位優先於 agent 的,逐欄比較。欄位留空就顯示 agent 的。",
|
|
356
|
+
"cardTagline": "標語(一行,最多 60 個字)",
|
|
357
|
+
"cardBio": "簡介(最多 280 個字、3 次換行)",
|
|
358
|
+
"cardPortrait": "肖像",
|
|
359
|
+
"cardBanner": "橫幅",
|
|
360
|
+
"cardRemove": "移除你的",
|
|
361
|
+
"cardKeep": "保留你的",
|
|
362
|
+
"clearCard": "清除卡片",
|
|
363
|
+
"keepCard": "保留卡片",
|
|
364
|
+
"clearCardNote": "儲存後移除你的標語、簡介、肖像與橫幅,改顯示 agent 自己的卡片。",
|
|
365
|
+
"resizeAsk": "這張圖片是 {w}×{h},會縮小為 {nw}×{nh}。",
|
|
366
|
+
"resizeGo": "縮小",
|
|
367
|
+
"cardFailed": "卡片未儲存:{error}",
|
|
368
|
+
"pictureTooLarge": "這張圖片無法縮到可上傳的大小。",
|
|
339
369
|
"saved": "已儲存",
|
|
340
370
|
"saveFailed": "儲存失敗:{error}",
|
|
341
371
|
"signInToEdit": "登入後才能編輯",
|
package/dist/ui/signin.html
CHANGED
|
@@ -55,6 +55,7 @@
|
|
|
55
55
|
<div id="msg" role="alert" aria-live="polite"></div>
|
|
56
56
|
<div class="hint" data-i18n="hint">No code? Send /web in General, then press a sign-in button to get one.</div>
|
|
57
57
|
<details class="help"><summary data-i18n="nobot">No chat bot?</summary><p data-i18n="nobot_body">Ask the operator to run <code>agend web --code</code> on the AgEnD computer.</p></details>
|
|
58
|
+
<details class="help"><summary data-i18n="safe">Page looks wrong after signing in?</summary><p data-i18n="safe_body">Your custom CSS may be hiding something. Add ?safe=1 to the end of the address to load the page without it, then fix it or turn it off in Settings → Appearance.</p></details>
|
|
58
59
|
</form>
|
|
59
60
|
<noscript>This page needs JavaScript to sign you in.</noscript>
|
|
60
61
|
</main>
|
package/dist/view-api.d.ts
CHANGED
|
@@ -3,6 +3,9 @@ import type { IncomingMessage, ServerResponse } from "node:http";
|
|
|
3
3
|
import type { FleetConfig } from "./types.js";
|
|
4
4
|
import type { Logger } from "./logger.js";
|
|
5
5
|
import type { WebSessionStore } from "./web-session.js";
|
|
6
|
+
import { type CardSlot, HumanCardStore } from "./web-card.js";
|
|
7
|
+
import { IdentityColorStore } from "./web-identity-color.js";
|
|
8
|
+
import type { ThemeCard } from "./web-theme.js";
|
|
6
9
|
export interface ViewApiContext {
|
|
7
10
|
readonly webToken: string | null;
|
|
8
11
|
/** Absent in a hand-built context: cookies are then not a credential; the header token still is. */
|
|
@@ -27,6 +30,20 @@ export interface ViewApiContext {
|
|
|
27
30
|
};
|
|
28
31
|
/** #1306: the preview origin chosen for a signed-in shell load (the same hook web-api.ts uses). */
|
|
29
32
|
previewForUi?: AppShellContext["previewForUi"];
|
|
33
|
+
/** #1629 r4 §1: the agent's side of the identity card (theme.card) and its pictures. */
|
|
34
|
+
readonly themeStore?: {
|
|
35
|
+
get(name: string): {
|
|
36
|
+
theme: {
|
|
37
|
+
card?: ThemeCard;
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
asset(name: string, id: string): {
|
|
41
|
+
bytes: Buffer;
|
|
42
|
+
mime: string;
|
|
43
|
+
} | null;
|
|
44
|
+
};
|
|
45
|
+
/** #1958: a profile, card or colour write landed — every open page repaints (a status frame), not only the writer's. */
|
|
46
|
+
identityChanged?(): void;
|
|
30
47
|
}
|
|
31
48
|
interface ProfileRow {
|
|
32
49
|
instance_name: string;
|
|
@@ -67,6 +84,18 @@ export declare function resolveInstanceIdentity(input: {
|
|
|
67
84
|
}): InstanceIdentity;
|
|
68
85
|
/** Profiles by instance, for a status read: only when profiles.db already exists — a status read never creates it. */
|
|
69
86
|
export declare function profileIdentities(dataDir: string): Map<string, Pick<ProfileRow, "display_name" | "description" | "role" | "avatar_path">>;
|
|
87
|
+
/** Every person-picked colour, for a status read (never creates profiles.db or its table). */
|
|
88
|
+
export declare function identityColors(dataDir: string): ReturnType<typeof IdentityColorStore.all>;
|
|
89
|
+
/** The card picture drawn for `slot`: the person's, else the agent's (the same rule as the card's JSON). */
|
|
90
|
+
export declare function cardPicture(ctx: {
|
|
91
|
+
dataDir: string;
|
|
92
|
+
themeStore?: ViewApiContext["themeStore"];
|
|
93
|
+
}, name: string, slot: CardSlot): {
|
|
94
|
+
bytes: Buffer;
|
|
95
|
+
mime: string;
|
|
96
|
+
} | null;
|
|
97
|
+
/** Every person-set card, for a status read (never creates profiles.db or its table). */
|
|
98
|
+
export declare function humanCards(dataDir: string): ReturnType<typeof HumanCardStore.all>;
|
|
70
99
|
/** A pane's logical grid size in cells, as tmux sees it. */
|
|
71
100
|
export interface PaneSize {
|
|
72
101
|
cols: number;
|