@elizaos/app-core 2.0.3-beta.4 → 2.0.3-beta.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/api/i18n-locale-routes.d.ts +7 -0
  2. package/api/i18n-locale-routes.d.ts.map +1 -0
  3. package/api/i18n-locale-routes.js +58 -0
  4. package/api/ios-local-agent-transport.d.ts +3 -0
  5. package/api/ios-local-agent-transport.d.ts.map +1 -1
  6. package/api/ios-local-agent-transport.js +22 -1
  7. package/api/server.d.ts.map +1 -1
  8. package/api/server.js +5 -0
  9. package/benchmark/cerebras-autowire.d.ts.map +1 -1
  10. package/benchmark/cerebras-autowire.js +9 -2
  11. package/package.json +36 -36
  12. package/packaging/debian/rules +1 -1
  13. package/packaging/flatpak/generate-sources.sh +6 -1
  14. package/packaging/snap/snapcraft.yaml +15 -11
  15. package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaAgentService.java +2 -1
  16. package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaNativeBridge.java +5 -0
  17. package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaStartupTrace.java +28 -0
  18. package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaVoicePlugin.java +1 -1
  19. package/platforms/android/app/src/main/java/ai/elizaos/app/MainActivity.java +1 -0
  20. package/platforms/electrobun/package.json +10 -10
  21. package/platforms/electrobun/remotes/local-model/README.md +2 -5
  22. package/platforms/electrobun/remotes/local-model/src/bun/eliza1-catalog.ts +0 -16
  23. package/platforms/electrobun/remotes/local-model/src/dev/phase8-smoke.ts +0 -2
  24. package/platforms/electrobun/scripts/smoke-test.sh +8 -2
  25. package/platforms/electrobun/scripts/stage-macos-release-artifacts.sh +8 -2
  26. package/platforms/electrobun/src/bridge/electrobun-boot-config.ts +8 -0
  27. package/platforms/electrobun/src/bridge/electrobun-crypto-ready.ts +5 -1
  28. package/platforms/electrobun/src/bridge/electrobun-direct-rpc.ts +5 -11
  29. package/platforms/electrobun/src/preload.js +1 -1
  30. package/platforms/ios/App/App/Base.lproj/Main.storyboard +1 -1
  31. package/platforms/ios/App/App/ElizaBridgeViewController.swift +22 -0
  32. package/platforms/ios/App/App/ElizaStartupTrace.swift +12 -0
  33. package/platforms/ios/App/App.xcodeproj/project.pbxproj +8 -0
  34. package/runtime/eliza.d.ts.map +1 -1
  35. package/runtime/eliza.js +18 -0
  36. package/runtime/voice-warmup.d.ts +4 -4
  37. package/runtime/voice-warmup.d.ts.map +1 -1
  38. package/runtime/voice-warmup.js +4 -4
  39. package/scripts/aosp/compile-libllama.mjs +25 -5
  40. package/scripts/aosp/compile-libllama.test.mjs +17 -1
  41. package/scripts/assert-required-bundled-packages.test.ts +24 -2
  42. package/scripts/build-bundled-agent-skills-artifact.mjs +32 -8
  43. package/scripts/build-bundled-orchestrator-artifact.mjs +34 -12
  44. package/scripts/build-llama-cpp-mtp.mjs +27 -3
  45. package/scripts/build-patched-electrobun-cli.mjs +30 -2
  46. package/scripts/bun-riscv64/bun-version.json +1 -1
  47. package/scripts/bun-riscv64/validate.sh +21 -8
  48. package/scripts/check-real-local-reset.ts +13 -1
  49. package/scripts/check-sms-gateway-completion-audit.mjs +96 -31
  50. package/scripts/check-sms-gateway-readiness.mjs +54 -17
  51. package/scripts/clean-repo.mjs +21 -3
  52. package/scripts/copy-runtime-node-modules.ts +68 -63
  53. package/scripts/desktop-build.mjs +46 -5
  54. package/scripts/dev-platform-no-install.test.mjs +19 -0
  55. package/scripts/dev-platform.mjs +6 -2
  56. package/scripts/disable-local-eliza-workspace.mjs +23 -1
  57. package/scripts/docker-ci-smoke.sh +6 -5
  58. package/scripts/ensure-avatars.mjs +18 -5
  59. package/scripts/ensure-bundled-workspaces.mjs +4 -15
  60. package/scripts/ensure-type-package-aliases.mjs +47 -3
  61. package/scripts/ffi-stub/Makefile +5 -2
  62. package/scripts/ios-xcframework/build-xcframework.mjs +22 -11
  63. package/scripts/ios-xcframework/run-physical-device-smoke.mjs +99 -6
  64. package/scripts/ios-xcframework/runtime-symbol-shim.c +560 -105
  65. package/scripts/lib/agent-source-watcher.test.mjs +24 -4
  66. package/scripts/lib/apple-entitlement-audit.test.mjs +19 -2
  67. package/scripts/lib/artifact-staleness.test.mts +18 -1
  68. package/scripts/lib/patch-bun-exports.mjs +20 -2
  69. package/scripts/lib/patch-bun-exports.test.mjs +108 -4
  70. package/scripts/lib/renderer-build-manifest.test.mts +18 -1
  71. package/scripts/lib/stage-android-agent.mjs +16 -3
  72. package/scripts/lib/verify-ondevice-artifact.test.mts +18 -1
  73. package/scripts/link-docker-local-app-packages.mjs +26 -1
  74. package/scripts/mas-smoke.test.mjs +25 -9
  75. package/scripts/omnivoice-fuse/prepare.mjs +27 -4
  76. package/scripts/package-electrobun-linux.mjs +22 -7
  77. package/scripts/patch-deps.mjs +18 -5
  78. package/scripts/playwright-ui-live-stack.ts +16 -3
  79. package/scripts/playwright-ui-smoke-api-stub.mjs +167 -2
  80. package/scripts/prepare-ios-cocoapods.sh +5 -1
  81. package/scripts/prune-cdn-local-assets.mjs +52 -5
  82. package/scripts/run-mobile-build-android-app-actions.test.mjs +19 -1
  83. package/scripts/run-mobile-build-mtp-staleness.test.mts +20 -4
  84. package/scripts/run-mobile-build.mjs +63 -2
  85. package/scripts/stage-android-agent.test.mjs +19 -1
  86. package/scripts/stage-desktop-fused-lib.mjs +26 -1
  87. package/scripts/sync-desktop-renderer.mjs +53 -12
  88. package/scripts/test-sms-gateway-software.mjs +20 -4
  89. package/cli/command-format.d.ts +0 -2
  90. package/cli/command-format.d.ts.map +0 -1
  91. package/cli/command-format.js +0 -20
  92. package/register-runtime-hooks.d.ts +0 -2
  93. package/register-runtime-hooks.d.ts.map +0 -1
  94. package/register-runtime-hooks.js +0 -19
@@ -5,7 +5,7 @@
5
5
  * This command is read-only: it does not send SMS and does not modify macOS,
6
6
  * Android, BlueBubbles, or cloud state.
7
7
  */
8
- import { spawnSync } from "node:child_process";
8
+ import { execFileSync, spawnSync } from "node:child_process";
9
9
  import fs from "node:fs";
10
10
  import path from "node:path";
11
11
  import { fileURLToPath } from "node:url";
@@ -13,9 +13,21 @@ import { fileURLToPath } from "node:url";
13
13
  const scriptDir = path.dirname(fileURLToPath(import.meta.url));
14
14
  const repoRoot = path.resolve(scriptDir, "..", "..", "..");
15
15
  const packagesRoot = path.resolve(scriptDir, "..", "..");
16
- const homepageScript = path.join(scriptDir, "check-homepage-public-readiness.mjs");
16
+ const cleanupHelperScript = path.join(
17
+ repoRoot,
18
+ "packages",
19
+ "scripts",
20
+ "rm-path-recursive.mjs",
21
+ );
22
+ const homepageScript = path.join(
23
+ scriptDir,
24
+ "check-homepage-public-readiness.mjs",
25
+ );
17
26
  const installScript = path.join(scriptDir, "install-android-sms-gateway.mjs");
18
- const cloudOnboardingScript = path.join(scriptDir, "verify-cloud-sms-onboarding-flow.mjs");
27
+ const cloudOnboardingScript = path.join(
28
+ scriptDir,
29
+ "verify-cloud-sms-onboarding-flow.mjs",
30
+ );
19
31
  const routingContractTests = [
20
32
  path.join(
21
33
  packagesRoot,
@@ -43,7 +55,8 @@ const routingContractTests = [
43
55
  "index.test.ts",
44
56
  ),
45
57
  ];
46
- const adbPath = "/opt/homebrew/share/android-commandlinetools/platform-tools/adb";
58
+ const adbPath =
59
+ "/opt/homebrew/share/android-commandlinetools/platform-tools/adb";
47
60
  const bridgeUrl = "http://127.0.0.1:8795";
48
61
  const onboardingContinuationUrl =
49
62
  "https://elizaos-homepage.pages.dev/get-started/?onboardingSession=readiness-smoke";
@@ -71,7 +84,9 @@ function printSection(title, result) {
71
84
  if (result.stdout.trim()) console.log(result.stdout.trim());
72
85
  if (result.stderr.trim()) console.error(result.stderr.trim());
73
86
  if (result.status !== 0) {
74
- console.error(`[sms-gateway-readiness] ${title} exited with ${result.status}`);
87
+ console.error(
88
+ `[sms-gateway-readiness] ${title} exited with ${result.status}`,
89
+ );
75
90
  blocked = true;
76
91
  }
77
92
  }
@@ -91,11 +106,14 @@ function usbInventoryFromIoreg() {
91
106
  const devices = [];
92
107
  for (const line of result.stdout.split(/\r?\n/)) {
93
108
  const nameMatch = line.match(/"USB Product Name"\s*=\s*"([^"]+)"/);
94
- const registryMatch = line.match(/\+\-o\s+([^@<]+)@/);
109
+ const registryMatch = line.match(/\+-o\s+([^@<]+)@/);
95
110
  const name = nameMatch?.[1] ?? registryMatch?.[1];
96
111
  if (!name) continue;
97
112
  const trimmed = name.trim();
98
- if (!trimmed || /Root Hub|XHCI|\bUSB\s*(?:3\.|2\.|1\.|Bus)\b/i.test(trimmed)) {
113
+ if (
114
+ !trimmed ||
115
+ /Root Hub|XHCI|\bUSB\s*(?:3\.|2\.|1\.|Bus)\b/i.test(trimmed)
116
+ ) {
99
117
  continue;
100
118
  }
101
119
  if (!devices.includes(trimmed)) devices.push(trimmed);
@@ -123,9 +141,7 @@ function printHostUsbState() {
123
141
  const fallback = usbInventoryFromIoreg();
124
142
  printSection("host usb", {
125
143
  status: fallback.status,
126
- stdout: fallback.stdout.trim()
127
- ? fallback.stdout.trim()
128
- : fallback.stdout,
144
+ stdout: fallback.stdout.trim() ? fallback.stdout.trim() : fallback.stdout,
129
145
  stderr: fallback.stderr.trim(),
130
146
  });
131
147
  }
@@ -155,7 +171,10 @@ function fetchDeployedGetStartedChunk(body) {
155
171
  };
156
172
  }
157
173
 
158
- const chunkUrl = new URL(`/assets/${getStartedMatch[1]}`, onboardingContinuationUrl).href;
174
+ const chunkUrl = new URL(
175
+ `/assets/${getStartedMatch[1]}`,
176
+ onboardingContinuationUrl,
177
+ ).href;
159
178
  const chunk = curlText(chunkUrl);
160
179
  if (chunk.status !== 0) return chunk;
161
180
 
@@ -186,16 +205,21 @@ function printOnboardingContinuationState() {
186
205
  const statusLine = lines.pop() ?? "";
187
206
  const body = lines.join("\n");
188
207
  const [httpCode, effectiveUrl] = statusLine.split(/\s+/, 2);
189
- const hasAppShell = body.includes('id="root"') && body.includes("/assets/index-");
208
+ const hasAppShell =
209
+ body.includes('id="root"') && body.includes("/assets/index-");
190
210
  const chunk = hasAppShell
191
211
  ? fetchDeployedGetStartedChunk(body)
192
212
  : { status: 1, stdout: "", stderr: "app shell missing" };
193
213
  const hasOnboardingSession = chunk.stdout.includes("onboardingSession");
194
- const hasOnboardingChatApi = chunk.stdout.includes("/api/eliza-app/first-run/chat");
214
+ const hasOnboardingChatApi = chunk.stdout.includes(
215
+ "/api/eliza-app/first-run/chat",
216
+ );
195
217
  const hasBlooioPlatform = chunk.stdout.includes("blooio");
196
218
  const passed =
197
219
  httpCode === "200" &&
198
- effectiveUrl?.startsWith("https://elizaos-homepage.pages.dev/get-started/") &&
220
+ effectiveUrl?.startsWith(
221
+ "https://elizaos-homepage.pages.dev/get-started/",
222
+ ) &&
199
223
  hasAppShell &&
200
224
  chunk.status === 0 &&
201
225
  hasOnboardingSession &&
@@ -207,12 +231,18 @@ function printOnboardingContinuationState() {
207
231
  `url=${effectiveUrl || onboardingContinuationUrl} status=${httpCode || "unknown"} app-shell=${hasAppShell ? "yes" : "no"}` +
208
232
  ` chunk=${chunk.url ?? "unknown"} onboardingSession=${hasOnboardingSession ? "yes" : "no"}` +
209
233
  ` onboardingChatApi=${hasOnboardingChatApi ? "yes" : "no"} platform=blooio:${hasBlooioPlatform ? "yes" : "no"}`,
210
- stderr: [result.stderr.trim(), chunk.stderr?.trim()].filter(Boolean).join("\n"),
234
+ stderr: [result.stderr.trim(), chunk.stderr?.trim()]
235
+ .filter(Boolean)
236
+ .join("\n"),
211
237
  });
212
238
  }
213
239
 
214
240
  function printAndroidState() {
215
- printGateSection("android doctor", run("node", [installScript, "--doctor"]), /\bBLOCKED\b/);
241
+ printGateSection(
242
+ "android doctor",
243
+ run("node", [installScript, "--doctor"]),
244
+ /\bBLOCKED\b/,
245
+ );
216
246
  printSection("adb devices", run(adbPath, ["devices", "-l"]));
217
247
  printHostUsbState();
218
248
  }
@@ -240,6 +270,13 @@ function createRoutingContractCwd() {
240
270
  return cwd;
241
271
  }
242
272
 
273
+ function removePathRecursive(targetPath) {
274
+ execFileSync(process.execPath, [cleanupHelperScript, targetPath], {
275
+ cwd: repoRoot,
276
+ stdio: "inherit",
277
+ });
278
+ }
279
+
243
280
  function printRoutingContractState() {
244
281
  const cwd = createRoutingContractCwd();
245
282
  try {
@@ -250,7 +287,7 @@ function printRoutingContractState() {
250
287
  );
251
288
  }
252
289
  } finally {
253
- fs.rmSync(cwd, { recursive: true, force: true });
290
+ removePathRecursive(cwd);
254
291
  }
255
292
  }
256
293
 
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { spawnSync } from "node:child_process";
2
+ import { execFileSync, spawnSync } from "node:child_process";
3
3
  /**
4
4
  * Remove build outputs and tool caches so the next `bun run build` / dev run is cold.
5
5
  *
@@ -21,14 +21,32 @@ import {
21
21
  import { resolveRepoRootFromImportMeta } from "./lib/repo-root.mjs";
22
22
 
23
23
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
24
+ const RECURSIVE_CLEANUP_SCRIPT = path.resolve(
25
+ __dirname,
26
+ "../../scripts/rm-path-recursive.mjs",
27
+ );
24
28
  const root = resolveRepoRootFromImportMeta(import.meta.url);
25
29
  const deep = process.argv.includes("--deep");
26
30
  const globalToolCache = process.env.ELIZA_CLEAN_GLOBAL_TOOL_CACHE === "1";
27
31
 
32
+ function removeDirectoryRecursive(targetPath) {
33
+ try {
34
+ execFileSync("node", [RECURSIVE_CLEANUP_SCRIPT, path.resolve(targetPath)], {
35
+ encoding: "utf8",
36
+ stdio: "pipe",
37
+ });
38
+ } catch (error) {
39
+ const detail = [error?.stdout, error?.stderr].filter(Boolean).join("\n");
40
+ throw new Error(detail || error?.message || String(error), {
41
+ cause: error,
42
+ });
43
+ }
44
+ }
45
+
28
46
  function rmPath(label, abs) {
29
47
  if (!existsSync(abs)) return;
30
48
  try {
31
- rmSync(abs, { recursive: true, force: true });
49
+ removeDirectoryRecursive(abs);
32
50
  console.log(` removed ${label}`);
33
51
  } catch (err) {
34
52
  console.warn(
@@ -70,7 +88,7 @@ function rmPluginDists() {
70
88
  if (!existsSync(pluginsRoot)) return;
71
89
  for (const name of CAPACITOR_PLUGIN_NAMES) {
72
90
  const dir = nativePluginDir(name);
73
- rmPath(path.relative(root, dir) + "/dist", path.join(dir, "dist"));
91
+ rmPath(`${path.relative(root, dir)}/dist`, path.join(dir, "dist"));
74
92
  }
75
93
  // Any extra plugin dirs (not in canonical list) still get dist removed
76
94
  try {
@@ -52,6 +52,12 @@ const TRACKED_PACKAGE_CACHE = path.join(
52
52
  os.tmpdir(),
53
53
  "eliza-tracked-package-cache",
54
54
  );
55
+ const RM_PATH_RECURSIVE_SCRIPT = path.join(
56
+ ROOT,
57
+ "packages",
58
+ "scripts",
59
+ "rm-path-recursive.mjs",
60
+ );
55
61
  const PUBLISHED_PACKAGE_FETCH_TIMEOUT_MS = 10_000;
56
62
  const ALLOW_REGISTRY_FETCH =
57
63
  process.env.ELIZA_RUNTIME_COPY_ALLOW_REGISTRY_FETCH === "1";
@@ -208,10 +214,6 @@ function readJson<T>(filePath: string): T {
208
214
  return JSON.parse(fs.readFileSync(filePath, "utf8")) as T;
209
215
  }
210
216
 
211
- function sleepSync(ms: number): void {
212
- Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
213
- }
214
-
215
217
  function isEnoentError(error: unknown): boolean {
216
218
  return Boolean(
217
219
  error &&
@@ -220,20 +222,6 @@ function isEnoentError(error: unknown): boolean {
220
222
  );
221
223
  }
222
224
 
223
- function isRetryableRemoveError(error: unknown): boolean {
224
- if (!error || typeof error !== "object") {
225
- return false;
226
- }
227
- const code = (error as { code?: string }).code;
228
- return (
229
- code === "EBUSY" ||
230
- code === "EMFILE" ||
231
- code === "ENFILE" ||
232
- code === "ENOTEMPTY" ||
233
- code === "EPERM"
234
- );
235
- }
236
-
237
225
  function isProcessAlive(pid: number): boolean {
238
226
  try {
239
227
  process.kill(pid, 0);
@@ -259,55 +247,72 @@ function readRuntimeCopyLockOwnerPid(lockDir: string): number | null {
259
247
  }
260
248
  }
261
249
 
262
- function rmRecursive(pathToRemove: string): void {
263
- const maxAttempts = 8;
264
- for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
265
- try {
266
- fs.rmSync(pathToRemove, {
267
- force: true,
268
- maxRetries: 5,
269
- recursive: true,
270
- retryDelay: 100,
271
- });
272
- return;
273
- } catch (error) {
274
- if (!isRetryableRemoveError(error)) {
275
- throw error;
276
- }
277
- if (attempt === maxAttempts - 1) break;
278
- sleepSync(100 * (attempt + 1));
279
- }
250
+ function recursiveRemoveErrorDetail(error: unknown): string {
251
+ if (error && typeof error === "object") {
252
+ const output = error as {
253
+ message?: string;
254
+ stderr?: unknown;
255
+ stdout?: unknown;
256
+ };
257
+ const detail = [output.stdout, output.stderr]
258
+ .filter(
259
+ (chunk): chunk is Buffer | string =>
260
+ Buffer.isBuffer(chunk) || typeof chunk === "string",
261
+ )
262
+ .map((chunk) => chunk.toString().trim())
263
+ .filter(Boolean)
264
+ .join("\n");
265
+ if (detail) return detail;
266
+ if (typeof output.message === "string") return output.message;
280
267
  }
268
+ return String(error);
269
+ }
281
270
 
282
- if (!fs.existsSync(pathToRemove)) {
283
- return;
271
+ function runSharedRecursiveRemove(pathToRemove: string): void {
272
+ try {
273
+ execFileSync(
274
+ "node",
275
+ [RM_PATH_RECURSIVE_SCRIPT, path.resolve(pathToRemove)],
276
+ {
277
+ encoding: "utf8",
278
+ stdio: "pipe",
279
+ },
280
+ );
281
+ } catch (error) {
282
+ throw new Error(recursiveRemoveErrorDetail(error), { cause: error });
284
283
  }
284
+ }
285
285
 
286
- const parentDir = path.dirname(pathToRemove);
287
- const tombstone = path.join(
288
- parentDir,
289
- `.${path.basename(pathToRemove)}.delete-${process.pid}-${Date.now()}`,
290
- );
286
+ function rmRecursive(pathToRemove: string): void {
291
287
  try {
292
- fs.renameSync(pathToRemove, tombstone);
288
+ runSharedRecursiveRemove(pathToRemove);
289
+ return;
293
290
  } catch (error) {
294
291
  if (!fs.existsSync(pathToRemove)) {
295
292
  return;
296
293
  }
297
- throw error;
298
- }
299
294
 
300
- try {
301
- fs.rmSync(tombstone, {
302
- force: true,
303
- maxRetries: 10,
304
- recursive: true,
305
- retryDelay: 100,
306
- });
307
- } catch (error) {
308
- console.warn(
309
- `[runtime-copy] warning: moved retry-resistant path aside but could not fully remove ${tombstone}: ${error instanceof Error ? error.message : String(error)}`,
295
+ const parentDir = path.dirname(pathToRemove);
296
+ const tombstone = path.join(
297
+ parentDir,
298
+ `.${path.basename(pathToRemove)}.delete-${process.pid}-${Date.now()}`,
310
299
  );
300
+ try {
301
+ fs.renameSync(pathToRemove, tombstone);
302
+ } catch {
303
+ if (!fs.existsSync(pathToRemove)) {
304
+ return;
305
+ }
306
+ throw error;
307
+ }
308
+
309
+ try {
310
+ runSharedRecursiveRemove(tombstone);
311
+ } catch (tombstoneError) {
312
+ console.warn(
313
+ `[runtime-copy] warning: moved retry-resistant path aside but could not fully remove ${tombstone}: ${recursiveRemoveErrorDetail(tombstoneError)}`,
314
+ );
315
+ }
311
316
  }
312
317
  }
313
318
 
@@ -331,7 +336,7 @@ function acquireRuntimeCopyLock(targetDist: string): () => void {
331
336
  )}\n`,
332
337
  );
333
338
  return () => {
334
- fs.rmSync(lockDir, { force: true, recursive: true });
339
+ rmRecursive(lockDir);
335
340
  };
336
341
  } catch (error) {
337
342
  if (
@@ -877,7 +882,7 @@ function pruneCopiedPackageDir(name: string, packageDir: string): void {
877
882
  const relativePath = path.relative(packageDir, entryPath);
878
883
 
879
884
  if (shouldPrunePackageRelativePath(name, relativePath)) {
880
- fs.rmSync(entryPath, { recursive: true, force: true });
885
+ rmRecursive(entryPath);
881
886
  continue;
882
887
  }
883
888
 
@@ -887,7 +892,7 @@ function pruneCopiedPackageDir(name: string, packageDir: string): void {
887
892
  !isRequiredRuntimeDocDirectory(entryPath) &&
888
893
  !shouldPreservePrunedPackageEntry(name, packageDir, entryPath))
889
894
  ) {
890
- fs.rmSync(entryPath, { recursive: true, force: true });
895
+ rmRecursive(entryPath);
891
896
  continue;
892
897
  }
893
898
 
@@ -908,7 +913,7 @@ function pruneCopiedPackageDir(name: string, packageDir: string): void {
908
913
  name,
909
914
  )
910
915
  ) {
911
- fs.rmSync(entryPath, { recursive: true, force: true });
916
+ rmRecursive(entryPath);
912
917
  continue;
913
918
  }
914
919
 
@@ -1489,7 +1494,7 @@ function patchCopiedElevenLabsTarSafePaths(
1489
1494
  function patchCopiedAiSdkProviderRuntimeSurface(packageDir: string): void {
1490
1495
  const distIndex = path.join(packageDir, "dist", "index.js");
1491
1496
  if (fs.existsSync(distIndex)) {
1492
- fs.rmSync(path.join(packageDir, "src"), { recursive: true, force: true });
1497
+ rmRecursive(path.join(packageDir, "src"));
1493
1498
  }
1494
1499
  }
1495
1500
 
@@ -1686,7 +1691,7 @@ function fetchPublishedPackage(
1686
1691
  return resolved;
1687
1692
  }
1688
1693
 
1689
- fs.rmSync(cacheDir, { recursive: true, force: true });
1694
+ rmRecursive(cacheDir);
1690
1695
  fs.mkdirSync(cacheDir, { recursive: true });
1691
1696
 
1692
1697
  try {
@@ -1742,7 +1747,7 @@ function materializeTrackedWorkspacePackage(
1742
1747
  return resolved;
1743
1748
  }
1744
1749
 
1745
- fs.rmSync(cacheDir, { recursive: true, force: true });
1750
+ rmRecursive(cacheDir);
1746
1751
  fs.mkdirSync(cacheDir, { recursive: true });
1747
1752
 
1748
1753
  try {
@@ -38,6 +38,12 @@ const COMMAND_PREFIX = (process.env.ELIZA_DESKTOP_COMMAND_PREFIX ?? "")
38
38
  .split(/\s+/)
39
39
  .filter(Boolean);
40
40
  const DESKTOP_BUILD_LOCK_DIR = path.join(ROOT, ".turbo", "desktop-build.lock");
41
+ const CLEANUP_HELPER_SCRIPT = path.join(
42
+ ROOT,
43
+ "packages",
44
+ "scripts",
45
+ "rm-path-recursive.mjs",
46
+ );
41
47
  const RUNTIME_COPY_SCRIPT = fs.existsSync(
42
48
  path.join(ROOT, "scripts", "copy-runtime-node-modules.ts"),
43
49
  )
@@ -88,6 +94,33 @@ function isProcessAlive(pid) {
88
94
  }
89
95
  }
90
96
 
97
+ function removePathRecursive(targetPath, label = "path cleanup") {
98
+ const result = spawnSync("node", [CLEANUP_HELPER_SCRIPT, targetPath], {
99
+ cwd: ROOT,
100
+ encoding: "utf8",
101
+ stdio: "pipe",
102
+ });
103
+
104
+ if (result.error) {
105
+ throw result.error;
106
+ }
107
+ if (result.status !== 0) {
108
+ const detail = [result.stdout, result.stderr]
109
+ .filter(Boolean)
110
+ .join("\n")
111
+ .trim();
112
+ throw new Error(
113
+ `${label} failed with exit code ${result.status ?? 1}${
114
+ detail ? `: ${detail}` : ""
115
+ }`,
116
+ );
117
+ }
118
+ }
119
+
120
+ function removeDesktopBuildLockDir() {
121
+ removePathRecursive(DESKTOP_BUILD_LOCK_DIR, "desktop build lock cleanup");
122
+ }
123
+
91
124
  function withDesktopBuildLock(run) {
92
125
  const lockParent = path.dirname(DESKTOP_BUILD_LOCK_DIR);
93
126
  const staleAfterMs = 30 * 60 * 1000;
@@ -120,7 +153,7 @@ function withDesktopBuildLock(run) {
120
153
  (ownerPid !== null && !isProcessAlive(ownerPid)) ||
121
154
  Date.now() - stat.mtimeMs > staleAfterMs
122
155
  ) {
123
- fs.rmSync(DESKTOP_BUILD_LOCK_DIR, { recursive: true, force: true });
156
+ removeDesktopBuildLockDir();
124
157
  continue;
125
158
  }
126
159
 
@@ -137,7 +170,7 @@ function withDesktopBuildLock(run) {
137
170
  try {
138
171
  run();
139
172
  } finally {
140
- fs.rmSync(DESKTOP_BUILD_LOCK_DIR, { recursive: true, force: true });
173
+ removeDesktopBuildLockDir();
141
174
  }
142
175
  }
143
176
 
@@ -1467,7 +1500,7 @@ function mirrorTreePreservingSymlinks(src, dst) {
1467
1500
  if (dstLstat) {
1468
1501
  try {
1469
1502
  if (dstLstat.isDirectory() && !dstLstat.isSymbolicLink()) {
1470
- fs.rmSync(dst, { force: true, recursive: true });
1503
+ removePathRecursive(dst, "desktop mirror directory cleanup");
1471
1504
  } else {
1472
1505
  fs.unlinkSync(dst);
1473
1506
  }
@@ -1569,8 +1602,16 @@ function packageDesktopBuild() {
1569
1602
  : "Packaging Electrobun app",
1570
1603
  });
1571
1604
 
1572
- mirrorCanonicalToLegacy("build");
1573
- mirrorCanonicalToLegacy("artifacts");
1605
+ // The legacy compatibility path (APP_DIR/electrobun) is not read by any
1606
+ // production code — only docs and this mirror fn reference it (the inno
1607
+ // installer takes BuildDir as a param; copy-runtime-node-modules reads the
1608
+ // canonical platforms/electrobun path). Mirroring the entire ~2.3 GB /
1609
+ // ~110k-file build tree on every package build is pure waste, so make it
1610
+ // opt-in (default off). Set ELIZA_ELECTROBUN_MIRROR_LEGACY=1 to restore it.
1611
+ if (process.env.ELIZA_ELECTROBUN_MIRROR_LEGACY === "1") {
1612
+ mirrorCanonicalToLegacy("build");
1613
+ mirrorCanonicalToLegacy("artifacts");
1614
+ }
1574
1615
 
1575
1616
  // Electrobun's built-in rcedit call references a CI-local D:\ path that
1576
1617
  // doesn't exist on developer machines. Re-embed the icon from a locally
@@ -0,0 +1,19 @@
1
+ import { readFileSync } from "node:fs";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { describe, expect, it } from "vitest";
5
+
6
+ const scriptPath = path.join(
7
+ path.dirname(fileURLToPath(import.meta.url)),
8
+ "dev-platform.mjs",
9
+ );
10
+
11
+ describe("dev-platform API child command", () => {
12
+ it("disables Bun auto-install for the runtime process", () => {
13
+ const source = readFileSync(scriptPath, "utf8");
14
+
15
+ expect(source).toContain(
16
+ 'const apiSourceConditionArgs = ["--no-install", "--conditions=eliza-source"];',
17
+ );
18
+ });
19
+ });
@@ -877,9 +877,13 @@ async function launch() {
877
877
  ...apiEmbeddingWarmupPolicy.env,
878
878
  };
879
879
  const apiWatchEnabled = envFlagEnabled("ELIZA_DESKTOP_API_WATCH");
880
+ // Runtime startup must never mutate dependencies. Optional plugin imports
881
+ // should fail fast when a package is absent instead of letting Bun auto-install
882
+ // transitive native packages inside the desktop app process.
883
+ const apiSourceConditionArgs = ["--no-install", "--conditions=eliza-source"];
880
884
  const apiArgs = apiWatchEnabled
881
- ? ["--watch", devServerEntry]
882
- : [devServerEntry];
885
+ ? [...apiSourceConditionArgs, "--watch", devServerEntry]
886
+ : [...apiSourceConditionArgs, devServerEntry];
883
887
  if (!apiWatchEnabled) {
884
888
  console.log(
885
889
  "[eliza] API file watcher disabled (set ELIZA_DESKTOP_API_WATCH=1 to enable).",
@@ -43,6 +43,7 @@
43
43
  * untouched.
44
44
  */
45
45
 
46
+ import { execFileSync } from "node:child_process";
46
47
  import fs from "node:fs";
47
48
  import path from "node:path";
48
49
  import { fileURLToPath } from "node:url";
@@ -63,6 +64,11 @@ export const CI_LOCKFILES = ["bun.lock", "bun.lockb"];
63
64
 
64
65
  const ELIZAOS_CORE_NAME = "@elizaos/core";
65
66
  const SCRIPT_PATH = fileURLToPath(import.meta.url);
67
+ const SCRIPT_DIR = path.dirname(SCRIPT_PATH);
68
+ const RECURSIVE_CLEANUP_SCRIPT = path.resolve(
69
+ SCRIPT_DIR,
70
+ "../../scripts/rm-path-recursive.mjs",
71
+ );
66
72
  const DEFAULT_REPO_ROOT = process.cwd();
67
73
 
68
74
  function isExactRegistryVersion(specifier) {
@@ -123,6 +129,22 @@ export function writePackageJson(filePath, originalRaw, pkg) {
123
129
  return true;
124
130
  }
125
131
 
132
+ function removeDirectoryRecursive(targetPath) {
133
+ try {
134
+ execFileSync("node", [RECURSIVE_CLEANUP_SCRIPT, path.resolve(targetPath)], {
135
+ cwd: DEFAULT_REPO_ROOT,
136
+ encoding: "utf8",
137
+ stdio: "pipe",
138
+ });
139
+ } catch (error) {
140
+ const detail = [error?.stdout, error?.stderr].filter(Boolean).join("\n");
141
+ throw new Error(
142
+ `Failed to remove directory ${targetPath}${detail ? `: ${detail}` : ""}`,
143
+ { cause: error },
144
+ );
145
+ }
146
+ }
147
+
126
148
  export function expandGlob(glob, { rootDir = DEFAULT_REPO_ROOT } = {}) {
127
149
  if (!glob.includes("*")) {
128
150
  return [glob];
@@ -244,7 +266,7 @@ export function disableLocalElizaWorkspace(
244
266
  const removedLockfiles = [];
245
267
 
246
268
  if (fs.existsSync(elizaRoot)) {
247
- fs.rmSync(disabledElizaRoot, { recursive: true, force: true });
269
+ removeDirectoryRecursive(disabledElizaRoot);
248
270
  fs.renameSync(elizaRoot, disabledElizaRoot);
249
271
  log(
250
272
  `[disable-local-eliza-workspace] Disabled repo-local eliza workspace at ${elizaRoot}`,
@@ -207,6 +207,7 @@ else
207
207
  fi
208
208
  APP_CORE_SCRIPTS_DIR="$APP_CORE_DIR/scripts"
209
209
  AGENT_DIR="$PACKAGES_DIR/agent"
210
+ RM_PATH_RECURSIVE=(node "$PACKAGES_DIR/scripts/rm-path-recursive.mjs")
210
211
  # @elizaos/core source lives under packages/core (current) or packages/typescript
211
212
  # (legacy). Prefer the current name; fall back to the legacy path so older branches
212
213
  # still work.
@@ -668,7 +669,7 @@ if [[ -f packages/contracts/package.json ]] && jq -e '.scripts.build' packages/c
668
669
  "$BUN_BIN" run build
669
670
  popd >/dev/null
670
671
  mkdir -p node_modules/@elizaos
671
- rm -rf node_modules/@elizaos/contracts
672
+ "${RM_PATH_RECURSIVE[@]}" node_modules/@elizaos/contracts
672
673
  ln -s ../../packages/contracts node_modules/@elizaos/contracts
673
674
  fi
674
675
 
@@ -682,7 +683,7 @@ if [[ -f packages/logger/package.json ]] && jq -e '.scripts.build' packages/logg
682
683
  "$BUN_BIN" run build
683
684
  popd >/dev/null
684
685
  mkdir -p node_modules/@elizaos
685
- rm -rf node_modules/@elizaos/logger
686
+ "${RM_PATH_RECURSIVE[@]}" node_modules/@elizaos/logger
686
687
  ln -s ../../packages/logger node_modules/@elizaos/logger
687
688
  fi
688
689
 
@@ -693,7 +694,7 @@ if [[ -f "$TYPESCRIPT_DIR/package.json" ]]; then
693
694
  popd >/dev/null
694
695
  node packages/scripts/prepare-package-dist.mjs "$TYPESCRIPT_DIR"
695
696
  CORE_NODE_MODULE="node_modules/@elizaos/core"
696
- rm -rf "$CORE_NODE_MODULE"
697
+ "${RM_PATH_RECURSIVE[@]}" "$CORE_NODE_MODULE"
697
698
  mkdir -p "$(dirname "$CORE_NODE_MODULE")"
698
699
  ln -s "../../$TYPESCRIPT_DIR" "$CORE_NODE_MODULE"
699
700
  node packages/scripts/patch-nested-core-dist.mjs || true
@@ -713,7 +714,7 @@ for package_dir in packages/shared packages/cloud-sdk packages/cloud-routing pac
713
714
  fi
714
715
  done
715
716
  mkdir -p node_modules/@elizaos
716
- rm -rf node_modules/@elizaos/shared node_modules/@elizaos/cloud-sdk node_modules/@elizaos/cloud-routing node_modules/@elizaos/skills
717
+ "${RM_PATH_RECURSIVE[@]}" node_modules/@elizaos/shared node_modules/@elizaos/cloud-sdk node_modules/@elizaos/cloud-routing node_modules/@elizaos/skills
717
718
  ln -s ../../packages/shared node_modules/@elizaos/shared
718
719
  ln -s ../../packages/cloud-sdk node_modules/@elizaos/cloud-sdk
719
720
  ln -s ../../packages/cloud-routing node_modules/@elizaos/cloud-routing
@@ -802,7 +803,7 @@ popd >/dev/null
802
803
 
803
804
  if [[ -n "${CORE_NODE_MODULE:-}" && -f "$TYPESCRIPT_DIR/dist/package.json" ]]; then
804
805
  log "Relinking @elizaos/core to built dist for Docker runtime"
805
- rm -rf "$CORE_NODE_MODULE"
806
+ "${RM_PATH_RECURSIVE[@]}" "$CORE_NODE_MODULE"
806
807
  mkdir -p "$(dirname "$CORE_NODE_MODULE")"
807
808
  ln -s "../../$TYPESCRIPT_DIR/dist" "$CORE_NODE_MODULE"
808
809
  fi