@vellumai/vellum-gateway 0.8.5 → 0.8.6
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/package.json +1 -1
- package/src/__tests__/guardian-init-lockfile.test.ts +18 -2
- package/src/__tests__/remote-feature-flag-sync.test.ts +8 -0
- package/src/__tests__/route-schema-guard.test.ts +2 -0
- package/src/auth/guardian-bootstrap.ts +8 -0
- package/src/email/register-callback.ts +8 -0
- package/src/feature-flag-registry.json +17 -17
- package/src/feature-flag-resolver.ts +36 -0
- package/src/http/middleware/cors.ts +4 -0
- package/src/http/routes/auth-token.ts +60 -0
- package/src/http/routes/channel-verification-session-proxy.test.ts +2 -2
- package/src/http/routes/channel-verification-session-proxy.ts +19 -10
- package/src/http/routes/feature-flags.ts +3 -1
- package/src/index.ts +18 -0
- package/src/risk/command-registry/commands/assistant.ts +3 -0
- package/src/schema.ts +2 -2
- package/src/telegram/webhook-manager.ts +8 -0
package/package.json
CHANGED
|
@@ -526,14 +526,30 @@ describe("guardian/reset-bootstrap", () => {
|
|
|
526
526
|
expect(body.error).toBe("Loopback-only endpoint");
|
|
527
527
|
});
|
|
528
528
|
|
|
529
|
-
test("rejects
|
|
529
|
+
test("rejects without valid bootstrap secret when secrets are configured", async () => {
|
|
530
530
|
process.env.GUARDIAN_BOOTSTRAP_SECRET = "some-secret";
|
|
531
531
|
const handler = createChannelVerificationSessionProxyHandler(makeConfig());
|
|
532
532
|
|
|
533
533
|
const res = await handler.handleResetBootstrap("127.0.0.1");
|
|
534
534
|
expect(res.status).toBe(403);
|
|
535
535
|
const body = await res.json();
|
|
536
|
-
expect(body.error).toBe("
|
|
536
|
+
expect(body.error).toBe("Invalid bootstrap secret");
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
test("allows reset with valid bootstrap secret", async () => {
|
|
540
|
+
process.env.GUARDIAN_BOOTSTRAP_SECRET = "some-secret";
|
|
541
|
+
writeFileSync(lockPath(), new Date().toISOString(), { mode: 0o600 });
|
|
542
|
+
writeFileSync(consumedPath(), JSON.stringify([0]) + "\n", { mode: 0o600 });
|
|
543
|
+
const handler = createChannelVerificationSessionProxyHandler(makeConfig());
|
|
544
|
+
|
|
545
|
+
const req = new Request("http://localhost:7830/v1/guardian/reset-bootstrap", {
|
|
546
|
+
method: "POST",
|
|
547
|
+
headers: { "x-bootstrap-secret": "some-secret" },
|
|
548
|
+
});
|
|
549
|
+
const res = await handler.handleResetBootstrap("127.0.0.1", req);
|
|
550
|
+
expect(res.status).toBe(200);
|
|
551
|
+
expect(lockFileExists()).toBe(false);
|
|
552
|
+
expect(consumedSecrets()).toEqual([]);
|
|
537
553
|
});
|
|
538
554
|
|
|
539
555
|
test("resets in-flight flag so init can proceed", async () => {
|
|
@@ -62,6 +62,14 @@ const TEST_REGISTRY = {
|
|
|
62
62
|
description: "Email channel integration",
|
|
63
63
|
defaultEnabled: false,
|
|
64
64
|
},
|
|
65
|
+
{
|
|
66
|
+
id: "platform-features-in-local-mode",
|
|
67
|
+
scope: "assistant",
|
|
68
|
+
key: "platform-features-in-local-mode",
|
|
69
|
+
label: "Platform Features in Local Mode",
|
|
70
|
+
description: "Gate platform API calls in local mode",
|
|
71
|
+
defaultEnabled: true,
|
|
72
|
+
},
|
|
65
73
|
],
|
|
66
74
|
};
|
|
67
75
|
|
|
@@ -175,6 +175,8 @@ const EXCLUDED_FROM_SCHEMA = new Set([
|
|
|
175
175
|
"/.well-known/agent-card.json",
|
|
176
176
|
// Internal-only: reachable only via vembda's trusted gateway-query proxy
|
|
177
177
|
"/v1/contacts/guardian/channel",
|
|
178
|
+
// BFF token auth — loopback-only, not part of the public gateway API
|
|
179
|
+
"/auth/token",
|
|
178
180
|
]);
|
|
179
181
|
|
|
180
182
|
// ── Schema paths that don't map to a discrete route definition ──
|
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
} from "../db/schema.js";
|
|
25
25
|
import { readCredential } from "../credential-reader.js";
|
|
26
26
|
import { credentialKey } from "../credential-key.js";
|
|
27
|
+
import { arePlatformFeaturesEnabled } from "../feature-flag-resolver.js";
|
|
27
28
|
import { getLogger } from "../logger.js";
|
|
28
29
|
|
|
29
30
|
import { CURRENT_POLICY_EPOCH } from "./policy.js";
|
|
@@ -544,6 +545,13 @@ function mintRefreshToken(
|
|
|
544
545
|
* callers fall back to a generated principal ID in that case.
|
|
545
546
|
*/
|
|
546
547
|
async function fetchPlatformOwnerDisplayName(): Promise<string | null> {
|
|
548
|
+
if (!arePlatformFeaturesEnabled()) {
|
|
549
|
+
log.debug(
|
|
550
|
+
"platform-features-in-local-mode is disabled — skipping platform owner display name fetch",
|
|
551
|
+
);
|
|
552
|
+
return null;
|
|
553
|
+
}
|
|
554
|
+
|
|
547
555
|
const isPlatform =
|
|
548
556
|
process.env.IS_PLATFORM?.trim().toLowerCase() === "true" ||
|
|
549
557
|
process.env.IS_PLATFORM?.trim() === "1";
|
|
@@ -2,6 +2,7 @@ import type { ConfigFileCache } from "../config-file-cache.js";
|
|
|
2
2
|
import type { CredentialCache } from "../credential-cache.js";
|
|
3
3
|
import { credentialKey } from "../credential-key.js";
|
|
4
4
|
import { fetchImpl } from "../fetch.js";
|
|
5
|
+
import { arePlatformFeaturesEnabled } from "../feature-flag-resolver.js";
|
|
5
6
|
import { getLogger } from "../logger.js";
|
|
6
7
|
|
|
7
8
|
const log = getLogger("email-callback");
|
|
@@ -37,6 +38,13 @@ export async function registerEmailCallbackRoute(caches?: {
|
|
|
37
38
|
credentials?: CredentialCache;
|
|
38
39
|
configFile?: ConfigFileCache;
|
|
39
40
|
}): Promise<string | undefined> {
|
|
41
|
+
if (!arePlatformFeaturesEnabled()) {
|
|
42
|
+
log.debug(
|
|
43
|
+
"platform-features-in-local-mode is disabled — skipping email callback registration",
|
|
44
|
+
);
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
|
|
40
48
|
const [platformBaseUrlRaw, assistantApiKeyRaw, assistantIdRaw] =
|
|
41
49
|
caches?.credentials
|
|
42
50
|
? await Promise.all([
|
|
@@ -25,6 +25,14 @@
|
|
|
25
25
|
"description": "Enable user-hosted onboarding flow",
|
|
26
26
|
"defaultEnabled": false
|
|
27
27
|
},
|
|
28
|
+
{
|
|
29
|
+
"id": "prechat-onboarding-condensed-flow",
|
|
30
|
+
"scope": "client",
|
|
31
|
+
"key": "prechat-onboarding-condensed-flow",
|
|
32
|
+
"label": "Condensed Pre-chat Onboarding",
|
|
33
|
+
"description": "Enable the condensed pre-chat onboarding flow for a standard LaunchDarkly percentage rollout.",
|
|
34
|
+
"defaultEnabled": false
|
|
35
|
+
},
|
|
28
36
|
{
|
|
29
37
|
"id": "local-docker-enabled",
|
|
30
38
|
"scope": "client",
|
|
@@ -190,7 +198,7 @@
|
|
|
190
198
|
"scope": "assistant",
|
|
191
199
|
"key": "fast-mode",
|
|
192
200
|
"label": "Fast Mode",
|
|
193
|
-
"description": "Enable Anthropic fast mode for Opus models (4.6, 4.7), delivering up to 2.5x higher output tokens per second at premium pricing",
|
|
201
|
+
"description": "Enable Anthropic fast mode for Opus models (4.6, 4.7, 4.8), delivering up to 2.5x higher output tokens per second at premium pricing",
|
|
194
202
|
"defaultEnabled": false
|
|
195
203
|
},
|
|
196
204
|
{
|
|
@@ -281,14 +289,6 @@
|
|
|
281
289
|
"description": "Expose the developer-only Compaction Playground tab in macOS Settings and enable the /playground/* HTTP endpoints for exercising compaction conditions. Dev-only; default off.",
|
|
282
290
|
"defaultEnabled": false
|
|
283
291
|
},
|
|
284
|
-
{
|
|
285
|
-
"id": "safe-storage-limits",
|
|
286
|
-
"scope": "assistant",
|
|
287
|
-
"key": "safe-storage-limits",
|
|
288
|
-
"label": "Safe Storage Limits",
|
|
289
|
-
"description": "Enable disk pressure protection flows that block background work and remote actors while storage is critically low.",
|
|
290
|
-
"defaultEnabled": false
|
|
291
|
-
},
|
|
292
292
|
{
|
|
293
293
|
"id": "account-deletion",
|
|
294
294
|
"scope": "assistant",
|
|
@@ -313,14 +313,6 @@
|
|
|
313
313
|
"description": "Show the 'Analyze' / 'Analyze conversation' option in conversation context menus and the conversation title actions dropdown.",
|
|
314
314
|
"defaultEnabled": false
|
|
315
315
|
},
|
|
316
|
-
{
|
|
317
|
-
"id": "pro-plan-adjust",
|
|
318
|
-
"scope": "client",
|
|
319
|
-
"key": "pro-plan-adjust",
|
|
320
|
-
"label": "Pro Plan Adjust",
|
|
321
|
-
"description": "Show the rich Plan card (current plan, features, Manage/Upgrade CTA) at the top of the macOS Settings \u2192 Billing tab.",
|
|
322
|
-
"defaultEnabled": false
|
|
323
|
-
},
|
|
324
316
|
{
|
|
325
317
|
"id": "external-plugins",
|
|
326
318
|
"scope": "assistant",
|
|
@@ -440,6 +432,14 @@
|
|
|
440
432
|
"label": "Memory Router Playground",
|
|
441
433
|
"description": "Expose the developer-only Memory Router Playground tab in macOS Settings and the /assistant/memory-router-playground web page for dry-running v4 router config overrides against the live page index. Dev-only; default off.",
|
|
442
434
|
"defaultEnabled": false
|
|
435
|
+
},
|
|
436
|
+
{
|
|
437
|
+
"id": "platform-features-in-local-mode",
|
|
438
|
+
"scope": "assistant",
|
|
439
|
+
"key": "platform-features-in-local-mode",
|
|
440
|
+
"label": "Platform Features in Local Mode",
|
|
441
|
+
"description": "When enabled, the assistant can call the Vellum platform API from local mode. When disabled, all platform API clients in the daemon, gateway, CES, and web UI no-op with a debug log instead of making outbound requests.",
|
|
442
|
+
"defaultEnabled": true
|
|
443
443
|
}
|
|
444
444
|
]
|
|
445
445
|
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { loadFeatureFlagDefaults } from "./feature-flag-defaults.js";
|
|
2
|
+
import {
|
|
3
|
+
hasRemoteFeatureFlagSnapshot,
|
|
4
|
+
readRemoteFeatureFlags,
|
|
5
|
+
} from "./feature-flag-remote-store.js";
|
|
6
|
+
import { readPersistedFeatureFlags } from "./feature-flag-store.js";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Resolve the effective enabled/disabled state for a feature flag.
|
|
10
|
+
*
|
|
11
|
+
* Priority: persisted (user-toggled) > remote (platform-pushed) > registry default.
|
|
12
|
+
* Undeclared keys with no override return `false` (fail closed).
|
|
13
|
+
*/
|
|
14
|
+
export function isFeatureFlagEnabled(key: string): boolean {
|
|
15
|
+
const persisted = readPersistedFeatureFlags();
|
|
16
|
+
const persistedValue = persisted[key];
|
|
17
|
+
if (persistedValue !== undefined) return persistedValue;
|
|
18
|
+
|
|
19
|
+
if (hasRemoteFeatureFlagSnapshot()) {
|
|
20
|
+
const remote = readRemoteFeatureFlags();
|
|
21
|
+
return remote[key] ?? false;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const defaults = loadFeatureFlagDefaults();
|
|
25
|
+
return defaults[key]?.defaultEnabled ?? false;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function isPlatformMode(): boolean {
|
|
29
|
+
const v = process.env.IS_PLATFORM?.trim().toLowerCase();
|
|
30
|
+
return v === "true" || v === "1";
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function arePlatformFeaturesEnabled(): boolean {
|
|
34
|
+
if (isPlatformMode()) return true;
|
|
35
|
+
return isFeatureFlagEnabled("platform-features-in-local-mode");
|
|
36
|
+
}
|
|
@@ -115,6 +115,10 @@ export function withExtensionCorsHeaders(
|
|
|
115
115
|
}
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
+
// ---------------------------------------------------------------------------
|
|
119
|
+
// WKWebView CORS
|
|
120
|
+
// ---------------------------------------------------------------------------
|
|
121
|
+
|
|
118
122
|
/**
|
|
119
123
|
* Check whether the request `Origin` header matches a known webview origin.
|
|
120
124
|
* Returns the validated origin string, or null if CORS headers should not be
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { Server } from "bun";
|
|
2
|
+
|
|
3
|
+
import { ensureVellumGuardianBinding } from "../../auth/guardian-bootstrap.js";
|
|
4
|
+
import { CURRENT_POLICY_EPOCH } from "../../auth/policy.js";
|
|
5
|
+
import { mintToken, verifyToken } from "../../auth/token-service.js";
|
|
6
|
+
import { getLogger } from "../../logger.js";
|
|
7
|
+
import { isLoopbackPeer } from "../../util/is-loopback-address.js";
|
|
8
|
+
|
|
9
|
+
const log = getLogger("auth-token");
|
|
10
|
+
|
|
11
|
+
const WEB_ORIGIN_RE = /^https?:\/\/(localhost|127\.0\.0\.1)(:\d+)?$/;
|
|
12
|
+
|
|
13
|
+
const TOKEN_TTL_SECONDS = 30 * 24 * 60 * 60;
|
|
14
|
+
|
|
15
|
+
export async function handleCreateToken(
|
|
16
|
+
req: Request,
|
|
17
|
+
server: Server<unknown> | undefined,
|
|
18
|
+
): Promise<Response> {
|
|
19
|
+
if (!server || !isLoopbackPeer(server, req)) {
|
|
20
|
+
log.warn("Token create rejected: not a loopback peer");
|
|
21
|
+
return Response.json({ error: "Forbidden" }, { status: 403 });
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const origin = req.headers.get("origin");
|
|
25
|
+
if (!origin || !WEB_ORIGIN_RE.test(origin)) {
|
|
26
|
+
log.warn({ origin }, "Token create rejected: missing or invalid Origin");
|
|
27
|
+
return Response.json({ error: "Forbidden" }, { status: 403 });
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const authHeader = req.headers.get("authorization");
|
|
31
|
+
if (!authHeader || !authHeader.toLowerCase().startsWith("bearer ")) {
|
|
32
|
+
log.warn("Token create rejected: missing or malformed Authorization header");
|
|
33
|
+
return Response.json({ error: "Unauthorized" }, { status: 401 });
|
|
34
|
+
}
|
|
35
|
+
const bearerToken = authHeader.slice(7);
|
|
36
|
+
const verifyResult = verifyToken(bearerToken, "vellum-gateway");
|
|
37
|
+
if (!verifyResult.ok) {
|
|
38
|
+
log.warn({ reason: verifyResult.reason }, "Token create rejected: invalid guardian token");
|
|
39
|
+
return Response.json({ error: "Unauthorized" }, { status: 401 });
|
|
40
|
+
}
|
|
41
|
+
if (verifyResult.claims.scope_profile !== "actor_client_v1") {
|
|
42
|
+
log.warn({ scope: verifyResult.claims.scope_profile }, "Token create rejected: insufficient scope");
|
|
43
|
+
return Response.json({ error: "Unauthorized" }, { status: 401 });
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const guardianPrincipalId = await ensureVellumGuardianBinding();
|
|
47
|
+
|
|
48
|
+
const token = mintToken({
|
|
49
|
+
aud: "vellum-gateway",
|
|
50
|
+
sub: `actor:self:${guardianPrincipalId}`,
|
|
51
|
+
scope_profile: "actor_client_v1",
|
|
52
|
+
policy_epoch: CURRENT_POLICY_EPOCH,
|
|
53
|
+
ttlSeconds: TOKEN_TTL_SECONDS,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const expiresAt = Math.floor(Date.now() / 1000) + TOKEN_TTL_SECONDS;
|
|
57
|
+
log.info("Bearer token minted for web local mode");
|
|
58
|
+
|
|
59
|
+
return Response.json({ token, expiresAt });
|
|
60
|
+
}
|
|
@@ -231,7 +231,7 @@ describe("channel-verification-session-proxy guardian init", () => {
|
|
|
231
231
|
expect(body.ok).toBe(true);
|
|
232
232
|
});
|
|
233
233
|
|
|
234
|
-
it("rejects handleResetBootstrap
|
|
234
|
+
it("rejects handleResetBootstrap without valid bootstrap secret", async () => {
|
|
235
235
|
process.env.GUARDIAN_BOOTSTRAP_SECRET = "secret-abc";
|
|
236
236
|
const config = makeConfig();
|
|
237
237
|
const handler = createChannelVerificationSessionProxyHandler(config);
|
|
@@ -240,6 +240,6 @@ describe("channel-verification-session-proxy guardian init", () => {
|
|
|
240
240
|
|
|
241
241
|
expect(response.status).toBe(403);
|
|
242
242
|
const body = await response.json();
|
|
243
|
-
expect(body.error).toBe("
|
|
243
|
+
expect(body.error).toBe("Invalid bootstrap secret");
|
|
244
244
|
});
|
|
245
245
|
});
|
|
@@ -458,15 +458,19 @@ export function createChannelVerificationSessionProxyHandler(
|
|
|
458
458
|
);
|
|
459
459
|
}
|
|
460
460
|
|
|
461
|
-
//
|
|
462
|
-
//
|
|
463
|
-
//
|
|
464
|
-
//
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
461
|
+
// When bootstrap secrets are configured, require a valid one to
|
|
462
|
+
// authorize the reset. This allows bare-metal re-pair (the macOS app
|
|
463
|
+
// reads the secret from the lockfile) while preventing unauthorized
|
|
464
|
+
// resets on Docker deployments where secrets are ephemeral.
|
|
465
|
+
const expectedSecrets = parseBootstrapSecrets();
|
|
466
|
+
if (expectedSecrets.length > 0) {
|
|
467
|
+
const provided = req?.headers.get("x-bootstrap-secret");
|
|
468
|
+
if (!provided || !expectedSecrets.includes(provided)) {
|
|
469
|
+
return Response.json(
|
|
470
|
+
{ error: "Invalid bootstrap secret" },
|
|
471
|
+
{ status: 403 },
|
|
472
|
+
);
|
|
473
|
+
}
|
|
470
474
|
}
|
|
471
475
|
|
|
472
476
|
// Refuse while an init request is awaiting an upstream response —
|
|
@@ -482,6 +486,7 @@ export function createChannelVerificationSessionProxyHandler(
|
|
|
482
486
|
|
|
483
487
|
const lockDir = getGatewaySecurityDir();
|
|
484
488
|
const lockPath = join(lockDir, "guardian-init.lock");
|
|
489
|
+
const consumedPath = join(lockDir, "guardian-init-consumed.json");
|
|
485
490
|
|
|
486
491
|
try {
|
|
487
492
|
if (existsSync(lockPath)) {
|
|
@@ -490,8 +495,12 @@ export function createChannelVerificationSessionProxyHandler(
|
|
|
490
495
|
"Guardian bootstrap lock file removed — re-init is now allowed",
|
|
491
496
|
);
|
|
492
497
|
}
|
|
498
|
+
if (existsSync(consumedPath)) {
|
|
499
|
+
unlinkSync(consumedPath);
|
|
500
|
+
log.info("Guardian consumed secrets file removed");
|
|
501
|
+
}
|
|
493
502
|
} catch (err) {
|
|
494
|
-
log.error({ err }, "Failed to remove guardian-init
|
|
503
|
+
log.error({ err }, "Failed to remove guardian-init lock/consumed files");
|
|
495
504
|
return Response.json(
|
|
496
505
|
{ error: "Failed to remove lock file" },
|
|
497
506
|
{ status: 500 },
|
|
@@ -35,7 +35,9 @@ export function createFeatureFlagsGetHandler() {
|
|
|
35
35
|
const remote = readRemoteFeatureFlags();
|
|
36
36
|
const hasRemoteSnapshot = hasRemoteFeatureFlagSnapshot();
|
|
37
37
|
|
|
38
|
-
// Build entries for ALL declared flags, merging persisted values
|
|
38
|
+
// Build entries for ALL declared flags, merging persisted values.
|
|
39
|
+
// When a remote snapshot exists, flags absent from it are treated
|
|
40
|
+
// as disabled (the platform omitted them intentionally).
|
|
39
41
|
const entries: FeatureFlagEntry[] = [];
|
|
40
42
|
for (const [key, def] of Object.entries(defaults)) {
|
|
41
43
|
const persistedValue = persisted[key];
|
package/src/index.ts
CHANGED
|
@@ -151,6 +151,7 @@ import {
|
|
|
151
151
|
handlePreflight,
|
|
152
152
|
withCorsHeaders,
|
|
153
153
|
} from "./http/middleware/cors.js";
|
|
154
|
+
import { handleCreateToken } from "./http/routes/auth-token.js";
|
|
154
155
|
import {
|
|
155
156
|
createRouter,
|
|
156
157
|
type RouteDefinition,
|
|
@@ -159,6 +160,7 @@ import {
|
|
|
159
160
|
import { SleepWakeDetector } from "./sleep-wake-detector.js";
|
|
160
161
|
import { callTelegramApi } from "./telegram/api.js";
|
|
161
162
|
import { fetchImpl } from "./fetch.js";
|
|
163
|
+
import { arePlatformFeaturesEnabled } from "./feature-flag-resolver.js";
|
|
162
164
|
import { isNewCommand, handleNewCommand } from "./webhook-pipeline.js";
|
|
163
165
|
import { reconcileTelegramWebhook } from "./telegram/webhook-manager.js";
|
|
164
166
|
import { registerEmailCallbackRoute } from "./email/register-callback.js";
|
|
@@ -1414,6 +1416,14 @@ async function main() {
|
|
|
1414
1416
|
},
|
|
1415
1417
|
];
|
|
1416
1418
|
|
|
1419
|
+
// ── Web token auth ──
|
|
1420
|
+
routes.push({
|
|
1421
|
+
path: "/auth/token",
|
|
1422
|
+
method: "POST",
|
|
1423
|
+
auth: "custom",
|
|
1424
|
+
handler: (req) => handleCreateToken(req, server),
|
|
1425
|
+
});
|
|
1426
|
+
|
|
1417
1427
|
// Runtime proxy catch-all — must be last so specific routes are checked first.
|
|
1418
1428
|
routes.push({
|
|
1419
1429
|
path: /^\//, // match everything
|
|
@@ -1685,6 +1695,7 @@ async function main() {
|
|
|
1685
1695
|
);
|
|
1686
1696
|
if (extensionOrigin)
|
|
1687
1697
|
return withExtensionCorsHeaders(body, extensionOrigin);
|
|
1698
|
+
|
|
1688
1699
|
return withCorsHeaders(body, webviewOrigin!);
|
|
1689
1700
|
}
|
|
1690
1701
|
log.error({ err }, "Unhandled gateway error");
|
|
@@ -1743,6 +1754,13 @@ async function main() {
|
|
|
1743
1754
|
* Throttled to at most one outbound POST per 30 seconds. */
|
|
1744
1755
|
let lastRecordActivityTs = 0;
|
|
1745
1756
|
async function notifyRecordActivity(): Promise<void> {
|
|
1757
|
+
if (!arePlatformFeaturesEnabled()) {
|
|
1758
|
+
log.debug(
|
|
1759
|
+
"platform-features-in-local-mode is disabled — skipping record-activity",
|
|
1760
|
+
);
|
|
1761
|
+
return;
|
|
1762
|
+
}
|
|
1763
|
+
|
|
1746
1764
|
const now = Date.now();
|
|
1747
1765
|
if (now - lastRecordActivityTs < 30_000) return;
|
|
1748
1766
|
lastRecordActivityTs = now;
|
|
@@ -72,6 +72,9 @@ const ASSISTANT_SUPPORTED_COMMAND_PATHS = [
|
|
|
72
72
|
"channel-verification-sessions resend",
|
|
73
73
|
"channel-verification-sessions cancel",
|
|
74
74
|
"channel-verification-sessions revoke",
|
|
75
|
+
"channels",
|
|
76
|
+
"channels list",
|
|
77
|
+
"channels get",
|
|
75
78
|
"clients",
|
|
76
79
|
"clients disconnect",
|
|
77
80
|
"clients list",
|
package/src/schema.ts
CHANGED
|
@@ -1935,13 +1935,13 @@ export function buildSchema(): Record<string, unknown> {
|
|
|
1935
1935
|
post: {
|
|
1936
1936
|
summary: "Reset guardian bootstrap lock",
|
|
1937
1937
|
description:
|
|
1938
|
-
"Loopback-only
|
|
1938
|
+
"Loopback-only endpoint that removes the guardian-init lock file so that /v1/guardian/init can be called again. When bootstrap secrets are configured, callers must provide a valid x-bootstrap-secret header. Used by the desktop app to recover from a lost actor token.",
|
|
1939
1939
|
operationId: "guardianResetBootstrap",
|
|
1940
1940
|
responses: {
|
|
1941
1941
|
"200": { description: "Lock file removed (or already absent)" },
|
|
1942
1942
|
"403": {
|
|
1943
1943
|
description:
|
|
1944
|
-
"Forbidden — non-loopback origin or
|
|
1944
|
+
"Forbidden — non-loopback origin or invalid bootstrap secret",
|
|
1945
1945
|
},
|
|
1946
1946
|
"409": {
|
|
1947
1947
|
description: "Guardian init is in progress — try again shortly",
|
|
@@ -2,6 +2,7 @@ import type { CredentialCache } from "../credential-cache.js";
|
|
|
2
2
|
import type { ConfigFileCache } from "../config-file-cache.js";
|
|
3
3
|
import { credentialKey } from "../credential-key.js";
|
|
4
4
|
import { fetchImpl } from "../fetch.js";
|
|
5
|
+
import { arePlatformFeaturesEnabled } from "../feature-flag-resolver.js";
|
|
5
6
|
import { callTelegramApi } from "./api.js";
|
|
6
7
|
import { getLogger } from "../logger.js";
|
|
7
8
|
|
|
@@ -31,6 +32,13 @@ interface PlatformCallbackRouteResponse {
|
|
|
31
32
|
async function registerManagedTelegramCallbackRoute(
|
|
32
33
|
caches?: WebhookManagerCaches,
|
|
33
34
|
): Promise<string | undefined> {
|
|
35
|
+
if (!arePlatformFeaturesEnabled()) {
|
|
36
|
+
log.debug(
|
|
37
|
+
"platform-features-in-local-mode is disabled — skipping managed Telegram callback registration",
|
|
38
|
+
);
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
|
|
34
42
|
const [platformBaseUrlRaw, assistantApiKeyRaw, assistantIdRaw] =
|
|
35
43
|
caches?.credentials
|
|
36
44
|
? await Promise.all([
|