@shardflux/sdk 0.15.0 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/CHANGELOG.md +42 -22
  2. package/README.md +99 -1
  3. package/dist/account.d.ts +2 -0
  4. package/dist/account.js +6 -0
  5. package/dist/cell.d.ts +2 -0
  6. package/dist/cell.js +2 -0
  7. package/dist/client.d.ts +25 -1
  8. package/dist/client.js +44 -3
  9. package/dist/computer.d.ts +11 -1
  10. package/dist/computer.js +89 -6
  11. package/dist/errors.d.ts +5 -1
  12. package/dist/errors.js +9 -0
  13. package/dist/executions.d.ts +2 -6
  14. package/dist/executions.js +9 -0
  15. package/dist/exit-code.d.ts +7 -0
  16. package/dist/exit-code.js +12 -0
  17. package/dist/generated/app-api.d.ts +565 -13
  18. package/dist/generated/cell-api.d.ts +2 -0
  19. package/dist/http.d.ts +7 -1
  20. package/dist/http.js +34 -13
  21. package/dist/index.d.ts +8 -5
  22. package/dist/index.js +4 -2
  23. package/dist/ports.d.ts +7 -0
  24. package/dist/ports.js +1 -1
  25. package/dist/progress.d.ts +2 -2
  26. package/dist/progress.js +1 -1
  27. package/dist/testing/index.d.ts +62 -0
  28. package/dist/testing/index.js +585 -0
  29. package/dist/testing/seed.d.ts +433 -0
  30. package/dist/testing/seed.js +449 -0
  31. package/dist/tools.d.ts +5 -0
  32. package/dist/tools.js +4 -2
  33. package/dist/tunnel-assets/linux-amd64.gz +0 -0
  34. package/dist/tunnel-assets/linux-arm64.gz +0 -0
  35. package/dist/tunnel-assets.d.ts +10 -0
  36. package/dist/tunnel-assets.js +11 -0
  37. package/dist/tunnel-packet.d.ts +3 -0
  38. package/dist/tunnel-packet.js +43 -0
  39. package/dist/tunnel-pty.d.ts +86 -0
  40. package/dist/tunnel-pty.js +243 -0
  41. package/dist/tunnels.d.ts +47 -0
  42. package/dist/tunnels.js +454 -0
  43. package/dist/workspace-ref.d.ts +4 -0
  44. package/dist/workspace-ref.js +24 -0
  45. package/dist/workspace.d.ts +18 -1
  46. package/dist/workspace.js +76 -3
  47. package/package.json +7 -2
package/CHANGELOG.md CHANGED
@@ -3,7 +3,31 @@
3
3
  Every API the README shows is available from the version named here. Breaking changes ship in minor releases and are
4
4
  marked **Breaking**.
5
5
 
6
- ## 0.15.0 (not yet published)
6
+ ## 0.16.0 (not yet published)
7
+
8
+ - Add `@shardflux/sdk/testing`: `createFakeShardflux` supplies an isolated in-memory transport beneath real SDK handles, refs and errors, with scripted commands, a virtual clock, fault controls and captured response shapes. A shared scenario manifest runs against the fake and live API.
9
+
10
+ - Add opt-in persistent workspace idle deletion retention, workspace/project setters and projected deletion times.
11
+
12
+ - Accelerate reverse tunnels with a bounded, acknowledged binary WebSocket stream, 64 KiB frames and batched credits. Select `auto`, `pty` or `exec` transport; reconnect preserves byte offsets. Opt-in binary PTY output accepts larger blocks for file transfers.
13
+
14
+ - Add opt-in resize controls to grow named resources only and return the held server answer without polling a pending operation.
15
+ - Expose typed stored workspace caps and preserve nullable grants. Fixed memory without an explicit size uses the template default.
16
+
17
+ - `WorkspaceRef.turn(fn, { afterSeconds: 0 })` and `Workspace.turn()` hint without waiting and request idle suspension when the body settles, including errors.
18
+ - Computer `act()` avoids an extra screenshot after `screenshot`/`zoom`. Viewer links are reused by kind with more than 60 seconds left; `fresh: true` remints, and `stopStream()` clears them.
19
+ - `computerToolset(ws, { beforeAction })` and `workspaceTools(ws, { computer: { beforeAction } })` check each action immediately before execution; refusal answers that action with an error and skips the rest.
20
+ - The default pooled transport honors environment HTTP(S) proxies and NO_PROXY. `SHARDFLUX_BASE_URL` aliases `SHARDFLUX_API_URL`, which takes precedence.
21
+ - Exec and execution results expose `exitCodePosix`. Missing cell file paths with `path_not_found` raise `FileNotFoundError`, distinct from workspace deletion and protocol errors.
22
+ - Embed live desktop viewers and port previews in a web app with an explicit HTTPS origin allowlist and partitioned browser sessions.
23
+
24
+ - Add `workspace.tunnels.reverse({ remotePort, target, bindAddress?, signal? })`: a private workspace TCP listener forwarded to this machine, with binary multiplexing, bounded backpressure, half-close, transport reconnection, suspend/resume, statistics and deterministic close.
25
+
26
+ - Add `ws.upgrade({ at })`, `upgradeAvailable` and `upgradePending`. An opt-in cold start preserves the workspace disk; `next_resume` schedules it. Lifecycle cold boot reasons include `upgraded`.
27
+
28
+ ### Integration testing
29
+
30
+ ## 0.15.0 — 2026-10-04
7
31
 
8
32
  ### Workspaces by key
9
33
 
@@ -45,12 +69,10 @@ Additive. A desktop in the workspace that agents drive with screenshots, clicks
45
69
  - `cloud.templates.setComputerUse(slug, enabled, { organizationId })` switches it for every workspace of one of your
46
70
  templates. `ToolName` includes `computer`; template views carry `computer_use`.
47
71
 
48
- ## 0.14.0 — 2026-10-04 (not yet published)
72
+ ## 0.14.0 — 2026-10-04
49
73
 
50
74
  - Opt-in `workspaceTools(workspace, { prewake: true })` input-start hooks prepare VM tools while their input streams. Default tools, offline reads and file-first workspaces install no hook.
51
75
 
52
- ## 0.14.0 (not yet published)
53
-
54
76
  ### Sign up from an agent
55
77
 
56
78
  Additive (API contracts §44).
@@ -159,7 +181,7 @@ gives a static allocation of `memory_mib`. Reopening an existing workspace with
159
181
  keeps its stored mode; a fork without caps inherits its source's. The SDK's requests do not change;
160
182
  `workspace.memory` and `workspace.allocationMode` report the mode the workspace got.
161
183
 
162
- ## 0.13.1 (not yet published)
184
+ ## 0.13.1 — 2026-10-03
163
185
 
164
186
  Patch, additive: the transport, a documented value, a fix for tool calls across a move and self-healing workspaces.
165
187
  `@shardflux/cli` 0.8.1, `@shardflux/mcp` 0.7.1 and the `shardflux` bundle 0.10.1 follow (`^0.13.1`).
@@ -216,7 +238,7 @@ failing with `409 conflict` (`workspace_not_running`).
216
238
  runs; new token)`.
217
239
  - The CLI, the MCP server and the `shardflux` bundle get it through their `^0.13.0` dependency.
218
240
 
219
- ## 0.13.0
241
+ ## 0.13.0 — 2026-10-02
220
242
 
221
243
  ### Elastic memory
222
244
 
@@ -326,14 +348,12 @@ template's newest published version, picked up at its next cold boot or resume.
326
348
  - The cancel that `exec.run` sends when its `signal` aborts passes `killGraceMs` capped at 60000, so a larger
327
349
  `killGraceMs` still cancels the command.
328
350
 
329
- ## 0.12.0 (release candidate)
351
+ ## 0.12.0 — 2026-09-30
330
352
 
331
353
  Completed exec output streams are drained before releasing their HTTP connections, with a bounded cleanup if a peer does not close.
332
354
 
333
355
  Pooled HTTP/1.1 on Node 26 with pinned undici 8.10.2; custom fetch stays unchanged. Labels on open/list and setLabels, typed idle(), keepalive() and setIdlePolicy(). Protocol errors carry source; isWorkspaceGone recognizes only an explicit API workspace_deleted refusal. Exec sessions accept stdin_open and offset-addressed exec.input. Failed workspaces recover through resume/auto-wake with the same ID. Completed deletions free keys for new IDs.
334
356
 
335
- Requires the QM integration backend release for labels, failed recovery, key reuse and pipe stdin. No production deployment has occurred from this branch.
336
-
337
357
  ### Instant suspend: durable storage in the result
338
358
 
339
359
  Additive. A suspend returns as soon as the workspace is sealed on its host; the copy lands in durable storage right
@@ -358,7 +378,7 @@ after. This release reads that from the result and can wait for it.
358
378
  Requires the instant-suspend cell release for `durable: false` results; against other servers every succeeded suspend
359
379
  is already durable and `durable: true` resolves at once.
360
380
 
361
- ## 0.11.1
381
+ ## 0.11.1 — 2026-09-30
362
382
 
363
383
  Wording: messages and JSDoc say what to do, without internals (no behaviour change).
364
384
 
@@ -372,7 +392,7 @@ Wording: messages and JSDoc say what to do, without internals (no behaviour chan
372
392
  `SHARDFLUX_HTTP_KEEPALIVE=1`.
373
393
  - The `formatTiming()` example is a production resume (413 ms).
374
394
 
375
- ## 0.11.0
395
+ ## 0.11.0 — 2026-09-29
376
396
 
377
397
  ### A resume that restarted processes says so (cold boot)
378
398
 
@@ -395,17 +415,17 @@ the resume's result through; this release reads it.
395
415
  - The new `ServerTiming` fields are optional in the type (always set by the SDK), so timings built by hand still
396
416
  type-check. No return type or behaviour changes: a wake that cold-booted still resolves `true`.
397
417
 
398
- ## 0.10.2
418
+ ## 0.10.2 — 2026-09-29
399
419
 
400
420
  Fix: the SDK reports 0.10.2. 0.10.1 was published reporting 0.10.0, its previous version; the build now
401
421
  fails when `SDK_VERSION` differs from package.json.
402
422
 
403
- ## 0.10.1
423
+ ## 0.10.1 — 2026-09-29
404
424
 
405
425
  Public support: package metadata and the README now link to the [shared bug tracker](https://github.com/shardfluxdev/community/issues),
406
426
  feature requests, and private support/security reporting.
407
427
 
408
- ## 0.10.0
428
+ ## 0.10.0 — 2026-09-29
409
429
 
410
430
  ### A command that could not start rejects exec.run() (ExecStartError)
411
431
 
@@ -453,7 +473,7 @@ from the API's OpenAPI); the account client's `setSpendPolicy()` gains the overa
453
473
 
454
474
  - `workspace.suspendWhenIdle({ afterSeconds, idempotencyKey? })` and `cloud.workspaces.suspendWhenIdle(id, {
455
475
  afterSeconds })` (POST /v1/workspaces/{id}/suspend-when-idle): the workspace is suspended once it has been idle for
456
- `afterSeconds` (30..3600), counted from the later of its last work and the request. Meant for the end of an agent
476
+ `afterSeconds` (0..3600 since the 2026-09-30 API release, commit `2b2f5e658`; originally 30..3600), counted from the later of its last work and the request. Meant for the end of an agent
457
477
  turn. A running command, an attached stream or a keepalive postpones it; the next tool call or a resume cancels it.
458
478
  Resolves with `{ suspendRequest, operation, workspace }`: `operation` is the suspend already in progress, if any
459
479
  (then nothing is recorded).
@@ -463,7 +483,7 @@ from the API's OpenAPI); the account client's `setSpendPolicy()` gains the overa
463
483
  count as the next turn and cancel the request.
464
484
  - Types `SuspendRequest`, `SuspendWhenIdleOptions`, `SuspendWhenIdleResult`, `SuspendWhenIdleResponse`.
465
485
 
466
- ## 0.9.0
486
+ ## 0.9.0 — 2026-09-29
467
487
 
468
488
  Elastic compute (decision 0007): file tools and wake hints for parked workspaces. Additive; older APIs and cell
469
489
  gateways keep working (the new calls answer 404 there).
@@ -659,7 +679,7 @@ behavior is the automatic version check (below), which makes one background requ
659
679
  - New exports: `FeedbackCategory`, `FeedbackContext`, `FeedbackReceipt`, `SendFeedbackParams`, `AccountFeedbackParams`,
660
680
  `FEEDBACK_CATEGORIES`, `FEEDBACK_MESSAGE_MAX_LENGTH`.
661
681
 
662
- ## 0.8.0 (2026-09-28)
682
+ ## 0.8.0 — 2026-09-28
663
683
 
664
684
  Types only; nothing changes at run time and the API is unchanged.
665
685
 
@@ -679,7 +699,7 @@ Types only; nothing changes at run time and the API is unchanged.
679
699
  - Checked at compile time against `@anthropic-ai/sdk` 0.128.0 and `openai` 7.23.0 (devDependencies only; the SDK still
680
700
  has no runtime dependencies).
681
701
 
682
- ## 0.7.0 (2026-09-28)
702
+ ## 0.7.0 — 2026-09-28
683
703
 
684
704
  Needs an API with the template editor; every new field is additive and older fields are unchanged.
685
705
 
@@ -767,7 +787,7 @@ docs/decisions/0006-tool-call-capture.md (shared with the Python SDK 0.3.0).
767
787
  - **Breaking (types only):** `LifecyclePhase` gains `capture_flush`; an exhaustive `switch` over phases without a
768
788
  `default` case needs the new member.
769
789
 
770
- ## 0.6.2 (2026-09-28)
790
+ ## 0.6.2 — 2026-09-28
771
791
 
772
792
  ### Starts that wait for capacity end
773
793
 
@@ -782,7 +802,7 @@ boot (and bill) long after every wait had given up.
782
802
  - `onProgress`: a `capacity_pending` `phase` event carries `deadlineAt` when the API reports it.
783
803
  - Docs: waits no longer say a pending start continues indefinitely.
784
804
 
785
- ## 0.6.1 (2026-09-28)
805
+ ## 0.6.1 — 2026-09-28
786
806
 
787
807
  - `formatTiming()` joined two phases that followed each other with `∥` (ran together) when their 0.1 ms times summed
788
808
  with a floating-point error (1000.2 + 300.1 > 1300.3); it now prints `→`.
@@ -792,7 +812,7 @@ boot (and bill) long after every wait had given up.
792
812
  belonged to no phase (a 1 s wait could show `queued 0 ms`).
793
813
  - README: the timing example is the formatter's real output (durations under a second print in ms).
794
814
 
795
- ## 0.6.0 (2026-09-28)
815
+ ## 0.6.0 — 2026-09-28
796
816
 
797
817
  ### Lifecycle calls: requested or finished
798
818
 
@@ -843,7 +863,7 @@ boot (and bill) long after every wait had given up.
843
863
  create, capture state, test instances, publish, discard).
844
864
  - `ShardfluxApiError.reason` (`details.reason`), with `KnownErrorReason`.
845
865
 
846
- ## 0.5.0 (2026-09-26)
866
+ ## 0.5.0 — 2026-09-26
847
867
 
848
868
  First public release: `Shardflux` client, `workspaces.open/get/list/listAll`, lifecycle calls returning operations,
849
869
  `waitForOperation()`, the cell client (`exec`, `files`, `pty`, `processes`, `git`, `browser`), `workspaceTools()` with
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @shardflux/sdk
2
2
 
3
- TypeScript SDK for [Shardflux](https://shardflux.dev): cloud computers for AI agents.
3
+ TypeScript SDK for [Shardflux](https://shardflux.dev): serverless VMs for AI agents.
4
4
 
5
5
  Open a persistent workspace by key, run commands and move files in it, suspend it when idle, resume
6
6
  it later with its disk and memory intact, and fork it. Hand your agent framework-neutral workspace
@@ -20,6 +20,34 @@ tool calls into the workspace ([tool-call capture](#tool-call-capture-070)).
20
20
  - Typed from the published OpenAPI documents.
21
21
  - Retries, idempotency keys, operation polling and tool-token refresh are handled for you.
22
22
 
23
+ ## Test your integration (0.16.0+)
24
+
25
+ Use the same client calls in fast, isolated tests with `@shardflux/sdk/testing`:
26
+
27
+ ```ts
28
+ import { createFakeShardflux } from '@shardflux/sdk/testing';
29
+
30
+ const cloud = createFakeShardflux({
31
+ onExec: (request) => ({ stdout: `ran ${request.argv.join(' ')}\n`, exitCode: 0 }),
32
+ });
33
+ const ref = cloud.workspace('acme/test', { template: 'python-node-browser' });
34
+ const result = await ref.exec(['python3', '-V'], { sessionId: 'version-check' });
35
+ await ref.files.write('/home/user/result.txt', result.stdout);
36
+ ```
37
+
38
+ The fake returns real SDK workspace handles, refs and errors. Workspace lifecycle calls, labels, paginated lists,
39
+ files, command output, desktop actions and port lists use an isolated in-memory store. Command results come from
40
+ `onExec`; returning `null` leaves a command running for `cloud.testing.completeCommand(id, sessionId, result)`.
41
+ Unscripted commands and unimplemented routes raise `FakeUnsupportedError`.
42
+
43
+ Control state with `cloud.testing.setFaults(id, { busy: true, failed: true, legacyLayout: true })`,
44
+ `computerUnavailable`, `deleted`, `resizeRefusal` (`shrink_not_supported` or `resize_not_available`), and
45
+ `memoryLimitMib` (a plan clamp). Set `cloud.testing.quota` to `retained_state` or `concurrent_workspaces`.
46
+ `cloud.testing.advance(milliseconds)` drives idle deadlines; `latencyMs` and `sleep` inject request latency.
47
+ `cloud.testing.inspect(id)` returns a copy of the raw workspace view. Each fake has its own state.
48
+ Import the testing subpath when building tests; the main SDK entry keeps its existing runtime dependencies.
49
+
50
+
23
51
  ## Support and bug reports
24
52
 
25
53
  [Report a bug](https://github.com/shardfluxdev/community/issues/new?template=bug_report.yml) or
@@ -31,6 +59,29 @@ For usage, account, billing, or private support, email [shardflux@heliosone.fi](
31
59
  Report vulnerabilities through [private security reporting](https://github.com/shardfluxdev/community/security/advisories/new).
32
60
  See the [support guide](https://github.com/shardfluxdev/community/blob/main/SUPPORT.md) for all reporting options.
33
61
 
62
+ ## Reverse tunnels (0.16.0+)
63
+
64
+ Forward a workspace TCP port to a service on the machine running your SDK:
65
+
66
+ ```ts
67
+ const tunnel = await workspace.tunnels.reverse({ remotePort: 8081, target: 'localhost:8081' });
68
+ try {
69
+ // Commands in the workspace can call http://127.0.0.1:8081.
70
+ console.log(tunnel.stats);
71
+ } finally {
72
+ await tunnel.close();
73
+ }
74
+ ```
75
+
76
+ The guest listener binds to `127.0.0.1` by default; `bindAddress: '0.0.0.0'` selects all guest interfaces.
77
+ The target is dialed from this machine. TCP binary data, concurrent connections and half-close are preserved;
78
+ network reconnection resumes the tunnel while the handle is open. `closed` reports completion or failure,
79
+ `sessionId` identifies the forwarder, and `stats` counts connections, bytes in each direction and reconnects.
80
+ An open tunnel keeps the workspace awake; close it when callbacks are finished. For persistent workspaces, explicit suspend/resume preserves
81
+ an open tunnel. The caller needs `exec` and `files` access.
82
+
83
+ The tunnel selects a streaming transport automatically when `pty` permission is granted. Choose `transport: 'pty'` or `transport: 'exec'` in the SDK, or `--transport pty|exec` in the CLI, to select it explicitly. The handle reports `transport`.
84
+
34
85
  ## Install
35
86
 
36
87
  ```sh
@@ -1330,3 +1381,50 @@ const tools = workspaceTools(workspace, { prewake: true });
1330
1381
  // In your model stream's tool-input-start handler:
1331
1382
  tools.find(tool => tool.name === toolName)?.onInputStart?.();
1332
1383
  ```
1384
+
1385
+ Resize controls (0.16.0+): `workspace.resize({ memoryMib: 6144, diskGib: 2, atLeast: true, wait: false })` grows
1386
+ only resources that need more capacity and returns the held server answer with its operation. `workspace.caps` exposes
1387
+ typed stored caps for later starts. A pending operation carries per-resource results once the resize has decided
1388
+ them. Fixed memory without `memory_mib` uses the template default, bounded by the plan and template.
1389
+
1390
+ ## New in 0.16.0
1391
+
1392
+ `ref.turn(async (ref) => { ... })` (also `workspace.turn`) sends a wake hint without waiting, returns the body's result, and requests `suspendWhenIdle({ afterSeconds: 0 })` when it settles, including on error. Pass `{ afterSeconds: 60 }` to keep the workspace warm for a billed minute. Use persistent workspaces for resumable turns.
1393
+
1394
+ Computer batches ending in `screenshot` or `zoom` return that action's image without an extra screenshot. `computer.stream()` reuses a link of the same kind with more than 60 seconds left; `{ fresh: true }` mints one, and `stopStream()` clears the cache. Gate each action with `computerToolset(ws, { beforeAction })` or `workspaceTools(ws, { computer: { beforeAction } })`: return `false`, a refusal string, or throw to answer that action with an error and skip the remaining actions. Hooks may be async.
1395
+
1396
+ Exec results expose `exitCodePosix`: timeout 124, canceled 130, nonnegative exit code, 128 + signal, otherwise 1. `FileNotFoundError` is a `ShardfluxApiError` for cell `path_not_found` replies. The default transport honors `HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY` and `NODE_USE_ENV_PROXY`. `SHARDFLUX_BASE_URL` aliases `SHARDFLUX_API_URL`; `SHARDFLUX_API_URL` wins.
1397
+
1398
+ ## Embed a live viewer (0.16.0+)
1399
+
1400
+ Watch the desktop or a port preview inside your web app. Choose 1–10 distinct HTTPS origins, then use the returned URL as the iframe source.
1401
+
1402
+ ```ts
1403
+ const { url } = await ws.computer.stream({ embed: { origins: ['https://app.example.com'] } });
1404
+ const preview = await ws.ports.link(3000, { embed: { origins: ['https://app.example.com'] } });
1405
+ ```
1406
+
1407
+ ```html
1408
+ <iframe src="RETURNED_LINK_URL" title="Watch the agent" style="width:100%;height:600px;border:0"></iframe>
1409
+ ```
1410
+
1411
+ The browser session is partitioned by the top-level site. Each origin is exactly a scheme and host with an optional port, such as `https://app.example.com:8443`. The viewer permits framing by those origins; interactive viewing uses the same allowlist.
1412
+
1413
+ ## Idle deletion retention (0.16.0+)
1414
+
1415
+ Keep one persistent workspace per conversation and delete it automatically after 14 idle days:
1416
+
1417
+ ```ts
1418
+ const ws = await cloud.workspaces.open({ key: 'chat/42', template: 'python-node-browser', retention: { delete_after_idle_days: 14 } });
1419
+ await ws.setRetention({ delete_after_idle_days: 30 });
1420
+ await cloud.projects.setRetention((await cloud.me()).api_key!.project_id, { delete_after_idle_days: 14, labels: { app: 'chat' } });
1421
+ await ws.setRetention(null); // follow the project default
1422
+ ```
1423
+
1424
+ `cloud.workspace(key, { template, retention })` accepts the same policy. `ws.retention` shows its effective days
1425
+ and projected `delete_at`. All project selector labels must match exactly; a workspace's own policy wins.
1426
+ Calls and ongoing work postpone deletion. Without a policy, your workspace stays until you delete it.
1427
+
1428
+ ## Workspace upgrade (0.16.0+)
1429
+
1430
+ `await ws.upgrade({ at: "now", wait: true })` keeps files, installed package files and home while cold starting on the supported current layout and base. `at: "next_resume"` schedules it for the next resume. Memory and running processes end at the cold start. Read `ws.upgradeAvailable` and `ws.upgradePending` for the target.
package/dist/account.d.ts CHANGED
@@ -277,6 +277,8 @@ export declare class AccountProjectsApi {
277
277
  name: string;
278
278
  slug?: string;
279
279
  }): Promise<Project>;
280
+ getRetention(projectId: string): Promise<import('./client.js').ProjectRetentionPolicy | null>;
281
+ setRetention(projectId: string, policy: import('./client.js').ProjectRetentionPolicy | null): Promise<import('./client.js').ProjectRetentionPolicy | null>;
280
282
  get(projectId: string): Promise<Project>;
281
283
  }
282
284
  /** Every tool permission an API key can carry (pass it as `toolPermissions` for a key with every tool). */
package/dist/account.js CHANGED
@@ -280,6 +280,12 @@ export class AccountProjectsApi {
280
280
  create(organizationId, params) {
281
281
  return json(this.#core, 'POST', `/v1/organizations/${enc(organizationId)}/projects`, { json: { name: params.name, ...(params.slug === undefined ? {} : { slug: params.slug }) } });
282
282
  }
283
+ getRetention(projectId) {
284
+ return json(this.#core, 'GET', `/v1/projects/${enc(projectId)}/retention`);
285
+ }
286
+ setRetention(projectId, policy) {
287
+ return json(this.#core, 'PUT', `/v1/projects/${enc(projectId)}/retention`, { json: policy });
288
+ }
283
289
  get(projectId) {
284
290
  return json(this.#core, 'GET', `/v1/projects/${enc(projectId)}`);
285
291
  }
package/dist/cell.d.ts CHANGED
@@ -248,6 +248,8 @@ export declare const CAPTURE_BARRIER: unique symbol;
248
248
  export interface RunResult {
249
249
  sessionId: string;
250
250
  exitCode: number | null;
251
+ /** POSIX status: timeout 124, canceled 130, exit code, 128 + signal, otherwise 1. */
252
+ exitCodePosix: number;
251
253
  termSignal: number | null;
252
254
  timedOut: boolean;
253
255
  canceled: boolean;
package/dist/cell.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { ExecStartError, NotSupportedForModeError, ShardfluxApiError, ShardfluxProtocolError, apiError, isErrorBody, isWorkingQuotaRefusal } from "./errors.js";
2
+ import { exitCodePosix } from "./exit-code.js";
2
3
  import { EXECUTION_ID, ExecutionResult, newExecutionId } from "./executions.js";
3
4
  import { HttpClient, defaultSleep, randomId, treeRevisionOf } from "./http.js";
4
5
  import { describeFailure, emitTo } from "./progress.js";
@@ -590,6 +591,7 @@ export class CellClient {
590
591
  return {
591
592
  sessionId,
592
593
  exitCode: session.exit_code ?? null,
594
+ exitCodePosix: exitCodePosix({ exitCode: session.exit_code ?? null, termSignal: session.term_signal ?? null, timedOut: session.timed_out ?? false, canceled: session.canceled ?? false }),
593
595
  termSignal: session.term_signal ?? null,
594
596
  timedOut: session.timed_out ?? false,
595
597
  canceled: session.canceled ?? false,
package/dist/client.d.ts CHANGED
@@ -159,7 +159,7 @@ export type AllocationMode = 'fixed' | 'elastic';
159
159
  export type WorkspaceMemory = WorkspaceView['memory'];
160
160
  export interface Caps {
161
161
  cpu_millis?: number;
162
- /** Memory in MiB; for an elastic workspace the promise (what it may grow to). */
162
+ /** Memory in MiB; elastic promise, or fixed size (the template default when omitted). */
163
163
  memory_mib?: number;
164
164
  disk_gib?: number;
165
165
  /**
@@ -197,6 +197,10 @@ export interface ResizeParams {
197
197
  cpuMillis?: number;
198
198
  /** Disk in GiB; disks grow only. */
199
199
  diskGib?: number;
200
+ /** Only grow each named numeric resource; a smaller or equal value leaves it unchanged. */
201
+ atLeast?: boolean;
202
+ /** Wait for completion (default true); false returns the held server answer without operation polling. */
203
+ wait?: boolean;
200
204
  /** Replays the stored response for a repeated request (default: a fresh key per call, so transport retries replay). */
201
205
  idempotencyKey?: string;
202
206
  /** Give up waiting for the resize after this long (default 300 000 ms); it continues server side. */
@@ -260,7 +264,21 @@ export interface WaitOptions {
260
264
  onProgress?: ProgressListener;
261
265
  }
262
266
  export type IdlePolicy = 'adaptive' | 'never' | `fixed:${number}`;
267
+ export interface RetentionPolicy {
268
+ delete_after_idle_days: number;
269
+ }
270
+ export interface ProjectRetentionPolicy extends RetentionPolicy {
271
+ labels?: Record<string, string>;
272
+ }
273
+ export declare class ProjectsRetentionApi {
274
+ #private;
275
+ constructor(ctx: () => ClientContext);
276
+ getRetention(projectId: string): Promise<ProjectRetentionPolicy | null>;
277
+ setRetention(projectId: string, policy: ProjectRetentionPolicy | null): Promise<ProjectRetentionPolicy | null>;
278
+ }
263
279
  export interface OpenParams {
280
+ /** Opt-in idle deletion in days (1..3650), persistent workspaces only. Omitted leaves it unchanged. */
281
+ retention?: RetentionPolicy;
264
282
  /** Searchable metadata; supplied labels replace the existing map. */
265
283
  labels?: Record<string, string>;
266
284
  idlePolicy?: IdlePolicy;
@@ -415,6 +433,7 @@ export declare class WorkspacesApi {
415
433
  /** null clears the override, restoring the template or platform policy. */
416
434
  /** Computer use (0.15.0+): the workspace's own switch; null follows the template. Returns the switch. */
417
435
  setComputerUse(workspaceId: string, enabled: boolean | null): Promise<ComputerUse>;
436
+ setRetention(workspaceId: string, policy: RetentionPolicy | null): Promise<Workspace>;
418
437
  setIdlePolicy(workspaceId: string, idlePolicy: IdlePolicy | null): Promise<Workspace>;
419
438
  list(params?: ListParams): Promise<Page<Workspace>>;
420
439
  /** Iterates every page. */
@@ -528,6 +547,10 @@ export declare class WorkspacesApi {
528
547
  * boots blank on the next resume. Returns the `reset` operation; its result names the recovery checkpoint (restorable
529
548
  * for 7 days). Errors: 409 legacy_disk_layout, not_resettable, operation_in_progress.
530
549
  */
550
+ /** Opt-in cold start preserving the disk; next_resume schedules it without stopping the VM. */
551
+ upgrade(workspaceId: string, opts?: LifecycleOptions & {
552
+ at?: 'now' | 'next_resume';
553
+ }): Promise<Operation>;
531
554
  reset(workspaceId: string, opts: WaitedLifecycleOptions): Promise<FinishedOperation>;
532
555
  reset(workspaceId: string, opts?: LifecycleOptions): Promise<Operation>;
533
556
  /**
@@ -583,6 +606,7 @@ export declare function fetchBillingCatalog(opts?: {
583
606
  }): Promise<BillingCatalog>;
584
607
  export declare class Shardflux {
585
608
  #private;
609
+ readonly projects: ProjectsRetentionApi;
586
610
  readonly workspaces: WorkspacesApi;
587
611
  readonly billing: BillingApi;
588
612
  /** Usage, allowances, estimates, grants/leases and spend (Phase 9). */
package/dist/client.js CHANGED
@@ -14,6 +14,18 @@ import { AFTER_WAIT, HELD_RESUME, TRACE, runLifecycle, waitOptionsOf } from "./l
14
14
  import { Trace, combineListeners, durabilityOf, isDurable, traced } from "./progress.js";
15
15
  import { CaptureRegistry } from "./capture.js";
16
16
  import { sendFeedback } from "./feedback.js";
17
+ export class ProjectsRetentionApi {
18
+ #ctx;
19
+ constructor(ctx) { this.#ctx = ctx; }
20
+ getRetention(projectId) {
21
+ const c = this.#ctx();
22
+ return c.http.json('GET', `/v1/projects/${encodeURIComponent(projectId)}/retention`, {}, c.authorization);
23
+ }
24
+ setRetention(projectId, policy) {
25
+ const c = this.#ctx();
26
+ return c.http.json('PUT', `/v1/projects/${encodeURIComponent(projectId)}/retention`, { json: policy }, c.authorization);
27
+ }
28
+ }
17
29
  /**
18
30
  * The workspace a key names: the live row (deleted_at null) when there is one, since at most one live
19
31
  * workspace holds a key; otherwise the newest tombstone (ended sessions leave tombstones with the same key, and a
@@ -63,6 +75,8 @@ function resizeBody(p) {
63
75
  body.disk_gib = p.diskGib;
64
76
  if (Object.keys(body).length === 0)
65
77
  throw new TypeError('resize() needs at least one of memoryMib, memoryMibHeld, allocationMode, cpuMillis, diskGib');
78
+ if (p.atLeast !== undefined)
79
+ body.at_least = p.atLeast;
66
80
  return body;
67
81
  }
68
82
  const RESIZE_KEYS = {
@@ -164,6 +178,8 @@ export class WorkspacesApi {
164
178
  body.inputs = params.inputs;
165
179
  if (params.labels !== undefined)
166
180
  body.labels = params.labels;
181
+ if (params.retention !== undefined)
182
+ body.retention = params.retention;
167
183
  if (params.idlePolicy !== undefined)
168
184
  body.idle_policy = params.idlePolicy;
169
185
  if (params.computerUse !== undefined)
@@ -399,6 +415,9 @@ export class WorkspacesApi {
399
415
  async setComputerUse(workspaceId, enabled) {
400
416
  return this.#http.json('PUT', `/v1/workspaces/${encodeURIComponent(workspaceId)}/computer-use`, { json: { enabled } }, this.#auth);
401
417
  }
418
+ async setRetention(workspaceId, policy) {
419
+ return this.#wrap(await this.#http.json('PUT', `/v1/workspaces/${encodeURIComponent(workspaceId)}/retention`, { json: policy }, this.#auth));
420
+ }
402
421
  async setIdlePolicy(workspaceId, idlePolicy) {
403
422
  return this.#wrap(await this.#http.json('PUT', `/v1/workspaces/${encodeURIComponent(workspaceId)}/idle-policy`, { json: { idle_policy: idlePolicy } }, this.#auth));
404
423
  }
@@ -474,7 +493,7 @@ export class WorkspacesApi {
474
493
  if (kind === 'delete' || kind === 'reset')
475
494
  this.#ctx().captures.discard(workspaceId, kind);
476
495
  return operation;
477
- }, opts, { settle: kind === 'suspend' || kind === 'snapshot' });
496
+ }, opts, { settle: kind === 'suspend' || kind === 'snapshot' || kind === 'upgrade' });
478
497
  }
479
498
  delete(workspaceId, opts = {}) {
480
499
  return this.#op('delete', workspaceId, undefined, opts);
@@ -640,7 +659,7 @@ export class WorkspacesApi {
640
659
  // Another lifecycle operation (a suspend, a resume, another resize) holds the workspace: the refused request
641
660
  // changed nothing, so wait for that operation and send it again (bounded, within timeoutMs).
642
661
  const active = e instanceof ShardfluxApiError && e.code === 'conflict' && e.reason === 'operation_in_progress' ? (e.operationId ?? e.details?.['active_operation_id']) : undefined;
643
- if (typeof active !== 'string' || attempt >= 3 || Date.now() - started >= timeoutMs)
662
+ if (params.wait === false || typeof active !== 'string' || attempt >= 3 || Date.now() - started >= timeoutMs)
644
663
  throw e;
645
664
  trace.retry({ request: `PATCH ${path}`, attempt: attempt + 1, cause: `conflict operation_in_progress (waits for ${active})`, delayMs: 0 });
646
665
  try {
@@ -658,6 +677,15 @@ export class WorkspacesApi {
658
677
  if (!operation || typeof operation !== 'object')
659
678
  throw new ShardfluxProtocolError('resize: 202 response has no operation', res.status, 'api');
660
679
  trace.observe(operation);
680
+ if (params.wait === false) {
681
+ if (operation.state === 'failed' || operation.state === 'canceled')
682
+ throw new OperationFailedError(operation);
683
+ const workspace = res.body.workspace;
684
+ const result = resizeResultBodyOf(operation);
685
+ return { workspaceId, operationId: operation.id, state: workspace?.observed_state ?? operation.state,
686
+ memory: result.memory ?? null, cpu: result.cpu ?? null, disk: result.disk ?? null,
687
+ caps: workspace?.caps ?? null, operation };
688
+ }
661
689
  let final = operation;
662
690
  if (operation.state !== 'succeeded') {
663
691
  if (TERMINAL.has(operation.state))
@@ -685,6 +713,17 @@ export class WorkspacesApi {
685
713
  }, opts, { settle: true });
686
714
  return { operation, workspace: workspace };
687
715
  }
716
+ /**
717
+ * Resets a layered workspace to its template: every change in the workspace layer is wiped; key,
718
+ * id, template version, caps, secret bindings and volume attachments stay. Running: restarted on a blank layer
719
+ * (processes are gone; old tool tokens get 409 stale_epoch and the SDK refreshes them). Suspended: stays suspended and
720
+ * boots blank on the next resume. Returns the `reset` operation; its result names the recovery checkpoint (restorable
721
+ * for 7 days). Errors: 409 legacy_disk_layout, not_resettable, operation_in_progress.
722
+ */
723
+ /** Opt-in cold start preserving the disk; next_resume schedules it without stopping the VM. */
724
+ upgrade(workspaceId, opts = {}) {
725
+ return this.#op('upgrade', workspaceId, { at: opts.at ?? 'now' }, opts);
726
+ }
688
727
  reset(workspaceId, opts = {}) {
689
728
  const body = { confirm_destructive: true };
690
729
  return this.#op('reset', workspaceId, body, opts);
@@ -787,6 +826,7 @@ export async function fetchBillingCatalog(opts = {}) {
787
826
  return http.json('GET', '/v1/billing/catalog');
788
827
  }
789
828
  export class Shardflux {
829
+ projects;
790
830
  workspaces;
791
831
  billing;
792
832
  /** Usage, allowances, estimates, grants/leases and spend (Phase 9). */
@@ -813,6 +853,7 @@ export class Shardflux {
813
853
  const f = opts.fetch ?? defaultFetch();
814
854
  const userAgent = opts.userAgent ?? `shardflux-sdk-ts/${SDK_VERSION}`;
815
855
  const sleep = opts.sleep ?? defaultSleep;
856
+ this.projects = new ProjectsRetentionApi(() => this.#ctx);
816
857
  this.workspaces = new WorkspacesApi(() => this.#ctx);
817
858
  this.billing = new BillingApi(() => this.#ctx);
818
859
  this.usage = new UsageApi(() => this.#ctx);
@@ -821,7 +862,7 @@ export class Shardflux {
821
862
  this.egress = new EgressPolicyApi(() => this.#ctx);
822
863
  this.audit = new AuditApi(() => this.#ctx);
823
864
  this.volumes = new VolumesApi(() => this.#ctx);
824
- const baseUrl = opts.baseUrl ?? envVar('SHARDFLUX_API_URL') ?? 'https://api.shardflux.dev';
865
+ const baseUrl = opts.baseUrl ?? envVar('SHARDFLUX_API_URL') ?? envVar('SHARDFLUX_BASE_URL') ?? 'https://api.shardflux.dev';
825
866
  this.#ctx = {
826
867
  http: new HttpClient({ baseUrl, fetch: f, userAgent, timeoutMs: opts.timeoutMs ?? 30_000, maxRetries: opts.maxRetries ?? 2, source: 'api', sleep, onSuccess: versionCheckHook(opts.versionCheck, baseUrl, f, userAgent) }),
827
868
  authorization: `Bearer ${apiKey}`,
@@ -29,6 +29,10 @@ export interface ComputerActOptions {
29
29
  signal?: AbortSignal;
30
30
  }
31
31
  export interface ComputerStreamOptions {
32
+ /** Mint a new link even when a cached link has more than 60 seconds left. */
33
+ fresh?: boolean;
34
+ /** Frame the viewer in these HTTPS origins with a partitioned browser session (§48). */
35
+ embed?: import('./ports.js').PortEmbedOptions;
32
36
  /** Let the viewer use the mouse and keyboard (default false: view only, enforced in the workspace). */
33
37
  interactive?: boolean;
34
38
  /** How long the link works (60..604800 s; default 86400). */
@@ -111,6 +115,8 @@ export interface ComputerToolResult {
111
115
  is_error?: true;
112
116
  }
113
117
  export interface ComputerToolsetOptions {
118
+ /** Called just before each action. Return false/a refusal string, or throw, to stop the turn. */
119
+ beforeAction?: BeforeComputerAction;
114
120
  /** Screenshot format (default png). */
115
121
  format?: 'png' | 'jpeg';
116
122
  quality?: number;
@@ -134,10 +140,14 @@ export interface ComputerToolsetOptions {
134
140
  */
135
141
  export declare function computerToolset(workspace: {
136
142
  computer: WorkspaceComputer;
137
- }): {
143
+ }, defaults?: ComputerToolsetOptions): {
138
144
  definition: {
139
145
  readonly type: "computer_toolset_20260801";
140
146
  };
141
147
  run(content: readonly unknown[], opts?: ComputerToolsetOptions): Promise<ComputerToolResult[]>;
142
148
  };
149
+ export type BeforeComputerAction = (action: Readonly<ComputerAction>) => void | boolean | string | Promise<void | boolean | string>;
150
+ type ComputerBatchResult = Partial<ComputerActionsResult> & Pick<ComputerActionsResult, 'results'>;
151
+ /** No hook: one batch as before. With a hook, check immediately before each individual action runs. */
152
+ export declare function runComputerActions(act: (actions: ComputerAction[], opts: ComputerActOptions) => Promise<ComputerActionsResult>, actions: ComputerAction[], opts: ComputerActOptions, beforeAction?: BeforeComputerAction): Promise<ComputerBatchResult>;
143
153
  export {};