@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
package/src/index.ts
CHANGED
|
@@ -104,6 +104,7 @@ import {
|
|
|
104
104
|
handleListDevices,
|
|
105
105
|
handleRevokeDevice,
|
|
106
106
|
} from "./http/routes/devices.js";
|
|
107
|
+
import { handleSaveIngressDomain } from "./http/routes/ingress-domains.js";
|
|
107
108
|
import { handlePair } from "./http/routes/pair.js";
|
|
108
109
|
import { handleCredentialEntryPage } from "./http/routes/credential-entry-page.js";
|
|
109
110
|
import {
|
|
@@ -123,7 +124,7 @@ import { createSlackControlPlaneProxyHandler } from "./http/routes/slack-control
|
|
|
123
124
|
import { createOAuthAppsProxyHandler } from "./http/routes/oauth-apps-proxy.js";
|
|
124
125
|
import { createOAuthProvidersProxyHandler } from "./http/routes/oauth-providers-proxy.js";
|
|
125
126
|
import { createChannelReadinessProxyHandler } from "./http/routes/channel-readiness-proxy.js";
|
|
126
|
-
import {
|
|
127
|
+
import { createCloudPushProxyHandler } from "./http/routes/cloud-push-proxy.js";
|
|
127
128
|
import { createPsHandler } from "./http/routes/ps.js";
|
|
128
129
|
import { createTunnelStatusHandler } from "./http/routes/tunnel-status.js";
|
|
129
130
|
import { createRuntimeHealthProxyHandler } from "./http/routes/runtime-health-proxy.js";
|
|
@@ -191,11 +192,11 @@ import {
|
|
|
191
192
|
} from "./http/routes/channel-permission-overrides.js";
|
|
192
193
|
import { getLogger, initLogger } from "./logger.js";
|
|
193
194
|
import {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
} from "./
|
|
198
|
-
import {
|
|
195
|
+
bindCloudIdentityCredentialCache,
|
|
196
|
+
ensureCloudIdentityIds,
|
|
197
|
+
resolveCloudAssistantIdOrUndefined,
|
|
198
|
+
} from "./cloud-identity.js";
|
|
199
|
+
import { getCloudBaseUrl } from "./cloud-url.js";
|
|
199
200
|
import { CircuitBreakerOpenError, uploadAttachment } from "./runtime/client.js";
|
|
200
201
|
import {
|
|
201
202
|
appendFailedAttachmentNotice,
|
|
@@ -233,7 +234,7 @@ import { SleepWakeDetector } from "./sleep-wake-detector.js";
|
|
|
233
234
|
import { callTelegramApi } from "./telegram/api.js";
|
|
234
235
|
import { fetchImpl } from "./fetch.js";
|
|
235
236
|
import {
|
|
236
|
-
|
|
237
|
+
areCloudFeaturesEnabled,
|
|
237
238
|
isFeatureFlagEnabled,
|
|
238
239
|
} from "./feature-flag-resolver.js";
|
|
239
240
|
import { isNewCommand, handleNewCommand } from "./webhook-pipeline.js";
|
|
@@ -444,8 +445,8 @@ async function main() {
|
|
|
444
445
|
// Handlers read dynamic credentials and config.json values from these
|
|
445
446
|
// caches at call time, with automatic TTL refresh.
|
|
446
447
|
const credentialCache = new CredentialCache();
|
|
447
|
-
|
|
448
|
-
void
|
|
448
|
+
bindCloudIdentityCredentialCache(credentialCache);
|
|
449
|
+
void ensureCloudIdentityIds();
|
|
449
450
|
const configFileCache = new ConfigFileCache();
|
|
450
451
|
const tunnelClient = createTunnelClient(config, {
|
|
451
452
|
credentials: credentialCache,
|
|
@@ -471,7 +472,7 @@ async function main() {
|
|
|
471
472
|
let cadetReady = false;
|
|
472
473
|
let tunnelStartRequested = false;
|
|
473
474
|
|
|
474
|
-
function
|
|
475
|
+
function maybeStartTunnelForTwilio(
|
|
475
476
|
reason: string,
|
|
476
477
|
twilioCredentials?: Record<string, string> | null,
|
|
477
478
|
): boolean {
|
|
@@ -493,14 +494,14 @@ async function main() {
|
|
|
493
494
|
*
|
|
494
495
|
* Tunnel is the browser's ingress for the live-voice WebSocket, but the tunnel
|
|
495
496
|
* was historically only started for Twilio (see
|
|
496
|
-
* {@link
|
|
497
|
+
* {@link maybeStartTunnelForTwilio}). Without this, an assistant with no
|
|
497
498
|
* Twilio setup never registers a Tunnel, so the browser's
|
|
498
499
|
* `/v1/live-voice` upgrade fails with "assistant tunnel is not connected".
|
|
499
500
|
* Live voice is available to every assistant, so this runs unconditionally at
|
|
500
501
|
* startup. Shares the `tunnelStartRequested` latch with the Twilio path — one
|
|
501
502
|
* tunnel serves both — and `tunnelClient.start()` is idempotent.
|
|
502
503
|
*/
|
|
503
|
-
function
|
|
504
|
+
function startTunnelForLiveVoice(reason: string): boolean {
|
|
504
505
|
if (tunnelStartRequested || !tunnelClient) {
|
|
505
506
|
return tunnelStartRequested;
|
|
506
507
|
}
|
|
@@ -519,7 +520,7 @@ async function main() {
|
|
|
519
520
|
* `tunnelStartRequested` latch — one tunnel serves every ingress consumer —
|
|
520
521
|
* and `tunnelClient.start()` is idempotent.
|
|
521
522
|
*/
|
|
522
|
-
function
|
|
523
|
+
function maybeStartTunnelForCredentialLink(reason: string): boolean {
|
|
523
524
|
if (tunnelStartRequested || !tunnelClient) {
|
|
524
525
|
return tunnelStartRequested;
|
|
525
526
|
}
|
|
@@ -543,7 +544,7 @@ async function main() {
|
|
|
543
544
|
await enablePublicIngress(configFileCache);
|
|
544
545
|
log.info("Auto-enabled public ingress for credential link creation");
|
|
545
546
|
}
|
|
546
|
-
|
|
547
|
+
maybeStartTunnelForCredentialLink("credential link minted");
|
|
547
548
|
}
|
|
548
549
|
|
|
549
550
|
async function readTwilioCredentialsForTunnelStartup(): Promise<Record<
|
|
@@ -668,7 +669,7 @@ async function main() {
|
|
|
668
669
|
const oauthAppsProxy = createOAuthAppsProxyHandler(config);
|
|
669
670
|
const oauthProvidersProxy = createOAuthProvidersProxyHandler(config);
|
|
670
671
|
const channelReadinessProxy = createChannelReadinessProxyHandler(config);
|
|
671
|
-
const
|
|
672
|
+
const cloudPushProxy = createCloudPushProxyHandler(credentialCache);
|
|
672
673
|
const psHandler = createPsHandler(config);
|
|
673
674
|
const tunnelStatusHandler = createTunnelStatusHandler(tunnelClient);
|
|
674
675
|
const runtimeHealthProxy = createRuntimeHealthProxyHandler(config);
|
|
@@ -888,6 +889,15 @@ async function main() {
|
|
|
888
889
|
handler: () => psHandler.handlePs(),
|
|
889
890
|
},
|
|
890
891
|
|
|
892
|
+
// ── Ingress domain recording (localhost CLI only, bearer-authed) ──
|
|
893
|
+
{
|
|
894
|
+
path: "/v1/ingress/domains",
|
|
895
|
+
method: "POST",
|
|
896
|
+
auth: "edge",
|
|
897
|
+
handler: (req, _params, getClientIp) =>
|
|
898
|
+
handleSaveIngressDomain(req, getClientIp(), configFileCache),
|
|
899
|
+
},
|
|
900
|
+
|
|
891
901
|
// ── Tunnel status ──
|
|
892
902
|
{
|
|
893
903
|
path: "/v1/tunnel/status",
|
|
@@ -1336,7 +1346,7 @@ async function main() {
|
|
|
1336
1346
|
//
|
|
1337
1347
|
// Trailing slash is optional to preserve compatibility with existing
|
|
1338
1348
|
// pollers. PlatformMigrationClient.pollImportStatus (macOS) and
|
|
1339
|
-
// cli/src/lib/
|
|
1349
|
+
// cli/src/lib/cloud-client.ts both hit `.../status/` against the
|
|
1340
1350
|
// platform API today; other callers may follow the bare-path
|
|
1341
1351
|
// convention (`.../status`). Regex routes in the gateway router are
|
|
1342
1352
|
// NOT trailing-slash-normalized, so the optionality is encoded here.
|
|
@@ -1447,7 +1457,7 @@ async function main() {
|
|
|
1447
1457
|
method: "POST",
|
|
1448
1458
|
auth: "edge-scoped",
|
|
1449
1459
|
scope: "settings.write",
|
|
1450
|
-
handler: (req) =>
|
|
1460
|
+
handler: (req) => cloudPushProxy.handleUpsertPushToken(req),
|
|
1451
1461
|
},
|
|
1452
1462
|
{
|
|
1453
1463
|
path: /^\/v1\/assistants\/[^/]+\/push-tokens\/([^/]+)\/?$/,
|
|
@@ -1455,14 +1465,14 @@ async function main() {
|
|
|
1455
1465
|
auth: "edge-scoped",
|
|
1456
1466
|
scope: "settings.write",
|
|
1457
1467
|
handler: (req, params) =>
|
|
1458
|
-
|
|
1468
|
+
cloudPushProxy.handleDeletePushToken(req, params[0]),
|
|
1459
1469
|
},
|
|
1460
1470
|
{
|
|
1461
1471
|
path: /^\/v1\/assistants\/[^/]+\/live-activity\/tokens\/?$/,
|
|
1462
1472
|
method: "POST",
|
|
1463
1473
|
auth: "edge-scoped",
|
|
1464
1474
|
scope: "settings.write",
|
|
1465
|
-
handler: (req) =>
|
|
1475
|
+
handler: (req) => cloudPushProxy.handleUpsertLiveActivityToken(req),
|
|
1466
1476
|
},
|
|
1467
1477
|
{
|
|
1468
1478
|
path: /^\/v1\/assistants\/[^/]+\/live-activity\/tokens\/([^/]+)\/?$/,
|
|
@@ -1470,7 +1480,7 @@ async function main() {
|
|
|
1470
1480
|
auth: "edge-scoped",
|
|
1471
1481
|
scope: "settings.write",
|
|
1472
1482
|
handler: (req, params) =>
|
|
1473
|
-
|
|
1483
|
+
cloudPushProxy.handleDeleteLiveActivityToken(req, params[0]),
|
|
1474
1484
|
},
|
|
1475
1485
|
|
|
1476
1486
|
// ── Channel readiness ──
|
|
@@ -1791,7 +1801,7 @@ async function main() {
|
|
|
1791
1801
|
// auth is edge-guardian rather than edge-scoped, because approving ingress
|
|
1792
1802
|
// is the decision the assistant must not make for itself; and there are no
|
|
1793
1803
|
// assistant-scoped variants, because the guardian reaches these directly
|
|
1794
|
-
// rather than through the
|
|
1804
|
+
// rather than through the cloud proxy. Deliberately absent from the IPC
|
|
1795
1805
|
// surface for the same reason as the auth choice. Approve and revoke are
|
|
1796
1806
|
// POST verb paths because a grant has no id of its own until a guardian
|
|
1797
1807
|
// creates one. The listing is guardian-only for a different reason: it says
|
|
@@ -1817,7 +1827,7 @@ async function main() {
|
|
|
1817
1827
|
|
|
1818
1828
|
// ── Channel ingress: assistant-scoped variants ──
|
|
1819
1829
|
// Same handlers, same guardian auth, reached through the prefix a client
|
|
1820
|
-
// built on the
|
|
1830
|
+
// built on the cloud's addressing produces. Approvals are
|
|
1821
1831
|
// gateway-global, so the assistant id is matched and discarded, the same
|
|
1822
1832
|
// precedent as channel-permission-overrides below. These exist because the
|
|
1823
1833
|
// web client's generated gateway SDK only speaks the prefixed form; the
|
|
@@ -1845,7 +1855,7 @@ async function main() {
|
|
|
1845
1855
|
// ── Channel permission overrides (matrix cells) — flat routes ──
|
|
1846
1856
|
// HTTP mirror of the channel-permission IPC surface so configuration
|
|
1847
1857
|
// clients can read/write cascade cells. Gateway-owned storage; same
|
|
1848
|
-
//
|
|
1858
|
+
// cloud-proxy path shape as channel-admission-policy above. The
|
|
1849
1859
|
// delete is a POST verb path because cells are keyed by a composite
|
|
1850
1860
|
// (selector × contact-type), not a row id.
|
|
1851
1861
|
{
|
|
@@ -2320,7 +2330,7 @@ async function main() {
|
|
|
2320
2330
|
}
|
|
2321
2331
|
|
|
2322
2332
|
// Reachable both ways: directly on a self-hosted ingress with an actor
|
|
2323
|
-
// edge JWT, and through the tunnel on a
|
|
2333
|
+
// edge JWT, and through the tunnel on a cloud assistant, which is
|
|
2324
2334
|
// why it is in `TUNNEL_ALLOWED_PATHS`. The handler authorizes each shape on
|
|
2325
2335
|
// its own terms and admits only the guardian either way. A paired
|
|
2326
2336
|
// assistant has no transport at all and never reaches here.
|
|
@@ -2494,12 +2504,12 @@ async function main() {
|
|
|
2494
2504
|
// loop, cleanup timer) with no reference to stop it.
|
|
2495
2505
|
let slackStartGeneration = 0;
|
|
2496
2506
|
|
|
2497
|
-
/** Fire-and-forget: notify the
|
|
2507
|
+
/** Fire-and-forget: notify the cloud of inbound Slack activity so the
|
|
2498
2508
|
* idle-sleep timer is reset for this assistant.
|
|
2499
2509
|
* Throttled to at most one outbound POST per 30 seconds. */
|
|
2500
2510
|
let lastRecordActivityTs = 0;
|
|
2501
2511
|
async function notifyRecordActivity(): Promise<void> {
|
|
2502
|
-
if (!
|
|
2512
|
+
if (!areCloudFeaturesEnabled()) {
|
|
2503
2513
|
log.debug("Platform features disabled — skipping record-activity");
|
|
2504
2514
|
return;
|
|
2505
2515
|
}
|
|
@@ -2509,18 +2519,16 @@ async function main() {
|
|
|
2509
2519
|
lastRecordActivityTs = now;
|
|
2510
2520
|
|
|
2511
2521
|
try {
|
|
2512
|
-
const [
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
],
|
|
2518
|
-
);
|
|
2522
|
+
const [assistantApiKey, assistantId] = await Promise.all([
|
|
2523
|
+
credentialCache.get(credentialKey("cadet", "assistant_api_key")),
|
|
2524
|
+
resolveCloudAssistantIdOrUndefined(),
|
|
2525
|
+
]);
|
|
2526
|
+
const cloudBaseUrl = getCloudBaseUrl();
|
|
2519
2527
|
|
|
2520
|
-
if (!
|
|
2528
|
+
if (!cloudBaseUrl || !assistantApiKey || !assistantId) return;
|
|
2521
2529
|
|
|
2522
2530
|
const res = await fetchImpl(
|
|
2523
|
-
`${
|
|
2531
|
+
`${cloudBaseUrl}/v1/assistants/${assistantId}/record-activity/`,
|
|
2524
2532
|
{
|
|
2525
2533
|
method: "POST",
|
|
2526
2534
|
headers: { Authorization: `Api-Key ${assistantApiKey.trim()}` },
|
|
@@ -2572,7 +2580,7 @@ async function main() {
|
|
|
2572
2580
|
slackSocketClient = createSlackSocketModeClient(
|
|
2573
2581
|
{ appToken, botToken, gatewayConfig: config, threadMode },
|
|
2574
2582
|
(normalized) => {
|
|
2575
|
-
// Notify the
|
|
2583
|
+
// Notify the cloud of inbound activity so the idle-sleep timer
|
|
2576
2584
|
// is reset for this assistant (fire-and-forget).
|
|
2577
2585
|
notifyRecordActivity();
|
|
2578
2586
|
|
|
@@ -2877,13 +2885,11 @@ async function main() {
|
|
|
2877
2885
|
);
|
|
2878
2886
|
|
|
2879
2887
|
const cadetCreds = event.credentials.get("cadet");
|
|
2880
|
-
cadetReady = !!
|
|
2881
|
-
cadetCreds?.platform_base_url && cadetCreds?.assistant_api_key
|
|
2882
|
-
);
|
|
2888
|
+
cadetReady = !!cadetCreds?.assistant_api_key;
|
|
2883
2889
|
if (cadetReady) {
|
|
2884
2890
|
// Re-run validate when the API key / base URL change so a warm-pool
|
|
2885
2891
|
// claim does not keep the previous assistant's bound owner ids.
|
|
2886
|
-
void
|
|
2892
|
+
void ensureCloudIdentityIds();
|
|
2887
2893
|
}
|
|
2888
2894
|
const twilioCreds = event.credentials.get("twilio");
|
|
2889
2895
|
|
|
@@ -2935,10 +2941,7 @@ async function main() {
|
|
|
2935
2941
|
}
|
|
2936
2942
|
|
|
2937
2943
|
if (changed.has("twilio")) {
|
|
2938
|
-
|
|
2939
|
-
"twilio credentials changed",
|
|
2940
|
-
twilioCreds,
|
|
2941
|
-
);
|
|
2944
|
+
maybeStartTunnelForTwilio("twilio credentials changed", twilioCreds);
|
|
2942
2945
|
syncConfiguredTwilioPhoneNumberWebhooks({
|
|
2943
2946
|
credentials: credentialCache,
|
|
2944
2947
|
configFile: configFileCache,
|
|
@@ -2947,7 +2950,7 @@ async function main() {
|
|
|
2947
2950
|
});
|
|
2948
2951
|
}
|
|
2949
2952
|
|
|
2950
|
-
// Register email callback route with the
|
|
2953
|
+
// Register email callback route with the cloud so inbound email
|
|
2951
2954
|
// webhooks are forwarded to this gateway (same pattern as Telegram).
|
|
2952
2955
|
// Fires on initial credential load and whenever cadet credentials change
|
|
2953
2956
|
// (key rotation, late provisioning).
|
|
@@ -2964,8 +2967,8 @@ async function main() {
|
|
|
2964
2967
|
});
|
|
2965
2968
|
|
|
2966
2969
|
// Cadet credentials are load-bearing for the Telegram webhook URL when
|
|
2967
|
-
// the
|
|
2968
|
-
// or rotating after Telegram setup must repoint Telegram at the
|
|
2970
|
+
// the cloud callback fallback applies: a cloud connection arriving
|
|
2971
|
+
// or rotating after Telegram setup must repoint Telegram at the cloud
|
|
2969
2972
|
// callback route without waiting for an unrelated Telegram or ingress
|
|
2970
2973
|
// change, or a system wake. Skipped when telegram credentials changed in
|
|
2971
2974
|
// the same event, since that branch already reconciled above.
|
|
@@ -2980,7 +2983,7 @@ async function main() {
|
|
|
2980
2983
|
|
|
2981
2984
|
// Force an immediate per-assistant feature-flag re-sync. A `cadet`
|
|
2982
2985
|
// credential change means a warm-pool claim / key rotation / late
|
|
2983
|
-
// provisioning — the assistant identity the
|
|
2986
|
+
// provisioning — the assistant identity the cloud evaluates flags
|
|
2984
2987
|
// for just changed. Without this, flags stay at their previous (often
|
|
2985
2988
|
// registry-default) values until the next ~5-min poll, which the
|
|
2986
2989
|
// onboarding auto-greet beats, so first-message flags read stale
|
|
@@ -3002,10 +3005,10 @@ async function main() {
|
|
|
3002
3005
|
log.error({ err }, "Failed to clear stale Tunnel public URL");
|
|
3003
3006
|
});
|
|
3004
3007
|
}
|
|
3005
|
-
|
|
3008
|
+
maybeStartTunnelForTwilio("startup", twilioStartupCredentials);
|
|
3006
3009
|
// Tunnel is also the browser's ingress for web live voice, so bring the tunnel
|
|
3007
3010
|
// up at startup for every assistant (not just for Twilio).
|
|
3008
|
-
|
|
3011
|
+
startTunnelForLiveVoice("startup");
|
|
3009
3012
|
|
|
3010
3013
|
// The credential watcher callback handles credential-backed startup side
|
|
3011
3014
|
// effects during the initial poll. Stale Tunnel-owned ingress is already
|
|
@@ -3050,7 +3053,7 @@ async function main() {
|
|
|
3050
3053
|
}
|
|
3051
3054
|
|
|
3052
3055
|
if (event.changedKeys.has("twilio")) {
|
|
3053
|
-
|
|
3056
|
+
maybeStartTunnelForTwilio("twilio config changed");
|
|
3054
3057
|
}
|
|
3055
3058
|
|
|
3056
3059
|
if (event.changedKeys.has("slack")) {
|
|
@@ -3063,7 +3066,7 @@ async function main() {
|
|
|
3063
3066
|
}
|
|
3064
3067
|
|
|
3065
3068
|
// Side effect: re-register email callback when ingress URL changes so
|
|
3066
|
-
// the
|
|
3069
|
+
// the cloud callback route points at the new self-hosted URL.
|
|
3067
3070
|
if (
|
|
3068
3071
|
event.changedKeys.has("ingress") &&
|
|
3069
3072
|
!onlyTunnelPublicBaseUrlChanged &&
|
|
@@ -3178,7 +3181,7 @@ async function main() {
|
|
|
3178
3181
|
// Force-reconnect Slack WebSocket (may be half-open after sleep)
|
|
3179
3182
|
slackSocketClient?.forceReconnect();
|
|
3180
3183
|
|
|
3181
|
-
// Invalidate caches so next read picks up any config changes (e.g. new
|
|
3184
|
+
// Invalidate caches so next read picks up any config changes (e.g. a new tunnel URL)
|
|
3182
3185
|
configFileCache.invalidate();
|
|
3183
3186
|
credentialCache.invalidate();
|
|
3184
3187
|
|
|
@@ -3197,8 +3200,6 @@ async function main() {
|
|
|
3197
3200
|
});
|
|
3198
3201
|
sleepWakeDetector.start();
|
|
3199
3202
|
|
|
3200
|
-
const drainMs = config.shutdownDrainMs;
|
|
3201
|
-
|
|
3202
3203
|
process.on("SIGTERM", () => {
|
|
3203
3204
|
log.info("SIGTERM received, starting graceful shutdown");
|
|
3204
3205
|
draining = true;
|
|
@@ -3232,7 +3233,7 @@ async function main() {
|
|
|
3232
3233
|
server.stop(true);
|
|
3233
3234
|
process.exit(0);
|
|
3234
3235
|
});
|
|
3235
|
-
},
|
|
3236
|
+
}, 5_000);
|
|
3236
3237
|
});
|
|
3237
3238
|
}
|
|
3238
3239
|
|
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
MAX_ACTIVE_CREDENTIAL_REQUESTS,
|
|
21
21
|
} from "../db/credential-request-store.js";
|
|
22
22
|
import { getLogger } from "../logger.js";
|
|
23
|
-
import {
|
|
23
|
+
import { resolveCloudAssistantIdOrUndefined } from "../cloud-identity.js";
|
|
24
24
|
import { resolvePublicHttpBaseUrl } from "../runtime/client.js";
|
|
25
25
|
import type { IpcRoute } from "./server.js";
|
|
26
26
|
|
|
@@ -76,7 +76,7 @@ export interface CreateCredentialRequestDeps {
|
|
|
76
76
|
* Tunnel fallback, so a config-resolved (self-hosted/manual) URL never
|
|
77
77
|
* loads identity.
|
|
78
78
|
*/
|
|
79
|
-
|
|
79
|
+
getCloudAssistantId?: () => Promise<string | undefined>;
|
|
80
80
|
/**
|
|
81
81
|
* Enable public ingress (if disabled) and start the Tunnel so the
|
|
82
82
|
* minted link is actually reachable. Wired by the gateway entrypoint, which
|
|
@@ -97,7 +97,7 @@ export function createCredentialRequestIpcRoutes(
|
|
|
97
97
|
handler: async (params?: Record<string, unknown>) => {
|
|
98
98
|
const parsed = CreateCredentialRequestSchema.parse(params ?? {});
|
|
99
99
|
return createCredentialRequest(config, configFile, parsed, {
|
|
100
|
-
|
|
100
|
+
getCloudAssistantId: () => resolveCloudAssistantIdOrUndefined(),
|
|
101
101
|
ensurePublicIngressLive,
|
|
102
102
|
});
|
|
103
103
|
},
|
|
@@ -114,7 +114,7 @@ export async function createCredentialRequest(
|
|
|
114
114
|
const publicBaseUrl = await resolvePublicHttpBaseUrl(
|
|
115
115
|
config,
|
|
116
116
|
configFile,
|
|
117
|
-
deps.
|
|
117
|
+
deps.getCloudAssistantId,
|
|
118
118
|
);
|
|
119
119
|
if (!publicBaseUrl) {
|
|
120
120
|
return { ok: false, error: "no_public_base_url" };
|
|
@@ -492,17 +492,17 @@ describe("skill classification", () => {
|
|
|
492
492
|
expect(result.risk).toBe("low");
|
|
493
493
|
});
|
|
494
494
|
|
|
495
|
-
test("
|
|
495
|
+
test("scaffold_cloud_skill is high risk", async () => {
|
|
496
496
|
const result = await classify({
|
|
497
|
-
tool: "
|
|
497
|
+
tool: "scaffold_cloud_skill",
|
|
498
498
|
skill: "new-skill",
|
|
499
499
|
});
|
|
500
500
|
expect(result.risk).toBe("high");
|
|
501
501
|
});
|
|
502
502
|
|
|
503
|
-
test("
|
|
503
|
+
test("delete_cloud_skill is high risk", async () => {
|
|
504
504
|
const result = await classify({
|
|
505
|
-
tool: "
|
|
505
|
+
tool: "delete_cloud_skill",
|
|
506
506
|
skill: "old-skill",
|
|
507
507
|
});
|
|
508
508
|
expect(result.risk).toBe("high");
|
|
@@ -471,8 +471,8 @@ export async function handleClassifyRisk(
|
|
|
471
471
|
|
|
472
472
|
// ── Skill tools ───────────────────────────────────────────────────────
|
|
473
473
|
case "skill_load":
|
|
474
|
-
case "
|
|
475
|
-
case "
|
|
474
|
+
case "scaffold_cloud_skill":
|
|
475
|
+
case "delete_cloud_skill": {
|
|
476
476
|
const assessment = await skillLoadRiskClassifier.classify({
|
|
477
477
|
toolName: tool,
|
|
478
478
|
skillSelector: params.skill,
|
|
@@ -4,7 +4,7 @@ import { getDeviceId } from "./device-id.js";
|
|
|
4
4
|
import { fetchImpl } from "./fetch.js";
|
|
5
5
|
import { loadFeatureFlagDefaults } from "./feature-flag-defaults.js";
|
|
6
6
|
import { writeRemoteFeatureFlags } from "./feature-flag-remote-store.js";
|
|
7
|
-
import {
|
|
7
|
+
import { areCloudFeaturesEnabled } from "./feature-flag-resolver.js";
|
|
8
8
|
import { getLogger } from "./logger.js";
|
|
9
9
|
|
|
10
10
|
const log = getLogger("remote-feature-flag-sync");
|
|
@@ -49,7 +49,7 @@ export type RemoteFeatureFlagSyncConfig = {
|
|
|
49
49
|
};
|
|
50
50
|
|
|
51
51
|
/**
|
|
52
|
-
* Manages the lifecycle of syncing remote feature flags from the
|
|
52
|
+
* Manages the lifecycle of syncing remote feature flags from the cloud.
|
|
53
53
|
*
|
|
54
54
|
* On start, fetches the current flag state and persists it to disk via the
|
|
55
55
|
* remote feature flag store, then polls with adaptive back-off: starts at
|
|
@@ -328,7 +328,7 @@ export class RemoteFeatureFlagSync {
|
|
|
328
328
|
}
|
|
329
329
|
|
|
330
330
|
private async fetchRemoteFeatureFlags(): Promise<RemoteFetchResult> {
|
|
331
|
-
if (!
|
|
331
|
+
if (!areCloudFeaturesEnabled()) {
|
|
332
332
|
log.debug("Remote feature flag sync skipped: platform features disabled");
|
|
333
333
|
return { status: "missing_credentials" };
|
|
334
334
|
}
|
|
@@ -336,31 +336,29 @@ export class RemoteFeatureFlagSync {
|
|
|
336
336
|
// Wrap credential reads so transient failures (CES unreachable, keychain
|
|
337
337
|
// errors) are treated as retriable errors with backoff, not as "missing
|
|
338
338
|
// credentials" which would pause polling indefinitely.
|
|
339
|
-
let platformUrlRaw: string | undefined;
|
|
340
339
|
let assistantApiKeyRaw: string | undefined;
|
|
341
340
|
try {
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
]);
|
|
341
|
+
assistantApiKeyRaw = await this.credentials.get(
|
|
342
|
+
credentialKey("cadet", "assistant_api_key"),
|
|
343
|
+
);
|
|
346
344
|
} catch (err) {
|
|
347
345
|
log.warn({ err }, "Failed to read credentials — will retry on next poll");
|
|
348
346
|
return { status: "error" };
|
|
349
347
|
}
|
|
350
348
|
|
|
351
|
-
//
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
""
|
|
356
|
-
)
|
|
349
|
+
// The base URL resolves from the environment only — the credential store
|
|
350
|
+
// never carries it.
|
|
351
|
+
const cloudUrl = (process.env.CADET_CLOUD_URL?.trim() || "").replace(
|
|
352
|
+
/\/+$/,
|
|
353
|
+
"",
|
|
354
|
+
);
|
|
357
355
|
|
|
358
356
|
const assistantCredential =
|
|
359
357
|
assistantApiKeyRaw?.trim() ||
|
|
360
358
|
process.env.ASSISTANT_API_KEY?.trim() ||
|
|
361
359
|
undefined;
|
|
362
360
|
|
|
363
|
-
if (!
|
|
361
|
+
if (!cloudUrl) {
|
|
364
362
|
log.debug("Remote feature flag sync skipped: no platform URL configured");
|
|
365
363
|
return { status: "missing_credentials" };
|
|
366
364
|
}
|
|
@@ -376,7 +374,7 @@ export class RemoteFeatureFlagSync {
|
|
|
376
374
|
return { status: "error" };
|
|
377
375
|
}
|
|
378
376
|
|
|
379
|
-
const url = `${
|
|
377
|
+
const url = `${cloudUrl}/v1/feature-flags/assistant-flag-values/`;
|
|
380
378
|
const headers: Record<string, string> = { Accept: "application/json" };
|
|
381
379
|
if (assistantCredential) {
|
|
382
380
|
headers["Authorization"] = `Api-Key ${assistantCredential}`;
|
|
@@ -412,9 +410,9 @@ export class RemoteFeatureFlagSync {
|
|
|
412
410
|
return { status: "error" };
|
|
413
411
|
}
|
|
414
412
|
|
|
415
|
-
// Accept boolean and string values from the
|
|
413
|
+
// Accept boolean and string values from the cloud. Prevent the
|
|
416
414
|
// platform from disabling boolean flags that are already GA
|
|
417
|
-
// (defaultEnabled: true in the registry). The
|
|
415
|
+
// (defaultEnabled: true in the registry). The cloud uses a
|
|
418
416
|
// blanket-deny posture, sending false for every flag it knows about.
|
|
419
417
|
// GA normalization only applies to boolean false values; string flag
|
|
420
418
|
// values pass through unchanged.
|
|
@@ -425,7 +423,7 @@ export class RemoteFeatureFlagSync {
|
|
|
425
423
|
if (value === false && registry[key]?.defaultEnabled === true) {
|
|
426
424
|
log.debug(
|
|
427
425
|
{ key },
|
|
428
|
-
"Normalizing remote false for GA flag to true
|
|
426
|
+
"Normalizing remote false for GA flag to true — defaultEnabled is true",
|
|
429
427
|
);
|
|
430
428
|
values[key] = true;
|
|
431
429
|
continue;
|
|
@@ -574,13 +574,13 @@ const riskOverrides: AssistantRiskOverride[] = [
|
|
|
574
574
|
path: "inference profiles update",
|
|
575
575
|
risk: "medium",
|
|
576
576
|
reason:
|
|
577
|
-
"Mutates a custom profile in llm.profiles; daemon rejects
|
|
577
|
+
"Mutates a custom profile in llm.profiles; daemon rejects cloud profiles",
|
|
578
578
|
},
|
|
579
579
|
{
|
|
580
580
|
path: "inference profiles delete",
|
|
581
581
|
risk: "medium",
|
|
582
582
|
reason:
|
|
583
|
-
"Deletes a custom profile from llm.profiles; daemon rejects
|
|
583
|
+
"Deletes a custom profile from llm.profiles; daemon rejects cloud profiles",
|
|
584
584
|
},
|
|
585
585
|
{
|
|
586
586
|
path: "inference profiles active",
|