@sealant/sdk 0.20.2 → 0.22.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/dist/client.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { AccessTokensNamespace, CreateOptions, InferenceNamespace, ListOptions, Run, Workspace, SealantConfig } from "./types.js";
1
+ import type { AccessTokensNamespace, ConnectedAccountsNamespace, CreateOptions, InferenceNamespace, ListOptions, Run, Workspace, SealantConfig, UsersNamespace } from "./types.js";
2
2
  export declare class Sealant {
3
3
  #private;
4
4
  constructor(config: SealantConfig);
@@ -21,6 +21,10 @@ export declare class Sealant {
21
21
  * the session endpoints enforce exactly those scopes (a read-stream token can stream but is
22
22
  * rejected for input and exec).
23
23
  */
24
+ /** Identity rows for products that own their own login (one client per user afterwards). */
25
+ readonly users: UsersNamespace;
26
+ /** This client's owner's Claude / Codex / GitHub accounts. Secrets go in; none come out. */
27
+ readonly connectedAccounts: ConnectedAccountsNamespace;
24
28
  readonly accessTokens: AccessTokensNamespace;
25
29
  /** Runs by id — so a record can be replayed long after its workspace is gone. */
26
30
  readonly runs: {
package/dist/client.js CHANGED
@@ -1,16 +1,4 @@
1
- /**
2
- * `Sealant` — the public client and the import users reach for.
3
- *
4
- * import { Sealant, opencode } from "@sealant/sdk"
5
- * const sealant = new Sealant({ baseUrl: "http://localhost:8080" })
6
- *
7
- * This is the plain-Promise facade over the Effect core: an app `Layer` built once in the
8
- * constructor providing the wire client derived from `@sealant/api-contracts`. Everything is a plain
9
- * HTTP call to `baseUrl`. Operations whose endpoints have not landed yet stay TYPED against the
10
- * stable surface and reject with `SealantNotImplementedError` so callers can compile and wire
11
- * against the final shape today.
12
- */
13
- import { createAccessTokenOp, createWorkspaceOp, getRunOp, getWorkspaceOp, inferenceRespondOp, listWorkspacesOp, } from "./effect/operations.js";
1
+ import { archiveConnectedAccountOp, createAccessTokenOp, createConnectedAccountOp, createWorkspaceOp, ensureUserOp, getRunOp, getUserOp, getWorkspaceOp, inferenceRespondOp, listConnectedAccountsOp, listWorkspacesOp, } from "./effect/operations.js";
14
2
  import { runHarness, startHarness } from "./effect/run-harness.js";
15
3
  import { makeSdkRuntime } from "./effect/runtime.js";
16
4
  import { SealantError } from "./errors.js";
@@ -20,6 +8,19 @@ import { buildCreateWorkspaceRequest } from "./internal/blueprint.js";
20
8
  import { resolveInternalConfig } from "./internal/config.js";
21
9
  import { parseTtlSeconds } from "./internal/duration.js";
22
10
  import { buildInferenceRespondRequest, mapInferenceResponse } from "./internal/inference.js";
11
+ const mapConnectedAccount = (wire) => ({
12
+ connectedAccountId: wire.connectedAccountId,
13
+ ownerUserId: wire.ownerUserId,
14
+ provider: wire.provider,
15
+ name: wire.name,
16
+ kind: wire.kind,
17
+ status: wire.status,
18
+ metadata: wire.metadata,
19
+ connectedAt: wire.connectedAt,
20
+ updatedAt: wire.updatedAt,
21
+ lastUsedAt: wire.lastUsedAt,
22
+ lastSyncedAt: wire.lastSyncedAt,
23
+ });
23
24
  // Wire the run-execution implementations into the Workspace facade (the injection point exists to
24
25
  // break the workspace <-> run-harness import cycle; the client is the composition root).
25
26
  registerHarnessExecutors({ run: runHarness, start: startHarness });
@@ -72,7 +73,7 @@ export class Sealant {
72
73
  return workspace.ready();
73
74
  },
74
75
  get: async (id) => {
75
- const details = await this.#runtime.run(getWorkspaceOp(id));
76
+ const details = await this.#runtime.run(getWorkspaceOp(id, this.#ctx.config.hostLocal.ownerUserId));
76
77
  return makeWorkspace(this.#ctx, {
77
78
  id: details.workspaceId,
78
79
  name: details.name,
@@ -109,6 +110,47 @@ export class Sealant {
109
110
  * the session endpoints enforce exactly those scopes (a read-stream token can stream but is
110
111
  * rejected for input and exec).
111
112
  */
113
+ /** Identity rows for products that own their own login (one client per user afterwards). */
114
+ users = {
115
+ ensure: async (options) => {
116
+ const wire = await this.#runtime.run(ensureUserOp({
117
+ email: options.email,
118
+ name: options.name,
119
+ ...(options.userId === undefined ? {} : { userId: options.userId }),
120
+ }));
121
+ return {
122
+ userId: wire.userId,
123
+ email: wire.email,
124
+ name: wire.name,
125
+ createdAt: wire.createdAt,
126
+ created: wire.created,
127
+ };
128
+ },
129
+ get: async (userId) => {
130
+ const wire = await this.#runtime.run(getUserOp(userId));
131
+ return { userId: wire.userId, email: wire.email, name: wire.name, createdAt: wire.createdAt };
132
+ },
133
+ };
134
+ /** This client's owner's Claude / Codex / GitHub accounts. Secrets go in; none come out. */
135
+ connectedAccounts = {
136
+ list: async () => {
137
+ const wire = await this.#runtime.run(listConnectedAccountsOp(this.#ctx.config.hostLocal.ownerUserId));
138
+ return wire.items.map(mapConnectedAccount);
139
+ },
140
+ connect: async (options) => {
141
+ const wire = await this.#runtime.run(createConnectedAccountOp({
142
+ ownerUserId: this.#ctx.config.hostLocal.ownerUserId,
143
+ provider: options.provider,
144
+ secret: options.secret,
145
+ ...(options.name === undefined ? {} : { name: options.name }),
146
+ }));
147
+ return mapConnectedAccount(wire);
148
+ },
149
+ disconnect: async (connectedAccountId) => {
150
+ const wire = await this.#runtime.run(archiveConnectedAccountOp(connectedAccountId, this.#ctx.config.hostLocal.ownerUserId));
151
+ return mapConnectedAccount(wire);
152
+ },
153
+ };
112
154
  accessTokens = {
113
155
  create: async (options) => {
114
156
  const wire = await this.#runtime.run(createAccessTokenOp({
@@ -130,7 +172,7 @@ export class Sealant {
130
172
  /** Runs by id — so a record can be replayed long after its workspace is gone. */
131
173
  runs = {
132
174
  get: async (runId) => {
133
- const wire = await this.#runtime.run(getRunOp(runId));
175
+ const wire = await this.#runtime.run(getRunOp(runId, this.#ctx.config.hostLocal.ownerUserId));
134
176
  return makeRun(this.#ctx, { wire });
135
177
  },
136
178
  };
@@ -537,6 +537,9 @@ declare const buildControlPlaneClient: (config: SealantInternalConfig) => Effect
537
537
  readonly params: {
538
538
  readonly runId: string;
539
539
  };
540
+ readonly query: {
541
+ readonly ownerUserId?: string | undefined;
542
+ };
540
543
  readonly responseMode?: Mode;
541
544
  }) => Effect.Effect<HttpApiClient.Client.Response<{
542
545
  readonly runId: string;
@@ -566,6 +569,9 @@ declare const buildControlPlaneClient: (config: SealantInternalConfig) => Effect
566
569
  readonly params: {
567
570
  readonly runId: string;
568
571
  };
572
+ readonly query: {
573
+ readonly ownerUserId?: string | undefined;
574
+ };
569
575
  readonly responseMode?: Mode;
570
576
  }) => Effect.Effect<HttpApiClient.Client.Response<{
571
577
  readonly files: readonly {
@@ -580,6 +586,9 @@ declare const buildControlPlaneClient: (config: SealantInternalConfig) => Effect
580
586
  readonly runId: string;
581
587
  readonly sequence: string;
582
588
  };
589
+ readonly query: {
590
+ readonly ownerUserId?: string | undefined;
591
+ };
583
592
  readonly responseMode?: Mode;
584
593
  }) => Effect.Effect<HttpApiClient.Client.Response<{
585
594
  readonly eventId: string;
@@ -603,6 +612,9 @@ declare const buildControlPlaneClient: (config: SealantInternalConfig) => Effect
603
612
  readonly params: {
604
613
  readonly runId: string;
605
614
  };
615
+ readonly query: {
616
+ readonly ownerUserId?: string | undefined;
617
+ };
606
618
  readonly responseMode?: Mode;
607
619
  }) => Effect.Effect<HttpApiClient.Client.Response<{
608
620
  readonly runId: string;
@@ -624,6 +636,7 @@ declare const buildControlPlaneClient: (config: SealantInternalConfig) => Effect
624
636
  readonly runId: string;
625
637
  };
626
638
  readonly query: {
639
+ readonly ownerUserId?: string | undefined;
627
640
  readonly processId: string;
628
641
  readonly stream: "pty" | "stderr" | "stdout";
629
642
  readonly atSequence?: string | undefined;
@@ -642,6 +655,7 @@ declare const buildControlPlaneClient: (config: SealantInternalConfig) => Effect
642
655
  readonly runId: string;
643
656
  };
644
657
  readonly query: {
658
+ readonly ownerUserId?: string | undefined;
645
659
  readonly fromSequence?: string | undefined;
646
660
  readonly toSequence?: string | undefined;
647
661
  readonly limit?: string | undefined;
@@ -758,6 +772,7 @@ declare const buildControlPlaneClient: (config: SealantInternalConfig) => Effect
758
772
  readonly cwd?: string | undefined;
759
773
  readonly cols: number;
760
774
  readonly rows: number;
775
+ readonly mode?: "pipe" | "pty" | undefined;
761
776
  readonly exitCode?: number | undefined;
762
777
  readonly exitSignal?: number | undefined;
763
778
  readonly errorMessage?: string | undefined;
@@ -783,6 +798,7 @@ declare const buildControlPlaneClient: (config: SealantInternalConfig) => Effect
783
798
  readonly cols?: number | undefined;
784
799
  readonly rows?: number | undefined;
785
800
  readonly term?: string | undefined;
801
+ readonly mode?: "pipe" | "pty" | undefined;
786
802
  readonly metadata?: {
787
803
  readonly [x: string]: unknown;
788
804
  } | undefined;
@@ -798,6 +814,7 @@ declare const buildControlPlaneClient: (config: SealantInternalConfig) => Effect
798
814
  readonly cwd?: string | undefined;
799
815
  readonly cols: number;
800
816
  readonly rows: number;
817
+ readonly mode?: "pipe" | "pty" | undefined;
801
818
  readonly exitCode?: number | undefined;
802
819
  readonly exitSignal?: number | undefined;
803
820
  readonly errorMessage?: string | undefined;
@@ -829,6 +846,7 @@ declare const buildControlPlaneClient: (config: SealantInternalConfig) => Effect
829
846
  readonly cwd?: string | undefined;
830
847
  readonly cols: number;
831
848
  readonly rows: number;
849
+ readonly mode?: "pipe" | "pty" | undefined;
832
850
  readonly exitCode?: number | undefined;
833
851
  readonly exitSignal?: number | undefined;
834
852
  readonly errorMessage?: string | undefined;
@@ -883,6 +901,7 @@ declare const buildControlPlaneClient: (config: SealantInternalConfig) => Effect
883
901
  readonly cwd?: string | undefined;
884
902
  readonly cols: number;
885
903
  readonly rows: number;
904
+ readonly mode?: "pipe" | "pty" | undefined;
886
905
  readonly exitCode?: number | undefined;
887
906
  readonly exitSignal?: number | undefined;
888
907
  readonly errorMessage?: string | undefined;
@@ -1033,6 +1052,33 @@ declare const buildControlPlaneClient: (config: SealantInternalConfig) => Effect
1033
1052
  readonly status: "ok";
1034
1053
  }, Mode>, import("effect/unstable/http/HttpClientError").HttpClientError | ([Mode] extends ["response-only"] ? never : import("effect/Schema").SchemaError), [Mode] extends ["response-only"] ? never : never>;
1035
1054
  };
1055
+ readonly users: {
1056
+ readonly ensureUser: <Mode extends import("effect/unstable/httpapi/HttpApiEndpoint").ClientResponseMode = import("effect/unstable/httpapi/HttpApiEndpoint").ClientResponseMode>(request: {
1057
+ readonly payload: {
1058
+ readonly email: string;
1059
+ readonly name: string;
1060
+ readonly userId?: string | undefined;
1061
+ };
1062
+ readonly responseMode?: Mode;
1063
+ }) => Effect.Effect<HttpApiClient.Client.Response<{
1064
+ readonly userId: string;
1065
+ readonly email: string;
1066
+ readonly name: string;
1067
+ readonly createdAt: string;
1068
+ readonly created: boolean;
1069
+ }, Mode>, import("effect/unstable/http/HttpClientError").HttpClientError | ([Mode] extends ["response-only"] ? never : import("effect/Schema").SchemaError | import("@sealant/api-contracts").UserBadRequestError | import("@sealant/api-contracts").UserInternalServerError), [Mode] extends ["response-only"] ? never : never>;
1070
+ readonly getUser: <Mode extends import("effect/unstable/httpapi/HttpApiEndpoint").ClientResponseMode = import("effect/unstable/httpapi/HttpApiEndpoint").ClientResponseMode>(request: {
1071
+ readonly params: {
1072
+ readonly userId: string;
1073
+ };
1074
+ readonly responseMode?: Mode;
1075
+ }) => Effect.Effect<HttpApiClient.Client.Response<{
1076
+ readonly userId: string;
1077
+ readonly email: string;
1078
+ readonly name: string;
1079
+ readonly createdAt: string;
1080
+ }, Mode>, import("effect/unstable/http/HttpClientError").HttpClientError | ([Mode] extends ["response-only"] ? never : import("effect/Schema").SchemaError | import("@sealant/api-contracts").UserInternalServerError | import("@sealant/api-contracts").UserNotFoundError), [Mode] extends ["response-only"] ? never : never>;
1081
+ };
1036
1082
  readonly workspaces: {
1037
1083
  readonly createWorkspace: <Mode extends import("effect/unstable/httpapi/HttpApiEndpoint").ClientResponseMode = import("effect/unstable/httpapi/HttpApiEndpoint").ClientResponseMode>(request: {
1038
1084
  readonly headers: {
@@ -1131,6 +1177,9 @@ declare const buildControlPlaneClient: (config: SealantInternalConfig) => Effect
1131
1177
  readonly params: {
1132
1178
  readonly workspaceId: string;
1133
1179
  };
1180
+ readonly query: {
1181
+ readonly ownerUserId?: string | undefined;
1182
+ };
1134
1183
  readonly responseMode?: Mode;
1135
1184
  }) => Effect.Effect<HttpApiClient.Client.Response<{
1136
1185
  readonly workspaceId: string;
@@ -1858,6 +1907,9 @@ declare const SealantApiClient_base: Context.ServiceClass<SealantApiClient, "@se
1858
1907
  readonly params: {
1859
1908
  readonly runId: string;
1860
1909
  };
1910
+ readonly query: {
1911
+ readonly ownerUserId?: string | undefined;
1912
+ };
1861
1913
  readonly responseMode?: Mode;
1862
1914
  }) => Effect.Effect<HttpApiClient.Client.Response<{
1863
1915
  readonly runId: string;
@@ -1887,6 +1939,9 @@ declare const SealantApiClient_base: Context.ServiceClass<SealantApiClient, "@se
1887
1939
  readonly params: {
1888
1940
  readonly runId: string;
1889
1941
  };
1942
+ readonly query: {
1943
+ readonly ownerUserId?: string | undefined;
1944
+ };
1890
1945
  readonly responseMode?: Mode;
1891
1946
  }) => Effect.Effect<HttpApiClient.Client.Response<{
1892
1947
  readonly files: readonly {
@@ -1901,6 +1956,9 @@ declare const SealantApiClient_base: Context.ServiceClass<SealantApiClient, "@se
1901
1956
  readonly runId: string;
1902
1957
  readonly sequence: string;
1903
1958
  };
1959
+ readonly query: {
1960
+ readonly ownerUserId?: string | undefined;
1961
+ };
1904
1962
  readonly responseMode?: Mode;
1905
1963
  }) => Effect.Effect<HttpApiClient.Client.Response<{
1906
1964
  readonly eventId: string;
@@ -1924,6 +1982,9 @@ declare const SealantApiClient_base: Context.ServiceClass<SealantApiClient, "@se
1924
1982
  readonly params: {
1925
1983
  readonly runId: string;
1926
1984
  };
1985
+ readonly query: {
1986
+ readonly ownerUserId?: string | undefined;
1987
+ };
1927
1988
  readonly responseMode?: Mode;
1928
1989
  }) => Effect.Effect<HttpApiClient.Client.Response<{
1929
1990
  readonly runId: string;
@@ -1945,6 +2006,7 @@ declare const SealantApiClient_base: Context.ServiceClass<SealantApiClient, "@se
1945
2006
  readonly runId: string;
1946
2007
  };
1947
2008
  readonly query: {
2009
+ readonly ownerUserId?: string | undefined;
1948
2010
  readonly processId: string;
1949
2011
  readonly stream: "pty" | "stderr" | "stdout";
1950
2012
  readonly atSequence?: string | undefined;
@@ -1963,6 +2025,7 @@ declare const SealantApiClient_base: Context.ServiceClass<SealantApiClient, "@se
1963
2025
  readonly runId: string;
1964
2026
  };
1965
2027
  readonly query: {
2028
+ readonly ownerUserId?: string | undefined;
1966
2029
  readonly fromSequence?: string | undefined;
1967
2030
  readonly toSequence?: string | undefined;
1968
2031
  readonly limit?: string | undefined;
@@ -2079,6 +2142,7 @@ declare const SealantApiClient_base: Context.ServiceClass<SealantApiClient, "@se
2079
2142
  readonly cwd?: string | undefined;
2080
2143
  readonly cols: number;
2081
2144
  readonly rows: number;
2145
+ readonly mode?: "pipe" | "pty" | undefined;
2082
2146
  readonly exitCode?: number | undefined;
2083
2147
  readonly exitSignal?: number | undefined;
2084
2148
  readonly errorMessage?: string | undefined;
@@ -2104,6 +2168,7 @@ declare const SealantApiClient_base: Context.ServiceClass<SealantApiClient, "@se
2104
2168
  readonly cols?: number | undefined;
2105
2169
  readonly rows?: number | undefined;
2106
2170
  readonly term?: string | undefined;
2171
+ readonly mode?: "pipe" | "pty" | undefined;
2107
2172
  readonly metadata?: {
2108
2173
  readonly [x: string]: unknown;
2109
2174
  } | undefined;
@@ -2119,6 +2184,7 @@ declare const SealantApiClient_base: Context.ServiceClass<SealantApiClient, "@se
2119
2184
  readonly cwd?: string | undefined;
2120
2185
  readonly cols: number;
2121
2186
  readonly rows: number;
2187
+ readonly mode?: "pipe" | "pty" | undefined;
2122
2188
  readonly exitCode?: number | undefined;
2123
2189
  readonly exitSignal?: number | undefined;
2124
2190
  readonly errorMessage?: string | undefined;
@@ -2150,6 +2216,7 @@ declare const SealantApiClient_base: Context.ServiceClass<SealantApiClient, "@se
2150
2216
  readonly cwd?: string | undefined;
2151
2217
  readonly cols: number;
2152
2218
  readonly rows: number;
2219
+ readonly mode?: "pipe" | "pty" | undefined;
2153
2220
  readonly exitCode?: number | undefined;
2154
2221
  readonly exitSignal?: number | undefined;
2155
2222
  readonly errorMessage?: string | undefined;
@@ -2204,6 +2271,7 @@ declare const SealantApiClient_base: Context.ServiceClass<SealantApiClient, "@se
2204
2271
  readonly cwd?: string | undefined;
2205
2272
  readonly cols: number;
2206
2273
  readonly rows: number;
2274
+ readonly mode?: "pipe" | "pty" | undefined;
2207
2275
  readonly exitCode?: number | undefined;
2208
2276
  readonly exitSignal?: number | undefined;
2209
2277
  readonly errorMessage?: string | undefined;
@@ -2354,6 +2422,33 @@ declare const SealantApiClient_base: Context.ServiceClass<SealantApiClient, "@se
2354
2422
  readonly status: "ok";
2355
2423
  }, Mode>, import("effect/unstable/http/HttpClientError").HttpClientError | ([Mode] extends ["response-only"] ? never : import("effect/Schema").SchemaError), [Mode] extends ["response-only"] ? never : never>;
2356
2424
  };
2425
+ readonly users: {
2426
+ readonly ensureUser: <Mode extends import("effect/unstable/httpapi/HttpApiEndpoint").ClientResponseMode = import("effect/unstable/httpapi/HttpApiEndpoint").ClientResponseMode>(request: {
2427
+ readonly payload: {
2428
+ readonly email: string;
2429
+ readonly name: string;
2430
+ readonly userId?: string | undefined;
2431
+ };
2432
+ readonly responseMode?: Mode;
2433
+ }) => Effect.Effect<HttpApiClient.Client.Response<{
2434
+ readonly userId: string;
2435
+ readonly email: string;
2436
+ readonly name: string;
2437
+ readonly createdAt: string;
2438
+ readonly created: boolean;
2439
+ }, Mode>, import("effect/unstable/http/HttpClientError").HttpClientError | ([Mode] extends ["response-only"] ? never : import("effect/Schema").SchemaError | import("@sealant/api-contracts").UserBadRequestError | import("@sealant/api-contracts").UserInternalServerError), [Mode] extends ["response-only"] ? never : never>;
2440
+ readonly getUser: <Mode extends import("effect/unstable/httpapi/HttpApiEndpoint").ClientResponseMode = import("effect/unstable/httpapi/HttpApiEndpoint").ClientResponseMode>(request: {
2441
+ readonly params: {
2442
+ readonly userId: string;
2443
+ };
2444
+ readonly responseMode?: Mode;
2445
+ }) => Effect.Effect<HttpApiClient.Client.Response<{
2446
+ readonly userId: string;
2447
+ readonly email: string;
2448
+ readonly name: string;
2449
+ readonly createdAt: string;
2450
+ }, Mode>, import("effect/unstable/http/HttpClientError").HttpClientError | ([Mode] extends ["response-only"] ? never : import("effect/Schema").SchemaError | import("@sealant/api-contracts").UserInternalServerError | import("@sealant/api-contracts").UserNotFoundError), [Mode] extends ["response-only"] ? never : never>;
2451
+ };
2357
2452
  readonly workspaces: {
2358
2453
  readonly createWorkspace: <Mode extends import("effect/unstable/httpapi/HttpApiEndpoint").ClientResponseMode = import("effect/unstable/httpapi/HttpApiEndpoint").ClientResponseMode>(request: {
2359
2454
  readonly headers: {
@@ -2452,6 +2547,9 @@ declare const SealantApiClient_base: Context.ServiceClass<SealantApiClient, "@se
2452
2547
  readonly params: {
2453
2548
  readonly workspaceId: string;
2454
2549
  };
2550
+ readonly query: {
2551
+ readonly ownerUserId?: string | undefined;
2552
+ };
2455
2553
  readonly responseMode?: Mode;
2456
2554
  }) => Effect.Effect<HttpApiClient.Client.Response<{
2457
2555
  readonly workspaceId: string;
@@ -37,7 +37,7 @@ const execWorkspaceEffect = (ctx, init, argv, options) => Effect.gen(function* (
37
37
  }));
38
38
  }
39
39
  yield* Effect.sleep(POLL_INTERVAL);
40
- wire = yield* getRunOp(runId);
40
+ wire = yield* getRunOp(runId, ctx.config.hostLocal.ownerUserId);
41
41
  }
42
42
  // Exec framing: "completed" means every command executed and was recorded — anything else means
43
43
  // the machinery broke and the exit code cannot be trusted, which IS the error case.
@@ -48,7 +48,7 @@ const execWorkspaceEffect = (ctx, init, argv, options) => Effect.gen(function* (
48
48
  const processId = findCommandProcessId(started, executable);
49
49
  const stdout = processId === undefined ? "" : yield* readScrollback(runId, processId, "stdout");
50
50
  const stderr = processId === undefined ? "" : yield* readScrollback(runId, processId, "stderr");
51
- const changes = toRunChangesData(yield* getRunChangesOp(runId));
51
+ const changes = toRunChangesData(yield* getRunChangesOp(runId, ctx.config.hostLocal.ownerUserId));
52
52
  return {
53
53
  exitCode: wire.exitCode ?? -1,
54
54
  stdout,
@@ -24,4 +24,4 @@ export { makeSdkRuntime } from "./runtime.js";
24
24
  export type { SdkRuntime, SdkServices } from "./runtime.js";
25
25
  export { resolveInternalConfig } from "../internal/config.js";
26
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";
27
+ export { ConnectedAccountBadRequestError, ConnectedAccountConflictError, ConnectedAccountInternalServerError, ConnectedAccountNotFoundError, ConnectedAccountServiceUnavailableError, InferenceBadRequestError, InferenceConflictError, InferenceInternalServerError, InferenceNotFoundError, InferenceUnavailableError, RunBadRequestError, RunInternalServerError, RunNotFoundError, UserBadRequestError, UserInternalServerError, UserNotFoundError, WorkspaceBadGatewayError, WorkspaceBadRequestError, WorkspaceConflictError, WorkspaceForbiddenError, WorkspaceInternalServerError, WorkspaceNotFoundError, WorkspaceServiceUnavailableError, WorkspaceUnauthorizedError, } from "@sealant/api-contracts";
@@ -28,4 +28,4 @@ export { resolveInternalConfig } from "../internal/config.js";
28
28
  // The typed contract errors carried on the client's failure channel (workspaces + runs +
29
29
  // inference — the groups the operations above call). Re-exported so Effect consumers don't need to
30
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";
31
+ export { ConnectedAccountBadRequestError, ConnectedAccountConflictError, ConnectedAccountInternalServerError, ConnectedAccountNotFoundError, ConnectedAccountServiceUnavailableError, InferenceBadRequestError, InferenceConflictError, InferenceInternalServerError, InferenceNotFoundError, InferenceUnavailableError, RunBadRequestError, RunInternalServerError, RunNotFoundError, UserBadRequestError, UserInternalServerError, UserNotFoundError, WorkspaceBadGatewayError, WorkspaceBadRequestError, WorkspaceConflictError, WorkspaceForbiddenError, WorkspaceInternalServerError, WorkspaceNotFoundError, WorkspaceServiceUnavailableError, WorkspaceUnauthorizedError, } from "@sealant/api-contracts";
@@ -37,7 +37,7 @@ export declare const createWorkspaceOp: (payload: {
37
37
  readonly repository: string;
38
38
  readonly tag: string;
39
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>;
40
- export declare const getWorkspaceOp: (workspaceId: string) => Effect.Effect<{
40
+ export declare const getWorkspaceOp: (workspaceId: string, ownerUserId?: string | undefined) => Effect.Effect<{
41
41
  readonly workspaceId: string;
42
42
  readonly name: string;
43
43
  readonly ownerUserId: string;
@@ -194,7 +194,7 @@ export declare const createRunOp: (payload: {
194
194
  readonly createdAt: string;
195
195
  readonly updatedAt: string;
196
196
  }, 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>;
197
- export declare const getRunOp: (runId: string) => Effect.Effect<{
197
+ export declare const getRunOp: (runId: string, ownerUserId?: string | undefined) => Effect.Effect<{
198
198
  readonly runId: string;
199
199
  readonly workspaceId: string;
200
200
  readonly attemptId?: string | undefined;
@@ -284,6 +284,7 @@ export declare const updateRunOp: (runId: string, payload: {
284
284
  readonly updatedAt: string;
285
285
  }, 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>;
286
286
  export declare const getRunTimelineOp: (runId: string, query: {
287
+ readonly ownerUserId?: string | undefined;
287
288
  readonly fromSequence?: string | undefined;
288
289
  readonly toSequence?: string | undefined;
289
290
  readonly limit?: string | undefined;
@@ -300,6 +301,7 @@ export declare const getRunTimelineOp: (runId: string, query: {
300
301
  readonly confidence: number;
301
302
  }[], 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>;
302
303
  export declare const getRunScrollbackOp: (runId: string, query: {
304
+ readonly ownerUserId?: string | undefined;
303
305
  readonly processId: string;
304
306
  readonly stream: "pty" | "stderr" | "stdout";
305
307
  readonly atSequence?: string | undefined;
@@ -311,7 +313,7 @@ export declare const getRunScrollbackOp: (runId: string, query: {
311
313
  readonly byteCount: number;
312
314
  readonly contentBase64: string;
313
315
  }, 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>;
314
- export declare const getRunLossOp: (runId: string) => Effect.Effect<{
316
+ export declare const getRunLossOp: (runId: string, ownerUserId?: string | undefined) => Effect.Effect<{
315
317
  readonly runId: string;
316
318
  readonly droppedEventCount: string;
317
319
  readonly sequenceGapCount: number;
@@ -326,7 +328,7 @@ export declare const getRunLossOp: (runId: string) => Effect.Effect<{
326
328
  readonly reason?: string | undefined;
327
329
  }[];
328
330
  }, import("effect/unstable/http/HttpClientError").HttpClientError | import("@sealant/api-contracts").RunInternalServerError | import("@sealant/api-contracts").RunNotFoundError | import("effect/Schema").SchemaError, SealantApiClient>;
329
- export declare const getRunChangesOp: (runId: string) => Effect.Effect<{
331
+ export declare const getRunChangesOp: (runId: string, ownerUserId?: string | undefined) => Effect.Effect<{
330
332
  readonly files: readonly {
331
333
  readonly path: string;
332
334
  readonly change: "added" | "deleted" | "modified" | "renamed";
@@ -345,6 +347,7 @@ export declare const createSessionOp: (payload: {
345
347
  readonly cols?: number | undefined;
346
348
  readonly rows?: number | undefined;
347
349
  readonly term?: string | undefined;
350
+ readonly mode?: "pipe" | "pty" | undefined;
348
351
  readonly metadata?: {
349
352
  readonly [x: string]: unknown;
350
353
  } | undefined;
@@ -358,6 +361,7 @@ export declare const createSessionOp: (payload: {
358
361
  readonly cwd?: string | undefined;
359
362
  readonly cols: number;
360
363
  readonly rows: number;
364
+ readonly mode?: "pipe" | "pty" | undefined;
361
365
  readonly exitCode?: number | undefined;
362
366
  readonly exitSignal?: number | undefined;
363
367
  readonly errorMessage?: string | undefined;
@@ -378,6 +382,7 @@ export declare const getSessionOp: (sessionId: string, ownerUserId?: string | un
378
382
  readonly cwd?: string | undefined;
379
383
  readonly cols: number;
380
384
  readonly rows: number;
385
+ readonly mode?: "pipe" | "pty" | undefined;
381
386
  readonly exitCode?: number | undefined;
382
387
  readonly exitSignal?: number | undefined;
383
388
  readonly errorMessage?: string | undefined;
@@ -404,6 +409,7 @@ export declare const listSessionsOp: (query: {
404
409
  readonly cwd?: string | undefined;
405
410
  readonly cols: number;
406
411
  readonly rows: number;
412
+ readonly mode?: "pipe" | "pty" | undefined;
407
413
  readonly exitCode?: number | undefined;
408
414
  readonly exitSignal?: number | undefined;
409
415
  readonly errorMessage?: string | undefined;
@@ -459,6 +465,7 @@ export declare const closeSessionOp: (sessionId: string, payload: {
459
465
  readonly cwd?: string | undefined;
460
466
  readonly cols: number;
461
467
  readonly rows: number;
468
+ readonly mode?: "pipe" | "pty" | undefined;
462
469
  readonly exitCode?: number | undefined;
463
470
  readonly exitSignal?: number | undefined;
464
471
  readonly errorMessage?: string | undefined;
@@ -486,6 +493,75 @@ export declare const createAccessTokenOp: (payload: {
486
493
  readonly createdAt: string;
487
494
  readonly token: string;
488
495
  }, import("@sealant/api-contracts").AccessTokenBadRequestError | import("@sealant/api-contracts").AccessTokenInternalServerError | import("@sealant/api-contracts").AccessTokenNotFoundError | import("effect/unstable/http/HttpClientError").HttpClientError | import("effect/Schema").SchemaError, SealantApiClient>;
496
+ export declare const ensureUserOp: (payload: {
497
+ readonly email: string;
498
+ readonly name: string;
499
+ readonly userId?: string | undefined;
500
+ }) => Effect.Effect<{
501
+ readonly userId: string;
502
+ readonly email: string;
503
+ readonly name: string;
504
+ readonly createdAt: string;
505
+ readonly created: boolean;
506
+ }, import("effect/unstable/http/HttpClientError").HttpClientError | import("effect/Schema").SchemaError | import("@sealant/api-contracts").UserBadRequestError | import("@sealant/api-contracts").UserInternalServerError, SealantApiClient>;
507
+ export declare const getUserOp: (userId: string) => Effect.Effect<{
508
+ readonly userId: string;
509
+ readonly email: string;
510
+ readonly name: string;
511
+ readonly createdAt: string;
512
+ }, import("effect/unstable/http/HttpClientError").HttpClientError | import("effect/Schema").SchemaError | import("@sealant/api-contracts").UserInternalServerError | import("@sealant/api-contracts").UserNotFoundError, SealantApiClient>;
513
+ export declare const listConnectedAccountsOp: (ownerUserId: string) => Effect.Effect<{
514
+ readonly items: readonly {
515
+ readonly connectedAccountId: string;
516
+ readonly ownerUserId: string;
517
+ readonly provider: "claude" | "codex" | "github";
518
+ readonly name: string;
519
+ readonly kind: string;
520
+ readonly status: "active" | "archived" | "invalid";
521
+ readonly metadata: {
522
+ readonly [x: string]: unknown;
523
+ };
524
+ readonly connectedAt: string;
525
+ readonly updatedAt: string;
526
+ readonly lastUsedAt: string | null;
527
+ readonly lastSyncedAt: string | null;
528
+ }[];
529
+ }, import("@sealant/api-contracts").ConnectedAccountInternalServerError | import("effect/unstable/http/HttpClientError").HttpClientError | import("effect/Schema").SchemaError, SealantApiClient>;
530
+ export declare const createConnectedAccountOp: (payload: {
531
+ readonly ownerUserId: string;
532
+ readonly provider: "claude" | "codex" | "github";
533
+ readonly name?: string | undefined;
534
+ readonly secret: string;
535
+ }) => Effect.Effect<{
536
+ readonly connectedAccountId: string;
537
+ readonly ownerUserId: string;
538
+ readonly provider: "claude" | "codex" | "github";
539
+ readonly name: string;
540
+ readonly kind: string;
541
+ readonly status: "active" | "archived" | "invalid";
542
+ readonly metadata: {
543
+ readonly [x: string]: unknown;
544
+ };
545
+ readonly connectedAt: string;
546
+ readonly updatedAt: string;
547
+ readonly lastUsedAt: string | null;
548
+ readonly lastSyncedAt: string | null;
549
+ }, import("@sealant/api-contracts").ConnectedAccountBadRequestError | import("@sealant/api-contracts").ConnectedAccountConflictError | import("@sealant/api-contracts").ConnectedAccountInternalServerError | import("@sealant/api-contracts").ConnectedAccountNotFoundError | import("@sealant/api-contracts").ConnectedAccountServiceUnavailableError | import("effect/unstable/http/HttpClientError").HttpClientError | import("effect/Schema").SchemaError, SealantApiClient>;
550
+ export declare const archiveConnectedAccountOp: (connectedAccountId: string, ownerUserId: string) => Effect.Effect<{
551
+ readonly connectedAccountId: string;
552
+ readonly ownerUserId: string;
553
+ readonly provider: "claude" | "codex" | "github";
554
+ readonly name: string;
555
+ readonly kind: string;
556
+ readonly status: "active" | "archived" | "invalid";
557
+ readonly metadata: {
558
+ readonly [x: string]: unknown;
559
+ };
560
+ readonly connectedAt: string;
561
+ readonly updatedAt: string;
562
+ readonly lastUsedAt: string | null;
563
+ readonly lastSyncedAt: string | null;
564
+ }, import("@sealant/api-contracts").ConnectedAccountInternalServerError | import("@sealant/api-contracts").ConnectedAccountNotFoundError | import("effect/unstable/http/HttpClientError").HttpClientError | import("effect/Schema").SchemaError, SealantApiClient>;
489
565
  export declare const inferenceRespondOp: (payload: {
490
566
  readonly ownerUserId: string;
491
567
  readonly credentials?: {
@@ -5,7 +5,10 @@ export const createWorkspaceOp = (payload, idempotencyKey) => Effect.flatMap(Sea
5
5
  payload,
6
6
  headers: idempotencyKey === undefined ? {} : { "idempotency-key": idempotencyKey },
7
7
  }));
8
- export const getWorkspaceOp = (workspaceId) => Effect.flatMap(SealantApiClient, (client) => client.workspaces.getWorkspace({ params: { workspaceId } }));
8
+ export const getWorkspaceOp = (workspaceId, ownerUserId) => Effect.flatMap(SealantApiClient, (client) => client.workspaces.getWorkspace({
9
+ params: { workspaceId },
10
+ query: ownerUserId === undefined ? {} : { ownerUserId },
11
+ }));
9
12
  export const listWorkspacesOp = (query) => Effect.flatMap(SealantApiClient, (client) => client.workspaces.listWorkspaces({ query }));
10
13
  export const execWorkspaceOp = (workspaceId, payload) => Effect.flatMap(SealantApiClient, (client) => client.workspaces.execWorkspace({ params: { workspaceId }, payload }));
11
14
  export const stopWorkspaceOp = (workspaceId, payload) => Effect.flatMap(SealantApiClient, (client) => client.workspaces.stopWorkspace({ params: { workspaceId }, payload }));
@@ -13,13 +16,14 @@ export const restartWorkspaceOp = (workspaceId, payload) => Effect.flatMap(Seala
13
16
  export const expireWorkspaceOp = (workspaceId, payload) => Effect.flatMap(SealantApiClient, (client) => client.workspaces.expireWorkspace({ params: { workspaceId }, payload }));
14
17
  // ---- runs ----
15
18
  export const createRunOp = (payload) => Effect.flatMap(SealantApiClient, (client) => client.runs.createRun({ payload }));
16
- export const getRunOp = (runId) => Effect.flatMap(SealantApiClient, (client) => client.runs.getRun({ params: { runId } }));
19
+ const ownerQuery = (ownerUserId) => ownerUserId === undefined ? {} : { ownerUserId };
20
+ export const getRunOp = (runId, ownerUserId) => Effect.flatMap(SealantApiClient, (client) => client.runs.getRun({ params: { runId }, query: ownerQuery(ownerUserId) }));
17
21
  export const listRunsOp = (query) => Effect.flatMap(SealantApiClient, (client) => client.runs.listRuns({ query }));
18
22
  export const updateRunOp = (runId, payload) => Effect.flatMap(SealantApiClient, (client) => client.runs.updateRun({ params: { runId }, payload }));
19
23
  export const getRunTimelineOp = (runId, query) => Effect.flatMap(SealantApiClient, (client) => Effect.map(client.runs.getRunTimeline({ params: { runId }, query }), (r) => r.items));
20
24
  export const getRunScrollbackOp = (runId, query) => Effect.flatMap(SealantApiClient, (client) => client.runs.getRunScrollback({ params: { runId }, query }));
21
- export const getRunLossOp = (runId) => Effect.flatMap(SealantApiClient, (client) => client.runs.getRunLoss({ params: { runId } }));
22
- export const getRunChangesOp = (runId) => Effect.flatMap(SealantApiClient, (client) => client.runs.getRunChanges({ params: { runId } }));
25
+ export const getRunLossOp = (runId, ownerUserId) => Effect.flatMap(SealantApiClient, (client) => client.runs.getRunLoss({ params: { runId }, query: ownerQuery(ownerUserId) }));
26
+ export const getRunChangesOp = (runId, ownerUserId) => Effect.flatMap(SealantApiClient, (client) => client.runs.getRunChanges({ params: { runId }, query: ownerQuery(ownerUserId) }));
23
27
  // ---- sessions ----
24
28
  export const createSessionOp = (payload) => Effect.flatMap(SealantApiClient, (client) => client.sessions.createSession({ payload, headers: {} }));
25
29
  export const getSessionOp = (sessionId, ownerUserId) => Effect.flatMap(SealantApiClient, (client) => client.sessions.getSession({
@@ -35,5 +39,15 @@ export const signalSessionOp = (sessionId, payload) => Effect.flatMap(SealantApi
35
39
  export const closeSessionOp = (sessionId, payload) => Effect.flatMap(SealantApiClient, (client) => client.sessions.closeSession({ params: { sessionId }, headers: {}, payload }));
36
40
  // ---- access tokens ----
37
41
  export const createAccessTokenOp = (payload) => Effect.flatMap(SealantApiClient, (client) => client.accessTokens.createAccessToken({ payload }));
42
+ // ---- users ----
43
+ export const ensureUserOp = (payload) => Effect.flatMap(SealantApiClient, (client) => client.users.ensureUser({ payload }));
44
+ export const getUserOp = (userId) => Effect.flatMap(SealantApiClient, (client) => client.users.getUser({ params: { userId } }));
45
+ // ---- connected accounts ----
46
+ export const listConnectedAccountsOp = (ownerUserId) => Effect.flatMap(SealantApiClient, (client) => client.connectedAccounts.listConnectedAccounts({ query: { ownerUserId } }));
47
+ export const createConnectedAccountOp = (payload) => Effect.flatMap(SealantApiClient, (client) => client.connectedAccounts.createConnectedAccount({ payload }));
48
+ export const archiveConnectedAccountOp = (connectedAccountId, ownerUserId) => Effect.flatMap(SealantApiClient, (client) => client.connectedAccounts.archiveConnectedAccount({
49
+ params: { connectedAccountId },
50
+ query: { ownerUserId },
51
+ }));
38
52
  // ---- inference ----
39
53
  export const inferenceRespondOp = (payload) => Effect.flatMap(SealantApiClient, (client) => client.inference.respond({ payload }));
@@ -38,7 +38,7 @@ const createHarnessRunEffect = (ctx, init, prompt, options) => Effect.gen(functi
38
38
  ...metadata,
39
39
  });
40
40
  }
41
- const details = yield* getWorkspaceOp(init.id);
41
+ const details = yield* getWorkspaceOp(init.id, ctx.config.hostLocal.ownerUserId);
42
42
  const spec = details.spec;
43
43
  const harnessId = spec?.harness?.id;
44
44
  if (harnessId === undefined) {
@@ -66,10 +66,10 @@ const runHarnessEffect = (ctx, init, prompt, options) => Effect.gen(function* ()
66
66
  }));
67
67
  }
68
68
  yield* Effect.sleep(POLL_INTERVAL);
69
- wire = yield* getRunOp(runId);
69
+ wire = yield* getRunOp(runId, ctx.config.hostLocal.ownerUserId);
70
70
  }
71
71
  // Read the changes the run produced (captured server-side).
72
- const changes = toRunChangesData(yield* getRunChangesOp(runId));
72
+ const changes = toRunChangesData(yield* getRunChangesOp(runId, ctx.config.hostLocal.ownerUserId));
73
73
  return makeRun(ctx, { wire, changes });
74
74
  });
75
75
  /** The BLOCKING `harness.run()` implementation, registered into the Workspace facade by the client. */
@@ -135,7 +135,10 @@ export const renderTranscript = (commands) => {
135
135
  return `${blocks.join("\n\n")}\n`;
136
136
  };
137
137
  export const makeRunRecord = (ctx, runId) => {
138
- const fetchTimeline = (from) => ctx.runtime.run(getRunTimelineOp(runId, from === undefined ? {} : { fromSequence: from.toString() }));
138
+ const fetchTimeline = (from) => ctx.runtime.run(getRunTimelineOp(runId, {
139
+ ownerUserId: ctx.config.hostLocal.ownerUserId,
140
+ ...(from === undefined ? {} : { fromSequence: from.toString() }),
141
+ }));
139
142
  return {
140
143
  runId,
141
144
  replay: async (options) => {
@@ -171,7 +174,10 @@ export const makeRunRecord = (ctx, runId) => {
171
174
  let from = options?.from;
172
175
  const deadline = Date.now() + STREAM_TIMEOUT_MS;
173
176
  for (;;) {
174
- const wire = await ctxRun.run(getRunTimelineOp(runId, from === undefined ? {} : { fromSequence: from.toString() }));
177
+ const wire = await ctxRun.run(getRunTimelineOp(runId, {
178
+ ownerUserId: ctx.config.hostLocal.ownerUserId,
179
+ ...(from === undefined ? {} : { fromSequence: from.toString() }),
180
+ }));
175
181
  for (const entry of wire) {
176
182
  const mapped = toTimelineEntry(entry);
177
183
  yield mapped;
@@ -179,9 +185,12 @@ export const makeRunRecord = (ctx, runId) => {
179
185
  }
180
186
  // Stop once the run is terminal — with one final drain to catch entries written between the
181
187
  // last timeline fetch and the status check.
182
- const run = await ctxRun.run(getRunOp(runId));
188
+ const run = await ctxRun.run(getRunOp(runId, ctx.config.hostLocal.ownerUserId));
183
189
  if (TERMINAL_RUN_STATUSES.has(run.status)) {
184
- const tail = await ctxRun.run(getRunTimelineOp(runId, from === undefined ? {} : { fromSequence: from.toString() }));
190
+ const tail = await ctxRun.run(getRunTimelineOp(runId, {
191
+ ownerUserId: ctx.config.hostLocal.ownerUserId,
192
+ ...(from === undefined ? {} : { fromSequence: from.toString() }),
193
+ }));
185
194
  for (const entry of tail) {
186
195
  yield toTimelineEntry(entry);
187
196
  }
@@ -208,7 +217,11 @@ export const makeRunRecord = (ctx, runId) => {
208
217
  scrollback: (processId, stream) => {
209
218
  const run = ctx.runtime;
210
219
  async function* iterate() {
211
- const response = await run.run(getRunScrollbackOp(runId, { processId, stream }));
220
+ const response = await run.run(getRunScrollbackOp(runId, {
221
+ ownerUserId: ctx.config.hostLocal.ownerUserId,
222
+ processId,
223
+ stream,
224
+ }));
212
225
  const bytes = Buffer.from(response.contentBase64, "base64");
213
226
  if (bytes.byteLength > 0) {
214
227
  yield new Uint8Array(bytes.buffer, bytes.byteOffset, bytes.byteLength);
@@ -216,9 +229,9 @@ export const makeRunRecord = (ctx, runId) => {
216
229
  }
217
230
  return iterate();
218
231
  },
219
- loss: async () => toLossReport(await ctx.runtime.run(getRunLossOp(runId))),
232
+ loss: async () => toLossReport(await ctx.runtime.run(getRunLossOp(runId, ctx.config.hostLocal.ownerUserId))),
220
233
  summary: async () => {
221
- const run = await ctx.runtime.run(getRunOp(runId));
234
+ const run = await ctx.runtime.run(getRunOp(runId, ctx.config.hostLocal.ownerUserId));
222
235
  const timeline = await fetchTimeline();
223
236
  const durationMs = run.startedAt !== undefined && run.finishedAt !== undefined
224
237
  ? new Date(run.finishedAt).getTime() - new Date(run.startedAt).getTime()
@@ -46,11 +46,12 @@ export const makeRun = (ctx, init) => {
46
46
  });
47
47
  }
48
48
  await delay(WAIT_POLL_INTERVAL_MS);
49
- current = await ctx.runtime.run(getRunOp(runId));
49
+ current = await ctx.runtime.run(getRunOp(runId, ctx.config.hostLocal.ownerUserId));
50
50
  }
51
51
  // Settle the changes: a handle from `harness.start()` or `runs.get()` has none captured yet,
52
52
  // so read the server-side capture now that the run is terminal.
53
- const settledChanges = changesData ?? toRunChangesData(await ctx.runtime.run(getRunChangesOp(runId)));
53
+ const settledChanges = changesData ??
54
+ toRunChangesData(await ctx.runtime.run(getRunChangesOp(runId, ctx.config.hostLocal.ownerUserId)));
54
55
  return makeRun(ctx, { wire: current, changes: settledChanges });
55
56
  },
56
57
  };
@@ -10,6 +10,7 @@ export declare const makeInteractiveSession: (ctx: SdkContext, wire: {
10
10
  readonly cwd?: string | undefined;
11
11
  readonly cols: number;
12
12
  readonly rows: number;
13
+ readonly mode?: "pipe" | "pty" | undefined;
13
14
  readonly exitCode?: number | undefined;
14
15
  readonly exitSignal?: number | undefined;
15
16
  readonly errorMessage?: string | undefined;
@@ -202,6 +202,7 @@ export const makeInteractiveSession = (ctx, wire) => {
202
202
  id: sessionId,
203
203
  workspaceId: wire.workspaceId,
204
204
  runId: wire.runId,
205
+ mode: wire.mode ?? "pty",
205
206
  send: async (input) => {
206
207
  const bytes = typeof input === "string" ? new TextEncoder().encode(input) : input;
207
208
  await ctx.runtime.run(sendSessionInputOp(sessionId, {
@@ -34,6 +34,7 @@ export const makeWorkspace = (ctx, init) => {
34
34
  ...(options?.cols === undefined ? {} : { cols: options.cols }),
35
35
  ...(options?.rows === undefined ? {} : { rows: options.rows }),
36
36
  ...(options?.term === undefined ? {} : { term: options.term }),
37
+ ...(options?.mode === undefined ? {} : { mode: options.mode }),
37
38
  ...(options?.metadata === undefined ? {} : { metadata: { ...options.metadata } }),
38
39
  }));
39
40
  return makeInteractiveSession(ctx, created);
@@ -62,7 +63,7 @@ export const makeWorkspace = (ctx, init) => {
62
63
  if (init.harness !== undefined) {
63
64
  return [init.harness.launchCommand ?? init.harness.id];
64
65
  }
65
- const details = await ctx.runtime.run(getWorkspaceOp(init.id));
66
+ const details = await ctx.runtime.run(getWorkspaceOp(init.id, ctx.config.hostLocal.ownerUserId));
66
67
  const spec = details.spec;
67
68
  const harnessId = spec?.harness?.id;
68
69
  if (harnessId === undefined) {
@@ -92,13 +93,13 @@ export const makeWorkspace = (ctx, init) => {
92
93
  id: init.id,
93
94
  name: init.name,
94
95
  status: async () => {
95
- const details = await ctx.runtime.run(getWorkspaceOp(init.id));
96
+ const details = await ctx.runtime.run(getWorkspaceOp(init.id, ctx.config.hostLocal.ownerUserId));
96
97
  return details.status;
97
98
  },
98
99
  ready: async () => {
99
100
  const deadline = Date.now() + READY_TIMEOUT_MS;
100
101
  for (;;) {
101
- const details = await ctx.runtime.run(getWorkspaceOp(init.id));
102
+ const details = await ctx.runtime.run(getWorkspaceOp(init.id, ctx.config.hostLocal.ownerUserId));
102
103
  // Gate on the coarse "ready" status, which the control plane now emits ONLY after the
103
104
  // in-workspace daemon's control socket is accepting (readiness probe in the launch path).
104
105
  // This is honest: when ready() resolves, harness.run() can connect without racing the socket.
@@ -127,7 +128,7 @@ export const makeWorkspace = (ctx, init) => {
127
128
  let lastStatus;
128
129
  const deadline = Date.now() + READY_TIMEOUT_MS;
129
130
  for (;;) {
130
- const details = await ctxRun.run(getWorkspaceOp(init.id));
131
+ const details = await ctxRun.run(getWorkspaceOp(init.id, ctx.config.hostLocal.ownerUserId));
131
132
  if (details.status !== lastStatus) {
132
133
  lastStatus = details.status;
133
134
  yield {
@@ -155,7 +156,7 @@ export const makeWorkspace = (ctx, init) => {
155
156
  await ctx.runtime.run(stopWorkspaceOp(init.id, { ownerUserId }));
156
157
  const deadline = Date.now() + STOP_TIMEOUT_MS;
157
158
  for (;;) {
158
- const details = await ctx.runtime.run(getWorkspaceOp(init.id));
159
+ const details = await ctx.runtime.run(getWorkspaceOp(init.id, ctx.config.hostLocal.ownerUserId));
159
160
  if (details.status === "stopped") {
160
161
  return;
161
162
  }
@@ -3,9 +3,9 @@
3
3
  *
4
4
  * The PUBLIC surface (`SealantConfig` in `../types.ts`) is intentionally minimal: `{ baseUrl, apiKey }`.
5
5
  * The SDK is now a thin HTTP client (run execution + telemetry moved server-side), so the only
6
- * host-local concerns left are a pre-auth owner principal and the registry id used on create/run
7
- * payloads. These live HERE resolved from the environment with docker-compose defaults so they
8
- * never leak into the published `SealantConfig`, and they disappear entirely once auth lands.
6
+ * host-local concerns left are the owner principal and the registry id used on create/run
7
+ * payloads. The owner comes from `SealantConfig.ownerUserId` when a product acts on behalf of a
8
+ * specific user (one client per user), else from the environment with docker-compose defaults.
9
9
  */
10
10
  import type { SealantConfig } from "../types.js";
11
11
  export interface SealantHostLocalConfig {
@@ -11,7 +11,7 @@ export const resolveInternalConfig = (config) => ({
11
11
  apiKey: config.apiKey,
12
12
  fetch: config.fetch,
13
13
  hostLocal: {
14
- ownerUserId: env("SEALANT_OWNER_USER_ID") ?? DEFAULT_OWNER_USER_ID,
14
+ ownerUserId: config.ownerUserId ?? env("SEALANT_OWNER_USER_ID") ?? DEFAULT_OWNER_USER_ID,
15
15
  registryId: env("SEALANT_REGISTRY_ID") ?? DEFAULT_REGISTRY_ID,
16
16
  },
17
17
  });
package/dist/types.d.ts CHANGED
@@ -23,8 +23,18 @@
23
23
  export interface SealantConfig {
24
24
  /** Base URL of the Sealant control-plane API (e.g. `http://localhost:8080`). */
25
25
  readonly baseUrl: string;
26
- /** Bearer token for authenticated deployments. Optional for a localhost demo with no auth. */
26
+ /**
27
+ * Bearer secret for authenticated deployments: a SERVICE KEY (`SEALANT_SERVICE_KEYS` on the
28
+ * control plane — lets this client act on behalf of any `ownerUserId`) or a scoped user access
29
+ * token (session surface only). Optional for a localhost demo with no auth.
30
+ */
27
31
  readonly apiKey?: string;
32
+ /**
33
+ * The user every call is attributed to. A product that owns its own login builds ONE client per
34
+ * user with that user's Sealant id (see `users.ensure`). Defaults to `SEALANT_OWNER_USER_ID`,
35
+ * then `usr_local`.
36
+ */
37
+ readonly ownerUserId?: string;
28
38
  /** Override the `fetch` implementation (tests, custom agents, proxies). */
29
39
  readonly fetch?: typeof fetch;
30
40
  }
@@ -367,14 +377,24 @@ export interface RunOptions {
367
377
  readonly metadata?: Readonly<Record<string, unknown>>;
368
378
  }
369
379
  /** Options for opening an interactive PTY session. */
380
+ /**
381
+ * How a session's leader is wired. `pty` (default) allocates a pseudoterminal — interactive
382
+ * shells and TUIs. `pipe` gives the leader plain stdio pipes and no tty — the shape for processes
383
+ * that speak a byte protocol over stdin/stdout (JSON-RPC / NDJSON servers such as
384
+ * `codex app-server`): `send` feeds stdin, `output`/`attach` carry stdout byte-exact, stderr is
385
+ * recorded as diagnostics only, and `resize` is rejected.
386
+ */
387
+ export type SessionMode = "pty" | "pipe";
370
388
  export interface SessionOptions {
371
389
  /** Working directory inside the workspace (defaults to the repository root). */
372
390
  readonly cwd?: string;
373
- /** Extra environment for the PTY process (not for secrets — use `credentials`). */
391
+ /** Extra environment for the session process (not for secrets — use `credentials`). */
374
392
  readonly env?: Readonly<Record<string, string>>;
375
393
  readonly cols?: number;
376
394
  readonly rows?: number;
377
395
  readonly term?: string;
396
+ /** Leader wiring; defaults to `pty`. `cols`/`rows`/`term` are ignored for `pipe`. */
397
+ readonly mode?: SessionMode;
378
398
  /** Opaque correlation bag, stored verbatim and echoed on reads. */
379
399
  readonly metadata?: Readonly<Record<string, unknown>>;
380
400
  }
@@ -705,7 +725,9 @@ export interface InteractiveSession {
705
725
  readonly workspaceId: string;
706
726
  /** The run recording this session — its record is the durable, replayable evidence. */
707
727
  readonly runId: string;
708
- /** Send keystrokes. Strings are UTF-8-encoded; bytes pass through untouched. */
728
+ /** Leader wiring: a pseudoterminal or plain stdio pipes. */
729
+ readonly mode: SessionMode;
730
+ /** Send input: keystrokes to a PTY, bytes to a pipe leader's stdin. Strings are UTF-8-encoded. */
709
731
  send(input: string | Uint8Array): Promise<void>;
710
732
  /**
711
733
  * Byte-exact output as a RESUMABLE stream: recorded history from `from` (inclusive; default the
@@ -716,7 +738,7 @@ export interface InteractiveSession {
716
738
  readonly from?: bigint;
717
739
  readonly signal?: AbortSignal;
718
740
  }): AsyncIterable<SessionOutputChunk>;
719
- /** Resize the PTY. */
741
+ /** Resize the PTY. Rejected for `pipe` sessions, which have no terminal. */
720
742
  resize(cols: number, rows: number): Promise<void>;
721
743
  /** Deliver a POSIX signal to the session's process (e.g. 2 = SIGINT). */
722
744
  signal(signal: number): Promise<void>;
@@ -881,3 +903,65 @@ export interface InferenceContinueOptions {
881
903
  export interface InferenceNamespace {
882
904
  respond(options: InferenceRespondOptions | InferenceContinueOptions): Promise<InferenceResponse>;
883
905
  }
906
+ export interface SealantUser {
907
+ readonly userId: string;
908
+ readonly email: string;
909
+ readonly name: string;
910
+ readonly createdAt: string;
911
+ }
912
+ export interface EnsureUserOptions {
913
+ readonly email: string;
914
+ readonly name: string;
915
+ /** Caller-chosen id for a NEW user; ignored when the email already exists. */
916
+ readonly userId?: string;
917
+ }
918
+ export interface EnsuredUser extends SealantUser {
919
+ /** True when this call created the user. */
920
+ readonly created: boolean;
921
+ }
922
+ /**
923
+ * Identity rows for products that own their own login. `ensure` is idempotent on email: call it on
924
+ * every sign-in and build the per-user client with the returned `userId` as `ownerUserId`.
925
+ */
926
+ export interface UsersNamespace {
927
+ ensure(options: EnsureUserOptions): Promise<EnsuredUser>;
928
+ get(userId: string): Promise<SealantUser>;
929
+ }
930
+ export type ConnectedAccountProvider = "claude" | "codex" | "github";
931
+ export type ConnectedAccountStatus = "active" | "invalid" | "archived";
932
+ /** A connected account as every surface sees it — NEVER carries secret material. */
933
+ export interface ConnectedAccount {
934
+ readonly connectedAccountId: string;
935
+ readonly ownerUserId: string;
936
+ readonly provider: ConnectedAccountProvider;
937
+ readonly name: string;
938
+ /** Provider-shaped payload kind: oauth-token | credentials-json | auth-json | gh-cli-token. */
939
+ readonly kind: string;
940
+ readonly status: ConnectedAccountStatus;
941
+ /** Non-secret display data (token suffix, codex account email, github login + scopes). */
942
+ readonly metadata: Readonly<Record<string, unknown>>;
943
+ readonly connectedAt: string;
944
+ readonly updatedAt: string;
945
+ readonly lastUsedAt: string | null;
946
+ readonly lastSyncedAt: string | null;
947
+ }
948
+ export interface ConnectConnectedAccountOptions {
949
+ readonly provider: ConnectedAccountProvider;
950
+ /**
951
+ * Provider-shaped plaintext, passed through and sealed server-side: a Claude setup token or
952
+ * verbatim `.credentials.json`, verbatim Codex `auth.json`, or a GitHub token. Never logged.
953
+ */
954
+ readonly secret: string;
955
+ /** Account name under the provider; defaults to `default` (the one `credentials: { x: true }` picks). */
956
+ readonly name?: string;
957
+ }
958
+ /**
959
+ * The owner's connected provider accounts. `connect` upserts on (provider, name), so reconnecting
960
+ * swaps the sealed credential in place. Secrets flow one way — in; no call returns them.
961
+ */
962
+ export interface ConnectedAccountsNamespace {
963
+ list(): Promise<readonly ConnectedAccount[]>;
964
+ connect(options: ConnectConnectedAccountOptions): Promise<ConnectedAccount>;
965
+ /** Soft-archives the account; uniform not-found for "does not exist" and "not yours". */
966
+ disconnect(connectedAccountId: string): Promise<ConnectedAccount>;
967
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sealant/sdk",
3
- "version": "0.20.2",
3
+ "version": "0.22.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.20.2"
29
+ "@sealant/api-contracts": "^0.22.0"
30
30
  },
31
31
  "devDependencies": {
32
32
  "@effect/vitest": "4.0.0-beta.85",