@mycadet/gateway 0.12.7 → 0.12.8
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 +1 -1
- 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/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 +1 -1
- 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 +1 -1
- 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/package.json +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 +17 -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/{platform-identity.ts → cloud-identity.ts} +60 -68
- package/src/cloud-url.ts +14 -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/cadet-identity.ts +2 -2
- package/src/http/routes/channel-ingress-routes.ts +1 -1
- package/src/http/routes/channel-ingress.test.ts +13 -2
- package/src/http/routes/channel-verification-session-proxy.ts +9 -9
- 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 +2 -2
- package/src/http/routes/email-webhook.test.ts +2 -3
- package/src/http/routes/email-webhook.ts +4 -4
- 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/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 +16 -18
- 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 +5 -5
- 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 +17 -17
- package/src/verification/shared-invite-registration.ts +22 -17
- package/src/version.ts +1 -1
- package/src/platform-url.ts +0 -24
|
@@ -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.
|
|
@@ -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",
|
|
@@ -19,10 +19,10 @@ describe("SkillLoadRiskClassifier", () => {
|
|
|
19
19
|
expect(result.matchType).toBe("registry");
|
|
20
20
|
});
|
|
21
21
|
|
|
22
|
-
test("
|
|
22
|
+
test("scaffold_cloud_skill is always High risk", async () => {
|
|
23
23
|
const classifier = new SkillLoadRiskClassifier();
|
|
24
24
|
const result = await classifier.classify({
|
|
25
|
-
toolName: "
|
|
25
|
+
toolName: "scaffold_cloud_skill",
|
|
26
26
|
});
|
|
27
27
|
expect(result.riskLevel).toBe("high");
|
|
28
28
|
expect(result.reason).toBe(
|
|
@@ -32,10 +32,10 @@ describe("SkillLoadRiskClassifier", () => {
|
|
|
32
32
|
expect(result.matchType).toBe("registry");
|
|
33
33
|
});
|
|
34
34
|
|
|
35
|
-
test("
|
|
35
|
+
test("delete_cloud_skill is always High risk", async () => {
|
|
36
36
|
const classifier = new SkillLoadRiskClassifier();
|
|
37
37
|
const result = await classifier.classify({
|
|
38
|
-
toolName: "
|
|
38
|
+
toolName: "delete_cloud_skill",
|
|
39
39
|
});
|
|
40
40
|
expect(result.riskLevel).toBe("high");
|
|
41
41
|
expect(result.reason).toBe(
|
|
@@ -55,19 +55,19 @@ describe("SkillLoadRiskClassifier", () => {
|
|
|
55
55
|
expect(result.reason).toBe("Skill load (default)");
|
|
56
56
|
});
|
|
57
57
|
|
|
58
|
-
test("
|
|
58
|
+
test("scaffold_cloud_skill with skillSelector is still High risk", async () => {
|
|
59
59
|
const classifier = new SkillLoadRiskClassifier();
|
|
60
60
|
const result = await classifier.classify({
|
|
61
|
-
toolName: "
|
|
61
|
+
toolName: "scaffold_cloud_skill",
|
|
62
62
|
skillSelector: "new-skill",
|
|
63
63
|
});
|
|
64
64
|
expect(result.riskLevel).toBe("high");
|
|
65
65
|
});
|
|
66
66
|
|
|
67
|
-
test("
|
|
67
|
+
test("delete_cloud_skill with skillSelector is still High risk", async () => {
|
|
68
68
|
const classifier = new SkillLoadRiskClassifier();
|
|
69
69
|
const result = await classifier.classify({
|
|
70
|
-
toolName: "
|
|
70
|
+
toolName: "delete_cloud_skill",
|
|
71
71
|
skillSelector: "old-skill",
|
|
72
72
|
});
|
|
73
73
|
expect(result.riskLevel).toBe("high");
|
|
@@ -163,56 +163,56 @@ describe("allowlistOptions", () => {
|
|
|
163
163
|
]);
|
|
164
164
|
});
|
|
165
165
|
|
|
166
|
-
test("
|
|
166
|
+
test("scaffold_cloud_skill produces skill-specific + wildcard options", async () => {
|
|
167
167
|
const classifier = new SkillLoadRiskClassifier();
|
|
168
168
|
const result = await classifier.classify({
|
|
169
|
-
toolName: "
|
|
169
|
+
toolName: "scaffold_cloud_skill",
|
|
170
170
|
skillSelector: "new-skill",
|
|
171
171
|
});
|
|
172
172
|
expect(result.allowlistOptions).toEqual([
|
|
173
173
|
{
|
|
174
174
|
label: "new-skill",
|
|
175
175
|
description: "This skill only",
|
|
176
|
-
pattern: "
|
|
176
|
+
pattern: "scaffold_cloud_skill:new-skill",
|
|
177
177
|
},
|
|
178
178
|
{
|
|
179
|
-
label: "
|
|
179
|
+
label: "scaffold_cloud_skill:*",
|
|
180
180
|
description: "All managed skill scaffolds",
|
|
181
|
-
pattern: "
|
|
181
|
+
pattern: "scaffold_cloud_skill:*",
|
|
182
182
|
},
|
|
183
183
|
]);
|
|
184
184
|
});
|
|
185
185
|
|
|
186
|
-
test("
|
|
186
|
+
test("delete_cloud_skill produces skill-specific + wildcard options", async () => {
|
|
187
187
|
const classifier = new SkillLoadRiskClassifier();
|
|
188
188
|
const result = await classifier.classify({
|
|
189
|
-
toolName: "
|
|
189
|
+
toolName: "delete_cloud_skill",
|
|
190
190
|
skillSelector: "old-skill",
|
|
191
191
|
});
|
|
192
192
|
expect(result.allowlistOptions).toEqual([
|
|
193
193
|
{
|
|
194
194
|
label: "old-skill",
|
|
195
195
|
description: "This skill only",
|
|
196
|
-
pattern: "
|
|
196
|
+
pattern: "delete_cloud_skill:old-skill",
|
|
197
197
|
},
|
|
198
198
|
{
|
|
199
|
-
label: "
|
|
199
|
+
label: "delete_cloud_skill:*",
|
|
200
200
|
description: "All managed skill deletes",
|
|
201
|
-
pattern: "
|
|
201
|
+
pattern: "delete_cloud_skill:*",
|
|
202
202
|
},
|
|
203
203
|
]);
|
|
204
204
|
});
|
|
205
205
|
|
|
206
|
-
test("
|
|
206
|
+
test("scaffold_cloud_skill without selector produces only wildcard", async () => {
|
|
207
207
|
const classifier = new SkillLoadRiskClassifier();
|
|
208
208
|
const result = await classifier.classify({
|
|
209
|
-
toolName: "
|
|
209
|
+
toolName: "scaffold_cloud_skill",
|
|
210
210
|
});
|
|
211
211
|
expect(result.allowlistOptions).toEqual([
|
|
212
212
|
{
|
|
213
|
-
label: "
|
|
213
|
+
label: "scaffold_cloud_skill:*",
|
|
214
214
|
description: "All managed skill scaffolds",
|
|
215
|
-
pattern: "
|
|
215
|
+
pattern: "scaffold_cloud_skill:*",
|
|
216
216
|
},
|
|
217
217
|
]);
|
|
218
218
|
});
|
|
@@ -228,8 +228,8 @@ describe("singleton", () => {
|
|
|
228
228
|
test("singleton produces same results as fresh instance", async () => {
|
|
229
229
|
const inputs: SkillClassifierInput[] = [
|
|
230
230
|
{ toolName: "skill_load" },
|
|
231
|
-
{ toolName: "
|
|
232
|
-
{ toolName: "
|
|
231
|
+
{ toolName: "scaffold_cloud_skill" },
|
|
232
|
+
{ toolName: "delete_cloud_skill" },
|
|
233
233
|
];
|
|
234
234
|
|
|
235
235
|
const fresh = new SkillLoadRiskClassifier();
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* Implements RiskClassifier<SkillClassifierInput> with constant risk levels
|
|
5
5
|
* for each skill tool type:
|
|
6
6
|
* - skill_load: Low (read-only skill loading)
|
|
7
|
-
* -
|
|
8
|
-
* -
|
|
7
|
+
* - scaffold_cloud_skill: High (writes persistent skill source code)
|
|
8
|
+
* - delete_cloud_skill: High (removes persistent skill source code)
|
|
9
9
|
*
|
|
10
10
|
* Gateway adaptation: accepts pre-resolved skill metadata via
|
|
11
11
|
* SkillClassifierInput.resolvedMetadata instead of importing the assistant's
|
|
@@ -50,7 +50,7 @@ export interface ResolvedSkillMetadata {
|
|
|
50
50
|
/** Input to the skill risk classifier. */
|
|
51
51
|
export interface SkillClassifierInput {
|
|
52
52
|
/** Which skill tool is being invoked. */
|
|
53
|
-
toolName: "skill_load" | "
|
|
53
|
+
toolName: "skill_load" | "scaffold_cloud_skill" | "delete_cloud_skill";
|
|
54
54
|
/** Optional skill selector (e.g. skill name or path). */
|
|
55
55
|
skillSelector?: string;
|
|
56
56
|
/**
|
|
@@ -134,12 +134,12 @@ function buildSkillLoadAllowlistOptions(
|
|
|
134
134
|
/**
|
|
135
135
|
* Build allowlist options for scaffold/delete managed skill tools.
|
|
136
136
|
*/
|
|
137
|
-
function
|
|
137
|
+
function buildCloudSkillAllowlistOptions(
|
|
138
138
|
toolName: string,
|
|
139
139
|
skillId?: string,
|
|
140
140
|
): AllowlistOption[] {
|
|
141
141
|
const toolLabel =
|
|
142
|
-
toolName === "
|
|
142
|
+
toolName === "scaffold_cloud_skill" ? "scaffold" : "delete";
|
|
143
143
|
const options: AllowlistOption[] = [];
|
|
144
144
|
if (skillId) {
|
|
145
145
|
options.push({
|
|
@@ -200,25 +200,25 @@ export class SkillLoadRiskClassifier implements RiskClassifier<SkillClassifierIn
|
|
|
200
200
|
};
|
|
201
201
|
break;
|
|
202
202
|
}
|
|
203
|
-
case "
|
|
203
|
+
case "scaffold_cloud_skill":
|
|
204
204
|
assessment = {
|
|
205
205
|
riskLevel: "high",
|
|
206
206
|
reason: "Skill scaffold — writes persistent skill source code",
|
|
207
207
|
scopeOptions: [],
|
|
208
208
|
matchType: "registry",
|
|
209
|
-
allowlistOptions:
|
|
209
|
+
allowlistOptions: buildCloudSkillAllowlistOptions(
|
|
210
210
|
toolName,
|
|
211
211
|
skillSelector,
|
|
212
212
|
),
|
|
213
213
|
};
|
|
214
214
|
break;
|
|
215
|
-
case "
|
|
215
|
+
case "delete_cloud_skill":
|
|
216
216
|
assessment = {
|
|
217
217
|
riskLevel: "high",
|
|
218
218
|
reason: "Skill delete — removes persistent skill source code",
|
|
219
219
|
scopeOptions: [],
|
|
220
220
|
matchType: "registry",
|
|
221
|
-
allowlistOptions:
|
|
221
|
+
allowlistOptions: buildCloudSkillAllowlistOptions(
|
|
222
222
|
toolName,
|
|
223
223
|
skillSelector,
|
|
224
224
|
),
|
|
@@ -227,7 +227,7 @@ export class SkillLoadRiskClassifier implements RiskClassifier<SkillClassifierIn
|
|
|
227
227
|
}
|
|
228
228
|
|
|
229
229
|
// User override is applied after normal classification. This means a user-defined
|
|
230
|
-
// rule CAN lower a security-escalated risk (e.g.,
|
|
230
|
+
// rule CAN lower a security-escalated risk (e.g., scaffold_cloud_skill).
|
|
231
231
|
// This is intentional — user overrides are authoritative for users who explicitly
|
|
232
232
|
// created them. Uses resolved skillId from metadata (when available), and
|
|
233
233
|
// skill_load_dynamic as the tool key for dynamic skills.
|
package/src/runtime/client.ts
CHANGED
|
@@ -397,12 +397,12 @@ function extractPlatformCallbackAssistantId(
|
|
|
397
397
|
|
|
398
398
|
function resolveTunnelBaseWssUrl(
|
|
399
399
|
config: GatewayConfig,
|
|
400
|
-
|
|
400
|
+
cloudAssistantId?: string,
|
|
401
401
|
): string | undefined {
|
|
402
|
-
if (!config.tunnelBaseUrl || !
|
|
402
|
+
if (!config.tunnelBaseUrl || !cloudAssistantId) return undefined;
|
|
403
403
|
|
|
404
404
|
const withPath =
|
|
405
|
-
config.tunnelBaseUrl.replace(/\/+$/, "") + "/" +
|
|
405
|
+
config.tunnelBaseUrl.replace(/\/+$/, "") + "/" + cloudAssistantId;
|
|
406
406
|
const normalizedTunnelUrl = normalizePublicBaseUrl(withPath);
|
|
407
407
|
return normalizedTunnelUrl
|
|
408
408
|
? toWebSocketBaseUrl(normalizedTunnelUrl)
|
|
@@ -418,7 +418,7 @@ function resolveTunnelBaseWssUrl(
|
|
|
418
418
|
* 2. `ingress.publicBaseUrl` from the config file when it is a real public
|
|
419
419
|
* gateway base URL — written by Tunnel after tunnel registration, or set
|
|
420
420
|
* manually for self-hosted.
|
|
421
|
-
* 3. `TUNNEL_BASE_URL` + the
|
|
421
|
+
* 3. `TUNNEL_BASE_URL` + the cloud assistant ID from a configured callback
|
|
422
422
|
* URL or credential cache.
|
|
423
423
|
*
|
|
424
424
|
* Returns `undefined` when no source provides a value — the placeholder
|
|
@@ -428,7 +428,7 @@ function resolveTunnelBaseWssUrl(
|
|
|
428
428
|
export function resolvePublicBaseWssUrl(
|
|
429
429
|
config: GatewayConfig,
|
|
430
430
|
configFile?: ConfigFileCache,
|
|
431
|
-
|
|
431
|
+
cloudAssistantId?: string,
|
|
432
432
|
validatedPublicUrl?: string,
|
|
433
433
|
): string | undefined {
|
|
434
434
|
const raw = configFile?.getString("ingress", "publicBaseUrl");
|
|
@@ -450,7 +450,7 @@ export function resolvePublicBaseWssUrl(
|
|
|
450
450
|
|
|
451
451
|
return resolveTunnelBaseWssUrl(
|
|
452
452
|
config,
|
|
453
|
-
configuredCallbackAssistantId ??
|
|
453
|
+
configuredCallbackAssistantId ?? cloudAssistantId,
|
|
454
454
|
);
|
|
455
455
|
}
|
|
456
456
|
|
|
@@ -462,12 +462,12 @@ export function resolvePublicBaseWssUrl(
|
|
|
462
462
|
* Sources (in priority order):
|
|
463
463
|
* 1. `ingress.publicBaseUrl` from the config file — written by Tunnel after
|
|
464
464
|
* tunnel registration, or set manually for self-hosted.
|
|
465
|
-
* 2. `TUNNEL_BASE_URL` + the
|
|
465
|
+
* 2. `TUNNEL_BASE_URL` + the cloud assistant ID. This mirrors the Twilio
|
|
466
466
|
* resolver's fallback and stays valid even while the tunnel is briefly
|
|
467
467
|
* disconnected (Tunnel clears the config value on every reconnect), so
|
|
468
468
|
* these links no longer break on a tunnel flap.
|
|
469
469
|
*
|
|
470
|
-
* The
|
|
470
|
+
* The cloud assistant ID is read through a lazy getter so callers that
|
|
471
471
|
* resolve from the config value (self-hosted / manual URL) never load
|
|
472
472
|
* identity. A transient credential-store outage must not fail those callers.
|
|
473
473
|
*
|
|
@@ -476,14 +476,14 @@ export function resolvePublicBaseWssUrl(
|
|
|
476
476
|
export async function resolvePublicHttpBaseUrl(
|
|
477
477
|
config: GatewayConfig,
|
|
478
478
|
configFile?: ConfigFileCache,
|
|
479
|
-
|
|
479
|
+
getCloudAssistantId?: () => Promise<string | undefined>,
|
|
480
480
|
): Promise<string | undefined> {
|
|
481
481
|
const resolved =
|
|
482
482
|
normalizeHttpPublicBaseUrl(
|
|
483
483
|
configFile?.getString("ingress", "publicBaseUrl"),
|
|
484
484
|
) ??
|
|
485
485
|
normalizeHttpPublicBaseUrl(
|
|
486
|
-
await resolveTunnelHttpFallback(config,
|
|
486
|
+
await resolveTunnelHttpFallback(config, getCloudAssistantId),
|
|
487
487
|
);
|
|
488
488
|
|
|
489
489
|
// Drop a root-path trailing slash so callers can append `/…` without
|
|
@@ -492,18 +492,18 @@ export async function resolvePublicHttpBaseUrl(
|
|
|
492
492
|
}
|
|
493
493
|
|
|
494
494
|
/**
|
|
495
|
-
* Build the `TUNNEL_BASE_URL/<
|
|
496
|
-
* reads the
|
|
495
|
+
* Build the `TUNNEL_BASE_URL/<cloud-assistant-id>` fallback base URL. Only
|
|
496
|
+
* reads the cloud assistant ID (via the getter) when `tunnelBaseUrl` is
|
|
497
497
|
* configured, so it's skipped entirely on self-hosted deployments.
|
|
498
498
|
*/
|
|
499
499
|
async function resolveTunnelHttpFallback(
|
|
500
500
|
config: GatewayConfig,
|
|
501
|
-
|
|
501
|
+
getCloudAssistantId?: () => Promise<string | undefined>,
|
|
502
502
|
): Promise<string | undefined> {
|
|
503
|
-
if (!config.tunnelBaseUrl || !
|
|
504
|
-
const
|
|
505
|
-
if (!
|
|
506
|
-
return config.tunnelBaseUrl.replace(/\/+$/, "") + "/" +
|
|
503
|
+
if (!config.tunnelBaseUrl || !getCloudAssistantId) return undefined;
|
|
504
|
+
const cloudAssistantId = await getCloudAssistantId();
|
|
505
|
+
if (!cloudAssistantId) return undefined;
|
|
506
|
+
return config.tunnelBaseUrl.replace(/\/+$/, "") + "/" + cloudAssistantId;
|
|
507
507
|
}
|
|
508
508
|
|
|
509
509
|
/**
|
package/src/schema.ts
CHANGED
|
@@ -1751,7 +1751,7 @@ export function buildSchema(): Record<string, unknown> {
|
|
|
1751
1751
|
post: {
|
|
1752
1752
|
summary: "Mark a contact channel as verified by guardian attestation",
|
|
1753
1753
|
description:
|
|
1754
|
-
"Guardian-only endpoint that attests a contact channel as verified without exchanging a challenge code. The caller must be the bound guardian — verified either by JWT actor principal (laptop / docker) or by `X-Cadet-User-Id` matching the stored platform user id (
|
|
1754
|
+
"Guardian-only endpoint that attests a contact channel as verified without exchanging a challenge code. The caller must be the bound guardian — verified either by JWT actor principal (laptop / docker) or by `X-Cadet-User-Id` matching the stored platform user id (cloud-managed). Idempotent: an already-verified channel returns 200 without re-writing. Mutation is gateway-DB primary with a best-effort dual-write to the assistant daemon DB for sync during the gateway-security-migration transition.",
|
|
1755
1755
|
operationId: "contactsChannelVerify",
|
|
1756
1756
|
security: [{ BearerAuth: [] }],
|
|
1757
1757
|
parameters: [
|
|
@@ -2407,7 +2407,7 @@ export function buildSchema(): Record<string, unknown> {
|
|
|
2407
2407
|
operationId: "oauthProvidersList",
|
|
2408
2408
|
parameters: [
|
|
2409
2409
|
{
|
|
2410
|
-
name: "
|
|
2410
|
+
name: "supports_cloud_mode",
|
|
2411
2411
|
in: "query",
|
|
2412
2412
|
required: false,
|
|
2413
2413
|
schema: { type: "boolean" },
|
|
@@ -2509,7 +2509,7 @@ export function buildSchema(): Record<string, unknown> {
|
|
|
2509
2509
|
post: {
|
|
2510
2510
|
summary: "Create OAuth app",
|
|
2511
2511
|
description:
|
|
2512
|
-
"Authenticated gateway endpoint that creates or updates a user-
|
|
2512
|
+
"Authenticated gateway endpoint that creates or updates a user-cloud OAuth app by proxying to the assistant runtime.",
|
|
2513
2513
|
operationId: "oauthAppsCreate",
|
|
2514
2514
|
security: [{ BearerAuth: [] }],
|
|
2515
2515
|
requestBody: {
|
|
@@ -2546,7 +2546,7 @@ export function buildSchema(): Record<string, unknown> {
|
|
|
2546
2546
|
delete: {
|
|
2547
2547
|
summary: "Delete OAuth app",
|
|
2548
2548
|
description:
|
|
2549
|
-
"Authenticated gateway endpoint that deletes a user-
|
|
2549
|
+
"Authenticated gateway endpoint that deletes a user-cloud OAuth app and disconnects its linked accounts by proxying to the assistant runtime.",
|
|
2550
2550
|
operationId: "oauthAppsDelete",
|
|
2551
2551
|
parameters: [
|
|
2552
2552
|
{
|
|
@@ -4346,7 +4346,7 @@ export function buildSchema(): Record<string, unknown> {
|
|
|
4346
4346
|
post: {
|
|
4347
4347
|
summary: "Auto-verify guardian email",
|
|
4348
4348
|
description:
|
|
4349
|
-
"Called by the
|
|
4349
|
+
"Called by the cloud to auto-verify the guardian's email channel. For BYO providers (resend, mailgun), validates the provider API key. For cloud-managed email (cadet), the route's auth is sufficient. Creates a guardian email channel binding.",
|
|
4350
4350
|
operationId: "inboundRegister",
|
|
4351
4351
|
security: [{ BearerAuth: [] }],
|
|
4352
4352
|
requestBody: {
|