@fleetless/sdk 3.1.1 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +31 -1
- package/CONTRIBUTING.md +3 -2
- package/README.md +4 -4
- package/dist/index.cjs +542 -232
- package/dist/index.d.cts +142 -18
- package/dist/index.d.ts +142 -18
- package/dist/index.js +542 -232
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -266,7 +266,6 @@ function isRenderKind(kind) {
|
|
|
266
266
|
case "texture":
|
|
267
267
|
return true;
|
|
268
268
|
case "urdf":
|
|
269
|
-
case "other":
|
|
270
269
|
return false;
|
|
271
270
|
default: {
|
|
272
271
|
const exhaustive = kind;
|
|
@@ -441,7 +440,7 @@ function createAssetsApi(http) {
|
|
|
441
440
|
};
|
|
442
441
|
}
|
|
443
442
|
|
|
444
|
-
// node_modules/.pnpm/@fleetless+contracts@
|
|
443
|
+
// node_modules/.pnpm/@fleetless+contracts@5.0.0/node_modules/@fleetless/contracts/dist/common.js
|
|
445
444
|
var import_zod = require("zod");
|
|
446
445
|
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
446
|
var slug = import_zod.z.string().min(2).max(63).regex(/^[a-z][a-z0-9]*(?:_[a-z0-9]+)*$/, SLUG_RULE);
|
|
@@ -456,7 +455,14 @@ var wireTimestampMs = import_zod.z.union([import_zod.z.string().regex(/^\d{1,15}
|
|
|
456
455
|
const year = new Date(ms).getUTCFullYear();
|
|
457
456
|
return Number.isFinite(year) && year >= 1 && year <= 9999;
|
|
458
457
|
}, "must fall within years 1..9999"));
|
|
459
|
-
var applyErrorKind = import_zod.z.enum([
|
|
458
|
+
var applyErrorKind = import_zod.z.enum([
|
|
459
|
+
"datapoint",
|
|
460
|
+
"action",
|
|
461
|
+
"service",
|
|
462
|
+
"publisher",
|
|
463
|
+
"camera",
|
|
464
|
+
"low_bandwidth"
|
|
465
|
+
]);
|
|
460
466
|
var applyError = import_zod.z.object({
|
|
461
467
|
slug: import_zod.z.string(),
|
|
462
468
|
kind: applyErrorKind,
|
|
@@ -465,7 +471,7 @@ var applyError = import_zod.z.object({
|
|
|
465
471
|
details: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).optional()
|
|
466
472
|
});
|
|
467
473
|
|
|
468
|
-
// node_modules/.pnpm/@fleetless+contracts@
|
|
474
|
+
// node_modules/.pnpm/@fleetless+contracts@5.0.0/node_modules/@fleetless/contracts/dist/mcp.js
|
|
469
475
|
var import_zod2 = require("zod");
|
|
470
476
|
function mcpAppEndpointPath(appIdentifier2) {
|
|
471
477
|
return `/mcp/${appIdentifier2}`;
|
|
@@ -514,17 +520,17 @@ var mcpRolePreviewResponse = import_zod2.z.object({
|
|
|
514
520
|
});
|
|
515
521
|
var MCP_ASSET_LINK_TTL_MS = 15 * 60 * 1e3;
|
|
516
522
|
|
|
517
|
-
// node_modules/.pnpm/@fleetless+contracts@
|
|
523
|
+
// node_modules/.pnpm/@fleetless+contracts@5.0.0/node_modules/@fleetless/contracts/dist/protocol.js
|
|
518
524
|
var import_zod8 = require("zod");
|
|
519
525
|
|
|
520
|
-
// node_modules/.pnpm/@fleetless+contracts@
|
|
526
|
+
// node_modules/.pnpm/@fleetless+contracts@5.0.0/node_modules/@fleetless/contracts/dist/assets.js
|
|
521
527
|
var import_zod3 = require("zod");
|
|
522
|
-
var assetKind = import_zod3.z.enum(["urdf", "mesh", "texture"
|
|
528
|
+
var assetKind = import_zod3.z.enum(["urdf", "mesh", "texture"]);
|
|
523
529
|
var asset = import_zod3.z.object({
|
|
524
530
|
id: import_zod3.z.uuid().meta({ description: "The asset's id in the store." }),
|
|
525
531
|
robot_id: import_zod3.z.uuid().meta({ description: "The robot this asset belongs to." }),
|
|
526
532
|
kind: assetKind.meta({
|
|
527
|
-
description: "What the file is: the `urdf` itself, a `mesh` it references, a `texture` a mesh or the URDF paints with
|
|
533
|
+
description: "What the file is: the `urdf` itself, a `mesh` it references, or a `texture` a mesh or the URDF paints with. A renderer decides from this alone, before fetching anything, what to pre-fetch."
|
|
528
534
|
}),
|
|
529
535
|
/**
|
|
530
536
|
* What the robot called it — for a mesh, the `package://` URI the URDF
|
|
@@ -623,16 +629,18 @@ var assetSyncResponse = import_zod3.z.object({
|
|
|
623
629
|
description: "The sync that has just started. A sync is long-running, so the answer is something to watch rather than a status that was true at the moment of asking."
|
|
624
630
|
})
|
|
625
631
|
});
|
|
626
|
-
var
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
632
|
+
var assetStoreRefusedDetails = import_zod3.z.object({
|
|
633
|
+
store_bytes: import_zod3.z.number().int().positive().meta({
|
|
634
|
+
description: "The robot's store, in bytes."
|
|
635
|
+
}),
|
|
636
|
+
used_bytes: import_zod3.z.number().int().nonnegative().meta({
|
|
637
|
+
description: "Bytes the robot's assets occupy before this upload."
|
|
630
638
|
}),
|
|
631
639
|
size_bytes: import_zod3.z.number().int().positive().meta({
|
|
632
|
-
description:
|
|
640
|
+
description: "The refused upload, in bytes."
|
|
633
641
|
})
|
|
634
642
|
});
|
|
635
|
-
var assetFailureKind = import_zod3.z.enum(["unresolvable", "upload_failed", "refused"
|
|
643
|
+
var assetFailureKind = import_zod3.z.enum(["unresolvable", "upload_failed", "refused"]);
|
|
636
644
|
var assetFailure = import_zod3.z.object({
|
|
637
645
|
/**
|
|
638
646
|
* What could not be provided, verbatim — the same string `asset.name` would
|
|
@@ -645,33 +653,28 @@ var assetFailure = import_zod3.z.object({
|
|
|
645
653
|
description: "What could not be provided, verbatim \u2014 the same string the asset would have been stored under, so a developer can match it against their own workspace by eye. For a failed URDF upload it is `robot_description`, which is **not** a mesh URI: a consumer must not assume every entry is one."
|
|
646
654
|
}),
|
|
647
655
|
kind: assetFailureKind.meta({
|
|
648
|
-
description: "Why it failed. `unresolvable` means the reference names nothing the producer can find or may read, and is **permanent** \u2014 the only kind reconciliation may treat as gone. `upload_failed` means the bytes exist and the transfer did not succeed, `refused` means it was never attempted because
|
|
656
|
+
description: "Why it failed. `unresolvable` means the reference names nothing the producer can find or may read, and is **permanent** \u2014 the only kind reconciliation may treat as gone. `upload_failed` means the bytes exist and the transfer did not succeed, and `refused` means it was never attempted, either because the robot's asset store had no room \u2014 then `details` carries the three numbers \u2014 or because a producer-side ceiling was hit."
|
|
649
657
|
}),
|
|
650
658
|
/**
|
|
651
|
-
* **The
|
|
659
|
+
* **The three numbers behind a full store.**
|
|
652
660
|
*
|
|
653
|
-
*
|
|
654
|
-
*
|
|
655
|
-
*
|
|
656
|
-
* under one kind puts two facts on one key, each overwriting the other.
|
|
661
|
+
* A reason without numbers is not one a caller can act on. *"Refused"* does
|
|
662
|
+
* not answer whether to delete an old sync or shrink the mesh;
|
|
663
|
+
* `store_bytes`, `used_bytes` and `size_bytes` do.
|
|
657
664
|
*
|
|
658
|
-
*
|
|
659
|
-
*
|
|
660
|
-
*
|
|
661
|
-
*
|
|
662
|
-
*
|
|
663
|
-
*
|
|
664
|
-
* described: a field whose rule lives only in a comment is a request.
|
|
665
|
+
* **Present only on `refused`, and not on every `refused`.** The other half
|
|
666
|
+
* of that kind is the single collective entry a sync emits when it stops
|
|
667
|
+
* naming individual failures, and no store number describes it — requiring
|
|
668
|
+
* details there would mean inventing them. So the enforcement below is the
|
|
669
|
+
* half that can be enforced: details belong to `refused` and to nothing
|
|
670
|
+
* else. A forced `details: null` on every `unresolvable` buys nothing.
|
|
665
671
|
*/
|
|
666
|
-
details:
|
|
667
|
-
description: "The
|
|
672
|
+
details: assetStoreRefusedDetails.nullish().meta({
|
|
673
|
+
description: "The three numbers behind a `refused` entry the robot's store had no room for, and absent for every other kind \u2014 a forced `null` on every `unresolvable` entry buys nothing. A `refused` entry may also carry no details: the producer's own ceiling is the other half of that kind, and no store number describes it."
|
|
668
674
|
})
|
|
669
675
|
}).superRefine((f, ctx) => {
|
|
670
|
-
if (f.kind
|
|
671
|
-
ctx.addIssue({ code: "custom", path: ["details"], message: "
|
|
672
|
-
}
|
|
673
|
-
if (f.kind !== "too_large" && f.details != null) {
|
|
674
|
-
ctx.addIssue({ code: "custom", path: ["details"], message: "size details belong to `too_large` only" });
|
|
676
|
+
if (f.kind !== "refused" && f.details != null) {
|
|
677
|
+
ctx.addIssue({ code: "custom", path: ["details"], message: "store details belong to `refused` only" });
|
|
675
678
|
}
|
|
676
679
|
});
|
|
677
680
|
var assetSyncState = import_zod3.z.enum(["running", "succeeded", "failed"]);
|
|
@@ -717,6 +720,36 @@ var assetSyncStatus = import_zod3.z.object({
|
|
|
717
720
|
reason: import_zod3.z.string().min(1).nullable().meta({
|
|
718
721
|
description: "Why the sync ended as it did, when that is not a per-reference fact. `null` when `failed` already says everything there is to say."
|
|
719
722
|
}),
|
|
723
|
+
/**
|
|
724
|
+
* **What the receiver counted, next to what the producer claimed.**
|
|
725
|
+
*
|
|
726
|
+
* `state` is the bridge's own terminal frame and nothing else. A dev stack
|
|
727
|
+
* with no object store answered `500` to every upload and the sync still
|
|
728
|
+
* read `succeeded` — the producer had genuinely sent every file, and no
|
|
729
|
+
* one had asked the store. These two numbers are the cloud's own count,
|
|
730
|
+
* taken after the terminal frame: how many of the announced files
|
|
731
|
+
* (`assets_available`'s URDF and mesh list) its store actually holds.
|
|
732
|
+
*
|
|
733
|
+
* They are a pair because neither alone answers anything. `stored` without
|
|
734
|
+
* `announced` cannot say whether four files is all of them or a tenth of
|
|
735
|
+
* them, and `announced` alone is what the producer said it had, which is
|
|
736
|
+
* the claim under examination.
|
|
737
|
+
*
|
|
738
|
+
* A partial store is still `succeeded`: some meshes were never going to
|
|
739
|
+
* resolve, and the per-reference `failed` entries say which. An empty one
|
|
740
|
+
* under a `succeeded` frame is `failed`, because no transport succeeds at
|
|
741
|
+
* nothing.
|
|
742
|
+
*
|
|
743
|
+
* **`stored` is `null` while the sync is still running.** The count is
|
|
744
|
+
* taken once, after the robot's terminal frame; a `0` before then would
|
|
745
|
+
* say the store is empty when nobody has looked.
|
|
746
|
+
*/
|
|
747
|
+
stored: import_zod3.z.number().int().nonnegative().nullable().meta({
|
|
748
|
+
description: "How many of the announced files the cloud's store actually holds. Counted once, after the robot reports the sync done, and `null` until then \u2014 nobody has looked yet. Read it against `announced`: `state` is what the robot reported, this is what arrived."
|
|
749
|
+
}),
|
|
750
|
+
announced: import_zod3.z.number().int().nonnegative().meta({
|
|
751
|
+
description: "How many files the robot announced for this sync \u2014 the URDF, if it has one, plus every mesh URI its description references. `0` when the robot announced nothing, and also `0` until it has answered at all: read it beside `stored`, which stays `null` until the terminal frame."
|
|
752
|
+
}),
|
|
720
753
|
started_at: import_zod3.z.iso.datetime().meta({
|
|
721
754
|
description: "When the sync started, as an ISO 8601 timestamp."
|
|
722
755
|
}),
|
|
@@ -760,6 +793,43 @@ var assetListResponse = import_zod3.z.object({
|
|
|
760
793
|
*/
|
|
761
794
|
urdf_available: import_zod3.z.boolean().nullable().meta({
|
|
762
795
|
description: 'What the connected bridge says it *could* transfer \u2014 deliberately separate from what has been transferred. `null` when no bridge is connected, distinct from `false`: "no robot is online to ask" and "the robot has no URDF" send a developer to different places. After a publisher is killed rather than shut down this can read `true` for some seconds, on the underlying DDS liveliness timeout rather than on any check made here.'
|
|
796
|
+
}),
|
|
797
|
+
/**
|
|
798
|
+
* **How full this robot's store is, on the list that already names what is
|
|
799
|
+
* in it.** A page showing assets is the page where "will the next sync fit"
|
|
800
|
+
* is asked, and a second round trip to some quota endpoint would answer it
|
|
801
|
+
* about the organisation instead — which is a different number about a
|
|
802
|
+
* different thing.
|
|
803
|
+
*/
|
|
804
|
+
store: import_zod3.z.object({
|
|
805
|
+
bytes: import_zod3.z.number().int().positive().meta({
|
|
806
|
+
description: "The robot's asset store, `ROBOT_ASSET_STORE_BYTES`."
|
|
807
|
+
}),
|
|
808
|
+
used_bytes: import_zod3.z.number().int().nonnegative().meta({
|
|
809
|
+
description: "Bytes its assets occupy."
|
|
810
|
+
})
|
|
811
|
+
}).meta({ description: "How full this robot's store is." }),
|
|
812
|
+
/**
|
|
813
|
+
* The datapoint that moves the joints in a renderer, chosen by a developer
|
|
814
|
+
* and stored on the robot. It rides on this list because a client that has
|
|
815
|
+
* just fetched the URDF and the meshes needs exactly one more thing to
|
|
816
|
+
* animate them, and asking a second endpoint for one slug is a round trip
|
|
817
|
+
* that buys nothing.
|
|
818
|
+
*
|
|
819
|
+
* `null` is an ordinary answer: none was ever chosen, or a publish removed
|
|
820
|
+
* the datapoint it named and the cloud cleared the mapping rather than
|
|
821
|
+
* leave it pointing at something that no longer qualifies.
|
|
822
|
+
*/
|
|
823
|
+
joint_state_slug: slug.nullable().meta({
|
|
824
|
+
description: "The whole-message `sensor_msgs/msg/JointState` datapoint that drives the console's URDF viewer; null when none is chosen or a publish removed it. Set through `PUT /api/robots/:id/urdf/joint-state`."
|
|
825
|
+
})
|
|
826
|
+
});
|
|
827
|
+
var assetsClearResponse = import_zod3.z.object({
|
|
828
|
+
deleted: import_zod3.z.number().int().nonnegative().meta({
|
|
829
|
+
description: "How many assets \u2014 URDF, meshes and textures together \u2014 were removed."
|
|
830
|
+
}),
|
|
831
|
+
bytes_freed: import_zod3.z.number().int().nonnegative().meta({
|
|
832
|
+
description: "The bytes the robot's store got back."
|
|
763
833
|
})
|
|
764
834
|
});
|
|
765
835
|
var missingAssetQuery = import_zod3.z.object({
|
|
@@ -773,10 +843,10 @@ var assetSyncBusyDetails = import_zod3.z.object({
|
|
|
773
843
|
started_at_ms: import_zod3.z.number().int().nonnegative()
|
|
774
844
|
});
|
|
775
845
|
|
|
776
|
-
// node_modules/.pnpm/@fleetless+contracts@
|
|
846
|
+
// node_modules/.pnpm/@fleetless+contracts@5.0.0/node_modules/@fleetless/contracts/dist/config.js
|
|
777
847
|
var import_zod5 = require("zod");
|
|
778
848
|
|
|
779
|
-
// node_modules/.pnpm/@fleetless+contracts@
|
|
849
|
+
// node_modules/.pnpm/@fleetless+contracts@5.0.0/node_modules/@fleetless/contracts/dist/alerts.js
|
|
780
850
|
var import_zod4 = require("zod");
|
|
781
851
|
var alertRowCondition = import_zod4.z.discriminatedUnion("kind", [
|
|
782
852
|
import_zod4.z.strictObject({
|
|
@@ -846,7 +916,7 @@ var putDatapointDisplayRequest = import_zod4.z.object({
|
|
|
846
916
|
y_max: import_zod4.z.number().finite().nullable()
|
|
847
917
|
}).strict();
|
|
848
918
|
|
|
849
|
-
// node_modules/.pnpm/@fleetless+contracts@
|
|
919
|
+
// node_modules/.pnpm/@fleetless+contracts@5.0.0/node_modules/@fleetless/contracts/dist/config.js
|
|
850
920
|
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:`.";
|
|
851
921
|
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:`.";
|
|
852
922
|
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.";
|
|
@@ -1237,6 +1307,9 @@ var datapointConfig = strictObject({
|
|
|
1237
1307
|
description: "A ceiling on how often this datapoint is sent, in hertz. Omitted or `0` means no throttling. It is **a ceiling, not a clock**: a slow topic stays slow, a value is never repeated to manufacture a rate, and within a window the newest value wins. The bridge enforces it, so the robot's bandwidth is genuinely saved.",
|
|
1238
1308
|
examples: [2, 0.5]
|
|
1239
1309
|
}).optional(),
|
|
1310
|
+
low_bandwidth: import_zod5.z.literal("keep").optional().meta({
|
|
1311
|
+
description: "`keep` exempts this datapoint from the low-bandwidth rate cap; its backfill still pauses."
|
|
1312
|
+
}),
|
|
1240
1313
|
description: serviceDescription.meta({
|
|
1241
1314
|
description: "Prose about what this value is, for whoever meets it in the console. It changes nothing the robot does, so a publish that touches only it pushes no configuration at all \u2014 but it is carried verbatim into `robot_describe`, where a model that has never seen this robot reads it. The datapoint is offered whenever the role grants it; without one it is offered with `description: null` and the model has less to go on, as for actions, services, publishers and cameras. Omission is the only way to say nothing; an empty string is refused, here and on all five.",
|
|
1242
1315
|
/**
|
|
@@ -1834,6 +1907,34 @@ var cameraConfig = strictObject({
|
|
|
1834
1907
|
/** The value position of one entry — `front: ▮` under `cameras:`. */
|
|
1835
1908
|
defaultSnippets: [CAMERA_SNIPPET]
|
|
1836
1909
|
});
|
|
1910
|
+
var lowBandwidthMode = import_zod5.z.enum(["auto", "on", "off"]);
|
|
1911
|
+
var lowBandwidthCamera = import_zod5.z.enum(["reduce", "stop"]);
|
|
1912
|
+
var lowBandwidthSection = strictObject({
|
|
1913
|
+
mode: lowBandwidthMode.optional().meta({
|
|
1914
|
+
description: "`auto` decides from the measured lag; `on` and `off` force the mode, for tests and for an operator who knows the link.",
|
|
1915
|
+
enumDescriptions: describeValues(lowBandwidthMode.options, {
|
|
1916
|
+
auto: "The bridge enters and leaves the mode on its own, from the lag the cloud reports and the dwell it measures in its own send queue. The setting to leave alone.",
|
|
1917
|
+
on: "The mode is held on, whatever the link is doing. For a robot on a link known to be poor, and for a test that would otherwise have to wait for a real one.",
|
|
1918
|
+
off: "The mode never engages, whatever the link is doing. The robot then sends at its configured rates over a link that cannot carry them, which is a choice and not a default."
|
|
1919
|
+
})
|
|
1920
|
+
}),
|
|
1921
|
+
enter_lag_ms: import_zod5.z.number().int().min(100).optional().meta({ description: "Lag or queue dwell above this enters the mode. Checked against exit_lag_ms only when both are in this document; a lone key composes with the bridge's parameter or the default on the robot, and a crossed pair is refused there when the configuration is applied, so name both when you change either." }),
|
|
1922
|
+
enter_after_s: import_zod5.z.number().int().min(1).optional().meta({ description: "The entry condition must hold this long." }),
|
|
1923
|
+
exit_lag_ms: import_zod5.z.number().int().min(0).optional().meta({ description: "Lag and dwell both at or below this leave the mode. Must be at or below enter_lag_ms: a crossed pair is a mode that leaves as it arrives. Checked here only when both keys are present; a lone key is checked on the robot against the parameter or default it composes with." }),
|
|
1924
|
+
exit_after_s: import_zod5.z.number().int().min(1).optional().meta({ description: "The exit condition must hold this long." }),
|
|
1925
|
+
datapoint_max_hz: import_zod5.z.number().gt(0).max(20).optional().meta({ description: "The long-run rate for every datapoint in the mode, unless the datapoint says `low_bandwidth: keep`. It is an average, not a minimum gap: after a quiet spell two samples may go out close together, and over any longer window the rate holds." }),
|
|
1926
|
+
camera: lowBandwidthCamera.optional().meta({
|
|
1927
|
+
description: "What happens to a running stream in the mode. New streams are refused either way.",
|
|
1928
|
+
enumDescriptions: describeValues(lowBandwidthCamera.options, {
|
|
1929
|
+
reduce: "A running stream is re-encoded at `camera_bitrate_kbps` and keeps running. A viewer sees a worse picture rather than none.",
|
|
1930
|
+
stop: "A running stream ends and the viewer is told why. The uplink is then free for datapoints, which is the right trade where video is the nice-to-have."
|
|
1931
|
+
})
|
|
1932
|
+
}),
|
|
1933
|
+
camera_bitrate_kbps: import_zod5.z.number().int().min(50).max(2e4).optional().meta({ description: "Bitrate applied to running streams under `reduce`." })
|
|
1934
|
+
}).superRefine((s, ctx) => {
|
|
1935
|
+
if (s.enter_lag_ms !== void 0 && s.exit_lag_ms !== void 0 && s.exit_lag_ms > s.enter_lag_ms)
|
|
1936
|
+
ctx.addIssue({ code: "custom", path: ["exit_lag_ms"], message: "low_bandwidth.exit_lag_ms must be at or below enter_lag_ms" });
|
|
1937
|
+
});
|
|
1837
1938
|
var FLEETLESS_FORMAT_VERSION = 1;
|
|
1838
1939
|
var capped = (entry, max, what) => slugKeyed(entry).refine((m) => Object.keys(m).length <= max, { message: `at most ${max} ${what}` });
|
|
1839
1940
|
var robotConfigDoc = strictObject({
|
|
@@ -1845,28 +1946,42 @@ var robotConfigDoc = strictObject({
|
|
|
1845
1946
|
defaultSnippets: [underSlug("${1:drive}", SHARED_MESSAGE_SNIPPET)]
|
|
1846
1947
|
}).optional(),
|
|
1847
1948
|
datapoints: capped(datapointConfig, 200, "datapoints").meta({
|
|
1848
|
-
description: "Values the robot publishes, each one field of one topic or a whole topic, and **never several topics**. Keys are slugs, one namespace across all five exposure sections, which is what lets a role grant say `{robot, slug}` without naming a kind; `bridge_state
|
|
1949
|
+
description: "Values the robot publishes, each one field of one topic or a whole topic, and **never several topics**. Keys are slugs, one namespace across all five exposure sections, which is what lets a role grant say `{robot, slug}` without naming a kind; `bridge_state` and `robot_details` are built-in, and `history` is reserved because `GET \u2026/jobs/history` would shadow an action of that name; all three are refused when the document is validated.",
|
|
1849
1950
|
defaultSnippets: [
|
|
1850
1951
|
underSlug("${1:battery_voltage}", DATAPOINT_SNIPPET),
|
|
1851
1952
|
underSlug("${1:battery}", NUMERIC_DATAPOINT_SNIPPET)
|
|
1852
1953
|
]
|
|
1853
1954
|
}).optional(),
|
|
1854
1955
|
actions: capped(actionConfig, 200, "actions").meta({
|
|
1855
|
-
description: "Things the robot does on request that take time, each reported as a job with progress. **At most one job runs per action slug**: a second call is refused `busy`, and every observer of that slug watches the same job. Keys are slugs, one namespace across all five exposure sections, which is what lets a role grant say `{robot, slug}` without naming a kind; `bridge_state
|
|
1956
|
+
description: "Things the robot does on request that take time, each reported as a job with progress. **At most one job runs per action slug**: a second call is refused `busy`, and every observer of that slug watches the same job. Keys are slugs, one namespace across all five exposure sections, which is what lets a role grant say `{robot, slug}` without naming a kind; `bridge_state` and `robot_details` are built-in, and `history` is reserved because `GET \u2026/jobs/history` would shadow an action of that name; all three are refused when the document is validated.",
|
|
1856
1957
|
defaultSnippets: [underSlug("${1:navigate}", ACTION_SNIPPET)]
|
|
1857
1958
|
}).optional(),
|
|
1858
1959
|
services: capped(serviceConfig, 200, "services").meta({
|
|
1859
|
-
description: "ROS service calls the robot answers \u2014 one request, one reply. Unlike an action a service reports **no progress** and the call returns with its result already on the job, so there is nothing left to observe; a second concurrent call is still refused `busy`, exactly as for an action. Keys are slugs, one namespace across all five exposure sections, which is what lets a role grant say `{robot, slug}` without naming a kind; `bridge_state
|
|
1960
|
+
description: "ROS service calls the robot answers \u2014 one request, one reply. Unlike an action a service reports **no progress** and the call returns with its result already on the job, so there is nothing left to observe; a second concurrent call is still refused `busy`, exactly as for an action. Keys are slugs, one namespace across all five exposure sections, which is what lets a role grant say `{robot, slug}` without naming a kind; `bridge_state` and `robot_details` are built-in, and `history` is reserved because `GET \u2026/jobs/history` would shadow an action of that name; all three are refused when the document is validated.",
|
|
1860
1961
|
defaultSnippets: [underSlug("${1:reset_odometry}", SERVICE_SNIPPET)]
|
|
1861
1962
|
}).optional(),
|
|
1862
1963
|
publishers: capped(publisherConfig, 200, "publishers").meta({
|
|
1863
|
-
description: "Topics clients may send to, and where the format's whole safety story lives. The `message` template fixes every value a caller cannot change, and **`failsafe` is required**: once a client falls silent the bridge sends the failsafe message itself, so an operator whose window closed does not leave a robot driving. Keys are slugs, one namespace across all five exposure sections, which is what lets a role grant say `{robot, slug}` without naming a kind; `bridge_state
|
|
1964
|
+
description: "Topics clients may send to, and where the format's whole safety story lives. The `message` template fixes every value a caller cannot change, and **`failsafe` is required**: once a client falls silent the bridge sends the failsafe message itself, so an operator whose window closed does not leave a robot driving. Keys are slugs, one namespace across all five exposure sections, which is what lets a role grant say `{robot, slug}` without naming a kind; `bridge_state` and `robot_details` are built-in, and `history` is reserved because `GET \u2026/jobs/history` would shadow an action of that name; all three are refused when the document is validated.",
|
|
1864
1965
|
defaultSnippets: [underSlug("${1:drive}", PUBLISHER_SNIPPET)]
|
|
1865
1966
|
}).optional(),
|
|
1866
1967
|
cameras: capped(cameraConfig, 50, "cameras").meta({
|
|
1867
|
-
description: "Video the robot streams, and the still frames the cloud serves from it. `width`, `height`, `fps` and `bitrate_kbps` are what **the bridge produces before sending**, not what the camera captures \u2014 they live in the configuration rather than in a viewer's request precisely so that no viewer can make a robot send more. Keys are slugs, one namespace across all five exposure sections, which is what lets a role grant say `{robot, slug}` without naming a kind; `bridge_state
|
|
1968
|
+
description: "Video the robot streams, and the still frames the cloud serves from it. `width`, `height`, `fps` and `bitrate_kbps` are what **the bridge produces before sending**, not what the camera captures \u2014 they live in the configuration rather than in a viewer's request precisely so that no viewer can make a robot send more. Keys are slugs, one namespace across all five exposure sections, which is what lets a role grant say `{robot, slug}` without naming a kind; `bridge_state` and `robot_details` are built-in, and `history` is reserved because `GET \u2026/jobs/history` would shadow an action of that name; all three are refused when the document is validated.",
|
|
1868
1969
|
defaultSnippets: [underSlug("${1:front}", CAMERA_SNIPPET)]
|
|
1869
|
-
}).optional()
|
|
1970
|
+
}).optional(),
|
|
1971
|
+
low_bandwidth: lowBandwidthSection.optional().meta({
|
|
1972
|
+
description: "Overrides for the bridge's low-bandwidth mode; see the section schema.",
|
|
1973
|
+
/**
|
|
1974
|
+
* The body is the three keys a developer actually reaches for — when the
|
|
1975
|
+
* mode engages, how hard it caps, and what it does to video. The five
|
|
1976
|
+
* timing keys stay out: they exist to be tuned once against a measured
|
|
1977
|
+
* link, and a skeleton that pre-fills them reads as a recommendation.
|
|
1978
|
+
*/
|
|
1979
|
+
defaultSnippets: [{
|
|
1980
|
+
label: "low-bandwidth mode, tuned",
|
|
1981
|
+
description: "Enters after ten seconds above two seconds of lag, caps every datapoint to 1 Hz and lets running streams continue at a reduced bitrate.",
|
|
1982
|
+
body: { enter_lag_ms: 2e3, datapoint_max_hz: 1, camera: "reduce" }
|
|
1983
|
+
}]
|
|
1984
|
+
})
|
|
1870
1985
|
});
|
|
1871
1986
|
var validationIssue = import_zod5.z.object({
|
|
1872
1987
|
path: import_zod5.z.string().min(1),
|
|
@@ -1893,7 +2008,7 @@ var configState = import_zod5.z.object({
|
|
|
1893
2008
|
applied_errors: import_zod5.z.array(applyError).nullable()
|
|
1894
2009
|
});
|
|
1895
2010
|
|
|
1896
|
-
// node_modules/.pnpm/@fleetless+contracts@
|
|
2011
|
+
// node_modules/.pnpm/@fleetless+contracts@5.0.0/node_modules/@fleetless/contracts/dist/introspection.js
|
|
1897
2012
|
var import_zod6 = require("zod");
|
|
1898
2013
|
var rosGraphEntry = import_zod6.z.object({
|
|
1899
2014
|
name: rosName,
|
|
@@ -1932,9 +2047,11 @@ var typeDefinition = import_zod6.z.discriminatedUnion("kind", [
|
|
|
1932
2047
|
})
|
|
1933
2048
|
]);
|
|
1934
2049
|
|
|
1935
|
-
// node_modules/.pnpm/@fleetless+contracts@
|
|
2050
|
+
// node_modules/.pnpm/@fleetless+contracts@5.0.0/node_modules/@fleetless/contracts/dist/jobs.js
|
|
1936
2051
|
var import_zod7 = require("zod");
|
|
1937
|
-
var jobState = import_zod7.z.enum(["running", "succeeded", "failed", "cancelled", "lost"]);
|
|
2052
|
+
var jobState = import_zod7.z.enum(["running", "unknown", "succeeded", "failed", "cancelled", "lost"]);
|
|
2053
|
+
var reportedJobState = jobState.exclude(["unknown"]);
|
|
2054
|
+
var jobOrigin = import_zod7.z.enum(["fleetless", "external"]);
|
|
1938
2055
|
var job = import_zod7.z.object({
|
|
1939
2056
|
id: import_zod7.z.uuid().meta({
|
|
1940
2057
|
description: "The job's id, minted by the cloud when the invocation is accepted. Informative \u2014 state is observed by slug; a cancel names this id to stop one specific job rather than whatever is running."
|
|
@@ -1944,7 +2061,10 @@ var job = import_zod7.z.object({
|
|
|
1944
2061
|
description: "The action or service this job is running, as the published configuration exposes it. One slug carries one job at a time, so every observer of that slug sees the same one."
|
|
1945
2062
|
}),
|
|
1946
2063
|
state: jobState.meta({
|
|
1947
|
-
description: "Where the job stands: `running`, `succeeded`, `failed`, `cancelled` or `lost`. `
|
|
2064
|
+
description: "Where the job stands: `running`, `unknown`, `succeeded`, `failed`, `cancelled` or `lost`. `unknown` is not an outcome \u2014 the robot went offline or silent and the cloud does not know yet; the slug stays occupied and the bridge's next statement resolves it, `error` naming why the cloud lost sight of it. `lost` is final: the bridge stated it does not know the job and nothing else runs on its action, or the action server vanished mid-goal."
|
|
2065
|
+
}),
|
|
2066
|
+
origin: jobOrigin.meta({
|
|
2067
|
+
description: "Who started this job. `fleetless` for everything minted by the cloud; `external` for a goal the bridge found active on a published action without having sent it \u2014 no parameters, no starter, never written to `job_runs`."
|
|
1948
2068
|
}),
|
|
1949
2069
|
started_at: import_zod7.z.iso.datetime().meta({
|
|
1950
2070
|
description: "When the cloud minted this job, as an ISO 8601 timestamp. For a job adopted from a reconnecting bridge, this is **adoption time**, not the real start \u2014 the cloud never minted it."
|
|
@@ -1963,7 +2083,7 @@ var job = import_zod7.z.object({
|
|
|
1963
2083
|
* exists for the same reason on the audit log.
|
|
1964
2084
|
*
|
|
1965
2085
|
* **Scoped honestly: per cloud process, per run.** Job state lives in memory
|
|
1966
|
-
* — that is why `lost`
|
|
2086
|
+
* — that is why `unknown` and `lost` exist at all — so this counter restarts when
|
|
1967
2087
|
* the cloud does, alongside the jobs it orders. Sound, because it only ever
|
|
1968
2088
|
* orders jobs that coexist in one registry — and stated, because a reader
|
|
1969
2089
|
* who assumed `auditEvent.seq`'s durable semantics would be wrong.
|
|
@@ -1998,7 +2118,7 @@ var job = import_zod7.z.object({
|
|
|
1998
2118
|
description: "The structured payload belonging to `code`, for the codes that document one \u2014 `job_queue_full` carries its `limit` and its `queued` count here. Absent for a failure with nothing structured to add, which is most of them."
|
|
1999
2119
|
})
|
|
2000
2120
|
}).nullable().meta({
|
|
2001
|
-
description: "Why the job failed: a human `message`, a `code` where one exists, and `details` for the codes that carry a documented payload. `
|
|
2121
|
+
description: "Why the job failed, or why the cloud does not know how it stands: a human `message`, a `code` where one exists, and `details` for the codes that carry a documented payload. Set on `failed` and `lost`, and on `unknown` \u2014 where `code` is `bridge_disconnected` or `bridge_timeout`, the cloud's own reason for not knowing, cleared when the bridge reports the job running again."
|
|
2002
2122
|
})
|
|
2003
2123
|
});
|
|
2004
2124
|
var jobEvent = import_zod7.z.object({
|
|
@@ -2057,16 +2177,16 @@ var jobRun = import_zod7.z.object({
|
|
|
2057
2177
|
description: "Whether the slug was an `action` or a `service`."
|
|
2058
2178
|
}),
|
|
2059
2179
|
state: jobState.meta({
|
|
2060
|
-
description: "How the run ended, or `running` while it is still going. `lost`
|
|
2180
|
+
description: "How the run ended, or `running` while it is still going. `unknown` while the robot has not accounted for it \u2014 offline or silent \u2014 and updated once the bridge says how it stands. `lost` is final: the bridge did not know the run and nothing else ran on its action, so the outcome is unknowable rather than unknown."
|
|
2061
2181
|
}),
|
|
2062
2182
|
started_at: import_zod7.z.iso.datetime().meta({
|
|
2063
2183
|
description: "When the run started, as an ISO 8601 timestamp. Runs are listed and filtered by this instant."
|
|
2064
2184
|
}),
|
|
2065
2185
|
ended_at: import_zod7.z.iso.datetime().nullable().meta({
|
|
2066
|
-
description: "When the run finished, as an ISO 8601 timestamp. `null` while it is still `running` \u2014 a run has an end only once it has one."
|
|
2186
|
+
description: "When the run finished, as an ISO 8601 timestamp. `null` while it is still `running` or `unknown` \u2014 a run has an end only once it has one."
|
|
2067
2187
|
}),
|
|
2068
2188
|
duration_ms: import_zod7.z.number().int().nonnegative().nullable().meta({
|
|
2069
|
-
description: 'How long the run took, in milliseconds. `null` while it is still `running`, never `0` standing in for "nothing so far".'
|
|
2189
|
+
description: 'How long the run took, in milliseconds. `null` while it is still `running` or `unknown`, never `0` standing in for "nothing so far".'
|
|
2070
2190
|
}),
|
|
2071
2191
|
result: import_zod7.z.unknown().nullable().meta({
|
|
2072
2192
|
description: "What the action or service returned once it succeeded, shaped by ROS itself. `null` otherwise."
|
|
@@ -2116,7 +2236,7 @@ var jobRunQuery = import_zod7.z.object({
|
|
|
2116
2236
|
description: "Only runs of this action or service."
|
|
2117
2237
|
}),
|
|
2118
2238
|
state: jobState.optional().meta({
|
|
2119
|
-
description: "Only runs in this state \u2014 `running`, `succeeded`, `failed`, `cancelled` or `lost`."
|
|
2239
|
+
description: "Only runs in this state \u2014 `running`, `unknown`, `succeeded`, `failed`, `cancelled` or `lost`."
|
|
2120
2240
|
}),
|
|
2121
2241
|
kind: jobRunKind.optional().meta({
|
|
2122
2242
|
description: "Only `action` runs, or only `service` runs."
|
|
@@ -2150,13 +2270,14 @@ var jobRunSummary = import_zod7.z.object({
|
|
|
2150
2270
|
since_ms: import_zod7.z.number().int().nonnegative()
|
|
2151
2271
|
});
|
|
2152
2272
|
|
|
2153
|
-
// node_modules/.pnpm/@fleetless+contracts@
|
|
2273
|
+
// node_modules/.pnpm/@fleetless+contracts@5.0.0/node_modules/@fleetless/contracts/dist/protocol.js
|
|
2274
|
+
var DAY_MS = 24 * 60 * 60 * 1e3;
|
|
2154
2275
|
var MAX_PATIENCE_MS = 12e4;
|
|
2155
2276
|
var MIN_PATIENCE_MS = 1e3;
|
|
2156
2277
|
var activeJob = import_zod8.z.object({
|
|
2157
2278
|
job_id: import_zod8.z.uuid(),
|
|
2158
2279
|
slug,
|
|
2159
|
-
state:
|
|
2280
|
+
state: reportedJobState
|
|
2160
2281
|
});
|
|
2161
2282
|
var bridgeHello = import_zod8.z.object({
|
|
2162
2283
|
type: import_zod8.z.literal("hello"),
|
|
@@ -2167,16 +2288,14 @@ var bridgeHello = import_zod8.z.object({
|
|
|
2167
2288
|
* Every job this bridge still knows about, right now.
|
|
2168
2289
|
*
|
|
2169
2290
|
* A reconnect and a restart look **identical** on the wire — same token,
|
|
2170
|
-
* same version, same frame —
|
|
2171
|
-
*
|
|
2172
|
-
*
|
|
2173
|
-
*
|
|
2174
|
-
*
|
|
2175
|
-
*
|
|
2176
|
-
*
|
|
2177
|
-
*
|
|
2178
|
-
* the cloud needs. A breadcrumb file would only add a window in which the
|
|
2179
|
-
* crash beat the write.
|
|
2291
|
+
* same version, same frame — so the bridge enumerates what it still has:
|
|
2292
|
+
* its live jobs, and after a restart every job whose goal it recognised
|
|
2293
|
+
* again from its persisted job-to-goal mapping. A job the cloud holds
|
|
2294
|
+
* `running` or `unknown` that is *not* named here is "not known to the
|
|
2295
|
+
* bridge"; it becomes `lost` (`job_unknown_to_bridge`) only once the
|
|
2296
|
+
* bridge's goal reports show its action free of goals it cannot
|
|
2297
|
+
* attribute — one of those may be that very job — and at once for a
|
|
2298
|
+
* service job, which has no goals to look at.
|
|
2180
2299
|
*
|
|
2181
2300
|
* Defaulted, so a bridge that sends no such field still parses; no jobs
|
|
2182
2301
|
* and no report both mean the same thing to the cloud: nothing to keep
|
|
@@ -2186,7 +2305,20 @@ var bridgeHello = import_zod8.z.object({
|
|
|
2186
2305
|
});
|
|
2187
2306
|
var cloudHelloOk = import_zod8.z.object({
|
|
2188
2307
|
type: import_zod8.z.literal("hello_ok"),
|
|
2189
|
-
robot_id: import_zod8.z.uuid()
|
|
2308
|
+
robot_id: import_zod8.z.uuid(),
|
|
2309
|
+
protocol: import_zod8.z.object({
|
|
2310
|
+
status: import_zod8.z.enum(["current", "deprecated"]).meta({
|
|
2311
|
+
description: "`current` or `deprecated` \u2014 never `unsupported`, which is a `hello_error`."
|
|
2312
|
+
}),
|
|
2313
|
+
sunset_at: import_zod8.z.iso.date().nullable().meta({
|
|
2314
|
+
description: "ISO date a deprecated version stops being served; `null` when current."
|
|
2315
|
+
})
|
|
2316
|
+
}).optional().meta({ description: "The cloud's verdict on the announced protocol version; absent from an older cloud." }),
|
|
2317
|
+
bridge: import_zod8.z.object({
|
|
2318
|
+
latest_version: import_zod8.z.string().min(1).meta({
|
|
2319
|
+
description: "The newest published fleetless-bridge package version, for the bridge's own upgrade hint."
|
|
2320
|
+
})
|
|
2321
|
+
}).optional().meta({ description: "What the cloud knows about bridge packages; absent from an older cloud." })
|
|
2190
2322
|
});
|
|
2191
2323
|
var cloudHelloError = import_zod8.z.object({
|
|
2192
2324
|
type: import_zod8.z.literal("hello_error"),
|
|
@@ -2197,16 +2329,25 @@ var datapointFrame = import_zod8.z.object({
|
|
|
2197
2329
|
type: import_zod8.z.literal("datapoint"),
|
|
2198
2330
|
slug,
|
|
2199
2331
|
value: import_zod8.z.unknown(),
|
|
2200
|
-
timestamp_ms: import_zod8.z.number().int().nonnegative()
|
|
2332
|
+
timestamp_ms: import_zod8.z.number().int().nonnegative(),
|
|
2333
|
+
backfill: import_zod8.z.boolean().optional().meta({ description: "true when the sample was captured while the bridge was disconnected and is being replayed after the reconnect. The cloud keeps such a sample out of its lag measure; absent means live." })
|
|
2201
2334
|
});
|
|
2202
2335
|
var cloudPing = import_zod8.z.object({
|
|
2203
2336
|
type: import_zod8.z.literal("ping"),
|
|
2204
|
-
ts_ms: import_zod8.z.number().int().nonnegative()
|
|
2337
|
+
ts_ms: import_zod8.z.number().int().nonnegative(),
|
|
2338
|
+
latency_ms: import_zod8.z.number().nonnegative().nullable().meta({ description: "Round trip of the last pong in milliseconds; null before the first." }),
|
|
2339
|
+
lag_ms: import_zod8.z.number().nonnegative().nullable().meta({ description: "Datapoint lag over the link: median of the last five seconds minus the ten-minute minimum, in milliseconds; null until a sample exists, and null again whenever no live sample arrived in the last five seconds, because a stale median would be a lie." })
|
|
2205
2340
|
});
|
|
2206
2341
|
var bridgePong = import_zod8.z.object({
|
|
2207
2342
|
type: import_zod8.z.literal("pong"),
|
|
2208
2343
|
ts_ms: import_zod8.z.number().int().nonnegative()
|
|
2209
2344
|
});
|
|
2345
|
+
var bridgeLinkMode = import_zod8.z.object({
|
|
2346
|
+
type: import_zod8.z.literal("link_mode"),
|
|
2347
|
+
low_bandwidth: import_zod8.z.boolean().meta({ description: "Whether the mode is active after this transition." }),
|
|
2348
|
+
reason: import_zod8.z.enum(["lag", "dwell", "forced", "recovered"]).meta({ description: "`lag`: the cloud-measured lag crossed the threshold; `dwell`: the bridge-measured queue dwell did; `forced`: `mode: on` or `off`; `recovered`: both measures stayed at or below the exit threshold." }),
|
|
2349
|
+
at_ms: import_zod8.z.number().int().nonnegative().meta({ description: "Bridge time of the transition, epoch milliseconds." })
|
|
2350
|
+
});
|
|
2210
2351
|
var cloudConfig = import_zod8.z.object({
|
|
2211
2352
|
type: import_zod8.z.literal("config"),
|
|
2212
2353
|
version: import_zod8.z.number().int().nonnegative(),
|
|
@@ -2260,9 +2401,27 @@ var cloudInvoke = import_zod8.z.object({
|
|
|
2260
2401
|
});
|
|
2261
2402
|
var cloudCancel = import_zod8.z.object({
|
|
2262
2403
|
type: import_zod8.z.literal("cancel"),
|
|
2404
|
+
request_id: import_zod8.z.string().min(1).max(64),
|
|
2263
2405
|
slug,
|
|
2264
2406
|
job_id: import_zod8.z.uuid().nullable()
|
|
2265
2407
|
});
|
|
2408
|
+
var cancelReturnCode = import_zod8.z.number().int().min(0).max(3);
|
|
2409
|
+
var bridgeCancelResultEntry = import_zod8.z.object({
|
|
2410
|
+
/** The job the goal belongs to — the bridge's own, or an external goal's derived id. */
|
|
2411
|
+
job_id: import_zod8.z.uuid(),
|
|
2412
|
+
/** The ROS 2 goal id the cancel was sent for. */
|
|
2413
|
+
goal_id: import_zod8.z.string().min(1),
|
|
2414
|
+
/** `null` when the action server did not answer the cancel request within the bridge's own bound. */
|
|
2415
|
+
return_code: cancelReturnCode.nullable()
|
|
2416
|
+
});
|
|
2417
|
+
var bridgeCancelResult = import_zod8.z.object({
|
|
2418
|
+
type: import_zod8.z.literal("cancel_result"),
|
|
2419
|
+
request_id: import_zod8.z.string().min(1).max(64),
|
|
2420
|
+
slug,
|
|
2421
|
+
goals: import_zod8.z.array(bridgeCancelResultEntry),
|
|
2422
|
+
/** Set when the bridge could not send the cancel at all (no such slug, a service, the server gone). */
|
|
2423
|
+
error: import_zod8.z.object({ code: import_zod8.z.string().min(1), message: import_zod8.z.string().min(1) }).nullable()
|
|
2424
|
+
});
|
|
2266
2425
|
var cloudPublish = import_zod8.z.object({
|
|
2267
2426
|
type: import_zod8.z.literal("publish"),
|
|
2268
2427
|
slug,
|
|
@@ -2282,7 +2441,11 @@ var bridgeJobUpdate = import_zod8.z.object({
|
|
|
2282
2441
|
type: import_zod8.z.literal("job_update"),
|
|
2283
2442
|
job_id: import_zod8.z.uuid(),
|
|
2284
2443
|
slug,
|
|
2285
|
-
|
|
2444
|
+
/** Never `unknown`: that is the cloud's word for not having heard. */
|
|
2445
|
+
state: reportedJobState,
|
|
2446
|
+
origin: jobOrigin,
|
|
2447
|
+
/** The ROS 2 goal id; `null` for a service job, which has no goal. */
|
|
2448
|
+
goal_id: import_zod8.z.string().min(1).nullable(),
|
|
2286
2449
|
feedback: import_zod8.z.unknown().nullable(),
|
|
2287
2450
|
progress: import_zod8.z.number().min(0).max(1).nullable(),
|
|
2288
2451
|
result: import_zod8.z.unknown().nullable(),
|
|
@@ -2292,7 +2455,29 @@ var bridgeJobUpdate = import_zod8.z.object({
|
|
|
2292
2455
|
});
|
|
2293
2456
|
var bridgeJobLost = import_zod8.z.object({
|
|
2294
2457
|
type: import_zod8.z.literal("job_lost"),
|
|
2295
|
-
job_ids: import_zod8.z.array(import_zod8.z.uuid())
|
|
2458
|
+
job_ids: import_zod8.z.array(import_zod8.z.uuid()),
|
|
2459
|
+
error: import_zod8.z.object({ code: import_zod8.z.string().min(1), message: import_zod8.z.string().min(1) }).optional()
|
|
2460
|
+
});
|
|
2461
|
+
var cloudJobQuery = import_zod8.z.object({
|
|
2462
|
+
type: import_zod8.z.literal("job_query"),
|
|
2463
|
+
request_id: import_zod8.z.string().min(1).max(64),
|
|
2464
|
+
job_ids: import_zod8.z.array(import_zod8.z.uuid()).min(1)
|
|
2465
|
+
});
|
|
2466
|
+
var bridgeJobStatusEntry = import_zod8.z.object({
|
|
2467
|
+
job_id: import_zod8.z.uuid(),
|
|
2468
|
+
/** Never `unknown` — the bridge only ever states a definite fact about a job it recognises. */
|
|
2469
|
+
state: reportedJobState,
|
|
2470
|
+
feedback: import_zod8.z.unknown().nullable(),
|
|
2471
|
+
progress: import_zod8.z.number().min(0).max(1).nullable(),
|
|
2472
|
+
result: import_zod8.z.unknown().nullable(),
|
|
2473
|
+
/** Same shape as `job.error`, `details` included — see `jobs.ts`. */
|
|
2474
|
+
error: import_zod8.z.object({ code: import_zod8.z.string().min(1), message: import_zod8.z.string().min(1), details: import_zod8.z.unknown().optional() }).nullable()
|
|
2475
|
+
});
|
|
2476
|
+
var bridgeJobStatus = import_zod8.z.object({
|
|
2477
|
+
type: import_zod8.z.literal("job_status"),
|
|
2478
|
+
request_id: import_zod8.z.string().min(1).max(64),
|
|
2479
|
+
jobs: import_zod8.z.array(bridgeJobStatusEntry),
|
|
2480
|
+
unknown_job_ids: import_zod8.z.array(import_zod8.z.uuid())
|
|
2296
2481
|
});
|
|
2297
2482
|
var cloudIntrospectRequest = import_zod8.z.object({
|
|
2298
2483
|
type: import_zod8.z.literal("introspect_request"),
|
|
@@ -2316,75 +2501,8 @@ var bridgeTypeDefinitions = import_zod8.z.object({
|
|
|
2316
2501
|
});
|
|
2317
2502
|
var bridgeState = import_zod8.z.object({
|
|
2318
2503
|
online: import_zod8.z.boolean(),
|
|
2319
|
-
latency_ms: import_zod8.z.number().nonnegative().nullable()
|
|
2320
|
-
})
|
|
2321
|
-
var bridgePressureTier = import_zod8.z.object({
|
|
2322
|
-
sent: import_zod8.z.number().int().nonnegative(),
|
|
2323
|
-
bytes: import_zod8.z.number().int().nonnegative(),
|
|
2324
|
-
drops: import_zod8.z.number().int().nonnegative(),
|
|
2325
|
-
high_water: import_zod8.z.number().int().nonnegative()
|
|
2326
|
-
});
|
|
2327
|
-
var bridgePressure = import_zod8.z.object({
|
|
2328
|
-
link: import_zod8.z.object({
|
|
2329
|
-
/** bytes/s the socket demonstrably drains, from sends >= 64 KiB
|
|
2330
|
-
* only; null until the first large send of the session. */
|
|
2331
|
-
rate_bps: import_zod8.z.number().nonnegative().nullable(),
|
|
2332
|
-
/**
|
|
2333
|
-
* the byte target snapshots are currently encoded to fit.
|
|
2334
|
-
*
|
|
2335
|
-
* `.nonnegative()`, not `.positive()`: the target is derived from
|
|
2336
|
-
* `rate_bps`, and a link measured below 0.5 B/s floors to 0 here. A
|
|
2337
|
-
* schema that rejects 0 does not prevent that link — it only makes the
|
|
2338
|
-
* frame reporting it unparseable, and a console that cannot parse a
|
|
2339
|
-
* pressure frame shows "no feed", i.e. reports a struggling robot as an
|
|
2340
|
-
* *old* one. Zero is a legitimate reading and says something true.
|
|
2341
|
-
*/
|
|
2342
|
-
snapshot_max_bytes: import_zod8.z.number().int().nonnegative()
|
|
2343
|
-
}),
|
|
2344
|
-
/**
|
|
2345
|
-
* String keys "0".."5" because JSON has no integer keys. Counters are
|
|
2346
|
-
* cumulative per session and reset on reconnect; clients window by
|
|
2347
|
-
* differencing two samples.
|
|
2348
|
-
*
|
|
2349
|
-
* **What this schema does not decide:** it does not guarantee all six
|
|
2350
|
-
* keys are present (`z.record` over the six literals is exhaustive in
|
|
2351
|
-
* zod 4 — tested here, it required every key and rejected none, the
|
|
2352
|
-
* opposite of what a partial sample needs — so this is a
|
|
2353
|
-
* `.strictObject().partial()` over the same six literal keys instead, a
|
|
2354
|
-
* deliberate deviation from the originally sketched `z.record` shape with
|
|
2355
|
-
* the same runtime behaviour). A missing tier key reads as zeros; the
|
|
2356
|
-
* schema names what it cannot decide rather than implying a completeness
|
|
2357
|
-
* it cannot check.
|
|
2358
|
-
*/
|
|
2359
|
-
tiers: import_zod8.z.strictObject({
|
|
2360
|
-
"0": bridgePressureTier,
|
|
2361
|
-
"1": bridgePressureTier,
|
|
2362
|
-
"2": bridgePressureTier,
|
|
2363
|
-
"3": bridgePressureTier,
|
|
2364
|
-
"4": bridgePressureTier,
|
|
2365
|
-
"5": bridgePressureTier
|
|
2366
|
-
}).partial(),
|
|
2367
|
-
video: import_zod8.z.object({
|
|
2368
|
-
active_streams: import_zod8.z.number().int().nonnegative(),
|
|
2369
|
-
bitrate_sum_kbps: import_zod8.z.number().int().nonnegative(),
|
|
2370
|
-
/**
|
|
2371
|
-
* The uplink budget the bridge was configured with
|
|
2372
|
-
* (`FLEETLESS_UPLINK_KBPS`), or `null` when none was set.
|
|
2373
|
-
*
|
|
2374
|
-
* `.nonnegative()`, not `.positive()`: `FLEETLESS_UPLINK_KBPS=0` is a
|
|
2375
|
-
* documented setting meaning "no video budget at all", and the bridge
|
|
2376
|
-
* emits that 0 verbatim. `.positive()` made every frame from such a
|
|
2377
|
-
* robot fail the console's `safeParse`, which renders an unparseable
|
|
2378
|
-
* frame as "no pressure feed" — so the one robot that had *deliberately*
|
|
2379
|
-
* turned video off was the one diagnosed as running a bridge too old to
|
|
2380
|
-
* report pressure. A value the producer legitimately sends must parse;
|
|
2381
|
-
* `null` is the only "not set" this field has.
|
|
2382
|
-
*/
|
|
2383
|
-
uplink_kbps: import_zod8.z.number().int().nonnegative().nullable(),
|
|
2384
|
-
override_kbps: import_zod8.z.number().int().nonnegative().nullable(),
|
|
2385
|
-
video_budget_kbps: import_zod8.z.number().int().nonnegative().nullable(),
|
|
2386
|
-
reserve_kbps: import_zod8.z.number().int().nonnegative()
|
|
2387
|
-
})
|
|
2504
|
+
latency_ms: import_zod8.z.number().nonnegative().nullable(),
|
|
2505
|
+
low_bandwidth: import_zod8.z.boolean().meta({ description: "Whether the bridge is in its low-bandwidth mode: datapoints capped, cameras reduced or stopped. Bridge-reported." })
|
|
2388
2506
|
});
|
|
2389
2507
|
var snapshotHeader = import_zod8.z.object({
|
|
2390
2508
|
type: import_zod8.z.literal("snapshot"),
|
|
@@ -2548,11 +2666,11 @@ var bridgeCameraState = import_zod8.z.object({
|
|
|
2548
2666
|
request_id: import_zod8.z.string().min(1).max(64).nullable()
|
|
2549
2667
|
});
|
|
2550
2668
|
|
|
2551
|
-
// node_modules/.pnpm/@fleetless+contracts@
|
|
2669
|
+
// node_modules/.pnpm/@fleetless+contracts@5.0.0/node_modules/@fleetless/contracts/dist/config-issues.js
|
|
2552
2670
|
var EXPOSURE_SECTIONS = ["datapoints", "actions", "services", "publishers", "cameras"];
|
|
2553
2671
|
var EXPOSURE_SECTION_NAMES = new Set(EXPOSURE_SECTIONS);
|
|
2554
2672
|
|
|
2555
|
-
// node_modules/.pnpm/@fleetless+contracts@
|
|
2673
|
+
// node_modules/.pnpm/@fleetless+contracts@5.0.0/node_modules/@fleetless/contracts/dist/rest.js
|
|
2556
2674
|
var import_zod9 = require("zod");
|
|
2557
2675
|
var robot = import_zod9.z.object({
|
|
2558
2676
|
id: import_zod9.z.uuid().meta({
|
|
@@ -2578,6 +2696,21 @@ var createRobotResponse = import_zod9.z.object({
|
|
|
2578
2696
|
robot,
|
|
2579
2697
|
token: robotToken
|
|
2580
2698
|
});
|
|
2699
|
+
var robotTokenRotateResponse = import_zod9.z.object({
|
|
2700
|
+
token: robotToken.meta({
|
|
2701
|
+
description: "The robot's new bridge token. Returned exactly once; the previous token stops working at the bridge's next hello."
|
|
2702
|
+
})
|
|
2703
|
+
});
|
|
2704
|
+
var jointStatePutRequest = import_zod9.z.object({
|
|
2705
|
+
slug: slug.nullable().meta({
|
|
2706
|
+
description: "The datapoint to read joint positions from, or `null` to choose none. It must name a whole-message `sensor_msgs/msg/JointState` datapoint of the published configuration; anything else is a `validation_error` naming the rule."
|
|
2707
|
+
})
|
|
2708
|
+
});
|
|
2709
|
+
var jointStatePutResponse = import_zod9.z.object({
|
|
2710
|
+
joint_state_slug: slug.nullable().meta({
|
|
2711
|
+
description: "The stored mapping after the call, `null` when none is chosen. The same value `assetListResponse.joint_state_slug` carries."
|
|
2712
|
+
})
|
|
2713
|
+
});
|
|
2581
2714
|
var exposureCounts = import_zod9.z.object({
|
|
2582
2715
|
datapoints: import_zod9.z.number().int().nonnegative(),
|
|
2583
2716
|
actions: import_zod9.z.number().int().nonnegative(),
|
|
@@ -2585,11 +2718,15 @@ var exposureCounts = import_zod9.z.object({
|
|
|
2585
2718
|
publishers: import_zod9.z.number().int().nonnegative(),
|
|
2586
2719
|
cameras: import_zod9.z.number().int().nonnegative()
|
|
2587
2720
|
});
|
|
2721
|
+
var protocolStatusValue = import_zod9.z.enum(["current", "deprecated", "refused"]);
|
|
2588
2722
|
var robotListItem = import_zod9.z.object({
|
|
2589
2723
|
...robot.shape,
|
|
2590
2724
|
bridge_state: bridgeState,
|
|
2591
2725
|
/** Required, not optional: "we did not look" and "it exposes nothing" must not render the same. */
|
|
2592
|
-
exposes: exposureCounts
|
|
2726
|
+
exposes: exposureCounts,
|
|
2727
|
+
protocol_status: protocolStatusValue.optional().meta({
|
|
2728
|
+
description: "Where this robot's bridge stands against the protocol window: `current`, `deprecated` (still served, sunset date on the detail), or `refused` (its last hello was refused for its version; offline until upgraded). Absent from a cloud older than 0.21.0; read absence as `current`."
|
|
2729
|
+
})
|
|
2593
2730
|
});
|
|
2594
2731
|
var robotListResponse = import_zod9.z.object({
|
|
2595
2732
|
robots: import_zod9.z.array(robotListItem)
|
|
@@ -2606,6 +2743,15 @@ var datapointValue = import_zod9.z.object({
|
|
|
2606
2743
|
var robotDetailResponse = import_zod9.z.object({
|
|
2607
2744
|
...robotListItem.shape,
|
|
2608
2745
|
bridge_version: import_zod9.z.string().min(1).nullable(),
|
|
2746
|
+
protocol_version: import_zod9.z.number().int().positive().nullable().optional().meta({
|
|
2747
|
+
description: "The protocol version the bridge announced in its last accepted hello; `null` before the first. Absent from a cloud older than 0.21.0."
|
|
2748
|
+
}),
|
|
2749
|
+
protocol: import_zod9.z.object({
|
|
2750
|
+
status: protocolStatusValue.meta({ description: "Same values as `protocol_status`." }),
|
|
2751
|
+
sunset_at: import_zod9.z.iso.date().nullable().meta({
|
|
2752
|
+
description: "ISO date the announced version stops being served; `null` when current or unknown."
|
|
2753
|
+
})
|
|
2754
|
+
}).optional().meta({ description: "The window verdict for `protocol_version`." }),
|
|
2609
2755
|
/**
|
|
2610
2756
|
* Cleared (set back to null) by the next successful hello from this
|
|
2611
2757
|
* robot's bridge — a warning that outlives the condition it warns
|
|
@@ -2659,7 +2805,7 @@ var fetchTypesResponse = import_zod9.z.object({
|
|
|
2659
2805
|
var datapointDescriptor = import_zod9.z.object({
|
|
2660
2806
|
slug: slug.meta({ description: "The name a client reads this datapoint by." }),
|
|
2661
2807
|
builtin: import_zod9.z.boolean().meta({
|
|
2662
|
-
description: "`true` for the datapoints every robot has \u2014 `bridge_state
|
|
2808
|
+
description: "`true` for the datapoints every robot has \u2014 `bridge_state` and `robot_details` \u2014 and `false` for everything the published configuration adds."
|
|
2663
2809
|
}),
|
|
2664
2810
|
unit: import_zod9.z.string().nullable().meta({
|
|
2665
2811
|
description: "The unit the value carries **after** any scale and offset, shown beside the number so nobody has to guess whether `15` means percent, volts or minutes. `null` when the configuration names none."
|
|
@@ -2677,7 +2823,7 @@ var datapointDescriptor = import_zod9.z.object({
|
|
|
2677
2823
|
});
|
|
2678
2824
|
var datapointListResponse = import_zod9.z.object({
|
|
2679
2825
|
datapoints: import_zod9.z.array(datapointDescriptor).meta({
|
|
2680
|
-
description: "Everything a client may read on this robot: the
|
|
2826
|
+
description: "Everything a client may read on this robot: the two built-ins, plus every datapoint the published configuration exposes and the caller's role grants."
|
|
2681
2827
|
})
|
|
2682
2828
|
});
|
|
2683
2829
|
var robotDetailsDoc = import_zod9.z.record(import_zod9.z.string().regex(/^[a-z][a-z0-9_-]{0,63}$/), import_zod9.z.union([import_zod9.z.string().max(4096), import_zod9.z.number(), import_zod9.z.boolean(), import_zod9.z.array(import_zod9.z.unknown()), import_zod9.z.record(import_zod9.z.string(), import_zod9.z.unknown())]));
|
|
@@ -3031,22 +3177,25 @@ var robotDeletionSummary = import_zod9.z.object({
|
|
|
3031
3177
|
bytes_freed: import_zod9.z.number().int().nonnegative(),
|
|
3032
3178
|
cameras: import_zod9.z.array(slug),
|
|
3033
3179
|
/**
|
|
3034
|
-
* Assets destroyed with the robot, and **`asset_bytes_freed` is what
|
|
3035
|
-
*
|
|
3180
|
+
* Assets destroyed with the robot, and **`asset_bytes_freed` is what the
|
|
3181
|
+
* robot's own store gives back** — every distinct mesh or texture blob it
|
|
3182
|
+
* holds, counted once, URDF excluded.
|
|
3036
3183
|
*
|
|
3037
|
-
* Storage is content-addressed,
|
|
3038
|
-
*
|
|
3039
|
-
*
|
|
3040
|
-
*
|
|
3041
|
-
*
|
|
3042
|
-
*
|
|
3043
|
-
*
|
|
3184
|
+
* Storage is content-addressed, but the store and its 1 GB ceiling are now
|
|
3185
|
+
* per robot: a blob another robot also references stays in the object
|
|
3186
|
+
* store but is still credited here, because each robot's counter carries
|
|
3187
|
+
* it regardless of what else points at the same bytes. Same reasoning that
|
|
3188
|
+
* keeps `cameras` out of `slug_count`: this summary is read aloud to a
|
|
3189
|
+
* human, and a number that is nearly right is worse here than an absent
|
|
3190
|
+
* one.
|
|
3044
3191
|
*
|
|
3045
3192
|
* `asset_count` is the plain count of the robot's asset rows, all of which
|
|
3046
3193
|
* do go away.
|
|
3047
3194
|
*/
|
|
3048
3195
|
asset_count: import_zod9.z.number().int().nonnegative(),
|
|
3049
|
-
asset_bytes_freed: import_zod9.z.number().int().nonnegative()
|
|
3196
|
+
asset_bytes_freed: import_zod9.z.number().int().nonnegative().meta({
|
|
3197
|
+
description: "What the robot's store gives back: every distinct mesh or texture blob it holds, counted once, URDF excluded; a blob another robot also references stays in the object store but is still credited here, because each robot's counter carries it."
|
|
3198
|
+
}),
|
|
3050
3199
|
/**
|
|
3051
3200
|
* How many rows of run history go with the robot — every recorded
|
|
3052
3201
|
* invocation of one of its actions or services, up to
|
|
@@ -3185,39 +3334,13 @@ var orgQuotas = import_zod9.z.object({
|
|
|
3185
3334
|
max_end_users: import_zod9.z.number().int().positive(),
|
|
3186
3335
|
max_retention_bytes: import_zod9.z.number().int().nonnegative(),
|
|
3187
3336
|
max_retention_writes_per_minute: import_zod9.z.number().int().nonnegative(),
|
|
3188
|
-
max_realtime_connections: import_zod9.z.number().int().positive()
|
|
3189
|
-
/**
|
|
3190
|
-
* Asset storage — **its own dial, not part of `max_retention_bytes`.** A
|
|
3191
|
-
* sync grows storage in jumps and time series grow steadily; one dial would
|
|
3192
|
-
* let the first crowd out the second, and the org that hit its limit would be
|
|
3193
|
-
* told to look at the wrong thing.
|
|
3194
|
-
*
|
|
3195
|
-
* **Counted per distinct blob *this org references* — not per asset row, and
|
|
3196
|
-
* not per object the platform stores on its behalf.** The two readings are
|
|
3197
|
-
* indistinguishable from the number alone and a customer is entitled to know
|
|
3198
|
-
* which one they are being charged for.
|
|
3199
|
-
*
|
|
3200
|
-
* Within an org, sharing is free: two robots referencing the same mesh cost
|
|
3201
|
-
* one copy, which is what dedup means to a customer, and anything else
|
|
3202
|
-
* charges an org twice for a fleet of identical robots — the normal case.
|
|
3203
|
-
*
|
|
3204
|
-
* **Across orgs, sharing is not free.** Storage stays globally
|
|
3205
|
-
* content-addressed (one object per sha256; that efficiency is real), but
|
|
3206
|
-
* accounting is per-org: an org is charged for each distinct blob it
|
|
3207
|
-
* references and credited when its own last reference goes, whether or not
|
|
3208
|
-
* the blob survives for somebody else. Global refcounting would make the
|
|
3209
|
-
* first org to sync a blob pay for it forever while every later org stored it
|
|
3210
|
-
* free — a quota evadable by anyone whose mesh someone else had already
|
|
3211
|
-
* uploaded, and an org's own number would depend on who got there first.
|
|
3212
|
-
*/
|
|
3213
|
-
max_asset_storage_bytes: import_zod9.z.number().int().nonnegative()
|
|
3337
|
+
max_realtime_connections: import_zod9.z.number().int().positive()
|
|
3214
3338
|
});
|
|
3215
3339
|
var orgQuotaUsageCounts = import_zod9.z.object({
|
|
3216
3340
|
max_robots: import_zod9.z.number().int().nonnegative(),
|
|
3217
3341
|
max_apps: import_zod9.z.number().int().nonnegative(),
|
|
3218
3342
|
max_end_users: import_zod9.z.number().int().nonnegative(),
|
|
3219
3343
|
max_retention_bytes: import_zod9.z.number().int().nonnegative(),
|
|
3220
|
-
max_asset_storage_bytes: import_zod9.z.number().int().nonnegative(),
|
|
3221
3344
|
max_retention_writes_per_minute: import_zod9.z.number().int().nonnegative(),
|
|
3222
3345
|
max_realtime_connections: import_zod9.z.number().int().nonnegative()
|
|
3223
3346
|
}).partial();
|
|
@@ -3312,13 +3435,13 @@ var slugUsageResponse = import_zod9.z.object({
|
|
|
3312
3435
|
alert_count: import_zod9.z.number().int().nonnegative()
|
|
3313
3436
|
});
|
|
3314
3437
|
|
|
3315
|
-
// node_modules/.pnpm/@fleetless+contracts@
|
|
3438
|
+
// node_modules/.pnpm/@fleetless+contracts@5.0.0/node_modules/@fleetless/contracts/dist/realtime.js
|
|
3316
3439
|
var import_zod14 = require("zod");
|
|
3317
3440
|
|
|
3318
|
-
// node_modules/.pnpm/@fleetless+contracts@
|
|
3441
|
+
// node_modules/.pnpm/@fleetless+contracts@5.0.0/node_modules/@fleetless/contracts/dist/client-auth.js
|
|
3319
3442
|
var import_zod13 = require("zod");
|
|
3320
3443
|
|
|
3321
|
-
// node_modules/.pnpm/@fleetless+contracts@
|
|
3444
|
+
// node_modules/.pnpm/@fleetless+contracts@5.0.0/node_modules/@fleetless/contracts/dist/apps.js
|
|
3322
3445
|
var import_zod10 = require("zod");
|
|
3323
3446
|
var appIdentifier = slug;
|
|
3324
3447
|
var app = import_zod10.z.object({
|
|
@@ -3378,6 +3501,26 @@ var appListResponse = import_zod10.z.object({
|
|
|
3378
3501
|
description: "Every app of the caller's organisation, oldest first by `created_at`. The org scope is the whole filter \u2014 there is no id to narrow by and nothing to refuse."
|
|
3379
3502
|
})
|
|
3380
3503
|
});
|
|
3504
|
+
var appDeletionSummary = import_zod10.z.object({
|
|
3505
|
+
user_count: import_zod10.z.number().int().nonnegative().meta({
|
|
3506
|
+
description: "App users deleted with the app. They are the developer's own customers, not Fleetless users, and exist in no other app."
|
|
3507
|
+
}),
|
|
3508
|
+
role_count: import_zod10.z.number().int().nonnegative().meta({
|
|
3509
|
+
description: "Roles deleted with the app, each with its per-robot slug grants."
|
|
3510
|
+
}),
|
|
3511
|
+
server_key_count: import_zod10.z.number().int().nonnegative().meta({
|
|
3512
|
+
description: "Server keys deleted with the app. A client still holding one is refused at its next request."
|
|
3513
|
+
}),
|
|
3514
|
+
invitation_count: import_zod10.z.number().int().nonnegative().meta({
|
|
3515
|
+
description: "Outstanding invitations \u2014 unspent and unexpired \u2014 that will never be accepted."
|
|
3516
|
+
}),
|
|
3517
|
+
oidc_provider_count: import_zod10.z.number().int().nonnegative().meta({
|
|
3518
|
+
description: "Identity providers configured for this app. The providers themselves are somebody else's; only this app's configuration of them goes."
|
|
3519
|
+
}),
|
|
3520
|
+
mail_template_count: import_zod10.z.number().int().nonnegative().meta({
|
|
3521
|
+
description: "Custom mail templates, of at most three. A kind using the Fleetless default text is not counted \u2014 there is no row to lose."
|
|
3522
|
+
})
|
|
3523
|
+
});
|
|
3381
3524
|
var createAppRequest = import_zod10.z.object({
|
|
3382
3525
|
name: import_zod10.z.string().min(1).max(120),
|
|
3383
3526
|
identifier: appIdentifier,
|
|
@@ -3500,10 +3643,10 @@ var rolePermissions = import_zod10.z.object({
|
|
|
3500
3643
|
})
|
|
3501
3644
|
});
|
|
3502
3645
|
|
|
3503
|
-
// node_modules/.pnpm/@fleetless+contracts@
|
|
3646
|
+
// node_modules/.pnpm/@fleetless+contracts@5.0.0/node_modules/@fleetless/contracts/dist/app-users.js
|
|
3504
3647
|
var import_zod12 = require("zod");
|
|
3505
3648
|
|
|
3506
|
-
// node_modules/.pnpm/@fleetless+contracts@
|
|
3649
|
+
// node_modules/.pnpm/@fleetless+contracts@5.0.0/node_modules/@fleetless/contracts/dist/identity.js
|
|
3507
3650
|
var import_zod11 = require("zod");
|
|
3508
3651
|
var password = import_zod11.z.string().min(12).max(256);
|
|
3509
3652
|
var USER_DISPLAY_NAME_MAX = 120;
|
|
@@ -3665,7 +3808,7 @@ var authMeResponse = import_zod11.z.object({ org, user: fleetlessUser });
|
|
|
3665
3808
|
var patchOrgRequest = import_zod11.z.object({ name: import_zod11.z.string().min(1).max(120) }).strict();
|
|
3666
3809
|
var patchAuthMeRequest = import_zod11.z.object({ display_name: import_zod11.z.string().min(1).max(USER_DISPLAY_NAME_MAX).nullable() }).strict();
|
|
3667
3810
|
|
|
3668
|
-
// node_modules/.pnpm/@fleetless+contracts@
|
|
3811
|
+
// node_modules/.pnpm/@fleetless+contracts@5.0.0/node_modules/@fleetless/contracts/dist/app-users.js
|
|
3669
3812
|
var APP_USER_DISPLAY_NAME_MAX = 120;
|
|
3670
3813
|
var providerSlug = import_zod12.z.string().max(40).regex(/^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/, "must be lowercase and hyphen-separated, starting with a letter");
|
|
3671
3814
|
var appUserStatus = import_zod12.z.enum(["pending_verification", "active", "blocked"]);
|
|
@@ -3884,7 +4027,9 @@ var appAuthConfig = import_zod12.z.object({
|
|
|
3884
4027
|
}),
|
|
3885
4028
|
updated_at: import_zod12.z.iso.datetime().meta({ description: "When the configuration was last written, as an ISO 8601 timestamp." })
|
|
3886
4029
|
});
|
|
3887
|
-
var
|
|
4030
|
+
var putAppAuthRegistrationRequest = appAuthConfig.pick({ self_registration: true, allowed_domains: true, allowed_origins: true }).strict();
|
|
4031
|
+
var putAppAuthUrlsRequest = appAuthConfig.pick({ invite_url: true, verify_url: true, reset_url: true }).strict();
|
|
4032
|
+
var putAppAuthMcpRequest = appAuthConfig.pick({ mcp_enabled: true, mcp_login_url: true }).strict();
|
|
3888
4033
|
var mailTemplateKind = import_zod12.z.enum(["invite", "verify", "reset"]);
|
|
3889
4034
|
var appMailTemplate = import_zod12.z.object({
|
|
3890
4035
|
kind: mailTemplateKind.meta({ description: "Which of the three mails this template replaces." }),
|
|
@@ -3916,7 +4061,7 @@ var mailOutcome = import_zod12.z.object({
|
|
|
3916
4061
|
})
|
|
3917
4062
|
});
|
|
3918
4063
|
|
|
3919
|
-
// node_modules/.pnpm/@fleetless+contracts@
|
|
4064
|
+
// node_modules/.pnpm/@fleetless+contracts@5.0.0/node_modules/@fleetless/contracts/dist/client-auth.js
|
|
3920
4065
|
var clientLoginRequest = import_zod13.z.object({
|
|
3921
4066
|
app_identifier: appIdentifier.meta({
|
|
3922
4067
|
description: "The app being logged in to: its globally unique, lowercase, underscore-separated identifier, chosen by the developer at creation. There is no organisation context at login, so this is what decides which app the credentials are checked for."
|
|
@@ -4105,7 +4250,7 @@ var clientIdentity = import_zod13.z.object({
|
|
|
4105
4250
|
})
|
|
4106
4251
|
});
|
|
4107
4252
|
|
|
4108
|
-
// node_modules/.pnpm/@fleetless+contracts@
|
|
4253
|
+
// node_modules/.pnpm/@fleetless+contracts@5.0.0/node_modules/@fleetless/contracts/dist/realtime.js
|
|
4109
4254
|
var clientAuth = import_zod14.z.object({
|
|
4110
4255
|
type: import_zod14.z.literal("auth"),
|
|
4111
4256
|
token: import_zod14.z.string().min(1)
|
|
@@ -4383,7 +4528,7 @@ var orgEventDropped = import_zod14.z.object({
|
|
|
4383
4528
|
dropped: import_zod14.z.number().int().positive()
|
|
4384
4529
|
}).strict();
|
|
4385
4530
|
|
|
4386
|
-
// node_modules/.pnpm/@fleetless+contracts@
|
|
4531
|
+
// node_modules/.pnpm/@fleetless+contracts@5.0.0/node_modules/@fleetless/contracts/dist/client-robots.js
|
|
4387
4532
|
var import_zod15 = require("zod");
|
|
4388
4533
|
var clientRobotListItem = import_zod15.z.object({
|
|
4389
4534
|
...robot.shape,
|
|
@@ -4400,7 +4545,7 @@ var clientRobotListResponse = import_zod15.z.object({
|
|
|
4400
4545
|
})
|
|
4401
4546
|
});
|
|
4402
4547
|
|
|
4403
|
-
// node_modules/.pnpm/@fleetless+contracts@
|
|
4548
|
+
// node_modules/.pnpm/@fleetless+contracts@5.0.0/node_modules/@fleetless/contracts/dist/audit.js
|
|
4404
4549
|
var import_zod16 = require("zod");
|
|
4405
4550
|
var auditActor = import_zod16.z.object({
|
|
4406
4551
|
kind: import_zod16.z.enum(["developer", "end_user", "app_user", "server_key", "bridge"]),
|
|
@@ -4530,7 +4675,7 @@ var auditListResponse = import_zod16.z.object({
|
|
|
4530
4675
|
next_cursor: import_zod16.z.number().int().positive().nullable()
|
|
4531
4676
|
});
|
|
4532
4677
|
|
|
4533
|
-
// node_modules/.pnpm/@fleetless+contracts@
|
|
4678
|
+
// node_modules/.pnpm/@fleetless+contracts@5.0.0/node_modules/@fleetless/contracts/dist/errors.js
|
|
4534
4679
|
var import_zod17 = require("zod");
|
|
4535
4680
|
var apiError = import_zod17.z.object({
|
|
4536
4681
|
code: import_zod17.z.string().min(1),
|
|
@@ -4547,7 +4692,7 @@ var parameterInvalidDetails = import_zod17.z.object({
|
|
|
4547
4692
|
violations: import_zod17.z.array(parameterViolation).min(1)
|
|
4548
4693
|
});
|
|
4549
4694
|
|
|
4550
|
-
// node_modules/.pnpm/@fleetless+contracts@
|
|
4695
|
+
// node_modules/.pnpm/@fleetless+contracts@5.0.0/node_modules/@fleetless/contracts/dist/oauth.js
|
|
4551
4696
|
var import_zod18 = require("zod");
|
|
4552
4697
|
var oauthErrorCode = import_zod18.z.enum([
|
|
4553
4698
|
"invalid_request",
|
|
@@ -4614,7 +4759,7 @@ var dynamicClientRegistrationRequest = import_zod18.z.object({
|
|
|
4614
4759
|
description: "`none`, RFC 7591's value for a public client, and the only value either server registers. Any other value is **refused rather than silently downgraded**: a client that believes it holds a secret and does not has a wrong mental model of its own security. There is no client secret to hold \u2014 mandatory PKCE (`S256`) is the defence."
|
|
4615
4760
|
}),
|
|
4616
4761
|
grant_types: import_zod18.z.array(import_zod18.z.enum(["authorization_code", "refresh_token"])).optional().meta({
|
|
4617
|
-
description: "Accepted for conformance with RFC 7591 and then **ignored
|
|
4762
|
+
description: "Accepted for conformance with RFC 7591 and then **ignored**: both MCP authorization servers grant `authorization_code` and `refresh_token` to every registration, and the answer states what was granted (\xA73.2.1) rather than what was asked."
|
|
4618
4763
|
}),
|
|
4619
4764
|
response_types: import_zod18.z.array(import_zod18.z.enum(["code"])).optional().meta({
|
|
4620
4765
|
description: "Accepted for conformance and then **ignored**; the response names `code`, which is the only response type OAuth 2.1 leaves, the implicit grant having been removed."
|
|
@@ -4636,7 +4781,7 @@ var dynamicClientRegistrationResponse = import_zod18.z.object({
|
|
|
4636
4781
|
description: "The redirect URIs this registration was accepted for. A code is returned to one of these and nowhere else."
|
|
4637
4782
|
}),
|
|
4638
4783
|
grant_types: import_zod18.z.array(import_zod18.z.string()).meta({
|
|
4639
|
-
description: 'The grants this client may use. Always exactly `["authorization_code"]` \u2014
|
|
4784
|
+
description: 'The grants this client may use. Always exactly `["authorization_code", "refresh_token"]` \u2014 an exchange mints a refresh token and the token endpoint rotates it.'
|
|
4640
4785
|
}),
|
|
4641
4786
|
response_types: import_zod18.z.array(import_zod18.z.string()).meta({
|
|
4642
4787
|
description: "The response types this client may ask for: `code`."
|
|
@@ -4651,9 +4796,9 @@ var dynamicClientRegistrationResponse = import_zod18.z.object({
|
|
|
4651
4796
|
description: "Always `0`, which is RFC 7591's way of saying the client secret never expires \u2014 there is none. The **registration** itself does expire: a self-registered client that never completes a flow is an unauthenticated write somebody left behind."
|
|
4652
4797
|
})
|
|
4653
4798
|
});
|
|
4654
|
-
var
|
|
4799
|
+
var oauthCodeTokenRequest = import_zod18.z.object({
|
|
4655
4800
|
grant_type: import_zod18.z.literal("authorization_code").meta({
|
|
4656
|
-
description: "
|
|
4801
|
+
description: "`authorization_code`: this request exchanges the code from the authorize redirect for an access token and a refresh token."
|
|
4657
4802
|
}),
|
|
4658
4803
|
code: import_zod18.z.string().min(1).max(500).meta({
|
|
4659
4804
|
description: "The authorization code from the redirect. It may be exchanged once; a second presentation is `invalid_grant`, the same answer a fabricated code gets."
|
|
@@ -4673,6 +4818,25 @@ var oauthTokenRequest = import_zod18.z.object({
|
|
|
4673
4818
|
}).meta({
|
|
4674
4819
|
description: "RFC 6749 \xA74.1.3's authorization-code exchange with PKCE, as either MCP authorization server reads it. Sent as `application/x-www-form-urlencoded`, per \xA74.1.3, though the server accepts a JSON body too."
|
|
4675
4820
|
});
|
|
4821
|
+
var oauthRefreshTokenRequest = import_zod18.z.object({
|
|
4822
|
+
grant_type: import_zod18.z.literal("refresh_token").meta({
|
|
4823
|
+
description: "`refresh_token`: this request rotates a refresh token into a new access token and a new refresh token. The presented token is consumed; presenting it again revokes the whole session."
|
|
4824
|
+
}),
|
|
4825
|
+
refresh_token: import_zod18.z.string().min(1).max(500).meta({
|
|
4826
|
+
description: "The refresh token from the last token response. Bound to the client that received it and to one identity space: presented by another client, or at the other MCP server, it is `invalid_grant` and stays unconsumed."
|
|
4827
|
+
}),
|
|
4828
|
+
client_id: import_zod18.z.string().min(1).max(200).meta({
|
|
4829
|
+
description: "The client the refresh token was issued to, as registered. A refresh token is not transferable between clients."
|
|
4830
|
+
}),
|
|
4831
|
+
resource: import_zod18.z.url().optional().meta({
|
|
4832
|
+
description: "The resource the new token is for, per RFC 8707. Optional; when named it must be the audience the session was issued for, or the answer is `invalid_target` and the refresh token is left untouched. The successor carries the same audience either way."
|
|
4833
|
+
})
|
|
4834
|
+
}).meta({
|
|
4835
|
+
description: "RFC 6749 \xA76's refresh, as either MCP authorization server reads it. Every use rotates: the answer carries a new refresh token and the presented one is dead."
|
|
4836
|
+
});
|
|
4837
|
+
var oauthTokenRequest = import_zod18.z.discriminatedUnion("grant_type", [oauthCodeTokenRequest, oauthRefreshTokenRequest]).meta({
|
|
4838
|
+
description: "What an MCP token endpoint accepts: the authorization-code exchange, or a refresh. Any other `grant_type` is `unsupported_grant_type`, refused before a lookup happens."
|
|
4839
|
+
});
|
|
4676
4840
|
var oauthTokenResponse = import_zod18.z.object({
|
|
4677
4841
|
access_token: import_zod18.z.string().min(1).meta({
|
|
4678
4842
|
description: "The bearer token. It is the same token the client login mints \u2014 only the envelope differs, because an RFC-compliant client parses this one and knows nothing about Fleetless."
|
|
@@ -4684,7 +4848,7 @@ var oauthTokenResponse = import_zod18.z.object({
|
|
|
4684
4848
|
description: "How long the access token is valid, in **seconds**, per RFC 6749 \xA75.1. Not a timestamp, and not milliseconds."
|
|
4685
4849
|
}),
|
|
4686
4850
|
refresh_token: import_zod18.z.string().min(1).optional().meta({
|
|
4687
|
-
description: "The refresh token
|
|
4851
|
+
description: "The refresh token. Both MCP token endpoints issue one on every exchange and every refresh; it rotates on every use, lives ninety days from its last use, and dies with the account's sessions \u2014 a block, a password change, a withdrawn consent. The console's own OAuth portal issues none."
|
|
4688
4852
|
}),
|
|
4689
4853
|
scope: import_zod18.z.string().max(500).optional().meta({
|
|
4690
4854
|
description: "The scopes the issued token actually carries, space-separated."
|
|
@@ -4707,7 +4871,7 @@ var authorizationServerMetadata = import_zod18.z.object({
|
|
|
4707
4871
|
description: "The response types this server offers: `code` only, the implicit grant being gone with OAuth 2.1."
|
|
4708
4872
|
}),
|
|
4709
4873
|
grant_types_supported: import_zod18.z.array(import_zod18.z.enum(["authorization_code", "refresh_token"])).meta({
|
|
4710
|
-
description: "The grants this server offers
|
|
4874
|
+
description: "The grants this server offers: `authorization_code` and `refresh_token`. OAuth 2.1 removes the implicit and password grants, so neither appears here."
|
|
4711
4875
|
}),
|
|
4712
4876
|
code_challenge_methods_supported: import_zod18.z.array(codeChallengeMethod).meta({
|
|
4713
4877
|
description: "The PKCE challenge methods accepted: `S256` only. `plain` is not offered \u2014 a challenge equal to its verifier defends against nothing, and offering it would make a downgrade negotiable."
|
|
@@ -4768,7 +4932,7 @@ var oauthAuthorizeQuery = import_zod18.z.object({
|
|
|
4768
4932
|
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."
|
|
4769
4933
|
});
|
|
4770
4934
|
|
|
4771
|
-
// node_modules/.pnpm/@fleetless+contracts@
|
|
4935
|
+
// node_modules/.pnpm/@fleetless+contracts@5.0.0/node_modules/@fleetless/contracts/dist/routes.js
|
|
4772
4936
|
var MCP_APP = MCP_APP_PATHS(":appIdentifier");
|
|
4773
4937
|
var APP_IDENTIFIER = {
|
|
4774
4938
|
name: "appIdentifier",
|
|
@@ -5138,6 +5302,42 @@ var ROUTES = [
|
|
|
5138
5302
|
transport: "http",
|
|
5139
5303
|
notes: "A `default_role_id` naming a role of another app is refused: it is the one cross-app authorization check this shape can carry. Changing the robot set closes every live subscription the app's users hold, since a grant may no longer name a reachable robot."
|
|
5140
5304
|
},
|
|
5305
|
+
{
|
|
5306
|
+
method: "GET",
|
|
5307
|
+
path: "/api/apps/:id/deletion-preview",
|
|
5308
|
+
section: "apps",
|
|
5309
|
+
summary: "Reports what deleting the app would destroy, without destroying it.",
|
|
5310
|
+
audience: "developer",
|
|
5311
|
+
auth: "developer",
|
|
5312
|
+
rateLimited: false,
|
|
5313
|
+
ownerTier: false,
|
|
5314
|
+
status: 200,
|
|
5315
|
+
params: [{ name: "id", description: "The app's uuid, as returned by `POST /api/apps` or listed by `GET /api/apps`." }],
|
|
5316
|
+
query: null,
|
|
5317
|
+
request: null,
|
|
5318
|
+
response: appDeletionSummary,
|
|
5319
|
+
errors: [...DEVELOPER_GUARD, "invalid_uuid", "not_found"],
|
|
5320
|
+
transport: "http",
|
|
5321
|
+
notes: "The same shape the delete's own audit event carries, computed by the same function on purpose: the confirmation dialog and the eventual receipt agree by construction, and any difference between them is real drift rather than two estimates that quietly disagree. \n\n**No `force` parameter, unlike the robot pair this is modelled on.** A robot's open live session is a single nameable state whose interruption is its own hazard, which is why that route makes the caller pass `force` explicitly. An app has no equivalent state to force past, and inventing one would be a guess wearing a guard's clothes \u2014 this preview is the guard."
|
|
5322
|
+
},
|
|
5323
|
+
{
|
|
5324
|
+
method: "DELETE",
|
|
5325
|
+
path: "/api/apps/:id",
|
|
5326
|
+
section: "apps",
|
|
5327
|
+
summary: "Deletes an app and everything it produced.",
|
|
5328
|
+
audience: "developer",
|
|
5329
|
+
auth: "developer",
|
|
5330
|
+
rateLimited: false,
|
|
5331
|
+
ownerTier: true,
|
|
5332
|
+
status: 204,
|
|
5333
|
+
params: [{ name: "id", description: "The app's uuid, as returned by `POST /api/apps` or listed by `GET /api/apps`." }],
|
|
5334
|
+
query: null,
|
|
5335
|
+
request: null,
|
|
5336
|
+
response: null,
|
|
5337
|
+
errors: [...DEVELOPER_GUARD, "tier_required", "invalid_uuid", "not_found"],
|
|
5338
|
+
transport: "http",
|
|
5339
|
+
notes: "Owner tier, and the gate runs **after** the org-scoped lookup: a developer-tier admin therefore sees the same `404` a stranger would for an app outside their org, rather than a tier refusal that confirms the id exists. A full cascade \u2014 its users, roles, server keys, invitations, OIDC provider configuration and mail templates all go, recorded once as `app.deleted` carrying an `appDeletionSummary`. Its robots are untouched: they belong to the org, not to the app. \n\n**No `force` parameter** \u2014 see `GET /api/apps/:id/deletion-preview`."
|
|
5340
|
+
},
|
|
5141
5341
|
{
|
|
5142
5342
|
method: "POST",
|
|
5143
5343
|
path: "/api/apps/:id/roles",
|
|
@@ -5644,13 +5844,49 @@ var ROUTES = [
|
|
|
5644
5844
|
response: appAuthConfig,
|
|
5645
5845
|
errors: [...DEVELOPER_GUARD, "invalid_uuid", "not_found"],
|
|
5646
5846
|
transport: "http",
|
|
5647
|
-
notes: "One row per app, created with the app and never absent \u2014 an app that has configured nothing reads back the defaults rather than a `404`. `oidc_callback_url` is in the answer and not in the request: it is minted by the cloud from its own public base URL, is the same for every app and every provider, and is the value a developer registers at their identity provider."
|
|
5847
|
+
notes: "One row per app, created with the app and never absent \u2014 an app that has configured nothing reads back the defaults rather than a `404`. `oidc_callback_url` is in the answer and not in the request: it is minted by the cloud from its own public base URL, is the same for every app and every provider, and is the value a developer registers at their identity provider. It stays read-only on every slice write below for a second reason: a writable callback URL would let a caller point the return leg of an OIDC sign-in, which carries an authorization code, at a host they own. `updated_at` is read-only for a duller one: the server stamps it on every write, and a client-supplied value would be a lie about when the row last changed."
|
|
5648
5848
|
},
|
|
5649
5849
|
{
|
|
5650
5850
|
method: "PUT",
|
|
5651
|
-
path: "/api/apps/:id/auth-config",
|
|
5851
|
+
path: "/api/apps/:id/auth-config/registration",
|
|
5852
|
+
section: "apps",
|
|
5853
|
+
summary: "Replaces who may self-register, and from where.",
|
|
5854
|
+
audience: "developer",
|
|
5855
|
+
auth: "developer",
|
|
5856
|
+
rateLimited: false,
|
|
5857
|
+
ownerTier: false,
|
|
5858
|
+
status: 200,
|
|
5859
|
+
params: [{ name: "id", description: "The app's uuid, as returned by `POST /api/apps` or listed by `GET /api/apps`." }],
|
|
5860
|
+
query: null,
|
|
5861
|
+
request: putAppAuthRegistrationRequest,
|
|
5862
|
+
response: appAuthConfig,
|
|
5863
|
+
errors: [...DEVELOPER_GUARD, "invalid_uuid", "validation_error", "not_found"],
|
|
5864
|
+
transport: "http",
|
|
5865
|
+
notes: "**A replace, not a merge, and `.strict()`**: `self_registration`, `allowed_domains` and `allowed_origins` all arrive or the write is refused, so a client built against an older shape cannot silently clear a setting it does not know about. `oidc_callback_url` and `updated_at` are the server's, refused in this body as in every slice's \u2014 see `GET`'s notes for why. \n\n`400 validation_error` is where the two field rules land: an entry in `allowed_domains` must be lowercase, since a capitalised one can never match a lowercased address, and an entry in `allowed_origins` must be a bare scheme-host-port with no path, since a browser sends nothing longer in its `Origin` header. Each refuses at configuration time rather than failing silently later. \n\nThe merge is server-side against the stored row, so this write never disturbs the urls or mcp slice."
|
|
5866
|
+
},
|
|
5867
|
+
{
|
|
5868
|
+
method: "PUT",
|
|
5869
|
+
path: "/api/apps/:id/auth-config/urls",
|
|
5870
|
+
section: "apps",
|
|
5871
|
+
summary: "Replaces the three pages Fleetless's mails point at.",
|
|
5872
|
+
audience: "developer",
|
|
5873
|
+
auth: "developer",
|
|
5874
|
+
rateLimited: false,
|
|
5875
|
+
ownerTier: false,
|
|
5876
|
+
status: 200,
|
|
5877
|
+
params: [{ name: "id", description: "The app's uuid, as returned by `POST /api/apps` or listed by `GET /api/apps`." }],
|
|
5878
|
+
query: null,
|
|
5879
|
+
request: putAppAuthUrlsRequest,
|
|
5880
|
+
response: appAuthConfig,
|
|
5881
|
+
errors: [...DEVELOPER_GUARD, "invalid_uuid", "validation_error", "not_found"],
|
|
5882
|
+
transport: "http",
|
|
5883
|
+
notes: "**A replace, not a merge, and `.strict()`**: `invite_url`, `verify_url` and `reset_url` all arrive or the write is refused, so a client built against an older shape cannot silently clear a setting it does not know about. `oidc_callback_url` and `updated_at` are the server's, refused in this body as in every slice's \u2014 see `GET`'s notes for why. \n\n`400 validation_error` is where the field rule lands: a URL template must be https (or `http` on `localhost`) and carry its placeholder exactly once \u2014 a second occurrence leaves one literal in a mailed link, refused here rather than failing silently once the mail is sent. \n\nThe merge is server-side against the stored row, so this write never disturbs the registration or mcp slice."
|
|
5884
|
+
},
|
|
5885
|
+
{
|
|
5886
|
+
method: "PUT",
|
|
5887
|
+
path: "/api/apps/:id/auth-config/mcp",
|
|
5652
5888
|
section: "apps",
|
|
5653
|
-
summary: "Replaces the
|
|
5889
|
+
summary: "Replaces the MCP switch and its login URL together.",
|
|
5654
5890
|
audience: "developer",
|
|
5655
5891
|
auth: "developer",
|
|
5656
5892
|
rateLimited: false,
|
|
@@ -5658,11 +5894,11 @@ var ROUTES = [
|
|
|
5658
5894
|
status: 200,
|
|
5659
5895
|
params: [{ name: "id", description: "The app's uuid, as returned by `POST /api/apps` or listed by `GET /api/apps`." }],
|
|
5660
5896
|
query: null,
|
|
5661
|
-
request:
|
|
5897
|
+
request: putAppAuthMcpRequest,
|
|
5662
5898
|
response: appAuthConfig,
|
|
5663
5899
|
errors: [...DEVELOPER_GUARD, "invalid_uuid", "validation_error", "not_found"],
|
|
5664
5900
|
transport: "http",
|
|
5665
|
-
notes: "**A replace, not a merge, and `.strict()`**:
|
|
5901
|
+
notes: "**A replace, not a merge, and `.strict()`**: `mcp_enabled` and `mcp_login_url` both arrive or the write is refused, so a client built against an older shape cannot silently clear a setting it does not know about. `oidc_callback_url` and `updated_at` are the server's, refused in this body as in every slice's \u2014 see `GET`'s notes for why. \n\n`mcp_login_url` answers to the same rule as the `urls` slice's three templates \u2014 https (or `http` on `localhost`), its placeholder exactly once \u2014 refused as `400 validation_error` rather than left to fail mid-OAuth, in a client's browser where no console screen is watching. \n\nThe merge is server-side against the stored row, so this write never disturbs the registration or urls slice."
|
|
5666
5902
|
},
|
|
5667
5903
|
{
|
|
5668
5904
|
method: "GET",
|
|
@@ -6041,7 +6277,7 @@ var ROUTES = [
|
|
|
6041
6277
|
response: dynamicClientRegistrationResponse,
|
|
6042
6278
|
errors: ["rate_limited"],
|
|
6043
6279
|
transport: "http",
|
|
6044
|
-
notes: "RFC 7591. **The request schema is what this endpoint accepts, not what it parses**: the handler reads the body field by field, because \xA73.2.2 distinguishes `invalid_redirect_uri` from `invalid_client_metadata` and one `safeParse` failure cannot say which of the two a caller earned. The shape is deliberately **not** strict, which is the schema agreeing with \xA73.1 rather than a gap in it \u2014 a conforming client sends `client_uri`, `logo_uri` and `software_id`, and both the schema and the server ignore them. `client_name` and `redirect_uris` are the two fields read; `grant_types`, `response_types` and `scope` are accepted and ignored. What comes back is what was actually granted, which \xA73.2.1 allows a server to substitute \u2014 this authorization server
|
|
6280
|
+
notes: "RFC 7591. **The request schema is what this endpoint accepts, not what it parses**: the handler reads the body field by field, because \xA73.2.2 distinguishes `invalid_redirect_uri` from `invalid_client_metadata` and one `safeParse` failure cannot say which of the two a caller earned. The shape is deliberately **not** strict, which is the schema agreeing with \xA73.1 rather than a gap in it \u2014 a conforming client sends `client_uri`, `logo_uri` and `software_id`, and both the schema and the server ignore them. `client_name` and `redirect_uris` are the two fields read; `grant_types`, `response_types` and `scope` are accepted and ignored. What comes back is what was actually granted, which \xA73.2.1 allows a server to substitute \u2014 this authorization server grants `authorization_code` and `refresh_token` to every registration. The registration carries a TTL. Refusals are `oauthError`; the rate limiter answers `apiError`."
|
|
6045
6281
|
},
|
|
6046
6282
|
{
|
|
6047
6283
|
method: "GET",
|
|
@@ -6167,7 +6403,7 @@ var ROUTES = [
|
|
|
6167
6403
|
response: oauthTokenResponse,
|
|
6168
6404
|
errors: [],
|
|
6169
6405
|
transport: "http",
|
|
6170
|
-
notes: "
|
|
6406
|
+
notes: "`authorization_code` mints an `mcp_session` access token bound to the central resource and a refresh token; `refresh_token` rotates that pair, and the presented refresh token is consumed \u2014 a second presentation revokes the session, as on `/api/auth/refresh`. The refresh token lives ninety days from its last use and is bound to the `client_id` it was issued to. A refresh re-reads the Fleetless user, so a removed account cannot refresh. Refusals are RFC 6749 \xA75.2's `oauthError`, so this route emits none of the codes in this reference. The code is single-use, PKCE-verified, and its `resource` must match the audience it was authorized for; a `resource` on a refresh must match the session's audience, and is checked before the token is consumed."
|
|
6171
6407
|
},
|
|
6172
6408
|
/* ------------------------------- developer auth (the console\'s OAuth portal) */
|
|
6173
6409
|
{
|
|
@@ -6458,7 +6694,7 @@ var ROUTES = [
|
|
|
6458
6694
|
response: dynamicClientRegistrationResponse,
|
|
6459
6695
|
errors: ["rate_limited", "not_found"],
|
|
6460
6696
|
transport: "http",
|
|
6461
|
-
notes: "RFC 7591, the same wire and the same handler as `POST /mcp/oauth/register` \u2014 one implementation, because a second answer to \"is this redirect URI acceptable\" would agree with the first only by luck. The request schema is what the endpoint accepts rather than what it parses, for the reason that row gives: \xA73.2.2 needs two distinguishable refusals and one `safeParse` failure offers one. `client_name` and `redirect_uris` are read; `grant_types`, `response_types` and `scope` are accepted and ignored, and what comes back is what was actually granted, which \xA73.2.1 allows \u2014
|
|
6697
|
+
notes: "RFC 7591, the same wire and the same handler as `POST /mcp/oauth/register` \u2014 one implementation, because a second answer to \"is this redirect URI acceptable\" would agree with the first only by luck. The request schema is what the endpoint accepts rather than what it parses, for the reason that row gives: \xA73.2.2 needs two distinguishable refusals and one `safeParse` failure offers one. `client_name` and `redirect_uris` are read; `grant_types`, `response_types` and `scope` are accepted and ignored, and what comes back is what was actually granted, which \xA73.2.1 allows \u2014 this authorization server grants `authorization_code` and `refresh_token` to every registration. The registration carries a TTL. \n\n**The registration is scoped to this app.** A `client_id` minted here authorizes at this app's endpoint and nowhere else, so a client registered against one app cannot walk into another's authorize with it, and a developer who switches MCP off is not left with strangers' registrations valid somewhere adjacent. \n\nRefusals are `oauthError`; the rate limiter and `404 not_found` answer `apiError`. That `404` covers an unknown identifier **and** an app with the switch off, mirroring the two metadata documents this endpoint is discovered from \u2014 a client that could not read those has no business registering here, and giving it a third distinct answer would only tell it something the documents deliberately do not."
|
|
6462
6698
|
},
|
|
6463
6699
|
{
|
|
6464
6700
|
method: "GET",
|
|
@@ -6494,7 +6730,7 @@ var ROUTES = [
|
|
|
6494
6730
|
response: oauthTokenResponse,
|
|
6495
6731
|
errors: [],
|
|
6496
6732
|
transport: "http",
|
|
6497
|
-
notes: "
|
|
6733
|
+
notes: "`authorization_code`, PKCE-verified and single-use, and `refresh_token`, which rotates the pair the exchange minted; the refresh token lives ninety days from its last use, is bound to its client and to this app, and a refresh re-reads the app user's status and their standing consent to the client, so a block or a withdrawn consent ends the session at its next refresh at the latest. **The `aud` is this app's endpoint URL on the canonical public base**, and the code's `resource` must match it \u2014 that is the whole of what stops a token minted for one app being spent at another's endpoint. \n\n**Every refusal is RFC 6749 \xA75.2's `oauthError`, so this route emits none of the codes in this reference \u2014 including the ones about the app.** An unknown identifier and a switched-off app are `invalid_client` here, not the `404` and `403` the authorize route beside it answers. The difference is who reads the answer: authorize is walked by a browser and its refusal is read by a person, while this endpoint is called by a client's own code in the middle of a flow, and handing that code an envelope its OAuth library cannot parse turns a clean refusal into an unexplained crash."
|
|
6498
6734
|
},
|
|
6499
6735
|
/* -------------------------------------------------- app-user (client) auth */
|
|
6500
6736
|
{
|
|
@@ -6879,6 +7115,42 @@ var ROUTES = [
|
|
|
6879
7115
|
transport: "http",
|
|
6880
7116
|
notes: "`token` is the only moment the raw bridge token exists outside the caller's hands \u2014 the cloud stores a hash, so nothing can read it back and a caller who loses it rotates rather than recovers. Audited: this mints a credential that can speak for the org from anywhere, and the event carries no `details`, because the one interesting value here is the token. `max_robots` is checked before anything is created, which is only safe because robot deletion exists."
|
|
6881
7117
|
},
|
|
7118
|
+
{
|
|
7119
|
+
method: "POST",
|
|
7120
|
+
path: "/api/robots/:id/token/rotate",
|
|
7121
|
+
section: "robots",
|
|
7122
|
+
summary: "Mints a new bridge token for the robot and invalidates the old one.",
|
|
7123
|
+
audience: "developer",
|
|
7124
|
+
auth: "developer",
|
|
7125
|
+
rateLimited: false,
|
|
7126
|
+
ownerTier: true,
|
|
7127
|
+
status: 201,
|
|
7128
|
+
params: [{ name: "id", description: "The robot's uuid, as returned by `POST /api/robots` or listed by `GET /api/robots`." }],
|
|
7129
|
+
query: null,
|
|
7130
|
+
request: null,
|
|
7131
|
+
response: robotTokenRotateResponse,
|
|
7132
|
+
errors: [...DEVELOPER_GUARD, "tier_required", "invalid_uuid", "not_found"],
|
|
7133
|
+
transport: "http",
|
|
7134
|
+
notes: "Owner tier, behind the org-scoped lookup, so a developer-tier admin sees the `404` a stranger would for a robot outside their org rather than a tier refusal that confirms the id exists. `token` is the only moment the new secret exists outside the caller's hands \u2014 the cloud stores a hash \u2014 so a caller who loses it rotates again. Audited as `robot.token_rotated`, with no `details`: the one interesting value here is the token. \n\n**It stops the bridge that is connected right now.** The old secret is gone the instant the hash is replaced, so the cloud closes that socket with `CLOSE_TOKEN_ROTATED` rather than leaving a bridge speaking on a credential nothing would accept again. A bridge that does not know the code reconnects and is refused at hello as `invalid_token`, which is the honest answer and ends the same way. **The robot is offline until somebody puts the new token on it** \u2014 this is a deliberate interruption, not a background rekey, and a fleet cannot be rotated without a visit to each robot."
|
|
7135
|
+
},
|
|
7136
|
+
{
|
|
7137
|
+
method: "PUT",
|
|
7138
|
+
path: "/api/robots/:id/urdf/joint-state",
|
|
7139
|
+
section: "robots",
|
|
7140
|
+
summary: "Chooses the datapoint whose joint positions move the robot's URDF, or clears it.",
|
|
7141
|
+
audience: "developer",
|
|
7142
|
+
auth: "developer",
|
|
7143
|
+
rateLimited: false,
|
|
7144
|
+
ownerTier: false,
|
|
7145
|
+
status: 200,
|
|
7146
|
+
params: [{ name: "id", description: "The robot's uuid, as returned by `POST /api/robots` or listed by `GET /api/robots`." }],
|
|
7147
|
+
query: null,
|
|
7148
|
+
request: jointStatePutRequest,
|
|
7149
|
+
response: jointStatePutResponse,
|
|
7150
|
+
errors: [...DEVELOPER_GUARD, "invalid_uuid", "not_found", "validation_error"],
|
|
7151
|
+
transport: "http",
|
|
7152
|
+
notes: '**What qualifies**: a datapoint of the **published** configuration whose ROS type is `sensor_msgs/msg/JointState` and which carries no `field` \u2014 the whole message, because positions and names arrive together and a single extracted field is half of a pose. Anything else is a `validation_error` naming that rule rather than a stored mapping that renders a battery reading as a robot. `{ "slug": null }` clears it, which is why the field is required and nullable rather than optional. \n\n**The mapping cannot outlive what it points at.** Every successful publish re-checks it against the new document and clears it when it no longer qualifies, recording `robot.joint_state_cleared` with the version that did it; a slug rename rewrites it like every other reference the editor already rewrites; deleting the robot takes it along. Every write through this route \u2014 a slug or `null` \u2014 is on the record too, as `robot.joint_state_set` with the actor and the slug, so a clear a person made is never mistaken for one a publish made. The stored value reads back on `GET /api/robots/:id/assets` as `joint_state_slug`, so a renderer fetches the URDF, the meshes and the mapping from one place.'
|
|
7153
|
+
},
|
|
6882
7154
|
{
|
|
6883
7155
|
method: "GET",
|
|
6884
7156
|
path: "/api/robots",
|
|
@@ -7419,7 +7691,7 @@ var ROUTES = [
|
|
|
7419
7691
|
"internal_error"
|
|
7420
7692
|
],
|
|
7421
7693
|
transport: "http",
|
|
7422
|
-
notes: "**One route for both kinds**, because a path segment naming the kind would demand a fact a role grant does not carry. An action answers `202` with an `invokeResponse` the moment the job exists; a service answers `200` with a `serviceCallResponse` once the result is in \u2014 two shapes, carried by one union (`invokeOrServiceResponse`) and told apart by whether `kind` or a bare `result` arrives. Parameters are checked **before** anything about the world (offline, busy): the same request must get the same verdict whether or not the robot happens to be reachable, or a developer testing against an offline robot never learns their parameters were wrong. A service the robot reports as failed answers `502` carrying **the job's own error code**, which is an open set and not one of the codes above."
|
|
7694
|
+
notes: "**One route for both kinds**, because a path segment naming the kind would demand a fact a role grant does not carry. An action answers `202` with an `invokeResponse` the moment the job exists; a service answers `200` with a `serviceCallResponse` once the result is in \u2014 two shapes, carried by one union (`invokeOrServiceResponse`) and told apart by whether `kind` or a bare `result` arrives. Parameters are checked **before** anything about the world (offline, busy): the same request must get the same verdict whether or not the robot happens to be reachable, or a developer testing against an offline robot never learns their parameters were wrong. A slug is `409 busy` while it holds a `running` job, an `unknown` one the robot has not accounted for yet, or an `external` goal someone else started; the refusal's `details.running` names that job, `state` and `origin` included. A service the robot reports as failed answers `502` carrying **the job's own error code**, which is an open set and not one of the codes above."
|
|
7423
7695
|
},
|
|
7424
7696
|
{
|
|
7425
7697
|
method: "GET",
|
|
@@ -7460,9 +7732,21 @@ var ROUTES = [
|
|
|
7460
7732
|
request: cancelRequest,
|
|
7461
7733
|
requestOptional: true,
|
|
7462
7734
|
response: jobResponse,
|
|
7463
|
-
errors: [
|
|
7735
|
+
errors: [
|
|
7736
|
+
...CLIENT_GUARD,
|
|
7737
|
+
"invalid_uuid",
|
|
7738
|
+
"not_found",
|
|
7739
|
+
"validation_error",
|
|
7740
|
+
"not_cancellable",
|
|
7741
|
+
"robot_offline",
|
|
7742
|
+
"cancel_rejected",
|
|
7743
|
+
"bridge_timeout",
|
|
7744
|
+
"unknown_slug",
|
|
7745
|
+
"action_server_lost",
|
|
7746
|
+
"internal_error"
|
|
7747
|
+
],
|
|
7464
7748
|
transport: "http",
|
|
7465
|
-
notes:
|
|
7749
|
+
notes: "The body is optional: a bodyless `POST` was every caller's shape before `job_id` existed, and absent or `job_id: null` both mean \"cancel whatever is running\". A named `job_id` that is **not** what is running cancels nothing and answers `404` \u2014 the caller named an id and thereby ruled the other one out. An `external` job is cancelled the same way, through its goal id. Cancelling an `unknown` job also cancels every external goal on its action, since one of them may be that job. A service is `422 not_cancellable`: a service call has no goal to cancel. Nothing running is a `200` with `job: null`. The answer waits for the bridge's `cancel_result`: a `200` means the action server accepted the cancel request, not that the goal has ended \u2014 the job's end arrives as its own update. Any goal answered `ERROR_REJECTED` makes it `409 cancel_rejected`, with every goal and its `return_code` in `details.goals`; no answer within `JOB_HEARTBEAT_TIMEOUT_MS` is `504 bridge_timeout`; a cancel the bridge could not send at all is `502` carrying the bridge's own code (`unknown_slug`, `action_server_lost`, `internal_error`)."
|
|
7466
7750
|
},
|
|
7467
7751
|
{
|
|
7468
7752
|
method: "POST",
|
|
@@ -7764,6 +8048,24 @@ var ROUTES = [
|
|
|
7764
8048
|
transport: "http",
|
|
7765
8049
|
notes: "Developer sessions only, like starting a sync: the guard admits three caller kinds and the handler answers `401 unauthorized` to the other two. A sync belonging to another robot reads exactly like one that never existed, which is why the robot is resolved first."
|
|
7766
8050
|
},
|
|
8051
|
+
{
|
|
8052
|
+
method: "DELETE",
|
|
8053
|
+
path: "/api/robots/:id/assets",
|
|
8054
|
+
section: "assets",
|
|
8055
|
+
summary: "Empties a robot's asset store: every URDF, mesh and texture, gone at once.",
|
|
8056
|
+
audience: "client",
|
|
8057
|
+
auth: "developer_or_client",
|
|
8058
|
+
rateLimited: false,
|
|
8059
|
+
ownerTier: true,
|
|
8060
|
+
status: 200,
|
|
8061
|
+
params: [{ name: "id", description: "The robot's uuid, as returned by `POST /api/robots` or listed by `GET /api/robots`." }],
|
|
8062
|
+
query: null,
|
|
8063
|
+
request: null,
|
|
8064
|
+
response: assetsClearResponse,
|
|
8065
|
+
errors: [...CLIENT_GUARD, "tier_required", "invalid_uuid", "not_found", "busy"],
|
|
8066
|
+
transport: "http",
|
|
8067
|
+
notes: "The store's escape hatch: a full store is never a dead end, and this is the blunt third of the three answers to it \u2014 the URDF upload is exempt from the gate, reconcile after a sync already frees what the new URDF stopped referencing, and this route lets an Owner clear the robot outright. Owner tier, unconditionally, like starting a sync. Removes every asset of the robot and resets its store to `0`; the next sync fills it again. It does not touch the bridge's availability report \u2014 `urdf_available` still answers from the connected robot, unrelated to what this cloud happens to have stored. A clear while a sync is running is `409 busy` naming that sync's details, the same refusal starting a second sync gets, because deleting under a running upload would leave the store counter wrong."
|
|
8068
|
+
},
|
|
7767
8069
|
/* ------------------------------------------------ org (quotas and fleet reads) */
|
|
7768
8070
|
{
|
|
7769
8071
|
method: "GET",
|
|
@@ -7888,9 +8190,9 @@ var ROUTES = [
|
|
|
7888
8190
|
query: null,
|
|
7889
8191
|
request: null,
|
|
7890
8192
|
response: asset,
|
|
7891
|
-
errors: ["unauthorized", "rate_limited", "
|
|
8193
|
+
errors: ["unauthorized", "rate_limited", "validation_error", "not_found", "quota_exceeded", "bad_request"],
|
|
7892
8194
|
transport: "http",
|
|
7893
|
-
notes: "The body is the **raw file bytes**, not JSON, so it has no request schema; everything about the file \u2014 its kind, its name, its sync id and its announced size \u2014 rides in the `x-fleetless-asset-*` headers `ASSET_UPLOAD_HEADERS` names. The credential is a short-lived upload token minted by `POST /api/robots/:id/assets/sync`, verified in a `preParsing` hook so a refusal precedes the work rather than following it: a `preHandler` would already have buffered the whole file.
|
|
8195
|
+
notes: "The body is the **raw file bytes**, not JSON, so it has no request schema; everything about the file \u2014 its kind, its name, its sync id and its announced size \u2014 rides in the `x-fleetless-asset-*` headers `ASSET_UPLOAD_HEADERS` names. The credential is a short-lived upload token minted by `POST /api/robots/:id/assets/sync`, verified in a `preParsing` hook so a refusal precedes the work rather than following it: a `preHandler` would already have buffered the whole file. **Nothing is refused for its own size** \u2014 the robot's asset store is the only limit, so the announced size is checked there against `ROBOT_ASSET_STORE_BYTES` and a file with no room left answers `409 quota_exceeded` carrying `store_bytes`, `used_bytes` and `size_bytes`, while the sync carries on with the next file. Past that, the server's own body limit answers a bare `413 bad_request` with none of those numbers in it. Rate limited per robot inside that same hook, which is why `rateLimited` is `false`: there is no rate-limiting preHandler registered on this route. The URDF itself is never refused for the store; only meshes and textures are charged against it."
|
|
7894
8196
|
},
|
|
7895
8197
|
/* ------------------------------------ realtime and bridge transports */
|
|
7896
8198
|
{
|
|
@@ -8165,13 +8467,16 @@ function oidcErrorFromCallbackParams(params) {
|
|
|
8165
8467
|
`The federated sign-in ended without a session (${known.data}). See ClientOidcErrorCode in @fleetless/contracts for what each code means, and branch on error.code rather than on this message.`
|
|
8166
8468
|
);
|
|
8167
8469
|
}
|
|
8168
|
-
function
|
|
8470
|
+
function sessionlessRefusal(option, method, needs) {
|
|
8169
8471
|
throw new FleetlessError(
|
|
8170
8472
|
"invalid_option",
|
|
8171
|
-
`auth.${method} is not available on a client constructed with a serverKey \u2014 ${needs}. Build a client with a tokenStore (an app user's own session) for this call.`
|
|
8473
|
+
`auth.${method} is not available on a client constructed with ${option === "serverKey" ? "a serverKey" : "a credentials source"} \u2014 ${needs}. Build a client with a tokenStore (an app user's own session) for this call.`
|
|
8172
8474
|
);
|
|
8173
8475
|
}
|
|
8174
|
-
function createServerKeyAuth(http, appIdentifier2) {
|
|
8476
|
+
function createServerKeyAuth(http, appIdentifier2, option = "serverKey") {
|
|
8477
|
+
const serverKeyRefusal = (method, needs) => sessionlessRefusal(option, method, needs);
|
|
8478
|
+
const subject = option === "serverKey" ? "a server key" : "a supplied credential";
|
|
8479
|
+
const holder = option === "serverKey" ? "a server-key client" : "a client with a supplied credential";
|
|
8175
8480
|
return {
|
|
8176
8481
|
// **`register`, `resendVerification` and `requestPasswordReset` are
|
|
8177
8482
|
// allowed here** — see `createPublicAuthCalls`. They are public routes
|
|
@@ -8180,25 +8485,25 @@ function createServerKeyAuth(http, appIdentifier2) {
|
|
|
8180
8485
|
// already has.
|
|
8181
8486
|
...createPublicAuthCalls(http, appIdentifier2),
|
|
8182
8487
|
async verifyEmail() {
|
|
8183
|
-
serverKeyRefusal("verifyEmail",
|
|
8488
|
+
serverKeyRefusal("verifyEmail", `the route answers a session and ${holder} has nowhere to store it, so the session would be silently discarded`);
|
|
8184
8489
|
},
|
|
8185
8490
|
async login() {
|
|
8186
|
-
serverKeyRefusal("login",
|
|
8491
|
+
serverKeyRefusal("login", `${subject} IS the credential; there is nothing to exchange`);
|
|
8187
8492
|
},
|
|
8188
8493
|
async logout() {
|
|
8189
|
-
serverKeyRefusal("logout",
|
|
8494
|
+
serverKeyRefusal("logout", `${subject} holds no session to end`);
|
|
8190
8495
|
},
|
|
8191
8496
|
async me() {
|
|
8192
8497
|
return http.request("/api/client/me", {});
|
|
8193
8498
|
},
|
|
8194
8499
|
async changePassword() {
|
|
8195
|
-
serverKeyRefusal("changePassword",
|
|
8500
|
+
serverKeyRefusal("changePassword", `${subject} has no password${option === "serverKey" ? "; rotate the key in the console instead" : ""}`);
|
|
8196
8501
|
},
|
|
8197
8502
|
async confirmPasswordReset() {
|
|
8198
|
-
serverKeyRefusal("confirmPasswordReset",
|
|
8503
|
+
serverKeyRefusal("confirmPasswordReset", `the route answers a session and ${holder} has nowhere to store it, so the session would be silently discarded`);
|
|
8199
8504
|
},
|
|
8200
8505
|
async acceptInvitation() {
|
|
8201
|
-
serverKeyRefusal("acceptInvitation",
|
|
8506
|
+
serverKeyRefusal("acceptInvitation", `the route answers a session and ${holder} has nowhere to store it, so the session would be silently discarded`);
|
|
8202
8507
|
},
|
|
8203
8508
|
async listProviders() {
|
|
8204
8509
|
const query = new URLSearchParams({ app_identifier: appIdentifier2 });
|
|
@@ -8218,16 +8523,16 @@ function createServerKeyAuth(http, appIdentifier2) {
|
|
|
8218
8523
|
return http.request(`/api/client/mcp/interactions/${pathSegment(id)}`, {});
|
|
8219
8524
|
},
|
|
8220
8525
|
async approveMcpInteraction() {
|
|
8221
|
-
serverKeyRefusal("approveMcpInteraction",
|
|
8526
|
+
serverKeyRefusal("approveMcpInteraction", `a consent is a person's decision, and ${subject} is not a person`);
|
|
8222
8527
|
},
|
|
8223
8528
|
async denyMcpInteraction() {
|
|
8224
|
-
serverKeyRefusal("denyMcpInteraction",
|
|
8529
|
+
serverKeyRefusal("denyMcpInteraction", `a consent is a person's decision, and ${subject} is not a person`);
|
|
8225
8530
|
},
|
|
8226
8531
|
async listMcpGrants() {
|
|
8227
|
-
serverKeyRefusal("listMcpGrants",
|
|
8532
|
+
serverKeyRefusal("listMcpGrants", `${subject} never went through a consent screen, so it has no grants of its own`);
|
|
8228
8533
|
},
|
|
8229
8534
|
async revokeMcpGrant() {
|
|
8230
|
-
serverKeyRefusal("revokeMcpGrant",
|
|
8535
|
+
serverKeyRefusal("revokeMcpGrant", `${subject} never went through a consent screen, so it has no grants of its own`);
|
|
8231
8536
|
}
|
|
8232
8537
|
};
|
|
8233
8538
|
}
|
|
@@ -8967,9 +9272,10 @@ var InMemoryTokenStore = class {
|
|
|
8967
9272
|
|
|
8968
9273
|
// src/client.ts
|
|
8969
9274
|
function createClient(options) {
|
|
8970
|
-
|
|
9275
|
+
const chosen = ["tokenStore", "serverKey", "credentials"].filter((name) => options[name] !== void 0);
|
|
9276
|
+
if (chosen.length > 1) {
|
|
8971
9277
|
throw new Error(
|
|
8972
|
-
|
|
9278
|
+
`createClient: pass exactly one of \`tokenStore\` (end-user login), \`serverKey\` (a server-side caller) or \`credentials\` (a bearer the caller owns) \u2014 got ${chosen.join(" and ")}.`
|
|
8973
9279
|
);
|
|
8974
9280
|
}
|
|
8975
9281
|
const config = Object.freeze({
|
|
@@ -8985,7 +9291,11 @@ function createClient(options) {
|
|
|
8985
9291
|
let auth;
|
|
8986
9292
|
let http;
|
|
8987
9293
|
let credentials;
|
|
8988
|
-
if (options.
|
|
9294
|
+
if (options.credentials !== void 0) {
|
|
9295
|
+
credentials = options.credentials;
|
|
9296
|
+
http = new HttpClient({ baseUrl: config.apiUrl, fetch: fetchImpl, credentials });
|
|
9297
|
+
auth = createServerKeyAuth(http, config.appIdentifier, "credentials");
|
|
9298
|
+
} else if (options.serverKey !== void 0) {
|
|
8989
9299
|
credentials = new ServerKeyCredentials(options.serverKey);
|
|
8990
9300
|
http = new HttpClient({ baseUrl: config.apiUrl, fetch: fetchImpl, credentials });
|
|
8991
9301
|
auth = createServerKeyAuth(http, config.appIdentifier);
|
|
@@ -9008,7 +9318,7 @@ function createClient(options) {
|
|
|
9008
9318
|
const jobs = createJobsApi(http);
|
|
9009
9319
|
const assets = createAssetsApi(http);
|
|
9010
9320
|
const robots = createRobotsApi(http);
|
|
9011
|
-
if (options.serverKey === void 0) {
|
|
9321
|
+
if (options.serverKey === void 0 && options.credentials === void 0) {
|
|
9012
9322
|
const baseLogout = auth.logout.bind(auth);
|
|
9013
9323
|
auth = {
|
|
9014
9324
|
...auth,
|