@opencode-ai/server 0.0.0-dev-18198 → 0.0.0-dev-18202

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
@@ -4469,6 +4469,150 @@ export declare const Api: import("@opencode-ai/protocol/api").Api<LocationMiddle
4469
4469
  ascending: (id?: string) => string & import("effect/Brand").Brand<"PtyID">;
4470
4470
  };
4471
4471
  }>;
4472
+ }, import("effect/Schema").Struct<{
4473
+ readonly id: import("effect/Schema").brand<import("effect/Schema").String, "Event.ID"> & {
4474
+ create: () => string & import("effect/Brand").Brand<"Event.ID">;
4475
+ };
4476
+ readonly created: import("effect/Schema").Finite;
4477
+ 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>;
4478
+ readonly type: import("effect/Schema").Literal<"persistent-pty.added">;
4479
+ readonly location: import("effect/Schema").decodeTo<import("effect/Schema").optional<import("effect/Schema").toType<import("effect/Schema").Struct<{
4480
+ readonly directory: import("effect/Schema").brand<import("effect/Schema").String, "AbsolutePath">;
4481
+ readonly workspaceID: import("effect/Schema").decodeTo<import("effect/Schema").optional<import("effect/Schema").toType<import("effect/Schema").brand<import("effect/Schema").String, "Workspace.ID"> & {
4482
+ ascending: (id?: string) => string & import("effect/Brand").Brand<"Workspace.ID">;
4483
+ create: () => string & import("effect/Brand").Brand<"Workspace.ID">;
4484
+ }>>, import("effect/Schema").optionalKey<import("effect/Schema").brand<import("effect/Schema").String, "Workspace.ID"> & {
4485
+ ascending: (id?: string) => string & import("effect/Brand").Brand<"Workspace.ID">;
4486
+ create: () => string & import("effect/Brand").Brand<"Workspace.ID">;
4487
+ }>, never, never>;
4488
+ }>>>, import("effect/Schema").optionalKey<import("effect/Schema").Struct<{
4489
+ readonly directory: import("effect/Schema").brand<import("effect/Schema").String, "AbsolutePath">;
4490
+ readonly workspaceID: import("effect/Schema").decodeTo<import("effect/Schema").optional<import("effect/Schema").toType<import("effect/Schema").brand<import("effect/Schema").String, "Workspace.ID"> & {
4491
+ ascending: (id?: string) => string & import("effect/Brand").Brand<"Workspace.ID">;
4492
+ create: () => string & import("effect/Brand").Brand<"Workspace.ID">;
4493
+ }>>, import("effect/Schema").optionalKey<import("effect/Schema").brand<import("effect/Schema").String, "Workspace.ID"> & {
4494
+ ascending: (id?: string) => string & import("effect/Brand").Brand<"Workspace.ID">;
4495
+ create: () => string & import("effect/Brand").Brand<"Workspace.ID">;
4496
+ }>, never, never>;
4497
+ }>>, never, never>;
4498
+ readonly data: import("effect/Schema").Struct<{
4499
+ readonly sessionID: import("effect/Schema").brand<import("effect/Schema").String, "SessionID"> & {
4500
+ create: () => string & import("effect/Brand").Brand<"SessionID">;
4501
+ descending: (id?: string) => string & import("effect/Brand").Brand<"SessionID">;
4502
+ };
4503
+ readonly terminal: import("effect/Schema").Struct<{
4504
+ readonly sessionID: import("effect/Schema").brand<import("effect/Schema").String, "SessionID"> & {
4505
+ create: () => string & import("effect/Brand").Brand<"SessionID">;
4506
+ descending: (id?: string) => string & import("effect/Brand").Brand<"SessionID">;
4507
+ };
4508
+ readonly foregroundProcess: import("effect/Schema").NullOr<import("effect/Schema").String>;
4509
+ readonly size: import("effect/Schema").Struct<{
4510
+ readonly cols: import("effect/Schema").Int;
4511
+ readonly rows: import("effect/Schema").Int;
4512
+ }>;
4513
+ readonly output: import("effect/Schema").Struct<{
4514
+ readonly head: import("effect/Schema").Int;
4515
+ readonly tail: import("effect/Schema").Int;
4516
+ }>;
4517
+ readonly id: import("effect/Schema").brand<import("effect/Schema").String, "PtyID"> & {
4518
+ create: () => string & import("effect/Brand").Brand<"PtyID">;
4519
+ ascending: (id?: string) => string & import("effect/Brand").Brand<"PtyID">;
4520
+ };
4521
+ readonly title: import("effect/Schema").String;
4522
+ readonly command: import("effect/Schema").String;
4523
+ readonly args: import("effect/Schema").$Array<import("effect/Schema").String>;
4524
+ readonly cwd: import("effect/Schema").String;
4525
+ readonly status: import("effect/Schema").Literals<readonly ["running", "exited"]>;
4526
+ readonly pid: import("effect/Schema").Int;
4527
+ readonly exitCode: 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>;
4528
+ }>;
4529
+ }>;
4530
+ }> & {
4531
+ type: "persistent-pty.added";
4532
+ durability: "ephemeral";
4533
+ durable: undefined;
4534
+ data: import("effect/Schema").Struct<{
4535
+ readonly sessionID: import("effect/Schema").brand<import("effect/Schema").String, "SessionID"> & {
4536
+ create: () => string & import("effect/Brand").Brand<"SessionID">;
4537
+ descending: (id?: string) => string & import("effect/Brand").Brand<"SessionID">;
4538
+ };
4539
+ readonly terminal: import("effect/Schema").Struct<{
4540
+ readonly sessionID: import("effect/Schema").brand<import("effect/Schema").String, "SessionID"> & {
4541
+ create: () => string & import("effect/Brand").Brand<"SessionID">;
4542
+ descending: (id?: string) => string & import("effect/Brand").Brand<"SessionID">;
4543
+ };
4544
+ readonly foregroundProcess: import("effect/Schema").NullOr<import("effect/Schema").String>;
4545
+ readonly size: import("effect/Schema").Struct<{
4546
+ readonly cols: import("effect/Schema").Int;
4547
+ readonly rows: import("effect/Schema").Int;
4548
+ }>;
4549
+ readonly output: import("effect/Schema").Struct<{
4550
+ readonly head: import("effect/Schema").Int;
4551
+ readonly tail: import("effect/Schema").Int;
4552
+ }>;
4553
+ readonly id: import("effect/Schema").brand<import("effect/Schema").String, "PtyID"> & {
4554
+ create: () => string & import("effect/Brand").Brand<"PtyID">;
4555
+ ascending: (id?: string) => string & import("effect/Brand").Brand<"PtyID">;
4556
+ };
4557
+ readonly title: import("effect/Schema").String;
4558
+ readonly command: import("effect/Schema").String;
4559
+ readonly args: import("effect/Schema").$Array<import("effect/Schema").String>;
4560
+ readonly cwd: import("effect/Schema").String;
4561
+ readonly status: import("effect/Schema").Literals<readonly ["running", "exited"]>;
4562
+ readonly pid: import("effect/Schema").Int;
4563
+ readonly exitCode: 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>;
4564
+ }>;
4565
+ }>;
4566
+ }, import("effect/Schema").Struct<{
4567
+ readonly id: import("effect/Schema").brand<import("effect/Schema").String, "Event.ID"> & {
4568
+ create: () => string & import("effect/Brand").Brand<"Event.ID">;
4569
+ };
4570
+ readonly created: import("effect/Schema").Finite;
4571
+ 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>;
4572
+ readonly type: import("effect/Schema").Literal<"persistent-pty.removed">;
4573
+ readonly location: import("effect/Schema").decodeTo<import("effect/Schema").optional<import("effect/Schema").toType<import("effect/Schema").Struct<{
4574
+ readonly directory: import("effect/Schema").brand<import("effect/Schema").String, "AbsolutePath">;
4575
+ readonly workspaceID: import("effect/Schema").decodeTo<import("effect/Schema").optional<import("effect/Schema").toType<import("effect/Schema").brand<import("effect/Schema").String, "Workspace.ID"> & {
4576
+ ascending: (id?: string) => string & import("effect/Brand").Brand<"Workspace.ID">;
4577
+ create: () => string & import("effect/Brand").Brand<"Workspace.ID">;
4578
+ }>>, import("effect/Schema").optionalKey<import("effect/Schema").brand<import("effect/Schema").String, "Workspace.ID"> & {
4579
+ ascending: (id?: string) => string & import("effect/Brand").Brand<"Workspace.ID">;
4580
+ create: () => string & import("effect/Brand").Brand<"Workspace.ID">;
4581
+ }>, never, never>;
4582
+ }>>>, import("effect/Schema").optionalKey<import("effect/Schema").Struct<{
4583
+ readonly directory: import("effect/Schema").brand<import("effect/Schema").String, "AbsolutePath">;
4584
+ readonly workspaceID: import("effect/Schema").decodeTo<import("effect/Schema").optional<import("effect/Schema").toType<import("effect/Schema").brand<import("effect/Schema").String, "Workspace.ID"> & {
4585
+ ascending: (id?: string) => string & import("effect/Brand").Brand<"Workspace.ID">;
4586
+ create: () => string & import("effect/Brand").Brand<"Workspace.ID">;
4587
+ }>>, import("effect/Schema").optionalKey<import("effect/Schema").brand<import("effect/Schema").String, "Workspace.ID"> & {
4588
+ ascending: (id?: string) => string & import("effect/Brand").Brand<"Workspace.ID">;
4589
+ create: () => string & import("effect/Brand").Brand<"Workspace.ID">;
4590
+ }>, never, never>;
4591
+ }>>, never, never>;
4592
+ readonly data: import("effect/Schema").Struct<{
4593
+ readonly sessionID: import("effect/Schema").brand<import("effect/Schema").String, "SessionID"> & {
4594
+ create: () => string & import("effect/Brand").Brand<"SessionID">;
4595
+ descending: (id?: string) => string & import("effect/Brand").Brand<"SessionID">;
4596
+ };
4597
+ readonly ptyID: import("effect/Schema").brand<import("effect/Schema").String, "PtyID"> & {
4598
+ create: () => string & import("effect/Brand").Brand<"PtyID">;
4599
+ ascending: (id?: string) => string & import("effect/Brand").Brand<"PtyID">;
4600
+ };
4601
+ }>;
4602
+ }> & {
4603
+ type: "persistent-pty.removed";
4604
+ durability: "ephemeral";
4605
+ durable: undefined;
4606
+ data: import("effect/Schema").Struct<{
4607
+ readonly sessionID: import("effect/Schema").brand<import("effect/Schema").String, "SessionID"> & {
4608
+ create: () => string & import("effect/Brand").Brand<"SessionID">;
4609
+ descending: (id?: string) => string & import("effect/Brand").Brand<"SessionID">;
4610
+ };
4611
+ readonly ptyID: import("effect/Schema").brand<import("effect/Schema").String, "PtyID"> & {
4612
+ create: () => string & import("effect/Brand").Brand<"PtyID">;
4613
+ ascending: (id?: string) => string & import("effect/Brand").Brand<"PtyID">;
4614
+ };
4615
+ }>;
4472
4616
  }, import("effect/Schema").Struct<{
4473
4617
  readonly id: import("effect/Schema").brand<import("effect/Schema").String, "Event.ID"> & {
4474
4618
  create: () => string & import("effect/Brand").Brand<"Event.ID">;
@@ -7285,21 +7429,18 @@ export declare const Api: import("@opencode-ai/protocol/api").Api<LocationMiddle
7285
7429
  } | {
7286
7430
  readonly id: string & import("effect/Brand").Brand<"Event.ID">;
7287
7431
  readonly data: {
7288
- readonly id: string & import("effect/Brand").Brand<"Plugin.ID">;
7432
+ readonly info: {
7433
+ readonly id: string & import("effect/Brand").Brand<"PtyID">;
7434
+ readonly command: string;
7435
+ readonly title: string;
7436
+ readonly status: "running" | "exited";
7437
+ readonly pid: number;
7438
+ readonly cwd: string;
7439
+ readonly args: readonly string[];
7440
+ readonly exitCode?: number | undefined;
7441
+ };
7289
7442
  };
7290
- readonly type: "plugin.added";
7291
- readonly created: number;
7292
- readonly location?: {
7293
- readonly directory: string & import("effect/Brand").Brand<"AbsolutePath">;
7294
- readonly workspaceID?: (string & import("effect/Brand").Brand<"Workspace.ID">) | undefined;
7295
- } | undefined;
7296
- readonly metadata?: {
7297
- readonly [x: string]: unknown;
7298
- } | undefined;
7299
- } | {
7300
- readonly id: string & import("effect/Brand").Brand<"Event.ID">;
7301
- readonly data: {};
7302
- readonly type: "plugin.updated";
7443
+ readonly type: "pty.created";
7303
7444
  readonly created: number;
7304
7445
  readonly location?: {
7305
7446
  readonly directory: string & import("effect/Brand").Brand<"AbsolutePath">;
@@ -7322,7 +7463,7 @@ export declare const Api: import("@opencode-ai/protocol/api").Api<LocationMiddle
7322
7463
  readonly exitCode?: number | undefined;
7323
7464
  };
7324
7465
  };
7325
- readonly type: "pty.created";
7466
+ readonly type: "pty.updated";
7326
7467
  readonly created: number;
7327
7468
  readonly location?: {
7328
7469
  readonly directory: string & import("effect/Brand").Brand<"AbsolutePath">;
@@ -7334,18 +7475,58 @@ export declare const Api: import("@opencode-ai/protocol/api").Api<LocationMiddle
7334
7475
  } | {
7335
7476
  readonly id: string & import("effect/Brand").Brand<"Event.ID">;
7336
7477
  readonly data: {
7337
- readonly info: {
7478
+ readonly id: string & import("effect/Brand").Brand<"PtyID">;
7479
+ readonly exitCode: number;
7480
+ };
7481
+ readonly type: "pty.exited";
7482
+ readonly created: number;
7483
+ readonly location?: {
7484
+ readonly directory: string & import("effect/Brand").Brand<"AbsolutePath">;
7485
+ readonly workspaceID?: (string & import("effect/Brand").Brand<"Workspace.ID">) | undefined;
7486
+ } | undefined;
7487
+ readonly metadata?: {
7488
+ readonly [x: string]: unknown;
7489
+ } | undefined;
7490
+ } | {
7491
+ readonly id: string & import("effect/Brand").Brand<"Event.ID">;
7492
+ readonly data: {
7493
+ readonly id: string & import("effect/Brand").Brand<"PtyID">;
7494
+ };
7495
+ readonly type: "pty.deleted";
7496
+ readonly created: number;
7497
+ readonly location?: {
7498
+ readonly directory: string & import("effect/Brand").Brand<"AbsolutePath">;
7499
+ readonly workspaceID?: (string & import("effect/Brand").Brand<"Workspace.ID">) | undefined;
7500
+ } | undefined;
7501
+ readonly metadata?: {
7502
+ readonly [x: string]: unknown;
7503
+ } | undefined;
7504
+ } | {
7505
+ readonly id: string & import("effect/Brand").Brand<"Event.ID">;
7506
+ readonly data: {
7507
+ readonly sessionID: string & import("effect/Brand").Brand<"SessionID">;
7508
+ readonly terminal: {
7338
7509
  readonly id: string & import("effect/Brand").Brand<"PtyID">;
7510
+ readonly size: {
7511
+ readonly cols: number;
7512
+ readonly rows: number;
7513
+ };
7514
+ readonly sessionID: string & import("effect/Brand").Brand<"SessionID">;
7339
7515
  readonly command: string;
7340
7516
  readonly title: string;
7341
7517
  readonly status: "running" | "exited";
7518
+ readonly output: {
7519
+ readonly head: number;
7520
+ readonly tail: number;
7521
+ };
7342
7522
  readonly pid: number;
7343
7523
  readonly cwd: string;
7344
7524
  readonly args: readonly string[];
7525
+ readonly foregroundProcess: string | null;
7345
7526
  readonly exitCode?: number | undefined;
7346
7527
  };
7347
7528
  };
7348
- readonly type: "pty.updated";
7529
+ readonly type: "persistent-pty.added";
7349
7530
  readonly created: number;
7350
7531
  readonly location?: {
7351
7532
  readonly directory: string & import("effect/Brand").Brand<"AbsolutePath">;
@@ -7357,10 +7538,10 @@ export declare const Api: import("@opencode-ai/protocol/api").Api<LocationMiddle
7357
7538
  } | {
7358
7539
  readonly id: string & import("effect/Brand").Brand<"Event.ID">;
7359
7540
  readonly data: {
7360
- readonly id: string & import("effect/Brand").Brand<"PtyID">;
7361
- readonly exitCode: number;
7541
+ readonly sessionID: string & import("effect/Brand").Brand<"SessionID">;
7542
+ readonly ptyID: string & import("effect/Brand").Brand<"PtyID">;
7362
7543
  };
7363
- readonly type: "pty.exited";
7544
+ readonly type: "persistent-pty.removed";
7364
7545
  readonly created: number;
7365
7546
  readonly location?: {
7366
7547
  readonly directory: string & import("effect/Brand").Brand<"AbsolutePath">;
@@ -7372,9 +7553,21 @@ export declare const Api: import("@opencode-ai/protocol/api").Api<LocationMiddle
7372
7553
  } | {
7373
7554
  readonly id: string & import("effect/Brand").Brand<"Event.ID">;
7374
7555
  readonly data: {
7375
- readonly id: string & import("effect/Brand").Brand<"PtyID">;
7556
+ readonly id: string & import("effect/Brand").Brand<"Plugin.ID">;
7376
7557
  };
7377
- readonly type: "pty.deleted";
7558
+ readonly type: "plugin.added";
7559
+ readonly created: number;
7560
+ readonly location?: {
7561
+ readonly directory: string & import("effect/Brand").Brand<"AbsolutePath">;
7562
+ readonly workspaceID?: (string & import("effect/Brand").Brand<"Workspace.ID">) | undefined;
7563
+ } | undefined;
7564
+ readonly metadata?: {
7565
+ readonly [x: string]: unknown;
7566
+ } | undefined;
7567
+ } | {
7568
+ readonly id: string & import("effect/Brand").Brand<"Event.ID">;
7569
+ readonly data: {};
7570
+ readonly type: "plugin.updated";
7378
7571
  readonly created: number;
7379
7572
  readonly location?: {
7380
7573
  readonly directory: string & import("effect/Brand").Brand<"AbsolutePath">;
@@ -0,0 +1,3 @@
1
+ import { PersistentPty } from "@opencode-ai/core/persistent-pty";
2
+ import { PtyTicket } from "@opencode-ai/core/pty/ticket";
3
+ export declare const PersistentPtyHandler: import("effect/Layer").Layer<import("effect/unstable/httpapi/HttpApiGroup").Service<"server", "server.experimental">, never, PtyTicket.Service | PersistentPty.Service | (import("@opencode-ai/protocol/middleware/schema-error").SchemaErrorMiddleware | import("@opencode-ai/protocol/middleware/authorization").Authorization)>;
@@ -0,0 +1,126 @@
1
+ import { PersistentPty } from "@opencode-ai/core/persistent-pty";
2
+ import { PtyTicket } from "@opencode-ai/core/pty/ticket";
3
+ import { ForbiddenError, PtyNotFoundError, ServiceUnavailableError } from "@opencode-ai/protocol/errors";
4
+ import {
5
+ PTY_CONNECT_TICKET_QUERY,
6
+ PTY_CONNECT_TOKEN_HEADER,
7
+ PTY_CONNECT_TOKEN_HEADER_VALUE
8
+ } from "@opencode-ai/protocol/groups/persistent-pty";
9
+ import { Effect, Queue, Semaphore } from "effect";
10
+ import { HttpServerRequest, HttpServerResponse } from "effect/unstable/http";
11
+ import { HttpApiBuilder, HttpApiSchema } from "effect/unstable/httpapi";
12
+ import { Socket } from "effect/unstable/socket";
13
+ import { Api } from "../api";
14
+ import { CorsConfig, isAllowedRequestOrigin } from "../cors";
15
+ export const PersistentPtyHandler = HttpApiBuilder.group(Api, "server.experimental", (handlers) => Effect.gen(function* () {
16
+ const tickets = yield* PtyTicket.Service, cors = yield* CorsConfig, pty = yield* PersistentPty.Service;
17
+ return handlers.handle("persistentPty.list", Effect.fn(function* (ctx) {
18
+ return { data: yield* pty.list(ctx.params.sessionID).pipe(mapUnavailable) };
19
+ })).handle("persistentPty.create", Effect.fn(function* (ctx) {
20
+ return {
21
+ data: yield* pty.create(ctx.params.sessionID, {
22
+ command: ctx.payload.command,
23
+ args: ctx.payload.args,
24
+ cwd: ctx.payload.cwd,
25
+ title: ctx.payload.title,
26
+ env: ctx.payload.env,
27
+ cols: ctx.payload.size?.cols,
28
+ rows: ctx.payload.size?.rows
29
+ }).pipe(Effect.catchTag("PersistentPty.UnavailableError", unavailable))
30
+ };
31
+ })).handle("persistentPty.shutdown", Effect.fn(function* () {
32
+ yield* pty.shutdown().pipe(mapUnavailable);
33
+ return HttpApiSchema.NoContent.make();
34
+ })).handle("persistentPty.get", Effect.fn(function* (ctx) {
35
+ return { data: yield* pty.get(ctx.params.ptyID).pipe(mapTerminalError) };
36
+ })).handle("persistentPty.update", Effect.fn(function* (ctx) {
37
+ yield* pty.resize(ctx.params.ptyID, ctx.payload.size.cols, ctx.payload.size.rows, ctx.payload.attachmentID).pipe(mapTerminalError);
38
+ return { data: yield* pty.get(ctx.params.ptyID).pipe(mapTerminalError) };
39
+ })).handle("persistentPty.snapshot", Effect.fn(function* (ctx) {
40
+ return { data: yield* pty.snapshot(ctx.params.ptyID).pipe(mapTerminalError) };
41
+ })).handle("persistentPty.remove", Effect.fn(function* (ctx) {
42
+ yield* pty.remove(ctx.params.ptyID).pipe(mapTerminalError);
43
+ return HttpApiSchema.NoContent.make();
44
+ })).handle("persistentPty.connectToken", Effect.fn(function* (ctx) {
45
+ const request = yield* HttpServerRequest.HttpServerRequest;
46
+ if (request.headers[PTY_CONNECT_TOKEN_HEADER] !== PTY_CONNECT_TOKEN_HEADER_VALUE || !isAllowedRequestOrigin(request.headers.origin, request.headers.host, cors))
47
+ return yield* new ForbiddenError({ message: "Invalid persistent PTY connect token request" });
48
+ yield* pty.get(ctx.params.ptyID).pipe(mapTerminalError);
49
+ return { data: yield* tickets.issue({ ptyID: ctx.params.ptyID }) };
50
+ })).handleRaw("persistentPty.connect", Effect.fn("PersistentPtyHandler.connect")(function* (ctx) {
51
+ if (!(yield* pty.get(ctx.params.ptyID).pipe(Effect.as(!0), Effect.catchTag("PersistentPty.NotFoundError", () => Effect.succeed(!1)), Effect.catchTag("PersistentPty.UnavailableError", () => Effect.succeed(!1)))))
52
+ return HttpServerResponse.empty({ status: 404 });
53
+ const url = new URL(ctx.request.url, "http://localhost"), ticket = url.searchParams.get(PTY_CONNECT_TICKET_QUERY);
54
+ if (ticket) {
55
+ if (!(isAllowedRequestOrigin(ctx.request.headers.origin, ctx.request.headers.host, cors) ? yield* tickets.consume({ ticket, ptyID: ctx.params.ptyID }) : !1))
56
+ return HttpServerResponse.empty({ status: 403 });
57
+ }
58
+ const cursor = Number(url.searchParams.get("cursor") ?? "0"), role = url.searchParams.get("role") === "observer" ? "observer" : "controller", framedInput = url.searchParams.get("input_protocol") === "1", attachmentID = url.searchParams.get("attachment_id") ?? crypto.randomUUID();
59
+ if (!Number.isSafeInteger(cursor) || cursor < 0)
60
+ return HttpServerResponse.empty({ status: 400 });
61
+ const socket = yield* Effect.orDie(ctx.request.upgrade), write = yield* socket.writer, outbox = yield* Queue.unbounded(), input = yield* Semaphore.make(1), attachment = yield* pty.attach(ctx.params.ptyID, {
62
+ cursor,
63
+ attachmentID,
64
+ role,
65
+ takeover: url.searchParams.get("takeover") === "true",
66
+ onEvent: (event) => {
67
+ if (event.type === "output")
68
+ Queue.offerUnsafe(outbox, event.data);
69
+ if (event.type === "resized")
70
+ Queue.offerUnsafe(outbox, JSON.stringify({ ...event, checkpoint: Buffer.from(event.checkpoint).toString("base64") }));
71
+ if (event.type !== "output" && event.type !== "resized")
72
+ Queue.offerUnsafe(outbox, JSON.stringify(event));
73
+ },
74
+ onEnd: () => Queue.offerUnsafe(outbox, new Socket.CloseEvent(1000))
75
+ }).pipe(Effect.catchTags({
76
+ "PersistentPty.NotFoundError": () => Effect.succeed(void 0),
77
+ "PersistentPty.UnavailableError": () => Effect.succeed(void 0)
78
+ }));
79
+ if (!attachment)
80
+ return HttpServerResponse.empty({ status: 404 });
81
+ Queue.offerUnsafe(outbox, JSON.stringify({
82
+ type: "attached",
83
+ attachmentID,
84
+ inputProtocol: framedInput ? 1 : 0,
85
+ info: attachment.info,
86
+ role: attachment.role,
87
+ generation: attachment.generation,
88
+ replay: {
89
+ requestedOffset: attachment.replay.requestedOffset,
90
+ availableOffset: attachment.replay.availableOffset,
91
+ endOffset: attachment.replay.endOffset,
92
+ truncated: attachment.replay.truncated
93
+ }
94
+ }));
95
+ if (attachment.replay.data.length > 0)
96
+ Queue.offerUnsafe(outbox, attachment.replay.data);
97
+ Queue.offerUnsafe(outbox, JSON.stringify({ type: "replay_complete", endOffset: attachment.replay.endOffset }));
98
+ attachment.activate();
99
+ const drain = Effect.gen(function* () {
100
+ while (!0) {
101
+ const item = yield* Queue.take(outbox);
102
+ yield* write(item);
103
+ if (item instanceof Socket.CloseEvent)
104
+ return;
105
+ }
106
+ });
107
+ yield* Effect.race(drain, socket.runRaw((message) => input.withPermit(Effect.suspend(() => {
108
+ const data = typeof message === "string" ? Buffer.from(message) : message;
109
+ if (!framedInput)
110
+ return pty.input(ctx.params.ptyID, attachmentID, attachment.info.size.cols, attachment.info.size.rows, data).pipe(Effect.ignore);
111
+ if (data.byteLength < 5)
112
+ return Effect.void;
113
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength), type = data[0], cols = view.getUint16(1), rows = view.getUint16(3);
114
+ if (type !== 0 && type !== 1 || cols === 0 || rows === 0)
115
+ return Effect.void;
116
+ if (type === 0)
117
+ return pty.control(ctx.params.ptyID, attachmentID, cols, rows).pipe(Effect.ignore);
118
+ return pty.input(ctx.params.ptyID, attachmentID, cols, rows, data.subarray(5)).pipe(Effect.ignore);
119
+ })))).pipe(Effect.catchReason("SocketError", "SocketCloseError", () => Effect.void), Effect.ensuring(Effect.sync(() => attachment.detach())), Effect.orDie);
120
+ return HttpServerResponse.empty();
121
+ }));
122
+ }));
123
+ const mapUnavailable = (effect) => effect.pipe(Effect.catchTag("PersistentPty.UnavailableError", unavailable)), mapTerminalError = (effect) => effect.pipe(Effect.catchTags({
124
+ "PersistentPty.NotFoundError": (error) => new PtyNotFoundError({ ptyID: error.ptyID, message: `PTY session not found: ${error.ptyID}` }),
125
+ "PersistentPty.UnavailableError": unavailable
126
+ })), unavailable = (error) => new ServiceUnavailableError({ message: error.message, service: "opencode-pty" });
@@ -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.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.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-ai/protocol/middleware/schema-error").SchemaErrorMiddleware | import("@opencode-ai/protocol/middleware/authorization").Authorization | import("@opencode-ai/util/global").Service | import("@opencode-ai/core/bus").Service | import("@opencode-ai/core/workspace").Service | import("@opencode-ai/core/location-service-map").Service | import("@opencode-ai/core/worktree").Service | import("@opencode-ai/core/session").Service | import("./location").LocationMiddleware | import("./middleware/form-location").FormLocationMiddleware | import("./middleware/session-location").SessionLocationMiddleware | import("@opencode-ai/core/pty/ticket").Service | import("@opencode-ai/core/session/transfer").Service | import("effect/unstable/http/HttpRouter").Request<"Requires", import("@opencode-ai/core/database/database").Service> | import("effect/unstable/http/HttpRouter").Request<"Requires", import("@opencode-ai/core/permission/saved").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-ai/core/wellknown").Service> | import("effect/unstable/http/HttpRouter").Request<"Requires", import("@opencode-ai/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.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-ai/protocol/middleware/schema-error").SchemaErrorMiddleware | import("@opencode-ai/protocol/middleware/authorization").Authorization | import("@opencode-ai/util/global").Service | import("@opencode-ai/core/bus").Service | import("@opencode-ai/core/workspace").Service | import("@opencode-ai/core/location-service-map").Service | import("@opencode-ai/core/worktree").Service | import("@opencode-ai/core/session").Service | import("./location").LocationMiddleware | import("./middleware/form-location").FormLocationMiddleware | import("./middleware/session-location").SessionLocationMiddleware | import("@opencode-ai/core/pty/ticket").Service | import("@opencode-ai/core/persistent-pty/index").Service | import("@opencode-ai/core/session/transfer").Service | import("effect/unstable/http/HttpRouter").Request<"Requires", import("@opencode-ai/core/database/database").Service> | import("effect/unstable/http/HttpRouter").Request<"Requires", import("@opencode-ai/core/permission/saved").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-ai/core/wellknown").Service> | import("effect/unstable/http/HttpRouter").Request<"Requires", import("@opencode-ai/core/project").Service>>;
package/dist/handlers.js CHANGED
@@ -16,6 +16,7 @@ import { HealthHandler } from "./handlers/health";
16
16
  import { ServerHandler } from "./handlers/server";
17
17
  import { DebugHandler } from "./handlers/debug";
18
18
  import { PtyHandler } from "./handlers/pty";
19
+ import { PersistentPtyHandler } from "./handlers/persistent-pty";
19
20
  import { ShellHandler } from "./handlers/shell";
20
21
  import { ReferenceHandler } from "./handlers/reference";
21
22
  import { LocationHandler } from "./handlers/location";
@@ -30,4 +31,4 @@ import { EventFeed } from "./event-feed";
30
31
  import { MigrationHandler } from "./handlers/migration";
31
32
  import { ConfigHandler } from "./handlers/config";
32
33
  import { WorkspaceHandler } from "./handlers/workspace";
33
- 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, EventHandler.pipe(Layer.provide(EventFeed.layer)), PtyHandler, ShellHandler, ReferenceHandler, WorktreeHandler, WorkspaceHandler, VcsHandler, ConfigHandler);
34
+ 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, EventHandler.pipe(Layer.provide(EventFeed.layer)), PtyHandler, PersistentPtyHandler, ShellHandler, ReferenceHandler, WorktreeHandler, WorkspaceHandler, VcsHandler, ConfigHandler);
@@ -3,6 +3,7 @@ import { UnauthorizedError } from "@opencode-ai/protocol/errors";
3
3
  import { Authorization } from "@opencode-ai/protocol/middleware/authorization";
4
4
  export { Authorization } from "@opencode-ai/protocol/middleware/authorization";
5
5
  import { hasPtyConnectTicketURL } from "@opencode-ai/protocol/groups/pty";
6
+ import { hasPersistentPtyConnectTicketURL } from "@opencode-ai/protocol/groups/persistent-pty";
6
7
  import { Effect, Encoding, Layer, Redacted } from "effect";
7
8
  import { HttpEffect, HttpServerRequest, HttpServerResponse } from "effect/unstable/http";
8
9
  const AUTH_TOKEN_QUERY = "auth_token", WWW_AUTHENTICATE = 'Basic realm="Secure Area"';
@@ -37,8 +38,8 @@ export const authorizationLayer = Layer.effect(Authorization, Effect.gen(functio
37
38
  if (!ServerAuth.required(config))
38
39
  return Authorization.of((effect) => effect);
39
40
  return Authorization.of((effect) => Effect.gen(function* () {
40
- const request = yield* HttpServerRequest.HttpServerRequest;
41
- if (hasPtyConnectTicketURL(new URL(request.url, "http://localhost")))
41
+ const request = yield* HttpServerRequest.HttpServerRequest, url = new URL(request.url, "http://localhost");
42
+ if (hasPtyConnectTicketURL(url) || hasPersistentPtyConnectTicketURL(url))
42
43
  return yield* effect;
43
44
  if (yield* authorizedRequest(request, config))
44
45
  return yield* effect;
package/dist/process.js CHANGED
@@ -2,6 +2,7 @@ export * as ServerProcess from "./process";
2
2
  import { NodeHttpServer } from "@effect/platform-node";
3
3
  import { SessionRestart } from "@opencode-ai/core/session/execution/restart";
4
4
  import { hasPtyConnectTicketURL } from "@opencode-ai/protocol/groups/pty";
5
+ import { hasPersistentPtyConnectTicketURL } from "@opencode-ai/protocol/groups/persistent-pty";
5
6
  import { Cause, Context, Effect, Exit, Latch, Layer, Option, Ref, Scope } from "effect";
6
7
  import {
7
8
  HttpMiddleware,
@@ -88,7 +89,7 @@ function dispatch(password, status, application, version) {
88
89
  return yield* healthResponse(status, version);
89
90
  }
90
91
  const state = yield* status.current, app = yield* Ref.get(application), ready = state.type === "ready" && Option.isSome(app);
91
- if ((!ready || !hasPtyConnectTicketURL(url)) && !(yield* authorizedRequest(request, auth)))
92
+ if ((!ready || !hasPtyConnectTicketURL(url) && !hasPersistentPtyConnectTicketURL(url)) && !(yield* authorizedRequest(request, auth)))
92
93
  return unauthorized();
93
94
  if (ready)
94
95
  return yield* app.value;
package/dist/routes.d.ts CHANGED
@@ -4,6 +4,7 @@ import { Bus } from "@opencode-ai/core/bus";
4
4
  import { Credential } from "@opencode-ai/core/credential";
5
5
  import { PermissionSaved } from "@opencode-ai/core/permission/saved";
6
6
  import { PtyTicket } from "@opencode-ai/core/pty/ticket";
7
+ import { PersistentPty } from "@opencode-ai/core/persistent-pty";
7
8
  import { Project } from "@opencode-ai/core/project";
8
9
  import { Session } from "@opencode-ai/core/session";
9
10
  import { SessionTransfer } from "@opencode-ai/core/session/transfer";
@@ -20,5 +21,5 @@ import { HttpRouter } from "effect/unstable/http";
20
21
  import { Layer } from "effect";
21
22
  import { PtyEnvironment } from "./pty-environment";
22
23
  import type { ServerOptions } from "./options";
23
- 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 | Job.Service | LocationServiceMap.Service | SdkPlugins.Service | PermissionSaved.Service | Worktree.Service | Session.Service | SessionRestart.Service | PtyTicket.Service | SessionTransfer.Service | LocationActivity.Service | PtyEnvironment.Service | HttpRouter.HttpRouter, Error, import("effect/FileSystem").FileSystem | import("effect/Path").Path | import("effect/unstable/http/Etag").Generator | import("effect/unstable/http/HttpPlatform").HttpPlatform>;
24
- export declare function createEmbeddedRoutes(options?: ServerOptions, 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 | Job.Service | LocationServiceMap.Service | SdkPlugins.Service | PermissionSaved.Service | Worktree.Service | Session.Service | SessionRestart.Service | PtyTicket.Service | SessionTransfer.Service | LocationActivity.Service | PtyEnvironment.Service | HttpRouter.HttpRouter, Error, import("effect/FileSystem").FileSystem | import("effect/Path").Path | import("effect/unstable/http/Etag").Generator | import("effect/unstable/http/HttpPlatform").HttpPlatform>;
24
+ 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 | Job.Service | LocationServiceMap.Service | SdkPlugins.Service | PermissionSaved.Service | Worktree.Service | Session.Service | SessionRestart.Service | PtyTicket.Service | PersistentPty.Service | SessionTransfer.Service | LocationActivity.Service | PtyEnvironment.Service | HttpRouter.HttpRouter, Error, import("effect/FileSystem").FileSystem | import("effect/Path").Path | import("effect/unstable/http/Etag").Generator | import("effect/unstable/http/HttpPlatform").HttpPlatform>;
25
+ export declare function createEmbeddedRoutes(options?: ServerOptions, 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 | Job.Service | LocationServiceMap.Service | SdkPlugins.Service | PermissionSaved.Service | Worktree.Service | Session.Service | SessionRestart.Service | PtyTicket.Service | PersistentPty.Service | SessionTransfer.Service | LocationActivity.Service | PtyEnvironment.Service | HttpRouter.HttpRouter, Error, import("effect/FileSystem").FileSystem | import("effect/Path").Path | import("effect/unstable/http/Etag").Generator | import("effect/unstable/http/HttpPlatform").HttpPlatform>;
package/dist/routes.js CHANGED
@@ -11,6 +11,7 @@ import { Credential } from "@opencode-ai/core/credential";
11
11
  import { Config } from "@opencode-ai/core/config";
12
12
  import { PermissionSaved } from "@opencode-ai/core/permission/saved";
13
13
  import { PtyTicket } from "@opencode-ai/core/pty/ticket";
14
+ import { PersistentPty } from "@opencode-ai/core/persistent-pty";
14
15
  import { Project } from "@opencode-ai/core/project";
15
16
  import { Session } from "@opencode-ai/core/session";
16
17
  import { SessionTransfer } from "@opencode-ai/core/session/transfer";
@@ -57,6 +58,7 @@ const applicationServiceNodes = [
57
58
  SdkPlugins.node,
58
59
  PermissionSaved.node,
59
60
  PtyTicket.node,
61
+ PersistentPty.node,
60
62
  Credential.node,
61
63
  WellKnown.node,
62
64
  PtyEnvironment.node,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
3
  "name": "@opencode-ai/server",
4
- "version": "0.0.0-dev-18198",
4
+ "version": "0.0.0-dev-18202",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "repository": {
@@ -29,11 +29,11 @@
29
29
  },
30
30
  "dependencies": {
31
31
  "@effect/platform-node": "4.0.0-rc.111",
32
- "@opencode-ai/core": "0.0.0-dev-18198",
33
- "@opencode-ai/protocol": "0.0.0-dev-18198",
34
- "@opencode-ai/schema": "0.0.0-dev-18198",
35
- "@opencode-ai/simulation": "0.0.0-dev-18198",
36
- "@opencode-ai/util": "0.0.0-dev-18198",
32
+ "@opencode-ai/core": "0.0.0-dev-18202",
33
+ "@opencode-ai/protocol": "0.0.0-dev-18202",
34
+ "@opencode-ai/schema": "0.0.0-dev-18202",
35
+ "@opencode-ai/simulation": "0.0.0-dev-18202",
36
+ "@opencode-ai/util": "0.0.0-dev-18202",
37
37
  "drizzle-orm": "1.0.0-rc.5-169397b",
38
38
  "effect": "4.0.0-rc.111"
39
39
  },