@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
package/dist/fleet-manager.js
CHANGED
|
@@ -139,14 +139,31 @@ import { LogService } from "./log-service.js";
|
|
|
139
139
|
import { WebChatHistory, WEB_CHAT_TEXT_MAX, isWebMessageId, newWebMessageId } from "./web-chat-history.js";
|
|
140
140
|
import { WebChatDiskStore } from "./web-chat-store.js";
|
|
141
141
|
import { SeenUsers, resolveMentions } from "./web-mentions.js";
|
|
142
|
-
import { WebPrefsStore } from "./web-prefs.js";
|
|
142
|
+
import { OPERATOR, WebPrefsStore } from "./web-prefs.js";
|
|
143
|
+
import { agendVersion } from "./agent-whats-new.js";
|
|
144
|
+
import { OwnerCssStore } from "./web-owner-css-store.js";
|
|
145
|
+
import { ThemeStore } from "./web-theme-store.js";
|
|
146
|
+
import { WebAppearance } from "./web-appearance.js";
|
|
147
|
+
import { WebEffectLibrary } from "./web-effect-library.js";
|
|
148
|
+
import { WebEffectTrials } from "./web-effect-trials.js";
|
|
149
|
+
import { WebEffectService } from "./web-effect-service.js";
|
|
150
|
+
import { WebEffectProfiles } from "./web-effect-profiles.js";
|
|
151
|
+
import { WebEffectTools, WEB_EFFECT_TOOLS } from "./web-effect-tools.js";
|
|
152
|
+
import { resolveEffectParams } from "./web-effect.js";
|
|
153
|
+
import { cw1EffectChecker } from "./web-mod-bundle.js";
|
|
154
|
+
import { effectiveAppearanceLimits } from "./web-appearance-limits.js";
|
|
143
155
|
import { WEB_NAME_FALLBACK, safeWebName, validateWebName, webNameInUse, webPersonKey } from "./web-profile.js";
|
|
144
156
|
import { ReplyButtonStore, parseReplyButtons, replyButtonClickText, replyButtonsFallbackText, REPLY_BUTTON_PREFIX } from "./reply-buttons.js";
|
|
145
157
|
import { ReplyButtonsController } from "./reply-buttons-controller.js";
|
|
146
158
|
import { parsePoll } from "./reply-polls.js";
|
|
147
159
|
import { isUploadRequest, limitRequestReceive, publicAttachment, sweepOrphanedUploads, UPLOAD_REQUEST_TIMEOUT_MS, uploadLimits, WebFileLedger } from "./web-upload.js";
|
|
148
160
|
import { REPLY_FILES_MAX_BYTES, snapshotReplyFile, sweepReplySnapshots, webFilesDir } from "./web-reply-files.js";
|
|
149
|
-
import { handleViewRequest, isViewPath, profileIdentities, resolveInstanceIdentity } from "./view-api.js";
|
|
161
|
+
import { cardPicture, handleViewRequest, humanCards, identityColors, isViewPath, profileIdentities, resolveInstanceIdentity } from "./view-api.js";
|
|
162
|
+
import { autoIdentityColor } from "./web-identity-color.js";
|
|
163
|
+
import { AppearanceTools } from "./web-appearance-tools.js";
|
|
164
|
+
import { cardVersion, resolveCard } from "./web-card.js";
|
|
165
|
+
import { ModStore } from "./web-mod-store.js";
|
|
166
|
+
import { AgentMods } from "./web-mod-agent.js";
|
|
150
167
|
import { downloadStickerImage } from "./channel/sticker-download.js";
|
|
151
168
|
import { WebImageCache, imageMimeForFile, WEB_IMAGE_MAX_AGE_MS, WEB_IMAGE_MAX_BYTES, WEB_IMAGE_RETRY_MS } from "./web-images.js";
|
|
152
169
|
import { accentColor, avatarFor, ownBotConnections } from "./instance-avatar.js";
|
|
@@ -192,6 +209,7 @@ import { NeedsYouHub } from "./needs-you-hub.js";
|
|
|
192
209
|
import { WEB_CHAT_NOTICE, claimNotice, hasWebChat, releaseNotice, upgradeNoticesPath } from "./upgrade-notices.js";
|
|
193
210
|
import { GENERAL_PAUSE_ERROR, isGeneralInstance } from "./general-instance.js";
|
|
194
211
|
import { buildOrgChart } from "./web-org.js";
|
|
212
|
+
import { GENERAL_POINTING_REFUSALS, SNOWFLAKE, hasPlaceRefs, placeRef, webRoute, webTextLinks, placeSuffix, renderPlaceRefs, stripPlaceRefs, topicUrl, worldPlaceOf } from "./topic-link.js";
|
|
195
213
|
import { CacheService, WINDOWS } from "./cache-service.js";
|
|
196
214
|
import { runWebCommand } from "./web-commands.js";
|
|
197
215
|
import { CompactWatch, launchIdentity } from "./compact-watch.js";
|
|
@@ -884,6 +902,183 @@ export class FleetManager {
|
|
|
884
902
|
return true;
|
|
885
903
|
return false;
|
|
886
904
|
}
|
|
905
|
+
themeStoreInstance;
|
|
906
|
+
webAppearanceService;
|
|
907
|
+
/** #1629: every instance's theme, placements, assets and lock (web-themes.json). */
|
|
908
|
+
get themeStore() {
|
|
909
|
+
// r4.1 Q3 (b): the card's limits are read from fleet.yaml at each upload (a change applies without a restart).
|
|
910
|
+
return (this.themeStoreInstance ??= new ThemeStore(this.dataDir, { limits: () => effectiveAppearanceLimits(this.fleetConfig?.web?.appearance).limits, effectsDropped: ids => this.effectLibrary.collectUnreferenced(ids) }));
|
|
911
|
+
}
|
|
912
|
+
/** #1629: the theme tools, a person's reset and lock, and the one appearance rate limiter (shared with effects). */
|
|
913
|
+
get webAppearance() {
|
|
914
|
+
return (this.webAppearanceService ??= new WebAppearance(this.themeStore, () => this.fleetConfig?.web ?? null, () => { try {
|
|
915
|
+
this.emitSseEvent("status", this.getUiStatus());
|
|
916
|
+
}
|
|
917
|
+
catch { /* the next frame carries the rev */ } }));
|
|
918
|
+
}
|
|
919
|
+
effectLibraryInstance;
|
|
920
|
+
effectTrialsInstance;
|
|
921
|
+
/** Lazy, single-owner storage. Register every host reference source before any operation can collect. */
|
|
922
|
+
get effectLibrary() {
|
|
923
|
+
if (!this.effectLibraryInstance) {
|
|
924
|
+
const library = new WebEffectLibrary(this.dataDir);
|
|
925
|
+
// Providers are lazy; publish only after every registration succeeds.
|
|
926
|
+
library.registerReferenceSource("themes", () => {
|
|
927
|
+
if (!this.themeStore.readable())
|
|
928
|
+
throw new Error("themes_unreadable");
|
|
929
|
+
return this.themeStore.placedEffects().flatMap(row => row.ids.map(id => ({ id, holder: `instance:${row.name}` })));
|
|
930
|
+
});
|
|
931
|
+
library.registerReferenceSource("prefs", () => this.webPrefs.ownerEffectReferences());
|
|
932
|
+
library.registerReferenceSource("mods", () => {
|
|
933
|
+
const mods = this.mods.list();
|
|
934
|
+
if (mods.unreadable)
|
|
935
|
+
throw new Error("mods_unreadable");
|
|
936
|
+
return mods.mods.flatMap(mod => mod.effects.map(effect => ({ id: effect.id, holder: `mod:${mod.id}` })));
|
|
937
|
+
});
|
|
938
|
+
this.effectTrialsInstance = new WebEffectTrials({ library, known: name => this.isKnownInstance(name) });
|
|
939
|
+
this.effectLibraryInstance = library;
|
|
940
|
+
}
|
|
941
|
+
return this.effectLibraryInstance;
|
|
942
|
+
}
|
|
943
|
+
/** Volatile Try offers and session-bound runs share this library. */
|
|
944
|
+
get effectTrials() { if (!this.effectTrialsInstance)
|
|
945
|
+
void this.effectLibrary; return this.effectTrialsInstance; }
|
|
946
|
+
effectProfilesInstance;
|
|
947
|
+
get effectProfiles() { return (this.effectProfilesInstance ??= new WebEffectProfiles(this.dataDir)); }
|
|
948
|
+
effectServiceInstance;
|
|
949
|
+
get effectService() {
|
|
950
|
+
return (this.effectServiceInstance ??= new WebEffectService({ library: this.effectLibrary, themes: this.themeStore, profiles: this.effectProfiles, prefs: this.webPrefs, trials: this.effectTrials,
|
|
951
|
+
known: name => this.isKnownInstance(name), changed: () => { try {
|
|
952
|
+
this.emitSseEvent("status", this.getUiStatus());
|
|
953
|
+
}
|
|
954
|
+
catch { /* next status carries revision */ } } }));
|
|
955
|
+
}
|
|
956
|
+
effectToolsService;
|
|
957
|
+
/** Caller identity is supplied only by the owned IPC connection / verified agent endpoint token. */
|
|
958
|
+
get effectTools() {
|
|
959
|
+
return (this.effectToolsService ??= new WebEffectTools({
|
|
960
|
+
library: this.effectLibrary, limiter: this.webAppearance.limiter,
|
|
961
|
+
authorize: (name, buckets) => !this.isKnownInstance(name) ? { error: "unknown_instance" } : this.webAppearance.authorizeEffect(name, buckets),
|
|
962
|
+
placed: name => this.themeStore.get(name).placements.effects,
|
|
963
|
+
place: (name, effects) => {
|
|
964
|
+
const before = this.themeStore.get(name).placements.effects;
|
|
965
|
+
const explicit = new Set(effects.filter(e => typeof e !== "string").map(e => e.id));
|
|
966
|
+
const w = this.themeStore.setPlacements(name, { effects }, "agent", what => {
|
|
967
|
+
// An unchanged human/private placement may be retained, never exposed or newly acquired by an agent.
|
|
968
|
+
const r = explicit.has(what.id) ? this.effectLibrary.read(what.id, { kind: "agent", id: name })
|
|
969
|
+
: this.effectLibrary.readReferenced(what.id, `instance:${name}`);
|
|
970
|
+
if (!r.ok)
|
|
971
|
+
return r.error;
|
|
972
|
+
const params = explicit.has(what.id) ? what.params : before.find(e => e.id === what.id)?.params;
|
|
973
|
+
return resolveEffectParams(r.entry.manifest, params ?? {}).ok ? null : "effect_params";
|
|
974
|
+
});
|
|
975
|
+
return w.ok ? { ok: true, rev: w.rev } : { ok: false, error: w.error };
|
|
976
|
+
},
|
|
977
|
+
changed: () => { try {
|
|
978
|
+
this.emitSseEvent("status", this.getUiStatus());
|
|
979
|
+
}
|
|
980
|
+
catch { /* later status carries placements */ } },
|
|
981
|
+
// A nonmutating own/shared offer is not an appearance write. Only an explicit local-admin click may start it.
|
|
982
|
+
preview: async (instance, id) => {
|
|
983
|
+
const result = this.effectTrials.offer(instance, id);
|
|
984
|
+
if (!result.ok)
|
|
985
|
+
return { error: "not_found" };
|
|
986
|
+
const present = this.webChatHistory.list(instance).some(row => row.effectTrial?.offer === result.offer.offer);
|
|
987
|
+
if (!present && !this.publishEffectTrial(instance, result.offer))
|
|
988
|
+
return { error: "trial_unavailable" };
|
|
989
|
+
return { id, status: "offered", expiresAt: result.offer.expiresAt, message: "A trial card is in your web chat. A signed-in local admin can start it; nothing was applied." };
|
|
990
|
+
},
|
|
991
|
+
profiles: id => this.effectProfiles.read(id),
|
|
992
|
+
profileSummary: id => this.effectService.profileSummary(id),
|
|
993
|
+
profileSummaries: ids => this.effectService.profileSummaries(ids),
|
|
994
|
+
}));
|
|
995
|
+
}
|
|
996
|
+
appearanceToolsService;
|
|
997
|
+
/** #1629 r4 §1: get_web_appearance / preview_web_appearance — the caller's own look, resolved as the page resolves it. */
|
|
998
|
+
get appearanceTools() {
|
|
999
|
+
return (this.appearanceToolsService ??= new AppearanceTools({
|
|
1000
|
+
frame: (name) => (this.getUiStatus().instances.find(i => i.name === name) ?? { name }),
|
|
1001
|
+
theme: (name) => { const r = this.themeStore.get(name); return { theme: r.theme, locked: r.locked, widgets: r.placements.widgets }; },
|
|
1002
|
+
card: (name) => resolveCard(name, humanCards(this.dataDir).get(name), this.themeStore.get(name).theme.card),
|
|
1003
|
+
// #1958: the host's identity-colour settings, as the page reads them (an unreadable prefs file: the defaults).
|
|
1004
|
+
colorPrefs: () => { try {
|
|
1005
|
+
const v = this.webPrefs.read(OPERATOR).values;
|
|
1006
|
+
return { show: v["color.show"], off: v["color.off"] === true };
|
|
1007
|
+
}
|
|
1008
|
+
catch {
|
|
1009
|
+
return {};
|
|
1010
|
+
} },
|
|
1011
|
+
cardPicture: (name, slot) => cardPicture(this, name, slot),
|
|
1012
|
+
themeAsset: (name, id) => this.themeStore.asset(name, id),
|
|
1013
|
+
profileAvatar: (name) => this.webImage("avatar", name),
|
|
1014
|
+
botAvatar: (_name, key) => this.webImage("bot", key),
|
|
1015
|
+
themeAnswer: (name) => this.webAppearance.getTheme(name),
|
|
1016
|
+
}));
|
|
1017
|
+
}
|
|
1018
|
+
/** #1958: a profile, card or colour write (view-api.ts) landed: every open page gets a fresh status frame. */
|
|
1019
|
+
identityChanged() {
|
|
1020
|
+
try {
|
|
1021
|
+
this.emitSseEvent("status", this.getUiStatus());
|
|
1022
|
+
}
|
|
1023
|
+
catch { /* the next frame carries it */ }
|
|
1024
|
+
}
|
|
1025
|
+
/** A fleet.yaml instance (own key) or a ClassicBot room, now (#1629: a person's reset/lock names one). */
|
|
1026
|
+
isKnownInstance(name) {
|
|
1027
|
+
return Object.hasOwn(this.fleetConfig?.instances ?? {}, name) || (this.classicChannels?.getAll() ?? []).some((ch) => ch.instanceName === name);
|
|
1028
|
+
}
|
|
1029
|
+
modStore;
|
|
1030
|
+
/**
|
|
1031
|
+
* #1629 r4-5 / B2: installed mods use the same validated library and scoped holder claims as direct uploads.
|
|
1032
|
+
*/
|
|
1033
|
+
get mods() {
|
|
1034
|
+
return (this.modStore ??= new ModStore(this.dataDir, {
|
|
1035
|
+
themes: this.themeStore,
|
|
1036
|
+
prefs: this.webPrefs,
|
|
1037
|
+
ownerCss: this.ownerCss,
|
|
1038
|
+
effects: this.effectLibrary,
|
|
1039
|
+
context: () => ({ agendVersion: agendVersion(), limits: this.themeStore.limits(), effects: cw1EffectChecker() }),
|
|
1040
|
+
isKnownInstance: (name) => this.isKnownInstance(name),
|
|
1041
|
+
prefsIdentity: OPERATOR,
|
|
1042
|
+
}));
|
|
1043
|
+
}
|
|
1044
|
+
agentModsService;
|
|
1045
|
+
/** #1629 r4-5b: export_web_mod / propose_web_mod — the caller's own look as a file, and a proposal for a person. */
|
|
1046
|
+
get agentMods() {
|
|
1047
|
+
return (this.agentModsService ??= new AgentMods({
|
|
1048
|
+
themes: this.themeStore, mods: this.mods, limiter: this.webAppearance.limiter,
|
|
1049
|
+
webConfig: () => this.fleetConfig?.web ?? null,
|
|
1050
|
+
// The mod store's own: a proposal is checked exactly as the person's import of it will be.
|
|
1051
|
+
context: () => this.mods.checkContext(),
|
|
1052
|
+
readOwnEffect: (instance, id) => { const r = this.effectLibrary.read(id, { kind: "agent", id: instance }); return r.ok ? { name: r.entry.manifest.name, files: r.entry.files } : null; },
|
|
1053
|
+
notice: (instance, text) => { try {
|
|
1054
|
+
this.emitSseEvent("message", { instance, sender: instance, role: "status", text, ts: new Date().toISOString() });
|
|
1055
|
+
}
|
|
1056
|
+
catch { /* the page reads it from Settings too */ } },
|
|
1057
|
+
changed: () => { try {
|
|
1058
|
+
this.emitSseEvent("status", this.getUiStatus());
|
|
1059
|
+
}
|
|
1060
|
+
catch { /* the next frame */ } },
|
|
1061
|
+
}));
|
|
1062
|
+
}
|
|
1063
|
+
/**
|
|
1064
|
+
* #1629 r4-5c: an effect as this person may carry it away in an export — their own claim, or one a person shared —
|
|
1065
|
+
* read with their principal (Sol's ACL). Being able to view or run a placed effect never grants packaging someone
|
|
1066
|
+
* else's private claim: the library's reference read is runtime access, not an export path. Null: not theirs.
|
|
1067
|
+
*/
|
|
1068
|
+
modPlacedEffect(person, id) {
|
|
1069
|
+
const r = this.effectLibrary.read(id, { kind: "human", id: person });
|
|
1070
|
+
return r.ok ? { name: r.entry.manifest.name, files: r.entry.files } : null;
|
|
1071
|
+
}
|
|
1072
|
+
/** #1629 r4-5: an instance's card text as every viewer sees it (a person's fields over the agent's). */
|
|
1073
|
+
modCardText(name) {
|
|
1074
|
+
const card = resolveCard(name, humanCards(this.dataDir).get(name), this.themeStore.get(name).theme.card);
|
|
1075
|
+
return { ...(card.tagline ? { tagline: card.tagline.text } : {}), ...(card.bio ? { bio: card.bio.text } : {}) };
|
|
1076
|
+
}
|
|
1077
|
+
ownerCssStore;
|
|
1078
|
+
/** #1629 r4 §3: each person's own CSS; which version is in force is their `css.active` in the web preferences. */
|
|
1079
|
+
get ownerCss() {
|
|
1080
|
+
return (this.ownerCssStore ??= new OwnerCssStore(this.dataDir, this.webPrefs));
|
|
1081
|
+
}
|
|
887
1082
|
get webPrefs() {
|
|
888
1083
|
// #1664: a favorite must be an instance of this fleet now — fleet.yaml's (own keys) or a ClassicBot room's.
|
|
889
1084
|
return (this.webPrefsStore ??= new WebPrefsStore(this.dataDir, {
|
|
@@ -3704,7 +3899,11 @@ export class FleetManager {
|
|
|
3704
3899
|
// A command off for want of fleet admins names where they go (`{0}` in its disabled reply).
|
|
3705
3900
|
if (decision.reply === spec.disabled)
|
|
3706
3901
|
args.push(this.adminListKey(adapterId));
|
|
3707
|
-
|
|
3902
|
+
// #1990: a refusal that sends the person to General links to it — not in a ClassicBot room, whose readers need
|
|
3903
|
+
// not be in the fleet's guild.
|
|
3904
|
+
const toGeneral = GENERAL_POINTING_REFUSALS.has(key) && scope !== "classic" && scope !== "none";
|
|
3905
|
+
const reply = toGeneral ? this.renderPlaces(adapterId, t(key, ...args) + placeSuffix({ kind: "general" }, "General")).text : t(key, ...args);
|
|
3906
|
+
await data.respond(reply).catch(() => { });
|
|
3708
3907
|
return;
|
|
3709
3908
|
}
|
|
3710
3909
|
if (data.command === "start") {
|
|
@@ -5302,7 +5501,8 @@ export class FleetManager {
|
|
|
5302
5501
|
const owner = id ? this.dashboardOwner(msg.userId, id, msg.chatId, msg.threadId, msg.username) : null;
|
|
5303
5502
|
if (!owner) {
|
|
5304
5503
|
const adapter = id ? this.adapters.get(id) : undefined;
|
|
5305
|
-
|
|
5504
|
+
const out = this.renderPlaces(id, t("dashboard.public_general") + placeSuffix({ kind: "general" }, "General"), { threadId: msg.threadId });
|
|
5505
|
+
await adapter?.sendText(msg.chatId, out.text, out.opts);
|
|
5306
5506
|
return;
|
|
5307
5507
|
}
|
|
5308
5508
|
if (!await this.showDashboardMenu(owner)) {
|
|
@@ -6273,7 +6473,7 @@ export class FleetManager {
|
|
|
6273
6473
|
...DEFAULT_SCHEDULER_CONFIG,
|
|
6274
6474
|
...this.fleetConfig?.defaults.scheduler,
|
|
6275
6475
|
};
|
|
6276
|
-
this.scheduler = new Scheduler(join(this.dataDir, "scheduler.db"), (schedule, runId, retry) => this.handleScheduleTrigger(schedule, runId, retry), schedulerConfig, (name) => this.fleetConfig?.instances?.[name] != null || !!this.classicChannels?.getAll().some(ch => ch.instanceName === name), (schedule, retry, reason) => this.scheduleRetryDropped(schedule, retry, reason));
|
|
6476
|
+
this.scheduler = new Scheduler(join(this.dataDir, "scheduler.db"), (schedule, runId, retry) => this.handleScheduleTrigger(schedule, runId, retry), schedulerConfig, (name) => this.fleetConfig?.instances?.[name] != null || !!this.classicChannels?.getAll().some(ch => ch.instanceName === name), (schedule, retry, reason) => this.scheduleRetryDropped(schedule, retry, reason), this.logger);
|
|
6277
6477
|
this.scheduler.init();
|
|
6278
6478
|
this.logger.info("Scheduler initialized");
|
|
6279
6479
|
// Tips share the daily-report clock but remain an independent internal
|
|
@@ -7505,6 +7705,48 @@ export class FleetManager {
|
|
|
7505
7705
|
this.adapterRestarting.delete(id);
|
|
7506
7706
|
}
|
|
7507
7707
|
}
|
|
7708
|
+
/**
|
|
7709
|
+
* #1990: how a reader in world `worldId`'s group reaches a place token's target — undefined (plain text) for another
|
|
7710
|
+
* world's instance, an instance without a fleet topic (a ClassicBot room may be anywhere), or an id no link is built
|
|
7711
|
+
* from. Discord: the channel mention; Telegram: the topic URL (General's topic included).
|
|
7712
|
+
*/
|
|
7713
|
+
placeLink(worldId, target) {
|
|
7714
|
+
const world = this.worlds.get(worldId ?? "");
|
|
7715
|
+
if (!world)
|
|
7716
|
+
return undefined;
|
|
7717
|
+
let topic;
|
|
7718
|
+
if (target.kind === "general") {
|
|
7719
|
+
const notice = this.fleetNoticeTarget(world.id);
|
|
7720
|
+
if (!notice || notice.chatId !== world.groupId || notice.opts.threadId === undefined)
|
|
7721
|
+
return undefined;
|
|
7722
|
+
topic = String(notice.opts.threadId);
|
|
7723
|
+
}
|
|
7724
|
+
else {
|
|
7725
|
+
if (this.getInstanceAdapterId(target.name) !== world.id)
|
|
7726
|
+
return undefined;
|
|
7727
|
+
const topicId = this.fleetConfig?.instances[target.name]?.topic_id;
|
|
7728
|
+
if (topicId == null)
|
|
7729
|
+
return undefined;
|
|
7730
|
+
topic = String(topicId);
|
|
7731
|
+
}
|
|
7732
|
+
if (world.type === "discord")
|
|
7733
|
+
return SNOWFLAKE.test(topic) && SNOWFLAKE.test(world.groupId) ? { mention: `<#${topic}>` } : undefined;
|
|
7734
|
+
const url = topicUrl(worldPlaceOf(world), topic, { general: target.kind === "general" });
|
|
7735
|
+
return url ? { url } : undefined;
|
|
7736
|
+
}
|
|
7737
|
+
/** #1990: a message for world `worldId`'s group with its place tokens rendered (links as text or Telegram entities). */
|
|
7738
|
+
renderPlaces(worldId, text, opts) {
|
|
7739
|
+
if (!hasPlaceRefs(text))
|
|
7740
|
+
return { text, opts };
|
|
7741
|
+
const type = this.worlds.get(worldId ?? "")?.type;
|
|
7742
|
+
const surface = type === "discord" ? "discord" : type === "telegram" ? "telegram" : "plain";
|
|
7743
|
+
const out = renderPlaceRefs(text, surface, target => this.placeLink(worldId, target));
|
|
7744
|
+
return { text: out.text, opts: out.textLinks.length ? { ...opts, textLinks: out.textLinks } : opts };
|
|
7745
|
+
}
|
|
7746
|
+
/** #1990: renderPlaces for the world that owns `instanceName`'s topic. */
|
|
7747
|
+
renderPlacesFor(instanceName, text, opts) {
|
|
7748
|
+
return this.renderPlaces(this.getInstanceAdapterId(instanceName), text, opts);
|
|
7749
|
+
}
|
|
7508
7750
|
/** Handle inbound message — transcribe voice if present, then route */
|
|
7509
7751
|
findGeneralInstance(adapterId) {
|
|
7510
7752
|
if (!this.fleetConfig)
|
|
@@ -9177,6 +9419,19 @@ export class FleetManager {
|
|
|
9177
9419
|
}
|
|
9178
9420
|
dispatchTypedIpc(name, msg) {
|
|
9179
9421
|
const type = String(msg.type);
|
|
9422
|
+
if (type.startsWith("fleet_") && WEB_EFFECT_TOOLS.has(type.slice(6))) {
|
|
9423
|
+
const ipc = this.instanceIpcClients.get(name), fleetRequestId = msg.fleetRequestId;
|
|
9424
|
+
if (!ipc || typeof fleetRequestId !== "string")
|
|
9425
|
+
return;
|
|
9426
|
+
void this.handleWebThemeHttp(name, type.slice(6), (msg.payload ?? {})).then(result => {
|
|
9427
|
+
if (this.instanceIpcClients.get(name) === ipc)
|
|
9428
|
+
ipc.send({ type: "fleet_web_theme_response", fleetRequestId, result });
|
|
9429
|
+
}, err => {
|
|
9430
|
+
if (this.instanceIpcClients.get(name) === ipc)
|
|
9431
|
+
ipc.send({ type: "fleet_web_theme_response", fleetRequestId, error: err instanceof Error ? err.message : "Effect operation failed" });
|
|
9432
|
+
}).catch(err => this.logger.debug({ err }, "Effect tool response could not be written"));
|
|
9433
|
+
return;
|
|
9434
|
+
}
|
|
9180
9435
|
if (type.startsWith("fleet_schedule_")) {
|
|
9181
9436
|
this.handleScheduleCrud(name, msg);
|
|
9182
9437
|
return;
|
|
@@ -9202,6 +9457,36 @@ export class FleetManager {
|
|
|
9202
9457
|
this.handlePersonaEmoji(name, msg);
|
|
9203
9458
|
return;
|
|
9204
9459
|
}
|
|
9460
|
+
if (type === "fleet_get_web_theme" || type === "fleet_set_web_theme" || type === "fleet_set_web_theme_asset" || type === "fleet_reset_web_theme"
|
|
9461
|
+
|| type === "fleet_get_web_appearance" || type === "fleet_preview_web_appearance"
|
|
9462
|
+
|| type === "fleet_set_web_widget" || type === "fleet_remove_web_widget"
|
|
9463
|
+
|| type === "fleet_export_web_mod" || type === "fleet_propose_web_mod") {
|
|
9464
|
+
this.handleWebTheme(name, msg);
|
|
9465
|
+
return;
|
|
9466
|
+
}
|
|
9467
|
+
}
|
|
9468
|
+
/**
|
|
9469
|
+
* #1629: the caller's own web theme over IPC (the agent endpoint calls the same service). `name` is the instance this
|
|
9470
|
+
* IPC connection belongs to — never anything the message says. A refusal is a result the agent reads (its `error`
|
|
9471
|
+
* and what to do), not a transport error.
|
|
9472
|
+
*/
|
|
9473
|
+
handleWebTheme(instanceName, msg) {
|
|
9474
|
+
const fleetRequestId = msg.fleetRequestId;
|
|
9475
|
+
const ipc = this.instanceIpcClients.get(instanceName);
|
|
9476
|
+
if (!ipc)
|
|
9477
|
+
return;
|
|
9478
|
+
const tool = String(msg.type).replace(/^fleet_/, "");
|
|
9479
|
+
try {
|
|
9480
|
+
const args = (msg.payload ?? {});
|
|
9481
|
+
const result = tool === "get_web_appearance" || tool === "preview_web_appearance"
|
|
9482
|
+
? this.appearanceTools.handle(instanceName, tool, args)
|
|
9483
|
+
: tool === "export_web_mod" || tool === "propose_web_mod" ? this.agentMods.handle(instanceName, tool, args)
|
|
9484
|
+
: this.webAppearance.handleTool(instanceName, tool, args);
|
|
9485
|
+
ipc.send({ type: "fleet_web_theme_response", fleetRequestId, result });
|
|
9486
|
+
}
|
|
9487
|
+
catch (err) {
|
|
9488
|
+
ipc.send({ type: "fleet_web_theme_response", fleetRequestId, error: err.message });
|
|
9489
|
+
}
|
|
9205
9490
|
}
|
|
9206
9491
|
/** `list_emojis` / `set_persona_emoji` / `preview_emojis` / `list_stickers` / `preview_stickers` over IPC; the agent endpoint calls the same methods. */
|
|
9207
9492
|
handlePersonaEmoji(instanceName, msg) {
|
|
@@ -9606,6 +9891,19 @@ export class FleetManager {
|
|
|
9606
9891
|
async handleSetPersonaEmojiHttp(instance, args) {
|
|
9607
9892
|
return this.setPersonaEmoji(instance, args);
|
|
9608
9893
|
}
|
|
9894
|
+
/** #1629: `agend-agent theme …` — the same service as the tools; `instance` is the token's. */
|
|
9895
|
+
async handleWebThemeHttp(instance, tool, args) {
|
|
9896
|
+
if (WEB_EFFECT_TOOLS.has(tool)) {
|
|
9897
|
+
if (!this.isKnownInstance(instance))
|
|
9898
|
+
return { error: "unknown_instance" };
|
|
9899
|
+
return this.effectTools.handle(instance, tool, args);
|
|
9900
|
+
}
|
|
9901
|
+
if (tool === "get_web_appearance" || tool === "preview_web_appearance")
|
|
9902
|
+
return this.appearanceTools.handle(instance, tool, args);
|
|
9903
|
+
if (tool === "export_web_mod" || tool === "propose_web_mod")
|
|
9904
|
+
return this.agentMods.handle(instance, tool, args);
|
|
9905
|
+
return this.webAppearance.handleTool(instance, tool, args);
|
|
9906
|
+
}
|
|
9609
9907
|
async handlePreviewEmojisHttp(instance, args) {
|
|
9610
9908
|
return this.previewEmojis(instance, args);
|
|
9611
9909
|
}
|
|
@@ -10308,6 +10606,17 @@ export class FleetManager {
|
|
|
10308
10606
|
catch (err) {
|
|
10309
10607
|
this.logger.warn({ err: err.message, name }, "Dropping a removed instance from web favorites failed");
|
|
10310
10608
|
}
|
|
10609
|
+
try {
|
|
10610
|
+
this.themeStore.forget(name);
|
|
10611
|
+
this.webAppearance.forget(name);
|
|
10612
|
+
this.appearanceTools.forget(name);
|
|
10613
|
+
this.effectToolsService?.forget(name);
|
|
10614
|
+
this.mods.forgetInstance(name);
|
|
10615
|
+
this.agentMods.forget(name);
|
|
10616
|
+
}
|
|
10617
|
+
catch (err) {
|
|
10618
|
+
this.logger.warn({ err: err.message, name }, "Dropping a removed instance's web theme failed");
|
|
10619
|
+
}
|
|
10311
10620
|
// Clean up statusline watcher + instance directory
|
|
10312
10621
|
this.statuslineWatcher.unwatch(name);
|
|
10313
10622
|
// instanceDirToClean and effectiveBackend are captured before lifecycle.remove
|
|
@@ -10596,10 +10905,25 @@ export class FleetManager {
|
|
|
10596
10905
|
const avatar = avatarFor({ name, displayName, profileVersion, bot: bot && connection ? { connection, version: bot.version } : null,
|
|
10597
10906
|
personaEmoji: cfg?.status_emojis?.delivered });
|
|
10598
10907
|
// The ring belongs to an own bot's face: only when that is what is shown (a shared bot's would ring everyone alike).
|
|
10599
|
-
|
|
10908
|
+
// #1958: the own bot's colour is also the identity colour's role tier — whatever face is shown (never a shared bot's).
|
|
10909
|
+
const roleColor = ownConnection && owner
|
|
10600
10910
|
? this.webAccent(ownConnection, owner, cfg ? (cfg.topic_id == null ? undefined : String(cfg.topic_id)) : this.classicChannels?.getChannelIdByInstance(name))
|
|
10601
10911
|
: null;
|
|
10602
|
-
|
|
10912
|
+
const accent = !profileVersion && bot && ownConnection && owner ? roleColor : null;
|
|
10913
|
+
// #1629 (first design §5, r2 (d)): the agent's theme may name its own picture and ring colour. Both come below a
|
|
10914
|
+
// person's upload (never when there is one) and ride beside the theme-free face: a viewer who turned agents'
|
|
10915
|
+
// themes off (fx.show), or a safe-mode load, shows `avatar` and `accent` as before.
|
|
10916
|
+
let theme = { v: 1 };
|
|
10917
|
+
try {
|
|
10918
|
+
theme = this.themeStore.get(name).theme;
|
|
10919
|
+
}
|
|
10920
|
+
catch { /* unreadable themes: none */ }
|
|
10921
|
+
const themeAvatar = !profileVersion && theme.avatar ? { kind: "image", src: `/ui/img/theme/${encodeURIComponent(name)}/${theme.avatar}` } : undefined;
|
|
10922
|
+
const themeAccent = !profileVersion && theme.accent ? theme.accent : undefined;
|
|
10923
|
+
return { avatar, ...(accent ? { accent } : {}), ...(themeAvatar ? { themeAvatar } : {}), ...(themeAccent ? { themeAccent } : {}),
|
|
10924
|
+
// #1958: the agent's own identity colour (theme.identity.color, else its accent) and where it would show it.
|
|
10925
|
+
...(roleColor ? { roleColor } : {}), ...(theme.identity?.color ?? theme.accent ? { agentColor: theme.identity?.color ?? theme.accent } : {}),
|
|
10926
|
+
...(theme.identity?.show ? { agentShow: theme.identity.show } : {}) };
|
|
10603
10927
|
}
|
|
10604
10928
|
/**
|
|
10605
10929
|
* GET /ui/img/bot|avatar/<key>: what is cached, never a fetch. Only a connection some instance or room speaks
|
|
@@ -11686,7 +12010,8 @@ export class FleetManager {
|
|
|
11686
12010
|
// Discord, so every such fallback failed inside the catch handler.
|
|
11687
12011
|
const target = this.fleetNoticeTarget();
|
|
11688
12012
|
if (this.adapter && target) {
|
|
11689
|
-
this.
|
|
12013
|
+
const out = this.renderPlaces(this.getPrimaryAdapterId(), body, target.opts);
|
|
12014
|
+
this.adapter.sendText(target.chatId, out.text, out.opts)
|
|
11690
12015
|
.catch(err => this.logger.warn({ err }, "Failed to send fleet error notification"));
|
|
11691
12016
|
dispatched = true;
|
|
11692
12017
|
}
|
|
@@ -11698,7 +12023,7 @@ export class FleetManager {
|
|
|
11698
12023
|
// also suppressed the next ten minutes of identical ones. Leaving the key
|
|
11699
12024
|
// unspent lets the next caller, once an adapter is up, actually get
|
|
11700
12025
|
// through. The counter is not reset either: nothing was shown.
|
|
11701
|
-
this.logger.warn({ text: body }, "Fleet error could not be delivered (no adapter or no target yet) — not consuming the throttle window");
|
|
12026
|
+
this.logger.warn({ text: stripPlaceRefs(body) }, "Fleet error could not be delivered (no adapter or no target yet) — not consuming the throttle window");
|
|
11702
12027
|
return false;
|
|
11703
12028
|
}
|
|
11704
12029
|
if (!throttled)
|
|
@@ -11736,24 +12061,28 @@ export class FleetManager {
|
|
|
11736
12061
|
const groupId = channelCfg?.group_id;
|
|
11737
12062
|
// Fleet topic instance
|
|
11738
12063
|
const threadId = this.fleetConfig?.instances[instanceName]?.topic_id;
|
|
12064
|
+
const worldId = this.getInstanceAdapterId(instanceName);
|
|
11739
12065
|
if (threadId != null && groupId) {
|
|
11740
|
-
|
|
12066
|
+
const out = this.renderPlaces(worldId, text, { threadId: String(threadId), ...extraOpts });
|
|
12067
|
+
adapter.sendText(String(groupId), out.text, out.opts)
|
|
11741
12068
|
.catch(e => this.logger.warn({ err: e, instanceName }, "Failed to send instance topic notification"));
|
|
11742
12069
|
return true;
|
|
11743
12070
|
}
|
|
11744
12071
|
// Classic instance: find its channelId from the classic manager
|
|
11745
12072
|
const classicChatId = this.classicChannels?.getChannelIdByInstance(instanceName);
|
|
11746
12073
|
if (classicChatId) {
|
|
11747
|
-
|
|
12074
|
+
// #1990: a ClassicBot room's readers need not be in the fleet's group, so its notices name places as plain text.
|
|
12075
|
+
adapter.sendText(classicChatId, stripPlaceRefs(text), extraOpts)
|
|
11748
12076
|
.catch(e => this.logger.warn({ err: e, instanceName }, "Failed to send classic notification"));
|
|
11749
12077
|
return true;
|
|
11750
12078
|
}
|
|
11751
12079
|
// Fallback: the instance has neither a topic nor a classic channel, so post
|
|
11752
12080
|
// where fleet-wide notices go. Not the bare group id: on Discord that is a
|
|
11753
12081
|
// guild, and the send fails inside the catch handler.
|
|
11754
|
-
const target = this.fleetNoticeTarget(
|
|
12082
|
+
const target = this.fleetNoticeTarget(worldId);
|
|
11755
12083
|
if (target) {
|
|
11756
|
-
|
|
12084
|
+
const out = this.renderPlaces(worldId, text, { ...target.opts, ...extraOpts });
|
|
12085
|
+
adapter.sendText(target.chatId, out.text, out.opts)
|
|
11757
12086
|
.catch(e => this.logger.warn({ err: e, instanceName }, "Failed to send notification (no topic)"));
|
|
11758
12087
|
return true;
|
|
11759
12088
|
}
|
|
@@ -11824,12 +12153,14 @@ export class FleetManager {
|
|
|
11824
12153
|
this.pendingNonceButtons.set(nonce, entry);
|
|
11825
12154
|
try {
|
|
11826
12155
|
const choices = opts.choices.map(c => ({ id: `${opts.prefix}${nonce}:${c.action}`, label: c.label }));
|
|
12156
|
+
// #1990: place names link for the group the prompt is posted in; the dashboard's copy is plain (for now).
|
|
12157
|
+
const posted = this.renderPlaces(opts.adapterId, opts.message, opts.threadId ? { threadId: opts.threadId } : undefined);
|
|
11827
12158
|
const sent = opts.deliver ? await opts.deliver(choices) : await opts.adapter.notifyAlert(opts.chatId, {
|
|
11828
12159
|
type: opts.alertType,
|
|
11829
12160
|
instanceName: opts.instanceName,
|
|
11830
|
-
message:
|
|
12161
|
+
message: posted.text,
|
|
11831
12162
|
choices,
|
|
11832
|
-
},
|
|
12163
|
+
}, posted.opts);
|
|
11833
12164
|
// Bind the nonce to the provider's canonical delivery address, not the
|
|
11834
12165
|
// logical routing input. Telegram, for example, represents General as
|
|
11835
12166
|
// AgEnD topic "1" on input but omits message_thread_id on the wire and in
|
|
@@ -11847,8 +12178,15 @@ export class FleetManager {
|
|
|
11847
12178
|
// Offered on the dashboard only once it is live on the platform (a failed post is disarmed above),
|
|
11848
12179
|
// and only if nothing claimed or expired it meanwhile.
|
|
11849
12180
|
if (WEB_MIRRORED_PROMPT_PREFIXES.has(opts.prefix) && this.pendingNonceButtons.get(nonce) === entry) {
|
|
12181
|
+
// #1990: on the dashboard an instance's name opens its terminal View (where the prompt is answered by hand).
|
|
12182
|
+
const web = renderPlaceRefs(opts.message, "web", target => {
|
|
12183
|
+
const route = target.kind === "instance" && Object.hasOwn(this.fleetConfig?.instances ?? {}, target.name) ? webRoute("view", target.name) : undefined;
|
|
12184
|
+
return route ? { url: route } : undefined;
|
|
12185
|
+
});
|
|
12186
|
+
const links = webTextLinks(web.textLinks, web.text);
|
|
11850
12187
|
entry.web = {
|
|
11851
|
-
text:
|
|
12188
|
+
text: web.text,
|
|
12189
|
+
...(links ? { links } : {}),
|
|
11852
12190
|
actions: opts.choices.map(c => ({ id: c.action, label: c.label })),
|
|
11853
12191
|
expiresAt: Date.now() + (opts.timeoutMs ?? NONCE_BUTTON_TIMEOUT_MS),
|
|
11854
12192
|
};
|
|
@@ -12100,7 +12438,7 @@ export class FleetManager {
|
|
|
12100
12438
|
isClassic: name => classic.has(name),
|
|
12101
12439
|
place: name => {
|
|
12102
12440
|
const world = this.worlds.get(this.getInstanceAdapterId(name) ?? "");
|
|
12103
|
-
return world ?
|
|
12441
|
+
return world ? worldPlaceOf(world) : undefined;
|
|
12104
12442
|
},
|
|
12105
12443
|
// fleet.yaml's topic only. A ClassicBot room has none here, so no link: its channel may sit in any allowed
|
|
12106
12444
|
// guild, not necessarily its world's group.
|
|
@@ -12265,9 +12603,7 @@ export class FleetManager {
|
|
|
12265
12603
|
finishedAt: Date.parse(d.finishedAt) || 0,
|
|
12266
12604
|
})),
|
|
12267
12605
|
ownerOf,
|
|
12268
|
-
worlds: () => [...this.worlds.values()].map((w) => ({
|
|
12269
|
-
id: w.id, adapter: w.adapter, place: { type: w.type, ...(w.groupId ? { groupId: w.groupId } : {}) },
|
|
12270
|
-
})),
|
|
12606
|
+
worlds: () => [...this.worlds.values()].map((w) => ({ id: w.id, adapter: w.adapter, place: worldPlaceOf(w) })),
|
|
12271
12607
|
noticeTarget: world => {
|
|
12272
12608
|
const target = this.fleetNoticeTarget(world);
|
|
12273
12609
|
return target ? { chatId: target.chatId, ...(target.opts.threadId !== undefined ? { threadId: String(target.opts.threadId) } : {}) } : null;
|
|
@@ -12919,12 +13255,12 @@ export class FleetManager {
|
|
|
12919
13255
|
* the operator's. Keep the instance notice passive and put the action in the
|
|
12920
13256
|
* same-world General topic where an administrator can make the choice. */
|
|
12921
13257
|
async notifyNormalExit(instanceName) {
|
|
12922
|
-
this.notifyInstanceTopic(instanceName, t("exit.instance_notice", instanceName));
|
|
13258
|
+
this.notifyInstanceTopic(instanceName, t("exit.instance_notice", instanceName) + placeSuffix({ kind: "general" }, "General"));
|
|
12923
13259
|
const web = this.webOnlyPromptPlace();
|
|
12924
13260
|
if (web) {
|
|
12925
13261
|
await this.postNonceButtonPrompt({
|
|
12926
13262
|
prefix: EXIT_RESTART_CALLBACK_PREFIX, alertType: "exit_restart", instanceName, ...web,
|
|
12927
|
-
message: t("exit.general_notice", instanceName),
|
|
13263
|
+
message: t("exit.general_notice", placeRef({ kind: "instance", name: instanceName }, instanceName)),
|
|
12928
13264
|
choices: [{ action: "restart", label: t("exit.restart") }, { action: "ignore", label: t("exit.ignore") }],
|
|
12929
13265
|
expiredText: t("exit.expired", instanceName),
|
|
12930
13266
|
});
|
|
@@ -12953,7 +13289,7 @@ export class FleetManager {
|
|
|
12953
13289
|
adapterId,
|
|
12954
13290
|
chatId,
|
|
12955
13291
|
threadId,
|
|
12956
|
-
message: t("exit.general_notice", instanceName),
|
|
13292
|
+
message: t("exit.general_notice", placeRef({ kind: "instance", name: instanceName }, instanceName)),
|
|
12957
13293
|
choices: [
|
|
12958
13294
|
{ action: "restart", label: t("exit.restart") },
|
|
12959
13295
|
{ action: "ignore", label: t("exit.ignore") },
|
|
@@ -13000,7 +13336,7 @@ export class FleetManager {
|
|
|
13000
13336
|
* contain usernames or secret-adjacent text and stays in daemon.log.
|
|
13001
13337
|
*/
|
|
13002
13338
|
async notifyInteractivePrompt(instanceName, kind) {
|
|
13003
|
-
this.notifyInstanceTopic(instanceName, t("interactive.instance_notice", instanceName));
|
|
13339
|
+
this.notifyInstanceTopic(instanceName, t("interactive.instance_notice", instanceName) + placeSuffix({ kind: "general" }, "General"));
|
|
13004
13340
|
// Multi-world fleets can have one General per bot/platform. Route the assist
|
|
13005
13341
|
// request to the General bound to the same adapter as the blocked instance.
|
|
13006
13342
|
const generalName = this.findGeneralInstance(this.getInstanceAdapterId(instanceName));
|
|
@@ -13011,7 +13347,7 @@ export class FleetManager {
|
|
|
13011
13347
|
const label = this.interactivePromptLabel(kind);
|
|
13012
13348
|
const prompt = {
|
|
13013
13349
|
prefix: INTERACTIVE_ASSIST_CALLBACK_PREFIX, alertType: "interactive_prompt", instanceName,
|
|
13014
|
-
message: t("interactive.general_notice", instanceName, label),
|
|
13350
|
+
message: t("interactive.general_notice", placeRef({ kind: "instance", name: instanceName }, instanceName), label),
|
|
13015
13351
|
choices: [{ action: "confirm", label: t("interactive.confirm") }, { action: "cancel", label: t("interactive.cancel") }],
|
|
13016
13352
|
expiredText: t("interactive.expired", instanceName),
|
|
13017
13353
|
extra: { generalName, promptKind: kind, assistFor: this.interactionWaitKey(instanceName) },
|
|
@@ -14610,6 +14946,14 @@ export class FleetManager {
|
|
|
14610
14946
|
const room = this.classicChannels?.getAll().find(c => c.instanceName === name);
|
|
14611
14947
|
return room ? `room:${room.channelId}:${room.adapterId ?? ""}` : null;
|
|
14612
14948
|
}
|
|
14949
|
+
/** The only Try-card publisher: no arbitrary SSE sender/role can manufacture this metadata. */
|
|
14950
|
+
publishEffectTrial(instance, offer) {
|
|
14951
|
+
const row = this.webChatHistory.recordEffectTrial(instance, offer, new Date().toISOString());
|
|
14952
|
+
if (!row)
|
|
14953
|
+
return false;
|
|
14954
|
+
broadcastSseEvent(this.sseClients, "message", row, err => this.logger.debug({ err }, "SSE client write failed; evicting"), this.webChatHistory.cursorOf(row));
|
|
14955
|
+
return true;
|
|
14956
|
+
}
|
|
14613
14957
|
/** Push an SSE event to all connected Web UI clients. */
|
|
14614
14958
|
emitSseEvent(event, data) {
|
|
14615
14959
|
// #1386: a prompt opened or closed, or an instance's state moved — "Needs you" may have changed.
|
|
@@ -14626,8 +14970,12 @@ export class FleetManager {
|
|
|
14626
14970
|
role: typeof m.role === "string" ? m.role : undefined,
|
|
14627
14971
|
buttons: m.buttons,
|
|
14628
14972
|
click: m.click,
|
|
14973
|
+
links: m.links,
|
|
14629
14974
|
});
|
|
14630
14975
|
broadcastSseEvent(this.sseClients, event, recorded, onError, this.webChatHistory.cursorOf(recorded));
|
|
14976
|
+
// #1629: live-only receipt after the recorded agent reply. Never persist/replay it or give it an SSE cursor.
|
|
14977
|
+
if (recorded.role === "agent")
|
|
14978
|
+
broadcastSseEvent(this.sseClients, "agent_reply_live", { v: 1, boot: recorded.boot, id: recorded.id, instance: recorded.instance }, onError);
|
|
14631
14979
|
return;
|
|
14632
14980
|
}
|
|
14633
14981
|
// #1266: a reply's buttons ended — the history shows it too (a later load, the public link's poll).
|
|
@@ -17295,6 +17643,14 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
17295
17643
|
catch (err) {
|
|
17296
17644
|
this.logger.warn({ err: err.message, instanceName: ch.instanceName }, "Dropping a removed room from web favorites failed");
|
|
17297
17645
|
}
|
|
17646
|
+
try {
|
|
17647
|
+
this.themeStore.forget(ch.instanceName);
|
|
17648
|
+
this.webAppearance.limiter.forget(ch.instanceName);
|
|
17649
|
+
this.effectToolsService?.forget(ch.instanceName);
|
|
17650
|
+
}
|
|
17651
|
+
catch (err) {
|
|
17652
|
+
this.logger.warn({ err: err.message, instanceName: ch.instanceName }, "Dropping a removed room's web theme failed");
|
|
17653
|
+
}
|
|
17298
17654
|
}
|
|
17299
17655
|
this.instanceWorldBinding.delete(ch.instanceName);
|
|
17300
17656
|
// #1716: say so in the reply when background work was ended with the stop.
|
|
@@ -20024,6 +20380,9 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
20024
20380
|
// /api/profiles (resolveInstanceIdentity) — the sidebar groups, searches and labels with it on every page.
|
|
20025
20381
|
const classicRooms = new Map((this.classicChannels?.getAll() ?? []).map(ch => [ch.instanceName, ch]));
|
|
20026
20382
|
const profiles = profileIdentities(this.dataDir);
|
|
20383
|
+
// #1629 r4 §1: the identity card's version per instance (the page reads the card when it moves).
|
|
20384
|
+
const cards = humanCards(this.dataDir);
|
|
20385
|
+
const colors = identityColors(this.dataDir);
|
|
20027
20386
|
// #1630: decorative — whatever goes wrong deciding an avatar never costs the frame (every instance gets its first character).
|
|
20028
20387
|
let bots = { own: new Map(), speaking: new Map() };
|
|
20029
20388
|
try {
|
|
@@ -20083,6 +20442,21 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
20083
20442
|
display_name: identity.display_name || undefined,
|
|
20084
20443
|
avatar: image.avatar,
|
|
20085
20444
|
...(image.accent ? { accent: image.accent } : {}),
|
|
20445
|
+
// #1629: the agent's theme's picture and ring colour, for viewers who show agents' themes.
|
|
20446
|
+
...(image.themeAvatar ? { theme_avatar: image.themeAvatar } : {}),
|
|
20447
|
+
...(image.themeAccent ? { theme_accent: image.themeAccent } : {}),
|
|
20448
|
+
...(() => { try {
|
|
20449
|
+
const v = cardVersion(resolveCard(name, cards.get(name), this.themeStore.get(name).theme.card));
|
|
20450
|
+
return v ? { card_v: v } : {};
|
|
20451
|
+
}
|
|
20452
|
+
catch {
|
|
20453
|
+
return {};
|
|
20454
|
+
} })(),
|
|
20455
|
+
// #1958: the identity colour's tiers (the page resolves them: the agent's only while it shows agents' themes).
|
|
20456
|
+
color: { ...(colors.get(name)?.color ? { person: colors.get(name).color } : {}), ...(image.agentColor ? { agent: image.agentColor } : {}),
|
|
20457
|
+
...(image.roleColor ? { role: image.roleColor } : {}), auto: autoIdentityColor(name) },
|
|
20458
|
+
// #1958 decision 2: where it shows — a person's choice for this instance, the agent's; else the viewer's host default.
|
|
20459
|
+
color_show: { ...(colors.get(name)?.show ? { person: colors.get(name).show } : {}), ...(image.agentShow ? { agent: image.agentShow } : {}) },
|
|
20086
20460
|
description: identity.description || undefined,
|
|
20087
20461
|
role: identity.role || undefined,
|
|
20088
20462
|
tags: identity.tags,
|
|
@@ -20109,6 +20483,8 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
20109
20483
|
connections: (this.fleetConfig?.channels ?? (this.fleetConfig?.channel ? [this.fleetConfig.channel] : [])).length,
|
|
20110
20484
|
// #1665: the host preferences' revision — a page that has another re-reads /ui/prefs (no values in this frame).
|
|
20111
20485
|
prefs_rev: this.webPrefs.rev(),
|
|
20486
|
+
// #1629: the themes' revision — the page re-reads GET /ui/themes when it moves.
|
|
20487
|
+
themes_rev: this.themeStore.rev(),
|
|
20112
20488
|
// #1685: host memory pressure while it slows or holds new agents (the dashboard's indicator; macOS elevated is
|
|
20113
20489
|
// shown here rather than in chat). null when normal, unknown or advisory.
|
|
20114
20490
|
host_memory: this.hostMemoryIndicator(),
|