@beeos-ai/device-agent 0.4.0
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/.pi/extensions/android-tools.ts +139 -0
- package/.pi/skills/.gitkeep +0 -0
- package/.pi/skills/ground-mobile-fallback/SKILL.md +50 -0
- package/LICENSE +201 -0
- package/dist/action/handler.d.ts +9 -0
- package/dist/action/handler.js +124 -0
- package/dist/action/handler.js.map +1 -0
- package/dist/adapters/agent-gateway-client.d.ts +256 -0
- package/dist/adapters/agent-gateway-client.js +332 -0
- package/dist/adapters/agent-gateway-client.js.map +1 -0
- package/dist/adapters/agentcore-inner-loop.d.ts +29 -0
- package/dist/adapters/agentcore-inner-loop.js +72 -0
- package/dist/adapters/agentcore-inner-loop.js.map +1 -0
- package/dist/adapters/agentcore-pi.d.ts +215 -0
- package/dist/adapters/agentcore-pi.js +464 -0
- package/dist/adapters/agentcore-pi.js.map +1 -0
- package/dist/adapters/agentcore-runner.d.ts +70 -0
- package/dist/adapters/agentcore-runner.js +255 -0
- package/dist/adapters/agentcore-runner.js.map +1 -0
- package/dist/adapters/device-android-adb.d.ts +41 -0
- package/dist/adapters/device-android-adb.js +279 -0
- package/dist/adapters/device-android-adb.js.map +1 -0
- package/dist/adapters/device-desktop.d.ts +37 -0
- package/dist/adapters/device-desktop.js +101 -0
- package/dist/adapters/device-desktop.js.map +1 -0
- package/dist/adapters/identity-file.d.ts +30 -0
- package/dist/adapters/identity-file.js +50 -0
- package/dist/adapters/identity-file.js.map +1 -0
- package/dist/adapters/transport-bridge-ws.d.ts +39 -0
- package/dist/adapters/transport-bridge-ws.js +49 -0
- package/dist/adapters/transport-bridge-ws.js.map +1 -0
- package/dist/adapters/vlm-inner-loop.d.ts +88 -0
- package/dist/adapters/vlm-inner-loop.js +199 -0
- package/dist/adapters/vlm-inner-loop.js.map +1 -0
- package/dist/agent-runtime/adapters/base.d.ts +145 -0
- package/dist/agent-runtime/adapters/base.js +29 -0
- package/dist/agent-runtime/adapters/base.js.map +1 -0
- package/dist/agent-runtime/adapters/index.d.ts +17 -0
- package/dist/agent-runtime/adapters/index.js +17 -0
- package/dist/agent-runtime/adapters/index.js.map +1 -0
- package/dist/agent-runtime/adapters/mock.d.ts +44 -0
- package/dist/agent-runtime/adapters/mock.js +54 -0
- package/dist/agent-runtime/adapters/mock.js.map +1 -0
- package/dist/agent-runtime/adapters/openai.d.ts +63 -0
- package/dist/agent-runtime/adapters/openai.js +302 -0
- package/dist/agent-runtime/adapters/openai.js.map +1 -0
- package/dist/agent-runtime/adapters/registry.d.ts +49 -0
- package/dist/agent-runtime/adapters/registry.js +129 -0
- package/dist/agent-runtime/adapters/registry.js.map +1 -0
- package/dist/agent-runtime/adapters/ui-tars.d.ts +13 -0
- package/dist/agent-runtime/adapters/ui-tars.js +22 -0
- package/dist/agent-runtime/adapters/ui-tars.js.map +1 -0
- package/dist/agent-runtime/agent-runner.d.ts +149 -0
- package/dist/agent-runtime/agent-runner.js +356 -0
- package/dist/agent-runtime/agent-runner.js.map +1 -0
- package/dist/agent-runtime/core.d.ts +170 -0
- package/dist/agent-runtime/core.js +624 -0
- package/dist/agent-runtime/core.js.map +1 -0
- package/dist/agent-runtime/index.d.ts +16 -0
- package/dist/agent-runtime/index.js +17 -0
- package/dist/agent-runtime/index.js.map +1 -0
- package/dist/agent-runtime/plan-executor.d.ts +118 -0
- package/dist/agent-runtime/plan-executor.js +501 -0
- package/dist/agent-runtime/plan-executor.js.map +1 -0
- package/dist/agent-runtime/planner/base.d.ts +128 -0
- package/dist/agent-runtime/planner/base.js +157 -0
- package/dist/agent-runtime/planner/base.js.map +1 -0
- package/dist/agent-runtime/planner/llm-planner.d.ts +58 -0
- package/dist/agent-runtime/planner/llm-planner.js +112 -0
- package/dist/agent-runtime/planner/llm-planner.js.map +1 -0
- package/dist/agent-runtime/planner/noop-planner.d.ts +13 -0
- package/dist/agent-runtime/planner/noop-planner.js +30 -0
- package/dist/agent-runtime/planner/noop-planner.js.map +1 -0
- package/dist/agent-runtime/planner/prompts.d.ts +33 -0
- package/dist/agent-runtime/planner/prompts.js +165 -0
- package/dist/agent-runtime/planner/prompts.js.map +1 -0
- package/dist/agent-runtime/reflection.d.ts +81 -0
- package/dist/agent-runtime/reflection.js +189 -0
- package/dist/agent-runtime/reflection.js.map +1 -0
- package/dist/agent-runtime/reflectors/action-reflector.d.ts +97 -0
- package/dist/agent-runtime/reflectors/action-reflector.js +233 -0
- package/dist/agent-runtime/reflectors/action-reflector.js.map +1 -0
- package/dist/agent-runtime/reflectors/policy.d.ts +53 -0
- package/dist/agent-runtime/reflectors/policy.js +106 -0
- package/dist/agent-runtime/reflectors/policy.js.map +1 -0
- package/dist/agent-runtime/reflectors/trajectory-reflector.d.ts +94 -0
- package/dist/agent-runtime/reflectors/trajectory-reflector.js +223 -0
- package/dist/agent-runtime/reflectors/trajectory-reflector.js.map +1 -0
- package/dist/agent-runtime/skills/embedding.d.ts +53 -0
- package/dist/agent-runtime/skills/embedding.js +134 -0
- package/dist/agent-runtime/skills/embedding.js.map +1 -0
- package/dist/agent-runtime/skills/fs-store.d.ts +88 -0
- package/dist/agent-runtime/skills/fs-store.js +483 -0
- package/dist/agent-runtime/skills/fs-store.js.map +1 -0
- package/dist/agent-runtime/skills/index.d.ts +10 -0
- package/dist/agent-runtime/skills/index.js +10 -0
- package/dist/agent-runtime/skills/index.js.map +1 -0
- package/dist/agent-runtime/skills/matcher-evolver.d.ts +88 -0
- package/dist/agent-runtime/skills/matcher-evolver.js +110 -0
- package/dist/agent-runtime/skills/matcher-evolver.js.map +1 -0
- package/dist/agent-runtime/skills/memory-store.d.ts +23 -0
- package/dist/agent-runtime/skills/memory-store.js +94 -0
- package/dist/agent-runtime/skills/memory-store.js.map +1 -0
- package/dist/agent-runtime/skills/normalise.d.ts +40 -0
- package/dist/agent-runtime/skills/normalise.js +152 -0
- package/dist/agent-runtime/skills/normalise.js.map +1 -0
- package/dist/agent-runtime/skills/replay.d.ts +46 -0
- package/dist/agent-runtime/skills/replay.js +106 -0
- package/dist/agent-runtime/skills/replay.js.map +1 -0
- package/dist/agent-runtime/skills/sqlite-store.d.ts +60 -0
- package/dist/agent-runtime/skills/sqlite-store.js +234 -0
- package/dist/agent-runtime/skills/sqlite-store.js.map +1 -0
- package/dist/agent-runtime/skills/types.d.ts +99 -0
- package/dist/agent-runtime/skills/types.js +14 -0
- package/dist/agent-runtime/skills/types.js.map +1 -0
- package/dist/agent.d.ts +110 -0
- package/dist/agent.js +162 -0
- package/dist/agent.js.map +1 -0
- package/dist/api.d.ts +27 -0
- package/dist/api.js +48 -0
- package/dist/api.js.map +1 -0
- package/dist/app/agent-host.d.ts +59 -0
- package/dist/app/agent-host.js +263 -0
- package/dist/app/agent-host.js.map +1 -0
- package/dist/app/device-manager.d.ts +28 -0
- package/dist/app/device-manager.js +64 -0
- package/dist/app/device-manager.js.map +1 -0
- package/dist/app/message-store.d.ts +55 -0
- package/dist/app/message-store.js +99 -0
- package/dist/app/message-store.js.map +1 -0
- package/dist/app/session-manager.d.ts +132 -0
- package/dist/app/session-manager.js +350 -0
- package/dist/app/session-manager.js.map +1 -0
- package/dist/bootstrap.d.ts +94 -0
- package/dist/bootstrap.js +459 -0
- package/dist/bootstrap.js.map +1 -0
- package/dist/chat.d.ts +16 -0
- package/dist/chat.js +175 -0
- package/dist/chat.js.map +1 -0
- package/dist/config/loader.d.ts +77 -0
- package/dist/config/loader.js +292 -0
- package/dist/config/loader.js.map +1 -0
- package/dist/config/schema.d.ts +162 -0
- package/dist/config/schema.js +243 -0
- package/dist/config/schema.js.map +1 -0
- package/dist/core/actions.d.ts +60 -0
- package/dist/core/actions.js +120 -0
- package/dist/core/actions.js.map +1 -0
- package/dist/core/errors.d.ts +8 -0
- package/dist/core/errors.js +9 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/screen.d.ts +7 -0
- package/dist/core/screen.js +8 -0
- package/dist/core/screen.js.map +1 -0
- package/dist/core/session.d.ts +42 -0
- package/dist/core/session.js +29 -0
- package/dist/core/session.js.map +1 -0
- package/dist/debug/dump-system-prompt.d.ts +1 -0
- package/dist/debug/dump-system-prompt.js +34 -0
- package/dist/debug/dump-system-prompt.js.map +1 -0
- package/dist/device/android.d.ts +25 -0
- package/dist/device/android.js +231 -0
- package/dist/device/android.js.map +1 -0
- package/dist/device/apps.d.ts +3 -0
- package/dist/device/apps.js +77 -0
- package/dist/device/apps.js.map +1 -0
- package/dist/device/base.d.ts +20 -0
- package/dist/device/base.js +2 -0
- package/dist/device/base.js.map +1 -0
- package/dist/device/ui-xml.d.ts +29 -0
- package/dist/device/ui-xml.js +194 -0
- package/dist/device/ui-xml.js.map +1 -0
- package/dist/fleet/adapters/process/node-process-manager.d.ts +157 -0
- package/dist/fleet/adapters/process/node-process-manager.js +664 -0
- package/dist/fleet/adapters/process/node-process-manager.js.map +1 -0
- package/dist/fleet/adapters/sources/local-adb.d.ts +105 -0
- package/dist/fleet/adapters/sources/local-adb.js +353 -0
- package/dist/fleet/adapters/sources/local-adb.js.map +1 -0
- package/dist/fleet/adapters/sources/remote-adb.d.ts +97 -0
- package/dist/fleet/adapters/sources/remote-adb.js +138 -0
- package/dist/fleet/adapters/sources/remote-adb.js.map +1 -0
- package/dist/fleet/adapters/state/beeos-cli-store.d.ts +72 -0
- package/dist/fleet/adapters/state/beeos-cli-store.js +250 -0
- package/dist/fleet/adapters/state/beeos-cli-store.js.map +1 -0
- package/dist/fleet/adapters/state/port-allocator.d.ts +43 -0
- package/dist/fleet/adapters/state/port-allocator.js +173 -0
- package/dist/fleet/adapters/state/port-allocator.js.map +1 -0
- package/dist/fleet/domain/device.d.ts +138 -0
- package/dist/fleet/domain/device.js +49 -0
- package/dist/fleet/domain/device.js.map +1 -0
- package/dist/fleet/domain/events.d.ts +102 -0
- package/dist/fleet/domain/events.js +15 -0
- package/dist/fleet/domain/events.js.map +1 -0
- package/dist/fleet/domain/fsm.d.ts +122 -0
- package/dist/fleet/domain/fsm.js +475 -0
- package/dist/fleet/domain/fsm.js.map +1 -0
- package/dist/fleet/http/status-client.d.ts +31 -0
- package/dist/fleet/http/status-client.js +115 -0
- package/dist/fleet/http/status-client.js.map +1 -0
- package/dist/fleet/http/status-server.d.ts +64 -0
- package/dist/fleet/http/status-server.js +200 -0
- package/dist/fleet/http/status-server.js.map +1 -0
- package/dist/fleet/index.d.ts +27 -0
- package/dist/fleet/index.js +17 -0
- package/dist/fleet/index.js.map +1 -0
- package/dist/fleet/installer/index.d.ts +12 -0
- package/dist/fleet/installer/index.js +13 -0
- package/dist/fleet/installer/index.js.map +1 -0
- package/dist/fleet/installer/launchd-mac.d.ts +73 -0
- package/dist/fleet/installer/launchd-mac.js +291 -0
- package/dist/fleet/installer/launchd-mac.js.map +1 -0
- package/dist/fleet/installer/service-spec.d.ts +70 -0
- package/dist/fleet/installer/service-spec.js +43 -0
- package/dist/fleet/installer/service-spec.js.map +1 -0
- package/dist/fleet/installer/spawn-env.d.ts +45 -0
- package/dist/fleet/installer/spawn-env.js +224 -0
- package/dist/fleet/installer/spawn-env.js.map +1 -0
- package/dist/fleet/ports/clock.d.ts +19 -0
- package/dist/fleet/ports/clock.js +26 -0
- package/dist/fleet/ports/clock.js.map +1 -0
- package/dist/fleet/ports/device-source.d.ts +28 -0
- package/dist/fleet/ports/device-source.js +18 -0
- package/dist/fleet/ports/device-source.js.map +1 -0
- package/dist/fleet/ports/process-manager.d.ts +110 -0
- package/dist/fleet/ports/process-manager.js +27 -0
- package/dist/fleet/ports/process-manager.js.map +1 -0
- package/dist/fleet/ports/state-registry.d.ts +49 -0
- package/dist/fleet/ports/state-registry.js +12 -0
- package/dist/fleet/ports/state-registry.js.map +1 -0
- package/dist/fleet/supervisor.d.ts +107 -0
- package/dist/fleet/supervisor.js +508 -0
- package/dist/fleet/supervisor.js.map +1 -0
- package/dist/grounding/grounder.d.ts +46 -0
- package/dist/grounding/grounder.js +82 -0
- package/dist/grounding/grounder.js.map +1 -0
- package/dist/inbound/acp/handlers/device.d.ts +43 -0
- package/dist/inbound/acp/handlers/device.js +153 -0
- package/dist/inbound/acp/handlers/device.js.map +1 -0
- package/dist/inbound/acp/handlers/initialize.d.ts +71 -0
- package/dist/inbound/acp/handlers/initialize.js +53 -0
- package/dist/inbound/acp/handlers/initialize.js.map +1 -0
- package/dist/inbound/acp/handlers/models.d.ts +16 -0
- package/dist/inbound/acp/handlers/models.js +23 -0
- package/dist/inbound/acp/handlers/models.js.map +1 -0
- package/dist/inbound/acp/handlers/session.d.ts +51 -0
- package/dist/inbound/acp/handlers/session.js +61 -0
- package/dist/inbound/acp/handlers/session.js.map +1 -0
- package/dist/inbound/acp/handlers/skills.d.ts +8 -0
- package/dist/inbound/acp/handlers/skills.js +9 -0
- package/dist/inbound/acp/handlers/skills.js.map +1 -0
- package/dist/inbound/acp/notifier.d.ts +35 -0
- package/dist/inbound/acp/notifier.js +45 -0
- package/dist/inbound/acp/notifier.js.map +1 -0
- package/dist/inbound/acp/router.d.ts +67 -0
- package/dist/inbound/acp/router.js +179 -0
- package/dist/inbound/acp/router.js.map +1 -0
- package/dist/inbound/cli/fleet-command.d.ts +34 -0
- package/dist/inbound/cli/fleet-command.js +792 -0
- package/dist/inbound/cli/fleet-command.js.map +1 -0
- package/dist/inbound/cli/main.d.ts +38 -0
- package/dist/inbound/cli/main.js +364 -0
- package/dist/inbound/cli/main.js.map +1 -0
- package/dist/inbound/cli/run-command.d.ts +101 -0
- package/dist/inbound/cli/run-command.js +405 -0
- package/dist/inbound/cli/run-command.js.map +1 -0
- package/dist/inbound/cli/skills-command.d.ts +39 -0
- package/dist/inbound/cli/skills-command.js +333 -0
- package/dist/inbound/cli/skills-command.js.map +1 -0
- package/dist/inbound/http/server.d.ts +7 -0
- package/dist/inbound/http/server.js +8 -0
- package/dist/inbound/http/server.js.map +1 -0
- package/dist/index.d.ts +66 -0
- package/dist/index.js +48 -0
- package/dist/index.js.map +1 -0
- package/dist/logger/index.d.ts +37 -0
- package/dist/logger/index.js +84 -0
- package/dist/logger/index.js.map +1 -0
- package/dist/mcp-client/autospawn.d.ts +67 -0
- package/dist/mcp-client/autospawn.js +173 -0
- package/dist/mcp-client/autospawn.js.map +1 -0
- package/dist/mcp-client/client.d.ts +78 -0
- package/dist/mcp-client/client.js +168 -0
- package/dist/mcp-client/client.js.map +1 -0
- package/dist/mcp-client/index.d.ts +7 -0
- package/dist/mcp-client/index.js +4 -0
- package/dist/mcp-client/index.js.map +1 -0
- package/dist/mcp-client/inproc.d.ts +47 -0
- package/dist/mcp-client/inproc.js +229 -0
- package/dist/mcp-client/inproc.js.map +1 -0
- package/dist/mcp-client/runtime.d.ts +34 -0
- package/dist/mcp-client/runtime.js +15 -0
- package/dist/mcp-client/runtime.js.map +1 -0
- package/dist/model/adapters/autoglm.d.ts +31 -0
- package/dist/model/adapters/autoglm.js +409 -0
- package/dist/model/adapters/autoglm.js.map +1 -0
- package/dist/model/adapters/base.d.ts +13 -0
- package/dist/model/adapters/base.js +27 -0
- package/dist/model/adapters/base.js.map +1 -0
- package/dist/model/adapters/guiowl.d.ts +1 -0
- package/dist/model/adapters/guiowl.js +253 -0
- package/dist/model/adapters/guiowl.js.map +1 -0
- package/dist/model/adapters/index.d.ts +6 -0
- package/dist/model/adapters/index.js +8 -0
- package/dist/model/adapters/index.js.map +1 -0
- package/dist/model/adapters/maiui.d.ts +1 -0
- package/dist/model/adapters/maiui.js +117 -0
- package/dist/model/adapters/maiui.js.map +1 -0
- package/dist/model/adapters/qwenvl.d.ts +1 -0
- package/dist/model/adapters/qwenvl.js +108 -0
- package/dist/model/adapters/qwenvl.js.map +1 -0
- package/dist/model/adapters/showui.d.ts +1 -0
- package/dist/model/adapters/showui.js +132 -0
- package/dist/model/adapters/showui.js.map +1 -0
- package/dist/model/adapters/tool-call.d.ts +6 -0
- package/dist/model/adapters/tool-call.js +102 -0
- package/dist/model/adapters/tool-call.js.map +1 -0
- package/dist/model/adapters/uitars.d.ts +1 -0
- package/dist/model/adapters/uitars.js +180 -0
- package/dist/model/adapters/uitars.js.map +1 -0
- package/dist/model/base.d.ts +32 -0
- package/dist/model/base.js +2 -0
- package/dist/model/base.js.map +1 -0
- package/dist/model/client.d.ts +26 -0
- package/dist/model/client.js +300 -0
- package/dist/model/client.js.map +1 -0
- package/dist/model/image/compress.d.ts +53 -0
- package/dist/model/image/compress.js +89 -0
- package/dist/model/image/compress.js.map +1 -0
- package/dist/model/image/transport.d.ts +134 -0
- package/dist/model/image/transport.js +154 -0
- package/dist/model/image/transport.js.map +1 -0
- package/dist/orchestrator-system-prompt.d.ts +6 -0
- package/dist/orchestrator-system-prompt.js +81 -0
- package/dist/orchestrator-system-prompt.js.map +1 -0
- package/dist/pi-context.d.ts +82 -0
- package/dist/pi-context.js +62 -0
- package/dist/pi-context.js.map +1 -0
- package/dist/pi-local-skills.d.ts +17 -0
- package/dist/pi-local-skills.js +24 -0
- package/dist/pi-local-skills.js.map +1 -0
- package/dist/plugins/acp/index.d.ts +46 -0
- package/dist/plugins/acp/index.js +111 -0
- package/dist/plugins/acp/index.js.map +1 -0
- package/dist/plugins/agent-sync/index.d.ts +40 -0
- package/dist/plugins/agent-sync/index.js +98 -0
- package/dist/plugins/agent-sync/index.js.map +1 -0
- package/dist/plugins/http-debug/index.d.ts +30 -0
- package/dist/plugins/http-debug/index.js +64 -0
- package/dist/plugins/http-debug/index.js.map +1 -0
- package/dist/plugins/http-debug/server.d.ts +92 -0
- package/dist/plugins/http-debug/server.js +464 -0
- package/dist/plugins/http-debug/server.js.map +1 -0
- package/dist/ports/agent-core.d.ts +158 -0
- package/dist/ports/agent-core.js +2 -0
- package/dist/ports/agent-core.js.map +1 -0
- package/dist/ports/agent-host.d.ts +125 -0
- package/dist/ports/agent-host.js +23 -0
- package/dist/ports/agent-host.js.map +1 -0
- package/dist/ports/device.d.ts +53 -0
- package/dist/ports/device.js +13 -0
- package/dist/ports/device.js.map +1 -0
- package/dist/ports/identity.d.ts +22 -0
- package/dist/ports/identity.js +9 -0
- package/dist/ports/identity.js.map +1 -0
- package/dist/ports/plugin.d.ts +62 -0
- package/dist/ports/plugin.js +35 -0
- package/dist/ports/plugin.js.map +1 -0
- package/dist/ports/transport.d.ts +32 -0
- package/dist/ports/transport.js +12 -0
- package/dist/ports/transport.js.map +1 -0
- package/dist/ports/vlm.d.ts +59 -0
- package/dist/ports/vlm.js +14 -0
- package/dist/ports/vlm.js.map +1 -0
- package/dist/test-lan-autoglm.d.ts +7 -0
- package/dist/test-lan-autoglm.js +46 -0
- package/dist/test-lan-autoglm.js.map +1 -0
- package/dist/test-mem-breakdown.d.ts +1 -0
- package/dist/test-mem-breakdown.js +103 -0
- package/dist/test-mem-breakdown.js.map +1 -0
- package/dist/test-mem.d.ts +1 -0
- package/dist/test-mem.js +60 -0
- package/dist/test-mem.js.map +1 -0
- package/dist/test-mobile-use.d.ts +1 -0
- package/dist/test-mobile-use.js +18 -0
- package/dist/test-mobile-use.js.map +1 -0
- package/dist/test-screenshot-loop.d.ts +1 -0
- package/dist/test-screenshot-loop.js +43 -0
- package/dist/test-screenshot-loop.js.map +1 -0
- package/dist/test-screenshot.d.ts +1 -0
- package/dist/test-screenshot.js +10 -0
- package/dist/test-screenshot.js.map +1 -0
- package/dist/test-ui-xml-dump.d.ts +15 -0
- package/dist/test-ui-xml-dump.js +49 -0
- package/dist/test-ui-xml-dump.js.map +1 -0
- package/dist/types.d.ts +69 -0
- package/dist/types.js +23 -0
- package/dist/types.js.map +1 -0
- package/package.json +94 -0
|
@@ -0,0 +1,664 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adapter — `IProcessManager` implementation backed by `child_process.spawn`.
|
|
3
|
+
*
|
|
4
|
+
* Spawns and supervises up to three long-running siblings per bound device:
|
|
5
|
+
*
|
|
6
|
+
* - **mcp** `device-mcp-server --backend <kind> --adb-serial <serial>
|
|
7
|
+
* --host 127.0.0.1 --port <mcpPort>`
|
|
8
|
+
* - **daemon** `device-agent --key <keyFile> --agent-gateway-url <url>
|
|
9
|
+
* --mcp-url http://127.0.0.1:<mcpPort> --adb <serial>
|
|
10
|
+
* [--http --http-port <httpPort>]`
|
|
11
|
+
* - **scrcpy** `scrcpy-bridge` (no args; configured via env mirroring
|
|
12
|
+
* `web/packages/core/src/runtime/scrcpy-bridge.ts` `buildEnv`).
|
|
13
|
+
* Optional — only spawned when the binding's `videoMode === "scrcpy"`.
|
|
14
|
+
*
|
|
15
|
+
* Lifecycle responsibilities:
|
|
16
|
+
*
|
|
17
|
+
* - `spawnMcp` waits for `GET /healthz` to return 200 (configurable timeout)
|
|
18
|
+
* before emitting `mcp_started`. If readiness times out the spawn is
|
|
19
|
+
* killed and an `mcp_exited` event is reported (so the FSM treats the
|
|
20
|
+
* attempt as a crash and either retries or backs off).
|
|
21
|
+
* - `spawnDaemon` emits `daemon_started` as soon as the child has a PID
|
|
22
|
+
* (the daemon's own readiness is observable through ACP traffic which is
|
|
23
|
+
* out of fleet scope; the daemon process itself dying is what we react to
|
|
24
|
+
* here). Optional `daemonHttpPort` is probed similarly when set.
|
|
25
|
+
* - `spawnScrcpy` is best-effort — a missing binary yields a synthetic
|
|
26
|
+
* `scrcpy_exited` event (with `reason`) instead of a thrown error.
|
|
27
|
+
* scrcpy-bridge has no `/healthz`, so readiness is degraded to
|
|
28
|
+
* "process still alive `scrcpyStartupGraceMs` after spawn"; before that
|
|
29
|
+
* window elapses we don't emit `scrcpy_started`. If the child exits
|
|
30
|
+
* within the grace, we emit `scrcpy_exited` instead.
|
|
31
|
+
* - `kill*` sends SIGTERM, then SIGKILL after `shutdownGraceMs`. When the
|
|
32
|
+
* supervisor initiates the kill we mark the resulting exit as `expected`
|
|
33
|
+
* so the pure FSM (`fsm.ts`) doesn't penalise restartCount.
|
|
34
|
+
* - When a child dies on its own (`expected=false`), we emit the matching
|
|
35
|
+
* `*_exited` event and let the supervisor (driving the FSM) decide whether
|
|
36
|
+
* to respawn. We never auto-restart from inside this adapter — the FSM
|
|
37
|
+
* owns the policy.
|
|
38
|
+
*
|
|
39
|
+
* Binary discovery:
|
|
40
|
+
* - mcp: `BEEOS_DEVICE_MCP_BIN` → `device-mcp-server` on PATH (override via opts.mcpBin).
|
|
41
|
+
* - daemon: `BEEOS_DEVICE_AGENT_BIN` → `device-agent` on PATH (override via opts.daemonBin).
|
|
42
|
+
* - scrcpy: `BEEOS_SCRCPY_BRIDGE_BIN` env → `~/.beeos/bin/scrcpy-bridge[.exe]` (managed install).
|
|
43
|
+
* Override via `opts.scrcpyBin` (absolute path) or `opts.findScrcpyBinFn` (test hook).
|
|
44
|
+
*
|
|
45
|
+
* The implementation deliberately exposes a hookable `spawnFn` /
|
|
46
|
+
* `httpProbeFn` / `findScrcpyBinFn` so unit tests can drive the manager with
|
|
47
|
+
* fake children and synthetic /healthz responses without touching the OS.
|
|
48
|
+
*/
|
|
49
|
+
import { spawn as nodeSpawn } from "node:child_process";
|
|
50
|
+
import * as fs from "node:fs";
|
|
51
|
+
import * as http from "node:http";
|
|
52
|
+
import * as os from "node:os";
|
|
53
|
+
import * as path from "node:path";
|
|
54
|
+
const noopLogger = {
|
|
55
|
+
debug: () => { },
|
|
56
|
+
info: () => { },
|
|
57
|
+
warn: () => { },
|
|
58
|
+
error: () => { },
|
|
59
|
+
};
|
|
60
|
+
// ── NodeProcessManager ──────────────────────────────────────────────────────
|
|
61
|
+
export class NodeProcessManager {
|
|
62
|
+
mcpBin;
|
|
63
|
+
daemonBin;
|
|
64
|
+
scrcpyBinOverride;
|
|
65
|
+
mcpBackend;
|
|
66
|
+
mcpHost;
|
|
67
|
+
agentGatewayUrl;
|
|
68
|
+
bridgeUrl;
|
|
69
|
+
region;
|
|
70
|
+
mcpHealthTimeoutMs;
|
|
71
|
+
scrcpyStartupGraceMs;
|
|
72
|
+
shutdownGraceMs;
|
|
73
|
+
spawnFn;
|
|
74
|
+
httpProbeFn;
|
|
75
|
+
findScrcpyBinFn;
|
|
76
|
+
logger;
|
|
77
|
+
extraEnv;
|
|
78
|
+
slots = new Map();
|
|
79
|
+
listeners = new Set();
|
|
80
|
+
started = false;
|
|
81
|
+
constructor(opts = {}) {
|
|
82
|
+
this.mcpBin =
|
|
83
|
+
opts.mcpBin ?? process.env.BEEOS_DEVICE_MCP_BIN ?? "device-mcp-server";
|
|
84
|
+
this.daemonBin =
|
|
85
|
+
opts.daemonBin ?? process.env.BEEOS_DEVICE_AGENT_BIN ?? "device-agent";
|
|
86
|
+
this.scrcpyBinOverride = opts.scrcpyBin;
|
|
87
|
+
this.mcpBackend = opts.mcpBackend ?? process.env.BEEOS_MCP_BACKEND ?? "android";
|
|
88
|
+
this.mcpHost = opts.mcpHost ?? "127.0.0.1";
|
|
89
|
+
this.agentGatewayUrl = opts.agentGatewayUrl ?? process.env.AGENT_GATEWAY_URL;
|
|
90
|
+
this.bridgeUrl = opts.bridgeUrl ?? process.env.BRIDGE_URL;
|
|
91
|
+
this.region = opts.region ?? process.env.BEEOS_REGION;
|
|
92
|
+
this.mcpHealthTimeoutMs = opts.mcpHealthTimeoutMs ?? 10_000;
|
|
93
|
+
this.scrcpyStartupGraceMs = opts.scrcpyStartupGraceMs ?? 5_000;
|
|
94
|
+
this.shutdownGraceMs = opts.shutdownGraceMs ?? 5_000;
|
|
95
|
+
this.spawnFn = opts.spawnFn ?? defaultSpawnFn;
|
|
96
|
+
this.httpProbeFn = opts.httpProbeFn ?? defaultHttpProbeFn;
|
|
97
|
+
this.findScrcpyBinFn = opts.findScrcpyBinFn ?? (() => defaultFindScrcpyBin(this.scrcpyBinOverride));
|
|
98
|
+
this.logger = opts.logger ?? noopLogger;
|
|
99
|
+
this.extraEnv = opts.extraEnv ?? {};
|
|
100
|
+
}
|
|
101
|
+
async start() {
|
|
102
|
+
this.started = true;
|
|
103
|
+
}
|
|
104
|
+
async stop() {
|
|
105
|
+
this.started = false;
|
|
106
|
+
const serials = Array.from(this.slots.keys());
|
|
107
|
+
await Promise.all(serials.flatMap((s) => [
|
|
108
|
+
this.killMcp(s, { expected: true }),
|
|
109
|
+
this.killDaemon(s, { expected: true }),
|
|
110
|
+
this.killScrcpy(s, { expected: true }),
|
|
111
|
+
]));
|
|
112
|
+
}
|
|
113
|
+
on(listener) {
|
|
114
|
+
this.listeners.add(listener);
|
|
115
|
+
return () => this.listeners.delete(listener);
|
|
116
|
+
}
|
|
117
|
+
isAlive(serial, which) {
|
|
118
|
+
const slot = this.slots.get(serial);
|
|
119
|
+
if (!slot)
|
|
120
|
+
return false;
|
|
121
|
+
return Boolean(this.slotFor(slot, which));
|
|
122
|
+
}
|
|
123
|
+
pid(serial, which) {
|
|
124
|
+
const slot = this.slots.get(serial);
|
|
125
|
+
if (!slot)
|
|
126
|
+
return null;
|
|
127
|
+
const childSlot = this.slotFor(slot, which);
|
|
128
|
+
return childSlot?.child.pid ?? null;
|
|
129
|
+
}
|
|
130
|
+
slotFor(slot, which) {
|
|
131
|
+
if (which === "mcp")
|
|
132
|
+
return slot.mcp;
|
|
133
|
+
if (which === "daemon")
|
|
134
|
+
return slot.daemon;
|
|
135
|
+
return slot.scrcpy;
|
|
136
|
+
}
|
|
137
|
+
setSlot(slot, which, value) {
|
|
138
|
+
if (which === "mcp")
|
|
139
|
+
slot.mcp = value;
|
|
140
|
+
else if (which === "daemon")
|
|
141
|
+
slot.daemon = value;
|
|
142
|
+
else
|
|
143
|
+
slot.scrcpy = value;
|
|
144
|
+
}
|
|
145
|
+
// ── Spawn paths ──────────────────────────────────────────────────────────
|
|
146
|
+
async spawnMcp(opts) {
|
|
147
|
+
const slot = this.ensureSlot(opts.serial);
|
|
148
|
+
if (slot.mcp) {
|
|
149
|
+
this.logger.warn("spawnMcp called while mcp already alive — ignoring", {
|
|
150
|
+
serial: opts.serial,
|
|
151
|
+
pid: slot.mcp.child.pid,
|
|
152
|
+
});
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
// Per-device backend takes precedence over the constructor-time
|
|
156
|
+
// default. `devices.json#backend` flows here via `SpawnOptions`,
|
|
157
|
+
// so once self-attach lands the same code path serves ADB,
|
|
158
|
+
// macOS / Linux / Windows hosts.
|
|
159
|
+
const backend = opts.backend ?? this.mcpBackend;
|
|
160
|
+
const args = ["--backend", backend];
|
|
161
|
+
// `--adb-serial` is only meaningful for the ADB backend. Other
|
|
162
|
+
// backends rely on the host process (one device-mcp-server per
|
|
163
|
+
// host), so passing the serial there would be confusing.
|
|
164
|
+
if (isAdbBackend(backend)) {
|
|
165
|
+
args.push("--adb-serial", opts.serial);
|
|
166
|
+
}
|
|
167
|
+
args.push("--host", this.mcpHost, "--port", String(opts.mcpPort));
|
|
168
|
+
const env = {
|
|
169
|
+
...process.env,
|
|
170
|
+
...this.extraEnv,
|
|
171
|
+
DEVICEAGENT_BACKEND: backend,
|
|
172
|
+
ANDROID_SERIAL: opts.serial,
|
|
173
|
+
MCP_HOST: this.mcpHost,
|
|
174
|
+
MCP_PORT: String(opts.mcpPort),
|
|
175
|
+
};
|
|
176
|
+
let child;
|
|
177
|
+
try {
|
|
178
|
+
child = this.spawnFn(this.mcpBin, args, env);
|
|
179
|
+
}
|
|
180
|
+
catch (e) {
|
|
181
|
+
this.logger.error("failed to spawn mcp", {
|
|
182
|
+
serial: opts.serial,
|
|
183
|
+
error: String(e),
|
|
184
|
+
});
|
|
185
|
+
this.emit({
|
|
186
|
+
kind: "mcp_exited",
|
|
187
|
+
serial: opts.serial,
|
|
188
|
+
code: -1,
|
|
189
|
+
signal: null,
|
|
190
|
+
expected: false,
|
|
191
|
+
});
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
const childSlot = {
|
|
195
|
+
child,
|
|
196
|
+
expectingKill: false,
|
|
197
|
+
spawnedAt: Date.now(),
|
|
198
|
+
bin: this.mcpBin,
|
|
199
|
+
args,
|
|
200
|
+
};
|
|
201
|
+
slot.mcp = childSlot;
|
|
202
|
+
this.logger.info("spawned device-mcp-server", {
|
|
203
|
+
serial: opts.serial,
|
|
204
|
+
pid: child.pid ?? null,
|
|
205
|
+
port: opts.mcpPort,
|
|
206
|
+
});
|
|
207
|
+
this.attachExitListener(opts.serial, "mcp", childSlot);
|
|
208
|
+
// Readiness probe (best-effort; in tests httpProbeFn returns true instantly).
|
|
209
|
+
const url = `http://${this.mcpHost}:${opts.mcpPort}/healthz`;
|
|
210
|
+
const ok = await this.httpProbeFn(url, this.mcpHealthTimeoutMs);
|
|
211
|
+
if (!ok) {
|
|
212
|
+
this.logger.warn("mcp /healthz did not return 200 within grace", {
|
|
213
|
+
serial: opts.serial,
|
|
214
|
+
url,
|
|
215
|
+
});
|
|
216
|
+
// Treat the readiness failure as a crash. Mark the upcoming exit as
|
|
217
|
+
// expected (we caused the kill) so the FSM won't double-count, but
|
|
218
|
+
// emit the synthetic exit AFTER the actual kill bookkeeping is done.
|
|
219
|
+
childSlot.expectingKill = true;
|
|
220
|
+
try {
|
|
221
|
+
child.kill("SIGTERM");
|
|
222
|
+
}
|
|
223
|
+
catch {
|
|
224
|
+
/* ignore */
|
|
225
|
+
}
|
|
226
|
+
this.emit({
|
|
227
|
+
kind: "mcp_exited",
|
|
228
|
+
serial: opts.serial,
|
|
229
|
+
code: null,
|
|
230
|
+
signal: "SIGTERM",
|
|
231
|
+
expected: false,
|
|
232
|
+
});
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
this.emit({
|
|
236
|
+
kind: "mcp_started",
|
|
237
|
+
serial: opts.serial,
|
|
238
|
+
pid: child.pid ?? 0,
|
|
239
|
+
port: opts.mcpPort,
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
async spawnDaemon(opts) {
|
|
243
|
+
const slot = this.ensureSlot(opts.serial);
|
|
244
|
+
if (slot.daemon) {
|
|
245
|
+
this.logger.warn("spawnDaemon called while daemon already alive — ignoring", {
|
|
246
|
+
serial: opts.serial,
|
|
247
|
+
pid: slot.daemon.child.pid,
|
|
248
|
+
});
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
const mcpUrl = `http://${this.mcpHost}:${opts.mcpPort}`;
|
|
252
|
+
const args = [
|
|
253
|
+
"--key",
|
|
254
|
+
opts.keyFile,
|
|
255
|
+
"--mcp-url",
|
|
256
|
+
mcpUrl,
|
|
257
|
+
"--adb",
|
|
258
|
+
opts.serial,
|
|
259
|
+
];
|
|
260
|
+
const agw = opts.agentGatewayUrl ?? this.agentGatewayUrl;
|
|
261
|
+
if (agw) {
|
|
262
|
+
args.push("--agent-gateway-url", agw);
|
|
263
|
+
}
|
|
264
|
+
if (this.bridgeUrl) {
|
|
265
|
+
args.push("--bridge", this.bridgeUrl);
|
|
266
|
+
}
|
|
267
|
+
if (this.region) {
|
|
268
|
+
args.push("--region", this.region);
|
|
269
|
+
}
|
|
270
|
+
if (typeof opts.daemonHttpPort === "number" && opts.daemonHttpPort > 0) {
|
|
271
|
+
args.push("--http", "--http-port", String(opts.daemonHttpPort));
|
|
272
|
+
}
|
|
273
|
+
// Per-instance VLM / orchestrator config that the CLI's
|
|
274
|
+
// `persistAgentConfigBestEffort` writes during `beeos device attach`.
|
|
275
|
+
// Injected only when the file actually exists so daemons started for
|
|
276
|
+
// edge / staging deploys (where the CLI hasn't run) still come up
|
|
277
|
+
// with their built-in defaults.
|
|
278
|
+
if (opts.instanceId) {
|
|
279
|
+
const cfg = path.join(beeoHome(), `${opts.instanceId}.config.json`);
|
|
280
|
+
if (await fileExists(cfg)) {
|
|
281
|
+
args.push("--vlm-config-file", cfg);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
const env = {
|
|
285
|
+
...process.env,
|
|
286
|
+
...this.extraEnv,
|
|
287
|
+
MCP_URL: mcpUrl,
|
|
288
|
+
ANDROID_SERIAL: opts.serial,
|
|
289
|
+
};
|
|
290
|
+
let child;
|
|
291
|
+
try {
|
|
292
|
+
child = this.spawnFn(this.daemonBin, args, env);
|
|
293
|
+
}
|
|
294
|
+
catch (e) {
|
|
295
|
+
this.logger.error("failed to spawn daemon", {
|
|
296
|
+
serial: opts.serial,
|
|
297
|
+
error: String(e),
|
|
298
|
+
});
|
|
299
|
+
this.emit({
|
|
300
|
+
kind: "daemon_exited",
|
|
301
|
+
serial: opts.serial,
|
|
302
|
+
code: -1,
|
|
303
|
+
signal: null,
|
|
304
|
+
expected: false,
|
|
305
|
+
});
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
const childSlot = {
|
|
309
|
+
child,
|
|
310
|
+
expectingKill: false,
|
|
311
|
+
spawnedAt: Date.now(),
|
|
312
|
+
bin: this.daemonBin,
|
|
313
|
+
args,
|
|
314
|
+
};
|
|
315
|
+
slot.daemon = childSlot;
|
|
316
|
+
this.logger.info("spawned device-agent daemon", {
|
|
317
|
+
serial: opts.serial,
|
|
318
|
+
pid: child.pid ?? null,
|
|
319
|
+
mcpUrl,
|
|
320
|
+
});
|
|
321
|
+
this.attachExitListener(opts.serial, "daemon", childSlot);
|
|
322
|
+
this.emit({
|
|
323
|
+
kind: "daemon_started",
|
|
324
|
+
serial: opts.serial,
|
|
325
|
+
pid: child.pid ?? 0,
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
async spawnScrcpy(opts) {
|
|
329
|
+
const slot = this.ensureSlot(opts.serial);
|
|
330
|
+
if (slot.scrcpy) {
|
|
331
|
+
this.logger.warn("spawnScrcpy called while scrcpy already alive — ignoring", {
|
|
332
|
+
serial: opts.serial,
|
|
333
|
+
pid: slot.scrcpy.child.pid,
|
|
334
|
+
});
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
const bin = await this.findScrcpyBinFn();
|
|
338
|
+
if (!bin) {
|
|
339
|
+
// Best-effort: missing binary must NOT block the device. Emit a
|
|
340
|
+
// synthetic exit (with a `reason`) so the supervisor records the
|
|
341
|
+
// degraded video state on the snapshot. mcp + daemon still come up.
|
|
342
|
+
this.logger.warn("scrcpy-bridge binary not found; video disabled", {
|
|
343
|
+
serial: opts.serial,
|
|
344
|
+
});
|
|
345
|
+
this.emit({
|
|
346
|
+
kind: "scrcpy_exited",
|
|
347
|
+
serial: opts.serial,
|
|
348
|
+
code: null,
|
|
349
|
+
signal: null,
|
|
350
|
+
expected: false,
|
|
351
|
+
reason: "binary_not_found",
|
|
352
|
+
});
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
// Mirror `web/packages/core/src/runtime/scrcpy-bridge.ts` `buildEnv`
|
|
356
|
+
// (kept inline here to avoid a `@beeos/core` dependency in the
|
|
357
|
+
// device-agent package).
|
|
358
|
+
const env = {
|
|
359
|
+
...process.env,
|
|
360
|
+
...this.extraEnv,
|
|
361
|
+
DEVICE_ID: `device-${opts.instanceId}`,
|
|
362
|
+
ADB_SERIAL: opts.serial,
|
|
363
|
+
AGENT_GATEWAY_URL: opts.agentGatewayUrl,
|
|
364
|
+
BRIDGE_PRIVATE_KEY_FILE: opts.bridgePrivateKeyFile,
|
|
365
|
+
BRIDGE_PUBLIC_KEY_FILE: opts.bridgePublicKeyFile,
|
|
366
|
+
LOG_FORMAT: "json",
|
|
367
|
+
...(opts.videoBitrateKbps != null
|
|
368
|
+
? { VIDEO_BITRATE_KBPS: String(opts.videoBitrateKbps) }
|
|
369
|
+
: {}),
|
|
370
|
+
...(opts.maxFps != null ? { MAX_FPS: String(opts.maxFps) } : {}),
|
|
371
|
+
...(opts.iceUrls?.length ? { ICE_URLS: opts.iceUrls.join(",") } : {}),
|
|
372
|
+
...(opts.turnUrls?.length ? { TURN_URLS: opts.turnUrls.join(",") } : {}),
|
|
373
|
+
...(opts.turnUsername ? { TURN_USERNAME: opts.turnUsername } : {}),
|
|
374
|
+
...(opts.turnCredential ? { TURN_CREDENTIAL: opts.turnCredential } : {}),
|
|
375
|
+
...(opts.extraEnv ?? {}),
|
|
376
|
+
};
|
|
377
|
+
let child;
|
|
378
|
+
try {
|
|
379
|
+
child = this.spawnFn(bin, [], env);
|
|
380
|
+
}
|
|
381
|
+
catch (e) {
|
|
382
|
+
this.logger.error("failed to spawn scrcpy-bridge", {
|
|
383
|
+
serial: opts.serial,
|
|
384
|
+
error: String(e),
|
|
385
|
+
});
|
|
386
|
+
this.emit({
|
|
387
|
+
kind: "scrcpy_exited",
|
|
388
|
+
serial: opts.serial,
|
|
389
|
+
code: -1,
|
|
390
|
+
signal: null,
|
|
391
|
+
expected: false,
|
|
392
|
+
reason: "spawn_failed",
|
|
393
|
+
});
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
const childSlot = {
|
|
397
|
+
child,
|
|
398
|
+
expectingKill: false,
|
|
399
|
+
spawnedAt: Date.now(),
|
|
400
|
+
bin,
|
|
401
|
+
args: [],
|
|
402
|
+
startedEmitted: false,
|
|
403
|
+
};
|
|
404
|
+
slot.scrcpy = childSlot;
|
|
405
|
+
this.logger.info("spawned scrcpy-bridge", {
|
|
406
|
+
serial: opts.serial,
|
|
407
|
+
pid: child.pid ?? null,
|
|
408
|
+
deviceId: env.DEVICE_ID,
|
|
409
|
+
});
|
|
410
|
+
this.attachExitListener(opts.serial, "scrcpy", childSlot);
|
|
411
|
+
// scrcpy-bridge has no /healthz endpoint (its readiness is the WebRTC
|
|
412
|
+
// signalling round-trip, which is per-session and out of fleet scope).
|
|
413
|
+
// We treat "still alive after `scrcpyStartupGraceMs`" as healthy. If
|
|
414
|
+
// the child exits earlier the exit listener emits `scrcpy_exited`
|
|
415
|
+
// BEFORE this timer fires and `startedEmitted` stays false.
|
|
416
|
+
const timer = setTimeout(() => {
|
|
417
|
+
if (childSlot.startedEmitted)
|
|
418
|
+
return;
|
|
419
|
+
// Re-check the slot is still ours (may have been replaced by a
|
|
420
|
+
// restart loop, in which case the new spawn owns the timer).
|
|
421
|
+
const current = this.slots.get(opts.serial)?.scrcpy;
|
|
422
|
+
if (current !== childSlot)
|
|
423
|
+
return;
|
|
424
|
+
childSlot.startedEmitted = true;
|
|
425
|
+
this.emit({
|
|
426
|
+
kind: "scrcpy_started",
|
|
427
|
+
serial: opts.serial,
|
|
428
|
+
pid: child.pid ?? 0,
|
|
429
|
+
});
|
|
430
|
+
}, this.scrcpyStartupGraceMs);
|
|
431
|
+
if (typeof timer === "object" && timer && "unref" in timer) {
|
|
432
|
+
try {
|
|
433
|
+
timer.unref();
|
|
434
|
+
}
|
|
435
|
+
catch {
|
|
436
|
+
/* ignore */
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
childSlot.startupTimer = timer;
|
|
440
|
+
}
|
|
441
|
+
// ── Kill paths ───────────────────────────────────────────────────────────
|
|
442
|
+
async killMcp(serial, opts = {}) {
|
|
443
|
+
await this.killChild(serial, "mcp", opts.expected ?? true);
|
|
444
|
+
}
|
|
445
|
+
async killDaemon(serial, opts = {}) {
|
|
446
|
+
await this.killChild(serial, "daemon", opts.expected ?? true);
|
|
447
|
+
}
|
|
448
|
+
async killScrcpy(serial, opts = {}) {
|
|
449
|
+
await this.killChild(serial, "scrcpy", opts.expected ?? true);
|
|
450
|
+
}
|
|
451
|
+
// ── Internals ────────────────────────────────────────────────────────────
|
|
452
|
+
ensureSlot(serial) {
|
|
453
|
+
let slot = this.slots.get(serial);
|
|
454
|
+
if (!slot) {
|
|
455
|
+
slot = { serial };
|
|
456
|
+
this.slots.set(serial, slot);
|
|
457
|
+
}
|
|
458
|
+
return slot;
|
|
459
|
+
}
|
|
460
|
+
attachExitListener(serial, which, childSlot) {
|
|
461
|
+
childSlot.child.once("exit", (code, signal) => {
|
|
462
|
+
const slot = this.slots.get(serial);
|
|
463
|
+
if (!slot)
|
|
464
|
+
return;
|
|
465
|
+
const previous = this.slotFor(slot, which);
|
|
466
|
+
if (previous !== childSlot) {
|
|
467
|
+
// Some other slot already replaced this one (rare race) — ignore.
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
const expected = childSlot.expectingKill;
|
|
471
|
+
this.setSlot(slot, which, undefined);
|
|
472
|
+
// scrcpy startup-grace timer must be cleared regardless of how the
|
|
473
|
+
// child died — otherwise a delayed `scrcpy_started` could fire
|
|
474
|
+
// after `scrcpy_exited`, confusing the supervisor's snapshot.
|
|
475
|
+
if (childSlot.startupTimer) {
|
|
476
|
+
clearTimeout(childSlot.startupTimer);
|
|
477
|
+
childSlot.startupTimer = undefined;
|
|
478
|
+
}
|
|
479
|
+
this.logger.warn("child exited", {
|
|
480
|
+
serial,
|
|
481
|
+
which,
|
|
482
|
+
code,
|
|
483
|
+
signal,
|
|
484
|
+
expected,
|
|
485
|
+
});
|
|
486
|
+
if (which === "mcp") {
|
|
487
|
+
this.emit({ kind: "mcp_exited", serial, code, signal, expected });
|
|
488
|
+
}
|
|
489
|
+
else if (which === "daemon") {
|
|
490
|
+
this.emit({ kind: "daemon_exited", serial, code, signal, expected });
|
|
491
|
+
}
|
|
492
|
+
else {
|
|
493
|
+
this.emit({
|
|
494
|
+
kind: "scrcpy_exited",
|
|
495
|
+
serial,
|
|
496
|
+
code,
|
|
497
|
+
signal,
|
|
498
|
+
expected,
|
|
499
|
+
// If the child died inside the startup grace window we tag it
|
|
500
|
+
// for snapshot consumers so they can render a degraded label
|
|
501
|
+
// (e.g. "scrcpy crashed during boot") without parsing logs.
|
|
502
|
+
reason: childSlot.startedEmitted === false ? "startup_failed" : undefined,
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
});
|
|
506
|
+
childSlot.child.once("error", (err) => {
|
|
507
|
+
this.logger.error("child error", { serial, which, error: String(err) });
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
async killChild(serial, which, expected) {
|
|
511
|
+
const slot = this.slots.get(serial);
|
|
512
|
+
if (!slot)
|
|
513
|
+
return;
|
|
514
|
+
const childSlot = this.slotFor(slot, which);
|
|
515
|
+
if (!childSlot)
|
|
516
|
+
return;
|
|
517
|
+
childSlot.expectingKill = expected;
|
|
518
|
+
try {
|
|
519
|
+
childSlot.child.kill("SIGTERM");
|
|
520
|
+
}
|
|
521
|
+
catch (e) {
|
|
522
|
+
this.logger.warn("SIGTERM failed", { serial, which, error: String(e) });
|
|
523
|
+
}
|
|
524
|
+
// Wait up to shutdownGraceMs for `exit`. The exit listener is what
|
|
525
|
+
// clears the slot; we just observe.
|
|
526
|
+
const start = Date.now();
|
|
527
|
+
while (this.slotFor(slot, which) && Date.now() - start < this.shutdownGraceMs) {
|
|
528
|
+
await sleep(50);
|
|
529
|
+
}
|
|
530
|
+
if (this.slotFor(slot, which)) {
|
|
531
|
+
this.logger.warn("SIGTERM grace expired — sending SIGKILL", {
|
|
532
|
+
serial,
|
|
533
|
+
which,
|
|
534
|
+
});
|
|
535
|
+
try {
|
|
536
|
+
childSlot.child.kill("SIGKILL");
|
|
537
|
+
}
|
|
538
|
+
catch (e) {
|
|
539
|
+
this.logger.warn("SIGKILL failed", { serial, which, error: String(e) });
|
|
540
|
+
}
|
|
541
|
+
// wait briefly for SIGKILL exit
|
|
542
|
+
const killStart = Date.now();
|
|
543
|
+
while (this.slotFor(slot, which) && Date.now() - killStart < 2_000) {
|
|
544
|
+
await sleep(50);
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
emit(event) {
|
|
549
|
+
for (const l of this.listeners) {
|
|
550
|
+
try {
|
|
551
|
+
l(event);
|
|
552
|
+
}
|
|
553
|
+
catch (e) {
|
|
554
|
+
this.logger.warn("process event listener threw", { error: String(e) });
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
// ── Defaults ────────────────────────────────────────────────────────────────
|
|
560
|
+
const defaultSpawnFn = (bin, args, env) => {
|
|
561
|
+
const child = nodeSpawn(bin, [...args], {
|
|
562
|
+
env,
|
|
563
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
564
|
+
detached: false,
|
|
565
|
+
});
|
|
566
|
+
// Pipe child output through fleet's logger transparently — the supervisor
|
|
567
|
+
// doesn't need parsed lines, just visibility.
|
|
568
|
+
child.stdout?.on("data", (chunk) => {
|
|
569
|
+
process.stdout.write(`[${bin}] ${chunk.toString()}`);
|
|
570
|
+
});
|
|
571
|
+
child.stderr?.on("data", (chunk) => {
|
|
572
|
+
process.stderr.write(`[${bin} stderr] ${chunk.toString()}`);
|
|
573
|
+
});
|
|
574
|
+
return child;
|
|
575
|
+
};
|
|
576
|
+
const defaultHttpProbeFn = async (url, timeoutMs) => {
|
|
577
|
+
const deadline = Date.now() + timeoutMs;
|
|
578
|
+
while (Date.now() < deadline) {
|
|
579
|
+
const ok = await probeOnce(url, 1_000);
|
|
580
|
+
if (ok)
|
|
581
|
+
return true;
|
|
582
|
+
await sleep(250);
|
|
583
|
+
}
|
|
584
|
+
return false;
|
|
585
|
+
};
|
|
586
|
+
function probeOnce(url, timeoutMs) {
|
|
587
|
+
return new Promise((resolve) => {
|
|
588
|
+
const req = http.get(url, (res) => {
|
|
589
|
+
const ok = res.statusCode === 200;
|
|
590
|
+
res.resume();
|
|
591
|
+
resolve(ok);
|
|
592
|
+
});
|
|
593
|
+
req.setTimeout(timeoutMs, () => {
|
|
594
|
+
req.destroy();
|
|
595
|
+
resolve(false);
|
|
596
|
+
});
|
|
597
|
+
req.on("error", () => resolve(false));
|
|
598
|
+
});
|
|
599
|
+
}
|
|
600
|
+
/**
|
|
601
|
+
* Default scrcpy-bridge binary discovery — used when no `findScrcpyBinFn`
|
|
602
|
+
* is injected. Mirrors `scrcpyBridgeRuntime.findBinary()` semantics in
|
|
603
|
+
* `web/packages/core/src/runtime/scrcpy-bridge.ts` (constructor override
|
|
604
|
+
* → `BEEOS_SCRCPY_BRIDGE_BIN` env → `~/.beeos/bin/scrcpy-bridge[.exe]`).
|
|
605
|
+
*
|
|
606
|
+
* We deliberately do NOT consult `$PATH` (vs the `@beeos/core` adapter
|
|
607
|
+
* which uses `which`) — the fleet daemon usually runs from a non-login
|
|
608
|
+
* shell where `$PATH` is minimal, so PATH-based discovery is unreliable.
|
|
609
|
+
* The CLI's `scrcpyBridgeRuntime.ensureInstalled` always lays the binary
|
|
610
|
+
* at `~/.beeos/bin/scrcpy-bridge`, so the managed-install path is
|
|
611
|
+
* sufficient for the supported deploy modes.
|
|
612
|
+
*/
|
|
613
|
+
async function defaultFindScrcpyBin(override) {
|
|
614
|
+
if (override && (await fileExists(override)))
|
|
615
|
+
return override;
|
|
616
|
+
const envBin = process.env.BEEOS_SCRCPY_BRIDGE_BIN;
|
|
617
|
+
if (envBin && (await fileExists(envBin)))
|
|
618
|
+
return envBin;
|
|
619
|
+
const home = beeoHome();
|
|
620
|
+
const exe = process.platform === "win32" ? "scrcpy-bridge.exe" : "scrcpy-bridge";
|
|
621
|
+
const managed = path.join(home, "bin", exe);
|
|
622
|
+
if (await fileExists(managed))
|
|
623
|
+
return managed;
|
|
624
|
+
return null;
|
|
625
|
+
}
|
|
626
|
+
/** Resolve the BeeOS home directory (override-aware, mirrors `port-allocator.ts`). */
|
|
627
|
+
function beeoHome() {
|
|
628
|
+
const override = process.env.BEEOS_HOME;
|
|
629
|
+
if (override && override.length > 0)
|
|
630
|
+
return override;
|
|
631
|
+
return path.join(os.homedir(), ".beeos");
|
|
632
|
+
}
|
|
633
|
+
async function fileExists(p) {
|
|
634
|
+
try {
|
|
635
|
+
await fs.promises.access(p, fs.constants.F_OK);
|
|
636
|
+
return true;
|
|
637
|
+
}
|
|
638
|
+
catch {
|
|
639
|
+
return false;
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
/**
|
|
643
|
+
* `device-mcp-server` `--adb-serial <serial>` is only meaningful when the
|
|
644
|
+
* backend talks to ADB. Currently that's just `adb` (canonical) and the
|
|
645
|
+
* `android` alias; desktop / mock backends ignore the flag entirely so we
|
|
646
|
+
* skip it to keep argv readable in `ps`.
|
|
647
|
+
*/
|
|
648
|
+
function isAdbBackend(name) {
|
|
649
|
+
return name === "adb" || name === "android";
|
|
650
|
+
}
|
|
651
|
+
function sleep(ms) {
|
|
652
|
+
return new Promise((res) => {
|
|
653
|
+
const t = setTimeout(res, ms);
|
|
654
|
+
if (typeof t === "object" && t && "unref" in t) {
|
|
655
|
+
try {
|
|
656
|
+
t.unref();
|
|
657
|
+
}
|
|
658
|
+
catch {
|
|
659
|
+
/* ignore */
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
});
|
|
663
|
+
}
|
|
664
|
+
//# sourceMappingURL=node-process-manager.js.map
|