@mycadet/gateway 0.12.7 → 0.12.9
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/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/package.json +2 -2
- package/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/src/{platform-credential.ts → cloud-credential.ts} +9 -9
- package/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/src/handles.ts +3 -3
- package/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/src/index.ts +1 -1
- package/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/src/ingress.ts +11 -5
- package/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/src/remote-web-pairing.ts +1 -1
- package/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/src/rpc.ts +14 -14
- package/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/src/trust-rules.ts +5 -5
- package/node_modules/@mycadet/ces-client/package.json +1 -1
- package/node_modules/@mycadet/ces-client/src/__tests__/ces-client.test.ts +14 -14
- package/node_modules/@mycadet/ces-client/src/rpc-client.ts +1 -1
- package/node_modules/@mycadet/environments/src/seeds.ts +6 -6
- package/node_modules/@mycadet/environments/src/shell.test.ts +1 -1
- package/node_modules/@mycadet/environments/src/types.ts +6 -6
- package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/package.json +2 -2
- package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/src/{platform-credential.ts → cloud-credential.ts} +9 -9
- package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/src/handles.ts +3 -3
- package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/src/index.ts +1 -1
- package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/src/ingress.ts +11 -5
- package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/src/remote-web-pairing.ts +1 -1
- package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/src/rpc.ts +14 -14
- package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/src/trust-rules.ts +5 -5
- package/node_modules/@mycadet/gateway-client/src/binding-strength-contract.ts +5 -5
- package/node_modules/@mycadet/gateway-client/src/gateway-ipc-contracts.ts +1 -1
- package/node_modules/@mycadet/gateway-client/src/inbound-contract.ts +2 -2
- package/node_modules/@mycadet/gateway-client/src/outbound-contract.ts +1 -1
- package/node_modules/@mycadet/service-contracts/package.json +2 -2
- package/node_modules/@mycadet/service-contracts/src/{platform-credential.ts → cloud-credential.ts} +9 -9
- package/node_modules/@mycadet/service-contracts/src/handles.ts +3 -3
- package/node_modules/@mycadet/service-contracts/src/index.ts +1 -1
- package/node_modules/@mycadet/service-contracts/src/ingress.ts +11 -5
- package/node_modules/@mycadet/service-contracts/src/remote-web-pairing.ts +1 -1
- package/node_modules/@mycadet/service-contracts/src/rpc.ts +14 -14
- package/node_modules/@mycadet/service-contracts/src/trust-rules.ts +5 -5
- package/openapi.json +17 -17
- package/package.json +1 -1
- package/scripts/generate-openapi.ts +1 -1
- package/src/__tests__/actor-token-revocation.test.ts +0 -1
- package/src/__tests__/{backup-worker-platform-mode.test.ts → backup-worker-cloud-mode.test.ts} +4 -4
- package/src/__tests__/channel-verification-session-proxy.test.ts +0 -1
- package/src/__tests__/{platform-identity.test.ts → cloud-identity.test.ts} +72 -86
- package/src/__tests__/config.test.ts +0 -24
- package/src/__tests__/contacts-control-plane-proxy.test.ts +0 -1
- package/src/__tests__/credential-requests.test.ts +6 -6
- package/src/__tests__/{credential-watcher-managed-bootstrap.test.ts → credential-watcher-cloud-bootstrap.test.ts} +1 -1
- package/src/__tests__/edge-auth.test.ts +18 -18
- package/src/__tests__/edge-guardian-auth.test.ts +10 -10
- package/src/__tests__/guardian-init-lockfile.test.ts +6 -8
- package/src/__tests__/guardian-pin.test.ts +8 -8
- package/src/__tests__/handle-inbound-admission.test.ts +0 -1
- package/src/__tests__/handle-inbound-app-context.test.ts +0 -1
- package/src/__tests__/handle-inbound-invite-intercept.test.ts +0 -1
- package/src/__tests__/handle-inbound-trust-verdict.test.ts +0 -1
- package/src/__tests__/inbound-contact-seed.test.ts +1 -1
- package/src/__tests__/live-voice-websocket.test.ts +12 -12
- package/src/__tests__/load-guards.test.ts +0 -1
- package/src/__tests__/migration-teleport-gcs-proxy.test.ts +0 -1
- package/src/__tests__/oauth-callback.test.ts +0 -1
- package/src/__tests__/remote-feature-flag-sync.test.ts +35 -45
- package/src/__tests__/resolve-assistant.test.ts +0 -1
- package/src/__tests__/route-schema-guard.test.ts +4 -1
- package/src/__tests__/runtime-client.test.ts +1 -2
- package/src/__tests__/runtime-health-proxy.test.ts +0 -1
- package/src/__tests__/runtime-proxy-auth.test.ts +3 -2
- package/src/__tests__/runtime-proxy-device-stamp.test.ts +0 -1
- package/src/__tests__/runtime-proxy-error-body.test.ts +0 -1
- package/src/__tests__/runtime-proxy.test.ts +0 -1
- package/src/__tests__/runtime-stream-test-utils.ts +0 -1
- package/src/__tests__/shared-invite-registration.test.ts +15 -32
- package/src/__tests__/slack-control-plane-proxy.test.ts +0 -1
- package/src/__tests__/slack-display-name.test.ts +0 -1
- package/src/__tests__/slack-normalize.test.ts +0 -1
- package/src/__tests__/slack-reaction-normalize.test.ts +0 -1
- package/src/__tests__/slack-socket-mode-catchup.test.ts +0 -1
- package/src/__tests__/slack-socket-mode-group-dm.test.ts +0 -1
- package/src/__tests__/slack-socket-mode-liveness.test.ts +0 -1
- package/src/__tests__/slack-socket-mode-outbound-root.test.ts +0 -1
- package/src/__tests__/slack-socket-mode-thread-tracking.test.ts +0 -1
- package/src/__tests__/speech-relay-websocket.test.ts +16 -14
- package/src/__tests__/stt-stream-websocket.test.ts +5 -5
- package/src/__tests__/telegram-control-plane-proxy.test.ts +0 -1
- package/src/__tests__/telegram-webhook-drop-visibility.test.ts +0 -1
- package/src/__tests__/telegram-webhook-handler.test.ts +0 -1
- package/src/__tests__/telegram-webhook-manager.test.ts +79 -80
- package/src/__tests__/twilio-media-websocket.test.ts +0 -1
- package/src/__tests__/twilio-webhooks.test.ts +6 -7
- package/src/__tests__/whatsapp-download.test.ts +0 -1
- package/src/__tests__/whatsapp-webhook.test.ts +0 -1
- package/src/attachments/ingest.test.ts +0 -1
- package/src/auth/guardian-bootstrap.ts +16 -15
- package/src/auth/scopes.ts +1 -1
- package/src/auth-fallback-reporter.ts +1 -1
- package/src/backup/backup-worker.ts +2 -2
- package/src/channels/inbound-event.ts +3 -3
- package/src/channels/ingress-inbound-vendors.test.ts +2 -2
- package/src/channels/ingress-inbound.ts +1 -1
- package/src/channels/ingress-verification.test.ts +2 -2
- package/src/channels/ingress-verification.ts +4 -4
- package/src/channels/plugin-ingress-approvals.test.ts +1 -1
- package/src/channels/plugin-ingress-approvals.ts +1 -1
- package/src/channels/plugin-ingress.test.ts +3 -3
- package/src/channels/plugin-ingress.ts +3 -3
- package/src/channels/room-admission.ts +2 -2
- package/src/cli/contacts.ts +3 -1
- package/src/cli/index.ts +1 -1
- package/src/{platform-identity.ts → cloud-identity.ts} +60 -68
- package/src/cloud-url.ts +25 -0
- package/src/{platform-user-id.ts → cloud-user-id.ts} +4 -4
- package/src/config.ts +2 -4
- package/src/credential-reader.ts +2 -2
- package/src/db/assistant-db-proxy.ts +1 -1
- package/src/discord/forward.ts +1 -1
- package/src/email/normalize.ts +4 -4
- package/src/email/register-callback.test.ts +27 -34
- package/src/email/register-callback.ts +21 -26
- package/src/feature-flag-registry.json +5 -5
- package/src/feature-flag-remote-store.ts +2 -2
- package/src/feature-flag-resolver.ts +5 -5
- package/src/http/cadet-signature.ts +1 -1
- package/src/http/edge-forwarded-header.ts +1 -1
- package/src/http/loopback-guard.ts +1 -1
- package/src/http/middleware/auth.ts +22 -22
- package/src/http/router.ts +1 -1
- package/src/http/routes/auto-approve-thresholds-routes.ts +1 -1
- package/src/http/routes/cadet-identity.ts +2 -2
- package/src/http/routes/channel-admission-policy-routes.ts +2 -2
- package/src/http/routes/channel-ingress-routes.ts +3 -3
- package/src/http/routes/channel-ingress.test.ts +13 -2
- package/src/http/routes/channel-permission-overrides-routes.ts +3 -3
- package/src/http/routes/channel-verification-session-proxy.ts +8 -22
- package/src/http/routes/{platform-push-proxy.test.ts → cloud-push-proxy.test.ts} +21 -22
- package/src/http/routes/{platform-push-proxy.ts → cloud-push-proxy.ts} +13 -14
- package/src/http/routes/contacts-control-plane-proxy.ts +3 -3
- package/src/http/routes/contacts-control-plane-routes.ts +5 -5
- package/src/http/routes/email-webhook.test.ts +2 -3
- package/src/http/routes/email-webhook.ts +4 -4
- package/src/http/routes/feature-flags.ts +1 -1
- package/src/http/routes/guardian-channel-create.ts +3 -3
- package/src/http/routes/guardian-pin.ts +9 -9
- package/src/http/routes/inbound-register.ts +2 -2
- package/src/http/routes/ingress-domains.test.ts +129 -0
- package/src/http/routes/ingress-domains.ts +73 -0
- package/src/http/routes/live-voice-websocket.ts +2 -2
- package/src/http/routes/log-export.test.ts +0 -1
- package/src/http/routes/log-tail.test.ts +0 -1
- package/src/http/routes/migration-proxy.ts +1 -1
- package/src/http/routes/plugin-webhook-websocket.test.ts +3 -3
- package/src/http/routes/plugin-webhook.test.ts +9 -8
- package/src/http/routes/plugin-webhook.ts +1 -1
- package/src/http/routes/speech-relay-websocket.ts +8 -11
- package/src/http/routes/stt-stream-websocket.ts +1 -1
- package/src/http/routes/telegram-webhook.test.ts +0 -1
- package/src/http/routes/trust-rules-routes.ts +2 -2
- package/src/http/routes/twilio-voice-verify-callback.ts +3 -3
- package/src/http/routes/twilio-voice-webhook.test.ts +3 -4
- package/src/http/routes/twilio-voice-webhook.ts +3 -3
- package/src/http/routes/whatsapp-webhook.test.ts +0 -1
- package/src/index.ts +57 -56
- package/src/ipc/credential-request-handlers.ts +4 -4
- package/src/ipc/debug-export-handlers.test.ts +0 -1
- package/src/ipc/log-tail-handlers.test.ts +0 -1
- package/src/ipc/risk-classification-handlers.test.ts +4 -4
- package/src/ipc/risk-classification-handlers.ts +2 -2
- package/src/remote-feature-flag-sync.ts +17 -19
- package/src/risk/command-registry/commands/assistant.ts +2 -2
- package/src/risk/skill-risk-classifier.test.ts +24 -24
- package/src/risk/skill-risk-classifier.ts +10 -10
- package/src/runtime/client.ts +17 -17
- package/src/schema.ts +29 -29
- package/src/telegram/send.test.ts +0 -1
- package/src/telegram/webhook-manager.ts +33 -38
- package/src/tunnel/allowed-paths.test.ts +3 -3
- package/src/tunnel/allowed-paths.ts +1 -1
- package/src/tunnel/client.test.ts +20 -21
- package/src/tunnel/client.ts +16 -17
- package/src/verification/shared-invite-registration.ts +22 -17
- package/src/version.ts +1 -1
- package/src/platform-url.ts +0 -24
|
@@ -44,8 +44,8 @@ mock.module("../feature-flag-resolver.js", () => ({
|
|
|
44
44
|
|
|
45
45
|
const { reconcileTelegramWebhook } =
|
|
46
46
|
await import("../telegram/webhook-manager.js");
|
|
47
|
-
const {
|
|
48
|
-
await import("../
|
|
47
|
+
const { applyCloudIdentityIds, _resetCloudIdentityForTests } =
|
|
48
|
+
await import("../cloud-identity.js");
|
|
49
49
|
|
|
50
50
|
afterEach(() => {
|
|
51
51
|
fetchMock = mock(async () => new Response());
|
|
@@ -53,10 +53,10 @@ afterEach(() => {
|
|
|
53
53
|
claimedRoutes = [];
|
|
54
54
|
claimError = undefined;
|
|
55
55
|
delete process.env.IS_CONTAINERIZED;
|
|
56
|
-
delete process.env.
|
|
57
|
-
delete process.env.
|
|
56
|
+
delete process.env.IS_CLOUD;
|
|
57
|
+
delete process.env.CADET_CLOUD_URL;
|
|
58
58
|
delete process.env.ASSISTANT_API_KEY;
|
|
59
|
-
|
|
59
|
+
_resetCloudIdentityForTests();
|
|
60
60
|
});
|
|
61
61
|
|
|
62
62
|
function makeTelegramResponse(result: unknown) {
|
|
@@ -74,9 +74,9 @@ function makeCaches(
|
|
|
74
74
|
webhookSecret?: string | null;
|
|
75
75
|
ingressUrl?: string | null;
|
|
76
76
|
ingressEnabled?: boolean;
|
|
77
|
-
|
|
77
|
+
cloudBaseUrl?: string | null;
|
|
78
78
|
assistantApiKey?: string | null;
|
|
79
|
-
|
|
79
|
+
cloudAssistantId?: string | null;
|
|
80
80
|
} = {},
|
|
81
81
|
) {
|
|
82
82
|
const botToken =
|
|
@@ -89,28 +89,28 @@ function makeCaches(
|
|
|
89
89
|
"ingressUrl" in opts
|
|
90
90
|
? (opts.ingressUrl ?? undefined)
|
|
91
91
|
: "https://example.ngrok.io";
|
|
92
|
-
const
|
|
93
|
-
"
|
|
92
|
+
const cloudBaseUrl =
|
|
93
|
+
"cloudBaseUrl" in opts ? (opts.cloudBaseUrl ?? undefined) : undefined;
|
|
94
94
|
const assistantApiKey =
|
|
95
95
|
"assistantApiKey" in opts ? (opts.assistantApiKey ?? undefined) : undefined;
|
|
96
|
-
const
|
|
97
|
-
"
|
|
98
|
-
? (opts.
|
|
96
|
+
const cloudAssistantId =
|
|
97
|
+
"cloudAssistantId" in opts
|
|
98
|
+
? (opts.cloudAssistantId ?? undefined)
|
|
99
99
|
: undefined;
|
|
100
|
+
if (cloudBaseUrl) process.env.CADET_CLOUD_URL = cloudBaseUrl;
|
|
100
101
|
const credentialMap: Record<string, string | undefined> = {
|
|
101
102
|
[credentialKey("telegram", "bot_token")]: botToken,
|
|
102
103
|
[credentialKey("telegram", "webhook_secret")]: webhookSecret,
|
|
103
|
-
[credentialKey("cadet", "platform_base_url")]: platformBaseUrl,
|
|
104
104
|
[credentialKey("cadet", "assistant_api_key")]: assistantApiKey,
|
|
105
|
-
[credentialKey("cadet", "platform_assistant_id")]:
|
|
105
|
+
[credentialKey("cadet", "platform_assistant_id")]: cloudAssistantId,
|
|
106
106
|
};
|
|
107
107
|
const credentials = {
|
|
108
108
|
get: async (key: string) => credentialMap[key],
|
|
109
109
|
invalidate: () => {},
|
|
110
110
|
} as unknown as CredentialCache;
|
|
111
|
-
if (
|
|
112
|
-
|
|
113
|
-
assistantId:
|
|
111
|
+
if (cloudAssistantId) {
|
|
112
|
+
applyCloudIdentityIds({
|
|
113
|
+
assistantId: cloudAssistantId,
|
|
114
114
|
organizationId: "",
|
|
115
115
|
userId: "",
|
|
116
116
|
});
|
|
@@ -134,15 +134,15 @@ function makeCaches(
|
|
|
134
134
|
}
|
|
135
135
|
|
|
136
136
|
const PLATFORM_ASSISTANT_ID = "11111111-2222-4333-8444-555555555555";
|
|
137
|
-
const MANAGED_CALLBACK_URL = `https://
|
|
137
|
+
const MANAGED_CALLBACK_URL = `https://test-cloud.mycadet.ai/v1/gateway/callbacks/${PLATFORM_ASSISTANT_ID}/webhooks/telegram/`;
|
|
138
138
|
|
|
139
139
|
/** Caches for a pod holding platform credentials, with the published URL varied. */
|
|
140
140
|
function makePlatformCaches(ingressUrl: string | undefined) {
|
|
141
141
|
return makeCaches({
|
|
142
142
|
ingressUrl,
|
|
143
|
-
|
|
143
|
+
cloudBaseUrl: "https://test-cloud.mycadet.ai",
|
|
144
144
|
assistantApiKey: "ast-managed-key",
|
|
145
|
-
|
|
145
|
+
cloudAssistantId: PLATFORM_ASSISTANT_ID,
|
|
146
146
|
});
|
|
147
147
|
}
|
|
148
148
|
|
|
@@ -348,14 +348,14 @@ describe("reconcileTelegramWebhook", () => {
|
|
|
348
348
|
expect(fetchMock).not.toHaveBeenCalled();
|
|
349
349
|
});
|
|
350
350
|
|
|
351
|
-
test("registers a
|
|
351
|
+
test("registers a cloud callback route when ingress URL is not configured", async () => {
|
|
352
352
|
const calls: { method: string; body: unknown }[] = [];
|
|
353
353
|
process.env.IS_CONTAINERIZED = "true";
|
|
354
354
|
const caches = makeCaches({
|
|
355
355
|
ingressUrl: undefined,
|
|
356
|
-
|
|
356
|
+
cloudBaseUrl: "https://test-cloud.mycadet.ai",
|
|
357
357
|
assistantApiKey: "ast-managed-key",
|
|
358
|
-
|
|
358
|
+
cloudAssistantId: "11111111-2222-4333-8444-555555555555",
|
|
359
359
|
});
|
|
360
360
|
|
|
361
361
|
fetchMock = mock(
|
|
@@ -368,14 +368,14 @@ describe("reconcileTelegramWebhook", () => {
|
|
|
368
368
|
: input.url;
|
|
369
369
|
if (
|
|
370
370
|
url ===
|
|
371
|
-
"https://
|
|
371
|
+
"https://test-cloud.mycadet.ai/v1/internal/gateway/callback-routes/register/"
|
|
372
372
|
) {
|
|
373
373
|
const body = init?.body ? JSON.parse(init.body as string) : null;
|
|
374
374
|
calls.push({ method: "registerCallbackRoute", body });
|
|
375
375
|
return new Response(
|
|
376
376
|
JSON.stringify({
|
|
377
377
|
callback_url:
|
|
378
|
-
"https://
|
|
378
|
+
"https://test-cloud.mycadet.ai/v1/gateway/callbacks/11111111-2222-4333-8444-555555555555/webhooks/telegram/",
|
|
379
379
|
}),
|
|
380
380
|
{
|
|
381
381
|
status: 201,
|
|
@@ -412,21 +412,21 @@ describe("reconcileTelegramWebhook", () => {
|
|
|
412
412
|
expect(calls[1].method).toBe("getWebhookInfo");
|
|
413
413
|
expect(calls[2].method).toBe("setWebhook");
|
|
414
414
|
expect((calls[2].body as any).url).toBe(
|
|
415
|
-
"https://
|
|
415
|
+
"https://test-cloud.mycadet.ai/v1/gateway/callbacks/11111111-2222-4333-8444-555555555555/webhooks/telegram/",
|
|
416
416
|
);
|
|
417
417
|
expect((calls[2].body as any).secret_token).toBe("test-webhook-secret");
|
|
418
418
|
});
|
|
419
419
|
|
|
420
|
-
test("registers a
|
|
420
|
+
test("registers a cloud callback route without IS_CONTAINERIZED when platform credentials are present (LUM-2899)", async () => {
|
|
421
421
|
const calls: { method: string; body: unknown }[] = [];
|
|
422
|
-
// IS_CONTAINERIZED deliberately unset: a
|
|
423
|
-
// assistant must complete the setWebhook handshake for the
|
|
422
|
+
// IS_CONTAINERIZED deliberately unset: a cloud-connected local
|
|
423
|
+
// assistant must complete the setWebhook handshake for the cloud
|
|
424
424
|
// callback mode that `hasWebhookRoutingConfigured` reports as configured.
|
|
425
425
|
const caches = makeCaches({
|
|
426
426
|
ingressUrl: undefined,
|
|
427
|
-
|
|
427
|
+
cloudBaseUrl: "https://test-cloud.mycadet.ai",
|
|
428
428
|
assistantApiKey: "ast-managed-key",
|
|
429
|
-
|
|
429
|
+
cloudAssistantId: "11111111-2222-4333-8444-555555555555",
|
|
430
430
|
});
|
|
431
431
|
|
|
432
432
|
fetchMock = mock(
|
|
@@ -439,14 +439,14 @@ describe("reconcileTelegramWebhook", () => {
|
|
|
439
439
|
: input.url;
|
|
440
440
|
if (
|
|
441
441
|
url ===
|
|
442
|
-
"https://
|
|
442
|
+
"https://test-cloud.mycadet.ai/v1/internal/gateway/callback-routes/register/"
|
|
443
443
|
) {
|
|
444
444
|
const body = init?.body ? JSON.parse(init.body as string) : null;
|
|
445
445
|
calls.push({ method: "registerCallbackRoute", body });
|
|
446
446
|
return new Response(
|
|
447
447
|
JSON.stringify({
|
|
448
448
|
callback_url:
|
|
449
|
-
"https://
|
|
449
|
+
"https://test-cloud.mycadet.ai/v1/gateway/callbacks/11111111-2222-4333-8444-555555555555/webhooks/telegram/",
|
|
450
450
|
}),
|
|
451
451
|
{
|
|
452
452
|
status: 201,
|
|
@@ -478,20 +478,20 @@ describe("reconcileTelegramWebhook", () => {
|
|
|
478
478
|
expect(calls[1].method).toBe("getWebhookInfo");
|
|
479
479
|
expect(calls[2].method).toBe("setWebhook");
|
|
480
480
|
expect((calls[2].body as any).url).toBe(
|
|
481
|
-
"https://
|
|
481
|
+
"https://test-cloud.mycadet.ai/v1/gateway/callbacks/11111111-2222-4333-8444-555555555555/webhooks/telegram/",
|
|
482
482
|
);
|
|
483
483
|
expect((calls[2].body as any).secret_token).toBe("test-webhook-secret");
|
|
484
484
|
});
|
|
485
485
|
|
|
486
|
-
test("includes callback_base_url when a
|
|
486
|
+
test("includes callback_base_url when a cloud pod has an ingress URL in config", async () => {
|
|
487
487
|
const calls: { method: string; body: unknown }[] = [];
|
|
488
|
-
process.env.
|
|
488
|
+
process.env.IS_CLOUD = "true";
|
|
489
489
|
process.env.IS_CONTAINERIZED = "true";
|
|
490
490
|
const caches = makeCaches({
|
|
491
491
|
ingressUrl: "https://tunnel.example.com",
|
|
492
|
-
|
|
492
|
+
cloudBaseUrl: "https://test-cloud.mycadet.ai",
|
|
493
493
|
assistantApiKey: "ast-managed-key",
|
|
494
|
-
|
|
494
|
+
cloudAssistantId: "11111111-2222-4333-8444-555555555555",
|
|
495
495
|
});
|
|
496
496
|
|
|
497
497
|
fetchMock = mock(
|
|
@@ -504,14 +504,14 @@ describe("reconcileTelegramWebhook", () => {
|
|
|
504
504
|
: input.url;
|
|
505
505
|
if (
|
|
506
506
|
url ===
|
|
507
|
-
"https://
|
|
507
|
+
"https://test-cloud.mycadet.ai/v1/internal/gateway/callback-routes/register/"
|
|
508
508
|
) {
|
|
509
509
|
const body = init?.body ? JSON.parse(init.body as string) : null;
|
|
510
510
|
calls.push({ method: "registerCallbackRoute", body });
|
|
511
511
|
return new Response(
|
|
512
512
|
JSON.stringify({
|
|
513
513
|
callback_url:
|
|
514
|
-
"https://
|
|
514
|
+
"https://test-cloud.mycadet.ai/v1/gateway/callbacks/11111111-2222-4333-8444-555555555555/webhooks/telegram/",
|
|
515
515
|
}),
|
|
516
516
|
{
|
|
517
517
|
status: 201,
|
|
@@ -546,18 +546,18 @@ describe("reconcileTelegramWebhook", () => {
|
|
|
546
546
|
callback_base_url: "https://tunnel.example.com",
|
|
547
547
|
});
|
|
548
548
|
expect((calls[2].body as { url: string }).url).toBe(
|
|
549
|
-
"https://
|
|
549
|
+
"https://test-cloud.mycadet.ai/v1/gateway/callbacks/11111111-2222-4333-8444-555555555555/webhooks/telegram/",
|
|
550
550
|
);
|
|
551
551
|
});
|
|
552
552
|
|
|
553
|
-
test("deregisters the webhook instead of using the
|
|
553
|
+
test("deregisters the webhook instead of using the cloud callback fallback when ingress is explicitly disabled", async () => {
|
|
554
554
|
const calls: string[] = [];
|
|
555
555
|
const caches = makeCaches({
|
|
556
556
|
ingressUrl: undefined,
|
|
557
557
|
ingressEnabled: false,
|
|
558
|
-
|
|
558
|
+
cloudBaseUrl: "https://test-cloud.mycadet.ai",
|
|
559
559
|
assistantApiKey: "ast-managed-key",
|
|
560
|
-
|
|
560
|
+
cloudAssistantId: "11111111-2222-4333-8444-555555555555",
|
|
561
561
|
});
|
|
562
562
|
|
|
563
563
|
fetchMock = mock(async (input: string | URL | Request) => {
|
|
@@ -607,15 +607,15 @@ describe("reconcileTelegramWebhook", () => {
|
|
|
607
607
|
expect(calls).toEqual(["deleteWebhook"]);
|
|
608
608
|
});
|
|
609
609
|
|
|
610
|
-
test("ignores ingress.enabled false on
|
|
610
|
+
test("ignores ingress.enabled false on cloud pods and registers the cloud callback route", async () => {
|
|
611
611
|
const calls: string[] = [];
|
|
612
|
-
process.env.
|
|
612
|
+
process.env.IS_CLOUD = "true";
|
|
613
613
|
const caches = makeCaches({
|
|
614
614
|
ingressUrl: undefined,
|
|
615
615
|
ingressEnabled: false,
|
|
616
|
-
|
|
616
|
+
cloudBaseUrl: "https://test-cloud.mycadet.ai",
|
|
617
617
|
assistantApiKey: "ast-managed-key",
|
|
618
|
-
|
|
618
|
+
cloudAssistantId: "11111111-2222-4333-8444-555555555555",
|
|
619
619
|
});
|
|
620
620
|
|
|
621
621
|
fetchMock = mock(async (input: string | URL | Request) => {
|
|
@@ -630,7 +630,7 @@ describe("reconcileTelegramWebhook", () => {
|
|
|
630
630
|
return new Response(
|
|
631
631
|
JSON.stringify({
|
|
632
632
|
callback_url:
|
|
633
|
-
"https://
|
|
633
|
+
"https://test-cloud.mycadet.ai/v1/gateway/callbacks/11111111-2222-4333-8444-555555555555/webhooks/telegram/",
|
|
634
634
|
}),
|
|
635
635
|
{ status: 201, headers: { "content-type": "application/json" } },
|
|
636
636
|
);
|
|
@@ -663,20 +663,20 @@ describe("reconcileTelegramWebhook", () => {
|
|
|
663
663
|
]);
|
|
664
664
|
});
|
|
665
665
|
|
|
666
|
-
test("prefers the
|
|
666
|
+
test("prefers the cloud callback route over the Tunnel ingress URL on cloud pods", async () => {
|
|
667
667
|
const calls: string[] = [];
|
|
668
668
|
let registeredUrl: string | undefined;
|
|
669
|
-
process.env.
|
|
669
|
+
process.env.IS_CLOUD = "true";
|
|
670
670
|
// A platform pod always has an `ingress.publicBaseUrl`: the Tunnel
|
|
671
|
-
// client publishes one at boot. It must not win over the
|
|
671
|
+
// client publishes one at boot. It must not win over the cloud callback
|
|
672
672
|
// route — the Tunnel address dies with the tunnel, and the daemon's
|
|
673
673
|
// `hasWebhookRoutingConfigured` reports this pod as managed-callback mode.
|
|
674
674
|
const caches = makeCaches({
|
|
675
675
|
ingressUrl:
|
|
676
676
|
"https://tunnel.mycadet.ai/11111111-2222-4333-8444-555555555555",
|
|
677
|
-
|
|
677
|
+
cloudBaseUrl: "https://test-cloud.mycadet.ai",
|
|
678
678
|
assistantApiKey: "ast-managed-key",
|
|
679
|
-
|
|
679
|
+
cloudAssistantId: "11111111-2222-4333-8444-555555555555",
|
|
680
680
|
});
|
|
681
681
|
|
|
682
682
|
fetchMock = mock(
|
|
@@ -692,7 +692,7 @@ describe("reconcileTelegramWebhook", () => {
|
|
|
692
692
|
return new Response(
|
|
693
693
|
JSON.stringify({
|
|
694
694
|
callback_url:
|
|
695
|
-
"https://
|
|
695
|
+
"https://test-cloud.mycadet.ai/v1/gateway/callbacks/11111111-2222-4333-8444-555555555555/webhooks/telegram/",
|
|
696
696
|
}),
|
|
697
697
|
{ status: 201, headers: { "content-type": "application/json" } },
|
|
698
698
|
);
|
|
@@ -725,13 +725,13 @@ describe("reconcileTelegramWebhook", () => {
|
|
|
725
725
|
"setWebhook",
|
|
726
726
|
]);
|
|
727
727
|
expect(registeredUrl).toBe(
|
|
728
|
-
"https://
|
|
728
|
+
"https://test-cloud.mycadet.ai/v1/gateway/callbacks/11111111-2222-4333-8444-555555555555/webhooks/telegram/",
|
|
729
729
|
);
|
|
730
730
|
expect(claimedRoutes).toEqual([]);
|
|
731
731
|
});
|
|
732
732
|
|
|
733
733
|
test("points a pod at its published Tunnel URL and claims the path first when the flag is on", async () => {
|
|
734
|
-
process.env.
|
|
734
|
+
process.env.IS_CLOUD = "true";
|
|
735
735
|
tunnelWebhooksEnabled = true;
|
|
736
736
|
const { calls, result } = mockReconcileFetch();
|
|
737
737
|
|
|
@@ -752,8 +752,8 @@ describe("reconcileTelegramWebhook", () => {
|
|
|
752
752
|
);
|
|
753
753
|
});
|
|
754
754
|
|
|
755
|
-
test("falls back to the
|
|
756
|
-
process.env.
|
|
755
|
+
test("falls back to the cloud callback route when the path claim fails", async () => {
|
|
756
|
+
process.env.IS_CLOUD = "true";
|
|
757
757
|
tunnelWebhooksEnabled = true;
|
|
758
758
|
claimError = new Error("database disk image is malformed");
|
|
759
759
|
const { calls, result } = mockReconcileFetch({
|
|
@@ -774,8 +774,8 @@ describe("reconcileTelegramWebhook", () => {
|
|
|
774
774
|
expect(result.registeredUrl).toBe(MANAGED_CALLBACK_URL);
|
|
775
775
|
});
|
|
776
776
|
|
|
777
|
-
test("falls back to the
|
|
778
|
-
process.env.
|
|
777
|
+
test("falls back to the cloud callback route when the flag is on but no URL is published", async () => {
|
|
778
|
+
process.env.IS_CLOUD = "true";
|
|
779
779
|
tunnelWebhooksEnabled = true;
|
|
780
780
|
const { calls, result } = mockReconcileFetch({
|
|
781
781
|
callbackUrl: MANAGED_CALLBACK_URL,
|
|
@@ -820,14 +820,14 @@ describe("reconcileTelegramWebhook", () => {
|
|
|
820
820
|
headers?: Record<string, string>;
|
|
821
821
|
}[] = [];
|
|
822
822
|
process.env.IS_CONTAINERIZED = "true";
|
|
823
|
-
process.env.
|
|
823
|
+
process.env.CADET_CLOUD_URL = "https://env-platform.example.com";
|
|
824
824
|
process.env.ASSISTANT_API_KEY = "env-key";
|
|
825
825
|
|
|
826
826
|
const caches = makeCaches({
|
|
827
827
|
ingressUrl: undefined,
|
|
828
|
-
|
|
828
|
+
cloudBaseUrl: undefined,
|
|
829
829
|
assistantApiKey: undefined,
|
|
830
|
-
|
|
830
|
+
cloudAssistantId: "aaaaaaaa-bbbb-4ccc-8ddd-eeeeeeeeeeee",
|
|
831
831
|
});
|
|
832
832
|
|
|
833
833
|
fetchMock = mock(
|
|
@@ -886,20 +886,19 @@ describe("reconcileTelegramWebhook", () => {
|
|
|
886
886
|
);
|
|
887
887
|
});
|
|
888
888
|
|
|
889
|
-
test("credential cache for assistant ID
|
|
889
|
+
test("credential cache for assistant ID and auth key; base URL rides the env", async () => {
|
|
890
890
|
const calls: {
|
|
891
891
|
method: string;
|
|
892
892
|
body: unknown;
|
|
893
893
|
headers?: Record<string, string>;
|
|
894
894
|
}[] = [];
|
|
895
895
|
process.env.IS_CONTAINERIZED = "true";
|
|
896
|
-
process.env.
|
|
896
|
+
process.env.CADET_CLOUD_URL = "https://env-platform.example.com";
|
|
897
897
|
|
|
898
898
|
const caches = makeCaches({
|
|
899
899
|
ingressUrl: undefined,
|
|
900
|
-
platformBaseUrl: "https://cache-platform.example.com",
|
|
901
900
|
assistantApiKey: "cache-api-key",
|
|
902
|
-
|
|
901
|
+
cloudAssistantId: "cache-assistant-id",
|
|
903
902
|
});
|
|
904
903
|
|
|
905
904
|
fetchMock = mock(
|
|
@@ -917,7 +916,7 @@ describe("reconcileTelegramWebhook", () => {
|
|
|
917
916
|
return new Response(
|
|
918
917
|
JSON.stringify({
|
|
919
918
|
callback_url:
|
|
920
|
-
"https://
|
|
919
|
+
"https://env-platform.example.com/v1/gateway/callbacks/cache-assistant-id/webhooks/telegram/",
|
|
921
920
|
}),
|
|
922
921
|
{
|
|
923
922
|
status: 201,
|
|
@@ -946,16 +945,17 @@ describe("reconcileTelegramWebhook", () => {
|
|
|
946
945
|
|
|
947
946
|
expect(calls).toHaveLength(3);
|
|
948
947
|
expect(calls[0].method).toBe("registerCallbackRoute");
|
|
949
|
-
//
|
|
948
|
+
// The base URL resolves from the environment only; ids and the API key
|
|
949
|
+
// come from the credential cache.
|
|
950
950
|
expect(calls[0].body).toEqual({
|
|
951
951
|
assistant_id: "cache-assistant-id",
|
|
952
952
|
callback_path: "webhooks/telegram",
|
|
953
953
|
type: "telegram",
|
|
954
954
|
});
|
|
955
955
|
expect(calls[0].headers?.Authorization).toBe("Api-Key cache-api-key");
|
|
956
|
-
// Registration URL should use
|
|
956
|
+
// Registration URL should use the env platform URL
|
|
957
957
|
expect((calls[2].body as any).url).toBe(
|
|
958
|
-
"https://
|
|
958
|
+
"https://env-platform.example.com/v1/gateway/callbacks/cache-assistant-id/webhooks/telegram/",
|
|
959
959
|
);
|
|
960
960
|
});
|
|
961
961
|
|
|
@@ -964,9 +964,9 @@ describe("reconcileTelegramWebhook", () => {
|
|
|
964
964
|
|
|
965
965
|
const caches = makeCaches({
|
|
966
966
|
ingressUrl: undefined,
|
|
967
|
-
|
|
967
|
+
cloudBaseUrl: undefined,
|
|
968
968
|
assistantApiKey: undefined,
|
|
969
|
-
|
|
969
|
+
cloudAssistantId: undefined,
|
|
970
970
|
});
|
|
971
971
|
|
|
972
972
|
fetchMock = mock(async () => new Response("", { status: 200 }));
|
|
@@ -1017,15 +1017,14 @@ const TUNNEL_WEBHOOK_URL = `${TUNNEL_BASE_URL}/webhooks/telegram`;
|
|
|
1017
1017
|
* rather than the value that was current when it was queued.
|
|
1018
1018
|
*/
|
|
1019
1019
|
function makeLivePlatformCaches(readIngressUrl: () => string | undefined) {
|
|
1020
|
+
process.env.CADET_CLOUD_URL = "https://test-cloud.mycadet.ai";
|
|
1020
1021
|
const credentialMap: Record<string, string | undefined> = {
|
|
1021
1022
|
[credentialKey("telegram", "bot_token")]: "test-bot-token",
|
|
1022
1023
|
[credentialKey("telegram", "webhook_secret")]: "test-webhook-secret",
|
|
1023
|
-
[credentialKey("cadet", "platform_base_url")]:
|
|
1024
|
-
"https://platform.example.com",
|
|
1025
1024
|
[credentialKey("cadet", "assistant_api_key")]: "ast-managed-key",
|
|
1026
1025
|
[credentialKey("cadet", "platform_assistant_id")]: PLATFORM_ASSISTANT_ID,
|
|
1027
1026
|
};
|
|
1028
|
-
|
|
1027
|
+
applyCloudIdentityIds({
|
|
1029
1028
|
assistantId: PLATFORM_ASSISTANT_ID,
|
|
1030
1029
|
organizationId: "",
|
|
1031
1030
|
userId: "",
|
|
@@ -1064,11 +1063,11 @@ async function waitFor(condition: () => boolean): Promise<void> {
|
|
|
1064
1063
|
|
|
1065
1064
|
describe("reconcileTelegramWebhook serialization", () => {
|
|
1066
1065
|
test("a reconcile latched behind a slow one settles on the newer address", async () => {
|
|
1067
|
-
process.env.
|
|
1066
|
+
process.env.IS_CLOUD = "true";
|
|
1068
1067
|
tunnelWebhooksEnabled = true;
|
|
1069
1068
|
|
|
1070
1069
|
// The tunnel clears its published URL and republishes it moments later.
|
|
1071
|
-
// The first reconcile sees the cleared state and resolves the
|
|
1070
|
+
// The first reconcile sees the cleared state and resolves the cloud
|
|
1072
1071
|
// callback route; the second must win with the Tunnel URL.
|
|
1073
1072
|
const published: { ingressUrl?: string } = {};
|
|
1074
1073
|
const { caches, refreshes } = makeLivePlatformCaches(
|
|
@@ -1127,7 +1126,7 @@ describe("reconcileTelegramWebhook serialization", () => {
|
|
|
1127
1126
|
releaseFirstSetWebhook();
|
|
1128
1127
|
await Promise.all([first, second]);
|
|
1129
1128
|
|
|
1130
|
-
// The
|
|
1129
|
+
// The cloud registration is the stale one, so it must not be what
|
|
1131
1130
|
// Telegram is left pointed at.
|
|
1132
1131
|
expect(registeredUrls).toEqual([MANAGED_CALLBACK_URL, TUNNEL_WEBHOOK_URL]);
|
|
1133
1132
|
expect(claimedRoutes).toEqual([
|
|
@@ -135,7 +135,6 @@ function makeConfig(overrides: Partial<GatewayConfig> = {}): GatewayConfig {
|
|
|
135
135
|
routingEntries: [],
|
|
136
136
|
port: 7830,
|
|
137
137
|
runtimeProxyRequireAuth: true,
|
|
138
|
-
shutdownDrainMs: 5000,
|
|
139
138
|
runtimeTimeoutMs: 30000,
|
|
140
139
|
runtimeMaxRetries: 2,
|
|
141
140
|
runtimeInitialBackoffMs: 500,
|
|
@@ -780,16 +779,16 @@ describe("Twilio webhook signature with canonical ingress base URL", () => {
|
|
|
780
779
|
|
|
781
780
|
const handler = createTwilioVoiceWebhookHandler(makeConfig(), makeCaches());
|
|
782
781
|
|
|
783
|
-
// The
|
|
782
|
+
// The cloud callback URL is what Twilio signs against — it includes
|
|
784
783
|
// the /v1/gateway/callbacks/{assistantId}/ prefix that the gateway
|
|
785
784
|
// never sees in the request path.
|
|
786
785
|
const platformCallbackUrl =
|
|
787
786
|
"https://cloud.mycadet.ai/v1/gateway/callbacks/abc123/webhooks/twilio/voice";
|
|
788
787
|
const localUrl =
|
|
789
|
-
"http://localhost:7830/webhooks/twilio/voice?callSessionId=
|
|
790
|
-
const params = { CallSid: "CA-
|
|
788
|
+
"http://localhost:7830/webhooks/twilio/voice?callSessionId=cloud-proxy-test";
|
|
789
|
+
const params = { CallSid: "CA-cloud-proxy" };
|
|
791
790
|
|
|
792
|
-
// Sign against the
|
|
791
|
+
// Sign against the cloud callback URL (as Twilio would)
|
|
793
792
|
const signature = computeSignature(platformCallbackUrl, params, AUTH_TOKEN);
|
|
794
793
|
const req = new Request(localUrl, {
|
|
795
794
|
method: "POST",
|
|
@@ -863,7 +862,7 @@ describe("Twilio webhook signature with canonical ingress base URL", () => {
|
|
|
863
862
|
expect(body).not.toContain("staging-cloud.mycadet.ai");
|
|
864
863
|
});
|
|
865
864
|
|
|
866
|
-
test("
|
|
865
|
+
test("cloud proxy URL takes priority over configured ingress", async () => {
|
|
867
866
|
fetchMock = mock(
|
|
868
867
|
async () =>
|
|
869
868
|
new Response('<?xml version="1.0" encoding="UTF-8"?><Response/>', {
|
|
@@ -884,7 +883,7 @@ describe("Twilio webhook signature with canonical ingress base URL", () => {
|
|
|
884
883
|
"http://localhost:7830/webhooks/twilio/voice?callSessionId=priority-test";
|
|
885
884
|
const params = { CallSid: "CA-priority" };
|
|
886
885
|
|
|
887
|
-
// Sign against the
|
|
886
|
+
// Sign against the cloud callback URL
|
|
888
887
|
const signature = computeSignature(platformCallbackUrl, params, AUTH_TOKEN);
|
|
889
888
|
const req = new Request(localUrl, {
|
|
890
889
|
method: "POST",
|
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
} from "../db/schema.js";
|
|
22
22
|
import { readCredential } from "../credential-reader.js";
|
|
23
23
|
import { credentialKey } from "../credential-key.js";
|
|
24
|
-
import {
|
|
24
|
+
import { areCloudFeaturesEnabled } from "../feature-flag-resolver.js";
|
|
25
25
|
import { reportGuardianMintRefused } from "../guardian-integrity-reporter.js";
|
|
26
26
|
import { ipcCallAssistant } from "../ipc/assistant-client.js";
|
|
27
27
|
import { getLogger } from "../logger.js";
|
|
@@ -41,6 +41,7 @@ import {
|
|
|
41
41
|
import { CURRENT_POLICY_EPOCH } from "./policy.js";
|
|
42
42
|
import { mintToken } from "./token-service.js";
|
|
43
43
|
import type { ScopeProfile } from "./types.js";
|
|
44
|
+
import { getIsCloud } from "../cloud-url.js";
|
|
44
45
|
|
|
45
46
|
const log = getLogger("guardian-bootstrap");
|
|
46
47
|
|
|
@@ -1030,38 +1031,38 @@ export function mintAndRecordBrowserTokenPair(params: {
|
|
|
1030
1031
|
// ---------------------------------------------------------------------------
|
|
1031
1032
|
|
|
1032
1033
|
/**
|
|
1033
|
-
* Attempt to fetch the assistant owner's display name from the
|
|
1034
|
+
* Attempt to fetch the assistant owner's display name from the cloud.
|
|
1034
1035
|
*
|
|
1035
|
-
* Only runs when
|
|
1036
|
-
*
|
|
1036
|
+
* Only runs when IS_CLOUD=true. Reads the assistant API key from the
|
|
1037
|
+
* credential store, resolves the base URL from CADET_CLOUD_URL, then calls
|
|
1037
1038
|
* GET /v1/internal/gateway/guardian/ with a 5-second timeout.
|
|
1038
1039
|
* Returns null on any missing credential, timeout, or network/parse failure —
|
|
1039
1040
|
* callers fall back to a generated principal ID in that case.
|
|
1040
1041
|
*/
|
|
1041
1042
|
async function fetchPlatformOwnerDisplayName(): Promise<string | null> {
|
|
1042
|
-
if (!
|
|
1043
|
+
if (!areCloudFeaturesEnabled()) {
|
|
1043
1044
|
log.debug(
|
|
1044
1045
|
"Platform features disabled — skipping platform owner display name fetch",
|
|
1045
1046
|
);
|
|
1046
1047
|
return null;
|
|
1047
1048
|
}
|
|
1048
1049
|
|
|
1049
|
-
const
|
|
1050
|
-
|
|
1051
|
-
process.env.IS_PLATFORM?.trim() === "1";
|
|
1052
|
-
if (!isPlatform) return null;
|
|
1050
|
+
const isCloud = getIsCloud();
|
|
1051
|
+
if (!isCloud) return null;
|
|
1053
1052
|
|
|
1054
|
-
const
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1053
|
+
const assistantApiKey = await readCredential(
|
|
1054
|
+
credentialKey("cadet", "assistant_api_key"),
|
|
1055
|
+
);
|
|
1056
|
+
const cloudBaseUrl = (
|
|
1057
|
+
process.env.CADET_CLOUD_URL?.trim() || ""
|
|
1058
|
+
).replace(/\/+$/, "");
|
|
1058
1059
|
|
|
1059
|
-
if (!
|
|
1060
|
+
if (!cloudBaseUrl || !assistantApiKey) {
|
|
1060
1061
|
return null;
|
|
1061
1062
|
}
|
|
1062
1063
|
|
|
1063
1064
|
try {
|
|
1064
|
-
const url = `${
|
|
1065
|
+
const url = `${cloudBaseUrl.replace(/\/+$/, "")}/v1/internal/gateway/guardian/`;
|
|
1065
1066
|
const response = await fetch(url, {
|
|
1066
1067
|
headers: { Authorization: `Api-Key ${assistantApiKey}` },
|
|
1067
1068
|
signal: AbortSignal.timeout(5_000),
|
package/src/auth/scopes.ts
CHANGED
|
@@ -54,7 +54,7 @@ const PROFILE_SCOPES: Record<ScopeProfile, ReadonlySet<Scope>> = {
|
|
|
54
54
|
// Mirrors the daemon profile a short-lived OAuth passthrough grant is
|
|
55
55
|
// minted with; the gateway resolves it on the IPC fast path.
|
|
56
56
|
oauth_proxy_v1: new Set<Scope>(["oauth.proxy"]),
|
|
57
|
-
//
|
|
57
|
+
// Cloud speech relay only (ATL-1033): the daemon's relay-dial token must
|
|
58
58
|
// not open any other edge-scoped route.
|
|
59
59
|
speech_relay_v1: new Set<Scope>(["speech.relay"]),
|
|
60
60
|
ui_page_v1: new Set<Scope>(["settings.read"]),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Periodically drains AuthFallbackCountTracker and ships the accumulated counts
|
|
2
2
|
// to the daemon's internal telemetry route, which persists them and forwards
|
|
3
|
-
// them to the
|
|
3
|
+
// them to the cloud. Best-effort background work: failures are logged and the
|
|
4
4
|
// drained counts are merged back so a transient daemon blip doesn't lose a
|
|
5
5
|
// window. Deliberately does NOT touch the runtime circuit breaker — this is
|
|
6
6
|
// non-critical traffic and must never affect chat routing.
|