@shardflux/sdk 0.11.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,12 +2,12 @@
2
2
  * A workspace handle: the latest view from the application API plus managed
3
3
  * tool tokens and cell clients (one per agent label / tool set).
4
4
  */
5
- import type { ClientContext, DiskLayout, ForkTarget, Operation, SuspendRequest, SuspendWhenIdleOptions, SuspendWhenIdleResult, WaitOptions, WorkspaceLifetime, WorkspaceOrigin, WorkspacePurpose, WorkspaceView } from './client.js';
5
+ import type { ClientContext, IdlePolicy, DiskLayout, ForkTarget, Operation, SuspendRequest, SuspendWhenIdleOptions, SuspendWhenIdleResult, WaitOptions, WorkspaceLifetime, WorkspaceOrigin, WorkspacePurpose, WorkspaceView } from './client.js';
6
6
  import { CAPTURE_BARRIER, CellClient } from './cell.js';
7
7
  import { ToolCallCapture } from './capture.js';
8
8
  import type { ToolCallCaptureOptions } from './capture.js';
9
9
  import type { WorkspaceMode } from './errors.js';
10
- import type { FinishedOperation, LifecycleOptions, ResumeOptions, WaitedLifecycleOptions, WaitedResumeOptions } from './lifecycle.js';
10
+ import type { FinishedOperation, LifecycleOptions, ResumeOptions, SuspendOptions, WaitedLifecycleOptions, WaitedResumeOptions, WaitedSuspendOptions } from './lifecycle.js';
11
11
  import { Trace } from './progress.js';
12
12
  import type { LifecycleTiming, ProgressListener } from './progress.js';
13
13
  import { WorkspaceSecrets } from './secrets.js';
@@ -22,7 +22,7 @@ export interface WakeOptions {
22
22
  /** Progress of the wake: the resume request, observed states, conflicts retried, and `done` with the timing. */
23
23
  onProgress?: ProgressListener;
24
24
  /**
25
- * The tool token the wake brings back (0.9.0): the held resume (contracts §22.6) returns one with the running
25
+ * The tool token the wake brings back (0.9.0): the held resume returns one with the running
26
26
  * workspace, for this agent label and tool set (defaults: those given to open()). It is kept for `cell()` clients of
27
27
  * the same label and tools, whose next call then needs no token request. A `cell()` client's own wake passes its
28
28
  * label and tools.
@@ -45,7 +45,7 @@ export interface HintOptions {
45
45
  }
46
46
  /** `workspace.hint()`: what the host found, or the background wake of a workspace that was not running. */
47
47
  export interface HintResult {
48
- /** The VM's residency when the hint arrived (contracts §25.1); null when the workspace was not running. */
48
+ /** The VM's residency when the hint arrived; null when the workspace was not running. */
49
49
  residency: Residency | null;
50
50
  /**
51
51
  * The wake started in the background because the workspace was not running (409 `workspace_not_running`), else null.
@@ -84,11 +84,11 @@ export declare class Workspace {
84
84
  get template(): WorkspaceView['template'];
85
85
  get pendingReason(): string | null;
86
86
  get activeOperation(): WorkspaceView['active_operation'];
87
- /** persistent or session (contracts §19.11); immutable. A view without the field (older API) is persistent. */
87
+ /** persistent or session; immutable. A view without the field (older API) is persistent. */
88
88
  get lifetime(): WorkspaceLifetime;
89
- /** standard, template_draft or template_test (contracts §19.9). */
89
+ /** standard, template_draft or template_test. */
90
90
  get purpose(): WorkspacePurpose;
91
- /** legacy or layered (contracts §19.2); reset, save-as-template and changes need layered. */
91
+ /** legacy or layered; reset, save-as-template and changes need layered. */
92
92
  get diskLayout(): DiskLayout;
93
93
  /** Sessions: seconds without activity after which the session ends (null for persistent workspaces). */
94
94
  get idleTimeoutSeconds(): number | null;
@@ -97,7 +97,7 @@ export declare class Workspace {
97
97
  /** How a session ended (closed, idle_timeout, draft_discarded); null while live or for a plain delete. */
98
98
  get endedReason(): WorkspaceView['ended_reason'];
99
99
  /**
100
- * Start commands and services of the workspace's template version (0.7.0; contracts §24.4): `state` pending, running,
100
+ * Start commands and services of the workspace's template version (0.7.0): `state` pending, running,
101
101
  * ready or failed (the failed step, its exit code, reason and output tail). Null when the version has neither, or on
102
102
  * an older API. A failed startup leaves the workspace running for inspection; the next open runs the failed step again.
103
103
  */
@@ -111,7 +111,7 @@ export declare class Workspace {
111
111
  /** The raw view (GET /v1/workspaces/{id}). */
112
112
  get data(): WorkspaceView;
113
113
  /**
114
- * `processful` (one VM keeps processes, memory and files) or `file_first` (0.9.0; contracts §29: a versioned file
114
+ * `processful` (one VM keeps processes, memory and files) or `file_first` (0.9.0: a versioned file
115
115
  * tree, commands run as executions). Immutable. A view without the field (older API) is processful.
116
116
  */
117
117
  get mode(): WorkspaceMode;
@@ -123,7 +123,7 @@ export declare class Workspace {
123
123
  */
124
124
  get treeRevision(): number | null;
125
125
  /**
126
- * Executions of this file-first workspace (contracts §29.8) through `cell()`'s default client: `run(argv, opts)` runs
126
+ * Executions of this file-first workspace through `cell()`'s default client: `run(argv, opts)` runs
127
127
  * a command in a fresh VM on the latest tree and returns an ExecutionResult (output, exit code, `changed`,
128
128
  * `treeRevision`); `get(id, { waitMs })` reads one. See CellClient.executions.
129
129
  *
@@ -135,11 +135,20 @@ export declare class Workspace {
135
135
  get secrets(): WorkspaceSecrets;
136
136
  /** The workspace's text inputs `{NAME: value}` (0.7.0; secret inputs are bound secrets, never listed here). */
137
137
  inputs(): Promise<Record<string, string>>;
138
+ get labels(): Record<string, string>;
139
+ setLabels(labels: Record<string, string>): Promise<this>;
140
+ setIdlePolicy(policy: IdlePolicy | null): Promise<this>;
141
+ idle(signal?: AbortSignal): ReturnType<CellClient['idle']>;
142
+ keepalive(seconds: number, signal?: AbortSignal): ReturnType<CellClient['keepalive']>;
138
143
  refresh(): Promise<this>;
139
- /** Waits for the active operation (if any) and refreshes. */
144
+ /**
145
+ * Waits for the active operation (if any) and refreshes. A suspend-when-idle that found the workspace active is
146
+ * canceled `workspace_active` (0.12.0+: `OperationFailedError.workspaceActive`); the workspace keeps running, so this
147
+ * resolves.
148
+ */
140
149
  waitUntilReady(opts?: WaitOptions): Promise<this>;
141
150
  /**
142
- * Deletes the workspace (tool access ends at once; keys are never reused). Resolves when the delete is REQUESTED;
151
+ * Deletes the workspace (tool access ends at once; the key can be reused after deletion finishes). Resolves when the delete is REQUESTED;
143
152
  * with `{ wait: true }`, once it has FINISHED.
144
153
  */
145
154
  delete(opts: WaitedLifecycleOptions): Promise<FinishedOperation>;
@@ -147,14 +156,17 @@ export declare class Workspace {
147
156
  /**
148
157
  * Suspends the workspace: memory and processes are checkpointed, compute stops. Resolves when the suspend is
149
158
  * REQUESTED (the operation is usually still `queued`, and the workspace still running); pass `{ wait: true }` to
150
- * resolve once it has FINISHED, with `workspace.state` then `suspended`.
159
+ * resolve once it has FINISHED, with `workspace.state` then `suspended`. That is as soon as the workspace is sealed on
160
+ * its host, typically in a few hundred ms. `result.durable` (also `lastTiming.server.durable`, 0.12.0+) turns true
161
+ * when the copy lands in durable storage, typically within a second; `{ durable: true }` resolves only then.
151
162
  *
152
163
  * await workspace.suspend({ wait: true });
164
+ * await workspace.suspend({ durable: true }); // 0.12.0+: also wait for the durable copy
153
165
  */
154
- suspend(opts: WaitedLifecycleOptions): Promise<FinishedOperation>;
155
- suspend(opts?: LifecycleOptions): Promise<Operation>;
166
+ suspend(opts: WaitedSuspendOptions): Promise<FinishedOperation>;
167
+ suspend(opts?: SuspendOptions): Promise<Operation>;
156
168
  /**
157
- * Suspends the workspace once it has been idle for `afterSeconds` (30..3600; 0.9.0): call it when your agent's turn
169
+ * Suspends the workspace once it has been idle for `afterSeconds` (0..3600, 0 = as soon as it is idle; 0.9.0): call it when your agent's turn
158
170
  * ends, so the workspace stops using RAM soon after instead of waiting out its idle policy. A command still running,
159
171
  * an attached stream or a keepalive postpones the suspend until `afterSeconds` after it ends; the next tool call (the
160
172
  * next turn) or a resume cancels it. Resolves with the recorded `suspendRequest` (also `workspace.suspendRequest`), or
@@ -172,11 +184,13 @@ export declare class Workspace {
172
184
  /**
173
185
  * Resumes a suspended workspace. Resolves when the resume is REQUESTED; with `{ wait: true }`, once the workspace runs.
174
186
  * Tool calls wake a suspended workspace by themselves, so this is rarely needed. With `wait` (0.9.0) it is one held
175
- * request (contracts §22.6): the handle takes the running view and a tool token for `agentLabel`/`tools` (defaults:
187
+ * request: the handle takes the running view and a tool token for `agentLabel`/`tools` (defaults:
176
188
  * those given to open()), so `cell()` calls with that label and tools start at once. A workspace that is already
177
189
  * running is ShardfluxApiError 409 `conflict` (`already_running`). The finished operation's `result.memory_restored`
178
190
  * (also `lastTiming.server.memoryRestored`, 0.11.0+) is false when the resume booted the saved disk instead
179
- * (`resume_path` `cold_boot`): files kept, processes restarted.
191
+ * (`resume_path` `cold_boot`): files kept, processes restarted. `result.lost_suspend` (also
192
+ * `lastTiming.server.lostSuspend`, `lostSuspendOf(op)`, 0.12.0+) names a suspend this resume could not restore and the
193
+ * checkpoint it restored instead (see the lifecycle reference).
180
194
  */
181
195
  resume(opts: WaitedResumeOptions): Promise<FinishedOperation>;
182
196
  resume(opts?: ResumeOptions): Promise<Operation>;
@@ -210,7 +224,7 @@ export declare class Workspace {
210
224
  close(opts: WaitedLifecycleOptions): Promise<FinishedOperation | null>;
211
225
  close(opts?: LifecycleOptions): Promise<Operation | null>;
212
226
  /**
213
- * Wipes every change in this layered workspace and restarts it on its template (contracts §19.12; sends
227
+ * Wipes every change in this layered workspace and restarts it on its template (sends
214
228
  * confirm_destructive). Returns the `reset` operation: requested, or with `{ wait: true }` finished. Tool tokens of
215
229
  * the old epoch are dropped.
216
230
  */
@@ -229,7 +243,7 @@ export declare class Workspace {
229
243
  captureToolCalls(opts?: ToolCallCaptureOptions): ToolCallCapture;
230
244
  /** Internal: waits for tool-call capture writes recorded so far (workspaceTools calls it before each tool). */
231
245
  [CAPTURE_BARRIER](): Promise<void> | undefined;
232
- /** Saves this layered workspace as the next version of an organization template (contracts §19.8). */
246
+ /** Saves this layered workspace as the next version of an organization template. */
233
247
  saveAsTemplate(params: SaveAsTemplateParams): Promise<SaveAsTemplateResponse>;
234
248
  /**
235
249
  * The workspace's changes against its template (cell gateway GET /v1/workspaces/{id}/changes; needs the `files` tool).
@@ -255,17 +269,18 @@ export declare class Workspace {
255
269
  tools?: ToolName[];
256
270
  } & CellClientOptions): CellClient;
257
271
  /**
258
- * Makes a suspended (or suspending/resuming) workspace run again and resolves once it does (contracts §20.4): resume,
272
+ * Makes a suspended (or suspending/resuming) workspace run again and resolves once it does: resume,
259
273
  * or join the active resume/open; an active suspend (or other operation) is waited out first. Resolves `true` when it
260
274
  * resumed or waited for a lifecycle operation, `false` when the workspace was already running. One deadline
261
275
  * (`timeoutMs`, default 120 000 ms) covers every step. Throws OperationFailedError when the resume/open fails,
262
276
  * OperationTimeoutError (naming the operation, its state and reason) when it is still queued, running or
263
277
  * capacity_pending at the deadline, and any other API error (a conflict other than already_running /
264
- * operation_in_progress) at once. Cell calls use it automatically when they meet `workspace_not_running`. A resume no
265
- * host admits within 15 minutes fails with `capacity_unavailable` (OperationFailedError, `retryable`: the workspace
266
- * stays suspended with its state; try again later).
278
+ * operation_in_progress) at once. Cell calls use it automatically when they meet `workspace_not_running`. A queued
279
+ * resume has a deadline 15 minutes after it was created; past it, it fails with `capacity_unavailable`
280
+ * (OperationFailedError, `retryable`: nothing was started and the workspace stays suspended with its state; send it
281
+ * again).
267
282
  *
268
- * Since 0.9.0 the resume is held by the server until the workspace runs (contracts §22.6): one request returns the
283
+ * Since 0.9.0 the resume is held by the server until the workspace runs: one request returns the
269
284
  * running workspace and a tool token for `agentLabel`/`tools`, which this handle keeps, so a tool call that woke the
270
285
  * workspace is retried at once (refused call, resume, call). Timing: one `request` phase with reason `held`. A server
271
286
  * that does not hold the request answers at once; the wake then waits for the operation and reads the view.
@@ -276,9 +291,9 @@ export declare class Workspace {
276
291
  */
277
292
  wake(opts?: WakeOptions): Promise<boolean>;
278
293
  /**
279
- * Announces an imminent tool call (cell `POST /wake-hint`, contracts §26.6; 0.9.0+) so a parked workspace is restored
294
+ * Announces an imminent tool call (cell `POST /wake-hint`; 0.9.0+) so a parked workspace is restored
280
295
  * ahead of it: call it when the model starts emitting a tool call, before its arguments are complete. The agent tools
281
- * of `workspaceTools()` send it when a call starts. Cheap and best effort: it returns what the host found. A workspace
296
+ * of `workspaceTools()` send it when a call starts. Cheap and non-blocking: it returns what the host found. A workspace
282
297
  * that is not running (suspended, or a token cannot be issued for it) is woken in the background (`wake`, not awaited
283
298
  * here) so the call finds it running sooner. Other errors (network, auth) are thrown; callers that fire and forget
284
299
  * should catch them.
package/dist/workspace.js CHANGED
@@ -33,7 +33,7 @@ export class Workspace {
33
33
  if (this.#treeRevision === null || rev > this.#treeRevision)
34
34
  this.#treeRevision = rev;
35
35
  }
36
- /** The local refusal of a lifecycle call a file-first workspace does not have (contracts §29.7). */
36
+ /** The local refusal of a lifecycle call a file-first workspace does not have. */
37
37
  #needsVm(operation) {
38
38
  if (this.#view.mode !== 'file_first')
39
39
  return null;
@@ -102,15 +102,15 @@ export class Workspace {
102
102
  get activeOperation() {
103
103
  return this.#view.active_operation;
104
104
  }
105
- /** persistent or session (contracts §19.11); immutable. A view without the field (older API) is persistent. */
105
+ /** persistent or session; immutable. A view without the field (older API) is persistent. */
106
106
  get lifetime() {
107
107
  return this.#view.lifetime ?? 'persistent';
108
108
  }
109
- /** standard, template_draft or template_test (contracts §19.9). */
109
+ /** standard, template_draft or template_test. */
110
110
  get purpose() {
111
111
  return this.#view.purpose ?? 'standard';
112
112
  }
113
- /** legacy or layered (contracts §19.2); reset, save-as-template and changes need layered. */
113
+ /** legacy or layered; reset, save-as-template and changes need layered. */
114
114
  get diskLayout() {
115
115
  return this.#view.disk_layout ?? 'legacy';
116
116
  }
@@ -127,7 +127,7 @@ export class Workspace {
127
127
  return this.#view.ended_reason ?? null;
128
128
  }
129
129
  /**
130
- * Start commands and services of the workspace's template version (0.7.0; contracts §24.4): `state` pending, running,
130
+ * Start commands and services of the workspace's template version (0.7.0): `state` pending, running,
131
131
  * ready or failed (the failed step, its exit code, reason and output tail). Null when the version has neither, or on
132
132
  * an older API. A failed startup leaves the workspace running for inspection; the next open runs the failed step again.
133
133
  */
@@ -147,7 +147,7 @@ export class Workspace {
147
147
  return this.#view;
148
148
  }
149
149
  /**
150
- * `processful` (one VM keeps processes, memory and files) or `file_first` (0.9.0; contracts §29: a versioned file
150
+ * `processful` (one VM keeps processes, memory and files) or `file_first` (0.9.0: a versioned file
151
151
  * tree, commands run as executions). Immutable. A view without the field (older API) is processful.
152
152
  */
153
153
  get mode() {
@@ -163,7 +163,7 @@ export class Workspace {
163
163
  return this.mode === 'file_first' ? this.#treeRevision : null;
164
164
  }
165
165
  /**
166
- * Executions of this file-first workspace (contracts §29.8) through `cell()`'s default client: `run(argv, opts)` runs
166
+ * Executions of this file-first workspace through `cell()`'s default client: `run(argv, opts)` runs
167
167
  * a command in a fresh VM on the latest tree and returns an ExecutionResult (output, exit code, `changed`,
168
168
  * `treeRevision`); `get(id, { waitMs })` reads one. See CellClient.executions.
169
169
  *
@@ -181,17 +181,39 @@ export class Workspace {
181
181
  inputs() {
182
182
  return this.#ctx.workspaces.inputs(this.id);
183
183
  }
184
+ get labels() { return { ...this.#view.labels }; }
185
+ async setLabels(labels) {
186
+ this.#view = (await this.#ctx.workspaces.setLabels(this.id, labels)).data;
187
+ return this;
188
+ }
189
+ async setIdlePolicy(policy) {
190
+ this.#view = (await this.#ctx.workspaces.setIdlePolicy(this.id, policy)).data;
191
+ return this;
192
+ }
193
+ idle(signal) { return this.cell().idle(signal); }
194
+ keepalive(seconds, signal) { return this.cell().keepalive(seconds, signal); }
184
195
  async refresh() {
185
196
  this.#view = await this.#ctx.http.json('GET', `/v1/workspaces/${encodeURIComponent(this.id)}`, {}, this.#ctx.authorization);
186
197
  if (this.#view.mode === 'file_first' && typeof this.#view.tree_revision === 'number')
187
198
  this.#noteTreeRevision(this.#view.tree_revision);
188
199
  return this;
189
200
  }
190
- /** Waits for the active operation (if any) and refreshes. */
201
+ /**
202
+ * Waits for the active operation (if any) and refreshes. A suspend-when-idle that found the workspace active is
203
+ * canceled `workspace_active` (0.12.0+: `OperationFailedError.workspaceActive`); the workspace keeps running, so this
204
+ * resolves.
205
+ */
191
206
  async waitUntilReady(opts = {}) {
192
207
  const op = this.#view.active_operation;
193
- if (op)
194
- await this.#ctx.workspaces.waitForOperation(op.id, opts);
208
+ if (op) {
209
+ try {
210
+ await this.#ctx.workspaces.waitForOperation(op.id, opts);
211
+ }
212
+ catch (e) {
213
+ if (!(e instanceof OperationFailedError) || !e.workspaceActive)
214
+ throw e;
215
+ }
216
+ }
195
217
  return this.refresh();
196
218
  }
197
219
  delete(opts = {}) {
@@ -204,7 +226,7 @@ export class Workspace {
204
226
  return this.#ctx.workspaces.suspend(this.id, { ...this.#tracked(opts), [AFTER_WAIT]: this.#refreshAfterWait() });
205
227
  }
206
228
  /**
207
- * Suspends the workspace once it has been idle for `afterSeconds` (30..3600; 0.9.0): call it when your agent's turn
229
+ * Suspends the workspace once it has been idle for `afterSeconds` (0..3600, 0 = as soon as it is idle; 0.9.0): call it when your agent's turn
208
230
  * ends, so the workspace stops using RAM soon after instead of waiting out its idle policy. A command still running,
209
231
  * an attached stream or a keepalive postpones the suspend until `afterSeconds` after it ends; the next tool call (the
210
232
  * next turn) or a resume cancels it. Resolves with the recorded `suspendRequest` (also `workspace.suspendRequest`), or
@@ -237,7 +259,7 @@ export class Workspace {
237
259
  const manager = this.tokens({ ...(opts.agentLabel !== undefined ? { agentLabel: opts.agentLabel } : {}), ...(opts.tools !== undefined ? { tools: opts.tools } : {}) });
238
260
  return this.#ctx.workspaces.resume(this.id, { ...this.#tracked(opts), [AFTER_WAIT]: this.#refreshAfterWait(), [HELD_RESUME]: this.#heldTarget(manager) });
239
261
  }
240
- /** A held resume's 200 (contracts §22.6) goes straight into this handle: the view, and the token for `manager`. */
262
+ /** A held resume's 200 goes straight into this handle: the view, and the token for `manager`. */
241
263
  #heldTarget(manager) {
242
264
  return {
243
265
  agentLabel: manager.agentLabel,
@@ -315,7 +337,7 @@ export class Workspace {
315
337
  [CAPTURE_BARRIER]() {
316
338
  return this.#ctx.captures.settle(this.id);
317
339
  }
318
- /** Saves this layered workspace as the next version of an organization template (contracts §19.8). */
340
+ /** Saves this layered workspace as the next version of an organization template. */
319
341
  saveAsTemplate(params) {
320
342
  const refusal = this.#needsVm('save_as_template');
321
343
  if (refusal)
@@ -378,17 +400,18 @@ export class Workspace {
378
400
  return c;
379
401
  }
380
402
  /**
381
- * Makes a suspended (or suspending/resuming) workspace run again and resolves once it does (contracts §20.4): resume,
403
+ * Makes a suspended (or suspending/resuming) workspace run again and resolves once it does: resume,
382
404
  * or join the active resume/open; an active suspend (or other operation) is waited out first. Resolves `true` when it
383
405
  * resumed or waited for a lifecycle operation, `false` when the workspace was already running. One deadline
384
406
  * (`timeoutMs`, default 120 000 ms) covers every step. Throws OperationFailedError when the resume/open fails,
385
407
  * OperationTimeoutError (naming the operation, its state and reason) when it is still queued, running or
386
408
  * capacity_pending at the deadline, and any other API error (a conflict other than already_running /
387
- * operation_in_progress) at once. Cell calls use it automatically when they meet `workspace_not_running`. A resume no
388
- * host admits within 15 minutes fails with `capacity_unavailable` (OperationFailedError, `retryable`: the workspace
389
- * stays suspended with its state; try again later).
409
+ * operation_in_progress) at once. Cell calls use it automatically when they meet `workspace_not_running`. A queued
410
+ * resume has a deadline 15 minutes after it was created; past it, it fails with `capacity_unavailable`
411
+ * (OperationFailedError, `retryable`: nothing was started and the workspace stays suspended with its state; send it
412
+ * again).
390
413
  *
391
- * Since 0.9.0 the resume is held by the server until the workspace runs (contracts §22.6): one request returns the
414
+ * Since 0.9.0 the resume is held by the server until the workspace runs: one request returns the
392
415
  * running workspace and a tool token for `agentLabel`/`tools`, which this handle keeps, so a tool call that woke the
393
416
  * workspace is retried at once (refused call, resume, call). Timing: one `request` phase with reason `held`. A server
394
417
  * that does not hold the request answers at once; the wake then waits for the operation and reads the view.
@@ -398,7 +421,7 @@ export class Workspace {
398
421
  * and the `done` progress event say so (`server.memoryRestored === false`, `server.resumePath` `cold_boot`).
399
422
  */
400
423
  async wake(opts = {}) {
401
- // A file-first workspace runs from creation and is never suspended (contracts §29.7): nothing to wake.
424
+ // A file-first workspace runs from creation and is never suspended: nothing to wake.
402
425
  if (this.#view.mode === 'file_first')
403
426
  return false;
404
427
  const trace = new Trace('wake', combineListeners(this.#ctx.onProgress, this.#tracked(opts).onProgress), { workspaceId: this.id });
@@ -415,7 +438,7 @@ export class Workspace {
415
438
  throw opts.signal.reason;
416
439
  let active;
417
440
  try {
418
- // Held resume (contracts §22.6): answered once the workspace runs, with the view and a token; it returns the
441
+ // Held resume: answered once the workspace runs, with the view and a token; it returns the
419
442
  // active resume/open when there is one, so concurrent wakes join a single operation. The request is made here
420
443
  // rather than through resume() so the wake is one trace.
421
444
  const waitS = Math.min(SERVER_WAIT_MAX_S, Math.floor((deadline - Date.now()) / 1000));
@@ -473,9 +496,9 @@ export class Workspace {
473
496
  throw new Error(`workspace ${this.id} did not become runnable after repeated lifecycle conflicts`);
474
497
  }
475
498
  /**
476
- * Announces an imminent tool call (cell `POST /wake-hint`, contracts §26.6; 0.9.0+) so a parked workspace is restored
499
+ * Announces an imminent tool call (cell `POST /wake-hint`; 0.9.0+) so a parked workspace is restored
477
500
  * ahead of it: call it when the model starts emitting a tool call, before its arguments are complete. The agent tools
478
- * of `workspaceTools()` send it when a call starts. Cheap and best effort: it returns what the host found. A workspace
501
+ * of `workspaceTools()` send it when a call starts. Cheap and non-blocking: it returns what the host found. A workspace
479
502
  * that is not running (suspended, or a token cannot be issued for it) is woken in the background (`wake`, not awaited
480
503
  * here) so the call finds it running sooner. Other errors (network, auth) are thrown; callers that fire and forget
481
504
  * should catch them.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shardflux/sdk",
3
- "version": "0.11.0",
3
+ "version": "0.12.0",
4
4
  "type": "module",
5
5
  "description": "Shardflux TypeScript SDK: open persistent agent workspaces by key and give your agent workspace tools (exec, files, processes, PTY, git, browser).",
6
6
  "license": "Apache-2.0",
@@ -71,6 +71,9 @@
71
71
  "yaml": "2.9.1",
72
72
  "zod": "4.6.5"
73
73
  },
74
+ "dependencies": {
75
+ "undici": "8.10.2"
76
+ },
74
77
  "scripts": {
75
78
  "build": "node scripts/build.mjs",
76
79
  "generate": "openapi-typescript ../contracts/openapi/app-api.json -o src/generated/app-api.ts && openapi-typescript ../contracts/openapi/cell-api.yaml --default-non-nullable false -o src/generated/cell-api.ts",