diff --git a/V2_HTTP_API_AUDIT.md b/V2_HTTP_API_AUDIT.md new file mode 100644 index 00000000000..f5bf88a889d --- /dev/null +++ b/V2_HTTP_API_AUDIT.md @@ -0,0 +1,261 @@ +# V2 HTTP API audit checklist + +**Source:** `packages/protocol/openapi.json` +**Current endpoint count:** 143 +**Last regenerated:** 2026-09-13 + +## How to use this checklist + +Review endpoints in document order. For each endpoint, select one disposition and capture rationale or follow-up work in Notes. Mark **Reviewed** only after the disposition is agreed. + +### Review criteria + +- Resource and operation naming +- HTTP method and idempotency +- Request parameters and location scope +- Response shape and error taxonomy +- Authentication and authorization +- Current production consumers +- Stability level: public, experimental, or internal +- Whether the generated client API is intuitive + +### Disposition legend + +- **Keep:** ship unchanged as a supported V2 API +- **Change:** retain after a defined contract change +- **Remove:** exclude from the official V2 API +- **Experimental-only:** retain outside the stable API commitment + +## Progress + +- [ ] Group 1: Foundation and placement (7) +- [ ] Group 2: Configuration and capability catalogs (17) +- [ ] Group 3: Credentials, integrations, MCP, and web search (22) +- [ ] Group 4: Session lifecycle (12) +- [ ] Group 5: Session execution and inputs (11) +- [ ] Group 6: Session history and recovery (13) +- [ ] Group 7: Inbox, permissions, and forms (19) +- [ ] Group 8: Filesystem, worktrees, and VCS (12) +- [ ] Group 9: PTYs, persistent terminals, and shells (24) +- [ ] Group 10: Events, RPC, and experimental operations (6) + +## Resolved during audit + +### [x] `GET /api/project/current` + +- **Decision:** Remove +- **Replacement:** `GET /api/location`, using `project` from the response. +- **Notes:** The endpoint duplicated `Location.Info.project`; production callers were migrated. + +## Group 1: Foundation and placement + +**Endpoints:** 7 + +| Done | Method | Path | Operation ID | Decision | Notes | +|---|---|---|---|---|---| +| [ ] 001 | `GET` | `/api/health` | `v2.health.get` | | | +| [ ] 002 | `GET` | `/api/server` | `v2.server.get` | | | +| [ ] 003 | `GET` | `/api/location` | `v2.location.get` | | | +| [ ] 004 | `GET` | `/api/project` | `v2.project.list` | | | +| [ ] 005 | `PATCH` | `/api/project/{projectID}` | `v2.project.update` | | | +| [ ] 006 | `POST` | `/api/workspace` | `v2.workspace.create` | Proposed remove | Awaiting feedback in `#core`. | +| [ ] 007 | `DELETE` | `/api/workspace/{workspaceID}` | `v2.workspace.destroy` | Proposed remove | Awaiting feedback in `#core`. | + +## Group 2: Configuration and capability catalogs + +**Endpoints:** 17 + +| Done | Method | Path | Operation ID | Decision | Notes | +|---|---|---|---|---|---| +| [ ] 008 | `GET` | `/api/agent` | `v2.agent.list` | | | +| [ ] 009 | `GET` | `/api/agent/{agentID}` | `v2.agent.get` | | | +| [ ] 010 | `GET` | `/api/plugin` | `v2.plugin.list` | | | +| [ ] 011 | `POST` | `/api/plugin/await-activation` | `v2.plugin.awaitActivation` | | | +| [ ] 012 | `POST` | `/api/plugin/check` | `v2.plugin.check` | | | +| [ ] 013 | `POST` | `/api/plugin/update` | `v2.plugin.update` | | | +| [ ] 014 | `GET` | `/api/model` | `v2.model.list` | | | +| [ ] 015 | `GET` | `/api/model/default` | `v2.model.default` | | | +| [ ] 016 | `GET` | `/api/provider` | `v2.provider.list` | | | +| [ ] 017 | `GET` | `/api/provider/{providerID}` | `v2.provider.get` | | | +| [ ] 018 | `GET` | `/api/command` | `v2.command.list` | | | +| [ ] 019 | `GET` | `/api/skill` | `v2.skill.list` | | | +| [ ] 020 | `GET` | `/api/reference` | `v2.reference.list` | | | +| [ ] 021 | `GET` | `/api/config` | `v2.config.get` | | | +| [ ] 022 | `GET` | `/api/config/preferences` | `v2.config.preferences` | | | +| [ ] 023 | `PATCH` | `/api/config/preferences` | `v2.config.updatePreferences` | | | +| [ ] 024 | `GET` | `/api/config/shell` | `v2.config.shells` | | | + +## Group 3: Credentials, integrations, MCP, and web search + +**Endpoints:** 22 + +| Done | Method | Path | Operation ID | Decision | Notes | +|---|---|---|---|---|---| +| [ ] 025 | `GET` | `/api/integration` | `v2.integration.list` | | | +| [ ] 026 | `GET` | `/api/integration/{integrationID}` | `v2.integration.get` | | | +| [ ] 027 | `POST` | `/api/experimental/integration/wellknown` | `v2.experimental.integration.wellknown.add` | | | +| [ ] 028 | `POST` | `/api/integration/{integrationID}/connect/key` | `v2.integration.connect.key` | | | +| [ ] 029 | `POST` | `/api/integration/{integrationID}/connect/oauth` | `v2.integration.oauth.connect` | | | +| [ ] 030 | `GET` | `/api/integration/{integrationID}/connect/oauth/{attemptID}` | `v2.integration.oauth.status` | | | +| [ ] 031 | `DELETE` | `/api/integration/{integrationID}/connect/oauth/{attemptID}` | `v2.integration.oauth.cancel` | | | +| [ ] 032 | `POST` | `/api/integration/{integrationID}/connect/oauth/{attemptID}/complete` | `v2.integration.oauth.complete` | | | +| [ ] 033 | `POST` | `/api/integration/{integrationID}/connect/command` | `v2.integration.command.connect` | | | +| [ ] 034 | `GET` | `/api/integration/{integrationID}/connect/command/{attemptID}` | `v2.integration.command.status` | | | +| [ ] 035 | `DELETE` | `/api/integration/{integrationID}/connect/command/{attemptID}` | `v2.integration.command.cancel` | | | +| [ ] 036 | `GET` | `/api/mcp` | `v2.mcp.list` | | | +| [ ] 037 | `PUT` | `/api/mcp/{server}` | `v2.mcp.add` | | | +| [ ] 038 | `DELETE` | `/api/mcp/{server}` | `v2.mcp.remove` | | | +| [ ] 039 | `POST` | `/api/mcp/{server}/connect` | `v2.mcp.connect` | | | +| [ ] 040 | `POST` | `/api/mcp/{server}/disconnect` | `v2.mcp.disconnect` | | | +| [ ] 041 | `GET` | `/api/mcp/resource` | `v2.mcp.resource.catalog` | | | +| [ ] 042 | `PATCH` | `/api/credential/{credentialID}` | `v2.credential.update` | | | +| [ ] 043 | `DELETE` | `/api/credential/{credentialID}` | `v2.credential.remove` | | | +| [ ] 044 | `POST` | `/api/credential/{credentialID}/activate` | `v2.credential.activate` | | | +| [ ] 045 | `GET` | `/api/websearch/provider` | `v2.websearch.providers` | | | +| [ ] 046 | `POST` | `/api/websearch` | `v2.websearch.query` | | | + +## Group 4: Session lifecycle + +**Endpoints:** 12 + +| Done | Method | Path | Operation ID | Decision | Notes | +|---|---|---|---|---|---| +| [ ] 047 | `GET` | `/api/session` | `v2.session.list` | | | +| [ ] 048 | `POST` | `/api/session` | `v2.session.create` | | | +| [ ] 049 | `GET` | `/api/session/stats` | `v2.session.stats` | | | +| [ ] 050 | `GET` | `/api/session/active` | `v2.session.active` | | | +| [ ] 051 | `GET` | `/api/session/{sessionID}` | `v2.session.get` | | | +| [ ] 052 | `DELETE` | `/api/session/{sessionID}` | `v2.session.remove` | | | +| [ ] 053 | `POST` | `/api/session/{sessionID}/fork` | `v2.session.fork` | | | +| [ ] 054 | `POST` | `/api/session/{sessionID}/agent` | `v2.session.switchAgent` | | | +| [ ] 055 | `POST` | `/api/session/{sessionID}/model` | `v2.session.switchModel` | | | +| [ ] 056 | `POST` | `/api/session/{sessionID}/rename` | `v2.session.rename` | | | +| [ ] 057 | `POST` | `/api/session/{sessionID}/move` | `v2.session.move` | | | +| [ ] 058 | `POST` | `/api/session/{sessionID}/background` | `v2.session.background` | | | + +## Group 5: Session execution and inputs + +**Endpoints:** 11 + +| Done | Method | Path | Operation ID | Decision | Notes | +|---|---|---|---|---|---| +| [ ] 059 | `POST` | `/api/session/{sessionID}/prompt` | `v2.session.prompt` | | | +| [ ] 060 | `POST` | `/api/session/{sessionID}/command` | `v2.session.command` | | | +| [ ] 061 | `POST` | `/api/session/{sessionID}/skill` | `v2.session.skill` | | | +| [ ] 062 | `POST` | `/api/session/{sessionID}/synthetic` | `v2.session.synthetic` | | | +| [ ] 063 | `POST` | `/api/session/{sessionID}/shell` | `v2.session.shell` | | | +| [ ] 064 | `POST` | `/api/session/{sessionID}/compact` | `v2.session.compact` | | | +| [ ] 065 | `POST` | `/api/session/{sessionID}/wait` | `v2.session.wait` | | | +| [ ] 066 | `POST` | `/api/session/{sessionID}/generate` | `v2.session.generate` | | | +| [ ] 067 | `POST` | `/api/session/{sessionID}/interrupt` | `v2.session.interrupt` | | | +| [ ] 068 | `PUT` | `/api/session/{sessionID}/environment` | `v2.session.environment` | | | +| [ ] 069 | `POST` | `/api/session/{sessionID}/view` | `v2.session.view` | | | + +## Group 6: Session history and recovery + +**Endpoints:** 13 + +| Done | Method | Path | Operation ID | Decision | Notes | +|---|---|---|---|---|---| +| [ ] 070 | `POST` | `/api/session/import` | `v2.session.import` | | | +| [ ] 071 | `GET` | `/api/session/{sessionID}/export` | `v2.session.export` | | | +| [ ] 072 | `POST` | `/api/session/{sessionID}/revert/stage` | `v2.session.revert.stage` | | | +| [ ] 073 | `POST` | `/api/session/{sessionID}/revert/clear` | `v2.session.revert.clear` | | | +| [ ] 074 | `POST` | `/api/session/{sessionID}/revert/commit` | `v2.session.revert.commit` | | | +| [ ] 075 | `GET` | `/api/session/{sessionID}/context` | `v2.session.context` | | | +| [ ] 076 | `GET` | `/api/session/{sessionID}/diff` | `v2.session.diff` | | | +| [ ] 077 | `GET` | `/api/session/{sessionID}/instructions/entries` | `v2.session.instructions.entry.list` | | | +| [ ] 078 | `PUT` | `/api/session/{sessionID}/instructions/entries/{key}` | `v2.session.instructions.entry.put` | | | +| [ ] 079 | `DELETE` | `/api/session/{sessionID}/instructions/entries/{key}` | `v2.session.instructions.entry.remove` | | | +| [ ] 080 | `GET` | `/api/experimental/session/{sessionID}/log` | `v2.session.log` | | | +| [ ] 081 | `GET` | `/api/session/{sessionID}/message/{messageID}` | `v2.session.message` | | | +| [ ] 082 | `GET` | `/api/session/{sessionID}/message` | `v2.message.list` | | | + +## Group 7: Inbox, permissions, and forms + +**Endpoints:** 19 + +| Done | Method | Path | Operation ID | Decision | Notes | +|---|---|---|---|---|---| +| [ ] 083 | `GET` | `/api/session/{sessionID}/inbox` | `v2.session.inbox.list` | | | +| [ ] 084 | `DELETE` | `/api/session/{sessionID}/inbox/{inboxID}` | `v2.session.inbox.cancel` | | | +| [ ] 085 | `POST` | `/api/session/{sessionID}/inbox/{inboxID}/steer` | `v2.session.inbox.steer` | | | +| [ ] 086 | `POST` | `/api/session/{sessionID}/inbox/{inboxID}/queue` | `v2.session.inbox.queue` | | | +| [ ] 087 | `GET` | `/api/form/request` | `v2.form.request.list` | | | +| [ ] 088 | `GET` | `/api/session/{sessionID}/form` | `v2.session.form.list` | | | +| [ ] 089 | `POST` | `/api/session/{sessionID}/form` | `v2.session.form.create` | | | +| [ ] 090 | `GET` | `/api/session/{sessionID}/form/{formID}` | `v2.session.form.get` | | | +| [ ] 091 | `GET` | `/api/session/{sessionID}/form/{formID}/state` | `v2.session.form.state` | | | +| [ ] 092 | `POST` | `/api/session/{sessionID}/form/{formID}/reply` | `v2.session.form.reply` | | | +| [ ] 093 | `POST` | `/api/session/{sessionID}/form/{formID}/cancel` | `v2.session.form.cancel` | | | +| [ ] 094 | `GET` | `/api/permission/request` | `v2.permission.request.list` | | | +| [ ] 095 | `GET` | `/api/permission/saved` | `v2.permission.saved.list` | | | +| [ ] 096 | `DELETE` | `/api/permission/saved/{id}` | `v2.permission.saved.remove` | | | +| [ ] 097 | `POST` | `/api/session/{sessionID}/permission` | `v2.session.permission.create` | | | +| [ ] 098 | `GET` | `/api/session/{sessionID}/permission` | `v2.session.permission.list` | | | +| [ ] 099 | `GET` | `/api/session/{sessionID}/permission/{requestID}` | `v2.session.permission.get` | | | +| [ ] 100 | `POST` | `/api/session/{sessionID}/permission/{requestID}/reply` | `v2.session.permission.reply` | | | +| [ ] 101 | `PUT` | `/api/session/{sessionID}/permission/rules` | `v2.session.permission.rules` | | | + +## Group 8: Filesystem, worktrees, and VCS + +**Endpoints:** 12 + +| Done | Method | Path | Operation ID | Decision | Notes | +|---|---|---|---|---|---| +| [ ] 102 | `GET` | `/api/fs/read/*` | `v2.fs.read` | | | +| [ ] 103 | `GET` | `/api/fs/list` | `v2.fs.list` | | | +| [ ] 104 | `GET` | `/api/fs/find` | `v2.fs.find` | | | +| [ ] 105 | `GET` | `/api/worktree` | `v2.worktree.list` | | | +| [ ] 106 | `POST` | `/api/worktree` | `v2.worktree.create` | | | +| [ ] 107 | `DELETE` | `/api/worktree` | `v2.worktree.remove` | | | +| [ ] 108 | `POST` | `/api/worktree/refresh` | `v2.worktree.refresh` | | | +| [ ] 109 | `GET` | `/api/vcs` | `v2.vcs.get` | | | +| [ ] 110 | `GET` | `/api/vcs/base` | `v2.vcs.base` | | | +| [ ] 111 | `GET` | `/api/vcs/status` | `v2.vcs.status` | | | +| [ ] 112 | `GET` | `/api/vcs/branches` | `v2.vcs.branches` | | | +| [ ] 113 | `GET` | `/api/vcs/diff` | `v2.vcs.diff` | | | + +## Group 9: PTYs, persistent terminals, and shells + +**Endpoints:** 24 + +| Done | Method | Path | Operation ID | Decision | Notes | +|---|---|---|---|---|---| +| [ ] 114 | `GET` | `/api/pty` | `v2.pty.list` | | | +| [ ] 115 | `POST` | `/api/pty` | `v2.pty.create` | | | +| [ ] 116 | `GET` | `/api/pty/{ptyID}` | `v2.pty.get` | | | +| [ ] 117 | `PUT` | `/api/pty/{ptyID}` | `v2.pty.update` | | | +| [ ] 118 | `DELETE` | `/api/pty/{ptyID}` | `v2.pty.remove` | | | +| [ ] 119 | `POST` | `/api/pty/{ptyID}/connect-token` | `v2.pty.connect.token` | | | +| [ ] 120 | `GET` | `/api/pty/{ptyID}/connect` | `v2.pty.connect` | | | +| [ ] 121 | `GET` | `/api/experimental/session/{sessionID}/terminal/read` | `server.experimental.persistentPty.read` | | | +| [ ] 122 | `GET` | `/api/experimental/session/{sessionID}/terminal` | `server.experimental.persistentPty.list` | | | +| [ ] 123 | `POST` | `/api/experimental/session/{sessionID}/terminal` | `server.experimental.persistentPty.create` | | | +| [ ] 124 | `POST` | `/api/experimental/persistent-pty/shutdown` | `server.experimental.persistentPty.shutdown` | | | +| [ ] 125 | `POST` | `/api/experimental/persistent-pty/handoff` | `server.experimental.persistentPty.handoff` | | | +| [ ] 126 | `GET` | `/api/experimental/persistent-pty/{ptyID}` | `server.experimental.persistentPty.get` | | | +| [ ] 127 | `PUT` | `/api/experimental/persistent-pty/{ptyID}` | `server.experimental.persistentPty.update` | | | +| [ ] 128 | `DELETE` | `/api/experimental/persistent-pty/{ptyID}` | `server.experimental.persistentPty.remove` | | | +| [ ] 129 | `GET` | `/api/experimental/persistent-pty/{ptyID}/snapshot` | `server.experimental.persistentPty.snapshot` | | | +| [ ] 130 | `POST` | `/api/experimental/persistent-pty/{ptyID}/connect-token` | `server.experimental.persistentPty.connectToken` | | | +| [ ] 131 | `GET` | `/api/experimental/persistent-pty/{ptyID}/connect` | `v2.persistentPty.connect` | | | +| [ ] 132 | `GET` | `/api/shell` | `v2.shell.list` | | | +| [ ] 133 | `POST` | `/api/shell` | `v2.shell.create` | | | +| [ ] 134 | `GET` | `/api/shell/{id}` | `v2.shell.get` | | | +| [ ] 135 | `DELETE` | `/api/shell/{id}` | `v2.shell.remove` | | | +| [ ] 136 | `PATCH` | `/api/shell/{id}/timeout` | `v2.shell.timeout` | | | +| [ ] 137 | `GET` | `/api/shell/{id}/output` | `v2.shell.output` | | | + +## Group 10: Events, RPC, and experimental operations + +**Endpoints:** 6 + +| Done | Method | Path | Operation ID | Decision | Notes | +|---|---|---|---|---|---| +| [ ] 138 | `POST` | `/api/generate` | `v2.generate.text` | | | +| [ ] 139 | `POST` | `/api/rpc/{rpcID}/{method}` | `v2.rpc.call` | | | +| [ ] 140 | `GET` | `/api/event` | `v2.event.subscribe` | | | +| [ ] 141 | `GET` | `/api/debug/location` | `v2.debug.location.list` | | | +| [ ] 142 | `DELETE` | `/api/debug/location` | `v2.debug.location.evict` | | | +| [ ] 143 | `GET` | `/api/experimental/migration/v1` | `v2.experimental.migration.v1.status` | | | diff --git a/packages/app/e2e/regression/cross-server-tab-close.spec.ts b/packages/app/e2e/regression/cross-server-tab-close.spec.ts index 09e7d370413..b0bbd778613 100644 --- a/packages/app/e2e/regression/cross-server-tab-close.spec.ts +++ b/packages/app/e2e/regression/cross-server-tab-close.spec.ts @@ -76,7 +76,7 @@ async function mockServers(page: Page, requests: string[]) { if (url.pathname === "/api/mcp") return json(route, { location: { directory: current.directory }, data: [] }) if (url.pathname === "/api/mcp/resource") return json(route, { location: { directory: current.directory }, data: { resources: [], templates: [] } }) - if (url.pathname === "/api/project" || url.pathname === "/api/project/current") { + if (url.pathname === "/api/project") { const project = { id: current.projectID, canonical: current.directory, @@ -84,7 +84,7 @@ async function mockServers(page: Page, requests: string[]) { time: { created: 1, updated: 1 }, sandboxes: [], } - return json(route, url.pathname === "/api/project" ? [project] : { id: project.id, directory: current.directory }) + return json(route, [project]) } if (url.pathname === "/api/location") return json(route, { diff --git a/packages/app/e2e/regression/remote-session-settings.spec.ts b/packages/app/e2e/regression/remote-session-settings.spec.ts index a62130a79ec..14e1617589a 100644 --- a/packages/app/e2e/regression/remote-session-settings.spec.ts +++ b/packages/app/e2e/regression/remote-session-settings.spec.ts @@ -398,8 +398,6 @@ async function mockServers( }, ]) } - if (url.pathname === "/api/project/current") - return json(route, { id: remote ? sessionB.projectID : "project-server-a", directory, canonical: directory }) if (url.pathname === "/api/session") return json(route, { data: sessions.map((session) => currentSession(session)), cursor: {} }) if (url.pathname === "/api/session/active") diff --git a/packages/app/e2e/regression/tab-navigate-mousedown.spec.ts b/packages/app/e2e/regression/tab-navigate-mousedown.spec.ts index 9762ce098de..f9bc5c8e16f 100644 --- a/packages/app/e2e/regression/tab-navigate-mousedown.spec.ts +++ b/packages/app/e2e/regression/tab-navigate-mousedown.spec.ts @@ -592,7 +592,7 @@ async function mockServer(page: Page) { if (url.pathname === "/api/mcp") return json(route, { location: { directory: sessionA.directory }, data: [] }) if (url.pathname === "/api/mcp/resource") return json(route, { location: { directory: sessionA.directory }, data: { resources: [], templates: [] } }) - if (url.pathname === "/api/project" || url.pathname === "/api/project/current") { + if (url.pathname === "/api/project") { const project = { id: sessionA.projectID, canonical: sessionA.directory, @@ -600,10 +600,7 @@ async function mockServer(page: Page) { time: { created: 1, updated: 1 }, sandboxes: [], } - return json( - route, - url.pathname === "/api/project" ? [project] : { id: project.id, directory: sessionA.directory }, - ) + return json(route, [project]) } if (url.pathname === "/api/location") return json(route, { diff --git a/packages/app/e2e/utils/mock-api.ts b/packages/app/e2e/utils/mock-api.ts index 2a2760b6689..bb4585be223 100644 --- a/packages/app/e2e/utils/mock-api.ts +++ b/packages/app/e2e/utils/mock-api.ts @@ -77,7 +77,6 @@ const Group = HttpApiGroup.make("mock") success: Json, }), ) - .add(HttpApiEndpoint.get("projectCurrent", "/api/project/current", { success: Json })) .add(HttpApiEndpoint.get("configPreferences", "/api/config/preferences", { success: Json })) .add( HttpApiEndpoint.patch("configUpdatePreferences", "/api/config/preferences", { diff --git a/packages/app/src/home/model.ts b/packages/app/src/home/model.ts index cb71e4ae709..dc26ac748e7 100644 --- a/packages/app/src/home/model.ts +++ b/packages/app/src/home/model.ts @@ -110,7 +110,7 @@ export function createHomeController() { .list({ path: ".", location }) .then(async (files) => { // TODO: Initialize empty directories when V2 exposes a native Git init API. - return ctx.sdk.api.project.current({ location }) + return ctx.sdk.api.location.get({ location }).then((result) => result.project) }) .then((project) => ctx.sync.child(item, { bootstrap: false })[1]("project", project.id)) .catch(() => undefined) diff --git a/packages/app/src/runtime/server/global-sync/bootstrap.ts b/packages/app/src/runtime/server/global-sync/bootstrap.ts index 902d1e69956..27d0c7cedb4 100644 --- a/packages/app/src/runtime/server/global-sync/bootstrap.ts +++ b/packages/app/src/runtime/server/global-sync/bootstrap.ts @@ -2,8 +2,6 @@ import type { Config, Path, Project, ProviderAuthResponse } from "@/runtime/serv import type { LocationGetInput, LocationGetOutput, - ProjectCurrentInput, - ProjectCurrentOutput, ProjectListOutput, } from "@opencode/client/promise" import { showToast } from "@/shell/notifications/toast" @@ -61,7 +59,6 @@ export const loadGlobalConfigQuery = (scope: ServerScope) => type ProjectApi = { readonly list: () => Promise - readonly current: (input?: ProjectCurrentInput) => Promise } type LocationApi = { readonly get: (input?: LocationGetInput) => Promise } @@ -132,6 +129,7 @@ export async function bootstrapDirectory(input: { mcp: boolean api: { readonly project: ProjectApi + readonly location: LocationApi } store: Store setStore: SetStoreFunction @@ -155,8 +153,8 @@ export async function bootstrapDirectory(input: { seededProject ? undefined : () => - retry(() => input.api.project.current({ location: { directory: input.directory } })).then((project) => - input.setStore("project", project.id), + retry(() => input.api.location.get({ location: { directory: input.directory } })).then((location) => + input.setStore("project", location.project.id), ), ].filter((task): task is () => Promise => !!task) diff --git a/packages/client/src/effect/api/api.ts b/packages/client/src/effect/api/api.ts index 73df291bef4..f79051c9f62 100644 --- a/packages/client/src/effect/api/api.ts +++ b/packages/client/src/effect/api/api.ts @@ -1489,16 +1489,9 @@ export type ProjectUpdateInput = { export type ProjectUpdateOutput = Project.Info export type ProjectUpdateOperation = (input: ProjectUpdateInput) => Effect.Effect -export type ProjectCurrentInput = { - readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined -} -export type ProjectCurrentOutput = Project.Current -export type ProjectCurrentOperation = (input?: ProjectCurrentInput) => Effect.Effect - export interface ProjectApi { readonly list: ProjectListOperation readonly update: ProjectUpdateOperation - readonly current: ProjectCurrentOperation } export type FormRequestListInput = { diff --git a/packages/client/src/effect/generated/client.ts b/packages/client/src/effect/generated/client.ts index 37cd5790491..5c8b63b8676 100644 --- a/packages/client/src/effect/generated/client.ts +++ b/packages/client/src/effect/generated/client.ts @@ -153,8 +153,6 @@ import type { ProjectListOutput, ProjectUpdateInput, ProjectUpdateOutput, - ProjectCurrentInput, - ProjectCurrentOutput, FormRequestListInput, FormRequestListOutput, FormListInput, @@ -1029,15 +1027,9 @@ const EndpointProjectUpdate = (raw: RawClient["server.project"]) => (input: Proj }).pipe(Effect.mapError(mapClientError)), ) -const EndpointProjectCurrent = (raw: RawClient["server.project"]) => (input?: ProjectCurrentInput) => - preserveEffect()( - raw["project.current"]({ query: { location: input?.["location"] } }).pipe(Effect.mapError(mapClientError)), - ) - const adaptGroupProject = (raw: RawClient["server.project"]) => ({ list: EndpointProjectList(raw), update: EndpointProjectUpdate(raw), - current: EndpointProjectCurrent(raw), }) const EndpointFormRequestList = (raw: RawClient["server.form"]) => (input?: FormRequestListInput) => diff --git a/packages/client/src/promise/generated/client.ts b/packages/client/src/promise/generated/client.ts index 097c7e629c7..9e7b794efa9 100644 --- a/packages/client/src/promise/generated/client.ts +++ b/packages/client/src/promise/generated/client.ts @@ -147,8 +147,6 @@ import type { ProjectListOutput, ProjectUpdateInput, ProjectUpdateOutput, - ProjectCurrentInput, - ProjectCurrentOutput, FormRequestListInput, FormRequestListOutput, FormListInput, @@ -1396,18 +1394,6 @@ export function make(options: ClientOptions) { }, requestOptions, ), - current: (input?: ProjectCurrentInput, requestOptions?: RequestOptions) => - request( - { - method: "GET", - path: `/api/project/current`, - query: { location: input?.["location"] }, - successStatus: 200, - declaredStatuses: [400, 401], - empty: false, - }, - requestOptions, - ), }, form: { request: { diff --git a/packages/client/src/promise/generated/types.ts b/packages/client/src/promise/generated/types.ts index d88244bf412..4c95e4b1a38 100644 --- a/packages/client/src/promise/generated/types.ts +++ b/packages/client/src/promise/generated/types.ts @@ -310,8 +310,6 @@ export type ProjectCommands = { start?: string } export type ProjectTime = { created: number; updated: number; initialized?: number } -export type ProjectCurrent = { id: string; directory: string; canonical: string } - export type FormMetadata = { [x: string]: JsonValue } export type FormValue = string | number | boolean | Array @@ -4912,14 +4910,6 @@ export type ProjectUpdateInput = { export type ProjectUpdateOutput = Project -export type ProjectCurrentInput = { - readonly location?: { - readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined - }["location"] -} - -export type ProjectCurrentOutput = ProjectCurrent - export type FormRequestListInput = { readonly location?: { readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined diff --git a/packages/codemode/test/fixtures/opencode-v2-openapi.json b/packages/codemode/test/fixtures/opencode-v2-openapi.json index 3acdffbe064..a858c062ceb 100644 --- a/packages/codemode/test/fixtures/opencode-v2-openapi.json +++ b/packages/codemode/test/fixtures/opencode-v2-openapi.json @@ -5170,89 +5170,6 @@ "summary": "List projects" } }, - "/api/project/current": { - "get": { - "tags": ["project"], - "operationId": "v2.project.current", - "parameters": [ - { - "name": "location", - "in": "query", - "schema": { - "anyOf": [ - { - "type": "object", - "properties": { - "directory": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ] - }, - "workspace": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "null" - } - ] - }, - "required": false, - "style": "deepObject", - "explode": true - } - ], - "security": [], - "responses": { - "200": { - "description": "Project.Current", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Project.Current" - } - } - } - }, - "400": { - "description": "InvalidRequestError", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/InvalidRequestError" - } - } - } - }, - "401": { - "description": "UnauthorizedError", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/UnauthorizedError" - } - } - } - } - }, - "description": "Resolve the project for the requested location.", - "summary": "Get current project" - } - }, "/api/project/{projectID}/directories": { "get": { "tags": ["project"], diff --git a/packages/protocol/openapi.json b/packages/protocol/openapi.json index 2c448b2f082..0afb13c3d81 100644 --- a/packages/protocol/openapi.json +++ b/packages/protocol/openapi.json @@ -1115,6 +1115,16 @@ "type": "null" } ] + }, + "permissions": { + "anyOf": [ + { + "$ref": "#/components/schemas/Permission.Ruleset" + }, + { + "type": "null" + } + ] } }, "additionalProperties": false @@ -7466,89 +7476,6 @@ } } }, - "/api/project/current": { - "get": { - "tags": ["project"], - "operationId": "v2.project.current", - "parameters": [ - { - "name": "location", - "in": "query", - "schema": { - "anyOf": [ - { - "type": "object", - "properties": { - "directory": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ] - }, - "workspace": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "null" - } - ] - }, - "required": false, - "style": "deepObject", - "explode": true - } - ], - "security": [], - "responses": { - "200": { - "description": "Project.Current", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Project.Current" - } - } - } - }, - "400": { - "description": "InvalidRequestError", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/InvalidRequestErrorEncoded" - } - } - } - }, - "401": { - "description": "UnauthorizedError", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/UnauthorizedErrorEncoded" - } - } - } - } - }, - "description": "Resolve the project for the requested location.", - "summary": "Get current project" - } - }, "/api/form/request": { "get": { "tags": ["form"], @@ -8783,6 +8710,85 @@ } } }, + "/api/session/{sessionID}/permission/rules": { + "put": { + "tags": ["permission"], + "operationId": "v2.session.permission.rules", + "parameters": [ + { + "name": "sessionID", + "in": "path", + "schema": { + "type": "string", + "pattern": "^ses" + }, + "required": true + } + ], + "security": [], + "responses": { + "204": { + "description": "" + }, + "400": { + "description": "InvalidRequestError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidRequestErrorEncoded" + } + } + } + }, + "401": { + "description": "UnauthorizedError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorEncoded" + } + } + } + }, + "404": { + "description": "SessionNotFoundError", + "content": { + "application/json": { + "schema": { + "anyOf": [ + { + "$ref": "#/components/schemas/SessionNotFoundErrorEncoded" + }, + { + "$ref": "#/components/schemas/SessionNotFoundErrorEncoded" + } + ] + } + } + } + } + }, + "description": "Replace the session-scoped permission rules. Rules are evaluated after the agent's rules, and the last matching rule wins.", + "summary": "Replace session permission rules", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "permissions": { + "$ref": "#/components/schemas/Permission.Ruleset" + } + }, + "required": ["permissions"], + "additionalProperties": false + } + } + }, + "required": true + } + } + }, "/api/fs/read/*": { "get": { "tags": ["filesystem"], @@ -13672,6 +13678,143 @@ "description": "Return configuration documents and discovery sources for the requested location, from lowest to highest priority.", "summary": "Get configuration" } + }, + "/api/config/preferences": { + "get": { + "tags": ["config"], + "operationId": "v2.config.preferences", + "parameters": [], + "security": [], + "responses": { + "200": { + "description": "Config.Preferences", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Config.Preferences" + } + } + } + }, + "400": { + "description": "InvalidRequestError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidRequestErrorEncoded" + } + } + } + }, + "401": { + "description": "UnauthorizedError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorEncoded" + } + } + } + } + }, + "description": "Return preferences from the highest-precedence global configuration document.", + "summary": "Get global preferences" + }, + "patch": { + "tags": ["config"], + "operationId": "v2.config.updatePreferences", + "parameters": [], + "security": [], + "responses": { + "200": { + "description": "Config.Preferences", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Config.Preferences" + } + } + } + }, + "400": { + "description": "InvalidRequestError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidRequestErrorEncoded" + } + } + } + }, + "401": { + "description": "UnauthorizedError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorEncoded" + } + } + } + } + }, + "description": "Patch preferences in the highest-precedence global configuration document.", + "summary": "Update global preferences", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Config.PreferencesPatch" + } + } + }, + "required": true + } + } + }, + "/api/config/shell": { + "get": { + "tags": ["config"], + "operationId": "v2.config.shells", + "parameters": [], + "security": [], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ConfigShell.Option" + } + } + } + } + }, + "400": { + "description": "InvalidRequestError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidRequestErrorEncoded" + } + } + } + }, + "401": { + "description": "UnauthorizedError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorEncoded" + } + } + } + } + }, + "description": "Return shells available to terminal and agent execution.", + "summary": "List available shells" + } } }, "components": { @@ -14532,6 +14675,60 @@ "required": ["package"], "additionalProperties": false }, + "Config.Preferences": { + "type": "object", + "properties": { + "shell": { + "type": "string" + }, + "websearch": { + "anyOf": [ + { + "type": "boolean", + "enum": [false] + }, + { + "$ref": "#/components/schemas/ConfigWebSearch.InfoEncoded" + } + ] + } + }, + "additionalProperties": false + }, + "Config.PreferencesPatch": { + "type": "object", + "properties": { + "shell": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "websearch": { + "anyOf": [ + { + "anyOf": [ + { + "type": "boolean", + "enum": [false] + }, + { + "$ref": "#/components/schemas/ConfigWebSearch.InfoEncoded" + } + ] + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + }, "Config.ProviderEncoded": { "type": "object", "properties": { @@ -14637,6 +14834,22 @@ "required": ["directory"], "additionalProperties": false }, + "ConfigShell.Option": { + "type": "object", + "properties": { + "path": { + "type": "string" + }, + "name": { + "type": "string" + }, + "acceptable": { + "type": "boolean" + } + }, + "required": ["path", "name", "acceptable"], + "additionalProperties": false + }, "ConfigWebSearch.InfoEncoded": { "type": "object", "properties": { @@ -17231,22 +17444,6 @@ }, "additionalProperties": false }, - "Project.Current": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "directory": { - "type": "string" - }, - "canonical": { - "type": "string" - } - }, - "required": ["id", "directory", "canonical"], - "additionalProperties": false - }, "Project.Icon": { "type": "object", "properties": { @@ -18151,6 +18348,9 @@ "metadata": { "$ref": "#/components/schemas/Session.Metadata" }, + "permissions": { + "$ref": "#/components/schemas/Permission.Ruleset" + }, "revert": { "$ref": "#/components/schemas/Session.Revert" } diff --git a/packages/protocol/src/groups/project.ts b/packages/protocol/src/groups/project.ts index 52a83e7ed52..39b50d18014 100644 --- a/packages/protocol/src/groups/project.ts +++ b/packages/protocol/src/groups/project.ts @@ -1,7 +1,6 @@ import { Project } from "@opencode/schema/project" import { Schema, Struct } from "effect" import { HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi" -import { LocationQuery, locationQueryOpenApi } from "./location.js" import { ProjectNotFoundError } from "../errors.js" const root = "/api/project" @@ -33,20 +32,6 @@ export const ProjectGroup = HttpApiGroup.make("server.project") }), ), ) - .add( - HttpApiEndpoint.get("project.current", `${root}/current`, { - query: LocationQuery, - success: Project.Current, - }) - .annotateMerge(locationQueryOpenApi) - .annotateMerge( - OpenApi.annotations({ - identifier: "v2.project.current", - summary: "Get current project", - description: "Resolve the project for the requested location.", - }), - ), - ) .annotateMerge( OpenApi.annotations({ title: "project", diff --git a/packages/server/src/handlers/project.ts b/packages/server/src/handlers/project.ts index 467183eb235..f6bcf2a1358 100644 --- a/packages/server/src/handlers/project.ts +++ b/packages/server/src/handlers/project.ts @@ -1,4 +1,3 @@ -import { Location } from "@opencode/core/location" import { Project } from "@opencode/core/project" import { Effect } from "effect" import { HttpApiBuilder } from "effect/unstable/httpapi" @@ -20,14 +19,5 @@ export const ProjectHandler = HttpApiBuilder.group(Api, "server.project", (handl ), ), ), - ) - .handle("project.current", () => - Location.Service.use((location) => - Effect.succeed({ - id: location.project.id, - directory: location.project.directory, - canonical: location.project.canonical, - }), - ), ), ) diff --git a/packages/tui/src/component/dialog-workspaces.tsx b/packages/tui/src/component/dialog-workspaces.tsx index 7690c8c5a77..8298dad46f7 100644 --- a/packages/tui/src/component/dialog-workspaces.tsx +++ b/packages/tui/src/component/dialog-workspaces.tsx @@ -70,9 +70,9 @@ export function DialogWorkspaces(props: DialogWorkspacesProps) { const [loadedProject] = createResource( () => (location()?.project.id === props.projectID ? undefined : props.projectID), (projectID) => - client.api.project - .current({ location: { directory: location()?.directory || paths.cwd } }) - .then((project) => (project.id === projectID ? project.directory : undefined)) + client.api.location + .get({ location: { directory: location()?.directory || paths.cwd } }) + .then((result) => (result.project.id === projectID ? result.project.directory : undefined)) .catch(() => undefined), ) const currentCheckout = createMemo(() => { diff --git a/packages/tui/src/component/prompt/move.tsx b/packages/tui/src/component/prompt/move.tsx index 5a73df5e7a7..0ae9efa6bfb 100644 --- a/packages/tui/src/component/prompt/move.tsx +++ b/packages/tui/src/component/prompt/move.tsx @@ -118,9 +118,9 @@ export function usePromptMove(input: { projectID: () => string | undefined; sess const location = homeLocation() const current = data.location.info(location) if (current) return current.project.id - return client.api.project - .current({ location: { directory: location.directory, workspace: location.workspaceID } }) - .then((project) => project.id) + return client.api.location + .get({ location: { directory: location.directory, workspace: location.workspaceID } }) + .then((result) => result.project.id) .catch(() => undefined) } diff --git a/packages/tui/test/cli/tui/prompt-move.test.tsx b/packages/tui/test/cli/tui/prompt-move.test.tsx index 2bcc5b4e1db..133012f0212 100644 --- a/packages/tui/test/cli/tui/prompt-move.test.tsx +++ b/packages/tui/test/cli/tui/prompt-move.test.tsx @@ -47,7 +47,7 @@ test.each([ expect(fixture.requests).toEqual([{ payload: { name: "fresh" }, directory: input.directory, workspace: null }]) expect(fixture.data.location.info({ directory: created })?.project.canonical).toBe(clone) - expect(fixture.reads.locations.filter((directory) => directory === input.directory)).toHaveLength(1) + expect(fixture.reads.locations.filter((directory) => directory === input.directory)).toHaveLength(input.home ? 3 : 1) expect(fixture.reads.session).toBe(input.home ? 0 : 1) expect(fixture.moves).toEqual([]) if (!input.home) expect(fixture.route.data).toEqual({ type: "home", location: { directory: created } }) @@ -149,11 +149,9 @@ test.each([false, true])("Ctrl+M moves only an existing session (home=%s)", asyn test.each([ { name: "session", unavailable: "session" as const }, { name: "location", unavailable: "location" as const }, - { name: "selected Home location", unavailable: "location" as const, home: true, launch: main }, ])("does not create from another clone when $name lookup fails", async (input) => { const fixture = await renderMove({ ...input, directory: `${linked}/packages/tui`, worktree: linked }) try { - if (input.home) fixture.location.set({ directory: `${linked}/packages/tui` }) await fixture.create() expect(fixture.requests).toEqual([]) @@ -165,6 +163,27 @@ test.each([ } }) +test("does not open creation when the selected Home location lookup fails", async () => { + const fixture = await renderMove({ + unavailable: "location", + home: true, + launch: main, + directory: `${linked}/packages/tui`, + worktree: linked, + }) + try { + fixture.location.set({ directory: `${linked}/packages/tui` }) + await fixture.move.open() + await fixture.app.waitFor(() => fixture.toast.currentToast !== null) + + expect(fixture.requests).toEqual([]) + expect(fixture.moves).toEqual([]) + expect(fixture.toast.currentToast).toMatchObject({ message: "Unable to determine current project", variant: "error" }) + } finally { + fixture.app.renderer.destroy() + } +}) + async function renderMove(input: { directory: string worktree?: string @@ -179,7 +198,7 @@ async function renderMove(input: { const moves: unknown[] = [] const reads = { session: 0, locations: [] as string[], worktrees: [] as string[] } const calls = createFetch(async (url, request) => { - if (url.pathname === "/api/location" || url.pathname === "/api/project/current") { + if (url.pathname === "/api/location") { const directory = url.searchParams.get("location[directory]") ?? launch const project = { id: directory === launch ? (input.launchProjectID ?? "proj_test") : "proj_test", @@ -191,7 +210,6 @@ async function renderMove(input: { ? launch : main, } - if (url.pathname === "/api/project/current") return json(project) reads.locations.push(directory) if (input.unavailable === "location" && directory === input.directory) return json({ message: "Location unavailable" }, { status: 503 }) diff --git a/packages/tui/test/fixture/tui-client.ts b/packages/tui/test/fixture/tui-client.ts index 558ead5e083..98986adb1f7 100644 --- a/packages/tui/test/fixture/tui-client.ts +++ b/packages/tui/test/fixture/tui-client.ts @@ -110,7 +110,6 @@ export function createFetch(override?: FetchHandler, events?: ReturnTypeProject[] | -| `project.current()` | جلب المشروع الحالي | Project | --- @@ -254,8 +253,6 @@ const agents = await client.app.agents() // List all projects const projects = await client.project.list() -// Get current project -const currentProject = await client.project.current() ``` --- diff --git a/packages/web/src/content/docs/bs/sdk.mdx b/packages/web/src/content/docs/bs/sdk.mdx index cc9c2b3bf43..034b26b8b21 100644 --- a/packages/web/src/content/docs/bs/sdk.mdx +++ b/packages/web/src/content/docs/bs/sdk.mdx @@ -244,7 +244,6 @@ const agents = await client.app.agents() | Metoda | Opis | Odgovor | | ------------------- | -------------------- | --------------------------------------------- | | `project.list()` | Lista svih projekata | Project[] | -| `project.current()` | Trenutni projekat | Project | --- @@ -254,8 +253,6 @@ const agents = await client.app.agents() // List all projects const projects = await client.project.list() -// Get current project -const currentProject = await client.project.current() ``` --- diff --git a/packages/web/src/content/docs/da/sdk.mdx b/packages/web/src/content/docs/da/sdk.mdx index cde874d5a2f..19c4b0d36c8 100644 --- a/packages/web/src/content/docs/da/sdk.mdx +++ b/packages/web/src/content/docs/da/sdk.mdx @@ -244,7 +244,6 @@ const agents = await client.app.agents() | Metode | Beskrivelse | Svar | | ------------------- | -------------------------- | ---------------------------------------------- | | `project.list()` | Liste over alle prosjekter | Prosjekt[] | -| `project.current()` | Få nåværende prosjekt | Prosjekt | --- @@ -254,8 +253,6 @@ const agents = await client.app.agents() // List all projects const projects = await client.project.list() -// Get current project -const currentProject = await client.project.current() ``` --- diff --git a/packages/web/src/content/docs/de/sdk.mdx b/packages/web/src/content/docs/de/sdk.mdx index 22e70071d62..5a0342f7d0f 100644 --- a/packages/web/src/content/docs/de/sdk.mdx +++ b/packages/web/src/content/docs/de/sdk.mdx @@ -246,7 +246,6 @@ const agents = await client.app.agents() | Method | Description | Response | | ------------------- | ---------------------------- | --------------------------------------------- | | `project.list()` | Listet alle Projekte | Project[] | -| `project.current()` | Ruft das aktuelle Projekt ab | Project | --- @@ -256,8 +255,6 @@ const agents = await client.app.agents() // List all projects const projects = await client.project.list() -// Get current project -const currentProject = await client.project.current() ``` --- diff --git a/packages/web/src/content/docs/es/sdk.mdx b/packages/web/src/content/docs/es/sdk.mdx index d75149cde85..d77cd561ad3 100644 --- a/packages/web/src/content/docs/es/sdk.mdx +++ b/packages/web/src/content/docs/es/sdk.mdx @@ -244,7 +244,6 @@ const agents = await client.app.agents() | Método | Descripción | Respuesta | | ------------------- | -------------------------- | --------------------------------------------- | | `project.list()` | Listar todos los proyectos | Project[] | -| `project.current()` | Obtener proyecto actual | Project | --- @@ -254,8 +253,6 @@ const agents = await client.app.agents() // List all projects const projects = await client.project.list() -// Get current project -const currentProject = await client.project.current() ``` --- diff --git a/packages/web/src/content/docs/fr/sdk.mdx b/packages/web/src/content/docs/fr/sdk.mdx index ee3a2dc49c2..c3c470c4382 100644 --- a/packages/web/src/content/docs/fr/sdk.mdx +++ b/packages/web/src/content/docs/fr/sdk.mdx @@ -244,7 +244,6 @@ const agents = await client.app.agents() | Méthode | Descriptif | Réponse | | ------------------- | -------------------------- | --------------------------------------------- | | `project.list()` | Lister tous les projets | Project[] | -| `project.current()` | Obtenir le projet en cours | Project | --- @@ -254,8 +253,6 @@ const agents = await client.app.agents() // List all projects const projects = await client.project.list() -// Get current project -const currentProject = await client.project.current() ``` --- diff --git a/packages/web/src/content/docs/it/sdk.mdx b/packages/web/src/content/docs/it/sdk.mdx index 2941bfb24cf..10c1565e206 100644 --- a/packages/web/src/content/docs/it/sdk.mdx +++ b/packages/web/src/content/docs/it/sdk.mdx @@ -244,7 +244,6 @@ const agents = await client.app.agents() | Metodo | Descrizione | Risposta | | ------------------- | ----------------- | --------------------------------------------- | | `project.list()` | Elenca i progetti | Project[] | -| `project.current()` | Progetto corrente | Project | --- @@ -254,8 +253,6 @@ const agents = await client.app.agents() // List all projects const projects = await client.project.list() -// Get current project -const currentProject = await client.project.current() ``` --- diff --git a/packages/web/src/content/docs/ja/sdk.mdx b/packages/web/src/content/docs/ja/sdk.mdx index 5afaeb4a130..e52beec38fb 100644 --- a/packages/web/src/content/docs/ja/sdk.mdx +++ b/packages/web/src/content/docs/ja/sdk.mdx @@ -244,7 +244,6 @@ const agents = await client.app.agents() | メソッド | 説明 | 戻り値 | | ------------------- | -------------------------------- | --------------------------------------------- | | `project.list()` | すべてのプロジェクトをリストする | Project[] | -| `project.current()` | 現在のプロジェクトを取得 | Project | --- @@ -254,8 +253,6 @@ const agents = await client.app.agents() // List all projects const projects = await client.project.list() -// Get current project -const currentProject = await client.project.current() ``` --- diff --git a/packages/web/src/content/docs/ko/sdk.mdx b/packages/web/src/content/docs/ko/sdk.mdx index a5d12d1ab05..e0ce9ba3609 100644 --- a/packages/web/src/content/docs/ko/sdk.mdx +++ b/packages/web/src/content/docs/ko/sdk.mdx @@ -244,7 +244,6 @@ const agents = await client.app.agents() | 방법 | 설명 | 응답 | | ------------------- | ----------------------- | --------------------------------------------- | | `project.list()` | 모든 프로젝트 보기 | Project[] | -| `project.current()` | 현재 프로젝트 가져 오기 | Project | --- @@ -254,8 +253,6 @@ const agents = await client.app.agents() // List all projects const projects = await client.project.list() -// Get current project -const currentProject = await client.project.current() ``` --- diff --git a/packages/web/src/content/docs/nb/sdk.mdx b/packages/web/src/content/docs/nb/sdk.mdx index a9470944767..494c565bb9a 100644 --- a/packages/web/src/content/docs/nb/sdk.mdx +++ b/packages/web/src/content/docs/nb/sdk.mdx @@ -244,7 +244,6 @@ const agents = await client.app.agents() | Metode | Beskrivelse | Svar | | ------------------- | ----------------------- | --------------------------------------------- | | `project.list()` | List alle prosjekter | Project[] | -| `project.current()` | Hent gjeldende prosjekt | Project | --- @@ -254,8 +253,6 @@ const agents = await client.app.agents() // List all projects const projects = await client.project.list() -// Get current project -const currentProject = await client.project.current() ``` --- diff --git a/packages/web/src/content/docs/pl/sdk.mdx b/packages/web/src/content/docs/pl/sdk.mdx index 40099713eff..6758eaf2d8e 100644 --- a/packages/web/src/content/docs/pl/sdk.mdx +++ b/packages/web/src/content/docs/pl/sdk.mdx @@ -244,7 +244,6 @@ const agents = await client.app.agents() | Metoda | Opis | Odpowiedź | | ------------------- | -------------------------- | --------------------------------------------- | | `project.list()` | Lista wszystkich projektów | Project[] | -| `project.current()` | Pobierz bieżący projekt | Project | --- @@ -254,8 +253,6 @@ const agents = await client.app.agents() // List all projects const projects = await client.project.list() -// Get current project -const currentProject = await client.project.current() ``` --- diff --git a/packages/web/src/content/docs/pt-br/sdk.mdx b/packages/web/src/content/docs/pt-br/sdk.mdx index 7fd765e4c14..cbb5318dce4 100644 --- a/packages/web/src/content/docs/pt-br/sdk.mdx +++ b/packages/web/src/content/docs/pt-br/sdk.mdx @@ -244,7 +244,6 @@ const agents = await client.app.agents() | Método | Descrição | Resposta | | ------------------- | ------------------------ | --------------------------------------------- | | `project.list()` | Listar todos os projetos | Project[] | -| `project.current()` | Obter projeto atual | Project | --- @@ -254,8 +253,6 @@ const agents = await client.app.agents() // List all projects const projects = await client.project.list() -// Get current project -const currentProject = await client.project.current() ``` --- diff --git a/packages/web/src/content/docs/ru/sdk.mdx b/packages/web/src/content/docs/ru/sdk.mdx index 0afdea1b6ff..4f0b6a95953 100644 --- a/packages/web/src/content/docs/ru/sdk.mdx +++ b/packages/web/src/content/docs/ru/sdk.mdx @@ -244,7 +244,6 @@ const agents = await client.app.agents() | Метод | Описание | Ответ | | ------------------- | ------------------- | --------------------------------------------- | | `project.list()` | List all projects | Project[] | -| `project.current()` | Get current project | Project | --- @@ -254,8 +253,6 @@ const agents = await client.app.agents() // List all projects const projects = await client.project.list() -// Get current project -const currentProject = await client.project.current() ``` --- diff --git a/packages/web/src/content/docs/sdk.mdx b/packages/web/src/content/docs/sdk.mdx index 24546213be5..887b66a37ab 100644 --- a/packages/web/src/content/docs/sdk.mdx +++ b/packages/web/src/content/docs/sdk.mdx @@ -244,7 +244,6 @@ const agents = await client.app.agents() | Method | Description | Response | | ------------------- | ------------------- | --------------------------------------------- | | `project.list()` | List all projects | Project[] | -| `project.current()` | Get current project | Project | --- @@ -254,8 +253,6 @@ const agents = await client.app.agents() // List all projects const projects = await client.project.list() -// Get current project -const currentProject = await client.project.current() ``` --- diff --git a/packages/web/src/content/docs/th/sdk.mdx b/packages/web/src/content/docs/th/sdk.mdx index 3454b576670..7299f2b7b92 100644 --- a/packages/web/src/content/docs/th/sdk.mdx +++ b/packages/web/src/content/docs/th/sdk.mdx @@ -244,7 +244,6 @@ const agents = await client.app.agents() | Method | คำอธิบาย | การตอบสนอง | | ------------------- | ------------------------ | --------------------------------------------- | | `project.list()` | แสดงรายการโครงการทั้งหมด | Project[] | -| `project.current()` | รับโครงการปัจจุบัน | Project | --- @@ -254,8 +253,6 @@ const agents = await client.app.agents() // List all projects const projects = await client.project.list() -// Get current project -const currentProject = await client.project.current() ``` --- diff --git a/packages/web/src/content/docs/tr/sdk.mdx b/packages/web/src/content/docs/tr/sdk.mdx index 44c910b1cb9..01967acb454 100644 --- a/packages/web/src/content/docs/tr/sdk.mdx +++ b/packages/web/src/content/docs/tr/sdk.mdx @@ -244,7 +244,6 @@ const agents = await client.app.agents() | Yöntem | Açıklama | Yanıt | | ------------------- | --------------------- | --------------------------------------------- | | `project.list()` | Tüm projeleri listele | Project[] | -| `project.current()` | Mevcut projeyi al | Project | --- @@ -254,8 +253,6 @@ const agents = await client.app.agents() // List all projects const projects = await client.project.list() -// Get current project -const currentProject = await client.project.current() ``` --- diff --git a/packages/web/src/content/docs/zh-cn/sdk.mdx b/packages/web/src/content/docs/zh-cn/sdk.mdx index 121e423d10d..6e5f1552062 100644 --- a/packages/web/src/content/docs/zh-cn/sdk.mdx +++ b/packages/web/src/content/docs/zh-cn/sdk.mdx @@ -244,7 +244,6 @@ const agents = await client.app.agents() | 方法 | 描述 | 响应 | | ------------------- | ------------ | --------------------------------------------- | | `project.list()` | 列出所有项目 | Project[] | -| `project.current()` | 获取当前项目 | Project | --- @@ -254,8 +253,6 @@ const agents = await client.app.agents() // List all projects const projects = await client.project.list() -// Get current project -const currentProject = await client.project.current() ``` --- diff --git a/packages/web/src/content/docs/zh-tw/sdk.mdx b/packages/web/src/content/docs/zh-tw/sdk.mdx index 2ed216d8b91..6867409b1d3 100644 --- a/packages/web/src/content/docs/zh-tw/sdk.mdx +++ b/packages/web/src/content/docs/zh-tw/sdk.mdx @@ -244,7 +244,6 @@ const agents = await client.app.agents() | 方法 | 描述 | 回應 | | ------------------- | ------------ | --------------------------------------------- | | `project.list()` | 列出所有專案 | Project[] | -| `project.current()` | 取得當前專案 | Project | --- @@ -254,8 +253,6 @@ const agents = await client.app.agents() // List all projects const projects = await client.project.list() -// Get current project -const currentProject = await client.project.current() ``` --- diff --git a/services/www/openapi.json b/services/www/openapi.json index 2c448b2f082..0afb13c3d81 100644 --- a/services/www/openapi.json +++ b/services/www/openapi.json @@ -1115,6 +1115,16 @@ "type": "null" } ] + }, + "permissions": { + "anyOf": [ + { + "$ref": "#/components/schemas/Permission.Ruleset" + }, + { + "type": "null" + } + ] } }, "additionalProperties": false @@ -7466,89 +7476,6 @@ } } }, - "/api/project/current": { - "get": { - "tags": ["project"], - "operationId": "v2.project.current", - "parameters": [ - { - "name": "location", - "in": "query", - "schema": { - "anyOf": [ - { - "type": "object", - "properties": { - "directory": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ] - }, - "workspace": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "null" - } - ] - }, - "required": false, - "style": "deepObject", - "explode": true - } - ], - "security": [], - "responses": { - "200": { - "description": "Project.Current", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Project.Current" - } - } - } - }, - "400": { - "description": "InvalidRequestError", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/InvalidRequestErrorEncoded" - } - } - } - }, - "401": { - "description": "UnauthorizedError", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/UnauthorizedErrorEncoded" - } - } - } - } - }, - "description": "Resolve the project for the requested location.", - "summary": "Get current project" - } - }, "/api/form/request": { "get": { "tags": ["form"], @@ -8783,6 +8710,85 @@ } } }, + "/api/session/{sessionID}/permission/rules": { + "put": { + "tags": ["permission"], + "operationId": "v2.session.permission.rules", + "parameters": [ + { + "name": "sessionID", + "in": "path", + "schema": { + "type": "string", + "pattern": "^ses" + }, + "required": true + } + ], + "security": [], + "responses": { + "204": { + "description": "" + }, + "400": { + "description": "InvalidRequestError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidRequestErrorEncoded" + } + } + } + }, + "401": { + "description": "UnauthorizedError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorEncoded" + } + } + } + }, + "404": { + "description": "SessionNotFoundError", + "content": { + "application/json": { + "schema": { + "anyOf": [ + { + "$ref": "#/components/schemas/SessionNotFoundErrorEncoded" + }, + { + "$ref": "#/components/schemas/SessionNotFoundErrorEncoded" + } + ] + } + } + } + } + }, + "description": "Replace the session-scoped permission rules. Rules are evaluated after the agent's rules, and the last matching rule wins.", + "summary": "Replace session permission rules", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "permissions": { + "$ref": "#/components/schemas/Permission.Ruleset" + } + }, + "required": ["permissions"], + "additionalProperties": false + } + } + }, + "required": true + } + } + }, "/api/fs/read/*": { "get": { "tags": ["filesystem"], @@ -13672,6 +13678,143 @@ "description": "Return configuration documents and discovery sources for the requested location, from lowest to highest priority.", "summary": "Get configuration" } + }, + "/api/config/preferences": { + "get": { + "tags": ["config"], + "operationId": "v2.config.preferences", + "parameters": [], + "security": [], + "responses": { + "200": { + "description": "Config.Preferences", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Config.Preferences" + } + } + } + }, + "400": { + "description": "InvalidRequestError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidRequestErrorEncoded" + } + } + } + }, + "401": { + "description": "UnauthorizedError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorEncoded" + } + } + } + } + }, + "description": "Return preferences from the highest-precedence global configuration document.", + "summary": "Get global preferences" + }, + "patch": { + "tags": ["config"], + "operationId": "v2.config.updatePreferences", + "parameters": [], + "security": [], + "responses": { + "200": { + "description": "Config.Preferences", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Config.Preferences" + } + } + } + }, + "400": { + "description": "InvalidRequestError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidRequestErrorEncoded" + } + } + } + }, + "401": { + "description": "UnauthorizedError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorEncoded" + } + } + } + } + }, + "description": "Patch preferences in the highest-precedence global configuration document.", + "summary": "Update global preferences", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Config.PreferencesPatch" + } + } + }, + "required": true + } + } + }, + "/api/config/shell": { + "get": { + "tags": ["config"], + "operationId": "v2.config.shells", + "parameters": [], + "security": [], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ConfigShell.Option" + } + } + } + } + }, + "400": { + "description": "InvalidRequestError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidRequestErrorEncoded" + } + } + } + }, + "401": { + "description": "UnauthorizedError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorEncoded" + } + } + } + } + }, + "description": "Return shells available to terminal and agent execution.", + "summary": "List available shells" + } } }, "components": { @@ -14532,6 +14675,60 @@ "required": ["package"], "additionalProperties": false }, + "Config.Preferences": { + "type": "object", + "properties": { + "shell": { + "type": "string" + }, + "websearch": { + "anyOf": [ + { + "type": "boolean", + "enum": [false] + }, + { + "$ref": "#/components/schemas/ConfigWebSearch.InfoEncoded" + } + ] + } + }, + "additionalProperties": false + }, + "Config.PreferencesPatch": { + "type": "object", + "properties": { + "shell": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "websearch": { + "anyOf": [ + { + "anyOf": [ + { + "type": "boolean", + "enum": [false] + }, + { + "$ref": "#/components/schemas/ConfigWebSearch.InfoEncoded" + } + ] + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + }, "Config.ProviderEncoded": { "type": "object", "properties": { @@ -14637,6 +14834,22 @@ "required": ["directory"], "additionalProperties": false }, + "ConfigShell.Option": { + "type": "object", + "properties": { + "path": { + "type": "string" + }, + "name": { + "type": "string" + }, + "acceptable": { + "type": "boolean" + } + }, + "required": ["path", "name", "acceptable"], + "additionalProperties": false + }, "ConfigWebSearch.InfoEncoded": { "type": "object", "properties": { @@ -17231,22 +17444,6 @@ }, "additionalProperties": false }, - "Project.Current": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "directory": { - "type": "string" - }, - "canonical": { - "type": "string" - } - }, - "required": ["id", "directory", "canonical"], - "additionalProperties": false - }, "Project.Icon": { "type": "object", "properties": { @@ -18151,6 +18348,9 @@ "metadata": { "$ref": "#/components/schemas/Session.Metadata" }, + "permissions": { + "$ref": "#/components/schemas/Permission.Ruleset" + }, "revert": { "$ref": "#/components/schemas/Session.Revert" } diff --git a/services/www/public/openapi.json b/services/www/public/openapi.json index 2c448b2f082..0afb13c3d81 100644 --- a/services/www/public/openapi.json +++ b/services/www/public/openapi.json @@ -1115,6 +1115,16 @@ "type": "null" } ] + }, + "permissions": { + "anyOf": [ + { + "$ref": "#/components/schemas/Permission.Ruleset" + }, + { + "type": "null" + } + ] } }, "additionalProperties": false @@ -7466,89 +7476,6 @@ } } }, - "/api/project/current": { - "get": { - "tags": ["project"], - "operationId": "v2.project.current", - "parameters": [ - { - "name": "location", - "in": "query", - "schema": { - "anyOf": [ - { - "type": "object", - "properties": { - "directory": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ] - }, - "workspace": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ] - } - }, - "additionalProperties": false - }, - { - "type": "null" - } - ] - }, - "required": false, - "style": "deepObject", - "explode": true - } - ], - "security": [], - "responses": { - "200": { - "description": "Project.Current", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Project.Current" - } - } - } - }, - "400": { - "description": "InvalidRequestError", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/InvalidRequestErrorEncoded" - } - } - } - }, - "401": { - "description": "UnauthorizedError", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/UnauthorizedErrorEncoded" - } - } - } - } - }, - "description": "Resolve the project for the requested location.", - "summary": "Get current project" - } - }, "/api/form/request": { "get": { "tags": ["form"], @@ -8783,6 +8710,85 @@ } } }, + "/api/session/{sessionID}/permission/rules": { + "put": { + "tags": ["permission"], + "operationId": "v2.session.permission.rules", + "parameters": [ + { + "name": "sessionID", + "in": "path", + "schema": { + "type": "string", + "pattern": "^ses" + }, + "required": true + } + ], + "security": [], + "responses": { + "204": { + "description": "" + }, + "400": { + "description": "InvalidRequestError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidRequestErrorEncoded" + } + } + } + }, + "401": { + "description": "UnauthorizedError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorEncoded" + } + } + } + }, + "404": { + "description": "SessionNotFoundError", + "content": { + "application/json": { + "schema": { + "anyOf": [ + { + "$ref": "#/components/schemas/SessionNotFoundErrorEncoded" + }, + { + "$ref": "#/components/schemas/SessionNotFoundErrorEncoded" + } + ] + } + } + } + } + }, + "description": "Replace the session-scoped permission rules. Rules are evaluated after the agent's rules, and the last matching rule wins.", + "summary": "Replace session permission rules", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "permissions": { + "$ref": "#/components/schemas/Permission.Ruleset" + } + }, + "required": ["permissions"], + "additionalProperties": false + } + } + }, + "required": true + } + } + }, "/api/fs/read/*": { "get": { "tags": ["filesystem"], @@ -13672,6 +13678,143 @@ "description": "Return configuration documents and discovery sources for the requested location, from lowest to highest priority.", "summary": "Get configuration" } + }, + "/api/config/preferences": { + "get": { + "tags": ["config"], + "operationId": "v2.config.preferences", + "parameters": [], + "security": [], + "responses": { + "200": { + "description": "Config.Preferences", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Config.Preferences" + } + } + } + }, + "400": { + "description": "InvalidRequestError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidRequestErrorEncoded" + } + } + } + }, + "401": { + "description": "UnauthorizedError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorEncoded" + } + } + } + } + }, + "description": "Return preferences from the highest-precedence global configuration document.", + "summary": "Get global preferences" + }, + "patch": { + "tags": ["config"], + "operationId": "v2.config.updatePreferences", + "parameters": [], + "security": [], + "responses": { + "200": { + "description": "Config.Preferences", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Config.Preferences" + } + } + } + }, + "400": { + "description": "InvalidRequestError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidRequestErrorEncoded" + } + } + } + }, + "401": { + "description": "UnauthorizedError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorEncoded" + } + } + } + } + }, + "description": "Patch preferences in the highest-precedence global configuration document.", + "summary": "Update global preferences", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Config.PreferencesPatch" + } + } + }, + "required": true + } + } + }, + "/api/config/shell": { + "get": { + "tags": ["config"], + "operationId": "v2.config.shells", + "parameters": [], + "security": [], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ConfigShell.Option" + } + } + } + } + }, + "400": { + "description": "InvalidRequestError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidRequestErrorEncoded" + } + } + } + }, + "401": { + "description": "UnauthorizedError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorEncoded" + } + } + } + } + }, + "description": "Return shells available to terminal and agent execution.", + "summary": "List available shells" + } } }, "components": { @@ -14532,6 +14675,60 @@ "required": ["package"], "additionalProperties": false }, + "Config.Preferences": { + "type": "object", + "properties": { + "shell": { + "type": "string" + }, + "websearch": { + "anyOf": [ + { + "type": "boolean", + "enum": [false] + }, + { + "$ref": "#/components/schemas/ConfigWebSearch.InfoEncoded" + } + ] + } + }, + "additionalProperties": false + }, + "Config.PreferencesPatch": { + "type": "object", + "properties": { + "shell": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "websearch": { + "anyOf": [ + { + "anyOf": [ + { + "type": "boolean", + "enum": [false] + }, + { + "$ref": "#/components/schemas/ConfigWebSearch.InfoEncoded" + } + ] + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + }, "Config.ProviderEncoded": { "type": "object", "properties": { @@ -14637,6 +14834,22 @@ "required": ["directory"], "additionalProperties": false }, + "ConfigShell.Option": { + "type": "object", + "properties": { + "path": { + "type": "string" + }, + "name": { + "type": "string" + }, + "acceptable": { + "type": "boolean" + } + }, + "required": ["path", "name", "acceptable"], + "additionalProperties": false + }, "ConfigWebSearch.InfoEncoded": { "type": "object", "properties": { @@ -17231,22 +17444,6 @@ }, "additionalProperties": false }, - "Project.Current": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "directory": { - "type": "string" - }, - "canonical": { - "type": "string" - } - }, - "required": ["id", "directory", "canonical"], - "additionalProperties": false - }, "Project.Icon": { "type": "object", "properties": { @@ -18151,6 +18348,9 @@ "metadata": { "$ref": "#/components/schemas/Session.Metadata" }, + "permissions": { + "$ref": "#/components/schemas/Permission.Ruleset" + }, "revert": { "$ref": "#/components/schemas/Session.Revert" }