@sealant/sdk 0.3.1 → 0.5.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.
@@ -0,0 +1,5 @@
1
+ import type { SdkContext } from "../facade/context.js";
2
+ import type { WorkspaceInit } from "../facade/workspace.js";
3
+ import type { WorkspaceExecOptions, WorkspaceExecResult } from "../types.js";
4
+ /** The `workspace.exec()` implementation (Promise boundary over the Effect above). */
5
+ export declare const execWorkspace: (ctx: SdkContext, init: WorkspaceInit, argv: readonly string[], options?: WorkspaceExecOptions | undefined) => Promise<WorkspaceExecResult>;
@@ -0,0 +1,60 @@
1
+ import { Effect } from "effect";
2
+ import { SealantError } from "../errors.js";
3
+ import { makeRun, toRunChangesData } from "../facade/run.js";
4
+ import { execWorkspaceOp, getRunChangesOp, getRunOp, getRunScrollbackOp, getRunTimelineOp, } from "./operations.js";
5
+ const TERMINAL_STATUSES = new Set(["completed", "failed", "cancelled"]);
6
+ const POLL_INTERVAL = "500 millis";
7
+ const EXEC_TIMEOUT_MS = 30 * 60 * 1_000;
8
+ const isRecord = (value) => typeof value === "object" && value !== null;
9
+ /**
10
+ * The exec'd command's process: the `processStarted` entry whose recorded executable matches, or the
11
+ * first one when attribution is imprecise (the record contains only processes this run exec'd).
12
+ */
13
+ const findCommandProcessId = (entries, executable) => {
14
+ const match = entries.find((entry) => isRecord(entry.ref) && entry.ref["executable"] === executable);
15
+ return (match ?? entries[0])?.processId;
16
+ };
17
+ const readScrollback = (runId, processId, stream) => Effect.map(getRunScrollbackOp(runId, { processId, stream }), (response) => Buffer.from(response.contentBase64, "base64").toString("utf8"));
18
+ const execWorkspaceEffect = (ctx, init, argv, options) => Effect.gen(function* () {
19
+ const [executable, ...args] = argv;
20
+ if (executable === undefined || executable.length === 0) {
21
+ return yield* Effect.fail(new SealantError("exec requires argv with at least the executable.", {
22
+ code: "invalid_argv",
23
+ }));
24
+ }
25
+ const created = yield* execWorkspaceOp(init.id, {
26
+ ownerUserId: ctx.config.hostLocal.ownerUserId,
27
+ commands: [{ executable, args, ...(options?.cwd === undefined ? {} : { cwd: options.cwd }) }],
28
+ });
29
+ const runId = created.runId;
30
+ // Block until the check run is terminal, polling the control plane (same shape as harness.run()).
31
+ const deadline = Date.now() + EXEC_TIMEOUT_MS;
32
+ let wire = created;
33
+ while (!TERMINAL_STATUSES.has(wire.status)) {
34
+ if (Date.now() > deadline) {
35
+ return yield* Effect.fail(new SealantError(`Timed out waiting for exec run ${runId} to complete.`, {
36
+ code: "exec_timeout",
37
+ }));
38
+ }
39
+ yield* Effect.sleep(POLL_INTERVAL);
40
+ wire = yield* getRunOp(runId);
41
+ }
42
+ // Exec framing: "completed" means every command executed and was recorded — anything else means
43
+ // the machinery broke and the exit code cannot be trusted, which IS the error case.
44
+ if (wire.status !== "completed") {
45
+ return yield* Effect.fail(new SealantError(`Workspace exec did not complete: ${wire.errorMessage ?? `run ${runId} is ${wire.status}`}`, { code: "exec_failed" }));
46
+ }
47
+ const started = yield* getRunTimelineOp(runId, { kinds: "processStarted" });
48
+ const processId = findCommandProcessId(started, executable);
49
+ const stdout = processId === undefined ? "" : yield* readScrollback(runId, processId, "stdout");
50
+ const stderr = processId === undefined ? "" : yield* readScrollback(runId, processId, "stderr");
51
+ const changes = toRunChangesData(yield* getRunChangesOp(runId));
52
+ return {
53
+ exitCode: wire.exitCode ?? -1,
54
+ stdout,
55
+ stderr,
56
+ run: makeRun(ctx, { wire, changes }),
57
+ };
58
+ });
59
+ /** The `workspace.exec()` implementation (Promise boundary over the Effect above). */
60
+ export const execWorkspace = (ctx, init, argv, options) => ctx.runtime.run(execWorkspaceEffect(ctx, init, argv, options));
@@ -0,0 +1,27 @@
1
+ /**
2
+ * @sealant/sdk/effect — the Effect-native core of the SDK, for consumers that are Effect end-to-end.
3
+ *
4
+ * The Promise facade (`@sealant/sdk`) is a thin wrapper over what this subpath exports directly:
5
+ *
6
+ * - `SealantApiClient` + `sealantApiClientLayer` — the contract-derived control-plane client as an
7
+ * Effect service. The client is generated from the `@sealant/api-contracts` `HttpApi`, so its
8
+ * request/response types and tagged error channel are the contract's, by construction.
9
+ * - The operation effects (`createWorkspaceOp`, `createRunOp`, `getRunTimelineOp`, …) — one per
10
+ * contract endpoint, returning WIRE types on a typed error channel.
11
+ * - `makeSdkRuntime` — the managed runtime the facade itself runs on, for consumers who want the
12
+ * memoized layer build + `SealantError` mapping without hand-rolling a scope.
13
+ * - The tagged contract errors (`WorkspaceNotFoundError`, `RunNotFoundError`, …) so failures can be
14
+ * matched with `Effect.catchTag` instead of string-matching a squashed `SealantError`.
15
+ *
16
+ * Errors: effects composed from this subpath fail with the TYPED contract errors (plus Effect HTTP
17
+ * client/schema errors) — nothing is squashed. The plain `SealantError` classes only enter play via
18
+ * `makeSdkRuntime`, which maps the typed channel at the Promise boundary exactly like the facade.
19
+ */
20
+ export { SealantApiClient, sealantApiClientLayer } from "./api-client.js";
21
+ export type { ControlPlaneClient } from "./api-client.js";
22
+ export * from "./operations.js";
23
+ export { makeSdkRuntime } from "./runtime.js";
24
+ export type { SdkRuntime, SdkServices } from "./runtime.js";
25
+ export { resolveInternalConfig } from "../internal/config.js";
26
+ export type { SealantHostLocalConfig, SealantInternalConfig } from "../internal/config.js";
27
+ export { InferenceBadRequestError, InferenceConflictError, InferenceInternalServerError, InferenceNotFoundError, InferenceUnavailableError, RunBadRequestError, RunInternalServerError, RunNotFoundError, WorkspaceBadGatewayError, WorkspaceBadRequestError, WorkspaceConflictError, WorkspaceForbiddenError, WorkspaceInternalServerError, WorkspaceNotFoundError, WorkspaceServiceUnavailableError, WorkspaceUnauthorizedError, } from "@sealant/api-contracts";
@@ -0,0 +1,31 @@
1
+ /**
2
+ * @sealant/sdk/effect — the Effect-native core of the SDK, for consumers that are Effect end-to-end.
3
+ *
4
+ * The Promise facade (`@sealant/sdk`) is a thin wrapper over what this subpath exports directly:
5
+ *
6
+ * - `SealantApiClient` + `sealantApiClientLayer` — the contract-derived control-plane client as an
7
+ * Effect service. The client is generated from the `@sealant/api-contracts` `HttpApi`, so its
8
+ * request/response types and tagged error channel are the contract's, by construction.
9
+ * - The operation effects (`createWorkspaceOp`, `createRunOp`, `getRunTimelineOp`, …) — one per
10
+ * contract endpoint, returning WIRE types on a typed error channel.
11
+ * - `makeSdkRuntime` — the managed runtime the facade itself runs on, for consumers who want the
12
+ * memoized layer build + `SealantError` mapping without hand-rolling a scope.
13
+ * - The tagged contract errors (`WorkspaceNotFoundError`, `RunNotFoundError`, …) so failures can be
14
+ * matched with `Effect.catchTag` instead of string-matching a squashed `SealantError`.
15
+ *
16
+ * Errors: effects composed from this subpath fail with the TYPED contract errors (plus Effect HTTP
17
+ * client/schema errors) — nothing is squashed. The plain `SealantError` classes only enter play via
18
+ * `makeSdkRuntime`, which maps the typed channel at the Promise boundary exactly like the facade.
19
+ */
20
+ // The contract-derived client service + layer.
21
+ export { SealantApiClient, sealantApiClientLayer } from "./api-client.js";
22
+ // One operation effect per contract endpoint (wire types in, wire types out).
23
+ export * from "./operations.js";
24
+ // The managed runtime the Promise facade runs on.
25
+ export { makeSdkRuntime } from "./runtime.js";
26
+ // The config the layer/runtime constructors take, and the resolver from the public `SealantConfig`.
27
+ export { resolveInternalConfig } from "../internal/config.js";
28
+ // The typed contract errors carried on the client's failure channel (workspaces + runs +
29
+ // inference — the groups the operations above call). Re-exported so Effect consumers don't need to
30
+ // depend on the contracts package directly to `Effect.catchTag` a failure.
31
+ export { InferenceBadRequestError, InferenceConflictError, InferenceInternalServerError, InferenceNotFoundError, InferenceUnavailableError, RunBadRequestError, RunInternalServerError, RunNotFoundError, WorkspaceBadGatewayError, WorkspaceBadRequestError, WorkspaceConflictError, WorkspaceForbiddenError, WorkspaceInternalServerError, WorkspaceNotFoundError, WorkspaceServiceUnavailableError, WorkspaceUnauthorizedError, } from "@sealant/api-contracts";
@@ -1,6 +1,6 @@
1
1
  import { Effect } from "effect";
2
2
  import { SealantApiClient } from "./api-client.js";
3
- export declare const createSandboxOp: (payload: {
3
+ export declare const createWorkspaceOp: (payload: {
4
4
  readonly ownerUserId: string;
5
5
  readonly registryId: string;
6
6
  readonly repository: string;
@@ -26,15 +26,15 @@ export declare const createSandboxOp: (payload: {
26
26
  } | undefined;
27
27
  readonly spec: unknown;
28
28
  }, idempotencyKey?: string | undefined) => Effect.Effect<{
29
- readonly sandboxId: string;
29
+ readonly workspaceId: string;
30
30
  readonly name: string;
31
31
  readonly status: "cancelled" | "failed" | "queued" | "ready" | "running";
32
32
  readonly registryId: string;
33
33
  readonly repository: string;
34
34
  readonly tag: string;
35
- }, import("effect/unstable/http/HttpClientError").HttpClientError | import("@sealant/api-contracts").SandboxBadGatewayError | import("@sealant/api-contracts").SandboxBadRequestError | import("@sealant/api-contracts").SandboxConflictError | import("@sealant/api-contracts").SandboxForbiddenError | import("@sealant/api-contracts").SandboxInternalServerError | import("@sealant/api-contracts").SandboxNotFoundError | import("@sealant/api-contracts").SandboxServiceUnavailableError | import("effect/Schema").SchemaError, SealantApiClient>;
36
- export declare const getSandboxOp: (sandboxId: string) => Effect.Effect<{
37
- readonly sandboxId: string;
35
+ }, 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>;
36
+ export declare const getWorkspaceOp: (workspaceId: string) => Effect.Effect<{
37
+ readonly workspaceId: string;
38
38
  readonly name: string;
39
39
  readonly ownerUserId: string;
40
40
  readonly status: "cancelled" | "failed" | "queued" | "ready" | "running";
@@ -62,14 +62,14 @@ export declare const getSandboxOp: (sandboxId: string) => Effect.Effect<{
62
62
  readonly startedAt?: string | undefined;
63
63
  readonly finishedAt?: string | undefined;
64
64
  readonly spec?: unknown;
65
- }, unknown, SealantApiClient>;
66
- export declare const listSandboxesOp: (query: {
65
+ }, import("effect/unstable/http/HttpClientError").HttpClientError | import("effect/Schema").SchemaError | import("@sealant/api-contracts").WorkspaceInternalServerError | import("@sealant/api-contracts").WorkspaceNotFoundError, SealantApiClient>;
66
+ export declare const listWorkspacesOp: (query: {
67
67
  readonly ownerUserId: string;
68
68
  readonly status?: "cancelled" | "failed" | "queued" | "ready" | "running" | undefined;
69
69
  readonly limit?: string | undefined;
70
70
  }) => Effect.Effect<{
71
71
  readonly items: readonly {
72
- readonly sandboxId: string;
72
+ readonly workspaceId: string;
73
73
  readonly name: string;
74
74
  readonly ownerUserId: string;
75
75
  readonly status: "cancelled" | "failed" | "queued" | "ready" | "running";
@@ -97,9 +97,32 @@ export declare const listSandboxesOp: (query: {
97
97
  readonly startedAt?: string | undefined;
98
98
  readonly finishedAt?: string | undefined;
99
99
  }[];
100
- }, import("effect/unstable/http/HttpClientError").HttpClientError | import("@sealant/api-contracts").SandboxBadRequestError | import("@sealant/api-contracts").SandboxInternalServerError | import("effect/Schema").SchemaError, SealantApiClient>;
100
+ }, import("effect/unstable/http/HttpClientError").HttpClientError | import("effect/Schema").SchemaError | import("@sealant/api-contracts").WorkspaceBadRequestError | import("@sealant/api-contracts").WorkspaceInternalServerError, SealantApiClient>;
101
+ export declare const execWorkspaceOp: (workspaceId: string, payload: {
102
+ readonly ownerUserId: string;
103
+ readonly commands: readonly {
104
+ readonly executable: string;
105
+ readonly args: readonly string[];
106
+ readonly cwd?: string | undefined;
107
+ }[];
108
+ }) => Effect.Effect<{
109
+ readonly runId: string;
110
+ readonly workspaceId: string;
111
+ readonly attemptId?: string | undefined;
112
+ readonly ownerUserId: string;
113
+ readonly harnessId: string;
114
+ readonly mode: "interactive" | "one-shot";
115
+ readonly status: "cancelled" | "completed" | "failed" | "queued" | "running";
116
+ readonly prompt?: string | undefined;
117
+ readonly exitCode?: number | undefined;
118
+ readonly errorMessage?: string | undefined;
119
+ readonly startedAt?: string | undefined;
120
+ readonly finishedAt?: string | undefined;
121
+ readonly createdAt: string;
122
+ readonly updatedAt: string;
123
+ }, import("effect/unstable/http/HttpClientError").HttpClientError | import("effect/Schema").SchemaError | import("@sealant/api-contracts").WorkspaceBadRequestError | import("@sealant/api-contracts").WorkspaceConflictError | import("@sealant/api-contracts").WorkspaceInternalServerError | import("@sealant/api-contracts").WorkspaceNotFoundError, SealantApiClient>;
101
124
  export declare const createRunOp: (payload: {
102
- readonly sandboxId: string;
125
+ readonly workspaceId: string;
103
126
  readonly harnessId: string;
104
127
  readonly ownerUserId: string;
105
128
  readonly mode?: "interactive" | "one-shot" | undefined;
@@ -112,7 +135,7 @@ export declare const createRunOp: (payload: {
112
135
  } | undefined;
113
136
  }) => Effect.Effect<{
114
137
  readonly runId: string;
115
- readonly sandboxId: string;
138
+ readonly workspaceId: string;
116
139
  readonly attemptId?: string | undefined;
117
140
  readonly ownerUserId: string;
118
141
  readonly harnessId: string;
@@ -125,10 +148,10 @@ export declare const createRunOp: (payload: {
125
148
  readonly finishedAt?: string | undefined;
126
149
  readonly createdAt: string;
127
150
  readonly updatedAt: string;
128
- }, unknown, SealantApiClient>;
151
+ }, import("effect/unstable/http/HttpClientError").HttpClientError | import("@sealant/api-contracts").RunBadRequestError | import("@sealant/api-contracts").RunInternalServerError | import("@sealant/api-contracts").RunNotFoundError | import("effect/Schema").SchemaError, SealantApiClient>;
129
152
  export declare const getRunOp: (runId: string) => Effect.Effect<{
130
153
  readonly runId: string;
131
- readonly sandboxId: string;
154
+ readonly workspaceId: string;
132
155
  readonly attemptId?: string | undefined;
133
156
  readonly ownerUserId: string;
134
157
  readonly harnessId: string;
@@ -141,16 +164,16 @@ export declare const getRunOp: (runId: string) => Effect.Effect<{
141
164
  readonly finishedAt?: string | undefined;
142
165
  readonly createdAt: string;
143
166
  readonly updatedAt: string;
144
- }, unknown, SealantApiClient>;
167
+ }, import("effect/unstable/http/HttpClientError").HttpClientError | import("@sealant/api-contracts").RunInternalServerError | import("@sealant/api-contracts").RunNotFoundError | import("effect/Schema").SchemaError, SealantApiClient>;
145
168
  export declare const listRunsOp: (query: {
146
- readonly sandboxId?: string | undefined;
169
+ readonly workspaceId?: string | undefined;
147
170
  readonly ownerUserId?: string | undefined;
148
171
  readonly status?: "cancelled" | "completed" | "failed" | "queued" | "running" | undefined;
149
172
  readonly limit?: string | undefined;
150
173
  }) => Effect.Effect<{
151
174
  readonly items: readonly {
152
175
  readonly runId: string;
153
- readonly sandboxId: string;
176
+ readonly workspaceId: string;
154
177
  readonly attemptId?: string | undefined;
155
178
  readonly ownerUserId: string;
156
179
  readonly harnessId: string;
@@ -177,7 +200,7 @@ export declare const updateRunOp: (runId: string, payload: {
177
200
  }[] | undefined;
178
201
  }) => Effect.Effect<{
179
202
  readonly runId: string;
180
- readonly sandboxId: string;
203
+ readonly workspaceId: string;
181
204
  readonly attemptId?: string | undefined;
182
205
  readonly ownerUserId: string;
183
206
  readonly harnessId: string;
@@ -190,7 +213,7 @@ export declare const updateRunOp: (runId: string, payload: {
190
213
  readonly finishedAt?: string | undefined;
191
214
  readonly createdAt: string;
192
215
  readonly updatedAt: string;
193
- }, unknown, SealantApiClient>;
216
+ }, import("effect/unstable/http/HttpClientError").HttpClientError | import("@sealant/api-contracts").RunBadRequestError | import("@sealant/api-contracts").RunInternalServerError | import("@sealant/api-contracts").RunNotFoundError | import("effect/Schema").SchemaError, SealantApiClient>;
194
217
  export declare const getRunTimelineOp: (runId: string, query: {
195
218
  readonly fromSequence?: string | undefined;
196
219
  readonly toSequence?: string | undefined;
@@ -206,7 +229,7 @@ export declare const getRunTimelineOp: (runId: string, query: {
206
229
  readonly processId?: string | undefined;
207
230
  readonly captureMethod: number;
208
231
  readonly confidence: number;
209
- }[], unknown, SealantApiClient>;
232
+ }[], import("effect/unstable/http/HttpClientError").HttpClientError | import("@sealant/api-contracts").RunBadRequestError | import("@sealant/api-contracts").RunInternalServerError | import("@sealant/api-contracts").RunNotFoundError | import("effect/Schema").SchemaError, SealantApiClient>;
210
233
  export declare const getRunScrollbackOp: (runId: string, query: {
211
234
  readonly processId: string;
212
235
  readonly stream: "stderr" | "stdout";
@@ -216,7 +239,7 @@ export declare const getRunScrollbackOp: (runId: string, query: {
216
239
  readonly stream: "stderr" | "stdout";
217
240
  readonly byteCount: number;
218
241
  readonly contentBase64: string;
219
- }, unknown, SealantApiClient>;
242
+ }, import("effect/unstable/http/HttpClientError").HttpClientError | import("@sealant/api-contracts").RunBadRequestError | import("@sealant/api-contracts").RunInternalServerError | import("@sealant/api-contracts").RunNotFoundError | import("effect/Schema").SchemaError, SealantApiClient>;
220
243
  export declare const getRunLossOp: (runId: string) => Effect.Effect<{
221
244
  readonly runId: string;
222
245
  readonly droppedEventCount: string;
@@ -231,7 +254,7 @@ export declare const getRunLossOp: (runId: string) => Effect.Effect<{
231
254
  readonly detectedVia: "gap" | "marker";
232
255
  readonly reason?: string | undefined;
233
256
  }[];
234
- }, unknown, SealantApiClient>;
257
+ }, import("effect/unstable/http/HttpClientError").HttpClientError | import("@sealant/api-contracts").RunInternalServerError | import("@sealant/api-contracts").RunNotFoundError | import("effect/Schema").SchemaError, SealantApiClient>;
235
258
  export declare const getRunChangesOp: (runId: string) => Effect.Effect<{
236
259
  readonly files: readonly {
237
260
  readonly path: string;
@@ -239,4 +262,49 @@ export declare const getRunChangesOp: (runId: string) => Effect.Effect<{
239
262
  readonly oldPath?: string | undefined;
240
263
  }[];
241
264
  readonly diff: string;
242
- }, unknown, SealantApiClient>;
265
+ }, import("effect/unstable/http/HttpClientError").HttpClientError | import("@sealant/api-contracts").RunInternalServerError | import("@sealant/api-contracts").RunNotFoundError | import("effect/Schema").SchemaError, SealantApiClient>;
266
+ export declare const inferenceRespondOp: (payload: {
267
+ readonly ownerUserId: string;
268
+ readonly credentials?: {
269
+ readonly profileId?: string | undefined;
270
+ readonly claude?: string | undefined;
271
+ readonly codex?: string | undefined;
272
+ } | undefined;
273
+ readonly prompt?: string | undefined;
274
+ readonly system?: string | undefined;
275
+ readonly model?: string | undefined;
276
+ readonly maxTurns?: number | undefined;
277
+ readonly tools?: readonly {
278
+ readonly name: string;
279
+ readonly description?: string | undefined;
280
+ readonly inputSchema: unknown;
281
+ }[] | undefined;
282
+ readonly responseFormat?: {
283
+ readonly type: "json";
284
+ readonly schema?: unknown;
285
+ } | undefined;
286
+ readonly sessionId?: string | undefined;
287
+ readonly toolResults?: readonly {
288
+ readonly toolCallId: string;
289
+ readonly content: string;
290
+ readonly isError?: boolean | undefined;
291
+ }[] | undefined;
292
+ }) => Effect.Effect<{
293
+ readonly sessionId: string;
294
+ readonly turn: {
295
+ readonly type: "text";
296
+ readonly text: string;
297
+ readonly json?: unknown;
298
+ } | {
299
+ readonly type: "toolCalls";
300
+ readonly calls: readonly {
301
+ readonly toolCallId: string;
302
+ readonly name: string;
303
+ readonly input: unknown;
304
+ }[];
305
+ };
306
+ readonly usage?: {
307
+ readonly inputTokens: number;
308
+ readonly outputTokens: number;
309
+ } | undefined;
310
+ }, import("effect/unstable/http/HttpClientError").HttpClientError | import("@sealant/api-contracts").InferenceBadRequestError | import("@sealant/api-contracts").InferenceConflictError | import("@sealant/api-contracts").InferenceInternalServerError | import("@sealant/api-contracts").InferenceNotFoundError | import("@sealant/api-contracts").InferenceUnavailableError | import("effect/Schema").SchemaError, SealantApiClient>;
@@ -1,12 +1,13 @@
1
1
  import { Effect } from "effect";
2
2
  import { SealantApiClient } from "./api-client.js";
3
- // ---- sandboxes ----
4
- export const createSandboxOp = (payload, idempotencyKey) => Effect.flatMap(SealantApiClient, (client) => client.sandboxes.createSandbox({
3
+ // ---- workspaces ----
4
+ export const createWorkspaceOp = (payload, idempotencyKey) => Effect.flatMap(SealantApiClient, (client) => client.workspaces.createWorkspace({
5
5
  payload,
6
6
  headers: idempotencyKey === undefined ? {} : { "idempotency-key": idempotencyKey },
7
7
  }));
8
- export const getSandboxOp = (sandboxId) => Effect.flatMap(SealantApiClient, (client) => client.sandboxes.getSandbox({ params: { sandboxId } }));
9
- export const listSandboxesOp = (query) => Effect.flatMap(SealantApiClient, (client) => client.sandboxes.listSandboxes({ query }));
8
+ export const getWorkspaceOp = (workspaceId) => Effect.flatMap(SealantApiClient, (client) => client.workspaces.getWorkspace({ params: { workspaceId } }));
9
+ export const listWorkspacesOp = (query) => Effect.flatMap(SealantApiClient, (client) => client.workspaces.listWorkspaces({ query }));
10
+ export const execWorkspaceOp = (workspaceId, payload) => Effect.flatMap(SealantApiClient, (client) => client.workspaces.execWorkspace({ params: { workspaceId }, payload }));
10
11
  // ---- runs ----
11
12
  export const createRunOp = (payload) => Effect.flatMap(SealantApiClient, (client) => client.runs.createRun({ payload }));
12
13
  export const getRunOp = (runId) => Effect.flatMap(SealantApiClient, (client) => client.runs.getRun({ params: { runId } }));
@@ -16,3 +17,5 @@ export const getRunTimelineOp = (runId, query) => Effect.flatMap(SealantApiClien
16
17
  export const getRunScrollbackOp = (runId, query) => Effect.flatMap(SealantApiClient, (client) => client.runs.getRunScrollback({ params: { runId }, query }));
17
18
  export const getRunLossOp = (runId) => Effect.flatMap(SealantApiClient, (client) => client.runs.getRunLoss({ params: { runId } }));
18
19
  export const getRunChangesOp = (runId) => Effect.flatMap(SealantApiClient, (client) => client.runs.getRunChanges({ params: { runId } }));
20
+ // ---- inference ----
21
+ export const inferenceRespondOp = (payload) => Effect.flatMap(SealantApiClient, (client) => client.inference.respond({ payload }));
@@ -1,5 +1,5 @@
1
- import type { RunHarnessFn } from "../facade/sandbox.js";
2
- /** The BLOCKING `harness.run()` implementation, registered into the Sandbox facade by the client. */
1
+ import type { RunHarnessFn } from "../facade/workspace.js";
2
+ /** The BLOCKING `harness.run()` implementation, registered into the Workspace facade by the client. */
3
3
  export declare const runHarness: RunHarnessFn;
4
4
  /**
5
5
  * The NON-BLOCKING `harness.start()` implementation: register the run and return the live handle
@@ -17,16 +17,16 @@ const POLL_INTERVAL = "500 millis";
17
17
  const RUN_TIMEOUT_MS = 30 * 60 * 1_000;
18
18
  /**
19
19
  * Registers the run WITH the harness command — the control plane executes it server-side (the worker
20
- * docker-execs it and ingests telemetry). The cwd is the sandbox repo, which the worker defaults to.
20
+ * docker-execs it and ingests telemetry). The cwd is the workspace repo, which the worker defaults to.
21
21
  */
22
22
  const createHarnessRunEffect = (ctx, init, prompt) => Effect.gen(function* () {
23
23
  const harness = init.harness;
24
24
  if (harness === undefined) {
25
- return yield* Effect.fail(new SealantError("This sandbox handle has no harness; use the handle returned by sandboxes.create().", { code: "harness_required" }));
25
+ return yield* Effect.fail(new SealantError("This workspace handle has no harness; use the handle returned by workspaces.create().", { code: "harness_required" }));
26
26
  }
27
27
  const command = harness.buildRunCommand(prompt);
28
28
  return yield* createRunOp({
29
- sandboxId: init.id,
29
+ workspaceId: init.id,
30
30
  ownerUserId: ctx.config.hostLocal.ownerUserId,
31
31
  harnessId: harness.id,
32
32
  mode: "one-shot",
@@ -53,7 +53,7 @@ const runHarnessEffect = (ctx, init, prompt) => Effect.gen(function* () {
53
53
  const changes = toRunChangesData(yield* getRunChangesOp(runId));
54
54
  return makeRun(ctx, { wire, changes });
55
55
  });
56
- /** The BLOCKING `harness.run()` implementation, registered into the Sandbox facade by the client. */
56
+ /** The BLOCKING `harness.run()` implementation, registered into the Workspace facade by the client. */
57
57
  export const runHarness = (ctx, init, prompt) => ctx.runtime.run(runHarnessEffect(ctx, init, prompt));
58
58
  /**
59
59
  * The NON-BLOCKING `harness.start()` implementation: register the run and return the live handle
package/dist/errors.d.ts CHANGED
@@ -13,7 +13,7 @@ export declare class SealantError extends Error {
13
13
  readonly cause?: unknown;
14
14
  });
15
15
  }
16
- /** A typed control/transport failure from the sandbox runtime daemon. */
16
+ /** A typed control/transport failure from the workspace runtime daemon. */
17
17
  export declare class SealantRuntimeError extends SealantError {
18
18
  readonly name = "SealantRuntimeError";
19
19
  constructor(message: string, options?: {
package/dist/errors.js CHANGED
@@ -13,7 +13,7 @@ export class SealantError extends Error {
13
13
  this.code = options?.code ?? "sealant_error";
14
14
  }
15
15
  }
16
- /** A typed control/transport failure from the sandbox runtime daemon. */
16
+ /** A typed control/transport failure from the workspace runtime daemon. */
17
17
  export class SealantRuntimeError extends SealantError {
18
18
  name = "SealantRuntimeError";
19
19
  constructor(message, options) {
@@ -1,5 +1,21 @@
1
- import type { RunCommand, RunRecord } from "../types.js";
1
+ import type { RunCommand, RunRecord, TimelineEntry } from "../types.js";
2
2
  import type { SdkContext } from "./context.js";
3
+ /**
4
+ * Maps a wire entry to the public DISCRIMINATED entry: the contract's payload schemas fold
5
+ * `(kind, ref)` into typed `data`, degrading to the `unknown` case (raw kind + payload preserved)
6
+ * for kinds newer than this SDK or payloads that fail their schema. Exported for tests.
7
+ */
8
+ export declare const toTimelineEntry: (wire: {
9
+ readonly eventId: string;
10
+ readonly sequence: string;
11
+ readonly kind: string;
12
+ readonly occurredAt: string;
13
+ readonly summary: string;
14
+ readonly ref?: unknown;
15
+ readonly processId?: string | undefined;
16
+ readonly captureMethod: number;
17
+ readonly confidence: number;
18
+ }) => TimelineEntry;
3
19
  /**
4
20
  * Folds the timeline into the ordered list of terminal commands. Process boundaries are
5
21
  * `processStarted`/`processExited`; `ioChunk` byte counts accrue to the current command. Daemon noise
@@ -1,3 +1,11 @@
1
+ /**
2
+ * The `RunRecord` facade — the execution record as the SDK exposes it. Backed by the run/record
3
+ * contract endpoints (`client.runs.*`), it maps the wire shapes to the public types: decimal-string
4
+ * sequences become `bigint`, base64 scrollback becomes `Uint8Array`. `replay()`/`timeline()`/
5
+ * `scrollback()`/`loss()`/`summary()`/`stream()` are live; the time-travel folds are typed but
6
+ * reject until their read models land (Phase 1).
7
+ */
8
+ import { decodeRecordEventPayload, } from "@sealant/api-contracts";
1
9
  import { getRunLossOp, getRunOp, getRunScrollbackOp, getRunTimelineOp, } from "../effect/operations.js";
2
10
  import { SealantNotImplementedError } from "../errors.js";
3
11
  // Live `stream()` is poll-backed for now: it tails the timeline endpoint until the run is terminal.
@@ -6,11 +14,17 @@ const TERMINAL_RUN_STATUSES = new Set(["completed", "failed", "cancelled"]);
6
14
  const STREAM_POLL_INTERVAL_MS = 500;
7
15
  const STREAM_TIMEOUT_MS = 30 * 60 * 1_000;
8
16
  const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
9
- const toTimelineEntry = (wire) => ({
17
+ /**
18
+ * Maps a wire entry to the public DISCRIMINATED entry: the contract's payload schemas fold
19
+ * `(kind, ref)` into typed `data`, degrading to the `unknown` case (raw kind + payload preserved)
20
+ * for kinds newer than this SDK or payloads that fail their schema. Exported for tests.
21
+ */
22
+ export const toTimelineEntry = (wire) => ({
10
23
  sequence: BigInt(wire.sequence),
11
- kind: wire.kind,
12
24
  occurredAt: wire.occurredAt,
13
- data: { summary: wire.summary, ...(wire.ref === undefined ? {} : { ref: wire.ref }) },
25
+ summary: wire.summary,
26
+ ...(wire.processId === undefined ? {} : { processId: wire.processId }),
27
+ ...decodeRecordEventPayload(wire.kind, wire.ref),
14
28
  });
15
29
  const toLossReport = (wire) => ({
16
30
  complete: !wire.earlyClose && wire.droppedEventCount === "0" && wire.sequenceGapCount === 0,
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * The `Run` facade — one harness execution as the SDK exposes it. Built from a `runs.get` lookup
3
- * (read a past run, record outlives the sandbox), from `harness.run()` (which also captures the file
3
+ * (read a past run, record outlives the workspace), from `harness.run()` (which also captures the file
4
4
  * changes inline), or from `harness.start()` (a live handle; `wait()` settles it). `result` is
5
5
  * derived from the wire status; `changes` is captured inline by `run()` and fetched by `wait()` once
6
6
  * the run is terminal (the event-sourced fileChange projection that backs reads arrives in Phase 1);
@@ -1,18 +1,18 @@
1
- import type { Harness, Sandbox, SandboxStatus } from "../types.js";
1
+ import type { Harness, Workspace, WorkspaceStatus } from "../types.js";
2
2
  import type { SdkContext } from "./context.js";
3
- export interface SandboxInit {
3
+ export interface WorkspaceInit {
4
4
  readonly id: string;
5
5
  readonly name: string;
6
- readonly status: SandboxStatus;
6
+ readonly status: WorkspaceStatus;
7
7
  /** Present when the handle came from `create()` (needed by `harness.run()`). */
8
8
  readonly harness?: Harness;
9
9
  }
10
10
  /**
11
11
  * The `harness.run()`/`harness.start()` implementations are injected by the run-execution module to
12
- * avoid a static dependency cycle (sandbox <-> run execution). Until they are registered, both
12
+ * avoid a static dependency cycle (workspace <-> run execution). Until they are registered, both
13
13
  * report that the feature is not wired in this build.
14
14
  */
15
- export type RunHarnessFn = (ctx: SdkContext, init: SandboxInit, prompt: string, options?: import("../types.js").RunOptions) => Promise<import("../types.js").Run>;
15
+ export type RunHarnessFn = (ctx: SdkContext, init: WorkspaceInit, prompt: string, options?: import("../types.js").RunOptions) => Promise<import("../types.js").Run>;
16
16
  export interface HarnessExecutors {
17
17
  /** BLOCKING `harness.run()`: resolves once the run is terminal. */
18
18
  readonly run: RunHarnessFn;
@@ -20,4 +20,4 @@ export interface HarnessExecutors {
20
20
  readonly start: RunHarnessFn;
21
21
  }
22
22
  export declare const registerHarnessExecutors: (executors: HarnessExecutors) => void;
23
- export declare const makeSandbox: (ctx: SdkContext, init: SandboxInit) => Sandbox;
23
+ export declare const makeWorkspace: (ctx: SdkContext, init: WorkspaceInit) => Workspace;
@@ -1,4 +1,5 @@
1
- import { getSandboxOp } from "../effect/operations.js";
1
+ import { execWorkspace } from "../effect/exec-workspace.js";
2
+ import { getWorkspaceOp } from "../effect/operations.js";
2
3
  import { SealantError, SealantNotImplementedError } from "../errors.js";
3
4
  const FAILED_STATUSES = new Set(["failed", "cancelled"]);
4
5
  const READY_POLL_INTERVAL_MS = 2_000;
@@ -8,7 +9,7 @@ let harnessExecutors;
8
9
  export const registerHarnessExecutors = (executors) => {
9
10
  harnessExecutors = executors;
10
11
  };
11
- export const makeSandbox = (ctx, init) => {
12
+ export const makeWorkspace = (ctx, init) => {
12
13
  const harness = {
13
14
  run: (prompt, options) => {
14
15
  if (harnessExecutors === undefined) {
@@ -24,36 +25,37 @@ export const makeSandbox = (ctx, init) => {
24
25
  },
25
26
  session: () => Promise.reject(new SealantNotImplementedError("harness.session (interactive, Phase 3)")),
26
27
  };
27
- const sandbox = {
28
+ const workspace = {
28
29
  id: init.id,
29
30
  name: init.name,
30
31
  status: async () => {
31
- const details = await ctx.runtime.run(getSandboxOp(init.id));
32
+ const details = await ctx.runtime.run(getWorkspaceOp(init.id));
32
33
  return details.status;
33
34
  },
34
35
  ready: async () => {
35
36
  const deadline = Date.now() + READY_TIMEOUT_MS;
36
37
  for (;;) {
37
- const details = await ctx.runtime.run(getSandboxOp(init.id));
38
+ const details = await ctx.runtime.run(getWorkspaceOp(init.id));
38
39
  // Gate on the coarse "ready" status, which the control plane now emits ONLY after the
39
- // in-sandbox daemon's control socket is accepting (readiness probe in the launch path).
40
+ // in-workspace daemon's control socket is accepting (readiness probe in the launch path).
40
41
  // This is honest: when ready() resolves, harness.run() can connect without racing the socket.
41
42
  if (details.status === "ready") {
42
- return sandbox;
43
+ return workspace;
43
44
  }
44
45
  if (FAILED_STATUSES.has(details.status)) {
45
- throw new SealantError(`Sandbox ${init.id} reached terminal status "${details.status}" before becoming ready.`, { code: "sandbox_not_ready" });
46
+ throw new SealantError(`Workspace ${init.id} reached terminal status "${details.status}" before becoming ready.`, { code: "workspace_not_ready" });
46
47
  }
47
48
  if (Date.now() > deadline) {
48
- throw new SealantError(`Timed out waiting for sandbox ${init.id} to become ready.`, {
49
- code: "sandbox_ready_timeout",
49
+ throw new SealantError(`Timed out waiting for workspace ${init.id} to become ready.`, {
50
+ code: "workspace_ready_timeout",
50
51
  });
51
52
  }
52
53
  await delay(READY_POLL_INTERVAL_MS);
53
54
  }
54
55
  },
55
56
  harness,
56
- // Poll-backed lifecycle stream: emit a coarse event on each status transition until the sandbox
57
+ exec: (argv, options) => execWorkspace(ctx, init, argv, options),
58
+ // Poll-backed lifecycle stream: emit a coarse event on each status transition until the workspace
57
59
  // reaches a terminal/ready state. Swaps to SSE over Postgres LISTEN/NOTIFY in Stage 5 (same shape).
58
60
  events: () => {
59
61
  const ctxRun = ctx.runtime;
@@ -61,13 +63,13 @@ export const makeSandbox = (ctx, init) => {
61
63
  let lastStatus;
62
64
  const deadline = Date.now() + READY_TIMEOUT_MS;
63
65
  for (;;) {
64
- const details = await ctxRun.run(getSandboxOp(init.id));
66
+ const details = await ctxRun.run(getWorkspaceOp(init.id));
65
67
  if (details.status !== lastStatus) {
66
68
  lastStatus = details.status;
67
69
  yield {
68
70
  type: `status.${details.status}`,
69
71
  occurredAt: new Date().toISOString(),
70
- message: `Sandbox status: ${details.status}`,
72
+ message: `Workspace status: ${details.status}`,
71
73
  };
72
74
  }
73
75
  if (details.status === "ready" || FAILED_STATUSES.has(details.status)) {
@@ -81,9 +83,9 @@ export const makeSandbox = (ctx, init) => {
81
83
  }
82
84
  return iterate();
83
85
  },
84
- stop: () => Promise.reject(new SealantNotImplementedError("sandbox.stop (lifecycle, Phase 3)")),
85
- restart: () => Promise.reject(new SealantNotImplementedError("sandbox.restart (lifecycle, Phase 3)")),
86
- expire: () => Promise.reject(new SealantNotImplementedError("sandbox.expire (lifecycle, Phase 3)")),
86
+ stop: () => Promise.reject(new SealantNotImplementedError("workspace.stop (lifecycle, Phase 3)")),
87
+ restart: () => Promise.reject(new SealantNotImplementedError("workspace.restart (lifecycle, Phase 3)")),
88
+ expire: () => Promise.reject(new SealantNotImplementedError("workspace.expire (lifecycle, Phase 3)")),
87
89
  };
88
- return sandbox;
90
+ return workspace;
89
91
  };
package/dist/harness.d.ts CHANGED
@@ -4,7 +4,7 @@
4
4
  *
5
5
  * NOTE — the one-shot invocation forms below (`opencode run <prompt>`, `codex exec <prompt>`,
6
6
  * `claude -p <prompt>`) are the expected headless shapes but are PENDING live verification against
7
- * the baked sandbox image (see the SDK plan's task #2, "verify harness one-shot CLI semantics").
7
+ * the baked workspace image (see the SDK plan's task #2, "verify harness one-shot CLI semantics").
8
8
  * Until that is confirmed, only `opencode()` is exercised end-to-end; the others are provided for the
9
9
  * typed surface and adjusted once verified.
10
10
  */