@danypops/vehicle-core 0.14.0 → 0.16.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.
@@ -1,4 +1,4 @@
1
- import type { VehicleJobWakeBudget } from "./vehicle-jobs.js";
1
+ import type { VehicleJobSnapshot, VehicleJobSubmitOptions, VehicleJobSubmitResult, VehicleJobTailResult, VehicleJobWakeBudget } from "./vehicle-jobs.js";
2
2
  export type JsonPrimitive = string | number | boolean | null;
3
3
  export type JsonValue = JsonPrimitive | readonly JsonValue[] | {
4
4
  readonly [key: string]: JsonValue;
@@ -178,9 +178,24 @@ export interface VehiclePrincipal {
178
178
  readonly id: string;
179
179
  readonly claims?: Readonly<Record<string, JsonValue>>;
180
180
  }
181
+ /**
182
+ * callerSessionId/callerProjectRoot identify the real host session (e.g. one Pi TUI process) that
183
+ * originated this call, and its working directory at call time -- a generic ownership/attribution
184
+ * hook any operation handler can read (e.g. scoping a background subscription to the session or
185
+ * project that created it), distinct from both:
186
+ * - correlationId: a caller-CHOSEN id deliberately meant to span several separate invoke() calls
187
+ * (a batch/business-transaction id), not an automatically-derived caller identity.
188
+ * - principal: broader identity/claims used for permission and approval decisions, usually a
189
+ * fixed per-extension value (e.g. {id: "pi-pipes"}), not a distinguishing per-session id.
190
+ * A Pi projection layer (see vehicle-client-pi's invokeVehicleOperation) auto-derives both from
191
+ * context.sessionManager.getSessionId()/context.cwd on every call, the same way it already
192
+ * auto-derives correlationId -- a handler that never reads them pays nothing extra.
193
+ */
181
194
  export interface VehicleInvocationOptions {
182
195
  readonly operationId?: string;
183
196
  readonly correlationId?: string;
197
+ readonly callerSessionId?: string;
198
+ readonly callerProjectRoot?: string;
184
199
  readonly signal?: AbortSignal;
185
200
  readonly deadline?: number;
186
201
  readonly permissions?: readonly string[];
@@ -194,6 +209,10 @@ export interface VehicleOperationContext<Input> {
194
209
  readonly input: Input;
195
210
  readonly operationId: string;
196
211
  readonly correlationId?: string;
212
+ /** See VehicleInvocationOptions's own doc comment. */
213
+ readonly callerSessionId?: string;
214
+ /** See VehicleInvocationOptions's own doc comment. */
215
+ readonly callerProjectRoot?: string;
197
216
  readonly signal: AbortSignal;
198
217
  readonly deadline: number;
199
218
  readonly permissions: readonly string[];
@@ -305,6 +324,24 @@ export interface VehicleClient {
305
324
  manifest(): Promise<VehicleManifest>;
306
325
  invoke<Output = unknown>(name: string, version: number, input: unknown, options?: VehicleInvocationOptions): Promise<Output>;
307
326
  close(): Promise<void>;
327
+ /**
328
+ * Vehicle Jobs -- submit a background-capable operation (one whose descriptor declares
329
+ * `background`, see {@link VehicleBackgroundCapability}) and get its jobId back immediately,
330
+ * without waiting for the operation itself to make any progress. Optional: a client that
331
+ * never talks to a job-capable Vehicle (or a hand-rolled test double) simply omits these five
332
+ * methods, exactly like this interface's own long-standing `subscribe()`-shaped extras --
333
+ * present on both LocalVehicleClient and RemoteVehicleClient, absent elsewhere. Feature-detect
334
+ * via the operation's own manifest `background` capability, not by probing for these methods.
335
+ */
336
+ submitJob?(name: string, version: number, input: unknown, options?: VehicleJobSubmitOptions): Promise<VehicleJobSubmitResult>;
337
+ /** Never blocks -- current status, plus output/error once terminal. */
338
+ pollJob?(jobId: string): Promise<VehicleJobSnapshot>;
339
+ /** Progress entries strictly after `cursor` (0 for everything so far), plus the next cursor. Never blocks. */
340
+ tailJob?(jobId: string, cursor?: number): Promise<VehicleJobTailResult>;
341
+ /** Pushes new input to an already-running job's handler, if it opted in via context.steerInputs. */
342
+ steerJob?(jobId: string, input: unknown): Promise<void>;
343
+ /** Best-effort cancellation of a still-running job -- a no-op against an already-terminal one. */
344
+ cancelJob?(jobId: string): Promise<void>;
308
345
  }
309
346
  export declare function defineVehicleOperation<Input, Output>(options: DefineVehicleOperationOptions<Input, Output>): VehicleOperation<Input, Output>;
310
347
  export declare function bindVehicleOperation<Input, Output>(operation: VehicleOperation<Input, Output>, bind: () => VehicleOperationHandler<Input, Output>): VehicleOperationBinding<Input, Output>;
@@ -1,4 +1,6 @@
1
1
  /** Pure pieces of Vehicle Jobs: a termination-reason resolver and a bounded wake-log accumulator. Orchestration lives in vehicle-server's VehicleJobStore. */
2
+ import type { VehiclePrincipal } from "./vehicle-contract.js";
3
+ import type { VehicleFailure } from "./vehicle-errors.js";
2
4
  export type VehicleJobStatus = "running" | "succeeded" | "failed" | "canceled";
3
5
  /** Highest precedence first -- an explicit cancel always wins even if the handler also settled around the same time. "orphaned" is a restart-reconciliation outcome: a job that was still "running" when its process died, so nothing ever really failed or succeeded -- the record's own status just goes stale. */
4
6
  export declare const VEHICLE_JOB_TERMINATION_PRECEDENCE: readonly ["canceled", "timeout", "orphaned", "failed", "succeeded"];
@@ -101,6 +103,49 @@ export interface VehicleJobRetentionOptions {
101
103
  readonly deliveredRetentionMs: number;
102
104
  readonly now: number;
103
105
  }
106
+ /**
107
+ * The client-facing wire shapes for Vehicle Jobs -- submit/poll/tail options and results, shared by
108
+ * vehicle-server's VehicleJobStore (the orchestration side) and vehicle-client's job-capable clients
109
+ * (the calling side), so both halves of the wire agree on one definition instead of two structurally
110
+ *-identical copies drifting apart. Every field type referenced here already lives in vehicle-core
111
+ * (VehiclePrincipal, VehicleFailure, VehicleJobStatus, ...), which is what makes it safe for these
112
+ * shapes to live here too, alongside the rest of Vehicle Jobs' pure pieces.
113
+ */
114
+ export interface VehicleJobSubmitOptions {
115
+ readonly permissions?: readonly string[];
116
+ readonly principal?: VehiclePrincipal;
117
+ readonly idempotencyKey?: string;
118
+ readonly expectedRevision?: string | number;
119
+ readonly approvalCapability?: string;
120
+ readonly correlationId?: string;
121
+ readonly callerSessionId?: string;
122
+ readonly callerProjectRoot?: string;
123
+ /** Defaults to "transition". */
124
+ readonly notifyMode?: VehicleJobNotifyMode;
125
+ /** Defaults to background.defaultWakeBudget; clamped to background.maxWakeBudget either way. */
126
+ readonly wakeBudget?: VehicleJobWakeBudget;
127
+ /** No default -- unset means the job runs until it settles or is canceled. */
128
+ readonly maxLifetimeMs?: number;
129
+ }
130
+ export interface VehicleJobSubmitResult {
131
+ readonly jobId: string;
132
+ }
133
+ export interface VehicleJobSnapshot {
134
+ readonly jobId: string;
135
+ readonly operationName: string;
136
+ readonly operationVersion: number;
137
+ readonly status: VehicleJobStatus;
138
+ readonly createdAt: number;
139
+ readonly updatedAt: number;
140
+ readonly delivered: boolean;
141
+ readonly terminationReason?: VehicleJobTerminationReason;
142
+ readonly output?: unknown;
143
+ readonly error?: VehicleFailure;
144
+ }
145
+ export interface VehicleJobTailResult {
146
+ readonly entries: readonly VehicleJobWakeEntry[];
147
+ readonly cursor: number;
148
+ }
104
149
  /**
105
150
  * Pure eviction-selection policy, kept separate from VehicleJobStore's own
106
151
  * bookkeeping so the bounded-retention rule is independently testable.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danypops/vehicle-core",
3
- "version": "0.14.0",
3
+ "version": "0.16.0",
4
4
  "description": "Vehicle's runtime-neutral wire contract: operation descriptors, schema codecs, failure shapes. Zero runtime dependencies, zero Bun-specific code -- the one thing every Vehicle client and server package depends on.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -1,4 +1,10 @@
1
- import type { VehicleJobWakeBudget } from "./vehicle-jobs.js";
1
+ import type {
2
+ VehicleJobSnapshot,
3
+ VehicleJobSubmitOptions,
4
+ VehicleJobSubmitResult,
5
+ VehicleJobTailResult,
6
+ VehicleJobWakeBudget,
7
+ } from "./vehicle-jobs.js";
2
8
 
3
9
  export type JsonPrimitive = string | number | boolean | null;
4
10
  export type JsonValue = JsonPrimitive | readonly JsonValue[] | { readonly [key: string]: JsonValue };
@@ -254,9 +260,24 @@ export interface VehiclePrincipal {
254
260
  readonly claims?: Readonly<Record<string, JsonValue>>;
255
261
  }
256
262
 
263
+ /**
264
+ * callerSessionId/callerProjectRoot identify the real host session (e.g. one Pi TUI process) that
265
+ * originated this call, and its working directory at call time -- a generic ownership/attribution
266
+ * hook any operation handler can read (e.g. scoping a background subscription to the session or
267
+ * project that created it), distinct from both:
268
+ * - correlationId: a caller-CHOSEN id deliberately meant to span several separate invoke() calls
269
+ * (a batch/business-transaction id), not an automatically-derived caller identity.
270
+ * - principal: broader identity/claims used for permission and approval decisions, usually a
271
+ * fixed per-extension value (e.g. {id: "pi-pipes"}), not a distinguishing per-session id.
272
+ * A Pi projection layer (see vehicle-client-pi's invokeVehicleOperation) auto-derives both from
273
+ * context.sessionManager.getSessionId()/context.cwd on every call, the same way it already
274
+ * auto-derives correlationId -- a handler that never reads them pays nothing extra.
275
+ */
257
276
  export interface VehicleInvocationOptions {
258
277
  readonly operationId?: string;
259
278
  readonly correlationId?: string;
279
+ readonly callerSessionId?: string;
280
+ readonly callerProjectRoot?: string;
260
281
  readonly signal?: AbortSignal;
261
282
  readonly deadline?: number;
262
283
  readonly permissions?: readonly string[];
@@ -271,6 +292,10 @@ export interface VehicleOperationContext<Input> {
271
292
  readonly input: Input;
272
293
  readonly operationId: string;
273
294
  readonly correlationId?: string;
295
+ /** See VehicleInvocationOptions's own doc comment. */
296
+ readonly callerSessionId?: string;
297
+ /** See VehicleInvocationOptions's own doc comment. */
298
+ readonly callerProjectRoot?: string;
274
299
  readonly signal: AbortSignal;
275
300
  readonly deadline: number;
276
301
  readonly permissions: readonly string[];
@@ -419,6 +444,24 @@ export interface VehicleClient {
419
444
  manifest(): Promise<VehicleManifest>;
420
445
  invoke<Output = unknown>(name: string, version: number, input: unknown, options?: VehicleInvocationOptions): Promise<Output>;
421
446
  close(): Promise<void>;
447
+ /**
448
+ * Vehicle Jobs -- submit a background-capable operation (one whose descriptor declares
449
+ * `background`, see {@link VehicleBackgroundCapability}) and get its jobId back immediately,
450
+ * without waiting for the operation itself to make any progress. Optional: a client that
451
+ * never talks to a job-capable Vehicle (or a hand-rolled test double) simply omits these five
452
+ * methods, exactly like this interface's own long-standing `subscribe()`-shaped extras --
453
+ * present on both LocalVehicleClient and RemoteVehicleClient, absent elsewhere. Feature-detect
454
+ * via the operation's own manifest `background` capability, not by probing for these methods.
455
+ */
456
+ submitJob?(name: string, version: number, input: unknown, options?: VehicleJobSubmitOptions): Promise<VehicleJobSubmitResult>;
457
+ /** Never blocks -- current status, plus output/error once terminal. */
458
+ pollJob?(jobId: string): Promise<VehicleJobSnapshot>;
459
+ /** Progress entries strictly after `cursor` (0 for everything so far), plus the next cursor. Never blocks. */
460
+ tailJob?(jobId: string, cursor?: number): Promise<VehicleJobTailResult>;
461
+ /** Pushes new input to an already-running job's handler, if it opted in via context.steerInputs. */
462
+ steerJob?(jobId: string, input: unknown): Promise<void>;
463
+ /** Best-effort cancellation of a still-running job -- a no-op against an already-terminal one. */
464
+ cancelJob?(jobId: string): Promise<void>;
422
465
  }
423
466
 
424
467
  export function defineVehicleOperation<Input, Output>(
@@ -1,5 +1,8 @@
1
1
  /** Pure pieces of Vehicle Jobs: a termination-reason resolver and a bounded wake-log accumulator. Orchestration lives in vehicle-server's VehicleJobStore. */
2
2
 
3
+ import type { VehiclePrincipal } from "./vehicle-contract.js";
4
+ import type { VehicleFailure } from "./vehicle-errors.js";
5
+
3
6
  export type VehicleJobStatus = "running" | "succeeded" | "failed" | "canceled";
4
7
 
5
8
  /** Highest precedence first -- an explicit cancel always wins even if the handler also settled around the same time. "orphaned" is a restart-reconciliation outcome: a job that was still "running" when its process died, so nothing ever really failed or succeeded -- the record's own status just goes stale. */
@@ -212,6 +215,53 @@ export interface VehicleJobRetentionOptions {
212
215
  readonly now: number;
213
216
  }
214
217
 
218
+ /**
219
+ * The client-facing wire shapes for Vehicle Jobs -- submit/poll/tail options and results, shared by
220
+ * vehicle-server's VehicleJobStore (the orchestration side) and vehicle-client's job-capable clients
221
+ * (the calling side), so both halves of the wire agree on one definition instead of two structurally
222
+ *-identical copies drifting apart. Every field type referenced here already lives in vehicle-core
223
+ * (VehiclePrincipal, VehicleFailure, VehicleJobStatus, ...), which is what makes it safe for these
224
+ * shapes to live here too, alongside the rest of Vehicle Jobs' pure pieces.
225
+ */
226
+ export interface VehicleJobSubmitOptions {
227
+ readonly permissions?: readonly string[];
228
+ readonly principal?: VehiclePrincipal;
229
+ readonly idempotencyKey?: string;
230
+ readonly expectedRevision?: string | number;
231
+ readonly approvalCapability?: string;
232
+ readonly correlationId?: string;
233
+ readonly callerSessionId?: string;
234
+ readonly callerProjectRoot?: string;
235
+ /** Defaults to "transition". */
236
+ readonly notifyMode?: VehicleJobNotifyMode;
237
+ /** Defaults to background.defaultWakeBudget; clamped to background.maxWakeBudget either way. */
238
+ readonly wakeBudget?: VehicleJobWakeBudget;
239
+ /** No default -- unset means the job runs until it settles or is canceled. */
240
+ readonly maxLifetimeMs?: number;
241
+ }
242
+
243
+ export interface VehicleJobSubmitResult {
244
+ readonly jobId: string;
245
+ }
246
+
247
+ export interface VehicleJobSnapshot {
248
+ readonly jobId: string;
249
+ readonly operationName: string;
250
+ readonly operationVersion: number;
251
+ readonly status: VehicleJobStatus;
252
+ readonly createdAt: number;
253
+ readonly updatedAt: number;
254
+ readonly delivered: boolean;
255
+ readonly terminationReason?: VehicleJobTerminationReason;
256
+ readonly output?: unknown;
257
+ readonly error?: VehicleFailure;
258
+ }
259
+
260
+ export interface VehicleJobTailResult {
261
+ readonly entries: readonly VehicleJobWakeEntry[];
262
+ readonly cursor: number;
263
+ }
264
+
215
265
  /**
216
266
  * Pure eviction-selection policy, kept separate from VehicleJobStore's own
217
267
  * bookkeeping so the bounded-retention rule is independently testable.