diff --git a/packages/codemode/test/openapi.test.ts b/packages/codemode/test/openapi.test.ts index da705977bf..3d17a45e3f 100644 --- a/packages/codemode/test/openapi.test.ts +++ b/packages/codemode/test/openapi.test.ts @@ -1021,13 +1021,12 @@ describe("OpenAPI.fromSpec", () => { await Effect.runPromise( location - .execute({ location: { directory: "/tmp", workspace: "workspace-1" } }) + .execute({ location: { directory: "/tmp" } }) .pipe(Effect.provide(client.layer)), ) const url = new URL(client.requests[0]!.url) expect(url.searchParams.get("location[directory]")).toBe("/tmp") - expect(url.searchParams.get("location[workspace]")).toBe("workspace-1") }) test("serializes supported simple and form parameter shapes", async () => { diff --git a/packages/core/src/control-plane/workspace.sql.ts b/packages/core/src/control-plane/workspace.sql.ts deleted file mode 100644 index b8fcebbfee..0000000000 --- a/packages/core/src/control-plane/workspace.sql.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core" -import { ProjectTable } from "../project/sql" -import { Project } from "../project" -import { Workspace } from "../workspace" - -export const WorkspaceTable = sqliteTable("workspace", { - id: text().$type().primaryKey(), - type: text().notNull(), - name: text().notNull().default(""), - branch: text(), - directory: text(), - extra: text({ mode: "json" }), - project_id: text() - .$type() - .notNull() - .references(() => ProjectTable.id, { onDelete: "cascade" }), - time_used: integer() - .notNull() - .$default(() => Date.now()), -}) diff --git a/packages/core/src/id/id.ts b/packages/core/src/id/id.ts index b4d6e496ea..b7b85ac25f 100644 --- a/packages/core/src/id/id.ts +++ b/packages/core/src/id/id.ts @@ -10,7 +10,6 @@ const prefixes = { part: "prt", pty: "pty", tool: "tool", - workspace: "wrk", } as const export function ascending(prefix: keyof typeof prefixes, given?: string) { diff --git a/packages/core/src/pty/ticket.ts b/packages/core/src/pty/ticket.ts index 033460818f..9e87e2350e 100644 --- a/packages/core/src/pty/ticket.ts +++ b/packages/core/src/pty/ticket.ts @@ -1,6 +1,5 @@ export * as PtyTicket from "./ticket" -import { Workspace } from "../workspace" import { PtyTicket } from "@opencode-ai/schema/pty-ticket" import { PtyID } from "./schema" import { Cache, Context, Duration, Effect, Layer } from "effect" @@ -14,7 +13,6 @@ export const ConnectToken = PtyTicket.ConnectToken export type Scope = { readonly ptyID: PtyID readonly directory?: string - readonly workspaceID?: Workspace.ID } export interface Interface { @@ -25,9 +23,7 @@ export interface Interface { export class Service extends Context.Service()("@opencode/PtyTicket") {} function matches(record: Scope, input: Scope) { - return ( - record.ptyID === input.ptyID && record.directory === input.directory && record.workspaceID === input.workspaceID - ) + return record.ptyID === input.ptyID && record.directory === input.directory } // Tickets are inserted via Cache.set and removed atomically via invalidateWhen. The lookup is diff --git a/packages/core/src/session.ts b/packages/core/src/session.ts index d52c156470..c541fc5cb3 100644 --- a/packages/core/src/session.ts +++ b/packages/core/src/session.ts @@ -5,7 +5,6 @@ import { Effect, Layer, Schema, Context, Stream, Scope } from "effect" import { ListAnchor } from "@opencode-ai/schema/session" import { and, asc, desc, eq, gt, isNotNull, isNull, like, lt, ne, or, type SQL } from "drizzle-orm" import { Project } from "./project" -import { Workspace } from "./workspace" import { Model } from "./model" import { Location } from "./location" import { SessionMessage } from "./session/message" @@ -59,12 +58,9 @@ import { fileURLToPath } from "url" // - by project // - by subpath -// - by workspace (home is special) - export { ListAnchor } const ListInputBase = { - workspaceID: Workspace.ID.pipe(Schema.optional), search: Schema.String.pipe(Schema.optional), limit: PositiveInt.pipe(Schema.optional), order: Schema.Literals(["asc", "desc"]).pipe(Schema.optional), @@ -206,7 +202,6 @@ export interface Interface { readonly move: (input: { sessionID: SessionSchema.ID directory: AbsolutePath - workspaceID?: Location.Ref["workspaceID"] }) => Effect.Effect readonly prompt: (input: { id?: SessionMessage.ID @@ -346,7 +341,6 @@ const layer = Layer.effect( parentID: input.parentID, directory: location.directory, path: path.relative(project.directory, location.directory).replaceAll("\\", "/"), - workspaceID: location.workspaceID ? Workspace.ID.make(location.workspaceID) : undefined, title: input.title, agent: input.agent, model: input.model @@ -434,7 +428,6 @@ const layer = Layer.effect( const sortColumn = SessionTable.time_updated const conditions: SQL[] = [] if ("directory" in input) conditions.push(eq(SessionTable.directory, input.directory)) - if (input.workspaceID) conditions.push(eq(SessionTable.workspace_id, input.workspaceID)) if ("project" in input) conditions.push(eq(SessionTable.project_id, input.project)) if ("project" in input && input.subpath !== undefined) conditions.push(eq(SessionTable.path, input.subpath)) if (input.search) conditions.push(like(SessionTable.title, `%${input.search}%`)) @@ -712,11 +705,7 @@ const layer = Layer.effect( const info = yield* fs.stat(directory).pipe(Effect.catch(() => Effect.succeed(undefined))) if (!info) return yield* new DestinationNotFoundError({ directory }) if (info.type !== "Directory") return yield* new DestinationNotDirectoryError({ directory }) - if ( - current.location.directory === directory && - current.location.workspaceID === input.workspaceID - ) - return + if (current.location.directory === directory) return const project = yield* projects.resolve(directory) yield* persistProject(project) if ((yield* execution.active).has(input.sessionID)) { @@ -725,7 +714,7 @@ const layer = Layer.effect( } yield* bus.publish(SessionEvent.Moved, { sessionID: input.sessionID, - location: Location.Ref.make({ directory, workspaceID: input.workspaceID }), + location: Location.Ref.make({ directory }), projectID: project.id, subpath: RelativePath.make(path.relative(project.directory, directory).replaceAll("\\", "/")), }) diff --git a/packages/core/src/session/projector.ts b/packages/core/src/session/projector.ts index 7c5ea02396..0a7e56b4a7 100644 --- a/packages/core/src/session/projector.ts +++ b/packages/core/src/session/projector.ts @@ -8,11 +8,9 @@ import { makeGlobalNode } from "@opencode-ai/util/effect/app-node" import { Model } from "../model" import { SessionEvent } from "./event" import { SessionV1 } from "../v1/session" -import { WorkspaceTable } from "../control-plane/workspace.sql" import { SessionMessage } from "./message" import { SessionMessageUpdater } from "./message-updater" import { SessionPending } from "./pending" -import { Workspace } from "../workspace" import { InstructionState } from "./instruction-state" import { MessageTable, PartTable, SessionPendingTable, SessionMessageTable, SessionTable } from "./sql" import type { DeepMutable } from "../schema" @@ -59,7 +57,7 @@ function sessionRow(info: SessionV1.SessionInfo): typeof SessionTable.$inferInse return { id: info.id, project_id: info.projectID, - workspace_id: info.workspaceID ?? null, + workspace_id: null, parent_id: info.parentID, slug: info.slug, directory: info.directory, @@ -429,14 +427,6 @@ const layer = Layer.effectDiscard( .get() .pipe(Effect.orDie) if (!stored) return yield* Effect.die(new SessionAlreadyProjected()) - if (event.data.info.workspaceID) { - yield* db - .update(WorkspaceTable) - .set({ time_used: Date.now() }) - .where(eq(WorkspaceTable.id, event.data.info.workspaceID)) - .run() - .pipe(Effect.orDie) - } }), ) yield* bus.project(SessionV1.Event.Updated, (event) => @@ -455,7 +445,7 @@ const layer = Layer.effectDiscard( directory: event.data.location.directory, path: event.data.subpath, ...(event.data.projectID ? { project_id: event.data.projectID } : {}), - workspace_id: event.data.location.workspaceID ? Workspace.ID.make(event.data.location.workspaceID) : null, + workspace_id: null, time_updated: DateTime.toEpochMillis(event.created), }) .where(eq(SessionTable.id, event.data.sessionID)) diff --git a/packages/core/test/pty/ticket.test.ts b/packages/core/test/pty/ticket.test.ts index 563264ec71..f3c56211cf 100644 --- a/packages/core/test/pty/ticket.test.ts +++ b/packages/core/test/pty/ticket.test.ts @@ -3,7 +3,6 @@ import { Effect, Layer } from "effect" import { LayerNode } from "@opencode-ai/util/effect/layer-node" import { PtyID } from "@opencode-ai/core/pty/schema" import { PtyTicket } from "@opencode-ai/core/pty/ticket" -import { Workspace } from "@opencode-ai/core/workspace" import { testEffect } from "../lib/effect" const it = testEffect(LayerNode.compile(PtyTicket.node)) @@ -46,17 +45,14 @@ describe("PTY websocket tickets", () => { }), ) - it.live("rejects tickets scoped to a different workspace", () => + it.live("rejects tickets scoped to a different pty", () => Effect.gen(function* () { const tickets = yield* PtyTicket.Service const ptyID = PtyID.ascending() - const workspaceID = Workspace.ID.ascending() - const issued = yield* tickets.issue({ ptyID, workspaceID }) + const issued = yield* tickets.issue({ ptyID }) - expect(yield* tickets.consume({ ptyID, workspaceID: Workspace.ID.ascending(), ticket: issued.ticket })).toBe( - false, - ) - expect(yield* tickets.consume({ ptyID, workspaceID, ticket: issued.ticket })).toBe(true) + expect(yield* tickets.consume({ ptyID: PtyID.ascending(), ticket: issued.ticket })).toBe(false) + expect(yield* tickets.consume({ ptyID, ticket: issued.ticket })).toBe(true) }), ) }) diff --git a/packages/core/test/session-create.test.ts b/packages/core/test/session-create.test.ts index c3d9f2be1f..5408701e20 100644 --- a/packages/core/test/session-create.test.ts +++ b/packages/core/test/session-create.test.ts @@ -24,7 +24,6 @@ import { SessionPending } from "@opencode-ai/core/session/pending" import { SessionEvent } from "@opencode-ai/core/session/event" import { SessionTable } from "@opencode-ai/core/session/sql" import { SessionStore } from "@opencode-ai/core/session/store" -import { Workspace } from "@opencode-ai/core/workspace" import { testEffect } from "./lib/effect" import { tmpdir } from "./fixture/tmpdir" @@ -119,7 +118,6 @@ describe("Session.create", () => { it.effect("stores supplied immutable create attributes", () => Effect.gen(function* () { const session = yield* Session.Service - const workspaceID = Workspace.ID.make("wrk_test") const model = Model.Ref.make({ id: Model.ID.make("sonnet"), providerID: Provider.ID.anthropic, @@ -128,11 +126,11 @@ describe("Session.create", () => { expect( yield* session.create({ - location: Location.Ref.make({ directory: location.directory, workspaceID }), + location: Location.Ref.make({ directory: location.directory }), agent: Agent.ID.make("build"), model, }), - ).toMatchObject({ location: { directory: location.directory, workspaceID }, agent: "build", model }) + ).toMatchObject({ location: { directory: location.directory }, agent: "build", model }) }), ) diff --git a/packages/plugin/src/v1/index.ts b/packages/plugin/src/v1/index.ts index edfa0139df..236f268e4a 100644 --- a/packages/plugin/src/v1/index.ts +++ b/packages/plugin/src/v1/index.ts @@ -23,44 +23,11 @@ export type ProviderContext = { options: Record } -export type WorkspaceInfo = { - id: string - type: string - name: string - branch: string | null - directory: string | null - extra: unknown | null - projectID: string -} - -export type WorkspaceTarget = - | { - type: "local" - directory: string - } - | { - type: "remote" - url: string | URL - headers?: HeadersInit - } - -export type WorkspaceAdapter = { - name: string - description: string - configure(config: WorkspaceInfo): WorkspaceInfo | Promise - create(config: WorkspaceInfo, env: Record, from?: WorkspaceInfo): Promise - remove(config: WorkspaceInfo): Promise - target(config: WorkspaceInfo): WorkspaceTarget | Promise -} - export type PluginInput = { client: ReturnType project: Project directory: string worktree: string - experimental_workspace: { - register(type: string, adapter: WorkspaceAdapter): void - } serverUrl: URL $: BunShell } diff --git a/packages/protocol/src/groups/location.ts b/packages/protocol/src/groups/location.ts index 4723b8a46a..871e904cac 100644 --- a/packages/protocol/src/groups/location.ts +++ b/packages/protocol/src/groups/location.ts @@ -6,7 +6,6 @@ export const LocationQuery = Schema.Struct({ location: Schema.optional( Schema.Struct({ directory: Schema.optional(Schema.String), - workspace: Schema.optional(Schema.String), }), ), }).annotate({ identifier: "LocationQuery" }) diff --git a/packages/protocol/src/groups/pty.ts b/packages/protocol/src/groups/pty.ts index a8fd1a3bc9..aa662e2e12 100644 --- a/packages/protocol/src/groups/pty.ts +++ b/packages/protocol/src/groups/pty.ts @@ -130,7 +130,7 @@ export const PtyGroup = HttpApiGroup.make("server.pty") "x-websocket": true, parameters: [ ...(operation.parameters ?? []), - ...["location[directory]", "location[workspace]", "cursor", PTY_CONNECT_TICKET_QUERY].map((name) => ({ + ...["location[directory]", "cursor", PTY_CONNECT_TICKET_QUERY].map((name) => ({ in: "query", name, schema: { type: "string" }, diff --git a/packages/protocol/src/groups/session.ts b/packages/protocol/src/groups/session.ts index 2abe6c47c7..8ed35d7139 100644 --- a/packages/protocol/src/groups/session.ts +++ b/packages/protocol/src/groups/session.ts @@ -6,7 +6,6 @@ import { InstructionEntry } from "@opencode-ai/schema/instruction-entry" import { Project } from "@opencode-ai/schema/project" import { AbsolutePath, PositiveInt, RelativePath, statics } from "@opencode-ai/schema/schema" import { Event } from "@opencode-ai/schema/event" -import { Workspace } from "@opencode-ai/schema/workspace" import { Context, Effect, Encoding, Result, Schema, SchemaGetter, Struct } from "effect" import { HttpApiEndpoint, HttpApiGroup, HttpApiMiddleware, HttpApiSchema, OpenApi } from "effect/unstable/httpapi" import { @@ -42,7 +41,6 @@ const ParentIDFilter = Schema.Union([ }) const SessionsQueryFields = { - workspace: Workspace.ID.pipe(Schema.optional), limit: Schema.NumberFromString.pipe(Schema.decodeTo(PositiveInt), Schema.optional).annotate({ description: "Maximum number of sessions to return. Defaults to the newest 50 sessions.", }), diff --git a/packages/protocol/test/session-cursor.test.ts b/packages/protocol/test/session-cursor.test.ts index 86c78d9503..c4b8300650 100644 --- a/packages/protocol/test/session-cursor.test.ts +++ b/packages/protocol/test/session-cursor.test.ts @@ -6,7 +6,6 @@ import { Session } from "@opencode-ai/schema/session" describe("SessionsCursor", () => { test("round trips without Node globals", async () => { const input = { - workspace: undefined, search: "protocol", order: "desc" as const, anchor: { id: Session.ID.make("ses_test"), time: 1, direction: "next" as const }, diff --git a/packages/schema/src/event-manifest.ts b/packages/schema/src/event-manifest.ts index 084c41dd7a..01931f4817 100644 --- a/packages/schema/src/event-manifest.ts +++ b/packages/schema/src/event-manifest.ts @@ -32,7 +32,6 @@ import { SessionStatusEvent } from "./session-status-event.js" import { SessionV1 } from "./session-v1.js" import { TuiEvent } from "./tui-event.js" import { VcsEvent } from "./vcs-event.js" -import { WorkspaceEvent } from "./workspace-event.js" import { WorktreeEvent } from "./worktree-event.js" import { WebSearch } from "./websearch.js" @@ -100,7 +99,6 @@ export const Definitions = Event.inventory( ...SessionStatusEvent.Definitions, ...SessionCompactionEvent.Definitions, ...VcsEvent.Definitions, - ...WorkspaceEvent.Definitions, ...WorktreeEvent.Definitions, ...ServerEvent.Definitions, ) diff --git a/packages/schema/src/workspace-event.ts b/packages/schema/src/workspace-event.ts deleted file mode 100644 index 2b6a752ef5..0000000000 --- a/packages/schema/src/workspace-event.ts +++ /dev/null @@ -1,32 +0,0 @@ -export * as WorkspaceEvent from "./workspace-event.js" - -import { Schema } from "effect" -import { Event } from "./event.js" -import { WorkspaceID } from "./workspace-id.js" - -export const ConnectionStatus = Schema.Struct({ - workspaceID: WorkspaceID, - status: Schema.Literals(["connected", "connecting", "disconnected", "error"]), -}).annotate({ identifier: "WorkspaceEvent.ConnectionStatus" }) -export interface ConnectionStatus extends Schema.Schema.Type {} - -export const Ready = Event.ephemeral({ - type: "workspace.ready", - schema: { - name: Schema.String, - }, -}) - -export const Failed = Event.ephemeral({ - type: "workspace.failed", - schema: { - message: Schema.String, - }, -}) - -export const Status = Event.ephemeral({ - type: "workspace.status", - schema: ConnectionStatus.fields, -}) - -export const Definitions = Event.inventory(Ready, Failed, Status) diff --git a/packages/schema/src/workspace.ts b/packages/schema/src/workspace.ts index f52cc3024f..40c0851e85 100644 --- a/packages/schema/src/workspace.ts +++ b/packages/schema/src/workspace.ts @@ -1,9 +1,6 @@ export * as Workspace from "./workspace.js" -import { WorkspaceEvent } from "./workspace-event.js" import { WorkspaceID } from "./workspace-id.js" export const ID = WorkspaceID export type ID = WorkspaceID - -export const Event = WorkspaceEvent diff --git a/packages/schema/test/event-manifest.test.ts b/packages/schema/test/event-manifest.test.ts index 40955425c7..b6632ce504 100644 --- a/packages/schema/test/event-manifest.test.ts +++ b/packages/schema/test/event-manifest.test.ts @@ -9,7 +9,6 @@ import { Project, Reference, Session, - Workspace, } from "../src/index.js" import { EventManifest } from "../src/event-manifest.js" import { FileSystemV1 } from "../src/filesystem-v1.js" @@ -20,7 +19,6 @@ import { SessionEvent } from "../src/session-event.js" import { SessionID } from "../src/session-id.js" import { SessionMessage } from "../src/session-message.js" import { SessionV1 } from "../src/session-v1.js" -import { WorkspaceEvent } from "../src/workspace-event.js" describe("public event manifest", () => { test("owns the complete public event surface", () => { @@ -60,8 +58,6 @@ describe("public event manifest", () => { test("uses canonical definitions for current public events", () => { expect(Session.Event).toBe(SessionEvent) expect(Session.Event.Definitions).toBe(SessionEvent.Definitions) - expect(Workspace.Event).toBe(WorkspaceEvent) - expect(Workspace.Event.Definitions).toBe(WorkspaceEvent.Definitions) expect(EventManifest.Latest.get("session.step.ended")).toBe(SessionEvent.Step.Ended) expect(EventManifest.Latest.get("agent.updated")).toBe(Agent.Event.Updated) expect(EventManifest.Latest.get("project.updated")).toBe(Project.Event.Updated) diff --git a/packages/server/src/handlers/location.ts b/packages/server/src/handlers/location.ts index ded8c8c2e0..9662cbb46f 100644 --- a/packages/server/src/handlers/location.ts +++ b/packages/server/src/handlers/location.ts @@ -10,7 +10,6 @@ export const LocationHandler = HttpApiBuilder.group(Api, "server.location", (han const location = yield* Location.Service return new Location.Info({ directory: location.directory, - workspaceID: location.workspaceID, project: location.project, }) }), diff --git a/packages/server/src/handlers/pty.ts b/packages/server/src/handlers/pty.ts index 7f8a9ea64e..f88cd409af 100644 --- a/packages/server/src/handlers/pty.ts +++ b/packages/server/src/handlers/pty.ts @@ -19,7 +19,7 @@ import { PtyEnvironment } from "../pty-environment" const ticketScope = Effect.gen(function* () { const location = yield* Location.Service - return { directory: location.directory as string, workspaceID: location.workspaceID } + return { directory: location.directory as string } }) export const PtyHandler = HttpApiBuilder.group(Api, "server.pty", (handlers) => diff --git a/packages/server/src/handlers/session.ts b/packages/server/src/handlers/session.ts index a62ccfcd6c..f10404e767 100644 --- a/packages/server/src/handlers/session.ts +++ b/packages/server/src/handlers/session.ts @@ -37,7 +37,6 @@ export const SessionHandler = HttpApiBuilder.group(Api, "server.session", (handl : ctx.query const page = yield* session.list({ ...query, - workspaceID: query.workspace, limit: ctx.query.limit ?? DefaultSessionsLimit, }) const sessions = page.data @@ -213,7 +212,6 @@ export const SessionHandler = HttpApiBuilder.group(Api, "server.session", (handl .move({ sessionID: ctx.params.sessionID, directory: ctx.payload.directory, - workspaceID: ctx.payload.workspaceID, }) .pipe( Effect.catchTag("Session.NotFoundError", (error) => diff --git a/packages/server/src/location.ts b/packages/server/src/location.ts index 7b23b5e489..7907c9c336 100644 --- a/packages/server/src/location.ts +++ b/packages/server/src/location.ts @@ -1,7 +1,6 @@ import { Location } from "@opencode-ai/core/location" import { LocationServiceMap } from "@opencode-ai/core/location-services" import { AbsolutePath } from "@opencode-ai/core/schema" -import { Workspace } from "@opencode-ai/core/workspace" import { Effect, Layer } from "effect" import { HttpServerRequest } from "effect/unstable/http" import { HttpApiMiddleware } from "effect/unstable/httpapi" @@ -18,7 +17,6 @@ export function response(data: Effect.Effect) { return { location: new Location.Info({ directory: location.directory, - workspaceID: location.workspaceID, project: location.project, }), data: yield* data, @@ -28,13 +26,11 @@ export function response(data: Effect.Effect) { export function requestRef(request: HttpServerRequest.HttpServerRequest): Location.Ref { const query = new URL(request.url, "http://localhost").searchParams - const workspaceID = query.get("location[workspace]") || request.headers["x-opencode-workspace"] const directory = query.get("location[directory]") || (request.headers["x-opencode-directory"] ? decode(request.headers["x-opencode-directory"]) : process.cwd()) return Location.Ref.make({ directory: AbsolutePath.make(directory), - workspaceID: workspaceID ? Workspace.ID.make(workspaceID) : undefined, }) } diff --git a/packages/server/src/middleware/session-location.ts b/packages/server/src/middleware/session-location.ts index d34076e5ce..fa91ff933e 100644 --- a/packages/server/src/middleware/session-location.ts +++ b/packages/server/src/middleware/session-location.ts @@ -4,7 +4,6 @@ import { Location } from "@opencode-ai/core/location" import { AbsolutePath } from "@opencode-ai/core/schema" import { Session } from "@opencode-ai/core/session" import { SessionTable } from "@opencode-ai/core/session/sql" -import { Workspace } from "@opencode-ai/core/workspace" import { eq } from "drizzle-orm" import { Effect, Layer, Schema } from "effect" import { HttpRouter } from "effect/unstable/http" @@ -40,7 +39,7 @@ export const sessionLocationLayer = Layer.effect( ), ) const row = yield* db - .select({ directory: SessionTable.directory, workspaceID: SessionTable.workspace_id }) + .select({ directory: SessionTable.directory }) .from(SessionTable) .where(eq(SessionTable.id, sessionID)) .get() @@ -56,7 +55,6 @@ export const sessionLocationLayer = Layer.effect( locations.get( Location.Ref.make({ directory: AbsolutePath.make(row.directory), - workspaceID: row.workspaceID ? Workspace.ID.make(row.workspaceID) : undefined, }), ), ),