pinesandbox 0.3.18 → 0.3.20

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.
data/README.md CHANGED
@@ -1,36 +1,34 @@
1
- # PineSandbox Computer SDK (Ruby)
1
+ # Pine Computer Ruby SDK
2
2
 
3
3
  `pinesandbox` is the Ruby server SDK for Pine Computers. Create and reconnect
4
4
  Computers, run agent tasks, manage files, and authorize desktop access from
5
5
  your backend. Stop/Start restores saved cookies, learned skills, and localStorage.
6
6
  Pause/Resume retains live tabs, sessions, and memory when enabled for your project.
7
7
 
8
- See the [integration guide](https://pinesandbox.io/docs/integration) for setup,
8
+ See the [integration guide](https://console.pinecomputer.io/docs/integration) for setup,
9
9
  authentication, and examples. This README covers the Ruby API.
10
10
 
11
11
  ## Install
12
12
 
13
- Admitted users download the versioned Ruby SDK bundle from
14
- <https://pinesandbox.io/sdks/ruby>. Extract it under `vendor/`, then reference
15
- the bundle from your `Gemfile`:
16
-
17
- ```ruby
18
- gem "pinesandbox", path: "vendor/pinesandbox-ruby-sdk-<version>"
13
+ ```sh
14
+ bundle add pinesandbox
19
15
  ```
20
16
 
21
- Use the directory name of the bundle you extracted; the download page shows the
22
- exact line for the latest release.
23
-
24
- No GitHub account or package token is required. See the
25
- [integration guide §Install](https://pinesandbox.io/docs/integration#1-install)
26
- for the download and extraction commands.
17
+ Bundler pins it to the latest release's compatibility line (`"~> 0.3.<patch>"`), so
18
+ `bundle update` takes later patches but not a new minor version. Using it needs a project key (`pk_…`) from the
19
+ [Console](https://console.pinecomputer.io). Pine Computer is in beta:
20
+ [request access](https://pinecomputer.io/#access) if you don't have one yet. Each
21
+ release is also an archive on the [Ruby SDK page](https://console.pinecomputer.io/sdks/ruby),
22
+ for vendoring.
27
23
 
28
24
  The current beta SDK is on the `0.3` compatibility line.
29
25
 
30
26
  ## Create a Computer
31
27
 
32
- A **Computer** is the identity and saved browser state you keep. A **runtime** is
33
- one running allocation of it. A **session** is work inside that runtime.
28
+ A **Computer** is the identity and saved browser state you keep. A **session** is
29
+ work inside the running Computer.
30
+ [Terms](https://console.pinecomputer.io/docs/how-a-computer-works#terms) defines
31
+ the rest.
34
32
 
35
33
  The examples use `store` for your application's encrypted database; it is not an
36
34
  SDK object. Save credentials **before** Create, then save the returned binding.
@@ -49,7 +47,7 @@ store.save_credentials(credentials.id, credentials.to_json)
49
47
  computer = client.create_computer(credentials)
50
48
  store.save_binding(computer.id, computer.binding.to_json)
51
49
 
52
- session = computer.create_session(name: "main", browser: true)
50
+ session = computer.create_session # Pine picks the name: save session.name
53
51
  session.create_tab(url: "https://example.com")
54
52
  ```
55
53
 
@@ -58,11 +56,12 @@ The two records have different lifetimes:
58
56
  | Save | When | Used for |
59
57
  |---|---|---|
60
58
  | `credentials.to_json` | Before Create; again if you change keys | Starting this Computer again and decrypting its saved state |
61
- | `computer.binding.to_json` | After each successful Create or Start | Reconnecting to that exact runtime from any backend |
59
+ | `computer.binding.to_json` | After each successful Create or Start | Reconnecting to the running Computer from any backend |
62
60
 
63
61
  Both contain secrets. Keep them encrypted on your backend, never in a browser.
64
62
  For a one-off script, `client.with_computer { |computer| … }` creates and stops the
65
- Computer for you, raising if Stop cannot be confirmed.
63
+ Computer for you, raising if Stop cannot be confirmed. When the block raises, its
64
+ error propagates and an unconfirmed Stop is only warned about.
66
65
 
67
66
  ## Reuse it from another backend
68
67
 
@@ -75,12 +74,13 @@ state = computer.status # e.g. :running, :paused, :resuming
75
74
  ```
76
75
 
77
76
  Reconnect reconstructs a handle locally. It makes no request, creates nothing,
78
- and does not replay work. Its next call targets the saved runtime, even if another
79
- backend has since replaced it. Store the binding revision alongside its JSON in
80
- your existing Computer record. Save a replacement only if its revision is newer
81
- (or it is the identical binding); a delayed reply must not overwrite a newer one.
82
- After confirmed Stop, clear the binding only if the stored revision and runtime
83
- still match the one stopped. Keep that revision and the durable credentials.
77
+ and does not replay work. Its next call goes to the Computer sandbox the binding
78
+ names, even if another backend has since started the Computer again. Store the binding
79
+ revision alongside its JSON in your existing Computer record. Save a replacement
80
+ only if its revision is newer (or it is the identical binding); a delayed reply
81
+ must not overwrite a newer one.
82
+ After confirmed Stop, clear the binding only if the stored revision and sandbox
83
+ id still match the one stopped. Keep that revision and the durable credentials.
84
84
  Session records belong to a binding revision and cannot be reused after Start.
85
85
 
86
86
  To continue a session, also save its name and token when you create it, then use
@@ -93,10 +93,10 @@ worker can wait for existing work rather than submit it again. See
93
93
 
94
94
  | Intent | Call | Result |
95
95
  |---|---|---|
96
- | Finish using compute | `computer.stop` | Saves supported state, releases the runtime and ends its sessions |
97
- | Start a stopped Computer | `client.start_computer(credentials)` | Restores saved state into a new runtime; create new sessions |
98
- | Keep live memory for later | `computer.pause` | Retains the runtime and sessions; requires project opt-in |
99
- | Continue paused work | `computer.resume` | Restores the retained runtime and sessions |
96
+ | Finish using compute | `computer.stop` | Saves supported state, shuts the Computer down and ends its sessions |
97
+ | Start a stopped Computer | `client.start_computer(credentials)` | Runs the Computer from its saved state; create new sessions |
98
+ | Keep live memory for later | `computer.pause` | Keeps the Computer and its sessions in memory; requires project opt-in |
99
+ | Continue paused work | `computer.resume` | Brings back the paused Computer and its sessions |
100
100
  | Observe after a connection failure | `computer.status` | Reads state without changing or renewing it |
101
101
 
102
102
  ```ruby
@@ -107,13 +107,14 @@ computer = client.start_computer(credentials)
107
107
  store.save_binding(computer.id, computer.binding.to_json)
108
108
  ```
109
109
 
110
- Stop/Start is also how an application replaces an old runtime. An SDK upgrade or
110
+ Stop/Start is also how an application updates a Computer. An SDK upgrade or
111
111
  Pine image rollout does not replace a running Computer. Start can receive a
112
112
  compatible older warm revision during rollout; it does not guarantee a build.
113
113
 
114
- `attach_computer` remains an alias for Start. **Use Reconnect for an existing
115
- runtime.** A failed Resume does not fall back to Start. Stop/Start restores saved
116
- browser state, not live tabs, session files or page memory. Export files first.
114
+ `attach_computer` remains an alias for Start. **Use Reconnect for a Computer
115
+ that's running or paused.** A failed Resume does not fall back to Start.
116
+ Stop/Start restores saved browser state, not live tabs, session files or page
117
+ memory. Export files first.
117
118
  [Persistence and teardown](#persistence--teardown) covers completion, recovery and
118
119
  permanent deletion.
119
120
 
@@ -121,19 +122,17 @@ permanent deletion.
121
122
 
122
123
  `timeout:` is the inactivity window (default five minutes), not an HTTP request
123
124
  timeout. Work renews it; reading status, watching events and other observation do
124
- not. It starts at allocation, so a one-minute window can expire during slow setup.
125
+ not. It starts when the Computer starts, so a one-minute window can expire during
126
+ slow setup.
125
127
 
126
- Projects can opt into a lifetime policy. The standard allowance is one minute to
127
- 24 hours of idle time and seven days of maximum runtime age; approved project
128
- limits can be higher. Existing allocations retain their admitted terms.
129
- Enrollment applies to new allocations from Ruby 0.3.13 / Go 0.3.15 and later,
130
- including integrations that do not use the new policy methods. A requested
131
- timeout above the allowance, including the SDK default, is rejected. Before
132
- enrolling a project, check its integrations' requested timeouts and handling of
133
- maximum age. Deployment alone does not enroll projects or alter existing runtimes.
128
+ Projects have a lifetime policy unless Pine removed it for one. The standard
129
+ allowance is one minute to 24 hours of idle time and seven days of maximum age;
130
+ approved project limits can be higher. A project without one has no maximum age
131
+ and no idle maximum. A running Computer keeps the terms it started with.
132
+ A requested timeout above the allowance, including the SDK default, is rejected.
134
133
 
135
134
  ```ruby
136
- details = computer.runtime_details
135
+ details = computer.sandbox_details
137
136
  if details.lifecycle
138
137
  updated = computer.set_idle_timeout(seconds: 3600)
139
138
  puts updated.lifecycle.idle_timeout_seconds
@@ -141,67 +140,71 @@ if details.lifecycle
141
140
  end
142
141
  ```
143
142
 
144
- For an enrolled runtime, changing idle timeout takes effect now while running,
145
- or on the next Resume while paused. It cannot extend maximum age or snapshot
146
- retention. Legacy runtimes keep their previous expiry clock while paused; enrolled
147
- runtimes use snapshot retention and maximum age. An identical value does nothing.
143
+ For a Computer started after enrollment, changing idle timeout takes effect now
144
+ while running, or on the next Resume while paused. It cannot extend maximum age or
145
+ snapshot retention. Computers started before enrollment keep their previous expiry
146
+ clock while paused; those started after use snapshot retention and maximum age. An
147
+ identical value does nothing.
148
148
  After a conflict or lost response,
149
- read `runtime_details` before deciding whether to edit again.
149
+ read `sandbox_details` before deciding whether to edit again.
150
150
 
151
151
  Maximum age includes paused time. Graceful Stop begins twelve minutes before the
152
152
  hard deadline; expiry is not proof that a final save succeeded. Plan Stop/Start
153
153
  at a safe point before that deadline when you need uninterrupted work.
154
154
 
155
- `runtime_details` describes the current allocation, including optional lifecycle
156
- terms and its opaque `runtime_revision`. Missing lifecycle means the runtime
157
- has no admitted project lifetime policy. `client.project_policy` optionally reads
158
- allowances for future allocations; ordinary operations do not require that call.
155
+ `sandbox_details` describes the current Computer sandbox, including optional
156
+ lifecycle terms and its opaque `runtime_revision`. Missing lifecycle means the Computer
157
+ was started without a project lifetime policy. `client.project_policy` optionally reads
158
+ allowances for future starts; ordinary operations do not require that call.
159
159
 
160
- `ready_timeout:` bounds allocation/readiness and `bind_ready_timeout:` bounds the
161
- following handshake. They do not change runtime expiry. For example,
162
- `timeout: 28_800, ready_timeout: 120` requests eight hours of idle time with a
163
- two-minute readiness wait. These waits are separate from transport retries and
164
- cleanup; see the [integration guide](https://pinesandbox.io/docs/integration).
160
+ `ready_timeout:` bounds waiting for the Computer to start and become ready, and
161
+ `bind_ready_timeout:` bounds the following handshake. They do not change the
162
+ Computer's expiry. For example, `timeout: 28_800, ready_timeout: 120` requests
163
+ eight hours of idle time with a two-minute readiness wait. These waits are
164
+ separate from transport retries and cleanup; see the
165
+ [integration guide](https://console.pinecomputer.io/docs/integration).
165
166
 
166
167
  ## Handle failures without replacing live work
167
168
 
168
- - A network error or `alive? == false` does not prove the runtime is gone.
169
- Read `status`; its `NotFoundError` confirms runtime-record absence. A
170
- data-plane `RuntimeGoneError` alone does not: paused/restoring Computers can
169
+ - A network error or `alive? == false` does not prove the Computer has stopped.
170
+ Read `status`; its `NotFoundError` confirms that it has. A
171
+ data-plane `SandboxGoneError` alone does not: paused/restoring Computers can
171
172
  temporarily have no route.
172
- - `RuntimeRetainedError` (a `BindError`) means Start found an existing runtime.
173
- Match its `runtime_id` to your saved binding, or reload the latest binding from
173
+ - `SandboxRetainedError` (a `BindError`) means Start found the Computer still
174
+ running or paused.
175
+ Match its `sandbox_id` to your saved binding, or reload the latest binding from
174
176
  your database. Reconnect, Resume or finish Stop as appropriate; an ID alone
175
177
  cannot reconstruct a missing token.
176
178
  - A Stop timeout means completion is unconfirmed, not canceled. Keep the binding
177
179
  and observe or retry Stop. Older servers report `StopSaveError` for a failed or
178
- unconfirmed save; current servers still release the runtime.
180
+ unconfirmed save; current servers still shut the Computer down.
179
181
  - Serialize Create/Start per Computer in your application. On
180
182
  `BindingRevisionConflictError`, reload the winner's saved binding instead of
181
183
  retrying with a higher revision.
182
184
  - If a registered Computer's Start reply is lost before saving its binding,
183
185
  serialize recovery, call `client.stop_computer(id)`, then Start separately with
184
- the saved credentials. This releases the runtime whose token was lost.
186
+ the saved credentials. This stops the Computer whose token was lost.
185
187
  - An interrupted first Create can fail before registering the Computer. Stop-by-ID
186
188
  then returns true, but Start raises `UnknownComputerError`. For that initial
187
189
  creation, retry Create with your saved credentials after confirming the project
188
190
  and ID are correct and the identity was not deleted. Do not treat every unknown
189
- ID as permission to create. An allocation abandoned before registration may
190
- remain until expiry; Stop-by-ID cannot discover it.
191
+ ID as permission to create. A Computer sandbox started before registration may
192
+ run until it expires; Stop-by-ID cannot find it.
191
193
  - On `AttachAuthorizationCommittedError`, registration has succeeded. Retain its
192
194
  non-nil credentials and reconcile the registered Computer before Start. Private
193
195
  keys cannot be recovered from an ID.
194
196
 
195
- Allocation retries within one call reuse its create ID. A fresh call is a new
196
- attempt. Failed attach attempts clean up known allocations best-effort; process
197
- crashes rely on expiry. Longer requested timeouts can leave abandoned compute
198
- running longer. See [Errors](#errors) for the error types.
197
+ Retries within one call reuse its create ID. A fresh call is a new attempt. A
198
+ failed attach shuts down the Computer sandbox it started, on a best-effort
199
+ basis; after a process crash, that Computer sandbox runs until it expires.
200
+ Longer requested timeouts can leave abandoned compute running longer. See
201
+ [Errors](#errors) for the error types.
199
202
 
200
203
  ## Stored credentials and older integrations
201
204
 
202
205
  The project API key stays on your backend. Computer credentials contain the state
203
206
  key, current and retained capture keys, prior state keys and ephemeral mode.
204
- Bindings contain the runtime ID, Computer token and binding revision. The SDK
207
+ Bindings contain the sandbox ID, Computer token and binding revision. The SDK
205
208
  handles short-lived project and attach authorization internally.
206
209
 
207
210
  `computer.credentials` returns an independent copy. Credentials and bindings use
@@ -219,22 +222,41 @@ For integrations using older storage or field-based methods:
219
222
  - Starting from a credentials value requires custom credential providers to
220
223
  implement `current_binding` and `credentials`. Bare callables still support
221
224
  Create and the field-based Attach form with an explicit binding revision.
222
- - `control_plane` and sandbox handles are SDK internals. Integrations use the
223
- Computer methods above.
225
+ - `control_plane` and the handles it returns are SDK internals. Integrations use
226
+ the Computer methods above.
224
227
 
225
228
  ## Sessions
226
229
 
227
- A session is the unit of work — a browser context + bash terminal. Create one, then
228
- drive it (next two sections). A Computer runs up to 3 sessions at once by default;
230
+ A session is the unit of work: its own browser window and bash terminal. Create one,
231
+ then drive it (next two sections). A Computer runs up to 3 sessions at once by default;
229
232
  `computer.capabilities` reports the limit (`sessions.concurrent`).
230
233
 
231
234
  ```ruby
232
- session = computer.create_session(name: "researcher-1", browser: true)
235
+ session = computer.create_session(name: "researcher-1")
233
236
  computer.session("researcher-1") # metadata by name; token is redacted
234
237
  computer.sessions # list all on this Computer
235
238
  computer.destroy_session("researcher-1") # tear down (keeps on-disk dir; clean: true to GC)
236
239
  ```
237
240
 
241
+ A session's agent can use the Computer's [artifacts](#artifacts) only when you create
242
+ the session with `artifact_access: "read_only"` (list and read files) or
243
+ `artifact_access: "read_write"` (also save and delete files and make public links).
244
+ The default is `"none"`. `session.artifact_access` reports the access it was created
245
+ with, and is `nil` for a session from `computer.adopt_session`, whose access only the
246
+ Computer knows (`computer.session(name).artifact_access` reads it). Your own access
247
+ with the project key, through `client.artifacts`, is the same either way.
248
+
249
+ ```ruby
250
+ session = computer.create_session(name: "analyst", artifact_access: "read_only")
251
+ session.artifact_access # => "read_only"
252
+ ```
253
+
254
+ A person who must type a password or an MFA code takes control of the session
255
+ (`session.take_control`, then `release_control`) and types it over the desktop while
256
+ the agent waits. A secret your application already holds goes in the task instead
257
+ (`session.agent.run(goal, secrets: {...})`): the agent reads it from
258
+ `$SESSION_SECRETS`.
259
+
238
260
  ## Drive — Pine's agent (hand off the loop)
239
261
 
240
262
  For an attached Computer, `computer.require_agent_protocol!` checks public agent
@@ -312,7 +334,7 @@ same.
312
334
 
313
335
  `run` raises `409` if a turn is already running, or a person or your own agent
314
336
  holds the drive slot. Routes: `/v2/sessions/{name}/agent/*` in the
315
- [Computer API reference](https://pinesandbox.io/spec/computer).
337
+ [Computer API reference](https://console.pinecomputer.io/spec/computer).
316
338
 
317
339
  ### Activity
318
340
 
@@ -350,11 +372,11 @@ observation; do not guess from nearby events. A text-only update leaves the last
350
372
  captured screen and its original attribution visible. Keep historical selection
351
373
  separate from following new frames; the event ID identifies each image for replay.
352
374
 
353
- This SDK requires a Computer runtime with the public V2 agent contract. There is
375
+ This SDK requires a Computer that serves the public V2 agent contract. There is
354
376
  no fallback to native trace. Activity includes progress, questions and results;
355
377
  native commands, reasoning and provider metadata are internal-only. Upgrade
356
- agent consumers before moving them to the new runtime: its legacy agent routes
357
- reject ordinary bindings.
378
+ agent consumers before you restart their Computers on the new release: its legacy
379
+ agent routes reject ordinary bindings.
358
380
 
359
381
  Paged history uses the same typed events as streaming. Scope every saved cursor
360
382
  to its Task; after a session replacement, discover the new Task before replaying
@@ -387,47 +409,82 @@ drive.upload_file("input[type=file]", "resume.pdf") # set a file input (deposi
387
409
 
388
410
  `observe` is read-only (allowed even while Pine's agent runs — you can watch);
389
411
  `computer_use` / `upload_file` take the drive slot (`409` if Pine's agent or a person
390
- holds it). Pine's resident agent can use private DOM/browser tools through
391
- `session.delegate`; direct customer shell execution runs public programs and
392
- works with session files. It cannot invoke Pine's private browser implementation.
393
- Raw CDP is an image-owned runtime transport and is intentionally absent from
394
- the SDK surface. Tab helpers
412
+ holds it). Shell commands run ordinary programs and work with the session's
413
+ files. Tab helpers
395
414
  (`create_tab`/`tabs`/`patch_tab`/`close_tab`), `focus!`, and `epoch` round out the manual
396
415
  surface. Public routes are defined by the
397
- [Computer API reference](https://pinesandbox.io/spec/computer).
416
+ [Computer API reference](https://console.pinecomputer.io/spec/computer).
398
417
 
399
- ## Files & artifacts
418
+ ## Files
400
419
 
401
- A session has a **workdir** (where `exec` runs and the agent works), an inbound
402
- **attachments** root (files you hand the agent), and an **artifact registry** (the
403
- session's managed outputs).
420
+ A session has a **workdir** (where `exec` runs and the agent works) and an inbound
421
+ **attachments** root (files you hand the agent), which appears inside the workdir as
422
+ `attachments/`.
404
423
 
405
424
  ```ruby
406
425
  # Read the workdir (what the agent produced / your shell wrote):
407
426
  session.list_files(pattern: "*.csv") # => [FileEntry, ...]
408
427
  session.read_file("out/report.csv") # => bytes (binary encoding)
428
+ session.open_file("out/export.zip") { |chunk| io.write(chunk) } # stream it without buffering
409
429
 
410
- # Deposit a material the agent should use. It lands in attachments/ and surfaces
411
- # to the agent via the materials preamble — a sibling of the workdir, so it is NOT
412
- # read_file-able (read_file serves the workdir).
430
+ # Give the agent an input file. It lands in attachments/ (readable as
431
+ # read_file("attachments/brief.md")), and the agent sees it when its next turn starts.
413
432
  session.write_file("brief.md", File.read("brief.md"))
414
433
 
415
- # Artifacts — the session's managed outputs, each with metadata + a download.
416
- art = session.upload_artifact("logo.png", File.binread("logo.png")) # => Models::Artifact
417
- session.list_artifacts # all, newest-first
418
- session.list_artifacts(turn_id: "turn_…") # just one agent turn's outputs
419
- session.read_artifact(art.id) # => bytes
420
- session.zip_artifacts # => a .zip of all artifacts (bytes)
421
- session.open_artifact(art.id) { |chunk| io.write(chunk) } # stream it without buffering
434
+ # The files an ended turn produced, by workdir path:
435
+ turn.result.artifacts.each do |file|
436
+ path = file.fetch("relative_path")
437
+ save(path, session.read_file(path))
438
+ end
422
439
  ```
423
440
 
424
- `created_by` names the producer: `upload` (you), `download` (a browser download,
425
- auto-indexed), `agent` (a turn's outputs, durably captured at the turn's result). Only
426
- `agent` artifacts carry a `turn_id`, so the `turn_id` filter scopes to one turn's
427
- outputs; uploads/downloads always appear unfiltered. Uploads are immutable +
428
- id-addressed (a repeated filename yields a distinct artifact). Routes: `/files`,
429
- `/artifacts[/{zip,artifact_id}]` in the
430
- [Computer API reference](https://pinesandbox.io/spec/computer).
441
+ A turn's result lists its files by `relative_path` in the workdir, with size, SHA-256
442
+ and media type. They aren't copies: read them when the turn ends, because a later turn
443
+ or shell command can change them. A browser download isn't one of them; to get one
444
+ back, ask for it in the goal, and the agent copies it into the workdir and lists it.
445
+ Without a block, `open_file` returns an Enumerator of the chunks. Session files last
446
+ until the Computer stops; save what you need first. Routes: `/files` in the
447
+ [Computer API reference](https://console.pinecomputer.io/spec/computer).
448
+
449
+ ## Artifacts
450
+
451
+ A Computer's **artifacts** are the files it keeps beyond its sessions. Reach them
452
+ with the project key, whether the Computer is running or stopped. A session's agent
453
+ can use them only when the session was created with `artifact_access: "read_only"`
454
+ or `"read_write"` (see [Sessions](#sessions)); that choice doesn't change your own
455
+ access here.
456
+
457
+ ```ruby
458
+ artifacts = client.artifacts(computer_id)
459
+
460
+ page = artifacts.list(prefix: "research/") # one page: files, folders, next_page_token
461
+ page.files.each { |file| puts "#{file.path} #{file.size} #{file.note}" }
462
+
463
+ artifacts.upload("inputs/brief.pdf", Pathname("brief.pdf"), note: "the brief")
464
+ artifacts.upload("inputs/notes.txt", "plain bytes")
465
+ artifacts.download("reports/q3.pdf", "q3.pdf") # => bytes written
466
+ artifacts.open("reports/q3.csv") { |chunk| io.write(chunk) }
467
+ artifacts.copy(other_computer_id, "templates/form.pdf", "inputs/form.pdf")
468
+ artifacts.delete("inputs/notes.txt")
469
+
470
+ # A public link, served by the running Computer until it stops:
471
+ computer.artifact_link("reports/q3.pdf", expires_in_seconds: 86_400) # => { "url" => ..., "expires_at" => ... }
472
+ ```
473
+
474
+ The SDK gets one short-lived storage token from Pine and sends the files straight to
475
+ and from storage, never through Pine; it renews the token before it lapses. An upload
476
+ takes a String of bytes, a `Pathname` or an IO with a known size, and resumes after a
477
+ dropped connection; without `content_type:` it is typed by its path's extension, the
478
+ same way in every Pine SDK. A download writes beside the destination and renames into
479
+ place. `download` and `open` resume after a dropped connection, a request timeout, a
480
+ 429 or a 5xx other than 501, from where they stopped and from the same version of the
481
+ file (one replaced meanwhile raises `PineSandbox::PreconditionFailedError`). `copy` also copies within the
482
+ Computer, given its own id. A missing file raises `PineSandbox::ArtifactNotFoundError`
483
+ (`#path`); while the Computer's artifacts are over their limit, `upload` and `copy`
484
+ raise `PineSandbox::ArtifactStorageFullError` without sending anything, and check
485
+ again on the next call, so deleting files makes room. `open` passes each chunk on as it
486
+ arrives with faraday-net_http 3.0 or later; an older faraday-net_http gives the body
487
+ only once it is complete, so `open` writes it to a temporary file first.
431
488
 
432
489
  ## Skills — `learn` / `teach` (and when each blocks)
433
490
 
@@ -477,20 +534,20 @@ gate: nothing learned reaches another agent until you activate it.
477
534
 
478
535
  ```ruby
479
536
  computer.checkpoint # synchronous state save (content-hash dedup) — before a risky step
480
- computer.stop # save cookies, learned skills and localStorage, then delete the sandbox
481
- computer.kill # delete without an acknowledged save
537
+ computer.stop # save cookies, learned skills and localStorage, then shut the Computer down
538
+ computer.kill # shut the Computer down without waiting for a save
482
539
  client.delete_computer(computer) # PERMANENT: kill if attached, then delete id + saved state
483
540
  ```
484
541
 
485
542
  `stop` and `kill` keep the Computer's identity and saved state; `delete_computer`
486
543
  (a `Computer` or its id) ends both. The id is refused for every later attach, saved
487
- state is purged after 24 hours, and the id is never reusable. Deleting an unknown,
544
+ state is purged after 25 hours, and the id is never reusable. Deleting an unknown,
488
545
  already-deleted, or other project's id also returns `true`. There is no undelete.
489
546
 
490
- `stop` submits graceful Stop to the runtime controller, then waits up to 390s
491
- (`wait_timeout:`) for compute release. The controller tries to save supported state
547
+ `stop` submits graceful Stop to Pine's controller, then waits up to 390s
548
+ (`wait_timeout:`) for the Computer to shut down. The controller tries to save supported state
492
549
  before deletion and continues if the SDK process disappears. Saving is best effort:
493
- a save that fails or is not confirmed by the deadline still releases the runtime,
550
+ a save that fails or is not confirmed by the deadline still shuts the Computer down,
494
551
  and `stop` returns true; the next Start restores the last saved state. Reconstruct
495
552
  the saved handle with `reconnect_computer` to observe or join the same operation from
496
553
  another backend. Only an older server keeps the source and raises
@@ -498,49 +555,49 @@ another backend. Only an older server keeps the source and raises
498
555
  failure from an unconfirmed outcome after the Computer's services restart.
499
556
  A `false` return means completion is unconfirmed. Keep the saved binding for retry.
500
557
  After Stop returns true, clear the stored binding only if its revision and
501
- runtime still match the handle stopped; a concurrent Start may have replaced it.
558
+ sandbox id still match the handle stopped; a concurrent Start may have replaced it.
502
559
  Keep the revision fence, identity, state key and capture keypairs for future Start. Ephemeral
503
- bindings skip persistence; the runtime's binding record decides this.
560
+ bindings skip persistence; Pine's binding record decides this.
504
561
 
505
- If a Start response was lost before saving the runtime token, use
506
- `client.stop_computer(id)` to stop the currently bound runtime with project
562
+ If a Start response was lost before saving the Computer token, use
563
+ `client.stop_computer(id)` to stop the Computer with project
507
564
  authorization alone, then call Start separately. This lookup selects one binding;
508
565
  it never follows a concurrent newer binding. Private keys remain necessary for
509
566
  Start and cannot be recovered from the ID.
510
567
 
511
- A true result proves compute release or authoritative runtime absence. It does
512
- not prove a final save by a runtime that disappeared, and provider snapshot cleanup
513
- can still be running. Start restores only the latest committed state. Cookies,
514
- learned skills and compatible localStorage survive supported saved-state recovery;
515
- tabs, session files, page memory and IndexedDB do not.
568
+ A true result proves the Computer has shut down or was already gone.
569
+ It does not prove a final save when the Computer had already gone, and provider
570
+ snapshot cleanup can still be running. Start restores only the latest committed
571
+ state. Cookies, learned skills and compatible localStorage survive supported
572
+ saved-state recovery; tabs, session files, page memory and IndexedDB do not.
516
573
 
517
- `start_computer` reads the current binding revision once, checks that the old
518
- runtime is gone or released, waiting for an already accepted deletion if needed,
574
+ `start_computer` reads the current binding revision once, checks that the
575
+ Computer has stopped, waiting for an already accepted shutdown if needed,
519
576
  and creates a new binding. A conflict stays a conflict;
520
577
  it never adopts a newer revision and displaces another worker's winner. For a
521
- recorded failed runtime, explicitly call
522
- `client.recover_computer(id, key: key, capture_keypair: keypair)`.
523
- Recovery accepts possible unsaved-state loss, releases the observed failed runtime
524
- with a version precondition, waits up to 90s for compute release, then Starts. It
525
- refuses healthy running/paused runtimes and an unresolved capture timeout. If
526
- release times out, retry later; no replacement has been allocated. `kill` remains
578
+ Computer you recorded as failed, explicitly call
579
+ `client.recover_computer(credentials)`.
580
+ Recovery accepts possible unsaved-state loss, shuts the failed Computer down
581
+ with a version precondition, waits up to 90s for it to shut down, then Starts. It
582
+ refuses healthy running or paused Computers and an unresolved capture timeout. If
583
+ shutting it down times out, retry later; nothing new has been started. `kill` remains
527
584
  an unconditional destructive operation.
528
585
 
529
- This SDK's retryable allocation and server-owned Stop require the updated backend
530
- and a compatible Computer runtime; neither requires Pause opt-in. Existing
586
+ This SDK's retryable Start and server-owned Stop require the updated backend
587
+ and a compatible Computer; neither requires Pause opt-in. Existing
531
588
  Computer create, capture/Finish and delete calls remain supported for applications
532
589
  keeping their prior SDK. Complete the backend rollout before upgrading, including
533
590
  the conditional-release support used by recovery. A pool image update does not
534
591
  upgrade an already-running Computer: stop old-image bindings with the prior SDK
535
592
  before upgrading their caller. Integrations using the removed generic
536
- public sandbox operations must migrate to the Computer contract; the private Ruby
593
+ public lifecycle operations must migrate to the Computer contract; the private Ruby
537
594
  `ControlPlane#create` adapter is removed as well.
538
595
 
539
- For projects opted in to Pause/Resume on snapshot-capable runtimes:
596
+ For projects opted in to Pause/Resume, on Computers that support snapshots:
540
597
 
541
598
  ```ruby
542
599
  computer.pause(timeout: 360) # waits for capture and confirmed source stop
543
- computer.resume(timeout: 360) # waits for restored routing and runtime recovery
600
+ computer.resume(timeout: 360) # waits until the Computer is restored and reachable
544
601
  ```
545
602
 
546
603
  Pause is disabled by default. Pine enables it for a project on request;
@@ -548,41 +605,45 @@ refresh the project token after it is enabled. Disabling the
548
605
  feature prevents new pauses but preserves Resume and Stop for retained memory.
549
606
  Ordinary Stop/Start remains available without the opt-in.
550
607
 
551
- The same sandbox handle, session capabilities and retained process memory remain.
552
- A pool image update does not change the captured runtime. Calls include acceptance
608
+ The same Computer handle, session capabilities and retained process memory remain.
609
+ A pool image update does not change a paused Computer. Calls include acceptance
553
610
  and completion in one bounded timeout. A timeout leaves the outcome unconfirmed;
554
- retry against the retained handle. The controller restores the same paused
555
- runtime before Stop can save it. Snapshot retention and admitted maximum age still apply; legacy expiry is unchanged.
611
+ retry against the retained handle. The controller restores the paused Computer
612
+ before Stop can save it. Snapshot retention and admitted maximum age still apply;
613
+ legacy expiry is unchanged.
556
614
 
557
615
  Do not rescue a failed `resume` by automatically calling `start_computer`.
558
616
  Retrying Resume keeps the existing memory-recovery intent; Start explicitly
559
617
  accepts recovery from the last saved state with new sessions and possible loss
560
618
  of unsaved work. An unavailable status or timeout alone does not prove that
561
- memory is lost. The [Computer API reference](https://pinesandbox.io/spec/computer)
619
+ memory is lost. The [Computer API reference](https://console.pinecomputer.io/spec/computer)
562
620
  defines the observed states and recovery choices: retry Resume, refresh the
563
621
  status, or Start from saved state.
564
622
 
565
623
  ## Errors
566
624
 
625
+ [Troubleshooting](https://console.pinecomputer.io/docs/troubleshooting) says what
626
+ each common error means and how to fix it.
627
+
567
628
  Three typed families, one per credential exchange (see `lib/pinesandbox/errors.rb`):
568
629
 
569
- Data-plane runtime loss raises `PineSandbox::RuntimeGoneError`; it preserves the
570
- wire diagnostics and original `ApiError` cause. Errors about a resource on a
571
- live Computer remain distinct, including `PineSandbox::SessionNotFound` and
572
- `PineSandbox::ControlNotHeld`.
630
+ A Computer that can no longer be reached raises `PineSandbox::SandboxGoneError`;
631
+ it preserves the wire diagnostics and original `ApiError` cause. Errors about a
632
+ resource on a live Computer remain distinct, including
633
+ `PineSandbox::SessionNotFound` and `PineSandbox::ControlNotHeld`.
573
634
 
574
635
  - **`BindError`** — `Computer#attach` outcomes: `BindAuthError`,
575
- `ComputerAlreadyAttachedError`, `RuntimeUnusableError`, `SavedStateUnavailableError`,
636
+ `ComputerAlreadyAttachedError`, `SandboxUnusableError`, `SavedStateUnavailableError`,
576
637
  `BindTimeoutError`; plus `TokenRejectedError` on a post-bind 401 for the
577
- Computer token (`ct_` not recognized — on a live runtime it is
578
- `binding_auth_lost`; re-attach only when the runtime is confirmed gone). A 401
638
+ Computer token (`ct_` not recognized — on a running Computer it is
639
+ `binding_auth_lost`; re-attach only when the Computer is confirmed stopped). A 401
579
640
  for a session token is an ordinary `UnauthorizedError`. If `create_computer` raises
580
- `AttachAuthorizationCommittedError`, retain its non-nil `#credentials` and inspect `#runtime_id` before deciding
581
- whether to stop or recover the failed runtime. Credential inspection redacts private material.
641
+ `AttachAuthorizationCommittedError`, retain its non-nil `#credentials` and inspect `#sandbox_id` before deciding
642
+ whether to stop or recover the failed Computer. Credential inspection redacts private material.
582
643
  - **`AttachCredentialsError`** — the portal attach-credentials flow:
583
644
  `UnknownComputerError` (404 — deleted or cross-project),
584
645
  `ProjectAccessDenied` (403), and `BindingRevisionConflictError` (412 — another start changed the binding; `#current_binding_revision` /
585
- `#current_runtime_id` identify the winner. Do not retry automatically). A bad `pk_` (401),
646
+ `#current_sandbox_id` identify the winner. Do not retry automatically). A bad `pk_` (401),
586
647
  rate limit (429), and server errors are the base `AttachCredentialsError`.
587
648
  Inspect the stable problem `#problem_type` and optional safe `#reason`; raw
588
649
  internal exceptions are never part of the API contract.
@@ -591,9 +652,9 @@ live Computer remain distinct, including `PineSandbox::SessionNotFound` and
591
652
  A `Retry-After` longer than the mint's 10-second backoff cap ends the mint at
592
653
  once with that error instead of retrying early.
593
654
 
594
- The old names `SandboxGoneError`, `SandboxFailedError`, `PodPoisonedError`,
595
- `BrokerUnreachableError`, `SandboxHandle` and the `sandbox_id` readers keep
596
- working as deprecated aliases; they warn when Ruby's deprecation warnings are on.
655
+ The old names `PodPoisonedError` (now `SandboxUnusableError`) and
656
+ `BrokerUnreachableError` (now `SavedStateUnavailableError`) keep working as
657
+ deprecated aliases; they warn when Ruby's deprecation warnings are on.
597
658
 
598
659
  ## Troubleshooting — reporting an issue
599
660
 
@@ -601,7 +662,7 @@ The SDK supports saved Computer diagnostics. They require the diagnostics backen
601
662
 
602
663
  ```ruby
603
664
  # Persist one key per logical request if you need to retry after a lost response.
604
- report = client.create_diagnostic(computer_id: saved_id, session: "main",
665
+ report = client.create_diagnostic(computer_id: saved_id, session: saved_session_name,
605
666
  idempotency_key: diagnostic_request_key)
606
667
  support_reference = report.fetch("id")
607
668
  # Read later. Only poll queued/running reports, with a bounded deadline.
@@ -628,17 +689,17 @@ begin
628
689
  session.agent.run("book the 9am slot")
629
690
  rescue PineSandbox::ApiError => e
630
691
  logger.error(e.message)
631
- # => "… (runtime=sbx_abc123, op=POST /v2/sessions/main/agent/run, request_id=req_abc123)"
692
+ # => "… (sandbox=<sandbox-id>, op=POST /v2/sessions/main/agent/run, request_id=req_abc123)"
632
693
  Report.open(computer_id: computer.id, session: session.name,
633
694
  at: Time.now.utc, request_id: e.request_id, op: e.op)
634
695
  end
635
696
  ```
636
697
 
637
698
  Each error class exposes exactly its own typed context (the message always carries the same
638
- data): Computer API **`ApiError`** → `#runtime_id` / `#op` / `#request_id`; control-plane errors
699
+ data): Computer API **`ApiError`** → `#sandbox_id` / `#op` / `#request_id`; control-plane errors
639
700
  → `#op` / `#request_id`; portal **`ControlTokenError`** / **`AttachCredentialsError`** → `#op`
640
701
  / `#request_id`. The suffix grammar is uniform `key=value` across the Go and Ruby SDKs; Ruby
641
- Computer API errors use **`runtime=`** because the Ruby SDK holds the runtime id, where the Go SDK
702
+ Computer API errors use **`sandbox=`** because the Ruby SDK holds the sandbox id, where the Go SDK
642
703
  uses **`host=`** because its client holds the Computer host (same Computer, different handle
643
704
  each SDK already has).
644
705
 
@@ -655,10 +716,10 @@ to find the failure:
655
716
 
656
717
  ## Known limitations
657
718
 
658
- - Pause/Resume requires a qualified snapshot-capable Computer runtime.
659
- Other runtimes reject it as unsupported. Snapshot loss, expiry or
660
- failed recovery surfaces an error; it never silently falls back to a fresh
661
- runtime. Enrolled runtimes use snapshot retention and maximum age while paused; legacy expiry is unchanged.
719
+ - Pause/Resume requires a Computer that supports snapshots; others reject it as
720
+ unsupported. Snapshot loss, expiry or failed recovery surfaces an error; it
721
+ never silently falls back to a fresh start. Computers started after enrollment
722
+ use snapshot retention and maximum age while paused; legacy expiry is unchanged.
662
723
 
663
724
  ## Develop
664
725