@elizaos/app-core 2.0.0-alpha.209 → 2.0.0-alpha.210

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elizaos/app-core",
3
- "version": "2.0.0-alpha.209",
3
+ "version": "2.0.0-alpha.210",
4
4
  "description": "Shared application core for elizaOS white-label agent apps.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -477,7 +477,7 @@
477
477
  "@capacitor/keyboard": "8.0.3",
478
478
  "@capacitor/preferences": "^8.0.1",
479
479
  "@clack/prompts": "^1.0.0",
480
- "@elizaos/agent": "^2.0.0-alpha.209",
480
+ "@elizaos/agent": "^2.0.0-alpha.210",
481
481
  "@elizaos/app-companion": "^0.0.0",
482
482
  "@elizaos/app-elizamaker": "^0.0.0",
483
483
  "@elizaos/app-lifeops": "^0.0.0",
@@ -486,9 +486,9 @@
486
486
  "@elizaos/app-task-coordinator": "^0.0.0",
487
487
  "@elizaos/app-training": "^0.0.1",
488
488
  "@elizaos/app-vincent": "^0.0.0",
489
- "@elizaos/core": "^2.0.0-alpha.209",
490
- "@elizaos/shared": "^2.0.0-alpha.209",
491
- "@elizaos/ui": "^2.0.0-alpha.209",
489
+ "@elizaos/core": "^2.0.0-alpha.210",
490
+ "@elizaos/shared": "^2.0.0-alpha.210",
491
+ "@elizaos/ui": "^2.0.0-alpha.210",
492
492
  "@radix-ui/react-checkbox": "^1.3.3",
493
493
  "@radix-ui/react-dialog": "^1.1.15",
494
494
  "@radix-ui/react-dropdown-menu": "^2.1.16",
@@ -1 +1 @@
1
- {"version":3,"file":"n8n-routes.d.ts","sourceRoot":"","sources":["../../../../../src/api/n8n-routes.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"n8n-routes.d.ts","sourceRoot":"","sources":["../../../../../src/api/n8n-routes.ts"],"names":[],"mappings":"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"}
@@ -201,9 +201,15 @@ function resolveProxyTarget(ctx, subpath, sidecar, native) {
201
201
  };
202
202
  if (apiKey)
203
203
  headers["X-N8N-API-KEY"] = apiKey;
204
+ // n8n serves TWO parallel workflow APIs:
205
+ // /rest/workflows — internal UI endpoint, requires JWT cookie auth.
206
+ // /api/v1/workflows — public API, accepts X-N8N-API-KEY.
207
+ // We provision an X-N8N-API-KEY during boot, so the public API is the
208
+ // only path that authenticates correctly. Hitting /rest/ was returning
209
+ // 401 "Unauthorized" even with a valid key — that's the wrong endpoint.
204
210
  return {
205
211
  target: {
206
- url: `${host.replace(/\/+$/, "")}/rest/workflows${subpath}`,
212
+ url: `${host.replace(/\/+$/, "")}/api/v1/workflows${subpath}`,
207
213
  headers,
208
214
  },
209
215
  };
@@ -62,7 +62,22 @@ export interface N8nSidecarConfig {
62
62
  startPort?: number;
63
63
  /** Bind host for the child. Default 127.0.0.1. */
64
64
  host?: string;
65
- /** Binary used to run n8n. Default "bunx". */
65
+ /**
66
+ * Binary used to run n8n. Default "npx".
67
+ *
68
+ * Why not "bunx"? bunx invokes n8n through Bun's runtime / shim, which
69
+ * fails two different ways depending on the n8n version:
70
+ * 1. n8n@1.70.x: bunx resolves `node` via $PATH before reading the
71
+ * package's `engines` field. On macOS with Homebrew that's v24,
72
+ * which 1.70 rejects ("Node.js version 24.5.0 is currently not
73
+ * supported").
74
+ * 2. n8n@1.108.x: bunx runs n8n under Bun's runtime which does not
75
+ * fully implement the `reflect-metadata` + TSyringe decorator
76
+ * pattern n8n relies on for DI, failing at bootstrap with
77
+ * "[DI] ErrorReporter is not decorated with Service".
78
+ * `npx --yes` uses npm's cache and execs n8n under the expected Node
79
+ * runtime, which works across every n8n + Node combo we care about.
80
+ */
66
81
  binary?: string;
67
82
  /** State directory root; owner email/password + sqlite live here. */
68
83
  stateDir?: string;
@@ -221,13 +236,42 @@ export declare class N8nSidecar {
221
236
  */
222
237
  private validateApiKey;
223
238
  /**
224
- * Provision a personal API key via n8n's internal REST endpoint. With
225
- * user management disabled, n8n accepts unauthenticated calls to
226
- * `/rest/me/api-keys`. If the pinned version rejects this flow (newer
227
- * n8n versions moved to an auth-required flow), we return null and
228
- * the caller must fall back to the JWT path documented in-file.
239
+ * Provision an API key by driving n8n's owner-setup login → api-key flow.
240
+ *
241
+ * n8n ≥ 1.90-ish removed the anonymous `/rest/me/api-keys` endpoint. The
242
+ * supported path now requires:
243
+ * 1. POST /rest/owner/setup { email, firstName, lastName, password }
244
+ * – returns `Set-Cookie: n8n-auth=<JWT>` when no owner exists yet.
245
+ * 2. POST /rest/login { emailOrLdapLoginId, password }
246
+ * – returns the same cookie on restarts, once the owner is set.
247
+ * 3. GET /rest/api-keys/scopes
248
+ * – enumerates the scopes the current role is allowed to grant.
249
+ * 4. POST /rest/api-keys { label, scopes, expiresAt: null }
250
+ * – returns `data.rawApiKey` which stays valid across restarts until
251
+ * explicitly revoked.
252
+ *
253
+ * Credentials are persisted to `{stateDir}/owner.json` (mode-600) so the
254
+ * same login works on every subsequent boot; we never re-generate. Password
255
+ * is random per install — there's no user-facing n8n UI flow in Milady, so
256
+ * storing it here is safe for a local single-user sidecar.
229
257
  */
230
258
  private provisionApiKey;
259
+ /**
260
+ * Load owner credentials from `{stateDir}/owner.json`, or generate a fresh
261
+ * pair and persist them mode-600. The email is deterministic (matches the
262
+ * label we show to the user); the password is a long random token.
263
+ */
264
+ private loadOrCreateOwnerCreds;
265
+ /**
266
+ * Returns a `Cookie: n8n-auth=<jwt>` string by either creating the owner
267
+ * (first boot) or logging in (subsequent boots). Returns null if both
268
+ * fail so the caller can back off gracefully.
269
+ */
270
+ private acquireOwnerCookie;
271
+ /** List scopes the current role may grant when creating an API key. */
272
+ private fetchApiKeyScopes;
273
+ /** 48 bytes of base64url entropy — ~64 chars, far above n8n's min length. */
274
+ private generateRandomPassword;
231
275
  /** Stop the sidecar. Idempotent. */
232
276
  stop(): Promise<void>;
233
277
  /** Public helper so callers can gate feature activation on running state. */
@@ -1 +1 @@
1
- {"version":3,"file":"n8n-sidecar.d.ts","sourceRoot":"","sources":["../../../../../src/services/n8n-sidecar.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"n8n-sidecar.d.ts","sourceRoot":"","sources":["../../../../../src/services/n8n-sidecar.ts"],"names":[],"mappings":"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"}
@@ -42,21 +42,33 @@ import os from "node:os";
42
42
  import path from "node:path";
43
43
  import { logger } from "@elizaos/core";
44
44
  // ── Implementation ───────────────────────────────────────────────────────────
45
- // n8n@1.70.0 declared `engines.node: ">=18.17 <= 22"` incompatible with
46
- // Node 24.x which ships with current Homebrew and with the Electrobun runtime.
47
- // 1.99.0+ widened to ">=20.19 <= 24.x". Pinning the last 1.x release so the
48
- // DB schema stays on a supported migration path and Node 24 hosts can boot.
49
- // Validated via `npm view n8n@<v> engines.node`. Bumping below 1.99 will break
50
- // every desktop whose system Node is 23+.
51
- const DEFAULT_N8N_VERSION = "1.108.0";
45
+ // n8n version selection this range is narrower than it looks.
46
+ //
47
+ // 1.70.x declares `engines.node: ">=18.17 <= 22"` rejects Node 23/24
48
+ // (which is what Homebrew ships). Fails at boot with
49
+ // "Node.js version 24.5.0 is currently not supported".
50
+ // 1.99.0 widens to ">=20.19 <= 24.x" and boots cleanly under Node 24
51
+ // 1.100.0 same engines range, boots cleanly ✓
52
+ // 1.108.0 has a DI-container bootstrap regression — throws
53
+ // "[DI] GlobalConfig is not decorated with Service" under every
54
+ // launcher (npx, npm install + node, bunx). Confirmed broken on
55
+ // both Node 22 and Node 24. See upstream n8n issue.
56
+ //
57
+ // 1.100.0 is the last version I verified end-to-end against Node 24 +
58
+ // npx --yes on 2026-04-19. Bump only with a re-run of the smoke test in
59
+ // docs/apps/n8n-sidecar.md. Validate `engines.node` via
60
+ // `npm view n8n@<v> engines`.
61
+ const DEFAULT_N8N_VERSION = "1.100.0";
52
62
  const DEFAULT_START_PORT = 5678;
53
63
  const DEFAULT_HOST = "127.0.0.1";
54
- const DEFAULT_BINARY = "bunx";
55
- // First-run `bunx n8n@<pinned>` downloads ~300MB before n8n can boot. A 60s
56
- // timeout was too aggressive — cold starts on typical home connections take
57
- // 60–120s just for the download. Bump to 180s so the first-run path reliably
58
- // lands inside the probe window on every desktop platform. Warm starts hit
59
- // the bunx cache and finish well under this budget.
64
+ // Spawn n8n via `npx --yes n8n@<pinned>`. See `binary?` docs for why bunx
65
+ // is broken (Node-version mismatch + Bun runtime breaks tsyringe decorators).
66
+ const DEFAULT_BINARY = "npx";
67
+ // First-run `npx n8n@<pinned>` downloads ~300MB of n8n + nodes into the
68
+ // npm cache before boot. On a typical home connection that's 60–120s of
69
+ // download plus 15–30s of n8n boot. Keep 180s so the first-run path
70
+ // reliably lands inside the probe window on every desktop platform.
71
+ // Warm starts hit the npm cache and finish well under this budget.
60
72
  const DEFAULT_PROBE_TIMEOUT_MS = 180_000;
61
73
  const DEFAULT_PROBE_INTERVAL_MS = 750;
62
74
  const DEFAULT_MAX_RETRIES = 3;
@@ -202,6 +214,28 @@ function fingerprint(secret) {
202
214
  return "***";
203
215
  return `${secret.slice(0, 4)}…${secret.slice(-2)} (len=${secret.length})`;
204
216
  }
217
+ /**
218
+ * Extract the n8n-auth cookie from a `Response` for re-use on subsequent
219
+ * calls. Returns a ready-to-send `Cookie:` header value, or null if the
220
+ * response didn't set one. Tolerates fetch implementations that expose
221
+ * multiple Set-Cookie values through `getSetCookie()` (Node 20.18+) or
222
+ * a single joined `set-cookie` header (older runtimes and the test fetch
223
+ * mock we use in unit tests).
224
+ */
225
+ function extractAuthCookie(res) {
226
+ const headers = res.headers;
227
+ const list = typeof headers.getSetCookie === "function"
228
+ ? headers.getSetCookie()
229
+ : ((headers.get("set-cookie") ?? "")
230
+ .split(/,(?=\s*[\w-]+=)/)
231
+ .filter((s) => s.length > 0));
232
+ for (const raw of list) {
233
+ const first = raw.split(";")[0]?.trim();
234
+ if (first?.startsWith("n8n-auth="))
235
+ return first;
236
+ }
237
+ return null;
238
+ }
205
239
  function resolveConfig(config) {
206
240
  return {
207
241
  enabled: config.enabled ?? true,
@@ -230,7 +264,11 @@ export class N8nSidecar {
230
264
  retries: 0,
231
265
  recentOutput: [],
232
266
  };
233
- static RECENT_OUTPUT_CAP = 40;
267
+ // 40 was too small — n8n's migration output alone is ~60 stdout lines on a
268
+ // cold boot, which evicts every preceding error/log from the buffer before
269
+ // the UI can read it. Sized to comfortably hold migrations + the api-key
270
+ // provisioning trace + a reasonable error tail.
271
+ static RECENT_OUTPUT_CAP = 200;
234
272
  /** Ring buffer of the child's recent stdout/stderr lines (see state.recentOutput). */
235
273
  recentOutput = [];
236
274
  child = null;
@@ -478,8 +516,10 @@ export class N8nSidecar {
478
516
  async spawnChild(port) {
479
517
  // n8n reads N8N_USER_MANAGEMENT_DISABLED to skip the owner-setup flow
480
518
  // on first boot; we pair it with a random owner email so no real user
481
- // data is needed. Using `bunx n8n@<pinned>` avoids adding n8n as a
482
- // package.json dep bun caches the install globally.
519
+ // data is needed. Using `npx n8n@<pinned>` pulls from the shared npm
520
+ // cache and runs under the native Node runtime, which n8n's DI stack
521
+ // depends on (bunx breaks both the Node version check for 1.70 and the
522
+ // tsyringe decorator handling for 1.100+ — see `binary?` docs above).
483
523
  const env = {
484
524
  ...process.env,
485
525
  N8N_PORT: String(port),
@@ -495,7 +535,17 @@ export class N8nSidecar {
495
535
  DB_SQLITE_DATABASE: path.join(this.config.stateDir, "database.sqlite"),
496
536
  };
497
537
  const versioned = `n8n@${this.config.version}`;
498
- const child = this.deps.spawn(this.config.binary, ["--", versioned, "start"], {
538
+ // Arg style depends on the launcher:
539
+ // npx: --yes <pkg> <args...> (auto-confirm install prompt)
540
+ // bunx: -- <pkg> <args...> (still supported for manual overrides)
541
+ // Anything else is passed through as <pkg> <args...>.
542
+ const binaryBase = this.config.binary.split("/").pop() ?? this.config.binary;
543
+ const launcherArgs = binaryBase === "npx"
544
+ ? ["--yes", versioned, "start"]
545
+ : binaryBase === "bunx"
546
+ ? ["--", versioned, "start"]
547
+ : [versioned, "start"];
548
+ const child = this.deps.spawn(this.config.binary, launcherArgs, {
499
549
  env,
500
550
  stdio: ["ignore", "pipe", "pipe"],
501
551
  detached: false,
@@ -721,34 +771,209 @@ export class N8nSidecar {
721
771
  }
722
772
  }
723
773
  /**
724
- * Provision a personal API key via n8n's internal REST endpoint. With
725
- * user management disabled, n8n accepts unauthenticated calls to
726
- * `/rest/me/api-keys`. If the pinned version rejects this flow (newer
727
- * n8n versions moved to an auth-required flow), we return null and
728
- * the caller must fall back to the JWT path documented in-file.
774
+ * Provision an API key by driving n8n's owner-setup login → api-key flow.
775
+ *
776
+ * n8n ≥ 1.90-ish removed the anonymous `/rest/me/api-keys` endpoint. The
777
+ * supported path now requires:
778
+ * 1. POST /rest/owner/setup { email, firstName, lastName, password }
779
+ * – returns `Set-Cookie: n8n-auth=<JWT>` when no owner exists yet.
780
+ * 2. POST /rest/login { emailOrLdapLoginId, password }
781
+ * – returns the same cookie on restarts, once the owner is set.
782
+ * 3. GET /rest/api-keys/scopes
783
+ * – enumerates the scopes the current role is allowed to grant.
784
+ * 4. POST /rest/api-keys { label, scopes, expiresAt: null }
785
+ * – returns `data.rawApiKey` which stays valid across restarts until
786
+ * explicitly revoked.
787
+ *
788
+ * Credentials are persisted to `{stateDir}/owner.json` (mode-600) so the
789
+ * same login works on every subsequent boot; we never re-generate. Password
790
+ * is random per install — there's no user-facing n8n UI flow in Milady, so
791
+ * storing it here is safe for a local single-user sidecar.
729
792
  */
730
793
  async provisionApiKey(host) {
794
+ const log = (msg) => {
795
+ // Route through both the central logger and the sidecar's recentOutput
796
+ // ring so operators can see the failure in /api/n8n/status even if the
797
+ // dev-server log rotates or gets lost. The strings never contain the
798
+ // raw API key (that's fingerprinted at the ensureApiKey callsite).
799
+ logger.warn(`[n8n-sidecar] ${msg}`);
800
+ this.recordOutput(`[provisionApiKey] ${msg}`);
801
+ };
731
802
  try {
732
- const res = await this.deps.fetch(`${host}/rest/me/api-keys`, {
803
+ const owner = await this.loadOrCreateOwnerCreds();
804
+ const cookie = await this.acquireOwnerCookie(host, owner, log);
805
+ if (!cookie) {
806
+ log("acquireOwnerCookie returned null — cannot create api key");
807
+ return null;
808
+ }
809
+ const scopes = await this.fetchApiKeyScopes(host, cookie);
810
+ if (!scopes || scopes.length === 0) {
811
+ log("/rest/api-keys/scopes returned no scopes");
812
+ return null;
813
+ }
814
+ const res = await this.deps.fetch(`${host}/rest/api-keys`, {
733
815
  method: "POST",
734
- headers: { "content-type": "application/json" },
735
- body: JSON.stringify({ label: "milady-sidecar" }),
816
+ headers: {
817
+ "content-type": "application/json",
818
+ cookie,
819
+ },
820
+ body: JSON.stringify({
821
+ label: "milady-sidecar",
822
+ scopes,
823
+ expiresAt: null,
824
+ }),
736
825
  signal: AbortSignal.timeout(5_000),
737
826
  });
738
827
  if (!res.ok) {
739
- // 401/403/404 endpoint disabled or moved; caller falls back.
828
+ const bodyText = await res.text().catch(() => "");
829
+ log(`api-key create failed: ${res.status} ${res.statusText}${bodyText ? ` — ${bodyText.slice(0, 200)}` : ""}`);
740
830
  return null;
741
831
  }
742
832
  const body = (await res.json());
743
- return (body.data?.rawApiKey ??
833
+ const key = body.data?.rawApiKey ??
744
834
  body.data?.apiKey ??
745
835
  body.rawApiKey ??
746
836
  body.apiKey ??
747
- null);
837
+ null;
838
+ if (!key) {
839
+ log("api-key create returned no rawApiKey in body");
840
+ }
841
+ return key;
842
+ }
843
+ catch (err) {
844
+ log(`provisionApiKey threw: ${err instanceof Error ? err.message : String(err)}`);
845
+ return null;
846
+ }
847
+ }
848
+ /**
849
+ * Load owner credentials from `{stateDir}/owner.json`, or generate a fresh
850
+ * pair and persist them mode-600. The email is deterministic (matches the
851
+ * label we show to the user); the password is a long random token.
852
+ */
853
+ async loadOrCreateOwnerCreds() {
854
+ const ownerPath = path.join(this.config.stateDir, "owner.json");
855
+ try {
856
+ const raw = await fs.readFile(ownerPath, "utf-8");
857
+ const parsed = JSON.parse(raw);
858
+ if (typeof parsed.email === "string" &&
859
+ typeof parsed.password === "string" &&
860
+ parsed.email.length > 0 &&
861
+ parsed.password.length > 0) {
862
+ return {
863
+ email: parsed.email,
864
+ firstName: typeof parsed.firstName === "string" ? parsed.firstName : "Milady",
865
+ lastName: typeof parsed.lastName === "string" ? parsed.lastName : "Local",
866
+ password: parsed.password,
867
+ };
868
+ }
748
869
  }
749
870
  catch {
871
+ /* fall through to generate fresh */
872
+ }
873
+ const password = this.generateRandomPassword();
874
+ const creds = {
875
+ email: "milady@milady.local",
876
+ firstName: "Milady",
877
+ lastName: "Local",
878
+ password,
879
+ };
880
+ try {
881
+ await fs.mkdir(this.config.stateDir, { recursive: true });
882
+ await fs.writeFile(ownerPath, JSON.stringify(creds, null, 2), {
883
+ mode: 0o600,
884
+ });
885
+ await fs.chmod(ownerPath, 0o600).catch(() => undefined);
886
+ }
887
+ catch (err) {
888
+ logger.warn(`[n8n-sidecar] failed to persist owner creds: ${err instanceof Error ? err.message : String(err)}`);
889
+ }
890
+ return creds;
891
+ }
892
+ /**
893
+ * Returns a `Cookie: n8n-auth=<jwt>` string by either creating the owner
894
+ * (first boot) or logging in (subsequent boots). Returns null if both
895
+ * fail so the caller can back off gracefully.
896
+ */
897
+ async acquireOwnerCookie(host, owner, log = () => undefined) {
898
+ // First boot: owner does not exist yet, /rest/owner/setup returns 200.
899
+ // Subsequent boots: returns 400 "instance owner already setup"; we fall
900
+ // through to /rest/login.
901
+ const setup = await this.deps
902
+ .fetch(`${host}/rest/owner/setup`, {
903
+ method: "POST",
904
+ headers: { "content-type": "application/json" },
905
+ body: JSON.stringify({
906
+ email: owner.email,
907
+ firstName: owner.firstName,
908
+ lastName: owner.lastName,
909
+ password: owner.password,
910
+ }),
911
+ signal: AbortSignal.timeout(10_000),
912
+ })
913
+ .catch((err) => {
914
+ log(`owner/setup fetch threw: ${err instanceof Error ? err.message : String(err)}`);
750
915
  return null;
916
+ });
917
+ if (setup?.ok) {
918
+ const cookie = extractAuthCookie(setup);
919
+ if (cookie)
920
+ return cookie;
921
+ log("owner/setup 200 but no n8n-auth cookie in response");
922
+ }
923
+ else if (setup) {
924
+ const text = await setup.text().catch(() => "");
925
+ log(`owner/setup ${setup.status}${text ? ` — ${text.slice(0, 160)}` : ""}`);
926
+ }
927
+ const login = await this.deps
928
+ .fetch(`${host}/rest/login`, {
929
+ method: "POST",
930
+ headers: { "content-type": "application/json" },
931
+ body: JSON.stringify({
932
+ emailOrLdapLoginId: owner.email,
933
+ password: owner.password,
934
+ }),
935
+ signal: AbortSignal.timeout(10_000),
936
+ })
937
+ .catch((err) => {
938
+ log(`login fetch threw: ${err instanceof Error ? err.message : String(err)}`);
939
+ return null;
940
+ });
941
+ if (login?.ok) {
942
+ const cookie = extractAuthCookie(login);
943
+ if (cookie)
944
+ return cookie;
945
+ log("login 200 but no n8n-auth cookie in response");
946
+ }
947
+ else if (login) {
948
+ const text = await login.text().catch(() => "");
949
+ log(`login ${login.status}${text ? ` — ${text.slice(0, 160)}` : ""}`);
751
950
  }
951
+ return null;
952
+ }
953
+ /** List scopes the current role may grant when creating an API key. */
954
+ async fetchApiKeyScopes(host, cookie) {
955
+ try {
956
+ const res = await this.deps.fetch(`${host}/rest/api-keys/scopes`, {
957
+ method: "GET",
958
+ headers: { cookie },
959
+ signal: AbortSignal.timeout(5_000),
960
+ });
961
+ if (!res.ok)
962
+ return null;
963
+ const body = (await res.json());
964
+ return Array.isArray(body.data) ? body.data : null;
965
+ }
966
+ catch {
967
+ return null;
968
+ }
969
+ }
970
+ /** 48 bytes of base64url entropy — ~64 chars, far above n8n's min length. */
971
+ generateRandomPassword() {
972
+ // crypto is Node 20+ global; fallback to Math.random if unavailable is
973
+ // intentionally not provided — insecure passwords are worse than a crash.
974
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
975
+ const nodeCrypto = require("node:crypto");
976
+ return nodeCrypto.randomBytes(48).toString("base64url");
752
977
  }
753
978
  /** Stop the sidecar. Idempotent. */
754
979
  async stop() {