@songsid/agend 2.3.3-beta.8 → 2.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/backend/claude-code.js +17 -0
- package/dist/backend/claude-code.js.map +1 -1
- package/dist/backend/codex.d.ts +15 -0
- package/dist/backend/codex.js +67 -18
- package/dist/backend/codex.js.map +1 -1
- package/dist/backend/types.d.ts +7 -0
- package/dist/backend/types.js.map +1 -1
- package/dist/channel/adapters/discord.js +60 -17
- package/dist/channel/adapters/discord.js.map +1 -1
- package/dist/daemon.d.ts +17 -0
- package/dist/daemon.js +85 -9
- package/dist/daemon.js.map +1 -1
- package/dist/fleet-manager.d.ts +31 -0
- package/dist/fleet-manager.js +106 -12
- package/dist/fleet-manager.js.map +1 -1
- package/dist/instance-lifecycle.d.ts +13 -0
- package/dist/instance-lifecycle.js +30 -6
- package/dist/instance-lifecycle.js.map +1 -1
- package/dist/locale.js +10 -2
- package/dist/locale.js.map +1 -1
- package/dist/parked-notices.d.ts +87 -0
- package/dist/parked-notices.js +194 -0
- package/dist/parked-notices.js.map +1 -0
- package/dist/public-link-progress.d.ts +5 -2
- package/dist/public-link-progress.js +4 -3
- package/dist/public-link-progress.js.map +1 -1
- package/dist/public-web-link.d.ts +10 -0
- package/dist/public-web-link.js +24 -5
- package/dist/public-web-link.js.map +1 -1
- package/dist/settings-api.d.ts +10 -0
- package/dist/settings-api.js +41 -0
- package/dist/settings-api.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/ui/panel-settings.js +5 -3
- package/dist/ui/settings-named-test.js +144 -0
- package/dist/ui/settings-strings.js +6 -6
- package/dist/ui/shared/app.css +7 -0
- package/dist/ui/shared/web-authorize.js +2 -2
- package/package.json +1 -1
package/dist/fleet-manager.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
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";
|
|
6
7
|
import type { LoginCodeOwner } from "./web-login.js";
|
|
@@ -1642,6 +1643,21 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
|
|
|
1642
1643
|
private publicWebLink;
|
|
1643
1644
|
/** #1723: the public link's preview tunnel — a manager and lease of its own, never the fleet's one tunnel lane. */
|
|
1644
1645
|
private getPreviewTunnel;
|
|
1646
|
+
private namedLinkTestRunning;
|
|
1647
|
+
/**
|
|
1648
|
+
* #2906 (the user, 2026-11-01): Settings → Access → Test connection, on the fixed-address values as filled in (the
|
|
1649
|
+
* typed connector token, else the saved one). One at a time; the result is closed step codes only — the token is
|
|
1650
|
+
* never logged or returned. Admin only, never over the public link (settings-api.ts).
|
|
1651
|
+
*/
|
|
1652
|
+
testNamedLink(input: {
|
|
1653
|
+
hostname: unknown;
|
|
1654
|
+
port: unknown;
|
|
1655
|
+
protocol: unknown;
|
|
1656
|
+
token?: unknown;
|
|
1657
|
+
current?: () => boolean;
|
|
1658
|
+
}): Promise<NamedTestResult | {
|
|
1659
|
+
busy: true;
|
|
1660
|
+
}>;
|
|
1645
1661
|
private getTunnelLane;
|
|
1646
1662
|
private publicOwnerCurrent;
|
|
1647
1663
|
/** Exact owning General. No primary-adapter or name fallback participates in authorization. */
|
|
@@ -2849,6 +2865,21 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
|
|
|
2849
2865
|
* would have to make this async and change every caller.
|
|
2850
2866
|
*/
|
|
2851
2867
|
notifyInstanceTopic(instanceName: string, text: string, extraOpts?: import("./channel/types.js").SendOpts): boolean;
|
|
2868
|
+
/**
|
|
2869
|
+
* notifyInstanceTopic's routing, handing back the send itself — and which world sent it — for a caller that must
|
|
2870
|
+
* edit or delete the message later (#2897). Null when there is no adapter or no postable target (already logged).
|
|
2871
|
+
*/
|
|
2872
|
+
private sendToInstanceTopic;
|
|
2873
|
+
private parkedNoticesStore;
|
|
2874
|
+
private get parkedNotices();
|
|
2875
|
+
/**
|
|
2876
|
+
* A held CLI dialog's notices: the instance-topic one and the General one, each edited in place while the
|
|
2877
|
+
* dialog stays (one live message per place, never a pile), and taken back by resolveParkedNotice.
|
|
2878
|
+
*/
|
|
2879
|
+
showParkedNotice(instanceName: string, topicText: string | null, generalText: string | null): void;
|
|
2880
|
+
/** The held dialog is resolved, or the instance stopped or restarted: delete its notices (no-op when none). */
|
|
2881
|
+
resolveParkedNotice(instanceName: string): void;
|
|
2882
|
+
private postParkedNotice;
|
|
2852
2883
|
/**
|
|
2853
2884
|
* Post decision buttons whose callback ids are `<prefix><nonce>:<action>`.
|
|
2854
2885
|
* The entry is registered before the send and rolled back if the send
|
package/dist/fleet-manager.js
CHANGED
|
@@ -27,6 +27,7 @@ import { clearPublicLinkOwner, readPublicLinkOwner, writePublicLinkOwner } from
|
|
|
27
27
|
import { PUBLIC_PREVIEW_FRAME_SRC, PUBLIC_PREVIEW_TUNNEL_DIR, PublicPreview, publicPreviewWanted } from "./public-preview.js";
|
|
28
28
|
import { CloudflaredProvider } from "./tunnel/cloudflared.js";
|
|
29
29
|
import { ensureCloudflared } from "./tunnel/cloudflared-install.js";
|
|
30
|
+
import { namedTestLane, runNamedLinkTest } from "./tunnel/named-link-test.js";
|
|
30
31
|
import { OFFICE_SHARE_TUNNEL_DIR, OfficeShareService } from "./office-share-service.js";
|
|
31
32
|
import { ALL_OFFICES, WHOLE_FLEET } from "./office-share.js";
|
|
32
33
|
import { TunnelPurposeLane } from "./tunnel/purpose-lane.js";
|
|
@@ -80,6 +81,7 @@ import { TmuxManager } from "./tmux-manager.js";
|
|
|
80
81
|
import { AccessManager } from "./channel/access-manager.js";
|
|
81
82
|
import { IpcClient } from "./channel/ipc-bridge.js";
|
|
82
83
|
import { usesChannelAddress } from "./channel/mcp-tools.js";
|
|
84
|
+
import { ParkedNotices, PARKED_NOTICE_LEDGER_FILE } from "./parked-notices.js";
|
|
83
85
|
import { formatFleetTime } from "./tz-utils.js";
|
|
84
86
|
import { createAdapter } from "./channel/factory.js";
|
|
85
87
|
import { isWebChannelEcho, WEB_ECHO_PREFIX } from "./web-channel-echo.js";
|
|
@@ -3079,6 +3081,7 @@ export class FleetManager {
|
|
|
3079
3081
|
if (this.reloadPending)
|
|
3080
3082
|
this.scheduleReconcile();
|
|
3081
3083
|
void this.sweepOrphanedCancelButtons();
|
|
3084
|
+
void this.parkedNotices.sweep();
|
|
3082
3085
|
}
|
|
3083
3086
|
/**
|
|
3084
3087
|
* We are the post-update fleet: the update is over by definition. Clearing it here (rather than in the update
|
|
@@ -6583,6 +6586,32 @@ export class FleetManager {
|
|
|
6583
6586
|
return this.previewTunnel ??= new ManagedTunnel({ dataDir: join(this.dataDir, PUBLIC_PREVIEW_TUNNEL_DIR),
|
|
6584
6587
|
log: () => this.logger.warn("Public preview tunnel cleanup requires attention") });
|
|
6585
6588
|
}
|
|
6589
|
+
namedLinkTestRunning = false;
|
|
6590
|
+
/**
|
|
6591
|
+
* #2906 (the user, 2026-11-01): Settings → Access → Test connection, on the fixed-address values as filled in (the
|
|
6592
|
+
* typed connector token, else the saved one). One at a time; the result is closed step codes only — the token is
|
|
6593
|
+
* never logged or returned. Admin only, never over the public link (settings-api.ts).
|
|
6594
|
+
*/
|
|
6595
|
+
async testNamedLink(input) {
|
|
6596
|
+
if (this.namedLinkTestRunning)
|
|
6597
|
+
return { busy: true };
|
|
6598
|
+
this.namedLinkTestRunning = true;
|
|
6599
|
+
try {
|
|
6600
|
+
const typed = typeof input.token === "string" && input.token.trim() ? input.token : null;
|
|
6601
|
+
const result = await runNamedLinkTest({ hostname: input.hostname, port: input.port, protocol: input.protocol, token: typed ?? process.env[PUBLIC_TUNNEL_TOKEN_ENV] ?? null }, {
|
|
6602
|
+
healthPort: this.fleetConfig?.health_port ?? 19280,
|
|
6603
|
+
ensureBinary: async (signal) => (await ensureCloudflared({ dataDir: this.dataDir, pinnedOnly: true, signal })).path,
|
|
6604
|
+
liveMarker: values => this.publicWebLink?.liveNamedMarker(values) ?? null,
|
|
6605
|
+
reserve: namedTestLane(this.getTunnelLane()),
|
|
6606
|
+
current: input.current,
|
|
6607
|
+
});
|
|
6608
|
+
this.logger[result.cleanup ? "warn" : "info"]({ steps: result.steps.map(s => `${s.step}:${s.code}`), typedToken: !!typed, ...(result.cleanup ? { cleanup: result.cleanup } : {}) }, result.cleanup ? "Named link test: the test connector's stop could not be proven; the tunnel lane stays held" : "Named link test");
|
|
6609
|
+
return result;
|
|
6610
|
+
}
|
|
6611
|
+
finally {
|
|
6612
|
+
this.namedLinkTestRunning = false;
|
|
6613
|
+
}
|
|
6614
|
+
}
|
|
6586
6615
|
getTunnelLane() {
|
|
6587
6616
|
return this.tunnelPurposeLane ??= new TunnelPurposeLane(new ManagedTunnel({ dataDir: this.dataDir,
|
|
6588
6617
|
log: () => this.logger.warn("Managed tunnel cleanup requires attention") }));
|
|
@@ -6958,7 +6987,8 @@ export class FleetManager {
|
|
|
6958
6987
|
: "\n" + t("dashboard.public_status", status.state, status.remainingSeconds ?? 0)
|
|
6959
6988
|
+ (status.expiresAt ? t("dashboard.public_until", formatFleetTime(status.expiresAt)) : ""));
|
|
6960
6989
|
const choices = [{ action: "local", label: t("dashboard.local_signin") },
|
|
6961
|
-
...(publicLinkSettings(this.fleetConfig?.web).allowed
|
|
6990
|
+
...(publicLinkSettings(this.fleetConfig?.web).allowed
|
|
6991
|
+
? [{ action: "public", label: t(publicLinkNamedHostname(this.fleetConfig?.web) ? "dashboard.public_open_named" : "dashboard.public_open") }] : []),
|
|
6962
6992
|
...(status.state !== "closed" ? [{ action: "close", label: t("dashboard.public_close") }] : [])];
|
|
6963
6993
|
const posted = await this.postNonceButtonPrompt({
|
|
6964
6994
|
prefix: "dashboard:", alertType: "login", instanceName: "dashboard", adapter, adapterId: owner.adapterId,
|
|
@@ -14278,11 +14308,22 @@ export class FleetManager {
|
|
|
14278
14308
|
* would have to make this async and change every caller.
|
|
14279
14309
|
*/
|
|
14280
14310
|
notifyInstanceTopic(instanceName, text, extraOpts) {
|
|
14311
|
+
const send = this.sendToInstanceTopic(instanceName, text, extraOpts);
|
|
14312
|
+
if (!send)
|
|
14313
|
+
return false;
|
|
14314
|
+
send.sent.catch(e => this.logger.warn({ err: e, instanceName }, send.failure));
|
|
14315
|
+
return true;
|
|
14316
|
+
}
|
|
14317
|
+
/**
|
|
14318
|
+
* notifyInstanceTopic's routing, handing back the send itself — and which world sent it — for a caller that must
|
|
14319
|
+
* edit or delete the message later (#2897). Null when there is no adapter or no postable target (already logged).
|
|
14320
|
+
*/
|
|
14321
|
+
sendToInstanceTopic(instanceName, text, extraOpts) {
|
|
14281
14322
|
const adapter = this.getAdapterForInstance(instanceName) ?? this.adapter;
|
|
14282
14323
|
if (!adapter) {
|
|
14283
14324
|
// Early startup: adapters are created well after the fleet object exists.
|
|
14284
14325
|
this.logger.warn({ instanceName }, "No adapter yet — instance topic notification not sent");
|
|
14285
|
-
return
|
|
14326
|
+
return null;
|
|
14286
14327
|
}
|
|
14287
14328
|
const channelCfg = this.getChannelConfig(this.getInstanceAdapterId(instanceName));
|
|
14288
14329
|
const groupId = channelCfg?.group_id;
|
|
@@ -14291,17 +14332,13 @@ export class FleetManager {
|
|
|
14291
14332
|
const worldId = this.getInstanceAdapterId(instanceName);
|
|
14292
14333
|
if (threadId != null && groupId) {
|
|
14293
14334
|
const out = this.renderPlaces(worldId, text, { threadId: String(threadId), ...extraOpts });
|
|
14294
|
-
adapter.sendText(String(groupId), out.text, out.opts)
|
|
14295
|
-
.catch(e => this.logger.warn({ err: e, instanceName }, "Failed to send instance topic notification"));
|
|
14296
|
-
return true;
|
|
14335
|
+
return { adapterId: worldId, sent: adapter.sendText(String(groupId), out.text, out.opts), failure: "Failed to send instance topic notification" };
|
|
14297
14336
|
}
|
|
14298
14337
|
// Classic instance: find its channelId from the classic manager
|
|
14299
14338
|
const classicChatId = this.classicChannels?.getChannelIdByInstance(instanceName);
|
|
14300
14339
|
if (classicChatId) {
|
|
14301
14340
|
// #1990: a ClassicBot room's readers need not be in the fleet's group, so its notices name places as plain text.
|
|
14302
|
-
adapter.sendText(classicChatId, stripPlaceRefs(text), extraOpts)
|
|
14303
|
-
.catch(e => this.logger.warn({ err: e, instanceName }, "Failed to send classic notification"));
|
|
14304
|
-
return true;
|
|
14341
|
+
return { adapterId: worldId, sent: adapter.sendText(classicChatId, stripPlaceRefs(text), extraOpts), failure: "Failed to send classic notification" };
|
|
14305
14342
|
}
|
|
14306
14343
|
// Fallback: the instance has neither a topic nor a classic channel, so post
|
|
14307
14344
|
// where fleet-wide notices go. Not the bare group id: on Discord that is a
|
|
@@ -14309,12 +14346,67 @@ export class FleetManager {
|
|
|
14309
14346
|
const target = this.fleetNoticeTarget(worldId);
|
|
14310
14347
|
if (target) {
|
|
14311
14348
|
const out = this.renderPlaces(worldId, text, { ...target.opts, ...extraOpts });
|
|
14312
|
-
adapter.sendText(target.chatId, out.text, out.opts)
|
|
14313
|
-
.catch(e => this.logger.warn({ err: e, instanceName }, "Failed to send notification (no topic)"));
|
|
14314
|
-
return true;
|
|
14349
|
+
return { adapterId: worldId, sent: adapter.sendText(target.chatId, out.text, out.opts), failure: "Failed to send notification (no topic)" };
|
|
14315
14350
|
}
|
|
14316
14351
|
this.logger.warn({ instanceName }, "No postable target — instance topic notification not sent");
|
|
14317
|
-
return
|
|
14352
|
+
return null;
|
|
14353
|
+
}
|
|
14354
|
+
// ── Parked-dialog notices (#2897) ──
|
|
14355
|
+
parkedNoticesStore = null;
|
|
14356
|
+
get parkedNotices() {
|
|
14357
|
+
this.parkedNoticesStore ??= new ParkedNotices({
|
|
14358
|
+
file: join(this.dataDir, PARKED_NOTICE_LEDGER_FILE),
|
|
14359
|
+
logger: this.logger,
|
|
14360
|
+
post: (instanceName, place, text) => this.postParkedNotice(instanceName, place, text),
|
|
14361
|
+
adapterFor: adapterId => this.worlds.get(adapterId)?.adapter
|
|
14362
|
+
?? (adapterId === this.getPrimaryAdapterId() ? this.adapter ?? undefined : undefined),
|
|
14363
|
+
resolvedText: () => t("fleet.dialog_parked_resolved"),
|
|
14364
|
+
// A ClassicBot room names places as plain text (#1990), as its post did.
|
|
14365
|
+
render: (row, text) => row.place === "topic" && this.classicChannels?.getChannelIdByInstance(row.instanceName) === row.chatId
|
|
14366
|
+
? stripPlaceRefs(text) : this.renderPlaces(row.adapterId, text).text,
|
|
14367
|
+
});
|
|
14368
|
+
return this.parkedNoticesStore;
|
|
14369
|
+
}
|
|
14370
|
+
/**
|
|
14371
|
+
* A held CLI dialog's notices: the instance-topic one and the General one, each edited in place while the
|
|
14372
|
+
* dialog stays (one live message per place, never a pile), and taken back by resolveParkedNotice.
|
|
14373
|
+
*/
|
|
14374
|
+
showParkedNotice(instanceName, topicText, generalText) {
|
|
14375
|
+
if (topicText !== null)
|
|
14376
|
+
void this.parkedNotices.show(instanceName, "topic", topicText);
|
|
14377
|
+
if (generalText !== null)
|
|
14378
|
+
void this.parkedNotices.show(instanceName, "general", generalText);
|
|
14379
|
+
}
|
|
14380
|
+
/** The held dialog is resolved, or the instance stopped or restarted: delete its notices (no-op when none). */
|
|
14381
|
+
resolveParkedNotice(instanceName) {
|
|
14382
|
+
void this.parkedNotices.resolve(instanceName);
|
|
14383
|
+
}
|
|
14384
|
+
async postParkedNotice(instanceName, place, text) {
|
|
14385
|
+
let send;
|
|
14386
|
+
if (place === "topic") {
|
|
14387
|
+
send = this.sendToInstanceTopic(instanceName, text);
|
|
14388
|
+
}
|
|
14389
|
+
else {
|
|
14390
|
+
// Where notifyFleetError posts, without its text throttle: the ledger is what keeps this to one message.
|
|
14391
|
+
const general = Object.entries(this.fleetConfig?.instances ?? {})
|
|
14392
|
+
.find(([, config]) => config.general_topic === true)?.[0];
|
|
14393
|
+
const target = general ? null : this.fleetNoticeTarget();
|
|
14394
|
+
if (general)
|
|
14395
|
+
send = this.sendToInstanceTopic(general, text);
|
|
14396
|
+
else if (this.adapter && target) {
|
|
14397
|
+
const out = this.renderPlaces(this.getPrimaryAdapterId(), text, target.opts);
|
|
14398
|
+
send = { adapterId: this.getPrimaryAdapterId(), sent: this.adapter.sendText(target.chatId, out.text, out.opts) };
|
|
14399
|
+
}
|
|
14400
|
+
else
|
|
14401
|
+
send = null;
|
|
14402
|
+
}
|
|
14403
|
+
if (!send)
|
|
14404
|
+
return null;
|
|
14405
|
+
const sent = await send.sent;
|
|
14406
|
+
const adapterId = send.adapterId ?? this.getPrimaryAdapterId();
|
|
14407
|
+
if (!adapterId)
|
|
14408
|
+
return null;
|
|
14409
|
+
return { adapterId, chatId: sent.chatId, ...(sent.threadId !== undefined ? { threadId: sent.threadId } : {}), messageId: sent.messageId };
|
|
14318
14410
|
}
|
|
14319
14411
|
// ── Nonce-armed button prompts (hang / assist / exit / clear) ──
|
|
14320
14412
|
//
|
|
@@ -21215,6 +21307,8 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
21215
21307
|
for (const timer of this.cancelButtonIdleRetireTimers.values())
|
|
21216
21308
|
clearTimeout(timer);
|
|
21217
21309
|
this.cancelButtonIdleRetireTimers.clear();
|
|
21310
|
+
// #2897: notices still being retired stay in the ledger; the next fleet's sweep finishes them.
|
|
21311
|
+
this.parkedNoticesStore?.dispose();
|
|
21218
21312
|
if (this.topicCleanupTimer) {
|
|
21219
21313
|
clearInterval(this.topicCleanupTimer);
|
|
21220
21314
|
this.topicCleanupTimer = null;
|