refactor(protocol): refine vcs and shell APIs

This commit is contained in:
Dax Raad
2026-09-15 20:18:45 -04:00
parent fa3721fad6
commit b1f6d21ef3
25 changed files with 133 additions and 589 deletions
+39 -39
View File
@@ -111,7 +111,7 @@ Review endpoints in document order. For each endpoint, select one disposition an
|---|---|---|---|---|---|
| [x] 025 | `GET` | `/api/integration` | `integration.list` | Keep | Full integration inventory is consumed by authentication and integration-selection clients. |
| [x] 026 | `GET` | `/api/integration/{integrationID}` | `integration.get` | Change | Missing integration now returns typed `404` instead of optional data. |
| [x] | `POST` | `/api/experimental/integration/wellknown` | `experimental.integration.wellknown.add` | Experimental-only | Retained outside the stable API commitment. |
| [x] 027 | `POST` | `/api/experimental/integration/wellknown` | `experimental.integration.wellknown.add` | Experimental-only | Retained outside the stable API commitment. |
| [ ] 028 | `POST` | `/api/integration/{integrationID}/connect/key` | `integration.connect.key` | | |
| [ ] 029 | `POST` | `/api/integration/{integrationID}/connect/oauth` | `integration.oauth.connect` | | |
| [ ] 030 | `GET` | `/api/integration/{integrationID}/connect/oauth/{attemptID}` | `integration.oauth.status` | | |
@@ -185,7 +185,7 @@ Review endpoints in document order. For each endpoint, select one disposition an
| [x] 077 | `GET` | `/api/experimental/session/{sessionID}/instructions/entries` | `experimental.session.instructions.entry.list` | Experimental-only | API-managed durable context entries retained outside the stable API. |
| [x] 078 | `PUT` | `/api/experimental/session/{sessionID}/instructions/entries/{key}` | `experimental.session.instructions.entry.put` | Experimental-only | API-managed durable context entries retained outside the stable API. |
| [x] 079 | `DELETE` | `/api/experimental/session/{sessionID}/instructions/entries/{key}` | `experimental.session.instructions.entry.remove` | Experimental-only | API-managed durable context entries retained outside the stable API. |
| [x] | `GET` | `/api/experimental/session/{sessionID}/log` | `session.log` | Experimental-only | Retained outside the stable API commitment. |
| [x] 080 | `GET` | `/api/experimental/session/{sessionID}/log` | `session.log` | Experimental-only | Retained outside the stable API commitment. |
| [x] 081 | `GET` | `/api/session/{sessionID}/message/{messageID}` | `session.message.get` | Change | Normalized specific-message operation ID. |
| [x] 082 | `GET` | `/api/session/{sessionID}/message` | `session.message.list` | Change | Normalized session-scoped message-list operation ID. |
@@ -221,18 +221,18 @@ Review endpoints in document order. For each endpoint, select one disposition an
| Done | Method | Path | Operation ID | Decision | Notes |
|---|---|---|---|---|---|
| [ ] 102 | `GET` | `/api/fs/read/*` | `fs.read` | | |
| [ ] 103 | `GET` | `/api/fs/list` | `fs.list` | | |
| [ ] 104 | `GET` | `/api/fs/find` | `fs.find` | | |
| [ ] 105 | `GET` | `/api/worktree` | `worktree.list` | | |
| [ ] 106 | `POST` | `/api/worktree` | `worktree.create` | | |
| [ ] 107 | `DELETE` | `/api/worktree` | `worktree.remove` | | |
| [ ] 108 | `POST` | `/api/worktree/refresh` | `worktree.refresh` | | |
| [ ] 109 | `GET` | `/api/vcs` | `vcs.get` | | |
| [ ] 110 | `GET` | `/api/vcs/base` | `vcs.base` | | |
| [ ] 111 | `GET` | `/api/vcs/status` | `vcs.status` | | |
| [ ] 112 | `GET` | `/api/vcs/branches` | `vcs.branches` | | |
| [ ] 113 | `GET` | `/api/vcs/diff` | `vcs.diff` | | |
| [x] 102 | `GET` | `/api/fs/read/*` | `fs.read` | Keep | Relative wildcard file reads and raw byte responses retained. |
| [x] 103 | `GET` | `/api/fs/list` | `fs.list` | Keep | Existing path scope and minimal entry metadata retained. |
| [x] 104 | `GET` | `/api/fs/find` | `fs.find` | Keep | Existing ranked filesystem search retained. |
| [x] 105 | `GET` | `/api/worktree` | `worktree.list` | Keep | Reviewed separately by coworker. |
| [x] 106 | `POST` | `/api/worktree` | `worktree.create` | Keep | Reviewed separately by coworker. |
| [x] 107 | `DELETE` | `/api/worktree` | `worktree.remove` | Keep | Reviewed separately by coworker. |
| [x] 108 | `POST` | `/api/worktree/refresh` | `worktree.refresh` | Keep | Reviewed separately by coworker. |
| [x] 109 | `GET` | `/api/vcs` | `vcs.get` | Change | Preserved branch nesting and added selected VCS provider ID. |
| [x] 110 | `GET` | `/api/vcs/base` | `vcs.base` | Keep | Review-base inference and nullable unavailable state retained. |
| [x] 111 | `GET` | `/api/vcs/status` | `vcs.status` | Keep | Existing working-copy status shape retained for now. |
| [x] 112 | `GET` | `/api/vcs/branch` | `vcs.branch.list` | Change | Singular collection path and normalized operation ID. |
| [x] 113 | `GET` | `/api/vcs/diff` | `vcs.diff` | Keep | Existing working, branch, and committed comparison modes retained. |
## Group 9: PTYs, persistent terminals, and shells
@@ -240,30 +240,30 @@ Review endpoints in document order. For each endpoint, select one disposition an
| Done | Method | Path | Operation ID | Decision | Notes |
|---|---|---|---|---|---|
| [ ] 114 | `GET` | `/api/pty` | `pty.list` | | |
| [ ] 115 | `POST` | `/api/pty` | `pty.create` | | |
| [ ] 116 | `GET` | `/api/pty/{ptyID}` | `pty.get` | | |
| [ ] 117 | `PUT` | `/api/pty/{ptyID}` | `pty.update` | | |
| [ ] 118 | `DELETE` | `/api/pty/{ptyID}` | `pty.remove` | | |
| [ ] 119 | `POST` | `/api/pty/{ptyID}/connect-token` | `pty.connect.token` | | |
| [ ] 120 | `GET` | `/api/pty/{ptyID}/connect` | `pty.connect` | | |
| [x] | `GET` | `/api/experimental/session/{sessionID}/terminal/read` | `server.experimental.persistentPty.read` | Experimental-only | Retained outside the stable API commitment. |
| [x] | `GET` | `/api/experimental/session/{sessionID}/terminal` | `server.experimental.persistentPty.list` | Experimental-only | Retained outside the stable API commitment. |
| [x] | `POST` | `/api/experimental/session/{sessionID}/terminal` | `server.experimental.persistentPty.create` | Experimental-only | Retained outside the stable API commitment. |
| [x] | `POST` | `/api/experimental/persistent-pty/shutdown` | `server.experimental.persistentPty.shutdown` | Experimental-only | Retained outside the stable API commitment. |
| [x] | `POST` | `/api/experimental/persistent-pty/handoff` | `server.experimental.persistentPty.handoff` | Experimental-only | Retained outside the stable API commitment. |
| [x] | `GET` | `/api/experimental/persistent-pty/{ptyID}` | `server.experimental.persistentPty.get` | Experimental-only | Retained outside the stable API commitment. |
| [x] | `PUT` | `/api/experimental/persistent-pty/{ptyID}` | `server.experimental.persistentPty.update` | Experimental-only | Retained outside the stable API commitment. |
| [x] | `DELETE` | `/api/experimental/persistent-pty/{ptyID}` | `server.experimental.persistentPty.remove` | Experimental-only | Retained outside the stable API commitment. |
| [x] | `GET` | `/api/experimental/persistent-pty/{ptyID}/snapshot` | `server.experimental.persistentPty.snapshot` | Experimental-only | Retained outside the stable API commitment. |
| [x] | `POST` | `/api/experimental/persistent-pty/{ptyID}/connect-token` | `server.experimental.persistentPty.connectToken` | Experimental-only | Retained outside the stable API commitment. |
| [x] | `GET` | `/api/experimental/persistent-pty/{ptyID}/connect` | `persistentPty.connect` | Experimental-only | Retained outside the stable API commitment. |
| [ ] 132 | `GET` | `/api/shell` | `shell.list` | | |
| [ ] 133 | `POST` | `/api/shell` | `shell.create` | | |
| [ ] 134 | `GET` | `/api/shell/{id}` | `shell.get` | | |
| [ ] 135 | `DELETE` | `/api/shell/{id}` | `shell.remove` | | |
| [ ] 136 | `PATCH` | `/api/shell/{id}/timeout` | `shell.timeout` | | |
| [ ] 137 | `GET` | `/api/shell/{id}/output` | `shell.output` | | |
| [x] 114 | `GET` | `/api/pty` | `pty.list` | Keep | PTY endpoints reviewed together and retained. |
| [x] 115 | `POST` | `/api/pty` | `pty.create` | Keep | PTY endpoints reviewed together and retained. |
| [x] 116 | `GET` | `/api/pty/{ptyID}` | `pty.get` | Keep | PTY endpoints reviewed together and retained. |
| [x] 117 | `PUT` | `/api/pty/{ptyID}` | `pty.update` | Keep | PTY endpoints reviewed together and retained. |
| [x] 118 | `DELETE` | `/api/pty/{ptyID}` | `pty.remove` | Keep | PTY endpoints reviewed together and retained. |
| [x] 119 | `POST` | `/api/pty/{ptyID}/connect-token` | `pty.connect.token` | Keep | PTY endpoints reviewed together and retained. |
| [x] 120 | `GET` | `/api/pty/{ptyID}/connect` | `pty.connect` | Keep | PTY endpoints reviewed together and retained. |
| [x] 121 | `GET` | `/api/experimental/session/{sessionID}/terminal/read` | `server.experimental.persistentPty.read` | Experimental-only | Retained outside the stable API commitment. |
| [x] 122 | `GET` | `/api/experimental/session/{sessionID}/terminal` | `server.experimental.persistentPty.list` | Experimental-only | Retained outside the stable API commitment. |
| [x] 123 | `POST` | `/api/experimental/session/{sessionID}/terminal` | `server.experimental.persistentPty.create` | Experimental-only | Retained outside the stable API commitment. |
| [x] 124 | `POST` | `/api/experimental/persistent-pty/shutdown` | `server.experimental.persistentPty.shutdown` | Experimental-only | Retained outside the stable API commitment. |
| [x] 125 | `POST` | `/api/experimental/persistent-pty/handoff` | `server.experimental.persistentPty.handoff` | Experimental-only | Retained outside the stable API commitment. |
| [x] 126 | `GET` | `/api/experimental/persistent-pty/{ptyID}` | `server.experimental.persistentPty.get` | Experimental-only | Retained outside the stable API commitment. |
| [x] 127 | `PUT` | `/api/experimental/persistent-pty/{ptyID}` | `server.experimental.persistentPty.update` | Experimental-only | Retained outside the stable API commitment. |
| [x] 128 | `DELETE` | `/api/experimental/persistent-pty/{ptyID}` | `server.experimental.persistentPty.remove` | Experimental-only | Retained outside the stable API commitment. |
| [x] 129 | `GET` | `/api/experimental/persistent-pty/{ptyID}/snapshot` | `server.experimental.persistentPty.snapshot` | Experimental-only | Retained outside the stable API commitment. |
| [x] 130 | `POST` | `/api/experimental/persistent-pty/{ptyID}/connect-token` | `server.experimental.persistentPty.connectToken` | Experimental-only | Retained outside the stable API commitment. |
| [x] 131 | `GET` | `/api/experimental/persistent-pty/{ptyID}/connect` | `persistentPty.connect` | Experimental-only | Retained outside the stable API commitment. |
| [x] 132 | `GET` | `/api/shell` | `shell.list` | Change | Stable shell inventory retained; numeric timestamps documented as epoch milliseconds. |
| [x] 133 | `POST` | `/api/shell` | `shell.create` | Change | Timeout is optional and defaults to zero; caller metadata retained. |
| [x] 134 | `GET` | `/api/shell/{id}` | `shell.get` | Keep | Specific running or retained shell read retained. |
| [x] 135 | `DELETE` | `/api/shell/{id}` | `shell.remove` | Change | Shell deletion is idempotent and returns `204` when already absent. |
| [x] 136 | — | — | — | Remove | Timeout mutation remains an internal Core shell operation. |
| [x] 137 | `GET` | `/api/shell/{id}/output` | `shell.output` | Keep | Existing byte-cursor text output paging retained. |
## Group 10: Events, RPC, and experimental operations
@@ -276,4 +276,4 @@ Review endpoints in document order. For each endpoint, select one disposition an
| [ ] 140 | `GET` | `/api/event` | `event.subscribe` | | |
| [ ] 141 | `GET` | `/api/debug/location` | `debug.location.list` | | |
| [ ] 142 | `DELETE` | `/api/debug/location` | `debug.location.evict` | | |
| [x] | `GET` | `/api/experimental/migration/v1` | `experimental.migration.v1.status` | Experimental-only | Retained outside the stable API commitment. |
| [x] 143 | `GET` | `/api/experimental/migration/v1` | `experimental.migration.v1.status` | Experimental-only | Retained outside the stable API commitment. |
@@ -22,7 +22,7 @@ test("selects a base branch for a new workspace", async ({ page }) => {
pageMessages: () => ({ items: [] }),
vcsBranches: ["feature/api", "main", "origin/release"],
})
await page.route("**/api/vcs/branches?*", (route) => {
await page.route("**/api/vcs/branch?*", (route) => {
if (new URL(route.request().url()).searchParams.get("search") !== "feature") return route.fallback()
return route.fulfill({ json: { location: { directory }, data: ["feature/api"] } })
})
+1 -1
View File
@@ -115,7 +115,7 @@ const Group = HttpApiGroup.make("mock")
.add(HttpApiEndpoint.get("formRequests", "/api/form", { success: Json }))
.add(HttpApiEndpoint.get("vcs", "/api/vcs", { success: Json }))
.add(HttpApiEndpoint.get("vcsStatus", "/api/vcs/status", { success: Json }))
.add(HttpApiEndpoint.get("vcsBranches", "/api/vcs/branches", { success: Json }))
.add(HttpApiEndpoint.get("vcsBranches", "/api/vcs/branch", { success: Json }))
.add(HttpApiEndpoint.get("vcsDiff", "/api/vcs/diff", { success: Json }))
.add(HttpApiEndpoint.get("fsList", "/api/fs/list", { query: Query, success: Json }))
.add(
@@ -142,7 +142,7 @@ export function createNewSessionWorkspaceController(input: {
() => (visible() ? { directory: projectRoot(), search: state.search } : undefined),
({ directory, search }) =>
serverSDK.api.vcs
.branches({ location: { directory }, search, limit: 50 })
.branch.list({ location: { directory }, search, limit: 50 })
.then((response) => ({ directory, search, data: response.data }))
.catch(() => ({ directory, search, data: [] })),
)
@@ -98,7 +98,7 @@ describe("createRequestQueue", () => {
test("classifies git and worktree endpoints as slow", () => {
expect(isSlowRequest("/api/vcs")).toBe(true)
expect(isSlowRequest("/api/vcs/branches")).toBe(true)
expect(isSlowRequest("/api/vcs/branch")).toBe(true)
expect(isSlowRequest("/api/worktree")).toBe(true)
expect(isSlowRequest("/api/vcsx")).toBe(false)
expect(isSlowRequest("/api/session")).toBe(false)
+5 -14
View File
@@ -2102,7 +2102,7 @@ export type ShellCreateInput = {
readonly location?: { readonly directory?: string | undefined } | undefined
readonly command: string
readonly cwd?: string | undefined
readonly timeout: number
readonly timeout?: number | undefined
readonly metadata?: { readonly [x: string]: unknown } | undefined
}
export type ShellCreateOutput = { readonly location: Location.PublicRef; readonly data: Shell.Info }
@@ -2115,14 +2115,6 @@ export type ShellGetInput = {
export type ShellGetOutput = { readonly location: Location.PublicRef; readonly data: Shell.Info }
export type ShellGetOperation<E = never> = (input: ShellGetInput) => Effect.Effect<ShellGetOutput, E>
export type ShellTimeoutInput = {
readonly id: Shell.ID
readonly location?: { readonly directory?: string | undefined } | undefined
readonly timeout: number
}
export type ShellTimeoutOutput = { readonly location: Location.PublicRef; readonly data: Shell.Info }
export type ShellTimeoutOperation<E = never> = (input: ShellTimeoutInput) => Effect.Effect<ShellTimeoutOutput, E>
export type ShellOutputInput = {
readonly id: Shell.ID
readonly location?: { readonly directory?: string | undefined } | undefined
@@ -2151,7 +2143,6 @@ export interface ShellApi<E = never> {
readonly list: ShellListOperation<E>
readonly create: ShellCreateOperation<E>
readonly get: ShellGetOperation<E>
readonly timeout: ShellTimeoutOperation<E>
readonly output: ShellOutputOperation<E>
readonly remove: ShellRemoveOperation<E>
}
@@ -2214,13 +2205,13 @@ export type VcsStatusInput = { readonly location?: { readonly directory?: string
export type VcsStatusOutput = { readonly location: Location.PublicRef; readonly data: ReadonlyArray<Vcs.FileStatus> }
export type VcsStatusOperation<E = never> = (input?: VcsStatusInput) => Effect.Effect<VcsStatusOutput, E>
export type VcsBranchesInput = {
export type VcsBranchListInput = {
readonly location?: { readonly directory?: string | undefined } | undefined
readonly search?: string | undefined
readonly limit?: number | undefined
}
export type VcsBranchesOutput = { readonly location: Location.PublicRef; readonly data: Vcs.BranchList }
export type VcsBranchesOperation<E = never> = (input?: VcsBranchesInput) => Effect.Effect<VcsBranchesOutput, E>
export type VcsBranchListOutput = { readonly location: Location.PublicRef; readonly data: Vcs.BranchList }
export type VcsBranchListOperation<E = never> = (input?: VcsBranchListInput) => Effect.Effect<VcsBranchListOutput, E>
export type VcsDiffInput = {
readonly location?: { readonly directory?: string | undefined } | undefined
@@ -2235,7 +2226,7 @@ export interface VcsApi<E = never> {
readonly get: VcsGetOperation<E>
readonly base: VcsBaseOperation<E>
readonly status: VcsStatusOperation<E>
readonly branches: VcsBranchesOperation<E>
readonly branch: { readonly list: VcsBranchListOperation<E> }
readonly diff: VcsDiffOperation<E>
}
+6 -18
View File
@@ -221,8 +221,6 @@ import type {
ShellCreateOutput,
ShellGetInput,
ShellGetOutput,
ShellTimeoutInput,
ShellTimeoutOutput,
ShellOutputInput,
ShellOutputOutput,
ShellRemoveInput,
@@ -243,8 +241,8 @@ import type {
VcsBaseOutput,
VcsStatusInput,
VcsStatusOutput,
VcsBranchesInput,
VcsBranchesOutput,
VcsBranchListInput,
VcsBranchListOutput,
VcsDiffInput,
VcsDiffOutput,
DebugLocationListOutput,
@@ -1363,15 +1361,6 @@ const EndpointShellGet = (raw: RawClient["server.shell"]) => (input: ShellGetInp
),
)
const EndpointShellTimeout = (raw: RawClient["server.shell"]) => (input: ShellTimeoutInput) =>
preserveEffect<ShellTimeoutOutput>()(
raw["shell.timeout"]({
params: { id: input["id"] },
query: { location: input["location"] },
payload: { timeout: input["timeout"] },
}).pipe(Effect.mapError(mapClientError)),
)
const EndpointShellOutput = (raw: RawClient["server.shell"]) => (input: ShellOutputInput) =>
preserveEffect<ShellOutputOutput>()(
raw["shell.output"]({
@@ -1391,7 +1380,6 @@ const adaptGroupShell = (raw: RawClient["server.shell"]) => ({
list: EndpointShellList(raw),
create: EndpointShellCreate(raw),
get: EndpointShellGet(raw),
timeout: EndpointShellTimeout(raw),
output: EndpointShellOutput(raw),
remove: EndpointShellRemove(raw),
})
@@ -1455,9 +1443,9 @@ const EndpointVcsStatus = (raw: RawClient["server.vcs"]) => (input?: VcsStatusIn
raw["vcs.status"]({ query: { location: input?.["location"] } }).pipe(Effect.mapError(mapClientError)),
)
const EndpointVcsBranches = (raw: RawClient["server.vcs"]) => (input?: VcsBranchesInput) =>
preserveEffect<VcsBranchesOutput>()(
raw["vcs.branches"]({
const EndpointVcsBranchList = (raw: RawClient["server.vcs"]) => (input?: VcsBranchListInput) =>
preserveEffect<VcsBranchListOutput>()(
raw["vcs.branch.list"]({
query: { location: input?.["location"], search: input?.["search"], limit: input?.["limit"] },
}).pipe(Effect.mapError(mapClientError)),
)
@@ -1473,7 +1461,7 @@ const adaptGroupVcs = (raw: RawClient["server.vcs"]) => ({
get: EndpointVcsGet(raw),
base: EndpointVcsBase(raw),
status: EndpointVcsStatus(raw),
branches: EndpointVcsBranches(raw),
branch: { list: EndpointVcsBranchList(raw) },
diff: EndpointVcsDiff(raw),
})
+17 -30
View File
@@ -217,8 +217,6 @@ import type {
ShellCreateOutput,
ShellGetInput,
ShellGetOutput,
ShellTimeoutInput,
ShellTimeoutOutput,
ShellOutputInput,
ShellOutputOutput,
ShellRemoveInput,
@@ -239,8 +237,8 @@ import type {
VcsBaseOutput,
VcsStatusInput,
VcsStatusOutput,
VcsBranchesInput,
VcsBranchesOutput,
VcsBranchListInput,
VcsBranchListOutput,
VcsDiffInput,
VcsDiffOutput,
DebugLocationListOutput,
@@ -1860,19 +1858,6 @@ export function make(options: ClientOptions) {
},
requestOptions,
),
timeout: (input: ShellTimeoutInput, requestOptions?: RequestOptions) =>
request<ShellTimeoutOutput>(
{
method: "PATCH",
path: `/api/shell/${encodeURIComponent(input.id)}/timeout`,
query: { location: input["location"] },
body: { timeout: input["timeout"] },
successStatus: 200,
declaredStatuses: [400, 401, 404],
empty: false,
},
requestOptions,
),
output: (input: ShellOutputInput, requestOptions?: RequestOptions) =>
request<ShellOutputOutput>(
{
@@ -1892,7 +1877,7 @@ export function make(options: ClientOptions) {
path: `/api/shell/${encodeURIComponent(input.id)}`,
query: { location: input["location"] },
successStatus: 204,
declaredStatuses: [400, 401, 404],
declaredStatuses: [400, 401],
empty: true,
},
requestOptions,
@@ -2005,18 +1990,20 @@ export function make(options: ClientOptions) {
},
requestOptions,
),
branches: (input?: VcsBranchesInput, requestOptions?: RequestOptions) =>
request<VcsBranchesOutput>(
{
method: "GET",
path: `/api/vcs/branches`,
query: { location: input?.["location"], search: input?.["search"], limit: input?.["limit"] },
successStatus: 200,
declaredStatuses: [400, 401],
empty: false,
},
requestOptions,
),
branch: {
list: (input?: VcsBranchListInput, requestOptions?: RequestOptions) =>
request<VcsBranchListOutput>(
{
method: "GET",
path: `/api/vcs/branch`,
query: { location: input?.["location"], search: input?.["search"], limit: input?.["limit"] },
successStatus: 200,
declaredStatuses: [400, 401],
empty: false,
},
requestOptions,
),
},
diff: (input: VcsDiffInput, requestOptions?: RequestOptions) =>
request<VcsDiffOutput>(
{
+8 -16
View File
@@ -1632,7 +1632,7 @@ export type ReferenceSource = ReferenceLocalSource | ReferenceGitSource
export type WorktreeList = Array<WorktreeDirectory>
export type VcsInfo = { branch: VcsBranch }
export type VcsInfo = { provider?: string; branch: VcsBranch }
export type SessionInboxMove = {
id: string
@@ -5998,25 +5998,25 @@ export type ShellCreateInput = {
readonly command: {
readonly command: string
readonly cwd?: string
readonly timeout: number
readonly timeout?: number
readonly metadata?: { readonly [x: string]: JsonValue }
}["command"]
readonly cwd?: {
readonly command: string
readonly cwd?: string
readonly timeout: number
readonly timeout?: number
readonly metadata?: { readonly [x: string]: JsonValue }
}["cwd"]
readonly timeout: {
readonly timeout?: {
readonly command: string
readonly cwd?: string
readonly timeout: number
readonly timeout?: number
readonly metadata?: { readonly [x: string]: JsonValue }
}["timeout"]
readonly metadata?: {
readonly command: string
readonly cwd?: string
readonly timeout: number
readonly timeout?: number
readonly metadata?: { readonly [x: string]: JsonValue }
}["metadata"]
}
@@ -6030,14 +6030,6 @@ export type ShellGetInput = {
export type ShellGetOutput = { location: LocationPublicRef; data: ShellInfo1 }
export type ShellTimeoutInput = {
readonly id: { readonly id: string }["id"]
readonly location?: { readonly location?: { readonly directory?: string | undefined } | undefined }["location"]
readonly timeout: { readonly timeout: number }["timeout"]
}
export type ShellTimeoutOutput = { location: LocationPublicRef; data: ShellInfo1 }
export type ShellOutputInput = {
readonly id: { readonly id: string }["id"]
readonly location?: {
@@ -6149,7 +6141,7 @@ export type VcsStatusInput = {
export type VcsStatusOutput = { location: LocationPublicRef; data: Array<VcsFileStatus> }
export type VcsBranchesInput = {
export type VcsBranchListInput = {
readonly location?: {
readonly location?: { readonly directory?: string | undefined } | undefined
readonly search?: string | undefined
@@ -6167,7 +6159,7 @@ export type VcsBranchesInput = {
}["limit"]
}
export type VcsBranchesOutput = { location: LocationPublicRef; data: VcsBranchList }
export type VcsBranchListOutput = { location: LocationPublicRef; data: VcsBranchList }
export type VcsDiffInput = {
readonly location?: {
+3 -1
View File
@@ -458,7 +458,9 @@ export const make = Effect.fn("PluginHost.make")(function* (
vcs: {
get: () => response(vcs.info()),
base: () => response(vcs.base()),
branches: (input) => response(vcs.branches({ search: input?.search, limit: input?.limit })),
branch: {
list: (input) => response(vcs.branches({ search: input?.search, limit: input?.limit })),
},
status: () => response(vcs.status()),
diff: (input) => response(vcs.diff(input.mode, { context: input.context, base: input.base })),
transform: vcs.transform,
+1 -1
View File
@@ -257,7 +257,7 @@ const layer = () =>
const invocation: ShellCreateBefore = {
command: input.command,
cwd: input.cwd ?? location.directory,
timeout: input.timeout,
timeout: input.timeout ?? 0,
shell: input.shell ?? (yield* shell.resolve({ priority: "config" })),
env: {
...(sessionEnvironment ?? process.env),
+4 -1
View File
@@ -123,7 +123,10 @@ const layer = Layer.effect(
const changed = yield* Effect.gen(function* () {
const provider = selected()
const next: Info = provider
? yield* protect(provider, "info", provider.info(scope).pipe(Effect.flatMap(decodeInfo)), { branch: {} })
? {
...(yield* protect(provider, "info", provider.info(scope).pipe(Effect.flatMap(decodeInfo)), { branch: {} })),
provider: provider.id,
}
: { branch: {} }
const changed = current.info.branch.current !== next.branch.current
current.info = next
+3 -1
View File
@@ -138,7 +138,9 @@ export function host(overrides: Overrides = {}): Plugin.Context {
vcs: overrides.vcs ?? {
base: () => Effect.die("unused vcs.base"),
get: () => Effect.die("unused vcs.get"),
branches: () => Effect.die("unused vcs.branches"),
branch: {
list: () => Effect.die("unused vcs.branch.list"),
},
status: () => Effect.die("unused vcs.status"),
diff: () => Effect.die("unused vcs.diff"),
transform: () => Effect.die("unused vcs.transform"),
+3 -1
View File
@@ -501,7 +501,9 @@ export function fromPromise(plugin: Plugin) {
vcs: {
get: adaptApiMethod(VcsEndpoints["vcs.get"], host.vcs.get),
base: adaptApiMethod(VcsEndpoints["vcs.base"], host.vcs.base),
branches: adaptApiMethod(VcsEndpoints["vcs.branches"], host.vcs.branches),
branch: {
list: adaptApiMethod(VcsEndpoints["vcs.branch.list"], host.vcs.branch.list),
},
status: adaptApiMethod(VcsEndpoints["vcs.status"], host.vcs.status),
diff: adaptApiMethod(VcsEndpoints["vcs.diff"], host.vcs.diff),
reload: () => run(host.vcs.reload()),
+9 -135
View File
@@ -10613,7 +10613,7 @@
"type": "object"
}
},
"required": ["command", "timeout"],
"required": ["command"],
"additionalProperties": false
}
}
@@ -10791,142 +10791,12 @@
}
}
}
},
"404": {
"description": "ShellNotFoundError",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ShellNotFoundErrorEncoded"
}
}
}
}
},
"description": "Terminate and remove one shell command and its retained output.",
"summary": "Remove shell command"
}
},
"/api/shell/{id}/timeout": {
"patch": {
"tags": ["shell"],
"operationId": "shell.timeout",
"parameters": [
{
"name": "id",
"in": "path",
"schema": {
"type": "string",
"pattern": "^sh_"
},
"required": true
},
{
"name": "location",
"in": "query",
"schema": {
"anyOf": [
{
"type": "object",
"properties": {
"directory": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
}
},
"additionalProperties": false
},
{
"type": "null"
}
]
},
"required": false,
"style": "deepObject",
"explode": true
}
],
"security": [],
"responses": {
"200": {
"description": "Success",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"location": {
"$ref": "#/components/schemas/Location.PublicRef"
},
"data": {
"$ref": "#/components/schemas/Shell.Info"
}
},
"required": ["location", "data"],
"additionalProperties": false
}
}
}
},
"400": {
"description": "InvalidRequestError",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/InvalidRequestErrorEncoded"
}
}
}
},
"401": {
"description": "UnauthorizedError",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UnauthorizedErrorEncoded"
}
}
}
},
"404": {
"description": "ShellNotFoundError",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ShellNotFoundErrorEncoded"
}
}
}
}
},
"description": "Replace a running shell command's timeout from now, or clear it with zero.",
"summary": "Update shell timeout",
"requestBody": {
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"timeout": {
"type": "integer",
"minimum": 0
}
},
"required": ["timeout"],
"additionalProperties": false
}
}
},
"required": true
}
}
},
"/api/shell/{id}/output": {
"get": {
"tags": ["shell"],
@@ -11677,10 +11547,10 @@
"summary": "VCS status"
}
},
"/api/vcs/branches": {
"/api/vcs/branch": {
"get": {
"tags": ["vcs"],
"operationId": "vcs.branches",
"operationId": "vcs.branch.list",
"parameters": [
{
"name": "location",
@@ -18498,7 +18368,8 @@
"type": "object",
"properties": {
"started": {
"type": "number"
"type": "number",
"description": "Start time in milliseconds since the Unix epoch"
},
"completed": {
"type": "number"
@@ -18752,6 +18623,9 @@
"Vcs.Info": {
"type": "object",
"properties": {
"provider": {
"type": "string"
},
"branch": {
"$ref": "#/components/schemas/Vcs.Branch"
}
@@ -19006,7 +18880,7 @@
},
{
"name": "shell",
"description": "Experimental location-scoped shell command routes."
"description": "Location-scoped shell command routes."
},
{
"name": "reference",
+1 -24
View File
@@ -1,15 +1,10 @@
import { Shell } from "@opencode/schema/shell"
import { Location } from "@opencode/schema/location"
import { NonNegativeInt } from "@opencode/schema/schema"
import { Schema } from "effect"
import { HttpApiEndpoint, HttpApiGroup, HttpApiSchema, OpenApi } from "effect/unstable/httpapi"
import { ShellNotFoundError } from "../errors.js"
import { LocationQuery, locationQueryOpenApi } from "./location.js"
const TimeoutInput = Schema.Struct({
timeout: NonNegativeInt,
})
export const ShellGroup = HttpApiGroup.make("server.shell")
.add(
HttpApiEndpoint.get("shell.list", "/api/shell", {
@@ -57,23 +52,6 @@ export const ShellGroup = HttpApiGroup.make("server.shell")
}),
),
)
.add(
HttpApiEndpoint.patch("shell.timeout", "/api/shell/:id/timeout", {
params: { id: Shell.ID },
query: LocationQuery,
payload: TimeoutInput,
success: Location.response(Shell.Info),
error: ShellNotFoundError,
})
.annotateMerge(locationQueryOpenApi)
.annotateMerge(
OpenApi.annotations({
identifier: "shell.timeout",
summary: "Update shell timeout",
description: "Replace a running shell command's timeout from now, or clear it with zero.",
}),
),
)
.add(
HttpApiEndpoint.get("shell.output", "/api/shell/:id/output", {
params: { id: Shell.ID },
@@ -95,7 +73,6 @@ export const ShellGroup = HttpApiGroup.make("server.shell")
params: { id: Shell.ID },
query: LocationQuery,
success: HttpApiSchema.NoContent,
error: ShellNotFoundError,
})
.annotateMerge(locationQueryOpenApi)
.annotateMerge(
@@ -107,5 +84,5 @@ export const ShellGroup = HttpApiGroup.make("server.shell")
),
)
.annotateMerge(
OpenApi.annotations({ title: "shell", description: "Experimental location-scoped shell command routes." }),
OpenApi.annotations({ title: "shell", description: "Location-scoped shell command routes." }),
)
+2 -2
View File
@@ -66,14 +66,14 @@ export const VcsGroup = HttpApiGroup.make("server.vcs")
),
)
.add(
HttpApiEndpoint.get("vcs.branches", "/api/vcs/branches", {
HttpApiEndpoint.get("vcs.branch.list", "/api/vcs/branch", {
query: BranchesQuery,
success: Location.response(Vcs.BranchList),
})
.annotateMerge(locationQueryOpenApi)
.annotateMerge(
OpenApi.annotations({
identifier: "vcs.branches",
identifier: "vcs.branch.list",
summary: "VCS branches",
description: "List local and remote branches available at the requested location.",
}),
+5 -3
View File
@@ -23,8 +23,10 @@ export const Status = Schema.Literals(["running", "exited", "timeout", "killed"]
export type Status = typeof Status.Type
export const Time = Schema.Struct({
started: Schema.Finite,
completed: optional(Schema.Finite),
started: Schema.Finite.annotate({ description: "Start time in milliseconds since the Unix epoch" }),
completed: optional(Schema.Finite).annotate({
description: "Completion time in milliseconds since the Unix epoch",
}),
})
export interface Time extends Schema.Schema.Type<typeof Time> {}
@@ -57,7 +59,7 @@ export const Event = { Created, Exited, Deleted, Definitions: inventory(Created,
export const CreateInput = Schema.Struct({
command: Schema.String,
cwd: optional(Schema.String),
timeout: NonNegativeInt,
timeout: optional(NonNegativeInt),
metadata: optional(Metadata),
})
export interface CreateInput extends Schema.Schema.Type<typeof CreateInput> {}
+1
View File
@@ -10,6 +10,7 @@ export const Branch = Schema.Struct({
export interface Branch extends Schema.Schema.Type<typeof Branch> {}
export const Info = Schema.Struct({
provider: optional(Schema.String),
branch: Branch,
}).annotate({ identifier: "Vcs.Info" })
export interface Info extends Schema.Schema.Type<typeof Info> {}
+1 -26
View File
@@ -43,23 +43,6 @@ export const ShellHandler = HttpApiBuilder.group(Api, "server.shell", (handlers)
)
}),
)
.handle(
"shell.timeout",
Effect.fn(function* (ctx) {
const shell = yield* Shell.Service
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}` }),
),
),
)
}),
)
.handle(
"shell.output",
Effect.fn(function* (ctx) {
@@ -81,15 +64,7 @@ export const ShellHandler = HttpApiBuilder.group(Api, "server.shell", (handlers)
"shell.remove",
Effect.fn(function* (ctx) {
const shell = yield* Shell.Service
yield* shell
.remove(ctx.params.id)
.pipe(
Effect.catchTag(
"Shell.NotFoundError",
() =>
new ShellNotFoundError({ id: ctx.params.id, message: `Shell command not found: ${ctx.params.id}` }),
),
)
yield* shell.remove(ctx.params.id).pipe(Effect.catchTag("Shell.NotFoundError", () => Effect.void))
return HttpApiSchema.NoContent.make()
}),
)
+1 -1
View File
@@ -34,7 +34,7 @@ export const VcsHandler = HttpApiBuilder.group(Api, "server.vcs", (handlers) =>
}),
),
)
.handle("vcs.branches", (ctx) =>
.handle("vcs.branch.list", (ctx) =>
response(
Effect.gen(function* () {
const vcs = yield* Vcs.Service
@@ -217,7 +217,7 @@ function DiffBaseDialog(props: {
const theme = props.context.theme.contextual.elevated
const [search, setSearch] = createDebouncedSignal("", 150)
const [branches] = createResource(search, (search) =>
props.context.client.vcs.branches({ location: props.location, search, limit: 100 }),
props.context.client.vcs.branch.list({ location: props.location, search, limit: 100 }),
)
const Empty = () => (
<box paddingLeft={4} paddingRight={4}>
@@ -1847,7 +1847,7 @@ async function renderDiffViewer(
? options.baseResponse()
: json({ location: session.location, data: options.base === undefined ? baseFixture : options.base })
}
if (url.pathname === "/api/vcs/branches") {
if (url.pathname === "/api/vcs/branch") {
branchesRequests.push(url)
return options.branchesResponse
? options.branchesResponse(url)
+9 -135
View File
@@ -10613,7 +10613,7 @@
"type": "object"
}
},
"required": ["command", "timeout"],
"required": ["command"],
"additionalProperties": false
}
}
@@ -10791,142 +10791,12 @@
}
}
}
},
"404": {
"description": "ShellNotFoundError",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ShellNotFoundErrorEncoded"
}
}
}
}
},
"description": "Terminate and remove one shell command and its retained output.",
"summary": "Remove shell command"
}
},
"/api/shell/{id}/timeout": {
"patch": {
"tags": ["shell"],
"operationId": "shell.timeout",
"parameters": [
{
"name": "id",
"in": "path",
"schema": {
"type": "string",
"pattern": "^sh_"
},
"required": true
},
{
"name": "location",
"in": "query",
"schema": {
"anyOf": [
{
"type": "object",
"properties": {
"directory": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
}
},
"additionalProperties": false
},
{
"type": "null"
}
]
},
"required": false,
"style": "deepObject",
"explode": true
}
],
"security": [],
"responses": {
"200": {
"description": "Success",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"location": {
"$ref": "#/components/schemas/Location.PublicRef"
},
"data": {
"$ref": "#/components/schemas/Shell.Info"
}
},
"required": ["location", "data"],
"additionalProperties": false
}
}
}
},
"400": {
"description": "InvalidRequestError",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/InvalidRequestErrorEncoded"
}
}
}
},
"401": {
"description": "UnauthorizedError",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UnauthorizedErrorEncoded"
}
}
}
},
"404": {
"description": "ShellNotFoundError",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ShellNotFoundErrorEncoded"
}
}
}
}
},
"description": "Replace a running shell command's timeout from now, or clear it with zero.",
"summary": "Update shell timeout",
"requestBody": {
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"timeout": {
"type": "integer",
"minimum": 0
}
},
"required": ["timeout"],
"additionalProperties": false
}
}
},
"required": true
}
}
},
"/api/shell/{id}/output": {
"get": {
"tags": ["shell"],
@@ -11677,10 +11547,10 @@
"summary": "VCS status"
}
},
"/api/vcs/branches": {
"/api/vcs/branch": {
"get": {
"tags": ["vcs"],
"operationId": "vcs.branches",
"operationId": "vcs.branch.list",
"parameters": [
{
"name": "location",
@@ -18498,7 +18368,8 @@
"type": "object",
"properties": {
"started": {
"type": "number"
"type": "number",
"description": "Start time in milliseconds since the Unix epoch"
},
"completed": {
"type": "number"
@@ -18752,6 +18623,9 @@
"Vcs.Info": {
"type": "object",
"properties": {
"provider": {
"type": "string"
},
"branch": {
"$ref": "#/components/schemas/Vcs.Branch"
}
@@ -19006,7 +18880,7 @@
},
{
"name": "shell",
"description": "Experimental location-scoped shell command routes."
"description": "Location-scoped shell command routes."
},
{
"name": "reference",
+9 -135
View File
@@ -10613,7 +10613,7 @@
"type": "object"
}
},
"required": ["command", "timeout"],
"required": ["command"],
"additionalProperties": false
}
}
@@ -10791,142 +10791,12 @@
}
}
}
},
"404": {
"description": "ShellNotFoundError",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ShellNotFoundErrorEncoded"
}
}
}
}
},
"description": "Terminate and remove one shell command and its retained output.",
"summary": "Remove shell command"
}
},
"/api/shell/{id}/timeout": {
"patch": {
"tags": ["shell"],
"operationId": "shell.timeout",
"parameters": [
{
"name": "id",
"in": "path",
"schema": {
"type": "string",
"pattern": "^sh_"
},
"required": true
},
{
"name": "location",
"in": "query",
"schema": {
"anyOf": [
{
"type": "object",
"properties": {
"directory": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
}
},
"additionalProperties": false
},
{
"type": "null"
}
]
},
"required": false,
"style": "deepObject",
"explode": true
}
],
"security": [],
"responses": {
"200": {
"description": "Success",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"location": {
"$ref": "#/components/schemas/Location.PublicRef"
},
"data": {
"$ref": "#/components/schemas/Shell.Info"
}
},
"required": ["location", "data"],
"additionalProperties": false
}
}
}
},
"400": {
"description": "InvalidRequestError",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/InvalidRequestErrorEncoded"
}
}
}
},
"401": {
"description": "UnauthorizedError",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UnauthorizedErrorEncoded"
}
}
}
},
"404": {
"description": "ShellNotFoundError",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ShellNotFoundErrorEncoded"
}
}
}
}
},
"description": "Replace a running shell command's timeout from now, or clear it with zero.",
"summary": "Update shell timeout",
"requestBody": {
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"timeout": {
"type": "integer",
"minimum": 0
}
},
"required": ["timeout"],
"additionalProperties": false
}
}
},
"required": true
}
}
},
"/api/shell/{id}/output": {
"get": {
"tags": ["shell"],
@@ -11677,10 +11547,10 @@
"summary": "VCS status"
}
},
"/api/vcs/branches": {
"/api/vcs/branch": {
"get": {
"tags": ["vcs"],
"operationId": "vcs.branches",
"operationId": "vcs.branch.list",
"parameters": [
{
"name": "location",
@@ -18498,7 +18368,8 @@
"type": "object",
"properties": {
"started": {
"type": "number"
"type": "number",
"description": "Start time in milliseconds since the Unix epoch"
},
"completed": {
"type": "number"
@@ -18752,6 +18623,9 @@
"Vcs.Info": {
"type": "object",
"properties": {
"provider": {
"type": "string"
},
"branch": {
"$ref": "#/components/schemas/Vcs.Branch"
}
@@ -19006,7 +18880,7 @@
},
{
"name": "shell",
"description": "Experimental location-scoped shell command routes."
"description": "Location-scoped shell command routes."
},
{
"name": "reference",