@sealant/sdk 0.3.0 → 0.4.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 +7 -7
- package/dist/client.d.ts +7 -7
- package/dist/client.js +21 -21
- package/dist/effect/api-client.d.ts +250 -250
- package/dist/effect/operations.d.ts +14 -14
- package/dist/effect/operations.js +4 -4
- package/dist/effect/run-harness.d.ts +2 -2
- package/dist/effect/run-harness.js +4 -4
- package/dist/errors.d.ts +1 -1
- package/dist/errors.js +1 -1
- package/dist/facade/run.d.ts +1 -1
- package/dist/facade/{sandbox.d.ts → workspace.d.ts} +6 -6
- package/dist/facade/{sandbox.js → workspace.js} +17 -17
- package/dist/harness.d.ts +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.js +4 -4
- package/dist/internal/blueprint.d.ts +1 -1
- package/dist/internal/blueprint.js +8 -8
- package/dist/internal/config.d.ts +2 -2
- package/dist/internal/credentials.d.ts +7 -7
- package/dist/internal/credentials.js +2 -2
- package/dist/internal/map-error.d.ts +1 -1
- package/dist/internal/map-error.js +1 -1
- package/dist/types.d.ts +35 -35
- package/dist/types.js +2 -2
- package/package.json +3 -3
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Effect } from "effect";
|
|
2
2
|
import { SealantApiClient } from "./api-client.js";
|
|
3
|
-
export declare const
|
|
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
|
|
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("
|
|
36
|
-
export declare const
|
|
37
|
-
readonly
|
|
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";
|
|
@@ -63,13 +63,13 @@ export declare const getSandboxOp: (sandboxId: string) => Effect.Effect<{
|
|
|
63
63
|
readonly finishedAt?: string | undefined;
|
|
64
64
|
readonly spec?: unknown;
|
|
65
65
|
}, unknown, SealantApiClient>;
|
|
66
|
-
export declare const
|
|
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
|
|
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,9 @@ 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("
|
|
100
|
+
}, import("effect/unstable/http/HttpClientError").HttpClientError | import("effect/Schema").SchemaError | import("@sealant/api-contracts").WorkspaceBadRequestError | import("@sealant/api-contracts").WorkspaceInternalServerError, SealantApiClient>;
|
|
101
101
|
export declare const createRunOp: (payload: {
|
|
102
|
-
readonly
|
|
102
|
+
readonly workspaceId: string;
|
|
103
103
|
readonly harnessId: string;
|
|
104
104
|
readonly ownerUserId: string;
|
|
105
105
|
readonly mode?: "interactive" | "one-shot" | undefined;
|
|
@@ -112,7 +112,7 @@ export declare const createRunOp: (payload: {
|
|
|
112
112
|
} | undefined;
|
|
113
113
|
}) => Effect.Effect<{
|
|
114
114
|
readonly runId: string;
|
|
115
|
-
readonly
|
|
115
|
+
readonly workspaceId: string;
|
|
116
116
|
readonly attemptId?: string | undefined;
|
|
117
117
|
readonly ownerUserId: string;
|
|
118
118
|
readonly harnessId: string;
|
|
@@ -128,7 +128,7 @@ export declare const createRunOp: (payload: {
|
|
|
128
128
|
}, unknown, SealantApiClient>;
|
|
129
129
|
export declare const getRunOp: (runId: string) => Effect.Effect<{
|
|
130
130
|
readonly runId: string;
|
|
131
|
-
readonly
|
|
131
|
+
readonly workspaceId: string;
|
|
132
132
|
readonly attemptId?: string | undefined;
|
|
133
133
|
readonly ownerUserId: string;
|
|
134
134
|
readonly harnessId: string;
|
|
@@ -143,14 +143,14 @@ export declare const getRunOp: (runId: string) => Effect.Effect<{
|
|
|
143
143
|
readonly updatedAt: string;
|
|
144
144
|
}, unknown, SealantApiClient>;
|
|
145
145
|
export declare const listRunsOp: (query: {
|
|
146
|
-
readonly
|
|
146
|
+
readonly workspaceId?: string | undefined;
|
|
147
147
|
readonly ownerUserId?: string | undefined;
|
|
148
148
|
readonly status?: "cancelled" | "completed" | "failed" | "queued" | "running" | undefined;
|
|
149
149
|
readonly limit?: string | undefined;
|
|
150
150
|
}) => Effect.Effect<{
|
|
151
151
|
readonly items: readonly {
|
|
152
152
|
readonly runId: string;
|
|
153
|
-
readonly
|
|
153
|
+
readonly workspaceId: string;
|
|
154
154
|
readonly attemptId?: string | undefined;
|
|
155
155
|
readonly ownerUserId: string;
|
|
156
156
|
readonly harnessId: string;
|
|
@@ -177,7 +177,7 @@ export declare const updateRunOp: (runId: string, payload: {
|
|
|
177
177
|
}[] | undefined;
|
|
178
178
|
}) => Effect.Effect<{
|
|
179
179
|
readonly runId: string;
|
|
180
|
-
readonly
|
|
180
|
+
readonly workspaceId: string;
|
|
181
181
|
readonly attemptId?: string | undefined;
|
|
182
182
|
readonly ownerUserId: string;
|
|
183
183
|
readonly harnessId: string;
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { Effect } from "effect";
|
|
2
2
|
import { SealantApiClient } from "./api-client.js";
|
|
3
|
-
// ----
|
|
4
|
-
export const
|
|
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
|
|
9
|
-
export const
|
|
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
10
|
// ---- runs ----
|
|
11
11
|
export const createRunOp = (payload) => Effect.flatMap(SealantApiClient, (client) => client.runs.createRun({ payload }));
|
|
12
12
|
export const getRunOp = (runId) => Effect.flatMap(SealantApiClient, (client) => client.runs.getRun({ params: { runId } }));
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { RunHarnessFn } from "../facade/
|
|
2
|
-
/** The BLOCKING `harness.run()` implementation, registered into the
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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) {
|
package/dist/facade/run.d.ts
CHANGED
|
@@ -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
|
|
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,
|
|
1
|
+
import type { Harness, Workspace, WorkspaceStatus } from "../types.js";
|
|
2
2
|
import type { SdkContext } from "./context.js";
|
|
3
|
-
export interface
|
|
3
|
+
export interface WorkspaceInit {
|
|
4
4
|
readonly id: string;
|
|
5
5
|
readonly name: string;
|
|
6
|
-
readonly status:
|
|
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 (
|
|
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:
|
|
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
|
|
23
|
+
export declare const makeWorkspace: (ctx: SdkContext, init: WorkspaceInit) => Workspace;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getWorkspaceOp } from "../effect/operations.js";
|
|
2
2
|
import { SealantError, SealantNotImplementedError } from "../errors.js";
|
|
3
3
|
const FAILED_STATUSES = new Set(["failed", "cancelled"]);
|
|
4
4
|
const READY_POLL_INTERVAL_MS = 2_000;
|
|
@@ -8,7 +8,7 @@ let harnessExecutors;
|
|
|
8
8
|
export const registerHarnessExecutors = (executors) => {
|
|
9
9
|
harnessExecutors = executors;
|
|
10
10
|
};
|
|
11
|
-
export const
|
|
11
|
+
export const makeWorkspace = (ctx, init) => {
|
|
12
12
|
const harness = {
|
|
13
13
|
run: (prompt, options) => {
|
|
14
14
|
if (harnessExecutors === undefined) {
|
|
@@ -24,36 +24,36 @@ export const makeSandbox = (ctx, init) => {
|
|
|
24
24
|
},
|
|
25
25
|
session: () => Promise.reject(new SealantNotImplementedError("harness.session (interactive, Phase 3)")),
|
|
26
26
|
};
|
|
27
|
-
const
|
|
27
|
+
const workspace = {
|
|
28
28
|
id: init.id,
|
|
29
29
|
name: init.name,
|
|
30
30
|
status: async () => {
|
|
31
|
-
const details = await ctx.runtime.run(
|
|
31
|
+
const details = await ctx.runtime.run(getWorkspaceOp(init.id));
|
|
32
32
|
return details.status;
|
|
33
33
|
},
|
|
34
34
|
ready: async () => {
|
|
35
35
|
const deadline = Date.now() + READY_TIMEOUT_MS;
|
|
36
36
|
for (;;) {
|
|
37
|
-
const details = await ctx.runtime.run(
|
|
37
|
+
const details = await ctx.runtime.run(getWorkspaceOp(init.id));
|
|
38
38
|
// Gate on the coarse "ready" status, which the control plane now emits ONLY after the
|
|
39
|
-
// in-
|
|
39
|
+
// in-workspace daemon's control socket is accepting (readiness probe in the launch path).
|
|
40
40
|
// This is honest: when ready() resolves, harness.run() can connect without racing the socket.
|
|
41
41
|
if (details.status === "ready") {
|
|
42
|
-
return
|
|
42
|
+
return workspace;
|
|
43
43
|
}
|
|
44
44
|
if (FAILED_STATUSES.has(details.status)) {
|
|
45
|
-
throw new SealantError(`
|
|
45
|
+
throw new SealantError(`Workspace ${init.id} reached terminal status "${details.status}" before becoming ready.`, { code: "workspace_not_ready" });
|
|
46
46
|
}
|
|
47
47
|
if (Date.now() > deadline) {
|
|
48
|
-
throw new SealantError(`Timed out waiting for
|
|
49
|
-
code: "
|
|
48
|
+
throw new SealantError(`Timed out waiting for workspace ${init.id} to become ready.`, {
|
|
49
|
+
code: "workspace_ready_timeout",
|
|
50
50
|
});
|
|
51
51
|
}
|
|
52
52
|
await delay(READY_POLL_INTERVAL_MS);
|
|
53
53
|
}
|
|
54
54
|
},
|
|
55
55
|
harness,
|
|
56
|
-
// Poll-backed lifecycle stream: emit a coarse event on each status transition until the
|
|
56
|
+
// Poll-backed lifecycle stream: emit a coarse event on each status transition until the workspace
|
|
57
57
|
// reaches a terminal/ready state. Swaps to SSE over Postgres LISTEN/NOTIFY in Stage 5 (same shape).
|
|
58
58
|
events: () => {
|
|
59
59
|
const ctxRun = ctx.runtime;
|
|
@@ -61,13 +61,13 @@ export const makeSandbox = (ctx, init) => {
|
|
|
61
61
|
let lastStatus;
|
|
62
62
|
const deadline = Date.now() + READY_TIMEOUT_MS;
|
|
63
63
|
for (;;) {
|
|
64
|
-
const details = await ctxRun.run(
|
|
64
|
+
const details = await ctxRun.run(getWorkspaceOp(init.id));
|
|
65
65
|
if (details.status !== lastStatus) {
|
|
66
66
|
lastStatus = details.status;
|
|
67
67
|
yield {
|
|
68
68
|
type: `status.${details.status}`,
|
|
69
69
|
occurredAt: new Date().toISOString(),
|
|
70
|
-
message: `
|
|
70
|
+
message: `Workspace status: ${details.status}`,
|
|
71
71
|
};
|
|
72
72
|
}
|
|
73
73
|
if (details.status === "ready" || FAILED_STATUSES.has(details.status)) {
|
|
@@ -81,9 +81,9 @@ export const makeSandbox = (ctx, init) => {
|
|
|
81
81
|
}
|
|
82
82
|
return iterate();
|
|
83
83
|
},
|
|
84
|
-
stop: () => Promise.reject(new SealantNotImplementedError("
|
|
85
|
-
restart: () => Promise.reject(new SealantNotImplementedError("
|
|
86
|
-
expire: () => Promise.reject(new SealantNotImplementedError("
|
|
84
|
+
stop: () => Promise.reject(new SealantNotImplementedError("workspace.stop (lifecycle, Phase 3)")),
|
|
85
|
+
restart: () => Promise.reject(new SealantNotImplementedError("workspace.restart (lifecycle, Phase 3)")),
|
|
86
|
+
expire: () => Promise.reject(new SealantNotImplementedError("workspace.expire (lifecycle, Phase 3)")),
|
|
87
87
|
};
|
|
88
|
-
return
|
|
88
|
+
return workspace;
|
|
89
89
|
};
|
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
|
|
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
|
*/
|
package/dist/index.d.ts
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @sealant/sdk — the fluent public SDK for Sealant.
|
|
3
3
|
*
|
|
4
|
-
* Create a live
|
|
5
|
-
* while it works, and keep the replayable execution record after the
|
|
4
|
+
* Create a live workspace around a real repository, run the harness you already use, stream progress
|
|
5
|
+
* while it works, and keep the replayable execution record after the workspace is gone:
|
|
6
6
|
*
|
|
7
7
|
* import { Sealant, opencode } from "@sealant/sdk"
|
|
8
8
|
*
|
|
9
9
|
* const sealant = new Sealant({ baseUrl: "http://localhost:8080" })
|
|
10
|
-
* const
|
|
10
|
+
* const workspace = await sealant.workspaces.create({
|
|
11
11
|
* repository: "github.com/acme/billing-service",
|
|
12
12
|
* harness: opencode(),
|
|
13
13
|
* })
|
|
14
|
-
* const run = await
|
|
14
|
+
* const run = await workspace.harness.run("Round invoice totals once, after applying the discount.")
|
|
15
15
|
* await run.record.replay()
|
|
16
16
|
*/
|
|
17
17
|
export { Sealant } from "./client.js";
|
package/dist/index.js
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @sealant/sdk — the fluent public SDK for Sealant.
|
|
3
3
|
*
|
|
4
|
-
* Create a live
|
|
5
|
-
* while it works, and keep the replayable execution record after the
|
|
4
|
+
* Create a live workspace around a real repository, run the harness you already use, stream progress
|
|
5
|
+
* while it works, and keep the replayable execution record after the workspace is gone:
|
|
6
6
|
*
|
|
7
7
|
* import { Sealant, opencode } from "@sealant/sdk"
|
|
8
8
|
*
|
|
9
9
|
* const sealant = new Sealant({ baseUrl: "http://localhost:8080" })
|
|
10
|
-
* const
|
|
10
|
+
* const workspace = await sealant.workspaces.create({
|
|
11
11
|
* repository: "github.com/acme/billing-service",
|
|
12
12
|
* harness: opencode(),
|
|
13
13
|
* })
|
|
14
|
-
* const run = await
|
|
14
|
+
* const run = await workspace.harness.run("Round invoice totals once, after applying the discount.")
|
|
15
15
|
* await run.record.replay()
|
|
16
16
|
*/
|
|
17
17
|
export { Sealant } from "./client.js";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { CreateOptions } from "../types.js";
|
|
2
2
|
import type { SealantInternalConfig } from "./config.js";
|
|
3
|
-
export declare const
|
|
3
|
+
export declare const buildCreateWorkspaceRequest: (options: CreateOptions, config: SealantInternalConfig) => {
|
|
4
4
|
readonly payload: {
|
|
5
5
|
readonly ownerUserId: string;
|
|
6
6
|
readonly registryId: string;
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Lowers the fluent `create({ repository, harness })` options into the existing
|
|
3
|
-
* `
|
|
3
|
+
* `createWorkspaceRequestSchema` the control plane accepts — entirely client-side, so the slice needs
|
|
4
4
|
* no contract change. The public `repository` is the SOURCE git repo (it becomes
|
|
5
|
-
* `spec.sources.
|
|
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
7
|
* connects to), the runtime target is pinned to docker (the only bridgeable adapter today), and the
|
|
8
|
-
* foreground is a keepalive so the
|
|
8
|
+
* foreground is a keepalive so the workspace idles with the daemon up and the harness is exec'd on
|
|
9
9
|
* demand by `run()` rather than launched at boot. `options.credentials`, if present, is lowered via
|
|
10
|
-
* `
|
|
10
|
+
* `mapWorkspaceCredentials` (see `./credentials.js`) and folded into `spec.credentials`; the control
|
|
11
11
|
* plane resolves those account references server-side (never secret material over this path).
|
|
12
12
|
*/
|
|
13
13
|
import { randomUUID } from "node:crypto";
|
|
14
|
-
import {
|
|
14
|
+
import { mapWorkspaceCredentials } from "./credentials.js";
|
|
15
15
|
const sanitizeRepoSlug = (value) => {
|
|
16
16
|
const slug = value
|
|
17
17
|
.toLowerCase()
|
|
@@ -26,16 +26,16 @@ const toGitUrl = (repository) => {
|
|
|
26
26
|
}
|
|
27
27
|
return `https://${repository}.git`;
|
|
28
28
|
};
|
|
29
|
-
export const
|
|
29
|
+
export const buildCreateWorkspaceRequest = (options, config) => {
|
|
30
30
|
const tail = options.repository
|
|
31
31
|
.split("/")
|
|
32
32
|
.filter((s) => s.length > 0)
|
|
33
33
|
.pop() ?? options.repository;
|
|
34
|
-
const credentials =
|
|
34
|
+
const credentials = mapWorkspaceCredentials(options.credentials);
|
|
35
35
|
const spec = {
|
|
36
36
|
version: "1",
|
|
37
37
|
sources: {
|
|
38
|
-
|
|
38
|
+
workspace: {
|
|
39
39
|
kind: "git",
|
|
40
40
|
provider: "generic",
|
|
41
41
|
url: toGitUrl(options.repository),
|
|
@@ -9,9 +9,9 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import type { SealantConfig } from "../types.js";
|
|
11
11
|
export interface SealantHostLocalConfig {
|
|
12
|
-
/** Owner principal the control plane attributes
|
|
12
|
+
/** Owner principal the control plane attributes workspaces/runs to (pre-auth). */
|
|
13
13
|
readonly ownerUserId: string;
|
|
14
|
-
/** Registry the
|
|
14
|
+
/** Registry the workspace image is published to and launched from. */
|
|
15
15
|
readonly registryId: string;
|
|
16
16
|
}
|
|
17
17
|
export interface SealantInternalConfig {
|
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Lowers the public `
|
|
3
|
-
* shape. Pure and side-effect free so it is unit-testable on its own; `
|
|
2
|
+
* Lowers the public `WorkspaceCredentialsOptions` onto the control-plane's workspace-create payload
|
|
3
|
+
* shape. Pure and side-effect free so it is unit-testable on its own; `buildCreateWorkspaceRequest`
|
|
4
4
|
* calls it and folds the result into the request it builds.
|
|
5
5
|
*
|
|
6
6
|
* SECURITY: this only ever moves account **references** (booleans/names/ids) — never token values or
|
|
7
7
|
* other secret material. `true` resolves to the literal account name `"default"`; a string passes
|
|
8
8
|
* through as the named account; `profile` becomes `profileId`.
|
|
9
9
|
*/
|
|
10
|
-
import type {
|
|
11
|
-
/** The control-plane's
|
|
12
|
-
export interface
|
|
10
|
+
import type { WorkspaceCredentialsOptions } from "../types.js";
|
|
11
|
+
/** The control-plane's workspace-create payload shape for connected-account credentials. */
|
|
12
|
+
export interface WorkspaceCredentialsPayload {
|
|
13
13
|
readonly profileId?: string;
|
|
14
14
|
readonly claude?: string;
|
|
15
15
|
readonly codex?: string;
|
|
16
16
|
readonly github?: string;
|
|
17
17
|
}
|
|
18
18
|
/**
|
|
19
|
-
* Maps `
|
|
19
|
+
* Maps `WorkspaceCredentialsOptions` to the API payload shape, or `undefined` when no credentials were
|
|
20
20
|
* requested (omitted entirely, rather than serialized as an empty object).
|
|
21
21
|
*/
|
|
22
|
-
export declare const
|
|
22
|
+
export declare const mapWorkspaceCredentials: (options: WorkspaceCredentialsOptions | undefined) => WorkspaceCredentialsPayload | undefined;
|
|
@@ -6,10 +6,10 @@ const mapAccountRef = (value) => {
|
|
|
6
6
|
return value === true ? DEFAULT_ACCOUNT_NAME : value;
|
|
7
7
|
};
|
|
8
8
|
/**
|
|
9
|
-
* Maps `
|
|
9
|
+
* Maps `WorkspaceCredentialsOptions` to the API payload shape, or `undefined` when no credentials were
|
|
10
10
|
* requested (omitted entirely, rather than serialized as an empty object).
|
|
11
11
|
*/
|
|
12
|
-
export const
|
|
12
|
+
export const mapWorkspaceCredentials = (options) => {
|
|
13
13
|
if (options === undefined) {
|
|
14
14
|
return undefined;
|
|
15
15
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Maps any failure crossing the Effect boundary onto a plain public `SealantError`. The Effect core
|
|
3
|
-
* fails with `@sealant/api-contracts` tagged errors (e.g. `
|
|
3
|
+
* fails with `@sealant/api-contracts` tagged errors (e.g. `WorkspaceNotFoundError`), Effect HTTP-client
|
|
4
4
|
* errors, or schema decode errors; the runtime squashes the `Cause` to its failure value and hands it
|
|
5
5
|
* here. This is the single funnel that keeps Effect internals out of the public, Promise-based API.
|
|
6
6
|
*/
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Maps any failure crossing the Effect boundary onto a plain public `SealantError`. The Effect core
|
|
3
|
-
* fails with `@sealant/api-contracts` tagged errors (e.g. `
|
|
3
|
+
* fails with `@sealant/api-contracts` tagged errors (e.g. `WorkspaceNotFoundError`), Effect HTTP-client
|
|
4
4
|
* errors, or schema decode errors; the runtime squashes the `Cause` to its failure value and hands it
|
|
5
5
|
* here. This is the single funnel that keeps Effect internals out of the public, Promise-based API.
|
|
6
6
|
*/
|