@songsid/agend 2.3.0-beta.21 → 2.3.0-beta.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-endpoint.d.ts +8 -0
- package/dist/agent-endpoint.js +11 -0
- package/dist/agent-endpoint.js.map +1 -1
- package/dist/auth-api.js +2 -1
- package/dist/auth-api.js.map +1 -1
- package/dist/backend/grok.js +56 -3
- package/dist/backend/grok.js.map +1 -1
- package/dist/channel/mcp-tools.js +2 -2
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/config-validator.js +8 -1
- package/dist/config-validator.js.map +1 -1
- package/dist/config.d.ts +2 -2
- package/dist/config.js +14 -2
- package/dist/config.js.map +1 -1
- package/dist/daemon.d.ts +6 -0
- package/dist/daemon.js +14 -0
- package/dist/daemon.js.map +1 -1
- package/dist/fleet-manager.d.ts +28 -2
- package/dist/fleet-manager.js +102 -15
- package/dist/fleet-manager.js.map +1 -1
- package/dist/general-knowledge/skills/fleet-restart/SKILL.md +1 -1
- package/dist/general-knowledge/skills/web-dashboard/SKILL.md +3 -1
- package/dist/instance-config-impact.js +3 -0
- package/dist/instance-config-impact.js.map +1 -1
- package/dist/instance-lifecycle.js +2 -2
- package/dist/instance-lifecycle.js.map +1 -1
- package/dist/instructions.js +2 -1
- package/dist/instructions.js.map +1 -1
- package/dist/locale.js +19 -6
- package/dist/locale.js.map +1 -1
- package/dist/outbound-handlers.js +25 -10
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/provider-secret-registry.d.ts +5 -3
- package/dist/provider-secret-registry.js +8 -2
- package/dist/provider-secret-registry.js.map +1 -1
- package/dist/public-tunnel-config.d.ts +6 -0
- package/dist/public-tunnel-config.js +24 -0
- package/dist/public-tunnel-config.js.map +1 -0
- package/dist/public-web-gateway.d.ts +2 -0
- package/dist/public-web-gateway.js +3 -2
- package/dist/public-web-gateway.js.map +1 -1
- package/dist/public-web-link.d.ts +13 -2
- package/dist/public-web-link.js +35 -9
- package/dist/public-web-link.js.map +1 -1
- package/dist/secret-store.d.ts +2 -0
- package/dist/secret-store.js +21 -0
- package/dist/secret-store.js.map +1 -1
- package/dist/settings-api.d.ts +1 -0
- package/dist/settings-api.js +13 -9
- package/dist/settings-api.js.map +1 -1
- package/dist/settings-change.js +2 -2
- package/dist/settings-change.js.map +1 -1
- package/dist/settings-effect.js +4 -1
- package/dist/settings-effect.js.map +1 -1
- package/dist/settings-http-confirmation.js +1 -1
- package/dist/settings-http-confirmation.js.map +1 -1
- package/dist/tunnel/cloudflared.d.ts +6 -15
- package/dist/tunnel/cloudflared.js +20 -9
- package/dist/tunnel/cloudflared.js.map +1 -1
- package/dist/types.d.ts +3 -0
- package/dist/ui/app.html +2 -0
- package/dist/ui/chat-render.js +39 -0
- package/dist/ui/chat-store.js +8 -0
- package/dist/ui/chat-strings.js +2 -2
- package/dist/ui/chat-thread.js +1 -1
- package/dist/ui/fleet-canvas.js +14 -6
- package/dist/ui/fleet-route-scene.js +86 -21
- package/dist/ui/fleet-traffic.js +57 -10
- package/dist/ui/icons/icon-192.png +0 -0
- package/dist/ui/icons/icon-512.png +0 -0
- package/dist/ui/panel-settings.js +57 -4
- package/dist/ui/settings-appearance.js +126 -1
- package/dist/ui/settings-learn.js +1 -0
- package/dist/ui/settings-strings.js +78 -0
- package/dist/ui/shared/app-stream.js +2 -1
- package/dist/ui/shared/app.css +47 -21
- package/dist/ui/signin.html +2 -0
- package/dist/web-api.d.ts +6 -2
- package/dist/web-api.js +114 -4
- package/dist/web-api.js.map +1 -1
- package/dist/web-app-icon-worker.d.ts +1 -0
- package/dist/web-app-icon-worker.js +15 -0
- package/dist/web-app-icon-worker.js.map +1 -0
- package/dist/web-app-icon.d.ts +107 -0
- package/dist/web-app-icon.js +256 -0
- package/dist/web-app-icon.js.map +1 -0
- package/dist/web-chat-history.d.ts +23 -0
- package/dist/web-chat-history.js +32 -2
- package/dist/web-chat-history.js.map +1 -1
- package/dist/web-guest-access.js +7 -1
- package/dist/web-guest-access.js.map +1 -1
- package/dist/web-icons.d.ts +33 -1
- package/dist/web-icons.js +83 -11
- package/dist/web-icons.js.map +1 -1
- package/dist/web-png.d.ts +29 -0
- package/dist/web-png.js +321 -0
- package/dist/web-png.js.map +1 -0
- package/dist/web-reactions.d.ts +34 -0
- package/dist/web-reactions.js +47 -0
- package/dist/web-reactions.js.map +1 -0
- package/package.json +1 -1
package/dist/fleet-manager.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type HumanOrigin } from "./human-delivery-receipts.js";
|
|
2
2
|
import { SettingsExecution } from "./settings-transaction.js";
|
|
3
|
+
import { AppIconStore } from "./web-app-icon.js";
|
|
3
4
|
import type { LoginCodeOwner } from "./web-login.js";
|
|
4
5
|
import { type ProfileTicket } from "./runtime-cpu-profile.js";
|
|
5
6
|
import type { CpuProfile } from "./cpu-profile.js";
|
|
@@ -57,6 +58,7 @@ export declare function applyTaskListCap<T extends {
|
|
|
57
58
|
import { type PublicPreviewForUi } from "./web-api.js";
|
|
58
59
|
import { type LogAnswer, type LogQuery } from "./log-service.js";
|
|
59
60
|
import { WebChatHistory } from "./web-chat-history.js";
|
|
61
|
+
import { type WebEchoCopy } from "./web-reactions.js";
|
|
60
62
|
import { SeenUsers, type MentionNames, type MentionQuery } from "./web-mentions.js";
|
|
61
63
|
import { WebPrefsStore } from "./web-prefs.js";
|
|
62
64
|
import { OwnerCssStore } from "./web-owner-css-store.js";
|
|
@@ -611,6 +613,9 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
|
|
|
611
613
|
tagline?: string;
|
|
612
614
|
bio?: string;
|
|
613
615
|
};
|
|
616
|
+
private appIconStore?;
|
|
617
|
+
/** #2308: the person's own app icon (Settings → Web appearance), under the state dir; none set: the bundled one. */
|
|
618
|
+
get appIcon(): AppIconStore;
|
|
614
619
|
private ownerCssStore?;
|
|
615
620
|
/** #1629 r4 §3: each person's own CSS; which version is in force is their `css.active` in the web preferences. */
|
|
616
621
|
get ownerCss(): OwnerCssStore;
|
|
@@ -1120,8 +1125,28 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
|
|
|
1120
1125
|
* #1920 §5.2: a web message's channel copy was sent — it is now what the channel saw. Kept so (and on disk), and sent
|
|
1121
1126
|
* to the guests' streams only: an admin's page already has it, and nothing about it changed for them.
|
|
1122
1127
|
*/
|
|
1123
|
-
webMessageInChannel(instance: string, messageId: string): void;
|
|
1124
|
-
|
|
1128
|
+
webMessageInChannel(instance: string, messageId: string, copies?: readonly WebEchoCopy[]): void;
|
|
1129
|
+
/** #2305: a web message's channel copies (topic, ClassicBot rooms), newest WEB_ECHO_COPIES_MAX, this process only. */
|
|
1130
|
+
private webEchoCopies;
|
|
1131
|
+
private rememberWebEchoCopies;
|
|
1132
|
+
/**
|
|
1133
|
+
* #2305: an instance's reaction to a web chat message (`messageId`, the id it was handed): its minimal reply, as on
|
|
1134
|
+
* the platforms. Kept on the message's row (and on disk), shown live (`reactions` on the stream), and put on the
|
|
1135
|
+
* message's channel copies when this process sent them — in the background, each within WEB_MIRROR_TIMEOUT_MS, so a
|
|
1136
|
+
* slow or rate-limited platform never holds the tool's answer (best effort: a copy's failure or timeout is logged,
|
|
1137
|
+
* never the tool's). `mirrored`: how many copies took it, when that is known.
|
|
1138
|
+
* The tool's errors: an emoji outside the web set or one of the instance's delivery marks (web-reactions.ts), or a
|
|
1139
|
+
* message the chat does not have. An emoji the message already has is a success that changes nothing.
|
|
1140
|
+
*/
|
|
1141
|
+
reactToWebMessage(instance: string, messageId: string, raw: unknown): Promise<{
|
|
1142
|
+
ok: true;
|
|
1143
|
+
mirrored: Promise<number>;
|
|
1144
|
+
} | {
|
|
1145
|
+
ok: false;
|
|
1146
|
+
error: string;
|
|
1147
|
+
}>;
|
|
1148
|
+
/** #2305: `copies` collects each room's copy (its message id), for a reaction to the web message. */
|
|
1149
|
+
sendClassicWebEcho(instance: string, text: string, sender?: string, copies?: WebEchoCopy[]): Promise<number>;
|
|
1125
1150
|
/**
|
|
1126
1151
|
* #1666: a delivered web chat message starts the same cancel/progress lifecycle in the instance's topic as a platform
|
|
1127
1152
|
* inbound (afterUserInboundDelivered): the same bubble, ticker and retire rules. Not its ✅ tracking — the web message
|
|
@@ -3753,6 +3778,7 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
|
|
|
3753
3778
|
verifyProviderSecret(input: {
|
|
3754
3779
|
specId: string;
|
|
3755
3780
|
secret: string;
|
|
3781
|
+
clear?: boolean;
|
|
3756
3782
|
sessionBinding: string;
|
|
3757
3783
|
idempotencyKey: string;
|
|
3758
3784
|
}): Promise<{
|
package/dist/fleet-manager.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { PUBLIC_TUNNEL_TOKEN_ENV, validPublicTunnelToken } from "./public-tunnel-config.js";
|
|
1
2
|
import { humanOwnerObjectId, webHumanOrigin } from "./human-delivery-receipts.js";
|
|
2
3
|
import { SettingsBaselines } from "./settings-baseline.js";
|
|
3
4
|
import { SettingsConfirmationStore } from "./settings-confirmation.js";
|
|
@@ -8,7 +9,8 @@ import { performance } from "node:perf_hooks";
|
|
|
8
9
|
import { withSpawnHoldReply } from "./spawn-hold-reply.js";
|
|
9
10
|
import { gatewayRequestContext } from "./web-request-context.js";
|
|
10
11
|
import { createPublicWebGateway, PUBLIC_GATEWAY_RECEIVE_MS } from "./public-web-gateway.js";
|
|
11
|
-
import { handleWebIconRequest } from "./web-icons.js";
|
|
12
|
+
import { handleWebIconRequest, useAppIconStore } from "./web-icons.js";
|
|
13
|
+
import { AppIconStore } from "./web-app-icon.js";
|
|
12
14
|
import { trackWebServer } from "./web-server-drain.js";
|
|
13
15
|
import { renderPublicLinkProgress, ThrottledMessageEditor } from "./public-link-progress.js";
|
|
14
16
|
import { PublicWebLink, publicLinkSettings } from "./public-web-link.js";
|
|
@@ -143,6 +145,7 @@ export function applyTaskListCap(tasks, filterAssignee, filterStatus) {
|
|
|
143
145
|
import { handleWebRequest, broadcastSseEvent, broadcastToGuests, SSE_HEARTBEAT_MS } from "./web-api.js";
|
|
144
146
|
import { LogService } from "./log-service.js";
|
|
145
147
|
import { WebChatHistory, WEB_CHAT_TEXT_MAX, isWebMessageId, newWebMessageId, webChatFrom } from "./web-chat-history.js";
|
|
148
|
+
import { WEB_ECHO_COPIES_MAX, WEB_MIRROR_TIMEOUT_MS, webReactionEmoji, webReactionProblem } from "./web-reactions.js";
|
|
146
149
|
import { WebChatDiskStore } from "./web-chat-store.js";
|
|
147
150
|
import { SeenUsers, resolveMentions } from "./web-mentions.js";
|
|
148
151
|
import { OPERATOR, WebPrefsStore } from "./web-prefs.js";
|
|
@@ -250,7 +253,7 @@ import { checkSelfRestartAllowance, recordSelfRestartAttempt } from "./self-rest
|
|
|
250
253
|
import { SecretStore } from "./secret-store.js";
|
|
251
254
|
import { opaqueId, safeSecretError, SECRET_CHALLENGE_TTL_MS, } from "./connection-secrets.js";
|
|
252
255
|
import { verifyDiscordToken, verifyTelegramToken } from "./provider-probe.js";
|
|
253
|
-
import { PROVIDER_SECRET_SPECS, providerSecretSpec, providerRegistryEnvKeys, isReservedProviderEnvKey, verifyProviderSecret, } from "./provider-secret-registry.js";
|
|
256
|
+
import { PROVIDER_SECRET_SPECS, PUBLIC_TUNNEL_SECRET_SPEC, providerSecretSpec, providerRegistryEnvKeys, isReservedProviderEnvKey, verifyProviderSecret, } from "./provider-secret-registry.js";
|
|
254
257
|
/** A self-restart is a whole service restart; 120s is the apply budget, not this. */
|
|
255
258
|
const SELF_RESTART_DEADLINE_MS = 300_000;
|
|
256
259
|
import { APPLY_FLEET_TARGET, ApplyJobStore, viewOf, } from "./apply-job.js";
|
|
@@ -1174,6 +1177,11 @@ export class FleetManager {
|
|
|
1174
1177
|
const card = resolveCard(name, humanCards(this.dataDir).get(name), this.themeStore.get(name).theme.card);
|
|
1175
1178
|
return { ...(card.tagline ? { tagline: card.tagline.text } : {}), ...(card.bio ? { bio: card.bio.text } : {}) };
|
|
1176
1179
|
}
|
|
1180
|
+
appIconStore;
|
|
1181
|
+
/** #2308: the person's own app icon (Settings → Web appearance), under the state dir; none set: the bundled one. */
|
|
1182
|
+
get appIcon() {
|
|
1183
|
+
return (this.appIconStore ??= new AppIconStore(this.dataDir));
|
|
1184
|
+
}
|
|
1177
1185
|
ownerCssStore;
|
|
1178
1186
|
/** #1629 r4 §3: each person's own CSS; which version is in force is their `css.active` in the web preferences. */
|
|
1179
1187
|
get ownerCss() {
|
|
@@ -3792,12 +3800,61 @@ export class FleetManager {
|
|
|
3792
3800
|
* #1920 §5.2: a web message's channel copy was sent — it is now what the channel saw. Kept so (and on disk), and sent
|
|
3793
3801
|
* to the guests' streams only: an admin's page already has it, and nothing about it changed for them.
|
|
3794
3802
|
*/
|
|
3795
|
-
webMessageInChannel(instance, messageId) {
|
|
3803
|
+
webMessageInChannel(instance, messageId, copies = []) {
|
|
3804
|
+
// #2305: the copies' own message ids, so an instance's reaction to the web message is put on them too.
|
|
3805
|
+
if (copies.length)
|
|
3806
|
+
this.rememberWebEchoCopies(messageId, copies);
|
|
3796
3807
|
const m = this.webChatHistory.markChannel(instance, messageId);
|
|
3797
3808
|
if (m)
|
|
3798
3809
|
broadcastToGuests(this.sseClients, "message", m, (err) => this.logger.debug({ err }, "SSE client write failed; evicting"));
|
|
3799
3810
|
}
|
|
3800
|
-
|
|
3811
|
+
/** #2305: a web message's channel copies (topic, ClassicBot rooms), newest WEB_ECHO_COPIES_MAX, this process only. */
|
|
3812
|
+
webEchoCopies = new Map();
|
|
3813
|
+
rememberWebEchoCopies(messageId, copies) {
|
|
3814
|
+
const list = [...(this.webEchoCopies.get(messageId) ?? []), ...copies.filter(c => c && typeof c.adapter?.react === "function" && c.messageId)];
|
|
3815
|
+
this.webEchoCopies.delete(messageId);
|
|
3816
|
+
this.webEchoCopies.set(messageId, list);
|
|
3817
|
+
while (this.webEchoCopies.size > WEB_ECHO_COPIES_MAX)
|
|
3818
|
+
this.webEchoCopies.delete(this.webEchoCopies.keys().next().value);
|
|
3819
|
+
}
|
|
3820
|
+
/**
|
|
3821
|
+
* #2305: an instance's reaction to a web chat message (`messageId`, the id it was handed): its minimal reply, as on
|
|
3822
|
+
* the platforms. Kept on the message's row (and on disk), shown live (`reactions` on the stream), and put on the
|
|
3823
|
+
* message's channel copies when this process sent them — in the background, each within WEB_MIRROR_TIMEOUT_MS, so a
|
|
3824
|
+
* slow or rate-limited platform never holds the tool's answer (best effort: a copy's failure or timeout is logged,
|
|
3825
|
+
* never the tool's). `mirrored`: how many copies took it, when that is known.
|
|
3826
|
+
* The tool's errors: an emoji outside the web set or one of the instance's delivery marks (web-reactions.ts), or a
|
|
3827
|
+
* message the chat does not have. An emoji the message already has is a success that changes nothing.
|
|
3828
|
+
*/
|
|
3829
|
+
async reactToWebMessage(instance, messageId, raw) {
|
|
3830
|
+
const problem = webReactionProblem(raw, this.statusEmojiAvoidList(instance));
|
|
3831
|
+
if (problem)
|
|
3832
|
+
return { ok: false, error: problem };
|
|
3833
|
+
const emoji = webReactionEmoji(raw);
|
|
3834
|
+
const r = this.webChatHistory.addReaction(instance, messageId, emoji);
|
|
3835
|
+
if (!r)
|
|
3836
|
+
return { ok: false, error: `react: ${messageId} is not a message in ${instance}'s web chat now (it left the history, or it is from before AgEnD restarted) — answer with reply instead` };
|
|
3837
|
+
if (!r.added)
|
|
3838
|
+
return { ok: true, mirrored: Promise.resolve(0) };
|
|
3839
|
+
this.emitSseEvent("reactions", { instance, boot: r.message.boot, id: r.message.id, reactions: r.message.reactions ?? [] });
|
|
3840
|
+
const mirrorOne = (copy) => {
|
|
3841
|
+
let timer;
|
|
3842
|
+
const timeout = new Promise((_, reject) => {
|
|
3843
|
+
timer = setTimeout(() => reject(new Error(`no answer in ${WEB_MIRROR_TIMEOUT_MS} ms`)), WEB_MIRROR_TIMEOUT_MS);
|
|
3844
|
+
timer.unref?.();
|
|
3845
|
+
});
|
|
3846
|
+
return Promise.race([Promise.resolve().then(() => copy.adapter.react(copy.chatId, copy.messageId, reactionForm(copy.adapter.type, emoji), copy.threadId)), timeout])
|
|
3847
|
+
.then(() => 1, (err) => {
|
|
3848
|
+
this.logger.warn({ err, instance, adapter: copy.adapter.type }, "Reaction on a web message's channel copy failed (shown on the web)");
|
|
3849
|
+
return 0;
|
|
3850
|
+
})
|
|
3851
|
+
.finally(() => clearTimeout(timer));
|
|
3852
|
+
};
|
|
3853
|
+
const mirrored = Promise.all((this.webEchoCopies.get(messageId) ?? []).map(mirrorOne)).then(n => n.reduce((a, b) => a + b, 0));
|
|
3854
|
+
return { ok: true, mirrored };
|
|
3855
|
+
}
|
|
3856
|
+
/** #2305: `copies` collects each room's copy (its message id), for a reaction to the web message. */
|
|
3857
|
+
async sendClassicWebEcho(instance, text, sender = WEB_NAME_FALLBACK, copies) {
|
|
3801
3858
|
const guard = this.webChannelEchoGuards.get(instance);
|
|
3802
3859
|
const handled = new Set();
|
|
3803
3860
|
let posted = 0;
|
|
@@ -3827,7 +3884,9 @@ export class FleetManager {
|
|
|
3827
3884
|
try {
|
|
3828
3885
|
if (!adapter?.sendText)
|
|
3829
3886
|
continue;
|
|
3830
|
-
await adapter.sendText(entry.channelId, text, { format: "text", allowedMentions: { parse: [] } });
|
|
3887
|
+
const sent = await adapter.sendText(entry.channelId, text, { format: "text", allowedMentions: { parse: [] } });
|
|
3888
|
+
if (copies && sent && typeof sent.messageId === "string" && sent.messageId)
|
|
3889
|
+
copies.push({ adapter, chatId: entry.channelId, messageId: sent.messageId });
|
|
3831
3890
|
ClassicChannelManager.logMessage(instance, sender, text, new Date());
|
|
3832
3891
|
posted++;
|
|
3833
3892
|
}
|
|
@@ -5866,7 +5925,7 @@ export class FleetManager {
|
|
|
5866
5925
|
dataDir: this.dataDir, web: () => this.fleetConfig?.web,
|
|
5867
5926
|
permitted: owner => this.publicOwnerCurrent(owner),
|
|
5868
5927
|
reserve: id => this.getTunnelLane().reserve("dashboard", id),
|
|
5869
|
-
createGateway: (exposureId, current, open, failed) => createPublicWebGateway({ exposureId, isCurrent: current, isOpen: open, onError: failed,
|
|
5928
|
+
createGateway: (exposureId, current, open, failed, named) => createPublicWebGateway({ ...named, exposureId, isCurrent: current, isOpen: open, onError: failed,
|
|
5870
5929
|
dispatch: (req, res) => this.dispatchWebHttp(req, res, this.fleetConfig?.health_port ?? 19280) }),
|
|
5871
5930
|
revoke: id => {
|
|
5872
5931
|
this.webLoginCodes?.revokeAudience(id);
|
|
@@ -9649,6 +9708,17 @@ export class FleetManager {
|
|
|
9649
9708
|
});
|
|
9650
9709
|
return;
|
|
9651
9710
|
}
|
|
9711
|
+
// #2305: a reaction to a web chat message is the web chat's own (any fleet, web-only included): never an adapter's.
|
|
9712
|
+
if (tool === "react" && typeof args.message_id === "string" && isWebMessageId(args.message_id)) {
|
|
9713
|
+
// The socket's own instance, never a session name the caller wrote itself (as checkToolPermission: it was not
|
|
9714
|
+
// identified) — a channel tool's call from a daemon carries none anyway (#2310 review).
|
|
9715
|
+
const r = await this.reactToWebMessage(instanceName, args.message_id, args.emoji);
|
|
9716
|
+
if (r.ok)
|
|
9717
|
+
respond("ok");
|
|
9718
|
+
else
|
|
9719
|
+
respond(null, r.error);
|
|
9720
|
+
return;
|
|
9721
|
+
}
|
|
9652
9722
|
// A fleet with no chat platform at all is driven from the web dashboard alone: a reply has nowhere
|
|
9653
9723
|
// else to go, and "retry shortly" would have the agent retry forever. It goes to the web chat.
|
|
9654
9724
|
const webOnlyReply = tool === "reply" && this.worlds.size === 0 && this.isWebOnlyFleet();
|
|
@@ -19285,6 +19355,7 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
19285
19355
|
await this.retirePendingNoncePrompts();
|
|
19286
19356
|
this.topicArchiver.stop();
|
|
19287
19357
|
this.taskBoardWaker?.stop();
|
|
19358
|
+
this.appIconStore?.close();
|
|
19288
19359
|
this.scheduler?.shutdown();
|
|
19289
19360
|
// What it was sent is written first (bounded: search-index.ts close()).
|
|
19290
19361
|
const search = this.searchIndex;
|
|
@@ -20006,12 +20077,12 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
20006
20077
|
return this.fleetConfig?.web?.provider_secrets === true;
|
|
20007
20078
|
}
|
|
20008
20079
|
listProviderSecrets() {
|
|
20009
|
-
return PROVIDER_SECRET_SPECS.map(spec => ({
|
|
20080
|
+
return [...PROVIDER_SECRET_SPECS, PUBLIC_TUNNEL_SECRET_SPEC].map(spec => ({
|
|
20010
20081
|
id: spec.id,
|
|
20011
20082
|
display_name: spec.displayName,
|
|
20012
20083
|
kind: spec.kind,
|
|
20013
20084
|
token_present: !!process.env[spec.envKey],
|
|
20014
|
-
verifier: spec.verifier ? "available" : "unsupported",
|
|
20085
|
+
verifier: spec.kind === "tunnel_token" ? "local_format" : spec.verifier ? "available" : "unsupported",
|
|
20015
20086
|
activation: spec.activation,
|
|
20016
20087
|
stale_consumers: this.providerSecretStaleConsumers(spec.envKey),
|
|
20017
20088
|
}));
|
|
@@ -20021,7 +20092,7 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
20021
20092
|
// a child process's private environment safely, so report the conservative
|
|
20022
20093
|
// set of already-running children; the UI can then say "restart these"
|
|
20023
20094
|
// rather than claiming an existing process reloaded.
|
|
20024
|
-
if (envKey === "GROQ_API_KEY")
|
|
20095
|
+
if (envKey === "GROQ_API_KEY" || envKey === PUBLIC_TUNNEL_TOKEN_ENV)
|
|
20025
20096
|
return [];
|
|
20026
20097
|
return [...this.children.keys()].sort();
|
|
20027
20098
|
}
|
|
@@ -20039,9 +20110,11 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
20039
20110
|
if (!spec || !this.providerSecretEnvAllowed(spec.envKey)) {
|
|
20040
20111
|
return { ok: false, status: "invalid", error: "provider secret is not configured" };
|
|
20041
20112
|
}
|
|
20042
|
-
|
|
20113
|
+
const tunnel = spec.kind === "tunnel_token";
|
|
20114
|
+
if (!tunnel && !spec.verifier)
|
|
20043
20115
|
return { ok: false, status: "unsupported_verifier", error: "this provider has no supported verifier" };
|
|
20044
|
-
if (
|
|
20116
|
+
if (input.clear && (!tunnel || input.secret !== "") || tunnel && !(input.clear === true && input.secret === "" || validPublicTunnelToken(input.secret))
|
|
20117
|
+
|| !input.clear && (!input.secret || input.secret.length > 4096 || /[\r\n\0]/.test(input.secret) || /[^\x20-\x7e]/.test(input.secret))) {
|
|
20045
20118
|
return { ok: false, status: "invalid", error: "secret is invalid" };
|
|
20046
20119
|
}
|
|
20047
20120
|
const challengeKey = `${input.sessionBinding}:api_key:${spec.id}:${spec.envKey}:${input.idempotencyKey}`;
|
|
@@ -20052,7 +20125,7 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
20052
20125
|
}
|
|
20053
20126
|
if (existingId)
|
|
20054
20127
|
this.providerSecretChallengesByKey.delete(challengeKey);
|
|
20055
|
-
const result = await verifyProviderSecret(spec, input.secret, this.providerSecretHttpClient);
|
|
20128
|
+
const result = tunnel ? { ok: true } : await verifyProviderSecret(spec, input.secret, this.providerSecretHttpClient);
|
|
20056
20129
|
if (!result.ok) {
|
|
20057
20130
|
// Do not log provider detail: the HTTP verifier already redacted it and
|
|
20058
20131
|
// this endpoint has no need to disclose whether a key was close to valid.
|
|
@@ -20140,6 +20213,7 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
20140
20213
|
async runProviderSecretApply(job, secret, execution) {
|
|
20141
20214
|
const spec = providerSecretSpec(job.specId);
|
|
20142
20215
|
const allowed = new Set([
|
|
20216
|
+
PUBLIC_TUNNEL_TOKEN_ENV,
|
|
20143
20217
|
...PROVIDER_SECRET_SPECS.map(item => item.envKey),
|
|
20144
20218
|
...(this.fleetConfig?.channels ?? (this.fleetConfig?.channel ? [this.fleetConfig.channel] : [])).map(channel => channel.bot_token_env),
|
|
20145
20219
|
]);
|
|
@@ -20153,7 +20227,14 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
20153
20227
|
// Construct inside the transaction: symlink/permission refusal must
|
|
20154
20228
|
// settle the job as a safe failure, not escape the queued microtask.
|
|
20155
20229
|
store = new SecretStore(join(this.dataDir, ".env"), allowed, { owner: execution?.owner });
|
|
20156
|
-
const write = () => {
|
|
20230
|
+
const write = () => {
|
|
20231
|
+
before = spec.kind === "tunnel_token" && !secret ? store.clear(job.envKey) : store.write(job.envKey, secret);
|
|
20232
|
+
wrote = true;
|
|
20233
|
+
if (secret)
|
|
20234
|
+
process.env[job.envKey] = secret;
|
|
20235
|
+
else
|
|
20236
|
+
delete process.env[job.envKey];
|
|
20237
|
+
};
|
|
20157
20238
|
if (execution)
|
|
20158
20239
|
execution.mutate(write);
|
|
20159
20240
|
else
|
|
@@ -20175,12 +20256,12 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
20175
20256
|
job.finishedAt = Date.now();
|
|
20176
20257
|
}
|
|
20177
20258
|
catch (err) {
|
|
20178
|
-
const safe = safeSecretError(err, secret);
|
|
20259
|
+
const safe = spec?.kind === "tunnel_token" ? "connector token change failed" : safeSecretError(err, secret);
|
|
20179
20260
|
this.logger.warn({ specId: job.specId, reason: safe }, "Provider API-key apply failed");
|
|
20180
20261
|
try {
|
|
20181
20262
|
// Memory and disk have separate receipts: a host-edited file must
|
|
20182
20263
|
// remain, but cannot strand our owned process value or hook snapshot.
|
|
20183
|
-
if (wrote && process.env[job.envKey] === secret) {
|
|
20264
|
+
if (wrote && (process.env[job.envKey] ?? "") === secret) {
|
|
20184
20265
|
if (previousProcessValue === undefined)
|
|
20185
20266
|
delete process.env[job.envKey];
|
|
20186
20267
|
else
|
|
@@ -20220,6 +20301,10 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
20220
20301
|
* hot activation contract explicit and gives tests a failure seam; no generic
|
|
20221
20302
|
* SIGHUP or caller-provided hook is ever executed. */
|
|
20222
20303
|
async runProviderSecretReloadHook(hookId, _next, _previous) {
|
|
20304
|
+
if (hookId === "cloudflare.public_link") {
|
|
20305
|
+
await this.publicWebLink?.close("connector token changed");
|
|
20306
|
+
return;
|
|
20307
|
+
}
|
|
20223
20308
|
if (hookId !== "groq.voice")
|
|
20224
20309
|
throw new Error("unknown provider secret reload hook");
|
|
20225
20310
|
const before = this.providerSecretHotSnapshots.get(hookId);
|
|
@@ -21498,6 +21583,8 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
21498
21583
|
return;
|
|
21499
21584
|
// Branding is public, after Host admission: automatic favicon requests
|
|
21500
21585
|
// must not become noisy web-token 401s in the browser console.
|
|
21586
|
+
// #2308: the icon routes and the pages' icon links follow the app icon set in Settings.
|
|
21587
|
+
useAppIconStore(this.appIcon);
|
|
21501
21588
|
if (handleWebIconRequest(req, res, requestPath))
|
|
21502
21589
|
return;
|
|
21503
21590
|
// Public: the health probe, /agent (instance-token auth of its own), the
|