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

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 (361) hide show
  1. package/agent-bridge.d.ts +5 -5
  2. package/api/auth/audit.d.ts +1 -1
  3. package/api/auth/auth-context.d.ts +1 -1
  4. package/api/auth/bootstrap-token.d.ts +1 -1
  5. package/api/auth/sessions.d.ts +1 -1
  6. package/api/auth-bootstrap-routes.d.ts +1 -1
  7. package/api/auth-pairing-routes.d.ts +1 -1
  8. package/api/auth-pairing-routes.d.ts.map +1 -1
  9. package/api/auth-pairing-routes.js +2 -3
  10. package/api/auth-session-routes.d.ts +2 -2
  11. package/api/auth.d.ts +1 -1
  12. package/api/automations-compat-routes.d.ts +1 -1
  13. package/api/background-tasks-routes.d.ts +1 -1
  14. package/api/catalog-routes.d.ts +1 -1
  15. package/api/cloud-voice-routes.d.ts +1 -1
  16. package/api/compat-route-shared.d.ts +12 -5
  17. package/api/compat-route-shared.d.ts.map +1 -1
  18. package/api/compat-route-shared.js +22 -187
  19. package/api/database-rows-compat-routes.d.ts +1 -1
  20. package/api/dev-compat-routes.d.ts +1 -1
  21. package/api/dev-route-catalog.d.ts.map +1 -1
  22. package/api/dev-route-catalog.js +20 -0
  23. package/api/drop-status-compat-route.d.ts +3 -0
  24. package/api/drop-status-compat-route.d.ts.map +1 -0
  25. package/api/drop-status-compat-route.js +12 -0
  26. package/api/first-run-routes.d.ts +1 -1
  27. package/api/internal-routes.d.ts +1 -1
  28. package/api/runtime-mode-routes.d.ts +1 -1
  29. package/api/secrets-manager-routes.d.ts.map +1 -1
  30. package/api/sensitive-request-routes.d.ts +2 -2
  31. package/api/server-config-filter.d.ts.map +1 -1
  32. package/api/server-config-filter.js +0 -1
  33. package/api/server-first-run-helpers.d.ts.map +1 -1
  34. package/api/server-first-run-helpers.js +0 -3
  35. package/api/server.d.ts +8 -8
  36. package/api/server.d.ts.map +1 -1
  37. package/api/server.js +13 -59
  38. package/api/training-benchmarks.d.ts +1 -1
  39. package/api/training-benchmarks.d.ts.map +1 -1
  40. package/api/training-benchmarks.js +31 -9
  41. package/api/workbench-compat-routes.d.ts +1 -1
  42. package/benchmark/server-utils.d.ts +2 -2
  43. package/browser.d.ts +4 -4
  44. package/cli/plugins-cli.d.ts.map +1 -1
  45. package/cli/plugins-cli.js +5 -0
  46. package/cli/program/register.auth.d.ts +27 -1
  47. package/cli/program/register.auth.d.ts.map +1 -1
  48. package/cli/program/register.auth.js +187 -0
  49. package/cli/program/register.dashboard.d.ts.map +1 -1
  50. package/cli/program/register.dashboard.js +11 -1
  51. package/cli/program.d.ts +1 -1
  52. package/first-run/first-run-config.d.ts +1 -1
  53. package/index.d.ts +46 -47
  54. package/index.d.ts.map +1 -1
  55. package/index.js +3 -7
  56. package/package.json +42 -36
  57. package/packaging/PUBLISHING_GUIDE.md +20 -20
  58. package/platform/elizaos-agent-browser-stub.d.ts +0 -1
  59. package/platform/elizaos-agent-browser-stub.d.ts.map +1 -1
  60. package/platform/elizaos-agent-browser-stub.js +0 -1
  61. package/platform/ios-runtime-bridge.d.ts.map +1 -1
  62. package/platform/ios-runtime-bridge.js +0 -1
  63. package/platform/native-plugin-entrypoints.d.ts +2 -2
  64. package/platforms/android/app/build.gradle +30 -7
  65. package/platforms/android/app/src/main/elizavoice-jni/elizavoice-jni.cpp +179 -35
  66. package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaAgentService.java +60 -32
  67. package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaBionicInferenceServer.java +405 -65
  68. package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaVoiceNative.java +35 -0
  69. package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaVoicePlugin.java +13 -6
  70. package/platforms/android/app/src/main/res/drawable/splash.png +0 -0
  71. package/platforms/android/app/src/main/res/drawable-land-hdpi/splash.png +0 -0
  72. package/platforms/android/app/src/main/res/drawable-land-mdpi/splash.png +0 -0
  73. package/platforms/android/app/src/main/res/drawable-land-xhdpi/splash.png +0 -0
  74. package/platforms/android/app/src/main/res/drawable-land-xxhdpi/splash.png +0 -0
  75. package/platforms/android/app/src/main/res/drawable-land-xxxhdpi/splash.png +0 -0
  76. package/platforms/android/app/src/main/res/drawable-port-hdpi/splash.png +0 -0
  77. package/platforms/android/app/src/main/res/drawable-port-mdpi/splash.png +0 -0
  78. package/platforms/android/app/src/main/res/drawable-port-xhdpi/splash.png +0 -0
  79. package/platforms/android/app/src/main/res/drawable-port-xxhdpi/splash.png +0 -0
  80. package/platforms/android/app/src/main/res/drawable-port-xxxhdpi/splash.png +0 -0
  81. package/platforms/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
  82. package/platforms/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png +0 -0
  83. package/platforms/android/app/src/main/res/mipmap-hdpi/ic_launcher_monochrome.png +0 -0
  84. package/platforms/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png +0 -0
  85. package/platforms/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
  86. package/platforms/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png +0 -0
  87. package/platforms/android/app/src/main/res/mipmap-mdpi/ic_launcher_monochrome.png +0 -0
  88. package/platforms/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png +0 -0
  89. package/platforms/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
  90. package/platforms/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png +0 -0
  91. package/platforms/android/app/src/main/res/mipmap-xhdpi/ic_launcher_monochrome.png +0 -0
  92. package/platforms/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png +0 -0
  93. package/platforms/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
  94. package/platforms/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png +0 -0
  95. package/platforms/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome.png +0 -0
  96. package/platforms/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png +0 -0
  97. package/platforms/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
  98. package/platforms/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png +0 -0
  99. package/platforms/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome.png +0 -0
  100. package/platforms/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png +0 -0
  101. package/platforms/android/app/src/main/res/values/colors.xml +5 -1
  102. package/platforms/android/app/src/main/res/values/ic_launcher_background.xml +1 -1
  103. package/platforms/android/app/src/main/res/values/styles.xml +4 -1
  104. package/platforms/electrobun/electrobun.config.ts +0 -1
  105. package/platforms/electrobun/native/macos/window-effects.mm +4 -0
  106. package/platforms/electrobun/package.json +11 -9
  107. package/platforms/electrobun/remotes/local-model/src/bun/eliza1-catalog.ts +1 -1
  108. package/platforms/electrobun/remotes/runtime/src/dev/phase2-smoke.ts +10 -5
  109. package/platforms/electrobun/remotes/runtime/src/dev/phase3-smoke.ts +11 -15
  110. package/platforms/electrobun/src/application-menu.ts +0 -7
  111. package/platforms/electrobun/src/desktop-http-request.test.ts +30 -1
  112. package/platforms/electrobun/src/desktop-http-request.ts +14 -2
  113. package/platforms/electrobun/src/desktop-test-bridge-server.ts +28 -0
  114. package/platforms/electrobun/src/fatal-shutdown.test.ts +3 -7
  115. package/platforms/electrobun/src/index.ts +64 -13
  116. package/platforms/electrobun/src/lifecycle/api-base-owner.test.ts +83 -0
  117. package/platforms/electrobun/src/lifecycle/api-base-owner.ts +37 -16
  118. package/platforms/electrobun/src/native/agent.ts +28 -17
  119. package/platforms/electrobun/src/native/credentials.ts +6 -1
  120. package/platforms/electrobun/src/native/desktop-window.test.ts +142 -26
  121. package/platforms/electrobun/src/native/desktop.ts +8 -4
  122. package/platforms/electrobun/src/native/index.ts +6 -0
  123. package/platforms/electrobun/src/native/remote-plugin-host.ts +18 -8
  124. package/platforms/electrobun/src/preload.js +1 -1
  125. package/platforms/electrobun/src/voice/voice-live-validation.test.ts +10 -5
  126. package/platforms/electrobun/src/voice/voice-runtime-adapter.ts +3 -2
  127. package/platforms/electrobun/src/voice/voice-service.test.ts +26 -0
  128. package/platforms/electrobun/src/voice/voice-service.ts +57 -17
  129. package/platforms/electrobun/tsconfig.json +17 -1
  130. package/platforms/ios/App/App/Base.lproj/LaunchScreen.storyboard +4 -6
  131. package/registry/index.d.ts +1 -6
  132. package/registry/index.d.ts.map +1 -1
  133. package/registry/index.js +9 -101
  134. package/runtime/android-avf-microdroid-bridge.d.ts +1 -1
  135. package/runtime/bundled-fused-lib.d.ts +36 -0
  136. package/runtime/bundled-fused-lib.d.ts.map +1 -0
  137. package/runtime/bundled-fused-lib.js +77 -0
  138. package/runtime/channel-plugin-map.d.ts +1 -5
  139. package/runtime/channel-plugin-map.d.ts.map +1 -1
  140. package/runtime/channel-plugin-map.js +11 -31
  141. package/runtime/desktop/index.d.ts +5 -5
  142. package/runtime/dev-server.js +11 -2
  143. package/runtime/eliza.d.ts.map +1 -1
  144. package/runtime/eliza.js +18 -8
  145. package/runtime/mode/route-mode-guard.d.ts +1 -1
  146. package/runtime/mode/route-mode-matrix.d.ts +1 -1
  147. package/runtime/voice-warmup.d.ts +4 -19
  148. package/runtime/voice-warmup.d.ts.map +1 -1
  149. package/runtime/voice-warmup.js +4 -14
  150. package/scripts/aosp/README.md +10 -1
  151. package/scripts/aosp/compile-libllama-paths.mjs +110 -0
  152. package/scripts/aosp/compile-libllama-zig-pin.test.mjs +147 -0
  153. package/scripts/aosp/compile-libllama.mjs +377 -81
  154. package/scripts/aosp/compile-libllama.test.mjs +133 -0
  155. package/scripts/aosp/compile-shim.mjs +21 -1
  156. package/scripts/aosp/deploy-pixel.mjs +1 -1
  157. package/scripts/aosp/stage-default-models.mjs +24 -31
  158. package/scripts/aosp/stage-default-models.test.mjs +23 -0
  159. package/scripts/assert-required-bundled-packages.test.ts +22 -1
  160. package/scripts/build-helpers/verify-fused-symbols.mjs +33 -0
  161. package/scripts/build-helpers/verify-fused-symbols.test.mjs +0 -0
  162. package/scripts/build-image.sh +1 -3
  163. package/scripts/build-llama-cpp-mtp.mjs +17 -16
  164. package/scripts/check-i18n.mjs +0 -1
  165. package/scripts/copy-runtime-node-modules.ts +147 -0
  166. package/scripts/desktop-build.mjs +317 -4
  167. package/scripts/docker-ci-smoke.sh +33 -8
  168. package/scripts/ensure-type-package-aliases.mjs +1 -1
  169. package/scripts/ios-xcframework/run-physical-device-smoke.mjs +1 -1
  170. package/scripts/lib/agent-source-watcher.mjs +0 -1
  171. package/scripts/lib/artifact-staleness.d.mts +33 -0
  172. package/scripts/lib/artifact-staleness.mjs +122 -0
  173. package/scripts/lib/artifact-staleness.test.mts +98 -0
  174. package/scripts/lib/duet-bridge.mjs +1 -1
  175. package/scripts/lib/ios-plist-url-scheme.mjs +80 -0
  176. package/scripts/lib/mobile-build-decisions.mjs +97 -0
  177. package/scripts/lib/renderer-build-manifest.d.mts +65 -0
  178. package/scripts/lib/renderer-build-manifest.mjs +257 -0
  179. package/scripts/lib/renderer-build-manifest.test.mts +277 -0
  180. package/scripts/lib/stage-android-agent.mjs +13 -4
  181. package/scripts/lib/verify-ondevice-artifact.d.mts +16 -0
  182. package/scripts/lib/verify-ondevice-artifact.mjs +61 -0
  183. package/scripts/lib/verify-ondevice-artifact.test.mts +98 -0
  184. package/scripts/lib/websocket-pending-queue.ts +92 -0
  185. package/scripts/link-docker-local-app-packages.mjs +6 -0
  186. package/scripts/playwright-ui-live-stack.ts +430 -144
  187. package/scripts/playwright-ui-smoke-api-stub.mjs +160 -168
  188. package/scripts/run-local-plugin-live-smoke.mjs +1 -6
  189. package/scripts/run-mobile-build-brand-separation.test.mts +33 -0
  190. package/scripts/run-mobile-build-ios-engine-gate.test.mjs +85 -0
  191. package/scripts/run-mobile-build-mtp-staleness.test.mts +198 -0
  192. package/scripts/run-mobile-build.mjs +408 -86
  193. package/scripts/serve-real-local-agent.ts +72 -0
  194. package/scripts/stage-desktop-fused-lib.mjs +43 -14
  195. package/scripts/verify-ondevice-artifact.mjs +111 -0
  196. package/scripts/voice-attribution-smoke.ts +144 -38
  197. package/scripts/voice-duet.mjs +6 -6
  198. package/scripts/voice-e2e-hardware.ts +22 -16
  199. package/scripts/voice-interactive.mjs +16 -45
  200. package/security/agent-vault-id.d.ts +1 -1
  201. package/security/platform-secure-store-node.d.ts +1 -1
  202. package/services/ambient-audio/consent.d.ts +1 -1
  203. package/services/ambient-audio/index.d.ts +6 -6
  204. package/services/ambient-audio/replay-buffer.d.ts +1 -1
  205. package/services/ambient-audio/response-gate.d.ts +1 -1
  206. package/services/ambient-audio/service.d.ts +3 -3
  207. package/services/connector-target-catalog.d.ts +1 -1
  208. package/services/sensitive-requests/index.d.ts +6 -6
  209. package/services/steward-sidecar/wallet-setup.d.ts +1 -1
  210. package/services/steward-sidecar.d.ts +3 -3
  211. package/services/tunnel-to-mobile/index.d.ts +1 -1
  212. package/services/vault-bootstrap.d.ts.map +1 -1
  213. package/services/vault-bootstrap.js +14 -10
  214. package/services/voice-profiles/diarization-pipeline.d.ts +1 -1
  215. package/services/voice-profiles/index.d.ts +11 -11
  216. package/services/voice-profiles/owner-confidence.d.ts +1 -1
  217. package/services/voice-profiles/private-challenge.d.ts +1 -1
  218. package/services/voice-profiles/store.d.ts +1 -1
  219. package/test/helpers/live-provider.test.ts +91 -1
  220. package/test/helpers/live-provider.ts +195 -51
  221. package/test/scripts/websocket-pending-queue.test.ts +119 -0
  222. package/test-support/test-helpers.d.ts +0 -33
  223. package/test-support/test-helpers.d.ts.map +1 -1
  224. package/test-support/test-helpers.js +81 -189
  225. package/ui-compat.d.ts +1 -0
  226. package/ui-compat.d.ts.map +1 -1
  227. package/ui-compat.js +4 -0
  228. package/platforms/electrobun/docs/convergence-audit.json +0 -3505
  229. package/platforms/electrobun/docs/convergence-audit.md +0 -694
  230. package/platforms/electrobun/scripts/ensure-whisper-gguf.sh +0 -55
  231. package/platforms/electrobun/scripts/ensure-whisper-model.sh +0 -35
  232. package/platforms/electrobun/scripts/generate-convergence-audit.ts +0 -1203
  233. package/platforms/electrobun/src/native/whisper-env.test.ts +0 -71
  234. package/platforms/electrobun/src/native/whisper-env.ts +0 -68
  235. package/registry/app-registry.d.ts +0 -14
  236. package/registry/app-registry.d.ts.map +0 -1
  237. package/registry/app-registry.js +0 -29
  238. package/registry/entries/apps/app-polymarket.json +0 -31
  239. package/registry/entries/apps/clawville.json +0 -27
  240. package/registry/entries/apps/companion.json +0 -28
  241. package/registry/entries/apps/database-viewer.json +0 -27
  242. package/registry/entries/apps/defense-of-the-agents.json +0 -27
  243. package/registry/entries/apps/documents.json +0 -30
  244. package/registry/entries/apps/feed.json +0 -27
  245. package/registry/entries/apps/hyperliquid.json +0 -31
  246. package/registry/entries/apps/log-viewer.json +0 -27
  247. package/registry/entries/apps/memory-viewer.json +0 -27
  248. package/registry/entries/apps/model-tester.json +0 -31
  249. package/registry/entries/apps/plugin-viewer.json +0 -27
  250. package/registry/entries/apps/relationship-viewer.json +0 -27
  251. package/registry/entries/apps/runtime-debugger.json +0 -27
  252. package/registry/entries/apps/shopify.json +0 -31
  253. package/registry/entries/apps/skills-viewer.json +0 -27
  254. package/registry/entries/apps/steward.json +0 -31
  255. package/registry/entries/apps/training.json +0 -54
  256. package/registry/entries/apps/trajectory-viewer.json +0 -27
  257. package/registry/entries/apps/vincent.json +0 -31
  258. package/registry/entries/connectors/bluebubbles.json +0 -99
  259. package/registry/entries/connectors/bluesky.json +0 -173
  260. package/registry/entries/connectors/discord.json +0 -119
  261. package/registry/entries/connectors/farcaster.json +0 -174
  262. package/registry/entries/connectors/feishu.json +0 -79
  263. package/registry/entries/connectors/google-chat.json +0 -120
  264. package/registry/entries/connectors/google.json +0 -82
  265. package/registry/entries/connectors/imessage.json +0 -96
  266. package/registry/entries/connectors/instagram.json +0 -64
  267. package/registry/entries/connectors/line.json +0 -86
  268. package/registry/entries/connectors/matrix.json +0 -94
  269. package/registry/entries/connectors/mattermost.json +0 -110
  270. package/registry/entries/connectors/msteams.json +0 -104
  271. package/registry/entries/connectors/nextcloud-talk.json +0 -104
  272. package/registry/entries/connectors/nostr.json +0 -70
  273. package/registry/entries/connectors/signal.json +0 -81
  274. package/registry/entries/connectors/slack.json +0 -102
  275. package/registry/entries/connectors/telegram.json +0 -71
  276. package/registry/entries/connectors/tlon.json +0 -94
  277. package/registry/entries/connectors/twitch.json +0 -110
  278. package/registry/entries/connectors/whatsapp.json +0 -113
  279. package/registry/entries/connectors/x.json +0 -231
  280. package/registry/entries/connectors/zalo.json +0 -112
  281. package/registry/entries/connectors/zalouser.json +0 -122
  282. package/registry/entries/plugins/agent-orchestrator.json +0 -33
  283. package/registry/entries/plugins/agent-skills.json +0 -72
  284. package/registry/entries/plugins/anthropic.json +0 -73
  285. package/registry/entries/plugins/app-control.json +0 -23
  286. package/registry/entries/plugins/auto-trader.json +0 -203
  287. package/registry/entries/plugins/background-runner.json +0 -26
  288. package/registry/entries/plugins/blooio.json +0 -102
  289. package/registry/entries/plugins/browser.json +0 -75
  290. package/registry/entries/plugins/cli.json +0 -40
  291. package/registry/entries/plugins/clipboard.json +0 -44
  292. package/registry/entries/plugins/coding-tools.json +0 -71
  293. package/registry/entries/plugins/commands.json +0 -63
  294. package/registry/entries/plugins/computeruse.json +0 -74
  295. package/registry/entries/plugins/copilot-proxy.json +0 -93
  296. package/registry/entries/plugins/directives.json +0 -63
  297. package/registry/entries/plugins/edge-tts.json +0 -97
  298. package/registry/entries/plugins/elevenlabs.json +0 -169
  299. package/registry/entries/plugins/elizacloud.json +0 -208
  300. package/registry/entries/plugins/evm.json +0 -134
  301. package/registry/entries/plugins/experience.json +0 -34
  302. package/registry/entries/plugins/facewear.json +0 -131
  303. package/registry/entries/plugins/form.json +0 -26
  304. package/registry/entries/plugins/github.json +0 -93
  305. package/registry/entries/plugins/gmail-watch.json +0 -25
  306. package/registry/entries/plugins/goals.json +0 -77
  307. package/registry/entries/plugins/google-genai.json +0 -106
  308. package/registry/entries/plugins/groq.json +0 -93
  309. package/registry/entries/plugins/hedera.json +0 -48
  310. package/registry/entries/plugins/inmemorydb.json +0 -25
  311. package/registry/entries/plugins/linear.json +0 -51
  312. package/registry/entries/plugins/local-inference.json +0 -137
  313. package/registry/entries/plugins/local-storage.json +0 -36
  314. package/registry/entries/plugins/localdb.json +0 -25
  315. package/registry/entries/plugins/mcp.json +0 -44
  316. package/registry/entries/plugins/memory.json +0 -124
  317. package/registry/entries/plugins/minecraft.json +0 -79
  318. package/registry/entries/plugins/moltbook.json +0 -83
  319. package/registry/entries/plugins/music.json +0 -155
  320. package/registry/entries/plugins/mysticism.json +0 -48
  321. package/registry/entries/plugins/nearai.json +0 -82
  322. package/registry/entries/plugins/ngrok.json +0 -69
  323. package/registry/entries/plugins/ollama.json +0 -96
  324. package/registry/entries/plugins/openai.json +0 -189
  325. package/registry/entries/plugins/openrouter.json +0 -188
  326. package/registry/entries/plugins/pdf.json +0 -26
  327. package/registry/entries/plugins/plugin-manager.json +0 -23
  328. package/registry/entries/plugins/prose.json +0 -48
  329. package/registry/entries/plugins/rlm.json +0 -26
  330. package/registry/entries/plugins/roblox.json +0 -88
  331. package/registry/entries/plugins/rss.json +0 -64
  332. package/registry/entries/plugins/s3-storage.json +0 -91
  333. package/registry/entries/plugins/scheduling.json +0 -35
  334. package/registry/entries/plugins/shell.json +0 -94
  335. package/registry/entries/plugins/social-alpha.json +0 -72
  336. package/registry/entries/plugins/tailscale.json +0 -81
  337. package/registry/entries/plugins/tee.json +0 -53
  338. package/registry/entries/plugins/todos.json +0 -26
  339. package/registry/entries/plugins/trajectory-logger.json +0 -33
  340. package/registry/entries/plugins/trust.json +0 -39
  341. package/registry/entries/plugins/tts.json +0 -71
  342. package/registry/entries/plugins/tunnel.json +0 -45
  343. package/registry/entries/plugins/twilio.json +0 -168
  344. package/registry/entries/plugins/vercel-ai-gateway.json +0 -128
  345. package/registry/entries/plugins/video.json +0 -23
  346. package/registry/entries/plugins/vision.json +0 -43
  347. package/registry/entries/plugins/webhooks.json +0 -23
  348. package/registry/entries/plugins/workflow.json +0 -25
  349. package/registry/entries/plugins/xai.json +0 -75
  350. package/registry/loader.d.ts +0 -28
  351. package/registry/loader.d.ts.map +0 -1
  352. package/registry/loader.js +0 -158
  353. package/registry/schema.d.ts +0 -1199
  354. package/registry/schema.d.ts.map +0 -1
  355. package/registry/schema.js +0 -300
  356. package/scripts/patches/llama-mobile-kokoro-tts.patch +0 -480
  357. package/scripts/release-workflow-drift.test.ts +0 -57
  358. package/test/helpers/i18n.ts +0 -5
  359. package/test/helpers/loopback.ts +0 -33
  360. package/test/helpers/react-test.ts +0 -40
  361. package/test/helpers/skip-without.ts +0 -53
@@ -2,6 +2,7 @@ package ai.elizaos.app;
2
2
 
3
3
  import android.net.LocalServerSocket;
4
4
  import android.net.LocalSocket;
5
+ import android.system.Os;
5
6
  import android.util.Base64;
6
7
  import android.util.Log;
7
8
 
@@ -60,6 +61,12 @@ final class ElizaBionicInferenceServer {
60
61
  private long residentCtx = 0L;
61
62
  private long residentStream = 0L;
62
63
  private String residentBundle = null;
64
+ private String residentDrafterPath = "";
65
+ /** The previous turn's prompt tokens — used to find the longest common prefix
66
+ * with the next turn's prompt so its KV can be reused (only the delta is
67
+ * re-prefilled). null when the stream has no reusable KV (first turn / after
68
+ * a reset/reopen). */
69
+ private int[] residentPrevTokens = null;
63
70
  private final Object residentLock = new Object();
64
71
  /** Hard decode ceiling for the resident stream (per-call cap is applied below). */
65
72
  private static final int RESIDENT_STREAM_MAX_TOKENS = 2048;
@@ -70,10 +77,62 @@ final class ElizaBionicInferenceServer {
70
77
  }
71
78
 
72
79
  /** Bind the abstract-namespace socket and start accepting. Idempotent. */
80
+ /**
81
+ * The bionic host runs the LLM in THIS (app) process via JNI, so the fused
82
+ * native lib reads its tuning from the app-process environment — NOT the bun
83
+ * agent subprocess env (which only carries the ELIZA_LLAMA_* names). On
84
+ * Mali-class 8 GB phones the 2B's non-flash-attn compute + logits buffers at
85
+ * the upstream n_batch=512 default push peak RSS past what the device can
86
+ * allocate ("llm_stream_open: failed to init llama context"). FA is disabled
87
+ * on Android (the scalar-FA race), so the non-FA attention buffer is the
88
+ * dominant cost and it scales with n_batch; capping n_batch shrinks both that
89
+ * and the n_vocab×n_batch logits buffer ~4x, which is what lets the context
90
+ * fit. n_ctx is left at the model-capped default (KV is only ~0.4 GB at 8k).
91
+ * Only sets a value when it is not already present, so any explicit override
92
+ * still wins.
93
+ */
94
+ private static void applyBionicInferenceMemoryDefaults() {
95
+ setEnvIfAbsent("ELIZA_LLM_N_BATCH", "128");
96
+ setEnvIfAbsent("ELIZA_LLM_N_CTX", "8192");
97
+ // The JNI bridge can default the KV cache to a fused QJL/TBQ quant
98
+ // (cache_type_k="qjl1_256"), but that path is a head_dim=128 sketch and
99
+ // is RETIRED for the shipped tiers (elizaOS/eliza#8848 / #9033 Gemma-4
100
+ // cutover): eliza-1 now ships Gemma-4-arch GGUFs whose KV is already
101
+ // minimal (MQA + windowed SWA + shared-KV; dual head dims 512 global /
102
+ // 256 SWA) and which run on stock f16/q8_0 KV. The head_dim=128 QJL/TBQ
103
+ // kernels are dimensionally inapplicable to Gemma, so keep KV quant OFF
104
+ // (stock f16, only ~0.4 GB at 8k ctx for the 2B). This is the intended
105
+ // Gemma KV path, not a fallback.
106
+ setEnvIfAbsent("ELIZA_BIONIC_KV_QUANT", "0");
107
+ // Allow MTP speculative decode when a Gemma-4 SEPARATE drafter
108
+ // (mtp/drafter-<tier>.gguf, loaded via "-md … --spec-type draft-mtp") —
109
+ // NOT a same-file NextN head embedded in the text GGUF; the JNI threads
110
+ // cfg.mtp_drafter_path when the TS bridge has staged one. MTP stays
111
+ // dormant when no drafter path is supplied; the retired same-file path
112
+ // is intentionally not used for Gemma bundles. The resident path uses
113
+ // MTP + full reset;
114
+ // prefix-KV reuse via reset_keep (resetAndPrefillResident →
115
+ // nativeLlmStreamResetKeep) stays wired for caches that support bounded
116
+ // partial removal (llama_memory_seq_rm).
117
+ setEnvIfAbsent("ELIZA_BIONIC_MTP", "1");
118
+ }
119
+
120
+ private static void setEnvIfAbsent(String key, String value) {
121
+ try {
122
+ if (System.getenv(key) == null) {
123
+ Os.setenv(key, value, true);
124
+ Log.i(TAG, "set " + key + "=" + value + " for in-process bionic inference");
125
+ }
126
+ } catch (Throwable t) {
127
+ Log.w(TAG, "could not set " + key, t);
128
+ }
129
+ }
130
+
73
131
  synchronized void start() {
74
132
  if (running.get()) {
75
133
  return;
76
134
  }
135
+ applyBionicInferenceMemoryDefaults();
77
136
  // Load the fused native engine up front so the first request doesn't pay
78
137
  // the dlopen + Vulkan-device init; also fail fast + loud if the GPU host
79
138
  // isn't actually usable, so the agent's refuse-and-fallback can engage.
@@ -150,6 +209,14 @@ final class ElizaBionicInferenceServer {
150
209
  if (requestJson == null) {
151
210
  break;
152
211
  }
212
+ // op="generateStream" server-pushes one frame per decode step on
213
+ // this same connection (handled inline so it can write many
214
+ // frames); every other op is one-request/one-response.
215
+ if ("generateStream".equals(opOf(requestJson))) {
216
+ generateStreamRequest(requestJson, out);
217
+ out.flush();
218
+ continue;
219
+ }
153
220
  String responseJson = handleRequest(requestJson);
154
221
  writeFrame(out, responseJson);
155
222
  out.flush();
@@ -176,13 +243,23 @@ final class ElizaBionicInferenceServer {
176
243
  return tts(bundleDir, req.optString("text", ""),
177
244
  (float) req.optDouble("speed", 1.0));
178
245
  }
246
+ if ("asr".equals(op)) {
247
+ return asr(bundleDir, req.optString("pcmBase64", ""),
248
+ req.optInt("sampleRate", 16000));
249
+ }
250
+ if ("image".equals(op)) {
251
+ return describeImage(bundleDir, req.optString("imageBase64", ""),
252
+ req.optString("mmprojPath", ""), req.optString("prompt", ""));
253
+ }
179
254
  if (!"generate".equals(op)) {
180
255
  return errorJson("unsupported op: " + op);
181
256
  }
182
257
  String prompt = req.optString("prompt", "");
258
+ String drafterPath = req.optString("drafterPath", "");
183
259
  int maxTokens = req.optInt("maxTokens", 256);
184
260
  Log.i(TAG, "GENERATE from agent: " + prompt.length() + " prompt chars,"
185
- + " maxTokens=" + maxTokens + ", bundle=" + bundleDir);
261
+ + " maxTokens=" + maxTokens + ", bundle=" + bundleDir
262
+ + ", drafter=" + (drafterPath.isEmpty() ? "(none)" : drafterPath));
186
263
  // RESIDENT path (default): the model + context stay loaded across turns;
187
264
  // only the KV cache + sampler are reset and the prompt re-prefilled per
188
265
  // turn, so we skip the ~7-8s model RELOAD that nativeLlmSelfTest paid every
@@ -194,7 +271,7 @@ final class ElizaBionicInferenceServer {
194
271
  // ELIZA_BIONIC_RESIDENT=0 to force the old path).
195
272
  if (!"0".equals(System.getenv("ELIZA_BIONIC_RESIDENT"))) {
196
273
  try {
197
- String r = generateResident(bundleDir, prompt, maxTokens);
274
+ String r = generateResident(bundleDir, drafterPath, prompt, maxTokens);
198
275
  Log.i(TAG, "GENERATE result (resident): "
199
276
  + (r.length() > 200 ? r.substring(0, 200) + "…" : r));
200
277
  return r;
@@ -218,29 +295,12 @@ final class ElizaBionicInferenceServer {
218
295
  * we skip the ~7-8s model reload. Greedy decode (temp=0, top_k=1), all-GPU.
219
296
  * Returns the same {ok,tokens,ms,tokS,text} JSON as nativeLlmSelfTest.
220
297
  */
221
- private String generateResident(String bundleDir, String prompt, int maxTokens)
298
+ private String generateResident(String bundleDir, String drafterPath, String prompt, int maxTokens)
222
299
  throws org.json.JSONException {
223
300
  synchronized (residentLock) {
224
- if (residentCtx == 0L || !bundleDir.equals(residentBundle)) {
225
- resetResident();
226
- residentCtx = ElizaVoiceNative.nativeContextCreate(bundleDir);
227
- if (residentCtx == 0L) {
228
- throw new IllegalStateException("resident contextCreate failed: " + bundleDir);
229
- }
230
- residentBundle = bundleDir;
231
- }
232
- if (residentStream == 0L) {
233
- residentStream = ElizaVoiceNative.nativeLlmStreamOpen(
234
- residentCtx, RESIDENT_STREAM_MAX_TOKENS, 0.0f, 1.0f, 1, -1, "");
235
- if (residentStream == 0L) {
236
- throw new IllegalStateException("resident streamOpen failed");
237
- }
238
- }
239
- // Drop the previous turn's KV + sampler state for a clean re-prefill.
240
- ElizaVoiceNative.nativeLlmStreamReset(residentStream);
241
- int[] toks = ElizaVoiceNative.nativeTokenize(residentCtx, prompt, true, true);
301
+ ensureResidentCtx(bundleDir);
242
302
  final long t0 = android.os.SystemClock.elapsedRealtime();
243
- ElizaVoiceNative.nativeLlmStreamPrefill(residentStream, toks);
303
+ resetAndPrefillResident(prompt, drafterPath);
244
304
  final StringBuilder sb = new StringBuilder();
245
305
  int produced = 0;
246
306
  final int cap = maxTokens > 0 ? maxTokens : 32;
@@ -266,6 +326,118 @@ final class ElizaBionicInferenceServer {
266
326
  }
267
327
  }
268
328
 
329
+ /** Cheap op discriminator without fully consuming the request. */
330
+ private static String opOf(String requestJson) {
331
+ try {
332
+ return new JSONObject(requestJson).optString("op", "generate");
333
+ } catch (org.json.JSONException e) {
334
+ return "generate";
335
+ }
336
+ }
337
+
338
+ /** Parse an op="generateStream" request and run the streaming decode. */
339
+ private void generateStreamRequest(String requestJson, DataOutputStream out)
340
+ throws IOException {
341
+ String bundleDir = defaultBundleDir;
342
+ String drafterPath = "";
343
+ String prompt = "";
344
+ int maxTokens = 256;
345
+ try {
346
+ JSONObject req = new JSONObject(requestJson);
347
+ bundleDir = req.optString("bundleDir", "");
348
+ if (bundleDir.isEmpty()) {
349
+ bundleDir = defaultBundleDir;
350
+ }
351
+ drafterPath = req.optString("drafterPath", "");
352
+ prompt = req.optString("prompt", "");
353
+ maxTokens = req.optInt("maxTokens", 256);
354
+ } catch (org.json.JSONException e) {
355
+ writeFrame(out, errorJson(e.getMessage() == null ? e.toString() : e.getMessage()));
356
+ return;
357
+ }
358
+ generateStream(bundleDir, drafterPath, prompt, maxTokens, out);
359
+ }
360
+
361
+ /**
362
+ * Streaming variant of {@link #generateResident}: the identical warm decode,
363
+ * but it writes one length-prefixed {type:"token",text} frame per decode step
364
+ * to {@code out} as tokens are produced, then a terminal
365
+ * {type:"done",ok,tokens,ms,tokS,text} frame. This lets the agent render
366
+ * tokens as they decode (first paint at the first token instead of after the
367
+ * whole reply) and unblocks phrase-chunked LLM→TTS. The buffered op="generate"
368
+ * is unchanged for non-streaming callers (embed/tts/self-test).
369
+ */
370
+ private void generateStream(String bundleDir, String drafterPath, String prompt, int maxTokens,
371
+ DataOutputStream out) throws IOException {
372
+ Log.i(TAG, "GENERATE_STREAM from agent: " + prompt.length() + " prompt chars,"
373
+ + " maxTokens=" + maxTokens + ", bundle=" + bundleDir
374
+ + ", drafter=" + (drafterPath.isEmpty() ? "(none)" : drafterPath));
375
+ final StringBuilder sb = new StringBuilder();
376
+ try {
377
+ synchronized (residentLock) {
378
+ ensureResidentCtx(bundleDir);
379
+ final long t0 = android.os.SystemClock.elapsedRealtime();
380
+ resetAndPrefillResident(prompt, drafterPath);
381
+ int produced = 0;
382
+ final int cap = maxTokens > 0 ? maxTokens : 32;
383
+ while (produced < cap) {
384
+ String stepJson = ElizaVoiceNative.nativeLlmStreamNext(residentStream);
385
+ if (stepJson == null) break;
386
+ JSONObject step = new JSONObject(stepJson);
387
+ String t = step.optString("text", "");
388
+ if (!t.isEmpty()) {
389
+ sb.append(t);
390
+ writeFrame(out, new JSONObject()
391
+ .put("type", "token").put("text", t).toString());
392
+ out.flush();
393
+ }
394
+ int nout = step.optInt("nout", 1);
395
+ produced += nout > 0 ? nout : 1;
396
+ if (step.optBoolean("done", false)) break;
397
+ }
398
+ final long ms = android.os.SystemClock.elapsedRealtime() - t0;
399
+ final double tokS = ms > 0 ? produced * 1000.0 / ms : 0.0;
400
+ writeFrame(out, new JSONObject()
401
+ .put("type", "done").put("ok", true)
402
+ .put("tokens", produced).put("ms", ms).put("tokS", tokS)
403
+ .put("text", sb.toString()).put("resident", true).toString());
404
+ out.flush();
405
+ Log.i(TAG, "GENERATE_STREAM done (resident): " + produced + " tok @ "
406
+ + String.format(java.util.Locale.US, "%.2f", tokS) + " tok/s");
407
+ }
408
+ } catch (Throwable t) {
409
+ Log.w(TAG, "generate_stream failed", t);
410
+ resetResident();
411
+ try {
412
+ writeFrame(out, new JSONObject()
413
+ .put("type", "done").put("ok", false)
414
+ .put("error", t.getMessage() == null ? t.toString() : t.getMessage())
415
+ .toString());
416
+ out.flush();
417
+ } catch (Throwable ignored) {
418
+ }
419
+ }
420
+ }
421
+
422
+ /**
423
+ * Get-or-create the shared resident inference context. ONE model load is
424
+ * reused by both generation (via residentStream) and embeddings (the native
425
+ * EliInferenceContext caches a separate non-causal embed_ctx + the causal
426
+ * stream within the same shared model weights), so embeds no longer reload
427
+ * the 1.27 GB model per call. Caller must hold residentLock.
428
+ */
429
+ private long ensureResidentCtx(String bundleDir) {
430
+ if (residentCtx == 0L || !bundleDir.equals(residentBundle)) {
431
+ resetResident();
432
+ residentCtx = ElizaVoiceNative.nativeContextCreate(bundleDir);
433
+ if (residentCtx == 0L) {
434
+ throw new IllegalStateException("resident contextCreate failed: " + bundleDir);
435
+ }
436
+ residentBundle = bundleDir;
437
+ }
438
+ return residentCtx;
439
+ }
440
+
269
441
  /** Tear down the resident model/context/stream (on bundle change, failure, or stop). */
270
442
  private void resetResident() {
271
443
  synchronized (residentLock) {
@@ -278,35 +450,111 @@ final class ElizaBionicInferenceServer {
278
450
  residentCtx = 0L;
279
451
  }
280
452
  residentBundle = null;
453
+ residentDrafterPath = "";
454
+ residentPrevTokens = null;
455
+ }
456
+ }
457
+
458
+ /**
459
+ * Reset the resident stream for a new turn and prefill the prompt, REUSING
460
+ * the KV of the longest common token prefix with the previous turn (the
461
+ * system + tool-schema block is identical turn-to-turn) so only the per-turn
462
+ * delta is decoded. On Mali's scalar-matmul prefill the prefix is the
463
+ * dominant per-turn cost, so this is the single biggest latency win. Falls
464
+ * back to a full reset (close+reopen on failure) when there is no reusable
465
+ * prefix or the stream can't be trimmed (e.g. an MTP stream). Caller holds
466
+ * residentLock.
467
+ */
468
+ private void resetAndPrefillResident(String prompt, String drafterPath) {
469
+ final String effectiveDrafterPath = drafterPath == null ? "" : drafterPath;
470
+ if (residentStream != 0L && !effectiveDrafterPath.equals(residentDrafterPath)) {
471
+ ElizaVoiceNative.nativeLlmStreamClose(residentStream);
472
+ residentStream = 0L;
473
+ residentPrevTokens = null;
474
+ }
475
+ if (residentStream == 0L) {
476
+ residentStream = ElizaVoiceNative.nativeLlmStreamOpen(
477
+ residentCtx, RESIDENT_STREAM_MAX_TOKENS, 0.0f, 1.0f, 1, -1,
478
+ effectiveDrafterPath);
479
+ if (residentStream == 0L) {
480
+ throw new IllegalStateException("resident streamOpen failed");
481
+ }
482
+ residentDrafterPath = effectiveDrafterPath;
483
+ residentPrevTokens = null;
484
+ }
485
+ final int[] toks = ElizaVoiceNative.nativeTokenize(residentCtx, prompt, true, true);
486
+ // Longest common token prefix with the previous turn, capped so at least
487
+ // one new token is prefilled (the decode samples from the last prefilled
488
+ // position's logits, so the suffix must be non-empty).
489
+ int lcp = 0;
490
+ if (residentPrevTokens != null) {
491
+ final int max = Math.min(residentPrevTokens.length, toks.length);
492
+ while (lcp < max && residentPrevTokens[lcp] == toks[lcp]) {
493
+ lcp++;
494
+ }
495
+ if (lcp >= toks.length) {
496
+ lcp = toks.length - 1;
497
+ }
498
+ }
499
+ int applied = lcp > 0
500
+ ? ElizaVoiceNative.nativeLlmStreamResetKeep(residentStream, lcp)
501
+ : -1;
502
+ if (applied < 0) {
503
+ // No reusable prefix (first turn / MTP / trim failure): full reset,
504
+ // close+reopen on failure.
505
+ if (ElizaVoiceNative.nativeLlmStreamReset(residentStream) != 1) {
506
+ ElizaVoiceNative.nativeLlmStreamClose(residentStream);
507
+ residentStream = ElizaVoiceNative.nativeLlmStreamOpen(
508
+ residentCtx, RESIDENT_STREAM_MAX_TOKENS, 0.0f, 1.0f, 1, -1,
509
+ effectiveDrafterPath);
510
+ if (residentStream == 0L) {
511
+ throw new IllegalStateException("resident streamReopen failed");
512
+ }
513
+ residentDrafterPath = effectiveDrafterPath;
514
+ }
515
+ applied = 0;
516
+ }
517
+ final int[] suffix = (applied <= 0)
518
+ ? toks
519
+ : java.util.Arrays.copyOfRange(toks, applied, toks.length);
520
+ ElizaVoiceNative.nativeLlmStreamPrefill(residentStream, suffix);
521
+ residentPrevTokens = toks;
522
+ if (applied > 0) {
523
+ Log.i(TAG, "resident prefill reuse: kept " + applied + "/" + toks.length
524
+ + " prefix tokens, prefilled " + suffix.length + " delta");
281
525
  }
282
526
  }
283
527
 
284
528
  /**
285
- * Embed text on the GPU via the fused model (--pooling last). Fresh context
286
- * per call (single forward pass, no autoregressive decode — fast + clean).
287
- * Returns {ok, embedding:[...], dim}. This is what lets on-device memory /
288
- * doc-seeding run locally instead of failing over to cloud BatchEmbeddings.
529
+ * Embed text on the GPU via the fused model (--pooling last). Reuses the
530
+ * shared resident context (the native side caches a non-causal embed_ctx
531
+ * inside it) so the 1.27 GB model is NOT reloaded per call — previously every
532
+ * embed did contextCreate→embed→contextDestroy (~15 s + a full model copy of
533
+ * memory churn each), which starved the LLM context on 8 GB devices. Single
534
+ * forward pass, no autoregressive decode. Returns {ok, embedding:[...], dim}.
289
535
  */
290
536
  private String embed(String bundleDir, String text) throws org.json.JSONException {
291
537
  final int POOLING_LAST = 3;
292
- long ctx = ElizaVoiceNative.nativeContextCreate(bundleDir);
293
- if (ctx == 0L) {
294
- return errorJson("embed: failed to create context for " + bundleDir);
295
- }
296
- try {
297
- float[] emb = ElizaVoiceNative.nativeEmbed(ctx, text, POOLING_LAST);
298
- org.json.JSONArray arr = new org.json.JSONArray();
299
- for (float v : emb) {
300
- arr.put((double) v);
538
+ synchronized (residentLock) {
539
+ final long ctx = ensureResidentCtx(bundleDir);
540
+ try {
541
+ float[] emb = ElizaVoiceNative.nativeEmbed(ctx, text, POOLING_LAST);
542
+ org.json.JSONArray arr = new org.json.JSONArray();
543
+ for (float v : emb) {
544
+ arr.put((double) v);
545
+ }
546
+ Log.i(TAG, "EMBED from agent: " + text.length() + " chars -> dim " + emb.length);
547
+ return new JSONObject()
548
+ .put("ok", true)
549
+ .put("embedding", arr)
550
+ .put("dim", emb.length)
551
+ .toString();
552
+ } catch (Throwable t) {
553
+ // A failed embed may leave the shared context in an unknown state;
554
+ // drop it so the next generate/embed rebuilds cleanly.
555
+ resetResident();
556
+ throw t;
301
557
  }
302
- Log.i(TAG, "EMBED from agent: " + text.length() + " chars -> dim " + emb.length);
303
- return new JSONObject()
304
- .put("ok", true)
305
- .put("embedding", arr)
306
- .put("dim", emb.length)
307
- .toString();
308
- } finally {
309
- ElizaVoiceNative.nativeContextDestroy(ctx);
310
558
  }
311
559
  }
312
560
 
@@ -329,30 +577,122 @@ final class ElizaBionicInferenceServer {
329
577
  if (gguf == null || voiceBin == null) {
330
578
  return errorJson("tts: Kokoro GGUF + voice .bin not found under " + kokoroDir);
331
579
  }
332
- long ctx = ElizaVoiceNative.nativeContextCreate(bundleDir);
333
- if (ctx == 0L) {
334
- return errorJson("tts: failed to create context for " + bundleDir);
580
+ // Reuse the ONE resident context (the 1.27 GB model is already loaded for
581
+ // generation/embeddings) instead of contextCreate/Destroy per call — a
582
+ // fresh context reloaded the whole model every utterance. Kokoro itself
583
+ // is loaded once and cached on the ctx (idempotent kokoro_load), so a
584
+ // multi-clause reply synthesizes each clause without any reload.
585
+ synchronized (residentLock) {
586
+ final long ctx = ensureResidentCtx(bundleDir);
587
+ try {
588
+ float[] pcm = ElizaVoiceNative.nativeKokoroSynthesize(
589
+ ctx, gguf, voiceBin, text, speed <= 0f ? 1.0f : speed);
590
+ int sampleRate = ElizaVoiceNative.nativeKokoroSampleRate(ctx);
591
+ // Pack fp32 PCM little-endian and base64 it for the JSON frame.
592
+ ByteBuffer buf = ByteBuffer.allocate(pcm.length * 4).order(ByteOrder.LITTLE_ENDIAN);
593
+ for (float v : pcm) {
594
+ buf.putFloat(v);
595
+ }
596
+ String b64 = Base64.encodeToString(buf.array(), Base64.NO_WRAP);
597
+ Log.i(TAG, "TTS (kokoro) from agent: " + text.length() + " chars -> "
598
+ + pcm.length + " samples @ " + sampleRate + " Hz");
599
+ return new JSONObject()
600
+ .put("ok", true)
601
+ .put("sampleRate", sampleRate)
602
+ .put("samples", pcm.length)
603
+ .put("pcmBase64", b64)
604
+ .toString();
605
+ } catch (Throwable t) {
606
+ // A failed synth may leave the shared ctx in an unknown state;
607
+ // drop it so the next generate/embed/tts rebuilds cleanly.
608
+ resetResident();
609
+ throw t;
610
+ }
335
611
  }
336
- try {
337
- float[] pcm = ElizaVoiceNative.nativeKokoroSynthesize(
338
- ctx, gguf, voiceBin, text, speed <= 0f ? 1.0f : speed);
339
- int sampleRate = ElizaVoiceNative.nativeKokoroSampleRate(ctx);
340
- // Pack fp32 PCM little-endian and base64 it for the JSON frame.
341
- ByteBuffer buf = ByteBuffer.allocate(pcm.length * 4).order(ByteOrder.LITTLE_ENDIAN);
342
- for (float v : pcm) {
343
- buf.putFloat(v);
612
+ }
613
+
614
+ /**
615
+ * On-device STT: decode the base64 little-endian fp32 PCM, run the fused
616
+ * local ASR batch transcribe on the resident context (it mmap-acquires the
617
+ * {@code asr/} weights on first use), and return {ok, text}. The agent's
618
+ * TRANSCRIPTION delegate routes here over UDS (op="asr"); the musl agent
619
+ * can't reach the fused lib itself. Reuses the ONE resident context like
620
+ * embed/tts so the model is not reloaded per call.
621
+ */
622
+ private String asr(String bundleDir, String pcmBase64, int sampleRate)
623
+ throws org.json.JSONException {
624
+ if (pcmBase64.isEmpty()) {
625
+ return errorJson("asr: empty pcmBase64");
626
+ }
627
+ byte[] raw = Base64.decode(pcmBase64, Base64.DEFAULT);
628
+ final int n = raw.length / 4;
629
+ if (n <= 0) {
630
+ return errorJson("asr: pcmBase64 decoded to " + raw.length + " bytes (need fp32 PCM)");
631
+ }
632
+ float[] pcm = new float[n];
633
+ ByteBuffer bb = ByteBuffer.wrap(raw).order(ByteOrder.LITTLE_ENDIAN);
634
+ for (int i = 0; i < n; i++) {
635
+ pcm[i] = bb.getFloat();
636
+ }
637
+ final int sr = sampleRate > 0 ? sampleRate : 16000;
638
+ synchronized (residentLock) {
639
+ final long ctx = ensureResidentCtx(bundleDir);
640
+ try {
641
+ String text = ElizaVoiceNative.nativeAsrTranscribe(ctx, pcm, sr);
642
+ Log.i(TAG, "ASR from agent: " + n + " samples @ " + sr + " Hz -> \""
643
+ + (text.length() > 200 ? text.substring(0, 200) + "…" : text) + "\"");
644
+ return new JSONObject()
645
+ .put("ok", true)
646
+ .put("text", text)
647
+ .toString();
648
+ } catch (Throwable t) {
649
+ // A failed transcribe may leave the shared ctx in an unknown state;
650
+ // drop it so the next generate/embed/tts/asr rebuilds cleanly.
651
+ resetResident();
652
+ throw t;
653
+ }
654
+ }
655
+ }
656
+
657
+ /**
658
+ * On-device vision / screen-recognition: decode the base64 image bytes and
659
+ * run the fused mmproj describe-image on the resident TEXT model. When the
660
+ * caller doesn't pass an explicit {@code mmprojPath}, resolve the projector
661
+ * GGUF from the bundle's {@code vision/} dir. Returns {ok, text}. The agent's
662
+ * IMAGE_DESCRIPTION delegate routes here over UDS (op="image").
663
+ */
664
+ private String describeImage(String bundleDir, String imageBase64,
665
+ String mmprojPath, String prompt) throws org.json.JSONException {
666
+ if (imageBase64.isEmpty()) {
667
+ return errorJson("image: empty imageBase64");
668
+ }
669
+ byte[] img = Base64.decode(imageBase64, Base64.DEFAULT);
670
+ if (img.length == 0) {
671
+ return errorJson("image: imageBase64 decoded to 0 bytes");
672
+ }
673
+ String mmproj = mmprojPath;
674
+ if (mmproj == null || mmproj.isEmpty()) {
675
+ File visionDir = new File(bundleDir, "vision");
676
+ mmproj = firstMatch(visionDir, ".gguf");
677
+ if (mmproj == null) {
678
+ return errorJson("image: mmproj GGUF not found under " + visionDir
679
+ + " (stage a vision projector or pass mmprojPath)");
680
+ }
681
+ }
682
+ synchronized (residentLock) {
683
+ final long ctx = ensureResidentCtx(bundleDir);
684
+ try {
685
+ String desc = ElizaVoiceNative.nativeDescribeImage(ctx, img, mmproj, prompt);
686
+ Log.i(TAG, "IMAGE from agent: " + img.length + " bytes (mmproj " + mmproj + ") -> \""
687
+ + (desc.length() > 200 ? desc.substring(0, 200) + "…" : desc) + "\"");
688
+ return new JSONObject()
689
+ .put("ok", true)
690
+ .put("text", desc)
691
+ .toString();
692
+ } catch (Throwable t) {
693
+ resetResident();
694
+ throw t;
344
695
  }
345
- String b64 = Base64.encodeToString(buf.array(), Base64.NO_WRAP);
346
- Log.i(TAG, "TTS (kokoro) from agent: " + text.length() + " chars -> "
347
- + pcm.length + " samples @ " + sampleRate + " Hz");
348
- return new JSONObject()
349
- .put("ok", true)
350
- .put("sampleRate", sampleRate)
351
- .put("samples", pcm.length)
352
- .put("pcmBase64", b64)
353
- .toString();
354
- } finally {
355
- ElizaVoiceNative.nativeContextDestroy(ctx);
356
696
  }
357
697
  }
358
698
 
@@ -144,6 +144,9 @@ final class ElizaVoiceNative {
144
144
  /** Read the diariz int8 frame labels for the i-th turn of the last call. */
145
145
  static native byte[] nativePipelineTurnLabels(long handle, int index);
146
146
 
147
+ /** Read the segmented fp32 PCM for the i-th turn of the last call. */
148
+ static native float[] nativePipelineTurnPcm(long handle, int index);
149
+
147
150
  static native void nativePipelineReset(long handle);
148
151
 
149
152
  static native void nativePipelineClose(long handle);
@@ -198,6 +201,14 @@ final class ElizaVoiceNative {
198
201
  /** Reset a persistent stream (clear KV + sampler) for warm reuse. 1=ok, 0=no. */
199
202
  static native int nativeLlmStreamReset(long streamHandle);
200
203
 
204
+ /**
205
+ * Prefix-preserving reset: keep the first {@code nKeep} tokens of KV cache
206
+ * resident and drop the rest, so the next prefill only decodes the per-turn
207
+ * delta. Returns the n_keep actually applied ({@code >= 0}), or a negative
208
+ * code on a null/MTP/unopened stream (caller falls back to a full reset).
209
+ */
210
+ static native int nativeLlmStreamResetKeep(long streamHandle, int nKeep);
211
+
201
212
  /**
202
213
  * KEYSTONE proof: run a whole greedy text generation in one native call,
203
214
  * in the bionic app process. With the dynamic-Vulkan lib staged, ggml-vulkan
@@ -217,4 +228,28 @@ final class ElizaVoiceNative {
217
228
  */
218
229
  static native float[] nativeKokoroSynthesize(
219
230
  long ctxHandle, String ggufPath, String voiceBinPath, String text, float speed);
231
+
232
+ // ── Batch ASR + mmproj vision (the agent's STT / screen-recognition path) ──
233
+
234
+ /**
235
+ * Transcribe {@code pcm} (16 kHz mono fp32) → UTF-8 transcript via the fused
236
+ * local ASR head ({@code eliza_inference_asr_transcribe}). VAD-free: the
237
+ * resident context mmap-acquires the {@code asr/} weights on first use. The
238
+ * bionic host's op="asr" calls this so the agent TRANSCRIPTION delegate gets
239
+ * a real on-device transcript without the full attribution pipeline.
240
+ */
241
+ static native String nativeAsrTranscribe(long ctxHandle, float[] pcm, int sampleRate);
242
+
243
+ /** {@code eliza_inference_vision_supported()} (ABI v9): 1 if mmproj vision is built. */
244
+ static native int nativeVisionSupported();
245
+
246
+ /**
247
+ * Describe a raw PNG/JPEG/WebP image with the resident TEXT model + the
248
+ * mmproj projector at {@code mmprojPath} ({@code eliza_inference_describe_image}).
249
+ * {@code prompt} may be empty (a default describe prompt is used). The bionic
250
+ * host's op="image" calls this so the agent IMAGE_DESCRIPTION delegate runs
251
+ * screen/vision recognition fully on-device.
252
+ */
253
+ static native String nativeDescribeImage(
254
+ long ctxHandle, byte[] imageBytes, String mmprojPath, String prompt);
220
255
  }