@songsid/agend 2.3.3-beta.1 → 2.3.3-beta.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-cli.js +13 -112
- package/dist/agent-cli.js.map +1 -1
- package/dist/agent-endpoint.d.ts +0 -1
- package/dist/agent-endpoint.js +1 -9
- package/dist/agent-endpoint.js.map +1 -1
- package/dist/agent-whats-new.d.ts +3 -6
- package/dist/agent-whats-new.js +7 -18
- package/dist/agent-whats-new.js.map +1 -1
- package/dist/auth-api.d.ts +0 -7
- package/dist/auth-api.js +1 -15
- package/dist/auth-api.js.map +1 -1
- package/dist/authority/authority-state.js +1 -1
- package/dist/authority/authority-state.js.map +1 -1
- package/dist/authority/web-authority.d.ts +9 -4
- package/dist/authority/web-authority.js +10 -5
- package/dist/authority/web-authority.js.map +1 -1
- package/dist/backend/antigravity.js +6 -1
- package/dist/backend/antigravity.js.map +1 -1
- package/dist/backend/claude-code.d.ts +7 -0
- package/dist/backend/claude-code.js +34 -28
- package/dist/backend/claude-code.js.map +1 -1
- package/dist/backend/claude-transcript-errors.d.ts +18 -0
- package/dist/backend/claude-transcript-errors.js +19 -0
- package/dist/backend/claude-transcript-errors.js.map +1 -0
- package/dist/backend/codex-session-lookup.d.ts +16 -3
- package/dist/backend/codex-session-lookup.js +45 -11
- package/dist/backend/codex-session-lookup.js.map +1 -1
- package/dist/backend/codex.d.ts +16 -0
- package/dist/backend/codex.js +184 -35
- package/dist/backend/codex.js.map +1 -1
- package/dist/backend/grok.js +58 -12
- package/dist/backend/grok.js.map +1 -1
- package/dist/backend/kiro.js +2 -0
- package/dist/backend/kiro.js.map +1 -1
- package/dist/backend/muse.js +2 -1
- package/dist/backend/muse.js.map +1 -1
- package/dist/backend/types.d.ts +46 -0
- package/dist/backend/types.js.map +1 -1
- package/dist/bundled-skills.d.ts +4 -6
- package/dist/bundled-skills.js +10 -14
- package/dist/bundled-skills.js.map +1 -1
- package/dist/channel/adapters/discord.js +60 -17
- package/dist/channel/adapters/discord.js.map +1 -1
- package/dist/channel/mcp-server.js +5 -5
- package/dist/channel/mcp-server.js.map +1 -1
- package/dist/channel/mcp-tools.d.ts +1 -3
- package/dist/channel/mcp-tools.js +4 -58
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/classic-channel-manager.d.ts +4 -1
- package/dist/classic-channel-manager.js +21 -7
- package/dist/classic-channel-manager.js.map +1 -1
- package/dist/cli.js +21 -4
- package/dist/cli.js.map +1 -1
- package/dist/config-history.d.ts +50 -0
- package/dist/config-history.js +263 -0
- package/dist/config-history.js.map +1 -0
- package/dist/config-validator.js +7 -7
- package/dist/config-validator.js.map +1 -1
- package/dist/config.d.ts +3 -0
- package/dist/config.js +11 -4
- package/dist/config.js.map +1 -1
- package/dist/daemon.d.ts +73 -3
- package/dist/daemon.js +334 -121
- package/dist/daemon.js.map +1 -1
- package/dist/daily-summary.d.ts +8 -1
- package/dist/daily-summary.js +8 -5
- package/dist/daily-summary.js.map +1 -1
- package/dist/dashboard-live.d.ts +1 -1
- package/dist/dashboard-live.js +3 -3
- package/dist/dashboard-live.js.map +1 -1
- package/dist/dashboard-permissions.d.ts +1 -1
- package/dist/dashboard-permissions.js +1 -3
- package/dist/dashboard-permissions.js.map +1 -1
- package/dist/delivery-outbox.d.ts +4 -0
- package/dist/delivery-outbox.js +12 -2
- package/dist/delivery-outbox.js.map +1 -1
- package/dist/doctor.d.ts +4 -0
- package/dist/doctor.js +16 -1
- package/dist/doctor.js.map +1 -1
- package/dist/fleet-level-config.js +1 -1
- package/dist/fleet-level-config.js.map +1 -1
- package/dist/fleet-manager.d.ts +70 -54
- package/dist/fleet-manager.js +257 -322
- package/dist/fleet-manager.js.map +1 -1
- package/dist/fleet-process-handlers.d.ts +5 -0
- package/dist/fleet-process-handlers.js +21 -0
- package/dist/fleet-process-handlers.js.map +1 -1
- package/dist/fleet-service-owner.d.ts +11 -0
- package/dist/fleet-service-owner.js +3 -0
- package/dist/fleet-service-owner.js.map +1 -0
- package/dist/general-knowledge/skills/credential-profiles/SKILL.md +11 -1
- package/dist/general-knowledge/skills/session-management/SKILL.md +6 -0
- package/dist/general-knowledge/skills/web-appearance/SKILL.md +13 -204
- package/dist/general-knowledge/skills/web-dashboard/SKILL.md +29 -67
- package/dist/instance-lifecycle.d.ts +19 -0
- package/dist/instance-lifecycle.js +52 -10
- package/dist/instance-lifecycle.js.map +1 -1
- package/dist/locale.js +92 -42
- package/dist/locale.js.map +1 -1
- package/dist/needs-you-hub.d.ts +1 -0
- package/dist/needs-you-hub.js +6 -3
- package/dist/needs-you-hub.js.map +1 -1
- package/dist/needs-you-live.d.ts +3 -1
- package/dist/needs-you-live.js +24 -2
- package/dist/needs-you-live.js.map +1 -1
- package/dist/needs-you.d.ts +13 -2
- package/dist/needs-you.js +11 -1
- package/dist/needs-you.js.map +1 -1
- package/dist/notify-socket.d.ts +10 -0
- package/dist/notify-socket.js +45 -0
- package/dist/notify-socket.js.map +1 -0
- package/dist/outbound-handlers.js +19 -7
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.d.ts +0 -62
- package/dist/outbound-schemas.js +3 -42
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/parked-notices.d.ts +87 -0
- package/dist/parked-notices.js +194 -0
- package/dist/parked-notices.js.map +1 -0
- package/dist/public-link-progress.d.ts +5 -2
- package/dist/public-link-progress.js +4 -3
- package/dist/public-link-progress.js.map +1 -1
- package/dist/public-web-gateway.js +0 -9
- package/dist/public-web-gateway.js.map +1 -1
- package/dist/public-web-link.d.ts +12 -0
- package/dist/public-web-link.js +29 -5
- package/dist/public-web-link.js.map +1 -1
- package/dist/quota-notice.d.ts +23 -0
- package/dist/quota-notice.js +22 -0
- package/dist/quota-notice.js.map +1 -0
- package/dist/scheduler/task-board.d.ts +0 -13
- package/dist/scheduler/task-board.js +0 -14
- package/dist/scheduler/task-board.js.map +1 -1
- package/dist/sd-notify.d.ts +10 -1
- package/dist/sd-notify.js +27 -2
- package/dist/sd-notify.js.map +1 -1
- package/dist/search/search-docs.d.ts +1 -1
- package/dist/search/search-docs.js +2 -2
- package/dist/search/search-docs.js.map +1 -1
- package/dist/settings-api.d.ts +16 -0
- package/dist/settings-api.js +55 -2
- package/dist/settings-api.js.map +1 -1
- package/dist/settings-change.js +3 -1
- package/dist/settings-change.js.map +1 -1
- package/dist/settings-confirmation.d.ts +17 -3
- package/dist/settings-confirmation.js +8 -5
- package/dist/settings-confirmation.js.map +1 -1
- package/dist/settings-http-confirmation.js +31 -1
- package/dist/settings-http-confirmation.js.map +1 -1
- package/dist/setup-wizard.d.ts +3 -0
- package/dist/setup-wizard.js +4 -2
- package/dist/setup-wizard.js.map +1 -1
- package/dist/task-board-waker.d.ts +4 -1
- package/dist/task-board-waker.js +13 -2
- package/dist/task-board-waker.js.map +1 -1
- package/dist/tool-permissions.js +3 -10
- package/dist/tool-permissions.js.map +1 -1
- package/dist/topic-commands.d.ts +27 -7
- package/dist/topic-commands.js +95 -36
- package/dist/topic-commands.js.map +1 -1
- package/dist/transcript-monitor.d.ts +7 -0
- package/dist/transcript-monitor.js +48 -3
- package/dist/transcript-monitor.js.map +1 -1
- package/dist/tunnel/cloudflared.d.ts +5 -0
- package/dist/tunnel/cloudflared.js +32 -1
- package/dist/tunnel/cloudflared.js.map +1 -1
- package/dist/tunnel/named-diagnostics.d.ts +2 -1
- package/dist/tunnel/named-diagnostics.js +4 -1
- package/dist/tunnel/named-diagnostics.js.map +1 -1
- package/dist/tunnel/named-link-test.d.ts +90 -0
- package/dist/tunnel/named-link-test.js +270 -0
- package/dist/tunnel/named-link-test.js.map +1 -0
- package/dist/tunnel/purpose-lane.d.ts +3 -1
- package/dist/tunnel/purpose-lane.js +2 -0
- package/dist/tunnel/purpose-lane.js.map +1 -1
- package/dist/types.d.ts +8 -6
- package/dist/ui/chat-ambient.js +4 -4
- package/dist/ui/chat-commands.js +3 -2
- package/dist/ui/chat-store.js +4 -1
- package/dist/ui/chat-strings.js +12 -46
- package/dist/ui/chat-thread.js +51 -59
- package/dist/ui/fleet-office-skin.js +37 -14
- package/dist/ui/fleet-office.js +31 -7
- package/dist/ui/html-attachment.js +22 -8
- package/dist/ui/panel-chat.js +21 -50
- package/dist/ui/panel-needs.js +11 -9
- package/dist/ui/panel-settings.js +5 -3
- package/dist/ui/preview-panel.js +12 -21
- package/dist/ui/preview.js +10 -3
- package/dist/ui/search-dialog.js +30 -14
- package/dist/ui/settings-appearance.js +1 -3
- package/dist/ui/settings-confirm.js +18 -0
- package/dist/ui/settings-guests.js +13 -4
- package/dist/ui/settings-learn.js +1 -1
- package/dist/ui/settings-mods.js +5 -12
- package/dist/ui/settings-named-test.js +144 -0
- package/dist/ui/settings-registry.js +0 -1
- package/dist/ui/settings-strings.js +14 -14
- package/dist/ui/settings-web-authority.js +4 -3
- package/dist/ui/shared/app-i18n.js +15 -4
- package/dist/ui/shared/app-needs.js +5 -1
- package/dist/ui/shared/app-prefs.js +17 -17
- package/dist/ui/shared/app-shell.js +47 -9
- package/dist/ui/shared/app-store.js +6 -2
- package/dist/ui/shared/app.css +33 -39
- package/dist/ui/shared/app.js +5 -4
- package/dist/ui/shared/instance-nav.js +9 -9
- package/dist/ui/shared/instance-switch.js +8 -2
- package/dist/ui/shared/panel-view.js +22 -8
- package/dist/ui/shared/view-strings.js +6 -0
- package/dist/ui/shared/web-authorize.js +35 -13
- package/dist/usage/usage-api.d.ts +3 -0
- package/dist/usage/usage-api.js +5 -1
- package/dist/usage/usage-api.js.map +1 -1
- package/dist/view-api.d.ts +2 -0
- package/dist/view-api.js +64 -0
- package/dist/view-api.js.map +1 -1
- package/dist/web-api.d.ts +9 -17
- package/dist/web-api.js +257 -371
- package/dist/web-api.js.map +1 -1
- package/dist/web-appearance-tools.d.ts +0 -2
- package/dist/web-appearance-tools.js +2 -5
- package/dist/web-appearance-tools.js.map +1 -1
- package/dist/web-appearance-view.d.ts +0 -6
- package/dist/web-appearance-view.js +0 -1
- package/dist/web-appearance-view.js.map +1 -1
- package/dist/web-appearance.d.ts +14 -7
- package/dist/web-appearance.js +5 -6
- package/dist/web-appearance.js.map +1 -1
- package/dist/web-asset-parts.d.ts +1 -17
- package/dist/web-asset-parts.js +1 -15
- package/dist/web-asset-parts.js.map +1 -1
- package/dist/web-auth.js +1 -7
- package/dist/web-auth.js.map +1 -1
- package/dist/web-chat-history.d.ts +3 -7
- package/dist/web-chat-history.js +10 -17
- package/dist/web-chat-history.js.map +1 -1
- package/dist/web-commands.d.ts +9 -1
- package/dist/web-commands.js +13 -0
- package/dist/web-commands.js.map +1 -1
- package/dist/web-experimental.d.ts +2 -11
- package/dist/web-experimental.js +8 -36
- package/dist/web-experimental.js.map +1 -1
- package/dist/web-guest-access.d.ts +40 -2
- package/dist/web-guest-access.js +50 -14
- package/dist/web-guest-access.js.map +1 -1
- package/dist/web-mod-bundle.d.ts +2 -45
- package/dist/web-mod-bundle.js +15 -63
- package/dist/web-mod-bundle.js.map +1 -1
- package/dist/web-mod-store.d.ts +3 -48
- package/dist/web-mod-store.js +30 -97
- package/dist/web-mod-store.js.map +1 -1
- package/dist/web-prefs.d.ts +13 -0
- package/dist/web-prefs.js +38 -0
- package/dist/web-prefs.js.map +1 -1
- package/dist/web-preview.d.ts +6 -13
- package/dist/web-preview.js +12 -23
- package/dist/web-preview.js.map +1 -1
- package/dist/web-session.d.ts +11 -3
- package/dist/web-session.js +22 -11
- package/dist/web-session.js.map +1 -1
- package/dist/web-settings-patch.d.ts +1 -0
- package/dist/web-settings-patch.js +6 -3
- package/dist/web-settings-patch.js.map +1 -1
- package/dist/web-settings-tools.d.ts +0 -13
- package/dist/web-settings-tools.js +12 -84
- package/dist/web-settings-tools.js.map +1 -1
- package/launcher/agend-install.cjs +7 -1
- package/launcher/fleet-service-owner.cjs +150 -0
- package/package.json +1 -1
- package/dist/general-knowledge/skills/web-appearance/SKILL.canvas-worker-off.md +0 -376
- package/dist/general-knowledge/skills/web-dashboard/SKILL.canvas-worker-off.md +0 -673
- package/dist/task-board-summary.d.ts +0 -101
- package/dist/task-board-summary.js +0 -177
- package/dist/task-board-summary.js.map +0 -1
- package/dist/ui/app-effects.js +0 -251
- package/dist/ui/chat-effect-posters.js +0 -42
- package/dist/ui/chat-effects.js +0 -167
- package/dist/ui/effect-trial-controller.js +0 -124
- package/dist/ui/effect-trial-offer.js +0 -2
- package/dist/ui/effect-trial-panel.js +0 -40
- package/dist/ui/effects-waker.js +0 -2
- package/dist/ui/settings-effects.js +0 -202
- package/dist/ui/settings-owner-effects.js +0 -82
- package/dist/ui/shared/effect-app-runtime.js +0 -299
- package/dist/ui/shared/effect-baseline.js +0 -64
- package/dist/ui/shared/effect-chat-runtime.js +0 -162
- package/dist/ui/shared/effect-cursor.js +0 -126
- package/dist/ui/shared/effect-display-metadata.js +0 -39
- package/dist/ui/shared/effect-frame.js +0 -146
- package/dist/ui/shared/effect-geometry.js +0 -137
- package/dist/ui/shared/effect-host-style.js +0 -10
- package/dist/ui/shared/effect-owner-regions.js +0 -74
- package/dist/ui/shared/effect-owner-source.js +0 -122
- package/dist/ui/shared/effect-policy.js +0 -151
- package/dist/ui/shared/effect-poster-reader.js +0 -98
- package/dist/ui/shared/effect-posters.js +0 -117
- package/dist/ui/shared/effect-profiler.js +0 -184
- package/dist/ui/shared/effect-profiles.js +0 -65
- package/dist/ui/shared/effect-registry.js +0 -545
- package/dist/ui/shared/effect-runtime-loader.js +0 -236
- package/dist/ui/shared/effect-safety.js +0 -153
- package/dist/ui/shared/effect-stock-snapshot.js +0 -43
- package/dist/ui/shared/effect-stock-trust.js +0 -217
- package/dist/ui/shared/effect-trial-shadow.js +0 -62
- package/dist/ui/shared/effect-trial-source.js +0 -103
- package/dist/ui/shared/effect-trial-stage.js +0 -170
- package/dist/ui/shared/effect-trial.css +0 -40
- package/dist/ui/shared/feature-flags.js +0 -127
- package/dist/ui/shared/owner-effect-prefs.js +0 -117
- package/dist/web-effect-bootstrap.d.ts +0 -12
- package/dist/web-effect-bootstrap.js +0 -49
- package/dist/web-effect-bootstrap.js.map +0 -1
- package/dist/web-effect-bundle.d.ts +0 -28
- package/dist/web-effect-bundle.js +0 -171
- package/dist/web-effect-bundle.js.map +0 -1
- package/dist/web-effect-canonical.d.ts +0 -8
- package/dist/web-effect-canonical.js +0 -27
- package/dist/web-effect-canonical.js.map +0 -1
- package/dist/web-effect-library.d.ts +0 -114
- package/dist/web-effect-library.js +0 -530
- package/dist/web-effect-library.js.map +0 -1
- package/dist/web-effect-playback.d.ts +0 -30
- package/dist/web-effect-playback.js +0 -71
- package/dist/web-effect-playback.js.map +0 -1
- package/dist/web-effect-poster.d.ts +0 -8
- package/dist/web-effect-poster.js +0 -52
- package/dist/web-effect-poster.js.map +0 -1
- package/dist/web-effect-profiles.d.ts +0 -118
- package/dist/web-effect-profiles.js +0 -181
- package/dist/web-effect-profiles.js.map +0 -1
- package/dist/web-effect-service.d.ts +0 -93
- package/dist/web-effect-service.js +0 -335
- package/dist/web-effect-service.js.map +0 -1
- package/dist/web-effect-tools.d.ts +0 -48
- package/dist/web-effect-tools.js +0 -129
- package/dist/web-effect-tools.js.map +0 -1
- package/dist/web-effect-trials.d.ts +0 -86
- package/dist/web-effect-trials.js +0 -204
- package/dist/web-effect-trials.js.map +0 -1
- package/dist/web-effect-upload.d.ts +0 -27
- package/dist/web-effect-upload.js +0 -185
- package/dist/web-effect-upload.js.map +0 -1
- package/dist/web-effect-worker.d.ts +0 -8
- package/dist/web-effect-worker.js +0 -170
- package/dist/web-effect-worker.js.map +0 -1
- package/dist/web-effect.d.ts +0 -103
- package/dist/web-effect.js +0 -153
- package/dist/web-effect.js.map +0 -1
- package/dist/web-experimental-http.d.ts +0 -2
- package/dist/web-experimental-http.js +0 -12
- package/dist/web-experimental-http.js.map +0 -1
- package/dist/web-mod-agent.d.ts +0 -50
- package/dist/web-mod-agent.js +0 -207
- package/dist/web-mod-agent.js.map +0 -1
package/dist/fleet-manager.js
CHANGED
|
@@ -27,6 +27,7 @@ import { clearPublicLinkOwner, readPublicLinkOwner, writePublicLinkOwner } from
|
|
|
27
27
|
import { PUBLIC_PREVIEW_FRAME_SRC, PUBLIC_PREVIEW_TUNNEL_DIR, PublicPreview, publicPreviewWanted } from "./public-preview.js";
|
|
28
28
|
import { CloudflaredProvider } from "./tunnel/cloudflared.js";
|
|
29
29
|
import { ensureCloudflared } from "./tunnel/cloudflared-install.js";
|
|
30
|
+
import { namedTestLane, runNamedLinkTest } from "./tunnel/named-link-test.js";
|
|
30
31
|
import { OFFICE_SHARE_TUNNEL_DIR, OfficeShareService } from "./office-share-service.js";
|
|
31
32
|
import { ALL_OFFICES, WHOLE_FLEET } from "./office-share.js";
|
|
32
33
|
import { TunnelPurposeLane } from "./tunnel/purpose-lane.js";
|
|
@@ -67,7 +68,7 @@ const __dirname = dirname(__filename);
|
|
|
67
68
|
const DEFAULT_OPEN_ACCESS = { mode: "open", allowed_users: [], max_pending_codes: 0, code_expiry_minutes: 0 };
|
|
68
69
|
import { STATUS_EMOJI_CONFIG_KEYS, STATUS_EMOJI_KEYS, STATUS_EMOJI_SUGGESTIONS, TELEGRAM_REACTION_EMOJIS, builtinStatusEmojis, customEmojiValue, emojiImageUrl, normalizeEmoji, previewStatusEmojis, reactionForm, reactionMatchKey, resolveStatusEmojis, statusAvoidList, statusEmojiProblem, statusMatchKey, statusMatchKeys, textForm, } from "./status-emojis.js";
|
|
69
70
|
import { isProbeableRouteTarget } from "./fleet-context.js";
|
|
70
|
-
import { getEffectiveInstanceDefaults, loadFleetConfig, loadRawFleetConfig, DEFAULT_COST_GUARD, DEFAULT_DAILY_SUMMARY, DEFAULT_INSTANCE_CONFIG, autoGeneralInstanceConfig } from "./config.js";
|
|
71
|
+
import { getEffectiveInstanceDefaults, loadFleetConfig, loadRawFleetConfig, loadFleetConfigSource, loadRawFleetConfigSource, DEFAULT_COST_GUARD, DEFAULT_DAILY_SUMMARY, DEFAULT_INSTANCE_CONFIG, autoGeneralInstanceConfig } from "./config.js";
|
|
71
72
|
import { EventLog } from "./event-log.js";
|
|
72
73
|
import { binaryProbe } from "./binary-probe.js";
|
|
73
74
|
import { AVAILABILITY_STALE_MS, BackendAvailabilityStore, hostAvailabilityProbes } from "./backend-availability.js";
|
|
@@ -80,6 +81,7 @@ import { TmuxManager } from "./tmux-manager.js";
|
|
|
80
81
|
import { AccessManager } from "./channel/access-manager.js";
|
|
81
82
|
import { IpcClient } from "./channel/ipc-bridge.js";
|
|
82
83
|
import { usesChannelAddress } from "./channel/mcp-tools.js";
|
|
84
|
+
import { ParkedNotices, PARKED_NOTICE_LEDGER_FILE } from "./parked-notices.js";
|
|
83
85
|
import { formatFleetTime } from "./tz-utils.js";
|
|
84
86
|
import { createAdapter } from "./channel/factory.js";
|
|
85
87
|
import { isWebChannelEcho, WEB_ECHO_PREFIX } from "./web-channel-echo.js";
|
|
@@ -100,7 +102,6 @@ import { escapeTelegramHtml, scheduleClock, scheduleRetryLabel } from "./schedul
|
|
|
100
102
|
import { DEFAULT_SCHEDULER_CONFIG } from "./scheduler/index.js";
|
|
101
103
|
import { TopicCommands, saveCommandForBackend, parseSaveFilename, parsePauseWakeCommand, parseCompactCommand, SAVE_FILENAME_RE, resolveInstanceContext, forgetInstanceContext, readStatuslineJson, statuslineModel } from "./topic-commands.js";
|
|
102
104
|
import { DailySummary } from "./daily-summary.js";
|
|
103
|
-
import { TaskBoardSummaryClock, buildSummary, isQuiet, renderSummary, summaryMarkFile, summaryTime } from "./task-board-summary.js";
|
|
104
105
|
import { WebhookEmitter } from "./webhook-emitter.js";
|
|
105
106
|
import { TmuxControlClient } from "./tmux-control.js";
|
|
106
107
|
import { safeHandler } from "./safe-async.js";
|
|
@@ -177,13 +178,6 @@ import { OfficeSkinStore } from "./web-office-skins.js";
|
|
|
177
178
|
import { OfficeActs } from "./web-office-acts.js";
|
|
178
179
|
import { OFFICE_PACK_TOOLS, OfficePackTools } from "./web-office-pack-tools.js";
|
|
179
180
|
import { WebAppearance } from "./web-appearance.js";
|
|
180
|
-
import { WebEffectLibrary } from "./web-effect-library.js";
|
|
181
|
-
import { WebEffectTrials } from "./web-effect-trials.js";
|
|
182
|
-
import { WebEffectService } from "./web-effect-service.js";
|
|
183
|
-
import { WebEffectProfiles } from "./web-effect-profiles.js";
|
|
184
|
-
import { WebEffectTools, WEB_EFFECT_TOOLS } from "./web-effect-tools.js";
|
|
185
|
-
import { resolveEffectParams } from "./web-effect.js";
|
|
186
|
-
import { cw1EffectChecker } from "./web-mod-bundle.js";
|
|
187
181
|
import { effectiveAppearanceLimits } from "./web-appearance-limits.js";
|
|
188
182
|
import { WEB_NAME_FALLBACK, safeWebName, validateWebName, webNameInUse, webPersonKey } from "./web-profile.js";
|
|
189
183
|
import { ReplyButtonStore, parseReplyButtons, replyButtonClickText, replyButtonsFallbackText, REPLY_BUTTON_PREFIX } from "./reply-buttons.js";
|
|
@@ -196,7 +190,6 @@ import { autoIdentityColor } from "./web-identity-color.js";
|
|
|
196
190
|
import { AppearanceTools } from "./web-appearance-tools.js";
|
|
197
191
|
import { cardVersion, resolveCard } from "./web-card.js";
|
|
198
192
|
import { ModStore } from "./web-mod-store.js";
|
|
199
|
-
import { AgentMods } from "./web-mod-agent.js";
|
|
200
193
|
import { downloadStickerImage } from "./channel/sticker-download.js";
|
|
201
194
|
import { WebImageCache, imageMimeForFile, WEB_IMAGE_MAX_AGE_MS, WEB_IMAGE_MAX_BYTES, WEB_IMAGE_RETRY_MS } from "./web-images.js";
|
|
202
195
|
import { accentColor, avatarFor, ownBotConnections } from "./instance-avatar.js";
|
|
@@ -215,7 +208,7 @@ import { crossInstanceVisibility, runVisibilityCommand, xmsgVisibility } from ".
|
|
|
215
208
|
import { SEARCH_RETENTION_DAYS, SearchIndex } from "./search/search-index.js";
|
|
216
209
|
import { chatDoc, taskDoc, xmsgDoc } from "./search/search-docs.js";
|
|
217
210
|
import { WebSettingsTools } from "./web-settings-tools.js";
|
|
218
|
-
import { bindGuestRequest, guestRouteDecision } from "./web-guest-access.js";
|
|
211
|
+
import { GUEST_COMMAND_RUNS, GUEST_PREF_WRITES, GUEST_SEARCHES, bindGuestRequest, guestRouteDecision } from "./web-guest-access.js";
|
|
219
212
|
import { GUEST_ROOM_TICK_MS, GuestRoomMembership, guestGroupRooms } from "./web-guest-rooms.js";
|
|
220
213
|
import { Delegations, TrafficLog, trafficItem } from "./fleet-traffic.js";
|
|
221
214
|
import { installedChannel, updateNoticeKey } from "./update-check.js";
|
|
@@ -235,6 +228,7 @@ import { remoteCardAction, remoteCardChoiceLabel, remoteCardOutcomeText, remoteC
|
|
|
235
228
|
import { recentChatContext } from "./log-tail.js";
|
|
236
229
|
import { describeSignalSource, recordInternalRequest, withOrigin } from "./fleet-control-audit.js";
|
|
237
230
|
import { handleAgentRequest, ToolNotPermittedError } from "./agent-endpoint.js";
|
|
231
|
+
import { ConfigHistory, ConfigSyntaxError, configIssueText } from "./config-history.js";
|
|
238
232
|
import { ClassicChannelManager, classicWebEchoOn, getClassicBackendChoices, inferClassicChannelType, isSelectableClassicBackend, readClassicLastActivityAt, readClassicRoomNames } from "./classic-channel-manager.js";
|
|
239
233
|
import { assertExplicitInstanceRemoval } from "./instance-removal.js";
|
|
240
234
|
import { KNOWN_BACKENDS, validateFleetConfig } from "./config-validator.js";
|
|
@@ -259,14 +253,14 @@ import { buildOrgChart } from "./web-org.js";
|
|
|
259
253
|
import { topMemoryHogs } from "./memory-hogs.js";
|
|
260
254
|
import { GENERAL_POINTING_REFUSALS, SNOWFLAKE, hasPlaceRefs, placeRef, webRoute, webTextLinks, placeSuffix, renderPlaceRefs, stripPlaceRefs, topicUrl, worldPlaceOf } from "./topic-link.js";
|
|
261
255
|
import { CacheService, cacheWindow } from "./cache-service.js";
|
|
262
|
-
import { runWebCommand } from "./web-commands.js";
|
|
256
|
+
import { guestWebCommands, runWebCommand } from "./web-commands.js";
|
|
263
257
|
import { CompactWatch, launchIdentity } from "./compact-watch.js";
|
|
264
258
|
import { agyLegacyAgentsFile, danglingSymlink, migrateAgyAgentsFile } from "./backend/antigravity.js";
|
|
265
259
|
import { claudeConfigDirFor, claudeProjectKey } from "./backend/claude-code.js";
|
|
266
260
|
import { sharedRolloutIndex } from "./rollout-index.js";
|
|
267
261
|
import { TASK_BOARD_ON_NOTICE, dashboardRefusalIn, taskBoardSwitchedOn } from "./dashboard-permissions.js";
|
|
268
262
|
import { mayUseTool, resolveToolSet, scheduleOpRefusal, toolForIpcType, toolRefusedMessage, } from "./tool-permissions.js";
|
|
269
|
-
import { authorizeSession, decideWebGate, loadOrCreateWebToken, readWebToken, sessionCookieUpgrade } from "./web-auth.js";
|
|
263
|
+
import { authorizeSession, decideWebGate, loadOrCreateWebToken, readSessionCookies, readWebToken, sessionCookieUpgrade } from "./web-auth.js";
|
|
270
264
|
import { bypassesWebGate, handleAuthRequest, serveGuestRefusedPage, serveSigninPage } from "./auth-api.js";
|
|
271
265
|
import { isWebPageNavigation } from "./web-shell-routes.js";
|
|
272
266
|
import { guestSessionPolicy, localSessionPolicy, tokenEpoch, WebSessionStore } from "./web-session.js";
|
|
@@ -279,10 +273,8 @@ import { decisionListAnswer } from "./decision-list.js";
|
|
|
279
273
|
import { CodeRequestLimiter, ReturningDevices } from "./web-returning-device.js";
|
|
280
274
|
import { allowedHostNames, applyWebSecurityHeaders, hostnameOf, isHostAllowed, LOOPBACK_HOST_NAMES, WEB_HOST_REJECTED_MESSAGE } from "./web-host-guard.js";
|
|
281
275
|
import { createPreviewListener, normalizeOrigin, previewAvailability, previewSettings } from "./web-preview.js";
|
|
282
|
-
import {
|
|
283
|
-
import { isCanvasWorkerHttpPath } from "./web-experimental-http.js";
|
|
276
|
+
import { isCanvasWorkerTool, CANVAS_WORKER_EXPERIMENTAL_MESSAGE } from "./web-experimental.js";
|
|
284
277
|
import { bundledSkillContents } from "./bundled-skills.js";
|
|
285
|
-
import { createEffectBootstrap } from "./web-effect-bootstrap.js";
|
|
286
278
|
import { fleetLevelDifferences, fleetLevelSignature, appendedConnections } from "./fleet-level-config.js";
|
|
287
279
|
import { checkSelfRestartAllowance, recordSelfRestartAttempt } from "./self-restart-limit.js";
|
|
288
280
|
import { SecretStore } from "./secret-store.js";
|
|
@@ -797,8 +789,6 @@ export class FleetManager {
|
|
|
797
789
|
costGuard = null;
|
|
798
790
|
statuslineWatcher;
|
|
799
791
|
dailySummary = null;
|
|
800
|
-
/** #2246 step 3: the task board's daily summary for the person (task-board-summary.ts). */
|
|
801
|
-
taskBoardSummary = null;
|
|
802
792
|
dailyTipScheduler = null;
|
|
803
793
|
/** The task board's wakes (step 2): an instance hears when someone else changes a card it owns or handed out. */
|
|
804
794
|
taskBoardWaker = null;
|
|
@@ -909,6 +899,12 @@ export class FleetManager {
|
|
|
909
899
|
kiroMigrationRuns = new Map();
|
|
910
900
|
/** #1386 "Needs you": the list, its live chat messages and Acknowledge. Started with the fleet (finishStartup). */
|
|
911
901
|
needsYou = null;
|
|
902
|
+
configHistory = null;
|
|
903
|
+
configHistoryTimer = null;
|
|
904
|
+
loadedFleetSource = null;
|
|
905
|
+
getConfigHistory() {
|
|
906
|
+
return this.configHistory ??= new ConfigHistory(this.dataDir, () => this.needsYou?.poke(), message => this.logger.warn(message));
|
|
907
|
+
}
|
|
912
908
|
/** When the fleet first saw an instance crashed, for its "Needs you" age; cleared when it is not. */
|
|
913
909
|
needsCrashedAt = new Map();
|
|
914
910
|
/** Pause reason/time read from the marker, per paused instance; dropped on any attention change of it, and at most
|
|
@@ -984,11 +980,6 @@ export class FleetManager {
|
|
|
984
980
|
collabInstances = new Set();
|
|
985
981
|
// Health endpoint
|
|
986
982
|
healthServer = null;
|
|
987
|
-
/** #2118: independent of HTML preview availability/port; fixed bootstrap only, no effect resources. */
|
|
988
|
-
effectBootstrap = createEffectBootstrap();
|
|
989
|
-
/** Fixed trusted document coherence, independent of the HTML preview listener; never a resource grant. */
|
|
990
|
-
get effectBoot() { return this.effectBootstrap.boot; }
|
|
991
|
-
canvasWorkerEnabled() { return canvasWorkerEnabled(this.fleetConfig?.web); }
|
|
992
983
|
healthServerDrain = null;
|
|
993
984
|
/** #1306: the preview listener (health_port + 1 by default), and whether it is listening. */
|
|
994
985
|
previewListener = null;
|
|
@@ -1167,7 +1158,7 @@ export class FleetManager {
|
|
|
1167
1158
|
/** #1629: every instance's theme, placements, assets and lock (web-themes.json). */
|
|
1168
1159
|
get themeStore() {
|
|
1169
1160
|
// r4.1 Q3 (b): the card's limits are read from fleet.yaml at each upload (a change applies without a restart).
|
|
1170
|
-
return (this.themeStoreInstance ??= new ThemeStore(this.dataDir, { limits: () => effectiveAppearanceLimits(this.fleetConfig?.web?.appearance).limits
|
|
1161
|
+
return (this.themeStoreInstance ??= new ThemeStore(this.dataDir, { limits: () => effectiveAppearanceLimits(this.fleetConfig?.web?.appearance).limits }));
|
|
1171
1162
|
}
|
|
1172
1163
|
officeStoreInstance;
|
|
1173
1164
|
/** Fleet → Org chart → Office: the offices people made (web-offices.json, beside web-themes.json). */
|
|
@@ -1179,6 +1170,12 @@ export class FleetManager {
|
|
|
1179
1170
|
officeSkinWrites = new OfficeWriteLimiter(undefined, { perWindow: 10, windowMs: 60_000 });
|
|
1180
1171
|
/** How often a person may save the offices (PUT /ui/offices); memory only, as the appearance limiter. */
|
|
1181
1172
|
officeWrites = new OfficeWriteLimiter();
|
|
1173
|
+
/** #2855: a guest's preference saves, per account. */
|
|
1174
|
+
guestPrefWrites = new OfficeWriteLimiter(undefined, GUEST_PREF_WRITES);
|
|
1175
|
+
/** #2855: a guest's commands, per account. */
|
|
1176
|
+
guestCommandRuns = new OfficeWriteLimiter(undefined, GUEST_COMMAND_RUNS);
|
|
1177
|
+
/** #2855: a trusted guest's searches, per account. */
|
|
1178
|
+
guestSearches = new OfficeWriteLimiter(undefined, { perWindow: GUEST_SEARCHES.perWindow, windowMs: GUEST_SEARCHES.windowMs });
|
|
1182
1179
|
officeSharesInstance;
|
|
1183
1180
|
/**
|
|
1184
1181
|
* #2636: Office share links — the live Office view for people outside the fleet, read-only, on a quick tunnel of
|
|
@@ -1374,96 +1371,16 @@ export class FleetManager {
|
|
|
1374
1371
|
}
|
|
1375
1372
|
catch { /* the next frame carries the rev */ } }));
|
|
1376
1373
|
}
|
|
1377
|
-
effectLibraryInstance;
|
|
1378
|
-
effectTrialsInstance;
|
|
1379
|
-
/** Lazy, single-owner storage. Register every host reference source before any operation can collect. */
|
|
1380
|
-
get effectLibrary() {
|
|
1381
|
-
if (!this.effectLibraryInstance) {
|
|
1382
|
-
const library = new WebEffectLibrary(this.dataDir);
|
|
1383
|
-
// Providers are lazy; publish only after every registration succeeds.
|
|
1384
|
-
library.registerReferenceSource("themes", () => {
|
|
1385
|
-
if (!this.themeStore.readable())
|
|
1386
|
-
throw new Error("themes_unreadable");
|
|
1387
|
-
return this.themeStore.placedEffects().flatMap(row => row.ids.map(id => ({ id, holder: `instance:${row.name}` })));
|
|
1388
|
-
});
|
|
1389
|
-
library.registerReferenceSource("prefs", () => this.webPrefs.ownerEffectReferences());
|
|
1390
|
-
library.registerReferenceSource("mods", () => {
|
|
1391
|
-
const mods = this.mods.list();
|
|
1392
|
-
if (mods.unreadable)
|
|
1393
|
-
throw new Error("mods_unreadable");
|
|
1394
|
-
return mods.mods.flatMap(mod => mod.effects.map(effect => ({ id: effect.id, holder: `mod:${mod.id}` })));
|
|
1395
|
-
});
|
|
1396
|
-
this.effectTrialsInstance = new WebEffectTrials({ library, known: name => this.isKnownInstance(name) });
|
|
1397
|
-
this.effectLibraryInstance = library;
|
|
1398
|
-
}
|
|
1399
|
-
return this.effectLibraryInstance;
|
|
1400
|
-
}
|
|
1401
|
-
/** Volatile Try offers and session-bound runs share this library. */
|
|
1402
|
-
get effectTrials() { if (!this.effectTrialsInstance)
|
|
1403
|
-
void this.effectLibrary; return this.effectTrialsInstance; }
|
|
1404
|
-
effectProfilesInstance;
|
|
1405
|
-
get effectProfiles() { return (this.effectProfilesInstance ??= new WebEffectProfiles(this.dataDir)); }
|
|
1406
|
-
effectServiceInstance;
|
|
1407
|
-
get effectService() {
|
|
1408
|
-
return (this.effectServiceInstance ??= new WebEffectService({ library: this.effectLibrary, themes: this.themeStore, profiles: this.effectProfiles, prefs: this.webPrefs, trials: this.effectTrials,
|
|
1409
|
-
known: name => this.isKnownInstance(name), changed: () => { try {
|
|
1410
|
-
this.emitSseEvent("status", this.getUiStatus());
|
|
1411
|
-
}
|
|
1412
|
-
catch { /* next status carries revision */ } } }));
|
|
1413
|
-
}
|
|
1414
|
-
effectToolsService;
|
|
1415
|
-
/** Caller identity is supplied only by the owned IPC connection / verified agent endpoint token. */
|
|
1416
|
-
get effectTools() {
|
|
1417
|
-
return (this.effectToolsService ??= new WebEffectTools({
|
|
1418
|
-
library: this.effectLibrary, limiter: this.webAppearance.limiter,
|
|
1419
|
-
authorize: (name, buckets) => !this.isKnownInstance(name) ? { error: "unknown_instance" } : this.webAppearance.authorizeEffect(name, buckets),
|
|
1420
|
-
placed: name => this.themeStore.get(name).placements.effects,
|
|
1421
|
-
place: (name, effects) => {
|
|
1422
|
-
const before = this.themeStore.get(name).placements.effects;
|
|
1423
|
-
const explicit = new Set(effects.filter(e => typeof e !== "string").map(e => e.id));
|
|
1424
|
-
const w = this.themeStore.setPlacements(name, { effects }, "agent", what => {
|
|
1425
|
-
// An unchanged human/private placement may be retained, never exposed or newly acquired by an agent.
|
|
1426
|
-
const r = explicit.has(what.id) ? this.effectLibrary.read(what.id, { kind: "agent", id: name })
|
|
1427
|
-
: this.effectLibrary.readReferenced(what.id, `instance:${name}`);
|
|
1428
|
-
if (!r.ok)
|
|
1429
|
-
return r.error;
|
|
1430
|
-
const params = explicit.has(what.id) ? what.params : before.find(e => e.id === what.id)?.params;
|
|
1431
|
-
return resolveEffectParams(r.entry.manifest, params ?? {}).ok ? null : "effect_params";
|
|
1432
|
-
});
|
|
1433
|
-
return w.ok ? { ok: true, rev: w.rev } : { ok: false, error: w.error };
|
|
1434
|
-
},
|
|
1435
|
-
changed: () => { try {
|
|
1436
|
-
this.emitSseEvent("status", this.getUiStatus());
|
|
1437
|
-
}
|
|
1438
|
-
catch { /* later status carries placements */ } },
|
|
1439
|
-
// A nonmutating own/shared offer is not an appearance write. Only an explicit non-public admin-session click may start it.
|
|
1440
|
-
preview: async (instance, id) => {
|
|
1441
|
-
const result = this.effectTrials.offer(instance, id);
|
|
1442
|
-
if (!result.ok)
|
|
1443
|
-
return { error: "not_found" };
|
|
1444
|
-
const present = this.webChatHistory.list(instance).some(row => row.effectTrial?.offer === result.offer.offer);
|
|
1445
|
-
if (!present && !this.publishEffectTrial(instance, result.offer))
|
|
1446
|
-
return { error: "trial_unavailable" };
|
|
1447
|
-
return { id, status: "offered", expiresAt: result.offer.expiresAt, message: "A trial card is in your web chat. A signed-in admin outside the public link can start it; nothing was applied." };
|
|
1448
|
-
},
|
|
1449
|
-
profiles: id => this.effectProfiles.read(id),
|
|
1450
|
-
profileSummary: id => this.effectService.profileSummary(id),
|
|
1451
|
-
profileSummaries: ids => this.effectService.profileSummaries(ids),
|
|
1452
|
-
playbackSummary: (name, id) => this.effectService.playbackSummary(name, id),
|
|
1453
|
-
}));
|
|
1454
|
-
}
|
|
1455
1374
|
appearanceToolsService;
|
|
1456
1375
|
/** #1629 r4 §1: get_web_appearance / preview_web_appearance — the caller's own look, resolved as the page resolves it. */
|
|
1457
1376
|
get appearanceTools() {
|
|
1458
1377
|
return (this.appearanceToolsService ??= new AppearanceTools({
|
|
1459
|
-
canvasWorkerEnabled: () => this.canvasWorkerEnabled(),
|
|
1460
1378
|
frame: (name) => (this.getUiStatus().instances.find(i => i.name === name) ?? { name }),
|
|
1461
1379
|
theme: (name) => {
|
|
1462
1380
|
const r = this.themeStore.get(name);
|
|
1463
1381
|
return { theme: r.theme, locked: r.locked, widgets: r.placements.widgets, ambient: r.placements.ambient,
|
|
1464
1382
|
glyphAsset: (id) => this.themeStore.asset(name, id) };
|
|
1465
1383
|
},
|
|
1466
|
-
effects: name => this.themeStore.readable() ? this.themeStore.get(name).placements.effects.map(p => ({ id: p.id, by: p.by, ...(p.params ? { params: p.params } : {}), playback: this.effectService.playbackSummary(name, p.id) })) : [],
|
|
1467
1384
|
card: (name) => resolveCard(name, humanCards(this.dataDir).get(name), this.themeStore.get(name).theme.card),
|
|
1468
1385
|
// #1958: the host's identity-colour settings, as the page reads them (an unreadable prefs file: the defaults).
|
|
1469
1386
|
colorPrefs: () => { try {
|
|
@@ -1492,49 +1409,17 @@ export class FleetManager {
|
|
|
1492
1409
|
return Object.hasOwn(this.fleetConfig?.instances ?? {}, name) || (this.classicChannels?.getAll() ?? []).some((ch) => ch.instanceName === name);
|
|
1493
1410
|
}
|
|
1494
1411
|
modStore;
|
|
1495
|
-
/**
|
|
1496
|
-
* #1629 r4-5 / B2: installed mods use the same validated library and scoped holder claims as direct uploads.
|
|
1497
|
-
*/
|
|
1412
|
+
/** #1629 r4-5: installed mods (#2799: a bundle that carries Canvas Worker effects is refused). */
|
|
1498
1413
|
get mods() {
|
|
1499
1414
|
return (this.modStore ??= new ModStore(this.dataDir, {
|
|
1500
1415
|
themes: this.themeStore,
|
|
1501
1416
|
prefs: this.webPrefs,
|
|
1502
1417
|
ownerCss: this.ownerCss,
|
|
1503
|
-
|
|
1504
|
-
canvasWorkerEnabled: () => this.canvasWorkerEnabled(),
|
|
1505
|
-
context: () => ({ agendVersion: agendVersion(), limits: this.themeStore.limits(), effects: cw1EffectChecker() }),
|
|
1418
|
+
context: () => ({ agendVersion: agendVersion(), limits: this.themeStore.limits() }),
|
|
1506
1419
|
isKnownInstance: (name) => this.isKnownInstance(name),
|
|
1507
1420
|
prefsIdentity: OPERATOR,
|
|
1508
1421
|
}));
|
|
1509
1422
|
}
|
|
1510
|
-
agentModsService;
|
|
1511
|
-
/** #1629 r4-5b: export_web_mod / propose_web_mod — the caller's own look as a file, and a proposal for a person. */
|
|
1512
|
-
get agentMods() {
|
|
1513
|
-
return (this.agentModsService ??= new AgentMods({
|
|
1514
|
-
themes: this.themeStore, mods: this.mods, limiter: this.webAppearance.limiter,
|
|
1515
|
-
webConfig: () => this.fleetConfig?.web ?? null,
|
|
1516
|
-
// The mod store's own: a proposal is checked exactly as the person's import of it will be.
|
|
1517
|
-
context: () => this.mods.checkContext(),
|
|
1518
|
-
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; },
|
|
1519
|
-
notice: (instance, text) => { try {
|
|
1520
|
-
this.emitSseEvent("message", { instance, sender: instance, role: "status", text, ts: new Date().toISOString() });
|
|
1521
|
-
}
|
|
1522
|
-
catch { /* the page reads it from Settings too */ } },
|
|
1523
|
-
changed: () => { try {
|
|
1524
|
-
this.emitSseEvent("status", this.getUiStatus());
|
|
1525
|
-
}
|
|
1526
|
-
catch { /* the next frame */ } },
|
|
1527
|
-
}));
|
|
1528
|
-
}
|
|
1529
|
-
/**
|
|
1530
|
-
* #1629 r4-5c: an effect as this person may carry it away in an export — their own claim, or one a person shared —
|
|
1531
|
-
* read with their principal (Sol's ACL). Being able to view or run a placed effect never grants packaging someone
|
|
1532
|
-
* else's private claim: the library's reference read is runtime access, not an export path. Null: not theirs.
|
|
1533
|
-
*/
|
|
1534
|
-
modPlacedEffect(person, id) {
|
|
1535
|
-
const r = this.effectLibrary.read(id, { kind: "human", id: person });
|
|
1536
|
-
return r.ok ? { name: r.entry.manifest.name, files: r.entry.files } : null;
|
|
1537
|
-
}
|
|
1538
1423
|
/** #1629 r4-5: an instance's card text as every viewer sees it (a person's fields over the agent's). */
|
|
1539
1424
|
modCardText(name) {
|
|
1540
1425
|
const card = resolveCard(name, humanCards(this.dataDir).get(name), this.themeStore.get(name).theme.card);
|
|
@@ -1557,8 +1442,7 @@ export class FleetManager {
|
|
|
1557
1442
|
*/
|
|
1558
1443
|
get webSettings() {
|
|
1559
1444
|
return (this.webSettingsToolsService ??= new WebSettingsTools({
|
|
1560
|
-
|
|
1561
|
-
prefs: this.webPrefs, ownerCss: this.ownerCss, mods: this.mods, effects: this.effectService,
|
|
1445
|
+
prefs: this.webPrefs, ownerCss: this.ownerCss,
|
|
1562
1446
|
instances: () => [...Object.keys(this.fleetConfig?.instances ?? {}),
|
|
1563
1447
|
...(this.classicChannels?.getAll() ?? []).map((ch) => ch.instanceName).filter((n) => !Object.hasOwn(this.fleetConfig?.instances ?? {}, n))],
|
|
1564
1448
|
colour: (name) => personIdentityColour(this.dataDir, name),
|
|
@@ -1567,7 +1451,6 @@ export class FleetManager {
|
|
|
1567
1451
|
bannerFocus: (name) => personBannerFocus(this.dataDir, name),
|
|
1568
1452
|
setBannerFocus: (name, focus) => { const w = setPersonBannerFocus(this.dataDir, name, focus); if (w.ok)
|
|
1569
1453
|
this.identityChanged(); return w; },
|
|
1570
|
-
placements: (name) => this.themeStore.get(name).placements.effects.map((e) => (typeof e === "string" ? { id: e } : e)),
|
|
1571
1454
|
ambient: (name) => Object.fromEntries(Object.entries(this.themeStore.get(name).placements.ambient).flatMap(([k, a]) => a ? [[k, { spec: a.spec, by: a.by }]] : [])),
|
|
1572
1455
|
setAmbient: (name, spec, which) => this.webAppearance.humanSetAmbient(name, spec, which),
|
|
1573
1456
|
announce: () => { try {
|
|
@@ -1881,7 +1764,7 @@ export class FleetManager {
|
|
|
1881
1764
|
retire();
|
|
1882
1765
|
continue;
|
|
1883
1766
|
}
|
|
1884
|
-
if (store.attachPrompt(view.id, retire))
|
|
1767
|
+
if (store.attachPrompt(view.id, retire, { platform: adapter.type, connection: adapterId }))
|
|
1885
1768
|
return;
|
|
1886
1769
|
}
|
|
1887
1770
|
catch {
|
|
@@ -2408,12 +2291,10 @@ export class FleetManager {
|
|
|
2408
2291
|
const notice = silent
|
|
2409
2292
|
? t(`delivery.fleet_${row.state}_silent_schedule`, row.targetInstance, row.operationId, row.deliveryId, row.lastError ?? "unknown")
|
|
2410
2293
|
: t(`delivery.fleet_${row.state}`, row.targetInstance, row.operationId, row.deliveryId);
|
|
2294
|
+
// #2804: log-only. The person sees a failed or unconfirmed delivery as a Needs-you item (with Acknowledge),
|
|
2295
|
+
// not as a separate General post; the sending agent still gets its delivery-outcome notice.
|
|
2411
2296
|
this.logger.error({ deliveryId: row.deliveryId, operationId: row.operationId, target: row.targetInstance, state: row.state,
|
|
2412
2297
|
cause: row.lastError, ...(silent ? { silentSchedule: true } : {}) }, notice);
|
|
2413
|
-
// A self-scheduled silent heartbeat stays log-only, including the
|
|
2414
|
-
// operator alert; another source still receives the normal notice.
|
|
2415
|
-
if (!silent || row.sourceInstance !== row.targetInstance)
|
|
2416
|
-
this.notifyFleetError(notice);
|
|
2417
2298
|
}
|
|
2418
2299
|
}
|
|
2419
2300
|
});
|
|
@@ -2445,8 +2326,9 @@ export class FleetManager {
|
|
|
2445
2326
|
if (event.count)
|
|
2446
2327
|
this.needsYou?.poke(); // #1386: rows that expired failed — they wait on someone now
|
|
2447
2328
|
this.wakeCoordinator?.kick();
|
|
2329
|
+
// #2804: the expired rows are Needs-you items now; the count is logged, not posted to General.
|
|
2448
2330
|
if (event.count)
|
|
2449
|
-
this.
|
|
2331
|
+
this.logger.warn({ count: event.count, uncertain: event.uncertain ?? 0 }, t("delivery.expired", event.count, event.uncertain ?? 0));
|
|
2450
2332
|
});
|
|
2451
2333
|
this.scheduleDeliveryOutboxPump();
|
|
2452
2334
|
}
|
|
@@ -2931,7 +2813,9 @@ export class FleetManager {
|
|
|
2931
2813
|
if ((current?.state === "delivering" || current?.state === "submission_started")
|
|
2932
2814
|
&& current.targetDaemonBootId === claimed.targetDaemonBootId
|
|
2933
2815
|
&& current.managerBootId === claimed.managerBootId) {
|
|
2934
|
-
|
|
2816
|
+
// #2804: logged, not posted. A delivery still in flight needs nobody yet; if it ends failed or unconfirmed it
|
|
2817
|
+
// becomes a Needs-you item.
|
|
2818
|
+
this.logger.warn({ deliveryId, target: claimed.targetInstance }, t("delivery.lane_waiting", claimed.targetInstance, Math.round(DURABLE_DELIVERY_LANE_ALERT_MS / 60_000), deliveryId));
|
|
2935
2819
|
}
|
|
2936
2820
|
}, DURABLE_DELIVERY_LANE_ALERT_MS);
|
|
2937
2821
|
alertTimer.unref?.();
|
|
@@ -3103,7 +2987,8 @@ export class FleetManager {
|
|
|
3103
2987
|
// fleet.yaml, sent SIGHUP, and got no reaction and no explanation.
|
|
3104
2988
|
this.logger.error({ err }, "SIGHUP config reload failed");
|
|
3105
2989
|
const message = err instanceof Error ? err.message : String(err);
|
|
3106
|
-
|
|
2990
|
+
if (!(err instanceof ConfigSyntaxError))
|
|
2991
|
+
this.notifyFleetError(t("fleet.reload_failed", message));
|
|
3107
2992
|
});
|
|
3108
2993
|
}
|
|
3109
2994
|
/**
|
|
@@ -3196,6 +3081,7 @@ export class FleetManager {
|
|
|
3196
3081
|
if (this.reloadPending)
|
|
3197
3082
|
this.scheduleReconcile();
|
|
3198
3083
|
void this.sweepOrphanedCancelButtons();
|
|
3084
|
+
void this.parkedNotices.sweep();
|
|
3199
3085
|
}
|
|
3200
3086
|
/**
|
|
3201
3087
|
* We are the post-update fleet: the update is over by definition. Clearing it here (rather than in the update
|
|
@@ -3339,19 +3225,19 @@ export class FleetManager {
|
|
|
3339
3225
|
};
|
|
3340
3226
|
}
|
|
3341
3227
|
/** Load fleet.yaml and build routing table */
|
|
3342
|
-
loadConfig(configPath) {
|
|
3228
|
+
loadConfig(configPath, options = {}) {
|
|
3343
3229
|
this.configPath = configPath;
|
|
3344
|
-
const
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
const
|
|
3350
|
-
const loaded = loadFleetConfig(configPath);
|
|
3230
|
+
const history = this.getConfigHistory();
|
|
3231
|
+
history.track("fleet.yaml", configPath);
|
|
3232
|
+
const selected = existsSync(configPath) ? history.read("fleet.yaml", configPath, options.startup) : { source: "{}\n", fallback: false };
|
|
3233
|
+
this.rawFleetDocument = parseDocument(selected.source, { keepSourceTokens: true });
|
|
3234
|
+
const raw = loadRawFleetConfigSource(selected.source);
|
|
3235
|
+
const loaded = loadFleetConfigSource(selected.source);
|
|
3351
3236
|
this.assertProviderSecretEnvKeys(loaded);
|
|
3352
3237
|
this.rawFleetConfig = raw;
|
|
3353
3238
|
this.settingsRuntimeRevision++;
|
|
3354
3239
|
this.fleetConfig = loaded;
|
|
3240
|
+
this.loadedFleetSource = selected;
|
|
3355
3241
|
this.savedFleetConfigSnapshot = structuredClone(this.fleetConfig);
|
|
3356
3242
|
this.warnAboutRemovedLoginMode(loaded);
|
|
3357
3243
|
this.enforceGuestSwitch();
|
|
@@ -3360,6 +3246,8 @@ export class FleetManager {
|
|
|
3360
3246
|
// Scope the path to its owning data directory so another inherited home's override cannot enable CW.
|
|
3361
3247
|
process.env.AGEND_FLEET_DATA_DIR = resolvePath(this.dataDir);
|
|
3362
3248
|
process.env.AGEND_FLEET_CONFIG_PATH = resolvePath(configPath);
|
|
3249
|
+
if (options.remember !== false && existsSync(configPath))
|
|
3250
|
+
history.accepted("fleet.yaml", configPath, selected);
|
|
3363
3251
|
return this.fleetConfig;
|
|
3364
3252
|
}
|
|
3365
3253
|
relayLoginModeWarned = false;
|
|
@@ -6644,10 +6532,12 @@ export class FleetManager {
|
|
|
6644
6532
|
dataDir: this.dataDir,
|
|
6645
6533
|
onWarn: message => this.logger.warn(message),
|
|
6646
6534
|
// #1620: fleet.yaml's web.session.local, read at every check — a Settings save or a reload applies at once
|
|
6647
|
-
// (shortening ends the sessions it now outlives). The public link's
|
|
6535
|
+
// (shortening ends the sessions it now outlives). The public link's own cap stays fixed.
|
|
6648
6536
|
localPolicy: () => localSessionPolicy(this.fleetConfig?.web?.session?.local),
|
|
6649
6537
|
// #1920 §5.4: a guest's, from web.guests.session (never longer than the surface's own).
|
|
6650
6538
|
guestPolicy: () => guestSessionPolicy(this.fleetConfig?.web?.guests?.session),
|
|
6539
|
+
// A session over the temporary link ends with the link (its TTL, at most 8 h on a quick tunnel).
|
|
6540
|
+
exposureEnd: id => this.publicWebLink?.endOf(id) ?? null,
|
|
6651
6541
|
});
|
|
6652
6542
|
this.enforceGuestSwitch(); // #1920: guests off while the fleet was down
|
|
6653
6543
|
}
|
|
@@ -6696,6 +6586,32 @@ export class FleetManager {
|
|
|
6696
6586
|
return this.previewTunnel ??= new ManagedTunnel({ dataDir: join(this.dataDir, PUBLIC_PREVIEW_TUNNEL_DIR),
|
|
6697
6587
|
log: () => this.logger.warn("Public preview tunnel cleanup requires attention") });
|
|
6698
6588
|
}
|
|
6589
|
+
namedLinkTestRunning = false;
|
|
6590
|
+
/**
|
|
6591
|
+
* #2906 (the user, 2026-11-01): Settings → Access → Test connection, on the fixed-address values as filled in (the
|
|
6592
|
+
* typed connector token, else the saved one). One at a time; the result is closed step codes only — the token is
|
|
6593
|
+
* never logged or returned. Admin only, never over the public link (settings-api.ts).
|
|
6594
|
+
*/
|
|
6595
|
+
async testNamedLink(input) {
|
|
6596
|
+
if (this.namedLinkTestRunning)
|
|
6597
|
+
return { busy: true };
|
|
6598
|
+
this.namedLinkTestRunning = true;
|
|
6599
|
+
try {
|
|
6600
|
+
const typed = typeof input.token === "string" && input.token.trim() ? input.token : null;
|
|
6601
|
+
const result = await runNamedLinkTest({ hostname: input.hostname, port: input.port, protocol: input.protocol, token: typed ?? process.env[PUBLIC_TUNNEL_TOKEN_ENV] ?? null }, {
|
|
6602
|
+
healthPort: this.fleetConfig?.health_port ?? 19280,
|
|
6603
|
+
ensureBinary: async (signal) => (await ensureCloudflared({ dataDir: this.dataDir, pinnedOnly: true, signal })).path,
|
|
6604
|
+
liveMarker: values => this.publicWebLink?.liveNamedMarker(values) ?? null,
|
|
6605
|
+
reserve: namedTestLane(this.getTunnelLane()),
|
|
6606
|
+
current: input.current,
|
|
6607
|
+
});
|
|
6608
|
+
this.logger[result.cleanup ? "warn" : "info"]({ steps: result.steps.map(s => `${s.step}:${s.code}`), typedToken: !!typed, ...(result.cleanup ? { cleanup: result.cleanup } : {}) }, result.cleanup ? "Named link test: the test connector's stop could not be proven; the tunnel lane stays held" : "Named link test");
|
|
6609
|
+
return result;
|
|
6610
|
+
}
|
|
6611
|
+
finally {
|
|
6612
|
+
this.namedLinkTestRunning = false;
|
|
6613
|
+
}
|
|
6614
|
+
}
|
|
6699
6615
|
getTunnelLane() {
|
|
6700
6616
|
return this.tunnelPurposeLane ??= new TunnelPurposeLane(new ManagedTunnel({ dataDir: this.dataDir,
|
|
6701
6617
|
log: () => this.logger.warn("Managed tunnel cleanup requires attention") }));
|
|
@@ -7071,7 +6987,8 @@ export class FleetManager {
|
|
|
7071
6987
|
: "\n" + t("dashboard.public_status", status.state, status.remainingSeconds ?? 0)
|
|
7072
6988
|
+ (status.expiresAt ? t("dashboard.public_until", formatFleetTime(status.expiresAt)) : ""));
|
|
7073
6989
|
const choices = [{ action: "local", label: t("dashboard.local_signin") },
|
|
7074
|
-
...(publicLinkSettings(this.fleetConfig?.web).allowed
|
|
6990
|
+
...(publicLinkSettings(this.fleetConfig?.web).allowed
|
|
6991
|
+
? [{ action: "public", label: t(publicLinkNamedHostname(this.fleetConfig?.web) ? "dashboard.public_open_named" : "dashboard.public_open") }] : []),
|
|
7075
6992
|
...(status.state !== "closed" ? [{ action: "close", label: t("dashboard.public_close") }] : [])];
|
|
7076
6993
|
const posted = await this.postNonceButtonPrompt({
|
|
7077
6994
|
prefix: "dashboard:", alertType: "login", instanceName: "dashboard", adapter, adapterId: owner.adapterId,
|
|
@@ -7291,7 +7208,7 @@ export class FleetManager {
|
|
|
7291
7208
|
return { ...a, name: badge.name, platform: badge.platform, scope: [...this.guestScope(owner)].sort(), reach: [...this.guestReach(owner)].sort(),
|
|
7292
7209
|
devices: devices.get(k) ?? 0, narrow: own(cfg?.narrow, `${a.adapterId}:${a.userId}`) ?? null,
|
|
7293
7210
|
// #2348: the grant as fleet.yaml has it (null: none), and the last code an admin sent from Settings.
|
|
7294
|
-
grant: grant ? { ...(grant.name ? { name: grant.name } : {}), instances: [...grant.instances] } : null,
|
|
7211
|
+
grant: grant ? { ...(grant.name ? { name: grant.name } : {}), instances: [...grant.instances], ...(grant.level === "trusted" ? { level: "trusted" } : {}) } : null,
|
|
7295
7212
|
...(sent ? { code: { at: sent.at, outcome: sent.outcome.kind, ...(sent.outcome.kind === "sent" ? { expiresAt: sent.at + sent.outcome.expiresInMs } : {}),
|
|
7296
7213
|
...(sent.outcome.kind === "dm_failed" ? { reason: sent.outcome.reason, platform: sent.outcome.platform, ...(sent.outcome.startUrl ? { startUrl: sent.outcome.startUrl } : {}) } : {}) } } : {}) };
|
|
7297
7214
|
}).sort((x, y) => (y.lastSeen ?? -1) - (x.lastSeen ?? -1));
|
|
@@ -7339,7 +7256,7 @@ export class FleetManager {
|
|
|
7339
7256
|
const when = Date.parse(r.created_at.includes("T") ? r.created_at : `${r.created_at.replace(" ", "T")}Z`);
|
|
7340
7257
|
return r.event_type === "guest_grant_changed"
|
|
7341
7258
|
? [{ kind: "grant", at: when, account, name, via: p.via === "confirmed" ? "confirmed" : "direct", added: names(p.added), removed: names(p.removed),
|
|
7342
|
-
endedSessions: typeof p.endedSessions === "number" ? p.endedSessions : 0 }]
|
|
7259
|
+
endedSessions: typeof p.endedSessions === "number" ? p.endedSessions : 0, ...(p.level === "chat" || p.level === "trusted" ? { level: p.level } : {}) }]
|
|
7343
7260
|
: [{ kind: "code", at: when, account, name, outcome: typeof p.outcome === "string" ? p.outcome : "unknown" }];
|
|
7344
7261
|
});
|
|
7345
7262
|
}
|
|
@@ -7378,7 +7295,7 @@ export class FleetManager {
|
|
|
7378
7295
|
* confirmed in General, the agents added and taken away, and the sessions a removal ended. Never message content.
|
|
7379
7296
|
*/
|
|
7380
7297
|
noteGuestGrantChange(change) {
|
|
7381
|
-
this.logger.info({ account: change.account, via: change.via, added: change.added.length, removed: change.removed.length }, "Guest access changed in Settings");
|
|
7298
|
+
this.logger.info({ account: change.account, via: change.via, added: change.added.length, removed: change.removed.length, ...(change.level ? { level: change.level } : {}) }, "Guest access changed in Settings");
|
|
7382
7299
|
try {
|
|
7383
7300
|
this.eventLog?.insert("fleet", "guest_grant_changed", { ...change });
|
|
7384
7301
|
}
|
|
@@ -7463,6 +7380,8 @@ export class FleetManager {
|
|
|
7463
7380
|
* #1920 §5.4: a guest's own `/web revoke` — its sessions and its outstanding code; nobody else's (an admin's revokes
|
|
7464
7381
|
* everyone's, as before).
|
|
7465
7382
|
*/
|
|
7383
|
+
/** #2855: the admin removed a guest — its own preferences (theme, language, favourites, sort, filter) go too. */
|
|
7384
|
+
forgetGuestPrefs(owner) { this.webPrefs.forgetGuest(webPersonKey(owner)); }
|
|
7466
7385
|
revokeGuestWebSessions(owner) {
|
|
7467
7386
|
this.initializeWebSessions();
|
|
7468
7387
|
const key = `${owner.adapterId}\u0000${owner.userId}`;
|
|
@@ -8169,7 +8088,7 @@ export class FleetManager {
|
|
|
8169
8088
|
this.tmuxExecHelperTimer ??= setInterval(() => tmuxExecHelper.maintain(), 5_000);
|
|
8170
8089
|
this.tmuxExecHelperTimer.unref?.();
|
|
8171
8090
|
this.rotateFleetLogs();
|
|
8172
|
-
const fleet = this.loadConfig(configPath);
|
|
8091
|
+
const fleet = this.loadConfig(configPath, { startup: true });
|
|
8173
8092
|
this.slimFleetConfigAtStartup();
|
|
8174
8093
|
this.logConfigWarningsAtStartup();
|
|
8175
8094
|
setLocale(detectLocale(fleet)); // user-facing text language (fleet.yaml defaults.locale / timezone)
|
|
@@ -8247,7 +8166,7 @@ export class FleetManager {
|
|
|
8247
8166
|
// migrates legacy single-bot entries and names without a suffix. Classic
|
|
8248
8167
|
// routing does NOT go through the routing engine (single-key, can't hold two
|
|
8249
8168
|
// bots in one channel) — it resolves per-bot via getInstanceByChannel.
|
|
8250
|
-
this.classicChannels = new ClassicChannelManager(this.dataDir, this.logger);
|
|
8169
|
+
this.classicChannels = new ClassicChannelManager(this.dataDir, this.logger, this.getConfigHistory());
|
|
8251
8170
|
const classicAdapters = fleet.channels?.length ? fleet.channels : (fleet.channel ? [fleet.channel] : []);
|
|
8252
8171
|
this.classicChannels.configureAdapters(classicAdapters);
|
|
8253
8172
|
// The unrecoverable-id report is deliberately NOT sent here: no adapter
|
|
@@ -8265,6 +8184,9 @@ export class FleetManager {
|
|
|
8265
8184
|
// rooms), before the dashboard serves anything; from now on every change is written back (off the loop).
|
|
8266
8185
|
this.restoreWebChat(fleet);
|
|
8267
8186
|
this.startSearchIndex(fleet);
|
|
8187
|
+
// Parse-only source monitoring; it does not apply hand-edited Fleet settings.
|
|
8188
|
+
this.configHistoryTimer = setInterval(() => this.configHistory?.check(), 1_000);
|
|
8189
|
+
this.configHistoryTimer.unref?.();
|
|
8268
8190
|
// Poll classicBot.yaml for external changes every 30s
|
|
8269
8191
|
this.classicReloadTimer = setInterval(() => {
|
|
8270
8192
|
// Attributes the synchronous part, up to the reload's first await (#1235).
|
|
@@ -8296,7 +8218,8 @@ export class FleetManager {
|
|
|
8296
8218
|
...fleet.defaults.daily_summary,
|
|
8297
8219
|
};
|
|
8298
8220
|
this.dailySummary = new DailySummary(summaryConfig, costGuardConfig.timezone, (text) => {
|
|
8299
|
-
|
|
8221
|
+
if (text !== null)
|
|
8222
|
+
this.postDailySummary(text);
|
|
8300
8223
|
// Rotate classic channel chat logs daily
|
|
8301
8224
|
this.classicChannels?.rotateLogs();
|
|
8302
8225
|
this.rotateInboxes();
|
|
@@ -8314,17 +8237,6 @@ export class FleetManager {
|
|
|
8314
8237
|
return DailySummary.generateText(this.eventLog, instances, costMap, this.costGuard?.getFleetTotalCents() ?? 0);
|
|
8315
8238
|
});
|
|
8316
8239
|
this.dailySummary.start();
|
|
8317
|
-
// #2246 step 3: the task board's daily summary — its own time (defaults.task_board.summary_at, read at every look),
|
|
8318
|
-
// in the fleet's zone; a summary missed while the fleet was down goes out when it is back.
|
|
8319
|
-
this.taskBoardSummary = new TaskBoardSummaryClock({
|
|
8320
|
-
at: () => summaryTime(this.fleetConfig?.defaults?.task_board?.summary_at) ?? null,
|
|
8321
|
-
zone: () => this.usageZone(),
|
|
8322
|
-
now: () => new Date(),
|
|
8323
|
-
mark: summaryMarkFile(join(this.dataDir, "task-board-summary.json")),
|
|
8324
|
-
send: (since, until) => this.postTaskBoardSummary(since, until),
|
|
8325
|
-
failed: (err) => this.logger.warn({ err }, "The task board's daily summary failed; the next minute tries again"),
|
|
8326
|
-
});
|
|
8327
|
-
this.taskBoardSummary.start();
|
|
8328
8240
|
// Rotate classic channel chat logs daily (piggyback on daily summary timer)
|
|
8329
8241
|
this.classicChannels?.rotateLogs();
|
|
8330
8242
|
this.rotateInboxes();
|
|
@@ -8413,7 +8325,7 @@ export class FleetManager {
|
|
|
8413
8325
|
fleet.instances[name] = autoGeneralInstanceConfig(fleet.defaults, generalDir, need.adapterId); // #2598: its kiro_ui is the fleet's
|
|
8414
8326
|
generalsCreated = true;
|
|
8415
8327
|
}
|
|
8416
|
-
if (generalsCreated)
|
|
8328
|
+
if (generalsCreated && !this.configHistory?.isFallback(this.configPath))
|
|
8417
8329
|
this.saveFleetConfig();
|
|
8418
8330
|
if (topicMode && (fleet.channel || fleet.channels?.length)) {
|
|
8419
8331
|
const schedulerConfig = {
|
|
@@ -8589,7 +8501,7 @@ export class FleetManager {
|
|
|
8589
8501
|
fixedGeneral = true;
|
|
8590
8502
|
}
|
|
8591
8503
|
}
|
|
8592
|
-
if (fixedGeneral)
|
|
8504
|
+
if (fixedGeneral && !this.configHistory?.isFallback(this.configPath))
|
|
8593
8505
|
this.saveFleetConfig();
|
|
8594
8506
|
// Auto-create topics AFTER adapter is ready (needs adapter.createTopic)
|
|
8595
8507
|
await this.topicCommands.autoCreateTopics();
|
|
@@ -10363,6 +10275,11 @@ export class FleetManager {
|
|
|
10363
10275
|
}
|
|
10364
10276
|
return;
|
|
10365
10277
|
}
|
|
10278
|
+
// #2870 (the user's choice): in a private chat, a command from someone ClassicBot does not serve here — not an
|
|
10279
|
+
// allowed user, not a ClassicBot admin, not a fleet admin, no room in this chat — gets no answer at all. /start
|
|
10280
|
+
// above still asks for access; a guest's /web is handled before it.
|
|
10281
|
+
if (isPrivateChat && text.startsWith("/") && this.isClassicPrivateStranger(msg.userId, chatId, msg.adapterId))
|
|
10282
|
+
return;
|
|
10366
10283
|
// Handle /stop command
|
|
10367
10284
|
if (text === "/stop" || text.startsWith("/stop ")) {
|
|
10368
10285
|
if (!this.classicChannels.isAdmin(msg.userId)) {
|
|
@@ -10837,8 +10754,8 @@ export class FleetManager {
|
|
|
10837
10754
|
}
|
|
10838
10755
|
}
|
|
10839
10756
|
checkToolPermission(sink, instanceName, tool) {
|
|
10840
|
-
if (isCanvasWorkerTool(tool)
|
|
10841
|
-
return { allowed: false, message: CANVAS_WORKER_EXPERIMENTAL_MESSAGE };
|
|
10757
|
+
if (isCanvasWorkerTool(tool))
|
|
10758
|
+
return { allowed: false, message: CANVAS_WORKER_EXPERIMENTAL_MESSAGE }; // #2799: removed
|
|
10842
10759
|
const profile = resolveToolSet(this.fleetConfig?.instances[instanceName], instanceName);
|
|
10843
10760
|
const allowed = mayUseTool(profile, tool);
|
|
10844
10761
|
if (!allowed) {
|
|
@@ -11571,19 +11488,6 @@ export class FleetManager {
|
|
|
11571
11488
|
}
|
|
11572
11489
|
dispatchTypedIpc(name, msg) {
|
|
11573
11490
|
const type = String(msg.type);
|
|
11574
|
-
if (type.startsWith("fleet_") && WEB_EFFECT_TOOLS.has(type.slice(6))) {
|
|
11575
|
-
const ipc = this.instanceIpcClients.get(name), fleetRequestId = msg.fleetRequestId;
|
|
11576
|
-
if (!ipc || typeof fleetRequestId !== "string")
|
|
11577
|
-
return;
|
|
11578
|
-
void this.handleWebThemeHttp(name, type.slice(6), (msg.payload ?? {})).then(result => {
|
|
11579
|
-
if (this.instanceIpcClients.get(name) === ipc)
|
|
11580
|
-
ipc.send({ type: "fleet_web_theme_response", fleetRequestId, result });
|
|
11581
|
-
}, err => {
|
|
11582
|
-
if (this.instanceIpcClients.get(name) === ipc)
|
|
11583
|
-
ipc.send({ type: "fleet_web_theme_response", fleetRequestId, error: err instanceof Error ? err.message : "Effect operation failed" });
|
|
11584
|
-
}).catch(err => this.logger.debug({ err }, "Effect tool response could not be written"));
|
|
11585
|
-
return;
|
|
11586
|
-
}
|
|
11587
11491
|
if (type.startsWith("fleet_schedule_")) {
|
|
11588
11492
|
this.handleScheduleCrud(name, msg);
|
|
11589
11493
|
return;
|
|
@@ -11637,10 +11541,9 @@ export class FleetManager {
|
|
|
11637
11541
|
const args = (msg.payload ?? {});
|
|
11638
11542
|
const result = tool === "get_web_appearance" || tool === "preview_web_appearance"
|
|
11639
11543
|
? this.appearanceTools.handle(instanceName, tool, args)
|
|
11640
|
-
: tool === "
|
|
11641
|
-
: tool
|
|
11642
|
-
:
|
|
11643
|
-
: this.webAppearance.handleTool(instanceName, tool, args);
|
|
11544
|
+
: tool === "office_view" || tool === "office_set" ? this.officeActs.handle(instanceName, tool, args)
|
|
11545
|
+
: OFFICE_PACK_IPC.has(`fleet_${tool}`) ? this.officePackTools.handle(instanceName, tool, args)
|
|
11546
|
+
: this.webAppearance.handleTool(instanceName, tool, args);
|
|
11644
11547
|
ipc.send({ type: "fleet_web_theme_response", fleetRequestId, result });
|
|
11645
11548
|
}
|
|
11646
11549
|
catch (err) {
|
|
@@ -12065,17 +11968,10 @@ export class FleetManager {
|
|
|
12065
11968
|
}
|
|
12066
11969
|
/** #1629: `agend-agent theme …` — the same service as the tools; `instance` is the token's. */
|
|
12067
11970
|
async handleWebThemeHttp(instance, tool, args) {
|
|
12068
|
-
if (isCanvasWorkerTool(tool)
|
|
12069
|
-
return { error: "canvas_worker_experimental", message: CANVAS_WORKER_EXPERIMENTAL_MESSAGE };
|
|
12070
|
-
if (WEB_EFFECT_TOOLS.has(tool)) {
|
|
12071
|
-
if (!this.isKnownInstance(instance))
|
|
12072
|
-
return { error: "unknown_instance" };
|
|
12073
|
-
return this.effectTools.handle(instance, tool, args);
|
|
12074
|
-
}
|
|
11971
|
+
if (isCanvasWorkerTool(tool))
|
|
11972
|
+
return { error: "canvas_worker_experimental", message: CANVAS_WORKER_EXPERIMENTAL_MESSAGE }; // #2799: removed
|
|
12075
11973
|
if (tool === "get_web_appearance" || tool === "preview_web_appearance")
|
|
12076
11974
|
return this.appearanceTools.handle(instance, tool, args);
|
|
12077
|
-
if (tool === "export_web_mod" || tool === "propose_web_mod")
|
|
12078
|
-
return this.agentMods.handle(instance, tool, args);
|
|
12079
11975
|
if (tool === "office_view" || tool === "office_set")
|
|
12080
11976
|
return this.officeActs.handle(instance, tool, args); // #2578
|
|
12081
11977
|
if (OFFICE_PACK_IPC.has(`fleet_${tool}`))
|
|
@@ -12616,6 +12512,7 @@ export class FleetManager {
|
|
|
12616
12512
|
},
|
|
12617
12513
|
});
|
|
12618
12514
|
this.rawFleetConfig = loadRawFleetConfig(this.configPath);
|
|
12515
|
+
this.configHistory?.accepted("fleet.yaml", this.configPath, { source: output, fallback: false });
|
|
12619
12516
|
// The instance map is defaults-expanded. A defaults edit changes that view even when it writes no instance
|
|
12620
12517
|
// leaves. Re-resolve from the committed YAML so inherited values cannot become stale overrides on the next save.
|
|
12621
12518
|
// Keep the FleetConfig object itself: Settings and other request contexts hold it.
|
|
@@ -12694,6 +12591,8 @@ export class FleetManager {
|
|
|
12694
12591
|
}
|
|
12695
12592
|
/** One-time upgrade migration; invalid YAML is never rewritten. */
|
|
12696
12593
|
migrateNoticeDeliveryStorage() {
|
|
12594
|
+
if (this.configHistory?.isFallback(this.configPath))
|
|
12595
|
+
return;
|
|
12697
12596
|
if (this.fleetConfig && this.configPath && (this.fleetConfig.web?.notify_login === "dm" || this.fleetConfig.defaults.tips === "dm"
|
|
12698
12597
|
|| this.fleetConfig.web?.notice_delivery !== undefined)
|
|
12699
12598
|
&& validateFleetConfig(this.rawFleetConfig).valid) {
|
|
@@ -12708,6 +12607,8 @@ export class FleetManager {
|
|
|
12708
12607
|
}
|
|
12709
12608
|
}
|
|
12710
12609
|
migrateOfficeStorage() {
|
|
12610
|
+
if (this.configHistory?.isFallback(this.configPath))
|
|
12611
|
+
return;
|
|
12711
12612
|
if (!this.fleetConfig || !this.configPath || !hasLegacyOffice(this.rawFleetConfig) || !validateFleetConfig(this.rawFleetConfig).valid)
|
|
12712
12613
|
return;
|
|
12713
12614
|
try {
|
|
@@ -12731,6 +12632,8 @@ export class FleetManager {
|
|
|
12731
12632
|
}
|
|
12732
12633
|
}
|
|
12733
12634
|
slimFleetConfigAtStartup() {
|
|
12635
|
+
if (this.configHistory?.isFallback(this.configPath))
|
|
12636
|
+
return;
|
|
12734
12637
|
this.migrateOfficeStorage();
|
|
12735
12638
|
this.migrateNoticeDeliveryStorage();
|
|
12736
12639
|
const redundantPaths = collectRedundantInstanceDefaultPaths(this.rawFleetConfig);
|
|
@@ -12952,9 +12855,7 @@ export class FleetManager {
|
|
|
12952
12855
|
this.themeStore.forget(name);
|
|
12953
12856
|
this.webAppearance.forget(name);
|
|
12954
12857
|
this.appearanceTools.forget(name);
|
|
12955
|
-
this.effectToolsService?.forget(name);
|
|
12956
12858
|
this.mods.forgetInstance(name);
|
|
12957
|
-
this.agentMods.forget(name);
|
|
12958
12859
|
this.officeActsInstance?.forget(name);
|
|
12959
12860
|
this.officePackToolsInstance?.forget(name);
|
|
12960
12861
|
}
|
|
@@ -14312,51 +14213,6 @@ export class FleetManager {
|
|
|
14312
14213
|
}
|
|
14313
14214
|
return { chatId, opts: {} };
|
|
14314
14215
|
}
|
|
14315
|
-
/**
|
|
14316
|
-
* #2246 step 3: the task board's daily summary of [since, until) (task-board-summary.ts). Each chat world's General
|
|
14317
|
-
* gets its own instances' cards — a card that no world's instance holds (nobody's, a person's) goes to the primary
|
|
14318
|
-
* one — and each of that world's Generals shows the same text in the web chat. A world with nothing to say gets
|
|
14319
|
-
* nothing. A web-only fleet: everything, in the web chat.
|
|
14320
|
-
*/
|
|
14321
|
-
postTaskBoardSummary(since, until) {
|
|
14322
|
-
const board = this.scheduler?.db.board;
|
|
14323
|
-
if (!board)
|
|
14324
|
-
return;
|
|
14325
|
-
const read = board.summaryRead(since.toISOString(), until.toISOString(), this.dispatcherGone);
|
|
14326
|
-
const projects = board.listProjects(true);
|
|
14327
|
-
const stats = board.projectStats(this.dispatcherGone);
|
|
14328
|
-
const names = new Map(projects.map((p) => [p.id, p.name]));
|
|
14329
|
-
const configured = this.getPrimaryAdapterId();
|
|
14330
|
-
const primary = configured !== undefined && this.worlds.has(configured) ? configured : this.worlds.keys().next().value;
|
|
14331
|
-
const worldOf = (assignee) => {
|
|
14332
|
-
const owner = assignee ? this.getInstanceAdapterId(assignee) : undefined;
|
|
14333
|
-
return owner !== undefined && this.worlds.has(owner) ? owner : primary;
|
|
14334
|
-
};
|
|
14335
|
-
const zone = this.usageZone();
|
|
14336
|
-
for (const world of this.worlds.size ? [...this.worlds.keys()] : [undefined]) {
|
|
14337
|
-
const summary = buildSummary(read, since, until, { inChat: (assignee) => worldOf(assignee) === world, projects, stats });
|
|
14338
|
-
if (isQuiet(summary))
|
|
14339
|
-
continue;
|
|
14340
|
-
const text = renderSummary(summary, { zone, t: (key, ...args) => t(key, ...args), projectOf: (c) => (c.projectId === null ? null : names.get(c.projectId) ?? null) });
|
|
14341
|
-
const cards = [...new Set([...summary.review, ...summary.blocked, ...summary.done, ...summary.created].map((c) => c.code))];
|
|
14342
|
-
if (world !== undefined) {
|
|
14343
|
-
const adapter = this.worlds.get(world)?.adapter;
|
|
14344
|
-
const target = this.fleetNoticeTarget(world);
|
|
14345
|
-
if (adapter && target) {
|
|
14346
|
-
adapter.sendText(target.chatId, text, target.opts).catch(e => this.logger.warn({ err: e, world }, "Failed to send the task board's daily summary"));
|
|
14347
|
-
}
|
|
14348
|
-
else {
|
|
14349
|
-
this.logger.warn({ world }, "The task board's daily summary has no postable target — set a General topic or channel.options.general_channel_id");
|
|
14350
|
-
}
|
|
14351
|
-
}
|
|
14352
|
-
for (const [name, cfg] of Object.entries(this.fleetConfig?.instances ?? {})) {
|
|
14353
|
-
if (cfg.general_topic !== true || (world !== undefined && this.getInstanceAdapterId(name) !== world))
|
|
14354
|
-
continue;
|
|
14355
|
-
// #2341 review: with the cards it names, so the public link withholds it while one of them is hidden there.
|
|
14356
|
-
this.emitSseEvent("message", { instance: name, sender: name, role: "status", text, ts: new Date().toISOString(), cards });
|
|
14357
|
-
}
|
|
14358
|
-
}
|
|
14359
|
-
}
|
|
14360
14216
|
/**
|
|
14361
14217
|
* Post the daily summary where fleet-wide notices go.
|
|
14362
14218
|
*
|
|
@@ -14452,11 +14308,22 @@ export class FleetManager {
|
|
|
14452
14308
|
* would have to make this async and change every caller.
|
|
14453
14309
|
*/
|
|
14454
14310
|
notifyInstanceTopic(instanceName, text, extraOpts) {
|
|
14311
|
+
const send = this.sendToInstanceTopic(instanceName, text, extraOpts);
|
|
14312
|
+
if (!send)
|
|
14313
|
+
return false;
|
|
14314
|
+
send.sent.catch(e => this.logger.warn({ err: e, instanceName }, send.failure));
|
|
14315
|
+
return true;
|
|
14316
|
+
}
|
|
14317
|
+
/**
|
|
14318
|
+
* notifyInstanceTopic's routing, handing back the send itself — and which world sent it — for a caller that must
|
|
14319
|
+
* edit or delete the message later (#2897). Null when there is no adapter or no postable target (already logged).
|
|
14320
|
+
*/
|
|
14321
|
+
sendToInstanceTopic(instanceName, text, extraOpts) {
|
|
14455
14322
|
const adapter = this.getAdapterForInstance(instanceName) ?? this.adapter;
|
|
14456
14323
|
if (!adapter) {
|
|
14457
14324
|
// Early startup: adapters are created well after the fleet object exists.
|
|
14458
14325
|
this.logger.warn({ instanceName }, "No adapter yet — instance topic notification not sent");
|
|
14459
|
-
return
|
|
14326
|
+
return null;
|
|
14460
14327
|
}
|
|
14461
14328
|
const channelCfg = this.getChannelConfig(this.getInstanceAdapterId(instanceName));
|
|
14462
14329
|
const groupId = channelCfg?.group_id;
|
|
@@ -14465,17 +14332,13 @@ export class FleetManager {
|
|
|
14465
14332
|
const worldId = this.getInstanceAdapterId(instanceName);
|
|
14466
14333
|
if (threadId != null && groupId) {
|
|
14467
14334
|
const out = this.renderPlaces(worldId, text, { threadId: String(threadId), ...extraOpts });
|
|
14468
|
-
adapter.sendText(String(groupId), out.text, out.opts)
|
|
14469
|
-
.catch(e => this.logger.warn({ err: e, instanceName }, "Failed to send instance topic notification"));
|
|
14470
|
-
return true;
|
|
14335
|
+
return { adapterId: worldId, sent: adapter.sendText(String(groupId), out.text, out.opts), failure: "Failed to send instance topic notification" };
|
|
14471
14336
|
}
|
|
14472
14337
|
// Classic instance: find its channelId from the classic manager
|
|
14473
14338
|
const classicChatId = this.classicChannels?.getChannelIdByInstance(instanceName);
|
|
14474
14339
|
if (classicChatId) {
|
|
14475
14340
|
// #1990: a ClassicBot room's readers need not be in the fleet's group, so its notices name places as plain text.
|
|
14476
|
-
adapter.sendText(classicChatId, stripPlaceRefs(text), extraOpts)
|
|
14477
|
-
.catch(e => this.logger.warn({ err: e, instanceName }, "Failed to send classic notification"));
|
|
14478
|
-
return true;
|
|
14341
|
+
return { adapterId: worldId, sent: adapter.sendText(classicChatId, stripPlaceRefs(text), extraOpts), failure: "Failed to send classic notification" };
|
|
14479
14342
|
}
|
|
14480
14343
|
// Fallback: the instance has neither a topic nor a classic channel, so post
|
|
14481
14344
|
// where fleet-wide notices go. Not the bare group id: on Discord that is a
|
|
@@ -14483,12 +14346,67 @@ export class FleetManager {
|
|
|
14483
14346
|
const target = this.fleetNoticeTarget(worldId);
|
|
14484
14347
|
if (target) {
|
|
14485
14348
|
const out = this.renderPlaces(worldId, text, { ...target.opts, ...extraOpts });
|
|
14486
|
-
adapter.sendText(target.chatId, out.text, out.opts)
|
|
14487
|
-
.catch(e => this.logger.warn({ err: e, instanceName }, "Failed to send notification (no topic)"));
|
|
14488
|
-
return true;
|
|
14349
|
+
return { adapterId: worldId, sent: adapter.sendText(target.chatId, out.text, out.opts), failure: "Failed to send notification (no topic)" };
|
|
14489
14350
|
}
|
|
14490
14351
|
this.logger.warn({ instanceName }, "No postable target — instance topic notification not sent");
|
|
14491
|
-
return
|
|
14352
|
+
return null;
|
|
14353
|
+
}
|
|
14354
|
+
// ── Parked-dialog notices (#2897) ──
|
|
14355
|
+
parkedNoticesStore = null;
|
|
14356
|
+
get parkedNotices() {
|
|
14357
|
+
this.parkedNoticesStore ??= new ParkedNotices({
|
|
14358
|
+
file: join(this.dataDir, PARKED_NOTICE_LEDGER_FILE),
|
|
14359
|
+
logger: this.logger,
|
|
14360
|
+
post: (instanceName, place, text) => this.postParkedNotice(instanceName, place, text),
|
|
14361
|
+
adapterFor: adapterId => this.worlds.get(adapterId)?.adapter
|
|
14362
|
+
?? (adapterId === this.getPrimaryAdapterId() ? this.adapter ?? undefined : undefined),
|
|
14363
|
+
resolvedText: () => t("fleet.dialog_parked_resolved"),
|
|
14364
|
+
// A ClassicBot room names places as plain text (#1990), as its post did.
|
|
14365
|
+
render: (row, text) => row.place === "topic" && this.classicChannels?.getChannelIdByInstance(row.instanceName) === row.chatId
|
|
14366
|
+
? stripPlaceRefs(text) : this.renderPlaces(row.adapterId, text).text,
|
|
14367
|
+
});
|
|
14368
|
+
return this.parkedNoticesStore;
|
|
14369
|
+
}
|
|
14370
|
+
/**
|
|
14371
|
+
* A held CLI dialog's notices: the instance-topic one and the General one, each edited in place while the
|
|
14372
|
+
* dialog stays (one live message per place, never a pile), and taken back by resolveParkedNotice.
|
|
14373
|
+
*/
|
|
14374
|
+
showParkedNotice(instanceName, topicText, generalText) {
|
|
14375
|
+
if (topicText !== null)
|
|
14376
|
+
void this.parkedNotices.show(instanceName, "topic", topicText);
|
|
14377
|
+
if (generalText !== null)
|
|
14378
|
+
void this.parkedNotices.show(instanceName, "general", generalText);
|
|
14379
|
+
}
|
|
14380
|
+
/** The held dialog is resolved, or the instance stopped or restarted: delete its notices (no-op when none). */
|
|
14381
|
+
resolveParkedNotice(instanceName) {
|
|
14382
|
+
void this.parkedNotices.resolve(instanceName);
|
|
14383
|
+
}
|
|
14384
|
+
async postParkedNotice(instanceName, place, text) {
|
|
14385
|
+
let send;
|
|
14386
|
+
if (place === "topic") {
|
|
14387
|
+
send = this.sendToInstanceTopic(instanceName, text);
|
|
14388
|
+
}
|
|
14389
|
+
else {
|
|
14390
|
+
// Where notifyFleetError posts, without its text throttle: the ledger is what keeps this to one message.
|
|
14391
|
+
const general = Object.entries(this.fleetConfig?.instances ?? {})
|
|
14392
|
+
.find(([, config]) => config.general_topic === true)?.[0];
|
|
14393
|
+
const target = general ? null : this.fleetNoticeTarget();
|
|
14394
|
+
if (general)
|
|
14395
|
+
send = this.sendToInstanceTopic(general, text);
|
|
14396
|
+
else if (this.adapter && target) {
|
|
14397
|
+
const out = this.renderPlaces(this.getPrimaryAdapterId(), text, target.opts);
|
|
14398
|
+
send = { adapterId: this.getPrimaryAdapterId(), sent: this.adapter.sendText(target.chatId, out.text, out.opts) };
|
|
14399
|
+
}
|
|
14400
|
+
else
|
|
14401
|
+
send = null;
|
|
14402
|
+
}
|
|
14403
|
+
if (!send)
|
|
14404
|
+
return null;
|
|
14405
|
+
const sent = await send.sent;
|
|
14406
|
+
const adapterId = send.adapterId ?? this.getPrimaryAdapterId();
|
|
14407
|
+
if (!adapterId)
|
|
14408
|
+
return null;
|
|
14409
|
+
return { adapterId, chatId: sent.chatId, ...(sent.threadId !== undefined ? { threadId: sent.threadId } : {}), messageId: sent.messageId };
|
|
14492
14410
|
}
|
|
14493
14411
|
// ── Nonce-armed button prompts (hang / assist / exit / clear) ──
|
|
14494
14412
|
//
|
|
@@ -15066,6 +14984,7 @@ export class FleetManager {
|
|
|
15066
14984
|
pauseReason, pausedAt,
|
|
15067
14985
|
crashedAt: crashed ? (this.needsCrashedAt.get(name) ?? null) : null,
|
|
15068
14986
|
signIn: pauseReason === "auth" ? this.signInFor(name) : null,
|
|
14987
|
+
usageHold: this.daemons.get(name)?.usageHold?.() ?? null,
|
|
15069
14988
|
});
|
|
15070
14989
|
}
|
|
15071
14990
|
return out;
|
|
@@ -15139,6 +15058,9 @@ export class FleetManager {
|
|
|
15139
15058
|
},
|
|
15140
15059
|
tasks: () => this.needsYouTaskItems(),
|
|
15141
15060
|
backendSignIns: () => this.needsYouBackendSignIns(),
|
|
15061
|
+
configIssues: () => (this.configHistory?.currentIssues() ?? []).map(issue => ({
|
|
15062
|
+
...issue, detail: configIssueText(issue, t), owner: this.worlds.keys().next().value,
|
|
15063
|
+
})),
|
|
15142
15064
|
taskAction: action => this.taskActionFromChat(action),
|
|
15143
15065
|
settings: () => ({
|
|
15144
15066
|
liveMessage: this.fleetConfig?.needs_you?.live_message !== false,
|
|
@@ -15888,6 +15810,16 @@ export class FleetManager {
|
|
|
15888
15810
|
return result;
|
|
15889
15811
|
}
|
|
15890
15812
|
/** #1269: an instance's chat command from the web chat (web-commands.ts); `publicLink`: a gateway request. */
|
|
15813
|
+
/** #2855: the table scope of an instance the web chat may command (a fleet instance or a ClassicBot room), else null. */
|
|
15814
|
+
webCommandScope(instance) {
|
|
15815
|
+
// Own entries only: a name like "__proto__" or "constructor" must not be found on the prototype (#1476 review).
|
|
15816
|
+
const instances = this.fleetConfig?.instances;
|
|
15817
|
+
if (instances && Object.prototype.hasOwnProperty.call(instances, instance))
|
|
15818
|
+
return isGeneralInstance(this.fleetConfig, instance) ? "general" : "fleet";
|
|
15819
|
+
return this.classicChannels?.getChannelIdByInstance(instance) !== undefined ? "classic" : null;
|
|
15820
|
+
}
|
|
15821
|
+
/** #2855: the platform an instance's channel is on, for the command table's cell (a web-only fleet: Discord's). */
|
|
15822
|
+
commandPlatform(instance) { return this.getAdapterForInstance(instance)?.type === "telegram" ? "telegram" : "discord"; }
|
|
15891
15823
|
webCommand(input, opts) {
|
|
15892
15824
|
const webMeta = (instance) => {
|
|
15893
15825
|
const topicId = this.fleetConfig?.instances[instance]?.topic_id;
|
|
@@ -15896,6 +15828,7 @@ export class FleetManager {
|
|
|
15896
15828
|
chatId: this.getGroupIdForInstance(instance) || "", messageId: newWebMessageId(),
|
|
15897
15829
|
username: opts.person?.name ?? WEB_NAME_FALLBACK, userId: opts.person?.userId ?? WEB_NAME_FALLBACK,
|
|
15898
15830
|
threadId: topicId != null ? String(topicId) : undefined, adapterId: this.getAdapterForInstance(instance)?.id ?? "", source: "web", humanOrigin: webHumanOrigin(true, opts.authority),
|
|
15831
|
+
...(opts.guest ? { webGuest: true } : {}), // #2855: a guest's /steer or /btw reaches the agent as a guest's
|
|
15899
15832
|
};
|
|
15900
15833
|
};
|
|
15901
15834
|
const choicesFromCache = (instance) => {
|
|
@@ -15907,13 +15840,8 @@ export class FleetManager {
|
|
|
15907
15840
|
return { current, options: this.withCurrentModel(cached.models, current).map(o => ({ id: o.id, label: this.modelChoiceLabel(o, current) })) };
|
|
15908
15841
|
};
|
|
15909
15842
|
return runWebCommand({
|
|
15910
|
-
|
|
15911
|
-
|
|
15912
|
-
const instances = this.fleetConfig?.instances;
|
|
15913
|
-
if (instances && Object.prototype.hasOwnProperty.call(instances, instance))
|
|
15914
|
-
return isGeneralInstance(this.fleetConfig, instance) ? "general" : "fleet";
|
|
15915
|
-
return this.classicChannels?.getChannelIdByInstance(instance) !== undefined ? "classic" : null;
|
|
15916
|
-
},
|
|
15843
|
+
scope: (instance) => this.webCommandScope(instance),
|
|
15844
|
+
platform: (instance) => this.commandPlatform(instance),
|
|
15917
15845
|
ctx: (instance) => this.topicCommands.getCtxText(instance),
|
|
15918
15846
|
// Board 003648ac: a watch for the paste, armed when the daemon says it was written (raw_paste_written); one
|
|
15919
15847
|
// for a command that was not handed over is dropped.
|
|
@@ -17590,6 +17518,13 @@ export class FleetManager {
|
|
|
17590
17518
|
lines.push(t(admin === "added" ? "classic.approve_done_admin" : "classic.approve_done_admin_already", adminUserId ?? ""));
|
|
17591
17519
|
}
|
|
17592
17520
|
this.notifyInstanceTopic(entry.instanceName, lines.join("\n"));
|
|
17521
|
+
// #2870: the chat just allowed — and the new ClassicBot admin's own chat — get the ClassicBot menu now, not at the
|
|
17522
|
+
// bot's next start (Telegram only; the menu is per chat there).
|
|
17523
|
+
const reply = entry.classicReplyTo;
|
|
17524
|
+
if (reply && reply.adapter.type === "telegram" && scope !== "guild") {
|
|
17525
|
+
for (const chat of new Set([groupId, ...(adminUserId ? [adminUserId] : [])]))
|
|
17526
|
+
void this.topicCommands.addClassicMenuChat(reply.adapterId, chat);
|
|
17527
|
+
}
|
|
17593
17528
|
await this.notifyClassicRequester(entry, "classic.request_allowed");
|
|
17594
17529
|
}
|
|
17595
17530
|
async notifyClassicRequester(entry, key) {
|
|
@@ -18189,14 +18124,6 @@ export class FleetManager {
|
|
|
18189
18124
|
const room = this.classicChannels?.getAll().find(c => c.instanceName === name);
|
|
18190
18125
|
return room ? `room:${room.channelId}:${room.adapterId ?? ""}` : null;
|
|
18191
18126
|
}
|
|
18192
|
-
/** The only Try-card publisher: no arbitrary SSE sender/role can manufacture this metadata. */
|
|
18193
|
-
publishEffectTrial(instance, offer) {
|
|
18194
|
-
const row = this.webChatHistory.recordEffectTrial(instance, offer, new Date().toISOString());
|
|
18195
|
-
if (!row)
|
|
18196
|
-
return false;
|
|
18197
|
-
broadcastSseEvent(this.sseClients, "message", row, err => this.logger.debug({ err }, "SSE client write failed; evicting"), this.webChatHistory.cursorOf(row));
|
|
18198
|
-
return true;
|
|
18199
|
-
}
|
|
18200
18127
|
/** Push an SSE event to all connected Web UI clients. */
|
|
18201
18128
|
emitSseEvent(event, data) {
|
|
18202
18129
|
// #1386: a prompt opened or closed, or an instance's state moved — "Needs you" may have changed.
|
|
@@ -18504,27 +18431,6 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
18504
18431
|
this.syncRoleSkills(workDir, backend, "general", knowledgeDir);
|
|
18505
18432
|
this.logger.debug({ knowledgeDir, workDir, backend }, "Synced general knowledge files");
|
|
18506
18433
|
}
|
|
18507
|
-
/** Update only AgEnD-managed skill copies; no launch or restart is needed for an experimental switch. */
|
|
18508
|
-
canvasWorkerSettingChanged() {
|
|
18509
|
-
this.refreshExperimentalSkills();
|
|
18510
|
-
this.emitSseEvent("status", this.getUiStatus());
|
|
18511
|
-
}
|
|
18512
|
-
refreshExperimentalSkills() {
|
|
18513
|
-
for (const [name, daemon] of this.daemons) {
|
|
18514
|
-
try {
|
|
18515
|
-
const cfg = daemon.getConfigSnapshot();
|
|
18516
|
-
const backend = cfg.backend ?? this.fleetConfig?.defaults?.backend ?? "claude-code";
|
|
18517
|
-
const workDir = this.resolveKnowledgeWorkDir(cfg.working_directory, backend, name);
|
|
18518
|
-
const classic = this.classicChannels?.getAll().some(ch => ch.instanceName === name) === true;
|
|
18519
|
-
if (classic && !insideManagedWorkspaces(workDir))
|
|
18520
|
-
continue;
|
|
18521
|
-
this.syncRoleSkills(workDir, backend, classic ? "classic" : cfg.general_topic ? "general" : "worker");
|
|
18522
|
-
}
|
|
18523
|
-
catch (err) {
|
|
18524
|
-
this.logger.warn({ name, err }, "Could not refresh experimental skill visibility");
|
|
18525
|
-
}
|
|
18526
|
-
}
|
|
18527
|
-
}
|
|
18528
18434
|
/** Publish only the bundled skills eligible for an instance role. */
|
|
18529
18435
|
syncRoleSkills(workDir, backend, role, knowledgeDir) {
|
|
18530
18436
|
const skillSegments = FleetManager.SKILLS_DIR_SEGMENTS[backend];
|
|
@@ -18729,7 +18635,7 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
18729
18635
|
continue;
|
|
18730
18636
|
}
|
|
18731
18637
|
managed.push(name);
|
|
18732
|
-
const newContent = bundledSkillContents(join(srcSkills, name)
|
|
18638
|
+
const newContent = bundledSkillContents(join(srcSkills, name));
|
|
18733
18639
|
if (refuse(dest))
|
|
18734
18640
|
continue;
|
|
18735
18641
|
try {
|
|
@@ -20887,6 +20793,17 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
20887
20793
|
return (adapterId ? generals.find(([name, cfg]) => (cfg.channel_id ?? this.getInstanceAdapterId(name)) === adapterId) : undefined)?.[0]
|
|
20888
20794
|
?? generals[0]?.[0];
|
|
20889
20795
|
}
|
|
20796
|
+
/**
|
|
20797
|
+
* #2870: someone ClassicBot does not serve in this private chat: not on allowed_users, not a ClassicBot admin, not a
|
|
20798
|
+
* fleet admin of this bot, and no ClassicBot room here. Their commands other than /start get no answer.
|
|
20799
|
+
*/
|
|
20800
|
+
isClassicPrivateStranger(userId, chatId, adapterId) {
|
|
20801
|
+
const classic = this.classicChannels;
|
|
20802
|
+
if (!classic)
|
|
20803
|
+
return false;
|
|
20804
|
+
return !classic.isAdmin(userId) && !classic.isUserAllowed(userId) && !this.isFleetAdmin(userId, adapterId ?? "")
|
|
20805
|
+
&& !classic.isClassicChannel(chatId, adapterId);
|
|
20806
|
+
}
|
|
20890
20807
|
/** Return a user-facing blocker without mutating ClassicBot state. */
|
|
20891
20808
|
validateClassicStart(channelId, userId, guildId, adapterId) {
|
|
20892
20809
|
const classic = this.classicChannels;
|
|
@@ -21169,7 +21086,6 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
21169
21086
|
try {
|
|
21170
21087
|
this.themeStore.forget(ch.instanceName);
|
|
21171
21088
|
this.webAppearance.limiter.forget(ch.instanceName);
|
|
21172
|
-
this.effectToolsService?.forget(ch.instanceName);
|
|
21173
21089
|
}
|
|
21174
21090
|
catch (err) {
|
|
21175
21091
|
this.logger.warn({ err: err.message, instanceName: ch.instanceName }, "Dropping a removed room's web theme failed");
|
|
@@ -21278,6 +21194,9 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
21278
21194
|
const sharesStopped = this.officeSharesInstance?.close(); // #2636: every Office share ends, and its tunnel
|
|
21279
21195
|
// #1386: every live "Needs you" message says the fleet stopped (capabilities revoked first) — while the
|
|
21280
21196
|
// adapters can still edit. Bounded: a platform that does not answer must not hold the shutdown.
|
|
21197
|
+
if (this.configHistoryTimer)
|
|
21198
|
+
clearInterval(this.configHistoryTimer);
|
|
21199
|
+
this.configHistoryTimer = null;
|
|
21281
21200
|
const needsStopped = this.stopNeedsYou();
|
|
21282
21201
|
// #1490 P3: stop() now returns a Promise; await it so the final
|
|
21283
21202
|
// ledger flush completes before process.exit(0) in doStopAll's caller.
|
|
@@ -21333,7 +21252,6 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
21333
21252
|
this.clearStatuslineWatchers();
|
|
21334
21253
|
this.costGuard?.stop();
|
|
21335
21254
|
this.dailySummary?.stop();
|
|
21336
|
-
this.taskBoardSummary?.stop();
|
|
21337
21255
|
this.dailyTipScheduler?.stop();
|
|
21338
21256
|
this.dailyTipScheduler = null;
|
|
21339
21257
|
if (this.updateCheckTimer) {
|
|
@@ -21389,6 +21307,8 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
21389
21307
|
for (const timer of this.cancelButtonIdleRetireTimers.values())
|
|
21390
21308
|
clearTimeout(timer);
|
|
21391
21309
|
this.cancelButtonIdleRetireTimers.clear();
|
|
21310
|
+
// #2897: notices still being retired stay in the ledger; the next fleet's sweep finishes them.
|
|
21311
|
+
this.parkedNoticesStore?.dispose();
|
|
21392
21312
|
if (this.topicCleanupTimer) {
|
|
21393
21313
|
clearInterval(this.topicCleanupTimer);
|
|
21394
21314
|
this.topicCleanupTimer = null;
|
|
@@ -21624,14 +21544,16 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
21624
21544
|
const previousRawConfig = this.rawFleetConfig;
|
|
21625
21545
|
const previousRawDocument = this.rawFleetDocument;
|
|
21626
21546
|
const previousSavedSnapshot = this.savedFleetConfigSnapshot;
|
|
21547
|
+
const previousSource = this.loadedFleetSource;
|
|
21627
21548
|
try {
|
|
21628
|
-
this.loadConfig(this.configPath);
|
|
21549
|
+
this.loadConfig(this.configPath, { remember: false });
|
|
21629
21550
|
}
|
|
21630
21551
|
catch (err) {
|
|
21631
21552
|
this.fleetConfig = oldConfig;
|
|
21632
21553
|
this.rawFleetConfig = previousRawConfig;
|
|
21633
21554
|
this.rawFleetDocument = previousRawDocument;
|
|
21634
21555
|
this.savedFleetConfigSnapshot = previousSavedSnapshot;
|
|
21556
|
+
this.loadedFleetSource = previousSource;
|
|
21635
21557
|
throw err;
|
|
21636
21558
|
}
|
|
21637
21559
|
const validation = validateFleetConfig(this.rawFleetConfig);
|
|
@@ -21647,6 +21569,7 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
21647
21569
|
this.rawFleetConfig = previousRawConfig;
|
|
21648
21570
|
this.rawFleetDocument = previousRawDocument;
|
|
21649
21571
|
this.savedFleetConfigSnapshot = previousSavedSnapshot;
|
|
21572
|
+
this.loadedFleetSource = previousSource;
|
|
21650
21573
|
this.observeWebAuthority(); // #2453 review: the configuration back in force is the one seen
|
|
21651
21574
|
this.logger.error({
|
|
21652
21575
|
oldCount,
|
|
@@ -21667,11 +21590,10 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
21667
21590
|
// mark every row done and tell the user "changes applied".
|
|
21668
21591
|
return { rejected: why };
|
|
21669
21592
|
}
|
|
21593
|
+
if (this.loadedFleetSource)
|
|
21594
|
+
this.getConfigHistory().accepted("fleet.yaml", this.configPath, this.loadedFleetSource);
|
|
21670
21595
|
this.migrateOfficeStorage();
|
|
21671
21596
|
this.migrateNoticeDeliveryStorage();
|
|
21672
|
-
if (canvasWorkerEnabled(oldConfig?.web) !== this.canvasWorkerEnabled()) {
|
|
21673
|
-
this.canvasWorkerSettingChanged();
|
|
21674
|
-
}
|
|
21675
21597
|
// Classic behavior settings share the fleet defaults but are not entries
|
|
21676
21598
|
// in fleet.yaml. Snapshot the old effective chain before reloading the
|
|
21677
21599
|
// Classic file so SIGHUP can hot-apply either source without waiting for
|
|
@@ -21881,7 +21803,7 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
21881
21803
|
* the panel shows the mismatch instead of a button.
|
|
21882
21804
|
*/
|
|
21883
21805
|
checkStartupSignatureConsistency() {
|
|
21884
|
-
if (!this.configPath) {
|
|
21806
|
+
if (!this.configPath || this.configHistory?.isFallback(this.configPath)) {
|
|
21885
21807
|
this.fleetSignatureMismatch = null;
|
|
21886
21808
|
return;
|
|
21887
21809
|
}
|
|
@@ -23538,6 +23460,21 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
23538
23460
|
}
|
|
23539
23461
|
return scope.size ? scope : NO_GUEST_SCOPE;
|
|
23540
23462
|
}
|
|
23463
|
+
/** #2855: whether a request carries a guest's live session (its cookie; checked without counting as activity). */
|
|
23464
|
+
presentsGuestSession(req) {
|
|
23465
|
+
if (!this.webToken || !this.webSessions)
|
|
23466
|
+
return false;
|
|
23467
|
+
const epoch = tokenEpoch(this.webToken);
|
|
23468
|
+
return readSessionCookies(req, this.webToken).some(id => this.webSessions.authenticate(id, epoch, { touch: false })?.tier === "guest");
|
|
23469
|
+
}
|
|
23470
|
+
/** #2855: whether the admin made this guest Trusted — only an explicit grant can (web.guests.grants[account].level). */
|
|
23471
|
+
guestTrusted(owner) {
|
|
23472
|
+
const guests = this.fleetConfig?.web?.guests;
|
|
23473
|
+
if (guests?.enabled !== true || !guests.grants)
|
|
23474
|
+
return false;
|
|
23475
|
+
const key = `${owner.adapterId}:${owner.userId}`;
|
|
23476
|
+
return Object.hasOwn(guests.grants, key) && guests.grants[key]?.level === "trusted";
|
|
23477
|
+
}
|
|
23541
23478
|
/**
|
|
23542
23479
|
* #1920 (#2104 review): what an account reaches before an admin's narrowing — its connection's topics (an open one's
|
|
23543
23480
|
* named agents), its own Telegram private room, its verified group rooms. Narrowing can only take from this set, so it
|
|
@@ -23716,13 +23653,6 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
23716
23653
|
return;
|
|
23717
23654
|
}
|
|
23718
23655
|
const requestPath = new URL(req.url ?? "/", `http://localhost:${port}`).pathname;
|
|
23719
|
-
if (!this.canvasWorkerEnabled() && isCanvasWorkerHttpPath(requestPath)) {
|
|
23720
|
-
res.writeHead(403, { "Content-Type": "application/json", "Cache-Control": "no-store" });
|
|
23721
|
-
res.end(JSON.stringify({ error: "canvas_worker_experimental", message: CANVAS_WORKER_EXPERIMENTAL_MESSAGE }));
|
|
23722
|
-
return;
|
|
23723
|
-
}
|
|
23724
|
-
if (this.effectBootstrap.handle(req, res))
|
|
23725
|
-
return;
|
|
23726
23656
|
// Branding is public, after Host admission: automatic favicon requests
|
|
23727
23657
|
// must not become noisy web-token 401s in the browser console.
|
|
23728
23658
|
// #2308: the icon routes and the pages' icon links follow the app icon set in Settings.
|
|
@@ -23730,8 +23660,9 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
23730
23660
|
if (handleWebIconRequest(req, res, requestPath))
|
|
23731
23661
|
return;
|
|
23732
23662
|
// Public: the health probe, /agent (instance-token auth of its own), the
|
|
23733
|
-
// sign-in surface, and /view's reads unless web.view_access says otherwise
|
|
23734
|
-
|
|
23663
|
+
// sign-in surface, and /view's reads unless web.view_access says otherwise (#2855: never a guest's, which go
|
|
23664
|
+
// through the guest gate and its limits on either listener).
|
|
23665
|
+
if (bypassesWebGate(req, requestPath, this.fleetConfig, p => !gatewayRequestContext(req) && (isViewPath(p) || isUsagePath(p)) && !this.presentsGuestSession(req))) {
|
|
23735
23666
|
// fall through to the handlers below
|
|
23736
23667
|
}
|
|
23737
23668
|
else {
|
|
@@ -23797,7 +23728,9 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
23797
23728
|
streamOpened: () => { void this.refreshGuestRooms(owner, { mode: "force" }); },
|
|
23798
23729
|
history: () => this.webChatHistory,
|
|
23799
23730
|
// #1920 §5.1: guests chat unless the host made them read-only (web.guests.chat: false).
|
|
23800
|
-
chat: () => this.fleetConfig?.web?.guests?.chat !== false
|
|
23731
|
+
chat: () => this.fleetConfig?.web?.guests?.chat !== false,
|
|
23732
|
+
// #2855: the admin's level for this guest, read at each request (a change applies at once).
|
|
23733
|
+
trusted: () => this.guestTrusted(owner) });
|
|
23801
23734
|
}
|
|
23802
23735
|
if (decision.via === "session" && this.webToken) {
|
|
23803
23736
|
const upgrade = sessionCookieUpgrade(req, this.webToken, decision.session);
|
|
@@ -24203,7 +24136,6 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
24203
24136
|
return;
|
|
24204
24137
|
}
|
|
24205
24138
|
const listener = createPreviewListener({ settings, healthPort: boundPort, config: this.fleetConfig,
|
|
24206
|
-
canvasWorkerEnabled: () => this.canvasWorkerEnabled(),
|
|
24207
24139
|
// #1723: with a web.preview_origin, the open public link's dashboard frames this listener's preview too.
|
|
24208
24140
|
extraOrigins: () => { const o = settings.origin ? this.publicWebLink?.openDashboardOrigin() : null; return o ? [o] : []; } });
|
|
24209
24141
|
this.previewListener = listener;
|
|
@@ -24380,8 +24312,6 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
24380
24312
|
catch {
|
|
24381
24313
|
return {};
|
|
24382
24314
|
} })(),
|
|
24383
|
-
// #2118: only this Chat's placement content; guest projection drops global revisions.
|
|
24384
|
-
effect_chat_v: this.canvasWorkerEnabled() ? createHash("sha256").update(JSON.stringify(this.themeStore.get(name).placements.effects)).digest("hex").slice(0, 32) : null,
|
|
24385
24315
|
// #1958: the identity colour's tiers (the page resolves them: the agent's only while it shows agents' themes).
|
|
24386
24316
|
color: { ...(colors.get(name)?.color ? { person: colors.get(name).color } : {}), ...(image.agentColor ? { agent: image.agentColor } : {}),
|
|
24387
24317
|
...(image.roleColor ? { role: image.roleColor } : {}), auto: autoIdentityColor(name) },
|
|
@@ -24392,6 +24322,12 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
24392
24322
|
tags: identity.tags,
|
|
24393
24323
|
teams: instanceTeams(this.fleetConfig?.teams, name), // #2172: the sidebar groups a team's members under it
|
|
24394
24324
|
last_reply: this.webChatHistory.lastAgentReply(name) ?? undefined, // #2179: its last reply to a person (ISO)
|
|
24325
|
+
// #2855: its last reply that reached the channel — the only reply time a guest's sidebar gets (guestStatus).
|
|
24326
|
+
last_reply_channel: this.webChatHistory.lastAgentReply(name, { channel: true }) ?? undefined,
|
|
24327
|
+
// #2855: the web commands a guest may run here — the table's "anyone" cells for this channel (guestWebCommands);
|
|
24328
|
+
// only while guests are on (no guest reads it otherwise).
|
|
24329
|
+
...(this.fleetConfig?.web?.guests?.enabled === true
|
|
24330
|
+
? { guest_commands: (() => { const scope = this.webCommandScope(name); return scope ? guestWebCommands(scope, this.commandPlatform(name)) : []; })() } : {}),
|
|
24395
24331
|
status: this.getInstanceStatus(name),
|
|
24396
24332
|
// `state` (presentation: may be awaiting_input) and `execution_state` (working / idle / stuck, or null —
|
|
24397
24333
|
// what the dashboard's activity events carry) come from instancePresentation (#1212).
|
|
@@ -24408,7 +24344,6 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
24408
24344
|
};
|
|
24409
24345
|
});
|
|
24410
24346
|
return {
|
|
24411
|
-
canvas_worker: this.canvasWorkerEnabled(),
|
|
24412
24347
|
instances,
|
|
24413
24348
|
startup: this.startupDashboard?.snapshot() ?? null,
|
|
24414
24349
|
publicLink: this.getPublicWebStatus(),
|