@elizaos/app-core 2.0.3-beta.2 → 2.0.3-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agent-bridge.d.ts +5 -5
- package/api/auth/audit.d.ts +1 -1
- package/api/auth/auth-context.d.ts +1 -1
- package/api/auth/bootstrap-token.d.ts +1 -1
- package/api/auth/sessions.d.ts +1 -1
- package/api/auth-bootstrap-routes.d.ts +1 -1
- package/api/auth-pairing-routes.d.ts +1 -1
- package/api/auth-pairing-routes.d.ts.map +1 -1
- package/api/auth-pairing-routes.js +2 -3
- package/api/auth-session-routes.d.ts +2 -2
- package/api/auth.d.ts +1 -1
- package/api/automations-compat-routes.d.ts +1 -1
- package/api/background-tasks-routes.d.ts +1 -1
- package/api/catalog-routes.d.ts +1 -1
- package/api/cloud-voice-routes.d.ts +1 -1
- package/api/compat-route-shared.d.ts +12 -5
- package/api/compat-route-shared.d.ts.map +1 -1
- package/api/compat-route-shared.js +22 -187
- package/api/database-rows-compat-routes.d.ts +1 -1
- package/api/dev-compat-routes.d.ts +1 -1
- package/api/dev-route-catalog.d.ts.map +1 -1
- package/api/dev-route-catalog.js +20 -0
- package/api/drop-status-compat-route.d.ts +3 -0
- package/api/drop-status-compat-route.d.ts.map +1 -0
- package/api/drop-status-compat-route.js +12 -0
- package/api/first-run-routes.d.ts +1 -1
- package/api/internal-routes.d.ts +1 -1
- package/api/runtime-mode-routes.d.ts +1 -1
- package/api/secrets-manager-routes.d.ts.map +1 -1
- package/api/sensitive-request-routes.d.ts +2 -2
- package/api/server-config-filter.d.ts.map +1 -1
- package/api/server-config-filter.js +0 -1
- package/api/server-first-run-helpers.d.ts.map +1 -1
- package/api/server-first-run-helpers.js +0 -3
- package/api/server.d.ts +8 -8
- package/api/server.d.ts.map +1 -1
- package/api/server.js +13 -59
- package/api/training-benchmarks.d.ts +1 -1
- package/api/training-benchmarks.d.ts.map +1 -1
- package/api/training-benchmarks.js +31 -9
- package/api/workbench-compat-routes.d.ts +1 -1
- package/benchmark/server-utils.d.ts +2 -2
- package/browser.d.ts +4 -4
- package/cli/plugins-cli.d.ts.map +1 -1
- package/cli/plugins-cli.js +5 -0
- package/cli/program/register.auth.d.ts +27 -1
- package/cli/program/register.auth.d.ts.map +1 -1
- package/cli/program/register.auth.js +187 -0
- package/cli/program/register.dashboard.d.ts.map +1 -1
- package/cli/program/register.dashboard.js +11 -1
- package/cli/program.d.ts +1 -1
- package/first-run/first-run-config.d.ts +1 -1
- package/index.d.ts +46 -47
- package/index.d.ts.map +1 -1
- package/index.js +3 -7
- package/package.json +42 -36
- package/packaging/PUBLISHING_GUIDE.md +20 -20
- package/platform/elizaos-agent-browser-stub.d.ts +0 -1
- package/platform/elizaos-agent-browser-stub.d.ts.map +1 -1
- package/platform/elizaos-agent-browser-stub.js +0 -1
- package/platform/ios-runtime-bridge.d.ts.map +1 -1
- package/platform/ios-runtime-bridge.js +0 -1
- package/platform/native-plugin-entrypoints.d.ts +2 -2
- package/platforms/android/app/build.gradle +30 -7
- package/platforms/android/app/src/main/elizavoice-jni/elizavoice-jni.cpp +179 -35
- package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaAgentService.java +60 -32
- package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaBionicInferenceServer.java +405 -65
- package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaVoiceNative.java +35 -0
- package/platforms/android/app/src/main/java/ai/elizaos/app/ElizaVoicePlugin.java +13 -6
- package/platforms/android/app/src/main/res/drawable/splash.png +0 -0
- package/platforms/android/app/src/main/res/drawable-land-hdpi/splash.png +0 -0
- package/platforms/android/app/src/main/res/drawable-land-mdpi/splash.png +0 -0
- package/platforms/android/app/src/main/res/drawable-land-xhdpi/splash.png +0 -0
- package/platforms/android/app/src/main/res/drawable-land-xxhdpi/splash.png +0 -0
- package/platforms/android/app/src/main/res/drawable-land-xxxhdpi/splash.png +0 -0
- package/platforms/android/app/src/main/res/drawable-port-hdpi/splash.png +0 -0
- package/platforms/android/app/src/main/res/drawable-port-mdpi/splash.png +0 -0
- package/platforms/android/app/src/main/res/drawable-port-xhdpi/splash.png +0 -0
- package/platforms/android/app/src/main/res/drawable-port-xxhdpi/splash.png +0 -0
- package/platforms/android/app/src/main/res/drawable-port-xxxhdpi/splash.png +0 -0
- package/platforms/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
- package/platforms/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png +0 -0
- package/platforms/android/app/src/main/res/mipmap-hdpi/ic_launcher_monochrome.png +0 -0
- package/platforms/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png +0 -0
- package/platforms/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
- package/platforms/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png +0 -0
- package/platforms/android/app/src/main/res/mipmap-mdpi/ic_launcher_monochrome.png +0 -0
- package/platforms/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png +0 -0
- package/platforms/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
- package/platforms/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png +0 -0
- package/platforms/android/app/src/main/res/mipmap-xhdpi/ic_launcher_monochrome.png +0 -0
- package/platforms/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png +0 -0
- package/platforms/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
- package/platforms/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png +0 -0
- package/platforms/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome.png +0 -0
- package/platforms/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png +0 -0
- package/platforms/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
- package/platforms/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png +0 -0
- package/platforms/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome.png +0 -0
- package/platforms/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png +0 -0
- package/platforms/android/app/src/main/res/values/colors.xml +5 -1
- package/platforms/android/app/src/main/res/values/ic_launcher_background.xml +1 -1
- package/platforms/android/app/src/main/res/values/styles.xml +4 -1
- package/platforms/electrobun/electrobun.config.ts +0 -1
- package/platforms/electrobun/native/macos/window-effects.mm +4 -0
- package/platforms/electrobun/package.json +11 -9
- package/platforms/electrobun/remotes/local-model/src/bun/eliza1-catalog.ts +1 -1
- package/platforms/electrobun/remotes/runtime/src/dev/phase2-smoke.ts +10 -5
- package/platforms/electrobun/remotes/runtime/src/dev/phase3-smoke.ts +11 -15
- package/platforms/electrobun/src/application-menu.ts +0 -7
- package/platforms/electrobun/src/desktop-http-request.test.ts +30 -1
- package/platforms/electrobun/src/desktop-http-request.ts +14 -2
- package/platforms/electrobun/src/desktop-test-bridge-server.ts +28 -0
- package/platforms/electrobun/src/fatal-shutdown.test.ts +3 -7
- package/platforms/electrobun/src/index.ts +64 -13
- package/platforms/electrobun/src/lifecycle/api-base-owner.test.ts +83 -0
- package/platforms/electrobun/src/lifecycle/api-base-owner.ts +37 -16
- package/platforms/electrobun/src/native/agent.ts +28 -17
- package/platforms/electrobun/src/native/credentials.ts +6 -1
- package/platforms/electrobun/src/native/desktop-window.test.ts +142 -26
- package/platforms/electrobun/src/native/desktop.ts +8 -4
- package/platforms/electrobun/src/native/index.ts +6 -0
- package/platforms/electrobun/src/native/remote-plugin-host.ts +18 -8
- package/platforms/electrobun/src/preload.js +1 -1
- package/platforms/electrobun/src/voice/voice-live-validation.test.ts +10 -5
- package/platforms/electrobun/src/voice/voice-runtime-adapter.ts +3 -2
- package/platforms/electrobun/src/voice/voice-service.test.ts +26 -0
- package/platforms/electrobun/src/voice/voice-service.ts +57 -17
- package/platforms/electrobun/tsconfig.json +17 -1
- package/platforms/ios/App/App/Base.lproj/LaunchScreen.storyboard +4 -6
- package/registry/index.d.ts +1 -6
- package/registry/index.d.ts.map +1 -1
- package/registry/index.js +9 -101
- package/runtime/android-avf-microdroid-bridge.d.ts +1 -1
- package/runtime/bundled-fused-lib.d.ts +36 -0
- package/runtime/bundled-fused-lib.d.ts.map +1 -0
- package/runtime/bundled-fused-lib.js +77 -0
- package/runtime/channel-plugin-map.d.ts +1 -5
- package/runtime/channel-plugin-map.d.ts.map +1 -1
- package/runtime/channel-plugin-map.js +11 -31
- package/runtime/desktop/index.d.ts +5 -5
- package/runtime/dev-server.js +11 -2
- package/runtime/eliza.d.ts.map +1 -1
- package/runtime/eliza.js +18 -8
- package/runtime/mode/route-mode-guard.d.ts +1 -1
- package/runtime/mode/route-mode-matrix.d.ts +1 -1
- package/runtime/voice-warmup.d.ts +4 -19
- package/runtime/voice-warmup.d.ts.map +1 -1
- package/runtime/voice-warmup.js +4 -14
- package/scripts/aosp/README.md +10 -1
- package/scripts/aosp/compile-libllama-zig-pin.test.mjs +147 -0
- package/scripts/aosp/compile-libllama.mjs +446 -59
- package/scripts/aosp/compile-libllama.test.mjs +133 -0
- package/scripts/aosp/compile-shim.mjs +21 -1
- package/scripts/aosp/deploy-pixel.mjs +1 -1
- package/scripts/aosp/stage-default-models.mjs +24 -31
- package/scripts/aosp/stage-default-models.test.mjs +23 -0
- package/scripts/assert-required-bundled-packages.test.ts +22 -1
- package/scripts/build-helpers/verify-fused-symbols.mjs +33 -0
- package/scripts/build-helpers/verify-fused-symbols.test.mjs +0 -0
- package/scripts/build-image.sh +1 -3
- package/scripts/build-llama-cpp-mtp.mjs +17 -16
- package/scripts/check-i18n.mjs +0 -1
- package/scripts/copy-runtime-node-modules.ts +147 -0
- package/scripts/desktop-build.mjs +317 -4
- package/scripts/docker-ci-smoke.sh +33 -8
- package/scripts/ensure-type-package-aliases.mjs +1 -1
- package/scripts/ios-xcframework/run-physical-device-smoke.mjs +1 -1
- package/scripts/lib/agent-source-watcher.mjs +0 -1
- package/scripts/lib/artifact-staleness.d.mts +33 -0
- package/scripts/lib/artifact-staleness.mjs +122 -0
- package/scripts/lib/artifact-staleness.test.mts +98 -0
- package/scripts/lib/duet-bridge.mjs +1 -1
- package/scripts/lib/ios-plist-url-scheme.mjs +80 -0
- package/scripts/lib/mobile-build-decisions.mjs +97 -0
- package/scripts/lib/renderer-build-manifest.d.mts +65 -0
- package/scripts/lib/renderer-build-manifest.mjs +257 -0
- package/scripts/lib/renderer-build-manifest.test.mts +277 -0
- package/scripts/lib/stage-android-agent.mjs +13 -4
- package/scripts/lib/verify-ondevice-artifact.d.mts +16 -0
- package/scripts/lib/verify-ondevice-artifact.mjs +61 -0
- package/scripts/lib/verify-ondevice-artifact.test.mts +98 -0
- package/scripts/lib/websocket-pending-queue.ts +92 -0
- package/scripts/link-docker-local-app-packages.mjs +6 -0
- package/scripts/playwright-ui-live-stack.ts +430 -144
- package/scripts/playwright-ui-smoke-api-stub.mjs +160 -168
- package/scripts/run-local-plugin-live-smoke.mjs +1 -6
- package/scripts/run-mobile-build-brand-separation.test.mts +33 -0
- package/scripts/run-mobile-build-ios-engine-gate.test.mjs +85 -0
- package/scripts/run-mobile-build-mtp-staleness.test.mts +198 -0
- package/scripts/run-mobile-build.mjs +408 -86
- package/scripts/serve-real-local-agent.ts +72 -0
- package/scripts/stage-desktop-fused-lib.mjs +43 -14
- package/scripts/verify-ondevice-artifact.mjs +111 -0
- package/scripts/voice-attribution-smoke.ts +144 -38
- package/scripts/voice-duet.mjs +6 -6
- package/scripts/voice-e2e-hardware.ts +22 -16
- package/scripts/voice-interactive.mjs +16 -45
- package/security/agent-vault-id.d.ts +1 -1
- package/security/platform-secure-store-node.d.ts +1 -1
- package/services/ambient-audio/consent.d.ts +1 -1
- package/services/ambient-audio/index.d.ts +6 -6
- package/services/ambient-audio/replay-buffer.d.ts +1 -1
- package/services/ambient-audio/response-gate.d.ts +1 -1
- package/services/ambient-audio/service.d.ts +3 -3
- package/services/connector-target-catalog.d.ts +1 -1
- package/services/sensitive-requests/index.d.ts +6 -6
- package/services/steward-sidecar/wallet-setup.d.ts +1 -1
- package/services/steward-sidecar.d.ts +3 -3
- package/services/tunnel-to-mobile/index.d.ts +1 -1
- package/services/vault-bootstrap.d.ts.map +1 -1
- package/services/vault-bootstrap.js +14 -10
- package/services/voice-profiles/diarization-pipeline.d.ts +1 -1
- package/services/voice-profiles/index.d.ts +11 -11
- package/services/voice-profiles/owner-confidence.d.ts +1 -1
- package/services/voice-profiles/private-challenge.d.ts +1 -1
- package/services/voice-profiles/store.d.ts +1 -1
- package/test/helpers/live-provider.test.ts +91 -1
- package/test/helpers/live-provider.ts +195 -51
- package/test/scripts/websocket-pending-queue.test.ts +119 -0
- package/test-support/test-helpers.d.ts +0 -33
- package/test-support/test-helpers.d.ts.map +1 -1
- package/test-support/test-helpers.js +81 -189
- package/ui-compat.d.ts +1 -0
- package/ui-compat.d.ts.map +1 -1
- package/ui-compat.js +4 -0
- package/platforms/electrobun/docs/convergence-audit.json +0 -3505
- package/platforms/electrobun/docs/convergence-audit.md +0 -694
- package/platforms/electrobun/scripts/ensure-whisper-gguf.sh +0 -55
- package/platforms/electrobun/scripts/ensure-whisper-model.sh +0 -35
- package/platforms/electrobun/scripts/generate-convergence-audit.ts +0 -1203
- package/platforms/electrobun/src/native/whisper-env.test.ts +0 -71
- package/platforms/electrobun/src/native/whisper-env.ts +0 -68
- package/registry/app-registry.d.ts +0 -14
- package/registry/app-registry.d.ts.map +0 -1
- package/registry/app-registry.js +0 -29
- package/registry/entries/apps/app-polymarket.json +0 -31
- package/registry/entries/apps/clawville.json +0 -27
- package/registry/entries/apps/companion.json +0 -28
- package/registry/entries/apps/database-viewer.json +0 -27
- package/registry/entries/apps/defense-of-the-agents.json +0 -27
- package/registry/entries/apps/documents.json +0 -30
- package/registry/entries/apps/feed.json +0 -27
- package/registry/entries/apps/hyperliquid.json +0 -31
- package/registry/entries/apps/log-viewer.json +0 -27
- package/registry/entries/apps/memory-viewer.json +0 -27
- package/registry/entries/apps/model-tester.json +0 -31
- package/registry/entries/apps/plugin-viewer.json +0 -27
- package/registry/entries/apps/relationship-viewer.json +0 -27
- package/registry/entries/apps/runtime-debugger.json +0 -27
- package/registry/entries/apps/shopify.json +0 -31
- package/registry/entries/apps/skills-viewer.json +0 -27
- package/registry/entries/apps/steward.json +0 -31
- package/registry/entries/apps/training.json +0 -54
- package/registry/entries/apps/trajectory-viewer.json +0 -27
- package/registry/entries/apps/vincent.json +0 -31
- package/registry/entries/connectors/bluebubbles.json +0 -99
- package/registry/entries/connectors/bluesky.json +0 -173
- package/registry/entries/connectors/discord.json +0 -119
- package/registry/entries/connectors/farcaster.json +0 -174
- package/registry/entries/connectors/feishu.json +0 -79
- package/registry/entries/connectors/google-chat.json +0 -120
- package/registry/entries/connectors/google.json +0 -82
- package/registry/entries/connectors/imessage.json +0 -96
- package/registry/entries/connectors/instagram.json +0 -64
- package/registry/entries/connectors/line.json +0 -86
- package/registry/entries/connectors/matrix.json +0 -94
- package/registry/entries/connectors/mattermost.json +0 -110
- package/registry/entries/connectors/msteams.json +0 -104
- package/registry/entries/connectors/nextcloud-talk.json +0 -104
- package/registry/entries/connectors/nostr.json +0 -70
- package/registry/entries/connectors/signal.json +0 -81
- package/registry/entries/connectors/slack.json +0 -102
- package/registry/entries/connectors/telegram.json +0 -71
- package/registry/entries/connectors/tlon.json +0 -94
- package/registry/entries/connectors/twitch.json +0 -110
- package/registry/entries/connectors/whatsapp.json +0 -113
- package/registry/entries/connectors/x.json +0 -231
- package/registry/entries/connectors/zalo.json +0 -112
- package/registry/entries/connectors/zalouser.json +0 -122
- package/registry/entries/plugins/agent-orchestrator.json +0 -33
- package/registry/entries/plugins/agent-skills.json +0 -72
- package/registry/entries/plugins/anthropic.json +0 -73
- package/registry/entries/plugins/app-control.json +0 -23
- package/registry/entries/plugins/auto-trader.json +0 -203
- package/registry/entries/plugins/background-runner.json +0 -26
- package/registry/entries/plugins/blooio.json +0 -102
- package/registry/entries/plugins/browser.json +0 -75
- package/registry/entries/plugins/cli.json +0 -40
- package/registry/entries/plugins/clipboard.json +0 -44
- package/registry/entries/plugins/coding-tools.json +0 -71
- package/registry/entries/plugins/commands.json +0 -63
- package/registry/entries/plugins/computeruse.json +0 -74
- package/registry/entries/plugins/copilot-proxy.json +0 -93
- package/registry/entries/plugins/directives.json +0 -63
- package/registry/entries/plugins/edge-tts.json +0 -97
- package/registry/entries/plugins/elevenlabs.json +0 -169
- package/registry/entries/plugins/elizacloud.json +0 -208
- package/registry/entries/plugins/evm.json +0 -134
- package/registry/entries/plugins/experience.json +0 -34
- package/registry/entries/plugins/facewear.json +0 -131
- package/registry/entries/plugins/form.json +0 -26
- package/registry/entries/plugins/github.json +0 -93
- package/registry/entries/plugins/gmail-watch.json +0 -25
- package/registry/entries/plugins/goals.json +0 -77
- package/registry/entries/plugins/google-genai.json +0 -106
- package/registry/entries/plugins/groq.json +0 -93
- package/registry/entries/plugins/hedera.json +0 -48
- package/registry/entries/plugins/inmemorydb.json +0 -25
- package/registry/entries/plugins/linear.json +0 -51
- package/registry/entries/plugins/local-inference.json +0 -137
- package/registry/entries/plugins/local-storage.json +0 -36
- package/registry/entries/plugins/localdb.json +0 -25
- package/registry/entries/plugins/mcp.json +0 -44
- package/registry/entries/plugins/memory.json +0 -124
- package/registry/entries/plugins/minecraft.json +0 -79
- package/registry/entries/plugins/moltbook.json +0 -83
- package/registry/entries/plugins/music.json +0 -155
- package/registry/entries/plugins/mysticism.json +0 -48
- package/registry/entries/plugins/nearai.json +0 -82
- package/registry/entries/plugins/ngrok.json +0 -69
- package/registry/entries/plugins/ollama.json +0 -96
- package/registry/entries/plugins/openai.json +0 -189
- package/registry/entries/plugins/openrouter.json +0 -188
- package/registry/entries/plugins/pdf.json +0 -26
- package/registry/entries/plugins/plugin-manager.json +0 -23
- package/registry/entries/plugins/prose.json +0 -48
- package/registry/entries/plugins/rlm.json +0 -26
- package/registry/entries/plugins/roblox.json +0 -88
- package/registry/entries/plugins/rss.json +0 -64
- package/registry/entries/plugins/s3-storage.json +0 -91
- package/registry/entries/plugins/scheduling.json +0 -35
- package/registry/entries/plugins/shell.json +0 -94
- package/registry/entries/plugins/social-alpha.json +0 -72
- package/registry/entries/plugins/tailscale.json +0 -81
- package/registry/entries/plugins/tee.json +0 -53
- package/registry/entries/plugins/todos.json +0 -26
- package/registry/entries/plugins/trajectory-logger.json +0 -33
- package/registry/entries/plugins/trust.json +0 -39
- package/registry/entries/plugins/tts.json +0 -71
- package/registry/entries/plugins/tunnel.json +0 -45
- package/registry/entries/plugins/twilio.json +0 -168
- package/registry/entries/plugins/vercel-ai-gateway.json +0 -128
- package/registry/entries/plugins/video.json +0 -23
- package/registry/entries/plugins/vision.json +0 -43
- package/registry/entries/plugins/webhooks.json +0 -23
- package/registry/entries/plugins/workflow.json +0 -25
- package/registry/entries/plugins/xai.json +0 -75
- package/registry/loader.d.ts +0 -28
- package/registry/loader.d.ts.map +0 -1
- package/registry/loader.js +0 -158
- package/registry/schema.d.ts +0 -1199
- package/registry/schema.d.ts.map +0 -1
- package/registry/schema.js +0 -300
- package/scripts/patches/llama-mobile-kokoro-tts.patch +0 -480
- package/scripts/release-workflow-drift.test.ts +0 -57
- package/test/helpers/i18n.ts +0 -5
- package/test/helpers/loopback.ts +0 -33
- package/test/helpers/react-test.ts +0 -40
- package/test/helpers/skip-without.ts +0 -53
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Renderer build manifest — the deterministic "build stamp" for the web/renderer
|
|
3
|
+
* bundle that every on-device artifact must carry.
|
|
4
|
+
*
|
|
5
|
+
* Issue #9309: the recurring on-device failure mode is a device booting STALE UI
|
|
6
|
+
* because a build step copied a cached `dist/` instead of the freshly built one.
|
|
7
|
+
* There was no machine-checkable identity for "which renderer is this", so a
|
|
8
|
+
* stale copy shipped silently.
|
|
9
|
+
*
|
|
10
|
+
* This module gives the renderer a content-derived identity:
|
|
11
|
+
* - The vite `renderer-build-manifest` plugin writes `eliza-renderer-build.json`
|
|
12
|
+
* into `dist/` at the end of EVERY renderer build (mobile, desktop, web).
|
|
13
|
+
* - The file is copied into the device artifact alongside the bundle (cap sync
|
|
14
|
+
* copies the whole webDir; desktop's Electrobun copy carries dist/), so it
|
|
15
|
+
* ships on-device as an asserted build stamp.
|
|
16
|
+
* - The platform orchestrators call `assertStagedRendererMatchesBuild()` after
|
|
17
|
+
* staging so a stale/missing/partial renderer FAILS THE BUILD LOUDLY instead
|
|
18
|
+
* of shipping old code.
|
|
19
|
+
*
|
|
20
|
+
* `buildId` is a sha256 over `index.html` + the sorted set of emitted asset
|
|
21
|
+
* file names (vite embeds each asset's content hash in its name) and their
|
|
22
|
+
* sizes. Two different source states therefore produce two different buildIds,
|
|
23
|
+
* which is exactly the freshness signal the issue asks for ("a real freshness
|
|
24
|
+
* check that fails the build when an input changed but the artifact didn't").
|
|
25
|
+
*/
|
|
26
|
+
import crypto from "node:crypto";
|
|
27
|
+
import fs from "node:fs";
|
|
28
|
+
import path from "node:path";
|
|
29
|
+
|
|
30
|
+
export const RENDERER_BUILD_MANIFEST_FILENAME = "eliza-renderer-build.json";
|
|
31
|
+
export const RENDERER_BUILD_MANIFEST_SCHEMA = "elizaos.renderer.build/v1";
|
|
32
|
+
|
|
33
|
+
function sha256(buf) {
|
|
34
|
+
return crypto.createHash("sha256").update(buf).digest("hex");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Deterministic content fingerprint of a built renderer directory.
|
|
39
|
+
* @param {string} distDir absolute path to the built renderer dir (has index.html)
|
|
40
|
+
* @returns {{ buildId: string, indexHtmlSha256: string, assetCount: number }}
|
|
41
|
+
*/
|
|
42
|
+
export function computeRendererFingerprint(distDir) {
|
|
43
|
+
const indexPath = path.join(distDir, "index.html");
|
|
44
|
+
if (!fs.existsSync(indexPath)) {
|
|
45
|
+
throw new Error(
|
|
46
|
+
`[renderer-build-manifest] no index.html in ${distDir} — not a built renderer.`,
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
const indexHtmlSha256 = sha256(fs.readFileSync(indexPath));
|
|
50
|
+
|
|
51
|
+
const assetEntries = [];
|
|
52
|
+
const assetsDir = path.join(distDir, "assets");
|
|
53
|
+
if (fs.existsSync(assetsDir)) {
|
|
54
|
+
const walk = (dir, rel) => {
|
|
55
|
+
for (const name of fs.readdirSync(dir).sort()) {
|
|
56
|
+
const full = path.join(dir, name);
|
|
57
|
+
const relName = rel ? `${rel}/${name}` : name;
|
|
58
|
+
const stat = fs.statSync(full);
|
|
59
|
+
if (stat.isDirectory()) {
|
|
60
|
+
walk(full, relName);
|
|
61
|
+
} else {
|
|
62
|
+
assetEntries.push(`${relName}:${stat.size}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
walk(assetsDir, "");
|
|
67
|
+
}
|
|
68
|
+
assetEntries.sort();
|
|
69
|
+
const buildId = sha256(`${indexHtmlSha256}\n${assetEntries.join("\n")}`);
|
|
70
|
+
return { buildId, indexHtmlSha256, assetCount: assetEntries.length };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Build the manifest object for a renderer dir (does not write it).
|
|
75
|
+
* @param {string} distDir
|
|
76
|
+
* @param {{ builtAt?: string, commit?: string|null, variant?: string|null,
|
|
77
|
+
* capacitorTarget?: string|null, runtimeMode?: string|null }} [meta]
|
|
78
|
+
*/
|
|
79
|
+
export function buildRendererManifest(distDir, meta = {}) {
|
|
80
|
+
const fingerprint = computeRendererFingerprint(distDir);
|
|
81
|
+
return {
|
|
82
|
+
schema: RENDERER_BUILD_MANIFEST_SCHEMA,
|
|
83
|
+
buildId: fingerprint.buildId,
|
|
84
|
+
indexHtmlSha256: fingerprint.indexHtmlSha256,
|
|
85
|
+
assetCount: fingerprint.assetCount,
|
|
86
|
+
builtAt: meta.builtAt ?? new Date().toISOString(),
|
|
87
|
+
commit: meta.commit ?? null,
|
|
88
|
+
variant: meta.variant ?? null,
|
|
89
|
+
capacitorTarget: meta.capacitorTarget ?? null,
|
|
90
|
+
runtimeMode: meta.runtimeMode ?? null,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Write `eliza-renderer-build.json` into a built renderer dir. Returns manifest.
|
|
96
|
+
* @param {string} distDir
|
|
97
|
+
* @param {object} [meta]
|
|
98
|
+
*/
|
|
99
|
+
export function writeRendererBuildManifest(distDir, meta = {}) {
|
|
100
|
+
const manifest = buildRendererManifest(distDir, meta);
|
|
101
|
+
fs.writeFileSync(
|
|
102
|
+
path.join(distDir, RENDERER_BUILD_MANIFEST_FILENAME),
|
|
103
|
+
`${JSON.stringify(manifest, null, 2)}\n`,
|
|
104
|
+
);
|
|
105
|
+
return manifest;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Read a renderer build manifest from a dir (the dir that holds index.html), or
|
|
110
|
+
* null if absent/unparseable.
|
|
111
|
+
* @param {string} dir
|
|
112
|
+
*/
|
|
113
|
+
export function readRendererBuildManifest(dir) {
|
|
114
|
+
const manifestPath = path.join(dir, RENDERER_BUILD_MANIFEST_FILENAME);
|
|
115
|
+
if (!fs.existsSync(manifestPath)) return null;
|
|
116
|
+
try {
|
|
117
|
+
return JSON.parse(fs.readFileSync(manifestPath, "utf8"));
|
|
118
|
+
} catch {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Assert a STAGED renderer directory carries exactly the renderer that was just
|
|
125
|
+
* built in `freshDistDir`. Throws loudly on any mismatch so a stale/missing/
|
|
126
|
+
* partial UI fails the build instead of shipping silently.
|
|
127
|
+
*
|
|
128
|
+
* @param {string} freshDistDir the just-built renderer (source of truth)
|
|
129
|
+
* @param {string} stagedDir where it was staged for the device artifact
|
|
130
|
+
* @param {{ label?: string }} [opts]
|
|
131
|
+
* @returns the fresh manifest on success
|
|
132
|
+
*/
|
|
133
|
+
export function assertStagedRendererMatchesBuild(
|
|
134
|
+
freshDistDir,
|
|
135
|
+
stagedDir,
|
|
136
|
+
{ label = "renderer" } = {},
|
|
137
|
+
) {
|
|
138
|
+
const fresh = readRendererBuildManifest(freshDistDir);
|
|
139
|
+
if (!fresh) {
|
|
140
|
+
throw new Error(
|
|
141
|
+
`[renderer-build-manifest] ${label}: the freshly built renderer in ${freshDistDir} ` +
|
|
142
|
+
`has no ${RENDERER_BUILD_MANIFEST_FILENAME}. The renderer-build-manifest vite plugin ` +
|
|
143
|
+
`did not run — refusing to ship an unverifiable bundle.`,
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
const staged = readRendererBuildManifest(stagedDir);
|
|
147
|
+
if (!staged) {
|
|
148
|
+
throw new Error(
|
|
149
|
+
`[renderer-build-manifest] ${label}: staged renderer at ${stagedDir} has no ` +
|
|
150
|
+
`${RENDERER_BUILD_MANIFEST_FILENAME}. Capacitor sync / staging did not copy the freshly ` +
|
|
151
|
+
`built bundle — the device would boot stale or missing UI. Failing the build.`,
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
if (staged.buildId !== fresh.buildId) {
|
|
155
|
+
throw new Error(
|
|
156
|
+
`[renderer-build-manifest] ${label}: STALE RENDERER staged. ` +
|
|
157
|
+
`staged buildId=${staged.buildId} (built ${staged.builtAt}) != ` +
|
|
158
|
+
`freshly built buildId=${fresh.buildId} (built ${fresh.builtAt}). ` +
|
|
159
|
+
`The device would boot an OLD UI — failing the build instead of shipping it.`,
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
// A copied manifest paired with a partial/old index.html would pass the buildId
|
|
163
|
+
// check above; re-hash the staged index.html to defend against a torn copy.
|
|
164
|
+
const stagedIndex = path.join(stagedDir, "index.html");
|
|
165
|
+
if (!fs.existsSync(stagedIndex)) {
|
|
166
|
+
throw new Error(
|
|
167
|
+
`[renderer-build-manifest] ${label}: staged index.html missing in ${stagedDir}.`,
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
const stagedHtmlSha = sha256(fs.readFileSync(stagedIndex));
|
|
171
|
+
if (stagedHtmlSha !== fresh.indexHtmlSha256) {
|
|
172
|
+
throw new Error(
|
|
173
|
+
`[renderer-build-manifest] ${label}: staged index.html hash ${stagedHtmlSha} != ` +
|
|
174
|
+
`freshly built ${fresh.indexHtmlSha256} — partial or stale copy. Failing the build.`,
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
return fresh;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Stale-web guard: overlay the freshly built renderer onto a Capacitor `public`
|
|
182
|
+
* dir, then assert it matches. `cap sync` has been observed to leave a stale
|
|
183
|
+
* `public` (old hashed assets shipping an ancient UI). The fresh `dist` is the
|
|
184
|
+
* source of truth, so clear the hashed `assets/` and copy `dist` over `public`.
|
|
185
|
+
* Non-renderer payloads staged outside `dist` (e.g. `public/agent`, PGlite root
|
|
186
|
+
* extension assets) live outside `dist` and survive — only `public/assets` is
|
|
187
|
+
* cleared and cpSync never deletes existing non-dist files. Throws if the fresh
|
|
188
|
+
* renderer is missing or the staged result doesn't match (issue #9309).
|
|
189
|
+
*
|
|
190
|
+
* @param {string} freshDistDir the just-built renderer (source of truth)
|
|
191
|
+
* @param {string} targetPublicDir the Capacitor `public` dir to overlay
|
|
192
|
+
* @param {{ label?: string }} [opts]
|
|
193
|
+
* @returns the fresh manifest on success
|
|
194
|
+
*/
|
|
195
|
+
export function overlayFreshRendererIntoPublic(
|
|
196
|
+
freshDistDir,
|
|
197
|
+
targetPublicDir,
|
|
198
|
+
{ label = "renderer" } = {},
|
|
199
|
+
) {
|
|
200
|
+
if (!fs.existsSync(path.join(freshDistDir, "index.html"))) {
|
|
201
|
+
throw new Error(
|
|
202
|
+
`[renderer-build-manifest] ${label}: no freshly built renderer at ${freshDistDir} ` +
|
|
203
|
+
`(missing index.html). Refusing to stage a missing/stale UI.`,
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
fs.mkdirSync(targetPublicDir, { recursive: true });
|
|
207
|
+
fs.rmSync(path.join(targetPublicDir, "assets"), {
|
|
208
|
+
recursive: true,
|
|
209
|
+
force: true,
|
|
210
|
+
});
|
|
211
|
+
fs.cpSync(freshDistDir, targetPublicDir, { recursive: true });
|
|
212
|
+
return assertStagedRendererMatchesBuild(freshDistDir, targetPublicDir, {
|
|
213
|
+
label,
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Assert a renderer manifest was (re)generated during THIS build invocation —
|
|
219
|
+
* i.e. it is not a stale leftover from a previous run. Used by the desktop build
|
|
220
|
+
* right after `vite build` to prove the renderer was actually rebuilt.
|
|
221
|
+
*
|
|
222
|
+
* @param {string} distDir
|
|
223
|
+
* @param {{ notBefore: number, expectVariant?: string|null, label?: string }} opts
|
|
224
|
+
* notBefore: epoch ms captured immediately before the renderer build started
|
|
225
|
+
* @returns the manifest on success
|
|
226
|
+
*/
|
|
227
|
+
export function assertRendererRebuiltSince(
|
|
228
|
+
distDir,
|
|
229
|
+
{ notBefore, expectVariant = null, label = "renderer" },
|
|
230
|
+
) {
|
|
231
|
+
const manifest = readRendererBuildManifest(distDir);
|
|
232
|
+
if (!manifest) {
|
|
233
|
+
throw new Error(
|
|
234
|
+
`[renderer-build-manifest] ${label}: no ${RENDERER_BUILD_MANIFEST_FILENAME} in ${distDir} ` +
|
|
235
|
+
`after the renderer build. The build did not produce a verifiable renderer.`,
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
const builtAtMs = Date.parse(manifest.builtAt);
|
|
239
|
+
if (!Number.isFinite(builtAtMs) || builtAtMs < notBefore) {
|
|
240
|
+
throw new Error(
|
|
241
|
+
`[renderer-build-manifest] ${label}: renderer manifest is STALE. builtAt=${manifest.builtAt} ` +
|
|
242
|
+
`predates this build invocation (started ${new Date(notBefore).toISOString()}). ` +
|
|
243
|
+
`A cached/stale dist was reused instead of a fresh build — failing.`,
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
if (
|
|
247
|
+
expectVariant != null &&
|
|
248
|
+
manifest.variant != null &&
|
|
249
|
+
manifest.variant !== expectVariant
|
|
250
|
+
) {
|
|
251
|
+
throw new Error(
|
|
252
|
+
`[renderer-build-manifest] ${label}: renderer built for variant '${manifest.variant}' ` +
|
|
253
|
+
`but this build targets '${expectVariant}'. A wrong-variant dist was reused — failing.`,
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
return manifest;
|
|
257
|
+
}
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
assertRendererRebuiltSince,
|
|
8
|
+
assertStagedRendererMatchesBuild,
|
|
9
|
+
buildRendererManifest,
|
|
10
|
+
computeRendererFingerprint,
|
|
11
|
+
overlayFreshRendererIntoPublic,
|
|
12
|
+
RENDERER_BUILD_MANIFEST_FILENAME,
|
|
13
|
+
readRendererBuildManifest,
|
|
14
|
+
writeRendererBuildManifest,
|
|
15
|
+
} from "./renderer-build-manifest.mjs";
|
|
16
|
+
|
|
17
|
+
let tmp: string;
|
|
18
|
+
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
tmp = fs.mkdtempSync(path.join(os.tmpdir(), "renderer-manifest-"));
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
afterEach(() => {
|
|
24
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Build a minimal renderer dist dir: index.html + assets/<name>. Mirrors what
|
|
29
|
+
* `vite build` emits (content-hashed asset names + an index.html referencing them).
|
|
30
|
+
*/
|
|
31
|
+
function makeDist(
|
|
32
|
+
dir: string,
|
|
33
|
+
{
|
|
34
|
+
indexHtml = "<!doctype html><html><body><div id=root></div></body></html>",
|
|
35
|
+
assets = { "index-abc123.js": "console.log(1)" },
|
|
36
|
+
}: { indexHtml?: string; assets?: Record<string, string> } = {},
|
|
37
|
+
) {
|
|
38
|
+
fs.mkdirSync(path.join(dir, "assets"), { recursive: true });
|
|
39
|
+
fs.writeFileSync(path.join(dir, "index.html"), indexHtml);
|
|
40
|
+
for (const [name, body] of Object.entries(assets)) {
|
|
41
|
+
fs.writeFileSync(path.join(dir, "assets", name), body);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
describe("computeRendererFingerprint", () => {
|
|
46
|
+
it("is deterministic for identical content", () => {
|
|
47
|
+
const a = path.join(tmp, "a");
|
|
48
|
+
const b = path.join(tmp, "b");
|
|
49
|
+
makeDist(a);
|
|
50
|
+
makeDist(b);
|
|
51
|
+
expect(computeRendererFingerprint(a).buildId).toBe(
|
|
52
|
+
computeRendererFingerprint(b).buildId,
|
|
53
|
+
);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("changes when index.html changes", () => {
|
|
57
|
+
const a = path.join(tmp, "a");
|
|
58
|
+
const b = path.join(tmp, "b");
|
|
59
|
+
makeDist(a);
|
|
60
|
+
makeDist(b, {
|
|
61
|
+
indexHtml: "<!doctype html><html><body>changed</body></html>",
|
|
62
|
+
});
|
|
63
|
+
expect(computeRendererFingerprint(a).buildId).not.toBe(
|
|
64
|
+
computeRendererFingerprint(b).buildId,
|
|
65
|
+
);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("changes when an asset is added or renamed (new content hash)", () => {
|
|
69
|
+
const a = path.join(tmp, "a");
|
|
70
|
+
const b = path.join(tmp, "b");
|
|
71
|
+
makeDist(a, { assets: { "index-abc123.js": "x" } });
|
|
72
|
+
makeDist(b, { assets: { "index-def456.js": "x" } });
|
|
73
|
+
expect(computeRendererFingerprint(a).buildId).not.toBe(
|
|
74
|
+
computeRendererFingerprint(b).buildId,
|
|
75
|
+
);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("throws when index.html is missing", () => {
|
|
79
|
+
const a = path.join(tmp, "a");
|
|
80
|
+
fs.mkdirSync(a, { recursive: true });
|
|
81
|
+
expect(() => computeRendererFingerprint(a)).toThrow(/not a built renderer/);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
describe("writeRendererBuildManifest / readRendererBuildManifest", () => {
|
|
86
|
+
it("round-trips and records metadata", () => {
|
|
87
|
+
const dist = path.join(tmp, "dist");
|
|
88
|
+
makeDist(dist);
|
|
89
|
+
const written = writeRendererBuildManifest(dist, {
|
|
90
|
+
builtAt: "2026-01-01T00:00:00.000Z",
|
|
91
|
+
commit: "deadbeef",
|
|
92
|
+
variant: "store",
|
|
93
|
+
capacitorTarget: "ios",
|
|
94
|
+
});
|
|
95
|
+
expect(
|
|
96
|
+
fs.existsSync(path.join(dist, RENDERER_BUILD_MANIFEST_FILENAME)),
|
|
97
|
+
).toBe(true);
|
|
98
|
+
const read = readRendererBuildManifest(dist);
|
|
99
|
+
expect(read).toEqual(written);
|
|
100
|
+
expect(read?.variant).toBe("store");
|
|
101
|
+
expect(read?.capacitorTarget).toBe("ios");
|
|
102
|
+
expect(read?.buildId).toBe(computeRendererFingerprint(dist).buildId);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it("returns null when no manifest is present", () => {
|
|
106
|
+
const dist = path.join(tmp, "dist");
|
|
107
|
+
makeDist(dist);
|
|
108
|
+
expect(readRendererBuildManifest(dist)).toBeNull();
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
describe("assertStagedRendererMatchesBuild", () => {
|
|
113
|
+
it("passes when the staged renderer is a faithful copy of the build", () => {
|
|
114
|
+
const dist = path.join(tmp, "dist");
|
|
115
|
+
const staged = path.join(tmp, "public");
|
|
116
|
+
makeDist(dist);
|
|
117
|
+
writeRendererBuildManifest(dist);
|
|
118
|
+
fs.cpSync(dist, staged, { recursive: true });
|
|
119
|
+
expect(() => assertStagedRendererMatchesBuild(dist, staged)).not.toThrow();
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it("fails when the fresh build has no manifest", () => {
|
|
123
|
+
const dist = path.join(tmp, "dist");
|
|
124
|
+
const staged = path.join(tmp, "public");
|
|
125
|
+
makeDist(dist);
|
|
126
|
+
fs.cpSync(dist, staged, { recursive: true });
|
|
127
|
+
expect(() => assertStagedRendererMatchesBuild(dist, staged)).toThrow(
|
|
128
|
+
/has no eliza-renderer-build\.json/,
|
|
129
|
+
);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it("fails when the staged renderer has no manifest (stale/missing copy)", () => {
|
|
133
|
+
const dist = path.join(tmp, "dist");
|
|
134
|
+
const staged = path.join(tmp, "public");
|
|
135
|
+
makeDist(dist);
|
|
136
|
+
writeRendererBuildManifest(dist);
|
|
137
|
+
makeDist(staged); // copied bundle but manifest never made it across
|
|
138
|
+
expect(() => assertStagedRendererMatchesBuild(dist, staged)).toThrow(
|
|
139
|
+
/staged renderer .* has no/,
|
|
140
|
+
);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it("fails loudly when the staged renderer is STALE (different buildId)", () => {
|
|
144
|
+
const dist = path.join(tmp, "dist");
|
|
145
|
+
const staged = path.join(tmp, "public");
|
|
146
|
+
// Stale staged renderer: built earlier from different content.
|
|
147
|
+
makeDist(staged, {
|
|
148
|
+
indexHtml: "<!doctype html><html><body>OLD UI</body></html>",
|
|
149
|
+
assets: { "index-old000.js": "old" },
|
|
150
|
+
});
|
|
151
|
+
writeRendererBuildManifest(staged);
|
|
152
|
+
// Fresh build with new content.
|
|
153
|
+
makeDist(dist, {
|
|
154
|
+
indexHtml: "<!doctype html><html><body>NEW UI</body></html>",
|
|
155
|
+
assets: { "index-new111.js": "new" },
|
|
156
|
+
});
|
|
157
|
+
writeRendererBuildManifest(dist);
|
|
158
|
+
expect(() => assertStagedRendererMatchesBuild(dist, staged)).toThrow(
|
|
159
|
+
/STALE RENDERER/,
|
|
160
|
+
);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it("fails when the manifest matches but index.html was torn during copy", () => {
|
|
164
|
+
const dist = path.join(tmp, "dist");
|
|
165
|
+
const staged = path.join(tmp, "public");
|
|
166
|
+
makeDist(dist);
|
|
167
|
+
writeRendererBuildManifest(dist);
|
|
168
|
+
fs.cpSync(dist, staged, { recursive: true });
|
|
169
|
+
// Corrupt the staged index.html after the manifest was copied.
|
|
170
|
+
fs.writeFileSync(path.join(staged, "index.html"), "partial");
|
|
171
|
+
expect(() => assertStagedRendererMatchesBuild(dist, staged)).toThrow(
|
|
172
|
+
/partial or stale copy/,
|
|
173
|
+
);
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
describe("overlayFreshRendererIntoPublic", () => {
|
|
178
|
+
it("overlays a stale public with the fresh build and preserves non-dist payloads", () => {
|
|
179
|
+
const dist = path.join(tmp, "dist");
|
|
180
|
+
const pub = path.join(tmp, "public");
|
|
181
|
+
// Fresh build.
|
|
182
|
+
makeDist(dist, {
|
|
183
|
+
indexHtml: "<!doctype html><html><body>NEW</body></html>",
|
|
184
|
+
assets: { "index-new111.js": "new" },
|
|
185
|
+
});
|
|
186
|
+
writeRendererBuildManifest(dist);
|
|
187
|
+
// Stale public with an OLD hashed asset + an on-device agent payload that
|
|
188
|
+
// lives outside dist and must survive the overlay.
|
|
189
|
+
fs.mkdirSync(path.join(pub, "assets"), { recursive: true });
|
|
190
|
+
fs.writeFileSync(path.join(pub, "index.html"), "OLD");
|
|
191
|
+
fs.writeFileSync(path.join(pub, "assets", "index-old000.js"), "old");
|
|
192
|
+
fs.mkdirSync(path.join(pub, "agent"), { recursive: true });
|
|
193
|
+
fs.writeFileSync(path.join(pub, "agent", "agent-bundle.js"), "AGENT");
|
|
194
|
+
|
|
195
|
+
overlayFreshRendererIntoPublic(dist, pub, { label: "ios" });
|
|
196
|
+
|
|
197
|
+
// Old hashed asset is gone; fresh one is present; index matches.
|
|
198
|
+
expect(fs.existsSync(path.join(pub, "assets", "index-old000.js"))).toBe(
|
|
199
|
+
false,
|
|
200
|
+
);
|
|
201
|
+
expect(fs.existsSync(path.join(pub, "assets", "index-new111.js"))).toBe(
|
|
202
|
+
true,
|
|
203
|
+
);
|
|
204
|
+
expect(fs.readFileSync(path.join(pub, "index.html"), "utf8")).toContain(
|
|
205
|
+
"NEW",
|
|
206
|
+
);
|
|
207
|
+
// The agent payload outside dist survived.
|
|
208
|
+
expect(
|
|
209
|
+
fs.readFileSync(path.join(pub, "agent", "agent-bundle.js"), "utf8"),
|
|
210
|
+
).toBe("AGENT");
|
|
211
|
+
// And it now passes the staged-matches-build assertion.
|
|
212
|
+
expect(() => assertStagedRendererMatchesBuild(dist, pub)).not.toThrow();
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it("throws when the fresh renderer is missing", () => {
|
|
216
|
+
const dist = path.join(tmp, "dist");
|
|
217
|
+
const pub = path.join(tmp, "public");
|
|
218
|
+
fs.mkdirSync(dist, { recursive: true }); // no index.html
|
|
219
|
+
expect(() => overlayFreshRendererIntoPublic(dist, pub)).toThrow(
|
|
220
|
+
/Refusing to stage a missing\/stale UI/,
|
|
221
|
+
);
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
describe("assertRendererRebuiltSince", () => {
|
|
226
|
+
it("passes when the manifest was built during this invocation", () => {
|
|
227
|
+
const dist = path.join(tmp, "dist");
|
|
228
|
+
makeDist(dist);
|
|
229
|
+
const notBefore = Date.now() - 1000;
|
|
230
|
+
writeRendererBuildManifest(dist, { variant: "base" });
|
|
231
|
+
expect(() =>
|
|
232
|
+
assertRendererRebuiltSince(dist, { notBefore, expectVariant: "base" }),
|
|
233
|
+
).not.toThrow();
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
it("fails when the manifest is older than this build (stale dist reused)", () => {
|
|
237
|
+
const dist = path.join(tmp, "dist");
|
|
238
|
+
makeDist(dist);
|
|
239
|
+
writeRendererBuildManifest(dist, {
|
|
240
|
+
builtAt: "2020-01-01T00:00:00.000Z",
|
|
241
|
+
});
|
|
242
|
+
expect(() =>
|
|
243
|
+
assertRendererRebuiltSince(dist, { notBefore: Date.now() }),
|
|
244
|
+
).toThrow(/STALE/);
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it("fails when the renderer was built for a different variant", () => {
|
|
248
|
+
const dist = path.join(tmp, "dist");
|
|
249
|
+
makeDist(dist);
|
|
250
|
+
const notBefore = Date.now() - 1000;
|
|
251
|
+
writeRendererBuildManifest(dist, { variant: "direct" });
|
|
252
|
+
expect(() =>
|
|
253
|
+
assertRendererRebuiltSince(dist, { notBefore, expectVariant: "store" }),
|
|
254
|
+
).toThrow(/wrong-variant/);
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it("fails when no manifest exists after the build", () => {
|
|
258
|
+
const dist = path.join(tmp, "dist");
|
|
259
|
+
makeDist(dist);
|
|
260
|
+
expect(() =>
|
|
261
|
+
assertRendererRebuiltSince(dist, { notBefore: Date.now() - 1000 }),
|
|
262
|
+
).toThrow(/no eliza-renderer-build\.json/);
|
|
263
|
+
});
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
describe("buildRendererManifest", () => {
|
|
267
|
+
it("defaults builtAt to an ISO timestamp and nulls unset metadata", () => {
|
|
268
|
+
const dist = path.join(tmp, "dist");
|
|
269
|
+
makeDist(dist);
|
|
270
|
+
const manifest = buildRendererManifest(dist);
|
|
271
|
+
expect(manifest.schema).toBe("elizaos.renderer.build/v1");
|
|
272
|
+
expect(Number.isFinite(Date.parse(manifest.builtAt))).toBe(true);
|
|
273
|
+
expect(manifest.commit).toBeNull();
|
|
274
|
+
expect(manifest.variant).toBeNull();
|
|
275
|
+
expect(manifest.capacitorTarget).toBeNull();
|
|
276
|
+
});
|
|
277
|
+
});
|
|
@@ -926,6 +926,7 @@ export async function stageAndroidAgentRuntime({
|
|
|
926
926
|
spikeDir,
|
|
927
927
|
cacheDir,
|
|
928
928
|
bunChannel: preferredBunChannel,
|
|
929
|
+
objective = false,
|
|
929
930
|
log = console.log,
|
|
930
931
|
} = {}) {
|
|
931
932
|
if (!androidDir)
|
|
@@ -959,7 +960,7 @@ export async function stageAndroidAgentRuntime({
|
|
|
959
960
|
let stagedCount = 0;
|
|
960
961
|
const stagedFiles = [];
|
|
961
962
|
const riscv64Artifact = {
|
|
962
|
-
required:
|
|
963
|
+
required: objective,
|
|
963
964
|
filename: RISCV64_BUN_ARTIFACT_FILENAME,
|
|
964
965
|
sha256: riscv64BunSha256(),
|
|
965
966
|
source: riscv64BunArtifactSource(),
|
|
@@ -976,10 +977,18 @@ export async function stageAndroidAgentRuntime({
|
|
|
976
977
|
if (bunArch === "riscv64") {
|
|
977
978
|
const riscvFile = riscv64BunFilePath();
|
|
978
979
|
const riscvUrl = riscv64BunUrl();
|
|
979
|
-
|
|
980
|
-
|
|
980
|
+
// riscv64 is fail-closed ONLY for objective AOSP/chip builds (they ship
|
|
981
|
+
// riscv64 and must prove it). A stock arm64 phone build skips the slice
|
|
982
|
+
// when no artifact is configured instead of aborting — upstream Bun has
|
|
983
|
+
// no riscv64-linux-musl release, so a plain `build:android` would
|
|
984
|
+
// otherwise demand a binary it never packages. ELIZA_BUN_RISCV64_OPTIONAL=1
|
|
985
|
+
// still force-skips even for objective builds (native-lib iteration). A
|
|
986
|
+
// genuine riscv64 build supplies ELIZA_BUN_RISCV64_FILE/_URL, so the slice
|
|
987
|
+
// stages either way — the skip only fires when there is no artifact at all.
|
|
988
|
+
const forceSkip = process.env.ELIZA_BUN_RISCV64_OPTIONAL === "1";
|
|
989
|
+
if (!riscvFile && !riscvUrl && (!objective || forceSkip)) {
|
|
981
990
|
tlog(
|
|
982
|
-
`Skipping
|
|
991
|
+
`Skipping ABI ${androidAbi}: no ELIZA_BUN_RISCV64_FILE or URL is set ` +
|
|
983
992
|
`(upstream Bun has no riscv64-linux-musl release). Build with ` +
|
|
984
993
|
`packages/app-core/scripts/bun-riscv64/build.sh and re-run for AOSP/chip evidence.`,
|
|
985
994
|
);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type declarations for `verify-ondevice-artifact.mjs` — post-build artifact
|
|
3
|
+
* verification gate (issue #9309).
|
|
4
|
+
*/
|
|
5
|
+
import type { RendererBuildManifest } from "./renderer-build-manifest.d.mts";
|
|
6
|
+
|
|
7
|
+
export function verifyStagedArtifact(opts: {
|
|
8
|
+
rendererDir: string;
|
|
9
|
+
freshDistDir?: string | null;
|
|
10
|
+
requiredFiles?: string[];
|
|
11
|
+
label?: string;
|
|
12
|
+
}): {
|
|
13
|
+
ok: boolean;
|
|
14
|
+
problems: string[];
|
|
15
|
+
manifest: RendererBuildManifest | null;
|
|
16
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build-verification for an on-device artifact (issue #9309).
|
|
3
|
+
*
|
|
4
|
+
* Asserts a staged platform artifact contains (a) the freshly built renderer
|
|
5
|
+
* (via the build stamp), and (b) every required companion file — the agent
|
|
6
|
+
* bundle and the platform's native lib set. Used as a post-build CI gate so a
|
|
7
|
+
* device artifact that is missing the latest renderer, the agent bundle, or a
|
|
8
|
+
* native lib FAILS loudly instead of shipping a half-staged build.
|
|
9
|
+
*/
|
|
10
|
+
import fs from "node:fs";
|
|
11
|
+
import path from "node:path";
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
assertStagedRendererMatchesBuild,
|
|
15
|
+
readRendererBuildManifest,
|
|
16
|
+
} from "./renderer-build-manifest.mjs";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @param {{
|
|
20
|
+
* rendererDir: string, // the staged web root (holds index.html + the build stamp)
|
|
21
|
+
* freshDistDir?: string|null, // the just-built dist to match against (optional)
|
|
22
|
+
* requiredFiles?: string[], // companion files (agent bundle, native libs); relative→rendererDir
|
|
23
|
+
* label?: string,
|
|
24
|
+
* }} opts
|
|
25
|
+
* @returns {{ ok: boolean, problems: string[], manifest: object|null }}
|
|
26
|
+
*/
|
|
27
|
+
export function verifyStagedArtifact({
|
|
28
|
+
rendererDir,
|
|
29
|
+
freshDistDir = null,
|
|
30
|
+
requiredFiles = [],
|
|
31
|
+
label = "artifact",
|
|
32
|
+
}) {
|
|
33
|
+
const problems = [];
|
|
34
|
+
let manifest = null;
|
|
35
|
+
|
|
36
|
+
if (freshDistDir) {
|
|
37
|
+
try {
|
|
38
|
+
manifest = assertStagedRendererMatchesBuild(freshDistDir, rendererDir, {
|
|
39
|
+
label,
|
|
40
|
+
});
|
|
41
|
+
} catch (error) {
|
|
42
|
+
problems.push(error instanceof Error ? error.message : String(error));
|
|
43
|
+
}
|
|
44
|
+
} else {
|
|
45
|
+
manifest = readRendererBuildManifest(rendererDir);
|
|
46
|
+
if (!manifest) {
|
|
47
|
+
problems.push(
|
|
48
|
+
`${label}: no renderer build stamp in ${rendererDir} — unverifiable renderer.`,
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
for (const file of requiredFiles) {
|
|
54
|
+
const abs = path.isAbsolute(file) ? file : path.join(rendererDir, file);
|
|
55
|
+
if (!fs.existsSync(abs)) {
|
|
56
|
+
problems.push(`${label}: missing required artifact file ${file}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return { ok: problems.length === 0, problems, manifest };
|
|
61
|
+
}
|