@sealant/sdk 0.15.0 → 0.17.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.
package/README.md CHANGED
@@ -105,8 +105,9 @@ and injects them at launch.
105
105
 
106
106
  ## Workspace tools and services
107
107
 
108
- Choose a supported operating-system family, request portable package names, and opt into services
109
- that need runtime support rather than a package install:
108
+ Choose a supported operating-system family `fedora` (the default), `arch`, `nix`, or `ubuntu` —
109
+ request portable package names, and opt into services that need runtime support rather than a
110
+ package install:
110
111
 
111
112
  ```ts
112
113
  const workspace = await sealant.workspaces.create({
@@ -123,6 +124,36 @@ const workspace = await sealant.workspaces.create({
123
124
  rootless daemon at launch. The workspace receives `DOCKER_HOST`; Sealant never mounts the host
124
125
  Docker socket. GitHub credentials provide both `GH_TOKEN` and `GITHUB_TOKEN` to the workspace.
125
126
 
127
+ ## Custom base images
128
+
129
+ Instead of a managed OS family, a workspace image can be built from any image reference you already
130
+ trust:
131
+
132
+ ```ts
133
+ const workspace = await sealant.workspaces.create({
134
+ repository: "github.com/acme/billing-service",
135
+ harness: codex(),
136
+ baseImage: "node:22-bookworm",
137
+ });
138
+ ```
139
+
140
+ Distro package installs are skipped entirely — the build overlays only the `sealantd` supervisor
141
+ (PID 1), the harness CLIs (installed with `npm`), and a fully static `socat` (the control-socket
142
+ relay), all copied in as static binaries. This is the **base-image contract**, checked at build time
143
+ with readable failures:
144
+
145
+ - **Any Linux base, `amd64`/`arm64`**, with a **POSIX shell** at `/bin/sh`. Shells beyond that are
146
+ not assumed: the workspace login shell is `/bin/sh`, and `defaultShell` selection is not supported
147
+ with `baseImage`.
148
+ - **Node.js + npm** at or above the harness CLIs' floor (the CLIs are installed with
149
+ `npm install -g` and run on the base's node).
150
+ - **git**, for clone- and mount-sourced workspaces.
151
+
152
+ `packages` still works: names pass through **verbatim** (no portable-name resolution) to the base's
153
+ own package manager — `apt`, `apk`, `dnf`, or `pacman`, autodetected — and the build fails with a
154
+ readable error when the base has none. Dotfiles are not supported with `baseImage`. `baseImage` and
155
+ `os` are mutually exclusive.
156
+
126
157
  ## Inference on connected accounts
127
158
 
128
159
  Run short, tool-calling inference loops on the caller's own subscription — server-side, through the
@@ -294,7 +294,7 @@ declare const buildControlPlaneClient: (config: SealantInternalConfig) => Effect
294
294
  readonly resolvePackage: <Mode extends import("effect/unstable/httpapi/HttpApiEndpoint").ClientResponseMode = import("effect/unstable/httpapi/HttpApiEndpoint").ClientResponseMode>(request: {
295
295
  readonly query: {
296
296
  readonly query: string;
297
- readonly targetOs?: "arch" | "fedora" | "nix" | undefined;
297
+ readonly targetOs?: "arch" | "fedora" | "nix" | "ubuntu" | undefined;
298
298
  };
299
299
  readonly responseMode?: Mode;
300
300
  }) => Effect.Effect<HttpApiClient.Client.Response<{
@@ -329,6 +329,14 @@ declare const buildControlPlaneClient: (config: SealantInternalConfig) => Effect
329
329
  readonly version?: string | undefined;
330
330
  readonly status?: string | undefined;
331
331
  };
332
+ readonly ubuntu: {
333
+ readonly supported: boolean;
334
+ readonly repo?: string | undefined;
335
+ readonly packageName?: string | undefined;
336
+ readonly projectName?: string | undefined;
337
+ readonly version?: string | undefined;
338
+ readonly status?: string | undefined;
339
+ };
332
340
  };
333
341
  readonly alternatives: readonly {
334
342
  readonly projectName: string;
@@ -1604,7 +1612,7 @@ declare const SealantApiClient_base: Context.ServiceClass<SealantApiClient, "@se
1604
1612
  readonly resolvePackage: <Mode extends import("effect/unstable/httpapi/HttpApiEndpoint").ClientResponseMode = import("effect/unstable/httpapi/HttpApiEndpoint").ClientResponseMode>(request: {
1605
1613
  readonly query: {
1606
1614
  readonly query: string;
1607
- readonly targetOs?: "arch" | "fedora" | "nix" | undefined;
1615
+ readonly targetOs?: "arch" | "fedora" | "nix" | "ubuntu" | undefined;
1608
1616
  };
1609
1617
  readonly responseMode?: Mode;
1610
1618
  }) => Effect.Effect<HttpApiClient.Client.Response<{
@@ -1639,6 +1647,14 @@ declare const SealantApiClient_base: Context.ServiceClass<SealantApiClient, "@se
1639
1647
  readonly version?: string | undefined;
1640
1648
  readonly status?: string | undefined;
1641
1649
  };
1650
+ readonly ubuntu: {
1651
+ readonly supported: boolean;
1652
+ readonly repo?: string | undefined;
1653
+ readonly packageName?: string | undefined;
1654
+ readonly projectName?: string | undefined;
1655
+ readonly version?: string | undefined;
1656
+ readonly status?: string | undefined;
1657
+ };
1642
1658
  };
1643
1659
  readonly alternatives: readonly {
1644
1660
  readonly projectName: string;
@@ -210,6 +210,9 @@ const openForward = (ctx, workspaceId, port, options) => {
210
210
  if (options?.host !== undefined) {
211
211
  url.searchParams.set("host", options.host);
212
212
  }
213
+ if (options?.protocol === "udp") {
214
+ url.searchParams.set("protocol", "udp");
215
+ }
213
216
  if (config.apiKey === undefined) {
214
217
  url.searchParams.set("ownerUserId", config.hostLocal.ownerUserId);
215
218
  }
@@ -38,6 +38,9 @@ export const buildCreateWorkspaceRequest = (options, config) => {
38
38
  code: "invalid_create_options",
39
39
  });
40
40
  }
41
+ if (options.os !== undefined && options.baseImage !== undefined) {
42
+ throw new SealantError("workspaces.create accepts either `os` (a managed OS family) or `baseImage` (a custom base image reference), not both.", { code: "invalid_create_options" });
43
+ }
41
44
  const sourceName = options.repository ?? options.source?.path ?? "workspace";
42
45
  const tail = sourceName
43
46
  .split("/")
@@ -95,7 +98,9 @@ export const buildCreateWorkspaceRequest = (options, config) => {
95
98
  harness: { id: options.harness.id },
96
99
  customization: { enableSealantd: true },
97
100
  target: {
98
- os: { family: options.os ?? "fedora", mode: "prefer" },
101
+ os: options.baseImage !== undefined
102
+ ? { family: "custom", mode: "require", baseImage: options.baseImage }
103
+ : { family: options.os ?? "fedora", mode: "prefer" },
99
104
  runtime: { family: "docker", mode: "require" },
100
105
  },
101
106
  lifecycle: {
package/dist/types.d.ts CHANGED
@@ -65,7 +65,8 @@ export interface WorkspaceEvent {
65
65
  readonly message?: string;
66
66
  }
67
67
  /** The supported workspace OS families (maps to the blueprint target). */
68
- export type WorkspaceOs = "fedora" | "arch" | "nix";
68
+ /** Supported workspace image OS families. `fedora` is the default when `os` is omitted. */
69
+ export type WorkspaceOs = "fedora" | "arch" | "nix" | "ubuntu";
69
70
  /**
70
71
  * Connected-account credentials to attach to a workspace at creation time, per provider — so the
71
72
  * harness inside the workspace authenticates as the caller's own Claude / Codex / GitHub identity
@@ -146,8 +147,17 @@ export interface CreateOptions {
146
147
  readonly ref?: string;
147
148
  /** Human-friendly name for the workspace. */
148
149
  readonly name?: string;
149
- /** OS family for the workspace image. */
150
+ /** OS family for the workspace image. Mutually exclusive with `baseImage`. */
150
151
  readonly os?: WorkspaceOs;
152
+ /**
153
+ * Build the workspace image FROM this arbitrary OCI image reference instead of a managed OS
154
+ * family (e.g. `"node:22-bookworm"`). Distro package installs are skipped; the build overlays
155
+ * only the sealantd supervisor, the harness CLIs (npm), and a static socat relay. See "Custom
156
+ * base images" in the SDK README for the base-image contract. Mutually exclusive with `os`;
157
+ * `packages` install through the base's own package manager (apt/apk/dnf/pacman) and fail the
158
+ * build readable when it has none.
159
+ */
160
+ readonly baseImage?: string;
151
161
  /** Extra OS packages to install in the workspace. */
152
162
  readonly packages?: readonly string[];
153
163
  /** Runtime-managed services that need more than installing an OS package. */
@@ -221,7 +231,7 @@ export interface Workspace {
221
231
  readonly in?: string | null;
222
232
  }): Promise<void>;
223
233
  /**
224
- * Open a raw TCP byte pipe INSIDE the workspace — the primitive for
234
+ * Open a raw TCP byte pipe (or a UDP datagram pipe) INSIDE the workspace — the primitive for
225
235
  * reaching a dev server or database the workspace runs. Protocol-agnostic:
226
236
  * nothing inspects or records the payload. One held WebSocket per forward;
227
237
  * rejects when nothing accepts the connection. The target host is a CLOSED
@@ -235,6 +245,13 @@ export interface Workspace {
235
245
  export interface WorkspaceForwardOptions {
236
246
  /** Target inside the workspace: its loopback (default) or the Docker sidecar. */
237
247
  readonly host?: "127.0.0.1" | "localhost" | "docker";
248
+ /**
249
+ * Forward transport. TCP (default) is a byte stream; `"udp"` opens a
250
+ * connected UDP socket where one frame on this pipe is exactly one
251
+ * datagram, both directions. UDP has no connection handshake: opening
252
+ * succeeds even when nothing listens yet — datagrams simply drop.
253
+ */
254
+ readonly protocol?: "tcp" | "udp";
238
255
  }
239
256
  /**
240
257
  * A live port forward — one WebSocket, held until `close()` or the remote
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sealant/sdk",
3
- "version": "0.15.0",
3
+ "version": "0.17.0",
4
4
  "description": "The fluent public SDK for Sealant — create a workspace, run a harness, replay the record.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -26,7 +26,7 @@
26
26
  "access": "public"
27
27
  },
28
28
  "dependencies": {
29
- "@sealant/api-contracts": "^0.15.0"
29
+ "@sealant/api-contracts": "^0.17.0"
30
30
  },
31
31
  "devDependencies": {
32
32
  "@effect/vitest": "4.0.0-beta.85",