@fleetless/sdk 3.0.1 → 3.0.2

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/CHANGELOG.md CHANGED
@@ -4,6 +4,73 @@ All notable changes to `@fleetless/sdk`. The format follows Keep a Changelog; th
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [3.0.2] — 2026-09-07
8
+
9
+ No API change, and one documentation example that now compiles. The guard
10
+ that was supposed to prove 3.0.1 clean was **green over four internal
11
+ references still inside the published bundle**, so it was rebuilt around the
12
+ question rather than around three directory names.
13
+
14
+ ### Fixed
15
+
16
+ - **Four internal references in the published 3.0.1 bundle**, which the guard
17
+ could not see: an internal decision label in `dist/index.js` and
18
+ `dist/index.cjs`, and a sentence in both about how a defect class had been
19
+ found. Measured against the registry rather than against this tree — 3.0.1
20
+ scores 4, 3.0.2 scores 0, and 3.0.0 scores 423.
21
+ - **The README's error-handling example did not compile.** `details` is
22
+ `unknown` on `FleetlessError`, deliberately, and the block read fields off
23
+ it directly — two `TS2339`s in a TypeScript SDK's own README, in the block a
24
+ reader copies first. It now narrows before reading, and it is opted in to
25
+ the documentation type check so it cannot drift again.
26
+ - **`Node 20 or newer` was listed as a runtime for the whole SDK.** Node 20
27
+ has no global `WebSocket` — it is behind a flag there — so anything realtime
28
+ fails on it with the SDK's own `no_websocket`. The README names Node 22, and
29
+ says what to pass on Node 20 instead.
30
+ - **Everything published pointed at a public repository that does not exist
31
+ yet.** `repository`, `bugs.url`, the README, `CONTRIBUTING.md` and
32
+ `RELEASING.md` all named it, and its four relative links resolved to 404s on
33
+ the npm page. They point at the maintainer address until the repository is
34
+ opened.
35
+ - **`CONTRIBUTING.md` linked `RELEASING.md`, which the tarball did not
36
+ carry.** `RELEASING.md` ships now, and every relative link in every shipped
37
+ document is checked against the tarball's own entries.
38
+ - **Each published file declares exactly one licence.** The bundler inlines
39
+ `@fleetless/contracts`, which is Apache-2.0, and the declaration files
40
+ carried six of its SPDX identifiers below this package's MIT one. A licence
41
+ scan over `node_modules` reported Apache obligations for a package that
42
+ ships no Apache text. The inherited identifiers are stripped at build time.
43
+ - **A CI comment sent whoever was reading a red publish job to a README
44
+ section that does not exist**, and `RELEASING.md` told a maintainer the
45
+ suite runs against a fake `fetch` — which `CONTRIBUTING.md` devotes a
46
+ section to denying, and which is untrue of three of the nineteen suites.
47
+
48
+ ### Changed
49
+
50
+ - **The guard scans what becomes public, computed rather than named.** The
51
+ union of what `npm pack` reports, what `git ls-files` reports and a walk of
52
+ every directory in `files`. Ten tracked files were outside the previous
53
+ shape, two of them published bytes; one of those two had already carried an
54
+ internal host to the registry inside `devDependencies`.
55
+ - **Only the German scan strips anything**, and only URLs and single-token
56
+ code spans. Stripping links and backticks before every class made the six
57
+ shipped documents blind to an internal hostname inside a markdown link.
58
+ - **Eighteen detectors, each carrying its own two fixtures**, with a floor
59
+ over the count. Seven of the previous fifteen had no fixture proving they
60
+ could fire, and two could be deleted with the whole suite green.
61
+ - **The tarball guard asserts absence as well as presence**: nothing outside
62
+ `files`, no sourcemaps, no source. It reads every dependency section rather
63
+ than the runtime one alone, and runs the marker detectors over the packed
64
+ manifest.
65
+
66
+ ### Added
67
+
68
+ - **`test/readme-pointers.test.ts`**: every pointer from the code into the
69
+ README resolves to a heading that is there. That was the defect 3.0.1 fixed
70
+ and left unguarded, one of them inside a runtime error message.
71
+ - **`scripts/verify-commit-messages.mjs`**, wired into the verify job. A
72
+ commit message is public the moment it is pushed.
73
+
7
74
  ## [3.0.1] — 2026-09-07
8
75
 
9
76
  No API change. This release exists because **3.0.0 published internal material
@@ -18,17 +85,33 @@ without it.
18
85
  comment beside it. In 3.0.0 those paths encoded a `git+ssh://` dependency
19
86
  specifier, so an internal hostname appeared **48 times** across the two
20
87
  bundles; the inlined comments carried German paragraphs and internal defect
21
- ids with them. Measured over the published tarballs, the same seventeen
22
- detectors over `dist/`:
23
-
24
- | | 3.0.0 | 3.0.1 |
25
- |---|---|---|
26
- | internal host or workspace name | 49 | 0 |
88
+ ids with them.
89
+
90
+ The table this entry first carried cited "the same seventeen detectors",
91
+ while the entry below it said the guard had fifteen classes and the guard
92
+ itself asserted fifteen. No seventeen-detector artefact ever existed, so
93
+ the numbers could not be reproduced from anything shipped. They are
94
+ replaced here by a measurement of the two **published tarballs** with the
95
+ guard as rebuilt in 3.0.2, which is a thing a reader can run:
96
+
97
+ | class | 3.0.0 | 3.0.1 |
98
+ |---|---:|---:|
99
+ | terse schedule label | 134 | 0 |
27
100
  | German prose | 58 | 0 |
28
- | internal wave labels | 134 | 0 |
29
- | internal decision labels | 42 | 0 |
30
- | internal defect ids | 14 | 0 |
31
- | every other class | 62 | 0 |
101
+ | internal host or workspace name | 49 | 0 |
102
+ | internal decision label | 44 | **2** |
103
+ | a reference to this package rather than the reader's | 30 | 0 |
104
+ | internal review codename | 28 | 0 |
105
+ | a reference to a document the reader does not have | 26 | 0 |
106
+ | longer schedule label | 16 | 0 |
107
+ | how the behaviour was found | 16 | **2** |
108
+ | internal defect id | 14 | 0 |
109
+ | internal feature id | 4 | 0 |
110
+ | the reference robot by name | 4 | 0 |
111
+ | **total** | **423** | **4** |
112
+
113
+ The four remaining in 3.0.1 are the subject of 3.0.2 below. They are here
114
+ rather than in that entry because this is the entry that claimed zero.
32
115
 
33
116
  None of it appeared in this repository's own source, which is why no sweep of
34
117
  the source had found it. The fix is the contracts pin — `@fleetless/contracts`
package/CONTRIBUTING.md CHANGED
@@ -88,9 +88,10 @@ came back and on which line.
88
88
 
89
89
  ## Pull requests
90
90
 
91
- **Pull requests are welcome on GitHub**, at
92
- <https://github.com/fleetless/sdk>. Open an issue first for anything that
93
- changes an existing method signature or what a method sends, so we can say what
91
+ **The public repository is not open yet.** It will be, and this section
92
+ describes how it will work then. Until it exists, send patches and questions to
93
+ <hello@fleetless.dev>. Either way, raise anything that changes an existing
94
+ method signature or what a method sends before you write it, so we can say what
94
95
  else has to move with it.
95
96
 
96
97
  **CI runs on GitLab.** This repository is mirrored from an internal GitLab
package/README.md CHANGED
@@ -12,8 +12,12 @@ behind roles. The SDK covers all of it: datapoints (read, subscribe, recorded
12
12
  history), actions, services, publishers, cameras, jobs, assets and URDF, and
13
13
  the whole client auth API your own sign-in UI calls. It is framework-agnostic,
14
14
  ships ESM and CJS with its own types, and runs wherever a `fetch` and a
15
- `WebSocket` exist — a browser, a mobile webview, Node 20 or newer, and
16
- server-side with a server key instead of a user session.
15
+ `WebSocket` exist — a browser, a mobile webview, Node 22 or newer, and
16
+ server-side with a server key instead of a user session. On Node 20 the REST
17
+ half works as it stands, but there is no global `WebSocket` (it is behind
18
+ `--experimental-websocket` there), so anything realtime needs one supplied:
19
+ `createClient({ …, WebSocket: (await import('ws')).WebSocket })`, or the flag.
20
+ Without it, `subscribe` calls `onError` with `no_websocket`.
17
21
 
18
22
  **The mental model in one paragraph.** A robot runs the Fleetless bridge, a
19
23
  ROS 2 node. In the [Fleetless Console](https://console.fleetless.dev) a
@@ -262,7 +266,7 @@ it is not true. So *how often* and *when* to publish is your application's
262
266
  decision, made where it can see the user's intent:
263
267
 
264
268
  ```ts
265
- // A control loop is yours to write, and yours to stop.
269
+ // A control loop is yours to write, and yours to stop. `speed` is your app's.
266
270
  const timer = setInterval(() => {
267
271
  void client.publishers.publish(robotId, 'cmd_vel', { 'linear.x': speed })
268
272
  }, 100)
@@ -300,27 +304,48 @@ Every refusal arrives as a `FleetlessError` with a stable `code`. Branch on the
300
304
  code; never parse the message, which is written for a developer reading a
301
305
  console and may change.
302
306
 
303
- ```ts
304
- import { FleetlessError } from '@fleetless/sdk'
307
+ `error.details` is typed `unknown`, deliberately: it is whatever the server
308
+ sent, and the SDK does not pretend to have validated it. Narrow it before you
309
+ read a field. `BusyDetails` and `RateLimitDetails` are exported as types, and
310
+ `parameterInvalidDetails` as a runtime schema you can `parse`.
311
+
312
+ ```ts checked
313
+ import { createClient, FleetlessError } from '@fleetless/sdk'
314
+ import type { BusyDetails, RateLimitDetails } from '@fleetless/sdk'
315
+
316
+ declare function showRunning(job: BusyDetails['running'] | undefined): void
317
+ declare function showOffline(): void
318
+ declare function showNotAllowed(): void
319
+ declare function showRetryLater(ms: number | undefined): void
320
+ declare function showUnexpected(error: FleetlessError): void
321
+
322
+ const client = createClient({ apiUrl: 'https://api.fleetless.dev', appIdentifier: 'warehouse_dash' })
323
+ const robotId = '4f2c1a90-7b3e-4d51-9c86-0a1b2c3d4e5f'
324
+
325
+ /** `details` is server-shaped: check the field is there before reading it. */
326
+ const field = <T,>(details: unknown, key: string): T | undefined =>
327
+ typeof details === 'object' && details !== null && key in details
328
+ ? ((details as Record<string, unknown>)[key] as T)
329
+ : undefined
305
330
 
306
331
  try {
307
332
  await client.actions.invoke(robotId, 'dock', {})
308
333
  } catch (error) {
309
334
  if (!(error instanceof FleetlessError)) throw error
310
335
  switch (error.code) {
311
- case 'busy': return showRunning(error.details?.running)
312
- case 'robot_offline': return showOffline()
313
- case 'forbidden': return showNotAllowed()
314
- case 'rate_limited': return showRetryLater(error.details?.retry_after_ms)
315
- default: return showUnexpected(error)
336
+ case 'busy': showRunning(field<BusyDetails['running']>(error.details, 'running')); break
337
+ case 'robot_offline': showOffline(); break
338
+ case 'forbidden': showNotAllowed(); break
339
+ case 'rate_limited': showRetryLater(field<RateLimitDetails['retry_after_ms']>(error.details, 'retry_after_ms')); break
340
+ default: showUnexpected(error); break
316
341
  }
317
342
  }
318
343
  ```
319
344
 
320
345
  - **`rate_limited` is surfaced, never retried behind your back.** The SDK does
321
- not sleep and re-send. `error.details.retry_after_ms` carries the wait the
322
- platform asked for, when it sent one — read it defensively, since a refusal
323
- from an intermediary may carry no `details` at all.
346
+ not sleep and re-send. `details.retry_after_ms` carries the wait the platform
347
+ asked for, when it sent one — read it defensively, since a refusal from an
348
+ intermediary may carry no `details` at all.
324
349
  - **The one retry the SDK does perform is `token_expired`**, and only once: it
325
350
  refreshes the session and re-sends the same request. That refresh is
326
351
  single-flight, so ten concurrent calls meeting an expired token share one.
@@ -361,7 +386,8 @@ belongs to the platform instead.
361
386
 
362
387
  ## Contributing
363
388
 
364
- Pull requests are welcome at <https://github.com/fleetless/sdk>. Read
389
+ The public repository is not open yet. Until it is, send patches and questions
390
+ to <hello@fleetless.dev>. Read
365
391
  [CONTRIBUTING.md](CONTRIBUTING.md) first: it covers the setup, the checks, the
366
392
  Contributor Licence Agreement, and the one rule this repository is strict about
367
393
  — the auth suites drive the SDK's own `fetch` against a real `node:http`
package/RELEASING.md ADDED
@@ -0,0 +1,195 @@
1
+ # Releasing `@fleetless/sdk`
2
+
3
+ Maintainer notes: the development setup, the checks, and the release
4
+ procedure. This file is not part of the published package.
5
+
6
+ **CI runs on GitLab; the repository will mirror to GitHub.** The pipeline
7
+ that verifies and publishes this package lives on an internal GitLab instance,
8
+ and that is the only thing that publishes to npm. The public repository does not
9
+ exist yet; when it does it is a mirror, where issues and pull requests arrive
10
+ and no check runs. A contributor's pull request is verified by a maintainer
11
+ running the same commands locally — see
12
+ [CONTRIBUTING.md](CONTRIBUTING.md), which says so to the contributor as well.
13
+
14
+ A mirror pushes what it is given, so **everything in a commit becomes public
15
+ the moment it is pushed**, including the commit message and every file the
16
+ branch touched.
17
+ ## Development setup
18
+
19
+ Node 22 via `nvm`, and pnpm through corepack:
20
+
21
+ ```sh
22
+ nvm use 22
23
+ corepack enable
24
+ pnpm install
25
+ ```
26
+
27
+ **That install needs nothing private.** `@fleetless/contracts` — the source
28
+ of truth for every wire type this SDK reads or writes — is a devDependency on
29
+ the public npm package, pinned to an exact version. It used to be a private
30
+ `git+ssh://` URL that only somebody with GitLab group access could install;
31
+ that is over. The published SDK is unaffected either way: `tsup` inlines
32
+ contracts' types into `dist/`, so a consumer of `@fleetless/sdk` never
33
+ resolves it.
34
+
35
+ Never redefine a shape the SDK sends to or reads from the API. Import it from
36
+ `@fleetless/contracts` instead.
37
+
38
+ ## Checks
39
+
40
+ | Command | What it does |
41
+ |---|---|
42
+ | `pnpm typecheck` | `tsc --noEmit` over the package. |
43
+ | `pnpm test` | vitest. Most suites drive a fake `fetch` and a fake WebSocket; the three auth suites drive the SDK's own default `fetch` against a real `node:http` server (`test/local-api.ts`). No cloud needed. |
44
+ | `pnpm build` | tsup into `dist/` — ESM, CJS and `.d.ts`. |
45
+ | `pnpm run test:pack` | `npm pack`s the tarball, installs it into a bare project with nothing but `typescript`, and typechecks and runs real usage against it. This is what catches a `dist/index.d.ts` that still imports from `@fleetless/contracts` — a devDependency, so a consumer of the SDK never installs it. |
46
+
47
+ Two further scripts check the built package against a **running** cloud
48
+ (`./infra/dev.sh` in the umbrella repo). Each one runs against `dist/`, not
49
+ `src/`, so run `pnpm build` first, and each fails by name on a missing
50
+ variable rather than defaulting quietly.
51
+
52
+ - `pnpm run verify:live` — a `busy` refusal carrying the job that is already
53
+ running, `command_outcome_unknown` and its documented recovery, a
54
+ `parameter_invalid` naming the flat key, job-id-addressed cancel, and
55
+ per-session camera release. Needs `FLEETLESS_API_URL` (or `API`),
56
+ `APP_IDENTIFIER`, `EMAIL`, `PASSWORD`, `ROBOT_ID`, `ACTION_SLUG`,
57
+ `SERVICE_SLUG`, `CAMERA_SLUG`, and two optional identities:
58
+ - `SECOND_EMAIL` — a second, distinct **app user** with the same role,
59
+ without which the busy check only proves that a second request is refused,
60
+ not that a different user's is.
61
+ - `OBSERVER_EMAIL` — a **third** app user, and the one whose password check
62
+ [6] rotates and restores. Leave it unset and the round trip runs on
63
+ `EMAIL`, the identity every other check in the file signs in as; the
64
+ script says so out loud when that happens. `infra/seed-dev.mjs --env`
65
+ exports all three.
66
+
67
+ Since 3.0.0 it also drives the client auth API as check [6]: `listProviders`
68
+ without a session, `login`, `me` (kind, app, role, address), `logout`, the
69
+ `changePassword` round trip with its restore, and the reset acknowledgement
70
+ compared byte for byte across a known and an unknown address. What it does
71
+ **not** drive is anything needing a mailed token — `register`,
72
+ `verifyEmail`, `acceptInvitation`, `confirmPasswordReset` — or the federated
73
+ and MCP-consent flows. Those are `infra/browser/app-auth-check.mjs`'s
74
+ subject, which reads maildev and drives a real Keycloak.
75
+ - `pnpm run verify:history` — a relative range and its absolute equivalent
76
+ returning the same samples, aggregation matching arithmetic done here from
77
+ the raw rows, and the `not_recorded` / `not_aggregatable` refusals. Needs
78
+ `FLEETLESS_API_URL`, `APP_IDENTIFIER`, `EMAIL`, `PASSWORD`, `ROBOT_ID`,
79
+ `RECORDED_NUMERIC_SLUG`, `LIVE_ONLY_SLUG`, and optionally
80
+ `NON_NUMERIC_RECORDED_SLUG`.
81
+ (`verify:hosted-login` is gone. The hosted login flow it drove —
82
+ `beginHostedLogin` / `completeHostedLogin` and the app OAuth client behind
83
+ them — was removed in 3.0.0 along with the script and its `package.json`
84
+ entry.)
85
+
86
+ `infra/seed-dev.mjs --env` in the umbrella repo exports most of those
87
+ variables for a freshly seeded world.
88
+
89
+ ## Commits
90
+
91
+ [Conventional Commits](https://www.conventionalcommits.org/). English, for
92
+ code, comments, commit messages and everything else that lands in the
93
+ repository.
94
+
95
+ ## Releasing
96
+
97
+ Every version on npm is published by this repository's GitLab pipeline from a
98
+ release tag. `npm publish` by hand is retired.
99
+
100
+ 1. Add the version's entry to `CHANGELOG.md` and set `version` in
101
+ `package.json` to the same number.
102
+ 2. Commit (`chore(release): X.Y.Z`), push, and wait for the branch pipeline's
103
+ `verify` job to go green.
104
+ 3. `git tag vX.Y.Z && git push origin vX.Y.Z`. The tag pipeline runs `verify`
105
+ again and then `publish`.
106
+ 4. Check the registry yourself. The `publish` job already asserts the first
107
+ line; this is the independent look, and the second line is the one that
108
+ says which dist-tag moved.
109
+
110
+ ```sh
111
+ npm view @fleetless/sdk@X.Y.Z version # answers X.Y.Z
112
+ npm view @fleetless/sdk dist-tags # latest -> X.Y.Z, or next -> X.Y.Z
113
+ ```
114
+
115
+ Name the version. A bare `npm view @fleetless/sdk version` resolves the
116
+ `latest` dist-tag, so after a pre-release publish it answers the *previous*
117
+ stable release and reads as a publish that did not happen.
118
+
119
+ A pre-release tag — `vX.Y.Z-beta.1`, `vX.Y.Z-rc.2` — publishes under the npm
120
+ dist-tag `next` instead of `latest`. Nothing else about it differs, and it is
121
+ exactly why step 4 names the version.
122
+
123
+ **A red `publish` job does not mean nothing was published.** The job runs
124
+ `npm publish` and then looks the version up on the registry for about two
125
+ minutes; npm answers reads from a replica that lags a publish, so the lookup
126
+ can time out on a version that did land. The job says so itself, and it is
127
+ worth repeating here because a red pipeline invites exactly one reaction —
128
+ press retry — and that reaction cannot work: npm refuses to publish over an
129
+ existing version, so the retry ends in a 403 that reads like a broken
130
+ pipeline rather than like a release that already happened.
131
+
132
+ > publish may have succeeded; the registry has not served the version yet; do
133
+ > NOT retry this job (npm refuses to republish a version) — check
134
+ > `npm view @fleetless/sdk@$VERSION` by hand
135
+
136
+ If the hand check answers the version, the release is done: move the dist-tag
137
+ by hand if it is wrong (`npm dist-tag add @fleetless/sdk@X.Y.Z latest`) and
138
+ leave the job red. If it answers nothing after several minutes, the publish
139
+ genuinely did not land and the job can be retried.
140
+
141
+ **A red `publish` job that ends in `npm error code EOTP` published nothing.**
142
+ npm is asking for a one-time password, which a pipeline cannot supply: the
143
+ token in `NPM_TOKEN` does not bypass two-factor authentication, or the
144
+ package's *Publishing access* setting on npmjs.com disallows tokens. Fix the
145
+ token (a granular access token created with *Bypass two-factor
146
+ authentication*) or the package setting (*Require two-factor authentication
147
+ or an automation token*), then retry the job — nothing reached the registry,
148
+ so a retry is safe here — measured on a pipeline that hit it.
149
+
150
+ **The pipeline refuses a tag whose version disagrees with `package.json`.**
151
+ `scripts/verify-version-tag.mjs` is the one place that rule lives; `verify`
152
+ runs it first on a tag pipeline, so a mistyped tag fails in seconds and
153
+ `publish` never starts (measured: `verify-version-tag: tag v9.9.9 names
154
+ 9.9.9 but package.json says 1.0.0`, publish skipped).
155
+
156
+ Removing that bad tag takes the API, not git. `v*` is a **protected** tag
157
+ pattern, so a delete over git is refused by the server with nothing but
158
+ `! [remote rejected] v9.9.9 (pre-receive hook declined)`:
159
+
160
+ ```sh
161
+ git tag -d vX.Y.Z # local
162
+ glab api -X DELETE projects/37/repository/tags/vX.Y.Z # remote
163
+ ```
164
+
165
+ Then fix `package.json` and tag again.
166
+
167
+ ### The one-time setting
168
+
169
+ It is already in place; it is written down because nothing in this repository
170
+ would tell you it exists if it were removed.
171
+
172
+ There used to be a second one — `fleetless/fleetless-sdk` on the job-token
173
+ allow-list of `fleetless/fleetless-contracts`, so the pipeline could rewrite
174
+ the private `git+ssh://` contracts URL to HTTPS with `CI_JOB_TOKEN`. Contracts
175
+ is a public npm package now, the rewrite is gone from `.gitlab-ci.yml`, and
176
+ the allow-list entry buys this project nothing. Removing it is safe; leaving
177
+ it is harmless.
178
+
179
+ - **`NPM_TOKEN` is a protected, masked CI variable on this project**, holding
180
+ an npm granular automation token with publish rights on `@fleetless/sdk`.
181
+ Protected means an unprotected ref receives an *empty* value rather than no
182
+ value, which is why the tag pattern `v*` is a protected tag and why the
183
+ `publish` job's first action is to refuse an empty `NPM_TOKEN` by name.
184
+
185
+ ```sh
186
+ glab api projects/37/protected_tags # v*, create access: Maintainers
187
+ ```
188
+
189
+ The token reaches npm through an `.npmrc` written in the job's working
190
+ directory holding `//registry.npmjs.org/:_authToken=${NPM_TOKEN}` **literally**
191
+ — npm expands the variable when it reads the file, so the secret itself never
192
+ lands on disk, and `after_script` removes the file either way. It cannot be
193
+ passed as an environment assignment instead: `NPM_CONFIG_//registry…` is not a
194
+ valid shell identifier, so both `bash` and `dash` parse it as a command name.
195
+
package/dist/index.cjs CHANGED
@@ -144,7 +144,7 @@ var HttpClient = class {
144
144
  * RequestOptionsFor<P>` would type-check by construction — the cast
145
145
  * bypasses the very check this exists to add, which is the identical
146
146
  * "special case that quietly exempts calls from the general rule" shape
147
- * this file has already been caught by once. Every current call site to a route
147
+ * this file has already made once. Every current call site to a route
148
148
  * with no body already passes `{}` explicitly for exactly this reason.
149
149
  */
150
150
  async request(path, options) {
@@ -441,7 +441,7 @@ function createAssetsApi(http) {
441
441
  };
442
442
  }
443
443
 
444
- // node_modules/.pnpm/@fleetless+contracts@1.0.4/node_modules/@fleetless/contracts/dist/common.js
444
+ // node_modules/.pnpm/@fleetless+contracts@1.0.5/node_modules/@fleetless/contracts/dist/common.js
445
445
  var import_zod = require("zod");
446
446
  var SLUG_RULE = "A name is lower-case: it starts with a letter, continues with letters and digits, and joins further words with a single underscore \u2014 `battery_voltage`. Capitals, dashes, dots, spaces, a leading digit and a doubled or trailing underscore are all refused.";
447
447
  var slug = import_zod.z.string().min(2).max(63).regex(/^[a-z][a-z0-9]*(?:_[a-z0-9]+)*$/, SLUG_RULE);
@@ -465,7 +465,7 @@ var applyError = import_zod.z.object({
465
465
  details: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).optional()
466
466
  });
467
467
 
468
- // node_modules/.pnpm/@fleetless+contracts@1.0.4/node_modules/@fleetless/contracts/dist/mcp.js
468
+ // node_modules/.pnpm/@fleetless+contracts@1.0.5/node_modules/@fleetless/contracts/dist/mcp.js
469
469
  var import_zod2 = require("zod");
470
470
  function mcpAppEndpointPath(appIdentifier2) {
471
471
  return `/mcp/${appIdentifier2}`;
@@ -514,10 +514,10 @@ var mcpRolePreviewResponse = import_zod2.z.object({
514
514
  });
515
515
  var MCP_ASSET_LINK_TTL_MS = 15 * 60 * 1e3;
516
516
 
517
- // node_modules/.pnpm/@fleetless+contracts@1.0.4/node_modules/@fleetless/contracts/dist/protocol.js
517
+ // node_modules/.pnpm/@fleetless+contracts@1.0.5/node_modules/@fleetless/contracts/dist/protocol.js
518
518
  var import_zod8 = require("zod");
519
519
 
520
- // node_modules/.pnpm/@fleetless+contracts@1.0.4/node_modules/@fleetless/contracts/dist/assets.js
520
+ // node_modules/.pnpm/@fleetless+contracts@1.0.5/node_modules/@fleetless/contracts/dist/assets.js
521
521
  var import_zod3 = require("zod");
522
522
  var assetKind = import_zod3.z.enum(["urdf", "mesh", "texture", "other"]);
523
523
  var asset = import_zod3.z.object({
@@ -775,10 +775,10 @@ var assetSyncBusyDetails = import_zod3.z.object({
775
775
  started_at_ms: import_zod3.z.number().int().nonnegative()
776
776
  });
777
777
 
778
- // node_modules/.pnpm/@fleetless+contracts@1.0.4/node_modules/@fleetless/contracts/dist/config.js
778
+ // node_modules/.pnpm/@fleetless+contracts@1.0.5/node_modules/@fleetless/contracts/dist/config.js
779
779
  var import_zod5 = require("zod");
780
780
 
781
- // node_modules/.pnpm/@fleetless+contracts@1.0.4/node_modules/@fleetless/contracts/dist/alerts.js
781
+ // node_modules/.pnpm/@fleetless+contracts@1.0.5/node_modules/@fleetless/contracts/dist/alerts.js
782
782
  var import_zod4 = require("zod");
783
783
  var alertRowCondition = import_zod4.z.discriminatedUnion("kind", [
784
784
  import_zod4.z.strictObject({
@@ -848,7 +848,7 @@ var putDatapointDisplayRequest = import_zod4.z.object({
848
848
  y_max: import_zod4.z.number().finite().nullable()
849
849
  }).strict();
850
850
 
851
- // node_modules/.pnpm/@fleetless+contracts@1.0.4/node_modules/@fleetless/contracts/dist/config.js
851
+ // node_modules/.pnpm/@fleetless+contracts@1.0.5/node_modules/@fleetless/contracts/dist/config.js
852
852
  var RTSP_URL_RULE = "The URL has to begin with `rtsp://` or `rtsps://` \u2014 `rtsp://cam-1.plant.local/stream1`. No other scheme is accepted: the bridge opens this with a library that would equally honour `file:`.";
853
853
  var MJPEG_URL_RULE = "The URL has to begin with `http://` or `https://` \u2014 `http://cam-1.plant.local/video.mjpg`. No other scheme is accepted: the bridge opens this with a library that would equally serve `file:`.";
854
854
  var DEVICE_PATH_RULE = "A capture device is a path under `/dev/`, and the character straight after it is a letter or a digit \u2014 `/dev/video0`, or a stable `/dev/v4l/by-id/...` symlink. Nothing outside `/dev/` is accepted: the string reaches OpenCV, which would as happily open an ordinary file.";
@@ -1895,7 +1895,7 @@ var configState = import_zod5.z.object({
1895
1895
  applied_errors: import_zod5.z.array(applyError).nullable()
1896
1896
  });
1897
1897
 
1898
- // node_modules/.pnpm/@fleetless+contracts@1.0.4/node_modules/@fleetless/contracts/dist/introspection.js
1898
+ // node_modules/.pnpm/@fleetless+contracts@1.0.5/node_modules/@fleetless/contracts/dist/introspection.js
1899
1899
  var import_zod6 = require("zod");
1900
1900
  var rosGraphEntry = import_zod6.z.object({
1901
1901
  name: rosName,
@@ -1934,7 +1934,7 @@ var typeDefinition = import_zod6.z.discriminatedUnion("kind", [
1934
1934
  })
1935
1935
  ]);
1936
1936
 
1937
- // node_modules/.pnpm/@fleetless+contracts@1.0.4/node_modules/@fleetless/contracts/dist/jobs.js
1937
+ // node_modules/.pnpm/@fleetless+contracts@1.0.5/node_modules/@fleetless/contracts/dist/jobs.js
1938
1938
  var import_zod7 = require("zod");
1939
1939
  var jobState = import_zod7.z.enum(["running", "succeeded", "failed", "cancelled", "lost"]);
1940
1940
  var job = import_zod7.z.object({
@@ -2152,7 +2152,7 @@ var jobRunSummary = import_zod7.z.object({
2152
2152
  since_ms: import_zod7.z.number().int().nonnegative()
2153
2153
  });
2154
2154
 
2155
- // node_modules/.pnpm/@fleetless+contracts@1.0.4/node_modules/@fleetless/contracts/dist/protocol.js
2155
+ // node_modules/.pnpm/@fleetless+contracts@1.0.5/node_modules/@fleetless/contracts/dist/protocol.js
2156
2156
  var MAX_PATIENCE_MS = 12e4;
2157
2157
  var MIN_PATIENCE_MS = 1e3;
2158
2158
  var activeJob = import_zod8.z.object({
@@ -2551,11 +2551,11 @@ var bridgeCameraState = import_zod8.z.object({
2551
2551
  request_id: import_zod8.z.string().min(1).max(64).nullable()
2552
2552
  });
2553
2553
 
2554
- // node_modules/.pnpm/@fleetless+contracts@1.0.4/node_modules/@fleetless/contracts/dist/config-issues.js
2554
+ // node_modules/.pnpm/@fleetless+contracts@1.0.5/node_modules/@fleetless/contracts/dist/config-issues.js
2555
2555
  var EXPOSURE_SECTIONS = ["datapoints", "actions", "services", "publishers", "cameras"];
2556
2556
  var EXPOSURE_SECTION_NAMES = new Set(EXPOSURE_SECTIONS);
2557
2557
 
2558
- // node_modules/.pnpm/@fleetless+contracts@1.0.4/node_modules/@fleetless/contracts/dist/rest.js
2558
+ // node_modules/.pnpm/@fleetless+contracts@1.0.5/node_modules/@fleetless/contracts/dist/rest.js
2559
2559
  var import_zod9 = require("zod");
2560
2560
  var robot = import_zod9.z.object({
2561
2561
  id: import_zod9.z.uuid().meta({
@@ -3315,13 +3315,13 @@ var slugUsageResponse = import_zod9.z.object({
3315
3315
  alert_count: import_zod9.z.number().int().nonnegative()
3316
3316
  });
3317
3317
 
3318
- // node_modules/.pnpm/@fleetless+contracts@1.0.4/node_modules/@fleetless/contracts/dist/realtime.js
3318
+ // node_modules/.pnpm/@fleetless+contracts@1.0.5/node_modules/@fleetless/contracts/dist/realtime.js
3319
3319
  var import_zod14 = require("zod");
3320
3320
 
3321
- // node_modules/.pnpm/@fleetless+contracts@1.0.4/node_modules/@fleetless/contracts/dist/client-auth.js
3321
+ // node_modules/.pnpm/@fleetless+contracts@1.0.5/node_modules/@fleetless/contracts/dist/client-auth.js
3322
3322
  var import_zod13 = require("zod");
3323
3323
 
3324
- // node_modules/.pnpm/@fleetless+contracts@1.0.4/node_modules/@fleetless/contracts/dist/apps.js
3324
+ // node_modules/.pnpm/@fleetless+contracts@1.0.5/node_modules/@fleetless/contracts/dist/apps.js
3325
3325
  var import_zod10 = require("zod");
3326
3326
  var appIdentifier = slug;
3327
3327
  var app = import_zod10.z.object({
@@ -3503,10 +3503,10 @@ var rolePermissions = import_zod10.z.object({
3503
3503
  })
3504
3504
  });
3505
3505
 
3506
- // node_modules/.pnpm/@fleetless+contracts@1.0.4/node_modules/@fleetless/contracts/dist/app-users.js
3506
+ // node_modules/.pnpm/@fleetless+contracts@1.0.5/node_modules/@fleetless/contracts/dist/app-users.js
3507
3507
  var import_zod12 = require("zod");
3508
3508
 
3509
- // node_modules/.pnpm/@fleetless+contracts@1.0.4/node_modules/@fleetless/contracts/dist/identity.js
3509
+ // node_modules/.pnpm/@fleetless+contracts@1.0.5/node_modules/@fleetless/contracts/dist/identity.js
3510
3510
  var import_zod11 = require("zod");
3511
3511
  var password = import_zod11.z.string().min(12).max(256);
3512
3512
  var USER_DISPLAY_NAME_MAX = 120;
@@ -3668,7 +3668,7 @@ var authMeResponse = import_zod11.z.object({ org, user: fleetlessUser });
3668
3668
  var patchOrgRequest = import_zod11.z.object({ name: import_zod11.z.string().min(1).max(120) }).strict();
3669
3669
  var patchAuthMeRequest = import_zod11.z.object({ display_name: import_zod11.z.string().min(1).max(USER_DISPLAY_NAME_MAX).nullable() }).strict();
3670
3670
 
3671
- // node_modules/.pnpm/@fleetless+contracts@1.0.4/node_modules/@fleetless/contracts/dist/app-users.js
3671
+ // node_modules/.pnpm/@fleetless+contracts@1.0.5/node_modules/@fleetless/contracts/dist/app-users.js
3672
3672
  var APP_USER_DISPLAY_NAME_MAX = 120;
3673
3673
  var providerSlug = import_zod12.z.string().max(40).regex(/^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/, "a provider slug is lowercase and hyphen-separated, starting with a letter");
3674
3674
  var appUserStatus = import_zod12.z.enum(["pending_verification", "active", "blocked"]);
@@ -3919,7 +3919,7 @@ var mailOutcome = import_zod12.z.object({
3919
3919
  })
3920
3920
  });
3921
3921
 
3922
- // node_modules/.pnpm/@fleetless+contracts@1.0.4/node_modules/@fleetless/contracts/dist/client-auth.js
3922
+ // node_modules/.pnpm/@fleetless+contracts@1.0.5/node_modules/@fleetless/contracts/dist/client-auth.js
3923
3923
  var clientLoginRequest = import_zod13.z.object({
3924
3924
  app_identifier: appIdentifier.meta({
3925
3925
  description: "The app being logged in to, as its globally unique identifier \u2014 the lowercase, underscore-separated string the developer chose when the app was created. There is no organisation context at login, so this is what decides which app the credentials are checked for."
@@ -4108,7 +4108,7 @@ var clientIdentity = import_zod13.z.object({
4108
4108
  })
4109
4109
  });
4110
4110
 
4111
- // node_modules/.pnpm/@fleetless+contracts@1.0.4/node_modules/@fleetless/contracts/dist/realtime.js
4111
+ // node_modules/.pnpm/@fleetless+contracts@1.0.5/node_modules/@fleetless/contracts/dist/realtime.js
4112
4112
  var clientAuth = import_zod14.z.object({
4113
4113
  type: import_zod14.z.literal("auth"),
4114
4114
  token: import_zod14.z.string().min(1)
@@ -4386,7 +4386,7 @@ var orgEventDropped = import_zod14.z.object({
4386
4386
  dropped: import_zod14.z.number().int().positive()
4387
4387
  }).strict();
4388
4388
 
4389
- // node_modules/.pnpm/@fleetless+contracts@1.0.4/node_modules/@fleetless/contracts/dist/audit.js
4389
+ // node_modules/.pnpm/@fleetless+contracts@1.0.5/node_modules/@fleetless/contracts/dist/audit.js
4390
4390
  var import_zod15 = require("zod");
4391
4391
  var auditActor = import_zod15.z.object({
4392
4392
  kind: import_zod15.z.enum(["developer", "end_user", "app_user", "server_key", "bridge"]),
@@ -4517,7 +4517,7 @@ var auditListResponse = import_zod15.z.object({
4517
4517
  next_cursor: import_zod15.z.number().int().positive().nullable()
4518
4518
  });
4519
4519
 
4520
- // node_modules/.pnpm/@fleetless+contracts@1.0.4/node_modules/@fleetless/contracts/dist/errors.js
4520
+ // node_modules/.pnpm/@fleetless+contracts@1.0.5/node_modules/@fleetless/contracts/dist/errors.js
4521
4521
  var import_zod16 = require("zod");
4522
4522
  var apiError = import_zod16.z.object({
4523
4523
  code: import_zod16.z.string().min(1),
@@ -4534,7 +4534,7 @@ var parameterInvalidDetails = import_zod16.z.object({
4534
4534
  violations: import_zod16.z.array(parameterViolation).min(1)
4535
4535
  });
4536
4536
 
4537
- // node_modules/.pnpm/@fleetless+contracts@1.0.4/node_modules/@fleetless/contracts/dist/oauth.js
4537
+ // node_modules/.pnpm/@fleetless+contracts@1.0.5/node_modules/@fleetless/contracts/dist/oauth.js
4538
4538
  var import_zod17 = require("zod");
4539
4539
  var oauthErrorCode = import_zod17.z.enum([
4540
4540
  "invalid_request",
@@ -4755,7 +4755,7 @@ var oauthAuthorizeQuery = import_zod17.z.object({
4755
4755
  description: "The authorization request an MCP client sends, per RFC 6749 \xA74.1.1 with mandatory PKCE. The handler reads it parameter by parameter rather than through one parse, because the answers differ: `client_id` and `redirect_uri` are refused flat, with no redirect, since until both are confirmed there is no trusted target to bounce a browser to, and everything after them is reported to the client's own callback as query parameters."
4756
4756
  });
4757
4757
 
4758
- // node_modules/.pnpm/@fleetless+contracts@1.0.4/node_modules/@fleetless/contracts/dist/routes.js
4758
+ // node_modules/.pnpm/@fleetless+contracts@1.0.5/node_modules/@fleetless/contracts/dist/routes.js
4759
4759
  var MCP_APP = MCP_APP_PATHS(":appIdentifier");
4760
4760
  var APP_IDENTIFIER = {
4761
4761
  name: "appIdentifier",
@@ -6355,7 +6355,7 @@ var ROUTES = [
6355
6355
  response: null,
6356
6356
  errors: ["not_found", "unauthorized", "forbidden"],
6357
6357
  transport: "http",
6358
- notes: "MCP's Streamable HTTP gives this path three verbs: `POST` carries JSON-RPC, `GET` opens the server-initiated SSE stream, and `DELETE` ends a session. This server has no sessions \u2014 the argument is in `MCP_PROTOCOL_VERSION`'s own note, and a per-process session map is what breaks at the second cloud instance \u2014 so `GET` and `DELETE` answer `405`, which is what a client is built to fall back from. \n\n**The `405` is this cloud's own answer, not the SDK's**, and the two differ: MCP SDK 1.30.0 opens an SSE stream on `GET` (`handleGetRequest`) and answers `200` on `DELETE` (`handleDeleteRequest`), neither of which a stateless server has any business doing, so the cloud writes the `405` itself in the transport's own JSON-RPC error shape with `Allow: POST`. \n\n**The row exists so that the `405` is not a `404`.** An unregistered verb answers `404`, and at a path whose last segment is an app identifier a `404` already means *no such app* \u2014 one answer for two states, which is one answer for two states. Registering the verb lets the endpoint say \"this app's server is here; this verb is not part of it\". The central `/mcp` registers neither verb and does not need to: its path takes no parameter, so nothing can misread its `404`. \n\n**The `405` body is the transport's JSON-RPC error object, not the `apiError` envelope.** The three codes above are the refusals that come *first* \u2014 the app, its switch, then the bearer, in the order `POST` describes \u2014 and they are `apiError` because they are answered before the transport is reached at all. If this server ever becomes stateful, this row and the `DELETE` beside it are where that lands, and the cloud's route-manifest test is what would make both repositories notice."
6358
+ notes: "MCP's Streamable HTTP gives this path three verbs: `POST` carries JSON-RPC, `GET` opens the server-initiated SSE stream, and `DELETE` ends a session. This server has no sessions \u2014 the argument is in `MCP_PROTOCOL_VERSION`'s own note, and a per-process session map is what breaks at the second cloud instance \u2014 so `GET` and `DELETE` answer `405`, which is what a client is built to fall back from. \n\n**The `405` is this cloud's own answer, not the SDK's**, and the two differ: MCP SDK 1.30.0 opens an SSE stream on `GET` (`handleGetRequest`) and answers `200` on `DELETE` (`handleDeleteRequest`), neither of which a stateless server has any business doing, so the cloud writes the `405` itself in the transport's own JSON-RPC error shape with `Allow: POST`. \n\n**The row exists so that the `405` is not a `404`.** An unregistered verb answers `404`, and at a path whose last segment is an app identifier a `404` already means *no such app* \u2014 so an unregistered verb and an unknown app would answer identically. Registering the verb lets the endpoint say \"this app's server is here; this verb is not part of it\". The central `/mcp` registers neither verb and does not need to: its path takes no parameter, so nothing can misread its `404`. \n\n**The `405` body is the transport's JSON-RPC error object, not the `apiError` envelope.** The three codes above are the refusals that come *first* \u2014 the app, its switch, then the bearer, in the order `POST` describes \u2014 and they are `apiError` because they are answered before the transport is reached at all. If this server ever becomes stateful, this row and the `DELETE` beside it are where that lands, and the cloud's route-manifest test is what would make both repositories notice."
6359
6359
  },
6360
6360
  {
6361
6361
  method: "DELETE",
@@ -7320,7 +7320,7 @@ var ROUTES = [
7320
7320
  response: jobRunListResponse,
7321
7321
  errors: [...CLIENT_GUARD, "invalid_uuid", "not_found", "capability_required", "validation_error"],
7322
7322
  transport: "http",
7323
- notes: "Needs the `action_history` capability, and **this route is what makes that switch mean something** \u2014 it was unkeepable while nothing durable recorded what had run. Two residuals worth stating rather than implying away. `history` is a syntactically valid slug and The router matches a static segment first, so a robot with a service literally slugged `history` can no longer be **read** through `GET /api/robots/:id/jobs/:slug`; invoking, cancelling and the listing are unaffected. And a run row names its actor by email address, so an end user holding this capability learns which other people have been driving the machine. `robot_id` in the query is shared with the org-wide read; a *different* one here is refused rather than quietly answered about the robot in the path."
7323
+ notes: "Needs the `action_history` capability, and **this route is what makes that switch mean something** \u2014 it was unkeepable while nothing durable recorded what had run. Two residuals worth stating rather than implying away. `history` is a syntactically valid slug, and a static segment matches before a parameter, so a robot with a service literally slugged `history` can no longer be **read** through `GET /api/robots/:id/jobs/:slug`; invoking, cancelling and the listing are unaffected. And a run row names its actor by email address, so an end user holding this capability learns which other people have been driving the machine. `robot_id` in the query is shared with the org-wide read; a *different* one here is refused rather than quietly answered about the robot in the path."
7324
7324
  },
7325
7325
  {
7326
7326
  method: "POST",
@@ -8339,7 +8339,7 @@ function createRealtimeCommandTransport(channel) {
8339
8339
  return {
8340
8340
  // Declared `async` deliberately, unlike `cancel`/`publish` below: it is
8341
8341
  // the only one of the three that can refuse *before* sending anything
8342
- // (resolveLocalWaitMs's `invalid_option`, D3a), and every caller of
8342
+ // (`resolveLocalWaitMs`'s `invalid_option`), and every caller of
8343
8343
  // this interface — starting with this file's own `sendCommand` callers
8344
8344
  // — is entitled to assume `CommandTransport.invoke` always returns a
8345
8345
  // promise rather than throwing synchronously. Without `async` here, a
package/dist/index.d.cts CHANGED
@@ -1,7 +1,6 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
  import { z } from 'zod';
3
3
 
4
- // SPDX-License-Identifier: Apache-2.0
5
4
 
6
5
  /**
7
6
  * Jobs: one running unit of work on a robot — an action
@@ -234,7 +233,6 @@ declare const historyBucketsResponse: z.ZodObject<{
234
233
  }, z.core.$strip>;
235
234
  type HistoryBucketsResponse = z.infer<typeof historyBucketsResponse>;
236
235
 
237
- // SPDX-License-Identifier: Apache-2.0
238
236
 
239
237
  /**
240
238
  * One datapoint sample pushed to a subscriber. The current value arrives
@@ -250,7 +248,6 @@ declare const datapointEvent: z.ZodObject<{
250
248
  }, z.core.$strip>;
251
249
  type DatapointEvent = z.infer<typeof datapointEvent>;
252
250
 
253
- // SPDX-License-Identifier: Apache-2.0
254
251
 
255
252
  /**
256
253
  * Access plus refresh. The access token is short-lived; the
@@ -267,7 +264,6 @@ declare const sessionTokens: z.ZodObject<{
267
264
  }, z.core.$strip>;
268
265
  type SessionTokens = z.infer<typeof sessionTokens>;
269
266
 
270
- // SPDX-License-Identifier: Apache-2.0
271
267
 
272
268
  /**
273
269
  * **Why a federated sign-in ended without a session, in a code the app can
@@ -416,7 +412,6 @@ declare const clientIdentity: z.ZodObject<{
416
412
  }, z.core.$strip>;
417
413
  type ClientIdentity = z.infer<typeof clientIdentity>;
418
414
 
419
- // SPDX-License-Identifier: Apache-2.0
420
415
 
421
416
  declare const asset: z.ZodObject<{
422
417
  id: z.ZodUUID;
@@ -551,7 +546,6 @@ declare const assetListResponse: z.ZodObject<{
551
546
  }, z.core.$strip>;
552
547
  type AssetListResponse = z.infer<typeof assetListResponse>;
553
548
 
554
- // SPDX-License-Identifier: Apache-2.0
555
549
 
556
550
  /**
557
551
  * One violated parameter rule. `details` on the envelope stays `unknown` — codes
package/dist/index.d.ts CHANGED
@@ -1,7 +1,6 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
  import { z } from 'zod';
3
3
 
4
- // SPDX-License-Identifier: Apache-2.0
5
4
 
6
5
  /**
7
6
  * Jobs: one running unit of work on a robot — an action
@@ -234,7 +233,6 @@ declare const historyBucketsResponse: z.ZodObject<{
234
233
  }, z.core.$strip>;
235
234
  type HistoryBucketsResponse = z.infer<typeof historyBucketsResponse>;
236
235
 
237
- // SPDX-License-Identifier: Apache-2.0
238
236
 
239
237
  /**
240
238
  * One datapoint sample pushed to a subscriber. The current value arrives
@@ -250,7 +248,6 @@ declare const datapointEvent: z.ZodObject<{
250
248
  }, z.core.$strip>;
251
249
  type DatapointEvent = z.infer<typeof datapointEvent>;
252
250
 
253
- // SPDX-License-Identifier: Apache-2.0
254
251
 
255
252
  /**
256
253
  * Access plus refresh. The access token is short-lived; the
@@ -267,7 +264,6 @@ declare const sessionTokens: z.ZodObject<{
267
264
  }, z.core.$strip>;
268
265
  type SessionTokens = z.infer<typeof sessionTokens>;
269
266
 
270
- // SPDX-License-Identifier: Apache-2.0
271
267
 
272
268
  /**
273
269
  * **Why a federated sign-in ended without a session, in a code the app can
@@ -416,7 +412,6 @@ declare const clientIdentity: z.ZodObject<{
416
412
  }, z.core.$strip>;
417
413
  type ClientIdentity = z.infer<typeof clientIdentity>;
418
414
 
419
- // SPDX-License-Identifier: Apache-2.0
420
415
 
421
416
  declare const asset: z.ZodObject<{
422
417
  id: z.ZodUUID;
@@ -551,7 +546,6 @@ declare const assetListResponse: z.ZodObject<{
551
546
  }, z.core.$strip>;
552
547
  type AssetListResponse = z.infer<typeof assetListResponse>;
553
548
 
554
- // SPDX-License-Identifier: Apache-2.0
555
549
 
556
550
  /**
557
551
  * One violated parameter rule. `details` on the envelope stays `unknown` — codes
package/dist/index.js CHANGED
@@ -114,7 +114,7 @@ var HttpClient = class {
114
114
  * RequestOptionsFor<P>` would type-check by construction — the cast
115
115
  * bypasses the very check this exists to add, which is the identical
116
116
  * "special case that quietly exempts calls from the general rule" shape
117
- * this file has already been caught by once. Every current call site to a route
117
+ * this file has already made once. Every current call site to a route
118
118
  * with no body already passes `{}` explicitly for exactly this reason.
119
119
  */
120
120
  async request(path, options) {
@@ -411,7 +411,7 @@ function createAssetsApi(http) {
411
411
  };
412
412
  }
413
413
 
414
- // node_modules/.pnpm/@fleetless+contracts@1.0.4/node_modules/@fleetless/contracts/dist/common.js
414
+ // node_modules/.pnpm/@fleetless+contracts@1.0.5/node_modules/@fleetless/contracts/dist/common.js
415
415
  import { z } from "zod";
416
416
  var SLUG_RULE = "A name is lower-case: it starts with a letter, continues with letters and digits, and joins further words with a single underscore \u2014 `battery_voltage`. Capitals, dashes, dots, spaces, a leading digit and a doubled or trailing underscore are all refused.";
417
417
  var slug = z.string().min(2).max(63).regex(/^[a-z][a-z0-9]*(?:_[a-z0-9]+)*$/, SLUG_RULE);
@@ -435,7 +435,7 @@ var applyError = z.object({
435
435
  details: z.record(z.string(), z.unknown()).optional()
436
436
  });
437
437
 
438
- // node_modules/.pnpm/@fleetless+contracts@1.0.4/node_modules/@fleetless/contracts/dist/mcp.js
438
+ // node_modules/.pnpm/@fleetless+contracts@1.0.5/node_modules/@fleetless/contracts/dist/mcp.js
439
439
  import { z as z2 } from "zod";
440
440
  function mcpAppEndpointPath(appIdentifier2) {
441
441
  return `/mcp/${appIdentifier2}`;
@@ -484,10 +484,10 @@ var mcpRolePreviewResponse = z2.object({
484
484
  });
485
485
  var MCP_ASSET_LINK_TTL_MS = 15 * 60 * 1e3;
486
486
 
487
- // node_modules/.pnpm/@fleetless+contracts@1.0.4/node_modules/@fleetless/contracts/dist/protocol.js
487
+ // node_modules/.pnpm/@fleetless+contracts@1.0.5/node_modules/@fleetless/contracts/dist/protocol.js
488
488
  import { z as z8 } from "zod";
489
489
 
490
- // node_modules/.pnpm/@fleetless+contracts@1.0.4/node_modules/@fleetless/contracts/dist/assets.js
490
+ // node_modules/.pnpm/@fleetless+contracts@1.0.5/node_modules/@fleetless/contracts/dist/assets.js
491
491
  import { z as z3 } from "zod";
492
492
  var assetKind = z3.enum(["urdf", "mesh", "texture", "other"]);
493
493
  var asset = z3.object({
@@ -745,10 +745,10 @@ var assetSyncBusyDetails = z3.object({
745
745
  started_at_ms: z3.number().int().nonnegative()
746
746
  });
747
747
 
748
- // node_modules/.pnpm/@fleetless+contracts@1.0.4/node_modules/@fleetless/contracts/dist/config.js
748
+ // node_modules/.pnpm/@fleetless+contracts@1.0.5/node_modules/@fleetless/contracts/dist/config.js
749
749
  import { z as z5 } from "zod";
750
750
 
751
- // node_modules/.pnpm/@fleetless+contracts@1.0.4/node_modules/@fleetless/contracts/dist/alerts.js
751
+ // node_modules/.pnpm/@fleetless+contracts@1.0.5/node_modules/@fleetless/contracts/dist/alerts.js
752
752
  import { z as z4 } from "zod";
753
753
  var alertRowCondition = z4.discriminatedUnion("kind", [
754
754
  z4.strictObject({
@@ -818,7 +818,7 @@ var putDatapointDisplayRequest = z4.object({
818
818
  y_max: z4.number().finite().nullable()
819
819
  }).strict();
820
820
 
821
- // node_modules/.pnpm/@fleetless+contracts@1.0.4/node_modules/@fleetless/contracts/dist/config.js
821
+ // node_modules/.pnpm/@fleetless+contracts@1.0.5/node_modules/@fleetless/contracts/dist/config.js
822
822
  var RTSP_URL_RULE = "The URL has to begin with `rtsp://` or `rtsps://` \u2014 `rtsp://cam-1.plant.local/stream1`. No other scheme is accepted: the bridge opens this with a library that would equally honour `file:`.";
823
823
  var MJPEG_URL_RULE = "The URL has to begin with `http://` or `https://` \u2014 `http://cam-1.plant.local/video.mjpg`. No other scheme is accepted: the bridge opens this with a library that would equally serve `file:`.";
824
824
  var DEVICE_PATH_RULE = "A capture device is a path under `/dev/`, and the character straight after it is a letter or a digit \u2014 `/dev/video0`, or a stable `/dev/v4l/by-id/...` symlink. Nothing outside `/dev/` is accepted: the string reaches OpenCV, which would as happily open an ordinary file.";
@@ -1865,7 +1865,7 @@ var configState = z5.object({
1865
1865
  applied_errors: z5.array(applyError).nullable()
1866
1866
  });
1867
1867
 
1868
- // node_modules/.pnpm/@fleetless+contracts@1.0.4/node_modules/@fleetless/contracts/dist/introspection.js
1868
+ // node_modules/.pnpm/@fleetless+contracts@1.0.5/node_modules/@fleetless/contracts/dist/introspection.js
1869
1869
  import { z as z6 } from "zod";
1870
1870
  var rosGraphEntry = z6.object({
1871
1871
  name: rosName,
@@ -1904,7 +1904,7 @@ var typeDefinition = z6.discriminatedUnion("kind", [
1904
1904
  })
1905
1905
  ]);
1906
1906
 
1907
- // node_modules/.pnpm/@fleetless+contracts@1.0.4/node_modules/@fleetless/contracts/dist/jobs.js
1907
+ // node_modules/.pnpm/@fleetless+contracts@1.0.5/node_modules/@fleetless/contracts/dist/jobs.js
1908
1908
  import { z as z7 } from "zod";
1909
1909
  var jobState = z7.enum(["running", "succeeded", "failed", "cancelled", "lost"]);
1910
1910
  var job = z7.object({
@@ -2122,7 +2122,7 @@ var jobRunSummary = z7.object({
2122
2122
  since_ms: z7.number().int().nonnegative()
2123
2123
  });
2124
2124
 
2125
- // node_modules/.pnpm/@fleetless+contracts@1.0.4/node_modules/@fleetless/contracts/dist/protocol.js
2125
+ // node_modules/.pnpm/@fleetless+contracts@1.0.5/node_modules/@fleetless/contracts/dist/protocol.js
2126
2126
  var MAX_PATIENCE_MS = 12e4;
2127
2127
  var MIN_PATIENCE_MS = 1e3;
2128
2128
  var activeJob = z8.object({
@@ -2521,11 +2521,11 @@ var bridgeCameraState = z8.object({
2521
2521
  request_id: z8.string().min(1).max(64).nullable()
2522
2522
  });
2523
2523
 
2524
- // node_modules/.pnpm/@fleetless+contracts@1.0.4/node_modules/@fleetless/contracts/dist/config-issues.js
2524
+ // node_modules/.pnpm/@fleetless+contracts@1.0.5/node_modules/@fleetless/contracts/dist/config-issues.js
2525
2525
  var EXPOSURE_SECTIONS = ["datapoints", "actions", "services", "publishers", "cameras"];
2526
2526
  var EXPOSURE_SECTION_NAMES = new Set(EXPOSURE_SECTIONS);
2527
2527
 
2528
- // node_modules/.pnpm/@fleetless+contracts@1.0.4/node_modules/@fleetless/contracts/dist/rest.js
2528
+ // node_modules/.pnpm/@fleetless+contracts@1.0.5/node_modules/@fleetless/contracts/dist/rest.js
2529
2529
  import { z as z9 } from "zod";
2530
2530
  var robot = z9.object({
2531
2531
  id: z9.uuid().meta({
@@ -3285,13 +3285,13 @@ var slugUsageResponse = z9.object({
3285
3285
  alert_count: z9.number().int().nonnegative()
3286
3286
  });
3287
3287
 
3288
- // node_modules/.pnpm/@fleetless+contracts@1.0.4/node_modules/@fleetless/contracts/dist/realtime.js
3288
+ // node_modules/.pnpm/@fleetless+contracts@1.0.5/node_modules/@fleetless/contracts/dist/realtime.js
3289
3289
  import { z as z14 } from "zod";
3290
3290
 
3291
- // node_modules/.pnpm/@fleetless+contracts@1.0.4/node_modules/@fleetless/contracts/dist/client-auth.js
3291
+ // node_modules/.pnpm/@fleetless+contracts@1.0.5/node_modules/@fleetless/contracts/dist/client-auth.js
3292
3292
  import { z as z13 } from "zod";
3293
3293
 
3294
- // node_modules/.pnpm/@fleetless+contracts@1.0.4/node_modules/@fleetless/contracts/dist/apps.js
3294
+ // node_modules/.pnpm/@fleetless+contracts@1.0.5/node_modules/@fleetless/contracts/dist/apps.js
3295
3295
  import { z as z10 } from "zod";
3296
3296
  var appIdentifier = slug;
3297
3297
  var app = z10.object({
@@ -3473,10 +3473,10 @@ var rolePermissions = z10.object({
3473
3473
  })
3474
3474
  });
3475
3475
 
3476
- // node_modules/.pnpm/@fleetless+contracts@1.0.4/node_modules/@fleetless/contracts/dist/app-users.js
3476
+ // node_modules/.pnpm/@fleetless+contracts@1.0.5/node_modules/@fleetless/contracts/dist/app-users.js
3477
3477
  import { z as z12 } from "zod";
3478
3478
 
3479
- // node_modules/.pnpm/@fleetless+contracts@1.0.4/node_modules/@fleetless/contracts/dist/identity.js
3479
+ // node_modules/.pnpm/@fleetless+contracts@1.0.5/node_modules/@fleetless/contracts/dist/identity.js
3480
3480
  import { z as z11 } from "zod";
3481
3481
  var password = z11.string().min(12).max(256);
3482
3482
  var USER_DISPLAY_NAME_MAX = 120;
@@ -3638,7 +3638,7 @@ var authMeResponse = z11.object({ org, user: fleetlessUser });
3638
3638
  var patchOrgRequest = z11.object({ name: z11.string().min(1).max(120) }).strict();
3639
3639
  var patchAuthMeRequest = z11.object({ display_name: z11.string().min(1).max(USER_DISPLAY_NAME_MAX).nullable() }).strict();
3640
3640
 
3641
- // node_modules/.pnpm/@fleetless+contracts@1.0.4/node_modules/@fleetless/contracts/dist/app-users.js
3641
+ // node_modules/.pnpm/@fleetless+contracts@1.0.5/node_modules/@fleetless/contracts/dist/app-users.js
3642
3642
  var APP_USER_DISPLAY_NAME_MAX = 120;
3643
3643
  var providerSlug = z12.string().max(40).regex(/^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/, "a provider slug is lowercase and hyphen-separated, starting with a letter");
3644
3644
  var appUserStatus = z12.enum(["pending_verification", "active", "blocked"]);
@@ -3889,7 +3889,7 @@ var mailOutcome = z12.object({
3889
3889
  })
3890
3890
  });
3891
3891
 
3892
- // node_modules/.pnpm/@fleetless+contracts@1.0.4/node_modules/@fleetless/contracts/dist/client-auth.js
3892
+ // node_modules/.pnpm/@fleetless+contracts@1.0.5/node_modules/@fleetless/contracts/dist/client-auth.js
3893
3893
  var clientLoginRequest = z13.object({
3894
3894
  app_identifier: appIdentifier.meta({
3895
3895
  description: "The app being logged in to, as its globally unique identifier \u2014 the lowercase, underscore-separated string the developer chose when the app was created. There is no organisation context at login, so this is what decides which app the credentials are checked for."
@@ -4078,7 +4078,7 @@ var clientIdentity = z13.object({
4078
4078
  })
4079
4079
  });
4080
4080
 
4081
- // node_modules/.pnpm/@fleetless+contracts@1.0.4/node_modules/@fleetless/contracts/dist/realtime.js
4081
+ // node_modules/.pnpm/@fleetless+contracts@1.0.5/node_modules/@fleetless/contracts/dist/realtime.js
4082
4082
  var clientAuth = z14.object({
4083
4083
  type: z14.literal("auth"),
4084
4084
  token: z14.string().min(1)
@@ -4356,7 +4356,7 @@ var orgEventDropped = z14.object({
4356
4356
  dropped: z14.number().int().positive()
4357
4357
  }).strict();
4358
4358
 
4359
- // node_modules/.pnpm/@fleetless+contracts@1.0.4/node_modules/@fleetless/contracts/dist/audit.js
4359
+ // node_modules/.pnpm/@fleetless+contracts@1.0.5/node_modules/@fleetless/contracts/dist/audit.js
4360
4360
  import { z as z15 } from "zod";
4361
4361
  var auditActor = z15.object({
4362
4362
  kind: z15.enum(["developer", "end_user", "app_user", "server_key", "bridge"]),
@@ -4487,7 +4487,7 @@ var auditListResponse = z15.object({
4487
4487
  next_cursor: z15.number().int().positive().nullable()
4488
4488
  });
4489
4489
 
4490
- // node_modules/.pnpm/@fleetless+contracts@1.0.4/node_modules/@fleetless/contracts/dist/errors.js
4490
+ // node_modules/.pnpm/@fleetless+contracts@1.0.5/node_modules/@fleetless/contracts/dist/errors.js
4491
4491
  import { z as z16 } from "zod";
4492
4492
  var apiError = z16.object({
4493
4493
  code: z16.string().min(1),
@@ -4504,7 +4504,7 @@ var parameterInvalidDetails = z16.object({
4504
4504
  violations: z16.array(parameterViolation).min(1)
4505
4505
  });
4506
4506
 
4507
- // node_modules/.pnpm/@fleetless+contracts@1.0.4/node_modules/@fleetless/contracts/dist/oauth.js
4507
+ // node_modules/.pnpm/@fleetless+contracts@1.0.5/node_modules/@fleetless/contracts/dist/oauth.js
4508
4508
  import { z as z17 } from "zod";
4509
4509
  var oauthErrorCode = z17.enum([
4510
4510
  "invalid_request",
@@ -4725,7 +4725,7 @@ var oauthAuthorizeQuery = z17.object({
4725
4725
  description: "The authorization request an MCP client sends, per RFC 6749 \xA74.1.1 with mandatory PKCE. The handler reads it parameter by parameter rather than through one parse, because the answers differ: `client_id` and `redirect_uri` are refused flat, with no redirect, since until both are confirmed there is no trusted target to bounce a browser to, and everything after them is reported to the client's own callback as query parameters."
4726
4726
  });
4727
4727
 
4728
- // node_modules/.pnpm/@fleetless+contracts@1.0.4/node_modules/@fleetless/contracts/dist/routes.js
4728
+ // node_modules/.pnpm/@fleetless+contracts@1.0.5/node_modules/@fleetless/contracts/dist/routes.js
4729
4729
  var MCP_APP = MCP_APP_PATHS(":appIdentifier");
4730
4730
  var APP_IDENTIFIER = {
4731
4731
  name: "appIdentifier",
@@ -6325,7 +6325,7 @@ var ROUTES = [
6325
6325
  response: null,
6326
6326
  errors: ["not_found", "unauthorized", "forbidden"],
6327
6327
  transport: "http",
6328
- notes: "MCP's Streamable HTTP gives this path three verbs: `POST` carries JSON-RPC, `GET` opens the server-initiated SSE stream, and `DELETE` ends a session. This server has no sessions \u2014 the argument is in `MCP_PROTOCOL_VERSION`'s own note, and a per-process session map is what breaks at the second cloud instance \u2014 so `GET` and `DELETE` answer `405`, which is what a client is built to fall back from. \n\n**The `405` is this cloud's own answer, not the SDK's**, and the two differ: MCP SDK 1.30.0 opens an SSE stream on `GET` (`handleGetRequest`) and answers `200` on `DELETE` (`handleDeleteRequest`), neither of which a stateless server has any business doing, so the cloud writes the `405` itself in the transport's own JSON-RPC error shape with `Allow: POST`. \n\n**The row exists so that the `405` is not a `404`.** An unregistered verb answers `404`, and at a path whose last segment is an app identifier a `404` already means *no such app* \u2014 one answer for two states, which is one answer for two states. Registering the verb lets the endpoint say \"this app's server is here; this verb is not part of it\". The central `/mcp` registers neither verb and does not need to: its path takes no parameter, so nothing can misread its `404`. \n\n**The `405` body is the transport's JSON-RPC error object, not the `apiError` envelope.** The three codes above are the refusals that come *first* \u2014 the app, its switch, then the bearer, in the order `POST` describes \u2014 and they are `apiError` because they are answered before the transport is reached at all. If this server ever becomes stateful, this row and the `DELETE` beside it are where that lands, and the cloud's route-manifest test is what would make both repositories notice."
6328
+ notes: "MCP's Streamable HTTP gives this path three verbs: `POST` carries JSON-RPC, `GET` opens the server-initiated SSE stream, and `DELETE` ends a session. This server has no sessions \u2014 the argument is in `MCP_PROTOCOL_VERSION`'s own note, and a per-process session map is what breaks at the second cloud instance \u2014 so `GET` and `DELETE` answer `405`, which is what a client is built to fall back from. \n\n**The `405` is this cloud's own answer, not the SDK's**, and the two differ: MCP SDK 1.30.0 opens an SSE stream on `GET` (`handleGetRequest`) and answers `200` on `DELETE` (`handleDeleteRequest`), neither of which a stateless server has any business doing, so the cloud writes the `405` itself in the transport's own JSON-RPC error shape with `Allow: POST`. \n\n**The row exists so that the `405` is not a `404`.** An unregistered verb answers `404`, and at a path whose last segment is an app identifier a `404` already means *no such app* \u2014 so an unregistered verb and an unknown app would answer identically. Registering the verb lets the endpoint say \"this app's server is here; this verb is not part of it\". The central `/mcp` registers neither verb and does not need to: its path takes no parameter, so nothing can misread its `404`. \n\n**The `405` body is the transport's JSON-RPC error object, not the `apiError` envelope.** The three codes above are the refusals that come *first* \u2014 the app, its switch, then the bearer, in the order `POST` describes \u2014 and they are `apiError` because they are answered before the transport is reached at all. If this server ever becomes stateful, this row and the `DELETE` beside it are where that lands, and the cloud's route-manifest test is what would make both repositories notice."
6329
6329
  },
6330
6330
  {
6331
6331
  method: "DELETE",
@@ -7290,7 +7290,7 @@ var ROUTES = [
7290
7290
  response: jobRunListResponse,
7291
7291
  errors: [...CLIENT_GUARD, "invalid_uuid", "not_found", "capability_required", "validation_error"],
7292
7292
  transport: "http",
7293
- notes: "Needs the `action_history` capability, and **this route is what makes that switch mean something** \u2014 it was unkeepable while nothing durable recorded what had run. Two residuals worth stating rather than implying away. `history` is a syntactically valid slug and The router matches a static segment first, so a robot with a service literally slugged `history` can no longer be **read** through `GET /api/robots/:id/jobs/:slug`; invoking, cancelling and the listing are unaffected. And a run row names its actor by email address, so an end user holding this capability learns which other people have been driving the machine. `robot_id` in the query is shared with the org-wide read; a *different* one here is refused rather than quietly answered about the robot in the path."
7293
+ notes: "Needs the `action_history` capability, and **this route is what makes that switch mean something** \u2014 it was unkeepable while nothing durable recorded what had run. Two residuals worth stating rather than implying away. `history` is a syntactically valid slug, and a static segment matches before a parameter, so a robot with a service literally slugged `history` can no longer be **read** through `GET /api/robots/:id/jobs/:slug`; invoking, cancelling and the listing are unaffected. And a run row names its actor by email address, so an end user holding this capability learns which other people have been driving the machine. `robot_id` in the query is shared with the org-wide read; a *different* one here is refused rather than quietly answered about the robot in the path."
7294
7294
  },
7295
7295
  {
7296
7296
  method: "POST",
@@ -8309,7 +8309,7 @@ function createRealtimeCommandTransport(channel) {
8309
8309
  return {
8310
8310
  // Declared `async` deliberately, unlike `cancel`/`publish` below: it is
8311
8311
  // the only one of the three that can refuse *before* sending anything
8312
- // (resolveLocalWaitMs's `invalid_option`, D3a), and every caller of
8312
+ // (`resolveLocalWaitMs`'s `invalid_option`), and every caller of
8313
8313
  // this interface — starting with this file's own `sendCommand` callers
8314
8314
  // — is entitled to assume `CommandTransport.invoke` always returns a
8315
8315
  // promise rather than throwing synchronously. Without `async` here, a
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fleetless/sdk",
3
- "version": "3.0.1",
3
+ "version": "3.0.2",
4
4
  "description": "The official TypeScript SDK for Fleetless client apps \u2014 a ROS 2 robot as a REST and realtime API, cameras, jobs, and the app's own user accounts, federated sign-in and MCP consent.",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -8,13 +8,8 @@
8
8
  "email": "hello@fleetless.dev",
9
9
  "url": "https://dehne-robotik.de"
10
10
  },
11
- "repository": {
12
- "type": "git",
13
- "url": "https://github.com/fleetless/sdk"
14
- },
15
11
  "homepage": "https://docs.fleetless.dev/reference/sdk/",
16
12
  "bugs": {
17
- "url": "https://github.com/fleetless/sdk/issues",
18
13
  "email": "hello@fleetless.dev"
19
14
  },
20
15
  "keywords": [
@@ -56,7 +51,8 @@
56
51
  "CHANGELOG.md",
57
52
  "SECURITY.md",
58
53
  "CONTRIBUTING.md",
59
- "CODE_OF_CONDUCT.md"
54
+ "CODE_OF_CONDUCT.md",
55
+ "RELEASING.md"
60
56
  ],
61
57
  "engines": {
62
58
  "node": ">=20"
@@ -69,13 +65,14 @@
69
65
  "test": "vitest run",
70
66
  "test:pack": "node scripts/verify-published-types.mjs",
71
67
  "verify:live": "node scripts/verify-live.mjs",
72
- "verify:history": "node scripts/verify-history.mjs"
68
+ "verify:history": "node scripts/verify-history.mjs",
69
+ "verify:commits": "node scripts/verify-commit-messages.mjs"
73
70
  },
74
71
  "dependencies": {
75
72
  "zod": "^4.0.0"
76
73
  },
77
74
  "devDependencies": {
78
- "@fleetless/contracts": "1.0.4",
75
+ "@fleetless/contracts": "1.0.5",
79
76
  "@types/node": "^22.20.1",
80
77
  "tsup": "^8.3.0",
81
78
  "typedoc": "0.28.20",