@sealant/sdk 0.22.0 → 0.24.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.
@@ -1122,7 +1122,7 @@ declare const buildControlPlaneClient: (config: SealantInternalConfig) => Effect
1122
1122
  readonly registryId: string;
1123
1123
  readonly repository: string;
1124
1124
  readonly tag: string;
1125
- }, Mode>, import("effect/unstable/http/HttpClientError").HttpClientError | ([Mode] extends ["response-only"] ? never : import("effect/Schema").SchemaError | import("@sealant/api-contracts").WorkspaceBadGatewayError | import("@sealant/api-contracts").WorkspaceBadRequestError | import("@sealant/api-contracts").WorkspaceConflictError | import("@sealant/api-contracts").WorkspaceForbiddenError | import("@sealant/api-contracts").WorkspaceInternalServerError | import("@sealant/api-contracts").WorkspaceNotFoundError | import("@sealant/api-contracts").WorkspaceServiceUnavailableError), [Mode] extends ["response-only"] ? never : never>;
1125
+ }, Mode>, import("effect/unstable/http/HttpClientError").HttpClientError | ([Mode] extends ["response-only"] ? never : import("effect/Schema").SchemaError | import("@sealant/api-contracts").WorkspaceBadGatewayError | import("@sealant/api-contracts").WorkspaceBadRequestError | import("@sealant/api-contracts").WorkspaceConflictError | import("@sealant/api-contracts").WorkspaceForbiddenError | import("@sealant/api-contracts").WorkspaceInternalServerError | import("@sealant/api-contracts").WorkspaceNotFoundError | import("@sealant/api-contracts").WorkspaceRuntimeEnvReferencesUnsupportedError | import("@sealant/api-contracts").WorkspaceServiceUnavailableError), [Mode] extends ["response-only"] ? never : never>;
1126
1126
  readonly execWorkspace: <Mode extends import("effect/unstable/httpapi/HttpApiEndpoint").ClientResponseMode = import("effect/unstable/httpapi/HttpApiEndpoint").ClientResponseMode>(request: {
1127
1127
  readonly params: {
1128
1128
  readonly workspaceId: string;
@@ -1190,7 +1190,7 @@ declare const buildControlPlaneClient: (config: SealantInternalConfig) => Effect
1190
1190
  readonly repository?: string | undefined;
1191
1191
  readonly tag?: string | undefined;
1192
1192
  readonly runtime?: {
1193
- readonly adapter: "docker" | "k3s" | "k8s";
1193
+ readonly adapter: "cloudflare" | "docker" | "k3s" | "k8s";
1194
1194
  readonly resourceId: string;
1195
1195
  readonly reference: string;
1196
1196
  readonly status: "failed" | "pending" | "ready" | "running" | "stopped";
@@ -1225,7 +1225,7 @@ declare const buildControlPlaneClient: (config: SealantInternalConfig) => Effect
1225
1225
  readonly workspaceId: string;
1226
1226
  readonly attemptId: string;
1227
1227
  readonly runtime: {
1228
- readonly adapter: "docker" | "k3s" | "k8s";
1228
+ readonly adapter: "cloudflare" | "docker" | "k3s" | "k8s";
1229
1229
  readonly resourceId: string;
1230
1230
  readonly reference: string;
1231
1231
  readonly status: "failed" | "pending" | "ready" | "running" | "stopped";
@@ -1248,7 +1248,7 @@ declare const buildControlPlaneClient: (config: SealantInternalConfig) => Effect
1248
1248
  readonly triggerType: "api" | "manual" | "retry" | "schedule";
1249
1249
  readonly triggerRef?: string | undefined;
1250
1250
  readonly runtime?: {
1251
- readonly adapter: "docker" | "k3s" | "k8s";
1251
+ readonly adapter: "cloudflare" | "docker" | "k3s" | "k8s";
1252
1252
  readonly resourceId: string;
1253
1253
  readonly reference: string;
1254
1254
  readonly status: "failed" | "pending" | "ready" | "running" | "stopped";
@@ -1309,7 +1309,7 @@ declare const buildControlPlaneClient: (config: SealantInternalConfig) => Effect
1309
1309
  readonly repository?: string | undefined;
1310
1310
  readonly tag?: string | undefined;
1311
1311
  readonly runtime?: {
1312
- readonly adapter: "docker" | "k3s" | "k8s";
1312
+ readonly adapter: "cloudflare" | "docker" | "k3s" | "k8s";
1313
1313
  readonly resourceId: string;
1314
1314
  readonly reference: string;
1315
1315
  readonly status: "failed" | "pending" | "ready" | "running" | "stopped";
@@ -2492,7 +2492,7 @@ declare const SealantApiClient_base: Context.ServiceClass<SealantApiClient, "@se
2492
2492
  readonly registryId: string;
2493
2493
  readonly repository: string;
2494
2494
  readonly tag: string;
2495
- }, Mode>, import("effect/unstable/http/HttpClientError").HttpClientError | ([Mode] extends ["response-only"] ? never : import("effect/Schema").SchemaError | import("@sealant/api-contracts").WorkspaceBadGatewayError | import("@sealant/api-contracts").WorkspaceBadRequestError | import("@sealant/api-contracts").WorkspaceConflictError | import("@sealant/api-contracts").WorkspaceForbiddenError | import("@sealant/api-contracts").WorkspaceInternalServerError | import("@sealant/api-contracts").WorkspaceNotFoundError | import("@sealant/api-contracts").WorkspaceServiceUnavailableError), [Mode] extends ["response-only"] ? never : never>;
2495
+ }, Mode>, import("effect/unstable/http/HttpClientError").HttpClientError | ([Mode] extends ["response-only"] ? never : import("effect/Schema").SchemaError | import("@sealant/api-contracts").WorkspaceBadGatewayError | import("@sealant/api-contracts").WorkspaceBadRequestError | import("@sealant/api-contracts").WorkspaceConflictError | import("@sealant/api-contracts").WorkspaceForbiddenError | import("@sealant/api-contracts").WorkspaceInternalServerError | import("@sealant/api-contracts").WorkspaceNotFoundError | import("@sealant/api-contracts").WorkspaceRuntimeEnvReferencesUnsupportedError | import("@sealant/api-contracts").WorkspaceServiceUnavailableError), [Mode] extends ["response-only"] ? never : never>;
2496
2496
  readonly execWorkspace: <Mode extends import("effect/unstable/httpapi/HttpApiEndpoint").ClientResponseMode = import("effect/unstable/httpapi/HttpApiEndpoint").ClientResponseMode>(request: {
2497
2497
  readonly params: {
2498
2498
  readonly workspaceId: string;
@@ -2560,7 +2560,7 @@ declare const SealantApiClient_base: Context.ServiceClass<SealantApiClient, "@se
2560
2560
  readonly repository?: string | undefined;
2561
2561
  readonly tag?: string | undefined;
2562
2562
  readonly runtime?: {
2563
- readonly adapter: "docker" | "k3s" | "k8s";
2563
+ readonly adapter: "cloudflare" | "docker" | "k3s" | "k8s";
2564
2564
  readonly resourceId: string;
2565
2565
  readonly reference: string;
2566
2566
  readonly status: "failed" | "pending" | "ready" | "running" | "stopped";
@@ -2595,7 +2595,7 @@ declare const SealantApiClient_base: Context.ServiceClass<SealantApiClient, "@se
2595
2595
  readonly workspaceId: string;
2596
2596
  readonly attemptId: string;
2597
2597
  readonly runtime: {
2598
- readonly adapter: "docker" | "k3s" | "k8s";
2598
+ readonly adapter: "cloudflare" | "docker" | "k3s" | "k8s";
2599
2599
  readonly resourceId: string;
2600
2600
  readonly reference: string;
2601
2601
  readonly status: "failed" | "pending" | "ready" | "running" | "stopped";
@@ -2618,7 +2618,7 @@ declare const SealantApiClient_base: Context.ServiceClass<SealantApiClient, "@se
2618
2618
  readonly triggerType: "api" | "manual" | "retry" | "schedule";
2619
2619
  readonly triggerRef?: string | undefined;
2620
2620
  readonly runtime?: {
2621
- readonly adapter: "docker" | "k3s" | "k8s";
2621
+ readonly adapter: "cloudflare" | "docker" | "k3s" | "k8s";
2622
2622
  readonly resourceId: string;
2623
2623
  readonly reference: string;
2624
2624
  readonly status: "failed" | "pending" | "ready" | "running" | "stopped";
@@ -2679,7 +2679,7 @@ declare const SealantApiClient_base: Context.ServiceClass<SealantApiClient, "@se
2679
2679
  readonly repository?: string | undefined;
2680
2680
  readonly tag?: string | undefined;
2681
2681
  readonly runtime?: {
2682
- readonly adapter: "docker" | "k3s" | "k8s";
2682
+ readonly adapter: "cloudflare" | "docker" | "k3s" | "k8s";
2683
2683
  readonly resourceId: string;
2684
2684
  readonly reference: string;
2685
2685
  readonly status: "failed" | "pending" | "ready" | "running" | "stopped";
@@ -36,7 +36,7 @@ export declare const createWorkspaceOp: (payload: {
36
36
  readonly registryId: string;
37
37
  readonly repository: string;
38
38
  readonly tag: string;
39
- }, import("effect/unstable/http/HttpClientError").HttpClientError | import("effect/Schema").SchemaError | import("@sealant/api-contracts").WorkspaceBadGatewayError | import("@sealant/api-contracts").WorkspaceBadRequestError | import("@sealant/api-contracts").WorkspaceConflictError | import("@sealant/api-contracts").WorkspaceForbiddenError | import("@sealant/api-contracts").WorkspaceInternalServerError | import("@sealant/api-contracts").WorkspaceNotFoundError | import("@sealant/api-contracts").WorkspaceServiceUnavailableError, SealantApiClient>;
39
+ }, import("effect/unstable/http/HttpClientError").HttpClientError | import("effect/Schema").SchemaError | import("@sealant/api-contracts").WorkspaceBadGatewayError | import("@sealant/api-contracts").WorkspaceBadRequestError | import("@sealant/api-contracts").WorkspaceConflictError | import("@sealant/api-contracts").WorkspaceForbiddenError | import("@sealant/api-contracts").WorkspaceInternalServerError | import("@sealant/api-contracts").WorkspaceNotFoundError | import("@sealant/api-contracts").WorkspaceRuntimeEnvReferencesUnsupportedError | import("@sealant/api-contracts").WorkspaceServiceUnavailableError, SealantApiClient>;
40
40
  export declare const getWorkspaceOp: (workspaceId: string, ownerUserId?: string | undefined) => Effect.Effect<{
41
41
  readonly workspaceId: string;
42
42
  readonly name: string;
@@ -46,7 +46,7 @@ export declare const getWorkspaceOp: (workspaceId: string, ownerUserId?: string
46
46
  readonly repository?: string | undefined;
47
47
  readonly tag?: string | undefined;
48
48
  readonly runtime?: {
49
- readonly adapter: "docker" | "k3s" | "k8s";
49
+ readonly adapter: "cloudflare" | "docker" | "k3s" | "k8s";
50
50
  readonly resourceId: string;
51
51
  readonly reference: string;
52
52
  readonly status: "failed" | "pending" | "ready" | "running" | "stopped";
@@ -82,7 +82,7 @@ export declare const listWorkspacesOp: (query: {
82
82
  readonly repository?: string | undefined;
83
83
  readonly tag?: string | undefined;
84
84
  readonly runtime?: {
85
- readonly adapter: "docker" | "k3s" | "k8s";
85
+ readonly adapter: "cloudflare" | "docker" | "k3s" | "k8s";
86
86
  readonly resourceId: string;
87
87
  readonly reference: string;
88
88
  readonly status: "failed" | "pending" | "ready" | "running" | "stopped";
@@ -3,18 +3,19 @@ import { closeSessionOp, getSessionOp, getSessionOutputOp, resizeSessionOp, send
3
3
  * Open the held-WebSocket terminal attachment (the data plane). One socket:
4
4
  * binary frames are PTY bytes in both directions, text frames are control
5
5
  * JSON (`{"t":"resize",...}` up, `{"t":"end"}` down). Auth rides the connect —
6
- * `?token=` for apiKey clients (WebSocket cannot set headers), `?ownerUserId=`
7
- * for host-local and never repeats per event.
6
+ * `?ownerUserId=` always, plus `?token=` for apiKey clients (WebSocket cannot
7
+ * set headers; a service principal needs the owner assertion *and* its key)
8
+ * and never repeats per event.
8
9
  */
9
10
  const openAttachment = (ctx, sessionId, options) => {
10
11
  const config = ctx.config;
11
12
  const url = new URL(`/v1/sessions/${sessionId}/attach`, config.baseUrl);
12
13
  url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
13
14
  url.searchParams.set("from", (options?.from ?? 0n).toString());
14
- if (config.apiKey === undefined) {
15
- url.searchParams.set("ownerUserId", config.hostLocal.ownerUserId);
16
- }
17
- else {
15
+ // The owner assertion always rides the URL; a service principal needs it *alongside* its key,
16
+ // and WebSocket cannot carry headers, so the key rides the URL too.
17
+ url.searchParams.set("ownerUserId", config.hostLocal.ownerUserId);
18
+ if (config.apiKey !== undefined) {
18
19
  url.searchParams.set("token", config.apiKey);
19
20
  }
20
21
  return new Promise((resolve, reject) => {
@@ -126,9 +127,7 @@ async function* streamOverSse(ctx, sessionId, from, signal) {
126
127
  const fetchImpl = config.fetch ?? fetch;
127
128
  const url = new URL(`/v1/sessions/${sessionId}/output/stream`, config.baseUrl);
128
129
  url.searchParams.set("from", from.toString());
129
- if (config.apiKey === undefined) {
130
- url.searchParams.set("ownerUserId", config.hostLocal.ownerUserId);
131
- }
130
+ url.searchParams.set("ownerUserId", config.hostLocal.ownerUserId);
132
131
  const response = await fetchImpl(url, {
133
132
  headers: {
134
133
  accept: "text/event-stream",
@@ -214,10 +214,10 @@ const openForward = (ctx, workspaceId, port, options) => {
214
214
  if (options?.protocol === "udp") {
215
215
  url.searchParams.set("protocol", "udp");
216
216
  }
217
- if (config.apiKey === undefined) {
218
- url.searchParams.set("ownerUserId", config.hostLocal.ownerUserId);
219
- }
220
- else {
217
+ // The owner assertion always rides the URL (a service principal needs it alongside its
218
+ // key, and WebSocket cannot carry headers — same contract as the session attach).
219
+ url.searchParams.set("ownerUserId", config.hostLocal.ownerUserId);
220
+ if (config.apiKey !== undefined) {
221
221
  url.searchParams.set("token", config.apiKey);
222
222
  }
223
223
  return new Promise((resolve, reject) => {
@@ -4,7 +4,8 @@
4
4
  * no contract change. The public `repository` is the SOURCE git repo (it becomes
5
5
  * `spec.sources.workspace.url`); the contract's `repository`/`tag` are the OCI push coordinates, which
6
6
  * we derive. `customization.enableSealantd` is forced on (it bakes + launches the daemon the run path
7
- * connects to), the runtime target is pinned to docker (the only bridgeable adapter today), and the
7
+ * connects to), the runtime target is `auto` (the deployment's default adapter — Docker on self-host, Kubernetes
8
+ * when the worker is configured for a cluster), and the
8
9
  * foreground is a keepalive so the workspace idles with the daemon up and the harness is exec'd on
9
10
  * demand by `run()` rather than launched at boot. `options.credentials`, if present, is lowered via
10
11
  * `mapWorkspaceCredentials` (see `./credentials.js`) and folded into `spec.credentials`; the control
@@ -123,6 +124,12 @@ export const buildCreateWorkspaceRequest = (options, config) => {
123
124
  })),
124
125
  }),
125
126
  ...(userEnv === undefined || Object.keys(userEnv).length === 0 ? {} : { userEnv }),
127
+ ...(options.envFrom === undefined || options.envFrom.length === 0
128
+ ? {}
129
+ : { envFrom: options.envFrom.map(({ kind, name }) => ({ kind, name })) }),
130
+ ...(options.kubernetes?.serviceAccountName === undefined
131
+ ? {}
132
+ : { kubernetes: { serviceAccountName: options.kubernetes.serviceAccountName } }),
126
133
  };
127
134
  const spec = {
128
135
  version: "1",
@@ -182,7 +189,9 @@ export const buildCreateWorkspaceRequest = (options, config) => {
182
189
  os: options.baseImage !== undefined
183
190
  ? { family: "custom", mode: "require", baseImage: options.baseImage }
184
191
  : { family: options.os ?? "fedora", mode: "prefer" },
185
- runtime: { family: "docker", mode: "require" },
192
+ // `auto` = the deployment's DEFAULT_RUNTIME_ADAPTER. SDK callers don't know (and must not
193
+ // care) whether the control plane runs workspaces as containers or Pods.
194
+ runtime: { family: "auto", mode: "prefer" },
186
195
  },
187
196
  lifecycle: {
188
197
  startup: { foreground: { kind: "command", run: "sleep infinity", shell: "bash" } },
package/dist/types.d.ts CHANGED
@@ -188,6 +188,12 @@ export interface WorkspaceServicesOptions {
188
188
  */
189
189
  readonly docker?: boolean;
190
190
  }
191
+ /** One cluster env source: a Kubernetes object whose keys become workspace environment. */
192
+ export interface WorkspaceEnvFromSource {
193
+ readonly kind: "secret" | "configmap";
194
+ /** Kubernetes object name (DNS-1123 subdomain) in the platform's workspaces namespace. */
195
+ readonly name: string;
196
+ }
191
197
  export interface CreateOptions {
192
198
  /**
193
199
  * Source git repository to build the workspace around (e.g. `"github.com/acme/billing-service"`).
@@ -255,6 +261,31 @@ export interface CreateOptions {
255
261
  * instead). Docker runtime only.
256
262
  */
257
263
  readonly secretEnv?: Readonly<Record<string, string>>;
264
+ /**
265
+ * Cluster env sources — Kubernetes runtimes only. Each entry names one Kubernetes `Secret` or
266
+ * `ConfigMap` in the platform's workspaces namespace whose KEYS become workspace environment,
267
+ * resolved by the platform worker at workspace creation (a launch keeps its snapshot across
268
+ * container restarts; rotation reaches only later launches). Ordered, last wins across kinds.
269
+ * Only objects the operator opted in with the `sealant.sh/workspace-env: "true"` label
270
+ * resolve; platform-managed objects are refused unconditionally. Explicit `env` / `secretEnv`
271
+ * and platform-owned names always win over bound keys; bound Secret values ride the transient
272
+ * secret channel and are masked in captured output. On a deployment whose workspaces do not
273
+ * run on Kubernetes, create is refused synchronously with the stable error code
274
+ * `runtime-env-references-unsupported` — no workspace is created.
275
+ */
276
+ readonly envFrom?: readonly WorkspaceEnvFromSource[];
277
+ /**
278
+ * Kubernetes-runtime-only settings. `serviceAccountName` is an explicit TRUST GRANT: the
279
+ * workspace Pod runs under it, and with IRSA / Workload Identity the session agent holds that
280
+ * role's full permissions for the whole session — bind a least-privilege role intended for
281
+ * untrusted code. Honored only against the install's allowlist
282
+ * (`SEALANT_K8S_ALLOWED_WORKSPACE_SERVICE_ACCOUNTS`); other names fail the launch readable.
283
+ * The Kubernetes API token is never automounted regardless. Non-Kubernetes deployments refuse
284
+ * at create time with `runtime-env-references-unsupported`.
285
+ */
286
+ readonly kubernetes?: {
287
+ readonly serviceAccountName?: string;
288
+ };
258
289
  /** Runtime-managed services that need more than installing an OS package. */
259
290
  readonly services?: WorkspaceServicesOptions;
260
291
  /** When true (default), resolve only once the workspace runtime is live. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sealant/sdk",
3
- "version": "0.22.0",
3
+ "version": "0.24.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.22.0"
29
+ "@sealant/api-contracts": "^0.24.0"
30
30
  },
31
31
  "devDependencies": {
32
32
  "@effect/vitest": "4.0.0-beta.85",