@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.
Files changed (179) hide show
  1. package/README.md +3 -3
  2. package/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/package.json +2 -2
  3. package/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/src/{platform-credential.ts → cloud-credential.ts} +9 -9
  4. package/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/src/handles.ts +3 -3
  5. package/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/src/index.ts +1 -1
  6. package/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/src/ingress.ts +11 -5
  7. package/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/src/remote-web-pairing.ts +1 -1
  8. package/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/src/rpc.ts +14 -14
  9. package/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/src/trust-rules.ts +5 -5
  10. package/node_modules/@mycadet/ces-client/package.json +1 -1
  11. package/node_modules/@mycadet/ces-client/src/__tests__/ces-client.test.ts +14 -14
  12. package/node_modules/@mycadet/ces-client/src/rpc-client.ts +1 -1
  13. package/node_modules/@mycadet/environments/src/seeds.ts +6 -6
  14. package/node_modules/@mycadet/environments/src/shell.test.ts +1 -1
  15. package/node_modules/@mycadet/environments/src/types.ts +6 -6
  16. package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/package.json +2 -2
  17. package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/src/{platform-credential.ts → cloud-credential.ts} +9 -9
  18. package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/src/handles.ts +3 -3
  19. package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/src/index.ts +1 -1
  20. package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/src/ingress.ts +11 -5
  21. package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/src/remote-web-pairing.ts +1 -1
  22. package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/src/rpc.ts +14 -14
  23. package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/src/trust-rules.ts +5 -5
  24. package/node_modules/@mycadet/gateway-client/src/binding-strength-contract.ts +5 -5
  25. package/node_modules/@mycadet/gateway-client/src/gateway-ipc-contracts.ts +1 -1
  26. package/node_modules/@mycadet/gateway-client/src/inbound-contract.ts +2 -2
  27. package/node_modules/@mycadet/gateway-client/src/outbound-contract.ts +1 -1
  28. package/node_modules/@mycadet/service-contracts/package.json +2 -2
  29. package/node_modules/@mycadet/service-contracts/src/{platform-credential.ts → cloud-credential.ts} +9 -9
  30. package/node_modules/@mycadet/service-contracts/src/handles.ts +3 -3
  31. package/node_modules/@mycadet/service-contracts/src/index.ts +1 -1
  32. package/node_modules/@mycadet/service-contracts/src/ingress.ts +11 -5
  33. package/node_modules/@mycadet/service-contracts/src/remote-web-pairing.ts +1 -1
  34. package/node_modules/@mycadet/service-contracts/src/rpc.ts +14 -14
  35. package/node_modules/@mycadet/service-contracts/src/trust-rules.ts +5 -5
  36. package/openapi.json +17 -17
  37. package/package.json +1 -1
  38. package/scripts/generate-openapi.ts +1 -1
  39. package/src/__tests__/actor-token-revocation.test.ts +0 -1
  40. package/src/__tests__/{backup-worker-platform-mode.test.ts → backup-worker-cloud-mode.test.ts} +4 -4
  41. package/src/__tests__/channel-verification-session-proxy.test.ts +0 -1
  42. package/src/__tests__/{platform-identity.test.ts → cloud-identity.test.ts} +72 -86
  43. package/src/__tests__/config.test.ts +0 -24
  44. package/src/__tests__/contacts-control-plane-proxy.test.ts +0 -1
  45. package/src/__tests__/credential-requests.test.ts +6 -6
  46. package/src/__tests__/{credential-watcher-managed-bootstrap.test.ts → credential-watcher-cloud-bootstrap.test.ts} +1 -1
  47. package/src/__tests__/edge-auth.test.ts +18 -18
  48. package/src/__tests__/edge-guardian-auth.test.ts +10 -10
  49. package/src/__tests__/guardian-init-lockfile.test.ts +6 -8
  50. package/src/__tests__/guardian-pin.test.ts +8 -8
  51. package/src/__tests__/handle-inbound-admission.test.ts +0 -1
  52. package/src/__tests__/handle-inbound-app-context.test.ts +0 -1
  53. package/src/__tests__/handle-inbound-invite-intercept.test.ts +0 -1
  54. package/src/__tests__/handle-inbound-trust-verdict.test.ts +0 -1
  55. package/src/__tests__/inbound-contact-seed.test.ts +1 -1
  56. package/src/__tests__/live-voice-websocket.test.ts +12 -12
  57. package/src/__tests__/load-guards.test.ts +0 -1
  58. package/src/__tests__/migration-teleport-gcs-proxy.test.ts +0 -1
  59. package/src/__tests__/oauth-callback.test.ts +0 -1
  60. package/src/__tests__/remote-feature-flag-sync.test.ts +35 -45
  61. package/src/__tests__/resolve-assistant.test.ts +0 -1
  62. package/src/__tests__/route-schema-guard.test.ts +4 -1
  63. package/src/__tests__/runtime-client.test.ts +1 -2
  64. package/src/__tests__/runtime-health-proxy.test.ts +0 -1
  65. package/src/__tests__/runtime-proxy-auth.test.ts +3 -2
  66. package/src/__tests__/runtime-proxy-device-stamp.test.ts +0 -1
  67. package/src/__tests__/runtime-proxy-error-body.test.ts +0 -1
  68. package/src/__tests__/runtime-proxy.test.ts +0 -1
  69. package/src/__tests__/runtime-stream-test-utils.ts +0 -1
  70. package/src/__tests__/shared-invite-registration.test.ts +15 -32
  71. package/src/__tests__/slack-control-plane-proxy.test.ts +0 -1
  72. package/src/__tests__/slack-display-name.test.ts +0 -1
  73. package/src/__tests__/slack-normalize.test.ts +0 -1
  74. package/src/__tests__/slack-reaction-normalize.test.ts +0 -1
  75. package/src/__tests__/slack-socket-mode-catchup.test.ts +0 -1
  76. package/src/__tests__/slack-socket-mode-group-dm.test.ts +0 -1
  77. package/src/__tests__/slack-socket-mode-liveness.test.ts +0 -1
  78. package/src/__tests__/slack-socket-mode-outbound-root.test.ts +0 -1
  79. package/src/__tests__/slack-socket-mode-thread-tracking.test.ts +0 -1
  80. package/src/__tests__/speech-relay-websocket.test.ts +16 -14
  81. package/src/__tests__/stt-stream-websocket.test.ts +5 -5
  82. package/src/__tests__/telegram-control-plane-proxy.test.ts +0 -1
  83. package/src/__tests__/telegram-webhook-drop-visibility.test.ts +0 -1
  84. package/src/__tests__/telegram-webhook-handler.test.ts +0 -1
  85. package/src/__tests__/telegram-webhook-manager.test.ts +79 -80
  86. package/src/__tests__/twilio-media-websocket.test.ts +0 -1
  87. package/src/__tests__/twilio-webhooks.test.ts +6 -7
  88. package/src/__tests__/whatsapp-download.test.ts +0 -1
  89. package/src/__tests__/whatsapp-webhook.test.ts +0 -1
  90. package/src/attachments/ingest.test.ts +0 -1
  91. package/src/auth/guardian-bootstrap.ts +16 -15
  92. package/src/auth/scopes.ts +1 -1
  93. package/src/auth-fallback-reporter.ts +1 -1
  94. package/src/backup/backup-worker.ts +2 -2
  95. package/src/channels/inbound-event.ts +3 -3
  96. package/src/channels/ingress-inbound-vendors.test.ts +2 -2
  97. package/src/channels/ingress-inbound.ts +1 -1
  98. package/src/channels/ingress-verification.test.ts +2 -2
  99. package/src/channels/ingress-verification.ts +4 -4
  100. package/src/channels/plugin-ingress-approvals.test.ts +1 -1
  101. package/src/channels/plugin-ingress-approvals.ts +1 -1
  102. package/src/channels/plugin-ingress.test.ts +3 -3
  103. package/src/channels/plugin-ingress.ts +3 -3
  104. package/src/channels/room-admission.ts +2 -2
  105. package/src/cli/contacts.ts +3 -1
  106. package/src/cli/index.ts +1 -1
  107. package/src/{platform-identity.ts → cloud-identity.ts} +60 -68
  108. package/src/cloud-url.ts +25 -0
  109. package/src/{platform-user-id.ts → cloud-user-id.ts} +4 -4
  110. package/src/config.ts +2 -4
  111. package/src/credential-reader.ts +2 -2
  112. package/src/db/assistant-db-proxy.ts +1 -1
  113. package/src/discord/forward.ts +1 -1
  114. package/src/email/normalize.ts +4 -4
  115. package/src/email/register-callback.test.ts +27 -34
  116. package/src/email/register-callback.ts +21 -26
  117. package/src/feature-flag-registry.json +5 -5
  118. package/src/feature-flag-remote-store.ts +2 -2
  119. package/src/feature-flag-resolver.ts +5 -5
  120. package/src/http/cadet-signature.ts +1 -1
  121. package/src/http/edge-forwarded-header.ts +1 -1
  122. package/src/http/loopback-guard.ts +1 -1
  123. package/src/http/middleware/auth.ts +22 -22
  124. package/src/http/router.ts +1 -1
  125. package/src/http/routes/auto-approve-thresholds-routes.ts +1 -1
  126. package/src/http/routes/cadet-identity.ts +2 -2
  127. package/src/http/routes/channel-admission-policy-routes.ts +2 -2
  128. package/src/http/routes/channel-ingress-routes.ts +3 -3
  129. package/src/http/routes/channel-ingress.test.ts +13 -2
  130. package/src/http/routes/channel-permission-overrides-routes.ts +3 -3
  131. package/src/http/routes/channel-verification-session-proxy.ts +8 -22
  132. package/src/http/routes/{platform-push-proxy.test.ts → cloud-push-proxy.test.ts} +21 -22
  133. package/src/http/routes/{platform-push-proxy.ts → cloud-push-proxy.ts} +13 -14
  134. package/src/http/routes/contacts-control-plane-proxy.ts +3 -3
  135. package/src/http/routes/contacts-control-plane-routes.ts +5 -5
  136. package/src/http/routes/email-webhook.test.ts +2 -3
  137. package/src/http/routes/email-webhook.ts +4 -4
  138. package/src/http/routes/feature-flags.ts +1 -1
  139. package/src/http/routes/guardian-channel-create.ts +3 -3
  140. package/src/http/routes/guardian-pin.ts +9 -9
  141. package/src/http/routes/inbound-register.ts +2 -2
  142. package/src/http/routes/ingress-domains.test.ts +129 -0
  143. package/src/http/routes/ingress-domains.ts +73 -0
  144. package/src/http/routes/live-voice-websocket.ts +2 -2
  145. package/src/http/routes/log-export.test.ts +0 -1
  146. package/src/http/routes/log-tail.test.ts +0 -1
  147. package/src/http/routes/migration-proxy.ts +1 -1
  148. package/src/http/routes/plugin-webhook-websocket.test.ts +3 -3
  149. package/src/http/routes/plugin-webhook.test.ts +9 -8
  150. package/src/http/routes/plugin-webhook.ts +1 -1
  151. package/src/http/routes/speech-relay-websocket.ts +8 -11
  152. package/src/http/routes/stt-stream-websocket.ts +1 -1
  153. package/src/http/routes/telegram-webhook.test.ts +0 -1
  154. package/src/http/routes/trust-rules-routes.ts +2 -2
  155. package/src/http/routes/twilio-voice-verify-callback.ts +3 -3
  156. package/src/http/routes/twilio-voice-webhook.test.ts +3 -4
  157. package/src/http/routes/twilio-voice-webhook.ts +3 -3
  158. package/src/http/routes/whatsapp-webhook.test.ts +0 -1
  159. package/src/index.ts +57 -56
  160. package/src/ipc/credential-request-handlers.ts +4 -4
  161. package/src/ipc/debug-export-handlers.test.ts +0 -1
  162. package/src/ipc/log-tail-handlers.test.ts +0 -1
  163. package/src/ipc/risk-classification-handlers.test.ts +4 -4
  164. package/src/ipc/risk-classification-handlers.ts +2 -2
  165. package/src/remote-feature-flag-sync.ts +17 -19
  166. package/src/risk/command-registry/commands/assistant.ts +2 -2
  167. package/src/risk/skill-risk-classifier.test.ts +24 -24
  168. package/src/risk/skill-risk-classifier.ts +10 -10
  169. package/src/runtime/client.ts +17 -17
  170. package/src/schema.ts +29 -29
  171. package/src/telegram/send.test.ts +0 -1
  172. package/src/telegram/webhook-manager.ts +33 -38
  173. package/src/tunnel/allowed-paths.test.ts +3 -3
  174. package/src/tunnel/allowed-paths.ts +1 -1
  175. package/src/tunnel/client.test.ts +20 -21
  176. package/src/tunnel/client.ts +16 -17
  177. package/src/verification/shared-invite-registration.ts +22 -17
  178. package/src/version.ts +1 -1
  179. 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 { createPlatformPushProxyHandler } from "./http/routes/platform-push-proxy.js";
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
- bindPlatformIdentityCredentialCache,
195
- ensurePlatformIdentityIds,
196
- resolvePlatformAssistantIdOrUndefined,
197
- } from "./platform-identity.js";
198
- import { getPlatformBaseUrl } from "./platform-url.js";
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
- arePlatformFeaturesEnabled,
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
- bindPlatformIdentityCredentialCache(credentialCache);
448
- void ensurePlatformIdentityIds();
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 maybeStartTunnelTunnelForTwilio(
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 maybeStartTunnelTunnelForTwilio}). Without this, an assistant with no
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 startTunnelTunnelForLiveVoice(reason: string): boolean {
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 maybeStartTunnelTunnelForCredentialLink(reason: string): boolean {
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
- maybeStartTunnelTunnelForCredentialLink("credential link minted");
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 platformPushProxy = createPlatformPushProxyHandler(credentialCache);
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/platform-client.ts both hit `.../status/` against the
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) => platformPushProxy.handleUpsertPushToken(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
- platformPushProxy.handleDeletePushToken(req, params[0]),
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) => platformPushProxy.handleUpsertLiveActivityToken(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
- platformPushProxy.handleDeleteLiveActivityToken(req, params[0]),
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 platform proxy. Deliberately absent from the IPC
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 platform's addressing produces. Approvals are
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
- // platform-proxy path shape as channel-admission-policy above. The
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 managed assistant, which is
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 platform of inbound Slack activity so 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 (!arePlatformFeaturesEnabled()) {
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 [platformBaseUrl, assistantApiKey, assistantId] = await Promise.all(
2513
- [
2514
- getPlatformBaseUrl(credentialCache),
2515
- credentialCache.get(credentialKey("cadet", "assistant_api_key")),
2516
- resolvePlatformAssistantIdOrUndefined(),
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 (!platformBaseUrl || !assistantApiKey || !assistantId) return;
2528
+ if (!cloudBaseUrl || !assistantApiKey || !assistantId) return;
2521
2529
 
2522
2530
  const res = await fetchImpl(
2523
- `${platformBaseUrl}/v1/assistants/${assistantId}/record-activity/`,
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 platform of inbound activity so the idle-sleep timer
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 ensurePlatformIdentityIds();
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
- maybeStartTunnelTunnelForTwilio(
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 platform so inbound email
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 managed callback fallback applies: a cloud connection arriving
2968
- // or rotating after Telegram setup must repoint Telegram at the managed
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 platform evaluates flags
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
- maybeStartTunnelTunnelForTwilio("startup", twilioStartupCredentials);
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
- startTunnelTunnelForLiveVoice("startup");
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
- maybeStartTunnelTunnelForTwilio("twilio config changed");
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 platform callback route points at the new self-hosted URL.
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 ngrok URL)
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
- }, drainMs);
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 { resolvePlatformAssistantIdOrUndefined } from "../platform-identity.js";
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
- getPlatformAssistantId?: () => Promise<string | undefined>;
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
- getPlatformAssistantId: () => resolvePlatformAssistantIdOrUndefined(),
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.getPlatformAssistantId,
117
+ deps.getCloudAssistantId,
118
118
  );
119
119
  if (!publicBaseUrl) {
120
120
  return { ok: false, error: "no_public_base_url" };
@@ -40,7 +40,6 @@ function makeConfig(logDir: string | undefined): GatewayConfig {
40
40
  runtimeMaxRetries: 0,
41
41
  runtimeProxyRequireAuth: true,
42
42
  runtimeTimeoutMs: 1000,
43
- shutdownDrainMs: 1,
44
43
  trustProxy: false,
45
44
  } as GatewayConfig;
46
45
  }
@@ -43,7 +43,6 @@ function makeConfig(): GatewayConfig {
43
43
  runtimeMaxRetries: 2,
44
44
  runtimeProxyRequireAuth: true,
45
45
  runtimeTimeoutMs: 30000,
46
- shutdownDrainMs: 5000,
47
46
  trustProxy: false,
48
47
  } as GatewayConfig;
49
48
  }
@@ -492,17 +492,17 @@ describe("skill classification", () => {
492
492
  expect(result.risk).toBe("low");
493
493
  });
494
494
 
495
- test("scaffold_managed_skill is high risk", async () => {
495
+ test("scaffold_cloud_skill is high risk", async () => {
496
496
  const result = await classify({
497
- tool: "scaffold_managed_skill",
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("delete_managed_skill is high risk", async () => {
503
+ test("delete_cloud_skill is high risk", async () => {
504
504
  const result = await classify({
505
- tool: "delete_managed_skill",
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 "scaffold_managed_skill":
475
- case "delete_managed_skill": {
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 { arePlatformFeaturesEnabled } from "./feature-flag-resolver.js";
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 platform.
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 (!arePlatformFeaturesEnabled()) {
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
- [platformUrlRaw, assistantApiKeyRaw] = await Promise.all([
343
- this.credentials.get(credentialKey("cadet", "platform_base_url")),
344
- this.credentials.get(credentialKey("cadet", "assistant_api_key")),
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
- // Fall back to env vars when managed pod credentials are not yet cached.
352
- const platformUrl = (
353
- platformUrlRaw?.trim() ||
354
- process.env.CADET_PLATFORM_URL?.trim() ||
355
- ""
356
- ).replace(/\/+$/, "");
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 (!platformUrl) {
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 = `${platformUrl}/v1/feature-flags/assistant-flag-values/`;
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 platform. Prevent 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 platform uses a
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 (defaultEnabled: 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 managed profiles",
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 managed profiles",
583
+ "Deletes a custom profile from llm.profiles; daemon rejects cloud profiles",
584
584
  },
585
585
  {
586
586
  path: "inference profiles active",