specrails-desktop 2.63.1 → 2.64.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/client/dist/assets/{ActivityFeedPage-B1bpTy7O.js → ActivityFeedPage-D23wXKgy.js} +1 -1
- package/client/dist/assets/{AgentBrowserCapture-BussBbN3.js → AgentBrowserCapture-DQzS4XcK.js} +1 -1
- package/client/dist/assets/{AgentModeAnalyticsPane-BiT1n3BU.js → AgentModeAnalyticsPane-CFTiSl8T.js} +2 -2
- package/client/dist/assets/{AgentModeCodePane-CUjNfeKa.js → AgentModeCodePane-DAaSzIPA.js} +2 -2
- package/client/dist/assets/{AgentModeJobsPane-RVEwyS59.js → AgentModeJobsPane-C29YpixV.js} +1 -1
- package/client/dist/assets/{AgentsPage-DKE-eMC5.js → AgentsPage-B-sSTP1I.js} +1 -1
- package/client/dist/assets/{AnalyticsPage-Bser2Ly3.js → AnalyticsPage-81fI80nA.js} +1 -1
- package/client/dist/assets/{CodePage-BK0grm6e.js → CodePage-Ct4XEPdx.js} +1 -1
- package/client/dist/assets/{DesktopAnalyticsPage-DEBidZbs.js → DesktopAnalyticsPage-Bt37XOLF.js} +1 -1
- package/client/dist/assets/{DocsDialog-Dhyo57n9.js → DocsDialog-_IEr8umI.js} +1 -1
- package/client/dist/assets/{DocsPage-BcEkPXd6.js → DocsPage-DX9WOqt2.js} +1 -1
- package/client/dist/assets/{ExportDropdown-mRmqdkjr.js → ExportDropdown-Bb1lHx3r.js} +1 -1
- package/client/dist/assets/{InteractiveJobComposer-BlCe3vrU.js → InteractiveJobComposer-DDI6TdsS.js} +1 -1
- package/client/dist/assets/{JobDetailModal-CkAejD1F.js → JobDetailModal-CkD0PBaZ.js} +1 -1
- package/client/dist/assets/{JobDetailPage-P-FS_lKz.js → JobDetailPage-B8Rd4U5J.js} +1 -1
- package/client/dist/assets/{JobsPage-CoUXgYse.js → JobsPage-ChOiSbJn.js} +1 -1
- package/client/dist/assets/{LoopBuilderPage-iFylNk68.js → LoopBuilderPage-6Xwt2G6G.js} +1 -1
- package/client/dist/assets/{LoopPreviewModal-4obS_Ptg.js → LoopPreviewModal-CyDapSvB.js} +1 -1
- package/client/dist/assets/{LoopWindowSurface-CLr5OPqj.js → LoopWindowSurface-gRdx4nmg.js} +1 -1
- package/client/dist/assets/{LoopsPage-eKpWFtCp.js → LoopsPage-BPGVFsH-.js} +1 -1
- package/client/dist/assets/{MinimizedChatsContext-BHIUPVmC.js → MinimizedChatsContext-DHfigjHS.js} +1 -1
- package/client/dist/assets/{PluginsPage-BgTC2CQx.js → PluginsPage-DQRRdCUi.js} +1 -1
- package/client/dist/assets/{ProjectSettingsDialog-B4chTfU8.js → ProjectSettingsDialog-Ce8DSG6H.js} +1 -1
- package/client/dist/assets/{RepositoryDeliveries-DAIxIYsl.js → RepositoryDeliveries-B4ybHOyB.js} +1 -1
- package/client/dist/assets/{RepositoryScopeSelector-DeCoUsRN.js → RepositoryScopeSelector-2eSqR9WK.js} +1 -1
- package/client/dist/assets/{ReviewPacketPage-Cktv0-9b.js → ReviewPacketPage-WVoio8GJ.js} +1 -1
- package/client/dist/assets/{TemplatePreviewModal-BZTV1TM1.js → TemplatePreviewModal-CTBZUXAN.js} +1 -1
- package/client/dist/assets/{TicketDetailModalContext-DiPngt2g.js → TicketDetailModalContext-Dkwo2cDs.js} +1 -1
- package/client/dist/assets/{Trans-ZiXIfj50.js → Trans-BbFxRFtR.js} +1 -1
- package/client/dist/assets/{formatDistanceToNow-CdN5lTyD.js → formatDistanceToNow-NjPHBLfB.js} +1 -1
- package/client/dist/assets/{getTimezoneOffsetInMilliseconds-62h40I4t.js → getTimezoneOffsetInMilliseconds-vJ7qUoqN.js} +1 -1
- package/client/dist/assets/{index-CECj3joE.js → index-IdGpV21X.js} +36 -36
- package/client/dist/assets/{jira-api-C_D_RDhl.js → jira-api-Cabj-H0J.js} +1 -1
- package/client/dist/assets/{project-repositories-Be0CWklo.js → project-repositories-Cnnuj9Ia.js} +1 -1
- package/client/dist/assets/{settings-Hw7av-B1.js → settings-B1NV8Ow9.js} +1 -1
- package/client/dist/assets/settings-BQ8XFZeE.js +1 -0
- package/client/dist/assets/settings-BRkxfBbO.js +1 -0
- package/client/dist/assets/settings-C_CcU1dP.js +1 -0
- package/client/dist/assets/settings-Cto9n0eL.js +1 -0
- package/client/dist/assets/{settings-nryN6wuS.js → settings-Pyw8uYtL.js} +1 -1
- package/client/dist/assets/settings-b3HVCNNd.js +1 -0
- package/client/dist/assets/settings-hdive4u6.js +1 -0
- package/client/dist/assets/{spending-eWiZGhC_.js → spending-CQnrWiR3.js} +1 -1
- package/client/dist/assets/{useDesktop-C0mRIlHT.js → useDesktop-BjLT0_EE.js} +1 -1
- package/client/dist/assets/{useRuntimeRuns-B1a6GChu.js → useRuntimeRuns-C97qEqMg.js} +1 -1
- package/client/dist/assets/{useSharedWebSocket-CcsE5986.js → useSharedWebSocket-Bj4LMp7b.js} +2 -2
- package/client/dist/index.html +15 -15
- package/docs/ci-cd.md +3 -4
- package/docs/getting-started.md +1 -1
- package/docs/internals/api-reference.md +26 -0
- package/docs/internals/core-runtime-updates.md +3 -3
- package/docs/internals/source-map.md +9 -3
- package/docs/platforms/macos.md +4 -4
- package/docs/platforms/windows.md +5 -5
- package/docs/running-pipelines.md +12 -0
- package/package.json +1 -1
- package/server/dist/index.js +14 -1
- package/server/dist/mobile/mobile-admin-router.js +16 -0
- package/server/dist/mobile/mobile-gateway.js +18 -0
- package/server/dist/mobile/mobile-missions.js +6 -4
- package/server/dist/mobile/mobile-preview-channel.js +179 -0
- package/server/dist/mobile/mobile-preview.js +366 -0
- package/server/dist/mobile/mobile-router.js +9 -1
- package/server/dist/mobile/mobile-webrtc-peer.js +54 -4
- package/server/dist/mobile/mobile-ws.js +4 -2
- package/server/dist/modules/browser/domain/preview-origins.js +117 -0
- package/server/dist/modules/browser/runtime/browser-capture-manager.js +141 -18
- package/server/dist/modules/browser/runtime/browser-capture-types.js +10 -1
- package/server/dist/modules/browser/runtime/browser-playwright.js +19 -0
- package/server/dist/modules/browser/runtime/preview-targets.js +80 -0
- package/server/dist/modules/terminals/runtime/background-process-service.js +24 -1
- package/server/dist/project-registry.js +10 -0
- package/client/dist/assets/settings-BO2N-oPf.js +0 -1
- package/client/dist/assets/settings-BQChAs_w.js +0 -1
- package/client/dist/assets/settings-BzmYlcQB.js +0 -1
- package/client/dist/assets/settings-CGjXb6TX.js +0 -1
- package/client/dist/assets/settings-CMxEWY46.js +0 -1
- package/client/dist/assets/settings-C_dRSgI2.js +0 -1
package/client/dist/index.html
CHANGED
|
@@ -204,7 +204,7 @@
|
|
|
204
204
|
.specrails-splash__mark .pill { opacity: 1; }
|
|
205
205
|
}
|
|
206
206
|
</style>
|
|
207
|
-
<script type="module" crossorigin src="/assets/index-
|
|
207
|
+
<script type="module" crossorigin src="/assets/index-IdGpV21X.js"></script>
|
|
208
208
|
<link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-CNC7AqOf.js">
|
|
209
209
|
<link rel="modulepreload" crossorigin href="/assets/jsx-runtime-BbYvbtY8.js">
|
|
210
210
|
<link rel="modulepreload" crossorigin href="/assets/react-dom-DDD8FYaU.js">
|
|
@@ -236,23 +236,23 @@
|
|
|
236
236
|
<link rel="modulepreload" crossorigin href="/assets/narration-DbHnz0qV.js">
|
|
237
237
|
<link rel="modulepreload" crossorigin href="/assets/nav--u7iPYlS.js">
|
|
238
238
|
<link rel="modulepreload" crossorigin href="/assets/packet-B0H15elv.js">
|
|
239
|
-
<link rel="modulepreload" crossorigin href="/assets/settings-
|
|
239
|
+
<link rel="modulepreload" crossorigin href="/assets/settings-Pyw8uYtL.js">
|
|
240
240
|
<link rel="modulepreload" crossorigin href="/assets/setup-CKGUxKr-.js">
|
|
241
241
|
<link rel="modulepreload" crossorigin href="/assets/specs-DfIdoU5L.js">
|
|
242
242
|
<link rel="modulepreload" crossorigin href="/assets/subscriptionUsage-CdlE5Mg1.js">
|
|
243
243
|
<link rel="modulepreload" crossorigin href="/assets/terminal-BAzpHtpY.js">
|
|
244
244
|
<link rel="modulepreload" crossorigin href="/assets/tickets-D41ZMQpf.js">
|
|
245
245
|
<link rel="modulepreload" crossorigin href="/assets/createLucideIcon-DtODIU64.js">
|
|
246
|
-
<link rel="modulepreload" crossorigin href="/assets/useSharedWebSocket-
|
|
247
|
-
<link rel="modulepreload" crossorigin href="/assets/Trans-
|
|
248
|
-
<link rel="modulepreload" crossorigin href="/assets/useDesktop-
|
|
246
|
+
<link rel="modulepreload" crossorigin href="/assets/useSharedWebSocket-Bj4LMp7b.js">
|
|
247
|
+
<link rel="modulepreload" crossorigin href="/assets/Trans-BbFxRFtR.js">
|
|
248
|
+
<link rel="modulepreload" crossorigin href="/assets/useDesktop-BjLT0_EE.js">
|
|
249
249
|
<link rel="modulepreload" crossorigin href="/assets/provider-capabilities-lePfeT_Y.js">
|
|
250
250
|
<link rel="modulepreload" crossorigin href="/assets/save-LmPSfsvZ.js">
|
|
251
|
-
<link rel="modulepreload" crossorigin href="/assets/spending-
|
|
251
|
+
<link rel="modulepreload" crossorigin href="/assets/spending-CQnrWiR3.js">
|
|
252
252
|
<link rel="modulepreload" crossorigin href="/assets/chevron-up-6EFqEHBj.js">
|
|
253
253
|
<link rel="modulepreload" crossorigin href="/assets/sparkles-CCfUdFGY.js">
|
|
254
254
|
<link rel="modulepreload" crossorigin href="/assets/x-Bu7X_eR2.js">
|
|
255
|
-
<link rel="modulepreload" crossorigin href="/assets/MinimizedChatsContext-
|
|
255
|
+
<link rel="modulepreload" crossorigin href="/assets/MinimizedChatsContext-DHfigjHS.js">
|
|
256
256
|
<link rel="modulepreload" crossorigin href="/assets/ban-wqfdzoXt.js">
|
|
257
257
|
<link rel="modulepreload" crossorigin href="/assets/file-text-C0blZHLh.js">
|
|
258
258
|
<link rel="modulepreload" crossorigin href="/assets/circle-x-D2ttKotB.js">
|
|
@@ -268,21 +268,21 @@
|
|
|
268
268
|
<link rel="modulepreload" crossorigin href="/assets/search-BDVp3fkH.js">
|
|
269
269
|
<link rel="modulepreload" crossorigin href="/assets/trash-D8zYUmTg.js">
|
|
270
270
|
<link rel="modulepreload" crossorigin href="/assets/triangle-alert-Cxpn8bqr.js">
|
|
271
|
-
<link rel="modulepreload" crossorigin href="/assets/jira-api-
|
|
271
|
+
<link rel="modulepreload" crossorigin href="/assets/jira-api-Cabj-H0J.js">
|
|
272
272
|
<link rel="modulepreload" crossorigin href="/assets/zap-bugHyWzQ.js">
|
|
273
273
|
<link rel="modulepreload" crossorigin href="/assets/clsx-CjueKrWZ.js">
|
|
274
274
|
<link rel="modulepreload" crossorigin href="/assets/utils-BC4DHlz2.js">
|
|
275
275
|
<link rel="modulepreload" crossorigin href="/assets/dialog-DtdR05v1.js">
|
|
276
276
|
<link rel="modulepreload" crossorigin href="/assets/lib-DWJfei6y.js">
|
|
277
|
-
<link rel="modulepreload" crossorigin href="/assets/getTimezoneOffsetInMilliseconds-
|
|
278
|
-
<link rel="modulepreload" crossorigin href="/assets/formatDistanceToNow-
|
|
277
|
+
<link rel="modulepreload" crossorigin href="/assets/getTimezoneOffsetInMilliseconds-vJ7qUoqN.js">
|
|
278
|
+
<link rel="modulepreload" crossorigin href="/assets/formatDistanceToNow-NjPHBLfB.js">
|
|
279
279
|
<link rel="modulepreload" crossorigin href="/assets/button-4FS1ZDTp.js">
|
|
280
|
-
<link rel="modulepreload" crossorigin href="/assets/project-repositories-
|
|
281
|
-
<link rel="modulepreload" crossorigin href="/assets/RepositoryScopeSelector-
|
|
282
|
-
<link rel="modulepreload" crossorigin href="/assets/TicketDetailModalContext-
|
|
280
|
+
<link rel="modulepreload" crossorigin href="/assets/project-repositories-Cnnuj9Ia.js">
|
|
281
|
+
<link rel="modulepreload" crossorigin href="/assets/RepositoryScopeSelector-2eSqR9WK.js">
|
|
282
|
+
<link rel="modulepreload" crossorigin href="/assets/TicketDetailModalContext-Dkwo2cDs.js">
|
|
283
283
|
<link rel="modulepreload" crossorigin href="/assets/core-B3mKBIh5.js">
|
|
284
|
-
<link rel="modulepreload" crossorigin href="/assets/RepositoryDeliveries-
|
|
285
|
-
<link rel="modulepreload" crossorigin href="/assets/useRuntimeRuns-
|
|
284
|
+
<link rel="modulepreload" crossorigin href="/assets/RepositoryDeliveries-B4ybHOyB.js">
|
|
285
|
+
<link rel="modulepreload" crossorigin href="/assets/useRuntimeRuns-C97qEqMg.js">
|
|
286
286
|
<link rel="modulepreload" crossorigin href="/assets/event-Bt7f3wXe.js">
|
|
287
287
|
<link rel="stylesheet" crossorigin href="/assets/index-BgbCbIua.css">
|
|
288
288
|
</head>
|
package/docs/ci-cd.md
CHANGED
|
@@ -38,7 +38,7 @@ Release admission uses the current run/attempt of the named CI workflow and the
|
|
|
38
38
|
|
|
39
39
|
## Native validation without publication
|
|
40
40
|
|
|
41
|
-
Manually running `Desktop Release` defaults to `validation_only=true`. Select a trusted branch in this repository: admission requires successful push CI for that exact branch and SHA, and refuses evidence from another branch, a pull request or a fork. Selecting an existing matching release tag instead requires its main push CI. Validation builds retain their artifacts in GitHub Actions; macOS signing/notarization remains mandatory and the existing Windows installer signing policy is unchanged. The entire GitHub Release
|
|
41
|
+
Manually running `Desktop Release` defaults to `validation_only=true`. Select a trusted branch in this repository: admission requires successful push CI for that exact branch and SHA, and refuses evidence from another branch, a pull request or a fork. Selecting an existing matching release tag instead requires its main push CI. Validation builds retain their artifacts in GitHub Actions; macOS signing/notarization remains mandatory and the existing Windows installer signing policy is unchanged. The entire GitHub Release publication job is skipped. A passing validation build does not publish a release or move an update channel.
|
|
42
42
|
|
|
43
43
|
Both validation and release builds use `assemble-chromium.mjs --release`. On macOS, the certificate and Apple API key are imported before Chromium assembly. The assembler must sign and notarize Chromium before writing a normal `chromium.tar.gz`; missing credentials or failed verification fail the build. Windows uses the same archive format without changing the browser's Authenticode signature. The archive keeps framework symlinks intact through Tauri resource copying. Staging and packaged macOS smoke tests require Chromium signature and notarization verification as well as actual browser execution.
|
|
44
44
|
|
|
@@ -48,14 +48,13 @@ The macOS job retains `artifacts/chromium-signing/` as the `chromium-signing-mac
|
|
|
48
48
|
|
|
49
49
|
## Credentials and external requirements
|
|
50
50
|
|
|
51
|
-
###
|
|
51
|
+
### Download channel
|
|
52
52
|
|
|
53
|
-
|
|
53
|
+
GitHub Releases is the only Desktop download channel. Each release carries the canonical installers, the Tauri updater assets and `manifest.json` (version, sha256 and size per platform, with GitHub asset URLs). The Tauri updater reads `releases/latest/download/latest.json`; specrails.dev reads the latest release through the GitHub API. Nothing is uploaded to Hostinger, so a release has no second publication step to recover.
|
|
54
54
|
|
|
55
55
|
- `RELEASE_PAT` must be authorized for release-please's pull requests and releases. It also permits tag pushes to trigger publication workflows; events created with the repository's ordinary `GITHUB_TOKEN` generally do not trigger another workflow.
|
|
56
56
|
- Publication uses one of two credentials. When the `NPM_TOKEN` secret is set, it must authenticate an account with publishing permission for `specrails-desktop` (npm now requires such tokens to be granular with *bypass 2FA*, and they expire within 90 days); the workflow checks authentication and gives an explicit error before publishing. When the secret is empty, the job publishes through npm **trusted publishing**: it installs npm 11, removes the empty token line that `setup-node` writes, and lets npm exchange the job's OIDC identity (`id-token: write`) for a short-lived publish grant. That path only works once the package's *Trusted publisher* on npmjs.com names this repository (`fjpulidop/specrails-desktop`) and workflow file (`npm-publish.yml`, no environment); registry configuration is never changed from here. Provenance does **not** grant publishing permission in either mode. An npm `E404` during publication is npm's answer to an unauthorized publish and does not identify which token or permission failed; an `E403` mentioning two-factor authentication means the stored token lacks *bypass 2FA*. [npm publishing and provenance](https://docs.github.com/en/actions/tutorials/publish-packages/publish-nodejs-packages).
|
|
57
57
|
- Existing Apple signing/notarization and Tauri updater signing secrets remain required. Installer build steps retain their platform-specific checks.
|
|
58
|
-
- Hostinger credentials must support **explicit FTPS**, a valid TLS certificate, uploads and `RNFR`/`RNTO` in the release directory. There is no plaintext FTP fallback. A failed transfer or rename fails the workflow before old files are removed. [FTP action protocol and security options](https://github.com/SamKirkland/FTP-Deploy-Action/blob/v4.3.6/action.yml).
|
|
59
58
|
|
|
60
59
|
No secret, registry authorization, signing certificate, hosting permission or branch protection is configured by these source changes. Dependency installation is separated from npm publishing credentials. Candidate Core packages and publication tarballs use `--ignore-scripts` at the relevant installation/publication boundaries. [npm installation lifecycle options](https://docs.npmjs.com/cli/v11/commands/npm-ci/).
|
|
61
60
|
|
package/docs/getting-started.md
CHANGED
|
@@ -16,7 +16,7 @@ Two options:
|
|
|
16
16
|
|
|
17
17
|
### Option 1 — Desktop app (recommended)
|
|
18
18
|
|
|
19
|
-
Download a signed build for your OS from
|
|
19
|
+
Download a signed build for your OS from the [latest GitHub Release](https://github.com/fjpulidop/specrails-desktop/releases/latest) (the **Download** button on [specrails.dev](https://specrails.dev) links the same files):
|
|
20
20
|
|
|
21
21
|
- **macOS** — `specrails-desktop-<version>-aarch64.dmg` (Apple Silicon, notarised)
|
|
22
22
|
- **Windows** — `specrails-desktop-<version>-x64-setup.exe` (NSIS; `-arm64-setup.exe` on ARM64)
|
|
@@ -127,6 +127,32 @@ Loopback-only admin surface for the Mobile Gateway (the gateway itself is a sepa
|
|
|
127
127
|
| `GET` | `/devices` | List paired devices |
|
|
128
128
|
| `DELETE` | `/devices/:id` | Revoke a paired device |
|
|
129
129
|
| `POST` | `/cert/rotate` | Rotate the gateway TLS identity (unpairs every device) |
|
|
130
|
+
| `GET` | `/previews` | Live Companion app previews (`{ previews: [{ projectId, projectName, sessionId, deviceId, deviceName, url, title, createdAt }] }`) |
|
|
131
|
+
| `DELETE` | `/previews/:projectId/:sessionId` | End a device-owned preview from Desktop (`{ ok }`; Desktop sessions are never affected) |
|
|
132
|
+
|
|
133
|
+
Revoking a device also ends its previews. The app-level WS event `mobile.previews_changed` (no `projectId`) tells Settings ▸ Mobile to refetch the list.
|
|
134
|
+
|
|
135
|
+
#### Companion app preview (gateway `/v1`)
|
|
136
|
+
|
|
137
|
+
A paired Companion views a project's locally served web app through a device-owned server-side browser session. The routes require the mission grant (all-projects device) and the `appPreview` capability (`GET /v1/capabilities` → `features.appPreview`, true when browser capture is enabled).
|
|
138
|
+
|
|
139
|
+
| Method | Path | Notes |
|
|
140
|
+
|--------|------|-------|
|
|
141
|
+
| `GET` | `/v1/projects/:pid/missions/:cid/preview-targets` | `{ targets: [{ processId, urls }] }`: local URLs announced in the logs of the mission's running background processes |
|
|
142
|
+
| `POST` | `/v1/projects/:pid/preview-sessions` | Body `{ missionId, processId, url, viewport? }`; `url` must be announced by that process. `201 { sessionId, channel, url }` |
|
|
143
|
+
| `POST` | `/v1/projects/:pid/preview-sessions/:sid/attach` | Re-attach after a reconnect: `{ sessionId, channel }` (a new DataChannel replaces the old one) |
|
|
144
|
+
| `POST` | `/v1/projects/:pid/preview-sessions/:sid/navigate` | Body `{ action: goto\|back\|forward\|reload, url? }`; `goto` accepts only paths on announced origins |
|
|
145
|
+
| `DELETE` | `/v1/projects/:pid/preview-sessions/:sid` | End the device's own preview |
|
|
146
|
+
|
|
147
|
+
Errors carry `{ error, code }`. Codes: `preview_unavailable` (404), `preview_target_gone` (404), `preview_session_closed` (404), `preview_url_not_announced` (400), `preview_limit` (429), `preview_requires_webrtc` (409), `preview_failed` (502). Sessions owned by Desktop or by another device are reported as `preview_session_closed`.
|
|
148
|
+
|
|
149
|
+
**Channel.** `channel` is the label of an ordered, reliable DataChannel (`preview:<sessionId>`) that Desktop opens on the device's existing WebRTC peer without renegotiation:
|
|
150
|
+
|
|
151
|
+
- **Desktop → Companion, text:** JSON control: `ready` (`{ id, url, title, viewport }`), `nav`, `popup`, `closed` (`{ reason }`) and `nav_rejected`.
|
|
152
|
+
- **Desktop → Companion, binary:** JPEG screencast frames in fragments of at most 16 000 bytes. Each fragment starts with an 8-byte big-endian header `u32 frameSeq | u16 index | u16 count`. Render a frame only when all of its fragments have arrived. A newer `frameSeq` discards an incomplete older frame.
|
|
153
|
+
- **Companion → Desktop:** JSON `{ type: 'input', event }` (mouse / wheel / key / resize, validated) and `{ type: 'navigate', action, url? }`, rate-limited to 6 000 messages per minute.
|
|
154
|
+
|
|
155
|
+
**Origin policy.** A device session may load a loopback origin only while a running background process of the project announces it. Every other request passes the regular server-side SSRF guard. Device sessions have their own budget: one per device (opening a new one replaces it) and two per project. They never count against Desktop's four sessions.
|
|
130
156
|
|
|
131
157
|
### specrails-tech proxy
|
|
132
158
|
|
|
@@ -13,7 +13,7 @@ framework is rejected rather than silently replacing an update.
|
|
|
13
13
|
|
|
14
14
|
## Persistence and publication
|
|
15
15
|
|
|
16
|
-
The release bundle pins Core 6.6.
|
|
16
|
+
The release bundle pins Core 6.6.3 in `desktop-release.yml` and
|
|
17
17
|
`scripts/assemble-bundled-core.lock.json`. Core 6 is the Desktop-only engine: it
|
|
18
18
|
publishes integration contract 5.1 (no standalone `update`) and ships only the
|
|
19
19
|
implement and retry workflows (Batch was folded into implement; Desktop no
|
|
@@ -27,8 +27,8 @@ dependency leaks into its tarball (today `@langchain/langgraph-sdk` ships
|
|
|
27
27
|
module still loads while every staged path stays inside the MSI budget
|
|
28
28
|
(`MAX_STAGED_RELATIVE_PATH`, 120 characters).
|
|
29
29
|
|
|
30
|
-
The paired CI checkout uses the immutable Core 6.6.
|
|
31
|
-
`
|
|
30
|
+
The paired CI checkout uses the immutable Core 6.6.3 release commit
|
|
31
|
+
`a8bc8181a206ea9630ae3b02e6198f8519d35604`. Bundle smoke checks use Node 22.22.3,
|
|
32
32
|
matching Core's minimum supported Node version and the Desktop release runtime.
|
|
33
33
|
|
|
34
34
|
Desktop updates retain the complete npm installation, including dependencies,
|
|
@@ -5,7 +5,7 @@ architectural dependency declaration. Search a heading or filename and read only
|
|
|
5
5
|
the relevant source and tests. Feature prefixes group the legacy server files;
|
|
6
6
|
new bounded modules live under `server/modules/`.
|
|
7
7
|
|
|
8
|
-
Includes
|
|
8
|
+
Includes 1050 source/build files. Nearby tests are linked where names
|
|
9
9
|
match; integration suites may live elsewhere. Use `rg` to find other consumers.
|
|
10
10
|
|
|
11
11
|
## cli
|
|
@@ -854,7 +854,6 @@ match; integration suites may live elsewhere. Use `rg` to find other consumers.
|
|
|
854
854
|
- [obfuscate-chromium.mjs](../../scripts/obfuscate-chromium.mjs)
|
|
855
855
|
- [paired-core-shards.mjs](../../scripts/paired-core-shards.mjs) · [test](../../scripts/paired-core-shards.test.mjs)
|
|
856
856
|
- [patch-node-pty.mjs](../../scripts/patch-node-pty.mjs) · [test](../../scripts/patch-node-pty.test.mjs)
|
|
857
|
-
- [recover-desktop-channel.mjs](../../scripts/recover-desktop-channel.mjs) · [test](../../scripts/recover-desktop-channel.test.mjs)
|
|
858
857
|
- [release-policy.mjs](../../scripts/release-policy.mjs) · [test](../../scripts/release-policy.test.mjs)
|
|
859
858
|
- [run-native-smoke.ps1](../../scripts/run-native-smoke.ps1)
|
|
860
859
|
- [sign-chromium-macos.mjs](../../scripts/sign-chromium-macos.mjs) · [test](../../scripts/sign-chromium-macos.test.mjs)
|
|
@@ -1058,6 +1057,8 @@ match; integration suites may live elsewhere. Use `rg` to find other consumers.
|
|
|
1058
1057
|
- [mobile-event-bus.ts](../../server/mobile/mobile-event-bus.ts)
|
|
1059
1058
|
- [mobile-gateway.ts](../../server/mobile/mobile-gateway.ts) · [test](../../server/mobile/mobile-gateway.test.ts)
|
|
1060
1059
|
- [mobile-missions.ts](../../server/mobile/mobile-missions.ts) · [test](../../server/mobile/mobile-missions.test.ts)
|
|
1060
|
+
- [mobile-preview-channel.ts](../../server/mobile/mobile-preview-channel.ts) · [test](../../server/mobile/mobile-preview-channel.test.ts)
|
|
1061
|
+
- [mobile-preview.ts](../../server/mobile/mobile-preview.ts) · [test](../../server/mobile/mobile-preview.test.ts)
|
|
1061
1062
|
- [mobile-redact.ts](../../server/mobile/mobile-redact.ts) · [test](../../server/mobile/mobile-redact.test.ts)
|
|
1062
1063
|
- [mobile-router.ts](../../server/mobile/mobile-router.ts) · [test](../../server/mobile/mobile-router.test.ts)
|
|
1063
1064
|
- [mobile-signal-reconnect.ts](../../server/mobile/mobile-signal-reconnect.ts) · [test](../../server/mobile/mobile-signal-reconnect.test.ts)
|
|
@@ -1149,6 +1150,10 @@ match; integration suites may live elsewhere. Use `rg` to find other consumers.
|
|
|
1149
1150
|
- [profile-manager.ts](../../server/modules/agents/runtime/profile-manager.ts) · [test](../../server/modules/agents/runtime/profile-manager.test.ts)
|
|
1150
1151
|
- [profiles-router.ts](../../server/modules/agents/runtime/profiles-router.ts) · [test](../../server/modules/agents/runtime/profiles-router.test.ts)
|
|
1151
1152
|
|
|
1153
|
+
## server/modules/browser/domain
|
|
1154
|
+
|
|
1155
|
+
- [preview-origins.ts](../../server/modules/browser/domain/preview-origins.ts) · [test](../../server/modules/browser/domain/preview-origins.test.ts)
|
|
1156
|
+
|
|
1152
1157
|
## server/modules/browser/runtime
|
|
1153
1158
|
|
|
1154
1159
|
- [browser-capture-manager.ts](../../server/modules/browser/runtime/browser-capture-manager.ts) · [test](../../server/modules/browser/runtime/browser-capture-manager.test.ts)
|
|
@@ -1157,6 +1162,7 @@ match; integration suites may live elsewhere. Use `rg` to find other consumers.
|
|
|
1157
1162
|
- [browser-network.ts](../../server/modules/browser/runtime/browser-network.ts) · [test](../../server/modules/browser/runtime/browser-network.test.ts)
|
|
1158
1163
|
- [browser-playwright.ts](../../server/modules/browser/runtime/browser-playwright.ts) · [test](../../server/modules/browser/runtime/browser-playwright.test.ts)
|
|
1159
1164
|
- [browser-viewport.ts](../../server/modules/browser/runtime/browser-viewport.ts) · [test](../../server/modules/browser/runtime/browser-viewport.test.ts)
|
|
1165
|
+
- [preview-targets.ts](../../server/modules/browser/runtime/preview-targets.ts) · [test](../../server/modules/browser/runtime/preview-targets.test.ts)
|
|
1160
1166
|
|
|
1161
1167
|
## server/modules/builder/runtime
|
|
1162
1168
|
|
|
@@ -1407,7 +1413,7 @@ match; integration suites may live elsewhere. Use `rg` to find other consumers.
|
|
|
1407
1413
|
## server/modules/terminals/runtime
|
|
1408
1414
|
|
|
1409
1415
|
- [background-process-control.ts](../../server/modules/terminals/runtime/background-process-control.ts) · [test](../../server/modules/terminals/runtime/background-process-control.test.ts)
|
|
1410
|
-
- [background-process-service.ts](../../server/modules/terminals/runtime/background-process-service.ts)
|
|
1416
|
+
- [background-process-service.ts](../../server/modules/terminals/runtime/background-process-service.ts) · [test](../../server/modules/terminals/runtime/background-process-service.test.ts)
|
|
1411
1417
|
- [background-process-store.ts](../../server/modules/terminals/runtime/background-process-store.ts) · [test](../../server/modules/terminals/runtime/background-process-store.test.ts)
|
|
1412
1418
|
- [background-windows-bootstrap.ts](../../server/modules/terminals/runtime/background-windows-bootstrap.ts) · [test](../../server/modules/terminals/runtime/background-windows-bootstrap.test.ts)
|
|
1413
1419
|
- [terminal-manager.ts](../../server/modules/terminals/runtime/terminal-manager.ts) · [test](../../server/modules/terminals/runtime/terminal-manager.test.ts)
|
package/docs/platforms/macos.md
CHANGED
|
@@ -7,19 +7,19 @@
|
|
|
7
7
|
|
|
8
8
|
## Installation
|
|
9
9
|
|
|
10
|
-
Signed, notarized builds are published on every release
|
|
10
|
+
Signed, notarized builds are published on every release as GitHub Release assets:
|
|
11
11
|
|
|
12
|
-
> 📥
|
|
12
|
+
> 📥 [https://github.com/fjpulidop/specrails-desktop/releases/latest](https://github.com/fjpulidop/specrails-desktop/releases/latest)
|
|
13
13
|
|
|
14
14
|
- `specrails-desktop-<version>-aarch64.dmg` — the Apple Silicon installer.
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
Every earlier version stays on its own [GitHub Release](https://github.com/fjpulidop/specrails-desktop/releases). Each release lists the sha256 of every asset and carries a machine-readable `manifest.json` (version, sha256, size) so you can verify a download:
|
|
17
17
|
|
|
18
18
|
```bash
|
|
19
19
|
shasum -a 256 specrails-desktop-<version>-aarch64.dmg
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
Compare the output against `platforms["darwin-arm64"].sha256` in `https://
|
|
22
|
+
Compare the output against `platforms["darwin-arm64"].sha256` in `https://github.com/fjpulidop/specrails-desktop/releases/latest/download/manifest.json`, or against the sha256 shown next to the asset on the release page.
|
|
23
23
|
|
|
24
24
|
To install: open the `.dmg` and drag **Specrails** into `Applications/`.
|
|
25
25
|
|
|
@@ -11,9 +11,9 @@
|
|
|
11
11
|
|
|
12
12
|
## Installation
|
|
13
13
|
|
|
14
|
-
Installers are published on every release
|
|
14
|
+
Installers are published on every release as GitHub Release assets:
|
|
15
15
|
|
|
16
|
-
> 📥
|
|
16
|
+
> 📥 [https://github.com/fjpulidop/specrails-desktop/releases/latest](https://github.com/fjpulidop/specrails-desktop/releases/latest)
|
|
17
17
|
|
|
18
18
|
Pick the installer that matches your CPU architecture:
|
|
19
19
|
|
|
@@ -22,9 +22,9 @@ Pick the installer that matches your CPU architecture:
|
|
|
22
22
|
| x64 | `specrails-desktop-<version>-x64-setup.exe` |
|
|
23
23
|
| ARM64 | `specrails-desktop-<version>-arm64-setup.exe` |
|
|
24
24
|
|
|
25
|
-
Windows ships only the NSIS installer. For unattended or managed deployment, run it silently with `/S` (optionally `/D=<install dir>` as the last argument). Releases up to 2.62 also published an `.msi`; see [Updates](#updates) for how those installations migrate. The installer provisions Microsoft Edge WebView2 when it is missing, downloading it through a small embedded bootstrapper (Windows 11 and current Windows 10 already include it).
|
|
25
|
+
Windows ships only the NSIS installer. For unattended or managed deployment, run it silently with `/S` (optionally `/D=<install dir>` as the last argument). Releases up to 2.62 also published an `.msi`; see [Updates](#updates) for how those installations migrate. The installer provisions Microsoft Edge WebView2 when it is missing, downloading it through a small embedded bootstrapper (Windows 11 and current Windows 10 already include it). Every earlier version stays on its own [GitHub Release](https://github.com/fjpulidop/specrails-desktop/releases).
|
|
26
26
|
|
|
27
|
-
A machine-readable `manifest.json`
|
|
27
|
+
A machine-readable `manifest.json` asset on each release describes the current release (version, sha256, size) per platform, including `windows-x64` and `windows-arm64` entries — consumers can read it to build download links without hardcoding a version.
|
|
28
28
|
|
|
29
29
|
## SmartScreen warning
|
|
30
30
|
|
|
@@ -44,7 +44,7 @@ This warning is expected and will persist until Authenticode code signing is add
|
|
|
44
44
|
Get-FileHash specrails-desktop-<version>-x64-setup.exe -Algorithm SHA256
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
-
Compare the output against the matching architecture's `sha256` (`platforms["windows-x64"].sha256` or `platforms["windows-arm64"].sha256`) in `https://
|
|
47
|
+
Compare the output against the matching architecture's `sha256` (`platforms["windows-x64"].sha256` or `platforms["windows-arm64"].sha256`) in `https://github.com/fjpulidop/specrails-desktop/releases/latest/download/manifest.json`, or against the sha256 shown next to the asset on the release page.
|
|
48
48
|
|
|
49
49
|
## What's bundled
|
|
50
50
|
|
|
@@ -156,6 +156,18 @@ Visible only when [telemetry](customizing.md#telemetry) was enabled for the job.
|
|
|
156
156
|
- `summary.md` — human-readable highlights
|
|
157
157
|
- `profile.json`, `plugins.json` — exact snapshots of what ran (when present)
|
|
158
158
|
|
|
159
|
+
### A run fails with `lease_lost` right after installing dependencies
|
|
160
|
+
|
|
161
|
+
On Desktop versions that bundle Core 6.6.2 or older, an Implement run on a large repository could fail in the `developer` step shortly after `[environment] yarn install` (or `npm install`) with:
|
|
162
|
+
|
|
163
|
+
```
|
|
164
|
+
Execution lease expired or was replaced; late effects cannot commit
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Core held an execution lease that it renews every few seconds, but it ran the dependency install synchronously. An install longer than about 45 s blocked that renewal, so the lease expired. Core 6.6.3 runs host installs asynchronously and the lease keeps renewing. Update Desktop and launch the spec again.
|
|
168
|
+
|
|
169
|
+
The interrupted attempt cannot be resumed: its write step was cut off, so the runtime cannot confirm what was applied. Its isolated worktree is preserved, and you can inspect it or delete it.
|
|
170
|
+
|
|
159
171
|
## Agent profiles
|
|
160
172
|
|
|
161
173
|
A **profile** is a named JSON file that bundles the agent chain + per-agent models + routing rules. Different rails can run different profiles.
|
package/package.json
CHANGED
package/server/dist/index.js
CHANGED
|
@@ -623,7 +623,20 @@ function applyPtyWsRateLimiting(ws) {
|
|
|
623
623
|
// Second HTTPS+WSS listener in THIS process; the main server stays loopback.
|
|
624
624
|
// The control plane is desktop-only: loopback + (via /api below) requireAuth.
|
|
625
625
|
// Mounted before the /api desktop router so its sub-path is unambiguous.
|
|
626
|
-
const mobileGateway = new mobile_1.MobileGateway({
|
|
626
|
+
const mobileGateway = new mobile_1.MobileGateway({
|
|
627
|
+
desktopDb: registry.desktopDb,
|
|
628
|
+
desktopPort: port,
|
|
629
|
+
broadcast,
|
|
630
|
+
// Companion app preview: each project's browser + the process-log targets.
|
|
631
|
+
preview: {
|
|
632
|
+
enabled: () => BROWSER_CAPTURE_ENABLED,
|
|
633
|
+
browserFor: (projectId) => registry.getContext(projectId)?.browserCaptureManager ?? null,
|
|
634
|
+
allBrowsers: () => registry.listProjects()
|
|
635
|
+
.map((project) => registry.getContext(project.id)?.browserCaptureManager)
|
|
636
|
+
.filter((manager) => manager != null),
|
|
637
|
+
targets: registry.previewTargets,
|
|
638
|
+
},
|
|
639
|
+
});
|
|
627
640
|
_mobileGateway = mobileGateway;
|
|
628
641
|
app.use('/api/mobile', auth_1.requireLoopback, (0, mobile_1.createMobileAdminRouter)({
|
|
629
642
|
gateway: mobileGateway,
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.createMobileAdminRouter = createMobileAdminRouter;
|
|
4
4
|
const express_1 = require("express");
|
|
5
5
|
const mobile_devices_1 = require("./mobile-devices");
|
|
6
|
+
const desktop_db_1 = require("../desktop-db");
|
|
6
7
|
const DEVICE_ID_RE = /^[A-Za-z0-9-]{1,64}$/;
|
|
7
8
|
function createMobileAdminRouter(deps) {
|
|
8
9
|
const router = (0, express_1.Router)();
|
|
@@ -37,10 +38,25 @@ function createMobileAdminRouter(deps) {
|
|
|
37
38
|
const changed = (0, mobile_devices_1.revokeDevice)(desktopDb, id);
|
|
38
39
|
if (changed) {
|
|
39
40
|
gateway.bridge?.closeForDevice(id);
|
|
41
|
+
void gateway.closePreviewsForDevice(id).catch(() => { });
|
|
40
42
|
deps.broadcast({ type: 'mobile.device_revoked', deviceId: id, timestamp: new Date().toISOString() });
|
|
41
43
|
}
|
|
42
44
|
res.json({ ok: changed });
|
|
43
45
|
});
|
|
46
|
+
// —— Companion app previews (Desktop indicator) ——
|
|
47
|
+
router.get('/previews', (_req, res) => {
|
|
48
|
+
const previews = gateway.preview?.listDeviceSessions() ?? [];
|
|
49
|
+
res.json({ previews: previews.map((p) => ({ ...p, projectName: (0, desktop_db_1.getProject)(desktopDb, p.projectId)?.name ?? p.projectId })) });
|
|
50
|
+
});
|
|
51
|
+
router.delete('/previews/:projectId/:sessionId', async (req, res) => {
|
|
52
|
+
const projectId = typeof req.params.projectId === 'string' ? req.params.projectId : '';
|
|
53
|
+
const sessionId = typeof req.params.sessionId === 'string' ? req.params.sessionId : '';
|
|
54
|
+
if (!/^[A-Za-z0-9_-]{1,160}$/.test(projectId) || !/^[A-Za-z0-9_-]{1,160}$/.test(sessionId)) {
|
|
55
|
+
res.status(400).json({ error: 'Invalid parameter' });
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
res.json({ ok: (await gateway.preview?.endFromDesktop(projectId, sessionId)) ?? false });
|
|
59
|
+
});
|
|
44
60
|
router.post('/cert/rotate', async (_req, res) => {
|
|
45
61
|
const status = await gateway.rotateCert();
|
|
46
62
|
res.json(status);
|
|
@@ -17,6 +17,7 @@ const mobile_webrtc_peer_1 = require("./mobile-webrtc-peer");
|
|
|
17
17
|
const mobile_signal_reconnect_1 = require("./mobile-signal-reconnect");
|
|
18
18
|
const mobile_webrtc_1 = require("./mobile-webrtc");
|
|
19
19
|
const agent_store_1 = require("../modules/agents/runtime/agent-store");
|
|
20
|
+
const mobile_preview_1 = require("./mobile-preview");
|
|
20
21
|
// Lifecycle owner of the second HTTPS+WSS listener (default :4202), hard-isolated
|
|
21
22
|
// from the main server. Off by default; started on enable or boot-if-enabled.
|
|
22
23
|
const DEFAULT_PORT = 4202;
|
|
@@ -47,12 +48,28 @@ class MobileGateway {
|
|
|
47
48
|
_running = false;
|
|
48
49
|
_boundPort = DEFAULT_PORT;
|
|
49
50
|
_portOverride;
|
|
51
|
+
_preview;
|
|
50
52
|
constructor(deps) {
|
|
51
53
|
this._db = deps.desktopDb;
|
|
52
54
|
this._desktopPort = deps.desktopPort;
|
|
53
55
|
this._broadcast = deps.broadcast;
|
|
54
56
|
this._bindHost = deps.bindHost ?? '0.0.0.0';
|
|
55
57
|
this._portOverride = deps.port;
|
|
58
|
+
this._preview = deps.preview
|
|
59
|
+
? new mobile_preview_1.MobilePreviewService({
|
|
60
|
+
...deps.preview,
|
|
61
|
+
openChannel: (deviceId, label) => this._webrtc?.openChannel(deviceId, label) ?? null,
|
|
62
|
+
onChange: () => this._broadcast({ type: 'mobile.previews_changed', timestamp: new Date().toISOString() }),
|
|
63
|
+
})
|
|
64
|
+
: null;
|
|
65
|
+
}
|
|
66
|
+
get preview() {
|
|
67
|
+
return this._preview;
|
|
68
|
+
}
|
|
69
|
+
/** A revoked device loses its peer mapping and every preview it owns. */
|
|
70
|
+
async closePreviewsForDevice(deviceId) {
|
|
71
|
+
this._webrtc?.forgetDevice(deviceId);
|
|
72
|
+
await this._preview?.closeForDevice(deviceId);
|
|
56
73
|
}
|
|
57
74
|
get bridge() {
|
|
58
75
|
return this._bridge;
|
|
@@ -139,6 +156,7 @@ class MobileGateway {
|
|
|
139
156
|
db: this._db,
|
|
140
157
|
desktopPort: this._desktopPort,
|
|
141
158
|
currentFingerprint: () => this._cert.fingerprint,
|
|
159
|
+
...(this._preview ? { preview: this._preview } : {}),
|
|
142
160
|
}));
|
|
143
161
|
const server = https_1.default.createServer({ cert: this._cert.certPem, key: this._cert.keyPem }, app);
|
|
144
162
|
// BUG-MOBILE-02: scope each socket's subscribe to the device's granted
|
|
@@ -16,9 +16,11 @@ const record = (value) => value && typeof value === 'object' && !Array.isArray(v
|
|
|
16
16
|
const pick = (value, keys) => Object.fromEntries(Object.entries(record(value)).filter(([key]) => keys.includes(key)));
|
|
17
17
|
exports.MOBILE_PROTOCOL_VERSION = 2;
|
|
18
18
|
exports.MISSION_ACCESS_REASON = 'Mission agents can access more than their pinned project. Missions require an all-projects device grant; your permitted Specs and Rails remain available.';
|
|
19
|
-
function mobileCapabilities(db, deviceId) {
|
|
19
|
+
function mobileCapabilities(db, deviceId, opts = {}) {
|
|
20
20
|
const missions = process.env.SPECRAILS_AGENT_CHAT !== 'false' && (0, mobile_devices_1.getAllowedProjects)(db, deviceId) === null;
|
|
21
|
-
|
|
21
|
+
// App preview targets come from mission processes, so it inherits the
|
|
22
|
+
// mission grant and additionally needs Desktop's browser capture.
|
|
23
|
+
return { protocolVersion: exports.MOBILE_PROTOCOL_VERSION, features: { missions, missionControl: missions, missionQueue: missions, missionProcesses: missions, repositories: true, codeExplorer: (0, feature_flags_1.isCodeExplorerEnabled)(), missionRailCards: missions && (0, feature_flags_1.isMissionRailCardsEnabled)(), appPreview: missions && opts.appPreview === true },
|
|
22
24
|
...(!missions ? { missionsUnavailableReason: process.env.SPECRAILS_AGENT_CHAT === 'false' ? 'Missions are disabled on Desktop.' : exports.MISSION_ACCESS_REASON } : {}) };
|
|
23
25
|
}
|
|
24
26
|
function conversationView(value) {
|
|
@@ -72,7 +74,7 @@ function missionSnapshot(value) {
|
|
|
72
74
|
pendingMessages, pendingTruncated: pending.length > 50,
|
|
73
75
|
live: { ...pick(live, ['isStreaming', 'startedAt']), streamingText, textTruncated: streamingText.length < liveRaw.length } };
|
|
74
76
|
}
|
|
75
|
-
function createMobileMissionsRouter({ db, upstream }) {
|
|
77
|
+
function createMobileMissionsRouter({ db, upstream, appPreview }) {
|
|
76
78
|
const router = (0, express_1.Router)();
|
|
77
79
|
const segment = (value) => typeof value === 'string' ? value : '';
|
|
78
80
|
const send = async (res, method, path, body, shape) => {
|
|
@@ -84,7 +86,7 @@ function createMobileMissionsRouter({ db, upstream }) {
|
|
|
84
86
|
res.status(502).json({ error: 'Desktop is unreachable. Retry when connected.' });
|
|
85
87
|
}
|
|
86
88
|
};
|
|
87
|
-
router.get('/capabilities', (req, res) => res.json(mobileCapabilities(db, req.mobileDevice.id)));
|
|
89
|
+
router.get('/capabilities', (req, res) => res.json(mobileCapabilities(db, req.mobileDevice.id, { appPreview: appPreview?.() ?? false })));
|
|
88
90
|
router.get('/mission-models', (req, res) => {
|
|
89
91
|
const provider = segment(req.query.provider);
|
|
90
92
|
if (!/^[a-z0-9_-]{1,40}$/.test(provider)) {
|