@opencode/server 2.0.3 → 2.0.5

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/api.d.ts CHANGED
@@ -145,7 +145,7 @@ export declare const Api: import("@opencode/protocol/api").Api<LocationMiddlewar
145
145
  };
146
146
  readonly created: import("effect/Schema").Finite;
147
147
  readonly metadata: import("effect/Schema").decodeTo<import("effect/Schema").optional<import("effect/Schema").toType<import("effect/Schema").$Record<import("effect/Schema").String, import("effect/Schema").Unknown>>>, import("effect/Schema").optionalKey<import("effect/Schema").$Record<import("effect/Schema").String, import("effect/Schema").Unknown>>, never, never>;
148
- readonly type: import("effect/Schema").Literal<"catalog.updated">;
148
+ readonly type: import("effect/Schema").Literal<"provider.updated">;
149
149
  readonly location: import("effect/Schema").decodeTo<import("effect/Schema").optional<import("effect/Schema").toType<import("effect/Schema").Struct<{
150
150
  readonly directory: import("effect/Schema").brand<import("effect/Schema").String, "AbsolutePath">;
151
151
  readonly workspaceID: import("effect/Schema").decodeTo<import("effect/Schema").optional<import("effect/Schema").toType<import("effect/Schema").brand<import("effect/Schema").String, "Workspace.ID"> & {
@@ -167,7 +167,39 @@ export declare const Api: import("@opencode/protocol/api").Api<LocationMiddlewar
167
167
  }>>, never, never>;
168
168
  readonly data: import("effect/Schema").Struct<{}>;
169
169
  }> & {
170
- type: "catalog.updated";
170
+ type: "provider.updated";
171
+ durability: "ephemeral";
172
+ durable: undefined;
173
+ data: import("effect/Schema").Struct<{}>;
174
+ }, import("effect/Schema").Struct<{
175
+ readonly id: import("effect/Schema").brand<import("effect/Schema").String, "Event.ID"> & {
176
+ create: () => string & import("effect/Brand").Brand<"Event.ID">;
177
+ };
178
+ readonly created: import("effect/Schema").Finite;
179
+ readonly metadata: import("effect/Schema").decodeTo<import("effect/Schema").optional<import("effect/Schema").toType<import("effect/Schema").$Record<import("effect/Schema").String, import("effect/Schema").Unknown>>>, import("effect/Schema").optionalKey<import("effect/Schema").$Record<import("effect/Schema").String, import("effect/Schema").Unknown>>, never, never>;
180
+ readonly type: import("effect/Schema").Literal<"model.updated">;
181
+ readonly location: import("effect/Schema").decodeTo<import("effect/Schema").optional<import("effect/Schema").toType<import("effect/Schema").Struct<{
182
+ readonly directory: import("effect/Schema").brand<import("effect/Schema").String, "AbsolutePath">;
183
+ readonly workspaceID: import("effect/Schema").decodeTo<import("effect/Schema").optional<import("effect/Schema").toType<import("effect/Schema").brand<import("effect/Schema").String, "Workspace.ID"> & {
184
+ ascending: (id?: string) => string & import("effect/Brand").Brand<"Workspace.ID">;
185
+ create: () => string & import("effect/Brand").Brand<"Workspace.ID">;
186
+ }>>, import("effect/Schema").optionalKey<import("effect/Schema").brand<import("effect/Schema").String, "Workspace.ID"> & {
187
+ ascending: (id?: string) => string & import("effect/Brand").Brand<"Workspace.ID">;
188
+ create: () => string & import("effect/Brand").Brand<"Workspace.ID">;
189
+ }>, never, never>;
190
+ }>>>, import("effect/Schema").optionalKey<import("effect/Schema").Struct<{
191
+ readonly directory: import("effect/Schema").brand<import("effect/Schema").String, "AbsolutePath">;
192
+ readonly workspaceID: import("effect/Schema").decodeTo<import("effect/Schema").optional<import("effect/Schema").toType<import("effect/Schema").brand<import("effect/Schema").String, "Workspace.ID"> & {
193
+ ascending: (id?: string) => string & import("effect/Brand").Brand<"Workspace.ID">;
194
+ create: () => string & import("effect/Brand").Brand<"Workspace.ID">;
195
+ }>>, import("effect/Schema").optionalKey<import("effect/Schema").brand<import("effect/Schema").String, "Workspace.ID"> & {
196
+ ascending: (id?: string) => string & import("effect/Brand").Brand<"Workspace.ID">;
197
+ create: () => string & import("effect/Brand").Brand<"Workspace.ID">;
198
+ }>, never, never>;
199
+ }>>, never, never>;
200
+ readonly data: import("effect/Schema").Struct<{}>;
201
+ }> & {
202
+ type: "model.updated";
171
203
  durability: "ephemeral";
172
204
  durable: undefined;
173
205
  data: import("effect/Schema").Struct<{}>;
@@ -789,7 +821,7 @@ export declare const Api: import("@opencode/protocol/api").Api<LocationMiddlewar
789
821
  };
790
822
  readonly created: import("effect/Schema").Finite;
791
823
  readonly metadata: import("effect/Schema").decodeTo<import("effect/Schema").optional<import("effect/Schema").toType<import("effect/Schema").$Record<import("effect/Schema").String, import("effect/Schema").Unknown>>>, import("effect/Schema").optionalKey<import("effect/Schema").$Record<import("effect/Schema").String, import("effect/Schema").Unknown>>, never, never>;
792
- readonly type: import("effect/Schema").Literal<"session.permissions.updated">;
824
+ readonly type: import("effect/Schema").Literal<"session.permissions">;
793
825
  readonly durable: import("effect/Schema").Struct<{
794
826
  readonly aggregateID: import("effect/Schema").String;
795
827
  readonly seq: import("effect/Schema").brand<import("effect/Schema").Int, "Event.Seq">;
@@ -826,7 +858,7 @@ export declare const Api: import("@opencode/protocol/api").Api<LocationMiddlewar
826
858
  };
827
859
  }>;
828
860
  }> & {
829
- type: "session.permissions.updated";
861
+ type: "session.permissions";
830
862
  durability: "durable";
831
863
  durable: {
832
864
  readonly version: number;
@@ -4541,7 +4573,6 @@ export declare const Api: import("@opencode/protocol/api").Api<LocationMiddlewar
4541
4573
  readonly time: import("effect/Schema").Struct<{
4542
4574
  readonly created: import("effect/Schema").Int;
4543
4575
  readonly updated: import("effect/Schema").Int;
4544
- readonly initialized: import("effect/Schema").decodeTo<import("effect/Schema").optional<import("effect/Schema").toType<import("effect/Schema").Int>>, import("effect/Schema").optionalKey<import("effect/Schema").Int>, never, never>;
4545
4576
  }>;
4546
4577
  readonly sandboxes: import("effect/Schema").$Array<import("effect/Schema").String>;
4547
4578
  }>;
@@ -4573,7 +4604,6 @@ export declare const Api: import("@opencode/protocol/api").Api<LocationMiddlewar
4573
4604
  readonly time: import("effect/Schema").Struct<{
4574
4605
  readonly created: import("effect/Schema").Int;
4575
4606
  readonly updated: import("effect/Schema").Int;
4576
- readonly initialized: import("effect/Schema").decodeTo<import("effect/Schema").optional<import("effect/Schema").toType<import("effect/Schema").Int>>, import("effect/Schema").optionalKey<import("effect/Schema").Int>, never, never>;
4577
4607
  }>;
4578
4608
  readonly sandboxes: import("effect/Schema").$Array<import("effect/Schema").String>;
4579
4609
  }>;
@@ -6156,13 +6186,6 @@ export declare const Api: import("@opencode/protocol/api").Api<LocationMiddlewar
6156
6186
  readonly type: import("effect/Schema").TemplateLiteral<readonly ["rpc.", import("effect/Schema").String]>;
6157
6187
  readonly location: import("effect/Schema").Struct<{
6158
6188
  readonly directory: import("effect/Schema").brand<import("effect/Schema").String, "AbsolutePath">;
6159
- readonly workspaceID: import("effect/Schema").decodeTo<import("effect/Schema").optional<import("effect/Schema").toType<import("effect/Schema").brand<import("effect/Schema").String, "Workspace.ID"> & {
6160
- ascending: (id?: string) => string & import("effect/Brand").Brand<"Workspace.ID">;
6161
- create: () => string & import("effect/Brand").Brand<"Workspace.ID">;
6162
- }>>, import("effect/Schema").optionalKey<import("effect/Schema").brand<import("effect/Schema").String, "Workspace.ID"> & {
6163
- ascending: (id?: string) => string & import("effect/Brand").Brand<"Workspace.ID">;
6164
- create: () => string & import("effect/Brand").Brand<"Workspace.ID">;
6165
- }>, never, never>;
6166
6189
  }>;
6167
6190
  readonly data: import("effect/Schema").$Record<import("effect/Schema").String, import("effect/Schema").Unknown>;
6168
6191
  }>, ...import("effect/Schema").Struct<{
@@ -6174,13 +6197,6 @@ export declare const Api: import("@opencode/protocol/api").Api<LocationMiddlewar
6174
6197
  readonly metadata: import("effect/Schema").optional<import("effect/Schema").$Record<import("effect/Schema").String, import("effect/Schema").Unknown>>;
6175
6198
  readonly location: import("effect/Schema").optional<import("effect/Schema").Struct<{
6176
6199
  readonly directory: import("effect/Schema").brand<import("effect/Schema").String, "AbsolutePath">;
6177
- readonly workspaceID: import("effect/Schema").decodeTo<import("effect/Schema").optional<import("effect/Schema").toType<import("effect/Schema").brand<import("effect/Schema").String, "Workspace.ID"> & {
6178
- ascending: (id?: string) => string & import("effect/Brand").Brand<"Workspace.ID">;
6179
- create: () => string & import("effect/Brand").Brand<"Workspace.ID">;
6180
- }>>, import("effect/Schema").optionalKey<import("effect/Schema").brand<import("effect/Schema").String, "Workspace.ID"> & {
6181
- ascending: (id?: string) => string & import("effect/Brand").Brand<"Workspace.ID">;
6182
- create: () => string & import("effect/Brand").Brand<"Workspace.ID">;
6183
- }>, never, never>;
6184
6200
  }>>;
6185
6201
  }>[]]>>, import("effect/Schema").Never, {
6186
6202
  readonly id: string & import("effect/Brand").Brand<"Event.ID">;
@@ -6199,8 +6215,6 @@ export declare const Api: import("@opencode/protocol/api").Api<LocationMiddlewar
6199
6215
  readonly data: {
6200
6216
  readonly form: {
6201
6217
  readonly id: string & import("effect/Brand").Brand<"Form.ID">;
6202
- readonly sessionID: string;
6203
- readonly title: string;
6204
6218
  readonly fields: readonly [{
6205
6219
  readonly type: "string";
6206
6220
  readonly key: string;
@@ -6380,6 +6394,8 @@ export declare const Api: import("@opencode/protocol/api").Api<LocationMiddlewar
6380
6394
  readonly description?: string | undefined;
6381
6395
  readonly title?: string | undefined;
6382
6396
  })[]];
6397
+ readonly sessionID: string;
6398
+ readonly title: string;
6383
6399
  readonly metadata?: {
6384
6400
  readonly [x: string]: unknown;
6385
6401
  } | undefined;
@@ -6466,6 +6482,30 @@ export declare const Api: import("@opencode/protocol/api").Api<LocationMiddlewar
6466
6482
  readonly metadata?: {
6467
6483
  readonly [x: string]: unknown;
6468
6484
  } | undefined;
6485
+ } | {
6486
+ readonly id: string & import("effect/Brand").Brand<"Event.ID">;
6487
+ readonly data: {};
6488
+ readonly type: "provider.updated";
6489
+ readonly created: number;
6490
+ readonly location?: {
6491
+ readonly directory: string & import("effect/Brand").Brand<"AbsolutePath">;
6492
+ readonly workspaceID?: (string & import("effect/Brand").Brand<"Workspace.ID">) | undefined;
6493
+ } | undefined;
6494
+ readonly metadata?: {
6495
+ readonly [x: string]: unknown;
6496
+ } | undefined;
6497
+ } | {
6498
+ readonly id: string & import("effect/Brand").Brand<"Event.ID">;
6499
+ readonly data: {};
6500
+ readonly type: "model.updated";
6501
+ readonly created: number;
6502
+ readonly location?: {
6503
+ readonly directory: string & import("effect/Brand").Brand<"AbsolutePath">;
6504
+ readonly workspaceID?: (string & import("effect/Brand").Brand<"Workspace.ID">) | undefined;
6505
+ } | undefined;
6506
+ readonly metadata?: {
6507
+ readonly [x: string]: unknown;
6508
+ } | undefined;
6469
6509
  } | {
6470
6510
  readonly id: string & import("effect/Brand").Brand<"Event.ID">;
6471
6511
  readonly data: {
@@ -6529,7 +6569,6 @@ export declare const Api: import("@opencode/protocol/api").Api<LocationMiddlewar
6529
6569
  readonly time: {
6530
6570
  readonly created: number;
6531
6571
  readonly updated: number;
6532
- readonly initialized?: number | undefined;
6533
6572
  };
6534
6573
  readonly sandboxes: readonly string[];
6535
6574
  readonly name?: string | undefined;
@@ -6765,7 +6804,7 @@ export declare const Api: import("@opencode/protocol/api").Api<LocationMiddlewar
6765
6804
  readonly seq: number & import("effect/Brand").Brand<"Event.Seq">;
6766
6805
  readonly version: number & import("effect/Brand").Brand<"Event.Version">;
6767
6806
  };
6768
- readonly type: "session.permissions.updated";
6807
+ readonly type: "session.permissions";
6769
6808
  readonly created: number;
6770
6809
  readonly location?: {
6771
6810
  readonly directory: string & import("effect/Brand").Brand<"AbsolutePath">;
@@ -7959,18 +7998,6 @@ export declare const Api: import("@opencode/protocol/api").Api<LocationMiddlewar
7959
7998
  readonly metadata?: {
7960
7999
  readonly [x: string]: unknown;
7961
8000
  } | undefined;
7962
- } | {
7963
- readonly id: string & import("effect/Brand").Brand<"Event.ID">;
7964
- readonly data: {};
7965
- readonly type: "catalog.updated";
7966
- readonly created: number;
7967
- readonly location?: {
7968
- readonly directory: string & import("effect/Brand").Brand<"AbsolutePath">;
7969
- readonly workspaceID?: (string & import("effect/Brand").Brand<"Workspace.ID">) | undefined;
7970
- } | undefined;
7971
- readonly metadata?: {
7972
- readonly [x: string]: unknown;
7973
- } | undefined;
7974
8001
  } | {
7975
8002
  readonly id: string & import("effect/Brand").Brand<"Event.ID">;
7976
8003
  readonly data: {};
@@ -8370,7 +8397,6 @@ export declare const Api: import("@opencode/protocol/api").Api<LocationMiddlewar
8370
8397
  readonly id: string & import("effect/Brand").Brand<"Event.ID">;
8371
8398
  readonly location: {
8372
8399
  readonly directory: string & import("effect/Brand").Brand<"AbsolutePath">;
8373
- readonly workspaceID?: (string & import("effect/Brand").Brand<"Workspace.ID">) | undefined;
8374
8400
  };
8375
8401
  readonly data: {
8376
8402
  readonly [x: string]: unknown;
@@ -8386,7 +8412,6 @@ export declare const Api: import("@opencode/protocol/api").Api<LocationMiddlewar
8386
8412
  readonly type: "server.connected";
8387
8413
  readonly location?: {
8388
8414
  readonly directory: string & import("effect/Brand").Brand<"AbsolutePath">;
8389
- readonly workspaceID?: (string & import("effect/Brand").Brand<"Workspace.ID">) | undefined;
8390
8415
  } | undefined;
8391
8416
  readonly metadata?: {
8392
8417
  readonly [x: string]: unknown;
@@ -3,16 +3,11 @@ import { ShellSelect } from "@opencode/core/shell/select";
3
3
  import { Effect } from "effect";
4
4
  import { HttpApiBuilder } from "effect/unstable/httpapi";
5
5
  import { Api } from "../api";
6
- export const ConfigHandler = HttpApiBuilder.group(Api, "server.config", (handlers) => handlers.handle("config.get", () => Config.Service.use((config) => config.entries())).handle("config.preferences", Effect.fn(function* () {
6
+ export const ConfigHandler = HttpApiBuilder.group(Api, "server.config", (handlers) => handlers.handle("config.get", () => Config.Service.use((config) => config.entries())).handle("config.update", Effect.fn(function* (ctx) {
7
7
  const config = yield* Config.Service;
8
- if (!config.preferences)
9
- return yield* Effect.die(Error("Config preferences are unavailable"));
10
- return yield* config.preferences().pipe(Effect.orDie);
11
- })).handle("config.updatePreferences", Effect.fn(function* (ctx) {
12
- const config = yield* Config.Service;
13
- if (!config.updatePreferences)
14
- return yield* Effect.die(Error("Config preference updates are unavailable"));
15
- return yield* config.updatePreferences(ctx.payload).pipe(Effect.orDie);
8
+ if (!config.update)
9
+ return yield* Effect.die(Error("Config updates are unavailable"));
10
+ return yield* config.update(ctx.payload).pipe(Effect.orDie);
16
11
  })).handle("config.shells", Effect.fn(function* () {
17
12
  const shell = yield* ShellSelect.Service;
18
13
  if (!shell.list)
@@ -1 +1,2 @@
1
- export declare const CredentialHandler: import("effect/Layer").Layer<import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.credential">, never, import("@opencode/protocol/middleware/schema-error").SchemaErrorMiddleware | import("@opencode/protocol/middleware/authorization").Authorization | import("../location").LocationMiddleware>;
1
+ import { Credential } from "@opencode/core/credential";
2
+ export declare const CredentialHandler: import("effect/Layer").Layer<import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.credential">, never, import("@opencode/protocol/middleware/schema-error").SchemaErrorMiddleware | import("@opencode/protocol/middleware/authorization").Authorization | import("effect/unstable/http/HttpRouter").Request<"Requires", Credential.Service>>;
@@ -1,14 +1,14 @@
1
- import { Integration } from "@opencode/core/integration";
1
+ import { Credential } from "@opencode/core/credential";
2
2
  import { Effect } from "effect";
3
3
  import { HttpApiBuilder, HttpApiSchema } from "effect/unstable/httpapi";
4
4
  import { Api } from "../api";
5
5
  export const CredentialHandler = HttpApiBuilder.group(Api, "server.credential", (handlers) => handlers.handle("credential.update", Effect.fn(function* (ctx) {
6
- yield* (yield* Integration.Service).connection.update(ctx.params.credentialID, { label: ctx.payload.label });
6
+ yield* (yield* Credential.Service).update(ctx.params.credentialID, { label: ctx.payload.label });
7
7
  return HttpApiSchema.NoContent.make();
8
8
  })).handle("credential.activate", Effect.fn(function* (ctx) {
9
- yield* (yield* Integration.Service).connection.activate(ctx.params.credentialID);
9
+ yield* (yield* Credential.Service).activate(ctx.params.credentialID);
10
10
  return HttpApiSchema.NoContent.make();
11
11
  })).handle("credential.remove", Effect.fn(function* (ctx) {
12
- yield* (yield* Integration.Service).connection.remove(ctx.params.credentialID);
12
+ yield* (yield* Credential.Service).remove(ctx.params.credentialID);
13
13
  return HttpApiSchema.NoContent.make();
14
14
  })));
@@ -1,4 +1 @@
1
- import { Instance } from "@opencode/core/instance/service";
2
- import { LocationServiceMap } from "@opencode/core/location-services";
3
- import { Session } from "@opencode/core/session";
4
- export declare const FormHandler: import("effect/Layer").Layer<import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.form">, never, LocationServiceMap.Service | Session.Service | Instance.Service | import("../location").LocationMiddleware | (import("@opencode/protocol/middleware/schema-error").SchemaErrorMiddleware | import("@opencode/protocol/middleware/authorization").Authorization | import("../middleware/form-location").FormLocationMiddleware)>;
1
+ export declare const FormHandler: import("effect/Layer").Layer<import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.form">, never, import("@opencode/protocol/middleware/schema-error").SchemaErrorMiddleware | import("@opencode/protocol/middleware/authorization").Authorization | import("../location").LocationMiddleware>;
@@ -1,61 +1,9 @@
1
1
  import { Form } from "@opencode/core/form";
2
- import { Instance } from "@opencode/core/instance/service";
3
- import { LocationServiceMap } from "@opencode/core/location-services";
4
- import { Session } from "@opencode/core/session";
5
- import {
6
- ConflictError,
7
- FormAlreadySettledError,
8
- FormInvalidAnswerError,
9
- FormNotFoundError,
10
- InvalidRequestError
11
- } from "@opencode/protocol/errors";
12
2
  import { Effect } from "effect";
13
- import { HttpApiBuilder, HttpApiSchema } from "effect/unstable/httpapi";
3
+ import { HttpApiBuilder } from "effect/unstable/httpapi";
14
4
  import { Api } from "../api";
15
- import { requestRef, response, sessionInfo } from "../location";
16
- function missingForm(id) {
17
- return new FormNotFoundError({ id, message: `Form not found: ${id}` });
18
- }
19
- export const FormHandler = HttpApiBuilder.group(Api, "server.form", (handlers) => Effect.gen(function* () {
20
- const locations = yield* LocationServiceMap.Service, instances = yield* Instance.Service, sessions = yield* Session.Service, requireOwnedForm = Effect.fnUntraced(function* (sessionID, formID) {
21
- const form = yield* Form.Service, info = yield* form.get(formID).pipe(Effect.catchTag("Form.NotFoundError", () => missingForm(formID)));
22
- if (info.sessionID !== sessionID)
23
- return yield* missingForm(formID);
24
- return { form, info };
25
- });
26
- return handlers.handle("form.request.list", Effect.fn(function* () {
27
- const form = yield* Form.Service;
28
- return yield* response(form.list());
29
- })).handle("session.form.list", Effect.fn(function* (ctx) {
30
- const session = ctx.params.sessionID === "global" ? void 0 : yield* sessionInfo(sessions, ctx.params.sessionID), read = Form.Service.use((form) => form.list({ sessionID: ctx.params.sessionID }));
31
- return { data: yield* session ? read.pipe(instances.provide(session)) : read.pipe(Effect.provide(locations.get(requestRef(ctx.request)))) };
32
- })).handle("session.form.create", Effect.fn(function* (ctx) {
33
- return { data: yield* (yield* Form.Service).create({
34
- id: ctx.payload.id,
35
- sessionID: ctx.params.sessionID,
36
- title: ctx.payload.title,
37
- metadata: ctx.payload.metadata,
38
- fields: ctx.payload.fields
39
- }).pipe(Effect.catchTags({
40
- "Form.AlreadyExistsError": (error) => new ConflictError({ resource: error.id, message: error.message }),
41
- "Form.InvalidFormError": (error) => new InvalidRequestError({ message: error.message, field: "fields" })
42
- })) };
43
- })).handle("session.form.get", Effect.fn(function* (ctx) {
44
- return { data: (yield* requireOwnedForm(ctx.params.sessionID, ctx.params.formID)).info };
45
- })).handle("session.form.state", Effect.fn(function* (ctx) {
46
- return { data: yield* (yield* requireOwnedForm(ctx.params.sessionID, ctx.params.formID)).form.state(ctx.params.formID).pipe(Effect.catchTag("Form.NotFoundError", () => missingForm(ctx.params.formID))) };
47
- })).handle("session.form.reply", Effect.fn(function* (ctx) {
48
- yield* (yield* requireOwnedForm(ctx.params.sessionID, ctx.params.formID)).form.reply({ id: ctx.params.formID, answer: ctx.payload.answer }).pipe(Effect.catchTags({
49
- "Form.AlreadySettledError": (error) => new FormAlreadySettledError({ id: error.id, message: error.message }),
50
- "Form.InvalidAnswerError": (error) => new FormInvalidAnswerError({ id: error.id, message: error.message }),
51
- "Form.NotFoundError": () => missingForm(ctx.params.formID)
52
- }));
53
- return HttpApiSchema.NoContent.make();
54
- })).handle("session.form.cancel", Effect.fn(function* (ctx) {
55
- yield* (yield* requireOwnedForm(ctx.params.sessionID, ctx.params.formID)).form.cancel(ctx.params.formID).pipe(Effect.catchTags({
56
- "Form.AlreadySettledError": (error) => new FormAlreadySettledError({ id: error.id, message: error.message }),
57
- "Form.NotFoundError": () => missingForm(ctx.params.formID)
58
- }));
59
- return HttpApiSchema.NoContent.make();
60
- }));
61
- }));
5
+ import { response } from "../location";
6
+ export const FormHandler = HttpApiBuilder.group(Api, "server.form", (handlers) => handlers.handle("form.list", Effect.fn(function* () {
7
+ const form = yield* Form.Service;
8
+ return yield* response(form.list());
9
+ })));
@@ -2,7 +2,12 @@ import { Integration } from "@opencode/core/integration";
2
2
  import { Effect } from "effect";
3
3
  import { HttpApiBuilder, HttpApiSchema } from "effect/unstable/httpapi";
4
4
  import { Api } from "../api";
5
- import { InvalidRequestError } from "@opencode/protocol/errors";
5
+ import {
6
+ IntegrationAttemptNotFoundError,
7
+ IntegrationMethodNotFoundError,
8
+ IntegrationNotFoundError,
9
+ InvalidRequestError
10
+ } from "@opencode/protocol/errors";
6
11
  import { response } from "../location";
7
12
  import { WellKnown } from "@opencode/core/wellknown";
8
13
  const authorize = (effect) => effect.pipe(Effect.mapError((error) => new InvalidRequestError({
@@ -14,8 +19,13 @@ export const IntegrationHandler = HttpApiBuilder.group(Api, "server.integration"
14
19
  const service = yield* Integration.Service;
15
20
  return yield* response(service.list());
16
21
  })).handle("integration.get", Effect.fn(function* (ctx) {
17
- const service = yield* Integration.Service;
18
- return yield* response(service.get(ctx.params.integrationID));
22
+ const integration = yield* (yield* Integration.Service).get(ctx.params.integrationID);
23
+ if (!integration)
24
+ return yield* new IntegrationNotFoundError({
25
+ integrationID: ctx.params.integrationID,
26
+ message: `Integration not found: ${ctx.params.integrationID}`
27
+ });
28
+ return yield* response(Effect.succeed(integration));
19
29
  })).handle("integration.wellknown.add", Effect.fn(function* (ctx) {
20
30
  const wellknown = yield* WellKnown.Service, integration = yield* Integration.Service;
21
31
  yield* wellknown.add(ctx.payload.url).pipe(Effect.mapError((error) => new InvalidRequestError({ message: error.message, kind: "well_known_discovery" })));
@@ -23,6 +33,11 @@ export const IntegrationHandler = HttpApiBuilder.group(Api, "server.integration"
23
33
  return HttpApiSchema.NoContent.make();
24
34
  })).handle("integration.connect.key", Effect.fn(function* (ctx) {
25
35
  const service = yield* Integration.Service;
36
+ if (!(yield* service.get(ctx.params.integrationID)))
37
+ return yield* new IntegrationNotFoundError({
38
+ integrationID: ctx.params.integrationID,
39
+ message: `Integration not found: ${ctx.params.integrationID}`
40
+ });
26
41
  yield* authorize(service.connection.key({
27
42
  integrationID: ctx.params.integrationID,
28
43
  key: ctx.payload.key,
@@ -40,19 +55,42 @@ export const IntegrationHandler = HttpApiBuilder.group(Api, "server.integration"
40
55
  })));
41
56
  })).handle("integration.oauth.status", Effect.fn(function* (ctx) {
42
57
  const service = yield* Integration.Service;
58
+ if (!(yield* service.get(ctx.params.integrationID)))
59
+ return yield* new IntegrationNotFoundError({
60
+ integrationID: ctx.params.integrationID,
61
+ message: `Integration not found: ${ctx.params.integrationID}`
62
+ });
43
63
  return yield* response(service.oauth.status({
44
64
  integrationID: ctx.params.integrationID,
45
65
  attemptID: ctx.params.attemptID
46
- }));
66
+ }).pipe(Effect.mapError((error) => new IntegrationAttemptNotFoundError({
67
+ integrationID: error.integrationID,
68
+ attemptID: error.attemptID,
69
+ message: `OAuth attempt not found: ${error.attemptID}`
70
+ }))));
47
71
  })).handle("integration.oauth.complete", Effect.fn(function* (ctx) {
48
- yield* (yield* Integration.Service).oauth.complete({
72
+ const service = yield* Integration.Service;
73
+ if (!(yield* service.get(ctx.params.integrationID)))
74
+ return yield* new IntegrationNotFoundError({
75
+ integrationID: ctx.params.integrationID,
76
+ message: `Integration not found: ${ctx.params.integrationID}`
77
+ });
78
+ yield* service.oauth.complete({
49
79
  integrationID: ctx.params.integrationID,
50
80
  attemptID: ctx.params.attemptID,
51
81
  code: ctx.payload.code
52
- }).pipe(Effect.mapError((error) => new InvalidRequestError({
53
- message: error._tag === "Integration.CodeRequired" ? "Authorization code is required" : "Authentication failed",
54
- kind: error._tag === "Integration.CodeRequired" ? "integration_code_required" : "integration_authorization"
55
- })));
82
+ }).pipe(Effect.mapError((error) => {
83
+ if (error._tag === "Integration.AttemptNotFound")
84
+ return new IntegrationAttemptNotFoundError({
85
+ integrationID: error.integrationID,
86
+ attemptID: error.attemptID,
87
+ message: `OAuth attempt not found: ${error.attemptID}`
88
+ });
89
+ return new InvalidRequestError({
90
+ message: error._tag === "Integration.CodeRequired" ? "Authorization code is required" : "Authentication failed",
91
+ kind: error._tag === "Integration.CodeRequired" ? "integration_code_required" : "integration_authorization"
92
+ });
93
+ }));
56
94
  return HttpApiSchema.NoContent.make();
57
95
  })).handle("integration.oauth.cancel", Effect.fn(function* (ctx) {
58
96
  yield* (yield* Integration.Service).oauth.cancel({
@@ -61,7 +99,18 @@ export const IntegrationHandler = HttpApiBuilder.group(Api, "server.integration"
61
99
  });
62
100
  return HttpApiSchema.NoContent.make();
63
101
  })).handle("integration.command.connect", Effect.fn(function* (ctx) {
64
- const service = yield* Integration.Service;
102
+ const service = yield* Integration.Service, integration = yield* service.get(ctx.params.integrationID);
103
+ if (!integration)
104
+ return yield* new IntegrationNotFoundError({
105
+ integrationID: ctx.params.integrationID,
106
+ message: `Integration not found: ${ctx.params.integrationID}`
107
+ });
108
+ if (!integration.methods.some((method) => method.type === "command" && method.id === ctx.payload.methodID))
109
+ return yield* new IntegrationMethodNotFoundError({
110
+ integrationID: ctx.params.integrationID,
111
+ methodID: ctx.payload.methodID,
112
+ message: `Integration method not found: ${ctx.payload.methodID}`
113
+ });
65
114
  return yield* response(authorize(service.command.connect({
66
115
  integrationID: ctx.params.integrationID,
67
116
  methodID: ctx.payload.methodID,
@@ -69,10 +118,19 @@ export const IntegrationHandler = HttpApiBuilder.group(Api, "server.integration"
69
118
  })));
70
119
  })).handle("integration.command.status", Effect.fn(function* (ctx) {
71
120
  const service = yield* Integration.Service;
121
+ if (!(yield* service.get(ctx.params.integrationID)))
122
+ return yield* new IntegrationNotFoundError({
123
+ integrationID: ctx.params.integrationID,
124
+ message: `Integration not found: ${ctx.params.integrationID}`
125
+ });
72
126
  return yield* response(service.command.status({
73
127
  integrationID: ctx.params.integrationID,
74
128
  attemptID: ctx.params.attemptID
75
- }));
129
+ }).pipe(Effect.mapError((error) => new IntegrationAttemptNotFoundError({
130
+ integrationID: error.integrationID,
131
+ attemptID: error.attemptID,
132
+ message: `Command attempt not found: ${error.attemptID}`
133
+ }))));
76
134
  })).handle("integration.command.cancel", Effect.fn(function* (ctx) {
77
135
  yield* (yield* Integration.Service).command.cancel({
78
136
  integrationID: ctx.params.integrationID,
@@ -6,7 +6,6 @@ export const LocationHandler = HttpApiBuilder.group(Api, "server.location", (han
6
6
  const location = yield* Location.Service;
7
7
  return new Location.Info({
8
8
  directory: location.directory,
9
- workspaceID: location.workspaceID,
10
9
  project: location.project
11
10
  });
12
11
  })));
@@ -1,14 +1,14 @@
1
- import { Catalog } from "@opencode/core/catalog";
1
+ import { Model } from "@opencode/core/model";
2
2
  import { Effect } from "effect";
3
3
  import { HttpApiBuilder } from "effect/unstable/httpapi";
4
4
  import { Api } from "../api";
5
5
  import { response } from "../location";
6
6
  export const ModelHandler = HttpApiBuilder.group(Api, "server.model", (handlers) => Effect.gen(function* () {
7
7
  return handlers.handle("model.list", Effect.fn(function* () {
8
- const catalog = yield* Catalog.Service;
9
- return yield* response(catalog.model.available());
8
+ const models = yield* Model.Service;
9
+ return yield* response(models.available());
10
10
  })).handle("model.default", Effect.fn(function* () {
11
- const catalog = yield* Catalog.Service;
12
- return yield* response(catalog.model.default());
11
+ const models = yield* Model.Service;
12
+ return yield* response(models.default());
13
13
  }));
14
14
  }));
@@ -1,4 +1,4 @@
1
1
  import { Instance } from "@opencode/core/instance/service";
2
2
  import { PermissionSaved } from "@opencode/core/permission/saved";
3
3
  import { Session } from "@opencode/core/session";
4
- export declare const PermissionHandler: import("effect/Layer").Layer<import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.permission">, never, import("@opencode/protocol/middleware/schema-error").SchemaErrorMiddleware | import("@opencode/protocol/middleware/authorization").Authorization | Session.Service | Instance.Service | import("../location").LocationMiddleware | import("../middleware/session-location").SessionLocationMiddleware | import("effect/unstable/http/HttpRouter").Request<"Requires", PermissionSaved.Service>>;
4
+ export declare const PermissionHandler: import("effect/Layer").Layer<import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.permission">, never, Session.Service | Instance.Service | import("../middleware/session-location").SessionLocationMiddleware | (import("@opencode/protocol/middleware/schema-error").SchemaErrorMiddleware | import("@opencode/protocol/middleware/authorization").Authorization | import("../location").LocationMiddleware | import("effect/unstable/http/HttpRouter").Request<"Requires", PermissionSaved.Service>)>;
@@ -41,10 +41,7 @@ export const PermissionHandler = HttpApiBuilder.group(Api, "server.permission",
41
41
  })).handle("session.permission.get", Effect.fn(function* (ctx) {
42
42
  return { data: (yield* requireOwnedRequest(ctx.params.sessionID, ctx.params.requestID)).request };
43
43
  })).handle("session.permission.reply", Effect.fn(function* (ctx) {
44
- yield* (yield* requireOwnedRequest(ctx.params.sessionID, ctx.params.requestID)).permission.reply({ requestID: ctx.params.requestID, reply: ctx.payload.reply, message: ctx.payload.message }).pipe(Effect.catchTag("Permission.NotFoundError", () => missingRequest(ctx.params.requestID)));
45
- return HttpApiSchema.NoContent.make();
46
- })).handle("session.permission.rules", Effect.fn(function* (ctx) {
47
- yield* sessions.setPermissions({ sessionID: ctx.params.sessionID, permissions: ctx.payload.permissions }).pipe(Effect.catchTag("Session.NotFoundError", missingSession));
44
+ yield* (yield* requireOwnedRequest(ctx.params.sessionID, ctx.params.requestID)).permission.reply({ requestID: ctx.params.requestID, reply: ctx.payload.decision, message: ctx.payload.message }).pipe(Effect.catchTag("Permission.NotFoundError", () => missingRequest(ctx.params.requestID)));
48
45
  return HttpApiSchema.NoContent.make();
49
46
  })).handle("permission.saved.list", Effect.fn(function* (ctx) {
50
47
  const location = yield* Location.Service;
@@ -7,10 +7,8 @@ import { Api } from "../api";
7
7
  import { response } from "../location";
8
8
  export const PluginHandler = HttpApiBuilder.group(Api, "server.plugin", (handlers) => handlers.handle("plugin.list", () => Effect.gen(function* () {
9
9
  return yield* response(Plugin.Service.use((plugin) => plugin.list()));
10
- })).handle("plugin.awaitActivation", () => Plugin.awaitActivation).handle("plugin.check", (ctx) => Effect.gen(function* () {
11
- const plugins = yield* Plugin.Service;
12
- yield* plugins.awaitActivation;
13
- const inventory = yield* plugins.list(), targets = [
10
+ })).handle("plugin.check", (ctx) => Effect.gen(function* () {
11
+ const inventory = yield* (yield* Plugin.Service).list(), targets = [
14
12
  ...new Set(inventory.flatMap((plugin) => plugin.source.type === "package" ? [plugin.source.target] : []))
15
13
  ].filter((target) => ctx.payload.target === void 0 || target === ctx.payload.target);
16
14
  if (ctx.payload.target !== void 0 && !targets.length)
@@ -34,9 +32,7 @@ export const PluginHandler = HttpApiBuilder.group(Api, "server.plugin", (handler
34
32
  };
35
33
  })));
36
34
  })).handle("plugin.update", (ctx) => Effect.gen(function* () {
37
- const plugins = yield* Plugin.Service;
38
- yield* plugins.awaitActivation;
39
- const inventory = new Set((yield* plugins.list()).flatMap((plugin) => plugin.source.type === "package" ? [plugin.source.target] : [])), unknown = ctx.payload.targets.filter((target) => !inventory.has(target));
35
+ const plugins = yield* Plugin.Service, inventory = new Set((yield* plugins.list()).flatMap((plugin) => plugin.source.type === "package" ? [plugin.source.target] : [])), unknown = ctx.payload.targets.filter((target) => !inventory.has(target));
40
36
  if (unknown.length)
41
37
  return yield* new InvalidRequestError({
42
38
  message: `Plugin packages are not in the current server inventory: ${unknown.join(", ")}`,
@@ -1,2 +1,2 @@
1
1
  import { Project } from "@opencode/core/project";
2
- export declare const ProjectHandler: import("effect/Layer").Layer<import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.project">, never, import("effect/unstable/http/HttpRouter").Request<"Requires", Project.Service> | (import("@opencode/protocol/middleware/schema-error").SchemaErrorMiddleware | import("@opencode/protocol/middleware/authorization").Authorization | import("../location").LocationMiddleware)>;
2
+ export declare const ProjectHandler: import("effect/Layer").Layer<import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.project">, never, import("@opencode/protocol/middleware/schema-error").SchemaErrorMiddleware | import("@opencode/protocol/middleware/authorization").Authorization | import("../location").LocationMiddleware | import("effect/unstable/http/HttpRouter").Request<"Requires", Project.Service>>;
@@ -1,4 +1,3 @@
1
- import { Location } from "@opencode/core/location";
2
1
  import { Project } from "@opencode/core/project";
3
2
  import { Effect } from "effect";
4
3
  import { HttpApiBuilder } from "effect/unstable/httpapi";
@@ -7,8 +6,4 @@ import { ProjectNotFoundError } from "@opencode/protocol/errors";
7
6
  export const ProjectHandler = HttpApiBuilder.group(Api, "server.project", (handlers) => handlers.handle("project.list", () => Project.Service.use((project) => project.list())).handle("project.update", (ctx) => Project.Service.use((project) => project.update({ ...ctx.payload, projectID: ctx.params.projectID }).pipe(Effect.mapError(() => new ProjectNotFoundError({
8
7
  projectID: ctx.params.projectID,
9
8
  message: `Project not found: ${ctx.params.projectID}`
10
- }))))).handle("project.current", () => Location.Service.use((location) => Effect.succeed({
11
- id: location.project.id,
12
- directory: location.project.directory,
13
- canonical: location.project.canonical
14
- }))));
9
+ }))))));
@@ -1,4 +1,4 @@
1
- import { Catalog } from "@opencode/core/catalog";
1
+ import { Provider } from "@opencode/core/provider";
2
2
  import { ProviderNotFoundError } from "@opencode/protocol/errors";
3
3
  import { Effect } from "effect";
4
4
  import { HttpApiBuilder } from "effect/unstable/httpapi";
@@ -6,10 +6,10 @@ import { Api } from "../api";
6
6
  import { response } from "../location";
7
7
  export const ProviderHandler = HttpApiBuilder.group(Api, "server.provider", (handlers) => Effect.gen(function* () {
8
8
  return handlers.handle("provider.list", Effect.fn(function* () {
9
- const catalog = yield* Catalog.Service;
10
- return yield* response(catalog.provider.available());
9
+ const providers = yield* Provider.Service;
10
+ return yield* response(providers.available());
11
11
  })).handle("provider.get", Effect.fn(function* (ctx) {
12
- const provider = yield* (yield* Catalog.Service).provider.get(ctx.params.providerID);
12
+ const provider = yield* (yield* Provider.Service).get(ctx.params.providerID);
13
13
  if (!provider)
14
14
  return yield* new ProviderNotFoundError({
15
15
  providerID: ctx.params.providerID,
@@ -2,6 +2,11 @@ import { Effect } from "effect";
2
2
  import { HttpApiBuilder } from "effect/unstable/httpapi";
3
3
  import { Api } from "../api";
4
4
  import { ServerInfo } from "../server-info";
5
- export const ServerHandler = HttpApiBuilder.group(Api, "server.server", (handlers) => handlers.handle("server.get", () => Effect.gen(function* () {
6
- return { urls: (yield* ServerInfo.Service).urls() };
5
+ export const ServerHandler = HttpApiBuilder.group(Api, "server.server", (handlers) => handlers.handle("server.status", () => Effect.gen(function* () {
6
+ const info = yield* ServerInfo.Service;
7
+ return {
8
+ version: info.app.version ?? "unknown",
9
+ pid: process.pid ?? 0,
10
+ urls: info.urls()
11
+ };
7
12
  })));
@@ -1,3 +1,3 @@
1
1
  import { Session } from "@opencode/core/session";
2
2
  import { SessionTransfer } from "@opencode/core/session/transfer";
3
- export declare const SessionHandler: import("effect/Layer").Layer<import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.session">, never, Session.Service | import("../middleware/session-location").SessionLocationMiddleware | SessionTransfer.Service | import("effect/unstable/http/HttpRouter").Request<"Requires", import("@opencode/core/database/database").Service> | (import("@opencode/protocol/middleware/schema-error").SchemaErrorMiddleware | import("@opencode/protocol/middleware/authorization").Authorization)>;
3
+ export declare const SessionHandler: import("effect/Layer").Layer<import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.session">, never, Session.Service | import("../middleware/session-location").SessionLocationMiddleware | SessionTransfer.Service | import("effect/unstable/http/HttpRouter").Request<"Requires", import("@opencode/core/database/database").Service> | (import("@opencode/protocol/middleware/schema-error").SchemaErrorMiddleware | import("@opencode/protocol/middleware/authorization").Authorization | import("../middleware/form-location").FormLocationMiddleware)>;
@@ -3,6 +3,7 @@ import { SessionStats } from "@opencode/core/session/stats";
3
3
  import { SessionTitle } from "@opencode/core/session/title";
4
4
  import { SessionTransfer } from "@opencode/core/session/transfer";
5
5
  import { InstructionEntry } from "@opencode/core/session/instruction-entry";
6
+ import { Form } from "@opencode/core/form";
6
7
  import { DateTime, Effect, Stream } from "effect";
7
8
  import { HttpApiBuilder, HttpApiSchema } from "effect/unstable/httpapi";
8
9
  import { Api } from "../api";
@@ -11,6 +12,9 @@ import {
11
12
  ConflictError,
12
13
  CommandExecutionError,
13
14
  CommandNotFoundError,
15
+ FormAlreadySettledError,
16
+ FormInvalidAnswerError,
17
+ FormNotFoundError,
14
18
  InvalidRequestError,
15
19
  InvalidCursorError,
16
20
  MessageNotFoundError,
@@ -21,15 +25,22 @@ import {
21
25
  import { AbsolutePath } from "@opencode/core/schema";
22
26
  import { failedMessageDecode, failedSnapshot, missingMessage, missingSession } from "./session-error";
23
27
  const DefaultSessionsLimit = 50;
28
+ function missingForm(id) {
29
+ return new FormNotFoundError({ id, message: `Form not found: ${id}` });
30
+ }
24
31
  export const SessionHandler = HttpApiBuilder.group(Api, "server.session", (handlers) => Effect.gen(function* () {
25
- const session = yield* Session.Service, transfer = yield* SessionTransfer.Service, busySession = (error) => new SessionBusyError({
32
+ const session = yield* Session.Service, transfer = yield* SessionTransfer.Service, requireOwnedForm = Effect.fnUntraced(function* (sessionID, formID) {
33
+ const form = yield* Form.Service, info = yield* form.get(formID).pipe(Effect.catchTag("Form.NotFoundError", () => missingForm(formID)));
34
+ if (info.sessionID !== sessionID)
35
+ return yield* missingForm(formID);
36
+ return { form, info };
37
+ }), busySession = (error) => new SessionBusyError({
26
38
  sessionID: error.sessionID,
27
39
  message: `Session is busy: ${error.sessionID}`
28
40
  }), pendingMutation = (effect, conflict) => effect.pipe(Effect.catchTag("Session.NotFoundError", missingSession), Effect.catchTag("Session.InboxConflictError", (error) => new ConflictError({ resource: error.inboxID, message: `${conflict}: ${error.inboxID}` })), Effect.as(HttpApiSchema.NoContent.make()));
29
41
  return handlers.handle("session.list", Effect.fn(function* (ctx) {
30
42
  const query = ctx.query.cursor !== void 0 ? yield* SessionsCursor.parse(ctx.query.cursor).pipe(Effect.mapError(() => new InvalidCursorError({ message: "Invalid cursor" }))) : ctx.query, sessions = (yield* session.list({
31
43
  ...query,
32
- workspaceID: query.workspace,
33
44
  limit: ctx.query.limit ?? DefaultSessionsLimit
34
45
  })).data, first = sessions[0], last = sessions.at(-1);
35
46
  return {
@@ -104,7 +115,7 @@ export const SessionHandler = HttpApiBuilder.group(Api, "server.session", (handl
104
115
  data: yield* session.get(ctx.params.sessionID).pipe(Effect.catchTag("Session.NotFoundError", missingSession))
105
116
  };
106
117
  })).handle("session.view", Effect.fn(function* (ctx) {
107
- yield* session.view({ sessionID: ctx.params.sessionID, idle: ctx.payload.idle }).pipe(Effect.catchTag("Session.NotFoundError", missingSession));
118
+ yield* session.view({ sessionID: ctx.params.sessionID, idle: DateTime.toEpochMillis(ctx.payload.idle) }).pipe(Effect.catchTag("Session.NotFoundError", missingSession));
108
119
  return HttpApiSchema.NoContent.make();
109
120
  })).handle("session.remove", Effect.fn(function* (ctx) {
110
121
  yield* session.remove(ctx.params.sessionID).pipe(Effect.catchTag("Session.NotFoundError", missingSession));
@@ -114,7 +125,7 @@ export const SessionHandler = HttpApiBuilder.group(Api, "server.session", (handl
114
125
  return HttpApiSchema.NoContent.make();
115
126
  })).handle("session.fork", Effect.fn(function* (ctx) {
116
127
  return {
117
- data: yield* session.fork({ sessionID: ctx.params.sessionID, boundary: ctx.payload.boundary }).pipe(Effect.catchTag("Session.NotFoundError", missingSession), Effect.catchTag("Session.MessageNotFoundError", missingMessage), Effect.catchTag("Session.ForkEmptyError", (error) => new InvalidRequestError({ message: error.message, kind: "empty_session" })))
128
+ data: yield* session.fork({ sessionID: ctx.params.sessionID, before: ctx.payload.before }).pipe(Effect.catchTag("Session.NotFoundError", missingSession), Effect.catchTag("Session.MessageNotFoundError", missingMessage), Effect.catchTag("Session.ForkEmptyError", (error) => new InvalidRequestError({ message: error.message, kind: "empty_session" })))
118
129
  };
119
130
  })).handle("session.switchAgent", Effect.fn(function* (ctx) {
120
131
  yield* session.switchAgent({ sessionID: ctx.params.sessionID, agent: ctx.payload.agent }).pipe(Effect.catchTag("Session.NotFoundError", missingSession));
@@ -122,18 +133,20 @@ export const SessionHandler = HttpApiBuilder.group(Api, "server.session", (handl
122
133
  })).handle("session.switchModel", Effect.fn(function* (ctx) {
123
134
  yield* session.switchModel({ sessionID: ctx.params.sessionID, model: ctx.payload.model }).pipe(Effect.catchTag("Session.NotFoundError", missingSession));
124
135
  return HttpApiSchema.NoContent.make();
125
- })).handle("session.rename", Effect.fn(function* (ctx) {
126
- if (ctx.payload.title) {
127
- yield* session.rename({ sessionID: ctx.params.sessionID, title: ctx.payload.title }).pipe(Effect.catchTag("Session.NotFoundError", missingSession));
128
- return HttpApiSchema.NoContent.make();
129
- }
130
- yield* (yield* SessionTitle.Service).generate(ctx.params.sessionID);
136
+ })).handle("session.update", Effect.fn(function* (ctx) {
137
+ yield* session.get(ctx.params.sessionID).pipe(Effect.catchTag("Session.NotFoundError", missingSession));
138
+ if (ctx.payload.title !== void 0)
139
+ if (ctx.payload.title)
140
+ yield* session.rename({ sessionID: ctx.params.sessionID, title: ctx.payload.title }).pipe(Effect.catchTag("Session.NotFoundError", missingSession));
141
+ else
142
+ yield* (yield* SessionTitle.Service).generate(ctx.params.sessionID);
143
+ if (ctx.payload.permissions !== void 0)
144
+ yield* session.setPermissions({ sessionID: ctx.params.sessionID, permissions: ctx.payload.permissions }).pipe(Effect.catchTag("Session.NotFoundError", missingSession));
131
145
  return HttpApiSchema.NoContent.make();
132
146
  })).handle("session.move", Effect.fn(function* (ctx) {
133
147
  yield* session.move({
134
148
  sessionID: ctx.params.sessionID,
135
149
  directory: ctx.payload.directory,
136
- workspaceID: ctx.payload.workspaceID,
137
150
  delivery: ctx.payload.delivery
138
151
  }).pipe(Effect.catchTag("Session.NotFoundError", missingSession), Effect.catchTag("Session.DestinationNotFoundError", (error) => Effect.fail(new InvalidRequestError({ message: `Directory does not exist: ${error.directory}` }))), Effect.catchTag("Session.DestinationNotDirectoryError", (error) => Effect.fail(new InvalidRequestError({ message: `Not a directory: ${error.directory}` }))), Effect.catchTag("Session.DestinationUnavailableError", (error) => Effect.fail(new InvalidRequestError({ message: `Directory is unavailable: ${error.directory}` }))));
139
152
  return HttpApiSchema.NoContent.make();
@@ -157,7 +170,7 @@ export const SessionHandler = HttpApiBuilder.group(Api, "server.session", (handl
157
170
  })).handle("session.command", Effect.fn(function* (ctx) {
158
171
  yield* session.command({
159
172
  sessionID: ctx.params.sessionID,
160
- command: ctx.payload.command,
173
+ command: ctx.payload.name,
161
174
  text: ctx.payload.text,
162
175
  files: ctx.payload.files,
163
176
  agents: ctx.payload.agents,
@@ -174,8 +187,7 @@ export const SessionHandler = HttpApiBuilder.group(Api, "server.session", (handl
174
187
  })).handle("session.skill", Effect.fn(function* (ctx) {
175
188
  yield* session.skill({
176
189
  sessionID: ctx.params.sessionID,
177
- id: ctx.payload.id,
178
- skill: ctx.payload.skill,
190
+ skill: ctx.payload.id,
179
191
  resume: ctx.payload.resume
180
192
  }).pipe(Effect.catchTag("Session.NotFoundError", missingSession), Effect.catchTag("Session.SkillNotFoundError", (error) => Effect.fail(new SkillNotFoundError({ skill: error.skill, message: `Skill not found: ${error.skill}` }))));
181
193
  return HttpApiSchema.NoContent.make();
@@ -235,11 +247,10 @@ export const SessionHandler = HttpApiBuilder.group(Api, "server.session", (handl
235
247
  data: yield* session.inbox(ctx.params.sessionID).pipe(Effect.catchTag("Session.NotFoundError", missingSession))
236
248
  };
237
249
  })).handle("session.inbox.cancel", Effect.fn(function* (ctx) {
238
- return yield* pendingMutation(session.cancelInbox({ sessionID: ctx.params.sessionID, inboxID: ctx.params.inboxID }), "Pending input can no longer be cancelled");
239
- })).handle("session.inbox.steer", Effect.fn(function* (ctx) {
240
- return yield* pendingMutation(session.steerInbox({ sessionID: ctx.params.sessionID, inboxID: ctx.params.inboxID }), "Pending input is no longer queued");
241
- })).handle("session.inbox.queue", Effect.fn(function* (ctx) {
242
- return yield* pendingMutation(session.queueInbox({ sessionID: ctx.params.sessionID, inboxID: ctx.params.inboxID }), "Pending input is no longer a steer");
250
+ yield* session.cancelInbox({ sessionID: ctx.params.sessionID, inboxID: ctx.params.inboxID }).pipe(Effect.catchTag("Session.NotFoundError", missingSession), Effect.catchTag("Session.InboxConflictError", () => Effect.void));
251
+ return HttpApiSchema.NoContent.make();
252
+ })).handle("session.inbox.update", Effect.fn(function* (ctx) {
253
+ return yield* pendingMutation(ctx.payload.delivery === "steer" ? session.steerInbox({ sessionID: ctx.params.sessionID, inboxID: ctx.params.inboxID }) : session.queueInbox({ sessionID: ctx.params.sessionID, inboxID: ctx.params.inboxID }), `Pending input cannot change to ${ctx.payload.delivery}`);
243
254
  })).handle("session.instructions.entry.list", Effect.fn(function* (ctx) {
244
255
  return { data: yield* (yield* InstructionEntry.Service).list(ctx.params.sessionID) };
245
256
  })).handle("session.instructions.entry.put", Effect.fn(function* (ctx) {
@@ -254,7 +265,7 @@ export const SessionHandler = HttpApiBuilder.group(Api, "server.session", (handl
254
265
  yield* session.get(ctx.params.sessionID).pipe(Effect.catchTag("Session.NotFoundError", missingSession));
255
266
  return session.log({ sessionID: ctx.params.sessionID, after: ctx.query.after, follow: ctx.query.follow }).pipe(Stream.orDie);
256
267
  })).handle("session.interrupt", Effect.fn(function* (ctx) {
257
- return { interrupted: yield* session.interrupt(ctx.params.sessionID, { continue: ctx.query.continue }) };
268
+ return { interrupted: yield* session.interrupt(ctx.params.sessionID, { resume: ctx.query.resume }) };
258
269
  })).handle("session.background", Effect.fn(function* (ctx) {
259
270
  yield* session.background(ctx.params.sessionID).pipe(Effect.catchTag("Session.NotFoundError", missingSession));
260
271
  return HttpApiSchema.NoContent.make();
@@ -268,5 +279,34 @@ export const SessionHandler = HttpApiBuilder.group(Api, "server.session", (handl
268
279
  messageID: ctx.params.messageID,
269
280
  message: `Message not found: ${ctx.params.messageID}`
270
281
  });
282
+ })).handle("session.form.list", Effect.fn(function* (ctx) {
283
+ return { data: yield* (yield* Form.Service).list({ sessionID: ctx.params.sessionID }) };
284
+ })).handle("session.form.create", Effect.fn(function* (ctx) {
285
+ return { data: yield* (yield* Form.Service).create({
286
+ id: ctx.payload.id,
287
+ sessionID: ctx.params.sessionID,
288
+ title: ctx.payload.title,
289
+ metadata: ctx.payload.metadata,
290
+ fields: ctx.payload.fields
291
+ }).pipe(Effect.catchTags({
292
+ "Form.AlreadyExistsError": (error) => new ConflictError({ resource: error.id, message: error.message }),
293
+ "Form.InvalidFormError": (error) => new InvalidRequestError({ message: error.message, field: "fields" })
294
+ })) };
295
+ })).handle("session.form.get", Effect.fn(function* (ctx) {
296
+ const owned = yield* requireOwnedForm(ctx.params.sessionID, ctx.params.formID), state = yield* owned.form.state(ctx.params.formID).pipe(Effect.catchTag("Form.NotFoundError", () => missingForm(ctx.params.formID)));
297
+ return { data: { ...owned.info, state } };
298
+ })).handle("session.form.reply", Effect.fn(function* (ctx) {
299
+ yield* (yield* requireOwnedForm(ctx.params.sessionID, ctx.params.formID)).form.reply({ id: ctx.params.formID, answer: ctx.payload.answer }).pipe(Effect.catchTags({
300
+ "Form.AlreadySettledError": (error) => new FormAlreadySettledError({ id: error.id, message: error.message }),
301
+ "Form.InvalidAnswerError": (error) => new FormInvalidAnswerError({ id: error.id, message: error.message }),
302
+ "Form.NotFoundError": () => missingForm(ctx.params.formID)
303
+ }));
304
+ return HttpApiSchema.NoContent.make();
305
+ })).handle("session.form.cancel", Effect.fn(function* (ctx) {
306
+ yield* (yield* requireOwnedForm(ctx.params.sessionID, ctx.params.formID)).form.cancel(ctx.params.formID).pipe(Effect.catchTags({
307
+ "Form.AlreadySettledError": (error) => new FormAlreadySettledError({ id: error.id, message: error.message }),
308
+ "Form.NotFoundError": () => missingForm(ctx.params.formID)
309
+ }));
310
+ return HttpApiSchema.NoContent.make();
271
311
  }));
272
312
  }));
@@ -15,14 +15,11 @@ export const ShellHandler = HttpApiBuilder.group(Api, "server.shell", (handlers)
15
15
  })).handle("shell.get", Effect.fn(function* (ctx) {
16
16
  const shell = yield* Shell.Service;
17
17
  return yield* response(shell.get(ctx.params.id).pipe(Effect.catchTag("Shell.NotFoundError", () => new ShellNotFoundError({ id: ctx.params.id, message: `Shell command not found: ${ctx.params.id}` }))));
18
- })).handle("shell.timeout", Effect.fn(function* (ctx) {
19
- const shell = yield* Shell.Service;
20
- return yield* response(shell.timeout(ctx.params.id, ctx.payload.timeout).pipe(Effect.catchTag("Shell.NotFoundError", () => new ShellNotFoundError({ id: ctx.params.id, message: `Shell command not found: ${ctx.params.id}` }))));
21
18
  })).handle("shell.output", Effect.fn(function* (ctx) {
22
19
  const shell = yield* Shell.Service;
23
20
  return yield* response(shell.output(ctx.params.id, { cursor: ctx.query.cursor, limit: ctx.query.limit }).pipe(Effect.catchTag("Shell.NotFoundError", () => new ShellNotFoundError({ id: ctx.params.id, message: `Shell command not found: ${ctx.params.id}` }))));
24
21
  })).handle("shell.remove", Effect.fn(function* (ctx) {
25
- yield* (yield* Shell.Service).remove(ctx.params.id).pipe(Effect.catchTag("Shell.NotFoundError", () => new ShellNotFoundError({ id: ctx.params.id, message: `Shell command not found: ${ctx.params.id}` })));
22
+ yield* (yield* Shell.Service).remove(ctx.params.id).pipe(Effect.catchTag("Shell.NotFoundError", () => Effect.void));
26
23
  return HttpApiSchema.NoContent.make();
27
24
  }));
28
25
  }));
@@ -11,7 +11,7 @@ export const VcsHandler = HttpApiBuilder.group(Api, "server.vcs", (handlers) =>
11
11
  return yield* (yield* Vcs.Service).base().pipe(Effect.mapError((error) => new ServiceUnavailableError({ service: "vcs", message: error.message })));
12
12
  }))).handle("vcs.status", () => response(Effect.gen(function* () {
13
13
  return yield* (yield* Vcs.Service).status();
14
- }))).handle("vcs.branches", (ctx) => response(Effect.gen(function* () {
14
+ }))).handle("vcs.branch.list", (ctx) => response(Effect.gen(function* () {
15
15
  return yield* (yield* Vcs.Service).branches({ search: ctx.query.search, limit: Math.min(ctx.query.limit ?? 50, 100) });
16
16
  }))).handle("vcs.diff", (ctx) => response(Effect.gen(function* () {
17
17
  return yield* (yield* Vcs.Service).diff(ctx.query.mode, { context: ctx.query.context, base: ctx.query.base }).pipe(Effect.mapError((error) => new ServiceUnavailableError({ service: "vcs", message: error.message })));
@@ -1 +1,2 @@
1
- export declare const WorktreeHandler: import("effect/Layer").Layer<import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.worktree">, never, import("@opencode/protocol/middleware/schema-error").SchemaErrorMiddleware | import("@opencode/protocol/middleware/authorization").Authorization | import("../location").LocationMiddleware>;
1
+ import { Worktree } from "@opencode/core/worktree";
2
+ export declare const WorktreeHandler: import("effect/Layer").Layer<import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.worktree">, never, import("@opencode/protocol/middleware/schema-error").SchemaErrorMiddleware | import("@opencode/protocol/middleware/authorization").Authorization | Worktree.Service>;
@@ -1,20 +1,17 @@
1
1
  import { Git } from "@opencode/core/git";
2
2
  import { Worktree } from "@opencode/core/worktree";
3
- import { Plugin } from "@opencode/core/plugin";
3
+ import { Project } from "@opencode/core/project";
4
+ import { ProjectNotFoundError } from "@opencode/protocol/errors";
4
5
  import { WorktreeError } from "@opencode/protocol/groups/worktree";
5
6
  import { Effect } from "effect";
6
7
  import { HttpApiBuilder, HttpApiSchema } from "effect/unstable/httpapi";
7
8
  import { Api } from "../api";
8
- export const WorktreeHandler = HttpApiBuilder.group(Api, "server.worktree", (handlers) => handlers.handle("worktree.list", () => run((worktrees) => worktrees.list())).handle("worktree.create", (ctx) => run((worktrees) => worktrees.create(ctx.payload))).handle("worktree.remove", (ctx) => run((worktrees) => worktrees.remove(ctx.payload)).pipe(Effect.as(HttpApiSchema.NoContent.make()))).handle("worktree.refresh", () => run((worktrees) => worktrees.refresh()).pipe(Effect.as(HttpApiSchema.NoContent.make()))));
9
- function run(action) {
10
- return Effect.gen(function* () {
11
- const plugins = yield* Plugin.Service, worktrees = yield* Worktree.Service;
12
- yield* plugins.awaitActivation;
13
- return yield* action(worktrees);
14
- }).pipe(badRequest);
15
- }
9
+ export const WorktreeHandler = HttpApiBuilder.group(Api, "server.worktree", (handlers) => Effect.gen(function* () {
10
+ const worktrees = yield* Worktree.Service;
11
+ return handlers.handle("worktree.list", (ctx) => worktrees.list(ctx.query).pipe(Effect.catchTag("Project.NotFoundError", missingProject))).handle("worktree.create", (ctx) => worktrees.create(ctx.payload).pipe(badRequest)).handle("worktree.remove", (ctx) => worktrees.remove(ctx.payload).pipe(badRequest, Effect.as(HttpApiSchema.NoContent.make()))).handle("worktree.refresh", (ctx) => worktrees.refresh(ctx.payload).pipe(badRequest, Effect.as(HttpApiSchema.NoContent.make())));
12
+ }));
16
13
  function badRequest(effect) {
17
- return effect.pipe(Effect.mapError((error) => new WorktreeError({
14
+ return effect.pipe(Effect.catchTag("Project.NotFoundError", missingProject), Effect.mapError((error) => error instanceof ProjectNotFoundError ? error : new WorktreeError({
18
15
  name: "WorktreeError",
19
16
  data: {
20
17
  message: message(error),
@@ -22,6 +19,9 @@ function badRequest(effect) {
22
19
  }
23
20
  })));
24
21
  }
22
+ function missingProject(error) {
23
+ return Effect.fail(new ProjectNotFoundError({ projectID: error.projectID, message: `Project not found: ${error.projectID}` }));
24
+ }
25
25
  function message(error) {
26
26
  if (error instanceof Worktree.SourceDirectoryNotFoundError)
27
27
  return error.directory ? `Worktree source not found: ${error.directory}` : `Worktree source not found for project: ${error.projectID}`;
@@ -33,7 +33,5 @@ function message(error) {
33
33
  return `Invalid worktree directory: ${error.directory}`;
34
34
  if (error instanceof Worktree.StrategyUnavailableError)
35
35
  return `Worktree strategy unavailable: ${error.strategy}`;
36
- if (error instanceof Worktree.UnsupportedLocationError)
37
- return "Worktree operations only support local locations";
38
36
  return error.message;
39
37
  }
@@ -1,2 +1,2 @@
1
1
  import { Layer } from "effect";
2
- export declare const handlers: Layer.Layer<import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.generate"> | import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.message"> | import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.model"> | import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.provider"> | import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.session"> | import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.permission"> | import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.fs"> | import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.form"> | import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.command"> | import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.skill"> | import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.rpc"> | import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.event"> | import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.agent"> | import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.plugin"> | import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.health"> | import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.server"> | import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.debug"> | import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.pty"> | import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.experimental"> | import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.shell"> | import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.reference"> | import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.location"> | import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.integration"> | import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.websearch"> | import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.mcp"> | import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.credential"> | import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.project"> | import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.worktree"> | import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.vcs"> | import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.migration"> | import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.config"> | import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.workspace">, never, import("@opencode/protocol/middleware/schema-error").SchemaErrorMiddleware | import("@opencode/protocol/middleware/authorization").Authorization | import("@opencode/util/global").Service | import("@opencode/core/bus").Service | import("@opencode/core/workspace").Service | import("@opencode/core/persistent-pty/index").Service | import("@opencode/core/location-service-map").Service | import("@opencode/core/session").Service | import("@opencode/core/instance").Service | import("./location").LocationMiddleware | import("./middleware/form-location").FormLocationMiddleware | import("./middleware/session-location").SessionLocationMiddleware | import("@opencode/core/pty/ticket").Service | import("@opencode/core/session/transfer").Service | import("effect/unstable/http/HttpRouter").Request<"Requires", import("@opencode/core/database/database").Service> | import("effect/unstable/http/HttpRouter").Request<"Requires", import("@opencode/core/permission/saved").Service> | import("effect/unstable/http/HttpRouter").Request<"Requires", import("@opencode/core/plugin/update").Service> | import("effect/unstable/http/HttpRouter").Request<"Requires", import("./server-info").Service> | import("./pty-environment").Service | import("effect/unstable/http/HttpRouter").Request<"Requires", import("@opencode/core/wellknown").Service> | import("effect/unstable/http/HttpRouter").Request<"Requires", import("@opencode/core/project").Service>>;
2
+ export declare const handlers: Layer.Layer<import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.generate"> | import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.message"> | import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.model"> | import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.provider"> | import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.session"> | import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.permission"> | import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.fs"> | import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.form"> | import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.command"> | import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.skill"> | import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.rpc"> | import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.event"> | import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.agent"> | import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.plugin"> | import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.server"> | import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.debug"> | import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.pty"> | import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.experimental"> | import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.shell"> | import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.reference"> | import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.location"> | import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.integration"> | import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.websearch"> | import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.mcp"> | import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.credential"> | import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.project"> | import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.worktree"> | import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.vcs"> | import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.migration"> | import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.config">, never, import("@opencode/protocol/middleware/schema-error").SchemaErrorMiddleware | import("@opencode/protocol/middleware/authorization").Authorization | import("@opencode/util/global").Service | import("@opencode/core/bus").Service | import("@opencode/core/persistent-pty/index").Service | import("@opencode/core/location-service-map").Service | import("@opencode/core/session").Service | import("@opencode/core/instance").Service | import("@opencode/core/worktree").Service | import("./location").LocationMiddleware | import("./middleware/form-location").FormLocationMiddleware | import("./middleware/session-location").SessionLocationMiddleware | import("@opencode/core/pty/ticket").Service | import("@opencode/core/session/transfer").Service | import("effect/unstable/http/HttpRouter").Request<"Requires", import("@opencode/core/database/database").Service> | import("effect/unstable/http/HttpRouter").Request<"Requires", import("@opencode/core/permission/saved").Service> | import("effect/unstable/http/HttpRouter").Request<"Requires", import("@opencode/core/plugin/update").Service> | import("effect/unstable/http/HttpRouter").Request<"Requires", import("./server-info").Service> | import("./pty-environment").Service | import("effect/unstable/http/HttpRouter").Request<"Requires", import("@opencode/core/wellknown").Service> | import("effect/unstable/http/HttpRouter").Request<"Requires", import("@opencode/core/credential").Service> | import("effect/unstable/http/HttpRouter").Request<"Requires", import("@opencode/core/project").Service>>;
package/dist/handlers.js CHANGED
@@ -13,7 +13,6 @@ import { RpcHandler } from "./handlers/rpc";
13
13
  import { EventHandler } from "./handlers/event";
14
14
  import { AgentHandler } from "./handlers/agent";
15
15
  import { PluginHandler } from "./handlers/plugin";
16
- import { HealthHandler } from "./handlers/health";
17
16
  import { ServerHandler } from "./handlers/server";
18
17
  import { DebugHandler } from "./handlers/debug";
19
18
  import { PtyHandler } from "./handlers/pty";
@@ -31,5 +30,4 @@ import { VcsHandler } from "./handlers/vcs";
31
30
  import { EventFeed } from "./event-feed";
32
31
  import { MigrationHandler } from "./handlers/migration";
33
32
  import { ConfigHandler } from "./handlers/config";
34
- import { WorkspaceHandler } from "./handlers/workspace";
35
- export const handlers = Layer.mergeAll(HealthHandler, ServerHandler, DebugHandler, MigrationHandler, LocationHandler, AgentHandler, PluginHandler, SessionHandler, MessageHandler, ModelHandler, GenerateHandler, ProviderHandler, IntegrationHandler, WebSearchHandler, McpHandler, CredentialHandler, ProjectHandler, FormHandler, PermissionHandler, FileSystemHandler, CommandHandler, SkillHandler, RpcHandler, EventHandler.pipe(Layer.provide(EventFeed.layer)), PtyHandler, PersistentPtyHandler, ShellHandler, ReferenceHandler, WorktreeHandler, WorkspaceHandler, VcsHandler, ConfigHandler);
33
+ export const handlers = Layer.mergeAll(ServerHandler, DebugHandler, MigrationHandler, LocationHandler, AgentHandler, PluginHandler, SessionHandler, MessageHandler, ModelHandler, GenerateHandler, ProviderHandler, IntegrationHandler, WebSearchHandler, McpHandler, CredentialHandler, ProjectHandler, FormHandler, PermissionHandler, FileSystemHandler, CommandHandler, SkillHandler, RpcHandler, EventHandler.pipe(Layer.provide(EventFeed.layer)), PtyHandler, PersistentPtyHandler, ShellHandler, ReferenceHandler, WorktreeHandler, VcsHandler, ConfigHandler);
package/dist/location.js CHANGED
@@ -2,7 +2,6 @@ import { Location } from "@opencode/core/location";
2
2
  import { LocationServiceMap } from "@opencode/core/location-services";
3
3
  import { AbsolutePath } from "@opencode/core/schema";
4
4
  import { Session } from "@opencode/core/session";
5
- import { Workspace } from "@opencode/core/workspace";
6
5
  import { InvalidRequestError } from "@opencode/protocol/errors";
7
6
  import { Effect, Layer, Schema } from "effect";
8
7
  import { HttpServerRequest } from "effect/unstable/http";
@@ -17,7 +16,6 @@ export function response(data) {
17
16
  return {
18
17
  location: new Location.Info({
19
18
  directory: location.directory,
20
- workspaceID: location.workspaceID,
21
19
  project: location.project
22
20
  }),
23
21
  data: yield* data
@@ -30,10 +28,9 @@ export const sessionInfo = Effect.fnUntraced(function* (sessions, sessionID) {
30
28
  return yield* sessions.get(id).pipe(Effect.catchTag("Session.NotFoundError", missingSession));
31
29
  });
32
30
  export function requestRef(request) {
33
- const query = new URL(request.url, "http://localhost").searchParams, workspaceID = query.get("location[workspace]") || request.headers["x-opencode-workspace"], directory = query.get("location[directory]") || (request.headers["x-opencode-directory"] ? decode(request.headers["x-opencode-directory"]) : process.cwd());
31
+ const directory = new URL(request.url, "http://localhost").searchParams.get("location[directory]") || (request.headers["x-opencode-directory"] ? decode(request.headers["x-opencode-directory"]) : process.cwd());
34
32
  return Location.Ref.make({
35
- directory: AbsolutePath.make(directory),
36
- workspaceID: workspaceID ? Workspace.ID.make(workspaceID) : void 0
33
+ directory: AbsolutePath.make(directory)
37
34
  });
38
35
  }
39
36
  function decode(input) {
package/dist/process.js CHANGED
@@ -27,8 +27,14 @@ export const start = Effect.fn("ServerProcess.start")(function* (options, lifecy
27
27
  const password = options.password;
28
28
  if (!password)
29
29
  return yield* Effect.fail(Error("Missing server password"));
30
- const hostname = options.hostname ?? "127.0.0.1", port = Option.fromNullishOr(options.port), shutdown = yield* Latch.make(), status = yield* Status.make(), bound = yield* listen({ hostname, port }), application = yield* Ref.make(Option.none());
31
- yield* bound.http.serve(dispatch(password, status, application, options.app?.version ?? "unknown").pipe(HttpMiddleware.cors({ allowedOrigins: (origin) => isAllowedCorsOrigin(origin, options), maxAge: 86400 })), errorResponseLogger).pipe(withoutParentSpan);
30
+ const hostname = options.hostname ?? "127.0.0.1", port = Option.fromNullishOr(options.port), shutdown = yield* Latch.make(), status = yield* Status.make(), bound = yield* listen({ hostname, port }), urls = () => {
31
+ const address = bound.server.address();
32
+ if (address === null || typeof address === "string")
33
+ return [];
34
+ const host = address.family === "IPv6" ? `[${address.address}]` : address.address;
35
+ return ServerInfo.connectionURLs(`http://${host}:${address.port}`, hostname);
36
+ }, application = yield* Ref.make(Option.none());
37
+ yield* bound.http.serve(dispatch(password, status, application, options.app?.version ?? "unknown", urls).pipe(HttpMiddleware.cors({ allowedOrigins: (origin) => isAllowedCorsOrigin(origin, options), maxAge: 86400 })), errorResponseLogger).pipe(withoutParentSpan);
32
38
  if (lifecycle)
33
39
  yield* lifecycle.onListen(bound.http.address, shutdown.open.pipe(Effect.asVoid)).pipe(Effect.flatMap((cleanup) => Effect.addFinalizer(() => Scope.close(bound.scope, Exit.void).pipe(Effect.andThen(cleanup)))), Effect.uninterruptible);
34
40
  const parentScope = yield* Scope.Scope, applicationScope = yield* Scope.fork(parentScope);
@@ -37,13 +43,7 @@ export const start = Effect.fn("ServerProcess.start")(function* (options, lifecy
37
43
  const context = yield* Layer.buildWithScope(createRoutes({
38
44
  ...options,
39
45
  password
40
- }, () => {
41
- const address = bound.server.address();
42
- if (address === null || typeof address === "string")
43
- return [];
44
- const host = address.family === "IPv6" ? `[${address.address}]` : address.address;
45
- return ServerInfo.connectionURLs(`http://${host}:${address.port}`, hostname);
46
- }).pipe(Layer.provideMerge(NodeHttpServer.layerHttpServices)), applicationScope);
46
+ }, urls).pipe(Layer.provideMerge(NodeHttpServer.layerHttpServices)), applicationScope);
47
47
  if (lifecycle)
48
48
  yield* installRestartContinuity(Context.get(context, SessionRestart.Service)).pipe(Effect.provideService(Scope.Scope, applicationScope));
49
49
  const app = Context.get(context, HttpRouter.HttpRouter).asHttpEffect().pipe(HttpMiddleware.compression(), Effect.provideService(HttpPlatform.HttpPlatform, Context.get(context, HttpPlatform.HttpPlatform)));
@@ -87,14 +87,14 @@ function addressInUse(error) {
87
87
  const cause = error.cause;
88
88
  return typeof cause === "object" && cause !== null && "code" in cause && cause.code === "EADDRINUSE";
89
89
  }
90
- function dispatch(password, status, application, version) {
90
+ function dispatch(password, status, application, version, urls) {
91
91
  const auth = ServerAuth.Config.of({ password: Option.some(password), username: "opencode" });
92
92
  return Effect.gen(function* () {
93
93
  const request = yield* HttpServerRequest.HttpServerRequest, url = new URL(request.url, "http://localhost");
94
- if (request.method === "GET" && url.pathname === "/api/health") {
94
+ if (request.method === "GET" && url.pathname === "/api/status") {
95
95
  if (!(yield* authorizedRequest(request, auth)))
96
96
  return unauthorized();
97
- return yield* healthResponse(status, version);
97
+ return yield* statusResponse(status, version, urls);
98
98
  }
99
99
  const state = yield* status.current, app = yield* Ref.get(application), ready = state.type === "ready" && Option.isSome(app);
100
100
  if ((!ready || !hasPtyConnectTicketURL(url) && !hasPersistentPtyConnectTicketURL(url)) && !(yield* authorizedRequest(request, auth)))
@@ -110,9 +110,9 @@ function unauthorized() {
110
110
  headers: { "www-authenticate": 'Basic realm="Secure Area"' }
111
111
  });
112
112
  }
113
- const healthResponse = Effect.fnUntraced(function* (status, version) {
113
+ const statusResponse = Effect.fnUntraced(function* (status, version, urls) {
114
114
  const state = yield* status.current;
115
- return HttpServerResponse.jsonUnsafe({ healthy: !0, version, pid: process.pid }, {
115
+ return HttpServerResponse.jsonUnsafe({ version, pid: process.pid, urls: urls() }, {
116
116
  status: state.type === "ready" ? 200 : state.type === "failed" ? 500 : 503,
117
117
  headers: state.type === "starting" || state.type === "stopping" ? { "retry-after": "1" } : void 0
118
118
  });
package/dist/routes.d.ts CHANGED
@@ -7,6 +7,7 @@ import { PermissionSaved } from "@opencode/core/permission/saved";
7
7
  import { PtyTicket } from "@opencode/core/pty/ticket";
8
8
  import { PersistentPty } from "@opencode/core/persistent-pty";
9
9
  import { Project } from "@opencode/core/project";
10
+ import { Worktree } from "@opencode/core/worktree";
10
11
  import { Session } from "@opencode/core/session";
11
12
  import { Instance } from "@opencode/core/instance/service";
12
13
  import { SessionTransfer } from "@opencode/core/session/transfer";
@@ -23,7 +24,7 @@ import { HttpRouter } from "effect/unstable/http";
23
24
  import { Layer } from "effect";
24
25
  import { PtyEnvironment } from "./pty-environment";
25
26
  import type { ServerOptions } from "./options";
26
- export declare function createRoutes(options?: ServerOptions, serviceURLs?: () => ReadonlyArray<string>, overrides?: LayerNode.Replacements): Layer.Layer<Global.Service | Database.Service | Bus.Service | import("effect/unstable/http/HttpClient").HttpClient | Project.Service | Credential.Service | WellKnown.Service | Workspace.Service | PermissionSaved.Service | PersistentPty.Service | LocationServiceMap.Service | SdkPlugins.Service | PluginUpdate.Service | Session.Service | Job.Service | Instance.Service | SessionRestart.Service | PtyTicket.Service | SessionTransfer.Service | LocationActivity.Service | PtyEnvironment.Service | HttpRouter.HttpRouter, Error | PersistentPty.UnavailableError, import("effect/FileSystem").FileSystem | import("effect/Path").Path | import("effect/unstable/http/Etag").Generator | import("effect/unstable/http/HttpPlatform").HttpPlatform>;
27
+ export declare function createRoutes(options?: ServerOptions, serviceURLs?: () => ReadonlyArray<string>, overrides?: LayerNode.Replacements): Layer.Layer<Global.Service | Database.Service | Bus.Service | import("effect/unstable/http/HttpClient").HttpClient | Project.Service | Credential.Service | WellKnown.Service | Workspace.Service | PermissionSaved.Service | PersistentPty.Service | LocationServiceMap.Service | SdkPlugins.Service | PluginUpdate.Service | Session.Service | Job.Service | Instance.Service | Worktree.Service | SessionRestart.Service | PtyTicket.Service | SessionTransfer.Service | LocationActivity.Service | PtyEnvironment.Service | HttpRouter.HttpRouter, Error | PersistentPty.UnavailableError, import("effect/FileSystem").FileSystem | import("effect/Path").Path | import("effect/unstable/http/Etag").Generator | import("effect/unstable/http/HttpPlatform").HttpPlatform>;
27
28
  type InstanceNode = (replacements: () => LayerNode.Replacements) => LayerNode.Provider<Instance.Service, never, typeof Node.tags.values.global>;
28
- export declare function createEmbeddedRoutes(options?: ServerOptions, overrides?: LayerNode.Replacements, instances?: InstanceNode): Layer.Layer<Global.Service | Database.Service | Bus.Service | import("effect/unstable/http/HttpClient").HttpClient | Project.Service | Credential.Service | WellKnown.Service | Workspace.Service | PermissionSaved.Service | PersistentPty.Service | LocationServiceMap.Service | SdkPlugins.Service | PluginUpdate.Service | Session.Service | Job.Service | Instance.Service | SessionRestart.Service | PtyTicket.Service | SessionTransfer.Service | LocationActivity.Service | PtyEnvironment.Service | HttpRouter.HttpRouter, Error | PersistentPty.UnavailableError, import("effect/FileSystem").FileSystem | import("effect/Path").Path | import("effect/unstable/http/Etag").Generator | import("effect/unstable/http/HttpPlatform").HttpPlatform>;
29
+ export declare function createEmbeddedRoutes(options?: ServerOptions, overrides?: LayerNode.Replacements, instances?: InstanceNode): Layer.Layer<Global.Service | Database.Service | Bus.Service | import("effect/unstable/http/HttpClient").HttpClient | Project.Service | Credential.Service | WellKnown.Service | Workspace.Service | PermissionSaved.Service | PersistentPty.Service | LocationServiceMap.Service | SdkPlugins.Service | PluginUpdate.Service | Session.Service | Job.Service | Instance.Service | Worktree.Service | SessionRestart.Service | PtyTicket.Service | SessionTransfer.Service | LocationActivity.Service | PtyEnvironment.Service | HttpRouter.HttpRouter, Error | PersistentPty.UnavailableError, import("effect/FileSystem").FileSystem | import("effect/Path").Path | import("effect/unstable/http/Etag").Generator | import("effect/unstable/http/HttpPlatform").HttpPlatform>;
29
30
  export {};
package/dist/routes.js CHANGED
@@ -14,6 +14,7 @@ import { PermissionSaved } from "@opencode/core/permission/saved";
14
14
  import { PtyTicket } from "@opencode/core/pty/ticket";
15
15
  import { PersistentPty } from "@opencode/core/persistent-pty";
16
16
  import { Project } from "@opencode/core/project";
17
+ import { Worktree } from "@opencode/core/worktree";
17
18
  import { Session } from "@opencode/core/session";
18
19
  import { Instance } from "@opencode/core/instance/service";
19
20
  import { SessionTransfer } from "@opencode/core/session/transfer";
@@ -53,6 +54,7 @@ const applicationServiceNodes = [
53
54
  httpClient,
54
55
  Job.node,
55
56
  Project.node,
57
+ Worktree.node,
56
58
  Session.node,
57
59
  Instance.node,
58
60
  SessionTransfer.node,
@@ -110,7 +112,7 @@ function makeRoutes(auth, options, serviceURLs, overrides, instances) {
110
112
  const { simulationReplacements } = yield* Effect.promise(() => import("@opencode/simulation/backend")), simulation = yield* simulationReplacements({ version: App.make(options.app).version });
111
113
  return build([...overrides, ...simulation]);
112
114
  })) : build(overrides)).pipe(Layer.flatMap((context) => {
113
- const services = Layer.succeedContext(context), requestServices = Layer.merge(Layer.succeedContext(Context.pick(Database.Service, PermissionSaved.Service, PluginUpdate.Service, Project.Service, WellKnown.Service)(context)), ServerInfo.layer(serviceURLs, options.app)), api = HttpApiBuilder.layer(Api, { openapiPath: "/openapi.json" }).pipe(Layer.provide(handlers.pipe(Layer.provide(services), Layer.provide(Layer.succeed(CorsConfig, options)))), Layer.provide(formLocationLayer), Layer.provide(sessionLocationLayer), Layer.provide(layer), Layer.provide(authorizationLayer), Layer.provide(schemaErrorLayer), Layer.provide(auth), HttpRouter.provideRequest(requestServices), Layer.provideMerge(services), Layer.provideMerge(HttpRouter.layer));
115
+ const services = Layer.succeedContext(context), requestServices = Layer.merge(Layer.succeedContext(Context.pick(Database.Service, Credential.Service, PermissionSaved.Service, PluginUpdate.Service, Project.Service, WellKnown.Service)(context)), ServerInfo.layer(serviceURLs, options.app)), api = HttpApiBuilder.layer(Api, { openapiPath: "/openapi.json" }).pipe(Layer.provide(handlers.pipe(Layer.provide(services), Layer.provide(Layer.succeed(CorsConfig, options)))), Layer.provide(formLocationLayer), Layer.provide(sessionLocationLayer), Layer.provide(layer), Layer.provide(authorizationLayer), Layer.provide(schemaErrorLayer), Layer.provide(auth), HttpRouter.provideRequest(requestServices), Layer.provideMerge(services), Layer.provideMerge(HttpRouter.layer));
114
116
  return Layer.merge(api, V1Migration.layer.pipe(Layer.provide(services)));
115
117
  }));
116
118
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
3
  "name": "@opencode/server",
4
- "version": "2.0.3",
4
+ "version": "2.0.5",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "repository": {
@@ -30,17 +30,17 @@
30
30
  "dependencies": {
31
31
  "@effect/platform-node": "4.0.0-rc.112",
32
32
  "@effect/platform-node-shared": "4.0.0-rc.112",
33
- "@opencode/core": "2.0.3",
34
- "@opencode/protocol": "2.0.3",
35
- "@opencode/schema": "2.0.3",
36
- "@opencode/simulation": "2.0.3",
37
- "@opencode/util": "2.0.3",
33
+ "@opencode/core": "2.0.5",
34
+ "@opencode/protocol": "2.0.5",
35
+ "@opencode/schema": "2.0.5",
36
+ "@opencode/simulation": "2.0.5",
37
+ "@opencode/util": "2.0.5",
38
38
  "drizzle-orm": "1.0.0-rc.5-169397b",
39
39
  "effect": "4.0.0-rc.112"
40
40
  },
41
41
  "devDependencies": {
42
- "@opencode/client": "2.0.3",
43
- "@opencode/plugin": "2.0.3",
42
+ "@opencode/client": "2.0.5",
43
+ "@opencode/plugin": "2.0.5",
44
44
  "@tsconfig/bun": "1.0.9",
45
45
  "@types/bun": "1.4.0",
46
46
  "@typescript/native-preview": "7.0.0-dev.20251207.1"
@@ -1,2 +0,0 @@
1
- import { ServerInfo } from "../server-info";
2
- export declare const HealthHandler: import("effect/Layer").Layer<import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.health">, never, import("@opencode/protocol/middleware/schema-error").SchemaErrorMiddleware | import("@opencode/protocol/middleware/authorization").Authorization | import("effect/unstable/http/HttpRouter").Request<"Requires", ServerInfo.Service>>;
@@ -1,11 +0,0 @@
1
- import { Effect } from "effect";
2
- import { HttpApiBuilder } from "effect/unstable/httpapi";
3
- import { Api } from "../api";
4
- import { ServerInfo } from "../server-info";
5
- export const HealthHandler = HttpApiBuilder.group(Api, "server.health", (handlers) => handlers.handle("health.get", () => Effect.gen(function* () {
6
- return {
7
- healthy: !0,
8
- version: (yield* ServerInfo.Service).app.version ?? "unknown",
9
- pid: process.pid ?? 0
10
- };
11
- })));
@@ -1,2 +0,0 @@
1
- import { Workspace } from "@opencode/core/workspace";
2
- export declare const WorkspaceHandler: import("effect/Layer").Layer<import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.workspace">, never, import("@opencode/protocol/middleware/schema-error").SchemaErrorMiddleware | import("@opencode/protocol/middleware/authorization").Authorization | Workspace.Service>;
@@ -1,20 +0,0 @@
1
- import { Workspace } from "@opencode/core/workspace";
2
- import { ConflictError, ProviderNotFoundError, UnknownError } from "@opencode/protocol/errors";
3
- import { Effect } from "effect";
4
- import { HttpApiBuilder } from "effect/unstable/httpapi";
5
- import { Api } from "../api";
6
- export const WorkspaceHandler = HttpApiBuilder.group(Api, "server.workspace", (handlers) => Effect.gen(function* () {
7
- const workspace = yield* Workspace.Service;
8
- return handlers.handle("workspace.create", (ctx) => workspace.create(ctx.payload).pipe(Effect.map((workspaceID) => ({ data: workspaceID })), Effect.catchTags({
9
- "Workspace.CreateConflict": (error) => new ConflictError({
10
- resource: error.workspaceID,
11
- message: `Workspace ${error.workspaceID} already uses provider ${error.existingProvider}, not ${error.provider}`
12
- }),
13
- "WorkspaceDriver.ProviderNotFound": (error) => new ProviderNotFoundError({
14
- providerID: error.provider,
15
- message: `Workspace provider not found: ${error.provider}`
16
- })
17
- }))).handle("workspace.destroy", (ctx) => workspace.destroy(ctx.params.workspaceID).pipe(Effect.mapError((error) => new UnknownError({
18
- message: error._tag === "WorkspaceDriver.ProviderNotFound" ? `Workspace provider not found: ${error.provider}` : error.message ?? "Workspace provider failed to destroy the workspace"
19
- }))));
20
- }));