@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 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 paired devices, approved clients and access requests
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. The hosted iOS
335
- app connects back to Metro on the client Mac. Automatic CLI placement and
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. This setup does not yet place CLI sessions remotely.
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; without it, upload.
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`; this driver launches the activity but
623
- does not wire an Android Metro bridge. Replaying an installed app attempt does
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. Android viewing, Metro and client
626
- placement remain under [#2266](https://github.com/appandflow/stim/issues/2266).
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 macOS app is `installed`, `device-host.app.launch` and
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, after stop and for iOS and Android sessions. The worker
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. Windows adapt to the backlog,
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 iOS window in epoch milliseconds. `log show` reads persisted entries;
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. Development installation uses that port for `RCT_jsLocation` and,
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
- This is a protocol API for approved clients; automatic iOS CLI placement,
760
- Android Metro/viewing remains in [#2266](https://github.com/appandflow/stim/issues/2266).
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 `ios-hosted` features advertise these relays.
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 supports named slots.
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, device artwork or screenshot fallback. macOS frames show only the one
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`. macOS also supports
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. This worker
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 iOS or macOS build or a revoked approval deletes it. An archive nobody fetched is deleted when its job
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
- required: ["backend"],
192
- properties: { deviceKey: { pattern: "^ios:mobile:[a-fA-F0-9-]{36}$" } }
193
- }, {
194
- not: { required: ["backend"] },
195
- properties: { deviceKey: { pattern: "^[A-Za-z0-9_-]+(?:\\.[A-Za-z0-9_-]+)+@[1-9][0-9]{0,9}$" } }
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: { const: "ios-instance" },
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
@@ -1,3 +1,3 @@
1
- import { t as protocolJsonSchema } from "./protocol-BHSl7bQ_.mjs";
1
+ import { t as protocolJsonSchema } from "./protocol-wBQmYsnp.mjs";
2
2
  export * from "@stim-cli/core/protocol";
3
3
  export { protocolJsonSchema };