@jawji/orchestrator 0.1.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.
Files changed (47) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +181 -0
  3. package/dist/adapters/mavsdk-adapter.d.ts +4 -0
  4. package/dist/adapters/mavsdk-adapter.d.ts.map +1 -0
  5. package/dist/adapters/mavsdk-adapter.js +51 -0
  6. package/dist/adapters/mavsdk-adapter.js.map +1 -0
  7. package/dist/adapters/vlm-client.d.ts +3 -0
  8. package/dist/adapters/vlm-client.d.ts.map +1 -0
  9. package/dist/adapters/vlm-client.js +19 -0
  10. package/dist/adapters/vlm-client.js.map +1 -0
  11. package/dist/grpc/mavsdk-client.d.ts +8 -0
  12. package/dist/grpc/mavsdk-client.d.ts.map +1 -0
  13. package/dist/grpc/mavsdk-client.js +34 -0
  14. package/dist/grpc/mavsdk-client.js.map +1 -0
  15. package/dist/index.d.ts +14 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +8 -0
  18. package/dist/index.js.map +1 -0
  19. package/dist/modes/landing-zone-check.d.ts +3 -0
  20. package/dist/modes/landing-zone-check.d.ts.map +1 -0
  21. package/dist/modes/landing-zone-check.js +52 -0
  22. package/dist/modes/landing-zone-check.js.map +1 -0
  23. package/dist/modes/vision-assist-mode.d.ts +8 -0
  24. package/dist/modes/vision-assist-mode.d.ts.map +1 -0
  25. package/dist/modes/vision-assist-mode.js +2 -0
  26. package/dist/modes/vision-assist-mode.js.map +1 -0
  27. package/dist/orchestrator.d.ts +13 -0
  28. package/dist/orchestrator.d.ts.map +1 -0
  29. package/dist/orchestrator.js +106 -0
  30. package/dist/orchestrator.js.map +1 -0
  31. package/dist/server/status-server.d.ts +23 -0
  32. package/dist/server/status-server.d.ts.map +1 -0
  33. package/dist/server/status-server.js +35 -0
  34. package/dist/server/status-server.js.map +1 -0
  35. package/dist/types.d.ts +55 -0
  36. package/dist/types.d.ts.map +1 -0
  37. package/dist/types.js +2 -0
  38. package/dist/types.js.map +1 -0
  39. package/dist/vlm/miril-client.d.ts +12 -0
  40. package/dist/vlm/miril-client.d.ts.map +1 -0
  41. package/dist/vlm/miril-client.js +45 -0
  42. package/dist/vlm/miril-client.js.map +1 -0
  43. package/package.json +47 -0
  44. package/proto/action/action.proto +368 -0
  45. package/proto/mavsdk_options.proto +23 -0
  46. package/proto/mission/mission.proto +285 -0
  47. package/proto/telemetry/telemetry.proto +897 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 utachicodes
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,181 @@
1
+ # jawji-orchestrator
2
+
3
+ [![npm version](https://img.shields.io/npm/v/%40jawji%2Forchestrator.svg)](https://www.npmjs.com/package/@jawji/orchestrator)
4
+ [![CI](https://github.com/utachicodes/jawji-orchestrator/actions/workflows/ci.yml/badge.svg)](https://github.com/utachicodes/jawji-orchestrator/actions/workflows/ci.yml)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
6
+
7
+ Standalone onboard vision assisted autonomy for a companion computer, starting with a landing zone safety check.
8
+
9
+ ## What this is
10
+
11
+ This package runs on a companion computer (a Jetson or Raspberry Pi, for example), as its own process, with its own direct connection to the flight controller through a local `mavsdk_server`. It watches vehicle telemetry, and when a configured mode is triggered (the landing zone check triggers on entering LAND mode), it holds the vehicle, captures a camera frame, sends that frame to a vision language model, and decides whether the current position looks safe.
12
+
13
+ ## What this is not
14
+
15
+ This is not a ground control station feature, and it does not require a ground control station to be connected. It has no dependency on any particular GCS. A GCS, or any other operator interface, can observe and confirm what this package is doing by talking to its local HTTP API, but that is entirely optional and the package works correctly with nothing else connected at all.
16
+
17
+ This package also does not include a vision language model server, and does not include camera capture code for any specific hardware. Both are supplied by the integrator through the adapter interfaces described below.
18
+
19
+ ## How the pieces fit together
20
+
21
+ 1. `mavsdk_server` runs on the companion computer, exposing MAVSDK's gRPC interface against the flight controller connection.
22
+ 2. This package connects to that `mavsdk_server` over gRPC, using vendored proto definitions from the official `mavlink/MAVSDK-Proto` repository, loaded at runtime with `@grpc/proto-loader`. There is no official MAVSDK Node.js client published to npm, which is why this package generates its own client rather than depending on one.
23
+ 3. When the vehicle's flight mode telemetry reports LAND, the orchestrator commands a hold through the MAVSDK action service, then asks the integrator supplied `CameraSource` for a frame.
24
+ 4. That frame, along with a prompt, is sent to the integrator supplied `VlmClient`. This package does not care which vision language model answers, only that the response includes a `status` field (`safe` or `unsafe`) and, when unsafe, an optional candidate location.
25
+ 5. If the verdict is safe, the vehicle resumes its mission automatically.
26
+ 6. If the verdict is unsafe or the vision language model call failed, the orchestrator holds and exposes the verdict on a small local HTTP server, bound to 127.0.0.1 only, by default on port 48500.
27
+ 7. Something else, a GCS, a remote control channel, anything with network access to that local API, posts a confirm or reject decision to `/confirm`. The orchestrator then either repositions to the candidate and re-evaluates, or resumes the original landing.
28
+ 8. Step 7 repeats up to three times if repositioning to a candidate still does not come back safe. After three attempts the orchestrator reports a no safe alternative found status and holds indefinitely rather than continuing to reposition on its own.
29
+ 9. If nothing responds to a confirm request at all, the vehicle simply continues holding. This package does not invent a timeout based fallback action. The flight controller's own existing failsafes, such as battery or radio control loss, remain the safety net if the vehicle is genuinely unattended.
30
+
31
+ ## Architecture
32
+
33
+ ```
34
+ +---------------------------+
35
+ | Companion computer |
36
+ | (Jetson, Raspberry Pi) |
37
+ | |
38
+ Flight <--gRPC-->| mavsdk_server |
39
+ controller | ^ |
40
+ | | gRPC (vendored |
41
+ | | MAVSDK-Proto) |
42
+ | v |
43
+ | jawji-orchestrator |
44
+ | | VisionAssistMode(s) |
45
+ | | - LandingZoneCheckMode |
46
+ | | VehicleAdapter |
47
+ | | CameraSource (yours) |
48
+ | | VlmClient (yours) |
49
+ | | local status/confirm |
50
+ | | HTTP API (127.0.0.1) |
51
+ +---------------------------+
52
+ ^
53
+ | HTTP, localhost only
54
+ | (you decide what, if
55
+ | anything, reaches this)
56
+ +---------------------------+
57
+ | Optional: a GCS, a relay, |
58
+ | jawji-agent, an SSH tunnel |
59
+ +---------------------------+
60
+ ```
61
+
62
+ The only two hard dependencies are `mavsdk_server` (for vehicle control and telemetry) and an HTTP endpoint implementing `VlmClient` (for scene assessment). Everything above the local HTTP API line is optional and this package has no awareness of what, if anything, is there.
63
+
64
+ ### Relationship with Jawji Agent
65
+
66
+ None, currently. This is worth stating plainly since jawji-orchestrator and [Jawji Agent](https://github.com/utachicodes/jawjideck/tree/master/packages/jawji-agent) get mentioned together often enough that it would be easy to assume they are connected. They are two separate, independently running pieces of software. If you deploy both on the same companion computer today, they run side by side, each unaware the other exists.
67
+
68
+ The intended integration, not yet built, is for Jawji Agent to poll this package's local `/status` endpoint and relay `/confirm` decisions through its own existing authenticated REST API, the same pattern it already uses to expose MediaMTX's local status to a connected Jawji desktop app. Until that exists, anything that wants to observe or confirm what jawji-orchestrator is doing needs to reach its local HTTP API some other way, for example an SSH tunnel, or a small relay process you write yourself.
69
+
70
+ ### Miril-Drone-2B-1 support
71
+
72
+ [`createMirilVlmClient`](src/vlm/miril-client.ts) is a built-in `VlmClient` implementation for [Miril-Drone-2B-1](https://huggingface.co/MirilAI/Miril-Drone-2B-1), a vision language model fine-tuned for aerial imagery, with response shapes for scene captioning, visual question answering, and operational coordinate pointing (the `operational_coordinate_v2` family used by `LandingZoneCheckMode`).
73
+
74
+ Miril has no single canonical HTTP protocol of its own. This client targets the OpenAI-compatible `/v1/chat/completions` shape instead, because that is what every realistic way of serving Miril implements for multimodal chat: `llama-server` from `llama.cpp`, vLLM, and SGLang all speak it. The image is sent as a base64 data URL in an `image_url` content part, alongside a text instruction built from the requested prompt family plus whatever prompt the calling mode supplies.
75
+
76
+ ```typescript
77
+ import { createOrchestrator, createMirilVlmClient, createLandingZoneCheckMode } from '@jawji/orchestrator';
78
+
79
+ const orchestrator = createOrchestrator({
80
+ mavsdkServerAddress: 'localhost:50051',
81
+ vlmClient: createMirilVlmClient({
82
+ endpoint: 'http://127.0.0.1:8000/v1/chat/completions', // llama-server, vLLM, or SGLang
83
+ promptFamily: 'operational_coordinate_v2',
84
+ }),
85
+ cameraSource: { async captureFrame() { /* ... */ throw new Error('not implemented'); } },
86
+ confirmPolicy: 'gated',
87
+ statusServerPort: 48500,
88
+ modes: [createLandingZoneCheckMode()],
89
+ });
90
+ ```
91
+
92
+ This does not ship a Miril server. You still need to stand one up yourself, for example `llama-server` running a GGUF quantization of Miril-Drone-2B-1 on the companion computer, and point `endpoint` at it. `createHttpVlmClient` remains available as the plain, protocol-agnostic option for any other vision language model, or any custom HTTP shape you prefer.
93
+
94
+ ### GPS-denied navigation (research, not implemented)
95
+
96
+ This was asked about directly, so it deserves an honest answer rather than either dismissing it or bolting on unreviewed code that would push position estimates into a real flight controller. There is no landmark memory or GPS-denied mode in this package today. Below is the concrete design it would take, written down so it can be reviewed and built deliberately rather than shipped without scrutiny, given that it touches the flight controller's position estimate.
97
+
98
+ **Why this is a different kind of problem than landing-zone-check.** `LandingZoneCheckMode` is a single-shot check: one frame in, one verdict out, no memory across time. Landmark-based re-localization needs the opposite: persistent state built up over the whole flight, a way to match a new frame against everything remembered so far, and a way to feed a corrected position back into the flight controller.
99
+
100
+ **The three pieces, and what already exists to support each of them:**
101
+
102
+ 1. **Recording landmarks while GPS is healthy.** MAVSDK's telemetry service has `SubscribeHealth`, which includes an `is_global_position_ok` field, confirmed directly against the real `telemetry.proto`. A recording step would run on a timer or distance interval while that flag is true, capturing a frame plus the current position and storing them.
103
+
104
+ 2. **Matching a new frame against stored landmarks when GPS is lost.** A captioning vision language model like Miril is the wrong tool for this specific step. It is built to describe a scene in words, not to produce a precise, viewpoint-stable embedding for place recognition, and comparing generated captions is a poor substitute for comparing actual visual features. The realistic options, roughly in order of how much they would add to this package's current no-native-addon design goal:
105
+ - **Perceptual image hashing** (average hash or difference hash) as a coarse, cheap first pass. Genuinely implementable in pure JavaScript with a library like `jimp` (no native addon, same constraint this package already holds itself to for `@grpc/grpc-js`), but only catches near-duplicate viewpoints, not real viewpoint-invariant matching. Honest baseline, not real visual place recognition.
106
+ - **A dedicated visual place recognition embedding model** (for example NetVLAD-style or a ViT-based place recognition model) run locally via an ONNX runtime, with nearest-neighbor search over stored embeddings. Meaningfully more reliable, but reintroduces a native-or-WASM ML runtime dependency this package has otherwise avoided, and needs a specific model choice and real accuracy validation against actual flight footage before it could be trusted.
107
+ - **Classical feature matching** (ORB or SIFT keypoints via OpenCV) as the well-established SLAM/relocalization approach, at the cost of an OpenCV dependency, which is a native addon on most platforms.
108
+
109
+ 3. **Feeding a match back to the flight controller.** MAVSDK's `mocap` service exists for exactly this: `MocapService.SetVisionPositionEstimate`, confirmed directly against the real `mocap.proto`, takes a body-frame position estimate (`PositionBody`: x/y/z in metres, not raw latitude/longitude) intended for exactly the case the proto's own comment describes, "navigation without global positioning sources available (e.g. indoors, or when flying under a bridge)." A landmark match would need converting from a stored global position plus the current relative offset into that body-frame form before calling it.
110
+
111
+ None of this is built. Given it would be capable of altering the flight controller's position estimate, and the accuracy of any of the matching approaches above needs validating against real flight footage before being trusted, the honest recommendation is to design and review this as its own effort, following the same pattern this package's own `LandingZoneCheckMode` did: a design document, an explicit confirm-gate default, and real testing before anything resembling autonomous behavior.
112
+
113
+ ## Confirm gate policy
114
+
115
+ By default, `confirmPolicy` is `gated`, meaning an unsafe or unknown verdict always blocks on step 7 above until an external confirm arrives. Setting `confirmPolicy` to `autonomous` skips that wait entirely and acts on the mode's own default decision immediately. This must be set explicitly. The default is `gated` on purpose, so that using this package does not silently grant a companion computer the ability to make unattended repositioning decisions unless that is a deliberate choice by whoever is integrating it.
116
+
117
+ ## Local HTTP API
118
+
119
+ Bound to `127.0.0.1` only, not exposed to the network by this package. Default port 48500.
120
+
121
+ `GET /status` returns the current orchestrator status as JSON, one of:
122
+
123
+ ```json
124
+ { "state": "idle" }
125
+ { "state": "holding" }
126
+ { "state": "awaiting-confirm", "verdict": { "status": "unsafe", "candidate": { "latitudeDeg": 1.23, "longitudeDeg": 4.56, "description": "clearer patch to the north east" } } }
127
+ { "state": "no-safe-alternative" }
128
+ ```
129
+
130
+ `POST /confirm` accepts a JSON body of either:
131
+
132
+ ```json
133
+ { "action": "accept-candidate" }
134
+ { "action": "reject" }
135
+ ```
136
+
137
+ Anything with network reachability to the companion computer's localhost, such as a reverse proxy, an agent process, or an SSH tunnel, can be layered in front of this API to expose it more broadly. This package deliberately does not do that itself, since deciding who is allowed to confirm a landing decision is a security and trust boundary question for the integrator, not something this package should assume an answer to.
138
+
139
+ ## Usage
140
+
141
+ ```typescript
142
+ import {
143
+ createOrchestrator,
144
+ createHttpVlmClient,
145
+ createLandingZoneCheckMode,
146
+ } from '@jawji/orchestrator';
147
+
148
+ const orchestrator = createOrchestrator({
149
+ mavsdkServerAddress: 'localhost:50051',
150
+ vlmClient: createHttpVlmClient('http://127.0.0.1:8000/query'),
151
+ cameraSource: {
152
+ async captureFrame() {
153
+ // Return the current frame as a JPEG or PNG Buffer, from whatever
154
+ // camera source is available on this companion computer.
155
+ throw new Error('not implemented');
156
+ },
157
+ },
158
+ confirmPolicy: 'gated',
159
+ statusServerPort: 48500,
160
+ modes: [createLandingZoneCheckMode()],
161
+ });
162
+
163
+ await orchestrator.start();
164
+ ```
165
+
166
+ ## Prerequisites
167
+
168
+ - A running `mavsdk_server` connected to the flight controller.
169
+ - A vision language model reachable over HTTP that accepts an image and a prompt and returns JSON with at least a `status` field. This package does not ship one. A small local server such as `llama-server` from `llama.cpp`, or a hosted endpoint, both work equally well as long as the response shape matches.
170
+
171
+ ## Development
172
+
173
+ ```bash
174
+ npm install
175
+ npm run build
176
+ npm test
177
+ ```
178
+
179
+ ## License
180
+
181
+ MIT, see [LICENSE](LICENSE).
@@ -0,0 +1,4 @@
1
+ import type { MavsdkClients } from '../grpc/mavsdk-client.js';
2
+ import type { VehicleAdapter } from '../types.js';
3
+ export declare function createMavsdkVehicleAdapter(clients: MavsdkClients): VehicleAdapter;
4
+ //# sourceMappingURL=mavsdk-adapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mavsdk-adapter.d.ts","sourceRoot":"","sources":["../../src/adapters/mavsdk-adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAC9D,OAAO,KAAK,EAAE,cAAc,EAAgB,MAAM,aAAa,CAAC;AAwBhE,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,aAAa,GAAG,cAAc,CAsCjF"}
@@ -0,0 +1,51 @@
1
+ function callUnary(method, request) {
2
+ return new Promise((resolve, reject) => {
3
+ method(request, (err, response) => {
4
+ if (err)
5
+ reject(err);
6
+ else
7
+ resolve(response);
8
+ });
9
+ });
10
+ }
11
+ const FLIGHT_MODE_MAP = {
12
+ FLIGHT_MODE_READY: 'READY',
13
+ FLIGHT_MODE_TAKEOFF: 'TAKEOFF',
14
+ FLIGHT_MODE_HOLD: 'HOLD',
15
+ FLIGHT_MODE_MISSION: 'MISSION',
16
+ FLIGHT_MODE_RETURN_TO_LAUNCH: 'RETURN_TO_LAUNCH',
17
+ FLIGHT_MODE_LAND: 'LAND',
18
+ FLIGHT_MODE_OFFBOARD: 'OFFBOARD',
19
+ };
20
+ export function createMavsdkVehicleAdapter(clients) {
21
+ return {
22
+ async hold() {
23
+ await callUnary(clients.action.hold.bind(clients.action), {});
24
+ },
25
+ async gotoLocation(coords) {
26
+ await callUnary(clients.action.gotoLocation.bind(clients.action), {
27
+ latitudeDeg: coords.latitudeDeg,
28
+ longitudeDeg: coords.longitudeDeg,
29
+ absoluteAltitudeM: coords.absoluteAltitudeM,
30
+ yawDeg: 0,
31
+ });
32
+ },
33
+ async resumeMission() {
34
+ await callUnary(clients.mission.startMission.bind(clients.mission), {});
35
+ },
36
+ subscribeFlightMode(onChange) {
37
+ const call = clients.telemetry.subscribeFlightMode({});
38
+ call.on('data', (response) => {
39
+ const mode = response.flightMode ?? 'FLIGHT_MODE_UNKNOWN';
40
+ onChange({
41
+ flightMode: FLIGHT_MODE_MAP[mode] ?? 'OTHER',
42
+ latitudeDeg: 0,
43
+ longitudeDeg: 0,
44
+ absoluteAltitudeM: 0,
45
+ });
46
+ });
47
+ return () => call.cancel();
48
+ },
49
+ };
50
+ }
51
+ //# sourceMappingURL=mavsdk-adapter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mavsdk-adapter.js","sourceRoot":"","sources":["../../src/adapters/mavsdk-adapter.ts"],"names":[],"mappings":"AAGA,SAAS,SAAS,CAChB,MAA8F,EAC9F,OAAgB;IAEhB,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,QAAQ,EAAE,EAAE;YAChC,IAAI,GAAG;gBAAE,MAAM,CAAC,GAAG,CAAC,CAAC;;gBAChB,OAAO,CAAC,QAAQ,CAAC,CAAC;QACzB,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,eAAe,GAA+C;IAClE,iBAAiB,EAAE,OAAO;IAC1B,mBAAmB,EAAE,SAAS;IAC9B,gBAAgB,EAAE,MAAM;IACxB,mBAAmB,EAAE,SAAS;IAC9B,4BAA4B,EAAE,kBAAkB;IAChD,gBAAgB,EAAE,MAAM;IACxB,oBAAoB,EAAE,UAAU;CACjC,CAAC;AAEF,MAAM,UAAU,0BAA0B,CAAC,OAAsB;IAC/D,OAAO;QACL,KAAK,CAAC,IAAI;YACR,MAAM,SAAS,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAU,EAAE,EAAE,CAAC,CAAC;QACzE,CAAC;QAED,KAAK,CAAC,YAAY,CAAC,MAAM;YACvB,MAAM,SAAS,CAAC,OAAO,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAU,EAAE;gBACzE,WAAW,EAAE,MAAM,CAAC,WAAW;gBAC/B,YAAY,EAAE,MAAM,CAAC,YAAY;gBACjC,iBAAiB,EAAE,MAAM,CAAC,iBAAiB;gBAC3C,MAAM,EAAE,CAAC;aACV,CAAC,CAAC;QACL,CAAC;QAED,KAAK,CAAC,aAAa;YACjB,MAAM,SAAS,CAAC,OAAO,CAAC,OAAO,CAAC,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAU,EAAE,EAAE,CAAC,CAAC;QACnF,CAAC;QAED,mBAAmB,CAAC,QAAQ;YAC1B,MAAM,IAAI,GAAI,OAAO,CAAC,SAAS,CAAC,mBAG9B,CAAC,EAAE,CAAC,CAAC;YAEP,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,QAAiB,EAAE,EAAE;gBACpC,MAAM,IAAI,GAAI,QAAoC,CAAC,UAAU,IAAI,qBAAqB,CAAC;gBACvF,QAAQ,CAAC;oBACP,UAAU,EAAE,eAAe,CAAC,IAAI,CAAC,IAAI,OAAO;oBAC5C,WAAW,EAAE,CAAC;oBACd,YAAY,EAAE,CAAC;oBACf,iBAAiB,EAAE,CAAC;iBACrB,CAAC,CAAC;YACL,CAAC,CAAC,CAAC;YAEH,OAAO,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;QAC7B,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { VlmClient } from '../types.js';
2
+ export declare function createHttpVlmClient(endpoint: string): VlmClient;
3
+ //# sourceMappingURL=vlm-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vlm-client.d.ts","sourceRoot":"","sources":["../../src/adapters/vlm-client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,CAmB/D"}
@@ -0,0 +1,19 @@
1
+ export function createHttpVlmClient(endpoint) {
2
+ return {
3
+ async query(image, prompt) {
4
+ const response = await fetch(endpoint, {
5
+ method: 'POST',
6
+ headers: { 'content-type': 'application/json' },
7
+ body: JSON.stringify({
8
+ imageBase64: image.toString('base64'),
9
+ prompt,
10
+ }),
11
+ });
12
+ if (!response.ok) {
13
+ throw new Error(`VLM query failed with status ${response.status}`);
14
+ }
15
+ return response.json();
16
+ },
17
+ };
18
+ }
19
+ //# sourceMappingURL=vlm-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vlm-client.js","sourceRoot":"","sources":["../../src/adapters/vlm-client.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,mBAAmB,CAAC,QAAgB;IAClD,OAAO;QACL,KAAK,CAAC,KAAK,CAAC,KAAa,EAAE,MAAc;YACvC,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,QAAQ,EAAE;gBACrC,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;gBAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;oBACnB,WAAW,EAAE,KAAK,CAAC,QAAQ,CAAC,QAAQ,CAAC;oBACrC,MAAM;iBACP,CAAC;aACH,CAAC,CAAC;YAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;gBACjB,MAAM,IAAI,KAAK,CAAC,gCAAgC,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;YACrE,CAAC;YAED,OAAO,QAAQ,CAAC,IAAI,EAAE,CAAC;QACzB,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,8 @@
1
+ import * as grpc from '@grpc/grpc-js';
2
+ export interface MavsdkClients {
3
+ action: grpc.Client & Record<string, (...args: unknown[]) => unknown>;
4
+ telemetry: grpc.Client & Record<string, (...args: unknown[]) => unknown>;
5
+ mission: grpc.Client & Record<string, (...args: unknown[]) => unknown>;
6
+ }
7
+ export declare function loadMavsdkClients(serverAddress: string): MavsdkClients;
8
+ //# sourceMappingURL=mavsdk-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mavsdk-client.d.ts","sourceRoot":"","sources":["../../src/grpc/mavsdk-client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,eAAe,CAAC;AA4BtC,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,CAAC;IACtE,SAAS,EAAE,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,CAAC;IACzE,OAAO,EAAE,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,CAAC;CACxE;AAED,wBAAgB,iBAAiB,CAAC,aAAa,EAAE,MAAM,GAAG,aAAa,CAkBtE"}
@@ -0,0 +1,34 @@
1
+ import * as grpc from '@grpc/grpc-js';
2
+ import * as protoLoader from '@grpc/proto-loader';
3
+ import path from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { getProtoPath } from 'google-proto-files';
6
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
7
+ const PROTO_ROOT = path.resolve(__dirname, '../../proto');
8
+ // getProtoPath('.') resolves to .../google-proto-files/google, so its parent
9
+ // is the include root that "google/protobuf/descriptor.proto" (imported by
10
+ // mavsdk_options.proto) resolves against.
11
+ const WELL_KNOWN_PROTO_ROOT = path.dirname(getProtoPath('.'));
12
+ function loadPackage(protoRelativePath) {
13
+ const packageDefinition = protoLoader.loadSync(path.join(PROTO_ROOT, protoRelativePath), {
14
+ keepCase: false,
15
+ longs: String,
16
+ enums: String,
17
+ defaults: true,
18
+ oneofs: true,
19
+ includeDirs: [PROTO_ROOT, WELL_KNOWN_PROTO_ROOT],
20
+ });
21
+ return grpc.loadPackageDefinition(packageDefinition);
22
+ }
23
+ export function loadMavsdkClients(serverAddress) {
24
+ const credentials = grpc.credentials.createInsecure();
25
+ const actionPkg = loadPackage('action/action.proto');
26
+ const telemetryPkg = loadPackage('telemetry/telemetry.proto');
27
+ const missionPkg = loadPackage('mission/mission.proto');
28
+ return {
29
+ action: new actionPkg.mavsdk.rpc.action.ActionService(serverAddress, credentials),
30
+ telemetry: new telemetryPkg.mavsdk.rpc.telemetry.TelemetryService(serverAddress, credentials),
31
+ mission: new missionPkg.mavsdk.rpc.mission.MissionService(serverAddress, credentials),
32
+ };
33
+ }
34
+ //# sourceMappingURL=mavsdk-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mavsdk-client.js","sourceRoot":"","sources":["../../src/grpc/mavsdk-client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,eAAe,CAAC;AACtC,OAAO,KAAK,WAAW,MAAM,oBAAoB,CAAC;AAClD,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAElD,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAC/D,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,aAAa,CAAC,CAAC;AAC1D,6EAA6E;AAC7E,2EAA2E;AAC3E,0CAA0C;AAC1C,MAAM,qBAAqB,GAAG,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC;AAE9D,SAAS,WAAW,CAAC,iBAAyB;IAC5C,MAAM,iBAAiB,GAAG,WAAW,CAAC,QAAQ,CAC5C,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,iBAAiB,CAAC,EACxC;QACE,QAAQ,EAAE,KAAK;QACf,KAAK,EAAE,MAAM;QACb,KAAK,EAAE,MAAM;QACb,QAAQ,EAAE,IAAI;QACd,MAAM,EAAE,IAAI;QACZ,WAAW,EAAE,CAAC,UAAU,EAAE,qBAAqB,CAAC;KACjD,CACF,CAAC;IACF,OAAO,IAAI,CAAC,qBAAqB,CAAC,iBAAiB,CAAC,CAAC;AACvD,CAAC;AAQD,MAAM,UAAU,iBAAiB,CAAC,aAAqB;IACrD,MAAM,WAAW,GAAG,IAAI,CAAC,WAAW,CAAC,cAAc,EAAE,CAAC;IAEtD,MAAM,SAAS,GAAG,WAAW,CAAC,qBAAqB,CAElD,CAAC;IACF,MAAM,YAAY,GAAG,WAAW,CAAC,2BAA2B,CAE3D,CAAC;IACF,MAAM,UAAU,GAAG,WAAW,CAAC,uBAAuB,CAErD,CAAC;IAEF,OAAO;QACL,MAAM,EAAE,IAAI,SAAS,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,aAAa,CAAC,aAAa,EAAE,WAAW,CAA4B;QAC5G,SAAS,EAAE,IAAI,YAAY,CAAC,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,gBAAgB,CAAC,aAAa,EAAE,WAAW,CAA+B;QAC3H,OAAO,EAAE,IAAI,UAAU,CAAC,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,cAAc,CAAC,aAAa,EAAE,WAAW,CAA6B;KAClH,CAAC;AACJ,CAAC"}
@@ -0,0 +1,14 @@
1
+ export { createOrchestrator } from './orchestrator.js';
2
+ export type { OrchestratorInstance } from './orchestrator.js';
3
+ export { createMavsdkVehicleAdapter } from './adapters/mavsdk-adapter.js';
4
+ export { createHttpVlmClient } from './adapters/vlm-client.js';
5
+ export { createMirilVlmClient } from './vlm/miril-client.js';
6
+ export type { MirilPromptFamily, MirilVlmClientOptions } from './vlm/miril-client.js';
7
+ export { createLandingZoneCheckMode } from './modes/landing-zone-check.js';
8
+ export type { VisionAssistMode } from './modes/vision-assist-mode.js';
9
+ export { loadMavsdkClients } from './grpc/mavsdk-client.js';
10
+ export type { MavsdkClients } from './grpc/mavsdk-client.js';
11
+ export { createStatusServer } from './server/status-server.js';
12
+ export type { OrchestratorStatus, StatusServer, StatusServerOptions } from './server/status-server.js';
13
+ export type { VehicleState, LandingCandidate, ModeVerdict, ConfirmDecision, VehicleAdapter, CameraSource, VlmClient, OrchestratorContext, OrchestratorConfig, ConfirmPolicy, } from './types.js';
14
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,YAAY,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAC9D,OAAO,EAAE,0BAA0B,EAAE,MAAM,8BAA8B,CAAC;AAC1E,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC/D,OAAO,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAC7D,YAAY,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AACtF,OAAO,EAAE,0BAA0B,EAAE,MAAM,+BAA+B,CAAC;AAC3E,YAAY,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACtE,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAC5D,YAAY,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAC/D,YAAY,EAAE,kBAAkB,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AACvG,YAAY,EACV,YAAY,EACZ,gBAAgB,EAChB,WAAW,EACX,eAAe,EACf,cAAc,EACd,YAAY,EACZ,SAAS,EACT,mBAAmB,EACnB,kBAAkB,EAClB,aAAa,GACd,MAAM,YAAY,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,8 @@
1
+ export { createOrchestrator } from './orchestrator.js';
2
+ export { createMavsdkVehicleAdapter } from './adapters/mavsdk-adapter.js';
3
+ export { createHttpVlmClient } from './adapters/vlm-client.js';
4
+ export { createMirilVlmClient } from './vlm/miril-client.js';
5
+ export { createLandingZoneCheckMode } from './modes/landing-zone-check.js';
6
+ export { loadMavsdkClients } from './grpc/mavsdk-client.js';
7
+ export { createStatusServer } from './server/status-server.js';
8
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAEvD,OAAO,EAAE,0BAA0B,EAAE,MAAM,8BAA8B,CAAC;AAC1E,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC/D,OAAO,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAE7D,OAAO,EAAE,0BAA0B,EAAE,MAAM,+BAA+B,CAAC;AAE3E,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAE5D,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { VisionAssistMode } from './vision-assist-mode.js';
2
+ export declare function createLandingZoneCheckMode(prompt?: string): VisionAssistMode;
3
+ //# sourceMappingURL=landing-zone-check.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"landing-zone-check.d.ts","sourceRoot":"","sources":["../../src/modes/landing-zone-check.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAwBhE,wBAAgB,0BAA0B,CAAC,MAAM,GAAE,MAAuB,GAAG,gBAAgB,CAyC5F"}
@@ -0,0 +1,52 @@
1
+ const DEFAULT_PROMPT = 'Assess whether this is a safe landing zone. If unsafe, point to a safer nearby spot.';
2
+ function toCandidate(raw, currentLat, currentLon) {
3
+ if (!raw.point_2d)
4
+ return undefined;
5
+ // point_2d is [y, x] normalized image coordinates from the VLM's operational_coordinate_v2
6
+ // response family. Converting that to a real lat/lon offset requires camera intrinsics,
7
+ // altitude, and gimbal angle, which are supplied by the integrator's CameraSource, not this
8
+ // package. This mode reports the raw point plus a placeholder offset; integrators wire real
9
+ // ground-plane projection through their own CameraSource/VlmClient before this is production-safe.
10
+ return {
11
+ latitudeDeg: currentLat,
12
+ longitudeDeg: currentLon,
13
+ description: raw.description ?? 'candidate landing spot',
14
+ };
15
+ }
16
+ export function createLandingZoneCheckMode(prompt = DEFAULT_PROMPT) {
17
+ let lastCandidate;
18
+ return {
19
+ id: 'landing-zone-check',
20
+ shouldTrigger(vehicleState) {
21
+ return vehicleState.flightMode === 'LAND';
22
+ },
23
+ async evaluate(image, ctx) {
24
+ await ctx.vehicle.hold();
25
+ let raw;
26
+ try {
27
+ raw = (await ctx.vlm.query(image, prompt));
28
+ }
29
+ catch (err) {
30
+ return { status: 'unknown', reason: err instanceof Error ? err.message : String(err) };
31
+ }
32
+ if (raw.status === 'safe') {
33
+ lastCandidate = undefined;
34
+ return { status: 'safe' };
35
+ }
36
+ lastCandidate = toCandidate(raw, 0, 0);
37
+ return { status: 'unsafe', candidate: lastCandidate };
38
+ },
39
+ async onDecision(decision, ctx) {
40
+ if (decision.action === 'accept-candidate' && lastCandidate) {
41
+ await ctx.vehicle.gotoLocation({
42
+ latitudeDeg: lastCandidate.latitudeDeg,
43
+ longitudeDeg: lastCandidate.longitudeDeg,
44
+ absoluteAltitudeM: 0,
45
+ });
46
+ return;
47
+ }
48
+ await ctx.vehicle.resumeMission();
49
+ },
50
+ };
51
+ }
52
+ //# sourceMappingURL=landing-zone-check.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"landing-zone-check.js","sourceRoot":"","sources":["../../src/modes/landing-zone-check.ts"],"names":[],"mappings":"AAGA,MAAM,cAAc,GAAG,sFAAsF,CAAC;AAQ9G,SAAS,WAAW,CAAC,GAAmB,EAAE,UAAkB,EAAE,UAAkB;IAC9E,IAAI,CAAC,GAAG,CAAC,QAAQ;QAAE,OAAO,SAAS,CAAC;IACpC,2FAA2F;IAC3F,wFAAwF;IACxF,4FAA4F;IAC5F,4FAA4F;IAC5F,mGAAmG;IACnG,OAAO;QACL,WAAW,EAAE,UAAU;QACvB,YAAY,EAAE,UAAU;QACxB,WAAW,EAAE,GAAG,CAAC,WAAW,IAAI,wBAAwB;KACzD,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,0BAA0B,CAAC,SAAiB,cAAc;IACxE,IAAI,aAA2C,CAAC;IAEhD,OAAO;QACL,EAAE,EAAE,oBAAoB;QAExB,aAAa,CAAC,YAAY;YACxB,OAAO,YAAY,CAAC,UAAU,KAAK,MAAM,CAAC;QAC5C,CAAC;QAED,KAAK,CAAC,QAAQ,CAAC,KAAK,EAAE,GAAG;YACvB,MAAM,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;YAEzB,IAAI,GAAmB,CAAC;YACxB,IAAI,CAAC;gBACH,GAAG,GAAG,CAAC,MAAM,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,EAAE,MAAM,CAAC,CAAmB,CAAC;YAC/D,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;YACzF,CAAC;YAED,IAAI,GAAG,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;gBAC1B,aAAa,GAAG,SAAS,CAAC;gBAC1B,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;YAC5B,CAAC;YAED,aAAa,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;YACvC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,aAAa,EAAE,CAAC;QACxD,CAAC;QAED,KAAK,CAAC,UAAU,CAAC,QAAyB,EAAE,GAAwB;YAClE,IAAI,QAAQ,CAAC,MAAM,KAAK,kBAAkB,IAAI,aAAa,EAAE,CAAC;gBAC5D,MAAM,GAAG,CAAC,OAAO,CAAC,YAAY,CAAC;oBAC7B,WAAW,EAAE,aAAa,CAAC,WAAW;oBACtC,YAAY,EAAE,aAAa,CAAC,YAAY;oBACxC,iBAAiB,EAAE,CAAC;iBACrB,CAAC,CAAC;gBACH,OAAO;YACT,CAAC;YACD,MAAM,GAAG,CAAC,OAAO,CAAC,aAAa,EAAE,CAAC;QACpC,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,8 @@
1
+ import type { ConfirmDecision, ModeVerdict, OrchestratorContext, VehicleState } from '../types.js';
2
+ export interface VisionAssistMode {
3
+ readonly id: string;
4
+ shouldTrigger(vehicleState: VehicleState): boolean;
5
+ evaluate(image: Buffer, ctx: OrchestratorContext): Promise<ModeVerdict>;
6
+ onDecision(decision: ConfirmDecision, ctx: OrchestratorContext): Promise<void>;
7
+ }
8
+ //# sourceMappingURL=vision-assist-mode.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vision-assist-mode.d.ts","sourceRoot":"","sources":["../../src/modes/vision-assist-mode.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,WAAW,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEnG,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,YAAY,EAAE,YAAY,GAAG,OAAO,CAAC;IACnD,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,mBAAmB,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;IACxE,UAAU,CAAC,QAAQ,EAAE,eAAe,EAAE,GAAG,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAChF"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=vision-assist-mode.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vision-assist-mode.js","sourceRoot":"","sources":["../../src/modes/vision-assist-mode.ts"],"names":[],"mappings":""}
@@ -0,0 +1,13 @@
1
+ import type { OrchestratorConfig, VehicleAdapter } from './types.js';
2
+ import type { VisionAssistMode } from './modes/vision-assist-mode.js';
3
+ export interface OrchestratorInstance {
4
+ start(): Promise<void>;
5
+ stop(): Promise<void>;
6
+ }
7
+ type FullOrchestratorConfig = OrchestratorConfig & {
8
+ modes: VisionAssistMode[];
9
+ vehicleAdapterOverride?: VehicleAdapter;
10
+ };
11
+ export declare function createOrchestrator(config: FullOrchestratorConfig): OrchestratorInstance;
12
+ export {};
13
+ //# sourceMappingURL=orchestrator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"orchestrator.d.ts","sourceRoot":"","sources":["../src/orchestrator.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,kBAAkB,EAAE,cAAc,EAA4C,MAAM,YAAY,CAAC;AAC/G,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAEtE,MAAM,WAAW,oBAAoB;IACnC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACvB;AAED,KAAK,sBAAsB,GAAG,kBAAkB,GAAG;IACjD,KAAK,EAAE,gBAAgB,EAAE,CAAC;IAC1B,sBAAsB,CAAC,EAAE,cAAc,CAAC;CACzC,CAAC;AAIF,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,sBAAsB,GAAG,oBAAoB,CAwHvF"}
@@ -0,0 +1,106 @@
1
+ import { loadMavsdkClients } from './grpc/mavsdk-client.js';
2
+ import { createMavsdkVehicleAdapter } from './adapters/mavsdk-adapter.js';
3
+ import { createStatusServer } from './server/status-server.js';
4
+ const MAX_REEVALUATION_ATTEMPTS = 3;
5
+ export function createOrchestrator(config) {
6
+ const mavsdkClients = config.vehicleAdapterOverride
7
+ ? null
8
+ : loadMavsdkClients(config.mavsdkServerAddress);
9
+ const vehicle = config.vehicleAdapterOverride ?? createMavsdkVehicleAdapter(mavsdkClients);
10
+ const statusServer = createStatusServer({
11
+ port: config.statusServerPort,
12
+ onConfirm: (decision) => handleConfirm(decision),
13
+ });
14
+ let activeMode = null;
15
+ let unsubscribe = null;
16
+ let pendingResolve = null;
17
+ function handleConfirm(decision) {
18
+ if (!pendingResolve)
19
+ return;
20
+ const resolve = pendingResolve;
21
+ pendingResolve = null;
22
+ resolve(decision);
23
+ }
24
+ async function evaluateOnce(mode, ctx) {
25
+ const image = await config.cameraSource.captureFrame();
26
+ const verdict = await mode.evaluate(image, ctx);
27
+ if (verdict.status === 'safe') {
28
+ await vehicle.resumeMission();
29
+ return 'safe';
30
+ }
31
+ if (config.confirmPolicy === 'autonomous') {
32
+ await mode.onDecision({ action: 'accept-candidate' }, ctx);
33
+ return 'repositioned';
34
+ }
35
+ statusServer.setStatus({
36
+ state: 'awaiting-confirm',
37
+ verdict: verdict.status === 'unsafe'
38
+ ? { status: 'unsafe', candidate: verdict.candidate }
39
+ : { status: 'unknown' },
40
+ });
41
+ const decision = await new Promise((resolve) => {
42
+ pendingResolve = resolve;
43
+ });
44
+ await mode.onDecision(decision, ctx);
45
+ if (decision.action === 'reject') {
46
+ return 'safe'; // resolved: onDecision already resumed the original landing
47
+ }
48
+ return 'repositioned';
49
+ }
50
+ async function runMode(mode) {
51
+ activeMode = mode;
52
+ statusServer.setStatus({ state: 'holding' });
53
+ const ctx = { vehicle, camera: config.cameraSource, vlm: config.vlmClient };
54
+ for (let attempt = 1; attempt <= MAX_REEVALUATION_ATTEMPTS; attempt += 1) {
55
+ const outcome = await evaluateOnce(mode, ctx);
56
+ if (outcome === 'safe') {
57
+ statusServer.setStatus({ state: 'idle' });
58
+ activeMode = null;
59
+ return;
60
+ }
61
+ // outcome === 'repositioned': loop again to re-evaluate the new position,
62
+ // up to MAX_REEVALUATION_ATTEMPTS total.
63
+ }
64
+ statusServer.setStatus({ state: 'no-safe-alternative' });
65
+ activeMode = null;
66
+ }
67
+ async function verifyMavsdkServerReachable() {
68
+ if (!mavsdkClients)
69
+ return; // test/mock adapters skip the real connectivity check
70
+ // isMissionFinished is documented as a SYNC, read-only RPC in mission.proto - safe to use
71
+ // purely as a "did the server answer" probe, unlike resumeMission/hold/gotoLocation which
72
+ // all have real side effects on the vehicle.
73
+ await new Promise((resolve, reject) => {
74
+ const timeout = setTimeout(() => reject(new Error(`Could not reach mavsdk_server at ${config.mavsdkServerAddress} within 5s`)), 5000);
75
+ const isMissionFinished = mavsdkClients.mission.isMissionFinished;
76
+ isMissionFinished({}, (err) => {
77
+ clearTimeout(timeout);
78
+ if (err)
79
+ reject(err);
80
+ else
81
+ resolve();
82
+ });
83
+ });
84
+ }
85
+ return {
86
+ async start() {
87
+ await verifyMavsdkServerReachable();
88
+ await statusServer.start();
89
+ unsubscribe = vehicle.subscribeFlightMode((state) => {
90
+ if (activeMode)
91
+ return;
92
+ for (const mode of config.modes) {
93
+ if (mode.shouldTrigger(state)) {
94
+ void runMode(mode);
95
+ break;
96
+ }
97
+ }
98
+ });
99
+ },
100
+ async stop() {
101
+ unsubscribe?.();
102
+ await statusServer.stop();
103
+ },
104
+ };
105
+ }
106
+ //# sourceMappingURL=orchestrator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"orchestrator.js","sourceRoot":"","sources":["../src/orchestrator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAsB,MAAM,yBAAyB,CAAC;AAChF,OAAO,EAAE,0BAA0B,EAAE,MAAM,8BAA8B,CAAC;AAC1E,OAAO,EAAE,kBAAkB,EAAqB,MAAM,2BAA2B,CAAC;AAclF,MAAM,yBAAyB,GAAG,CAAC,CAAC;AAEpC,MAAM,UAAU,kBAAkB,CAAC,MAA8B;IAC/D,MAAM,aAAa,GAAyB,MAAM,CAAC,sBAAsB;QACvE,CAAC,CAAC,IAAI;QACN,CAAC,CAAC,iBAAiB,CAAC,MAAM,CAAC,mBAAmB,CAAC,CAAC;IAClD,MAAM,OAAO,GACX,MAAM,CAAC,sBAAsB,IAAI,0BAA0B,CAAC,aAAc,CAAC,CAAC;IAE9E,MAAM,YAAY,GAAiB,kBAAkB,CAAC;QACpD,IAAI,EAAE,MAAM,CAAC,gBAAgB;QAC7B,SAAS,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,aAAa,CAAC,QAAQ,CAAC;KACjD,CAAC,CAAC;IAEH,IAAI,UAAU,GAA4B,IAAI,CAAC;IAC/C,IAAI,WAAW,GAAwB,IAAI,CAAC;IAC5C,IAAI,cAAc,GAAiD,IAAI,CAAC;IAExE,SAAS,aAAa,CAAC,QAAyB;QAC9C,IAAI,CAAC,cAAc;YAAE,OAAO;QAC5B,MAAM,OAAO,GAAG,cAAc,CAAC;QAC/B,cAAc,GAAG,IAAI,CAAC;QACtB,OAAO,CAAC,QAAQ,CAAC,CAAC;IACpB,CAAC;IAED,KAAK,UAAU,YAAY,CACzB,IAAsB,EACtB,GAAsE;QAEtE,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC,YAAY,CAAC,YAAY,EAAE,CAAC;QACvD,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;QAEhD,IAAI,OAAO,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;YAC9B,MAAM,OAAO,CAAC,aAAa,EAAE,CAAC;YAC9B,OAAO,MAAM,CAAC;QAChB,CAAC;QAED,IAAI,MAAM,CAAC,aAAa,KAAK,YAAY,EAAE,CAAC;YAC1C,MAAM,IAAI,CAAC,UAAU,CAAC,EAAE,MAAM,EAAE,kBAAkB,EAAE,EAAE,GAAG,CAAC,CAAC;YAC3D,OAAO,cAAc,CAAC;QACxB,CAAC;QAED,YAAY,CAAC,SAAS,CAAC;YACrB,KAAK,EAAE,kBAAkB;YACzB,OAAO,EAAE,OAAO,CAAC,MAAM,KAAK,QAAQ;gBAClC,CAAC,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,OAAO,CAAC,SAAS,EAAE;gBACpD,CAAC,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE;SAC1B,CAAC,CAAC;QAEH,MAAM,QAAQ,GAAG,MAAM,IAAI,OAAO,CAAkB,CAAC,OAAO,EAAE,EAAE;YAC9D,cAAc,GAAG,OAAO,CAAC;QAC3B,CAAC,CAAC,CAAC;QAEH,MAAM,IAAI,CAAC,UAAU,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;QAErC,IAAI,QAAQ,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;YACjC,OAAO,MAAM,CAAC,CAAC,4DAA4D;QAC7E,CAAC;QACD,OAAO,cAAc,CAAC;IACxB,CAAC;IAED,KAAK,UAAU,OAAO,CAAC,IAAsB;QAC3C,UAAU,GAAG,IAAI,CAAC;QAClB,YAAY,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC;QAE7C,MAAM,GAAG,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,YAAY,EAAE,GAAG,EAAE,MAAM,CAAC,SAAS,EAAE,CAAC;QAE5E,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,IAAI,yBAAyB,EAAE,OAAO,IAAI,CAAC,EAAE,CAAC;YACzE,MAAM,OAAO,GAAG,MAAM,YAAY,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;YAC9C,IAAI,OAAO,KAAK,MAAM,EAAE,CAAC;gBACvB,YAAY,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC;gBAC1C,UAAU,GAAG,IAAI,CAAC;gBAClB,OAAO;YACT,CAAC;YACD,0EAA0E;YAC1E,yCAAyC;QAC3C,CAAC;QAED,YAAY,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,qBAAqB,EAAE,CAAC,CAAC;QACzD,UAAU,GAAG,IAAI,CAAC;IACpB,CAAC;IAED,KAAK,UAAU,2BAA2B;QACxC,IAAI,CAAC,aAAa;YAAE,OAAO,CAAC,sDAAsD;QAClF,0FAA0F;QAC1F,0FAA0F;QAC1F,6CAA6C;QAC7C,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YAC1C,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,CAC/C,oCAAoC,MAAM,CAAC,mBAAmB,YAAY,CAC3E,CAAC,EAAE,IAAI,CAAC,CAAC;YACV,MAAM,iBAAiB,GAAG,aAAa,CAAC,OAAO,CAAC,iBAGvC,CAAC;YACV,iBAAiB,CAAC,EAAE,EAAE,CAAC,GAAG,EAAE,EAAE;gBAC5B,YAAY,CAAC,OAAO,CAAC,CAAC;gBACtB,IAAI,GAAG;oBAAE,MAAM,CAAC,GAAG,CAAC,CAAC;;oBAChB,OAAO,EAAE,CAAC;YACjB,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC;IAED,OAAO;QACL,KAAK,CAAC,KAAK;YACT,MAAM,2BAA2B,EAAE,CAAC;YACpC,MAAM,YAAY,CAAC,KAAK,EAAE,CAAC;YAC3B,WAAW,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAC,KAAK,EAAE,EAAE;gBAClD,IAAI,UAAU;oBAAE,OAAO;gBACvB,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;oBAChC,IAAI,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC;wBAC9B,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;wBACnB,MAAM;oBACR,CAAC;gBACH,CAAC;YACH,CAAC,CAAC,CAAC;QACL,CAAC;QACD,KAAK,CAAC,IAAI;YACR,WAAW,EAAE,EAAE,CAAC;YAChB,MAAM,YAAY,CAAC,IAAI,EAAE,CAAC;QAC5B,CAAC;KACF,CAAC;AACJ,CAAC"}