@opengeni/sdk 0.23.0 → 0.25.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengeni/sdk",
3
- "version": "0.23.0",
3
+ "version": "0.25.0",
4
4
  "description": "Framework-agnostic TypeScript SDK for the OpenGeni API: typed client, session lifecycle, SSE event streaming with reconnect + replay-by-sequence, and proxy re-streaming helpers.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
package/src/client.ts CHANGED
@@ -1,4 +1,8 @@
1
- import { OpenGeniApiContractMismatchError, OpenGeniApiError } from "./errors";
1
+ import {
2
+ OpenGeniApiContractMismatchError,
3
+ OpenGeniApiError,
4
+ OpenGeniSessionListCursorError,
5
+ } from "./errors";
2
6
  import {
3
7
  streamSessionEvents,
4
8
  type SessionEventStreamTransport,
@@ -112,7 +116,9 @@ import type {
112
116
  DeleteSessionQueueItemRequest,
113
117
  EditSessionQueueItemRequest,
114
118
  MoveSessionQueueItemRequest,
119
+ NewSessionDraft,
115
120
  SaveComposerDraftRequest,
121
+ SaveNewSessionDraftRequest,
116
122
  SteerSessionQueueItemRequest,
117
123
  SessionControlResponse,
118
124
  WorkspaceInferenceControlResponse,
@@ -193,6 +199,17 @@ import {
193
199
  RETAINED_OUTPUT_MAX_PAGE_BYTES,
194
200
  } from "./types";
195
201
 
202
+ function sessionListQuery(options: {
203
+ limit?: number;
204
+ parentSessionId?: string | null;
205
+ }): Record<string, string> {
206
+ const { limit, parentSessionId } = options;
207
+ return {
208
+ ...(limit === undefined ? {} : { limit: String(limit) }),
209
+ ...(parentSessionId === undefined ? {} : { parentSessionId: parentSessionId ?? "null" }),
210
+ };
211
+ }
212
+
196
213
  export type FetchLike = (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
197
214
 
198
215
  export type WorkspaceControlEventPage = {
@@ -269,6 +286,24 @@ export class OpenGeniClient {
269
286
  );
270
287
  }
271
288
 
289
+ async getNewSessionDraft(workspaceId: string): Promise<NewSessionDraft> {
290
+ return await this.requestJson<NewSessionDraft>(
291
+ "GET",
292
+ `/v1/workspaces/${workspaceId}/new-session-draft`,
293
+ );
294
+ }
295
+
296
+ async saveNewSessionDraft(
297
+ workspaceId: string,
298
+ request: SaveNewSessionDraftRequest,
299
+ ): Promise<NewSessionDraft> {
300
+ return await this.requestJson<NewSessionDraft>(
301
+ "PUT",
302
+ `/v1/workspaces/${workspaceId}/new-session-draft`,
303
+ request,
304
+ );
305
+ }
306
+
272
307
  async getSession(workspaceId: string, sessionId: string): Promise<Session> {
273
308
  return await this.requestJson<Session>(
274
309
  "GET",
@@ -314,21 +349,21 @@ export class OpenGeniClient {
314
349
  search?: string;
315
350
  } = {},
316
351
  ): Promise<Session[]> {
352
+ // Search was added with the pin-aware page endpoint. An older API silently
353
+ // ignores unknown query parameters on the historical array endpoint, which
354
+ // would turn a search into a plausible-looking unfiltered result. Route
355
+ // searches through listSessionPage so its rolling-version shape check can
356
+ // fail explicitly on an older server; retain the array endpoint for every
357
+ // pre-existing call shape.
358
+ if (options.search?.trim()) {
359
+ const page = await this.listSessionPage(workspaceId, options);
360
+ return [...page.pinned, ...page.sessions];
361
+ }
317
362
  return await this.requestJson<Session[]>(
318
363
  "GET",
319
364
  `/v1/workspaces/${workspaceId}/sessions`,
320
365
  undefined,
321
- {
322
- ...(options.limit !== undefined ? { limit: String(options.limit) } : {}),
323
- ...(options.search?.trim() ? { search: options.search.trim() } : {}),
324
- ...(Object.prototype.hasOwnProperty.call(options, "parentSessionId") &&
325
- options.parentSessionId !== undefined
326
- ? {
327
- parentSessionId:
328
- options.parentSessionId === null ? "null" : String(options.parentSessionId),
329
- }
330
- : {}),
331
- },
366
+ sessionListQuery(options),
332
367
  );
333
368
  }
334
369
 
@@ -340,26 +375,51 @@ export class OpenGeniClient {
340
375
  parentSessionId?: string | null;
341
376
  cursor?: string;
342
377
  search?: string;
378
+ /** Return only the complete personal pinned projection. */
379
+ pinsOnly?: boolean;
343
380
  } = {},
344
381
  ): Promise<SessionListResponse> {
345
- return await this.requestJson<SessionListResponse>(
346
- "GET",
347
- `/v1/workspaces/${workspaceId}/sessions`,
348
- undefined,
349
- {
350
- view: "page",
351
- ...(options.limit !== undefined ? { limit: String(options.limit) } : {}),
352
- ...(options.cursor !== undefined ? { cursor: options.cursor } : {}),
353
- ...(options.search?.trim() ? { search: options.search.trim() } : {}),
354
- ...(Object.prototype.hasOwnProperty.call(options, "parentSessionId") &&
355
- options.parentSessionId !== undefined
356
- ? {
357
- parentSessionId:
358
- options.parentSessionId === null ? "null" : String(options.parentSessionId),
359
- }
360
- : {}),
361
- },
362
- );
382
+ const search = options.search?.trim();
383
+ let response: SessionListResponse | Session[];
384
+ try {
385
+ response = await this.requestJson<SessionListResponse | Session[]>(
386
+ "GET",
387
+ `/v1/workspaces/${workspaceId}/sessions`,
388
+ undefined,
389
+ {
390
+ view: "page",
391
+ ...sessionListQuery(options),
392
+ ...(options.cursor !== undefined ? { cursor: options.cursor } : {}),
393
+ ...(search ? { search } : {}),
394
+ ...(options.pinsOnly ? { pinsOnly: "true" } : {}),
395
+ },
396
+ );
397
+ } catch (error) {
398
+ if (error instanceof OpenGeniApiError && error.status === 410) {
399
+ throw new OpenGeniSessionListCursorError(error.status, error.body);
400
+ }
401
+ throw error;
402
+ }
403
+ if (Array.isArray(response)) {
404
+ // Rolling/same-major compatibility: an older API ignores `view=page` and
405
+ // returns the historical array. That is an honest one-page projection;
406
+ // never pretend it honored a cursor supplied directly by a caller.
407
+ if (options.cursor) {
408
+ throw new Error("The connected OpenGeni API does not support stable session-page cursors");
409
+ }
410
+ // Older APIs ignore unknown query parameters. Treating their unfiltered
411
+ // array as a successful search would be worse than an explicit rolling-
412
+ // upgrade error (and client-side filtering cannot recover matches beyond
413
+ // the old endpoint's bounded first page).
414
+ if (search) {
415
+ throw new Error("The connected OpenGeni API does not support session search");
416
+ }
417
+ if (options.pinsOnly) {
418
+ throw new Error("The connected OpenGeni API does not support pins-only session lists");
419
+ }
420
+ return { pinned: [], sessions: response, nextCursor: null };
421
+ }
422
+ return response;
363
423
  }
364
424
 
365
425
  /** Set this authenticated member's personal workspace pin for a session. */
@@ -1919,21 +1979,27 @@ export class OpenGeniClient {
1919
1979
  * -> complete. Returns the ready `FileAsset`.
1920
1980
  */
1921
1981
  async uploadFile(workspaceId: string, input: UploadFileInput): Promise<FileAsset> {
1922
- // Copy Uint8Array views into a Blob so byte offsets/shared buffers can't
1923
- // leak surrounding bytes into the PUT body.
1982
+ // Snapshot mutable inputs before hashing so the digest always describes the
1983
+ // exact bytes later sent to object storage. Copy Uint8Array views into a
1984
+ // Blob so byte offsets/shared buffers can't leak surrounding bytes.
1924
1985
  const body: Blob | ArrayBuffer | string =
1925
- input.data instanceof Uint8Array ? new Blob([input.data.slice()]) : input.data;
1986
+ input.data instanceof Uint8Array
1987
+ ? new Blob([input.data.slice()])
1988
+ : input.data instanceof ArrayBuffer
1989
+ ? input.data.slice(0)
1990
+ : input.data;
1926
1991
  const sizeBytes =
1927
1992
  typeof body === "string"
1928
1993
  ? new TextEncoder().encode(body).byteLength
1929
1994
  : body instanceof Blob
1930
1995
  ? body.size
1931
1996
  : body.byteLength;
1997
+ const sha256 = input.sha256 ?? (await sha256ForUpload(body));
1932
1998
  const upload = await this.beginFileUpload(workspaceId, {
1933
1999
  filename: input.filename,
1934
2000
  contentType: input.contentType,
1935
2001
  sizeBytes,
1936
- ...(input.sha256 !== undefined ? { sha256: input.sha256 } : {}),
2002
+ sha256,
1937
2003
  });
1938
2004
  const putResponse = await this.fetchImpl(upload.putUrl, {
1939
2005
  method: "PUT",
@@ -2720,6 +2786,17 @@ function assertApiContractResponse(response: Response): void {
2720
2786
  }
2721
2787
  }
2722
2788
 
2789
+ async function sha256ForUpload(body: Blob | ArrayBuffer | string): Promise<string> {
2790
+ const bytes =
2791
+ typeof body === "string"
2792
+ ? new TextEncoder().encode(body)
2793
+ : body instanceof Blob
2794
+ ? new Uint8Array(await body.arrayBuffer())
2795
+ : new Uint8Array(body);
2796
+ const digest = await globalThis.crypto.subtle.digest("SHA-256", bytes);
2797
+ return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
2798
+ }
2799
+
2723
2800
  async function safeText(response: Response): Promise<string> {
2724
2801
  try {
2725
2802
  return await response.text();
package/src/errors.ts CHANGED
@@ -1,16 +1,37 @@
1
1
  /** Error for a non-2xx OpenGeni API response. */
2
2
  export class OpenGeniApiError extends Error {
3
3
  readonly status: number;
4
+ readonly code: string | undefined;
4
5
  readonly body: string;
5
6
 
6
7
  constructor(status: number, body: string) {
7
- super(`OpenGeni API ${status}: ${body || "(empty body)"}`);
8
+ const decoded = decodeApiErrorBody(body);
9
+ super(`OpenGeni API ${status}: ${decoded.message ?? (body || "(empty body)")}`);
8
10
  this.name = "OpenGeniApiError";
9
11
  this.status = status;
12
+ this.code = decoded.code;
10
13
  this.body = body;
11
14
  }
12
15
  }
13
16
 
17
+ function decodeApiErrorBody(body: string): { code?: string; message?: string } {
18
+ if (!body) return {};
19
+ try {
20
+ const decoded: unknown = JSON.parse(body);
21
+ if (!decoded || typeof decoded !== "object" || Array.isArray(decoded)) return {};
22
+ const record = decoded as Record<string, unknown>;
23
+ return {
24
+ ...(typeof record.code === "string" && record.code ? { code: record.code } : {}),
25
+ ...(typeof record.message === "string" && record.message ? { message: record.message } : {}),
26
+ };
27
+ } catch {
28
+ return {};
29
+ }
30
+ }
31
+
32
+ /** A short-lived session-list snapshot cursor can no longer be continued. */
33
+ export class OpenGeniSessionListCursorError extends OpenGeniApiError {}
34
+
14
35
  /** The browser bundle and API disagree about their state-changing wire contract. */
15
36
  export class OpenGeniApiContractMismatchError extends Error {
16
37
  readonly expected: string;
package/src/index.ts CHANGED
@@ -10,6 +10,7 @@ export type {
10
10
  export {
11
11
  OpenGeniApiContractMismatchError,
12
12
  OpenGeniApiError,
13
+ OpenGeniSessionListCursorError,
13
14
  OpenGeniStreamError,
14
15
  isRetryableStreamError,
15
16
  } from "./errors";
@@ -142,6 +143,11 @@ export type {
142
143
  CodexConnectionStatus,
143
144
  CodexConnectStart,
144
145
  CodexConnectPoll,
146
+ CodexFleetConfidence,
147
+ CodexFleetCacheState,
148
+ CodexFleetDecisionEventPayload,
149
+ CodexFleetDecisionScore,
150
+ CodexFleetShadowComparison,
145
151
  CodexOverviewResponse,
146
152
  CodexResetCredit,
147
153
  CodexResetRedemptionRecovery,
@@ -276,7 +282,10 @@ export type {
276
282
  EffectiveControlResumeOption,
277
283
  EffectiveSessionControl,
278
284
  MoveSessionQueueItemRequest,
285
+ NewSessionDraft,
286
+ NewSessionDraftOptions,
279
287
  SaveComposerDraftRequest,
288
+ SaveNewSessionDraftRequest,
280
289
  SessionCommandReceipt,
281
290
  SteerSessionQueueItemRequest,
282
291
  WorkspaceInferenceControlResponse,
package/src/types.ts CHANGED
@@ -65,6 +65,9 @@ export type SessionCapabilities = {
65
65
  os: SandboxOs;
66
66
  liveness: "cold" | "warming" | "warm" | "draining";
67
67
  leaseEpoch: number;
68
+ workspaceGeneration: number | null;
69
+ archiveGeneration: number | null;
70
+ archiveComplete: boolean;
68
71
  viewerHeartbeatIntervalMs: number;
69
72
  FileSystem: {
70
73
  available: boolean;
@@ -182,6 +185,9 @@ export type ViewerHolder = {
182
185
  sandboxGroupId: string;
183
186
  liveness: "cold" | "warming" | "warm" | "draining";
184
187
  leaseEpoch: number;
188
+ workspaceGeneration: number | null;
189
+ archiveGeneration: number | null;
190
+ archiveComplete: boolean;
185
191
  viewerHeartbeatIntervalMs: number;
186
192
  dataPlaneUrl: string | null;
187
193
  };
@@ -487,6 +493,13 @@ export type Session = {
487
493
  firstPartyMcpPermissions: string[] | null;
488
494
  mcpServers: SessionMcpServerMetadata[];
489
495
  parentSessionId: string | null;
496
+ /** Immutable server-authored nested-agent lineage and policy snapshot. */
497
+ rootSessionId: string;
498
+ nestedAgentDepth: number;
499
+ maxNestedAgentDepthOverride: number | null;
500
+ effectiveMaxNestedAgentDepth: number;
501
+ nestedAgentDepthPolicySource: "session" | "workspace" | "deployment" | "default";
502
+ nestedAgentDepthPolicySessionId: string | null;
490
503
  createIdempotencyKey: string | null;
491
504
  temporalWorkflowId: string | null;
492
505
  activeTurnId: string | null;
@@ -753,6 +766,8 @@ export const SESSION_EVENT_TYPES = [
753
766
  "codex.account.switched",
754
767
  // credential allocator metadata-only per-turn credential selection audit.
755
768
  "codex.credential.selected",
769
+ // Bounded, identity-free deterministic shadow/replay decision.
770
+ "codex.fleet.decision",
756
771
  // credential allocator durable zero-capacity wait lifecycle. These are system/runtime
757
772
  // events, never synthetic user messages.
758
773
  "codex.capacity.waiting",
@@ -957,6 +972,89 @@ export type AgentToolCallCreatedPayload = {
957
972
  export type AgentToolCallOutputPayload = { id: string | null; output: unknown };
958
973
  export type SessionStatusChangedPayload = { status: SessionStatus };
959
974
 
975
+ // Adaptive-fleet shadow event. This is the typed, identity-free view
976
+ // consumed by UI/manager tooling; the durable replay record also contains the
977
+ // complete normalized policy/input needed for offline deterministic replay.
978
+ export type CodexFleetConfidence = "unknown" | "low" | "medium" | "high";
979
+ export type CodexFleetCacheState = "unknown" | "healthy" | "collapsed";
980
+ export type CodexFleetShadowComparison =
981
+ | "match"
982
+ | "different_candidate"
983
+ | "different_outcome"
984
+ | "not_comparable_truncated";
985
+ export type CodexFleetDecisionScore = {
986
+ candidateKey: string;
987
+ eligible: boolean;
988
+ rejectionReason:
989
+ | "allocator_disabled"
990
+ | "unavailable"
991
+ | "cooling"
992
+ | "quota_ceiling"
993
+ | "overlay_isolation"
994
+ | null;
995
+ quotaPressure: number;
996
+ leasePressure: number;
997
+ observedBurnPressure: number;
998
+ inferredBurnPressure: number;
999
+ runwayPressure: number;
1000
+ uncertaintyPressure: number;
1001
+ cacheAffinityBenefit: number;
1002
+ cacheState: CodexFleetCacheState;
1003
+ overlayPreferenceBenefit: number;
1004
+ total: number;
1005
+ confidence: CodexFleetConfidence;
1006
+ };
1007
+ export type CodexFleetDecisionEventPayload = {
1008
+ schemaVersion: 1;
1009
+ mode: "shadow";
1010
+ actual: {
1011
+ outcome: "selected" | "waiting" | "none";
1012
+ candidateKey: string | null;
1013
+ reason: "lease_reused" | "pin" | "rotation" | "active" | "all_capped" | "none";
1014
+ };
1015
+ comparison: CodexFleetShadowComparison;
1016
+ replay: {
1017
+ schemaVersion: 1;
1018
+ policyVersion: "adaptive-shadow-v1";
1019
+ mode: "shadow";
1020
+ input: { candidates: Array<{ key: string }> } & Record<string, unknown>;
1021
+ truncatedCandidateCount: number;
1022
+ inputFingerprint: string;
1023
+ decisionFingerprint: string;
1024
+ decision: {
1025
+ outcome: "selected" | "paced" | "none";
1026
+ selectedCandidateKey: string | null;
1027
+ reason:
1028
+ | "fenced_in_flight"
1029
+ | "fenced_candidate_missing"
1030
+ | "admission_paced"
1031
+ | "no_eligible_candidate"
1032
+ | "overlay_isolated_empty"
1033
+ | "best_score"
1034
+ | "affinity_best"
1035
+ | "hysteresis_hold";
1036
+ admission: {
1037
+ outcome: "admit" | "pace";
1038
+ reason:
1039
+ | "fenced_in_flight"
1040
+ | "pacing_disabled"
1041
+ | "capacity_unknown"
1042
+ | "capacity_available"
1043
+ | "work_conserving_borrow"
1044
+ | "manager_priority"
1045
+ | "standard_starvation_bound"
1046
+ | "capacity_saturated"
1047
+ | "emergency_fuse";
1048
+ borrowedIdleCapacity: boolean;
1049
+ };
1050
+ borrowedOverlayCapacity: boolean;
1051
+ strandedEligibleCount: number;
1052
+ confidence: CodexFleetConfidence;
1053
+ scores: CodexFleetDecisionScore[];
1054
+ };
1055
+ } & Record<string, unknown>;
1056
+ };
1057
+
960
1058
  // Recording payloads (P4.3 — plain TS mirror of the contracts Zod schemas; the
961
1059
  // SDK is zero-runtime-dep so these are TYPES, not Zod, F15). The contract-parity
962
1060
  // test asserts the event-type literals; these shapes document the wire payloads.
@@ -1046,7 +1144,7 @@ export type TerminalPtyOutputDeltaPayload = {
1046
1144
  export type TerminalPtyExitedPayload = {
1047
1145
  ptyId: string;
1048
1146
  exitCode: number | null;
1049
- reason: "exit" | "killed" | "owner_gone" | "timeout";
1147
+ reason: "exit" | "killed" | "owner_gone" | "timeout" | "lost";
1050
1148
  };
1051
1149
 
1052
1150
  // A2 FileSystem request/response.
@@ -1333,8 +1431,8 @@ export type TerminalExecRequest = {
1333
1431
  export type TerminalExecResponse = {
1334
1432
  stdout: string;
1335
1433
  stderr: string;
1336
- exitCode: number | null;
1337
- running: boolean;
1434
+ exitCode: number;
1435
+ running: false;
1338
1436
  wallTimeSeconds: number;
1339
1437
  };
1340
1438
  export type PtyOpenRequest = {
@@ -1398,6 +1496,7 @@ export type ScheduledTaskAgentConfig = {
1398
1496
  reasoningEffort?: ReasoningEffort | undefined;
1399
1497
  sandboxBackend?: SandboxBackend | undefined;
1400
1498
  goal?: GoalSpec | undefined;
1499
+ maxNestedAgentDepth?: number | undefined;
1401
1500
  };
1402
1501
 
1403
1502
  export type ScheduledTask = {
@@ -1459,6 +1558,10 @@ export type CreateSessionRequest = {
1459
1558
  // double-submit/retry of the same logical create collapse to one session.
1460
1559
  // Distinct from the per-call clientEventId.
1461
1560
  idempotencyKey?: string | undefined;
1561
+ // Exact actor-private pre-session draft revision represented by this create.
1562
+ // The server consumes only this revision after durable initialization.
1563
+ expectedNewSessionDraftRevision?: number | undefined;
1564
+ maxNestedAgentDepth?: number | undefined;
1462
1565
  firstPartyMcpPermissions?: string[] | undefined;
1463
1566
  mcpServers?: SessionMcpServerInput[] | undefined;
1464
1567
  // Shared-sandbox placement (mirror of `@opengeni/contracts` CreateSessionRequest.sandbox,
@@ -1978,12 +2081,14 @@ export type Workspace = {
1978
2081
  export type WorkspaceSettings = {
1979
2082
  memoryEnabled?: boolean | undefined;
1980
2083
  transcription?: WorkspaceTranscriptionPolicy | undefined;
2084
+ maxNestedAgentDepth?: number | null | undefined;
1981
2085
  [key: string]: unknown;
1982
2086
  };
1983
2087
 
1984
2088
  export type UpdateWorkspaceSettingsRequest = {
1985
2089
  memoryEnabled?: boolean | undefined;
1986
2090
  transcription?: WorkspaceTranscriptionPolicy | undefined;
2091
+ maxNestedAgentDepth?: number | null | undefined;
1987
2092
  [key: string]: unknown;
1988
2093
  };
1989
2094
 
@@ -2068,6 +2173,36 @@ export type SessionGoalStatus = "active" | "paused" | "completed";
2068
2173
 
2069
2174
  export type SessionGoalCreatedBy = "api" | "agent" | "scheduled_task";
2070
2175
 
2176
+ export type SessionGoalContinuationState =
2177
+ | "inactive"
2178
+ | "scheduled"
2179
+ | "running"
2180
+ | "blocked"
2181
+ | "invariant_broken";
2182
+
2183
+ export type SessionGoalContinuationReason =
2184
+ | "goal_inactive"
2185
+ | "wake_pending"
2186
+ | "continuation_pending"
2187
+ | "human_work_pending"
2188
+ | "goal_turn_running"
2189
+ | "human_turn_running"
2190
+ | "workstream_paused"
2191
+ | "approval_required"
2192
+ | "provider_backpressure"
2193
+ | "session_cancelled"
2194
+ | "system_work_pending"
2195
+ | "missing_obligation";
2196
+
2197
+ export type SessionGoalContinuation = {
2198
+ state: SessionGoalContinuationState;
2199
+ reason: SessionGoalContinuationReason;
2200
+ wakeRevision: number;
2201
+ observedRevision: number;
2202
+ nextAttemptAt: string | null;
2203
+ lastError: string | null;
2204
+ };
2205
+
2071
2206
  export type SessionGoal = {
2072
2207
  id: string;
2073
2208
  accountId: string;
@@ -2085,6 +2220,8 @@ export type SessionGoal = {
2085
2220
  noProgressStreak: number;
2086
2221
  maxAutoContinuations: number | null;
2087
2222
  metadata: Record<string, unknown>;
2223
+ /** Optional for source compatibility; the API always supplies this projection. */
2224
+ continuation?: SessionGoalContinuation | undefined;
2088
2225
  createdAt: string;
2089
2226
  updatedAt: string;
2090
2227
  };
@@ -2172,6 +2309,27 @@ export type ComposerDraft = {
2172
2309
  updatedAt: string | null;
2173
2310
  };
2174
2311
 
2312
+ export type NewSessionDraftOptions = {
2313
+ sandboxBackend?: SandboxBackend | undefined;
2314
+ targetSandboxId?: string | undefined;
2315
+ workingDir?: string | undefined;
2316
+ variableSetId?: string | undefined;
2317
+ rigId?: string | undefined;
2318
+ goal?: GoalSpec | undefined;
2319
+ firstPartyMcpPermissions?: Permission[] | undefined;
2320
+ };
2321
+
2322
+ export type NewSessionDraft = {
2323
+ revision: number;
2324
+ text: string;
2325
+ resources: ResourceRef[];
2326
+ tools: ToolRef[];
2327
+ model: string;
2328
+ reasoningEffort: ReasoningEffort;
2329
+ options: NewSessionDraftOptions;
2330
+ updatedAt: string | null;
2331
+ };
2332
+
2175
2333
  export type SessionQueueSnapshot = {
2176
2334
  version: number;
2177
2335
  effectiveControl: EffectiveSessionControl;
@@ -2300,6 +2458,10 @@ export type SaveComposerDraftRequest = Omit<
2300
2458
  "revision" | "sourceTurnId" | "sourceTurnVersion" | "updatedAt"
2301
2459
  > & { expectedRevision: number };
2302
2460
 
2461
+ export type SaveNewSessionDraftRequest = Omit<NewSessionDraft, "revision" | "updatedAt"> & {
2462
+ expectedRevision: number;
2463
+ };
2464
+
2303
2465
  // --- Scheduled tasks: requests + runs ----------------------------------------
2304
2466
 
2305
2467
  /** Input shape for agent config on create/update (server applies defaults). */
@@ -2312,6 +2474,7 @@ export type ScheduledTaskAgentConfigInput = {
2312
2474
  reasoningEffort?: ReasoningEffort | undefined;
2313
2475
  sandboxBackend?: SandboxBackend | undefined;
2314
2476
  goal?: GoalSpec | undefined;
2477
+ maxNestedAgentDepth?: number | undefined;
2315
2478
  };
2316
2479
 
2317
2480
  export type CreateScheduledTaskRequest = {
@@ -3379,6 +3542,9 @@ export type MachineView = {
3379
3542
  state: MachineState;
3380
3543
  active: boolean;
3381
3544
  isSessionGroup: boolean;
3545
+ workspaceGeneration: number | null;
3546
+ archiveGeneration: number | null;
3547
+ archiveComplete: boolean;
3382
3548
  os: string;
3383
3549
  arch: string;
3384
3550
  hasDisplay: boolean;
@@ -3428,7 +3594,10 @@ export type SwapActiveSandboxResponse = {
3428
3594
  | "offline_enrollment"
3429
3595
  | "unsupported_backend_context"
3430
3596
  | "transient_establishment"
3431
- | "concurrent_swap";
3597
+ | "concurrent_swap"
3598
+ | "recovery_in_progress"
3599
+ | "recovery_degraded"
3600
+ | "recovery_unrecoverable";
3432
3601
  };
3433
3602
 
3434
3603
  // ── Self-hosted enrollment UX (design 11) ────────────────────────────────────