@usestatemachines/sdk 0.6.1 → 0.7.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  # @usestatemachines/sdk
2
2
 
3
3
  TypeScript SDK for [State Machines](https://usestatemachines.com): create disposable environments
4
- of replica apps, connect to them, take snapshots of their data, pause and resume them, and delete
4
+ of replica apps, connect to them, take snapshots of their data, and delete
5
5
  them.
6
6
 
7
7
  Documentation: [docs.usestatemachines.com](https://docs.usestatemachines.com)
@@ -14,13 +14,18 @@ npm install @usestatemachines/sdk
14
14
 
15
15
  ## Quickstart
16
16
 
17
+ <!-- quickstart:start -->
17
18
  ```ts
18
19
  import { StateMachines } from '@usestatemachines/sdk';
19
20
 
20
21
  const sm = new StateMachines(); // reads STATEMACHINES_API_KEY
21
- const env = await sm.environments.create({ apps: ['salesforce'] });
22
+ const env = await sm.environments.create(
23
+ { apps: ['salesforce'] },
24
+ { wait: false },
25
+ );
22
26
 
23
27
  try {
28
+ await sm.environments.wait(env.id, { status: 'running' });
24
29
  const salesforce = await env.connect('salesforce');
25
30
  const res = await salesforce.fetch('/services/data/v67.0/limits');
26
31
  console.log(res.status, await res.json());
@@ -28,9 +33,12 @@ try {
28
33
  await env.delete();
29
34
  }
30
35
  ```
36
+ <!-- quickstart:end -->
31
37
 
32
- `create` waits until the environment is `running`. If an app fails to start, the server deletes
33
- the environment. [Waiting](#waiting) lists what each method throws.
38
+ `create` waits until the environment is `running` by default. This example uses `wait: false`
39
+ to retain the environment ID before waiting, so cleanup also covers startup failures. It rejects
40
+ unsuccessful app responses and preserves both verification and cleanup errors.
41
+ [Waiting](#waiting) lists what each method throws.
34
42
 
35
43
  ## The client
36
44
 
@@ -49,13 +57,12 @@ const sm = new StateMachines({
49
57
  | `sm` | `me()` |
50
58
  | `sm.apps` | `list(query?)`, `versions(appId, query?)` |
51
59
  | `sm.workspaces` | `list(query?)`, `get(id)` |
52
- | `sm.environments` | `create(input, options?)`, `list(query?)`, `get(id)`, `update(id, { name?, labels? })`, `pause(id)`, `resume(id, options?)`, `delete(id)`, `wait(id, { status })`, `connect(id, app, { actorId? }?)` |
60
+ | `sm.environments` | `create(input, options?)`, `list(query?)`, `get(id)`, `update(id, { name?, labels? })`, `delete(id)`, `wait(id, { status })`, `connect(id, app, { actorId? }?)` |
53
61
  | `sm.snapshots` | `create({ environmentId, name? }, options?)`, `list(query?)`, `get(id)`, `update(id, { name })`, `delete(id)`, `wait(id, { status })` |
54
62
  | `sm.requests` | `list(query)`, `get(id)`, `stats(query)`, `body(id, 'request' \| 'response')` |
55
63
  | `sm.auditEvents` | `list(query?)` |
56
64
 
57
- Every environment the SDK returns carries bound helpers: `env.connect(app)`, `env.pause()`,
58
- `env.resume()`, `env.delete()`, `env.refresh()` and `env.snapshots.create({ name? })`. They are
65
+ Every environment the SDK returns carries bound helpers: `env.connect(app)`, `env.delete()`, `env.refresh()` and `env.snapshots.create({ name? })`. They are
59
66
  not enumerable, so `JSON.stringify(env)` holds only the API's fields.
60
67
 
61
68
  ## Apps
@@ -93,7 +100,7 @@ A new environment runs each app's default version.
93
100
  `env.connect(app, { actorId? })` returns the API's `Credentials`: `url`, `headers`, `token`,
94
101
  `expiresAt` and `login`, plus `fetch(path, init?)`.
95
102
 
96
- - `fetch` joins `path` to `url`; a leading `/` means the root of the app. It adds `headers`
103
+ - `fetch` joins `path` to `url`. A leading `/` means the root of the app. It adds `headers`
97
104
  (`StateMachines-Environment-Token`), and puts `token` in the header of the API whose path
98
105
  matches: `Authorization: Bearer` for Salesforce REST and Bulk 2.0.
99
106
  - Salesforce SOAP takes the token in the envelope: put `credentials.token` in
@@ -110,13 +117,6 @@ A new environment runs each app's default version.
110
117
  Environments created with `tokenInUrl: true` carry the environment token in `url` instead, and
111
118
  `headers` is empty.
112
119
 
113
- ## Pausing and resuming
114
-
115
- `env.pause()` takes effect when the API answers: the gateway refuses calls, the compute is
116
- released and the data stays. The time limit stops counting. `env.resume()` boots the apps again
117
- on the same data and waits until `running`. A paused or failed environment is deleted after one
118
- day.
119
-
120
120
  ## Snapshots
121
121
 
122
122
  ```ts
@@ -125,7 +125,7 @@ const copy = await sm.environments.create({ snapshotId: seeded.id });
125
125
  ```
126
126
 
127
127
  A snapshot holds every app of a running environment at one moment, and waits until `ready`. A
128
- pause or delete during the snapshot fails it with `failure.code: 'interrupted'`. One snapshot is
128
+ delete during the snapshot fails it with `failure.code: 'interrupted'`. One snapshot is
129
129
  `saving` per environment at a time. `sm.snapshots.delete(id)` deletes a `ready` or `failed`
130
130
  snapshot.
131
131
 
@@ -133,14 +133,13 @@ snapshot.
133
133
 
134
134
  | Method | Waits for | Throws |
135
135
  | --- | --- | --- |
136
- | `environments.create` | `running` | `EnvironmentFailedError` when the environment fails; `UnexpectedStatusError` on `paused` or `deleted` for another reason; `WaitTimeoutError` |
137
- | `environments.resume` | `running` | `EnvironmentFailedError` when the environment fails; `UnexpectedStatusError` on `paused` or `deleted` for another reason; `WaitTimeoutError` |
138
- | `snapshots.create` | `ready` | `SnapshotFailedError` on `failed`; `UnexpectedStatusError` on `deleted` |
136
+ | `environments.create` | `running` | `EnvironmentFailedError` when the environment fails, `UnexpectedStatusError` on `deleted` for another reason, and `WaitTimeoutError` |
137
+ | `snapshots.create` | `ready` | `SnapshotFailedError` on `failed` and `UnexpectedStatusError` on `deleted` |
139
138
  | `environments.wait(id, { status })`, `snapshots.wait(id, { status })` | `status` | the same rule: a status that cannot reach `status` without another call |
140
139
 
141
- Pass `{ wait: false }` to `create`, `resume` or `snapshots.create` to get the API's answer at once.
140
+ Pass `{ wait: false }` to `create` or `snapshots.create` to get the API's answer at once.
142
141
  `timeoutMs` bounds the wait, and `signal` aborts it. The option types document each default.
143
- `pause` and `delete` do not wait: the status changes when the API answers.
142
+ `delete` returns when the API confirms the status change.
144
143
 
145
144
  ## YAML
146
145
 
@@ -171,7 +170,7 @@ response body.
171
170
 
172
171
  | Code | Status | Meaning |
173
172
  | --- | --- | --- |
174
- | `invalid_request` | 400 | A wrong field; see `fields`. |
173
+ | `invalid_request` | 400 | A wrong field. See `fields`. |
175
174
  | `unauthenticated` | 401 | A missing, expired or revoked key. |
176
175
  | `forbidden` | 403 | The key lacks the permission. |
177
176
  | `not_found` | 404 | No such resource in this workspace. |
@@ -192,17 +191,16 @@ error envelope, and never retries them. These codes come from State Machines, in
192
191
 
193
192
  | Code | Status | Meaning |
194
193
  | --- | --- | --- |
195
- | `environment_not_running` | 409 | The environment is `paused`, `failed` or `deleted`. The body's `error.status` names the status. |
194
+ | `environment_not_running` | 409 | The environment is `failed` or `deleted`. The body's `error.status` names the status. |
196
195
  | `environment_starting` | 503 | The environment is still `starting`. The response carries `Retry-After`. |
197
196
  | `environment_unavailable` | 503 | The app is restarting or did not answer. The response carries `Retry-After`. |
198
197
  | `response_too_large` | 502 | The app's response is over the size limit of State Machines. |
199
- | `capacity_exceeded` | 429 | The app has too many requests in flight. |
200
198
  | `unsupported_protocol` | 501 | The app does not serve this API version or protocol. |
201
199
 
202
200
  ## Retries and timeouts
203
201
 
204
202
  The SDK retries `502`, `503`, `504` and connection errors with backoff, until a deadline.
205
- `create` and `snapshots.create` send one `Idempotency-Key` for every retry; pass your own
203
+ `create` and `snapshots.create` send one `Idempotency-Key` for every retry. Pass your own
206
204
  `idempotencyKey` to replay a call whose outcome you don't know. Each HTTP attempt times out after
207
205
  `attemptTimeoutMs`.
208
206
 
@@ -239,11 +237,9 @@ against it.
239
237
  | Default host `https://state-api.usestatemachines.workers.dev` | `https://api.usestatemachines.com` |
240
238
  | `sm.environments.start(input)` | `sm.environments.create(input)`, which waits until `running` |
241
239
  | `env.stop()`, `sm.environments.stop(id)` | `env.delete()`, `sm.environments.delete(id)` |
242
- | `env.suspend()`, `sm.environments.suspend(id)` | `env.pause()`, `sm.environments.pause(id)` |
243
- | `env.resume()` then `env.waitUntilReady()` | `env.resume()`, which waits until `running` |
244
240
  | `waitUntilReady`, `waitUntilStopped`, `waitUntilSuspended` | `sm.environments.wait(id, { status })` |
245
241
  | Apps as `{ key, appId }` | Apps as `{ app, name }`, or the app name as a string |
246
- | `env.connect(app, { apiId, actorId })` returns a `Connection` | `env.connect(app, { actorId })` returns `Credentials`; `fetch` picks the API from the path |
242
+ | `env.connect(app, { apiId, actorId })` returns a `Connection` | `env.connect(app, { actorId })` returns `Credentials`, and `fetch` picks the API from the path |
247
243
  | `connection.baseUrl`, `connection.headers()` | `credentials.url`, `credentials.headers`, `credentials.token` |
248
244
  | `X-State-Environment-Token` | `StateMachines-Environment-Token` |
249
245
  | `env.snapshot(options)`, `sm.snapshots.capture(environmentId, options)` | `env.snapshots.create(options)`, `sm.snapshots.create({ environmentId, ...options })` |
@@ -253,7 +249,7 @@ against it.
253
249
  | The catalog | `sm.apps.list()`, `sm.apps.versions(appId)` |
254
250
  | `TimeoutError` | `WaitTimeoutError` |
255
251
  | `error.retryAfter` | `error.retryAfterMs` |
256
- | Error codes such as `environment-limit` and `capture-in-progress` | Codes such as `environment_limit_reached` and `snapshot_in_progress`; see [Errors](#errors) |
252
+ | Error codes such as `environment-limit` and `capture-in-progress` | Codes such as `environment_limit_reached` and `snapshot_in_progress`. See [Errors](#errors) |
257
253
 
258
254
  ## License
259
255
 
package/agents/SKILL.md CHANGED
@@ -1,27 +1,56 @@
1
1
  ---
2
2
  name: state-machines
3
- description: Use when an agent or test needs a disposable replica of a SaaS app such as Salesforce, to run integration tests or try API calls against a disposable copy instead of a real org. Covers the @usestatemachines/sdk TypeScript SDK - creating environments, credentials, choosing an actor, snapshots, pausing and resuming, cleanup, and errors such as environment_limit_reached and version_unavailable. Also use when the user mentions State Machines, STATEMACHINES_API_KEY or @usestatemachines/sdk.
3
+ description: Use when setting up State Machines or running code, integration tests, or API experiments against disposable SaaS replicas such as Salesforce. Applies to @usestatemachines/sdk and STATEMACHINES_API_KEY, including environment creation, actors, snapshots, cleanup and recovery.
4
4
  ---
5
5
 
6
- Read `node_modules/@usestatemachines/sdk/README.md` before relying on memory; it matches the installed version. If the package is missing, run `npm install @usestatemachines/sdk`.
6
+ ## Start with the user's task
7
7
 
8
- ## Workflow
8
+ During skill installation, read the repository instructions and preserve its existing structure. Do not create a new project, install dependencies, write examples or create remote resources. Report the installed skill location and any reload requirement, then ask "What would you like to run or test?" and wait. Skill installation does not verify a connection.
9
9
 
10
- 1. Check that `STATEMACHINES_API_KEY` is set without printing it (`test -n "$STATEMACHINES_API_KEY"`). If it isn't, ask the user to set it; the SDK doesn't read `.env` files.
11
- 2. Create the environment with only the apps the task needs: `const env = await sm.environments.create({ apps: ['salesforce'] })`. Log `await sm.apps.list()` for each app's ID; don't guess IDs. `create` waits until the environment is `running`. If the start fails, the server deletes the environment and `create` throws `EnvironmentFailedError` with `err.environment.failure`.
12
- 3. Open a `try` right after `create` returns, and call `await env.delete()` in its `finally`. It deletes the environment and its data.
13
- 4. Get credentials for one app: `const crm = await env.connect('salesforce')`. This acts as the app's default actor, the Salesforce administrator. To act with another user's permissions, pass an `actorId` from the `actors` of the environment's version: the entry of `(await sm.apps.versions('salesforce')).data` whose `id` is `env.apps[0].versionId`. Salesforce actor IDs are its user IDs, such as `005…`, never a role name.
14
- 5. Call the app with `crm.fetch(path)`. A leading `/` means the root of the app's `url`.
10
+ Once the user chooses a task, inspect the project's package manager, runtime, runner and secret loader. Install or reuse a compatible `@usestatemachines/sdk` only when the task needs it. Read the installed README for API signatures and `package.json` for runtime requirements. The SDK is ESM and runs on Node.js and Cloudflare Workers, not in a browser. Follow the user's task and existing project conventions without requiring a separate onboarding example.
15
11
 
16
- The whole flow, as the package ships it. It is ESM with top-level `await`: save it as `state-quickstart.mts` and run `npx tsx state-quickstart.mts`.
12
+ ## Check access in the right process
13
+
14
+ The SDK reads `STATEMACHINES_API_KEY` from the process environment. It does not load `.env` files. Use the project's existing test command or secret loader. An empty variable in your current shell does not prove that the test process lacks it. Verify presence without displaying the value. In Workers, pass the binding explicitly with `new StateMachines({ apiKey: env.STATEMACHINES_API_KEY })`.
15
+
16
+ Call `sm.me()` to verify access and workspace scope before creating resources. `me.limits` can be null. If the key is missing, prepare the local test or example and tell the user the exact local file, binding, or launch command to configure and rerun, without asking for the key in chat. Do not call the API until access is available. If access is denied or the workspace differs from the task, report the blocker. Do not change membership or permissions.
17
+
18
+ Never print, log or commit API keys, `credentials.token`, `credentials.headers`, environment dumps, or secret-file contents. Use synthetic test data.
19
+
20
+ ## Run the user’s chosen task
21
+
22
+ Apply this section after setup, once the user has chosen a task.
23
+
24
+ - `sm.apps.list()` returns app IDs. Every list returns `{ data, nextCursor }`. Follow cursors until the required entry is found or `nextCursor` is null.
25
+ - Create only the apps the task needs. A string such as `'salesforce'` is both its app ID and its name in the environment. For named apps, later `connect()` calls use the name.
26
+ - `connect(appName)` uses the app's default actor, the Salesforce administrator. For another actor, find the matching app by name in `environment.apps`, read its `versionId`, then find that exact version through `sm.apps.versions(appId)`. Use an actor ID from its `actors`, never a role label, workspace member ID, or the first version in a list.
27
+ - Check the declared APIs and [known differences](https://docs.usestatemachines.com/apps/known-differences.md) for the behavior under test. A replica does not import a customer's org or run arbitrary customer automation.
28
+ - Configure the existing integration's test transport with the returned app URL and credentials. Verify its requests reach that URL. A successful standalone SDK request does not prove the integration uses the replica. If transport configuration needs a broader change, explain it before expanding the task.
29
+ - Check the expected HTTP status and assert the result. For writes, read the stored data back and assert the relevant fields. For negative tests, assert the expected error. Do not weaken an assertion to make unsupported behavior pass.
30
+
31
+ ## Own the full lifecycle
32
+
33
+ Create with `{ wait: false }` to retain the accepted environment ID before waiting. Enter `try` immediately, then wait for `running`, connect and run the check inside it. Delete in `finally`, including after a startup failure, timeout or failed assertion. An aborted wait does not cancel server work. Cleanup must use a fresh, un-aborted signal. Preserve both verification and cleanup errors, and report the environment ID if deletion fails.
34
+
35
+ Delete only environments created for this task. If the organization is at its limit, clean up a known leftover from this task or ask the user to free capacity. Do not delete unrelated environments or a user-supplied reusable snapshot.
36
+
37
+ The SDK retries management connection errors and `502`, `503`, `504` with one idempotency key per create call. To recover across separate attempts or a restart, retain the input and an explicit `idempotencyKey` before creation. Replay using the same input, key, workspace and caller. Do not create a replacement with a new key while the first outcome is unknown. See [operation recovery](https://docs.usestatemachines.com/sdk-api/recovery.md).
38
+
39
+ ## Optional connection check
40
+
41
+ Run this only when the user asks to verify the connection. It proves the connection and cleanup, not an existing integration's behavior. Save it in the project's examples or test location and run it with the existing TypeScript runner and secret loader. In a new Node.js project, `state-quickstart.mts` and `npx tsx state-quickstart.mts` are a fallback. Load the key into that process first.
17
42
 
18
43
  ```ts
19
44
  import { StateMachines } from '@usestatemachines/sdk';
20
45
 
21
46
  const sm = new StateMachines(); // reads STATEMACHINES_API_KEY
22
- const env = await sm.environments.create({ apps: ['salesforce'] });
47
+ const env = await sm.environments.create(
48
+ { apps: ['salesforce'] },
49
+ { wait: false },
50
+ );
23
51
 
24
52
  try {
53
+ await sm.environments.wait(env.id, { status: 'running' });
25
54
  const salesforce = await env.connect('salesforce');
26
55
  const res = await salesforce.fetch('/services/data/v67.0/limits');
27
56
  console.log(res.status, await res.json());
@@ -30,42 +59,34 @@ try {
30
59
  }
31
60
  ```
32
61
 
33
- ## Rules
34
-
35
- - Always delete in `finally`.
36
- - `env.pause()` releases the compute and keeps the data; it is not cleanup. `env.resume()` waits until `running` again. A paused or failed environment is deleted after one day, and still needs `env.delete()` once you are done.
37
- - To create without waiting, pass `{ wait: false }`, then `sm.environments.wait(env.id, { status: 'running' })`.
38
- - The time limit counts `starting` and `running`. `(await sm.me()).limits.timeLimitMinutes` gives its `default` and `maximum`. Set `timeLimitMinutes` in `create` for longer runs.
39
- - Running environments are capped per organization: `limits.concurrentEnvironments` from `sm.me()`. Delete the ones you no longer need. A paused or failed environment doesn't count until it resumes.
40
- - Never print, log or commit `STATEMACHINES_API_KEY`, `credentials.token` or `credentials.headers`.
41
- - Salesforce's SOAP API takes the actor token in the envelope, not a header: put `crm.token` in `SessionHeader/sessionId`. `crm.fetch` then sends only the environment token header.
42
- - `crm.fetch` never sends your API key, never follows redirects and only reaches URLs under the app's `url`.
43
- - Runtimes: Node.js, at the versions in the package's `engines` field, and Cloudflare Workers. ESM only. TypeScript needs `lib: ["DOM"]` or `@types/node`. Never ship an API key to a browser.
44
- - In Cloudflare Workers, pass the key explicitly: `new StateMachines({ apiKey: env.STATEMACHINES_API_KEY })`. A start takes longer than one request may last, so create with `{ wait: false }` and read `sm.environments.get(id)` in later requests until it is `running`, or pass a `signal` no shorter than the expected start.
45
- - Every `list` returns one page, `{ data, nextCursor }`. Pass `nextCursor` back as `cursor` until it is `null`.
46
- - To reuse seeded data, `await env.snapshots.create({ name })` once, then `sm.environments.create({ snapshotId })` in place of `apps`.
47
- - Check errors with `instanceof` (`StateMachinesError`, `EnvironmentFailedError`, `SnapshotFailedError`, `UnexpectedStatusError`, `WaitTimeoutError`), not by `name`.
48
-
49
- ## Recovering a failed create
50
-
51
- The SDK already retries `502`, `503`, `504` and connection errors with one idempotency key. If you need to replay a create whose outcome is unknown, pass your own key: `sm.environments.create(input, { idempotencyKey })`, and call it again with the same input and key. It returns the environment the first call created, or creates it if that call never arrived. A different input fails with `idempotency_key_reused`.
52
-
53
- ## Troubleshooting
54
-
55
- | Symptom | Cause | Fix |
56
- | --- | --- | --- |
57
- | `version_unavailable`, 422 | The app has no default version. | Check the app ID against `sm.apps.list()`. |
58
- | `environment_limit_reached`, 429 | The organization is at its cap of running environments. | List them with `sm.environments.list({ status: ['starting', 'running'] })`, delete or pause one, then create again. |
59
- | `invalid_status`, 409 | The status does not allow the call, for example pausing an environment that is `starting`. | Read `sm.environments.get(id)` and act on its `status`. |
60
- | `invalid_request`, 400 | A wrong field. | Read `err.fields`, keyed by dotted path. |
61
- | `EnvironmentFailedError` | The environment failed to start or resume. | Read `err.environment.failure`: `{ app, code, message }`. `invalid_settings` means the app refused its settings. |
62
- | `UnexpectedStatusError` | The environment was paused or deleted while the SDK waited. | Read `err.status` and `err.resource`. |
63
- | `WaitTimeoutError` | The status was not reached within `timeoutMs`. | Wait again with `sm.environments.wait(id, { status })`, or delete the environment. |
64
- | `snapshot_in_progress`, 409 | The environment already has a snapshot in `saving`. | `const [running] = (await sm.snapshots.list({ environmentId: env.id, status: ['saving'] })).data`, then `await sm.snapshots.wait(running.id, { status: 'ready' })`, then create another. |
65
- | `SnapshotFailedError` | The snapshot failed. `interrupted` means the environment was paused or deleted first. | Create another snapshot while the environment is `running`. |
66
- | `environment_not_running`, 409, from `crm.fetch` | The environment is paused, failed or deleted. `crm.fetch` returns it as a response and never retries. | Read `(await res.json()).error.status`; `env.resume()` a paused or failed environment. |
67
- | `environment_starting` or `environment_unavailable`, 503, from `crm.fetch` | The app is starting or restarting. | Wait the `Retry-After` seconds and call again. |
68
- | A vendor client such as jsforce is refused by the gateway | The gateway needs `StateMachines-Environment-Token` on every request. | Prefer `crm.fetch`. If you pass `crm.headers` to another client, send them only to `crm.url` and disable redirects. |
69
- | `TypeError: Pass apiKey or set STATEMACHINES_API_KEY.` | No key in the environment. | Ask the user to set `STATEMACHINES_API_KEY`; in Workers, pass `apiKey`. |
70
-
71
- More: https://docs.usestatemachines.com, https://app.usestatemachines.com/llms.txt and https://www.npmjs.com/package/@usestatemachines/sdk
62
+ ## Load details when needed
63
+
64
+ - **Repeated fixtures or parallel tests:** Read [isolated tests](https://docs.usestatemachines.com/sdk-api/testing.md). Save seeded data with `env.snapshots.create({ name })`, then create each test's environment with `{ snapshotId }`. A snapshot in `saving` cannot be deleted yet. Retain its ID and wait for `ready` or `failed` before cleanup. Delete a reusable snapshot only when the task calls for it.
65
+ - **Another actor, vendor client or SOAP:** Read [credentials](https://docs.usestatemachines.com/environments/credentials.md). `credentials.fetch()` confines requests to the app URL, adds its authentication headers, and never follows redirects. External clients must send the environment headers only to that URL and disable redirects. For Salesforce SOAP, put the actor token in `SessionHeader/sessionId` in the envelope.
66
+ - **Cloudflare Workers:** Pass the key explicitly. Use `{ wait: false }` and keep the environment ID across requests or in the job's state. A request ending does not cancel startup. Follow the worker's background-job lifecycle for polling and cleanup.
67
+ - **Long runs:** Read `sm.me().limits` when non-null. The environment time limit covers starting and running. Use a supported `timeLimitMinutes` when the task needs it.
68
+
69
+ ## Recover without changing the test's meaning
70
+
71
+ Management failures throw SDK errors. App HTTP failures are returned as `Response` objects. Use `instanceof` with the SDK error classes. Native `credentials.fetch()` never retries. Read the gateway error code and distinguish it from an app's own error.
72
+
73
+ | Failure | Next action |
74
+ | --- | --- |
75
+ | `invalid_request` | Read `err.fields` and the installed method signature. |
76
+ | `version_unavailable` | Discover the app ID and available versions. Stop if the required version is unavailable. |
77
+ | `environment_limit_reached` | Clean up only this task's known leftovers, or report the capacity blocker. |
78
+ | `EnvironmentFailedError` | Read `err.environment.failure` and clean up the owned environment. |
79
+ | `WaitTimeoutError` or abort | Use the retained environment ID. If older code waited inside `create`, a non-null `WaitTimeoutError.resource` carries its ID. If neither an ID nor a replay key is recoverable, report unresolved cleanup. Do not guess from a list. Wait again only if the task should continue. Otherwise delete the owned environment. |
80
+ | Unknown create outcome | Replay the retained input and idempotency key. Do not start another operation with a new key. |
81
+ | `invalid_status` | Read the current resource status before choosing the next call. |
82
+ | `snapshot_in_progress` | Find the saving snapshot for this environment and wait for it before creating another. |
83
+ | `SnapshotFailedError` | Report the failure. Retry only if the task still needs a snapshot and the source environment is running. |
84
+ | App `environment_not_running` | Inspect the environment. Do not silently switch the integration to another target. |
85
+ | App or gateway `503` | For a retryable read, honor `Retry-After`. For a write with an uncertain outcome, inspect stored state or report uncertainty before retrying. |
86
+ | Cleanup failure | Preserve the task result, report the owned resource ID and retry deletion with valid access and a fresh signal. Do not report cleanup as successful. |
87
+
88
+ ## Finish with evidence
89
+
90
+ Leave the smallest rerunnable test or example. Report the behavior checked, command, assertion result, SDK and app versions, selected actor, resource IDs without secrets, and cleanup outcome. Say when execution was blocked or only typechecking ran. A passing replica check establishes the asserted behavior for that version and actor, not full production compatibility.
91
+
92
+ Find other guides through https://docs.usestatemachines.com/llms.txt. Read focused pages before the full documentation bundle. This dashboard also serves an agent index at https://app.usestatemachines.com/llms.txt.