@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/dist/workspace.js CHANGED
@@ -6,6 +6,7 @@ import { AFTER_WAIT, HELD_RESUME, TRACE } from "./lifecycle.js";
6
6
  import { Trace, combineListeners, traced } from "./progress.js";
7
7
  import { WorkspaceComputer } from "./computer.js";
8
8
  import { WorkspacePorts } from "./ports.js";
9
+ import { WorkspaceTunnels } from "./tunnels.js";
9
10
  import { WorkspaceSecrets } from "./secrets.js";
10
11
  import { ToolTokenManager } from "./tokens.js";
11
12
  const notRunning = (e) => e instanceof ShardfluxApiError && (e.code === 'workspace_not_running' || (e.code === 'conflict' && e.reason === 'workspace_not_running'));
@@ -16,12 +17,14 @@ export class Workspace {
16
17
  #ctx;
17
18
  #defaults;
18
19
  #managers = new Map();
20
+ #computer;
19
21
  #cells = new Map();
20
22
  /** The open() that produced this handle; its timing is final once open() has returned. */
21
23
  #openTrace;
22
24
  #lastTiming;
23
25
  /** The newest tree revision seen (file-first): the view's, or any cell response's since. */
24
26
  #treeRevision;
27
+ #tunnels;
25
28
  /**
26
29
  * Whether the open() that returned this handle created the workspace (0.15.0+, contracts §46.2): true for a new key
27
30
  * (or a key whose session ended), false when it reconnected to or resumed an existing workspace. Null for handles
@@ -101,6 +104,10 @@ export class Workspace {
101
104
  get grants() {
102
105
  return this.#view.grants;
103
106
  }
107
+ /** Stored caps every later start uses, as of the latest workspace view. */
108
+ get caps() {
109
+ return this.#view.caps;
110
+ }
104
111
  get ceilings() {
105
112
  return this.#view.ceilings;
106
113
  }
@@ -220,13 +227,17 @@ export class Workspace {
220
227
  get ports() {
221
228
  return new WorkspacePorts(this.#ctx, this.id);
222
229
  }
230
+ /** Forward a guest TCP port to this machine. Close the returned handle in finally. */
231
+ get tunnels() {
232
+ return this.#tunnels ??= new WorkspaceTunnels(() => this.cell({ transitionTimeoutMs: 30_000 }), () => this.grantedTools);
233
+ }
223
234
  /**
224
235
  * The workspace desktop (0.15.0+, contracts §45): `act(actions)`, `screenshot()`, `stream()` (a private link to watch
225
236
  * it), `status()`, `start()`, `stop()`. Needs computer use on (`setComputerUse(true)`, or the template's switch); the
226
237
  * platform starts the desktop on the first call that needs it.
227
238
  */
228
239
  get computer() {
229
- return new WorkspaceComputer({
240
+ return this.#computer ??= new WorkspaceComputer({
230
241
  cell: () => this.cell(),
231
242
  ports: () => this.ports,
232
243
  setEnabled: (enabled) => this.setComputerUse(enabled).then(() => this.computerUse),
@@ -257,6 +268,11 @@ export class Workspace {
257
268
  this.#view = (await this.#ctx.workspaces.setLabels(this.id, labels)).data;
258
269
  return this;
259
270
  }
271
+ get retention() { return this.#view.retention ?? null; }
272
+ async setRetention(policy) {
273
+ this.#view = (await this.#ctx.workspaces.setRetention(this.id, policy)).data;
274
+ return this;
275
+ }
260
276
  async setIdlePolicy(policy) {
261
277
  this.#view = (await this.#ctx.workspaces.setIdlePolicy(this.id, policy)).data;
262
278
  return this;
@@ -342,7 +358,10 @@ export class Workspace {
342
358
  throw refusal;
343
359
  const out = await this.#ctx.workspaces.resize(this.id, this.#tracked(params));
344
360
  // The resize has happened: a failed view read leaves the old view (the next refresh() reads it again).
345
- await this.refresh().catch(() => undefined);
361
+ if (params.wait !== false)
362
+ await this.refresh().catch(() => undefined);
363
+ else if (out.caps)
364
+ this.#view = { ...this.#view, caps: out.caps };
346
365
  return out;
347
366
  }
348
367
  resume(opts = {}) {
@@ -378,19 +397,49 @@ export class Workspace {
378
397
  return this.#ctx.workspaces.fork(this.id, target, this.#tracked(opts));
379
398
  }
380
399
  async close(opts = {}) {
400
+ let tunnelError;
401
+ try {
402
+ await this.#tunnels?.close();
403
+ }
404
+ catch (e) {
405
+ tunnelError = e instanceof Error ? e : new Error(String(e));
406
+ }
381
407
  // Closing aborts in-flight cell requests: tool-call capture writes recorded before it are flushed first (bounded).
382
408
  await this.#ctx.captures.settle(this.id);
383
409
  for (const c of this.#cells.values())
384
410
  c.close();
385
411
  this.#cells.clear();
386
- if (this.lifetime !== 'session')
412
+ if (this.lifetime !== 'session') {
413
+ if (tunnelError)
414
+ throw tunnelError;
387
415
  return null;
416
+ }
388
417
  const out = await this.#ctx.workspaces.closeWithView(this.id, this.#tracked(opts));
389
418
  this.#view = out.workspace;
390
419
  for (const m of this.#managers.values())
391
420
  m.invalidate();
421
+ if (tunnelError)
422
+ throw tunnelError;
392
423
  return out.operation;
393
424
  }
425
+ /**
426
+ * Wipes every change in this layered workspace and restarts it on its template (sends
427
+ * confirm_destructive). Returns the `reset` operation: requested, or with `{ wait: true }` finished. Tool tokens of
428
+ * the old epoch are dropped.
429
+ */
430
+ /** Upgrade in place: keep files/packages/home; cold start drops memory and processes. */
431
+ async upgrade(opts = {}) {
432
+ const refusal = this.#needsVm('upgrade');
433
+ if (refusal)
434
+ throw refusal;
435
+ const op = await this.#ctx.workspaces.upgrade(this.id, { ...this.#tracked(opts), [AFTER_WAIT]: this.#refreshAfterWait() });
436
+ if (opts.at !== 'next_resume')
437
+ for (const m of this.#managers.values())
438
+ m.invalidate();
439
+ return op;
440
+ }
441
+ get upgradeAvailable() { return this.#view.upgrade_available ?? null; }
442
+ get upgradePending() { return this.#view.upgrade_pending ?? null; }
394
443
  async reset(opts = {}) {
395
444
  const refusal = this.#needsVm('reset');
396
445
  if (refusal)
@@ -590,6 +639,30 @@ export class Workspace {
590
639
  }
591
640
  throw new Error(`workspace ${this.id} did not become runnable after repeated lifecycle conflicts`);
592
641
  }
642
+ /** Hints without waiting, runs the turn, and requests idle suspension even if the body throws. */
643
+ async turn(fn, { afterSeconds = 0 } = {}) {
644
+ void this.hint().catch(() => undefined);
645
+ let result;
646
+ let failure;
647
+ let failed = false;
648
+ try {
649
+ result = await fn(this);
650
+ }
651
+ catch (err) {
652
+ failed = true;
653
+ failure = err;
654
+ }
655
+ try {
656
+ await this.suspendWhenIdle({ afterSeconds });
657
+ }
658
+ catch (err) {
659
+ if (!failed)
660
+ throw err;
661
+ }
662
+ if (failed)
663
+ throw failure;
664
+ return result;
665
+ }
593
666
  /**
594
667
  * Announces an imminent tool call (cell `POST /wake-hint`; 0.9.0+) so a parked workspace is restored
595
668
  * ahead of it: call it when the model starts emitting a tool call, before its arguments are complete. The agent tools
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@shardflux/sdk",
3
- "version": "0.15.0",
3
+ "version": "0.16.0",
4
4
  "type": "module",
5
- "description": "Shardflux TypeScript SDK: open persistent agent workspaces by key and give your agent workspace tools (exec, files, processes, PTY, git, browser).",
5
+ "description": "Shardflux TypeScript SDK: serverless VMs for AI agents. Open a workspace by key and give your agent workspace tools (exec, files, processes, PTY, git, browser).",
6
6
  "license": "Apache-2.0",
7
7
  "homepage": "https://shardflux.dev",
8
8
  "bugs": {
@@ -29,6 +29,11 @@
29
29
  "types": "./dist/index.d.ts",
30
30
  "import": "./dist/index.js",
31
31
  "default": "./dist/index.js"
32
+ },
33
+ "./testing": {
34
+ "types": "./dist/testing/index.d.ts",
35
+ "import": "./dist/testing/index.js",
36
+ "default": "./dist/testing/index.js"
32
37
  }
33
38
  },
34
39
  "files": [