@sealant/api-contracts-next 0.39.0-next.696 → 0.39.0-next.700

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.
@@ -855,7 +855,52 @@ export declare const ControlPlaneAPI: HttpApi.HttpApi<"sealantControlPlaneApi",
855
855
  readonly email: import("effect/Schema").String;
856
856
  readonly name: import("effect/Schema").String;
857
857
  readonly createdAt: import("effect/Schema").String;
858
- }>>, import("effect/unstable/httpapi/HttpApiEndpoint").Json<typeof import("./users.js").UserNotFoundError | typeof import("./users.js").UserInternalServerError>, never, never>, false> | import("effect/unstable/httpapi/HttpApiGroup").HttpApiGroup<"workspaces", import("effect/unstable/httpapi/HttpApiEndpoint").HttpApiEndpoint<"bindWorkspace", "POST", "/v1/workspaces/:workspaceId/bind", import("effect/unstable/httpapi/HttpApiEndpoint").StringTree<import("effect/Schema").Struct<{
858
+ }>>, import("effect/unstable/httpapi/HttpApiEndpoint").Json<typeof import("./users.js").UserNotFoundError | typeof import("./users.js").UserInternalServerError>, never, never>, false> | import("effect/unstable/httpapi/HttpApiGroup").HttpApiGroup<"workspaces", import("effect/unstable/httpapi/HttpApiEndpoint").HttpApiEndpoint<"applyWorkspaceDotfiles", "POST", "/v1/workspaces/:workspaceId/dotfiles", import("effect/unstable/httpapi/HttpApiEndpoint").StringTree<import("effect/Schema").Struct<{
859
+ readonly workspaceId: import("effect/Schema").String;
860
+ }>>, import("effect/unstable/httpapi/HttpApiEndpoint").StringTree<never>, import("effect/unstable/httpapi/HttpApiEndpoint").Json<import("effect/Schema").Struct<{
861
+ readonly ownerUserId: import("effect/Schema").String;
862
+ readonly onBehalfOfUserId: import("effect/Schema").String;
863
+ readonly user: import("effect/Schema").String;
864
+ readonly home: import("effect/Schema").String;
865
+ readonly repository: import("effect/Schema").optional<import("effect/Schema").Struct<{
866
+ readonly url: import("effect/Schema").String;
867
+ readonly ref: import("effect/Schema").optional<import("effect/Schema").String>;
868
+ readonly manager: import("effect/Schema").optional<import("effect/Schema").Literals<readonly ["auto", "chezmoi", "stow", "copy"]>>;
869
+ readonly bootstrap: import("effect/Schema").optional<import("effect/Schema").Boolean>;
870
+ readonly bootstrapCommand: import("effect/Schema").optional<import("effect/Schema").String>;
871
+ }>>;
872
+ readonly archives: import("effect/Schema").optional<import("effect/Schema").$Array<import("effect/Schema").Struct<{
873
+ readonly data: import("effect/Schema").String;
874
+ readonly manager: import("effect/Schema").optional<import("effect/Schema").Literals<readonly ["auto", "chezmoi", "stow", "copy"]>>;
875
+ readonly target: import("effect/Schema").optional<import("effect/Schema").Literals<readonly ["home", "config"]>>;
876
+ readonly bootstrap: import("effect/Schema").optional<import("effect/Schema").Boolean>;
877
+ readonly bootstrapCommand: import("effect/Schema").optional<import("effect/Schema").String>;
878
+ }>>>;
879
+ }>>, import("effect/unstable/httpapi/HttpApiEndpoint").StringTree<never>, import("effect/unstable/httpapi/HttpApiEndpoint").Json<import("effect/Schema").Struct<{
880
+ readonly runId: import("effect/Schema").String;
881
+ readonly workspaceId: import("effect/Schema").String;
882
+ readonly attemptId: import("effect/Schema").optional<import("effect/Schema").String>;
883
+ readonly ownerUserId: import("effect/Schema").String;
884
+ readonly harnessId: import("effect/Schema").String;
885
+ readonly mode: import("effect/Schema").Literals<readonly ["one-shot", "interactive"]>;
886
+ readonly status: import("effect/Schema").Literals<readonly ["queued", "running", "completed", "failed", "cancelled"]>;
887
+ readonly prompt: import("effect/Schema").optional<import("effect/Schema").String>;
888
+ readonly command: import("effect/Schema").optional<import("effect/Schema").Struct<{
889
+ readonly executable: import("effect/Schema").String;
890
+ readonly args: import("effect/Schema").$Array<import("effect/Schema").String>;
891
+ readonly cwd: import("effect/Schema").optional<import("effect/Schema").String>;
892
+ readonly argCount: import("effect/Schema").optional<import("effect/Schema").Number>;
893
+ readonly argLengths: import("effect/Schema").optional<import("effect/Schema").$Array<import("effect/Schema").Number>>;
894
+ }>>;
895
+ readonly metadata: import("effect/Schema").optional<import("effect/Schema").$Record<import("effect/Schema").String, import("effect/Schema").Unknown>>;
896
+ readonly exitCode: import("effect/Schema").optional<import("effect/Schema").Number>;
897
+ readonly errorMessage: import("effect/Schema").optional<import("effect/Schema").String>;
898
+ readonly startedAt: import("effect/Schema").optional<import("effect/Schema").String>;
899
+ readonly finishedAt: import("effect/Schema").optional<import("effect/Schema").String>;
900
+ readonly recordDeletedAt: import("effect/Schema").optional<import("effect/Schema").String>;
901
+ readonly createdAt: import("effect/Schema").String;
902
+ readonly updatedAt: import("effect/Schema").String;
903
+ }>>, import("effect/unstable/httpapi/HttpApiEndpoint").Json<typeof import("./workspaces.js").WorkspaceBadRequestError | typeof import("./workspaces.js").WorkspaceForbiddenError | typeof import("./workspaces.js").WorkspaceNotFoundError | typeof import("./workspaces.js").WorkspaceConflictError | typeof import("./workspaces.js").WorkspaceBadGatewayError | typeof import("./workspaces.js").WorkspaceServiceUnavailableError | typeof import("./workspaces.js").WorkspaceInternalServerError>, never, never> | import("effect/unstable/httpapi/HttpApiEndpoint").HttpApiEndpoint<"bindWorkspace", "POST", "/v1/workspaces/:workspaceId/bind", import("effect/unstable/httpapi/HttpApiEndpoint").StringTree<import("effect/Schema").Struct<{
859
904
  readonly workspaceId: import("effect/Schema").String;
860
905
  }>>, import("effect/unstable/httpapi/HttpApiEndpoint").StringTree<never>, import("effect/unstable/httpapi/HttpApiEndpoint").Json<import("effect/Schema").Struct<{
861
906
  readonly ownerUserId: import("effect/Schema").String;
@@ -1286,6 +1331,14 @@ export declare const ControlPlaneAPI: HttpApi.HttpApi<"sealantControlPlaneApi",
1286
1331
  readonly connectedAccountId: import("effect/Schema").String;
1287
1332
  readonly name: import("effect/Schema").String;
1288
1333
  }>>;
1334
+ readonly pi: import("effect/Schema").optional<import("effect/Schema").Struct<{
1335
+ readonly connectedAccountId: import("effect/Schema").String;
1336
+ readonly name: import("effect/Schema").String;
1337
+ }>>;
1338
+ readonly opencode: import("effect/Schema").optional<import("effect/Schema").Struct<{
1339
+ readonly connectedAccountId: import("effect/Schema").String;
1340
+ readonly name: import("effect/Schema").String;
1341
+ }>>;
1289
1342
  }>;
1290
1343
  }>>;
1291
1344
  }>>, import("effect/unstable/httpapi/HttpApiEndpoint").Json<typeof import("./workspaces.js").WorkspaceForbiddenError | typeof import("./workspaces.js").WorkspaceNotFoundError | typeof import("./workspaces.js").WorkspaceConflictError | typeof import("./workspaces.js").WorkspaceInternalServerError>, never, never> | import("effect/unstable/httpapi/HttpApiEndpoint").HttpApiEndpoint<"listWorkspaceEvents", "GET", "/v1/workspaces/:workspaceId/events", import("effect/unstable/httpapi/HttpApiEndpoint").StringTree<import("effect/Schema").Struct<{
@@ -1360,6 +1413,9 @@ export declare const ControlPlaneAPI: HttpApi.HttpApi<"sealantControlPlaneApi",
1360
1413
  readonly claude: import("effect/Schema").optional<import("effect/Schema").NullOr<import("effect/Schema").String>>;
1361
1414
  readonly codex: import("effect/Schema").optional<import("effect/Schema").NullOr<import("effect/Schema").String>>;
1362
1415
  readonly github: import("effect/Schema").optional<import("effect/Schema").NullOr<import("effect/Schema").String>>;
1416
+ readonly pi: import("effect/Schema").optional<import("effect/Schema").NullOr<import("effect/Schema").String>>;
1417
+ readonly opencode: import("effect/Schema").optional<import("effect/Schema").NullOr<import("effect/Schema").String>>;
1418
+ readonly partial: import("effect/Schema").optional<import("effect/Schema").Boolean>;
1363
1419
  }>>, import("effect/unstable/httpapi/HttpApiEndpoint").StringTree<never>, import("effect/unstable/httpapi/HttpApiEndpoint").Json<import("effect/Schema").Struct<{
1364
1420
  readonly workspaceId: import("effect/Schema").String;
1365
1421
  readonly runId: import("effect/Schema").String;
@@ -1379,8 +1435,21 @@ export declare const ControlPlaneAPI: HttpApi.HttpApi<"sealantControlPlaneApi",
1379
1435
  readonly connectedAccountId: import("effect/Schema").String;
1380
1436
  readonly name: import("effect/Schema").String;
1381
1437
  }>>;
1438
+ readonly pi: import("effect/Schema").optional<import("effect/Schema").Struct<{
1439
+ readonly connectedAccountId: import("effect/Schema").String;
1440
+ readonly name: import("effect/Schema").String;
1441
+ }>>;
1442
+ readonly opencode: import("effect/Schema").optional<import("effect/Schema").Struct<{
1443
+ readonly connectedAccountId: import("effect/Schema").String;
1444
+ readonly name: import("effect/Schema").String;
1445
+ }>>;
1382
1446
  }>;
1383
1447
  }>;
1448
+ readonly skipped: import("effect/Schema").optional<import("effect/Schema").$Array<import("effect/Schema").Struct<{
1449
+ readonly provider: import("effect/Schema").Literals<readonly ["claude", "codex", "github", "pi", "opencode"]>;
1450
+ readonly code: import("effect/Schema").Literals<readonly ["connected-account-missing", "connected-account-invalid", "connected-account-unsupported", "login-file-unusable"]>;
1451
+ readonly message: import("effect/Schema").String;
1452
+ }>>>;
1384
1453
  }>>, import("effect/unstable/httpapi/HttpApiEndpoint").Json<typeof import("./workspaces.js").WorkspaceBadRequestError | typeof import("./workspaces.js").WorkspaceForbiddenError | typeof import("./workspaces.js").WorkspaceNotFoundError | typeof import("./workspaces.js").WorkspaceConflictError | typeof import("./workspaces.js").WorkspaceBadGatewayError | typeof import("./workspaces.js").WorkspaceServiceUnavailableError | typeof import("./workspaces.js").WorkspaceInternalServerError>, never, never> | import("effect/unstable/httpapi/HttpApiEndpoint").HttpApiEndpoint<"recoverWorkspace", "POST", "/v1/workspaces/:workspaceId/recover", import("effect/unstable/httpapi/HttpApiEndpoint").StringTree<import("effect/Schema").Struct<{
1385
1454
  readonly workspaceId: import("effect/Schema").String;
1386
1455
  }>>, import("effect/unstable/httpapi/HttpApiEndpoint").StringTree<never>, import("effect/unstable/httpapi/HttpApiEndpoint").Json<import("effect/Schema").Struct<{
@@ -186,6 +186,95 @@ export declare const execWorkspaceRequestSchema: Schema.Struct<{
186
186
  readonly user: Schema.optional<Schema.String>;
187
187
  }>;
188
188
  export type ExecWorkspaceRequest = typeof execWorkspaceRequestSchema.Type;
189
+ /** The `harnessId` stamped on runs that apply a person's dotfiles (`applyWorkspaceDotfiles`). */
190
+ export declare const dotfilesRunHarnessId = "dotfiles";
191
+ /** How a dotfiles tree is applied: chezmoi and stow layouts are detected by `auto`. */
192
+ export declare const workspaceDotfilesManagerSchema: Schema.Literals<readonly ["auto", "chezmoi", "stow", "copy"]>;
193
+ /**
194
+ * A dotfiles repository cloned as the person (no credential reaches the workspace: a repository
195
+ * only the person's own identity can reach is resolved by the caller and sent as an archive).
196
+ */
197
+ export declare const applyWorkspaceDotfilesRepositorySchema: Schema.Struct<{
198
+ /** An `https://` clone URL with no credential in it (no `user:token@`). */
199
+ readonly url: Schema.String;
200
+ /** Branch or tag; omitted clones the remote's default branch. */
201
+ readonly ref: Schema.optional<Schema.String>;
202
+ readonly manager: Schema.optional<Schema.Literals<readonly ["auto", "chezmoi", "stow", "copy"]>>;
203
+ /** Run the bootstrap command after applying (when the checkout has it). Default true. */
204
+ readonly bootstrap: Schema.optional<Schema.Boolean>;
205
+ /** Relative to the checkout; default `./install.sh`. */
206
+ readonly bootstrapCommand: Schema.optional<Schema.String>;
207
+ }>;
208
+ /** A caller-resolved dotfiles tree, as at create (`spec.runtime.dotfilesArchives`). */
209
+ export declare const applyWorkspaceDotfilesArchiveSchema: Schema.Struct<{
210
+ /** base64 of a `.tar.gz`; at most 6 MiB of base64 (about 4 MiB decoded). */
211
+ readonly data: Schema.String;
212
+ readonly manager: Schema.optional<Schema.Literals<readonly ["auto", "chezmoi", "stow", "copy"]>>;
213
+ /** Where the tree lands: `home` (default) or `config` (`~/.config`, copy manager only). */
214
+ readonly target: Schema.optional<Schema.Literals<readonly ["home", "config"]>>;
215
+ /** Run `./install.sh` (or `bootstrapCommand`) after applying when present. Default true. */
216
+ readonly bootstrap: Schema.optional<Schema.Boolean>;
217
+ readonly bootstrapCommand: Schema.optional<Schema.String>;
218
+ }>;
219
+ /**
220
+ * Apply a person's dotfiles into their home of a RUNNING workspace, as their Linux user
221
+ * (docs/connected-accounts-design.md §6g; Mend's ADR 0016 decision 11): the same sources as a
222
+ * create's dotfiles (a repository, archives, or both; the repository first, archives after in
223
+ * order) and the same applier (sealantd's: chezmoi, stow or copy, then each tree's bootstrap).
224
+ *
225
+ * `user` must exist and must not be root or in root's group, and `home` must be its passwd home: an
226
+ * existing directory of the user's, reached without a symbolic link. The clone, chezmoi, stow and the
227
+ * bootstrap run as the user; sealantd's archive staging and `copy` manager still run as root inside
228
+ * the home and follow links the person planted there, until sealantd's fix for it lands (see
229
+ * docs/connected-accounts-design.md §6g). `onBehalfOfUserId` names whose dotfiles they are: it is
230
+ * recorded on the run, and a home whose logins another person holds is refused (`home-held`). Answered `202` with a run (`harnessId`
231
+ * `dotfiles`) once the archives are staged: the run's first `processStarted` is the bootstrap,
232
+ * started once every file is applied; its output and exit are the run's. The run completes with the
233
+ * bootstrap's exit code (0 when no tree had one) and fails, with the daemon's words, when the apply
234
+ * was refused or the bootstrap ran past 30 minutes (it is stopped).
235
+ */
236
+ export declare const applyWorkspaceDotfilesRequestSchema: Schema.Struct<{
237
+ /** The workspace's owner (owner scope; uniform 404 otherwise). */
238
+ readonly ownerUserId: Schema.String;
239
+ /** The Sealant user whose dotfiles these are: recorded on the run (`metadata.dotfiles`). */
240
+ readonly onBehalfOfUserId: Schema.String;
241
+ /** The Linux user to apply as (a login name or a uid); never root. */
242
+ readonly user: Schema.String;
243
+ /** The user's passwd home: absolute, normalised, never under `/workspace`. */
244
+ readonly home: Schema.String;
245
+ readonly repository: Schema.optional<Schema.Struct<{
246
+ /** An `https://` clone URL with no credential in it (no `user:token@`). */
247
+ readonly url: Schema.String;
248
+ /** Branch or tag; omitted clones the remote's default branch. */
249
+ readonly ref: Schema.optional<Schema.String>;
250
+ readonly manager: Schema.optional<Schema.Literals<readonly ["auto", "chezmoi", "stow", "copy"]>>;
251
+ /** Run the bootstrap command after applying (when the checkout has it). Default true. */
252
+ readonly bootstrap: Schema.optional<Schema.Boolean>;
253
+ /** Relative to the checkout; default `./install.sh`. */
254
+ readonly bootstrapCommand: Schema.optional<Schema.String>;
255
+ }>>;
256
+ readonly archives: Schema.optional<Schema.$Array<Schema.Struct<{
257
+ /** base64 of a `.tar.gz`; at most 6 MiB of base64 (about 4 MiB decoded). */
258
+ readonly data: Schema.String;
259
+ readonly manager: Schema.optional<Schema.Literals<readonly ["auto", "chezmoi", "stow", "copy"]>>;
260
+ /** Where the tree lands: `home` (default) or `config` (`~/.config`, copy manager only). */
261
+ readonly target: Schema.optional<Schema.Literals<readonly ["home", "config"]>>;
262
+ /** Run `./install.sh` (or `bootstrapCommand`) after applying when present. Default true. */
263
+ readonly bootstrap: Schema.optional<Schema.Boolean>;
264
+ readonly bootstrapCommand: Schema.optional<Schema.String>;
265
+ }>>>;
266
+ }>;
267
+ export type ApplyWorkspaceDotfilesRequest = typeof applyWorkspaceDotfilesRequestSchema.Type;
268
+ /**
269
+ * Stable `code`s of the `WorkspaceConflictError` a dotfiles apply answers, nothing applied:
270
+ * `workspace-not-running` (no ready executor), `dotfiles-user-unsupported` (the workspace's
271
+ * sealantd cannot apply dotfiles as a user), `user-unknown` (no such user: make it first),
272
+ * `user-root` (the user is root or in root's group), `home-mismatch` (the home is not the user's
273
+ * passwd home), `home-unusable` (the home is missing, not a directory, not the user's, or reached
274
+ * through a symbolic link), `home-held` (another person's logins are held in the home).
275
+ */
276
+ export declare const workspaceDotfilesConflictCodes: readonly ["workspace-not-running", "dotfiles-user-unsupported", "user-unknown", "user-root", "home-mismatch", "home-unusable", "home-held"];
277
+ export type WorkspaceDotfilesConflictCode = (typeof workspaceDotfilesConflictCodes)[number];
189
278
  /**
190
279
  * Bind a standby workspace's working directory, or a bindable extra mount, to one subdirectory of
191
280
  * its root (sealantd ADR-0014). `mountPath` defaults to the working directory; an empty `subpath`
@@ -618,9 +707,26 @@ export declare const inspectWorkspaceImageResponseSchema: Schema.Struct<{
618
707
  }>;
619
708
  }>;
620
709
  export type InspectWorkspaceImageResponse = typeof inspectWorkspaceImageResponseSchema.Type;
621
- /** The providers a home holds logins for (docs/connected-accounts-design.md §6c). */
622
- export declare const workspaceCredentialHomeProviders: readonly ["claude", "codex", "github"];
710
+ /**
711
+ * Stable codes of a refused connected account, carried with the account's `provider` by the error
712
+ * that refuses it (a create's or a credentials put's): `connected-account-missing`
713
+ * (`WorkspaceNotFoundError`, 404: the person has no such account, or it is archived or someone
714
+ * else's) and `connected-account-invalid` (`WorkspaceConflictError`, 409: the account is marked
715
+ * invalid, or holds an unusable credential; reconnect it) and `connected-account-unsupported`
716
+ * (`WorkspaceConflictError`, 409: the account cannot serve what was asked, as a Codex API key cannot
717
+ * be pi's or opencode's ChatGPT login). Nothing was written either way.
718
+ */
719
+ export declare const connectedAccountRefusalCodes: readonly ["connected-account-missing", "connected-account-invalid", "connected-account-unsupported"];
720
+ export type ConnectedAccountRefusalCode = (typeof connectedAccountRefusalCodes)[number];
721
+ /**
722
+ * The providers a home holds logins for (docs/connected-accounts-design.md §6c). `pi` and
723
+ * `opencode` are the person's ChatGPT login (a Codex connected account), written into each tool's
724
+ * own `auth.json`.
725
+ */
726
+ export declare const workspaceCredentialHomeProviders: readonly ["claude", "codex", "github", "pi", "opencode"];
623
727
  export type WorkspaceCredentialHomeProvider = (typeof workspaceCredentialHomeProviders)[number];
728
+ /** The connected account's provider each home provider's login is made from. */
729
+ export declare const workspaceCredentialHomeAccountProvider: Readonly<Record<WorkspaceCredentialHomeProvider, "claude" | "codex" | "github">>;
624
730
  /**
625
731
  * Put one person's logins into one home of a RUNNING workspace (docs/connected-accounts-design.md
626
732
  * §6c). A home holds one person's logins while it is held: the first put names its person, a put
@@ -652,6 +758,25 @@ export declare const putWorkspaceCredentialsRequestSchema: Schema.Struct<{
652
758
  readonly claude: Schema.optional<Schema.NullOr<Schema.String>>;
653
759
  readonly codex: Schema.optional<Schema.NullOr<Schema.String>>;
654
760
  readonly github: Schema.optional<Schema.NullOr<Schema.String>>;
761
+ /**
762
+ * pi's ChatGPT login, made from a Codex account of `onBehalfOfUserId` (an id or a Codex account
763
+ * name), as the `openai-codex` entry of `<home>/.pi/agent/auth.json`. The file is merged: its
764
+ * other entries stay, and an `openai-codex` login the person made in pi is never replaced.
765
+ */
766
+ readonly pi: Schema.optional<Schema.NullOr<Schema.String>>;
767
+ /**
768
+ * opencode's ChatGPT login, made the same way, as the `openai` entry of opencode's `auth.json`
769
+ * (`<home>/.local/share/opencode/auth.json`, followed through links to where it really is, which
770
+ * must be inside the home). A login the person made in opencode is never replaced or removed.
771
+ */
772
+ readonly opencode: Schema.optional<Schema.NullOr<Schema.String>>;
773
+ /**
774
+ * Write what the person has connected and leave out the rest: a provider whose account is refused
775
+ * (`connected-account-missing`, `-invalid` or `-unsupported`) is not written, its login is removed
776
+ * from the home as `null` would remove it, and the answer's `skipped` says which and why, instead
777
+ * of the whole put failing. Every other refusal still fails the put. Default false.
778
+ */
779
+ readonly partial: Schema.optional<Schema.Boolean>;
655
780
  }>;
656
781
  export type PutWorkspaceCredentialsRequest = typeof putWorkspaceCredentialsRequestSchema.Type;
657
782
  /** One account whose copy a home holds. */
@@ -683,9 +808,33 @@ export declare const workspaceCredentialHomeSchema: Schema.Struct<{
683
808
  /** The account's name under its provider, as it is now. */
684
809
  readonly name: Schema.String;
685
810
  }>>;
811
+ /** The Codex account pi's ChatGPT login is made from. */
812
+ readonly pi: Schema.optional<Schema.Struct<{
813
+ readonly connectedAccountId: Schema.String;
814
+ /** The account's name under its provider, as it is now. */
815
+ readonly name: Schema.String;
816
+ }>>;
817
+ /** The Codex account opencode's ChatGPT login is made from. */
818
+ readonly opencode: Schema.optional<Schema.Struct<{
819
+ readonly connectedAccountId: Schema.String;
820
+ /** The account's name under its provider, as it is now. */
821
+ readonly name: Schema.String;
822
+ }>>;
686
823
  }>;
687
824
  }>;
688
825
  export type WorkspaceCredentialHome = typeof workspaceCredentialHomeSchema.Type;
826
+ /** A provider a partial put left out, and why (its account's refusal). */
827
+ export declare const workspaceCredentialSkipSchema: Schema.Struct<{
828
+ readonly provider: Schema.Literals<readonly ["claude", "codex", "github", "pi", "opencode"]>;
829
+ /**
830
+ * The account's refusal, or `login-file-unusable`: pi's or opencode's `auth.json` in the home
831
+ * cannot be written there (not a regular file, or really outside the home).
832
+ */
833
+ readonly code: Schema.Literals<readonly ["connected-account-missing", "connected-account-invalid", "connected-account-unsupported", "login-file-unusable"]>;
834
+ /** The refusal's words, as a whole put would have answered them. */
835
+ readonly message: Schema.String;
836
+ }>;
837
+ export type WorkspaceCredentialSkip = typeof workspaceCredentialSkipSchema.Type;
689
838
  export declare const putWorkspaceCredentialsResponseSchema: Schema.Struct<{
690
839
  readonly workspaceId: Schema.String;
691
840
  /** The launch attempt whose executor was written. */
@@ -712,8 +861,31 @@ export declare const putWorkspaceCredentialsResponseSchema: Schema.Struct<{
712
861
  /** The account's name under its provider, as it is now. */
713
862
  readonly name: Schema.String;
714
863
  }>>;
864
+ /** The Codex account pi's ChatGPT login is made from. */
865
+ readonly pi: Schema.optional<Schema.Struct<{
866
+ readonly connectedAccountId: Schema.String;
867
+ /** The account's name under its provider, as it is now. */
868
+ readonly name: Schema.String;
869
+ }>>;
870
+ /** The Codex account opencode's ChatGPT login is made from. */
871
+ readonly opencode: Schema.optional<Schema.Struct<{
872
+ readonly connectedAccountId: Schema.String;
873
+ /** The account's name under its provider, as it is now. */
874
+ readonly name: Schema.String;
875
+ }>>;
715
876
  }>;
716
877
  }>;
878
+ /** A partial put's left-out providers, in the order named (empty when all were written). */
879
+ readonly skipped: Schema.optional<Schema.$Array<Schema.Struct<{
880
+ readonly provider: Schema.Literals<readonly ["claude", "codex", "github", "pi", "opencode"]>;
881
+ /**
882
+ * The account's refusal, or `login-file-unusable`: pi's or opencode's `auth.json` in the home
883
+ * cannot be written there (not a regular file, or really outside the home).
884
+ */
885
+ readonly code: Schema.Literals<readonly ["connected-account-missing", "connected-account-invalid", "connected-account-unsupported", "login-file-unusable"]>;
886
+ /** The refusal's words, as a whole put would have answered them. */
887
+ readonly message: Schema.String;
888
+ }>>>;
717
889
  }>;
718
890
  export type PutWorkspaceCredentialsResponse = typeof putWorkspaceCredentialsResponseSchema.Type;
719
891
  /** Release one home: its login files are removed and its record deleted. */
@@ -759,6 +931,18 @@ export declare const listWorkspaceCredentialsResponseSchema: Schema.Struct<{
759
931
  /** The account's name under its provider, as it is now. */
760
932
  readonly name: Schema.String;
761
933
  }>>;
934
+ /** The Codex account pi's ChatGPT login is made from. */
935
+ readonly pi: Schema.optional<Schema.Struct<{
936
+ readonly connectedAccountId: Schema.String;
937
+ /** The account's name under its provider, as it is now. */
938
+ readonly name: Schema.String;
939
+ }>>;
940
+ /** The Codex account opencode's ChatGPT login is made from. */
941
+ readonly opencode: Schema.optional<Schema.Struct<{
942
+ readonly connectedAccountId: Schema.String;
943
+ /** The account's name under its provider, as it is now. */
944
+ readonly name: Schema.String;
945
+ }>>;
762
946
  }>;
763
947
  }>>;
764
948
  }>;
@@ -766,13 +950,15 @@ export type ListWorkspaceCredentialsResponse = typeof listWorkspaceCredentialsRe
766
950
  /**
767
951
  * Stable `code`s of the `WorkspaceConflictError` the credential routes answer:
768
952
  * `workspace-not-running` (no ready executor), `connected-account-invalid` (a named account is
769
- * marked invalid: reconnect it), `home-held` (the home holds another person's logins: release it
953
+ * marked invalid or unusable: reconnect it; `provider` names its provider),
954
+ * `connected-account-unsupported` (a Codex account named for pi or opencode is not a ChatGPT
955
+ * login), `home-held` (the home holds another person's logins: release it
770
956
  * first; `/root` while the launch's own logins are at `$HOME`, or for anyone but the workspace's
771
957
  * owner), `home-unusable` (the home does not exist, is not a directory, is reached through a
772
958
  * symbolic link, or a login directory in it links outside it), `home-busy` (another write into the
773
959
  * home, or too many into this workspace, is still running: nothing was done, try again).
774
960
  */
775
- export declare const workspaceCredentialsConflictCodes: readonly ["workspace-not-running", "connected-account-invalid", "home-held", "home-unusable", "home-busy"];
961
+ export declare const workspaceCredentialsConflictCodes: readonly ["workspace-not-running", "connected-account-invalid", "connected-account-unsupported", "home-held", "home-unusable", "home-busy"];
776
962
  export type WorkspaceCredentialsConflictCode = (typeof workspaceCredentialsConflictCodes)[number];
777
963
  export declare const expireWorkspaceRequestSchema: Schema.Struct<{
778
964
  readonly ownerUserId: Schema.String;
@@ -1419,6 +1605,10 @@ export declare class WorkspaceForbiddenError extends WorkspaceForbiddenError_bas
1419
1605
  }
1420
1606
  declare const WorkspaceNotFoundError_base: Schema.Class<WorkspaceNotFoundError, Schema.TaggedStruct<"WorkspaceNotFoundError", {
1421
1607
  readonly message: Schema.String;
1608
+ /** A stable reason, where one applies (`connected-account-missing`). */
1609
+ readonly code: Schema.optional<Schema.String>;
1610
+ /** The provider of the connected account the reason is about. */
1611
+ readonly provider: Schema.optional<Schema.Literals<readonly ["claude", "codex", "github"]>>;
1422
1612
  }>, import("effect/Cause").YieldableError>;
1423
1613
  export declare class WorkspaceNotFoundError extends WorkspaceNotFoundError_base {
1424
1614
  }
@@ -1426,6 +1616,8 @@ declare const WorkspaceConflictError_base: Schema.Class<WorkspaceConflictError,
1426
1616
  readonly message: Schema.String;
1427
1617
  /** A stable reason, where one applies (`create-cancelled`: the create's key was cancelled). */
1428
1618
  readonly code: Schema.optional<Schema.String>;
1619
+ /** The provider of the connected account the reason is about (`connected-account-invalid`). */
1620
+ readonly provider: Schema.optional<Schema.Literals<readonly ["claude", "codex", "github"]>>;
1429
1621
  }>, import("effect/Cause").YieldableError>;
1430
1622
  export declare class WorkspaceConflictError extends WorkspaceConflictError_base {
1431
1623
  }
@@ -1444,7 +1636,63 @@ declare const WorkspaceInternalServerError_base: Schema.Class<WorkspaceInternalS
1444
1636
  }>, import("effect/Cause").YieldableError>;
1445
1637
  export declare class WorkspaceInternalServerError extends WorkspaceInternalServerError_base {
1446
1638
  }
1447
- export declare const WorkspacesGroup: HttpApiGroup.HttpApiGroup<"workspaces", HttpApiEndpoint.HttpApiEndpoint<"bindWorkspace", "POST", "/:workspaceId/bind", HttpApiEndpoint.StringTree<Schema.Struct<{
1639
+ export declare const WorkspacesGroup: HttpApiGroup.HttpApiGroup<"workspaces", HttpApiEndpoint.HttpApiEndpoint<"applyWorkspaceDotfiles", "POST", "/:workspaceId/dotfiles", HttpApiEndpoint.StringTree<Schema.Struct<{
1640
+ readonly workspaceId: Schema.String;
1641
+ }>>, HttpApiEndpoint.StringTree<never>, HttpApiEndpoint.Json<Schema.Struct<{
1642
+ /** The workspace's owner (owner scope; uniform 404 otherwise). */
1643
+ readonly ownerUserId: Schema.String;
1644
+ /** The Sealant user whose dotfiles these are: recorded on the run (`metadata.dotfiles`). */
1645
+ readonly onBehalfOfUserId: Schema.String;
1646
+ /** The Linux user to apply as (a login name or a uid); never root. */
1647
+ readonly user: Schema.String;
1648
+ /** The user's passwd home: absolute, normalised, never under `/workspace`. */
1649
+ readonly home: Schema.String;
1650
+ readonly repository: Schema.optional<Schema.Struct<{
1651
+ /** An `https://` clone URL with no credential in it (no `user:token@`). */
1652
+ readonly url: Schema.String;
1653
+ /** Branch or tag; omitted clones the remote's default branch. */
1654
+ readonly ref: Schema.optional<Schema.String>;
1655
+ readonly manager: Schema.optional<Schema.Literals<readonly ["auto", "chezmoi", "stow", "copy"]>>;
1656
+ /** Run the bootstrap command after applying (when the checkout has it). Default true. */
1657
+ readonly bootstrap: Schema.optional<Schema.Boolean>;
1658
+ /** Relative to the checkout; default `./install.sh`. */
1659
+ readonly bootstrapCommand: Schema.optional<Schema.String>;
1660
+ }>>;
1661
+ readonly archives: Schema.optional<Schema.$Array<Schema.Struct<{
1662
+ /** base64 of a `.tar.gz`; at most 6 MiB of base64 (about 4 MiB decoded). */
1663
+ readonly data: Schema.String;
1664
+ readonly manager: Schema.optional<Schema.Literals<readonly ["auto", "chezmoi", "stow", "copy"]>>;
1665
+ /** Where the tree lands: `home` (default) or `config` (`~/.config`, copy manager only). */
1666
+ readonly target: Schema.optional<Schema.Literals<readonly ["home", "config"]>>;
1667
+ /** Run `./install.sh` (or `bootstrapCommand`) after applying when present. Default true. */
1668
+ readonly bootstrap: Schema.optional<Schema.Boolean>;
1669
+ readonly bootstrapCommand: Schema.optional<Schema.String>;
1670
+ }>>>;
1671
+ }>>, HttpApiEndpoint.StringTree<never>, HttpApiEndpoint.Json<Schema.Struct<{
1672
+ readonly runId: Schema.String;
1673
+ readonly workspaceId: Schema.String;
1674
+ readonly attemptId: Schema.optional<Schema.String>;
1675
+ readonly ownerUserId: Schema.String;
1676
+ readonly harnessId: Schema.String;
1677
+ readonly mode: Schema.Literals<readonly ["one-shot", "interactive"]>;
1678
+ readonly status: Schema.Literals<readonly ["queued", "running", "completed", "failed", "cancelled"]>;
1679
+ readonly prompt: Schema.optional<Schema.String>;
1680
+ readonly command: Schema.optional<Schema.Struct<{
1681
+ readonly executable: Schema.String;
1682
+ readonly args: Schema.$Array<Schema.String>;
1683
+ readonly cwd: Schema.optional<Schema.String>;
1684
+ readonly argCount: Schema.optional<Schema.Number>;
1685
+ readonly argLengths: Schema.optional<Schema.$Array<Schema.Number>>;
1686
+ }>>;
1687
+ readonly metadata: Schema.optional<Schema.$Record<Schema.String, Schema.Unknown>>;
1688
+ readonly exitCode: Schema.optional<Schema.Number>;
1689
+ readonly errorMessage: Schema.optional<Schema.String>;
1690
+ readonly startedAt: Schema.optional<Schema.String>;
1691
+ readonly finishedAt: Schema.optional<Schema.String>;
1692
+ readonly recordDeletedAt: Schema.optional<Schema.String>;
1693
+ readonly createdAt: Schema.String;
1694
+ readonly updatedAt: Schema.String;
1695
+ }>>, HttpApiEndpoint.Json<typeof WorkspaceBadRequestError | typeof WorkspaceForbiddenError | typeof WorkspaceNotFoundError | typeof WorkspaceConflictError | typeof WorkspaceBadGatewayError | typeof WorkspaceServiceUnavailableError | typeof WorkspaceInternalServerError>, never, never> | HttpApiEndpoint.HttpApiEndpoint<"bindWorkspace", "POST", "/:workspaceId/bind", HttpApiEndpoint.StringTree<Schema.Struct<{
1448
1696
  readonly workspaceId: Schema.String;
1449
1697
  }>>, HttpApiEndpoint.StringTree<never>, HttpApiEndpoint.Json<Schema.Struct<{
1450
1698
  readonly ownerUserId: Schema.String;
@@ -2154,6 +2402,18 @@ export declare const WorkspacesGroup: HttpApiGroup.HttpApiGroup<"workspaces", Ht
2154
2402
  /** The account's name under its provider, as it is now. */
2155
2403
  readonly name: Schema.String;
2156
2404
  }>>;
2405
+ /** The Codex account pi's ChatGPT login is made from. */
2406
+ readonly pi: Schema.optional<Schema.Struct<{
2407
+ readonly connectedAccountId: Schema.String;
2408
+ /** The account's name under its provider, as it is now. */
2409
+ readonly name: Schema.String;
2410
+ }>>;
2411
+ /** The Codex account opencode's ChatGPT login is made from. */
2412
+ readonly opencode: Schema.optional<Schema.Struct<{
2413
+ readonly connectedAccountId: Schema.String;
2414
+ /** The account's name under its provider, as it is now. */
2415
+ readonly name: Schema.String;
2416
+ }>>;
2157
2417
  }>;
2158
2418
  }>>;
2159
2419
  }>>, HttpApiEndpoint.Json<typeof WorkspaceForbiddenError | typeof WorkspaceNotFoundError | typeof WorkspaceConflictError | typeof WorkspaceInternalServerError>, never, never> | HttpApiEndpoint.HttpApiEndpoint<"listWorkspaceEvents", "GET", "/:workspaceId/events", HttpApiEndpoint.StringTree<Schema.Struct<{
@@ -2262,6 +2522,25 @@ export declare const WorkspacesGroup: HttpApiGroup.HttpApiGroup<"workspaces", Ht
2262
2522
  readonly claude: Schema.optional<Schema.NullOr<Schema.String>>;
2263
2523
  readonly codex: Schema.optional<Schema.NullOr<Schema.String>>;
2264
2524
  readonly github: Schema.optional<Schema.NullOr<Schema.String>>;
2525
+ /**
2526
+ * pi's ChatGPT login, made from a Codex account of `onBehalfOfUserId` (an id or a Codex account
2527
+ * name), as the `openai-codex` entry of `<home>/.pi/agent/auth.json`. The file is merged: its
2528
+ * other entries stay, and an `openai-codex` login the person made in pi is never replaced.
2529
+ */
2530
+ readonly pi: Schema.optional<Schema.NullOr<Schema.String>>;
2531
+ /**
2532
+ * opencode's ChatGPT login, made the same way, as the `openai` entry of opencode's `auth.json`
2533
+ * (`<home>/.local/share/opencode/auth.json`, followed through links to where it really is, which
2534
+ * must be inside the home). A login the person made in opencode is never replaced or removed.
2535
+ */
2536
+ readonly opencode: Schema.optional<Schema.NullOr<Schema.String>>;
2537
+ /**
2538
+ * Write what the person has connected and leave out the rest: a provider whose account is refused
2539
+ * (`connected-account-missing`, `-invalid` or `-unsupported`) is not written, its login is removed
2540
+ * from the home as `null` would remove it, and the answer's `skipped` says which and why, instead
2541
+ * of the whole put failing. Every other refusal still fails the put. Default false.
2542
+ */
2543
+ readonly partial: Schema.optional<Schema.Boolean>;
2265
2544
  }>>, HttpApiEndpoint.StringTree<never>, HttpApiEndpoint.Json<Schema.Struct<{
2266
2545
  readonly workspaceId: Schema.String;
2267
2546
  /** The launch attempt whose executor was written. */
@@ -2288,8 +2567,31 @@ export declare const WorkspacesGroup: HttpApiGroup.HttpApiGroup<"workspaces", Ht
2288
2567
  /** The account's name under its provider, as it is now. */
2289
2568
  readonly name: Schema.String;
2290
2569
  }>>;
2570
+ /** The Codex account pi's ChatGPT login is made from. */
2571
+ readonly pi: Schema.optional<Schema.Struct<{
2572
+ readonly connectedAccountId: Schema.String;
2573
+ /** The account's name under its provider, as it is now. */
2574
+ readonly name: Schema.String;
2575
+ }>>;
2576
+ /** The Codex account opencode's ChatGPT login is made from. */
2577
+ readonly opencode: Schema.optional<Schema.Struct<{
2578
+ readonly connectedAccountId: Schema.String;
2579
+ /** The account's name under its provider, as it is now. */
2580
+ readonly name: Schema.String;
2581
+ }>>;
2291
2582
  }>;
2292
2583
  }>;
2584
+ /** A partial put's left-out providers, in the order named (empty when all were written). */
2585
+ readonly skipped: Schema.optional<Schema.$Array<Schema.Struct<{
2586
+ readonly provider: Schema.Literals<readonly ["claude", "codex", "github", "pi", "opencode"]>;
2587
+ /**
2588
+ * The account's refusal, or `login-file-unusable`: pi's or opencode's `auth.json` in the home
2589
+ * cannot be written there (not a regular file, or really outside the home).
2590
+ */
2591
+ readonly code: Schema.Literals<readonly ["connected-account-missing", "connected-account-invalid", "connected-account-unsupported", "login-file-unusable"]>;
2592
+ /** The refusal's words, as a whole put would have answered them. */
2593
+ readonly message: Schema.String;
2594
+ }>>>;
2293
2595
  }>>, HttpApiEndpoint.Json<typeof WorkspaceBadRequestError | typeof WorkspaceForbiddenError | typeof WorkspaceNotFoundError | typeof WorkspaceConflictError | typeof WorkspaceBadGatewayError | typeof WorkspaceServiceUnavailableError | typeof WorkspaceInternalServerError>, never, never> | HttpApiEndpoint.HttpApiEndpoint<"recoverWorkspace", "POST", "/:workspaceId/recover", HttpApiEndpoint.StringTree<Schema.Struct<{
2294
2596
  readonly workspaceId: Schema.String;
2295
2597
  }>>, HttpApiEndpoint.StringTree<never>, HttpApiEndpoint.Json<Schema.Struct<{
@@ -1,6 +1,7 @@
1
1
  import { Schema } from "effect";
2
2
  import { HttpApiEndpoint, HttpApiGroup, HttpApiSchema, OpenApi } from "effect/unstable/httpapi";
3
3
  import { BudgetExceededError } from "./budgets.js";
4
+ import { connectedAccountProviderSchema } from "./connected-accounts.js";
4
5
  import { runCommandSchema, runSchema } from "./runs.js";
5
6
  import { workspaceProcessUserSchema } from "./sessions.js";
6
7
  const NonEmptyString = Schema.String.check(Schema.isNonEmpty(), Schema.isTrimmed());
@@ -169,6 +170,82 @@ export const execWorkspaceRequestSchema = Schema.Struct({
169
170
  /** Run every command as this Linux user (see `workspaceProcessUserSchema`). */
170
171
  user: Schema.optional(workspaceProcessUserSchema),
171
172
  });
173
+ /** The `harnessId` stamped on runs that apply a person's dotfiles (`applyWorkspaceDotfiles`). */
174
+ export const dotfilesRunHarnessId = "dotfiles";
175
+ /** How a dotfiles tree is applied: chezmoi and stow layouts are detected by `auto`. */
176
+ export const workspaceDotfilesManagerSchema = Schema.Literals(["auto", "chezmoi", "stow", "copy"]);
177
+ /**
178
+ * A dotfiles repository cloned as the person (no credential reaches the workspace: a repository
179
+ * only the person's own identity can reach is resolved by the caller and sent as an archive).
180
+ */
181
+ export const applyWorkspaceDotfilesRepositorySchema = Schema.Struct({
182
+ /** An `https://` clone URL with no credential in it (no `user:token@`). */
183
+ url: NonEmptyString.check(Schema.isPattern(/^https:\/\/[^\s'"\\]+$/)),
184
+ /** Branch or tag; omitted clones the remote's default branch. */
185
+ ref: Schema.optional(NonEmptyString),
186
+ manager: Schema.optional(workspaceDotfilesManagerSchema),
187
+ /** Run the bootstrap command after applying (when the checkout has it). Default true. */
188
+ bootstrap: Schema.optional(Schema.Boolean),
189
+ /** Relative to the checkout; default `./install.sh`. */
190
+ bootstrapCommand: Schema.optional(NonEmptyString),
191
+ });
192
+ /** A caller-resolved dotfiles tree, as at create (`spec.runtime.dotfilesArchives`). */
193
+ export const applyWorkspaceDotfilesArchiveSchema = Schema.Struct({
194
+ /** base64 of a `.tar.gz`; at most 6 MiB of base64 (about 4 MiB decoded). */
195
+ data: Schema.String.check(Schema.isMinLength(1), Schema.isMaxLength(6 * 1024 * 1024), Schema.isPattern(/^[A-Za-z0-9+/]+={0,2}$/)),
196
+ manager: Schema.optional(workspaceDotfilesManagerSchema),
197
+ /** Where the tree lands: `home` (default) or `config` (`~/.config`, copy manager only). */
198
+ target: Schema.optional(Schema.Literals(["home", "config"])),
199
+ /** Run `./install.sh` (or `bootstrapCommand`) after applying when present. Default true. */
200
+ bootstrap: Schema.optional(Schema.Boolean),
201
+ bootstrapCommand: Schema.optional(NonEmptyString),
202
+ });
203
+ /**
204
+ * Apply a person's dotfiles into their home of a RUNNING workspace, as their Linux user
205
+ * (docs/connected-accounts-design.md §6g; Mend's ADR 0016 decision 11): the same sources as a
206
+ * create's dotfiles (a repository, archives, or both; the repository first, archives after in
207
+ * order) and the same applier (sealantd's: chezmoi, stow or copy, then each tree's bootstrap).
208
+ *
209
+ * `user` must exist and must not be root or in root's group, and `home` must be its passwd home: an
210
+ * existing directory of the user's, reached without a symbolic link. The clone, chezmoi, stow and the
211
+ * bootstrap run as the user; sealantd's archive staging and `copy` manager still run as root inside
212
+ * the home and follow links the person planted there, until sealantd's fix for it lands (see
213
+ * docs/connected-accounts-design.md §6g). `onBehalfOfUserId` names whose dotfiles they are: it is
214
+ * recorded on the run, and a home whose logins another person holds is refused (`home-held`). Answered `202` with a run (`harnessId`
215
+ * `dotfiles`) once the archives are staged: the run's first `processStarted` is the bootstrap,
216
+ * started once every file is applied; its output and exit are the run's. The run completes with the
217
+ * bootstrap's exit code (0 when no tree had one) and fails, with the daemon's words, when the apply
218
+ * was refused or the bootstrap ran past 30 minutes (it is stopped).
219
+ */
220
+ export const applyWorkspaceDotfilesRequestSchema = Schema.Struct({
221
+ /** The workspace's owner (owner scope; uniform 404 otherwise). */
222
+ ownerUserId: NonEmptyString,
223
+ /** The Sealant user whose dotfiles these are: recorded on the run (`metadata.dotfiles`). */
224
+ onBehalfOfUserId: NonEmptyString,
225
+ /** The Linux user to apply as (a login name or a uid); never root. */
226
+ user: workspaceProcessUserSchema,
227
+ /** The user's passwd home: absolute, normalised, never under `/workspace`. */
228
+ home: NonEmptyString,
229
+ repository: Schema.optional(applyWorkspaceDotfilesRepositorySchema),
230
+ archives: Schema.optional(Schema.Array(applyWorkspaceDotfilesArchiveSchema).check(Schema.isMaxLength(4))),
231
+ });
232
+ /**
233
+ * Stable `code`s of the `WorkspaceConflictError` a dotfiles apply answers, nothing applied:
234
+ * `workspace-not-running` (no ready executor), `dotfiles-user-unsupported` (the workspace's
235
+ * sealantd cannot apply dotfiles as a user), `user-unknown` (no such user: make it first),
236
+ * `user-root` (the user is root or in root's group), `home-mismatch` (the home is not the user's
237
+ * passwd home), `home-unusable` (the home is missing, not a directory, not the user's, or reached
238
+ * through a symbolic link), `home-held` (another person's logins are held in the home).
239
+ */
240
+ export const workspaceDotfilesConflictCodes = [
241
+ "workspace-not-running",
242
+ "dotfiles-user-unsupported",
243
+ "user-unknown",
244
+ "user-root",
245
+ "home-mismatch",
246
+ "home-unusable",
247
+ "home-held",
248
+ ];
172
249
  /**
173
250
  * Bind a standby workspace's working directory, or a bindable extra mount, to one subdirectory of
174
251
  * its root (sealantd ADR-0014). `mountPath` defaults to the working directory; an empty `subpath`
@@ -517,8 +594,34 @@ export const inspectWorkspaceImageResponseSchema = Schema.Struct({
517
594
  /** The image's per-person capability (`unknown` when none has been built yet). */
518
595
  personLayout: workspaceImagePersonLayoutSchema,
519
596
  });
520
- /** The providers a home holds logins for (docs/connected-accounts-design.md §6c). */
521
- export const workspaceCredentialHomeProviders = ["claude", "codex", "github"];
597
+ /**
598
+ * Stable codes of a refused connected account, carried with the account's `provider` by the error
599
+ * that refuses it (a create's or a credentials put's): `connected-account-missing`
600
+ * (`WorkspaceNotFoundError`, 404: the person has no such account, or it is archived or someone
601
+ * else's) and `connected-account-invalid` (`WorkspaceConflictError`, 409: the account is marked
602
+ * invalid, or holds an unusable credential; reconnect it) and `connected-account-unsupported`
603
+ * (`WorkspaceConflictError`, 409: the account cannot serve what was asked, as a Codex API key cannot
604
+ * be pi's or opencode's ChatGPT login). Nothing was written either way.
605
+ */
606
+ export const connectedAccountRefusalCodes = [
607
+ "connected-account-missing",
608
+ "connected-account-invalid",
609
+ "connected-account-unsupported",
610
+ ];
611
+ /**
612
+ * The providers a home holds logins for (docs/connected-accounts-design.md §6c). `pi` and
613
+ * `opencode` are the person's ChatGPT login (a Codex connected account), written into each tool's
614
+ * own `auth.json`.
615
+ */
616
+ export const workspaceCredentialHomeProviders = [
617
+ "claude",
618
+ "codex",
619
+ "github",
620
+ "pi",
621
+ "opencode",
622
+ ];
623
+ /** The connected account's provider each home provider's login is made from. */
624
+ export const workspaceCredentialHomeAccountProvider = { claude: "claude", codex: "codex", github: "github", pi: "codex", opencode: "codex" };
522
625
  /**
523
626
  * Put one person's logins into one home of a RUNNING workspace (docs/connected-accounts-design.md
524
627
  * §6c). A home holds one person's logins while it is held: the first put names its person, a put
@@ -550,6 +653,25 @@ export const putWorkspaceCredentialsRequestSchema = Schema.Struct({
550
653
  claude: Schema.optional(Schema.NullOr(NonEmptyString)),
551
654
  codex: Schema.optional(Schema.NullOr(NonEmptyString)),
552
655
  github: Schema.optional(Schema.NullOr(NonEmptyString)),
656
+ /**
657
+ * pi's ChatGPT login, made from a Codex account of `onBehalfOfUserId` (an id or a Codex account
658
+ * name), as the `openai-codex` entry of `<home>/.pi/agent/auth.json`. The file is merged: its
659
+ * other entries stay, and an `openai-codex` login the person made in pi is never replaced.
660
+ */
661
+ pi: Schema.optional(Schema.NullOr(NonEmptyString)),
662
+ /**
663
+ * opencode's ChatGPT login, made the same way, as the `openai` entry of opencode's `auth.json`
664
+ * (`<home>/.local/share/opencode/auth.json`, followed through links to where it really is, which
665
+ * must be inside the home). A login the person made in opencode is never replaced or removed.
666
+ */
667
+ opencode: Schema.optional(Schema.NullOr(NonEmptyString)),
668
+ /**
669
+ * Write what the person has connected and leave out the rest: a provider whose account is refused
670
+ * (`connected-account-missing`, `-invalid` or `-unsupported`) is not written, its login is removed
671
+ * from the home as `null` would remove it, and the answer's `skipped` says which and why, instead
672
+ * of the whole put failing. Every other refusal still fails the put. Default false.
673
+ */
674
+ partial: Schema.optional(Schema.Boolean),
553
675
  });
554
676
  /** One account whose copy a home holds. */
555
677
  export const workspaceHomeAccountSchema = Schema.Struct({
@@ -567,14 +689,31 @@ export const workspaceCredentialHomeSchema = Schema.Struct({
567
689
  claude: Schema.optional(workspaceHomeAccountSchema),
568
690
  codex: Schema.optional(workspaceHomeAccountSchema),
569
691
  github: Schema.optional(workspaceHomeAccountSchema),
692
+ /** The Codex account pi's ChatGPT login is made from. */
693
+ pi: Schema.optional(workspaceHomeAccountSchema),
694
+ /** The Codex account opencode's ChatGPT login is made from. */
695
+ opencode: Schema.optional(workspaceHomeAccountSchema),
570
696
  }),
571
697
  });
698
+ /** A provider a partial put left out, and why (its account's refusal). */
699
+ export const workspaceCredentialSkipSchema = Schema.Struct({
700
+ provider: Schema.Literals(workspaceCredentialHomeProviders),
701
+ /**
702
+ * The account's refusal, or `login-file-unusable`: pi's or opencode's `auth.json` in the home
703
+ * cannot be written there (not a regular file, or really outside the home).
704
+ */
705
+ code: Schema.Literals([...connectedAccountRefusalCodes, "login-file-unusable"]),
706
+ /** The refusal's words, as a whole put would have answered them. */
707
+ message: Schema.String,
708
+ });
572
709
  export const putWorkspaceCredentialsResponseSchema = Schema.Struct({
573
710
  workspaceId: NonEmptyString,
574
711
  /** The launch attempt whose executor was written. */
575
712
  runId: NonEmptyString,
576
713
  /** The home as it is after the put. */
577
714
  home: workspaceCredentialHomeSchema,
715
+ /** A partial put's left-out providers, in the order named (empty when all were written). */
716
+ skipped: Schema.optional(Schema.Array(workspaceCredentialSkipSchema)),
578
717
  });
579
718
  /** Release one home: its login files are removed and its record deleted. */
580
719
  export const releaseWorkspaceCredentialsQuerySchema = Schema.Struct({
@@ -600,7 +739,9 @@ export const listWorkspaceCredentialsResponseSchema = Schema.Struct({
600
739
  /**
601
740
  * Stable `code`s of the `WorkspaceConflictError` the credential routes answer:
602
741
  * `workspace-not-running` (no ready executor), `connected-account-invalid` (a named account is
603
- * marked invalid: reconnect it), `home-held` (the home holds another person's logins: release it
742
+ * marked invalid or unusable: reconnect it; `provider` names its provider),
743
+ * `connected-account-unsupported` (a Codex account named for pi or opencode is not a ChatGPT
744
+ * login), `home-held` (the home holds another person's logins: release it
604
745
  * first; `/root` while the launch's own logins are at `$HOME`, or for anyone but the workspace's
605
746
  * owner), `home-unusable` (the home does not exist, is not a directory, is reached through a
606
747
  * symbolic link, or a login directory in it links outside it), `home-busy` (another write into the
@@ -609,6 +750,7 @@ export const listWorkspaceCredentialsResponseSchema = Schema.Struct({
609
750
  export const workspaceCredentialsConflictCodes = [
610
751
  "workspace-not-running",
611
752
  "connected-account-invalid",
753
+ "connected-account-unsupported",
612
754
  "home-held",
613
755
  "home-unusable",
614
756
  "home-busy",
@@ -907,12 +1049,18 @@ export class WorkspaceForbiddenError extends Schema.TaggedErrorClass()("Workspac
907
1049
  }
908
1050
  export class WorkspaceNotFoundError extends Schema.TaggedErrorClass()("WorkspaceNotFoundError", {
909
1051
  message: Schema.String,
1052
+ /** A stable reason, where one applies (`connected-account-missing`). */
1053
+ code: Schema.optional(NonEmptyString),
1054
+ /** The provider of the connected account the reason is about. */
1055
+ provider: Schema.optional(connectedAccountProviderSchema),
910
1056
  }, { httpApiStatus: 404 }) {
911
1057
  }
912
1058
  export class WorkspaceConflictError extends Schema.TaggedErrorClass()("WorkspaceConflictError", {
913
1059
  message: Schema.String,
914
1060
  /** A stable reason, where one applies (`create-cancelled`: the create's key was cancelled). */
915
1061
  code: Schema.optional(NonEmptyString),
1062
+ /** The provider of the connected account the reason is about (`connected-account-invalid`). */
1063
+ provider: Schema.optional(connectedAccountProviderSchema),
916
1064
  }, { httpApiStatus: 409 }) {
917
1065
  }
918
1066
  export class WorkspaceBadGatewayError extends Schema.TaggedErrorClass()("WorkspaceBadGatewayError", {
@@ -1041,6 +1189,27 @@ HttpApiEndpoint.post("execWorkspace", "/:workspaceId/exec", {
1041
1189
  ],
1042
1190
  }))
1043
1191
  .add(
1192
+ // Async like exec: 202 + the queued run once the archives are staged; poll `GET /v1/runs/:runId`.
1193
+ HttpApiEndpoint.post("applyWorkspaceDotfiles", "/:workspaceId/dotfiles", {
1194
+ params: workspaceIdParams,
1195
+ payload: applyWorkspaceDotfilesRequestSchema,
1196
+ success: runSchema.pipe(HttpApiSchema.status(202)),
1197
+ error: [
1198
+ // Root, a malformed home, a URL with a credential, or nothing to apply.
1199
+ WorkspaceBadRequestError,
1200
+ // Only a service key may apply a person's dotfiles.
1201
+ WorkspaceForbiddenError,
1202
+ WorkspaceNotFoundError,
1203
+ // See `workspaceDotfilesConflictCodes`.
1204
+ WorkspaceConflictError,
1205
+ // The executor did not confirm the staging.
1206
+ WorkspaceBadGatewayError,
1207
+ // Core cannot reach the executor.
1208
+ WorkspaceServiceUnavailableError,
1209
+ WorkspaceInternalServerError,
1210
+ ],
1211
+ }))
1212
+ .add(
1044
1213
  // Async: 202 = the stop was accepted and enqueued; the worker removes the container and the
1045
1214
  // workspace transitions to "stopped". Idempotent — stopping a stopped workspace is a no-op 202.
1046
1215
  HttpApiEndpoint.post("stopWorkspace", "/:workspaceId/stop", {
@@ -1106,7 +1275,7 @@ HttpApiEndpoint.post("putWorkspaceCredentials", "/:workspaceId/credentials", {
1106
1275
  WorkspaceBadRequestError,
1107
1276
  // The caller may not act for the people named (only a service key may).
1108
1277
  WorkspaceForbiddenError,
1109
- // The workspace or a named account (unknown, someone else's, archived).
1278
+ // The workspace, or a named account (`connected-account-missing` with its `provider`).
1110
1279
  WorkspaceNotFoundError,
1111
1280
  // See `workspaceCredentialsConflictCodes`.
1112
1281
  WorkspaceConflictError,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sealant/api-contracts-next",
3
- "version": "0.39.0-next.696",
3
+ "version": "0.39.0-next.700",
4
4
  "description": "Contract-first HTTP API definitions for Sealant services",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -32,7 +32,7 @@
32
32
  "peerDependencies": {
33
33
  "effect": ">=4.0.0-beta.85 <5"
34
34
  },
35
- "gitHead": "55326dd1cfb131e4324cecc5cd896f5bee35980c",
35
+ "gitHead": "78e1763231f005b670f7291cfb86917cec639598",
36
36
  "publishConfig": {
37
37
  "access": "public"
38
38
  }