@songsid/agend 2.3.3 → 2.3.4-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/README.zh-TW.md +3 -3
- package/dist/agent-cli-instructions.md +1 -1
- package/dist/auth-api.d.ts +16 -3
- package/dist/auth-api.js +22 -6
- package/dist/auth-api.js.map +1 -1
- package/dist/authority/web-authority.d.ts +15 -1
- package/dist/authority/web-authority.js +27 -10
- package/dist/authority/web-authority.js.map +1 -1
- package/dist/backend/claude-code.js +11 -1
- package/dist/backend/claude-code.js.map +1 -1
- package/dist/backend/codex.js +12 -0
- package/dist/backend/codex.js.map +1 -1
- package/dist/backend/types.d.ts +7 -0
- package/dist/backend/types.js.map +1 -1
- package/dist/channel/adapters/discord.js +13 -0
- package/dist/channel/adapters/discord.js.map +1 -1
- package/dist/chat-log-ids.d.ts +48 -0
- package/dist/chat-log-ids.js +182 -0
- package/dist/chat-log-ids.js.map +1 -0
- package/dist/classic-channel-manager.d.ts +8 -1
- package/dist/classic-channel-manager.js +24 -2
- package/dist/classic-channel-manager.js.map +1 -1
- package/dist/config-validator.js +18 -1
- package/dist/config-validator.js.map +1 -1
- package/dist/daemon.js +8 -1
- package/dist/daemon.js.map +1 -1
- package/dist/env-file.d.ts +2 -0
- package/dist/env-file.js +13 -8
- package/dist/env-file.js.map +1 -1
- package/dist/fleet-manager.d.ts +42 -1
- package/dist/fleet-manager.js +206 -23
- package/dist/fleet-manager.js.map +1 -1
- package/dist/general-knowledge/skills/cross-instance-messaging/SKILL.md +1 -1
- package/dist/general-knowledge/skills/web-dashboard/SKILL.md +4 -0
- package/dist/general-knowledge/skills/worker-collaboration/SKILL.md +1 -1
- package/dist/instance-config-impact.js +1 -0
- package/dist/instance-config-impact.js.map +1 -1
- package/dist/instance-lifecycle.d.ts +2 -0
- package/dist/instance-lifecycle.js +23 -7
- package/dist/instance-lifecycle.js.map +1 -1
- package/dist/instructions.d.ts +1 -1
- package/dist/instructions.js +1 -1
- package/dist/instructions.js.map +1 -1
- package/dist/interaction-observation.d.ts +1 -0
- package/dist/interaction-observation.js +1 -1
- package/dist/interaction-observation.js.map +1 -1
- package/dist/locale.js +48 -0
- package/dist/locale.js.map +1 -1
- package/dist/next-steps.d.ts +12 -2
- package/dist/next-steps.js +19 -3
- package/dist/next-steps.js.map +1 -1
- package/dist/office-share-service.d.ts +57 -0
- package/dist/office-share-service.js +76 -11
- package/dist/office-share-service.js.map +1 -1
- package/dist/office-share.d.ts +8 -0
- package/dist/office-share.js +34 -0
- package/dist/office-share.js.map +1 -1
- package/dist/outbound-schemas.js +1 -1
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/page-share.d.ts +122 -0
- package/dist/page-share.js +199 -0
- package/dist/page-share.js.map +1 -0
- package/dist/public-preview.d.ts +4 -0
- package/dist/public-preview.js +5 -4
- package/dist/public-preview.js.map +1 -1
- package/dist/public-web-gateway.js +5 -0
- package/dist/public-web-gateway.js.map +1 -1
- package/dist/quickstart-api.d.ts +5 -0
- package/dist/quickstart-api.js +1 -1
- package/dist/quickstart-api.js.map +1 -1
- package/dist/quickstart.d.ts +40 -0
- package/dist/quickstart.js +174 -28
- package/dist/quickstart.js.map +1 -1
- package/dist/remote-card-answer.js +2 -0
- package/dist/remote-card-answer.js.map +1 -1
- package/dist/remote-card-prompt.js +4 -2
- package/dist/remote-card-prompt.js.map +1 -1
- package/dist/selection-card.d.ts +21 -0
- package/dist/selection-card.js +129 -0
- package/dist/selection-card.js.map +1 -0
- package/dist/settings-api.js +4 -0
- package/dist/settings-api.js.map +1 -1
- package/dist/settings-change.js +5 -0
- package/dist/settings-change.js.map +1 -1
- package/dist/settings-effect.js +2 -0
- package/dist/settings-effect.js.map +1 -1
- package/dist/setup-host.js +2 -2
- package/dist/setup-host.js.map +1 -1
- package/dist/setup-wizard.js +2 -2
- package/dist/setup-wizard.js.map +1 -1
- package/dist/types.d.ts +10 -0
- package/dist/ui/chat-store.js +8 -1
- package/dist/ui/chat-strings.js +38 -2
- package/dist/ui/chat-thread.js +52 -10
- package/dist/ui/page-share-dialog.js +191 -0
- package/dist/ui/panel-chat.js +14 -0
- package/dist/ui/panel-notices.js +98 -0
- package/dist/ui/panel-settings.js +9 -2
- package/dist/ui/preview-pages.js +41 -0
- package/dist/ui/preview-panel-actions.js +161 -0
- package/dist/ui/preview-panel.js +209 -28
- package/dist/ui/preview-versions.js +155 -0
- package/dist/ui/preview.js +88 -3
- package/dist/ui/settings-registry.js +1 -0
- package/dist/ui/settings-strings.js +4 -0
- package/dist/ui/shared/app-i18n.js +2 -2
- package/dist/ui/shared/app-route.js +4 -0
- package/dist/ui/shared/app-shell.js +5 -3
- package/dist/ui/shared/app-store.js +71 -1
- package/dist/ui/shared/app-stream.js +1 -1
- package/dist/ui/shared/app.css +57 -3
- package/dist/ui/shared/app.js +22 -1
- package/dist/ui/shared/signin.js +12 -1
- package/dist/ui/shared/ui-icons.js +3 -0
- package/dist/ui/shared/web-authorize.js +3 -1
- package/dist/web-api.d.ts +26 -0
- package/dist/web-api.js +180 -1
- package/dist/web-api.js.map +1 -1
- package/dist/web-auth.js +7 -1
- package/dist/web-auth.js.map +1 -1
- package/dist/web-login.d.ts +2 -0
- package/dist/web-login.js +3 -1
- package/dist/web-login.js.map +1 -1
- package/dist/web-notices.d.ts +51 -0
- package/dist/web-notices.js +118 -0
- package/dist/web-notices.js.map +1 -0
- package/dist/web-only-fleet.d.ts +22 -0
- package/dist/web-only-fleet.js +13 -0
- package/dist/web-only-fleet.js.map +1 -0
- package/dist/web-preview.d.ts +32 -3
- package/dist/web-preview.js +52 -19
- package/dist/web-preview.js.map +1 -1
- package/dist/web-session.d.ts +7 -0
- package/dist/web-session.js +2 -0
- package/dist/web-session.js.map +1 -1
- package/dist/web-settings-patch.d.ts +3 -1
- package/dist/web-settings-patch.js +4 -1
- package/dist/web-settings-patch.js.map +1 -1
- package/dist/web-shell-routes.d.ts +2 -0
- package/dist/web-shell-routes.js +3 -0
- package/dist/web-shell-routes.js.map +1 -1
- package/package.json +1 -1
package/dist/fleet-manager.js
CHANGED
|
@@ -28,6 +28,7 @@ import { PUBLIC_PREVIEW_FRAME_SRC, PUBLIC_PREVIEW_TUNNEL_DIR, PublicPreview, pub
|
|
|
28
28
|
import { CloudflaredProvider } from "./tunnel/cloudflared.js";
|
|
29
29
|
import { ensureCloudflared } from "./tunnel/cloudflared-install.js";
|
|
30
30
|
import { namedTestLane, runNamedLinkTest } from "./tunnel/named-link-test.js";
|
|
31
|
+
import { daemonTopicMode, isWebOnlyFleetConfig } from "./web-only-fleet.js";
|
|
31
32
|
import { OFFICE_SHARE_TUNNEL_DIR, OfficeShareService } from "./office-share-service.js";
|
|
32
33
|
import { ALL_OFFICES, WHOLE_FLEET } from "./office-share.js";
|
|
33
34
|
import { TunnelPurposeLane } from "./tunnel/purpose-lane.js";
|
|
@@ -264,7 +265,8 @@ import { authorizeSession, decideWebGate, loadOrCreateWebToken, readSessionCooki
|
|
|
264
265
|
import { bypassesWebGate, handleAuthRequest, serveGuestRefusedPage, serveSigninPage } from "./auth-api.js";
|
|
265
266
|
import { isWebPageNavigation } from "./web-shell-routes.js";
|
|
266
267
|
import { guestSessionPolicy, localSessionPolicy, tokenEpoch, WebSessionStore } from "./web-session.js";
|
|
267
|
-
import {
|
|
268
|
+
import { WebNotices, WEB_NOTICES_FILE } from "./web-notices.js";
|
|
269
|
+
import { authorityNow, evaluateWebAuthority, HOST_OPERATOR, VERIFIED_SESSION_HOURS, webAuthorityPolicy } from "./authority/web-authority.js";
|
|
268
270
|
import { AuthorityStateStore } from "./authority/authority-state.js";
|
|
269
271
|
import { SessionAuthorizations } from "./authority/session-authorizations.js";
|
|
270
272
|
import { SessionAuthorizationRequests } from "./web-session-authorization.js";
|
|
@@ -300,6 +302,7 @@ import { SpawnGate } from "./spawn-gate.js";
|
|
|
300
302
|
import { BackendOutageTracker } from "./backend-outage.js";
|
|
301
303
|
import { canUnlockAdvancedTips, DailyTipScheduler, selectTip, visibleTipLevels, } from "./tips.js";
|
|
302
304
|
import { getTmuxSession } from "./config.js";
|
|
305
|
+
import { OwnStatusMessages, discordMessageKey } from "./chat-log-ids.js";
|
|
303
306
|
// Closed migration cohort: these 11 names existed immediately before the first
|
|
304
307
|
// managed-skill publisher (a1ac499f; parent e267e684). Never grow this set when
|
|
305
308
|
// adding a bundled skill: its new name is not proof of old AgEnD ownership.
|
|
@@ -1201,6 +1204,11 @@ export class FleetManager {
|
|
|
1201
1204
|
officeExists: id => this.officeStore.list().offices.some(o => o.id === id),
|
|
1202
1205
|
ownerCurrent: owner => this.officeShareOwnerCurrent(owner),
|
|
1203
1206
|
changed: () => this.emitSseEvent("office_shares", {}),
|
|
1207
|
+
// #2911: page shares on the same tunnel. Their end (expired, stopped, the feature off, the maker no longer an admin,
|
|
1208
|
+
// the fleet stopping) is said in the page's own channel, as its start is (pageShareNotice); never its URL.
|
|
1209
|
+
pageConfig: () => this.fleetConfig?.web?.page_share,
|
|
1210
|
+
pageCdn: () => this.fleetConfig?.web?.preview_cdn === true,
|
|
1211
|
+
pagesChanged: (list, ended) => this.pageSharesChanged(list, ended),
|
|
1204
1212
|
log: (event, detail) => this.logger.info({ event, ...(detail ? { detail } : {}) }, "Office share link"),
|
|
1205
1213
|
}));
|
|
1206
1214
|
}
|
|
@@ -1215,6 +1223,17 @@ export class FleetManager {
|
|
|
1215
1223
|
: share ? t("office_share.revoked_notice", who, office) : t("office_share.revoked_all_notice", who, count);
|
|
1216
1224
|
this.notifyFleetError(text, { throttle: false });
|
|
1217
1225
|
}
|
|
1226
|
+
/** #2911: the open page shares changed — the page hears it; each one handed out that ended is said in its channel. */
|
|
1227
|
+
pageSharesChanged(_list, ended) {
|
|
1228
|
+
this.emitSseEvent("page_shares", {});
|
|
1229
|
+
for (const s of ended)
|
|
1230
|
+
this.notifyInstanceTopic(s.instance, t("page_share.ended_notice", s.title || t("page_share.untitled")));
|
|
1231
|
+
}
|
|
1232
|
+
/** #2911: a page share was made — said in the channel of the instance whose chat it came from (never its URL). */
|
|
1233
|
+
pageShareNotice(who, share) {
|
|
1234
|
+
const minutes = Math.round((share.expiresAt - share.createdAt) / 60_000);
|
|
1235
|
+
this.notifyInstanceTopic(share.instance, t("page_share.created_notice", who, share.title || t("page_share.untitled"), minutes < 60 ? `${minutes} min` : `${Math.round(minutes / 60)} h`));
|
|
1236
|
+
}
|
|
1218
1237
|
/** A share's owner: a person (`person:<adapter>:<user>`) still a fleet admin there; the host's own admin session stays. */
|
|
1219
1238
|
officeShareOwnerCurrent(owner) {
|
|
1220
1239
|
const m = /^person:([^:]+):(.+)$/.exec(owner);
|
|
@@ -1612,6 +1631,17 @@ export class FleetManager {
|
|
|
1612
1631
|
const actor = verified();
|
|
1613
1632
|
if (!actor)
|
|
1614
1633
|
return null;
|
|
1634
|
+
// #2910 D1: a web-only fleet's host operator (its `agend web --code` session on this computer) confirms its own
|
|
1635
|
+
// change — the trust `agend settings confirm` has here; there is no account to be an admin of and no General to
|
|
1636
|
+
// ask. Asked again at execution: still the host operator now (this computer, not revoked or run out), still web-only.
|
|
1637
|
+
const hostOperator = (a) => !!a && a.adapter_id === HOST_OPERATOR.adapterId && a.user_id === HOST_OPERATOR.userId && this.isWebOnlyFleet();
|
|
1638
|
+
if (hostOperator(actor)) {
|
|
1639
|
+
return {
|
|
1640
|
+
label: t("settings_confirm.host_operator_label"),
|
|
1641
|
+
current: () => hostOperator(verified()),
|
|
1642
|
+
done: view => { this.logger.info({ id: view.id, state: view.state }, "Settings change confirmed from the web by this computer's admin"); },
|
|
1643
|
+
};
|
|
1644
|
+
}
|
|
1615
1645
|
const chat = this.settingsChatAuthority(store, id);
|
|
1616
1646
|
if (!chat)
|
|
1617
1647
|
return null;
|
|
@@ -5690,8 +5720,7 @@ export class FleetManager {
|
|
|
5690
5720
|
}
|
|
5691
5721
|
/** Recreate a daemon for a marker-only paused instance after an explicit wake/delivery. */
|
|
5692
5722
|
async startPersistedPausedInstance(name, transition) {
|
|
5693
|
-
const topicMode = this.fleetConfig
|
|
5694
|
-
|| !!this.fleetConfig?.channels?.some(channel => channel.mode === "topic");
|
|
5723
|
+
const topicMode = daemonTopicMode(this.fleetConfig);
|
|
5695
5724
|
const fleetConfig = this.fleetConfig?.instances[name];
|
|
5696
5725
|
if (fleetConfig) {
|
|
5697
5726
|
await this.startInstance(name, fleetConfig, topicMode, "fleet-topic", false, transition);
|
|
@@ -5902,8 +5931,7 @@ export class FleetManager {
|
|
|
5902
5931
|
this.logger.info({ name, attempt: attempt + 1 }, "Startup retry deferred — tmux storm window is blocking spawns");
|
|
5903
5932
|
return;
|
|
5904
5933
|
}
|
|
5905
|
-
const topicMode = this.fleetConfig
|
|
5906
|
-
|| !!this.fleetConfig?.channels?.some(channel => channel.mode === "topic");
|
|
5934
|
+
const topicMode = daemonTopicMode(this.fleetConfig);
|
|
5907
5935
|
this.startupRetriesRunning.add(name);
|
|
5908
5936
|
try {
|
|
5909
5937
|
await this.spawnGate.run({
|
|
@@ -6426,7 +6454,7 @@ export class FleetManager {
|
|
|
6426
6454
|
const backend = config.backend ?? this.fleetConfig?.defaults?.backend ?? "claude-code";
|
|
6427
6455
|
return { backend, profile: credentialHomeSpec(backend) ? instanceCredentialProfile(config, this.fleetConfig?.defaults, backend) : null };
|
|
6428
6456
|
});
|
|
6429
|
-
const topicMode = this.fleetConfig
|
|
6457
|
+
const topicMode = daemonTopicMode(this.fleetConfig);
|
|
6430
6458
|
await this.startAfterRestart(name, wasPaused, pausedAt, pauseReason, () => opts?.kiroMigration ? this.startInstance(name, config, topicMode ?? false, "fleet-topic", false, transition, undefined, opts.kiroMigration)
|
|
6431
6459
|
: this.startInstance(name, config, topicMode ?? false, "fleet-topic", false, transition));
|
|
6432
6460
|
this.stormWindow.markRecovered(name);
|
|
@@ -6729,6 +6757,7 @@ export class FleetManager {
|
|
|
6729
6757
|
if (!publicPreviewWanted(this.fleetConfig?.web))
|
|
6730
6758
|
return null;
|
|
6731
6759
|
return new PublicPreview({ exposureId: id, expiresAt, dashboardOrigin, isCurrent, manager: this.getPreviewTunnel(),
|
|
6760
|
+
cdn: () => this.fleetConfig?.web?.preview_cdn === true,
|
|
6732
6761
|
provider: () => new CloudflaredProvider({ binaryName: binary, protocol }),
|
|
6733
6762
|
log: (event, detail) => this.logger.info({ event, exposureId: id, ...(detail ? { detail } : {}) }, "Public web link") });
|
|
6734
6763
|
},
|
|
@@ -7690,7 +7719,8 @@ export class FleetManager {
|
|
|
7690
7719
|
return;
|
|
7691
7720
|
// The session handle makes each notice distinct: notifyFleetError throttles by text, and a second
|
|
7692
7721
|
// sign-in from the same kind of browser is exactly the one the operator most needs to hear about.
|
|
7693
|
-
|
|
7722
|
+
// #2910 D2: a web-only fleet has no /web to revoke with — its Notice names the host's way (every browser signed out).
|
|
7723
|
+
this.notifyFleetError(t(this.webOnlyPromptPlace() ? "web.login_notice_web_only" : "web.login_notice", info.label, info.surface, info.handle.slice(0, 8)));
|
|
7694
7724
|
}
|
|
7695
7725
|
getDashboardAccess() {
|
|
7696
7726
|
return { ready: this.healthServerListening, token: this.webToken };
|
|
@@ -8110,7 +8140,8 @@ export class FleetManager {
|
|
|
8110
8140
|
// Must run before the first General/CLI spawn: pressure can hold startup.
|
|
8111
8141
|
// Logs remain available even before the adapters and health listener exist.
|
|
8112
8142
|
this.memoryPressure.start();
|
|
8113
|
-
|
|
8143
|
+
// #2910: a web-only fleet's agents run in topic mode too (their cross-instance tools work; the dashboard is their place).
|
|
8144
|
+
const topicMode = daemonTopicMode(fleet);
|
|
8114
8145
|
// Set tmux socket isolation for custom AGEND_HOME
|
|
8115
8146
|
const { getTmuxSocketName: getSocket } = await import("./paths.js");
|
|
8116
8147
|
TmuxManager.setSocketName(getSocket());
|
|
@@ -8327,7 +8358,8 @@ export class FleetManager {
|
|
|
8327
8358
|
}
|
|
8328
8359
|
if (generalsCreated && !this.configHistory?.isFallback(this.configPath))
|
|
8329
8360
|
this.saveFleetConfig();
|
|
8330
|
-
|
|
8361
|
+
// #2910: the scheduler (schedules, the task board, decisions) runs on a web-only fleet too; it needs no chat.
|
|
8362
|
+
if (topicMode) {
|
|
8331
8363
|
const schedulerConfig = {
|
|
8332
8364
|
...DEFAULT_SCHEDULER_CONFIG,
|
|
8333
8365
|
...this.fleetConfig?.defaults.scheduler,
|
|
@@ -9109,6 +9141,9 @@ export class FleetManager {
|
|
|
9109
9141
|
return;
|
|
9110
9142
|
await this.handleInboundMessage(msg);
|
|
9111
9143
|
}, this.logger, "adapter.message"));
|
|
9144
|
+
// #2940: a deleted message's chat-log line goes (and it is never logged after this).
|
|
9145
|
+
adapter.on("message_deleted", (e) => { if (isCurrentAdapter())
|
|
9146
|
+
this.onPlatformMessageDeleted(e); });
|
|
9112
9147
|
adapter.on("reaction", safeHandler(async (r) => {
|
|
9113
9148
|
if (!isCurrentAdapter())
|
|
9114
9149
|
return;
|
|
@@ -9259,6 +9294,9 @@ export class FleetManager {
|
|
|
9259
9294
|
return;
|
|
9260
9295
|
await this.handleInboundMessage(msg);
|
|
9261
9296
|
}, this.logger, `adapter[${adapterId}].message`));
|
|
9297
|
+
// #2940: a deleted message's chat-log line goes (and it is never logged after this).
|
|
9298
|
+
adapter.on("message_deleted", (e) => { if (isCurrentAdapter())
|
|
9299
|
+
this.onPlatformMessageDeleted(e); });
|
|
9262
9300
|
adapter.on("reaction", safeHandler(async (r) => {
|
|
9263
9301
|
if (!isCurrentAdapter())
|
|
9264
9302
|
return;
|
|
@@ -10836,7 +10874,10 @@ export class FleetManager {
|
|
|
10836
10874
|
// else to go, and "retry shortly" would have the agent retry forever. It goes to the web chat.
|
|
10837
10875
|
const webOnlyReply = tool === "reply" && this.worlds.size === 0 && this.isWebOnlyFleet();
|
|
10838
10876
|
if (this.worlds.size === 0 && !webOnlyReply) {
|
|
10839
|
-
|
|
10877
|
+
// #2910: on a web-only fleet there will never be an adapter — say so, so the agent does not retry forever.
|
|
10878
|
+
respond(null, this.isWebOnlyFleet()
|
|
10879
|
+
? `${tool} is not available on a web-only fleet (no Discord or Telegram connection); answer with reply`
|
|
10880
|
+
: "Channel adapters are not ready — retry shortly");
|
|
10840
10881
|
return;
|
|
10841
10882
|
}
|
|
10842
10883
|
// Resolve threadId: use sender's topic_id if sender is a known fleet instance,
|
|
@@ -11036,9 +11077,9 @@ export class FleetManager {
|
|
|
11036
11077
|
}
|
|
11037
11078
|
/** Side effects of a routed reply: cancel-button lifecycle, logs, SSE, chat log. */
|
|
11038
11079
|
/** No chat platform is configured at all: the web dashboard is the fleet's only surface. */
|
|
11080
|
+
/** #2910: no Discord or Telegram connection at all (src/web-only-fleet.ts). */
|
|
11039
11081
|
isWebOnlyFleet() {
|
|
11040
|
-
|
|
11041
|
-
return config != null && !config.channel && !(config.channels?.length);
|
|
11082
|
+
return isWebOnlyFleetConfig(this.fleetConfig);
|
|
11042
11083
|
}
|
|
11043
11084
|
/** On a fleet with no chat platform, the dashboard is where an instance-health prompt is posted (#1307 item 6). */
|
|
11044
11085
|
webOnlyPromptPlace() {
|
|
@@ -11413,6 +11454,9 @@ export class FleetManager {
|
|
|
11413
11454
|
// through the target's adapter, so a Telegram-created schedule for a
|
|
11414
11455
|
// Discord target announced its failure into the wrong platform.
|
|
11415
11456
|
const adapter = this.scheduleSourceAdapter(schedule);
|
|
11457
|
+
// #2910 D2: a web-only fleet has no chat to say it in: a Notice about the schedule's target.
|
|
11458
|
+
if (!adapter && this.recordWebNotice("schedule", `⏰ Schedule "${schedule.label ?? schedule.id}" trigger failed: instance ${schedule.target} is offline.`, schedule.target))
|
|
11459
|
+
return;
|
|
11416
11460
|
if (!adapter) {
|
|
11417
11461
|
this.logger.warn({ scheduleId: schedule.id, chatId: schedule.reply_chat_id, source: schedule.source }, "No adapter can reach the schedule's source chat — failure notice not sent");
|
|
11418
11462
|
return;
|
|
@@ -14221,6 +14265,8 @@ export class FleetManager {
|
|
|
14221
14265
|
* back to posting at the bare group id, which is the defect this replaced.
|
|
14222
14266
|
*/
|
|
14223
14267
|
postDailySummary(text) {
|
|
14268
|
+
if (this.recordWebNotice("summary", text))
|
|
14269
|
+
return; // #2910 D2: a web-only fleet's Notices
|
|
14224
14270
|
const target = this.fleetNoticeTarget();
|
|
14225
14271
|
if (!this.adapter || !target) {
|
|
14226
14272
|
this.logger.warn("Daily summary has no postable target — set a General topic or channel.options.general_channel_id");
|
|
@@ -14273,6 +14319,10 @@ export class FleetManager {
|
|
|
14273
14319
|
dispatched = true;
|
|
14274
14320
|
}
|
|
14275
14321
|
}
|
|
14322
|
+
// #2910 D2: a web-only fleet has no General: the notice goes to the dashboard's Notices instead (and spends the
|
|
14323
|
+
// throttle window like a post would).
|
|
14324
|
+
if (!dispatched && this.recordWebNotice("fleet", body))
|
|
14325
|
+
dispatched = true;
|
|
14276
14326
|
if (!dispatched) {
|
|
14277
14327
|
// Do NOT record the throttle key. It used to be claimed before delivery
|
|
14278
14328
|
// was attempted, so a message nobody could receive — every fleet error
|
|
@@ -14295,6 +14345,31 @@ export class FleetManager {
|
|
|
14295
14345
|
}
|
|
14296
14346
|
return true;
|
|
14297
14347
|
}
|
|
14348
|
+
webNotices = null;
|
|
14349
|
+
/** #2910 D2 (web-api): the Notices on a web-only fleet; null on any other (its General says them). */
|
|
14350
|
+
webNoticesFeed() { return this.webOnlyPromptPlace() ? this.webNoticeStore() : null; }
|
|
14351
|
+
/** #2910 D2: the web-only fleet's Notices (web-notices.json), read on first use. */
|
|
14352
|
+
webNoticeStore() {
|
|
14353
|
+
if (!this.dataDir)
|
|
14354
|
+
return null;
|
|
14355
|
+
this.webNotices ??= new WebNotices(join(this.dataDir, WEB_NOTICES_FILE), this.logger);
|
|
14356
|
+
return this.webNotices;
|
|
14357
|
+
}
|
|
14358
|
+
/**
|
|
14359
|
+
* #2910 D2: on a web-only fleet (no chat connection at all), keep what General or an agent's topic would have been
|
|
14360
|
+
* told as a Notice, and push it to the dashboard. False on any other fleet (its chat says it; nothing is kept).
|
|
14361
|
+
*/
|
|
14362
|
+
recordWebNotice(kind, text, instance) {
|
|
14363
|
+
if (!this.webOnlyPromptPlace())
|
|
14364
|
+
return false;
|
|
14365
|
+
const store = this.webNoticeStore();
|
|
14366
|
+
if (!store)
|
|
14367
|
+
return false;
|
|
14368
|
+
const known = instance !== undefined && Object.hasOwn(this.fleetConfig?.instances ?? {}, instance) ? instance : undefined;
|
|
14369
|
+
const notice = store.add({ kind, text: stripPlaceRefs(text), ...(known ? { instance: known } : {}) });
|
|
14370
|
+
this.emitSseEvent("notice", { notice, unread: store.unread() });
|
|
14371
|
+
return true;
|
|
14372
|
+
}
|
|
14298
14373
|
static FLEET_ERROR_THROTTLE_MS = 10 * 60_000;
|
|
14299
14374
|
fleetErrorNotices = new Map();
|
|
14300
14375
|
/**
|
|
@@ -14308,6 +14383,9 @@ export class FleetManager {
|
|
|
14308
14383
|
* would have to make this async and change every caller.
|
|
14309
14384
|
*/
|
|
14310
14385
|
notifyInstanceTopic(instanceName, text, extraOpts) {
|
|
14386
|
+
// #2910 D2: a web-only fleet has no topics: what the agent's topic would have been told is a Notice about it.
|
|
14387
|
+
if (this.webOnlyPromptPlace())
|
|
14388
|
+
return this.recordWebNotice("instance", text, instanceName);
|
|
14311
14389
|
const send = this.sendToInstanceTopic(instanceName, text, extraOpts);
|
|
14312
14390
|
if (!send)
|
|
14313
14391
|
return false;
|
|
@@ -15584,6 +15662,7 @@ export class FleetManager {
|
|
|
15584
15662
|
isAdmin: (userId, adapterId) => this.adminGate(userId, adapterId) === "ok",
|
|
15585
15663
|
authorization: input.session ? this.sessionAuthorizationStore()?.get(input.session.handle) ?? null : null,
|
|
15586
15664
|
policy: { ...webAuthorityPolicy(this.fleetConfig?.web), enabled: state.enabled, loopbackOnly: state.loopbackOnly, namedLink: state.namedLink, quickLink: state.quickLink }, epoch: state.epoch, now: Date.now(),
|
|
15665
|
+
hostOperator: this.isWebOnlyFleet(),
|
|
15587
15666
|
});
|
|
15588
15667
|
}
|
|
15589
15668
|
sessionAuthorizations = null;
|
|
@@ -15616,6 +15695,33 @@ export class FleetManager {
|
|
|
15616
15695
|
store.prune(a => a.epoch === state.epoch && now <= Math.min(a.until, a.authorizedAt + policy.maxSessionMs) && live.has(a.handle));
|
|
15617
15696
|
return { ok: true, ...store.authorize(session.handle, session.owner, now, state.epoch, now + policy.maxSessionMs, peer) };
|
|
15618
15697
|
}
|
|
15698
|
+
/**
|
|
15699
|
+
* #2910 D1 (auth-api, at sign-in): a web-only fleet's host session — signed in with the host's own code (`agend web
|
|
15700
|
+
* --code`) on this computer — is the host operator, authorized from now for max_session_hours (as an account's
|
|
15701
|
+
* Confirm would be), under the current epoch: a revocation ends it, and only a new sign-in on this computer gives it
|
|
15702
|
+
* again. A fleet with a chat connection, another session, or another peer: nothing (§3 as before).
|
|
15703
|
+
*/
|
|
15704
|
+
authorizeHostSession(session, peer) {
|
|
15705
|
+
const store = this.sessionAuthorizationStore();
|
|
15706
|
+
if (!store || !session.host || session.owner || !this.isWebOnlyFleet())
|
|
15707
|
+
return;
|
|
15708
|
+
const state = this.webAuthorityState(), now = Date.now(), policy = webAuthorityPolicy(this.fleetConfig?.web);
|
|
15709
|
+
const check = evaluateWebAuthority({
|
|
15710
|
+
session, gateway: false, peer, tokenEpoch: this.webToken ? tokenEpoch(this.webToken) : null,
|
|
15711
|
+
isAdmin: () => false,
|
|
15712
|
+
authorization: { handle: session.handle, owner: HOST_OPERATOR, authorizedAt: now, until: now + policy.maxSessionMs, epoch: state.epoch },
|
|
15713
|
+
policy: { ...policy, enabled: state.enabled, loopbackOnly: state.loopbackOnly, namedLink: state.namedLink, quickLink: state.quickLink }, epoch: state.epoch, now,
|
|
15714
|
+
hostOperator: true,
|
|
15715
|
+
});
|
|
15716
|
+
if (check.refusal) {
|
|
15717
|
+
this.logger.info({ handle: session.handle, refusal: check.refusal }, "Host web session not authorized");
|
|
15718
|
+
return;
|
|
15719
|
+
}
|
|
15720
|
+
const live = new Set(this.webSessions?.list(undefined, () => true).map(r => r.handle) ?? []);
|
|
15721
|
+
store.prune(a => a.epoch === state.epoch && now <= Math.min(a.until, a.authorizedAt + policy.maxSessionMs) && live.has(a.handle));
|
|
15722
|
+
const { durable } = store.authorize(session.handle, HOST_OPERATOR, now, state.epoch, now + policy.maxSessionMs, peer);
|
|
15723
|
+
this.logger.info({ handle: session.handle, durable }, "Host web session authorized (this computer's admin)");
|
|
15724
|
+
}
|
|
15619
15725
|
authorityStateStore = null;
|
|
15620
15726
|
/**
|
|
15621
15727
|
* #1939 §10: the switch, "Only this computer can authorize" and the revocation epoch in force for the configuration as
|
|
@@ -15671,7 +15777,10 @@ export class FleetManager {
|
|
|
15671
15777
|
const sessions = new Map((this.webSessions?.list(undefined, () => true) ?? []).map(r => [r.handle, r]));
|
|
15672
15778
|
const authorized = state.enabled ? (this.sessionAuthorizationStore()?.list() ?? [])
|
|
15673
15779
|
.filter(a => a.epoch === state.epoch && sessions.has(a.handle) && now <= Math.min(a.until, a.authorizedAt + maxMs))
|
|
15674
|
-
.map(a => ({ handle: a.handle, label: sessions.get(a.handle).label,
|
|
15780
|
+
.map(a => ({ handle: a.handle, label: sessions.get(a.handle).label,
|
|
15781
|
+
// #2910 D1: the host operator's own session says whose it is, not "host:operator".
|
|
15782
|
+
account: a.owner.adapterId === HOST_OPERATOR.adapterId && a.owner.userId === HOST_OPERATOR.userId && this.isWebOnlyFleet()
|
|
15783
|
+
? t("settings_confirm.host_operator_label") : `${a.owner.adapterId}:${a.owner.userId}`, authorizedAt: a.authorizedAt,
|
|
15675
15784
|
until: Math.min(a.until, a.authorizedAt + maxMs, sessions.get(a.handle).absoluteExpiry),
|
|
15676
15785
|
// Over a link: the temporary one (§0.3), else the named one (a record from before §0.3 has no peer).
|
|
15677
15786
|
...(sessions.get(a.handle).surface === "gateway" ? (a.peer === "quick_link" ? { quick: true } : { named: true }) : {}) })) : [];
|
|
@@ -15781,6 +15890,8 @@ export class FleetManager {
|
|
|
15781
15890
|
isAdmin: (userId, adapterId) => this.adminGate(userId, adapterId) === "ok",
|
|
15782
15891
|
authorization: s?.owner ? { handle: s.handle, owner: s.owner, authorizedAt: now, until: now + policy.maxSessionMs, epoch: state.epoch } : null,
|
|
15783
15892
|
policy: { ...policy, enabled: state.enabled, loopbackOnly: state.loopbackOnly, namedLink: state.namedLink, quickLink: state.quickLink }, epoch: state.epoch, now,
|
|
15893
|
+
// #2910 D1: a host operator's session is never asked about (no account to ask): only a sign-in authorizes it.
|
|
15894
|
+
hostOperator: this.isWebOnlyFleet(),
|
|
15784
15895
|
});
|
|
15785
15896
|
return r.refusal;
|
|
15786
15897
|
}
|
|
@@ -16088,8 +16199,10 @@ export class FleetManager {
|
|
|
16088
16199
|
? [{ action: "resume", label: t("inst.session_oom_resume") }, { action: "fresh", label: t("inst.session_oom_fresh") }]
|
|
16089
16200
|
: [{ action: "restart", label: t("exit.restart") }, { action: "ignore", label: t("exit.ignore") }];
|
|
16090
16201
|
const extra = sessionOomRetry ? { sessionOomRetry, ...(current ? { confirmationCurrent: current } : {}) } : undefined;
|
|
16091
|
-
this.notifyInstanceTopic(instanceName, (oom ? message : t("exit.instance_notice", instanceName)) + placeSuffix({ kind: "general" }, "General"));
|
|
16092
16202
|
const web = this.webOnlyPromptPlace();
|
|
16203
|
+
// A pointer to General's controls; on a web-only fleet (#2910 D2) the controls are in Needs you and nothing points there.
|
|
16204
|
+
if (!web)
|
|
16205
|
+
this.notifyInstanceTopic(instanceName, (oom ? message : t("exit.instance_notice", instanceName)) + placeSuffix({ kind: "general" }, "General"));
|
|
16093
16206
|
if (web) {
|
|
16094
16207
|
await this.postNonceButtonPrompt({
|
|
16095
16208
|
prefix, alertType: "exit_restart", instanceName, ...web,
|
|
@@ -16168,11 +16281,19 @@ export class FleetManager {
|
|
|
16168
16281
|
* contain usernames or secret-adjacent text and stays in daemon.log.
|
|
16169
16282
|
*/
|
|
16170
16283
|
async notifyInteractivePrompt(instanceName, kind) {
|
|
16171
|
-
|
|
16284
|
+
// #2910 D2: on a web-only fleet the assist prompt is in Needs you; there is no General to point to.
|
|
16285
|
+
if (!this.webOnlyPromptPlace())
|
|
16286
|
+
this.notifyInstanceTopic(instanceName, t("interactive.instance_notice", instanceName) + placeSuffix({ kind: "general" }, "General"));
|
|
16172
16287
|
// Multi-world fleets can have one General per bot/platform. Route the assist
|
|
16173
16288
|
// request to the General bound to the same adapter as the blocked instance.
|
|
16174
16289
|
const generalName = this.findGeneralInstance(this.getInstanceAdapterId(instanceName));
|
|
16175
16290
|
if (!generalName) {
|
|
16291
|
+
// #2910 D4: a web-only fleet with no General to ask for help — and a password or sudo prompt is not answered from a
|
|
16292
|
+
// web page: a Notice says where it waits (its View, where the person types it).
|
|
16293
|
+
if (this.webOnlyPromptPlace()) {
|
|
16294
|
+
this.recordWebNotice("instance", t("interactive.web_only_notice", instanceName, this.interactivePromptLabel(kind)), instanceName);
|
|
16295
|
+
return;
|
|
16296
|
+
}
|
|
16176
16297
|
this.logger.warn({ instanceName }, "Interactive prompt has no General topic notification target");
|
|
16177
16298
|
return;
|
|
16178
16299
|
}
|
|
@@ -16331,6 +16452,10 @@ export class FleetManager {
|
|
|
16331
16452
|
* was gone; this is the fallback).
|
|
16332
16453
|
*/
|
|
16333
16454
|
async postRemoteCard(instanceName, card) {
|
|
16455
|
+
// #2910 D4: a CLI's own dialog read as a card (selection-card.ts) is put to people only on a web-only fleet — the
|
|
16456
|
+
// dashboard; a chat fleet keeps its parked notice and its General's controls, as before.
|
|
16457
|
+
if (card.kind === "question" && card.webOnly && !this.webOnlyPromptPlace())
|
|
16458
|
+
return;
|
|
16334
16459
|
this.retireRemoteCard(instanceName, null, t("card.answered_in_pane"));
|
|
16335
16460
|
const place = this.remoteCardPlace(instanceName);
|
|
16336
16461
|
if (!place) {
|
|
@@ -16660,6 +16785,9 @@ export class FleetManager {
|
|
|
16660
16785
|
choices: [{ id: `cancel:${instanceName}`, label: t("cancel.button") }],
|
|
16661
16786
|
}, threadId ? { threadId } : undefined);
|
|
16662
16787
|
publication.inFlight = false;
|
|
16788
|
+
// #2940: a status bubble of ours — never a chat-log line, for any bot of this fleet in the room; one a sibling
|
|
16789
|
+
// already logged (its gateway event can come before this answer) is taken out again.
|
|
16790
|
+
this.noteOwnStatusMessage(adapter, sent);
|
|
16663
16791
|
const entry = {
|
|
16664
16792
|
instanceName,
|
|
16665
16793
|
adapterId,
|
|
@@ -18771,6 +18899,45 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
18771
18899
|
await adapter.sendText(msg.chatId, t("cmd.not_a_command"), { threadId: msg.threadId });
|
|
18772
18900
|
return true;
|
|
18773
18901
|
}
|
|
18902
|
+
/** #2940: this fleet's own status bubbles and the messages the platform said were deleted (ids, never text). */
|
|
18903
|
+
ownStatusMessages = new OwnStatusMessages();
|
|
18904
|
+
/**
|
|
18905
|
+
* #2940: a status bubble of ours was posted: never logged, and a line already logged for it (by a sibling bot) goes.
|
|
18906
|
+
* Discord only (#2943 review: the only platform where our bots see each other's posts), keyed by channel and id.
|
|
18907
|
+
*/
|
|
18908
|
+
noteOwnStatusMessage(adapter, sent) {
|
|
18909
|
+
if (adapter.type !== "discord")
|
|
18910
|
+
return;
|
|
18911
|
+
const key = discordMessageKey(sent.threadId ?? sent.chatId, sent.messageId);
|
|
18912
|
+
if (!key)
|
|
18913
|
+
return;
|
|
18914
|
+
this.ownStatusMessages.addStatus(key);
|
|
18915
|
+
const removed = ClassicChannelManager.loggedLines.remove(key);
|
|
18916
|
+
if (removed)
|
|
18917
|
+
this.logger.debug({ key, removed }, "A status bubble logged before its id was known was taken out of the chat log");
|
|
18918
|
+
}
|
|
18919
|
+
/** #2940: Discord says a message was deleted — its chat-log line goes, and it is never logged after this. */
|
|
18920
|
+
onPlatformMessageDeleted(e) {
|
|
18921
|
+
const key = discordMessageKey(e?.channelId, e?.messageId);
|
|
18922
|
+
if (!key)
|
|
18923
|
+
return;
|
|
18924
|
+
this.ownStatusMessages.markDeleted(key);
|
|
18925
|
+
const removed = ClassicChannelManager.loggedLines.remove(key);
|
|
18926
|
+
if (removed)
|
|
18927
|
+
this.logger.debug({ key, removed }, "A deleted message was taken out of the chat log");
|
|
18928
|
+
}
|
|
18929
|
+
/** #2940: the key a Discord message's chat-log line is kept under (none for another platform: nothing is tracked). */
|
|
18930
|
+
chatLogKey(msg) {
|
|
18931
|
+
return msg.source === "discord" ? discordMessageKey(msg.threadId ?? msg.chatId, msg.messageId) : undefined;
|
|
18932
|
+
}
|
|
18933
|
+
/** #2940: whether `msg` must not enter the chat log (only its key is looked at; the debug trail names keys, not text). */
|
|
18934
|
+
chatLogExcludes(instanceName, msg) {
|
|
18935
|
+
const key = this.chatLogKey(msg);
|
|
18936
|
+
if (!this.ownStatusMessages.excluded(key))
|
|
18937
|
+
return false;
|
|
18938
|
+
this.logger.debug({ instanceName, key }, "Not logged: a status bubble of this fleet, or a deleted message");
|
|
18939
|
+
return true;
|
|
18940
|
+
}
|
|
18774
18941
|
/** Handle a message in a classic channel: log it, forward only /chat messages */
|
|
18775
18942
|
async handleClassicChannelMessage(instanceName, msg) {
|
|
18776
18943
|
const text = msg.text ?? "";
|
|
@@ -18814,7 +18981,10 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
18814
18981
|
: (msg.attachments?.length
|
|
18815
18982
|
? ` [${msg.attachments.map(a => `${a.kind === "photo" ? "📷" : "📎"} ${a.filename || a.kind}`).join(", ")}]`
|
|
18816
18983
|
: "");
|
|
18817
|
-
|
|
18984
|
+
// #2940: one of this fleet's own status bubbles (any bot's), or a message already deleted, never enters the log.
|
|
18985
|
+
if (this.chatLogExcludes(instanceName, msg))
|
|
18986
|
+
return;
|
|
18987
|
+
ClassicChannelManager.logMessage(instanceName, msg.username, text + collabAttachTag, msg.timestamp, msg.replyToText, this.chatLogKey(msg));
|
|
18818
18988
|
this.logger.info({ instanceName, user: msg.username, textLen: text.length, attachments: msg.attachments?.length ?? 0, source: msg.source }, "Collab mode message");
|
|
18819
18989
|
// Check for @mention trigger: must be exact <@BOT_USER_ID>, not @everyone/@here.
|
|
18820
18990
|
// Each bot matches ONLY its own id. A secondary bot must NOT fall back to the
|
|
@@ -18915,7 +19085,9 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
18915
19085
|
const attachmentTag = saved ? ` [${saved.kind === "photo" ? "📷" : "📎"} saved: ${saved.paths.join(", ")}]`
|
|
18916
19086
|
: msg.attachments?.length ? ` [${msg.attachments.map(a => `📎 ${a.kind}${a.filename ? `: ${a.filename}` : ""}`).join(", ")}]`
|
|
18917
19087
|
: "";
|
|
18918
|
-
|
|
19088
|
+
// #2940: likewise in a room without collab (a bot's message reaches here only through a /chat text command).
|
|
19089
|
+
if (!this.chatLogExcludes(instanceName, msg))
|
|
19090
|
+
ClassicChannelManager.logMessage(instanceName, msg.username, text + attachmentTag, msg.timestamp, msg.replyToText, this.chatLogKey(msg));
|
|
18919
19091
|
// Bare attachment without /chat: save + log only, don't trigger agent
|
|
18920
19092
|
if (!isChat) {
|
|
18921
19093
|
const reactAdapter = this.worlds.get(msg.adapterId ?? "")?.adapter ?? this.adapter;
|
|
@@ -21624,7 +21796,7 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
21624
21796
|
this.reconcilePreviewListener();
|
|
21625
21797
|
this.publicWebLink?.refresh();
|
|
21626
21798
|
const newInstances = this.fleetConfig.instances;
|
|
21627
|
-
const topicMode = this.fleetConfig
|
|
21799
|
+
const topicMode = daemonTopicMode(this.fleetConfig);
|
|
21628
21800
|
// Only what a fresh process can adopt, and only relative to the signature
|
|
21629
21801
|
// this process came up on: a Settings edit mutates this.fleetConfig in place
|
|
21630
21802
|
// before the reload, so comparing the pre-load copy sees nothing at all.
|
|
@@ -23203,7 +23375,7 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
23203
23375
|
}
|
|
23204
23376
|
const fleet = this.loadConfig(this.configPath);
|
|
23205
23377
|
this.fleetConfig = fleet;
|
|
23206
|
-
const topicMode = fleet
|
|
23378
|
+
const topicMode = daemonTopicMode(fleet);
|
|
23207
23379
|
const restartProgress = new RestartProgress(this.runnableStartupCount(fleet, topicMode), restartStartedAt, this.logger);
|
|
23208
23380
|
// Phase 1: generals first
|
|
23209
23381
|
const restartEntries = Object.entries(fleet.instances);
|
|
@@ -23355,7 +23527,8 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
23355
23527
|
// Only notify when target is genuinely newer (semver), so a beta user on
|
|
23356
23528
|
// 2.0.8-beta.16 is never told that stable 2.0.7 is "available".
|
|
23357
23529
|
if (target && this.semverGt(target, currentVersion)) {
|
|
23358
|
-
|
|
23530
|
+
// #2910 D2 (#2925 review): a web-only fleet first — even with a General instance it has no chat for /update.
|
|
23531
|
+
const generalId = this.webOnlyPromptPlace() ? null : this.findGeneralInstance();
|
|
23359
23532
|
if (generalId) {
|
|
23360
23533
|
// No release URL — Discord's SuppressEmbeds proved unreliable and the
|
|
23361
23534
|
// link preview looked bad. Version + /update instruction is enough.
|
|
@@ -23366,6 +23539,12 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
23366
23539
|
if (key)
|
|
23367
23540
|
this.notifyInstanceTopic(generalId, t(key, `v${target}`, `v${currentVersion}`));
|
|
23368
23541
|
}
|
|
23542
|
+
else if (this.webOnlyPromptPlace() && (fromChannel || updateNoticeKey(currentVersion, target))) {
|
|
23543
|
+
// #2910 D2: a web-only fleet: a Notice, once per version (the check runs daily); installed from this computer.
|
|
23544
|
+
const text = t("update.available_web_only", `v${target}`, `v${currentVersion}`);
|
|
23545
|
+
if (this.webNoticeStore()?.latest("update")?.text !== text)
|
|
23546
|
+
this.recordWebNotice("update", text);
|
|
23547
|
+
}
|
|
23369
23548
|
}
|
|
23370
23549
|
}
|
|
23371
23550
|
catch { /* silent — network issues */ }
|
|
@@ -23679,7 +23858,7 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
23679
23858
|
if (decision.reason === "no-credential" && req.method === "GET"
|
|
23680
23859
|
&& String(req.headers.accept ?? "").includes("text/html")
|
|
23681
23860
|
&& isWebPageNavigation(requestPath)) {
|
|
23682
|
-
serveSigninPage(res, 401);
|
|
23861
|
+
serveSigninPage(res, 401, { webOnly: this.isWebOnlyFleet() });
|
|
23683
23862
|
return;
|
|
23684
23863
|
}
|
|
23685
23864
|
res.writeHead(decision.status);
|
|
@@ -23902,7 +24081,7 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
23902
24081
|
(async () => {
|
|
23903
24082
|
let held = false;
|
|
23904
24083
|
try {
|
|
23905
|
-
const topicMode = this.fleetConfig
|
|
24084
|
+
const topicMode = daemonTopicMode(this.fleetConfig);
|
|
23906
24085
|
await withSpawnHoldReply(async (message) => {
|
|
23907
24086
|
held = true;
|
|
23908
24087
|
res.writeHead(202, { "Content-Type": "application/json" });
|
|
@@ -24137,7 +24316,9 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
24137
24316
|
}
|
|
24138
24317
|
const listener = createPreviewListener({ settings, healthPort: boundPort, config: this.fleetConfig,
|
|
24139
24318
|
// #1723: with a web.preview_origin, the open public link's dashboard frames this listener's preview too.
|
|
24140
|
-
extraOrigins: () => { const o = settings.origin ? this.publicWebLink?.openDashboardOrigin() : null; return o ? [o] : []; }
|
|
24319
|
+
extraOrigins: () => { const o = settings.origin ? this.publicWebLink?.openDashboardOrigin() : null; return o ? [o] : []; },
|
|
24320
|
+
// #2911 (7): CDN libraries in previews, as fleet.yaml says now (a change applies to the next preview started).
|
|
24321
|
+
cdn: () => this.fleetConfig?.web?.preview_cdn === true });
|
|
24141
24322
|
this.previewListener = listener;
|
|
24142
24323
|
listener.server.on("error", (err) => {
|
|
24143
24324
|
if (this.previewListener !== listener)
|
|
@@ -24357,6 +24538,8 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
24357
24538
|
// #1685: host memory pressure while it slows or holds new agents (the dashboard's indicator; macOS elevated is
|
|
24358
24539
|
// shown here rather than in chat). null when normal, unknown or advisory.
|
|
24359
24540
|
host_memory: this.hostMemoryIndicator(),
|
|
24541
|
+
// #2910 D2: a web-only fleet's newest Notice — a polling page (no SSE `notice`) reads the list again when it moves.
|
|
24542
|
+
...(this.webOnlyPromptPlace() ? { notices_newest: this.webNoticeStore()?.newest() ?? 0 } : {}),
|
|
24360
24543
|
// #2015: the largest processes while it lasts (name, pid, RSS, and whose: an instance, the fleet or tmux).
|
|
24361
24544
|
...(this.hostMemoryIndicator() && this.memoryTop ? { host_memory_top: this.memoryTop.processes } : {}),
|
|
24362
24545
|
};
|