@sealant/sdk 0.5.2 → 0.7.1
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/dist/client.d.ts +8 -1
- package/dist/client.js +26 -1
- package/dist/effect/api-client.d.ts +618 -4
- package/dist/effect/operations.d.ts +199 -2
- package/dist/effect/operations.js +15 -0
- package/dist/effect/run-harness.js +32 -13
- package/dist/facade/session.d.ts +22 -0
- package/dist/facade/session.js +171 -0
- package/dist/facade/workspace.js +60 -2
- package/dist/internal/blueprint.js +21 -9
- package/dist/types.d.ts +121 -8
- package/package.json +2 -2
|
@@ -11,6 +11,7 @@
|
|
|
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 { SealantError } from "../errors.js";
|
|
14
15
|
import { mapWorkspaceCredentials } from "./credentials.js";
|
|
15
16
|
import { parseTtlSeconds } from "./duration.js";
|
|
16
17
|
const sanitizeRepoSlug = (value) => {
|
|
@@ -28,21 +29,32 @@ const toGitUrl = (repository) => {
|
|
|
28
29
|
return `https://${repository}.git`;
|
|
29
30
|
};
|
|
30
31
|
export const buildCreateWorkspaceRequest = (options, config) => {
|
|
31
|
-
|
|
32
|
+
if ((options.repository === undefined) === (options.source === undefined)) {
|
|
33
|
+
throw new SealantError("workspaces.create requires exactly one of `repository` (a git remote to clone) or `source` (a caller-owned mount).", { code: "invalid_create_options" });
|
|
34
|
+
}
|
|
35
|
+
if (options.source !== undefined && options.ref !== undefined) {
|
|
36
|
+
throw new SealantError("`ref` applies only to `repository` sources, not mounts.", {
|
|
37
|
+
code: "invalid_create_options",
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
const sourceName = options.repository ?? options.source?.path ?? "workspace";
|
|
41
|
+
const tail = sourceName
|
|
32
42
|
.split("/")
|
|
33
43
|
.filter((s) => s.length > 0)
|
|
34
|
-
.pop() ??
|
|
44
|
+
.pop() ?? sourceName;
|
|
35
45
|
const credentials = mapWorkspaceCredentials(options.credentials);
|
|
36
46
|
const spec = {
|
|
37
47
|
version: "1",
|
|
38
48
|
sources: {
|
|
39
|
-
workspace:
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
49
|
+
workspace: options.repository !== undefined
|
|
50
|
+
? {
|
|
51
|
+
kind: "git",
|
|
52
|
+
provider: "generic",
|
|
53
|
+
url: toGitUrl(options.repository),
|
|
54
|
+
// Omitted ref = the repository's default branch, resolved by the clone itself.
|
|
55
|
+
...(options.ref === undefined ? {} : { ref: options.ref }),
|
|
56
|
+
}
|
|
57
|
+
: { kind: "mount", hostPath: options.source?.path },
|
|
46
58
|
},
|
|
47
59
|
harness: { id: options.harness.id },
|
|
48
60
|
customization: { enableSealantd: true },
|
package/dist/types.d.ts
CHANGED
|
@@ -90,12 +90,30 @@ export interface WorkspaceCredentialsOptions {
|
|
|
90
90
|
/** `true` for the caller's default GitHub account, or a string naming a specific one. */
|
|
91
91
|
readonly github?: boolean | string;
|
|
92
92
|
}
|
|
93
|
+
/**
|
|
94
|
+
* A workspace sourced from a CALLER-OWNED host directory instead of a fresh clone. The platform
|
|
95
|
+
* bind-mounts `path` as the workspace working directory and treats it as caller-owned: writes
|
|
96
|
+
* persist across workspace stop/restart/expiry, and the path is never reprovisioned or deleted.
|
|
97
|
+
* The install must allowlist the path's root (`SEALANT_WORKSPACE_MOUNT_ALLOWED_ROOTS`); paths
|
|
98
|
+
* outside the allowlist are rejected at create. Credentials and dotfiles options compose
|
|
99
|
+
* unchanged. Clone-based workspaces remain the right shape for independent verification.
|
|
100
|
+
*/
|
|
101
|
+
export interface WorkspaceMountSource {
|
|
102
|
+
readonly kind: "mount";
|
|
103
|
+
/** Absolute, normalized host path (no `..` segments). */
|
|
104
|
+
readonly path: string;
|
|
105
|
+
}
|
|
93
106
|
export interface CreateOptions {
|
|
94
|
-
/**
|
|
95
|
-
|
|
107
|
+
/**
|
|
108
|
+
* Source git repository to build the workspace around (e.g. `"github.com/acme/billing-service"`).
|
|
109
|
+
* Exactly one of `repository` or `source` must be provided.
|
|
110
|
+
*/
|
|
111
|
+
readonly repository?: string;
|
|
112
|
+
/** Alternative to `repository`: source the workspace from a caller-owned mount. */
|
|
113
|
+
readonly source?: WorkspaceMountSource;
|
|
96
114
|
/** The harness to run inside the workspace. */
|
|
97
115
|
readonly harness: Harness;
|
|
98
|
-
/** Git ref to check out (defaults to the repository's default branch). */
|
|
116
|
+
/** Git ref to check out (defaults to the repository's default branch; `repository` only). */
|
|
99
117
|
readonly ref?: string;
|
|
100
118
|
/** Human-friendly name for the workspace. */
|
|
101
119
|
readonly name?: string;
|
|
@@ -156,6 +174,8 @@ export interface Workspace {
|
|
|
156
174
|
* run record like any other process. `argv[0]` is the executable, the rest its arguments.
|
|
157
175
|
*/
|
|
158
176
|
exec(argv: readonly string[], options?: WorkspaceExecOptions): Promise<WorkspaceExecResult>;
|
|
177
|
+
/** Interactive PTY sessions: open new ones, reattach to existing ones by id. */
|
|
178
|
+
readonly sessions: WorkspaceSessions;
|
|
159
179
|
/** Lifecycle events as an async stream. */
|
|
160
180
|
events(): AsyncIterable<WorkspaceEvent>;
|
|
161
181
|
/** Stop the workspace: remove its container and settle it in the terminal "stopped" status. */
|
|
@@ -175,9 +195,23 @@ export interface RunOptions {
|
|
|
175
195
|
readonly signal?: AbortSignal;
|
|
176
196
|
/** Idempotency key so a retried call does not start a duplicate run. */
|
|
177
197
|
readonly idempotencyKey?: string;
|
|
198
|
+
/**
|
|
199
|
+
* Opaque correlation bag ({ projectId, sessionId, ... }): stored verbatim by the platform and
|
|
200
|
+
* echoed on reads. No platform-side semantics.
|
|
201
|
+
*/
|
|
202
|
+
readonly metadata?: Readonly<Record<string, unknown>>;
|
|
178
203
|
}
|
|
204
|
+
/** Options for opening an interactive PTY session. */
|
|
179
205
|
export interface SessionOptions {
|
|
180
|
-
|
|
206
|
+
/** Working directory inside the workspace (defaults to the repository root). */
|
|
207
|
+
readonly cwd?: string;
|
|
208
|
+
/** Extra environment for the PTY process (not for secrets — use `credentials`). */
|
|
209
|
+
readonly env?: Readonly<Record<string, string>>;
|
|
210
|
+
readonly cols?: number;
|
|
211
|
+
readonly rows?: number;
|
|
212
|
+
readonly term?: string;
|
|
213
|
+
/** Opaque correlation bag, stored verbatim and echoed on reads. */
|
|
214
|
+
readonly metadata?: Readonly<Record<string, unknown>>;
|
|
181
215
|
}
|
|
182
216
|
/** Runs a harness in a workspace, one-shot or interactive. */
|
|
183
217
|
export interface HarnessRunner {
|
|
@@ -185,7 +219,7 @@ export interface HarnessRunner {
|
|
|
185
219
|
run(prompt: string, options?: RunOptions): Promise<Run>;
|
|
186
220
|
/** NON-BLOCKING: returns a live handle immediately for streaming via `run.record.stream()`. */
|
|
187
221
|
start(prompt: string, options?: RunOptions): Promise<Run>;
|
|
188
|
-
/**
|
|
222
|
+
/** Opens an interactive PTY session running the harness's launch command. */
|
|
189
223
|
session(options?: SessionOptions): Promise<InteractiveSession>;
|
|
190
224
|
}
|
|
191
225
|
export type RunOutcome = "completed" | "failed";
|
|
@@ -476,12 +510,91 @@ export interface Run {
|
|
|
476
510
|
/** Resolves once the run has terminally completed (no-op if already settled). */
|
|
477
511
|
wait(): Promise<Run>;
|
|
478
512
|
}
|
|
479
|
-
/**
|
|
513
|
+
/** Lifecycle status of an interactive session. */
|
|
514
|
+
export type SessionStatus = "starting" | "running" | "exited" | "failed";
|
|
515
|
+
/** One recorded output chunk. `sequence` is the durable resume cursor. */
|
|
516
|
+
export interface SessionOutputChunk {
|
|
517
|
+
readonly sequence: bigint;
|
|
518
|
+
readonly data: Uint8Array;
|
|
519
|
+
}
|
|
520
|
+
/** A point-in-time report of an interactive session's lifecycle. */
|
|
521
|
+
export interface InteractiveSessionStatus {
|
|
522
|
+
readonly status: SessionStatus;
|
|
523
|
+
readonly exitCode?: number;
|
|
524
|
+
readonly exitSignal?: number;
|
|
525
|
+
/**
|
|
526
|
+
* Highest recorded output sequence — resume a disconnected reader with
|
|
527
|
+
* `output({ from: outputHighWater + 1n })` (or re-read from `0n` for full history).
|
|
528
|
+
*/
|
|
529
|
+
readonly outputHighWater: bigint;
|
|
530
|
+
}
|
|
531
|
+
/**
|
|
532
|
+
* An interactive PTY session over a live workspace. Sessions are DURABLE PLATFORM RESOURCES, not
|
|
533
|
+
* client connections: the PTY keeps running when this handle (or the whole process) goes away, and
|
|
534
|
+
* a session can be re-fetched by id from any workspace handle (`workspace.sessions.get(id)`) and
|
|
535
|
+
* driven from there. Output is byte-exact, redacted, and sequence-keyed — `output({ from: 0n })`
|
|
536
|
+
* after a reconnect replays the full recorded history and then live-tails.
|
|
537
|
+
*/
|
|
480
538
|
export interface InteractiveSession {
|
|
481
|
-
|
|
482
|
-
|
|
539
|
+
readonly id: string;
|
|
540
|
+
readonly workspaceId: string;
|
|
541
|
+
/** The run recording this session — its record is the durable, replayable evidence. */
|
|
542
|
+
readonly runId: string;
|
|
543
|
+
/** Send keystrokes. Strings are UTF-8-encoded; bytes pass through untouched. */
|
|
544
|
+
send(input: string | Uint8Array): Promise<void>;
|
|
545
|
+
/**
|
|
546
|
+
* Byte-exact output as a RESUMABLE stream: recorded history from `from` (inclusive; default the
|
|
547
|
+
* beginning), then the live tail until the session settles. Each chunk carries its durable
|
|
548
|
+
* sequence, so a disconnected consumer resumes with `from: lastChunk.sequence + 1n`.
|
|
549
|
+
*/
|
|
550
|
+
output(options?: {
|
|
551
|
+
readonly from?: bigint;
|
|
552
|
+
readonly signal?: AbortSignal;
|
|
553
|
+
}): AsyncIterable<SessionOutputChunk>;
|
|
554
|
+
/** Resize the PTY. */
|
|
555
|
+
resize(cols: number, rows: number): Promise<void>;
|
|
556
|
+
/** Deliver a POSIX signal to the session's process (e.g. 2 = SIGINT). */
|
|
557
|
+
signal(signal: number): Promise<void>;
|
|
558
|
+
/** Current lifecycle + the output high-water mark (the resume cursor). */
|
|
559
|
+
status(): Promise<InteractiveSessionStatus>;
|
|
560
|
+
/** Close the PTY (hang up the terminal). Resolves once the session settles. */
|
|
483
561
|
close(): Promise<void>;
|
|
484
562
|
}
|
|
563
|
+
/** Interactive sessions of one workspace: open new ones, reattach to existing ones. */
|
|
564
|
+
export interface WorkspaceSessions {
|
|
565
|
+
/** Opens a PTY session running `argv` (argv[0] is the program). */
|
|
566
|
+
open(argv: readonly string[], options?: SessionOptions): Promise<InteractiveSession>;
|
|
567
|
+
/** Reattach to a session by id — works from ANY handle, not just the creating one. */
|
|
568
|
+
get(sessionId: string): Promise<InteractiveSession>;
|
|
569
|
+
/** Sessions of this workspace, newest first. */
|
|
570
|
+
list(): Promise<readonly InteractiveSession[]>;
|
|
571
|
+
}
|
|
572
|
+
/**
|
|
573
|
+
* Scopes for the session surface: `session:read` (stream/status/output), `session:input`
|
|
574
|
+
* (input/resize/signal), `workspace:exec` (open sessions/terminals, exec). A client holding only
|
|
575
|
+
* `session:read` can stream output but is rejected for input and exec.
|
|
576
|
+
*/
|
|
577
|
+
export type AccessTokenScope = "session:read" | "session:input" | "workspace:exec";
|
|
578
|
+
export interface CreateAccessTokenOptions {
|
|
579
|
+
readonly scopes: readonly AccessTokenScope[];
|
|
580
|
+
readonly name?: string;
|
|
581
|
+
/** Narrow the token to one workspace. */
|
|
582
|
+
readonly workspaceId?: string;
|
|
583
|
+
/** Time-to-live, e.g. `"15m"`, `"2h"`. Omitted = no expiry. */
|
|
584
|
+
readonly ttl?: string;
|
|
585
|
+
}
|
|
586
|
+
export interface CreatedAccessToken {
|
|
587
|
+
readonly tokenId: string;
|
|
588
|
+
/** The bearer secret — shown exactly once, never retrievable again. Use it as `apiKey`. */
|
|
589
|
+
readonly token: string;
|
|
590
|
+
readonly scopes: readonly AccessTokenScope[];
|
|
591
|
+
readonly workspaceId?: string;
|
|
592
|
+
readonly expiresAt?: string;
|
|
593
|
+
}
|
|
594
|
+
/** Mint scoped bearer tokens (e.g. for a mobile pairing flow's per-scope grants). */
|
|
595
|
+
export interface AccessTokensNamespace {
|
|
596
|
+
create(options: CreateAccessTokenOptions): Promise<CreatedAccessToken>;
|
|
597
|
+
}
|
|
485
598
|
/**
|
|
486
599
|
* Connected-account selection for inference — the same reference shape as workspace creation,
|
|
487
600
|
* minus GitHub (not a model provider). `true` means "my default account"; a string names one.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sealant/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.1",
|
|
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": {
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"effect": "^4.0.0-beta.85",
|
|
30
|
-
"@sealant/api-contracts": "^0.
|
|
30
|
+
"@sealant/api-contracts": "^0.7.1"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"@effect/vitest": "^4.0.0-beta.85",
|