@mycadet/cli 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 +2 -2
- package/knip.json +1 -1
- package/node_modules/@mycadet/assistant-api/events/cloud-disconnected.ts +21 -0
- package/node_modules/@mycadet/assistant-api/events/host-bash.ts +1 -1
- package/node_modules/@mycadet/assistant-api/events/host-cu.ts +1 -1
- package/node_modules/@mycadet/assistant-api/events/host-file.ts +1 -1
- package/node_modules/@mycadet/assistant-api/events/notification-intent.ts +1 -1
- package/node_modules/@mycadet/assistant-api/events/{show-platform-login.ts → show-cloud-login.ts} +3 -5
- package/node_modules/@mycadet/assistant-api/index.ts +11 -11
- package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/package.json +1 -1
- package/node_modules/@mycadet/{service-contracts/src/platform-credential.ts → assistant-api/node_modules/@mycadet/service-contracts/src/cloud-credential.ts} +9 -9
- package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/handles.ts +3 -3
- package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/index.ts +1 -1
- package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/ingress.ts +11 -5
- package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/remote-web-pairing.ts +1 -1
- package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/rpc.ts +14 -14
- package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/trust-rules.ts +5 -5
- package/node_modules/@mycadet/assistant-api/responses/home.ts +1 -1
- package/node_modules/@mycadet/assistant-api/surfaces.ts +4 -4
- package/node_modules/@mycadet/avatar-manifest/src/__tests__/notification-avatar.test.ts +1 -1
- package/node_modules/@mycadet/avatar-manifest/src/notification-avatar.ts +2 -2
- 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/local-mode/node_modules/@mycadet/avatar-manifest/src/__tests__/notification-avatar.test.ts +1 -1
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/avatar-manifest/src/notification-avatar.ts +2 -2
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/environments/src/seeds.ts +6 -6
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/environments/src/shell.test.ts +1 -1
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/environments/src/types.ts +6 -6
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/package.json +1 -1
- package/node_modules/@mycadet/{assistant-api/node_modules/@mycadet/service-contracts/src/platform-credential.ts → local-mode/node_modules/@mycadet/service-contracts/src/cloud-credential.ts} +9 -9
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/handles.ts +3 -3
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/index.ts +1 -1
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/ingress.ts +11 -5
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/remote-web-pairing.ts +1 -1
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/rpc.ts +14 -14
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/trust-rules.ts +5 -5
- package/node_modules/@mycadet/local-mode/src/__tests__/pair.test.ts +5 -5
- package/node_modules/@mycadet/local-mode/src/__tests__/retire.test.ts +1 -1
- package/node_modules/@mycadet/local-mode/src/config.ts +2 -2
- package/node_modules/@mycadet/local-mode/src/index.ts +1 -1
- package/node_modules/@mycadet/local-mode/src/lockfile-contract.test.ts +3 -3
- package/node_modules/@mycadet/local-mode/src/lockfile-contract.ts +10 -5
- package/node_modules/@mycadet/local-mode/src/lockfile.test.ts +15 -15
- package/node_modules/@mycadet/local-mode/src/lockfile.ts +9 -9
- package/node_modules/@mycadet/local-mode/src/pair.ts +2 -2
- package/node_modules/@mycadet/local-mode/src/retire.ts +3 -3
- package/node_modules/@mycadet/local-mode/src/teleport-backup-policy.ts +2 -2
- package/node_modules/@mycadet/local-mode/src/unpair.ts +2 -2
- package/node_modules/@mycadet/service-contracts/package.json +1 -1
- package/node_modules/@mycadet/{local-mode/node_modules/@mycadet/service-contracts/src/platform-credential.ts → service-contracts/src/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 +3 -2
- package/src/__tests__/assistant-api-key-resolution.test.ts +6 -6
- package/src/__tests__/{assistant-client-platform-auth.test.ts → assistant-client-cloud-auth.test.ts} +10 -10
- package/src/__tests__/assistant-client-refresh.test.ts +2 -2
- package/src/__tests__/backup.test.ts +86 -91
- package/src/__tests__/client-token.test.ts +1 -1
- package/src/__tests__/client-tui-refresh.test.ts +1 -1
- package/src/__tests__/client-web-config.test.ts +2 -2
- package/src/__tests__/{platform-client.test.ts → cloud-client.test.ts} +59 -59
- package/src/__tests__/{platform-releases.test.ts → cloud-releases.test.ts} +5 -5
- package/src/__tests__/{cloudflare-tunnel.test.ts → cloudflare.test.ts} +1 -1
- package/src/__tests__/connect-import.test.ts +55 -39
- package/src/__tests__/debug.test.ts +38 -38
- package/src/__tests__/guardian-token.test.ts +149 -56
- package/src/__tests__/hatch-provider-secrets.test.ts +2 -2
- package/src/__tests__/helpers/guardian-token.ts +22 -0
- package/src/__tests__/helpers/http.ts +10 -0
- package/src/__tests__/nginx-ingress.test.ts +18 -18
- package/src/__tests__/orphan-detection.test.ts +1 -1
- package/src/__tests__/pair.test.ts +1 -8
- package/src/__tests__/{ps-platform-status.test.ts → ps-cloud-status.test.ts} +79 -26
- package/src/__tests__/{recover.test.ts → restore.test.ts} +29 -28
- package/src/__tests__/retire.test.ts +58 -199
- package/src/__tests__/root-process-lockfile.test.ts +99 -0
- package/src/__tests__/sleep.test.ts +19 -19
- package/src/__tests__/statefulset.test.ts +8 -8
- package/src/__tests__/{tailscale-tunnel.test.ts → tailscale.test.ts} +1 -1
- package/src/__tests__/teleport-backup.test.ts +3 -3
- package/src/__tests__/teleport.test.ts +180 -196
- package/src/__tests__/tunnel.test.ts +255 -227
- package/src/__tests__/uninstall.test.ts +13 -4
- package/src/__tests__/upgrade-local.test.ts +3 -3
- package/src/__tests__/upgrade-replay-env.test.ts +12 -12
- package/src/__tests__/wake.test.ts +258 -41
- package/src/__tests__/workos-pkce.test.ts +2 -2
- package/src/commands/backup.ts +22 -22
- package/src/commands/client.ts +76 -76
- package/src/commands/debug.ts +20 -20
- package/src/commands/exec.ts +5 -5
- package/src/commands/hatch.ts +22 -22
- package/src/commands/login.ts +22 -22
- package/src/commands/logs.ts +1 -1
- package/src/commands/ps.ts +188 -79
- package/src/commands/restart.ts +30 -0
- package/src/commands/restore.ts +138 -20
- package/src/commands/retire.ts +46 -148
- package/src/commands/rollback.ts +18 -24
- package/src/commands/setup.ts +2 -2
- package/src/commands/teleport.ts +93 -93
- package/src/commands/terminal.ts +157 -17
- package/src/commands/tunnel.ts +80 -122
- package/src/commands/uninstall.ts +55 -7
- package/src/commands/unpair.ts +3 -3
- package/src/commands/upgrade.ts +22 -46
- package/src/commands/wake.ts +162 -46
- package/src/index.ts +3 -9
- package/src/lib/__tests__/{platform-client-signed-url.test.ts → cloud-client-signed-url.test.ts} +16 -16
- package/src/lib/__tests__/job-polling.test.ts +1 -1
- package/src/lib/__tests__/local-runtime-client.test.ts +4 -4
- package/src/lib/__tests__/runtime-url.test.ts +1 -1
- package/src/lib/assistant-api-key-resolution.ts +5 -5
- package/src/lib/assistant-client.ts +8 -8
- package/src/lib/assistant-config.ts +58 -12
- package/src/lib/{platform-client.ts → cloud-client.ts} +107 -98
- package/src/lib/cloud-login.ts +24 -24
- package/src/lib/{platform-releases.ts → cloud-releases.ts} +5 -5
- package/src/lib/cloud-values.ts +3 -0
- package/src/lib/config-utils.ts +1 -1
- package/src/lib/detached-process.ts +1 -1
- package/src/lib/docker.ts +111 -10
- package/src/lib/environments/__tests__/paths.test.ts +2 -2
- package/src/lib/environments/__tests__/resolve.test.ts +26 -26
- package/src/lib/environments/resolve.ts +8 -8
- package/src/lib/guardian-token.ts +36 -4
- package/src/lib/hatch-local.ts +19 -23
- package/src/lib/health-check.ts +6 -6
- package/src/lib/ingress-config.ts +8 -35
- package/src/lib/job-polling.ts +1 -1
- package/src/lib/live-voice/connection.ts +12 -12
- package/src/lib/live-voice/tunnel.test.ts +11 -11
- package/src/lib/live-voice/tunnel.ts +15 -15
- package/src/lib/local-runtime-client.ts +20 -20
- package/src/lib/local.ts +9 -30
- package/src/lib/nginx-ingress.ts +41 -14
- package/src/lib/process.ts +1 -1
- package/src/lib/provider-secrets.ts +3 -3
- package/src/lib/retire-apple-container.ts +1 -1
- package/src/lib/runtime-url.ts +8 -8
- package/src/lib/statefulset.ts +2 -2
- package/src/lib/sync-cloud-assistants.ts +18 -18
- package/src/lib/teleport-backup.ts +4 -4
- package/src/lib/terminal-client.ts +17 -17
- package/src/lib/terminal-session.ts +24 -24
- package/src/lib/{tunnel-edge.ts → tunnel.ts} +199 -11
- package/src/lib/tunnels/cadet.ts +29 -0
- package/src/lib/{cloudflare-tunnel.ts → tunnels/cloudflare.ts} +15 -4
- package/src/lib/tunnels/gateway-notify.ts +61 -0
- package/src/lib/{ngrok.ts → tunnels/ngrok.ts} +201 -30
- package/src/lib/{tailscale-tunnel.ts → tunnels/tailscale.ts} +19 -5
- package/src/lib/upgrade-lifecycle.ts +3 -17
- package/src/lib/upgrade-preflight.ts +2 -2
- package/src/lib/workos-pkce.ts +5 -5
- package/node_modules/@mycadet/assistant-api/events/platform-disconnected.ts +0 -21
- package/src/commands/recover.ts +0 -131
- package/src/commands/roadmap.ts +0 -459
- package/src/commands/ssh.ts +0 -144
- package/src/commands/workflows.ts +0 -305
- package/src/lib/managed-cloud.ts +0 -3
package/src/lib/hatch-local.ts
CHANGED
|
@@ -32,7 +32,10 @@ import {
|
|
|
32
32
|
} from "./local.js";
|
|
33
33
|
|
|
34
34
|
import { generateInstanceName } from "./random-name.js";
|
|
35
|
-
import {
|
|
35
|
+
import {
|
|
36
|
+
GuardianLeasePermanentError,
|
|
37
|
+
leaseGuardianToken,
|
|
38
|
+
} from "./guardian-token.js";
|
|
36
39
|
import { archiveLogFile, resetLogFile } from "./xdg-log.js";
|
|
37
40
|
import {
|
|
38
41
|
consoleLifecycleReporter,
|
|
@@ -54,7 +57,7 @@ import {
|
|
|
54
57
|
ensureCloudLoginInteractive,
|
|
55
58
|
registerLocalAssistantWithPlatform,
|
|
56
59
|
} from "./cloud-login.js";
|
|
57
|
-
import { fetchCurrentUser,
|
|
60
|
+
import { fetchCurrentUser, readCloudToken } from "./cloud-client.js";
|
|
58
61
|
import { withLocalHatchLock } from "./local-hatch-lock.js";
|
|
59
62
|
|
|
60
63
|
/**
|
|
@@ -185,8 +188,6 @@ export interface HatchLocalResult {
|
|
|
185
188
|
guardianAccessToken?: string;
|
|
186
189
|
}
|
|
187
190
|
|
|
188
|
-
|
|
189
|
-
|
|
190
191
|
export async function hatchLocal(
|
|
191
192
|
params: HatchLocalParams = {},
|
|
192
193
|
): Promise<HatchLocalResult> {
|
|
@@ -199,8 +200,7 @@ export async function hatchLocal(
|
|
|
199
200
|
} = params;
|
|
200
201
|
const reporter = params.reporter ?? consoleLifecycleReporter;
|
|
201
202
|
const verbose = params.verbose ?? false;
|
|
202
|
-
const setupProviderCredentials =
|
|
203
|
-
params.setupProviderCredentials !== false;
|
|
203
|
+
const setupProviderCredentials = params.setupProviderCredentials !== false;
|
|
204
204
|
setStartupLogging(verbose);
|
|
205
205
|
let provider = setupProviderCredentials
|
|
206
206
|
? resolveHatchProvider(configValues)
|
|
@@ -226,12 +226,7 @@ export async function hatchLocal(
|
|
|
226
226
|
|
|
227
227
|
const resources = await allocateLocalResources(instanceName);
|
|
228
228
|
|
|
229
|
-
const logsDir = join(
|
|
230
|
-
resources.instanceDir,
|
|
231
|
-
"workspace",
|
|
232
|
-
"data",
|
|
233
|
-
"logs",
|
|
234
|
-
);
|
|
229
|
+
const logsDir = join(resources.instanceDir, "workspace", "data", "logs");
|
|
235
230
|
archiveLogFile("hatch.log", logsDir);
|
|
236
231
|
resetLogFile("hatch.log");
|
|
237
232
|
|
|
@@ -263,7 +258,7 @@ export async function hatchLocal(
|
|
|
263
258
|
picked === "cadet" &&
|
|
264
259
|
!process.env.CADET_DESKTOP_APP &&
|
|
265
260
|
process.stdin.isTTY &&
|
|
266
|
-
!
|
|
261
|
+
!readCloudToken()
|
|
267
262
|
) {
|
|
268
263
|
reporter.log("");
|
|
269
264
|
reporter.log(
|
|
@@ -276,9 +271,7 @@ export async function hatchLocal(
|
|
|
276
271
|
reporter.warn(
|
|
277
272
|
`⚠️ Cadet Cloud login failed: ${describeLoginError(error)}`,
|
|
278
273
|
);
|
|
279
|
-
reporter.warn(
|
|
280
|
-
" You can retry later with `cadet login`.",
|
|
281
|
-
);
|
|
274
|
+
reporter.warn(" You can retry later with `cadet login`.");
|
|
282
275
|
}
|
|
283
276
|
}
|
|
284
277
|
}
|
|
@@ -380,7 +373,10 @@ export async function hatchLocal(
|
|
|
380
373
|
guardianAccessToken = tokenData.accessToken;
|
|
381
374
|
break;
|
|
382
375
|
} catch (err) {
|
|
383
|
-
if (
|
|
376
|
+
if (
|
|
377
|
+
attempt < maxLeaseAttempts &&
|
|
378
|
+
!(err instanceof GuardianLeasePermanentError)
|
|
379
|
+
) {
|
|
384
380
|
const delayMs = 2000 * 2 ** (attempt - 1);
|
|
385
381
|
reporter.error(
|
|
386
382
|
`⚠️ Guardian token lease attempt ${attempt}/${maxLeaseAttempts} failed — retrying in ${delayMs / 1000}s: ${err}`,
|
|
@@ -388,9 +384,9 @@ export async function hatchLocal(
|
|
|
388
384
|
await new Promise((r) => setTimeout(r, delayMs));
|
|
389
385
|
} else {
|
|
390
386
|
reporter.error(
|
|
391
|
-
`⚠️ Guardian token lease failed after ${maxLeaseAttempts} attempts: ${err}\n` +
|
|
387
|
+
`⚠️ Guardian token lease failed${err instanceof GuardianLeasePermanentError ? " (not retrying — a malformed 200 will not fix itself)" : ` after ${maxLeaseAttempts} attempts`}: ${err}\n` +
|
|
392
388
|
` The assistant is running but guardian-token.json was not written.\n` +
|
|
393
|
-
`
|
|
389
|
+
` Run \`cadet restart ${JSON.stringify(instanceName)} --repair-guardian\` to re-lease a token.`,
|
|
394
390
|
);
|
|
395
391
|
}
|
|
396
392
|
}
|
|
@@ -415,13 +411,13 @@ export async function hatchLocal(
|
|
|
415
411
|
});
|
|
416
412
|
}
|
|
417
413
|
|
|
418
|
-
// Picking Cadet Cloud ends with the assistant registered on the
|
|
414
|
+
// Picking Cadet Cloud ends with the assistant registered on the cloud
|
|
419
415
|
// and its cloud credentials injected — the same post-login flow
|
|
420
|
-
// `cadet login` runs — so messages route through the
|
|
416
|
+
// `cadet login` runs — so messages route through the cloud connection
|
|
421
417
|
// immediately after hatch. Non-fatal: the assistant is up either way.
|
|
422
|
-
if (provider === "cadet" &&
|
|
418
|
+
if (provider === "cadet" && readCloudToken()) {
|
|
423
419
|
try {
|
|
424
|
-
const token =
|
|
420
|
+
const token = readCloudToken()!;
|
|
425
421
|
const user = await fetchCurrentUser(token);
|
|
426
422
|
await registerLocalAssistantWithPlatform(token, user, {
|
|
427
423
|
bearerToken: guardianAccessToken,
|
package/src/lib/health-check.ts
CHANGED
|
@@ -19,8 +19,8 @@ export async function checkManagedHealth(
|
|
|
19
19
|
runtimeUrl: string,
|
|
20
20
|
assistantId: string,
|
|
21
21
|
): Promise<HealthCheckResult> {
|
|
22
|
-
const {
|
|
23
|
-
const token =
|
|
22
|
+
const { readCloudToken } = await import("./cloud-client.js");
|
|
23
|
+
const token = readCloudToken();
|
|
24
24
|
if (!token) {
|
|
25
25
|
return {
|
|
26
26
|
status: "error (auth)",
|
|
@@ -30,7 +30,7 @@ export async function checkManagedHealth(
|
|
|
30
30
|
|
|
31
31
|
let headers: Record<string, string>;
|
|
32
32
|
try {
|
|
33
|
-
const { authHeaders } = await import("./
|
|
33
|
+
const { authHeaders } = await import("./cloud-client.js");
|
|
34
34
|
headers = await authHeaders(token, runtimeUrl);
|
|
35
35
|
} catch (err) {
|
|
36
36
|
return {
|
|
@@ -130,9 +130,9 @@ export async function fetchManagedPs(
|
|
|
130
130
|
runtimeUrl: string,
|
|
131
131
|
assistantId: string,
|
|
132
132
|
): Promise<ManagedPsResponse | null> {
|
|
133
|
-
const {
|
|
134
|
-
await import("./
|
|
135
|
-
const token =
|
|
133
|
+
const { readCloudToken, authHeaders } =
|
|
134
|
+
await import("./cloud-client.js");
|
|
135
|
+
const token = readCloudToken();
|
|
136
136
|
if (!token) return null;
|
|
137
137
|
|
|
138
138
|
let headers: Record<string, string>;
|
|
@@ -18,14 +18,13 @@ import { getStateDir } from "./environments/paths.js";
|
|
|
18
18
|
import { getCurrentEnvironment } from "./environments/resolve.js";
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
|
-
* Shared workspace-config helpers for tunnel providers
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* no-`.cadet/`-reads boundary in cli/AGENTS.md.
|
|
21
|
+
* Shared workspace-config helpers for tunnel providers and the nginx ingress
|
|
22
|
+
* proxy. Each provider fronts the local edge and records the resulting public
|
|
23
|
+
* URL under `ingress.publicBaseUrl` so webhook integrations can reach the
|
|
24
|
+
* assistant. The workspace config is the gateway-facing contract; when an
|
|
25
|
+
* `assistantId` is supplied, the URL is also mirrored onto the lockfile entry
|
|
26
|
+
* (`ingressUrl`) — the CLI-owned contract that CLI features (e.g. remote-web
|
|
27
|
+
* pairing defaults) read, per the no-`.cadet/`-reads boundary in cli/AGENTS.md.
|
|
29
28
|
*/
|
|
30
29
|
|
|
31
30
|
function parsePortFromUrl(url: unknown): number | undefined {
|
|
@@ -48,7 +47,7 @@ export function isLocalContainerEntry(entry: AssistantEntry): boolean {
|
|
|
48
47
|
/**
|
|
49
48
|
* Derive the gateway port from an entry's recorded URLs, preferring the
|
|
50
49
|
* loopback `localUrl` over `runtimeUrl`. Undefined when neither carries an
|
|
51
|
-
* explicit port (e.g. a
|
|
50
|
+
* explicit port (e.g. a cloud-hosted https runtime URL).
|
|
52
51
|
*/
|
|
53
52
|
export function parseGatewayPortFromEntryUrls(
|
|
54
53
|
entry: AssistantEntry | undefined,
|
|
@@ -186,32 +185,6 @@ export function savePairingTunnel(
|
|
|
186
185
|
saveRawConfig(workspaceDir, config);
|
|
187
186
|
}
|
|
188
187
|
|
|
189
|
-
/** Persist a reserved ngrok domain under `ingress.ngrok.domain`; null clears it. */
|
|
190
|
-
export function saveNgrokDomain(
|
|
191
|
-
workspaceDir: string,
|
|
192
|
-
domain: string | null,
|
|
193
|
-
): void {
|
|
194
|
-
const config = loadRawConfig(workspaceDir);
|
|
195
|
-
const ingress = (config.ingress ?? {}) as Record<string, unknown>;
|
|
196
|
-
if (domain) {
|
|
197
|
-
ingress.ngrok = { domain };
|
|
198
|
-
} else {
|
|
199
|
-
delete ingress.ngrok;
|
|
200
|
-
}
|
|
201
|
-
config.ingress = ingress;
|
|
202
|
-
saveRawConfig(workspaceDir, config);
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
/** Read the reserved ngrok domain from the workspace config, if saved. */
|
|
206
|
-
export function loadNgrokDomain(workspaceDir: string): string | null {
|
|
207
|
-
const ingress = loadRawConfig(workspaceDir).ingress as
|
|
208
|
-
| Record<string, unknown>
|
|
209
|
-
| undefined;
|
|
210
|
-
const ngrok = ingress?.ngrok as Record<string, unknown> | undefined;
|
|
211
|
-
const domain = ngrok?.domain;
|
|
212
|
-
return typeof domain === "string" && domain.trim() ? domain : null;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
188
|
/** Clear the ingress public base URL from the workspace config. */
|
|
216
189
|
export function clearIngressUrl(
|
|
217
190
|
workspaceDir: string,
|
package/src/lib/job-polling.ts
CHANGED
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
* passes by construction, and there is nothing new to provision here.
|
|
26
26
|
*/
|
|
27
27
|
|
|
28
|
-
import {
|
|
28
|
+
import { isCloudHome } from "../cloud-values.js";
|
|
29
29
|
import {
|
|
30
30
|
formatAssistantLookupError,
|
|
31
31
|
formatAssistantReference,
|
|
@@ -33,7 +33,7 @@ import {
|
|
|
33
33
|
resolveTargetAssistant,
|
|
34
34
|
type AssistantEntry,
|
|
35
35
|
} from "../assistant-config.js";
|
|
36
|
-
import {
|
|
36
|
+
import { getCloudUrl, readCloudToken } from "../cloud-client.js";
|
|
37
37
|
import {
|
|
38
38
|
guardianTokenDueForRenewal,
|
|
39
39
|
loadGuardianToken,
|
|
@@ -88,11 +88,11 @@ export async function resolveLiveVoiceConnection(
|
|
|
88
88
|
? resolveNamedAssistant(assistantIdArg)
|
|
89
89
|
: resolveTargetAssistant();
|
|
90
90
|
|
|
91
|
-
//
|
|
91
|
+
// Cloud assistants are reached the other way round. Their gateway
|
|
92
92
|
// is behind tunnel, which authenticates nobody and forwards an attestation
|
|
93
93
|
// the gateway trusts, so the credential is not the guardian JWT below but a
|
|
94
|
-
// WS token minted from the
|
|
95
|
-
if (
|
|
94
|
+
// WS token minted from the cloud session the CLI already holds.
|
|
95
|
+
if (isCloudHome(entry.cloud)) {
|
|
96
96
|
return await resolveCloudConnection(entry);
|
|
97
97
|
}
|
|
98
98
|
|
|
@@ -126,7 +126,7 @@ export async function resolveLiveVoiceConnection(
|
|
|
126
126
|
}
|
|
127
127
|
|
|
128
128
|
/**
|
|
129
|
-
* Resolve a live-voice connection to a
|
|
129
|
+
* Resolve a live-voice connection to a cloud-hosted assistant.
|
|
130
130
|
*
|
|
131
131
|
* The session token is the CLI's own cloud login, not anything specific to
|
|
132
132
|
* this assistant, so a missing one is a `cadet login` problem rather than an
|
|
@@ -139,7 +139,7 @@ export async function resolveLiveVoiceConnection(
|
|
|
139
139
|
async function resolveCloudConnection(
|
|
140
140
|
entry: AssistantEntry,
|
|
141
141
|
): Promise<LiveVoiceConnection> {
|
|
142
|
-
const sessionToken =
|
|
142
|
+
const sessionToken = readCloudToken();
|
|
143
143
|
if (!sessionToken) {
|
|
144
144
|
throw new LiveVoiceConnectionError(
|
|
145
145
|
`${formatAssistantReference(entry)} is a Cadet cloud assistant, which ` +
|
|
@@ -147,10 +147,10 @@ async function resolveCloudConnection(
|
|
|
147
147
|
);
|
|
148
148
|
}
|
|
149
149
|
|
|
150
|
-
// `runtimeUrl` is the
|
|
151
|
-
// one that can mint for it. `
|
|
150
|
+
// `runtimeUrl` is the cloud this entry was hatched against, which is the
|
|
151
|
+
// one that can mint for it. `getCloudUrl()` is the fallback for an entry
|
|
152
152
|
// written before that field was recorded.
|
|
153
|
-
const
|
|
153
|
+
const cloudUrl = (entry.runtimeUrl || getCloudUrl()).replace(
|
|
154
154
|
/\/+$/,
|
|
155
155
|
"",
|
|
156
156
|
);
|
|
@@ -158,7 +158,7 @@ async function resolveCloudConnection(
|
|
|
158
158
|
let token: string;
|
|
159
159
|
try {
|
|
160
160
|
token = await mintTunnelWsToken({
|
|
161
|
-
|
|
161
|
+
cloudUrl,
|
|
162
162
|
assistantId: entry.assistantId,
|
|
163
163
|
sessionToken,
|
|
164
164
|
});
|
|
@@ -171,7 +171,7 @@ async function resolveCloudConnection(
|
|
|
171
171
|
|
|
172
172
|
return {
|
|
173
173
|
url: buildTunnelLiveVoiceUrl({
|
|
174
|
-
|
|
174
|
+
cloudUrl,
|
|
175
175
|
assistantId: entry.assistantId,
|
|
176
176
|
token,
|
|
177
177
|
}),
|
|
@@ -2,7 +2,7 @@ import { afterEach, describe, expect, test } from "bun:test";
|
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
buildTunnelLiveVoiceUrl,
|
|
5
|
-
|
|
5
|
+
tunnelHostForCloudUrl,
|
|
6
6
|
tunnelWsScheme,
|
|
7
7
|
} from "./tunnel.js";
|
|
8
8
|
|
|
@@ -16,13 +16,13 @@ afterEach(() => {
|
|
|
16
16
|
}
|
|
17
17
|
});
|
|
18
18
|
|
|
19
|
-
describe("
|
|
20
|
-
test("maps a
|
|
19
|
+
describe("tunnelHostForCloudUrl", () => {
|
|
20
|
+
test("maps a cloud host onto its environment's tunnel", () => {
|
|
21
21
|
delete process.env.CADET_TUNNEL_HOST;
|
|
22
|
-
expect(
|
|
22
|
+
expect(tunnelHostForCloudUrl("https://cloud.mycadet.ai")).toBe(
|
|
23
23
|
"tunnel.mycadet.ai",
|
|
24
24
|
);
|
|
25
|
-
expect(
|
|
25
|
+
expect(tunnelHostForCloudUrl("https://dev-cloud.mycadet.ai")).toBe(
|
|
26
26
|
"tunnel-dev.mycadet.ai",
|
|
27
27
|
);
|
|
28
28
|
});
|
|
@@ -31,15 +31,15 @@ describe("tunnelHostForPlatformUrl", () => {
|
|
|
31
31
|
delete process.env.CADET_TUNNEL_HOST;
|
|
32
32
|
// A local platform predicts no tunnel of its own; guessing one would be
|
|
33
33
|
// worse than dialing the host that certainly exists.
|
|
34
|
-
expect(
|
|
34
|
+
expect(tunnelHostForCloudUrl("http://localhost:8000")).toBe(
|
|
35
35
|
"tunnel.mycadet.ai",
|
|
36
36
|
);
|
|
37
|
-
expect(
|
|
37
|
+
expect(tunnelHostForCloudUrl("not a url")).toBe("tunnel.mycadet.ai");
|
|
38
38
|
});
|
|
39
39
|
|
|
40
40
|
test("the env override wins, which is how a local vel up tunnel is reached", () => {
|
|
41
41
|
process.env.CADET_TUNNEL_HOST = "localhost:8501";
|
|
42
|
-
expect(
|
|
42
|
+
expect(tunnelHostForCloudUrl("https://cloud.mycadet.ai")).toBe(
|
|
43
43
|
"localhost:8501",
|
|
44
44
|
);
|
|
45
45
|
});
|
|
@@ -58,7 +58,7 @@ describe("buildTunnelLiveVoiceUrl", () => {
|
|
|
58
58
|
delete process.env.CADET_TUNNEL_HOST;
|
|
59
59
|
const url = new URL(
|
|
60
60
|
buildTunnelLiveVoiceUrl({
|
|
61
|
-
|
|
61
|
+
cloudUrl: "https://cloud.mycadet.ai",
|
|
62
62
|
assistantId: "01a02514-e452-7019-88d5-30828f42cf17",
|
|
63
63
|
token: "tok_abc",
|
|
64
64
|
}),
|
|
@@ -76,7 +76,7 @@ describe("buildTunnelLiveVoiceUrl", () => {
|
|
|
76
76
|
delete process.env.CADET_TUNNEL_HOST;
|
|
77
77
|
const url = new URL(
|
|
78
78
|
buildTunnelLiveVoiceUrl({
|
|
79
|
-
|
|
79
|
+
cloudUrl: "https://cloud.mycadet.ai",
|
|
80
80
|
assistantId: "a1",
|
|
81
81
|
token: "a+b/c=&d",
|
|
82
82
|
}),
|
|
@@ -88,7 +88,7 @@ describe("buildTunnelLiveVoiceUrl", () => {
|
|
|
88
88
|
process.env.CADET_TUNNEL_HOST = "localhost:8501";
|
|
89
89
|
const url = new URL(
|
|
90
90
|
buildTunnelLiveVoiceUrl({
|
|
91
|
-
|
|
91
|
+
cloudUrl: "http://localhost:8000",
|
|
92
92
|
assistantId: "a1",
|
|
93
93
|
token: "t",
|
|
94
94
|
}),
|
|
@@ -2,17 +2,17 @@
|
|
|
2
2
|
* The cloud leg of a live-voice session: minting a tunnel WS token and building
|
|
3
3
|
* the URL it opens.
|
|
4
4
|
*
|
|
5
|
-
* A
|
|
5
|
+
* A cloud-hosted assistant is not reachable the way a local one is. Its
|
|
6
6
|
* gateway sits behind tunnel, tunnel performs no user authentication of its own,
|
|
7
7
|
* and the gateway upstream accepts only an actor edge JWT or a tunnel
|
|
8
8
|
* attestation. The CLI holds neither for a cloud instance: what it has is a
|
|
9
9
|
* platform session token, which is the wrong credential for the socket but the
|
|
10
|
-
* right one for the
|
|
10
|
+
* right one for the cloud.
|
|
11
11
|
*
|
|
12
12
|
* So the connection is made in two hops, the same two the web voice room makes
|
|
13
13
|
* (`clients/web/src/domains/chat/voice/live-voice/connection.ts`):
|
|
14
14
|
*
|
|
15
|
-
* 1. POST the
|
|
15
|
+
* 1. POST the cloud for a short-lived, org+assistant-scoped WS token.
|
|
16
16
|
* 2. Open `wss://<tunnel>/<assistantId>/v1/live-voice?token=<wsToken>`.
|
|
17
17
|
*
|
|
18
18
|
* tunnel consumes the token on the upgrade and injects the authenticated user
|
|
@@ -30,9 +30,9 @@
|
|
|
30
30
|
|
|
31
31
|
import { tunnelHostForPlatformHost } from "@mycadet/service-contracts/ingress";
|
|
32
32
|
|
|
33
|
-
import { authHeaders } from "../
|
|
33
|
+
import { authHeaders } from "../cloud-client.js";
|
|
34
34
|
|
|
35
|
-
/** Production tunnel, when the
|
|
35
|
+
/** Production tunnel, when the cloud host implies nothing more specific. */
|
|
36
36
|
const DEFAULT_TUNNEL_HOST = "tunnel.mycadet.ai";
|
|
37
37
|
|
|
38
38
|
/** Give up on the mint rather than hang the session behind a stalled platform. */
|
|
@@ -53,20 +53,20 @@ export class TunnelWsTokenError extends Error {
|
|
|
53
53
|
*
|
|
54
54
|
* `CADET_TUNNEL_HOST` wins when set, which is how a local `vel up` stack is
|
|
55
55
|
* reached: its tunnel is a loopback port that no naming convention predicts.
|
|
56
|
-
* Otherwise the deployment convention maps the
|
|
56
|
+
* Otherwise the deployment convention maps the cloud host onto its sibling
|
|
57
57
|
* (`cloud.mycadet.ai` to `tunnel.mycadet.ai`, `dev-cloud.mycadet.ai` to
|
|
58
58
|
* `tunnel-dev.mycadet.ai`). A host outside that convention, including localhost,
|
|
59
59
|
* yields the production default, because guessing a tunnel for it would be
|
|
60
60
|
* worse than dialing the one host that certainly exists.
|
|
61
61
|
*/
|
|
62
|
-
export function
|
|
62
|
+
export function tunnelHostForCloudUrl(cloudUrl: string): string {
|
|
63
63
|
const override = process.env.CADET_TUNNEL_HOST?.trim();
|
|
64
64
|
if (override) {
|
|
65
65
|
return override;
|
|
66
66
|
}
|
|
67
67
|
try {
|
|
68
68
|
return (
|
|
69
|
-
tunnelHostForPlatformHost(new URL(
|
|
69
|
+
tunnelHostForPlatformHost(new URL(cloudUrl).host) ??
|
|
70
70
|
DEFAULT_TUNNEL_HOST
|
|
71
71
|
);
|
|
72
72
|
} catch {
|
|
@@ -106,11 +106,11 @@ export function tunnelWsScheme(host: string): "ws" | "wss" {
|
|
|
106
106
|
* credential anywhere tunnel reads except the query string.
|
|
107
107
|
*/
|
|
108
108
|
export function buildTunnelLiveVoiceUrl(args: {
|
|
109
|
-
|
|
109
|
+
cloudUrl: string;
|
|
110
110
|
assistantId: string;
|
|
111
111
|
token: string;
|
|
112
112
|
}): string {
|
|
113
|
-
const host =
|
|
113
|
+
const host = tunnelHostForCloudUrl(args.cloudUrl);
|
|
114
114
|
const url = new URL(
|
|
115
115
|
`${tunnelWsScheme(host)}://${host}/${args.assistantId}/v1/live-voice`,
|
|
116
116
|
);
|
|
@@ -131,22 +131,22 @@ interface LiveVoiceTokenResponse {
|
|
|
131
131
|
* (`SessionAuthentication` and `XSessionTokenAuthentication`) and deliberately
|
|
132
132
|
* excludes API-key auth, so `X-Session-Token` is the CLI's way in and a
|
|
133
133
|
* `vak_` key is not: a caller holding only an API key cannot mint here, by
|
|
134
|
-
* design on the
|
|
134
|
+
* design on the cloud's side.
|
|
135
135
|
*
|
|
136
136
|
* Throws {@link TunnelWsTokenError} carrying the HTTP status, so the caller can
|
|
137
|
-
* tell "sign in again" (401) from "not your assistant" (403) from a
|
|
137
|
+
* tell "sign in again" (401) from "not your assistant" (403) from a cloud
|
|
138
138
|
* that is simply down.
|
|
139
139
|
*/
|
|
140
140
|
export async function mintTunnelWsToken(args: {
|
|
141
|
-
|
|
141
|
+
cloudUrl: string;
|
|
142
142
|
assistantId: string;
|
|
143
143
|
sessionToken: string;
|
|
144
144
|
}): Promise<string> {
|
|
145
|
-
const url = `${args.
|
|
145
|
+
const url = `${args.cloudUrl.replace(/\/+$/, "")}/v1/auth/live-voice-token/`;
|
|
146
146
|
// `authHeaders` resolves `Cadet-Organization-Id`, which the endpoint needs
|
|
147
147
|
// to scope the token, and which is cached per token so a session does not
|
|
148
148
|
// pay for an extra round trip.
|
|
149
|
-
const headers = await authHeaders(args.sessionToken, args.
|
|
149
|
+
const headers = await authHeaders(args.sessionToken, args.cloudUrl);
|
|
150
150
|
|
|
151
151
|
let response: Response;
|
|
152
152
|
try {
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { isCloudHome } from "./cloud-values.js";
|
|
2
2
|
import type { AssistantEntry } from "./assistant-config.js";
|
|
3
3
|
import {
|
|
4
4
|
authHeaders,
|
|
5
5
|
invalidateOrgIdCache,
|
|
6
6
|
parseUnifiedJobStatus,
|
|
7
7
|
type UnifiedJobStatus,
|
|
8
|
-
} from "./
|
|
8
|
+
} from "./cloud-client.js";
|
|
9
9
|
import {
|
|
10
10
|
resolveRuntimeMigrationUrl,
|
|
11
11
|
resolveRuntimeUrl,
|
|
@@ -49,7 +49,7 @@ function bearerHeaders(token: string): Record<string, string> {
|
|
|
49
49
|
/**
|
|
50
50
|
* Build the auth + content headers for a runtime migration request.
|
|
51
51
|
*
|
|
52
|
-
* - For `
|
|
52
|
+
* - For `isCloudHome(cloud)` we go through the cloud's wildcard runtime
|
|
53
53
|
* proxy, which authenticates user-session / vak_ tokens via DRF's default
|
|
54
54
|
* authentication classes — `authHeaders()` produces the right combination
|
|
55
55
|
* (`X-Session-Token` + `Cadet-Organization-Id`, or `Authorization: Bearer
|
|
@@ -60,7 +60,7 @@ async function migrationRequestHeaders(
|
|
|
60
60
|
entry: Pick<AssistantEntry, "cloud" | "runtimeUrl">,
|
|
61
61
|
token: string,
|
|
62
62
|
): Promise<Record<string, string>> {
|
|
63
|
-
if (
|
|
63
|
+
if (isCloudHome(entry.cloud)) {
|
|
64
64
|
return {
|
|
65
65
|
...(await authHeaders(token, entry.runtimeUrl)),
|
|
66
66
|
Accept: "application/json",
|
|
@@ -116,10 +116,10 @@ async function throwIfInProgress(
|
|
|
116
116
|
*
|
|
117
117
|
* For local/docker assistants this POSTs to
|
|
118
118
|
* `{runtimeUrl}/v1/migrations/export-to-gcs` with guardian-token bearer
|
|
119
|
-
* auth. For
|
|
119
|
+
* auth. For cloud-managed (cloud="cadet") assistants the URL is rewritten
|
|
120
120
|
* to the wildcard-runtime-proxy shape
|
|
121
|
-
* `{
|
|
122
|
-
* authenticated via the
|
|
121
|
+
* `{cloudUrl}/v1/assistants/<assistantId>/migrations/export-to-gcs` and
|
|
122
|
+
* authenticated via the cloud-token header set the cloud's DRF auth
|
|
123
123
|
* accepts (session / vak_).
|
|
124
124
|
*
|
|
125
125
|
* Returns the 202-accepted `job_id`. On 409 (another export in flight)
|
|
@@ -179,10 +179,10 @@ export async function localRuntimeExportToGcs(
|
|
|
179
179
|
*
|
|
180
180
|
* For local/docker assistants this POSTs to
|
|
181
181
|
* `{runtimeUrl}/v1/migrations/import-from-gcs` with guardian-token bearer
|
|
182
|
-
* auth. For
|
|
182
|
+
* auth. For cloud-managed (cloud="cadet") assistants the URL is rewritten
|
|
183
183
|
* to the wildcard-runtime-proxy shape
|
|
184
|
-
* `{
|
|
185
|
-
* authenticated via the
|
|
184
|
+
* `{cloudUrl}/v1/assistants/<assistantId>/migrations/import-from-gcs` and
|
|
185
|
+
* authenticated via the cloud token. On 409 throws
|
|
186
186
|
* {@link MigrationInProgressError}.
|
|
187
187
|
*/
|
|
188
188
|
export async function localRuntimeImportFromGcs(
|
|
@@ -261,10 +261,10 @@ export async function localRuntimePreflightFromGcs(
|
|
|
261
261
|
*
|
|
262
262
|
* For local/docker assistants this GETs
|
|
263
263
|
* `{runtimeUrl}/v1/migrations/jobs/{jobId}` directly (guardian-token
|
|
264
|
-
* bearer). For
|
|
264
|
+
* bearer). For cloud assistants it routes through the wildcard
|
|
265
265
|
* runtime proxy at
|
|
266
|
-
* `{
|
|
267
|
-
* platform-token auth — important: the
|
|
266
|
+
* `{cloudUrl}/v1/assistants/<assistantId>/migrations/jobs/{jobId}` with
|
|
267
|
+
* platform-token auth — important: the cloud's dedicated
|
|
268
268
|
* `/v1/migrations/jobs/{id}/` endpoint queries platform-side ImportJob
|
|
269
269
|
* records and would 404 on runtime-created job IDs.
|
|
270
270
|
*/
|
|
@@ -320,15 +320,15 @@ export interface RuntimeIdentity {
|
|
|
320
320
|
* (no filesystem reads), so it's safe to call against any running runtime.
|
|
321
321
|
*
|
|
322
322
|
* For local/docker assistants this GETs `{runtimeUrl}/v1/health` with
|
|
323
|
-
* guardian-token bearer auth. For
|
|
323
|
+
* guardian-token bearer auth. For cloud-managed (cloud="cadet")
|
|
324
324
|
* assistants the URL is rewritten to the wildcard runtime proxy shape
|
|
325
|
-
* `{
|
|
326
|
-
* the
|
|
325
|
+
* `{cloudUrl}/v1/assistants/<assistantId>/health` and authenticated via
|
|
326
|
+
* the cloud token.
|
|
327
327
|
*
|
|
328
328
|
* For the cadet target this is the FIRST network call in the
|
|
329
329
|
* teleport/backup export flow, so a stale `Cadet-Organization-Id` cache
|
|
330
330
|
* entry would surface as a hard abort before any retry-friendly call (like
|
|
331
|
-
* `
|
|
331
|
+
* `cloudRequestSignedUrl`) gets a chance to recover. Mirror that helper's
|
|
332
332
|
* one-shot 401-retry: invalidate the org-ID cache and retry once. Local /
|
|
333
333
|
* docker entries do not use the org-ID cache and are wrapped in
|
|
334
334
|
* `callRuntimeWithAuthRetry` by callers for guardian-token refresh, so the
|
|
@@ -353,10 +353,10 @@ export async function localRuntimeIdentity(
|
|
|
353
353
|
});
|
|
354
354
|
|
|
355
355
|
let response = await doRequest();
|
|
356
|
-
if (response.status === 401 &&
|
|
357
|
-
// `entry.runtimeUrl` is the
|
|
356
|
+
if (response.status === 401 && isCloudHome(entry.cloud)) {
|
|
357
|
+
// `entry.runtimeUrl` is the cloud host for cadet-cloud entries
|
|
358
358
|
// (the wildcard runtime proxy lives there). Pass it as the cache key
|
|
359
|
-
//
|
|
359
|
+
// cloudUrl so we invalidate the same entry that authHeaders cached.
|
|
360
360
|
invalidateOrgIdCache(token, entry.runtimeUrl);
|
|
361
361
|
response = await doRequest();
|
|
362
362
|
}
|
package/src/lib/local.ts
CHANGED
|
@@ -74,11 +74,10 @@ import {
|
|
|
74
74
|
executableName,
|
|
75
75
|
isProcessAlive,
|
|
76
76
|
pathListDelimiter,
|
|
77
|
-
readProcessCommandLine,
|
|
78
77
|
resolveProcessState,
|
|
79
|
-
stopProcess,
|
|
80
78
|
stopProcessByPidFile,
|
|
81
79
|
} from "./process.js";
|
|
80
|
+
import { stopTunnelProcess } from "./tunnel.js";
|
|
82
81
|
import { stripVersionPrefix } from "./version-compat.js";
|
|
83
82
|
import { openLogFile, pipeToLogFile } from "./xdg-log.js";
|
|
84
83
|
|
|
@@ -699,7 +698,7 @@ function sleep(ms: number): Promise<void> {
|
|
|
699
698
|
* A readiness answer proves only that something listens on the port: a foreign
|
|
700
699
|
* listener holding it answers in the daemon's place, so a daemon aborting over
|
|
701
700
|
* the collision still reads as "up" or "migrating". Ownership is confirmed
|
|
702
|
-
* from the listening PID where the
|
|
701
|
+
* from the listening PID where the cloud can report it. Otherwise the spawn
|
|
703
702
|
* gets a short settle window, which is long enough because transports bind
|
|
704
703
|
* before migrations run, so an address collision aborts the daemon early.
|
|
705
704
|
*/
|
|
@@ -1632,7 +1631,7 @@ export async function startLocalDaemon(
|
|
|
1632
1631
|
"ANTHROPIC_API_KEY",
|
|
1633
1632
|
"APP_VERSION",
|
|
1634
1633
|
"CADET_ENVIRONMENT",
|
|
1635
|
-
"
|
|
1634
|
+
"CADET_CLOUD_URL",
|
|
1636
1635
|
"QDRANT_HTTP_PORT",
|
|
1637
1636
|
"QDRANT_URL",
|
|
1638
1637
|
"RUNTIME_HTTP_PORT",
|
|
@@ -1642,7 +1641,7 @@ export async function startLocalDaemon(
|
|
|
1642
1641
|
"CADET_DEBUG",
|
|
1643
1642
|
"CADET_DEV",
|
|
1644
1643
|
"CADET_DESKTOP_APP",
|
|
1645
|
-
"
|
|
1644
|
+
"CADET_DISABLE_CLOUD",
|
|
1646
1645
|
"CADET_MIGRATION_EXPORT_ALLOWED_HOSTS",
|
|
1647
1646
|
"CADET_MIGRATION_IMPORT_ALLOWED_HOSTS",
|
|
1648
1647
|
"CADET_WORKSPACE_DIR",
|
|
@@ -1931,16 +1930,6 @@ export async function startGateway(
|
|
|
1931
1930
|
return gatewayUrl;
|
|
1932
1931
|
}
|
|
1933
1932
|
|
|
1934
|
-
/** Check whether a PID belongs to an ngrok process via its command line. */
|
|
1935
|
-
function isNgrokProcess(pid: number): boolean {
|
|
1936
|
-
try {
|
|
1937
|
-
const output = readProcessCommandLine(pid);
|
|
1938
|
-
return /ngrok/i.test(output);
|
|
1939
|
-
} catch {
|
|
1940
|
-
return false;
|
|
1941
|
-
}
|
|
1942
|
-
}
|
|
1943
|
-
|
|
1944
1933
|
/**
|
|
1945
1934
|
* Stop any locally-running daemon and gateway processes
|
|
1946
1935
|
* and clean up PID files. Called when hatch fails partway through
|
|
@@ -1969,22 +1958,12 @@ export async function stopLocalProcesses(
|
|
|
1969
1958
|
const cesPidFile = join(cadetDir, "ces.pid");
|
|
1970
1959
|
await stopProcessByPidFile(cesPidFile, "credential-executor");
|
|
1971
1960
|
|
|
1972
|
-
//
|
|
1973
|
-
//
|
|
1974
|
-
//
|
|
1975
|
-
|
|
1976
|
-
const ngrokPidFile = join(cadetDir, "ngrok.pid");
|
|
1977
|
-
if (existsSync(ngrokPidFile)) {
|
|
1978
|
-
try {
|
|
1979
|
-
const pid = parseInt(readFileSync(ngrokPidFile, "utf-8").trim(), 10);
|
|
1980
|
-
if (!isNaN(pid) && isNgrokProcess(pid)) {
|
|
1981
|
-
await stopProcess(pid, "ngrok");
|
|
1982
|
-
}
|
|
1983
|
-
unlinkSync(ngrokPidFile);
|
|
1984
|
-
} catch {}
|
|
1985
|
-
}
|
|
1961
|
+
// Stop the tunnel agent this CLI spawned (root lockfile `tunnel` entry),
|
|
1962
|
+
// verifying the pid still belongs to the provider's binary before signaling
|
|
1963
|
+
// it — PID reuse must never send an unrelated process a signal.
|
|
1964
|
+
await stopTunnelProcess();
|
|
1986
1965
|
|
|
1987
1966
|
// Stop the nginx ingress if one is fronting this gateway (it guards against
|
|
1988
|
-
// PID reuse itself
|
|
1967
|
+
// PID reuse itself).
|
|
1989
1968
|
await stopIngressNginx(join(cadetDir, "workspace"));
|
|
1990
1969
|
}
|