@songsid/agend 2.3.3 → 2.3.4-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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/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 +25 -1
- package/dist/fleet-manager.js +150 -21
- 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 +36 -0
- 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 +43 -1
- 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/env-file.d.ts
CHANGED
|
@@ -9,3 +9,5 @@
|
|
|
9
9
|
* - a key that appears twice: the last one wins.
|
|
10
10
|
*/
|
|
11
11
|
export declare function parseEnvText(text: string): Map<string, string>;
|
|
12
|
+
/** The key a .env line sets, as parseEnvText reads it (`export KEY=…` included); null for a blank, comment or keyless line. */
|
|
13
|
+
export declare function envLineKey(line: string): string | null;
|
package/dist/env-file.js
CHANGED
|
@@ -11,17 +11,22 @@
|
|
|
11
11
|
export function parseEnvText(text) {
|
|
12
12
|
const out = new Map();
|
|
13
13
|
for (const line of text.split("\n")) {
|
|
14
|
-
const
|
|
15
|
-
if (!trimmed || trimmed.startsWith("#"))
|
|
16
|
-
continue;
|
|
17
|
-
const eqIdx = trimmed.indexOf("=");
|
|
18
|
-
if (eqIdx < 0)
|
|
19
|
-
continue;
|
|
20
|
-
const key = trimmed.slice(0, eqIdx).replace(/^export\s+/, "").trim();
|
|
14
|
+
const key = envLineKey(line);
|
|
21
15
|
if (!key)
|
|
22
16
|
continue;
|
|
23
|
-
|
|
17
|
+
const trimmed = line.trim();
|
|
18
|
+
out.set(key, trimmed.slice(trimmed.indexOf("=") + 1).replace(/^["'](.*)["']$/, "$1"));
|
|
24
19
|
}
|
|
25
20
|
return out;
|
|
26
21
|
}
|
|
22
|
+
/** The key a .env line sets, as parseEnvText reads it (`export KEY=…` included); null for a blank, comment or keyless line. */
|
|
23
|
+
export function envLineKey(line) {
|
|
24
|
+
const trimmed = line.trim();
|
|
25
|
+
if (!trimmed || trimmed.startsWith("#"))
|
|
26
|
+
return null;
|
|
27
|
+
const eqIdx = trimmed.indexOf("=");
|
|
28
|
+
if (eqIdx < 0)
|
|
29
|
+
return null;
|
|
30
|
+
return trimmed.slice(0, eqIdx).replace(/^export\s+/, "").trim() || null;
|
|
31
|
+
}
|
|
27
32
|
//# sourceMappingURL=env-file.js.map
|
package/dist/env-file.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"env-file.js","sourceRoot":"","sources":["../src/env-file.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,MAAM,GAAG,GAAG,IAAI,GAAG,EAAkB,CAAC;IACtC,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACpC,MAAM,
|
|
1
|
+
{"version":3,"file":"env-file.js","sourceRoot":"","sources":["../src/env-file.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,MAAM,GAAG,GAAG,IAAI,GAAG,EAAkB,CAAC;IACtC,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACpC,MAAM,GAAG,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC;QAC7B,IAAI,CAAC,GAAG;YAAE,SAAS;QACnB,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC5B,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,gBAAgB,EAAE,IAAI,CAAC,CAAC,CAAC;IACxF,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,+HAA+H;AAC/H,MAAM,UAAU,UAAU,CAAC,IAAY;IACrC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;IAC5B,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IACrD,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACnC,IAAI,KAAK,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IAC3B,OAAO,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,OAAO,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,IAAI,CAAC;AAC1E,CAAC"}
|
package/dist/fleet-manager.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { AppIconStore } from "./web-app-icon.js";
|
|
|
4
4
|
import { type NamedTestResult } from "./tunnel/named-link-test.js";
|
|
5
5
|
import { OfficeShareService } from "./office-share-service.js";
|
|
6
6
|
import { type OfficeShare } from "./office-share.js";
|
|
7
|
+
import type { PageShare } from "./page-share.js";
|
|
7
8
|
import type { LoginCodeOwner } from "./web-login.js";
|
|
8
9
|
import { type ProfileTicket } from "./runtime-cpu-profile.js";
|
|
9
10
|
import type { CpuProfile } from "./cpu-profile.js";
|
|
@@ -100,6 +101,7 @@ import { type MemoryHog } from "./memory-hogs.js";
|
|
|
100
101
|
import { type CacheReport } from "./cache-service.js";
|
|
101
102
|
import { type WebCommandResult } from "./web-commands.js";
|
|
102
103
|
import { type SessionRecord } from "./web-session.js";
|
|
104
|
+
import { WebNotices } from "./web-notices.js";
|
|
103
105
|
import { VERIFIED_SESSION_HOURS, type HumanAuthority, type WebAuthorityRefusal, type WebAuthorityResult, type WebPeer } from "./authority/web-authority.js";
|
|
104
106
|
import { type AuthorityState } from "./authority/authority-state.js";
|
|
105
107
|
import { type SessionAuthorization } from "./authority/session-authorizations.js";
|
|
@@ -687,6 +689,10 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
|
|
|
687
689
|
* `share` null with `count`: "Revoke all".
|
|
688
690
|
*/
|
|
689
691
|
officeShareNotice(event: "created" | "revoked", who: string, share: OfficeShare | null, count: number): void;
|
|
692
|
+
/** #2911: the open page shares changed — the page hears it; each one handed out that ended is said in its channel. */
|
|
693
|
+
pageSharesChanged(_list: PageShare[], ended: PageShare[]): void;
|
|
694
|
+
/** #2911: a page share was made — said in the channel of the instance whose chat it came from (never its URL). */
|
|
695
|
+
pageShareNotice(who: string, share: PageShare): void;
|
|
690
696
|
/** A share's owner: a person (`person:<adapter>:<user>`) still a fleet admin there; the host's own admin session stays. */
|
|
691
697
|
private officeShareOwnerCurrent;
|
|
692
698
|
private officeActsInstance?;
|
|
@@ -2289,7 +2295,8 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
|
|
|
2289
2295
|
private handleOutboundFromInstance;
|
|
2290
2296
|
/** Side effects of a routed reply: cancel-button lifecycle, logs, SSE, chat log. */
|
|
2291
2297
|
/** No chat platform is configured at all: the web dashboard is the fleet's only surface. */
|
|
2292
|
-
|
|
2298
|
+
/** #2910: no Discord or Telegram connection at all (src/web-only-fleet.ts). */
|
|
2299
|
+
isWebOnlyFleet(): boolean;
|
|
2293
2300
|
/** On a fleet with no chat platform, the dashboard is where an instance-health prompt is posted (#1307 item 6). */
|
|
2294
2301
|
private webOnlyPromptPlace;
|
|
2295
2302
|
/** Resolves when the reply is in the web chat (#1770: after its files are copied); the caller need not wait. */
|
|
@@ -2852,6 +2859,16 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
|
|
|
2852
2859
|
notifyFleetError(text: string, opts?: {
|
|
2853
2860
|
throttle?: boolean;
|
|
2854
2861
|
}): boolean;
|
|
2862
|
+
private webNotices;
|
|
2863
|
+
/** #2910 D2 (web-api): the Notices on a web-only fleet; null on any other (its General says them). */
|
|
2864
|
+
webNoticesFeed(): WebNotices | null;
|
|
2865
|
+
/** #2910 D2: the web-only fleet's Notices (web-notices.json), read on first use. */
|
|
2866
|
+
webNoticeStore(): WebNotices | null;
|
|
2867
|
+
/**
|
|
2868
|
+
* #2910 D2: on a web-only fleet (no chat connection at all), keep what General or an agent's topic would have been
|
|
2869
|
+
* told as a Notice, and push it to the dashboard. False on any other fleet (its chat says it; nothing is kept).
|
|
2870
|
+
*/
|
|
2871
|
+
private recordWebNotice;
|
|
2855
2872
|
private static readonly FLEET_ERROR_THROTTLE_MS;
|
|
2856
2873
|
private fleetErrorNotices;
|
|
2857
2874
|
/**
|
|
@@ -3144,6 +3161,13 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
|
|
|
3144
3161
|
ok: false;
|
|
3145
3162
|
refusal: WebAuthorityRefusal;
|
|
3146
3163
|
};
|
|
3164
|
+
/**
|
|
3165
|
+
* #2910 D1 (auth-api, at sign-in): a web-only fleet's host session — signed in with the host's own code (`agend web
|
|
3166
|
+
* --code`) on this computer — is the host operator, authorized from now for max_session_hours (as an account's
|
|
3167
|
+
* Confirm would be), under the current epoch: a revocation ends it, and only a new sign-in on this computer gives it
|
|
3168
|
+
* again. A fleet with a chat connection, another session, or another peer: nothing (§3 as before).
|
|
3169
|
+
*/
|
|
3170
|
+
authorizeHostSession(session: SessionRecord, peer: WebPeer): void;
|
|
3147
3171
|
private authorityStateStore;
|
|
3148
3172
|
/**
|
|
3149
3173
|
* #1939 §10: the switch, "Only this computer can authorize" and the revocation epoch in force for the configuration as
|
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";
|
|
@@ -1201,6 +1203,11 @@ export class FleetManager {
|
|
|
1201
1203
|
officeExists: id => this.officeStore.list().offices.some(o => o.id === id),
|
|
1202
1204
|
ownerCurrent: owner => this.officeShareOwnerCurrent(owner),
|
|
1203
1205
|
changed: () => this.emitSseEvent("office_shares", {}),
|
|
1206
|
+
// #2911: page shares on the same tunnel. Their end (expired, stopped, the feature off, the maker no longer an admin,
|
|
1207
|
+
// the fleet stopping) is said in the page's own channel, as its start is (pageShareNotice); never its URL.
|
|
1208
|
+
pageConfig: () => this.fleetConfig?.web?.page_share,
|
|
1209
|
+
pageCdn: () => this.fleetConfig?.web?.preview_cdn === true,
|
|
1210
|
+
pagesChanged: (list, ended) => this.pageSharesChanged(list, ended),
|
|
1204
1211
|
log: (event, detail) => this.logger.info({ event, ...(detail ? { detail } : {}) }, "Office share link"),
|
|
1205
1212
|
}));
|
|
1206
1213
|
}
|
|
@@ -1215,6 +1222,17 @@ export class FleetManager {
|
|
|
1215
1222
|
: share ? t("office_share.revoked_notice", who, office) : t("office_share.revoked_all_notice", who, count);
|
|
1216
1223
|
this.notifyFleetError(text, { throttle: false });
|
|
1217
1224
|
}
|
|
1225
|
+
/** #2911: the open page shares changed — the page hears it; each one handed out that ended is said in its channel. */
|
|
1226
|
+
pageSharesChanged(_list, ended) {
|
|
1227
|
+
this.emitSseEvent("page_shares", {});
|
|
1228
|
+
for (const s of ended)
|
|
1229
|
+
this.notifyInstanceTopic(s.instance, t("page_share.ended_notice", s.title || t("page_share.untitled")));
|
|
1230
|
+
}
|
|
1231
|
+
/** #2911: a page share was made — said in the channel of the instance whose chat it came from (never its URL). */
|
|
1232
|
+
pageShareNotice(who, share) {
|
|
1233
|
+
const minutes = Math.round((share.expiresAt - share.createdAt) / 60_000);
|
|
1234
|
+
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`));
|
|
1235
|
+
}
|
|
1218
1236
|
/** A share's owner: a person (`person:<adapter>:<user>`) still a fleet admin there; the host's own admin session stays. */
|
|
1219
1237
|
officeShareOwnerCurrent(owner) {
|
|
1220
1238
|
const m = /^person:([^:]+):(.+)$/.exec(owner);
|
|
@@ -1612,6 +1630,17 @@ export class FleetManager {
|
|
|
1612
1630
|
const actor = verified();
|
|
1613
1631
|
if (!actor)
|
|
1614
1632
|
return null;
|
|
1633
|
+
// #2910 D1: a web-only fleet's host operator (its `agend web --code` session on this computer) confirms its own
|
|
1634
|
+
// change — the trust `agend settings confirm` has here; there is no account to be an admin of and no General to
|
|
1635
|
+
// ask. Asked again at execution: still the host operator now (this computer, not revoked or run out), still web-only.
|
|
1636
|
+
const hostOperator = (a) => !!a && a.adapter_id === HOST_OPERATOR.adapterId && a.user_id === HOST_OPERATOR.userId && this.isWebOnlyFleet();
|
|
1637
|
+
if (hostOperator(actor)) {
|
|
1638
|
+
return {
|
|
1639
|
+
label: t("settings_confirm.host_operator_label"),
|
|
1640
|
+
current: () => hostOperator(verified()),
|
|
1641
|
+
done: view => { this.logger.info({ id: view.id, state: view.state }, "Settings change confirmed from the web by this computer's admin"); },
|
|
1642
|
+
};
|
|
1643
|
+
}
|
|
1615
1644
|
const chat = this.settingsChatAuthority(store, id);
|
|
1616
1645
|
if (!chat)
|
|
1617
1646
|
return null;
|
|
@@ -5690,8 +5719,7 @@ export class FleetManager {
|
|
|
5690
5719
|
}
|
|
5691
5720
|
/** Recreate a daemon for a marker-only paused instance after an explicit wake/delivery. */
|
|
5692
5721
|
async startPersistedPausedInstance(name, transition) {
|
|
5693
|
-
const topicMode = this.fleetConfig
|
|
5694
|
-
|| !!this.fleetConfig?.channels?.some(channel => channel.mode === "topic");
|
|
5722
|
+
const topicMode = daemonTopicMode(this.fleetConfig);
|
|
5695
5723
|
const fleetConfig = this.fleetConfig?.instances[name];
|
|
5696
5724
|
if (fleetConfig) {
|
|
5697
5725
|
await this.startInstance(name, fleetConfig, topicMode, "fleet-topic", false, transition);
|
|
@@ -5902,8 +5930,7 @@ export class FleetManager {
|
|
|
5902
5930
|
this.logger.info({ name, attempt: attempt + 1 }, "Startup retry deferred — tmux storm window is blocking spawns");
|
|
5903
5931
|
return;
|
|
5904
5932
|
}
|
|
5905
|
-
const topicMode = this.fleetConfig
|
|
5906
|
-
|| !!this.fleetConfig?.channels?.some(channel => channel.mode === "topic");
|
|
5933
|
+
const topicMode = daemonTopicMode(this.fleetConfig);
|
|
5907
5934
|
this.startupRetriesRunning.add(name);
|
|
5908
5935
|
try {
|
|
5909
5936
|
await this.spawnGate.run({
|
|
@@ -6426,7 +6453,7 @@ export class FleetManager {
|
|
|
6426
6453
|
const backend = config.backend ?? this.fleetConfig?.defaults?.backend ?? "claude-code";
|
|
6427
6454
|
return { backend, profile: credentialHomeSpec(backend) ? instanceCredentialProfile(config, this.fleetConfig?.defaults, backend) : null };
|
|
6428
6455
|
});
|
|
6429
|
-
const topicMode = this.fleetConfig
|
|
6456
|
+
const topicMode = daemonTopicMode(this.fleetConfig);
|
|
6430
6457
|
await this.startAfterRestart(name, wasPaused, pausedAt, pauseReason, () => opts?.kiroMigration ? this.startInstance(name, config, topicMode ?? false, "fleet-topic", false, transition, undefined, opts.kiroMigration)
|
|
6431
6458
|
: this.startInstance(name, config, topicMode ?? false, "fleet-topic", false, transition));
|
|
6432
6459
|
this.stormWindow.markRecovered(name);
|
|
@@ -6729,6 +6756,7 @@ export class FleetManager {
|
|
|
6729
6756
|
if (!publicPreviewWanted(this.fleetConfig?.web))
|
|
6730
6757
|
return null;
|
|
6731
6758
|
return new PublicPreview({ exposureId: id, expiresAt, dashboardOrigin, isCurrent, manager: this.getPreviewTunnel(),
|
|
6759
|
+
cdn: () => this.fleetConfig?.web?.preview_cdn === true,
|
|
6732
6760
|
provider: () => new CloudflaredProvider({ binaryName: binary, protocol }),
|
|
6733
6761
|
log: (event, detail) => this.logger.info({ event, exposureId: id, ...(detail ? { detail } : {}) }, "Public web link") });
|
|
6734
6762
|
},
|
|
@@ -7690,7 +7718,8 @@ export class FleetManager {
|
|
|
7690
7718
|
return;
|
|
7691
7719
|
// The session handle makes each notice distinct: notifyFleetError throttles by text, and a second
|
|
7692
7720
|
// sign-in from the same kind of browser is exactly the one the operator most needs to hear about.
|
|
7693
|
-
|
|
7721
|
+
// #2910 D2: a web-only fleet has no /web to revoke with — its Notice names the host's way (every browser signed out).
|
|
7722
|
+
this.notifyFleetError(t(this.webOnlyPromptPlace() ? "web.login_notice_web_only" : "web.login_notice", info.label, info.surface, info.handle.slice(0, 8)));
|
|
7694
7723
|
}
|
|
7695
7724
|
getDashboardAccess() {
|
|
7696
7725
|
return { ready: this.healthServerListening, token: this.webToken };
|
|
@@ -8110,7 +8139,8 @@ export class FleetManager {
|
|
|
8110
8139
|
// Must run before the first General/CLI spawn: pressure can hold startup.
|
|
8111
8140
|
// Logs remain available even before the adapters and health listener exist.
|
|
8112
8141
|
this.memoryPressure.start();
|
|
8113
|
-
|
|
8142
|
+
// #2910: a web-only fleet's agents run in topic mode too (their cross-instance tools work; the dashboard is their place).
|
|
8143
|
+
const topicMode = daemonTopicMode(fleet);
|
|
8114
8144
|
// Set tmux socket isolation for custom AGEND_HOME
|
|
8115
8145
|
const { getTmuxSocketName: getSocket } = await import("./paths.js");
|
|
8116
8146
|
TmuxManager.setSocketName(getSocket());
|
|
@@ -8327,7 +8357,8 @@ export class FleetManager {
|
|
|
8327
8357
|
}
|
|
8328
8358
|
if (generalsCreated && !this.configHistory?.isFallback(this.configPath))
|
|
8329
8359
|
this.saveFleetConfig();
|
|
8330
|
-
|
|
8360
|
+
// #2910: the scheduler (schedules, the task board, decisions) runs on a web-only fleet too; it needs no chat.
|
|
8361
|
+
if (topicMode) {
|
|
8331
8362
|
const schedulerConfig = {
|
|
8332
8363
|
...DEFAULT_SCHEDULER_CONFIG,
|
|
8333
8364
|
...this.fleetConfig?.defaults.scheduler,
|
|
@@ -10836,7 +10867,10 @@ export class FleetManager {
|
|
|
10836
10867
|
// else to go, and "retry shortly" would have the agent retry forever. It goes to the web chat.
|
|
10837
10868
|
const webOnlyReply = tool === "reply" && this.worlds.size === 0 && this.isWebOnlyFleet();
|
|
10838
10869
|
if (this.worlds.size === 0 && !webOnlyReply) {
|
|
10839
|
-
|
|
10870
|
+
// #2910: on a web-only fleet there will never be an adapter — say so, so the agent does not retry forever.
|
|
10871
|
+
respond(null, this.isWebOnlyFleet()
|
|
10872
|
+
? `${tool} is not available on a web-only fleet (no Discord or Telegram connection); answer with reply`
|
|
10873
|
+
: "Channel adapters are not ready — retry shortly");
|
|
10840
10874
|
return;
|
|
10841
10875
|
}
|
|
10842
10876
|
// Resolve threadId: use sender's topic_id if sender is a known fleet instance,
|
|
@@ -11036,9 +11070,9 @@ export class FleetManager {
|
|
|
11036
11070
|
}
|
|
11037
11071
|
/** Side effects of a routed reply: cancel-button lifecycle, logs, SSE, chat log. */
|
|
11038
11072
|
/** No chat platform is configured at all: the web dashboard is the fleet's only surface. */
|
|
11073
|
+
/** #2910: no Discord or Telegram connection at all (src/web-only-fleet.ts). */
|
|
11039
11074
|
isWebOnlyFleet() {
|
|
11040
|
-
|
|
11041
|
-
return config != null && !config.channel && !(config.channels?.length);
|
|
11075
|
+
return isWebOnlyFleetConfig(this.fleetConfig);
|
|
11042
11076
|
}
|
|
11043
11077
|
/** On a fleet with no chat platform, the dashboard is where an instance-health prompt is posted (#1307 item 6). */
|
|
11044
11078
|
webOnlyPromptPlace() {
|
|
@@ -11413,6 +11447,9 @@ export class FleetManager {
|
|
|
11413
11447
|
// through the target's adapter, so a Telegram-created schedule for a
|
|
11414
11448
|
// Discord target announced its failure into the wrong platform.
|
|
11415
11449
|
const adapter = this.scheduleSourceAdapter(schedule);
|
|
11450
|
+
// #2910 D2: a web-only fleet has no chat to say it in: a Notice about the schedule's target.
|
|
11451
|
+
if (!adapter && this.recordWebNotice("schedule", `⏰ Schedule "${schedule.label ?? schedule.id}" trigger failed: instance ${schedule.target} is offline.`, schedule.target))
|
|
11452
|
+
return;
|
|
11416
11453
|
if (!adapter) {
|
|
11417
11454
|
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
11455
|
return;
|
|
@@ -14221,6 +14258,8 @@ export class FleetManager {
|
|
|
14221
14258
|
* back to posting at the bare group id, which is the defect this replaced.
|
|
14222
14259
|
*/
|
|
14223
14260
|
postDailySummary(text) {
|
|
14261
|
+
if (this.recordWebNotice("summary", text))
|
|
14262
|
+
return; // #2910 D2: a web-only fleet's Notices
|
|
14224
14263
|
const target = this.fleetNoticeTarget();
|
|
14225
14264
|
if (!this.adapter || !target) {
|
|
14226
14265
|
this.logger.warn("Daily summary has no postable target — set a General topic or channel.options.general_channel_id");
|
|
@@ -14273,6 +14312,10 @@ export class FleetManager {
|
|
|
14273
14312
|
dispatched = true;
|
|
14274
14313
|
}
|
|
14275
14314
|
}
|
|
14315
|
+
// #2910 D2: a web-only fleet has no General: the notice goes to the dashboard's Notices instead (and spends the
|
|
14316
|
+
// throttle window like a post would).
|
|
14317
|
+
if (!dispatched && this.recordWebNotice("fleet", body))
|
|
14318
|
+
dispatched = true;
|
|
14276
14319
|
if (!dispatched) {
|
|
14277
14320
|
// Do NOT record the throttle key. It used to be claimed before delivery
|
|
14278
14321
|
// was attempted, so a message nobody could receive — every fleet error
|
|
@@ -14295,6 +14338,31 @@ export class FleetManager {
|
|
|
14295
14338
|
}
|
|
14296
14339
|
return true;
|
|
14297
14340
|
}
|
|
14341
|
+
webNotices = null;
|
|
14342
|
+
/** #2910 D2 (web-api): the Notices on a web-only fleet; null on any other (its General says them). */
|
|
14343
|
+
webNoticesFeed() { return this.webOnlyPromptPlace() ? this.webNoticeStore() : null; }
|
|
14344
|
+
/** #2910 D2: the web-only fleet's Notices (web-notices.json), read on first use. */
|
|
14345
|
+
webNoticeStore() {
|
|
14346
|
+
if (!this.dataDir)
|
|
14347
|
+
return null;
|
|
14348
|
+
this.webNotices ??= new WebNotices(join(this.dataDir, WEB_NOTICES_FILE), this.logger);
|
|
14349
|
+
return this.webNotices;
|
|
14350
|
+
}
|
|
14351
|
+
/**
|
|
14352
|
+
* #2910 D2: on a web-only fleet (no chat connection at all), keep what General or an agent's topic would have been
|
|
14353
|
+
* told as a Notice, and push it to the dashboard. False on any other fleet (its chat says it; nothing is kept).
|
|
14354
|
+
*/
|
|
14355
|
+
recordWebNotice(kind, text, instance) {
|
|
14356
|
+
if (!this.webOnlyPromptPlace())
|
|
14357
|
+
return false;
|
|
14358
|
+
const store = this.webNoticeStore();
|
|
14359
|
+
if (!store)
|
|
14360
|
+
return false;
|
|
14361
|
+
const known = instance !== undefined && Object.hasOwn(this.fleetConfig?.instances ?? {}, instance) ? instance : undefined;
|
|
14362
|
+
const notice = store.add({ kind, text: stripPlaceRefs(text), ...(known ? { instance: known } : {}) });
|
|
14363
|
+
this.emitSseEvent("notice", { notice, unread: store.unread() });
|
|
14364
|
+
return true;
|
|
14365
|
+
}
|
|
14298
14366
|
static FLEET_ERROR_THROTTLE_MS = 10 * 60_000;
|
|
14299
14367
|
fleetErrorNotices = new Map();
|
|
14300
14368
|
/**
|
|
@@ -14308,6 +14376,9 @@ export class FleetManager {
|
|
|
14308
14376
|
* would have to make this async and change every caller.
|
|
14309
14377
|
*/
|
|
14310
14378
|
notifyInstanceTopic(instanceName, text, extraOpts) {
|
|
14379
|
+
// #2910 D2: a web-only fleet has no topics: what the agent's topic would have been told is a Notice about it.
|
|
14380
|
+
if (this.webOnlyPromptPlace())
|
|
14381
|
+
return this.recordWebNotice("instance", text, instanceName);
|
|
14311
14382
|
const send = this.sendToInstanceTopic(instanceName, text, extraOpts);
|
|
14312
14383
|
if (!send)
|
|
14313
14384
|
return false;
|
|
@@ -15584,6 +15655,7 @@ export class FleetManager {
|
|
|
15584
15655
|
isAdmin: (userId, adapterId) => this.adminGate(userId, adapterId) === "ok",
|
|
15585
15656
|
authorization: input.session ? this.sessionAuthorizationStore()?.get(input.session.handle) ?? null : null,
|
|
15586
15657
|
policy: { ...webAuthorityPolicy(this.fleetConfig?.web), enabled: state.enabled, loopbackOnly: state.loopbackOnly, namedLink: state.namedLink, quickLink: state.quickLink }, epoch: state.epoch, now: Date.now(),
|
|
15658
|
+
hostOperator: this.isWebOnlyFleet(),
|
|
15587
15659
|
});
|
|
15588
15660
|
}
|
|
15589
15661
|
sessionAuthorizations = null;
|
|
@@ -15616,6 +15688,33 @@ export class FleetManager {
|
|
|
15616
15688
|
store.prune(a => a.epoch === state.epoch && now <= Math.min(a.until, a.authorizedAt + policy.maxSessionMs) && live.has(a.handle));
|
|
15617
15689
|
return { ok: true, ...store.authorize(session.handle, session.owner, now, state.epoch, now + policy.maxSessionMs, peer) };
|
|
15618
15690
|
}
|
|
15691
|
+
/**
|
|
15692
|
+
* #2910 D1 (auth-api, at sign-in): a web-only fleet's host session — signed in with the host's own code (`agend web
|
|
15693
|
+
* --code`) on this computer — is the host operator, authorized from now for max_session_hours (as an account's
|
|
15694
|
+
* Confirm would be), under the current epoch: a revocation ends it, and only a new sign-in on this computer gives it
|
|
15695
|
+
* again. A fleet with a chat connection, another session, or another peer: nothing (§3 as before).
|
|
15696
|
+
*/
|
|
15697
|
+
authorizeHostSession(session, peer) {
|
|
15698
|
+
const store = this.sessionAuthorizationStore();
|
|
15699
|
+
if (!store || !session.host || session.owner || !this.isWebOnlyFleet())
|
|
15700
|
+
return;
|
|
15701
|
+
const state = this.webAuthorityState(), now = Date.now(), policy = webAuthorityPolicy(this.fleetConfig?.web);
|
|
15702
|
+
const check = evaluateWebAuthority({
|
|
15703
|
+
session, gateway: false, peer, tokenEpoch: this.webToken ? tokenEpoch(this.webToken) : null,
|
|
15704
|
+
isAdmin: () => false,
|
|
15705
|
+
authorization: { handle: session.handle, owner: HOST_OPERATOR, authorizedAt: now, until: now + policy.maxSessionMs, epoch: state.epoch },
|
|
15706
|
+
policy: { ...policy, enabled: state.enabled, loopbackOnly: state.loopbackOnly, namedLink: state.namedLink, quickLink: state.quickLink }, epoch: state.epoch, now,
|
|
15707
|
+
hostOperator: true,
|
|
15708
|
+
});
|
|
15709
|
+
if (check.refusal) {
|
|
15710
|
+
this.logger.info({ handle: session.handle, refusal: check.refusal }, "Host web session not authorized");
|
|
15711
|
+
return;
|
|
15712
|
+
}
|
|
15713
|
+
const live = new Set(this.webSessions?.list(undefined, () => true).map(r => r.handle) ?? []);
|
|
15714
|
+
store.prune(a => a.epoch === state.epoch && now <= Math.min(a.until, a.authorizedAt + policy.maxSessionMs) && live.has(a.handle));
|
|
15715
|
+
const { durable } = store.authorize(session.handle, HOST_OPERATOR, now, state.epoch, now + policy.maxSessionMs, peer);
|
|
15716
|
+
this.logger.info({ handle: session.handle, durable }, "Host web session authorized (this computer's admin)");
|
|
15717
|
+
}
|
|
15619
15718
|
authorityStateStore = null;
|
|
15620
15719
|
/**
|
|
15621
15720
|
* #1939 §10: the switch, "Only this computer can authorize" and the revocation epoch in force for the configuration as
|
|
@@ -15671,7 +15770,10 @@ export class FleetManager {
|
|
|
15671
15770
|
const sessions = new Map((this.webSessions?.list(undefined, () => true) ?? []).map(r => [r.handle, r]));
|
|
15672
15771
|
const authorized = state.enabled ? (this.sessionAuthorizationStore()?.list() ?? [])
|
|
15673
15772
|
.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,
|
|
15773
|
+
.map(a => ({ handle: a.handle, label: sessions.get(a.handle).label,
|
|
15774
|
+
// #2910 D1: the host operator's own session says whose it is, not "host:operator".
|
|
15775
|
+
account: a.owner.adapterId === HOST_OPERATOR.adapterId && a.owner.userId === HOST_OPERATOR.userId && this.isWebOnlyFleet()
|
|
15776
|
+
? t("settings_confirm.host_operator_label") : `${a.owner.adapterId}:${a.owner.userId}`, authorizedAt: a.authorizedAt,
|
|
15675
15777
|
until: Math.min(a.until, a.authorizedAt + maxMs, sessions.get(a.handle).absoluteExpiry),
|
|
15676
15778
|
// Over a link: the temporary one (§0.3), else the named one (a record from before §0.3 has no peer).
|
|
15677
15779
|
...(sessions.get(a.handle).surface === "gateway" ? (a.peer === "quick_link" ? { quick: true } : { named: true }) : {}) })) : [];
|
|
@@ -15781,6 +15883,8 @@ export class FleetManager {
|
|
|
15781
15883
|
isAdmin: (userId, adapterId) => this.adminGate(userId, adapterId) === "ok",
|
|
15782
15884
|
authorization: s?.owner ? { handle: s.handle, owner: s.owner, authorizedAt: now, until: now + policy.maxSessionMs, epoch: state.epoch } : null,
|
|
15783
15885
|
policy: { ...policy, enabled: state.enabled, loopbackOnly: state.loopbackOnly, namedLink: state.namedLink, quickLink: state.quickLink }, epoch: state.epoch, now,
|
|
15886
|
+
// #2910 D1: a host operator's session is never asked about (no account to ask): only a sign-in authorizes it.
|
|
15887
|
+
hostOperator: this.isWebOnlyFleet(),
|
|
15784
15888
|
});
|
|
15785
15889
|
return r.refusal;
|
|
15786
15890
|
}
|
|
@@ -16088,8 +16192,10 @@ export class FleetManager {
|
|
|
16088
16192
|
? [{ action: "resume", label: t("inst.session_oom_resume") }, { action: "fresh", label: t("inst.session_oom_fresh") }]
|
|
16089
16193
|
: [{ action: "restart", label: t("exit.restart") }, { action: "ignore", label: t("exit.ignore") }];
|
|
16090
16194
|
const extra = sessionOomRetry ? { sessionOomRetry, ...(current ? { confirmationCurrent: current } : {}) } : undefined;
|
|
16091
|
-
this.notifyInstanceTopic(instanceName, (oom ? message : t("exit.instance_notice", instanceName)) + placeSuffix({ kind: "general" }, "General"));
|
|
16092
16195
|
const web = this.webOnlyPromptPlace();
|
|
16196
|
+
// A pointer to General's controls; on a web-only fleet (#2910 D2) the controls are in Needs you and nothing points there.
|
|
16197
|
+
if (!web)
|
|
16198
|
+
this.notifyInstanceTopic(instanceName, (oom ? message : t("exit.instance_notice", instanceName)) + placeSuffix({ kind: "general" }, "General"));
|
|
16093
16199
|
if (web) {
|
|
16094
16200
|
await this.postNonceButtonPrompt({
|
|
16095
16201
|
prefix, alertType: "exit_restart", instanceName, ...web,
|
|
@@ -16168,11 +16274,19 @@ export class FleetManager {
|
|
|
16168
16274
|
* contain usernames or secret-adjacent text and stays in daemon.log.
|
|
16169
16275
|
*/
|
|
16170
16276
|
async notifyInteractivePrompt(instanceName, kind) {
|
|
16171
|
-
|
|
16277
|
+
// #2910 D2: on a web-only fleet the assist prompt is in Needs you; there is no General to point to.
|
|
16278
|
+
if (!this.webOnlyPromptPlace())
|
|
16279
|
+
this.notifyInstanceTopic(instanceName, t("interactive.instance_notice", instanceName) + placeSuffix({ kind: "general" }, "General"));
|
|
16172
16280
|
// Multi-world fleets can have one General per bot/platform. Route the assist
|
|
16173
16281
|
// request to the General bound to the same adapter as the blocked instance.
|
|
16174
16282
|
const generalName = this.findGeneralInstance(this.getInstanceAdapterId(instanceName));
|
|
16175
16283
|
if (!generalName) {
|
|
16284
|
+
// #2910 D4: a web-only fleet with no General to ask for help — and a password or sudo prompt is not answered from a
|
|
16285
|
+
// web page: a Notice says where it waits (its View, where the person types it).
|
|
16286
|
+
if (this.webOnlyPromptPlace()) {
|
|
16287
|
+
this.recordWebNotice("instance", t("interactive.web_only_notice", instanceName, this.interactivePromptLabel(kind)), instanceName);
|
|
16288
|
+
return;
|
|
16289
|
+
}
|
|
16176
16290
|
this.logger.warn({ instanceName }, "Interactive prompt has no General topic notification target");
|
|
16177
16291
|
return;
|
|
16178
16292
|
}
|
|
@@ -16331,6 +16445,10 @@ export class FleetManager {
|
|
|
16331
16445
|
* was gone; this is the fallback).
|
|
16332
16446
|
*/
|
|
16333
16447
|
async postRemoteCard(instanceName, card) {
|
|
16448
|
+
// #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
|
|
16449
|
+
// dashboard; a chat fleet keeps its parked notice and its General's controls, as before.
|
|
16450
|
+
if (card.kind === "question" && card.webOnly && !this.webOnlyPromptPlace())
|
|
16451
|
+
return;
|
|
16334
16452
|
this.retireRemoteCard(instanceName, null, t("card.answered_in_pane"));
|
|
16335
16453
|
const place = this.remoteCardPlace(instanceName);
|
|
16336
16454
|
if (!place) {
|
|
@@ -21624,7 +21742,7 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
21624
21742
|
this.reconcilePreviewListener();
|
|
21625
21743
|
this.publicWebLink?.refresh();
|
|
21626
21744
|
const newInstances = this.fleetConfig.instances;
|
|
21627
|
-
const topicMode = this.fleetConfig
|
|
21745
|
+
const topicMode = daemonTopicMode(this.fleetConfig);
|
|
21628
21746
|
// Only what a fresh process can adopt, and only relative to the signature
|
|
21629
21747
|
// this process came up on: a Settings edit mutates this.fleetConfig in place
|
|
21630
21748
|
// before the reload, so comparing the pre-load copy sees nothing at all.
|
|
@@ -23203,7 +23321,7 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
23203
23321
|
}
|
|
23204
23322
|
const fleet = this.loadConfig(this.configPath);
|
|
23205
23323
|
this.fleetConfig = fleet;
|
|
23206
|
-
const topicMode = fleet
|
|
23324
|
+
const topicMode = daemonTopicMode(fleet);
|
|
23207
23325
|
const restartProgress = new RestartProgress(this.runnableStartupCount(fleet, topicMode), restartStartedAt, this.logger);
|
|
23208
23326
|
// Phase 1: generals first
|
|
23209
23327
|
const restartEntries = Object.entries(fleet.instances);
|
|
@@ -23355,7 +23473,8 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
23355
23473
|
// Only notify when target is genuinely newer (semver), so a beta user on
|
|
23356
23474
|
// 2.0.8-beta.16 is never told that stable 2.0.7 is "available".
|
|
23357
23475
|
if (target && this.semverGt(target, currentVersion)) {
|
|
23358
|
-
|
|
23476
|
+
// #2910 D2 (#2925 review): a web-only fleet first — even with a General instance it has no chat for /update.
|
|
23477
|
+
const generalId = this.webOnlyPromptPlace() ? null : this.findGeneralInstance();
|
|
23359
23478
|
if (generalId) {
|
|
23360
23479
|
// No release URL — Discord's SuppressEmbeds proved unreliable and the
|
|
23361
23480
|
// link preview looked bad. Version + /update instruction is enough.
|
|
@@ -23366,6 +23485,12 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
23366
23485
|
if (key)
|
|
23367
23486
|
this.notifyInstanceTopic(generalId, t(key, `v${target}`, `v${currentVersion}`));
|
|
23368
23487
|
}
|
|
23488
|
+
else if (this.webOnlyPromptPlace() && (fromChannel || updateNoticeKey(currentVersion, target))) {
|
|
23489
|
+
// #2910 D2: a web-only fleet: a Notice, once per version (the check runs daily); installed from this computer.
|
|
23490
|
+
const text = t("update.available_web_only", `v${target}`, `v${currentVersion}`);
|
|
23491
|
+
if (this.webNoticeStore()?.latest("update")?.text !== text)
|
|
23492
|
+
this.recordWebNotice("update", text);
|
|
23493
|
+
}
|
|
23369
23494
|
}
|
|
23370
23495
|
}
|
|
23371
23496
|
catch { /* silent — network issues */ }
|
|
@@ -23679,7 +23804,7 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
23679
23804
|
if (decision.reason === "no-credential" && req.method === "GET"
|
|
23680
23805
|
&& String(req.headers.accept ?? "").includes("text/html")
|
|
23681
23806
|
&& isWebPageNavigation(requestPath)) {
|
|
23682
|
-
serveSigninPage(res, 401);
|
|
23807
|
+
serveSigninPage(res, 401, { webOnly: this.isWebOnlyFleet() });
|
|
23683
23808
|
return;
|
|
23684
23809
|
}
|
|
23685
23810
|
res.writeHead(decision.status);
|
|
@@ -23902,7 +24027,7 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
23902
24027
|
(async () => {
|
|
23903
24028
|
let held = false;
|
|
23904
24029
|
try {
|
|
23905
|
-
const topicMode = this.fleetConfig
|
|
24030
|
+
const topicMode = daemonTopicMode(this.fleetConfig);
|
|
23906
24031
|
await withSpawnHoldReply(async (message) => {
|
|
23907
24032
|
held = true;
|
|
23908
24033
|
res.writeHead(202, { "Content-Type": "application/json" });
|
|
@@ -24137,7 +24262,9 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
24137
24262
|
}
|
|
24138
24263
|
const listener = createPreviewListener({ settings, healthPort: boundPort, config: this.fleetConfig,
|
|
24139
24264
|
// #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] : []; }
|
|
24265
|
+
extraOrigins: () => { const o = settings.origin ? this.publicWebLink?.openDashboardOrigin() : null; return o ? [o] : []; },
|
|
24266
|
+
// #2911 (7): CDN libraries in previews, as fleet.yaml says now (a change applies to the next preview started).
|
|
24267
|
+
cdn: () => this.fleetConfig?.web?.preview_cdn === true });
|
|
24141
24268
|
this.previewListener = listener;
|
|
24142
24269
|
listener.server.on("error", (err) => {
|
|
24143
24270
|
if (this.previewListener !== listener)
|
|
@@ -24357,6 +24484,8 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
24357
24484
|
// #1685: host memory pressure while it slows or holds new agents (the dashboard's indicator; macOS elevated is
|
|
24358
24485
|
// shown here rather than in chat). null when normal, unknown or advisory.
|
|
24359
24486
|
host_memory: this.hostMemoryIndicator(),
|
|
24487
|
+
// #2910 D2: a web-only fleet's newest Notice — a polling page (no SSE `notice`) reads the list again when it moves.
|
|
24488
|
+
...(this.webOnlyPromptPlace() ? { notices_newest: this.webNoticeStore()?.newest() ?? 0 } : {}),
|
|
24360
24489
|
// #2015: the largest processes while it lasts (name, pid, RSS, and whose: an instance, the fleet or tmux).
|
|
24361
24490
|
...(this.hostMemoryIndicator() && this.memoryTop ? { host_memory_top: this.memoryTop.processes } : {}),
|
|
24362
24491
|
};
|