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

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 (40) hide show
  1. package/package.json +37 -36
  2. package/platforms/electrobun/electrobun.config.ts +31 -1
  3. package/platforms/electrobun/package.json +10 -10
  4. package/platforms/electrobun/remotes/fs/README.md +0 -1
  5. package/platforms/electrobun/remotes/fs/package.json +0 -1
  6. package/platforms/electrobun/remotes/git/README.md +1 -2
  7. package/platforms/electrobun/remotes/git/package.json +0 -1
  8. package/platforms/electrobun/remotes/local-model/README.md +0 -1
  9. package/platforms/electrobun/remotes/local-model/package.json +0 -1
  10. package/platforms/electrobun/remotes/pty/README.md +1 -2
  11. package/platforms/electrobun/remotes/pty/package.json +0 -1
  12. package/platforms/electrobun/remotes/runtime/README.md +1 -5
  13. package/platforms/electrobun/remotes/runtime/package.json +0 -1
  14. package/platforms/electrobun/remotes/surface/README.md +1 -2
  15. package/platforms/electrobun/remotes/surface/package.json +0 -1
  16. package/platforms/electrobun/scripts/sync-web-assets.mjs +31 -2
  17. package/platforms/electrobun/src/api-base.ts +11 -2
  18. package/platforms/electrobun/src/bridge/electrobun-direct-rpc.ts +15 -1
  19. package/platforms/electrobun/src/index.ts +25 -28
  20. package/platforms/electrobun/src/lifecycle/api-base-owner.test.ts +67 -5
  21. package/platforms/electrobun/src/lifecycle/api-base-owner.ts +36 -6
  22. package/platforms/electrobun/src/native/desktop.ts +1 -1
  23. package/platforms/electrobun/src/onboarding-overlay-window.ts +7 -8
  24. package/platforms/electrobun/src/preload.js +1 -1
  25. package/platforms/electrobun/src/rpc-schema.ts +5 -1
  26. package/runtime/voice-warmup.d.ts.map +1 -1
  27. package/runtime/voice-warmup.js +8 -2
  28. package/scripts/benchmark-preflight.mjs +31 -2
  29. package/scripts/build-image.sh +5 -2
  30. package/scripts/bun-riscv64/build.sh +12 -6
  31. package/scripts/bun-riscv64/run-build.sh +10 -1
  32. package/scripts/dev-platform.mjs +19 -10
  33. package/scripts/ios-xcframework/README.md +15 -9
  34. package/scripts/ios-xcframework/run-physical-device-smoke.mjs +5 -3
  35. package/scripts/playwright-ui-live-stack.ts +32 -8
  36. package/scripts/run-mobile-build.mjs +11 -14
  37. package/platforms/electrobun/src/desktop-pill-config.test.ts +0 -27
  38. package/platforms/electrobun/src/desktop-pill-config.ts +0 -40
  39. package/platforms/electrobun/src/pill-window.test.ts +0 -91
  40. package/platforms/electrobun/src/pill-window.ts +0 -99
@@ -30,6 +30,7 @@
30
30
  */
31
31
 
32
32
  import {
33
+ normalizeApiBase,
33
34
  pushApiBaseToRenderer,
34
35
  resolveDesktopRuntimeMode,
35
36
  resolveDesktopRuntimeModeSignal,
@@ -51,6 +52,31 @@ function resolveStartupTraceId(): string | null {
51
52
  return getStartupTraceConfig().sessionId;
52
53
  }
53
54
 
55
+ function resolveCurrentExternalApiBase(): string | null {
56
+ const runtime = resolveDesktopRuntimeMode(process.env);
57
+ if (runtime.mode === "external" && runtime.externalApi.base) {
58
+ return runtime.externalApi.base;
59
+ }
60
+ const currentBase = normalizeApiBase(current.base ?? undefined);
61
+ if (!currentBase) return null;
62
+ try {
63
+ const parsed = new URL(currentBase);
64
+ const hostname = parsed.hostname.toLowerCase();
65
+ if (
66
+ (parsed.protocol === "http:" || parsed.protocol === "https:") &&
67
+ hostname !== "localhost" &&
68
+ hostname !== "127.0.0.1" &&
69
+ hostname !== "::1" &&
70
+ hostname !== "[::1]"
71
+ ) {
72
+ return parsed.origin;
73
+ }
74
+ } catch {
75
+ return null;
76
+ }
77
+ return null;
78
+ }
79
+
54
80
  /**
55
81
  * Update the singleton with the latest known API base + token. Subsequent
56
82
  * `injectIntoHtml(...)` and `pushToWindow(...)` calls read this state.
@@ -107,11 +133,10 @@ export function injectIntoHtml(html: string): string {
107
133
  const runtimeModeInject = runtimeModeSignal
108
134
  ? `window.__ELIZA_DESKTOP_RUNTIME_MODE__=${safeJsonForHtml(runtimeModeSignal)};`
109
135
  : "";
110
- const runtime = resolveDesktopRuntimeMode(process.env);
111
- const externalApiBaseInject =
112
- runtime.mode === "external" && runtime.externalApi.base
113
- ? `window.__ELIZA_DESKTOP_EXTERNAL_API_BASE__=${safeJsonForHtml(runtime.externalApi.base)};`
114
- : "";
136
+ const externalApiBase = resolveCurrentExternalApiBase();
137
+ const externalApiBaseInject = externalApiBase
138
+ ? `window.__ELIZA_DESKTOP_EXTERNAL_API_BASE__=${safeJsonForHtml(externalApiBase)};`
139
+ : "";
115
140
  apiBaseInject = `window.__ELIZA_API_BASE__=${baseLiteral};${runtimeModeInject}${externalApiBaseInject}${tokenInject}${bootConfigInject}`;
116
141
  }
117
142
 
@@ -133,7 +158,12 @@ export function injectIntoHtml(html: string): string {
133
158
  */
134
159
  export function pushToWindow(win: { webview: { rpc?: unknown } }): void {
135
160
  if (!current.base) return;
136
- pushApiBaseToRenderer(win, current.base, current.token || undefined);
161
+ pushApiBaseToRenderer(
162
+ win,
163
+ current.base,
164
+ current.token || undefined,
165
+ resolveCurrentExternalApiBase(),
166
+ );
137
167
  }
138
168
 
139
169
  /**
@@ -2252,7 +2252,7 @@ X-GNOME-Autostart-enabled=true
2252
2252
  error instanceof Error ? error.message : error ? String(error) : null;
2253
2253
 
2254
2254
  try {
2255
- const localInfo = await Updater.getLocallocalInfo();
2255
+ const localInfo = await Updater.getLocalInfo();
2256
2256
  currentVersion = localInfo.version;
2257
2257
  currentHash = localInfo.hash;
2258
2258
  channel = localInfo.channel;
@@ -18,7 +18,8 @@
18
18
  * onboarding completes the overlay is closed and the normal dashboard window
19
19
  * opens.
20
20
  *
21
- * Modeled on pill-window.ts (the existing small borderless/transparent overlay).
21
+ * Uses the same small borderless/transparent-window constraints as the other
22
+ * focused native overlay surfaces.
22
23
  */
23
24
 
24
25
  import { type BrowserWindow, Screen } from "electrobun/bun";
@@ -51,11 +52,10 @@ export function createOnboardingOverlayWindow(args: {
51
52
  }
52
53
 
53
54
  // Use the full work area so the renderer can place UI elements anywhere on
54
- // screen — the onboarding card is pinned top-right via CSS, and the voice
55
- // pill is centered at the bottom. The window is transparent + passthrough,
56
- // so clicks on empty regions fall through to the desktop. The
57
- // makeKeyAndOrderFront call after dom-ready ensures the interactive elements
58
- // (buttons, pill) receive clicks.
55
+ // screen — the onboarding card is pinned top-right via CSS. The window is
56
+ // transparent + passthrough, so clicks on empty regions fall through to the
57
+ // desktop. The makeKeyAndOrderFront call after dom-ready ensures the
58
+ // interactive elements receive clicks.
59
59
  const workArea = Screen.getPrimaryDisplay().workArea;
60
60
  const frame = {
61
61
  x: workArea.x,
@@ -72,8 +72,7 @@ export function createOnboardingOverlayWindow(args: {
72
72
  frame,
73
73
  titleBarStyle: "hidden",
74
74
  transparent: true,
75
- // Match the pill window's proven-visible config. A small, borderless,
76
- // transparent window only showed reliably with activate:false — a
75
+ // A small, borderless, transparent window only showed reliably with activate:false — a
77
76
  // borderless NSWindow cannot become key, and activate:true left the small
78
77
  // window unshown (the full-screen variant happened to paint regardless).
79
78
  // No passthrough: the window is small, so clicks outside it already reach
@@ -1 +1 @@
1
- (()=>{function w(e,t){let r=e.map((n)=>`"${n}"`).join(", ");return Error(`This RPC instance cannot ${t} because the transport did not provide one or more of these methods: ${r}`)}function I(e={}){let t={},r={},n=void 0;function o(s){if(r.unregisterHandler)r.unregisterHandler();r=s,r.registerHandler?.($)}function c(s){if(typeof s==="function"){n=s;return}n=(i,u)=>{let p=s[i];if(p)return p(u);let g=s._;if(!g)throw Error(`The requested method has no handler: ${String(i)}`);return g(i,u)}}let{maxRequestTime:a=1000}=e;if(e.transport)o(e.transport);if(e.requestHandler)c(e.requestHandler);if(e._debugHooks)t=e._debugHooks;let R=0;function m(){if(R<=10000000000)return++R;return R=0}let d=new Map,l=new Map;function S(s,...i){let u=i[0];return new Promise((p,g)=>{if(!r.send)throw w(["send"],"make requests");let f=m(),B={type:"request",id:f,method:s,params:u};if(d.set(f,{resolve:p,reject:g}),a!==1/0)l.set(f,setTimeout(()=>{l.delete(f),d.delete(f),g(Error("RPC request timed out."))},a));t.onSend?.(B),r.send(B)})}let q=new Proxy(S,{get:(s,i,u)=>{if(i in s)return Reflect.get(s,i,u);return(p)=>S(i,p)}}),T=q;function x(s,...i){let u=i[0];if(!r.send)throw w(["send"],"send messages");let p={type:"message",id:s,payload:u};t.onSend?.(p),r.send(p)}let v=new Proxy(x,{get:(s,i,u)=>{if(i in s)return Reflect.get(s,i,u);return(p)=>x(i,p)}}),O=v,h=new Map,b=new Set;function Y(s,i){if(!r.registerHandler)throw w(["registerHandler"],"register message listeners");if(s==="*"){b.add(i);return}if(!h.has(s))h.set(s,new Set);h.get(s).add(i)}function U(s,i){if(s==="*"){b.delete(i);return}if(h.get(s)?.delete(i),h.get(s)?.size===0)h.delete(s)}async function $(s){if(t.onReceive?.(s),!("type"in s))throw Error("Message does not contain a type.");if(s.type==="request"){if(!r.send||!n)throw w(["send","requestHandler"],"handle requests");let{id:i,method:u,params:p}=s,g;try{g={type:"response",id:i,success:!0,payload:await n(u,p)}}catch(f){if(!(f instanceof Error))throw f;g={type:"response",id:i,success:!1,error:f.message}}t.onSend?.(g),r.send(g);return}if(s.type==="response"){let i=l.get(s.id);if(i!=null)clearTimeout(i);l.delete(s.id);let{resolve:u,reject:p}=d.get(s.id)??{};if(d.delete(s.id),!s.success)p?.(Error(s.error));else u?.(s.payload);return}if(s.type==="message"){for(let u of b)u(s.id,s.payload);let i=h.get(s.id);if(!i)return;for(let u of i)u(s.payload);return}throw Error(`Unexpected RPC message type: ${s.type}`)}return{setTransport:o,setRequestHandler:c,request:q,requestProxy:T,send:v,sendProxy:O,addMessageListener:Y,removeMessageListener:U,proxy:{send:O,request:T}}}function H(e,t){let r={maxRequestTime:t.maxRequestTime,requestHandler:{...t.handlers.requests,...t.extraRequestHandlers},transport:{registerHandler:()=>{}}},n=I(r),o=t.handlers.messages;if(o)n.addMessageListener("*",(c,a)=>{let R=o["*"];if(R)R(c,a);let m=o[c];if(m)m(a)});return n}var{__electrobunWebviewId:L,__electrobunRpcSocketPort:W}=window;class P{bunSocket;rpc;rpcHandler;constructor(e){this.rpc=e.rpc,this.init()}init(){if(this.initSocketToBun(),window.__electrobun.receiveMessageFromBun=this.receiveMessageFromBun.bind(this),this.rpc)this.rpc.setTransport(this.createTransport())}initSocketToBun(){if(!W||!L)return;let e=new WebSocket(`ws://localhost:${W}/socket?webviewId=${L}`);this.bunSocket=e,e.addEventListener("open",()=>{}),e.addEventListener("message",async(t)=>{let r=t.data;if(typeof r==="string")try{let n=JSON.parse(r),o=await window.__electrobun_decrypt(n.encryptedData,n.iv,n.tag);this.rpcHandler?.(JSON.parse(o))}catch(n){console.error("Error parsing bun message:",n)}else if(r instanceof Blob);else console.error("UNKNOWN DATA TYPE RECEIVED:",t.data)}),e.addEventListener("error",(t)=>{console.error("Socket error:",t)}),e.addEventListener("close",(t)=>{})}createTransport(){let e=this;return{send(t){try{let r=JSON.stringify(t);e.bunBridge(r)}catch(r){console.error("bun: failed to serialize message to webview",r)}},registerHandler(t){e.rpcHandler=t}}}async bunBridge(e){if(this.bunSocket?.readyState===WebSocket.OPEN)try{let{encryptedData:t,iv:r,tag:n}=await window.__electrobun_encrypt(e),c=JSON.stringify({encryptedData:t,iv:r,tag:n});this.bunSocket.send(c);return}catch(t){console.error("Error sending message to bun via socket:",t)}window.__electrobunBunBridge?.postMessage(e)}receiveMessageFromBun(e){if(this.rpcHandler)this.rpcHandler(e)}static defineRPC(e){return H("webview",{...e,extraRequestHandlers:{evaluateJavascriptWithResponse:({script:t})=>new Promise((r)=>{try{let o=Function(t)();if(o instanceof Promise)o.then((c)=>{r(c)}).catch((c)=>{console.error("bun: async script execution failed",c),r(String(c))});else r(o)}catch(n){console.error("bun: failed to eval script",n),r(String(n))}})}})}}function C(e,t){if(e===404&&z(t))return null;return e>=500?"error":"warn"}function z(e){let t=e?.method?.toUpperCase()??"GET";if(t!=="GET"&&t!=="HEAD")return!1;let r=e?.url;if(!r)return!1;let n=r;try{n=new URL(r,"http://localhost").pathname}catch{n=r.split("?")[0]??r}return n==="/api/vincent/status"}var A={evaluate:async(e)=>({ok:!1,error:`BrowserWorkspaceView is not mounted — cannot evaluate tab ${e}`}),getTabRect:async()=>null};function E(){if(typeof window>"u")return A;return window.__ELIZA_BROWSER_TABS_REGISTRY__??A}var Z=Symbol.for("elizaos.app.boot-config"),N=Z;function F(e,t){let n={...e.__ELIZAOS_APP_BOOT_CONFIG__??e.__ELIZA_APP_BOOT_CONFIG__??e[N]?.current??{},...t};return e.__ELIZAOS_APP_BOOT_CONFIG__=n,e.__ELIZA_APP_BOOT_CONFIG__=n,e[N]={current:n},n}function D(){if(typeof window.__electrobun>"u")window.__electrobun={receiveMessageFromBun:(e)=>{},receiveInternalMessageFromBun:(e)=>{}}}var y={},K="__ELIZA_ELECTROBUN_LOG_MIRROR__";function G(e){if(!e||typeof e!=="object")throw Error("Electrobun RPC params must be an object");return e}function k(e,t){let r=e[t];if(typeof r!=="string")throw Error(`Electrobun RPC param "${t}" must be a string`);return r}function j(e,t){let r=e[t];if(typeof r!=="number"||!Number.isFinite(r))throw Error(`Electrobun RPC param "${t}" must be a finite number`);return r}D();function X(e,t){if(e==="apiBaseUpdate"){let n=t;if(window.__ELIZA_API_BASE__=n.base,n.token)Object.defineProperty(window,"__ELIZA_API_TOKEN__",{value:n.token,configurable:!0,writable:!0,enumerable:!1});F(window,{apiBase:n.base,...n.token?{apiToken:n.token}:{}})}let r=y[e];if(!r)return;for(let n of Array.from(r))try{n(t)}catch(o){console.error(`[ElectrobunBridge] Listener error for ${e}:`,o)}}function J(e,t){if(typeof e==="string")X(e,t)}var _=P.defineRPC({maxRequestTime:600000,handlers:{requests:{browserWorkspaceRendererEvaluate:async(e)=>{let t=G(e),r=k(t,"id"),n=k(t,"script"),o=j(t,"timeoutMs");return await E().evaluate(r,n,o)},browserWorkspaceRendererGetTabRect:async(e)=>{let t=G(e);return E().getTabRect(k(t,"id"))}},messages:{"*":J}}});new P({rpc:_});function M(e){if(e instanceof Error)return{name:e.name,message:e.message,stack:e.stack};return e}var V=new Proxy(_.request,{get(e,t,r){let n=Reflect.get(e,t,r);if(typeof n!=="function")return n;return async(o)=>{try{return await n.call(e,o)}catch(c){throw _.request.rendererReportDiagnostic({level:"error",source:"rpc",message:`Electrobun RPC request failed: ${String(t)}`,details:M(c)}).catch(()=>{}),c}}}}),Q={request:V,onMessage:(e,t)=>{if(!y[e])y[e]=new Set;y[e].add(t)},offMessage:(e,t)=>{if(y[e]?.delete(t),y[e]?.size===0)delete y[e]}};window.__ELIZA_ELECTROBUN_RPC__=Q;function ee(){let e=window;if(e[K])return;e[K]=!0;let t=(n,o,c,a)=>{_.request.rendererReportDiagnostic({level:n,source:o,message:c,details:a}).catch(()=>{})},r=["log","info","warn","error"];for(let n of r){let o=console[n].bind(console);console[n]=(...c)=>{o(...c),t(n,"console",c.map((a)=>{if(typeof a==="string")return a;try{return JSON.stringify(a)}catch{return String(a)}}).join(" "))}}if(window.addEventListener("error",(n)=>{let o=n.target;if(o&&(o.src||o.href)){t("error","resource","Failed to load resource",{tagName:o.tagName,src:o.src,href:o.href});return}t("error","window.onerror",n.message||"Unhandled window error",{filename:n.filename,lineno:n.lineno,colno:n.colno})},!0),window.addEventListener("unhandledrejection",(n)=>{t("error","unhandledrejection","Unhandled promise rejection",M(n.reason))}),typeof window.fetch==="function"){let n=window.fetch.bind(window);window.fetch=async(...o)=>{let c=Date.now(),a=o[0],R=o[1],m=typeof a==="string"?a:a instanceof Request?a.url:String(a),d=R?.method??(a instanceof Request?a.method:void 0)??"GET";try{let l=await n(...o),S=l.ok?null:C(l.status,{url:m,method:d});if(S)t(S,"fetch",`HTTP ${l.status} ${l.statusText}`,{url:m,method:d,durationMs:Date.now()-c});return l}catch(l){throw t("error","fetch","Fetch failed",{url:m,method:d,durationMs:Date.now()-c,error:M(l)}),l}}}if(typeof XMLHttpRequest<"u"){let n=XMLHttpRequest.prototype.open,o=XMLHttpRequest.prototype.send;XMLHttpRequest.prototype.open=function(c,a,...R){return this.__elizaDiag={method:c,url:String(a),startedAt:Date.now()},n.call(this,c,a,...R)},XMLHttpRequest.prototype.send=function(...c){let a=this,R=()=>{let d=a.__elizaDiag;if(!d)return;let l=a.status>=400?C(a.status,{url:d.url,method:d.method}):null;if(l)t(l,"xhr",`HTTP ${a.status}`,{url:d.url,method:d.method,durationMs:Date.now()-d.startedAt})},m=()=>{let d=a.__elizaDiag;t("error","xhr","XMLHttpRequest failed",{url:d?.url,method:d?.method,durationMs:d?Date.now()-d.startedAt:void 0})};return a.addEventListener("loadend",R,{once:!0}),a.addEventListener("error",m,{once:!0}),o.call(this,...c)}}}ee();})();
1
+ (()=>{function _(e,t){let r=e.map((n)=>`"${n}"`).join(", ");return Error(`This RPC instance cannot ${t} because the transport did not provide one or more of these methods: ${r}`)}function I(e={}){let t={},r={},n=void 0;function o(s){if(r.unregisterHandler)r.unregisterHandler();r=s,r.registerHandler?.($)}function c(s){if(typeof s==="function"){n=s;return}n=(i,u)=>{let p=s[i];if(p)return p(u);let g=s._;if(!g)throw Error(`The requested method has no handler: ${String(i)}`);return g(i,u)}}let{maxRequestTime:a=1000}=e;if(e.transport)o(e.transport);if(e.requestHandler)c(e.requestHandler);if(e._debugHooks)t=e._debugHooks;let R=0;function m(){if(R<=10000000000)return++R;return R=0}let d=new Map,l=new Map;function S(s,...i){let u=i[0];return new Promise((p,g)=>{if(!r.send)throw _(["send"],"make requests");let f=m(),B={type:"request",id:f,method:s,params:u};if(d.set(f,{resolve:p,reject:g}),a!==1/0)l.set(f,setTimeout(()=>{l.delete(f),d.delete(f),g(Error("RPC request timed out."))},a));t.onSend?.(B),r.send(B)})}let q=new Proxy(S,{get:(s,i,u)=>{if(i in s)return Reflect.get(s,i,u);return(p)=>S(i,p)}}),T=q;function x(s,...i){let u=i[0];if(!r.send)throw _(["send"],"send messages");let p={type:"message",id:s,payload:u};t.onSend?.(p),r.send(p)}let v=new Proxy(x,{get:(s,i,u)=>{if(i in s)return Reflect.get(s,i,u);return(p)=>x(i,p)}}),O=v,h=new Map,b=new Set;function Y(s,i){if(!r.registerHandler)throw _(["registerHandler"],"register message listeners");if(s==="*"){b.add(i);return}if(!h.has(s))h.set(s,new Set);h.get(s).add(i)}function U(s,i){if(s==="*"){b.delete(i);return}if(h.get(s)?.delete(i),h.get(s)?.size===0)h.delete(s)}async function $(s){if(t.onReceive?.(s),!("type"in s))throw Error("Message does not contain a type.");if(s.type==="request"){if(!r.send||!n)throw _(["send","requestHandler"],"handle requests");let{id:i,method:u,params:p}=s,g;try{g={type:"response",id:i,success:!0,payload:await n(u,p)}}catch(f){if(!(f instanceof Error))throw f;g={type:"response",id:i,success:!1,error:f.message}}t.onSend?.(g),r.send(g);return}if(s.type==="response"){let i=l.get(s.id);if(i!=null)clearTimeout(i);l.delete(s.id);let{resolve:u,reject:p}=d.get(s.id)??{};if(d.delete(s.id),!s.success)p?.(Error(s.error));else u?.(s.payload);return}if(s.type==="message"){for(let u of b)u(s.id,s.payload);let i=h.get(s.id);if(!i)return;for(let u of i)u(s.payload);return}throw Error(`Unexpected RPC message type: ${s.type}`)}return{setTransport:o,setRequestHandler:c,request:q,requestProxy:T,send:v,sendProxy:O,addMessageListener:Y,removeMessageListener:U,proxy:{send:O,request:T}}}function H(e,t){let r={maxRequestTime:t.maxRequestTime,requestHandler:{...t.handlers.requests,...t.extraRequestHandlers},transport:{registerHandler:()=>{}}},n=I(r),o=t.handlers.messages;if(o)n.addMessageListener("*",(c,a)=>{let R=o["*"];if(R)R(c,a);let m=o[c];if(m)m(a)});return n}var{__electrobunWebviewId:L,__electrobunRpcSocketPort:A}=window;class w{bunSocket;rpc;rpcHandler;constructor(e){this.rpc=e.rpc,this.init()}init(){if(this.initSocketToBun(),window.__electrobun.receiveMessageFromBun=this.receiveMessageFromBun.bind(this),this.rpc)this.rpc.setTransport(this.createTransport())}initSocketToBun(){if(!A||!L)return;let e=new WebSocket(`ws://localhost:${A}/socket?webviewId=${L}`);this.bunSocket=e,e.addEventListener("open",()=>{}),e.addEventListener("message",async(t)=>{let r=t.data;if(typeof r==="string")try{let n=JSON.parse(r),o=await window.__electrobun_decrypt(n.encryptedData,n.iv,n.tag);this.rpcHandler?.(JSON.parse(o))}catch(n){console.error("Error parsing bun message:",n)}else if(r instanceof Blob);else console.error("UNKNOWN DATA TYPE RECEIVED:",t.data)}),e.addEventListener("error",(t)=>{console.error("Socket error:",t)}),e.addEventListener("close",(t)=>{})}createTransport(){let e=this;return{send(t){try{let r=JSON.stringify(t);e.bunBridge(r)}catch(r){console.error("bun: failed to serialize message to webview",r)}},registerHandler(t){e.rpcHandler=t}}}async bunBridge(e){if(this.bunSocket?.readyState===WebSocket.OPEN)try{let{encryptedData:t,iv:r,tag:n}=await window.__electrobun_encrypt(e),c=JSON.stringify({encryptedData:t,iv:r,tag:n});this.bunSocket.send(c);return}catch(t){console.error("Error sending message to bun via socket:",t)}window.__electrobunBunBridge?.postMessage(e)}receiveMessageFromBun(e){if(this.rpcHandler)this.rpcHandler(e)}static defineRPC(e){return H("webview",{...e,extraRequestHandlers:{evaluateJavascriptWithResponse:({script:t})=>new Promise((r)=>{try{let o=Function(t)();if(o instanceof Promise)o.then((c)=>{r(c)}).catch((c)=>{console.error("bun: async script execution failed",c),r(String(c))});else r(o)}catch(n){console.error("bun: failed to eval script",n),r(String(n))}})}})}}function C(e,t){if(e===404&&Z(t))return null;return e>=500?"error":"warn"}function Z(e){let t=e?.method?.toUpperCase()??"GET";if(t!=="GET"&&t!=="HEAD")return!1;let r=e?.url;if(!r)return!1;let n=r;try{n=new URL(r,"http://localhost").pathname}catch{n=r.split("?")[0]??r}return n==="/api/vincent/status"}var W={evaluate:async(e)=>({ok:!1,error:`BrowserWorkspaceView is not mounted — cannot evaluate tab ${e}`}),getTabRect:async()=>null};function E(){if(typeof window>"u")return W;return window.__ELIZA_BROWSER_TABS_REGISTRY__??W}var z=Symbol.for("elizaos.app.boot-config"),N=z;function F(e,t){let n={...e.__ELIZAOS_APP_BOOT_CONFIG__??e.__ELIZA_APP_BOOT_CONFIG__??e[N]?.current??{},...t};return e.__ELIZAOS_APP_BOOT_CONFIG__=n,e.__ELIZA_APP_BOOT_CONFIG__=n,e[N]={current:n},n}function D(){if(typeof window.__electrobun>"u")window.__electrobun={receiveMessageFromBun:(e)=>{},receiveInternalMessageFromBun:(e)=>{}}}var y={},K="__ELIZA_ELECTROBUN_LOG_MIRROR__";function G(e){if(!e||typeof e!=="object")throw Error("Electrobun RPC params must be an object");return e}function k(e,t){let r=e[t];if(typeof r!=="string")throw Error(`Electrobun RPC param "${t}" must be a string`);return r}function X(e,t){let r=e[t];if(typeof r!=="number"||!Number.isFinite(r))throw Error(`Electrobun RPC param "${t}" must be a finite number`);return r}D();function j(e,t){if(e==="apiBaseUpdate"){let n=t;if(window.__ELIZA_API_BASE__=n.base,typeof n.externalApiBase==="string"&&n.externalApiBase.trim())window.__ELIZA_DESKTOP_EXTERNAL_API_BASE__=n.externalApiBase.trim();else Reflect.deleteProperty(window,"__ELIZA_DESKTOP_EXTERNAL_API_BASE__");if(n.token)Object.defineProperty(window,"__ELIZA_API_TOKEN__",{value:n.token,configurable:!0,writable:!0,enumerable:!1});F(window,{apiBase:n.base,...n.token?{apiToken:n.token}:{}})}let r=y[e];if(!r)return;for(let n of Array.from(r))try{n(t)}catch(o){console.error(`[ElectrobunBridge] Listener error for ${e}:`,o)}}function J(e,t){if(typeof e==="string")j(e,t)}var P=w.defineRPC({maxRequestTime:600000,handlers:{requests:{browserWorkspaceRendererEvaluate:async(e)=>{let t=G(e),r=k(t,"id"),n=k(t,"script"),o=X(t,"timeoutMs");return await E().evaluate(r,n,o)},browserWorkspaceRendererGetTabRect:async(e)=>{let t=G(e);return E().getTabRect(k(t,"id"))}},messages:{"*":J}}});new w({rpc:P});function M(e){if(e instanceof Error)return{name:e.name,message:e.message,stack:e.stack};return e}var V=new Proxy(P.request,{get(e,t,r){let n=Reflect.get(e,t,r);if(typeof n!=="function")return n;return async(o)=>{try{return await n.call(e,o)}catch(c){throw P.request.rendererReportDiagnostic({level:"error",source:"rpc",message:`Electrobun RPC request failed: ${String(t)}`,details:M(c)}).catch(()=>{}),c}}}}),Q={request:V,onMessage:(e,t)=>{if(!y[e])y[e]=new Set;y[e].add(t)},offMessage:(e,t)=>{if(y[e]?.delete(t),y[e]?.size===0)delete y[e]}};window.__ELIZA_ELECTROBUN_RPC__=Q;function ee(){let e=window;if(e[K])return;e[K]=!0;let t=(n,o,c,a)=>{P.request.rendererReportDiagnostic({level:n,source:o,message:c,details:a}).catch(()=>{})},r=["log","info","warn","error"];for(let n of r){let o=console[n].bind(console);console[n]=(...c)=>{o(...c),t(n,"console",c.map((a)=>{if(typeof a==="string")return a;try{return JSON.stringify(a)}catch{return String(a)}}).join(" "))}}if(window.addEventListener("error",(n)=>{let o=n.target;if(o&&(o.src||o.href)){t("error","resource","Failed to load resource",{tagName:o.tagName,src:o.src,href:o.href});return}t("error","window.onerror",n.message||"Unhandled window error",{filename:n.filename,lineno:n.lineno,colno:n.colno})},!0),window.addEventListener("unhandledrejection",(n)=>{t("error","unhandledrejection","Unhandled promise rejection",M(n.reason))}),typeof window.fetch==="function"){let n=window.fetch.bind(window);window.fetch=async(...o)=>{let c=Date.now(),a=o[0],R=o[1],m=typeof a==="string"?a:a instanceof Request?a.url:String(a),d=R?.method??(a instanceof Request?a.method:void 0)??"GET";try{let l=await n(...o),S=l.ok?null:C(l.status,{url:m,method:d});if(S)t(S,"fetch",`HTTP ${l.status} ${l.statusText}`,{url:m,method:d,durationMs:Date.now()-c});return l}catch(l){throw t("error","fetch","Fetch failed",{url:m,method:d,durationMs:Date.now()-c,error:M(l)}),l}}}if(typeof XMLHttpRequest<"u"){let n=XMLHttpRequest.prototype.open,o=XMLHttpRequest.prototype.send;XMLHttpRequest.prototype.open=function(c,a,...R){return this.__elizaDiag={method:c,url:String(a),startedAt:Date.now()},n.call(this,c,a,...R)},XMLHttpRequest.prototype.send=function(...c){let a=this,R=()=>{let d=a.__elizaDiag;if(!d)return;let l=a.status>=400?C(a.status,{url:d.url,method:d.method}):null;if(l)t(l,"xhr",`HTTP ${a.status}`,{url:d.url,method:d.method,durationMs:Date.now()-d.startedAt})},m=()=>{let d=a.__elizaDiag;t("error","xhr","XMLHttpRequest failed",{url:d?.url,method:d?.method,durationMs:d?Date.now()-d.startedAt:void 0})};return a.addEventListener("loadend",R,{once:!0}),a.addEventListener("error",m,{once:!0}),o.call(this,...c)}}}ee();})();
@@ -2363,7 +2363,11 @@ export type ElizaDesktopRPCSchema = {
2363
2363
  floatingChatStatusChanged: FloatingChatStatus;
2364
2364
 
2365
2365
  // API Base injection
2366
- apiBaseUpdate: { base: string; token?: string };
2366
+ apiBaseUpdate: {
2367
+ base: string;
2368
+ token?: string;
2369
+ externalApiBase?: string | null;
2370
+ };
2367
2371
 
2368
2372
  // Share target
2369
2373
  shareTargetReceived: { url: string; text?: string };
@@ -1 +1 @@
1
- {"version":3,"file":"voice-warmup.d.ts","sourceRoot":"","sources":["../../../../../src/runtime/voice-warmup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,iFAAiF;AACjF,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACjE;AAED,MAAM,WAAW,eAAe;IAC9B,oEAAoE;IACpE,MAAM,EAAE,OAAO,CAAC;IAChB,4CAA4C;IAC5C,OAAO,EAAE,OAAO,CAAC;IACjB,8EAA8E;IAC9E,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;;;;;OAOG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,kEAAkE;AAClE,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,eAAe,GAAG,OAAO,CAMhE;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,IAAI,MAAM,CAmB7C;AAED,MAAM,WAAW,qBAAqB;IACpC,+EAA+E;IAC/E,OAAO,EAAE,OAAO,CAAC;IACjB,qCAAqC;IACrC,iBAAiB,EAAE,OAAO,CAAC;CAC5B;AAED,KAAK,OAAO,GAAG;IACb,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5B,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAC7B,CAAC;AA8BF;;;;;;;GAOG;AACH,wBAAsB,eAAe,CACnC,OAAO,EAAE,kBAAkB,EAC3B,KAAK,EAAE,qBAAqB,EAC5B,GAAG,GAAE,OAAiB,GACrB,OAAO,CAAC,IAAI,CAAC,CA+Bf"}
1
+ {"version":3,"file":"voice-warmup.d.ts","sourceRoot":"","sources":["../../../../../src/runtime/voice-warmup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,iFAAiF;AACjF,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACjE;AAED,MAAM,WAAW,eAAe;IAC9B,oEAAoE;IACpE,MAAM,EAAE,OAAO,CAAC;IAChB,4CAA4C;IAC5C,OAAO,EAAE,OAAO,CAAC;IACjB,8EAA8E;IAC9E,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;;;;;OAOG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,kEAAkE;AAClE,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,eAAe,GAAG,OAAO,CAMhE;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,IAAI,MAAM,CAmB7C;AAED,MAAM,WAAW,qBAAqB;IACpC,+EAA+E;IAC/E,OAAO,EAAE,OAAO,CAAC;IACjB,qCAAqC;IACrC,iBAAiB,EAAE,OAAO,CAAC;CAC5B;AAED,KAAK,OAAO,GAAG;IACb,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5B,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAC7B,CAAC;AAmCF;;;;;;;GAOG;AACH,wBAAsB,eAAe,CACnC,OAAO,EAAE,kBAAkB,EAC3B,KAAK,EAAE,qBAAqB,EAC5B,GAAG,GAAE,OAAiB,GACrB,OAAO,CAAC,IAAI,CAAC,CAiCf"}
@@ -52,6 +52,10 @@ export function buildSilentWarmupWav() {
52
52
  return buf;
53
53
  }
54
54
  const noopLog = { info: () => { }, warn: () => { } };
55
+ function isMissingModelHandlerError(err) {
56
+ const message = err instanceof Error ? err.message : String(err);
57
+ return message.includes("No handler found for delegate type");
58
+ }
55
59
  /**
56
60
  * Retry a warmup call up to `maxRetries` times when the error message
57
61
  * indicates a transient HTTP issue (429 / 503). Waits `delayMs` between
@@ -89,13 +93,15 @@ export async function warmVoiceModels(runtime, types, log = noopLog) {
89
93
  log.info("[eliza] Voice TTS model: ready");
90
94
  }
91
95
  catch (err) {
92
- log.warn(`[eliza] Voice TTS warmup failed (will load on first use): ${err instanceof Error ? err.message : String(err)}`);
96
+ const logMethod = isMissingModelHandlerError(err) ? log.info : log.warn;
97
+ logMethod(`[eliza] Voice TTS warmup skipped (will load on first use): ${err instanceof Error ? err.message : String(err)}`);
93
98
  }
94
99
  try {
95
100
  await withRetry(() => runtime.useModel(types.transcriptionType, buildSilentWarmupWav()));
96
101
  log.info("[eliza] Voice STT model: ready");
97
102
  }
98
103
  catch (err) {
99
- log.warn(`[eliza] Voice STT warmup failed (will load on first use): ${err instanceof Error ? err.message : String(err)}`);
104
+ const logMethod = isMissingModelHandlerError(err) ? log.info : log.warn;
105
+ logMethod(`[eliza] Voice STT warmup skipped (will load on first use): ${err instanceof Error ? err.message : String(err)}`);
100
106
  }
101
107
  }
@@ -1,8 +1,18 @@
1
1
  #!/usr/bin/env node
2
2
  import { spawnSync } from "node:child_process";
3
3
  import { existsSync } from "node:fs";
4
- import { mkdir, rm } from "node:fs/promises";
4
+ import { mkdir } from "node:fs/promises";
5
5
  import path from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+
8
+ const scriptDir = path.dirname(fileURLToPath(import.meta.url));
9
+ const rmPathRecursiveScript = path.resolve(
10
+ scriptDir,
11
+ "..",
12
+ "..",
13
+ "scripts",
14
+ "rm-path-recursive.mjs",
15
+ );
6
16
 
7
17
  function parseArgs(argv) {
8
18
  const args = {
@@ -48,6 +58,25 @@ function runCommand(command, commandArgs, cwd, dryRun = false) {
48
58
  }
49
59
  }
50
60
 
61
+ function rmRecursive(pathToRemove) {
62
+ const result = spawnSync(
63
+ process.execPath,
64
+ [rmPathRecursiveScript, path.resolve(pathToRemove)],
65
+ {
66
+ encoding: "utf8",
67
+ stdio: ["ignore", "pipe", "pipe"],
68
+ },
69
+ );
70
+ if (result.status !== 0) {
71
+ const reason =
72
+ result.stderr?.trim() ||
73
+ result.stdout?.trim() ||
74
+ result.error?.message ||
75
+ `exit status ${String(result.status)}`;
76
+ fail(`failed to recursively remove ${pathToRemove}: ${reason}`);
77
+ }
78
+ }
79
+
51
80
  function shellQuote(value) {
52
81
  return `'${value.replaceAll("'", "'\\''")}'`;
53
82
  }
@@ -127,7 +156,7 @@ async function main() {
127
156
  if (args.dryRun) {
128
157
  console.log(`[dry-run] rm -rf ${venvPath}`);
129
158
  } else {
130
- await rm(venvPath, { recursive: true, force: true });
159
+ rmRecursive(venvPath);
131
160
  }
132
161
  }
133
162
 
@@ -395,9 +395,11 @@ if $REMOTE; then
395
395
  for arg in "${BUILD_ARGS[@]}"; do
396
396
  REMOTE_BUILD_ARGS+=" $(printf '%q' "$arg")"
397
397
  done
398
+ REMOTE_BUILD_DIR_QUOTED="$(printf '%q' "${REMOTE_BUILD_DIR}")"
399
+ REMOTE_CLEANUP_HELPER="$(printf '%q' "${REMOTE_BUILD_DIR}/packages/scripts/rm-path-recursive.mjs")"
398
400
  REMOTE_SCRIPT=$(cat <<SCRIPT
399
401
  set -e
400
- cd ${REMOTE_BUILD_DIR}
402
+ cd ${REMOTE_BUILD_DIR_QUOTED}
401
403
  tar -xzf build.tar.gz
402
404
  rm build.tar.gz
403
405
  echo "[remote] Extracted build context"
@@ -406,7 +408,8 @@ docker build -f $(printf '%q' "${DOCKERFILE}")${REMOTE_BUILD_ARGS} \
406
408
  . 2>&1 | tail -30
407
409
  echo "[remote] Build complete"
408
410
  docker images '${IMAGE_NAME}' --format 'Image ready: {{.Repository}}:{{.Tag}} ({{.Size}})'
409
- rm -rf ${REMOTE_BUILD_DIR}
411
+ cd /tmp
412
+ node ${REMOTE_CLEANUP_HELPER} ${REMOTE_BUILD_DIR_QUOTED}
410
413
  SCRIPT
411
414
  )
412
415
  run "ssh ${SSH_OPTS} -i '${SSH_KEY}' ${BUILD_SERVER} \"${REMOTE_SCRIPT}\""
@@ -43,6 +43,12 @@ export CARGO_HOME="/home/builder/.cargo"
43
43
  log() { printf '[bun-riscv64] %s\n' "$*"; }
44
44
  die() { printf '[bun-riscv64][FATAL] %s\n' "$*" >&2; exit 1; }
45
45
 
46
+ RM_PATH_RECURSIVE="${RM_PATH_RECURSIVE:-/opt/rm-path-recursive.mjs}"
47
+ remove_path_recursive() {
48
+ [ -r "$RM_PATH_RECURSIVE" ] || die "recursive cleanup helper not mounted at $RM_PATH_RECURSIVE"
49
+ bun "$RM_PATH_RECURSIVE" "$@"
50
+ }
51
+
46
52
  prepare_ninja_object_dirs() {
47
53
  local ninja_file="$1"
48
54
  local ninja_dir
@@ -175,7 +181,7 @@ if compgen -G "/opt/webkit-patches/*.patch" >/dev/null; then
175
181
  cd "$SRC_ROOT/WebKit"
176
182
  git config user.email "bun-riscv64@eliza.local"
177
183
  git config user.name "bun-riscv64 build"
178
- for p in $(ls /opt/webkit-patches/*.patch | sort); do
184
+ while IFS= read -r p; do
179
185
  # In C_LOOP mode, the 0003-disable-dfg-ftl-on-riscv64 patch is
180
186
  # unnecessary — ENABLE_C_LOOP=ON in CMake forcibly disables every
181
187
  # JIT tier, so the upstream PlatformEnable.h ifdefs never reach
@@ -198,7 +204,7 @@ if compgen -G "/opt/webkit-patches/*.patch" >/dev/null; then
198
204
  # 3-way merge is tolerant of context drift; on hard conflict, fail
199
205
  # rather than silently skipping.
200
206
  git apply --3way "$p" || die "WebKit patch failed: $p — see webkit-patches/README.md for rebase guidance"
201
- done
207
+ done < <(find /opt/webkit-patches -maxdepth 1 -type f -name '*.patch' | sort)
202
208
  cd "$SRC_ROOT"
203
209
  else
204
210
  log "No webkit-patches/*.patch present; building WebKit @ ${WEBKIT_COMMIT} as-is."
@@ -230,14 +236,14 @@ if compgen -G "/opt/bun-patches/*.patch" >/dev/null; then
230
236
  cd "$SRC_ROOT/bun"
231
237
  git config user.email "bun-riscv64@eliza.local"
232
238
  git config user.name "bun-riscv64 build"
233
- for p in $(ls /opt/bun-patches/*.patch | sort); do
239
+ while IFS= read -r p; do
234
240
  log " -> $p"
235
241
  if git apply --reverse --check "$p" >/dev/null 2>&1; then
236
242
  log " already applied; skipping"
237
243
  continue
238
244
  fi
239
245
  git apply --3way "$p" || die "Bun patch failed: $p"
240
- done
246
+ done < <(find /opt/bun-patches -maxdepth 1 -type f -name '*.patch' | sort)
241
247
  cd "$SRC_ROOT"
242
248
  else
243
249
  die "No bun-patches/*.patch present — Bun's build system needs riscv64 awareness (Arch type, cpu flags, WebKit pin, etc.). Populate bun-patches/ before running build.sh."
@@ -370,7 +376,7 @@ export BUN_SYSROOT=/sysroot
370
376
  export BUN_DISABLE_TINYCC=1
371
377
 
372
378
  BUN_BUILD_DIR="$SRC_ROOT/bun/build/release"
373
- rm -rf "$BUN_BUILD_DIR"
379
+ remove_path_recursive "$BUN_BUILD_DIR"
374
380
  mkdir -p "$BUN_BUILD_DIR/deps"
375
381
  ln -s "$WEBKIT_BUILD_DIR" "$BUN_BUILD_DIR/deps/WebKit"
376
382
 
@@ -436,7 +442,7 @@ log " → eval reports: $QEMU_EVAL_OUT"
436
442
 
437
443
  log "Smoke test: qemu-riscv64-static bun <script.js>"
438
444
  SMOKE_DIR="$(mktemp -d /tmp/bun-riscv64-smoke.XXXXXX)"
439
- trap 'rm -rf "$SMOKE_DIR"' EXIT
445
+ trap 'remove_path_recursive "$SMOKE_DIR"' EXIT
440
446
  SMOKE_JS="$SMOKE_DIR/entrypoint.js"
441
447
  printf '%s\n' 'console.log("bun-riscv64-script-ok", process.arch);' > "$SMOKE_JS"
442
448
  QEMU_SCRIPT_OUT="$(qemu-riscv64-static -L /sysroot "$BUN_BIN" "$SMOKE_JS" 2>&1)" || \
@@ -19,8 +19,15 @@
19
19
  set -euo pipefail
20
20
 
21
21
  HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
22
+ SCRIPT_FILE="$HERE/$(basename "${BASH_SOURCE[0]}")"
22
23
  cd "$HERE"
23
24
 
25
+ RM_PATH_RECURSIVE_HOST="$(cd "$HERE/../../.." && pwd)/scripts/rm-path-recursive.mjs"
26
+ [ -r "$RM_PATH_RECURSIVE_HOST" ] || {
27
+ echo "FATAL: cleanup helper not found at $RM_PATH_RECURSIVE_HOST" >&2
28
+ exit 1
29
+ }
30
+
24
31
  IMAGE_TAG="eliza/bun-riscv64-builder"
25
32
  NO_CACHE=""
26
33
  FORCE_CLOOP="1"
@@ -44,7 +51,7 @@ while [ $# -gt 0 ]; do
44
51
  -h|--help)
45
52
  # Usage block lives in the file header (lines 3-17 of the
46
53
  # leading comment, just below the shebang).
47
- grep '^#' "$0" | sed -n '3,17p' | sed 's|^# \?||'
54
+ grep '^#' "$SCRIPT_FILE" | sed -n '3,17p' | sed 's|^# \?||'
48
55
  exit 0 ;;
49
56
  *)
50
57
  echo "Unknown arg: $1" >&2
@@ -100,6 +107,7 @@ if [ "$SHELL_MODE" = "1" ]; then
100
107
  exec docker run --rm -it \
101
108
  --platform "${PLATFORM}" \
102
109
  -v "$HERE:/work-host:rw" \
110
+ -v "$RM_PATH_RECURSIVE_HOST:/opt/rm-path-recursive.mjs:ro" \
103
111
  --entrypoint /bin/bash \
104
112
  "${IMAGE_TAG}"
105
113
  fi
@@ -120,6 +128,7 @@ DOCKER_RUN_ARGS=(
120
128
  --rm
121
129
  --platform "${PLATFORM}"
122
130
  -v "$HERE/build.sh:/opt/build.sh:ro"
131
+ -v "$RM_PATH_RECURSIVE_HOST:/opt/rm-path-recursive.mjs:ro"
123
132
  -v "$HERE/bun-version.json:/opt/bun-version.json:ro"
124
133
  -v "$PATCH_MOUNT:/opt/bun-patches:ro"
125
134
  -v "$HERE/webkit-patches:/opt/webkit-patches:ro"
@@ -324,17 +324,12 @@ const rendererBuildSkipRequested =
324
324
  process.env.ELIZA_DESKTOP_RENDERER_BUILD === "skip";
325
325
  const viteWatch = process.env.ELIZA_DESKTOP_VITE_WATCH === "1";
326
326
  const viteDepForceCli = process.argv.includes("--vite-force");
327
- const viteDepForce =
328
- viteDepForceCli ||
329
- process.env.ELIZA_VITE_FORCE === "1" ||
330
- process.env.ELIZA_VITE_FORCE === "1";
327
+ const viteDepForce = viteDepForceCli || process.env.ELIZA_VITE_FORCE === "1";
331
328
  const viteRollupWatchCli = process.argv.includes("--rollup-watch");
332
329
  /** Legacy: Rollup `vite build --watch` (tens of seconds per edit on large graphs). */
333
330
  const viteRollupWatch =
334
331
  viteWatch &&
335
- (viteRollupWatchCli ||
336
- process.env.ELIZA_DESKTOP_VITE_BUILD_WATCH === "1" ||
337
- process.env.ELIZA_DESKTOP_VITE_BUILD_WATCH === "1");
332
+ (viteRollupWatchCli || process.env.ELIZA_DESKTOP_VITE_BUILD_WATCH === "1");
338
333
  /** Default when VITE_WATCH: Vite dev server + Electrobun ELIZA_RENDERER_URL (fast HMR). */
339
334
  const viteDevServer = viteWatch && !viteRollupWatch;
340
335
  /** On by default for `dev:desktop` / `dev:desktop:watch`; set to 0/false/no/off to disable. */
@@ -373,7 +368,7 @@ const desktopCefWorkaroundEnv = (() => {
373
368
  return explicit;
374
369
  }
375
370
 
376
- return "1";
371
+ return null;
377
372
  })();
378
373
  const desktopUnsafeDevtoolsEnv = (() => {
379
374
  if (process.platform !== "darwin") {
@@ -673,12 +668,22 @@ const CHILD_COLORS = {
673
668
  default: chalk.white,
674
669
  };
675
670
 
671
+ function shouldSuppressExpectedDevLine(line) {
672
+ return (
673
+ line.includes("Notification authorization error:") &&
674
+ line.includes("falling back to legacy API")
675
+ );
676
+ }
677
+
676
678
  function prefixStream(name, stream) {
677
679
  const plainTag = `[${name.padEnd(PREFIX_PAD)}]`;
678
680
  const colorFn = CHILD_COLORS[name] ?? CHILD_COLORS.default;
679
681
  stream.on("data", (chunk) => {
680
682
  for (const line of chunk.toString().split("\n")) {
681
683
  if (line.trim()) {
684
+ if (shouldSuppressExpectedDevLine(line)) {
685
+ continue;
686
+ }
682
687
  // Use the child color for both streams. Many tools (including Electrobun)
683
688
  // write normal startup logs to stderr; red prefixes read as errors.
684
689
  const coloredTag = colorFn(plainTag);
@@ -695,12 +700,16 @@ function prefixStream(name, stream) {
695
700
  });
696
701
  }
697
702
 
703
+ function childColorEnv() {
704
+ return process.env.NO_COLOR === undefined ? { FORCE_COLOR: "1" } : {};
705
+ }
706
+
698
707
  function pushChild(name, cmd, args, cwd, extraEnv = {}) {
699
708
  const resolvedCmd = cmd === "bun" ? BUN_EXECUTABLE : cmd;
700
709
  const child = spawn(resolvedCmd, args, {
701
710
  cwd,
702
711
  env: extendNodePathEnv(
703
- { ...process.env, ...extraEnv, FORCE_COLOR: "1" },
712
+ { ...process.env, ...extraEnv, ...childColorEnv() },
704
713
  bundleRoot,
705
714
  ),
706
715
  stdio: ["ignore", "pipe", "pipe"],
@@ -901,7 +910,7 @@ async function launch() {
901
910
  ...apiEnv,
902
911
  [API_PROCESS_SPAWNED_AT_ENV]: apiProcessSpawnedAtMs,
903
912
  [PROCESS_SPAWNED_AT_ENV]: apiProcessSpawnedAtMs,
904
- FORCE_COLOR: "1",
913
+ ...childColorEnv(),
905
914
  },
906
915
  bundleRoot,
907
916
  ),
@@ -177,14 +177,15 @@ ELIZA_IOS_INCLUDE_LLAMA=1 \
177
177
 
178
178
  ## Known gaps
179
179
 
180
- ### iOS runtime bridge is symbol-ready, not generation-ready
180
+ ### iOS runtime bridge: text-benchmark ready, voice symbol-ready
181
181
 
182
182
  The iOS slices now embed the same shipped Metal kernel payload as desktop
183
183
  Metal and `build-xcframework.mjs --verify` passes the kernel-symbol and
184
184
  runtime-symbol audits. The added `libeliza-ios-runtime-shim.a` is a link
185
- and smoke-test bridge, not a complete mobile inference engine. It refuses
186
- real text/voice generation until the mobile bridge is wired to a live llama
187
- context and real OmniVoice GGUF assets.
185
+ and smoke-test bridge. Its text entrypoint is wired to a live llama context for
186
+ the physical-device `--benchmark-model` path; its voice entrypoints still
187
+ refuse real TTS/ASR with structured unsupported/not-loaded errors until the
188
+ mobile runtime is wired to real OmniVoice GGUF assets.
188
189
 
189
190
  ### Real iPhone hardware verification
190
191
 
@@ -214,11 +215,16 @@ If no physical device is attached, unlocked, trusted, and in Developer
214
215
  Mode, the script exits non-zero and prints the offline device list
215
216
  reported by `xcrun xctrace list devices`.
216
217
 
217
- This still does **not** claim text/voice numerical generation because no
218
- Eliza-1 weights are bundled into the XCTest package. The next gate after
219
- this smoke passes is a real bundle smoke that downloads or stages final
220
- Eliza-1 artifacts, loads the selected tier, and measures first-token /
221
- first-audio latency plus peak RSS.
218
+ By default, this is still a symbol/ABI smoke and no model weights are bundled.
219
+ With `--benchmark-model <path-to.gguf>`, the generated host app bundles that
220
+ GGUF and runs a real CPU + Metal text-generation benchmark on the physical
221
+ device. The report captures prompt/generation throughput, token counts, process
222
+ memory footprint before/after generation, and thermal state before/after.
223
+
224
+ This does **not** claim voice numerical generation or first-audio latency yet.
225
+ The iOS voice ABI currently links and fail-closes with structured errors for
226
+ missing TTS/ASR assets; first-audio requires a future full-bundle or app-shell
227
+ smoke that stages real OmniVoice assets and exercises the mobile voice runtime.
222
228
 
223
229
  ### Why no fallback to the npm-bundled framework
224
230
 
@@ -21,9 +21,11 @@
21
21
  * - libelizainference voice ABI symbols resolve, unless explicitly disabled
22
22
  * with --skip-voice-abi for diagnosis.
23
23
  *
24
- * No model weights are bundled here, so this does not claim text/voice
25
- * numerical generation. It is the device-runtime gate that must pass before a
26
- * real Eliza-1 bundle smoke can run.
24
+ * No model weights are bundled by default. When --benchmark-model is supplied,
25
+ * the smoke also bundles that GGUF and records real physical-device text
26
+ * throughput plus memory/thermal telemetry for CPU and Metal. Voice and
27
+ * first-audio latency remain out of scope until the mobile voice runtime is
28
+ * wired to real OmniVoice assets instead of the current fail-closed ABI shim.
27
29
  */
28
30
 
29
31
  import { execFileSync, spawn, spawnSync } from "node:child_process";
@@ -10,7 +10,6 @@ import {
10
10
  mkdir,
11
11
  mkdtemp,
12
12
  readFile,
13
- rm,
14
13
  writeFile,
15
14
  } from "node:fs/promises";
16
15
  import {
@@ -139,12 +138,37 @@ function markStateDirOwnedByStack(stateDir: string): void {
139
138
  ownedStateDirs.add(stateDir);
140
139
  }
141
140
 
141
+ async function removePathRecursive(targetPath: string): Promise<void> {
142
+ await new Promise<void>((resolve, reject) => {
143
+ const child = spawn(process.execPath, [CLEANUP_HELPER_SCRIPT, targetPath], {
144
+ cwd: REPO_ROOT,
145
+ stdio: "inherit",
146
+ });
147
+ child.on("error", reject);
148
+ child.on("exit", (code, signal) => {
149
+ if (signal) {
150
+ reject(new Error(`rm-path-recursive exited due to signal ${signal}`));
151
+ return;
152
+ }
153
+ if ((code ?? 1) !== 0) {
154
+ reject(
155
+ new Error(
156
+ `rm-path-recursive failed for ${targetPath} with status ${
157
+ code ?? 1
158
+ }`,
159
+ ),
160
+ );
161
+ return;
162
+ }
163
+ resolve();
164
+ });
165
+ });
166
+ }
167
+
142
168
  async function cleanupPendingStateDirs(): Promise<void> {
143
169
  const stateDirs = Array.from(pendingStateDirs);
144
170
  pendingStateDirs.clear();
145
- await Promise.all(
146
- stateDirs.map((stateDir) => rm(stateDir, { force: true, recursive: true })),
147
- );
171
+ await Promise.all(stateDirs.map(removePathRecursive));
148
172
  }
149
173
 
150
174
  function cleanupKnownStateDirsSync(): void {
@@ -694,7 +718,7 @@ async function ensureUiDistReady(): Promise<void> {
694
718
  return;
695
719
  }
696
720
 
697
- await rm(path.join(APP_DIR, ".vite"), { force: true, recursive: true });
721
+ await removePathRecursive(path.join(APP_DIR, ".vite"));
698
722
 
699
723
  const logs: string[] = [];
700
724
  const child = spawn(resolveBunCommand(), ["run", "build:web"], {
@@ -931,7 +955,7 @@ async function startStubStack(): Promise<StartedStack> {
931
955
  } catch (error) {
932
956
  await closeUiServer(uiServer);
933
957
  await stopApiChild(apiChild);
934
- await rm(stateDir, { force: true, recursive: true });
958
+ await removePathRecursive(stateDir);
935
959
  pendingStateDirs.delete(stateDir);
936
960
  throw error;
937
961
  }
@@ -1046,7 +1070,7 @@ async function startRealStack(): Promise<StartedStack> {
1046
1070
  } catch (error) {
1047
1071
  await closeUiServer(uiServer);
1048
1072
  await stopApiChild(apiChild);
1049
- await rm(stateDir, { force: true, recursive: true });
1073
+ await removePathRecursive(stateDir);
1050
1074
  pendingStateDirs.delete(stateDir);
1051
1075
  throw error;
1052
1076
  }
@@ -1060,7 +1084,7 @@ async function stopRealStack(stack: StartedStack | null): Promise<void> {
1060
1084
  await closeUiServer(stack.uiServer);
1061
1085
  await stopApiChild(stack.apiChild);
1062
1086
 
1063
- await rm(stack.stateDir, { force: true, recursive: true });
1087
+ await removePathRecursive(stack.stateDir);
1064
1088
  ownedStateDirs.delete(stack.stateDir);
1065
1089
  }
1066
1090