@stim-cli/server 1.16.0 → 1.17.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/README.md +129 -35
- package/dist/{protocol-BHSl7bQ_.mjs → protocol-wBQmYsnp.mjs} +120 -10
- package/dist/protocol.mjs +1 -1
- package/dist/protocol.schema.json +261 -1
- package/dist/stim-frames/DuoModelAsset.swift +34 -0
- package/dist/stim-frames/DuoModelView.swift +1 -9
- package/dist/{stim-server-CvqlgkBK.mjs → stim-server-CP50F1Y_.mjs} +3299 -2758
- package/dist/stim-server.mjs +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ The design is in
|
|
|
16
16
|
stim-server [--port <n>] # serve paired clients, port 7787 by default
|
|
17
17
|
stim-server pair [--port <n>] [--control]
|
|
18
18
|
# print a single-use pairing payload
|
|
19
|
-
stim-server devices [list] # list
|
|
19
|
+
stim-server devices [list] # list pairings, approvals, requests and hosted sessions
|
|
20
20
|
stim-server devices grant <id> --control|--read|--build|--device-host
|
|
21
21
|
# let a paired device run actions, or only read;
|
|
22
22
|
# --build approves builds; --device-host approves device hosting
|
|
@@ -331,16 +331,15 @@ tokens.
|
|
|
331
331
|
Device hosting has a separate `device-host` capability. An approved client can
|
|
332
332
|
reserve, boot, reconnect to and stop its own iOS simulator or Android emulator
|
|
333
333
|
through the protocol. It can deliver, install and launch a compatible iOS app
|
|
334
|
-
bundle, Android APK or prebuilt macOS app, stream the iOS simulator or macOS app and control it.
|
|
335
|
-
|
|
336
|
-
Android Metro and viewing remain in [#2266](https://github.com/appandflow/stim/issues/2266).
|
|
334
|
+
bundle, Android APK or prebuilt macOS app, stream the iOS simulator, Android emulator or macOS app and control it. Hosted
|
|
335
|
+
iOS and Android apps connect back to Metro on the client Mac. Automatic placement remains in [#2266](https://github.com/appandflow/stim/issues/2266).
|
|
337
336
|
|
|
338
337
|
The Stim client can name expected hosts with
|
|
339
338
|
`stim settings set hosting.machines '["<mac>"]'` and request access with
|
|
340
339
|
`stim doctor --fix`. It stores a separate private credential in
|
|
341
340
|
`$STIM_HOME/device-host-machines.json`, pins the worker's tailnet node, and
|
|
342
341
|
reports approval under `deviceHosts` in doctor JSON. Plain doctor makes no
|
|
343
|
-
approval request.
|
|
342
|
+
approval request. Use `stim ios --remote <machine>` or `stim android --remote <machine>` for explicit placement.
|
|
344
343
|
|
|
345
344
|
A client on the tailnet sends `hello` with
|
|
346
345
|
`auth: { "request": "device-host", "deviceName": "Laptop" }`. As with a build
|
|
@@ -365,6 +364,31 @@ from phone pairings and build clients. A hosting token grants no `read`,
|
|
|
365
364
|
read, control and build tokens cannot gain hosting through `devices grant`.
|
|
366
365
|
Loopback requests and `pair --device-host` are refused.
|
|
367
366
|
|
|
367
|
+
### Hosted here
|
|
368
|
+
|
|
369
|
+
`stim-server devices` adds a **Hosted here** block after pairings and approvals,
|
|
370
|
+
with the client name, platform, device label, installed app bundle id (or `-`),
|
|
371
|
+
state and start time. `--json` adds `hostedSessions` next to `devices`, including
|
|
372
|
+
an empty array when there are none. Rows are newest first; plain stopped
|
|
373
|
+
sessions are omitted, while parked and unresolved sessions stay visible.
|
|
374
|
+
A parked row uses its parking time. A removed client's name falls back to its id.
|
|
375
|
+
The command reads the host journal directly and needs no server connection.
|
|
376
|
+
|
|
377
|
+
`device-host.sessions` takes no params (or `{}`) and returns `{ sessions }`.
|
|
378
|
+
Each row has `id`, `client: { id, name }`, `platform`, nullable `device` and `app`,
|
|
379
|
+
`state`, `parked`, `since` (ISO time), and `workspace`.
|
|
380
|
+
`device-host.sessions.stop` takes `{ "session": "<id>" }` and returns
|
|
381
|
+
`{ "id": "<id>", "state": "stopping" | "stopped" }`. It stops any client's
|
|
382
|
+
session through the existing stop path, closing Metro, view, agent and logs
|
|
383
|
+
before owned-device teardown. Repeating a stopped session returns `stopped`;
|
|
384
|
+
an absent id gets `unknown-session`.
|
|
385
|
+
Both requests require an authenticated loopback Desktop connection with local
|
|
386
|
+
identity and control access. Local read-only connections, paired phones and
|
|
387
|
+
remote hosting clients get `forbidden`. A session whose active owner is not
|
|
388
|
+
attached to this server reads as `unknown`; stop reconciles it through the same
|
|
389
|
+
ownership checks. The approved-client `device-host.stop` remains limited to
|
|
390
|
+
that client's own sessions.
|
|
391
|
+
|
|
368
392
|
### Hosted availability offers
|
|
369
393
|
|
|
370
394
|
Before reserving, an approved hosting client can send:
|
|
@@ -572,7 +596,7 @@ session before starting another transfer. Manifest, file and partial blobs are s
|
|
|
572
596
|
by attempts of one session in its content-addressed store; offers recheck complete
|
|
573
597
|
blob digests and request only missing bytes.
|
|
574
598
|
|
|
575
|
-
An iOS simulator or macOS session can take its content from a build this Mac ran instead: after
|
|
599
|
+
An iOS simulator, Android emulator or macOS session can take its content from a build this Mac ran instead: after
|
|
576
600
|
the manifest arrives, send `device-host.app.handoff` with
|
|
577
601
|
`{session, attempt, build: {handoff, sha256}}`, where `handoff` is the token
|
|
578
602
|
and `sha256` the archive digest from that job's `build.artifact` answer. The
|
|
@@ -583,7 +607,10 @@ declares links, whose directory resolves inside the staged bundle and whose
|
|
|
583
607
|
bytes match their manifest digest; it answers `{files, bytes}` for what it
|
|
584
608
|
took. The token is spent once the handoff starts. Re-offer and upload whatever
|
|
585
609
|
is still missing; an older server answers `unknown-method`. For iOS, check the
|
|
586
|
-
`hosted-ios-data` hello feature before requesting handoff;
|
|
610
|
+
`hosted-ios-data` hello feature before requesting handoff; for Android check
|
|
611
|
+
`hosted-android-data`. Without the feature, upload. Android takes only a regular
|
|
612
|
+
APK file matching the single `App.apk` manifest entry. Handoff failure falls back
|
|
613
|
+
to upload. Reused APK blobs skip transfer, while every new attempt still installs.
|
|
587
614
|
|
|
588
615
|
After every digest is verified, call `device-host.app.launch` with
|
|
589
616
|
`{session, attempt}`. Poll `device-host.app.attach` for `installed` or `unknown`.
|
|
@@ -619,11 +646,31 @@ signature or installation conflict refuses; hosting does not uninstall an
|
|
|
619
646
|
existing app to resolve it.
|
|
620
647
|
|
|
621
648
|
Release launch reports `true` only after observing a live package process.
|
|
622
|
-
Development launch remains `unverified
|
|
623
|
-
|
|
649
|
+
Development launch remains `unverified` until the client Metro bridge provides
|
|
650
|
+
bundle evidence. Replaying an installed app attempt does
|
|
624
651
|
not install or launch again. Owner loss, stop, revocation and uncertain outcomes
|
|
625
|
-
retain the same reconciliation rules as iOS.
|
|
626
|
-
|
|
652
|
+
retain the same reconciliation rules as iOS.
|
|
653
|
+
|
|
654
|
+
With `hosting.agentDriver: agent-device`, each installed Android session gets
|
|
655
|
+
its own daemon and child-aware claim under `device-host/sessions/<session>/agent-device.claims`.
|
|
656
|
+
Both Macs need agent-device 0.21.22 or later. The host verifies the serial policy
|
|
657
|
+
digest and `android-instance` backend and advertises `hosted-android-agent`.
|
|
658
|
+
`device-host.app.attach` and session attach carry a session grant with
|
|
659
|
+
`android:mobile:<serial>` and the same token and client-node checks as iOS.
|
|
660
|
+
The client writes a 0600 remote config under `hosted-android/<slot>/` and reports
|
|
661
|
+
it as `android.host.agent`. Use `agent-device <command> --remote-config <file>`.
|
|
662
|
+
Missing or unsupported agent-device reports `driver: none` with its reason.
|
|
663
|
+
|
|
664
|
+
The policy pins the ledger-owned serial and filters `devices` to that emulator.
|
|
665
|
+
Inspection and interaction commands match iOS. Installs, reinstall, uninstall,
|
|
666
|
+
boot/shutdown, `close --shutdown`, record, logs, uploads, other serials (including
|
|
667
|
+
nested batches and leases), and host paths are refused. Ambient fields are
|
|
668
|
+
stripped and replaced with host-owned platform, serial and lease scope.
|
|
669
|
+
Stim installs apps. Android snapshots use one-shot helpers without adb forwards;
|
|
670
|
+
after daemon exit the host stops and verifies only the two fixed agent-device
|
|
671
|
+
helper packages on that serial. Their APKs leave with emulator deletion.
|
|
672
|
+
Reinstall, stop, revocation and close await agent teardown before native work;
|
|
673
|
+
unresolved agent claims refuse replacement and deletion after restart too.
|
|
627
674
|
|
|
628
675
|
### Hosted macOS app sessions
|
|
629
676
|
|
|
@@ -672,12 +719,12 @@ service despite the isolated home. Stop removes the app home and delivered bytes
|
|
|
672
719
|
from every app attempt, keeping logs and receipts so `app.attach` can still report
|
|
673
720
|
the recorded state.
|
|
674
721
|
|
|
675
|
-
Once a
|
|
722
|
+
Once a hosted app is `installed`, `device-host.app.launch` and
|
|
676
723
|
`device-host.app.attach` return the agent grant the host's driver issued for that
|
|
677
724
|
session, or `agent: { driver: 'none' }` while no driver runs or it cannot scope
|
|
678
725
|
the app; the driver's reason, if any, is the result's `notice`. The field is
|
|
679
726
|
built for the response and never stored in the receipt or journal; it is absent
|
|
680
|
-
before installation
|
|
727
|
+
before installation and after stop. The worker
|
|
681
728
|
reports the app's pid, `DeviceHost` registers the running app with the agent
|
|
682
729
|
driver before the receipt reads `installed`, and ends that registration when the
|
|
683
730
|
session stops, is revoked or the server closes.
|
|
@@ -686,9 +733,19 @@ session stops, is revoked or the server closes.
|
|
|
686
733
|
the NDJSON records the session's macOS app wrote to its captured log (stdout and
|
|
687
734
|
stderr as `client` records, and its exit). iOS sessions capture app-filtered native
|
|
688
735
|
logs as `device` records through bounded `simctl log show` worker queries, with
|
|
689
|
-
a persisted time checkpoint in the isolated home.
|
|
736
|
+
a persisted time checkpoint in the isolated home. Android sessions use
|
|
737
|
+
`logcat -d -v epoch -T <epoch> --pid <pid>` on the exact ledger-owned serial after
|
|
738
|
+
re-verifying its AVD name. Each collection resolves `pidof <package>` and also
|
|
739
|
+
queries the last observed PID of that app attempt, to drain a crash or restart.
|
|
740
|
+
Without any observed PID, logs cannot be attributed to the app. PID-based logcat
|
|
741
|
+
cannot distinguish historical reuse of an exited PID. Output is bounded
|
|
742
|
+
by the executor's 64 MiB limit per command and the collection's ten-second budget;
|
|
743
|
+
an oversized query retries the recent tail and emits a `device` warning naming
|
|
744
|
+
the dropped interval. Logcat entries already evicted from its finite buffer are
|
|
745
|
+
unavailable. Both native collectors use the same checkpoint and NDJSON writer.
|
|
746
|
+
Windows adapt to the backlog,
|
|
690
747
|
overlap by five seconds and de-duplicate event identities. `checkpoint` is the last
|
|
691
|
-
completed
|
|
748
|
+
completed native window in epoch milliseconds. `log show` reads persisted entries;
|
|
692
749
|
info-level entries and persistence delayed beyond the overlap may be unavailable.
|
|
693
750
|
The worker holds a separate child-aware log claim. Queries coalesce onto an
|
|
694
751
|
in-flight collection or read captured files; collection starts at most once every
|
|
@@ -713,8 +770,9 @@ byte offset after the last complete line read; pass the previous result's cursor
|
|
|
713
770
|
receive only newer records, and repeat while `more` is true. Without a cursor, each
|
|
714
771
|
file starts at most 4 MiB before its end. A page holds at most 1 MiB; a file that
|
|
715
772
|
rotated since the cursor is read from the end of its previous generation. The
|
|
716
|
-
session's own client only, for iOS and macOS sessions, and also after the session stopped,
|
|
717
|
-
because stop keeps captured logs. iOS clients check hello feature `hosted-ios-data
|
|
773
|
+
session's own client only, for iOS, Android and macOS sessions, and also after the session stopped,
|
|
774
|
+
because stop keeps captured logs. iOS clients check hello feature `hosted-ios-data`
|
|
775
|
+
and Android clients check `hosted-android-data`;
|
|
718
776
|
without it, they show an update note and copied logs. Servers that predate macOS queries answer `forbidden` or
|
|
719
777
|
`unknown-method`.
|
|
720
778
|
|
|
@@ -731,10 +789,18 @@ gateway accepts only that worker address and authenticates each connection
|
|
|
731
789
|
before forwarding to the fixed local Metro port. It never targets a client
|
|
732
790
|
supplied URL. Close the gateway when its session ends.
|
|
733
791
|
|
|
734
|
-
Call `device-host.metro.open` with `{session, gatewayPort, secret}` on the
|
|
792
|
+
Call `device-host.metro.open` with `{session, gatewayPort, secret, clientMetroPort?}` on the
|
|
735
793
|
approved hosted connection. The server connects only to that connection's
|
|
736
794
|
authenticated tailnet peer and returns `{port}` for the worker's loopback
|
|
737
|
-
endpoint.
|
|
795
|
+
endpoint. `clientMetroPort` is required for Android and validated as an integer
|
|
796
|
+
from 1 through 65535. The session records it alongside the bridge port. Android
|
|
797
|
+
development installation reverses `tcp:<clientMetroPort>` to `tcp:<port>` on
|
|
798
|
+
the exact ledger-owned serial and sets `debug_http_host` to
|
|
799
|
+
`localhost:<clientMetroPort>`. Reopening an installed development session runs
|
|
800
|
+
a fixed reverse worker under its child-aware claim, serialized with install
|
|
801
|
+
and stop, so a client reload restores the mapping after a host adb server
|
|
802
|
+
restart. The worker never restarts the shared adb server. iOS development
|
|
803
|
+
installation uses the bridge port for `RCT_jsLocation` and,
|
|
738
804
|
when offered, the Expo development-client deep link. HTTP and WebSocket bytes
|
|
739
805
|
stream over WireGuard with socket backpressure; no public tunnel, Funnel or
|
|
740
806
|
Tailscale serve configuration change is needed. The 64 KiB server message
|
|
@@ -756,18 +822,23 @@ placement still needs to check that contract before selecting hosted Metro.
|
|
|
756
822
|
Bare React Native uses the worker `RCT_jsLocation`. Bridge readiness and
|
|
757
823
|
manifest requests are not launch proof; development remains `unverified` until
|
|
758
824
|
the workspace observes the app's own bundle delivery.
|
|
759
|
-
|
|
760
|
-
|
|
825
|
+
Approved clients use `stim ios|android --remote auto` for automatic device placement.
|
|
826
|
+
They stay local while there is a free slot, no queued device run, normal memory
|
|
827
|
+
pressure, no budget refusal and load below `offload.maxLoadPerCore`. Otherwise
|
|
828
|
+
they rank compatible offers from `hosting.machines` and select a host, or wait
|
|
829
|
+
locally if none admits. A reserve refusal after an offer fails with
|
|
830
|
+
`STIM_HOSTING_REFUSED` and asks to retry; it does not re-place the built app.
|
|
831
|
+
Automatic native macOS placement remains separate work.
|
|
761
832
|
|
|
762
833
|
### Hosted viewer relay
|
|
763
834
|
|
|
764
|
-
The client's stim-server relays a hosted workspace's iOS simulator or macOS view and input to
|
|
835
|
+
The client's stim-server relays a hosted workspace's iOS simulator, Android emulator or macOS view and input to
|
|
765
836
|
its host using the client's approved device-host credential over the pinned
|
|
766
837
|
tailnet connection. Stim Desktop and phones keep talking only to their own
|
|
767
|
-
server. The `macos-hosted` and `
|
|
768
|
-
iOS resolves `ios.host` by the requested slot; macOS retains its default slot.
|
|
838
|
+
server. The `macos-hosted`, `ios-hosted` and `android-hosted` features advertise these relays.
|
|
839
|
+
iOS resolves `ios.host` and Android resolves `android.host` by the requested slot; macOS retains its default slot.
|
|
769
840
|
The local platform and slot replace the host's private target fields in events
|
|
770
|
-
and iOS control audit records. H.264 packets retain their flags and payload and
|
|
841
|
+
and iOS/Android control audit records. H.264 packets retain their flags and payload and
|
|
771
842
|
carry the local subscription ID, which identifies the platform and slot.
|
|
772
843
|
The relay forwards iOS device artwork and orientation metadata. Hosted targets
|
|
773
844
|
refuse replay (`at`/`rate`), `duoFrame` and `physical` with `bad-request`.
|
|
@@ -783,14 +854,14 @@ its packets until the next keyframe and, when the host advertises the
|
|
|
783
854
|
`hosted-congestion` feature, sends `device-host.frames.congested` at most every
|
|
784
855
|
250 ms, so the host lowers that app's bitrate as it does for a local subscriber
|
|
785
856
|
whose socket backs up.
|
|
786
|
-
Hosted macOS frames and control require the default slot; hosted iOS
|
|
857
|
+
Hosted macOS frames and control require the default slot; hosted iOS and Android support named slots.
|
|
787
858
|
`control.begin` still needs the local `control` grant. For macOS apps, Screen Recording for
|
|
788
859
|
viewing and Accessibility for control are granted on the host, not the client.
|
|
789
860
|
|
|
790
|
-
### Hosted iOS and macOS view and input
|
|
861
|
+
### Hosted iOS, Android and macOS view and input
|
|
791
862
|
|
|
792
863
|
An approved hosting client can subscribe to its ready session's exact owned
|
|
793
|
-
iOS simulator or running macOS app without access to the worker's registered
|
|
864
|
+
iOS simulator, Android emulator or running macOS app without access to the worker's registered
|
|
794
865
|
workspaces:
|
|
795
866
|
|
|
796
867
|
```json
|
|
@@ -810,7 +881,7 @@ subscription ID as `params.subscription`. `device-host.frames.congested`
|
|
|
810
881
|
behind on that H.264 subscription; it lowers the bitrate the same way a backed-up
|
|
811
882
|
socket does.
|
|
812
883
|
Hosted capture requires the compiled `stim-frames` helper and does not support
|
|
813
|
-
replay
|
|
884
|
+
replay or screenshot fallback. iOS supports device artwork. macOS frames show only the one
|
|
814
885
|
window of the hosted app. Viewing is refused before launch and after the app
|
|
815
886
|
exits. On the hosting Mac, `stim-server service install` runs the server and
|
|
816
887
|
`stim-frames` under Stim Host, whose Screen Recording and control grants they
|
|
@@ -820,13 +891,27 @@ System Audio Recording ends the stream; missing Device Control and Data Access
|
|
|
820
891
|
(Accessibility on macOS 26 and earlier) leaves viewing available.
|
|
821
892
|
See [Run as a service](#run-as-a-service) for the settings panes and Dev caveat.
|
|
822
893
|
|
|
894
|
+
Android capture uses the same `android-device <serial> <adb> <scrcpy-server>`
|
|
895
|
+
helper transport as physical Android, addressed only to the session's exact
|
|
896
|
+
`emulator-<port>` serial. It bypasses workspace target resolution and device
|
|
897
|
+
leases. The server rechecks the private home's created-device ledger, recorded
|
|
898
|
+
serial and system image, the running AVD identity and ABI. Unknown state refuses
|
|
899
|
+
capture and input. The helper uses the host SDK from `ANDROID_HOME`,
|
|
900
|
+
`ANDROID_SDK_ROOT` or `$HOME/Library/Android/sdk`, including under the LaunchAgent.
|
|
901
|
+
It pushes the built scrcpy jar to `/data/local/tmp` and removes both that jar
|
|
902
|
+
and its adb forward when capture stops. One viewer pool serves each session;
|
|
903
|
+
the lifetime claim tracks the helper as its child. Android capture requires no
|
|
904
|
+
macOS screen-recording permission.
|
|
905
|
+
|
|
823
906
|
Start control with `device-host.control.begin` and
|
|
824
907
|
`{"session":"<hosted-session-id>"}`. Its result returns a connection-bound
|
|
825
908
|
control session ID and `lease: null`: the hosted lifetime claim protects this
|
|
826
909
|
private device. Only one controller can drive the device; `takeOver: true`
|
|
827
910
|
replaces the previous controller. Use the returned control ID with
|
|
828
911
|
`device-host.input.touch|text`, using the same parameters as ordinary input.
|
|
829
|
-
iOS also supports `device-host.input.button|rotate|posture`.
|
|
912
|
+
iOS also supports `device-host.input.button|rotate|posture`. Android supports
|
|
913
|
+
`device-host.input.button` through scrcpy; rotation and posture are unavailable
|
|
914
|
+
on this path, as for physical Android devices. macOS also supports
|
|
830
915
|
`device-host.input.scroll|key`; these two methods are for macOS sessions only.
|
|
831
916
|
Touch coordinates range from 0 to 1 on the streamed display.
|
|
832
917
|
End it with `device-host.control.end` and `{"session":"<control-id>"}`.
|
|
@@ -847,8 +932,7 @@ Hosted posture commands hold a separate child-aware input claim. A surviving
|
|
|
847
932
|
command or unresolved child identity blocks replacement ownership, install and
|
|
848
933
|
stop even after the server and capture helper exit; the refusal names the claim
|
|
849
934
|
and its manual cleanup command. An unknown or lost owner requires explicit stop
|
|
850
|
-
before replacement.
|
|
851
|
-
protocol does not add CLI placement or a local viewer relay; those remain in
|
|
935
|
+
before replacement. Automatic placement remains in
|
|
852
936
|
[#2266](https://github.com/appandflow/stim/issues/2266).
|
|
853
937
|
|
|
854
938
|
## Run as a service
|
|
@@ -1177,11 +1261,11 @@ closes its connections and cancels its builds.
|
|
|
1177
1261
|
another client, gets `bad-request`.
|
|
1178
1262
|
- `build.artifact` takes the `job` of a successful build and sends the archive
|
|
1179
1263
|
as binary frames, each 32 bytes of its sha256 and then the next bytes, then
|
|
1180
|
-
answers `{ "name", "size", "sha256" }`, and deletes it here. For an iOS simulator or macOS
|
|
1264
|
+
answers `{ "name", "size", "sha256" }`, and deletes it here. For an iOS simulator, Android emulator or macOS
|
|
1181
1265
|
job the answer also carries `handoff`, a single-use token, and the staged
|
|
1182
|
-
`.app` stays for 10 minutes so a hosted session on this Mac can take it with
|
|
1266
|
+
`.app` or APK stays for 10 minutes so a hosted session on this Mac can take it with
|
|
1183
1267
|
`device-host.app.handoff`. A client keeps at most one such bundle; its next
|
|
1184
|
-
fetched
|
|
1268
|
+
fetched native build or a revoked approval deletes it. An archive nobody fetched is deleted when its job
|
|
1185
1269
|
is cancelled; one left by a server that crashed stays under
|
|
1186
1270
|
`repos/<repo>/out/` until you delete it.
|
|
1187
1271
|
|
|
@@ -1472,6 +1556,16 @@ Events are `{ "event", "subscription", ... }`.
|
|
|
1472
1556
|
`at` and `frames.seek` on it fail with `no-recording`, and it counts
|
|
1473
1557
|
toward no idle check.
|
|
1474
1558
|
|
|
1559
|
+
- **Archive detail.** `archive.detail` takes `{ archive: archived[].id }` and needs
|
|
1560
|
+
`read`. It returns `{ builds, recordings }`: the archive's own iOS and Android
|
|
1561
|
+
build history in the live environment's `builds` shape, and every retained
|
|
1562
|
+
recording slot as `{ platform, slot, spans }`. Spans use the same ranges as
|
|
1563
|
+
`replay.range`; devices without spans are omitted. Missing or unreadable state
|
|
1564
|
+
yields `builds: {}`, and no recordings yields `recordings: []`. Reads share the
|
|
1565
|
+
archive request queue, refuse unknown archives with `unknown-workspace`, and
|
|
1566
|
+
skip symlinks. Older servers refuse it with `unknown-method`.
|
|
1567
|
+
`status` keeps only the archive's build summary.
|
|
1568
|
+
|
|
1475
1569
|
- **Replay.** `replay.range` takes exactly one of `workspace` or `archive`, plus `platform` and `slot`
|
|
1476
1570
|
(`default` when absent), like `frames.subscribe`, and returns what can be
|
|
1477
1571
|
replayed of that device slot's [recording](#recording):
|
|
@@ -187,13 +187,26 @@ function protocolJsonSchema() {
|
|
|
187
187
|
"deviceKey"
|
|
188
188
|
],
|
|
189
189
|
additionalProperties: false,
|
|
190
|
-
oneOf: [
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
190
|
+
oneOf: [
|
|
191
|
+
{
|
|
192
|
+
required: ["backend"],
|
|
193
|
+
properties: {
|
|
194
|
+
backend: { const: "ios-instance" },
|
|
195
|
+
deviceKey: { pattern: "^ios:mobile:[a-fA-F0-9-]{36}$" }
|
|
196
|
+
}
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
required: ["backend"],
|
|
200
|
+
properties: {
|
|
201
|
+
backend: { const: "android-instance" },
|
|
202
|
+
deviceKey: { pattern: "^android:mobile:emulator-[0-9]+$" }
|
|
203
|
+
}
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
not: { required: ["backend"] },
|
|
207
|
+
properties: { deviceKey: { pattern: "^[A-Za-z0-9_-]+(?:\\.[A-Za-z0-9_-]+)+@[1-9][0-9]{0,9}$" } }
|
|
208
|
+
}
|
|
209
|
+
],
|
|
197
210
|
properties: {
|
|
198
211
|
tenant: {
|
|
199
212
|
type: "string",
|
|
@@ -207,7 +220,7 @@ function protocolJsonSchema() {
|
|
|
207
220
|
type: "string",
|
|
208
221
|
pattern: "^[A-Za-z0-9._-]{1,128}$"
|
|
209
222
|
},
|
|
210
|
-
backend: {
|
|
223
|
+
backend: { enum: ["ios-instance", "android-instance"] },
|
|
211
224
|
deviceKey: { type: "string" }
|
|
212
225
|
}
|
|
213
226
|
}
|
|
@@ -391,6 +404,19 @@ function protocolJsonSchema() {
|
|
|
391
404
|
}
|
|
392
405
|
}
|
|
393
406
|
},
|
|
407
|
+
HostedLocalDeviceCount: { anyOf: [{
|
|
408
|
+
type: "integer",
|
|
409
|
+
minimum: 0
|
|
410
|
+
}, {
|
|
411
|
+
type: "object",
|
|
412
|
+
required: ["unknown"],
|
|
413
|
+
additionalProperties: false,
|
|
414
|
+
properties: { unknown: {
|
|
415
|
+
type: "string",
|
|
416
|
+
minLength: 1,
|
|
417
|
+
maxLength: 4e3
|
|
418
|
+
} }
|
|
419
|
+
}] },
|
|
394
420
|
HostedDeviceOffer: {
|
|
395
421
|
type: "object",
|
|
396
422
|
required: [
|
|
@@ -444,7 +470,8 @@ function protocolJsonSchema() {
|
|
|
444
470
|
workerDiskFreeBytes: {
|
|
445
471
|
type: ["number", "null"],
|
|
446
472
|
minimum: 0
|
|
447
|
-
}
|
|
473
|
+
},
|
|
474
|
+
localDevices: { $ref: "#/$defs/HostedLocalDeviceCount" }
|
|
448
475
|
}
|
|
449
476
|
},
|
|
450
477
|
capacity: {
|
|
@@ -467,7 +494,8 @@ function protocolJsonSchema() {
|
|
|
467
494
|
available: {
|
|
468
495
|
type: ["integer", "null"],
|
|
469
496
|
minimum: 0
|
|
470
|
-
}
|
|
497
|
+
},
|
|
498
|
+
local: { $ref: "#/$defs/HostedLocalDeviceCount" }
|
|
471
499
|
}
|
|
472
500
|
}
|
|
473
501
|
},
|
|
@@ -541,6 +569,68 @@ function protocolJsonSchema() {
|
|
|
541
569
|
maximum: 65535
|
|
542
570
|
} }
|
|
543
571
|
},
|
|
572
|
+
HostedSessionRow: {
|
|
573
|
+
type: "object",
|
|
574
|
+
required: [
|
|
575
|
+
"id",
|
|
576
|
+
"client",
|
|
577
|
+
"platform",
|
|
578
|
+
"device",
|
|
579
|
+
"app",
|
|
580
|
+
"state",
|
|
581
|
+
"parked",
|
|
582
|
+
"since",
|
|
583
|
+
"workspace"
|
|
584
|
+
],
|
|
585
|
+
additionalProperties: false,
|
|
586
|
+
properties: {
|
|
587
|
+
id: { type: "string" },
|
|
588
|
+
client: {
|
|
589
|
+
type: "object",
|
|
590
|
+
required: ["id", "name"],
|
|
591
|
+
additionalProperties: false,
|
|
592
|
+
properties: {
|
|
593
|
+
id: { type: "string" },
|
|
594
|
+
name: { type: "string" }
|
|
595
|
+
}
|
|
596
|
+
},
|
|
597
|
+
platform: { enum: [
|
|
598
|
+
"ios",
|
|
599
|
+
"android",
|
|
600
|
+
"macos"
|
|
601
|
+
] },
|
|
602
|
+
device: { type: ["string", "null"] },
|
|
603
|
+
app: { type: ["string", "null"] },
|
|
604
|
+
state: { enum: [
|
|
605
|
+
"preparing",
|
|
606
|
+
"ready",
|
|
607
|
+
"stopping",
|
|
608
|
+
"stopped",
|
|
609
|
+
"unknown"
|
|
610
|
+
] },
|
|
611
|
+
parked: { type: "boolean" },
|
|
612
|
+
since: { type: "string" },
|
|
613
|
+
workspace: { type: "string" }
|
|
614
|
+
}
|
|
615
|
+
},
|
|
616
|
+
HostedSessionsResult: {
|
|
617
|
+
type: "object",
|
|
618
|
+
required: ["sessions"],
|
|
619
|
+
additionalProperties: false,
|
|
620
|
+
properties: { sessions: {
|
|
621
|
+
type: "array",
|
|
622
|
+
items: { $ref: "#/$defs/HostedSessionRow" }
|
|
623
|
+
} }
|
|
624
|
+
},
|
|
625
|
+
HostedSessionStopResult: {
|
|
626
|
+
type: "object",
|
|
627
|
+
required: ["id", "state"],
|
|
628
|
+
additionalProperties: false,
|
|
629
|
+
properties: {
|
|
630
|
+
id: { type: "string" },
|
|
631
|
+
state: { enum: ["stopping", "stopped"] }
|
|
632
|
+
}
|
|
633
|
+
},
|
|
544
634
|
HostedDeviceSession: {
|
|
545
635
|
type: "object",
|
|
546
636
|
required: [
|
|
@@ -662,6 +752,7 @@ function protocolJsonSchema() {
|
|
|
662
752
|
createdAt: { type: "string" },
|
|
663
753
|
notice: { type: "string" },
|
|
664
754
|
appAttempt: { type: "string" },
|
|
755
|
+
agent: { $ref: "#/$defs/HostedAppDelivery/properties/agent" },
|
|
665
756
|
metroPort: {
|
|
666
757
|
type: "integer",
|
|
667
758
|
minimum: 1,
|
|
@@ -1689,6 +1780,11 @@ function protocolJsonSchema() {
|
|
|
1689
1780
|
minimum: 1,
|
|
1690
1781
|
maximum: 65535
|
|
1691
1782
|
},
|
|
1783
|
+
clientMetroPort: {
|
|
1784
|
+
type: "integer",
|
|
1785
|
+
minimum: 1,
|
|
1786
|
+
maximum: 65535
|
|
1787
|
+
},
|
|
1692
1788
|
secret: {
|
|
1693
1789
|
type: "string",
|
|
1694
1790
|
pattern: "^[a-f0-9]{64}$"
|
|
@@ -1865,6 +1961,16 @@ function protocolJsonSchema() {
|
|
|
1865
1961
|
}
|
|
1866
1962
|
}
|
|
1867
1963
|
}),
|
|
1964
|
+
request("archive.detail", {
|
|
1965
|
+
type: "object",
|
|
1966
|
+
required: ["archive"],
|
|
1967
|
+
additionalProperties: false,
|
|
1968
|
+
properties: { archive: {
|
|
1969
|
+
type: "string",
|
|
1970
|
+
minLength: 1,
|
|
1971
|
+
pattern: "^(?!.*\\.\\.)[^/\\\\\\u0000]+$"
|
|
1972
|
+
} }
|
|
1973
|
+
}),
|
|
1868
1974
|
request("replay.keyframe", {
|
|
1869
1975
|
type: "object",
|
|
1870
1976
|
required: ["platform", "at"],
|
|
@@ -2263,6 +2369,8 @@ function protocolJsonSchema() {
|
|
|
2263
2369
|
request("build.artifact", buildJob),
|
|
2264
2370
|
request("build.attach", buildJob),
|
|
2265
2371
|
request("server.update.status"),
|
|
2372
|
+
request("device-host.sessions"),
|
|
2373
|
+
request("device-host.sessions.stop", session({})),
|
|
2266
2374
|
request("machines.update.start", {
|
|
2267
2375
|
type: "object",
|
|
2268
2376
|
required: ["machine"],
|
|
@@ -2352,6 +2460,8 @@ function protocolJsonSchema() {
|
|
|
2352
2460
|
result: { anyOf: [
|
|
2353
2461
|
{ $ref: "#/$defs/HostedDeviceOffer" },
|
|
2354
2462
|
{ $ref: "#/$defs/HostedDeviceSession" },
|
|
2463
|
+
{ $ref: "#/$defs/HostedSessionsResult" },
|
|
2464
|
+
{ $ref: "#/$defs/HostedSessionStopResult" },
|
|
2355
2465
|
{ $ref: "#/$defs/HostedAppDelivery" },
|
|
2356
2466
|
{ $ref: "#/$defs/HostedMetroResult" },
|
|
2357
2467
|
{ $ref: "#/$defs/HostedAppOfferResult" },
|
package/dist/protocol.mjs
CHANGED