@shardflux/sdk 0.11.1 → 0.12.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/dist/errors.js CHANGED
@@ -1,4 +1,4 @@
1
- import { capacityDeadlineOf } from "./progress.js";
1
+ import { capacityDeadlineOf, durabilityOf } from "./progress.js";
2
2
  export function isErrorBody(v) {
3
3
  if (typeof v !== 'object' || v === null || !('error' in v))
4
4
  return false;
@@ -129,12 +129,20 @@ export function apiError(status, body, source, retryAfterSeconds, treeRevision)
129
129
  /** The response was not the documented shape (e.g. a proxy error page). */
130
130
  export class ShardfluxProtocolError extends Error {
131
131
  status;
132
- constructor(message, status) {
132
+ /** The responding surface; unknown only for errors constructed by older callers. Never proof of deletion. */
133
+ source;
134
+ constructor(message, status, source = 'unknown') {
133
135
  super(message);
134
136
  this.name = 'ShardfluxProtocolError';
135
137
  this.status = status;
138
+ this.source = source;
136
139
  }
137
140
  }
141
+ /** True only for an explicit API tombstone refusal. A 404, even a structured one, may be routing or scope. */
142
+ export function isWorkspaceGone(error) {
143
+ return error instanceof ShardfluxApiError && error.source === 'api' &&
144
+ error.code === 'conflict' && error.reason === 'workspace_deleted';
145
+ }
138
146
  /**
139
147
  * Waiting for an operation ran out of time. The operation keeps running server side:
140
148
  * resume with `cloud.workspaces.waitForOperation(err.operationId)` or call open() again
@@ -154,10 +162,18 @@ export class OperationTimeoutError extends Error {
154
162
  waitedMs;
155
163
  /** Where the time went: client phases, retries and the operation's own server timing so far. */
156
164
  timing = undefined;
157
- constructor(op, waitedMs) {
165
+ /**
166
+ * `durable` (0.12.0+): the wait was for the durable copy of a succeeded suspend or fork (`suspend({ durable: true })`,
167
+ * `waitForDurable()`); `lastState` is then `succeeded` and the copy continues server side.
168
+ */
169
+ durable;
170
+ constructor(op, waitedMs, durable = false) {
158
171
  const deadlineAt = capacityDeadlineOf(op);
159
172
  const after = deadlineAt ? `it stays queued server side until ${deadlineAt} and fails with capacity_unavailable if it has not started by then.` : 'it continues server side.';
160
- super(`Operation ${op.id} (${op.kind}) is still ${op.state}${op.state_reason ? ` (${op.state_reason})` : ''} after ${Math.round(waitedMs)} ms; ${after}`);
173
+ super(durable
174
+ ? `Operation ${op.id} (${op.kind}) succeeded; its durable copy was still ${durabilityOf(op)?.state ?? 'pending'} after ${Math.round(waitedMs)} ms and continues server side.`
175
+ : `Operation ${op.id} (${op.kind}) is still ${op.state}${op.state_reason ? ` (${op.state_reason})` : ''} after ${Math.round(waitedMs)} ms; ${after}`);
176
+ this.durable = durable;
161
177
  this.name = 'OperationTimeoutError';
162
178
  this.operationId = op.id;
163
179
  this.workspaceId = op.workspace_id;
@@ -167,11 +183,17 @@ export class OperationTimeoutError extends Error {
167
183
  this.waitedMs = waitedMs;
168
184
  }
169
185
  }
170
- /** The operation reached `failed` or `canceled`. */
186
+ /**
187
+ * The operation reached `failed` or `canceled`. A suspend-when-idle that found the workspace active (a tool call after
188
+ * the request, an attached stream) is `canceled` with `errorCode` `workspace_active` (`workspaceActive` true, 0.12.0+):
189
+ * nothing changed and the workspace keeps running. `waitUntilReady()` and `wake()` treat it as running, not as a failure.
190
+ */
171
191
  export class OperationFailedError extends Error {
172
192
  operation;
173
193
  operationId;
174
194
  errorCode;
195
+ /** 0.12.0+: a suspend-when-idle canceled because the workspace was active; it keeps running, nothing changed. */
196
+ workspaceActive;
175
197
  /**
176
198
  * `operation.error.retryable` (false when absent): the same request may succeed later. `capacity_unavailable` is
177
199
  * retryable: the start passed its deadline and nothing was started; send it again. The SDK never retries a failed
@@ -187,6 +209,29 @@ export class OperationFailedError extends Error {
187
209
  this.operation = op;
188
210
  this.operationId = op.id;
189
211
  this.errorCode = code;
212
+ this.workspaceActive = code === 'workspace_active' && op.kind === 'suspend';
190
213
  this.retryable = op.error?.retryable === true;
191
214
  }
192
215
  }
216
+ /**
217
+ * `suspend({ durable: true })` or `waitForDurable()` (0.12.0+): the suspend (or fork) succeeded, but its durable copy
218
+ * could not be made (`durability.state` `lost`). `durability` has the reason; see the lifecycle reference for what the
219
+ * next resume restores.
220
+ */
221
+ export class DurabilityLostError extends Error {
222
+ operation;
223
+ operationId;
224
+ workspaceId;
225
+ durability;
226
+ /** Where the time went. */
227
+ timing = undefined;
228
+ constructor(op) {
229
+ const durability = durabilityOf(op) ?? { state: 'lost', checkpointId: null, generationId: null, localCommitAt: null, durableBy: null, durableAt: null, localCommitToDurableMs: null, overdueAt: null, reason: null };
230
+ super(`Operation ${op.id} (${op.kind}): the durable copy of checkpoint ${durability.checkpointId ?? '?'} was not made${durability.reason ? ` (${durability.reason})` : ''}`);
231
+ this.name = 'DurabilityLostError';
232
+ this.operation = op;
233
+ this.operationId = op.id;
234
+ this.workspaceId = op.workspace_id;
235
+ this.durability = durability;
236
+ }
237
+ }
package/dist/feedback.js CHANGED
@@ -33,7 +33,7 @@ function feedbackBody(params) {
33
33
  export async function sendFeedback(ctx, params) {
34
34
  const { status, body } = await ctx.http.jsonWithStatus('POST', '/v1/feedback', { json: feedbackBody(params) }, ctx.authorization);
35
35
  if (typeof body?.id !== 'string' || typeof body.received_at !== 'string' || typeof body.duplicate !== 'boolean') {
36
- throw new ShardfluxProtocolError('POST /v1/feedback: the response is not {id, received_at, duplicate}', status);
36
+ throw new ShardfluxProtocolError('POST /v1/feedback: the response is not {id, received_at, duplicate}', status, 'api');
37
37
  }
38
38
  return { id: body.id, receivedAt: body.received_at, duplicate: body.duplicate };
39
39
  }
@@ -188,7 +188,7 @@ export interface paths {
188
188
  put?: never;
189
189
  /**
190
190
  * Consume an email verification token
191
- * @description Single use. The emailed link is a frontend page (`/auth/verify-email#token=...`); GET never consumes. The first verification signs out every session, disables any MFA enrolled while unverified and invalidates the other open verification links. A link sent by a registration applies the password chosen in that same registration (`status: verified`; sign in again). A link from "resend" carries no password, so the stored one (set by an unverified registrant) is discarded: the response is `status: password_reset_required` with a single-use `reset_token` for POST /api/auth/password/reset/confirm, and no sign-in is possible until a password is set.
191
+ * @description Single use. The emailed link is a frontend page (`/auth/verify-email#token=...`); GET never consumes. The first verification signs out every session, disables any MFA enrolled while unverified and invalidates the other open verification links. A link sent by a registration applies the password chosen in that same registration (`status: verified`). A link from "resend" carries no password, so the stored one (set by an unverified registrant) is discarded: the response is `status: password_reset_required` with a single-use `reset_token` for POST /api/auth/password/reset/confirm, and no sign-in is possible until a password is set. Consumed in the browser whose registration sent the link (its __Host-sf_signup cookie), a `verified` link also signs that browser in: `signed_in: true` with the new session cookie and its `csrf_token`. Anywhere else `signed_in` is false: sign in.
192
192
  */
193
193
  post: operations["postApiAuthVerifyEmail"];
194
194
  delete?: never;
@@ -576,7 +576,7 @@ export interface paths {
576
576
  put?: never;
577
577
  /**
578
578
  * Consume an email verification token
579
- * @description Single use. The emailed link is a frontend page (`/auth/verify-email#token=...`); GET never consumes. The first verification signs out every session, disables any MFA enrolled while unverified and invalidates the other open verification links. A link sent by a registration applies the password chosen in that same registration (`status: verified`; sign in again). A link from "resend" carries no password, so the stored one (set by an unverified registrant) is discarded: the response is `status: password_reset_required` with a single-use `reset_token` for POST /v1/auth/password/reset/confirm, and no sign-in is possible until a password is set.
579
+ * @description Single use. The emailed link is a frontend page (`/auth/verify-email#token=...`); GET never consumes. The first verification signs out every session, disables any MFA enrolled while unverified and invalidates the other open verification links. A link sent by a registration applies the password chosen in that same registration (`status: verified`). A link from "resend" carries no password, so the stored one (set by an unverified registrant) is discarded: the response is `status: password_reset_required` with a single-use `reset_token` for POST /v1/auth/password/reset/confirm, and no sign-in is possible until a password is set. Sign in afterwards.
580
580
  */
581
581
  post: operations["postV1AuthVerifyEmail"];
582
582
  delete?: never;
@@ -1366,7 +1366,7 @@ export interface paths {
1366
1366
  put?: never;
1367
1367
  /**
1368
1368
  * Open a workspace by key (create on first use, reconnect or resume afterwards)
1369
- * @description New keys resolve `template` to its latest published version; reopening never changes or resets the workspace (the response reports the template version actually used). 200 when the workspace is already running and ready (with `cell_endpoint` and a tool token); otherwise 202 with the operation to poll. A running workspace whose startup failed (`startup.state` failed, contracts §24.4) is not ready: the open is 202 with an `open` operation (input.startup_retry) that runs the failed step again. Concurrent opens of one key share one workspace and one operation. `secrets` (optional) binds secret names injected into every exec/PTY start: it sets the binding of a new key and replaces it on an existing key (omitted = unchanged); an unknown or unusable name is 422 details.reason secret_not_available with details.names (nothing is created or changed). The template version’s secret inputs (contracts §24.3) join the binding on create and whenever `secrets` is given; a required one this workspace may not use is 422 input_required (details.kind secret); a bound name equal to a template env key or text input is 422 env_collision (details.name). `inputs` (optional): the version’s text inputs {NAME: string}; stored on create (else the declared default) and replaced on an existing key (omitted = unchanged); 422 input_unknown, input_invalid or input_required (details.names). `lifetime` (contracts §19.11): omitted = the version’s default (else persistent); `session` workspaces are discarded when the session ends (close(), idle timeout), after which the key opens a NEW workspace; reopening a live key with another lifetime is 409 lifetime_mismatch. A new workspace is `layered` when its version supports it and layered opens are enabled (`disk_layout`). Errors: 404 template/key outside scope, 402 `entitlement_required`, 403 `quota_exceeded` (details.limit), 409 operation in progress, deleted key (workspace_deleted) or lifetime_mismatch, 422 reserved_key_prefix (keys starting with sf:). Supports Idempotency-Key. Held open (contracts §22.3): with `Prefer: wait=<seconds>` (at most 20) an open whose outcome is an operation is held until the operation is terminal or the wait elapses; success answers 200 with the running workspace, the succeeded operation and a tool token (`Preference-Applied: wait=<seconds>`), anything else 202 with the fresh operation. Without `Preference-Applied` the server did not wait: poll the operation. `mode` (contracts §29): omitted = processful for a new key and the stored mode for an existing one; `file_first` creates a workspace whose state is a versioned file tree with no VM between executions: it is ready at once (200 with a tool token, `observed_state` running, `tree_revision` 0, no operation), needs a layered template version (409 layout_unsupported otherwise) and is persistent (`lifetime: session` or `idle_policy` with it are 422 not_supported_for_mode); each open of a file-first key re-resolves the size of its execution VMs from `caps`, the template and the plan. 422 mode_not_available while the deployment does not offer file-first workspaces; reopening a key with another mode is 409 mode_mismatch.
1369
+ * @description New keys resolve `template` to its latest published version; reopening never changes or resets the workspace (the response reports the template version actually used). 200 when the workspace is already running and ready (with `cell_endpoint` and a tool token); otherwise 202 with the operation to poll. A running workspace whose startup failed (`startup.state` failed, contracts §24.4) is not ready: the open is 202 with an `open` operation (input.startup_retry) that runs the failed step again. Concurrent opens of one key share one workspace and one operation. `secrets` (optional) binds secret names injected into every exec/PTY start: it sets the binding of a new key and replaces it on an existing key (omitted = unchanged); an unknown or unusable name is 422 details.reason secret_not_available with details.names (nothing is created or changed). The template version’s secret inputs (contracts §24.3) join the binding on create and whenever `secrets` is given; a required one this workspace may not use is 422 input_required (details.kind secret); a bound name equal to a template env key or text input is 422 env_collision (details.name). `inputs` (optional): the version’s text inputs {NAME: string}; stored on create (else the declared default) and replaced on an existing key (omitted = unchanged); 422 input_unknown, input_invalid or input_required (details.names). `lifetime` (contracts §19.11): omitted = the version’s default (else persistent); `session` workspaces are discarded when the session ends (close(), idle timeout), after which the key opens a NEW workspace; reopening a live key with another lifetime is 409 lifetime_mismatch. A new workspace is `layered` when its version supports it and layered opens are enabled (`disk_layout`). Errors: 404 template/key outside scope, 402 `entitlement_required`, 403 `quota_exceeded` (details.limit), 409 operation in progress, key whose deletion is still in progress (workspace_deleted) or lifetime_mismatch, 422 reserved_key_prefix (keys starting with sf:). Supports Idempotency-Key. Held open (contracts §22.3): with `Prefer: wait=<seconds>` (at most 20) an open whose outcome is an operation is held until the operation is terminal or the wait elapses; success answers 200 with the running workspace, the succeeded operation and a tool token (`Preference-Applied: wait=<seconds>`), anything else 202 with the fresh operation. Without `Preference-Applied` the server did not wait: poll the operation. `mode` (contracts §29): omitted = processful for a new key and the stored mode for an existing one; `file_first` creates a workspace whose state is a versioned file tree with no VM between executions: it is ready at once (200 with a tool token, `observed_state` running, `tree_revision` 0, no operation), needs a layered template version (409 layout_unsupported otherwise) and is persistent (`lifetime: session` or `idle_policy` with it are 422 not_supported_for_mode); each open of a file-first key re-resolves the size of its execution VMs from `caps`, the template and the plan. 422 mode_not_available while the deployment does not offer file-first workspaces; reopening a key with another mode is 409 mode_mismatch.
1370
1370
  */
1371
1371
  post: operations["postV1WorkspacesOpen"];
1372
1372
  delete?: never;
@@ -1428,7 +1428,7 @@ export interface paths {
1428
1428
  post?: never;
1429
1429
  /**
1430
1430
  * Delete a workspace (tombstone now, storage cleanup by the cell)
1431
- * @description Sets desired_state=deleted and deleted_at, revokes tool access immediately (workspace revocation watermark, agent sessions revoked) and creates a `delete` operation for the cell. A file-first workspace’s tree revisions are deleted with the tombstone and the cell deletes its stored files (contracts §29). Repeating returns the same operation. The key is never reused. Supports Idempotency-Key.
1431
+ * @description Sets desired_state=deleted and deleted_at, revokes tool access immediately (workspace revocation watermark, agent sessions revoked) and creates a `delete` operation for the cell. A file-first workspace’s tree revisions are deleted with the tombstone and the cell deletes its stored files (contracts §29). Repeating returns the same operation. After deletion finishes (observed_state deleted), the key can open a new workspace ID; old IDs and tokens remain deleted. Supports Idempotency-Key.
1432
1432
  */
1433
1433
  delete: operations["deleteV1WorkspacesWorkspaceId"];
1434
1434
  options?: never;
@@ -1456,6 +1456,23 @@ export interface paths {
1456
1456
  patch?: never;
1457
1457
  trace?: never;
1458
1458
  };
1459
+ "/v1/workspaces/{workspace_id}/labels": {
1460
+ parameters: {
1461
+ query?: never;
1462
+ header?: never;
1463
+ path?: never;
1464
+ cookie?: never;
1465
+ };
1466
+ get?: never;
1467
+ /** Replace workspace labels */
1468
+ put: operations["putV1WorkspacesWorkspaceIdLabels"];
1469
+ post?: never;
1470
+ delete?: never;
1471
+ options?: never;
1472
+ head?: never;
1473
+ patch?: never;
1474
+ trace?: never;
1475
+ };
1459
1476
  "/v1/workspaces/{workspace_id}/idle-policy": {
1460
1477
  parameters: {
1461
1478
  query?: never;
@@ -1507,7 +1524,7 @@ export interface paths {
1507
1524
  put?: never;
1508
1525
  /**
1509
1526
  * Resume a suspended workspace (admitted like a start)
1510
- * @description Creates a `resume` operation executed by the cell, or returns the active resume/open (concurrent wakes join one operation); 202 with it: poll GET /operations/{id}. Errors: 409 already_running or not_suspended, operation_in_progress (a suspend or another operation is active; details.active_operation_id), workspace_deleted, not_supported_for_mode (file-first, contracts §29); 402/403 as for open (admitted like a start). Supports Idempotency-Key. Held resume (contracts §22.6): with `Prefer: wait=<seconds>` (at most 20) the response is held until the operation is terminal or the wait elapses, exactly like a held open (§22.3); success answers 200 with the running workspace, the succeeded operation, `cell_endpoint` and a tool token for `agent_label`/`tools` (minted while the restore is in flight, at the new ownership epoch), `Preference-Applied: wait=<seconds>`; anything else is 202 with the fresh operation. With the preference a workspace that is already running answers 200 at once (operation null, a token) instead of 409 already_running. Without `Preference-Applied` the server did not wait: poll the operation. `tools` beyond the principal’s tool permissions are 403 (nothing is created); a token the API cannot issue otherwise is `tool_token: null`.
1527
+ * @description Creates a `resume` operation for a suspended workspace, or an `open` recovery for a failed workspace (same ID and disk, no reset), or returns the active resume/open (concurrent wakes join one operation); 202 with it: poll GET /operations/{id}. Errors: 409 already_running or not_suspended, operation_in_progress (a suspend or another operation is active; details.active_operation_id), workspace_deleted, not_supported_for_mode (file-first, contracts §29); 402/403 as for open (admitted like a start). Supports Idempotency-Key. Held resume (contracts §22.6): with `Prefer: wait=<seconds>` (at most 20) the response is held until the operation is terminal or the wait elapses, exactly like a held open (§22.3); success answers 200 with the running workspace, the succeeded operation, `cell_endpoint` and a tool token for `agent_label`/`tools` (minted while the restore is in flight, at the new ownership epoch), `Preference-Applied: wait=<seconds>`; anything else is 202 with the fresh operation. With the preference a workspace that is already running answers 200 at once (operation null, a token) instead of 409 already_running. Without `Preference-Applied` the server did not wait: poll the operation. `tools` beyond the principal’s tool permissions are 403 (nothing is created); a token the API cannot issue otherwise is `tool_token: null`.
1511
1528
  */
1512
1529
  post: operations["postV1WorkspacesWorkspaceIdResume"];
1513
1530
  delete?: never;
@@ -1742,7 +1759,7 @@ export interface paths {
1742
1759
  put?: never;
1743
1760
  /**
1744
1761
  * Open a workspace by key (create on first use, reconnect or resume afterwards)
1745
- * @description New keys resolve `template` to its latest published version; reopening never changes or resets the workspace (the response reports the template version actually used). 200 when the workspace is already running and ready (with `cell_endpoint` and a tool token); otherwise 202 with the operation to poll. A running workspace whose startup failed (`startup.state` failed, contracts §24.4) is not ready: the open is 202 with an `open` operation (input.startup_retry) that runs the failed step again. Concurrent opens of one key share one workspace and one operation. `secrets` (optional) binds secret names injected into every exec/PTY start: it sets the binding of a new key and replaces it on an existing key (omitted = unchanged); an unknown or unusable name is 422 details.reason secret_not_available with details.names (nothing is created or changed). The template version’s secret inputs (contracts §24.3) join the binding on create and whenever `secrets` is given; a required one this workspace may not use is 422 input_required (details.kind secret); a bound name equal to a template env key or text input is 422 env_collision (details.name). `inputs` (optional): the version’s text inputs {NAME: string}; stored on create (else the declared default) and replaced on an existing key (omitted = unchanged); 422 input_unknown, input_invalid or input_required (details.names). `lifetime` (contracts §19.11): omitted = the version’s default (else persistent); `session` workspaces are discarded when the session ends (close(), idle timeout), after which the key opens a NEW workspace; reopening a live key with another lifetime is 409 lifetime_mismatch. A new workspace is `layered` when its version supports it and layered opens are enabled (`disk_layout`). Errors: 404 template/key outside scope, 402 `entitlement_required`, 403 `quota_exceeded` (details.limit), 409 operation in progress, deleted key (workspace_deleted) or lifetime_mismatch, 422 reserved_key_prefix (keys starting with sf:). Supports Idempotency-Key. Held open (contracts §22.3): with `Prefer: wait=<seconds>` (at most 20) an open whose outcome is an operation is held until the operation is terminal or the wait elapses; success answers 200 with the running workspace, the succeeded operation and a tool token (`Preference-Applied: wait=<seconds>`), anything else 202 with the fresh operation. Without `Preference-Applied` the server did not wait: poll the operation. `mode` (contracts §29): omitted = processful for a new key and the stored mode for an existing one; `file_first` creates a workspace whose state is a versioned file tree with no VM between executions: it is ready at once (200 with a tool token, `observed_state` running, `tree_revision` 0, no operation), needs a layered template version (409 layout_unsupported otherwise) and is persistent (`lifetime: session` or `idle_policy` with it are 422 not_supported_for_mode); each open of a file-first key re-resolves the size of its execution VMs from `caps`, the template and the plan. 422 mode_not_available while the deployment does not offer file-first workspaces; reopening a key with another mode is 409 mode_mismatch.
1762
+ * @description New keys resolve `template` to its latest published version; reopening never changes or resets the workspace (the response reports the template version actually used). 200 when the workspace is already running and ready (with `cell_endpoint` and a tool token); otherwise 202 with the operation to poll. A running workspace whose startup failed (`startup.state` failed, contracts §24.4) is not ready: the open is 202 with an `open` operation (input.startup_retry) that runs the failed step again. Concurrent opens of one key share one workspace and one operation. `secrets` (optional) binds secret names injected into every exec/PTY start: it sets the binding of a new key and replaces it on an existing key (omitted = unchanged); an unknown or unusable name is 422 details.reason secret_not_available with details.names (nothing is created or changed). The template version’s secret inputs (contracts §24.3) join the binding on create and whenever `secrets` is given; a required one this workspace may not use is 422 input_required (details.kind secret); a bound name equal to a template env key or text input is 422 env_collision (details.name). `inputs` (optional): the version’s text inputs {NAME: string}; stored on create (else the declared default) and replaced on an existing key (omitted = unchanged); 422 input_unknown, input_invalid or input_required (details.names). `lifetime` (contracts §19.11): omitted = the version’s default (else persistent); `session` workspaces are discarded when the session ends (close(), idle timeout), after which the key opens a NEW workspace; reopening a live key with another lifetime is 409 lifetime_mismatch. A new workspace is `layered` when its version supports it and layered opens are enabled (`disk_layout`). Errors: 404 template/key outside scope, 402 `entitlement_required`, 403 `quota_exceeded` (details.limit), 409 operation in progress, key whose deletion is still in progress (workspace_deleted) or lifetime_mismatch, 422 reserved_key_prefix (keys starting with sf:). Supports Idempotency-Key. Held open (contracts §22.3): with `Prefer: wait=<seconds>` (at most 20) an open whose outcome is an operation is held until the operation is terminal or the wait elapses; success answers 200 with the running workspace, the succeeded operation and a tool token (`Preference-Applied: wait=<seconds>`), anything else 202 with the fresh operation. Without `Preference-Applied` the server did not wait: poll the operation. `mode` (contracts §29): omitted = processful for a new key and the stored mode for an existing one; `file_first` creates a workspace whose state is a versioned file tree with no VM between executions: it is ready at once (200 with a tool token, `observed_state` running, `tree_revision` 0, no operation), needs a layered template version (409 layout_unsupported otherwise) and is persistent (`lifetime: session` or `idle_policy` with it are 422 not_supported_for_mode); each open of a file-first key re-resolves the size of its execution VMs from `caps`, the template and the plan. 422 mode_not_available while the deployment does not offer file-first workspaces; reopening a key with another mode is 409 mode_mismatch.
1746
1763
  */
1747
1764
  post: operations["postApiV1WorkspacesOpen"];
1748
1765
  delete?: never;
@@ -1804,7 +1821,7 @@ export interface paths {
1804
1821
  post?: never;
1805
1822
  /**
1806
1823
  * Delete a workspace (tombstone now, storage cleanup by the cell)
1807
- * @description Sets desired_state=deleted and deleted_at, revokes tool access immediately (workspace revocation watermark, agent sessions revoked) and creates a `delete` operation for the cell. A file-first workspace’s tree revisions are deleted with the tombstone and the cell deletes its stored files (contracts §29). Repeating returns the same operation. The key is never reused. Supports Idempotency-Key.
1824
+ * @description Sets desired_state=deleted and deleted_at, revokes tool access immediately (workspace revocation watermark, agent sessions revoked) and creates a `delete` operation for the cell. A file-first workspace’s tree revisions are deleted with the tombstone and the cell deletes its stored files (contracts §29). Repeating returns the same operation. After deletion finishes (observed_state deleted), the key can open a new workspace ID; old IDs and tokens remain deleted. Supports Idempotency-Key.
1808
1825
  */
1809
1826
  delete: operations["deleteApiV1WorkspacesWorkspaceId"];
1810
1827
  options?: never;
@@ -1832,6 +1849,23 @@ export interface paths {
1832
1849
  patch?: never;
1833
1850
  trace?: never;
1834
1851
  };
1852
+ "/api/v1/workspaces/{workspace_id}/labels": {
1853
+ parameters: {
1854
+ query?: never;
1855
+ header?: never;
1856
+ path?: never;
1857
+ cookie?: never;
1858
+ };
1859
+ get?: never;
1860
+ /** Replace workspace labels */
1861
+ put: operations["putApiV1WorkspacesWorkspaceIdLabels"];
1862
+ post?: never;
1863
+ delete?: never;
1864
+ options?: never;
1865
+ head?: never;
1866
+ patch?: never;
1867
+ trace?: never;
1868
+ };
1835
1869
  "/api/v1/workspaces/{workspace_id}/idle-policy": {
1836
1870
  parameters: {
1837
1871
  query?: never;
@@ -1883,7 +1917,7 @@ export interface paths {
1883
1917
  put?: never;
1884
1918
  /**
1885
1919
  * Resume a suspended workspace (admitted like a start)
1886
- * @description Creates a `resume` operation executed by the cell, or returns the active resume/open (concurrent wakes join one operation); 202 with it: poll GET /operations/{id}. Errors: 409 already_running or not_suspended, operation_in_progress (a suspend or another operation is active; details.active_operation_id), workspace_deleted, not_supported_for_mode (file-first, contracts §29); 402/403 as for open (admitted like a start). Supports Idempotency-Key. Held resume (contracts §22.6): with `Prefer: wait=<seconds>` (at most 20) the response is held until the operation is terminal or the wait elapses, exactly like a held open (§22.3); success answers 200 with the running workspace, the succeeded operation, `cell_endpoint` and a tool token for `agent_label`/`tools` (minted while the restore is in flight, at the new ownership epoch), `Preference-Applied: wait=<seconds>`; anything else is 202 with the fresh operation. With the preference a workspace that is already running answers 200 at once (operation null, a token) instead of 409 already_running. Without `Preference-Applied` the server did not wait: poll the operation. `tools` beyond the principal’s tool permissions are 403 (nothing is created); a token the API cannot issue otherwise is `tool_token: null`.
1920
+ * @description Creates a `resume` operation for a suspended workspace, or an `open` recovery for a failed workspace (same ID and disk, no reset), or returns the active resume/open (concurrent wakes join one operation); 202 with it: poll GET /operations/{id}. Errors: 409 already_running or not_suspended, operation_in_progress (a suspend or another operation is active; details.active_operation_id), workspace_deleted, not_supported_for_mode (file-first, contracts §29); 402/403 as for open (admitted like a start). Supports Idempotency-Key. Held resume (contracts §22.6): with `Prefer: wait=<seconds>` (at most 20) the response is held until the operation is terminal or the wait elapses, exactly like a held open (§22.3); success answers 200 with the running workspace, the succeeded operation, `cell_endpoint` and a tool token for `agent_label`/`tools` (minted while the restore is in flight, at the new ownership epoch), `Preference-Applied: wait=<seconds>`; anything else is 202 with the fresh operation. With the preference a workspace that is already running answers 200 at once (operation null, a token) instead of 409 already_running. Without `Preference-Applied` the server did not wait: poll the operation. `tools` beyond the principal’s tool permissions are 403 (nothing is created); a token the API cannot issue otherwise is `tool_token: null`.
1887
1921
  */
1888
1922
  post: operations["postApiV1WorkspacesWorkspaceIdResume"];
1889
1923
  delete?: never;
@@ -5548,6 +5582,55 @@ export interface components {
5548
5582
  [key: string]: unknown;
5549
5583
  };
5550
5584
  result?: {
5585
+ /** @description suspend and fork: whether the capture is in durable storage. false while `durability.state` is pending (the suspend completed on its host) or lost; true once it committed. Absent on other kinds. */
5586
+ durable?: boolean;
5587
+ /** @description suspend: `local_commit` when the suspend completed on its host and the durable copy followed in the background. */
5588
+ suspend_path?: string;
5589
+ durability?: {
5590
+ /**
5591
+ * @description pending: sealed on its host, the durable copy is being written; durable: the copy committed; lost: the copy could not be made and the previous recovery point stays current.
5592
+ * @enum {string}
5593
+ */
5594
+ state: "pending" | "durable" | "lost";
5595
+ /** @description The checkpoint being made durable. */
5596
+ checkpoint_id?: string;
5597
+ /** @description Its generation on the host. */
5598
+ generation_id?: string;
5599
+ /** @description RFC 3339: when the suspend (or fork) completed on its host. */
5600
+ local_commit_at?: string;
5601
+ /** @description RFC 3339: when the durable copy is expected to have committed (local_commit_at + the deadline). */
5602
+ durable_by?: string;
5603
+ /** @description RFC 3339 (state durable): when the durable copy committed. */
5604
+ durable_at?: string;
5605
+ /** @description State durable: local_commit_at to durable_at, in milliseconds. */
5606
+ local_commit_to_durable_ms?: number;
5607
+ /** @description RFC 3339: set once durable_by passed while the copy was still pending; it keeps retrying. */
5608
+ overdue_at?: string;
5609
+ /** @description State lost: why, e.g. host_lost. */
5610
+ reason?: string;
5611
+ /** @description State lost: the error code of the failed copy. */
5612
+ code?: string;
5613
+ recovery_point_checkpoint_id?: string | null;
5614
+ } & {
5615
+ [key: string]: unknown;
5616
+ };
5617
+ lost_suspend?: {
5618
+ /** @description The suspend checkpoint whose durable copy was lost. */
5619
+ checkpoint_id: string;
5620
+ /** @description Its generation on the host. */
5621
+ generation_id?: string;
5622
+ /** @description Why it was lost, e.g. host_lost. */
5623
+ reason?: string;
5624
+ /** @description RFC 3339: when that suspend captured the workspace. */
5625
+ suspended_at?: string;
5626
+ /** @description The earlier durable checkpoint this resume restored. */
5627
+ restored_checkpoint_id?: string;
5628
+ /** @description RFC 3339: when the restored checkpoint committed; the workspace state is as of then. */
5629
+ state_as_of?: string;
5630
+ } & {
5631
+ [key: string]: unknown;
5632
+ };
5633
+ } & {
5551
5634
  [key: string]: unknown;
5552
5635
  };
5553
5636
  };
@@ -6668,6 +6751,10 @@ export interface components {
6668
6751
  */
6669
6752
  project_id: string;
6670
6753
  workspace_key: string;
6754
+ /** @description Searchable workspace metadata; not secrets. At most 50 labels. */
6755
+ labels: {
6756
+ [key: string]: string;
6757
+ };
6671
6758
  /**
6672
6759
  * Format: uuid
6673
6760
  * @description UUIDv7, lowercase canonical form.
@@ -7488,6 +7575,10 @@ export interface operations {
7488
7575
  status: "verified" | "password_reset_required";
7489
7576
  /** @description Present with password_reset_required: single-use token for POST /api/auth/password/reset/confirm. */
7490
7577
  reset_token?: string;
7578
+ /** @description True when this response opened a session (the registering browser); false: sign in. */
7579
+ signed_in: boolean;
7580
+ /** @description Send as X-CSRF-Token on subsequent mutations. */
7581
+ csrf_token?: string;
7491
7582
  };
7492
7583
  };
7493
7584
  };
@@ -8487,7 +8578,7 @@ export interface operations {
8487
8578
  content: {
8488
8579
  "application/json": {
8489
8580
  status: "verified" | "password_reset_required";
8490
- /** @description Present with password_reset_required: single-use token for POST /api/auth/password/reset/confirm. */
8581
+ /** @description Present with password_reset_required: single-use token for POST /v1/auth/password/reset/confirm. */
8491
8582
  reset_token?: string;
8492
8583
  };
8493
8584
  };
@@ -11495,6 +11586,10 @@ export interface operations {
11495
11586
  "application/json": {
11496
11587
  /** @description Stable workspace key, unique per organization (e.g. `${customerId}/${projectId}`). */
11497
11588
  key: string;
11589
+ /** @description Searchable workspace metadata; not secrets. At most 50 labels. */
11590
+ labels?: {
11591
+ [key: string]: string;
11592
+ };
11498
11593
  /** @description Template slug; new workspaces use its latest published version. */
11499
11594
  template: string;
11500
11595
  /** @description Optional user caps; the ceiling is min(template, cap, plan). Absent fields add no restriction. */
@@ -11586,6 +11681,8 @@ export interface operations {
11586
11681
  state?: "creating" | "starting" | "running" | "suspending" | "suspended" | "resuming" | "forking" | "stopping" | "failed" | "deleting" | "deleted";
11587
11682
  desired_state?: "running" | "suspended" | "deleted";
11588
11683
  key_prefix?: string;
11684
+ /** @description JSON object of exact label matches; all pairs must match. */
11685
+ labels?: string;
11589
11686
  include_deleted?: boolean;
11590
11687
  /** @description persistent (default), session or any. Key lookups pass any. */
11591
11688
  lifetime?: "persistent" | "session" | "any";
@@ -11641,6 +11738,8 @@ export interface operations {
11641
11738
  state?: "creating" | "starting" | "running" | "suspending" | "suspended" | "resuming" | "forking" | "stopping" | "failed" | "deleting" | "deleted";
11642
11739
  desired_state?: "running" | "suspended" | "deleted";
11643
11740
  key_prefix?: string;
11741
+ /** @description JSON object of exact label matches; all pairs must match. */
11742
+ labels?: string;
11644
11743
  include_deleted?: boolean;
11645
11744
  /** @description persistent (default), session or any. Key lookups pass any. */
11646
11745
  lifetime?: "persistent" | "session" | "any";
@@ -11814,6 +11913,56 @@ export interface operations {
11814
11913
  };
11815
11914
  };
11816
11915
  };
11916
+ putV1WorkspacesWorkspaceIdLabels: {
11917
+ parameters: {
11918
+ query?: never;
11919
+ header?: never;
11920
+ path: {
11921
+ /** @description UUIDv7, lowercase canonical form. */
11922
+ workspace_id: string;
11923
+ };
11924
+ cookie?: never;
11925
+ };
11926
+ requestBody: {
11927
+ content: {
11928
+ "application/json": {
11929
+ /** @description Searchable workspace metadata; not secrets. At most 50 labels. */
11930
+ labels: {
11931
+ [key: string]: string;
11932
+ };
11933
+ };
11934
+ };
11935
+ };
11936
+ responses: {
11937
+ /** @description Default Response */
11938
+ 200: {
11939
+ headers: {
11940
+ [name: string]: unknown;
11941
+ };
11942
+ content: {
11943
+ "application/json": components["schemas"]["Workspace"];
11944
+ };
11945
+ };
11946
+ /** @description Default Response */
11947
+ "4XX": {
11948
+ headers: {
11949
+ [name: string]: unknown;
11950
+ };
11951
+ content: {
11952
+ "application/json": components["schemas"]["ErrorBody"];
11953
+ };
11954
+ };
11955
+ /** @description Default Response */
11956
+ "5XX": {
11957
+ headers: {
11958
+ [name: string]: unknown;
11959
+ };
11960
+ content: {
11961
+ "application/json": components["schemas"]["ErrorBody"];
11962
+ };
11963
+ };
11964
+ };
11965
+ };
11817
11966
  putV1WorkspacesWorkspaceIdIdlePolicy: {
11818
11967
  parameters: {
11819
11968
  query?: never;
@@ -12623,6 +12772,10 @@ export interface operations {
12623
12772
  "application/json": {
12624
12773
  /** @description Stable workspace key, unique per organization (e.g. `${customerId}/${projectId}`). */
12625
12774
  key: string;
12775
+ /** @description Searchable workspace metadata; not secrets. At most 50 labels. */
12776
+ labels?: {
12777
+ [key: string]: string;
12778
+ };
12626
12779
  /** @description Template slug; new workspaces use its latest published version. */
12627
12780
  template: string;
12628
12781
  /** @description Optional user caps; the ceiling is min(template, cap, plan). Absent fields add no restriction. */
@@ -12714,6 +12867,8 @@ export interface operations {
12714
12867
  state?: "creating" | "starting" | "running" | "suspending" | "suspended" | "resuming" | "forking" | "stopping" | "failed" | "deleting" | "deleted";
12715
12868
  desired_state?: "running" | "suspended" | "deleted";
12716
12869
  key_prefix?: string;
12870
+ /** @description JSON object of exact label matches; all pairs must match. */
12871
+ labels?: string;
12717
12872
  include_deleted?: boolean;
12718
12873
  /** @description persistent (default), session or any. Key lookups pass any. */
12719
12874
  lifetime?: "persistent" | "session" | "any";
@@ -12769,6 +12924,8 @@ export interface operations {
12769
12924
  state?: "creating" | "starting" | "running" | "suspending" | "suspended" | "resuming" | "forking" | "stopping" | "failed" | "deleting" | "deleted";
12770
12925
  desired_state?: "running" | "suspended" | "deleted";
12771
12926
  key_prefix?: string;
12927
+ /** @description JSON object of exact label matches; all pairs must match. */
12928
+ labels?: string;
12772
12929
  include_deleted?: boolean;
12773
12930
  /** @description persistent (default), session or any. Key lookups pass any. */
12774
12931
  lifetime?: "persistent" | "session" | "any";
@@ -12942,6 +13099,56 @@ export interface operations {
12942
13099
  };
12943
13100
  };
12944
13101
  };
13102
+ putApiV1WorkspacesWorkspaceIdLabels: {
13103
+ parameters: {
13104
+ query?: never;
13105
+ header?: never;
13106
+ path: {
13107
+ /** @description UUIDv7, lowercase canonical form. */
13108
+ workspace_id: string;
13109
+ };
13110
+ cookie?: never;
13111
+ };
13112
+ requestBody: {
13113
+ content: {
13114
+ "application/json": {
13115
+ /** @description Searchable workspace metadata; not secrets. At most 50 labels. */
13116
+ labels: {
13117
+ [key: string]: string;
13118
+ };
13119
+ };
13120
+ };
13121
+ };
13122
+ responses: {
13123
+ /** @description Default Response */
13124
+ 200: {
13125
+ headers: {
13126
+ [name: string]: unknown;
13127
+ };
13128
+ content: {
13129
+ "application/json": components["schemas"]["Workspace"];
13130
+ };
13131
+ };
13132
+ /** @description Default Response */
13133
+ "4XX": {
13134
+ headers: {
13135
+ [name: string]: unknown;
13136
+ };
13137
+ content: {
13138
+ "application/json": components["schemas"]["ErrorBody"];
13139
+ };
13140
+ };
13141
+ /** @description Default Response */
13142
+ "5XX": {
13143
+ headers: {
13144
+ [name: string]: unknown;
13145
+ };
13146
+ content: {
13147
+ "application/json": components["schemas"]["ErrorBody"];
13148
+ };
13149
+ };
13150
+ };
13151
+ };
12945
13152
  putApiV1WorkspacesWorkspaceIdIdlePolicy: {
12946
13153
  parameters: {
12947
13154
  query?: never;
@@ -33652,6 +33859,10 @@ export interface operations {
33652
33859
  controller_leases: {
33653
33860
  total: number;
33654
33861
  expired: number;
33862
+ /** @description Leases whose loop_state is late. */
33863
+ late: number;
33864
+ /** @description Leases whose loop_state is stalled (what Status alerts on). */
33865
+ stalled: number;
33655
33866
  leases: {
33656
33867
  name: string;
33657
33868
  holder: string;
@@ -33673,6 +33884,13 @@ export interface operations {
33673
33884
  expires_at: string;
33674
33885
  expired: boolean;
33675
33886
  renewed_age_seconds: number;
33887
+ /**
33888
+ * @description Against the loop's cell-loop-stalled CloudWatch alarm threshold: running (held), handover (no holder for at most one interval + 60 s, e.g. a deploy's release), late (no holder for longer, not stalled yet), stalled (no renewal for longer than the threshold).
33889
+ * @enum {string}
33890
+ */
33891
+ loop_state: "running" | "handover" | "late" | "stalled";
33892
+ /** @description The loop's alarm threshold: stalled past this many seconds without a renewal. */
33893
+ stall_after_seconds: number;
33676
33894
  }[];
33677
33895
  };
33678
33896
  stale_after_seconds: number;
@@ -170,6 +170,29 @@ export interface paths {
170
170
  patch?: never;
171
171
  trace?: never;
172
172
  };
173
+ "/v1/workspaces/{workspace_id}/exec/{session_id}/stdin": {
174
+ parameters: {
175
+ query?: never;
176
+ header?: never;
177
+ path: {
178
+ workspace_id: components["parameters"]["WorkspaceId"];
179
+ session_id: components["parameters"]["SessionId"];
180
+ };
181
+ cookie?: never;
182
+ };
183
+ get?: never;
184
+ put?: never;
185
+ /**
186
+ * Write pipe stdin or send EOF to an exec session
187
+ * @description Requires stdin_open at start (processful only), host exec_stdin and guest exec_stdin.v1. Offset is the total bytes previously accepted (initially 0). A repeated identical last frame is safe; a different stale offset is refused. Writes are bounded: offset in the response may acknowledge only a prefix; continue from that offset. close takes effect only after the whole frame is accepted. The transport writes no input log or payload file; program output and full-state snapshots retain their normal persistence. An ended session refuses input.
188
+ */
189
+ post: operations["execInput"];
190
+ delete?: never;
191
+ options?: never;
192
+ head?: never;
193
+ patch?: never;
194
+ trace?: never;
195
+ };
173
196
  "/v1/workspaces/{workspace_id}/exec/{session_id}/signal": {
174
197
  parameters: {
175
198
  query?: never;
@@ -843,6 +866,8 @@ export interface components {
843
866
  /** @description Absolute working directory; omitted or empty starts in the default (/home/user). A relative path is refused, not resolved: 422 validation_failed, details.reason invalid_cwd, details.field cwd, the message naming the absolute path it likely means. A cwd that is not a directory ends the session failed_to_start (processful) or the execution failed with details.reason exec_failed_to_start (file-first). */
844
867
  cwd?: string;
845
868
  user?: string;
869
+ /** @description Keep a pipe open for exec stdin writes; processful only, mutually exclusive with stdin. Requires exec_stdin.v1. */
870
+ stdin_open?: boolean;
846
871
  /** @description Written to stdin, which is then closed (max 1 MiB decoded). */
847
872
  stdin?: string;
848
873
  /** Format: int64 */
@@ -979,6 +1004,19 @@ export interface components {
979
1004
  };
980
1005
  /** @description Signal number (1-64) or name (e.g. "SIGTERM", "TERM"). */
981
1006
  SignalValue: number | string;
1007
+ ExecInputRequest: {
1008
+ /** @description At most 64 KiB decoded. */
1009
+ data?: string;
1010
+ /** Format: int64 */
1011
+ offset: number;
1012
+ /** @default false */
1013
+ close?: boolean;
1014
+ };
1015
+ ExecInputResult: {
1016
+ /** Format: int64 */
1017
+ offset: number;
1018
+ closed: boolean;
1019
+ };
982
1020
  SignalRequest: {
983
1021
  signal: components["schemas"]["SignalValue"];
984
1022
  /**
@@ -1607,6 +1645,34 @@ export interface operations {
1607
1645
  default: components["responses"]["Error"];
1608
1646
  };
1609
1647
  };
1648
+ execInput: {
1649
+ parameters: {
1650
+ query?: never;
1651
+ header?: never;
1652
+ path: {
1653
+ workspace_id: components["parameters"]["WorkspaceId"];
1654
+ session_id: components["parameters"]["SessionId"];
1655
+ };
1656
+ cookie?: never;
1657
+ };
1658
+ requestBody: {
1659
+ content: {
1660
+ "application/json": components["schemas"]["ExecInputRequest"];
1661
+ };
1662
+ };
1663
+ responses: {
1664
+ /** @description Acknowledged input offset and EOF state. */
1665
+ 200: {
1666
+ headers: {
1667
+ [name: string]: unknown;
1668
+ };
1669
+ content: {
1670
+ "application/json": components["schemas"]["ExecInputResult"];
1671
+ };
1672
+ };
1673
+ default: components["responses"]["Error"];
1674
+ };
1675
+ };
1610
1676
  execSignal: {
1611
1677
  parameters: {
1612
1678
  query?: never;