@danypops/vehicle-core 0.17.0 → 0.17.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -0
- package/dist/{vehicle-approvals.d.ts → approvals/approval.d.ts} +4 -11
- package/dist/{vehicle-approvals.js → approvals/approval.js} +33 -23
- package/dist/approvals/index.d.ts +1 -0
- package/dist/approvals/index.js +1 -0
- package/dist/client/client.d.ts +29 -0
- package/dist/client/client.js +1 -0
- package/dist/client/index.d.ts +1 -0
- package/dist/client/index.js +1 -0
- package/dist/content/content.d.ts +32 -0
- package/dist/content/content.js +25 -0
- package/dist/content/index.d.ts +1 -0
- package/dist/content/index.js +1 -0
- package/dist/{vehicle-errors.d.ts → errors/error.d.ts} +10 -9
- package/dist/errors/index.d.ts +1 -0
- package/dist/errors/index.js +1 -0
- package/dist/events/event.d.ts +42 -0
- package/dist/events/event.js +35 -0
- package/dist/events/index.d.ts +1 -0
- package/dist/events/index.js +1 -0
- package/dist/idempotency/index.d.ts +1 -0
- package/dist/idempotency/index.js +1 -0
- package/dist/{vehicle-idempotency.d.ts → idempotency/receipt.d.ts} +1 -1
- package/dist/index.d.ts +26 -8
- package/dist/index.js +26 -8
- package/dist/jobs/identity.d.ts +11 -0
- package/dist/jobs/identity.js +13 -0
- package/dist/jobs/index.d.ts +7 -0
- package/dist/jobs/index.js +7 -0
- package/dist/jobs/replay.d.ts +8 -0
- package/dist/jobs/replay.js +9 -0
- package/dist/jobs/retention.d.ts +26 -0
- package/dist/jobs/retention.js +39 -0
- package/dist/jobs/steer.d.ts +23 -0
- package/dist/jobs/steer.js +41 -0
- package/dist/jobs/termination.d.ts +5 -0
- package/dist/jobs/termination.js +11 -0
- package/dist/jobs/wake-log.d.ts +39 -0
- package/dist/jobs/wake-log.js +65 -0
- package/dist/jobs/wire.d.ts +47 -0
- package/dist/jobs/wire.js +1 -0
- package/dist/manifest/index.d.ts +1 -0
- package/dist/manifest/index.js +1 -0
- package/dist/manifest/manifest.d.ts +49 -0
- package/dist/manifest/manifest.js +1 -0
- package/dist/operations/context.d.ts +52 -0
- package/dist/operations/context.js +1 -0
- package/dist/operations/effect.d.ts +3 -0
- package/dist/operations/effect.js +2 -0
- package/dist/operations/index.d.ts +3 -0
- package/dist/operations/index.js +3 -0
- package/dist/operations/operation.d.ts +98 -0
- package/dist/operations/operation.js +79 -0
- package/dist/{atomic-json.d.ts → persistence/atomic-json.d.ts} +1 -2
- package/dist/{atomic-json.js → persistence/atomic-json.js} +1 -2
- package/dist/persistence/index.d.ts +1 -0
- package/dist/persistence/index.js +1 -0
- package/dist/schedules/index.d.ts +1 -0
- package/dist/schedules/index.js +1 -0
- package/dist/{vehicle-scheduler.d.ts → schedules/schedule.d.ts} +6 -8
- package/dist/schemas/codec.d.ts +25 -0
- package/dist/schemas/codec.js +7 -0
- package/dist/schemas/index.d.ts +4 -0
- package/dist/schemas/index.js +4 -0
- package/dist/schemas/json.d.ts +7 -0
- package/dist/schemas/json.js +14 -0
- package/dist/schemas/loose-object.d.ts +20 -0
- package/dist/schemas/loose-object.js +73 -0
- package/dist/schemas/presentation.d.ts +11 -0
- package/dist/schemas/presentation.js +22 -0
- package/dist/watches/index.d.ts +1 -0
- package/dist/watches/index.js +1 -0
- package/dist/{vehicle-watchers.d.ts → watches/registry.d.ts} +2 -9
- package/dist/{vehicle-watchers.js → watches/registry.js} +2 -9
- package/package.json +5 -1
- package/src/{vehicle-approvals.ts → approvals/approval.ts} +27 -24
- package/src/approvals/index.ts +1 -0
- package/src/client/client.ts +31 -0
- package/src/client/index.ts +1 -0
- package/src/content/content.ts +46 -0
- package/src/content/index.ts +1 -0
- package/src/{vehicle-errors.ts → errors/error.ts} +10 -9
- package/src/errors/index.ts +1 -0
- package/src/events/event.ts +72 -0
- package/src/events/index.ts +1 -0
- package/src/idempotency/index.ts +1 -0
- package/src/{vehicle-idempotency.ts → idempotency/receipt.ts} +1 -1
- package/src/index.ts +26 -8
- package/src/jobs/identity.ts +13 -0
- package/src/jobs/index.ts +7 -0
- package/src/jobs/replay.ts +17 -0
- package/src/jobs/retention.ts +59 -0
- package/src/jobs/steer.ts +49 -0
- package/src/jobs/termination.ts +13 -0
- package/src/jobs/wake-log.ts +98 -0
- package/src/jobs/wire.ts +51 -0
- package/src/manifest/index.ts +1 -0
- package/src/manifest/manifest.ts +52 -0
- package/src/operations/context.ts +56 -0
- package/src/operations/effect.ts +3 -0
- package/src/operations/index.ts +3 -0
- package/src/operations/operation.ts +184 -0
- package/src/{atomic-json.ts → persistence/atomic-json.ts} +1 -2
- package/src/persistence/index.ts +1 -0
- package/src/schedules/index.ts +1 -0
- package/src/{vehicle-scheduler.ts → schedules/schedule.ts} +6 -8
- package/src/schemas/codec.ts +30 -0
- package/src/schemas/index.ts +4 -0
- package/src/schemas/json.ts +17 -0
- package/src/schemas/loose-object.ts +82 -0
- package/src/schemas/presentation.ts +28 -0
- package/src/watches/index.ts +1 -0
- package/src/{vehicle-watchers.ts → watches/registry.ts} +2 -9
- package/dist/vehicle-contract.d.ts +0 -353
- package/dist/vehicle-contract.js +0 -253
- package/dist/vehicle-jobs.d.ts +0 -159
- package/dist/vehicle-jobs.js +0 -182
- package/src/vehicle-contract.ts +0 -597
- package/src/vehicle-jobs.ts +0 -305
- /package/dist/{vehicle-errors.js → errors/error.js} +0 -0
- /package/dist/{vehicle-idempotency.js → idempotency/receipt.js} +0 -0
- /package/dist/{vehicle-scheduler.js → schedules/schedule.js} +0 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export type JsonPrimitive = string | number | boolean | null;
|
|
2
|
+
export type JsonValue = JsonPrimitive | readonly JsonValue[] | { readonly [key: string]: JsonValue };
|
|
3
|
+
export type JsonSchema = Readonly<Record<string, JsonValue>>;
|
|
4
|
+
|
|
5
|
+
export function cloneJson<T extends JsonValue>(value: T): T {
|
|
6
|
+
const serialized = JSON.stringify(value);
|
|
7
|
+
if (serialized === undefined) throw new Error("Vehicle JSON metadata must be serializable");
|
|
8
|
+
return freezeJson(JSON.parse(serialized) as JsonValue) as T;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function freezeJson(value: JsonValue): JsonValue {
|
|
12
|
+
if (Array.isArray(value)) return Object.freeze(value.map(freezeJson));
|
|
13
|
+
if (value !== null && typeof value === "object") {
|
|
14
|
+
return Object.freeze(Object.fromEntries(Object.entries(value).map(([key, child]) => [key, freezeJson(child)])));
|
|
15
|
+
}
|
|
16
|
+
return value;
|
|
17
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { defineVehicleSchema, type VehicleSchemaCodec } from "./codec.js";
|
|
2
|
+
import type { JsonValue } from "./json.js";
|
|
3
|
+
|
|
4
|
+
export interface LooseObjectProperty {
|
|
5
|
+
readonly type: string;
|
|
6
|
+
readonly enum?: readonly string[];
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/** JSON Schema's own `type` keyword vocabulary -- checked for real below so a declared `type: "number"` (say) can't silently accept a string forever. `"integer"` additionally requires no fractional part, matching JSON Schema's own distinction from plain `"number"`. An unrecognized type name is treated as "anything goes" (matches passthroughVehicleSchema's own precedent for a shape not worth strictly enforcing) rather than rejecting every input outright for what would otherwise be a schema-authoring typo. */
|
|
10
|
+
function matchesLooseObjectPropertyType(type: string, value: unknown): boolean {
|
|
11
|
+
switch (type) {
|
|
12
|
+
case "string":
|
|
13
|
+
return typeof value === "string";
|
|
14
|
+
case "number":
|
|
15
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
16
|
+
case "integer":
|
|
17
|
+
return typeof value === "number" && Number.isInteger(value);
|
|
18
|
+
case "boolean":
|
|
19
|
+
return typeof value === "boolean";
|
|
20
|
+
case "object":
|
|
21
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
22
|
+
case "array":
|
|
23
|
+
return Array.isArray(value);
|
|
24
|
+
case "null":
|
|
25
|
+
return value === null;
|
|
26
|
+
default:
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* A VehicleRegistry only ever calls a schema's own safeParse -- jsonSchema is
|
|
33
|
+
* descriptive metadata surfaced to a client/Pi projection, never itself
|
|
34
|
+
* enforced at runtime -- so a declared `type`/`enum`/`additionalProperties: false`
|
|
35
|
+
* has to be checked here for real, or it's a documentation gesture, not an
|
|
36
|
+
* honest contract (the exact drift this function's own jsonSchema metadata
|
|
37
|
+
* had before: it always advertised `additionalProperties: false` and a
|
|
38
|
+
* per-property `type`, while safeParse only ever checked `required` and
|
|
39
|
+
* `enum`). Every consumer projecting a plain-object input onto a
|
|
40
|
+
* VehicleOperation needs the same required/type/extra-key/enum checks; this
|
|
41
|
+
* is that check written once.
|
|
42
|
+
*/
|
|
43
|
+
export function defineLooseObjectSchema(
|
|
44
|
+
properties: Record<string, LooseObjectProperty>,
|
|
45
|
+
required: readonly string[] = [],
|
|
46
|
+
): VehicleSchemaCodec<Record<string, unknown>> {
|
|
47
|
+
return defineVehicleSchema<Record<string, unknown>>({
|
|
48
|
+
// LooseObjectProperty's named fields (type, enum) are all JSON-value-shaped
|
|
49
|
+
// at runtime, but TypeScript's structural check against the recursive
|
|
50
|
+
// JsonValue union doesn't see that through a plain interface -- the cast
|
|
51
|
+
// is a type-system limitation, not a runtime concern.
|
|
52
|
+
jsonSchema: { type: "object", properties: properties as unknown as JsonValue, required: [...required], additionalProperties: false },
|
|
53
|
+
safeParse(value) {
|
|
54
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
55
|
+
return { success: false, issues: [{ path: [], message: "input must be an object" }] };
|
|
56
|
+
}
|
|
57
|
+
const input = value as Record<string, unknown>;
|
|
58
|
+
for (const key of required) {
|
|
59
|
+
if (!(key in input)) return { success: false, issues: [{ path: [key], message: `${key} is required` }] };
|
|
60
|
+
}
|
|
61
|
+
for (const key of Object.keys(input)) {
|
|
62
|
+
if (!(key in properties)) return { success: false, issues: [{ path: [key], message: `${key} is not a recognized property` }] };
|
|
63
|
+
}
|
|
64
|
+
for (const [key, schema] of Object.entries(properties)) {
|
|
65
|
+
if (!(key in input)) continue;
|
|
66
|
+
if (!matchesLooseObjectPropertyType(schema.type, input[key])) {
|
|
67
|
+
return { success: false, issues: [{ path: [key], message: `${key} must be of type ${schema.type}` }] };
|
|
68
|
+
}
|
|
69
|
+
if (schema.enum && !schema.enum.includes(input[key] as string)) {
|
|
70
|
+
return { success: false, issues: [{ path: [key], message: `${key} must be one of ${schema.enum.join(", ")}` }] };
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return { success: true, value: input };
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Accepts any value unvalidated -- for an operation whose output shape isn't worth a dedicated schema (an internal/low-stakes result, or one already validated upstream by the domain logic it wraps). */
|
|
79
|
+
export const passthroughVehicleSchema: VehicleSchemaCodec<unknown> = defineVehicleSchema<unknown>({
|
|
80
|
+
jsonSchema: { type: "object" },
|
|
81
|
+
safeParse: (value) => ({ success: true, value }),
|
|
82
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/** Shared defense-in-depth vocabulary for credential-shaped fields at every Vehicle projection boundary. */
|
|
2
|
+
export const VEHICLE_CREDENTIAL_FIELD_NAMES = Object.freeze([
|
|
3
|
+
"password",
|
|
4
|
+
"token",
|
|
5
|
+
"accessToken",
|
|
6
|
+
"refreshToken",
|
|
7
|
+
"apiKey",
|
|
8
|
+
"secret",
|
|
9
|
+
"authorization",
|
|
10
|
+
"credential",
|
|
11
|
+
] as const);
|
|
12
|
+
|
|
13
|
+
const NORMALIZED_VEHICLE_CREDENTIAL_FIELD_NAMES = new Set(
|
|
14
|
+
VEHICLE_CREDENTIAL_FIELD_NAMES.map((name) => name.replace(/[^a-z0-9]/gi, "").toLowerCase()),
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
/** Case/separator-insensitive credential-name check used as a fallback when a schema annotation is missing. */
|
|
18
|
+
export function isVehicleCredentialFieldName(name: string): boolean {
|
|
19
|
+
return NORMALIZED_VEHICLE_CREDENTIAL_FIELD_NAMES.has(name.replace(/[^a-z0-9]/gi, "").toLowerCase());
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* JSON Schema property annotation consumed by human-facing Vehicle adapters.
|
|
24
|
+
* `omit` hides the field; `summarize` may show shape/size but never its value.
|
|
25
|
+
* Standard `writeOnly: true` and `format: "password"` always imply omission.
|
|
26
|
+
*/
|
|
27
|
+
export const VEHICLE_SCHEMA_PRESENTATION_EXTENSION = "x-vehicle-presentation" as const;
|
|
28
|
+
export type VehicleSchemaPresentation = "omit" | "summarize";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./registry.js";
|
|
@@ -1,13 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* "Watch a changing resource, get notified" --
|
|
3
|
-
*
|
|
4
|
-
* generalized past Lector's own workspace/pattern vocabulary (`workspaceId`
|
|
5
|
-
* -> `scope`, `pattern` -> `resource`) into a shared Vehicle primitive.
|
|
6
|
-
* Confirmed independently reinvented three-plus times across this house's
|
|
7
|
-
* own ecosystem before this existed: Lector's own registry, Lector's CLI
|
|
8
|
-
* (a second, non-resilient reimplementation of the same watch/subscribe
|
|
9
|
-
* shape), and Papyrus's TaskOverlay/NoteOverlay (each hand-rolling the same
|
|
10
|
-
* ensurePushChannel()+poll dance independently).
|
|
2
|
+
* "Watch a changing resource, get notified" -- a shared Vehicle primitive
|
|
3
|
+
* generalizing a (scope, resource) watch registration into a wire topic.
|
|
11
4
|
*
|
|
12
5
|
* Pure, in-memory bookkeeping only -- no filesystem, network, or PushChannel
|
|
13
6
|
* I/O here. Matching pattern/resource against a real changed resource is a
|
|
@@ -1,353 +0,0 @@
|
|
|
1
|
-
import type { VehicleJobSnapshot, VehicleJobSubmitOptions, VehicleJobSubmitResult, VehicleJobTailResult, VehicleJobWakeBudget } from "./vehicle-jobs.js";
|
|
2
|
-
export type JsonPrimitive = string | number | boolean | null;
|
|
3
|
-
export type JsonValue = JsonPrimitive | readonly JsonValue[] | {
|
|
4
|
-
readonly [key: string]: JsonValue;
|
|
5
|
-
};
|
|
6
|
-
export type JsonSchema = Readonly<Record<string, JsonValue>>;
|
|
7
|
-
/** Shared defense-in-depth vocabulary for credential-shaped fields at every Vehicle projection boundary. */
|
|
8
|
-
export declare const VEHICLE_CREDENTIAL_FIELD_NAMES: readonly ["password", "token", "accessToken", "refreshToken", "apiKey", "secret", "authorization", "credential"];
|
|
9
|
-
/** Case/separator-insensitive credential-name check used as a fallback when a schema annotation is missing. */
|
|
10
|
-
export declare function isVehicleCredentialFieldName(name: string): boolean;
|
|
11
|
-
/**
|
|
12
|
-
* JSON Schema property annotation consumed by human-facing Vehicle adapters.
|
|
13
|
-
* `omit` hides the field; `summarize` may show shape/size but never its value.
|
|
14
|
-
* Standard `writeOnly: true` and `format: "password"` always imply omission.
|
|
15
|
-
*/
|
|
16
|
-
export declare const VEHICLE_SCHEMA_PRESENTATION_EXTENSION: "x-vehicle-presentation";
|
|
17
|
-
export type VehicleSchemaPresentation = "omit" | "summarize";
|
|
18
|
-
export interface VehicleSchemaIssue {
|
|
19
|
-
readonly path: readonly (string | number)[];
|
|
20
|
-
readonly message: string;
|
|
21
|
-
}
|
|
22
|
-
export type VehicleSchemaResult<T> = {
|
|
23
|
-
readonly success: true;
|
|
24
|
-
readonly value: T;
|
|
25
|
-
} | {
|
|
26
|
-
readonly success: false;
|
|
27
|
-
readonly issues?: readonly VehicleSchemaIssue[];
|
|
28
|
-
};
|
|
29
|
-
/**
|
|
30
|
-
* A serializable, descriptive `jsonSchema` (surfaced to a client or Pi tool
|
|
31
|
-
* projection) paired with a real `safeParse` that actually enforces it at
|
|
32
|
-
* runtime -- a Vehicle registry's own `invoke()` only ever calls
|
|
33
|
-
* `safeParse`; `jsonSchema` alone is never itself enforced, so a codec that
|
|
34
|
-
* only sets `jsonSchema` without a matching `safeParse` is a documentation
|
|
35
|
-
* gesture, not an honest contract.
|
|
36
|
-
*/
|
|
37
|
-
export interface VehicleSchemaCodec<T> {
|
|
38
|
-
readonly jsonSchema: JsonSchema;
|
|
39
|
-
safeParse(value: unknown): VehicleSchemaResult<T>;
|
|
40
|
-
}
|
|
41
|
-
export declare function defineVehicleSchema<T>(codec: VehicleSchemaCodec<T>): VehicleSchemaCodec<T>;
|
|
42
|
-
export interface LooseObjectProperty {
|
|
43
|
-
readonly type: string;
|
|
44
|
-
readonly enum?: readonly string[];
|
|
45
|
-
}
|
|
46
|
-
/**
|
|
47
|
-
* A VehicleRegistry only ever calls a schema's own safeParse -- jsonSchema is
|
|
48
|
-
* descriptive metadata surfaced to a client/Pi projection, never itself
|
|
49
|
-
* enforced at runtime -- so a declared `type`/`enum`/`additionalProperties: false`
|
|
50
|
-
* has to be checked here for real, or it's a documentation gesture, not an
|
|
51
|
-
* honest contract (the exact drift this function's own jsonSchema metadata
|
|
52
|
-
* had before: it always advertised `additionalProperties: false` and a
|
|
53
|
-
* per-property `type`, while safeParse only ever checked `required` and
|
|
54
|
-
* `enum`). Every consumer projecting a plain-object input onto a
|
|
55
|
-
* VehicleOperation needs the same required/type/extra-key/enum checks; this
|
|
56
|
-
* is that check written once.
|
|
57
|
-
*/
|
|
58
|
-
export declare function defineLooseObjectSchema(properties: Record<string, LooseObjectProperty>, required?: readonly string[]): VehicleSchemaCodec<Record<string, unknown>>;
|
|
59
|
-
/** Accepts any value unvalidated -- for an operation whose output shape isn't worth a dedicated schema (an internal/low-stakes result, or one already validated upstream by the domain logic it wraps). */
|
|
60
|
-
export declare const passthroughVehicleSchema: VehicleSchemaCodec<unknown>;
|
|
61
|
-
/**
|
|
62
|
-
* A block of narrative text meant to be read by the model, not parsed as
|
|
63
|
-
* data -- same field name and shape MCP's own CallToolResult.content and
|
|
64
|
-
* Pi's own ToolDefinition.execute() return already use, so a Vehicle
|
|
65
|
-
* operation adopting this needs no translation layer at either boundary.
|
|
66
|
-
* Only the "text" variant exists here; there's no Vehicle use case yet for
|
|
67
|
-
* MCP's image/audio/resource-link block kinds.
|
|
68
|
-
*/
|
|
69
|
-
export interface VehicleContentBlock {
|
|
70
|
-
readonly type: "text";
|
|
71
|
-
readonly text: string;
|
|
72
|
-
}
|
|
73
|
-
/**
|
|
74
|
-
* An operation's Output type can intersect this to carry its own
|
|
75
|
-
* model-facing narrative alongside its structured data, e.g.
|
|
76
|
-
* `type RunOutput = { runId: string; created: Task[] } & WithVehicleContent`.
|
|
77
|
-
* The operation itself builds `content` since it's the only code that
|
|
78
|
-
* actually knows how to describe what it computed -- never a per-consumer
|
|
79
|
-
* override bolted on wherever the operation happens to get registered.
|
|
80
|
-
*/
|
|
81
|
-
export interface WithVehicleContent {
|
|
82
|
-
readonly content?: readonly VehicleContentBlock[];
|
|
83
|
-
}
|
|
84
|
-
/**
|
|
85
|
-
* Reads an operation's own `content` blocks off its output when present and
|
|
86
|
-
* well-formed, so a generic Vehicle client can prefer them over dumping raw
|
|
87
|
-
* JSON at the model -- without knowing anything about the operation's own
|
|
88
|
-
* domain shape. Returns undefined for a malformed or absent `content` field;
|
|
89
|
-
* the caller falls back to its own default (formatted JSON) rather than
|
|
90
|
-
* risk forwarding partial/garbled blocks.
|
|
91
|
-
*/
|
|
92
|
-
export declare function extractVehicleContent(output: unknown): readonly VehicleContentBlock[] | undefined;
|
|
93
|
-
/** Canonical list VehicleEffect is itself derived from, so a runtime discriminator check (e.g. a persisted/wire VehicleApprovalRequest's own `effect` field) has one real source to check against instead of a second, driftable hardcoded list. */
|
|
94
|
-
export declare const VEHICLE_EFFECTS: readonly ["read", "local-write", "external-write", "destructive", "open-world"];
|
|
95
|
-
export type VehicleEffect = (typeof VEHICLE_EFFECTS)[number];
|
|
96
|
-
export type VehicleIdempotency = {
|
|
97
|
-
readonly mode: "safe";
|
|
98
|
-
} | {
|
|
99
|
-
readonly mode: "keyed";
|
|
100
|
-
readonly retentionMs: number;
|
|
101
|
-
} | {
|
|
102
|
-
readonly mode: "unsafe";
|
|
103
|
-
};
|
|
104
|
-
export interface VehicleLimits {
|
|
105
|
-
readonly defaultTimeoutMs: number;
|
|
106
|
-
readonly maxTimeoutMs: number;
|
|
107
|
-
readonly maxRequestBytes: number;
|
|
108
|
-
readonly maxResponseBytes: number;
|
|
109
|
-
}
|
|
110
|
-
/** One structured, documented failure mode a {@link VehicleOperationDescriptor} declares up front -- part of the operation's own serializable contract, not an ad hoc thrown Error a caller has to reverse-engineer from a message string. */
|
|
111
|
-
export interface VehicleFailureDescriptor {
|
|
112
|
-
readonly code: string;
|
|
113
|
-
readonly description: string;
|
|
114
|
-
}
|
|
115
|
-
/** Declares an operation safe to run as a Vehicle Job (detached, polled/tailed/canceled by id). Absent means live-invoke only. */
|
|
116
|
-
export interface VehicleBackgroundCapability {
|
|
117
|
-
readonly supported: true;
|
|
118
|
-
readonly defaultWakeBudget: VehicleJobWakeBudget;
|
|
119
|
-
readonly maxWakeBudget: VehicleJobWakeBudget;
|
|
120
|
-
}
|
|
121
|
-
/**
|
|
122
|
-
* The serializable half of a Vehicle operation -- name, version, schemas,
|
|
123
|
-
* ownership-implying permissions, effect classification, idempotency,
|
|
124
|
-
* streaming/long-running capability, request/response limits, and declared
|
|
125
|
-
* {@link VehicleFailureDescriptor} failure modes. Kept separate from the
|
|
126
|
-
* executable {@link VehicleOperationHandler} on purpose: a manifest, a Pi
|
|
127
|
-
* tool projection, or a client's own capability check can all inspect this
|
|
128
|
-
* shape without ever touching (or needing to trust) the implementation
|
|
129
|
-
* behind it.
|
|
130
|
-
*/
|
|
131
|
-
export interface VehicleOperationDescriptor {
|
|
132
|
-
readonly name: string;
|
|
133
|
-
readonly version: number;
|
|
134
|
-
readonly description: string;
|
|
135
|
-
readonly inputSchema: JsonSchema;
|
|
136
|
-
readonly outputSchema: JsonSchema;
|
|
137
|
-
readonly permissions: readonly string[];
|
|
138
|
-
readonly effect: VehicleEffect;
|
|
139
|
-
readonly idempotency: VehicleIdempotency;
|
|
140
|
-
readonly streaming: boolean;
|
|
141
|
-
readonly longRunning: boolean;
|
|
142
|
-
readonly limits: VehicleLimits;
|
|
143
|
-
readonly errors: readonly VehicleFailureDescriptor[];
|
|
144
|
-
readonly background?: VehicleBackgroundCapability;
|
|
145
|
-
/**
|
|
146
|
-
* Owner-declared override for whether this specific operation is ever a candidate for
|
|
147
|
-
* approval gating, independent of its `effect`. Undefined (the default) means "derive it
|
|
148
|
-
* from `effect` against the registry's own requireApprovalForEffects set instead" --
|
|
149
|
-
* VehicleRegistry.manifest()'s own resolution rule, unchanged for every existing
|
|
150
|
-
* operation that never sets this.
|
|
151
|
-
*
|
|
152
|
-
* Exists because VehicleEffect's five values are coarse enough that two operations a
|
|
153
|
-
* real owner classifies very differently (e.g. "restart an already-installed,
|
|
154
|
-
* already-vetted service" vs. "sync a read-only catalog mirror") can land in the same
|
|
155
|
-
* effect bucket (both external-write) -- no single requireApprovalForEffects set can
|
|
156
|
-
* gate one without also gating the other. The owner who registers the operation knows
|
|
157
|
-
* its real risk far better than a 5-value enum can; this lets them say so directly.
|
|
158
|
-
*/
|
|
159
|
-
readonly requiresApproval?: boolean;
|
|
160
|
-
}
|
|
161
|
-
export interface VehicleOperation<Input, Output> {
|
|
162
|
-
readonly descriptor: VehicleOperationDescriptor;
|
|
163
|
-
readonly input: VehicleSchemaCodec<Input>;
|
|
164
|
-
readonly output: VehicleSchemaCodec<Output>;
|
|
165
|
-
}
|
|
166
|
-
export interface DefineVehicleOperationOptions<Input, Output> {
|
|
167
|
-
readonly name: string;
|
|
168
|
-
readonly version: number;
|
|
169
|
-
readonly description: string;
|
|
170
|
-
readonly input: VehicleSchemaCodec<Input>;
|
|
171
|
-
readonly output: VehicleSchemaCodec<Output>;
|
|
172
|
-
readonly permissions?: readonly string[];
|
|
173
|
-
readonly effect: VehicleEffect;
|
|
174
|
-
readonly idempotency: VehicleIdempotency;
|
|
175
|
-
readonly streaming?: boolean;
|
|
176
|
-
readonly longRunning?: boolean;
|
|
177
|
-
readonly limits: VehicleLimits;
|
|
178
|
-
readonly errors?: readonly VehicleFailureDescriptor[];
|
|
179
|
-
readonly background?: VehicleBackgroundCapability;
|
|
180
|
-
/** See {@link VehicleOperationDescriptor.requiresApproval}. */
|
|
181
|
-
readonly requiresApproval?: boolean;
|
|
182
|
-
}
|
|
183
|
-
export interface VehiclePrincipal {
|
|
184
|
-
readonly id: string;
|
|
185
|
-
readonly claims?: Readonly<Record<string, JsonValue>>;
|
|
186
|
-
}
|
|
187
|
-
/**
|
|
188
|
-
* callerSessionId/callerProjectRoot identify the real host session (e.g. one Pi TUI process) that
|
|
189
|
-
* originated this call, and its working directory at call time -- a generic ownership/attribution
|
|
190
|
-
* hook any operation handler can read (e.g. scoping a background subscription to the session or
|
|
191
|
-
* project that created it), distinct from both:
|
|
192
|
-
* - correlationId: a caller-CHOSEN id deliberately meant to span several separate invoke() calls
|
|
193
|
-
* (a batch/business-transaction id), not an automatically-derived caller identity.
|
|
194
|
-
* - principal: broader identity/claims used for permission and approval decisions, usually a
|
|
195
|
-
* fixed per-extension value (e.g. {id: "pi-pipes"}), not a distinguishing per-session id.
|
|
196
|
-
* A Pi projection layer (see vehicle-client-pi's invokeVehicleOperation) auto-derives both from
|
|
197
|
-
* context.sessionManager.getSessionId()/context.cwd on every call, the same way it already
|
|
198
|
-
* auto-derives correlationId -- a handler that never reads them pays nothing extra.
|
|
199
|
-
*/
|
|
200
|
-
export interface VehicleInvocationOptions {
|
|
201
|
-
readonly operationId?: string;
|
|
202
|
-
readonly correlationId?: string;
|
|
203
|
-
readonly callerSessionId?: string;
|
|
204
|
-
readonly callerProjectRoot?: string;
|
|
205
|
-
readonly signal?: AbortSignal;
|
|
206
|
-
readonly deadline?: number;
|
|
207
|
-
readonly permissions?: readonly string[];
|
|
208
|
-
readonly principal?: VehiclePrincipal;
|
|
209
|
-
readonly idempotencyKey?: string;
|
|
210
|
-
readonly expectedRevision?: string | number;
|
|
211
|
-
readonly approvalCapability?: string;
|
|
212
|
-
readonly onProgress?: (progress: unknown) => void;
|
|
213
|
-
}
|
|
214
|
-
export interface VehicleOperationContext<Input> {
|
|
215
|
-
readonly input: Input;
|
|
216
|
-
readonly operationId: string;
|
|
217
|
-
readonly correlationId?: string;
|
|
218
|
-
/** See VehicleInvocationOptions's own doc comment. */
|
|
219
|
-
readonly callerSessionId?: string;
|
|
220
|
-
/** See VehicleInvocationOptions's own doc comment. */
|
|
221
|
-
readonly callerProjectRoot?: string;
|
|
222
|
-
readonly signal: AbortSignal;
|
|
223
|
-
readonly deadline: number;
|
|
224
|
-
readonly permissions: readonly string[];
|
|
225
|
-
readonly principal?: VehiclePrincipal;
|
|
226
|
-
readonly idempotencyKey?: string;
|
|
227
|
-
readonly expectedRevision?: string | number;
|
|
228
|
-
readonly approvalCapability?: string;
|
|
229
|
-
/** Set only for a job execution (VehicleJobStore.submit()); undefined for a plain invoke(). A handler that wants mid-flight input opts in with `for await (const input of context.steerInputs ?? [])`. */
|
|
230
|
-
readonly steerInputs?: AsyncIterable<unknown>;
|
|
231
|
-
reportProgress(progress: unknown): void;
|
|
232
|
-
}
|
|
233
|
-
export type VehicleOperationHandler<Input, Output> = (context: VehicleOperationContext<Input>) => Promise<Output>;
|
|
234
|
-
export interface VehicleOperationBinding<Input, Output> {
|
|
235
|
-
readonly operation: VehicleOperation<Input, Output>;
|
|
236
|
-
bind(): VehicleOperationHandler<Input, Output>;
|
|
237
|
-
}
|
|
238
|
-
export interface VehicleManifestIdentity {
|
|
239
|
-
readonly name: string;
|
|
240
|
-
readonly version: string;
|
|
241
|
-
readonly description: string;
|
|
242
|
-
readonly guidance?: readonly string[];
|
|
243
|
-
}
|
|
244
|
-
/**
|
|
245
|
-
* A manifest's own view of an operation: the static descriptor plus
|
|
246
|
-
* whether it's currently usable on this particular server instance right
|
|
247
|
-
* now. Availability is a runtime property of a live registry (a
|
|
248
|
-
* credential got configured or removed), never baked into the static
|
|
249
|
-
* descriptor defineVehicleOperation() produces -- two manifest() calls
|
|
250
|
-
* against the same registry can report different availability for the
|
|
251
|
-
* exact same descriptor.
|
|
252
|
-
*/
|
|
253
|
-
export interface VehicleManifestOperation extends VehicleOperationDescriptor {
|
|
254
|
-
readonly available: boolean;
|
|
255
|
-
readonly unavailableReason?: string;
|
|
256
|
-
/**
|
|
257
|
-
* The registry's own, live, fully-resolved answer to "does invoking this operation right
|
|
258
|
-
* now require approval" -- accounts for the registry's current approval policy being
|
|
259
|
-
* enabled/disabled, this operation's own `requiresApproval` override when set, and the
|
|
260
|
-
* effect-derived default otherwise. A real VehicleRegistry.manifest() always sets this
|
|
261
|
-
* (false when the registry never called configureApprovals() at all) -- unlike
|
|
262
|
-
* `requiresApproval` (the static, author-declared override on the descriptor itself),
|
|
263
|
-
* this always reflects the current instant, so a client re-fetching the manifest after a
|
|
264
|
-
* live policy change (VehicleRegistry.updateApprovalPolicy) sees the new answer with no
|
|
265
|
-
* separate sync mechanism needed.
|
|
266
|
-
*
|
|
267
|
-
* Optional purely for backward compatibility with every hand-authored VehicleManifest
|
|
268
|
-
* test fixture across the ecosystem that predates this field (the same reason
|
|
269
|
-
* VehicleManifest.events is optional) -- a consumer reading it should treat undefined the
|
|
270
|
-
* same as a caller of classifyVehicleOperationSafety does: fall back to the effect-level
|
|
271
|
-
* default, never assume false.
|
|
272
|
-
*/
|
|
273
|
-
readonly approvalRequired?: boolean;
|
|
274
|
-
}
|
|
275
|
-
/**
|
|
276
|
-
* A named, schema'd event type a provider declares as part of its
|
|
277
|
-
* manifest -- the typed alternative to a raw PushChannel.publish(topic,
|
|
278
|
-
* payload) call with a hand-invented topic string. Confirmed independently
|
|
279
|
-
* reinvented three-plus times across Papyrus and Lector before this
|
|
280
|
-
* existed (see this task's own body). No `available` flag the way an
|
|
281
|
-
* operation has one: an event type, once declared, is always emittable --
|
|
282
|
-
* there's no credential-gated "this event is currently unavailable"
|
|
283
|
-
* concept the way a live external-service-backed operation can have.
|
|
284
|
-
*/
|
|
285
|
-
export interface VehicleEventDescriptor {
|
|
286
|
-
readonly name: string;
|
|
287
|
-
readonly version: number;
|
|
288
|
-
readonly description: string;
|
|
289
|
-
readonly payloadSchema: JsonSchema;
|
|
290
|
-
/** Same bounded-resource discipline as an operation's own maxRequestBytes/maxResponseBytes -- required, never silently defaulted. */
|
|
291
|
-
readonly maxPayloadBytes: number;
|
|
292
|
-
}
|
|
293
|
-
export interface VehicleEvent<Payload> {
|
|
294
|
-
readonly descriptor: VehicleEventDescriptor;
|
|
295
|
-
readonly payload: VehicleSchemaCodec<Payload>;
|
|
296
|
-
}
|
|
297
|
-
export interface DefineVehicleEventOptions<Payload> {
|
|
298
|
-
readonly name: string;
|
|
299
|
-
readonly version: number;
|
|
300
|
-
readonly description: string;
|
|
301
|
-
readonly payload: VehicleSchemaCodec<Payload>;
|
|
302
|
-
readonly maxPayloadBytes: number;
|
|
303
|
-
}
|
|
304
|
-
export declare function defineVehicleEvent<Payload>(options: DefineVehicleEventOptions<Payload>): VehicleEvent<Payload>;
|
|
305
|
-
export type VehicleManifestEvent = VehicleEventDescriptor;
|
|
306
|
-
export type VehicleEventHandler<Payload> = (payload: Payload) => void;
|
|
307
|
-
export interface VehicleSubscription {
|
|
308
|
-
close(): void;
|
|
309
|
-
}
|
|
310
|
-
/**
|
|
311
|
-
* The wire topic name a bridge (bridgeVehicleEventsToPushChannel, in
|
|
312
|
-
* vehicle-server) publishes an event under, and a subscriber
|
|
313
|
-
* (RemoteVehicleClient.subscribe()) subscribes to -- one shared naming
|
|
314
|
-
* function in vehicle-core so both sides can never drift apart on the
|
|
315
|
-
* convention, the same failure mode this primitive exists to prevent
|
|
316
|
-
* providers from reinventing per-project.
|
|
317
|
-
*/
|
|
318
|
-
export declare function vehicleEventTopic(name: string, version: number): string;
|
|
319
|
-
/**
|
|
320
|
-
* `events` is optional purely for backward compatibility with every
|
|
321
|
-
* hand-authored VehicleManifest test fixture across the ecosystem that
|
|
322
|
-
* predates this field -- a real VehicleRegistry.manifest() always
|
|
323
|
-
* populates it (as [] when no events are declared), never omits it.
|
|
324
|
-
*/
|
|
325
|
-
export interface VehicleManifest extends VehicleManifestIdentity {
|
|
326
|
-
readonly operations: readonly VehicleManifestOperation[];
|
|
327
|
-
readonly events?: readonly VehicleManifestEvent[];
|
|
328
|
-
}
|
|
329
|
-
export interface VehicleClient {
|
|
330
|
-
manifest(): Promise<VehicleManifest>;
|
|
331
|
-
invoke<Output = unknown>(name: string, version: number, input: unknown, options?: VehicleInvocationOptions): Promise<Output>;
|
|
332
|
-
close(): Promise<void>;
|
|
333
|
-
/**
|
|
334
|
-
* Vehicle Jobs -- submit a background-capable operation (one whose descriptor declares
|
|
335
|
-
* `background`, see {@link VehicleBackgroundCapability}) and get its jobId back immediately,
|
|
336
|
-
* without waiting for the operation itself to make any progress. Optional: a client that
|
|
337
|
-
* never talks to a job-capable Vehicle (or a hand-rolled test double) simply omits these five
|
|
338
|
-
* methods, exactly like this interface's own long-standing `subscribe()`-shaped extras --
|
|
339
|
-
* present on both LocalVehicleClient and RemoteVehicleClient, absent elsewhere. Feature-detect
|
|
340
|
-
* via the operation's own manifest `background` capability, not by probing for these methods.
|
|
341
|
-
*/
|
|
342
|
-
submitJob?(name: string, version: number, input: unknown, options?: VehicleJobSubmitOptions): Promise<VehicleJobSubmitResult>;
|
|
343
|
-
/** Never blocks -- current status, plus output/error once terminal. */
|
|
344
|
-
pollJob?(jobId: string): Promise<VehicleJobSnapshot>;
|
|
345
|
-
/** Progress entries strictly after `cursor` (0 for everything so far), plus the next cursor. Never blocks. */
|
|
346
|
-
tailJob?(jobId: string, cursor?: number): Promise<VehicleJobTailResult>;
|
|
347
|
-
/** Pushes new input to an already-running job's handler, if it opted in via context.steerInputs. */
|
|
348
|
-
steerJob?(jobId: string, input: unknown): Promise<void>;
|
|
349
|
-
/** Best-effort cancellation of a still-running job -- a no-op against an already-terminal one. */
|
|
350
|
-
cancelJob?(jobId: string): Promise<void>;
|
|
351
|
-
}
|
|
352
|
-
export declare function defineVehicleOperation<Input, Output>(options: DefineVehicleOperationOptions<Input, Output>): VehicleOperation<Input, Output>;
|
|
353
|
-
export declare function bindVehicleOperation<Input, Output>(operation: VehicleOperation<Input, Output>, bind: () => VehicleOperationHandler<Input, Output>): VehicleOperationBinding<Input, Output>;
|