@vellumai/cli 0.10.3 → 0.10.4-dev.202607010119.ad15dbb
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/node_modules/@vellumai/local-mode/src/__tests__/retire.test.ts +79 -0
- package/node_modules/@vellumai/local-mode/src/index.ts +12 -3
- package/node_modules/@vellumai/local-mode/src/lockfile-contract.test.ts +11 -2
- package/node_modules/@vellumai/local-mode/src/lockfile-contract.ts +6 -0
- package/node_modules/@vellumai/local-mode/src/lockfile.test.ts +29 -1
- package/node_modules/@vellumai/local-mode/src/retire.ts +27 -5
- package/package.json +1 -1
- package/src/__tests__/flags.test.ts +10 -10
- package/src/__tests__/recover.test.ts +34 -27
- package/src/__tests__/retire.test.ts +353 -0
- package/src/__tests__/teleport.test.ts +50 -17
- package/src/commands/flags.ts +1 -1
- package/src/commands/login.ts +10 -2
- package/src/commands/retire.ts +197 -24
- package/src/commands/teleport.ts +51 -18
- package/src/lib/local-runtime-client.ts +38 -0
- package/src/lib/platform-client.ts +14 -0
package/src/commands/retire.ts
CHANGED
|
@@ -20,7 +20,9 @@ import { getConfigDir } from "../lib/environments/paths.js";
|
|
|
20
20
|
import { getCurrentEnvironment } from "../lib/environments/resolve.js";
|
|
21
21
|
import {
|
|
22
22
|
authHeaders,
|
|
23
|
+
authHeadersForKnownOrganization,
|
|
23
24
|
getPlatformUrl,
|
|
25
|
+
readGatewayCredential,
|
|
24
26
|
readPlatformToken,
|
|
25
27
|
} from "../lib/platform-client.js";
|
|
26
28
|
import { retireInstance as retireAwsInstance } from "../lib/aws.js";
|
|
@@ -45,6 +47,180 @@ interface RetireArgs {
|
|
|
45
47
|
yes: boolean;
|
|
46
48
|
}
|
|
47
49
|
|
|
50
|
+
interface SelfHostedPlatformRegistration {
|
|
51
|
+
platformAssistantId: string;
|
|
52
|
+
platformBaseUrl: string | null;
|
|
53
|
+
organizationId: string | null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const PLATFORM_TOKEN_ENV = "VELLUM_PLATFORM_TOKEN";
|
|
57
|
+
|
|
58
|
+
function readPlatformRetireToken(): string | null {
|
|
59
|
+
const envToken = process.env[PLATFORM_TOKEN_ENV]?.trim();
|
|
60
|
+
if (envToken) return envToken;
|
|
61
|
+
return readPlatformToken();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function platformOrigin(platformUrl: string): string | null {
|
|
65
|
+
try {
|
|
66
|
+
return new URL(platformUrl).origin;
|
|
67
|
+
} catch {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function resolveTrustedSelfHostedPlatformUrl(
|
|
73
|
+
storedPlatformUrl: string | null,
|
|
74
|
+
): string | null {
|
|
75
|
+
const configuredPlatformUrl = getPlatformUrl();
|
|
76
|
+
if (!storedPlatformUrl) return configuredPlatformUrl;
|
|
77
|
+
|
|
78
|
+
const storedOrigin = platformOrigin(storedPlatformUrl);
|
|
79
|
+
const configuredOrigin = platformOrigin(configuredPlatformUrl);
|
|
80
|
+
if (storedOrigin && configuredOrigin && storedOrigin === configuredOrigin) {
|
|
81
|
+
return configuredPlatformUrl;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
console.warn(
|
|
85
|
+
"⚠️ Stored platform URL does not match the configured platform URL; skipping platform unregister.",
|
|
86
|
+
);
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function nonEmptyString(value: string | null | undefined): string | null {
|
|
91
|
+
const trimmed = value?.trim();
|
|
92
|
+
return trimmed ? trimmed : null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function lockfileSelfHostedPlatformRegistration(
|
|
96
|
+
entry: AssistantEntry,
|
|
97
|
+
): SelfHostedPlatformRegistration | null {
|
|
98
|
+
const platformAssistantId = nonEmptyString(entry.platformAssistantId);
|
|
99
|
+
if (!platformAssistantId) return null;
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
platformAssistantId,
|
|
103
|
+
platformBaseUrl: nonEmptyString(entry.platformBaseUrl),
|
|
104
|
+
organizationId: nonEmptyString(entry.platformOrganizationId),
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async function deletePlatformAssistant(
|
|
109
|
+
assistantId: string,
|
|
110
|
+
options: {
|
|
111
|
+
token: string;
|
|
112
|
+
platformUrl?: string;
|
|
113
|
+
organizationId?: string;
|
|
114
|
+
label: string;
|
|
115
|
+
successMessage: string;
|
|
116
|
+
alreadyGoneMessage: string;
|
|
117
|
+
},
|
|
118
|
+
): Promise<void> {
|
|
119
|
+
const platformUrl = options.platformUrl || getPlatformUrl();
|
|
120
|
+
const url = `${platformUrl}/v1/assistants/${encodeURIComponent(assistantId)}/retire/`;
|
|
121
|
+
const headers = options.organizationId
|
|
122
|
+
? authHeadersForKnownOrganization(options.token, options.organizationId)
|
|
123
|
+
: await authHeaders(options.token, platformUrl);
|
|
124
|
+
|
|
125
|
+
const response = await loopbackSafeFetch(url, {
|
|
126
|
+
method: "DELETE",
|
|
127
|
+
headers,
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
if (!response.ok && response.status !== 404) {
|
|
131
|
+
const body = await response.text();
|
|
132
|
+
throw new Error(`${options.label} failed (${response.status}): ${body}`);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (response.status === 404) {
|
|
136
|
+
console.log(options.alreadyGoneMessage);
|
|
137
|
+
} else {
|
|
138
|
+
console.log(options.successMessage);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async function readSelfHostedPlatformRegistration(
|
|
143
|
+
entry: AssistantEntry,
|
|
144
|
+
): Promise<SelfHostedPlatformRegistration | null> {
|
|
145
|
+
const fallback = lockfileSelfHostedPlatformRegistration(entry);
|
|
146
|
+
const gatewayUrl = entry.localUrl ?? entry.runtimeUrl;
|
|
147
|
+
const platformAssistant = await readGatewayCredential(
|
|
148
|
+
gatewayUrl,
|
|
149
|
+
"vellum:platform_assistant_id",
|
|
150
|
+
entry.bearerToken,
|
|
151
|
+
);
|
|
152
|
+
if (platformAssistant.unreachable) {
|
|
153
|
+
if (fallback) {
|
|
154
|
+
console.warn(
|
|
155
|
+
"\u26a0\ufe0f Could not read platform registration from the local assistant; using saved platform registration.",
|
|
156
|
+
);
|
|
157
|
+
return fallback;
|
|
158
|
+
}
|
|
159
|
+
console.warn(
|
|
160
|
+
"\u26a0\ufe0f Could not read platform registration from the local assistant and no saved platform registration is available; skipping platform unregister.",
|
|
161
|
+
);
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const platformAssistantId =
|
|
166
|
+
nonEmptyString(platformAssistant.value) ?? fallback?.platformAssistantId;
|
|
167
|
+
if (!platformAssistantId) return null;
|
|
168
|
+
|
|
169
|
+
const [platformBaseUrl, organizationId] = await Promise.all([
|
|
170
|
+
readGatewayCredential(
|
|
171
|
+
gatewayUrl,
|
|
172
|
+
"vellum:platform_base_url",
|
|
173
|
+
entry.bearerToken,
|
|
174
|
+
),
|
|
175
|
+
readGatewayCredential(
|
|
176
|
+
gatewayUrl,
|
|
177
|
+
"vellum:platform_organization_id",
|
|
178
|
+
entry.bearerToken,
|
|
179
|
+
),
|
|
180
|
+
]);
|
|
181
|
+
|
|
182
|
+
return {
|
|
183
|
+
platformAssistantId,
|
|
184
|
+
platformBaseUrl:
|
|
185
|
+
nonEmptyString(platformBaseUrl.value) ??
|
|
186
|
+
fallback?.platformBaseUrl ??
|
|
187
|
+
null,
|
|
188
|
+
organizationId:
|
|
189
|
+
nonEmptyString(organizationId.value) ?? fallback?.organizationId ?? null,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
async function unregisterSelfHostedLocalPlatformRecord(
|
|
194
|
+
entry: AssistantEntry,
|
|
195
|
+
): Promise<void> {
|
|
196
|
+
const token = readPlatformRetireToken();
|
|
197
|
+
if (!token) return;
|
|
198
|
+
|
|
199
|
+
const registration = await readSelfHostedPlatformRegistration(entry);
|
|
200
|
+
if (!registration) return;
|
|
201
|
+
|
|
202
|
+
const platformUrl = resolveTrustedSelfHostedPlatformUrl(
|
|
203
|
+
registration.platformBaseUrl,
|
|
204
|
+
);
|
|
205
|
+
if (!platformUrl) return;
|
|
206
|
+
if (!registration.organizationId) {
|
|
207
|
+
console.warn(
|
|
208
|
+
"⚠️ Could not read platform organization ID from the local assistant; skipping platform unregister.",
|
|
209
|
+
);
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
await deletePlatformAssistant(registration.platformAssistantId, {
|
|
214
|
+
token,
|
|
215
|
+
platformUrl,
|
|
216
|
+
organizationId: registration.organizationId,
|
|
217
|
+
label: "Platform self-hosted unregister",
|
|
218
|
+
successMessage: "\u2705 Platform self-hosted registration unregistered.",
|
|
219
|
+
alreadyGoneMessage:
|
|
220
|
+
"\u2705 Platform self-hosted registration already unregistered (404).",
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
|
|
48
224
|
async function retireCustom(entry: AssistantEntry): Promise<void> {
|
|
49
225
|
const host = extractHostFromUrl(entry.runtimeUrl);
|
|
50
226
|
const sshUser = entry.sshUser ?? "root";
|
|
@@ -86,7 +262,7 @@ async function retireVellum(
|
|
|
86
262
|
): Promise<void> {
|
|
87
263
|
console.log("\u{1F5D1}\ufe0f Retiring platform-hosted instance...\n");
|
|
88
264
|
|
|
89
|
-
const token =
|
|
265
|
+
const token = readPlatformRetireToken();
|
|
90
266
|
if (!token) {
|
|
91
267
|
console.error(
|
|
92
268
|
"Error: Not logged in. Run `vellum login --token <token>` first.",
|
|
@@ -94,33 +270,21 @@ async function retireVellum(
|
|
|
94
270
|
process.exit(1);
|
|
95
271
|
}
|
|
96
272
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
// cleanup avoids leaving a stale entry that would otherwise wedge the
|
|
108
|
-
// macOS app in a permanent health-check loop.
|
|
109
|
-
if (!response.ok && response.status !== 404) {
|
|
110
|
-
const body = await response.text();
|
|
273
|
+
try {
|
|
274
|
+
await deletePlatformAssistant(assistantId, {
|
|
275
|
+
token,
|
|
276
|
+
platformUrl: runtimeUrl,
|
|
277
|
+
label: "Platform retire",
|
|
278
|
+
successMessage: "\u2705 Platform-hosted instance retired.",
|
|
279
|
+
alreadyGoneMessage:
|
|
280
|
+
"\u2705 Platform-hosted instance already retired (404) \u2014 cleaning up local state.",
|
|
281
|
+
});
|
|
282
|
+
} catch (error) {
|
|
111
283
|
console.error(
|
|
112
|
-
`Error:
|
|
284
|
+
`Error: ${error instanceof Error ? error.message : String(error)}`,
|
|
113
285
|
);
|
|
114
286
|
process.exit(1);
|
|
115
287
|
}
|
|
116
|
-
|
|
117
|
-
if (response.status === 404) {
|
|
118
|
-
console.log(
|
|
119
|
-
"\u2705 Platform-hosted instance already retired (404) — cleaning up local state.",
|
|
120
|
-
);
|
|
121
|
-
} else {
|
|
122
|
-
console.log("\u2705 Platform-hosted instance retired.");
|
|
123
|
-
}
|
|
124
288
|
}
|
|
125
289
|
|
|
126
290
|
function parseRetireArgs(args: string[]): RetireArgs {
|
|
@@ -303,6 +467,15 @@ async function retireInner(): Promise<void> {
|
|
|
303
467
|
} else if (cloud === "docker") {
|
|
304
468
|
await retireDocker(assistantId);
|
|
305
469
|
} else if (cloud === "local") {
|
|
470
|
+
try {
|
|
471
|
+
await unregisterSelfHostedLocalPlatformRecord(entry);
|
|
472
|
+
} catch (error) {
|
|
473
|
+
console.warn(
|
|
474
|
+
`⚠️ Platform self-hosted unregister failed; continuing local retire: ${
|
|
475
|
+
error instanceof Error ? error.message : String(error)
|
|
476
|
+
}`,
|
|
477
|
+
);
|
|
478
|
+
}
|
|
306
479
|
await retireLocal(assistantId, entry);
|
|
307
480
|
} else if (cloud === "custom") {
|
|
308
481
|
await retireCustom(entry);
|
package/src/commands/teleport.ts
CHANGED
|
@@ -33,6 +33,7 @@ import {
|
|
|
33
33
|
localRuntimeExportToGcs,
|
|
34
34
|
localRuntimeIdentity,
|
|
35
35
|
localRuntimeImportFromGcs,
|
|
36
|
+
localRuntimePreflightFromGcs,
|
|
36
37
|
localRuntimePollJobStatus,
|
|
37
38
|
MigrationInProgressError,
|
|
38
39
|
} from "../lib/local-runtime-client.js";
|
|
@@ -707,11 +708,46 @@ async function importToAssistant(
|
|
|
707
708
|
|
|
708
709
|
if (cloud === "local" || cloud === "docker") {
|
|
709
710
|
if (dryRun) {
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
711
|
+
console.log("Running preflight analysis...\n");
|
|
712
|
+
|
|
713
|
+
// Query the target runtime's version before requesting a signed
|
|
714
|
+
// download URL — the platform uses it for the bundle compatibility check.
|
|
715
|
+
let targetRuntimeVersion: string;
|
|
716
|
+
try {
|
|
717
|
+
const identity = await callRuntimeWithAuthRetry(entry, (token) =>
|
|
718
|
+
localRuntimeIdentity(entry, token),
|
|
719
|
+
);
|
|
720
|
+
targetRuntimeVersion = identity.version;
|
|
721
|
+
} catch (err) {
|
|
722
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
723
|
+
console.error(
|
|
724
|
+
`Error: Could not read target runtime version from '${entry.assistantId}': ${msg}`,
|
|
725
|
+
);
|
|
726
|
+
console.error(`Try: vellum wake ${entry.assistantId}`);
|
|
727
|
+
process.exit(1);
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
let bundleUrl: string;
|
|
731
|
+
try {
|
|
732
|
+
const result = await platformRequestSignedUrl(
|
|
733
|
+
{ operation: "download", bundleKey, targetRuntimeVersion },
|
|
734
|
+
platformToken,
|
|
735
|
+
bundlePlatformUrl,
|
|
736
|
+
);
|
|
737
|
+
bundleUrl = result.url;
|
|
738
|
+
} catch (err) {
|
|
739
|
+
if (err instanceof VersionMismatchError) {
|
|
740
|
+
console.error(`Error: ${err.message}`);
|
|
741
|
+
process.exit(1);
|
|
742
|
+
}
|
|
743
|
+
throw err;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
const preflightData = await callRuntimeWithAuthRetry(entry, (token) =>
|
|
747
|
+
localRuntimePreflightFromGcs(entry, token, { bundleUrl }),
|
|
713
748
|
);
|
|
714
|
-
|
|
749
|
+
printPreflightSummary(preflightData as unknown as PreflightResponse);
|
|
750
|
+
return;
|
|
715
751
|
}
|
|
716
752
|
|
|
717
753
|
// Ask the platform for a signed download URL and hand it to the local
|
|
@@ -1087,6 +1123,7 @@ async function tryInjectPlatformCredentials(
|
|
|
1087
1123
|
fetchCurrentVersion(entry.runtimeUrl),
|
|
1088
1124
|
fetchAssistantIngressUrl(entry.runtimeUrl, entry.bearerToken),
|
|
1089
1125
|
]);
|
|
1126
|
+
const platformUrl = getPlatformUrl();
|
|
1090
1127
|
const registration = await ensureSelfHostedLocalRegistration(
|
|
1091
1128
|
token,
|
|
1092
1129
|
orgId,
|
|
@@ -1094,9 +1131,15 @@ async function tryInjectPlatformCredentials(
|
|
|
1094
1131
|
entry.assistantId,
|
|
1095
1132
|
"cli",
|
|
1096
1133
|
assistantVersion,
|
|
1097
|
-
|
|
1134
|
+
platformUrl,
|
|
1098
1135
|
ingressUrl,
|
|
1099
1136
|
);
|
|
1137
|
+
saveAssistantEntry({
|
|
1138
|
+
...entry,
|
|
1139
|
+
platformAssistantId: registration.assistant.id,
|
|
1140
|
+
platformBaseUrl: platformUrl,
|
|
1141
|
+
platformOrganizationId: orgId,
|
|
1142
|
+
});
|
|
1100
1143
|
|
|
1101
1144
|
// Resolve the API key: 1) fresh from registration, 2) existing from
|
|
1102
1145
|
// daemon credential store, 3) reprovision as last resort (revokes old key).
|
|
@@ -1128,7 +1171,7 @@ async function tryInjectPlatformCredentials(
|
|
|
1128
1171
|
bearerToken: entry.bearerToken,
|
|
1129
1172
|
assistantApiKey,
|
|
1130
1173
|
platformAssistantId: registration.assistant.id,
|
|
1131
|
-
platformBaseUrl:
|
|
1174
|
+
platformBaseUrl: platformUrl,
|
|
1132
1175
|
organizationId: orgId,
|
|
1133
1176
|
userId: user.id,
|
|
1134
1177
|
webhookSecret: registration.webhook_secret,
|
|
@@ -1231,18 +1274,8 @@ export async function teleport(): Promise<void> {
|
|
|
1231
1274
|
process.exit(1);
|
|
1232
1275
|
}
|
|
1233
1276
|
|
|
1234
|
-
//
|
|
1235
|
-
//
|
|
1236
|
-
// so we can't actually run a dry-run against it; burning a GCS upload
|
|
1237
|
-
// just to fail afterwards would be wasteful.
|
|
1238
|
-
// TODO(cli): support dry-run against local targets (needs a
|
|
1239
|
-
// preflight-from-gcs endpoint on the runtime).
|
|
1240
|
-
if (toCloud === "local" || toCloud === "docker") {
|
|
1241
|
-
console.error(
|
|
1242
|
-
"Error: --dry-run is not yet supported for local or docker targets (no preflight-from-gcs endpoint on the runtime).",
|
|
1243
|
-
);
|
|
1244
|
-
process.exit(1);
|
|
1245
|
-
}
|
|
1277
|
+
// Nothing to reject — preflight-from-gcs is now implemented for all
|
|
1278
|
+
// target topologies including local and docker.
|
|
1246
1279
|
|
|
1247
1280
|
// Version guard: block platform→non-platform when target is behind
|
|
1248
1281
|
if (fromCloud === "vellum" && toCloud !== "vellum") {
|
|
@@ -195,6 +195,44 @@ export async function localRuntimeImportFromGcs(
|
|
|
195
195
|
return { jobId: json.job_id };
|
|
196
196
|
}
|
|
197
197
|
|
|
198
|
+
/**
|
|
199
|
+
* Run a dry-run preflight analysis on a .vbundle archive stored at a signed
|
|
200
|
+
* GCS download URL.
|
|
201
|
+
*
|
|
202
|
+
* For local/docker assistants this POSTs to
|
|
203
|
+
* `{runtimeUrl}/v1/migrations/preflight-from-gcs` with guardian-token bearer
|
|
204
|
+
* auth. The daemon fetches the bundle from GCS, validates it, and returns a
|
|
205
|
+
* report of what would change on import — without writing anything to disk.
|
|
206
|
+
*
|
|
207
|
+
* Returns the raw preflight response body. Callers should cast to their
|
|
208
|
+
* local `PreflightResponse` shape (mirrors the `/import-preflight` shape).
|
|
209
|
+
*/
|
|
210
|
+
export async function localRuntimePreflightFromGcs(
|
|
211
|
+
entry: Pick<AssistantEntry, "cloud" | "runtimeUrl" | "assistantId">,
|
|
212
|
+
token: string,
|
|
213
|
+
params: { bundleUrl: string },
|
|
214
|
+
): Promise<Record<string, unknown>> {
|
|
215
|
+
const response = await fetch(
|
|
216
|
+
resolveRuntimeMigrationUrl(entry, "preflight-from-gcs"),
|
|
217
|
+
{
|
|
218
|
+
method: "POST",
|
|
219
|
+
headers: await migrationRequestHeaders(entry, token),
|
|
220
|
+
body: JSON.stringify({ bundle_url: params.bundleUrl }),
|
|
221
|
+
},
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
if (response.status !== 200) {
|
|
225
|
+
const errText = await response.text().catch(() => "");
|
|
226
|
+
throw new Error(
|
|
227
|
+
`Local runtime preflight-from-gcs failed (${response.status}): ${
|
|
228
|
+
errText || response.statusText
|
|
229
|
+
}`,
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return (await response.json()) as Record<string, unknown>;
|
|
234
|
+
}
|
|
235
|
+
|
|
198
236
|
/**
|
|
199
237
|
* Poll the runtime's unified job-status endpoint.
|
|
200
238
|
*
|
|
@@ -96,6 +96,20 @@ function tokenAuthHeader(token: string): Record<string, string> {
|
|
|
96
96
|
return { "X-Session-Token": token };
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
+
export function authHeadersForKnownOrganization(
|
|
100
|
+
token: string,
|
|
101
|
+
organizationId: string,
|
|
102
|
+
): Record<string, string> {
|
|
103
|
+
const headers: Record<string, string> = {
|
|
104
|
+
"Content-Type": "application/json",
|
|
105
|
+
...tokenAuthHeader(token),
|
|
106
|
+
};
|
|
107
|
+
if (!token.startsWith(VAK_PREFIX)) {
|
|
108
|
+
headers["Vellum-Organization-Id"] = organizationId;
|
|
109
|
+
}
|
|
110
|
+
return headers;
|
|
111
|
+
}
|
|
112
|
+
|
|
99
113
|
/** Module-level cache for org IDs to avoid redundant fetches in polling loops. */
|
|
100
114
|
const orgIdCache = new Map<string, { orgId: string; expiresAt: number }>();
|
|
101
115
|
const ORG_ID_CACHE_TTL_MS = 60_000; // 60 seconds
|