@songsid/agend 2.3.3-beta.9 → 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 +41 -1
- package/dist/fleet-manager.js +179 -22
- 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 +56 -2
- 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-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-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/public-web-link.d.ts +10 -0
- package/dist/public-web-link.js +24 -5
- package/dist/public-web-link.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.d.ts +10 -0
- package/dist/settings-api.js +45 -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/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 +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 +14 -5
- 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-named-test.js +144 -0
- package/dist/ui/settings-registry.js +1 -0
- package/dist/ui/settings-strings.js +10 -6
- 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 +50 -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 +5 -3
- 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
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { type HumanOrigin } from "./human-delivery-receipts.js";
|
|
2
2
|
import { SettingsExecution } from "./settings-transaction.js";
|
|
3
3
|
import { AppIconStore } from "./web-app-icon.js";
|
|
4
|
+
import { type NamedTestResult } from "./tunnel/named-link-test.js";
|
|
4
5
|
import { OfficeShareService } from "./office-share-service.js";
|
|
5
6
|
import { type OfficeShare } from "./office-share.js";
|
|
7
|
+
import type { PageShare } from "./page-share.js";
|
|
6
8
|
import type { LoginCodeOwner } from "./web-login.js";
|
|
7
9
|
import { type ProfileTicket } from "./runtime-cpu-profile.js";
|
|
8
10
|
import type { CpuProfile } from "./cpu-profile.js";
|
|
@@ -99,6 +101,7 @@ import { type MemoryHog } from "./memory-hogs.js";
|
|
|
99
101
|
import { type CacheReport } from "./cache-service.js";
|
|
100
102
|
import { type WebCommandResult } from "./web-commands.js";
|
|
101
103
|
import { type SessionRecord } from "./web-session.js";
|
|
104
|
+
import { WebNotices } from "./web-notices.js";
|
|
102
105
|
import { VERIFIED_SESSION_HOURS, type HumanAuthority, type WebAuthorityRefusal, type WebAuthorityResult, type WebPeer } from "./authority/web-authority.js";
|
|
103
106
|
import { type AuthorityState } from "./authority/authority-state.js";
|
|
104
107
|
import { type SessionAuthorization } from "./authority/session-authorizations.js";
|
|
@@ -686,6 +689,10 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
|
|
|
686
689
|
* `share` null with `count`: "Revoke all".
|
|
687
690
|
*/
|
|
688
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;
|
|
689
696
|
/** A share's owner: a person (`person:<adapter>:<user>`) still a fleet admin there; the host's own admin session stays. */
|
|
690
697
|
private officeShareOwnerCurrent;
|
|
691
698
|
private officeActsInstance?;
|
|
@@ -1642,6 +1649,21 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
|
|
|
1642
1649
|
private publicWebLink;
|
|
1643
1650
|
/** #1723: the public link's preview tunnel — a manager and lease of its own, never the fleet's one tunnel lane. */
|
|
1644
1651
|
private getPreviewTunnel;
|
|
1652
|
+
private namedLinkTestRunning;
|
|
1653
|
+
/**
|
|
1654
|
+
* #2906 (the user, 2026-11-01): Settings → Access → Test connection, on the fixed-address values as filled in (the
|
|
1655
|
+
* typed connector token, else the saved one). One at a time; the result is closed step codes only — the token is
|
|
1656
|
+
* never logged or returned. Admin only, never over the public link (settings-api.ts).
|
|
1657
|
+
*/
|
|
1658
|
+
testNamedLink(input: {
|
|
1659
|
+
hostname: unknown;
|
|
1660
|
+
port: unknown;
|
|
1661
|
+
protocol: unknown;
|
|
1662
|
+
token?: unknown;
|
|
1663
|
+
current?: () => boolean;
|
|
1664
|
+
}): Promise<NamedTestResult | {
|
|
1665
|
+
busy: true;
|
|
1666
|
+
}>;
|
|
1645
1667
|
private getTunnelLane;
|
|
1646
1668
|
private publicOwnerCurrent;
|
|
1647
1669
|
/** Exact owning General. No primary-adapter or name fallback participates in authorization. */
|
|
@@ -2273,7 +2295,8 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
|
|
|
2273
2295
|
private handleOutboundFromInstance;
|
|
2274
2296
|
/** Side effects of a routed reply: cancel-button lifecycle, logs, SSE, chat log. */
|
|
2275
2297
|
/** No chat platform is configured at all: the web dashboard is the fleet's only surface. */
|
|
2276
|
-
|
|
2298
|
+
/** #2910: no Discord or Telegram connection at all (src/web-only-fleet.ts). */
|
|
2299
|
+
isWebOnlyFleet(): boolean;
|
|
2277
2300
|
/** On a fleet with no chat platform, the dashboard is where an instance-health prompt is posted (#1307 item 6). */
|
|
2278
2301
|
private webOnlyPromptPlace;
|
|
2279
2302
|
/** Resolves when the reply is in the web chat (#1770: after its files are copied); the caller need not wait. */
|
|
@@ -2836,6 +2859,16 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
|
|
|
2836
2859
|
notifyFleetError(text: string, opts?: {
|
|
2837
2860
|
throttle?: boolean;
|
|
2838
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;
|
|
2839
2872
|
private static readonly FLEET_ERROR_THROTTLE_MS;
|
|
2840
2873
|
private fleetErrorNotices;
|
|
2841
2874
|
/**
|
|
@@ -3128,6 +3161,13 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
|
|
|
3128
3161
|
ok: false;
|
|
3129
3162
|
refusal: WebAuthorityRefusal;
|
|
3130
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;
|
|
3131
3171
|
private authorityStateStore;
|
|
3132
3172
|
/**
|
|
3133
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
|
@@ -27,6 +27,8 @@ 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";
|
|
31
|
+
import { daemonTopicMode, isWebOnlyFleetConfig } from "./web-only-fleet.js";
|
|
30
32
|
import { OFFICE_SHARE_TUNNEL_DIR, OfficeShareService } from "./office-share-service.js";
|
|
31
33
|
import { ALL_OFFICES, WHOLE_FLEET } from "./office-share.js";
|
|
32
34
|
import { TunnelPurposeLane } from "./tunnel/purpose-lane.js";
|
|
@@ -263,7 +265,8 @@ import { authorizeSession, decideWebGate, loadOrCreateWebToken, readSessionCooki
|
|
|
263
265
|
import { bypassesWebGate, handleAuthRequest, serveGuestRefusedPage, serveSigninPage } from "./auth-api.js";
|
|
264
266
|
import { isWebPageNavigation } from "./web-shell-routes.js";
|
|
265
267
|
import { guestSessionPolicy, localSessionPolicy, tokenEpoch, WebSessionStore } from "./web-session.js";
|
|
266
|
-
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";
|
|
267
270
|
import { AuthorityStateStore } from "./authority/authority-state.js";
|
|
268
271
|
import { SessionAuthorizations } from "./authority/session-authorizations.js";
|
|
269
272
|
import { SessionAuthorizationRequests } from "./web-session-authorization.js";
|
|
@@ -1200,6 +1203,11 @@ export class FleetManager {
|
|
|
1200
1203
|
officeExists: id => this.officeStore.list().offices.some(o => o.id === id),
|
|
1201
1204
|
ownerCurrent: owner => this.officeShareOwnerCurrent(owner),
|
|
1202
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),
|
|
1203
1211
|
log: (event, detail) => this.logger.info({ event, ...(detail ? { detail } : {}) }, "Office share link"),
|
|
1204
1212
|
}));
|
|
1205
1213
|
}
|
|
@@ -1214,6 +1222,17 @@ export class FleetManager {
|
|
|
1214
1222
|
: share ? t("office_share.revoked_notice", who, office) : t("office_share.revoked_all_notice", who, count);
|
|
1215
1223
|
this.notifyFleetError(text, { throttle: false });
|
|
1216
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
|
+
}
|
|
1217
1236
|
/** A share's owner: a person (`person:<adapter>:<user>`) still a fleet admin there; the host's own admin session stays. */
|
|
1218
1237
|
officeShareOwnerCurrent(owner) {
|
|
1219
1238
|
const m = /^person:([^:]+):(.+)$/.exec(owner);
|
|
@@ -1611,6 +1630,17 @@ export class FleetManager {
|
|
|
1611
1630
|
const actor = verified();
|
|
1612
1631
|
if (!actor)
|
|
1613
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
|
+
}
|
|
1614
1644
|
const chat = this.settingsChatAuthority(store, id);
|
|
1615
1645
|
if (!chat)
|
|
1616
1646
|
return null;
|
|
@@ -5689,8 +5719,7 @@ export class FleetManager {
|
|
|
5689
5719
|
}
|
|
5690
5720
|
/** Recreate a daemon for a marker-only paused instance after an explicit wake/delivery. */
|
|
5691
5721
|
async startPersistedPausedInstance(name, transition) {
|
|
5692
|
-
const topicMode = this.fleetConfig
|
|
5693
|
-
|| !!this.fleetConfig?.channels?.some(channel => channel.mode === "topic");
|
|
5722
|
+
const topicMode = daemonTopicMode(this.fleetConfig);
|
|
5694
5723
|
const fleetConfig = this.fleetConfig?.instances[name];
|
|
5695
5724
|
if (fleetConfig) {
|
|
5696
5725
|
await this.startInstance(name, fleetConfig, topicMode, "fleet-topic", false, transition);
|
|
@@ -5901,8 +5930,7 @@ export class FleetManager {
|
|
|
5901
5930
|
this.logger.info({ name, attempt: attempt + 1 }, "Startup retry deferred — tmux storm window is blocking spawns");
|
|
5902
5931
|
return;
|
|
5903
5932
|
}
|
|
5904
|
-
const topicMode = this.fleetConfig
|
|
5905
|
-
|| !!this.fleetConfig?.channels?.some(channel => channel.mode === "topic");
|
|
5933
|
+
const topicMode = daemonTopicMode(this.fleetConfig);
|
|
5906
5934
|
this.startupRetriesRunning.add(name);
|
|
5907
5935
|
try {
|
|
5908
5936
|
await this.spawnGate.run({
|
|
@@ -6425,7 +6453,7 @@ export class FleetManager {
|
|
|
6425
6453
|
const backend = config.backend ?? this.fleetConfig?.defaults?.backend ?? "claude-code";
|
|
6426
6454
|
return { backend, profile: credentialHomeSpec(backend) ? instanceCredentialProfile(config, this.fleetConfig?.defaults, backend) : null };
|
|
6427
6455
|
});
|
|
6428
|
-
const topicMode = this.fleetConfig
|
|
6456
|
+
const topicMode = daemonTopicMode(this.fleetConfig);
|
|
6429
6457
|
await this.startAfterRestart(name, wasPaused, pausedAt, pauseReason, () => opts?.kiroMigration ? this.startInstance(name, config, topicMode ?? false, "fleet-topic", false, transition, undefined, opts.kiroMigration)
|
|
6430
6458
|
: this.startInstance(name, config, topicMode ?? false, "fleet-topic", false, transition));
|
|
6431
6459
|
this.stormWindow.markRecovered(name);
|
|
@@ -6585,6 +6613,32 @@ export class FleetManager {
|
|
|
6585
6613
|
return this.previewTunnel ??= new ManagedTunnel({ dataDir: join(this.dataDir, PUBLIC_PREVIEW_TUNNEL_DIR),
|
|
6586
6614
|
log: () => this.logger.warn("Public preview tunnel cleanup requires attention") });
|
|
6587
6615
|
}
|
|
6616
|
+
namedLinkTestRunning = false;
|
|
6617
|
+
/**
|
|
6618
|
+
* #2906 (the user, 2026-11-01): Settings → Access → Test connection, on the fixed-address values as filled in (the
|
|
6619
|
+
* typed connector token, else the saved one). One at a time; the result is closed step codes only — the token is
|
|
6620
|
+
* never logged or returned. Admin only, never over the public link (settings-api.ts).
|
|
6621
|
+
*/
|
|
6622
|
+
async testNamedLink(input) {
|
|
6623
|
+
if (this.namedLinkTestRunning)
|
|
6624
|
+
return { busy: true };
|
|
6625
|
+
this.namedLinkTestRunning = true;
|
|
6626
|
+
try {
|
|
6627
|
+
const typed = typeof input.token === "string" && input.token.trim() ? input.token : null;
|
|
6628
|
+
const result = await runNamedLinkTest({ hostname: input.hostname, port: input.port, protocol: input.protocol, token: typed ?? process.env[PUBLIC_TUNNEL_TOKEN_ENV] ?? null }, {
|
|
6629
|
+
healthPort: this.fleetConfig?.health_port ?? 19280,
|
|
6630
|
+
ensureBinary: async (signal) => (await ensureCloudflared({ dataDir: this.dataDir, pinnedOnly: true, signal })).path,
|
|
6631
|
+
liveMarker: values => this.publicWebLink?.liveNamedMarker(values) ?? null,
|
|
6632
|
+
reserve: namedTestLane(this.getTunnelLane()),
|
|
6633
|
+
current: input.current,
|
|
6634
|
+
});
|
|
6635
|
+
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");
|
|
6636
|
+
return result;
|
|
6637
|
+
}
|
|
6638
|
+
finally {
|
|
6639
|
+
this.namedLinkTestRunning = false;
|
|
6640
|
+
}
|
|
6641
|
+
}
|
|
6588
6642
|
getTunnelLane() {
|
|
6589
6643
|
return this.tunnelPurposeLane ??= new TunnelPurposeLane(new ManagedTunnel({ dataDir: this.dataDir,
|
|
6590
6644
|
log: () => this.logger.warn("Managed tunnel cleanup requires attention") }));
|
|
@@ -6702,6 +6756,7 @@ export class FleetManager {
|
|
|
6702
6756
|
if (!publicPreviewWanted(this.fleetConfig?.web))
|
|
6703
6757
|
return null;
|
|
6704
6758
|
return new PublicPreview({ exposureId: id, expiresAt, dashboardOrigin, isCurrent, manager: this.getPreviewTunnel(),
|
|
6759
|
+
cdn: () => this.fleetConfig?.web?.preview_cdn === true,
|
|
6705
6760
|
provider: () => new CloudflaredProvider({ binaryName: binary, protocol }),
|
|
6706
6761
|
log: (event, detail) => this.logger.info({ event, exposureId: id, ...(detail ? { detail } : {}) }, "Public web link") });
|
|
6707
6762
|
},
|
|
@@ -6960,7 +7015,8 @@ export class FleetManager {
|
|
|
6960
7015
|
: "\n" + t("dashboard.public_status", status.state, status.remainingSeconds ?? 0)
|
|
6961
7016
|
+ (status.expiresAt ? t("dashboard.public_until", formatFleetTime(status.expiresAt)) : ""));
|
|
6962
7017
|
const choices = [{ action: "local", label: t("dashboard.local_signin") },
|
|
6963
|
-
...(publicLinkSettings(this.fleetConfig?.web).allowed
|
|
7018
|
+
...(publicLinkSettings(this.fleetConfig?.web).allowed
|
|
7019
|
+
? [{ action: "public", label: t(publicLinkNamedHostname(this.fleetConfig?.web) ? "dashboard.public_open_named" : "dashboard.public_open") }] : []),
|
|
6964
7020
|
...(status.state !== "closed" ? [{ action: "close", label: t("dashboard.public_close") }] : [])];
|
|
6965
7021
|
const posted = await this.postNonceButtonPrompt({
|
|
6966
7022
|
prefix: "dashboard:", alertType: "login", instanceName: "dashboard", adapter, adapterId: owner.adapterId,
|
|
@@ -7662,7 +7718,8 @@ export class FleetManager {
|
|
|
7662
7718
|
return;
|
|
7663
7719
|
// The session handle makes each notice distinct: notifyFleetError throttles by text, and a second
|
|
7664
7720
|
// sign-in from the same kind of browser is exactly the one the operator most needs to hear about.
|
|
7665
|
-
|
|
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)));
|
|
7666
7723
|
}
|
|
7667
7724
|
getDashboardAccess() {
|
|
7668
7725
|
return { ready: this.healthServerListening, token: this.webToken };
|
|
@@ -8082,7 +8139,8 @@ export class FleetManager {
|
|
|
8082
8139
|
// Must run before the first General/CLI spawn: pressure can hold startup.
|
|
8083
8140
|
// Logs remain available even before the adapters and health listener exist.
|
|
8084
8141
|
this.memoryPressure.start();
|
|
8085
|
-
|
|
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);
|
|
8086
8144
|
// Set tmux socket isolation for custom AGEND_HOME
|
|
8087
8145
|
const { getTmuxSocketName: getSocket } = await import("./paths.js");
|
|
8088
8146
|
TmuxManager.setSocketName(getSocket());
|
|
@@ -8299,7 +8357,8 @@ export class FleetManager {
|
|
|
8299
8357
|
}
|
|
8300
8358
|
if (generalsCreated && !this.configHistory?.isFallback(this.configPath))
|
|
8301
8359
|
this.saveFleetConfig();
|
|
8302
|
-
|
|
8360
|
+
// #2910: the scheduler (schedules, the task board, decisions) runs on a web-only fleet too; it needs no chat.
|
|
8361
|
+
if (topicMode) {
|
|
8303
8362
|
const schedulerConfig = {
|
|
8304
8363
|
...DEFAULT_SCHEDULER_CONFIG,
|
|
8305
8364
|
...this.fleetConfig?.defaults.scheduler,
|
|
@@ -10808,7 +10867,10 @@ export class FleetManager {
|
|
|
10808
10867
|
// else to go, and "retry shortly" would have the agent retry forever. It goes to the web chat.
|
|
10809
10868
|
const webOnlyReply = tool === "reply" && this.worlds.size === 0 && this.isWebOnlyFleet();
|
|
10810
10869
|
if (this.worlds.size === 0 && !webOnlyReply) {
|
|
10811
|
-
|
|
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");
|
|
10812
10874
|
return;
|
|
10813
10875
|
}
|
|
10814
10876
|
// Resolve threadId: use sender's topic_id if sender is a known fleet instance,
|
|
@@ -11008,9 +11070,9 @@ export class FleetManager {
|
|
|
11008
11070
|
}
|
|
11009
11071
|
/** Side effects of a routed reply: cancel-button lifecycle, logs, SSE, chat log. */
|
|
11010
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). */
|
|
11011
11074
|
isWebOnlyFleet() {
|
|
11012
|
-
|
|
11013
|
-
return config != null && !config.channel && !(config.channels?.length);
|
|
11075
|
+
return isWebOnlyFleetConfig(this.fleetConfig);
|
|
11014
11076
|
}
|
|
11015
11077
|
/** On a fleet with no chat platform, the dashboard is where an instance-health prompt is posted (#1307 item 6). */
|
|
11016
11078
|
webOnlyPromptPlace() {
|
|
@@ -11385,6 +11447,9 @@ export class FleetManager {
|
|
|
11385
11447
|
// through the target's adapter, so a Telegram-created schedule for a
|
|
11386
11448
|
// Discord target announced its failure into the wrong platform.
|
|
11387
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;
|
|
11388
11453
|
if (!adapter) {
|
|
11389
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");
|
|
11390
11455
|
return;
|
|
@@ -14193,6 +14258,8 @@ export class FleetManager {
|
|
|
14193
14258
|
* back to posting at the bare group id, which is the defect this replaced.
|
|
14194
14259
|
*/
|
|
14195
14260
|
postDailySummary(text) {
|
|
14261
|
+
if (this.recordWebNotice("summary", text))
|
|
14262
|
+
return; // #2910 D2: a web-only fleet's Notices
|
|
14196
14263
|
const target = this.fleetNoticeTarget();
|
|
14197
14264
|
if (!this.adapter || !target) {
|
|
14198
14265
|
this.logger.warn("Daily summary has no postable target — set a General topic or channel.options.general_channel_id");
|
|
@@ -14245,6 +14312,10 @@ export class FleetManager {
|
|
|
14245
14312
|
dispatched = true;
|
|
14246
14313
|
}
|
|
14247
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;
|
|
14248
14319
|
if (!dispatched) {
|
|
14249
14320
|
// Do NOT record the throttle key. It used to be claimed before delivery
|
|
14250
14321
|
// was attempted, so a message nobody could receive — every fleet error
|
|
@@ -14267,6 +14338,31 @@ export class FleetManager {
|
|
|
14267
14338
|
}
|
|
14268
14339
|
return true;
|
|
14269
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
|
+
}
|
|
14270
14366
|
static FLEET_ERROR_THROTTLE_MS = 10 * 60_000;
|
|
14271
14367
|
fleetErrorNotices = new Map();
|
|
14272
14368
|
/**
|
|
@@ -14280,6 +14376,9 @@ export class FleetManager {
|
|
|
14280
14376
|
* would have to make this async and change every caller.
|
|
14281
14377
|
*/
|
|
14282
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);
|
|
14283
14382
|
const send = this.sendToInstanceTopic(instanceName, text, extraOpts);
|
|
14284
14383
|
if (!send)
|
|
14285
14384
|
return false;
|
|
@@ -15556,6 +15655,7 @@ export class FleetManager {
|
|
|
15556
15655
|
isAdmin: (userId, adapterId) => this.adminGate(userId, adapterId) === "ok",
|
|
15557
15656
|
authorization: input.session ? this.sessionAuthorizationStore()?.get(input.session.handle) ?? null : null,
|
|
15558
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(),
|
|
15559
15659
|
});
|
|
15560
15660
|
}
|
|
15561
15661
|
sessionAuthorizations = null;
|
|
@@ -15588,6 +15688,33 @@ export class FleetManager {
|
|
|
15588
15688
|
store.prune(a => a.epoch === state.epoch && now <= Math.min(a.until, a.authorizedAt + policy.maxSessionMs) && live.has(a.handle));
|
|
15589
15689
|
return { ok: true, ...store.authorize(session.handle, session.owner, now, state.epoch, now + policy.maxSessionMs, peer) };
|
|
15590
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
|
+
}
|
|
15591
15718
|
authorityStateStore = null;
|
|
15592
15719
|
/**
|
|
15593
15720
|
* #1939 §10: the switch, "Only this computer can authorize" and the revocation epoch in force for the configuration as
|
|
@@ -15643,7 +15770,10 @@ export class FleetManager {
|
|
|
15643
15770
|
const sessions = new Map((this.webSessions?.list(undefined, () => true) ?? []).map(r => [r.handle, r]));
|
|
15644
15771
|
const authorized = state.enabled ? (this.sessionAuthorizationStore()?.list() ?? [])
|
|
15645
15772
|
.filter(a => a.epoch === state.epoch && sessions.has(a.handle) && now <= Math.min(a.until, a.authorizedAt + maxMs))
|
|
15646
|
-
.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,
|
|
15647
15777
|
until: Math.min(a.until, a.authorizedAt + maxMs, sessions.get(a.handle).absoluteExpiry),
|
|
15648
15778
|
// Over a link: the temporary one (§0.3), else the named one (a record from before §0.3 has no peer).
|
|
15649
15779
|
...(sessions.get(a.handle).surface === "gateway" ? (a.peer === "quick_link" ? { quick: true } : { named: true }) : {}) })) : [];
|
|
@@ -15753,6 +15883,8 @@ export class FleetManager {
|
|
|
15753
15883
|
isAdmin: (userId, adapterId) => this.adminGate(userId, adapterId) === "ok",
|
|
15754
15884
|
authorization: s?.owner ? { handle: s.handle, owner: s.owner, authorizedAt: now, until: now + policy.maxSessionMs, epoch: state.epoch } : null,
|
|
15755
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(),
|
|
15756
15888
|
});
|
|
15757
15889
|
return r.refusal;
|
|
15758
15890
|
}
|
|
@@ -16060,8 +16192,10 @@ export class FleetManager {
|
|
|
16060
16192
|
? [{ action: "resume", label: t("inst.session_oom_resume") }, { action: "fresh", label: t("inst.session_oom_fresh") }]
|
|
16061
16193
|
: [{ action: "restart", label: t("exit.restart") }, { action: "ignore", label: t("exit.ignore") }];
|
|
16062
16194
|
const extra = sessionOomRetry ? { sessionOomRetry, ...(current ? { confirmationCurrent: current } : {}) } : undefined;
|
|
16063
|
-
this.notifyInstanceTopic(instanceName, (oom ? message : t("exit.instance_notice", instanceName)) + placeSuffix({ kind: "general" }, "General"));
|
|
16064
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"));
|
|
16065
16199
|
if (web) {
|
|
16066
16200
|
await this.postNonceButtonPrompt({
|
|
16067
16201
|
prefix, alertType: "exit_restart", instanceName, ...web,
|
|
@@ -16140,11 +16274,19 @@ export class FleetManager {
|
|
|
16140
16274
|
* contain usernames or secret-adjacent text and stays in daemon.log.
|
|
16141
16275
|
*/
|
|
16142
16276
|
async notifyInteractivePrompt(instanceName, kind) {
|
|
16143
|
-
|
|
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"));
|
|
16144
16280
|
// Multi-world fleets can have one General per bot/platform. Route the assist
|
|
16145
16281
|
// request to the General bound to the same adapter as the blocked instance.
|
|
16146
16282
|
const generalName = this.findGeneralInstance(this.getInstanceAdapterId(instanceName));
|
|
16147
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
|
+
}
|
|
16148
16290
|
this.logger.warn({ instanceName }, "Interactive prompt has no General topic notification target");
|
|
16149
16291
|
return;
|
|
16150
16292
|
}
|
|
@@ -16303,6 +16445,10 @@ export class FleetManager {
|
|
|
16303
16445
|
* was gone; this is the fallback).
|
|
16304
16446
|
*/
|
|
16305
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;
|
|
16306
16452
|
this.retireRemoteCard(instanceName, null, t("card.answered_in_pane"));
|
|
16307
16453
|
const place = this.remoteCardPlace(instanceName);
|
|
16308
16454
|
if (!place) {
|
|
@@ -21596,7 +21742,7 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
21596
21742
|
this.reconcilePreviewListener();
|
|
21597
21743
|
this.publicWebLink?.refresh();
|
|
21598
21744
|
const newInstances = this.fleetConfig.instances;
|
|
21599
|
-
const topicMode = this.fleetConfig
|
|
21745
|
+
const topicMode = daemonTopicMode(this.fleetConfig);
|
|
21600
21746
|
// Only what a fresh process can adopt, and only relative to the signature
|
|
21601
21747
|
// this process came up on: a Settings edit mutates this.fleetConfig in place
|
|
21602
21748
|
// before the reload, so comparing the pre-load copy sees nothing at all.
|
|
@@ -23175,7 +23321,7 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
23175
23321
|
}
|
|
23176
23322
|
const fleet = this.loadConfig(this.configPath);
|
|
23177
23323
|
this.fleetConfig = fleet;
|
|
23178
|
-
const topicMode = fleet
|
|
23324
|
+
const topicMode = daemonTopicMode(fleet);
|
|
23179
23325
|
const restartProgress = new RestartProgress(this.runnableStartupCount(fleet, topicMode), restartStartedAt, this.logger);
|
|
23180
23326
|
// Phase 1: generals first
|
|
23181
23327
|
const restartEntries = Object.entries(fleet.instances);
|
|
@@ -23327,7 +23473,8 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
23327
23473
|
// Only notify when target is genuinely newer (semver), so a beta user on
|
|
23328
23474
|
// 2.0.8-beta.16 is never told that stable 2.0.7 is "available".
|
|
23329
23475
|
if (target && this.semverGt(target, currentVersion)) {
|
|
23330
|
-
|
|
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();
|
|
23331
23478
|
if (generalId) {
|
|
23332
23479
|
// No release URL — Discord's SuppressEmbeds proved unreliable and the
|
|
23333
23480
|
// link preview looked bad. Version + /update instruction is enough.
|
|
@@ -23338,6 +23485,12 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
23338
23485
|
if (key)
|
|
23339
23486
|
this.notifyInstanceTopic(generalId, t(key, `v${target}`, `v${currentVersion}`));
|
|
23340
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
|
+
}
|
|
23341
23494
|
}
|
|
23342
23495
|
}
|
|
23343
23496
|
catch { /* silent — network issues */ }
|
|
@@ -23651,7 +23804,7 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
23651
23804
|
if (decision.reason === "no-credential" && req.method === "GET"
|
|
23652
23805
|
&& String(req.headers.accept ?? "").includes("text/html")
|
|
23653
23806
|
&& isWebPageNavigation(requestPath)) {
|
|
23654
|
-
serveSigninPage(res, 401);
|
|
23807
|
+
serveSigninPage(res, 401, { webOnly: this.isWebOnlyFleet() });
|
|
23655
23808
|
return;
|
|
23656
23809
|
}
|
|
23657
23810
|
res.writeHead(decision.status);
|
|
@@ -23874,7 +24027,7 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
23874
24027
|
(async () => {
|
|
23875
24028
|
let held = false;
|
|
23876
24029
|
try {
|
|
23877
|
-
const topicMode = this.fleetConfig
|
|
24030
|
+
const topicMode = daemonTopicMode(this.fleetConfig);
|
|
23878
24031
|
await withSpawnHoldReply(async (message) => {
|
|
23879
24032
|
held = true;
|
|
23880
24033
|
res.writeHead(202, { "Content-Type": "application/json" });
|
|
@@ -24109,7 +24262,9 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
24109
24262
|
}
|
|
24110
24263
|
const listener = createPreviewListener({ settings, healthPort: boundPort, config: this.fleetConfig,
|
|
24111
24264
|
// #1723: with a web.preview_origin, the open public link's dashboard frames this listener's preview too.
|
|
24112
|
-
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 });
|
|
24113
24268
|
this.previewListener = listener;
|
|
24114
24269
|
listener.server.on("error", (err) => {
|
|
24115
24270
|
if (this.previewListener !== listener)
|
|
@@ -24329,6 +24484,8 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
24329
24484
|
// #1685: host memory pressure while it slows or holds new agents (the dashboard's indicator; macOS elevated is
|
|
24330
24485
|
// shown here rather than in chat). null when normal, unknown or advisory.
|
|
24331
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 } : {}),
|
|
24332
24489
|
// #2015: the largest processes while it lasts (name, pid, RSS, and whose: an instance, the fleet or tmux).
|
|
24333
24490
|
...(this.hostMemoryIndicator() && this.memoryTop ? { host_memory_top: this.memoryTop.processes } : {}),
|
|
24334
24491
|
};
|