|
@@ -87,6 +87,10 @@ export const UpdateInput = Schema.Struct({
|
|
|
|
|
|
|
|
export type UpdateInput = Types.DeepMutable<Schema.Schema.Type<typeof UpdateInput>>
|
|
export type UpdateInput = Types.DeepMutable<Schema.Schema.Type<typeof UpdateInput>>
|
|
|
|
|
|
|
|
|
|
+export class NotFoundError extends Schema.TaggedErrorClass<NotFoundError>()("Pty.NotFoundError", {
|
|
|
|
|
+ ptyID: PtyID,
|
|
|
|
|
+}) {}
|
|
|
|
|
+
|
|
|
export const Event = {
|
|
export const Event = {
|
|
|
Created: BusEvent.define("pty.created", Schema.Struct({ info: Info })),
|
|
Created: BusEvent.define("pty.created", Schema.Struct({ info: Info })),
|
|
|
Updated: BusEvent.define("pty.updated", Schema.Struct({ info: Info })),
|
|
Updated: BusEvent.define("pty.updated", Schema.Struct({ info: Info })),
|
|
@@ -96,17 +100,17 @@ export const Event = {
|
|
|
|
|
|
|
|
export interface Interface {
|
|
export interface Interface {
|
|
|
readonly list: () => Effect.Effect<Info[]>
|
|
readonly list: () => Effect.Effect<Info[]>
|
|
|
- readonly get: (id: PtyID) => Effect.Effect<Info | undefined>
|
|
|
|
|
|
|
+ readonly get: (id: PtyID) => Effect.Effect<Info, NotFoundError>
|
|
|
readonly create: (input: CreateInput) => Effect.Effect<Info>
|
|
readonly create: (input: CreateInput) => Effect.Effect<Info>
|
|
|
- readonly update: (id: PtyID, input: UpdateInput) => Effect.Effect<Info | undefined>
|
|
|
|
|
- readonly remove: (id: PtyID) => Effect.Effect<void>
|
|
|
|
|
- readonly resize: (id: PtyID, cols: number, rows: number) => Effect.Effect<void>
|
|
|
|
|
- readonly write: (id: PtyID, data: string) => Effect.Effect<void>
|
|
|
|
|
|
|
+ readonly update: (id: PtyID, input: UpdateInput) => Effect.Effect<Info, NotFoundError>
|
|
|
|
|
+ readonly remove: (id: PtyID) => Effect.Effect<void, NotFoundError>
|
|
|
|
|
+ readonly resize: (id: PtyID, cols: number, rows: number) => Effect.Effect<void, NotFoundError>
|
|
|
|
|
+ readonly write: (id: PtyID, data: string) => Effect.Effect<void, NotFoundError>
|
|
|
readonly connect: (
|
|
readonly connect: (
|
|
|
id: PtyID,
|
|
id: PtyID,
|
|
|
ws: Socket,
|
|
ws: Socket,
|
|
|
cursor?: number,
|
|
cursor?: number,
|
|
|
- ) => Effect.Effect<{ onMessage: (message: string | ArrayBuffer) => void; onClose: () => void } | undefined>
|
|
|
|
|
|
|
+ ) => Effect.Effect<{ onMessage: (message: string | ArrayBuffer) => void; onClose: () => void } | undefined, NotFoundError>
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
export class Service extends Context.Service<Service, Interface>()("@opencode/Pty") {}
|
|
export class Service extends Context.Service<Service, Interface>()("@opencode/Pty") {}
|
|
@@ -150,10 +154,15 @@ export const layer = Layer.effect(
|
|
|
}),
|
|
}),
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
|
|
+ const requireSession = Effect.fn("Pty.requireSession")(function* (id: PtyID) {
|
|
|
|
|
+ const session = (yield* InstanceState.get(state)).sessions.get(id)
|
|
|
|
|
+ if (!session) return yield* new NotFoundError({ ptyID: id })
|
|
|
|
|
+ return session
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
const remove = Effect.fn("Pty.remove")(function* (id: PtyID) {
|
|
const remove = Effect.fn("Pty.remove")(function* (id: PtyID) {
|
|
|
const s = yield* InstanceState.get(state)
|
|
const s = yield* InstanceState.get(state)
|
|
|
- const session = s.sessions.get(id)
|
|
|
|
|
- if (!session) return
|
|
|
|
|
|
|
+ const session = yield* requireSession(id)
|
|
|
s.sessions.delete(id)
|
|
s.sessions.delete(id)
|
|
|
log.info("removing session", { id })
|
|
log.info("removing session", { id })
|
|
|
teardown(session)
|
|
teardown(session)
|
|
@@ -166,8 +175,7 @@ export const layer = Layer.effect(
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
const get = Effect.fn("Pty.get")(function* (id: PtyID) {
|
|
const get = Effect.fn("Pty.get")(function* (id: PtyID) {
|
|
|
- const s = yield* InstanceState.get(state)
|
|
|
|
|
- return s.sessions.get(id)?.info
|
|
|
|
|
|
|
+ return (yield* requireSession(id)).info
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
const create = Effect.fn("Pty.create")(function* (input: CreateInput) {
|
|
const create = Effect.fn("Pty.create")(function* (input: CreateInput) {
|
|
@@ -262,9 +270,7 @@ export const layer = Layer.effect(
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
const update = Effect.fn("Pty.update")(function* (id: PtyID, input: UpdateInput) {
|
|
const update = Effect.fn("Pty.update")(function* (id: PtyID, input: UpdateInput) {
|
|
|
- const s = yield* InstanceState.get(state)
|
|
|
|
|
- const session = s.sessions.get(id)
|
|
|
|
|
- if (!session) return
|
|
|
|
|
|
|
+ const session = yield* requireSession(id)
|
|
|
if (input.title) {
|
|
if (input.title) {
|
|
|
session.info.title = input.title
|
|
session.info.title = input.title
|
|
|
}
|
|
}
|
|
@@ -276,28 +282,27 @@ export const layer = Layer.effect(
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
const resize = Effect.fn("Pty.resize")(function* (id: PtyID, cols: number, rows: number) {
|
|
const resize = Effect.fn("Pty.resize")(function* (id: PtyID, cols: number, rows: number) {
|
|
|
- const s = yield* InstanceState.get(state)
|
|
|
|
|
- const session = s.sessions.get(id)
|
|
|
|
|
- if (session && session.info.status === "running") {
|
|
|
|
|
|
|
+ const session = yield* requireSession(id)
|
|
|
|
|
+ if (session.info.status === "running") {
|
|
|
session.process.resize(cols, rows)
|
|
session.process.resize(cols, rows)
|
|
|
}
|
|
}
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
const write = Effect.fn("Pty.write")(function* (id: PtyID, data: string) {
|
|
const write = Effect.fn("Pty.write")(function* (id: PtyID, data: string) {
|
|
|
- const s = yield* InstanceState.get(state)
|
|
|
|
|
- const session = s.sessions.get(id)
|
|
|
|
|
- if (session && session.info.status === "running") {
|
|
|
|
|
|
|
+ const session = yield* requireSession(id)
|
|
|
|
|
+ if (session.info.status === "running") {
|
|
|
session.process.write(data)
|
|
session.process.write(data)
|
|
|
}
|
|
}
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
const connect = Effect.fn("Pty.connect")(function* (id: PtyID, ws: Socket, cursor?: number) {
|
|
const connect = Effect.fn("Pty.connect")(function* (id: PtyID, ws: Socket, cursor?: number) {
|
|
|
- const s = yield* InstanceState.get(state)
|
|
|
|
|
- const session = s.sessions.get(id)
|
|
|
|
|
- if (!session) {
|
|
|
|
|
- ws.close()
|
|
|
|
|
- return
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ const session = yield* requireSession(id).pipe(
|
|
|
|
|
+ Effect.tapError(() =>
|
|
|
|
|
+ Effect.sync(() => {
|
|
|
|
|
+ ws.close()
|
|
|
|
|
+ }),
|
|
|
|
|
+ ),
|
|
|
|
|
+ )
|
|
|
log.info("client connected to session", { id })
|
|
log.info("client connected to session", { id })
|
|
|
|
|
|
|
|
const sub = sock(ws)
|
|
const sub = sock(ws)
|