fix(tui): gate background shortcut by capability (#32837)
This commit is contained in:
@@ -25,6 +25,10 @@ const ConsoleStateResponse = Schema.Struct({
|
|||||||
switchableOrgCount: NonNegativeInt,
|
switchableOrgCount: NonNegativeInt,
|
||||||
}).annotate({ identifier: "ConsoleState" })
|
}).annotate({ identifier: "ConsoleState" })
|
||||||
|
|
||||||
|
const CapabilitiesResponse = Schema.Struct({
|
||||||
|
backgroundSubagents: Schema.Boolean,
|
||||||
|
}).annotate({ identifier: "ExperimentalCapabilities" })
|
||||||
|
|
||||||
const ConsoleOrgOption = Schema.Struct({
|
const ConsoleOrgOption = Schema.Struct({
|
||||||
accountID: Schema.String,
|
accountID: Schema.String,
|
||||||
accountEmail: Schema.String,
|
accountEmail: Schema.String,
|
||||||
@@ -84,6 +88,7 @@ export const SessionListQuery = Schema.Struct({
|
|||||||
})
|
})
|
||||||
|
|
||||||
export const ExperimentalPaths = {
|
export const ExperimentalPaths = {
|
||||||
|
capabilities: "/experimental/capabilities",
|
||||||
console: "/experimental/console",
|
console: "/experimental/console",
|
||||||
consoleOrgs: "/experimental/console/orgs",
|
consoleOrgs: "/experimental/console/orgs",
|
||||||
consoleSwitch: "/experimental/console/switch",
|
consoleSwitch: "/experimental/console/switch",
|
||||||
@@ -100,6 +105,16 @@ export const ExperimentalApi = HttpApi.make("experimental")
|
|||||||
.add(
|
.add(
|
||||||
HttpApiGroup.make("experimental")
|
HttpApiGroup.make("experimental")
|
||||||
.add(
|
.add(
|
||||||
|
HttpApiEndpoint.get("capabilities", ExperimentalPaths.capabilities, {
|
||||||
|
query: WorkspaceRoutingQuery,
|
||||||
|
success: described(CapabilitiesResponse, "Experimental capabilities"),
|
||||||
|
}).annotateMerge(
|
||||||
|
OpenApi.annotations({
|
||||||
|
identifier: "experimental.capabilities.get",
|
||||||
|
summary: "Get experimental capabilities",
|
||||||
|
description: "Get experimental features enabled on the OpenCode server.",
|
||||||
|
}),
|
||||||
|
),
|
||||||
HttpApiEndpoint.get("console", ExperimentalPaths.console, {
|
HttpApiEndpoint.get("console", ExperimentalPaths.console, {
|
||||||
query: WorkspaceRoutingQuery,
|
query: WorkspaceRoutingQuery,
|
||||||
success: described(ConsoleStateResponse, "Active Console provider metadata"),
|
success: described(ConsoleStateResponse, "Active Console provider metadata"),
|
||||||
|
|||||||
@@ -36,6 +36,10 @@ export const experimentalHandlers = HttpApiBuilder.group(InstanceHttpApi, "exper
|
|||||||
const background = yield* BackgroundJob.Service
|
const background = yield* BackgroundJob.Service
|
||||||
const flags = yield* RuntimeFlags.Service
|
const flags = yield* RuntimeFlags.Service
|
||||||
|
|
||||||
|
const capabilities = Effect.fn("ExperimentalHttpApi.capabilities")(function* () {
|
||||||
|
return { backgroundSubagents: flags.experimentalBackgroundSubagents }
|
||||||
|
})
|
||||||
|
|
||||||
const getConsole = Effect.fn("ExperimentalHttpApi.console")(function* () {
|
const getConsole = Effect.fn("ExperimentalHttpApi.console")(function* () {
|
||||||
const [state, groups] = yield* Effect.all(
|
const [state, groups] = yield* Effect.all(
|
||||||
[
|
[
|
||||||
@@ -171,6 +175,7 @@ export const experimentalHandlers = HttpApiBuilder.group(InstanceHttpApi, "exper
|
|||||||
})
|
})
|
||||||
|
|
||||||
return handlers
|
return handlers
|
||||||
|
.handle("capabilities", capabilities)
|
||||||
.handle("console", getConsole)
|
.handle("console", getConsole)
|
||||||
.handle("consoleOrgs", listConsoleOrgs)
|
.handle("consoleOrgs", listConsoleOrgs)
|
||||||
.handle("consoleSwitch", switchConsole)
|
.handle("consoleSwitch", switchConsole)
|
||||||
|
|||||||
@@ -578,6 +578,12 @@ const scenarios: Scenario[] = [
|
|||||||
.get("/experimental/session", "experimental.session.list")
|
.get("/experimental/session", "experimental.session.list")
|
||||||
.at((ctx) => ({ path: "/experimental/session?roots=false&archived=false", headers: ctx.headers() }))
|
.at((ctx) => ({ path: "/experimental/session?roots=false&archived=false", headers: ctx.headers() }))
|
||||||
.json(200, array),
|
.json(200, array),
|
||||||
|
http.protected
|
||||||
|
.get("/experimental/capabilities", "experimental.capabilities.get")
|
||||||
|
.json(200, (body) => {
|
||||||
|
check(typeof body === "object" && body !== null, "capabilities should be an object")
|
||||||
|
check("backgroundSubagents" in body, "capabilities should report background subagents")
|
||||||
|
}),
|
||||||
http.protected
|
http.protected
|
||||||
.post("/experimental/session/{sessionID}/background", "experimental.session.background")
|
.post("/experimental/session/{sessionID}/background", "experimental.session.background")
|
||||||
.mutating()
|
.mutating()
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ import type {
|
|||||||
EventTuiPromptAppend,
|
EventTuiPromptAppend,
|
||||||
EventTuiSessionSelect,
|
EventTuiSessionSelect,
|
||||||
EventTuiToastShow,
|
EventTuiToastShow,
|
||||||
|
ExperimentalCapabilitiesGetErrors,
|
||||||
|
ExperimentalCapabilitiesGetResponses,
|
||||||
ExperimentalConsoleGetErrors,
|
ExperimentalConsoleGetErrors,
|
||||||
ExperimentalConsoleGetResponses,
|
ExperimentalConsoleGetResponses,
|
||||||
ExperimentalConsoleListOrgsErrors,
|
ExperimentalConsoleListOrgsErrors,
|
||||||
@@ -627,6 +629,42 @@ export class ControlPlane extends HeyApiClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class Capabilities extends HeyApiClient {
|
||||||
|
/**
|
||||||
|
* Get experimental capabilities
|
||||||
|
*
|
||||||
|
* Get experimental features enabled on the OpenCode server.
|
||||||
|
*/
|
||||||
|
public get<ThrowOnError extends boolean = false>(
|
||||||
|
parameters?: {
|
||||||
|
directory?: string
|
||||||
|
workspace?: string
|
||||||
|
},
|
||||||
|
options?: Options<never, ThrowOnError>,
|
||||||
|
) {
|
||||||
|
const params = buildClientParams(
|
||||||
|
[parameters],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
args: [
|
||||||
|
{ in: "query", key: "directory" },
|
||||||
|
{ in: "query", key: "workspace" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
)
|
||||||
|
return (options?.client ?? this.client).get<
|
||||||
|
ExperimentalCapabilitiesGetResponses,
|
||||||
|
ExperimentalCapabilitiesGetErrors,
|
||||||
|
ThrowOnError
|
||||||
|
>({
|
||||||
|
url: "/experimental/capabilities",
|
||||||
|
...options,
|
||||||
|
...params,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class Console extends HeyApiClient {
|
export class Console extends HeyApiClient {
|
||||||
/**
|
/**
|
||||||
* Get active Console provider metadata
|
* Get active Console provider metadata
|
||||||
@@ -1180,6 +1218,11 @@ export class Experimental extends HeyApiClient {
|
|||||||
return (this._controlPlane ??= new ControlPlane({ client: this.client }))
|
return (this._controlPlane ??= new ControlPlane({ client: this.client }))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _capabilities?: Capabilities
|
||||||
|
get capabilities(): Capabilities {
|
||||||
|
return (this._capabilities ??= new Capabilities({ client: this.client }))
|
||||||
|
}
|
||||||
|
|
||||||
private _console?: Console
|
private _console?: Console
|
||||||
get console(): Console {
|
get console(): Console {
|
||||||
return (this._console ??= new Console({ client: this.client }))
|
return (this._console ??= new Console({ client: this.client }))
|
||||||
|
|||||||
@@ -2144,6 +2144,10 @@ export type Provider = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ExperimentalCapabilities = {
|
||||||
|
backgroundSubagents: boolean
|
||||||
|
}
|
||||||
|
|
||||||
export type ConsoleState = {
|
export type ConsoleState = {
|
||||||
consoleManagedProviders: Array<string>
|
consoleManagedProviders: Array<string>
|
||||||
activeOrgName?: string
|
activeOrgName?: string
|
||||||
@@ -5549,6 +5553,36 @@ export type ConfigProvidersResponses = {
|
|||||||
|
|
||||||
export type ConfigProvidersResponse = ConfigProvidersResponses[keyof ConfigProvidersResponses]
|
export type ConfigProvidersResponse = ConfigProvidersResponses[keyof ConfigProvidersResponses]
|
||||||
|
|
||||||
|
export type ExperimentalCapabilitiesGetData = {
|
||||||
|
body?: never
|
||||||
|
path?: never
|
||||||
|
query?: {
|
||||||
|
directory?: string
|
||||||
|
workspace?: string
|
||||||
|
}
|
||||||
|
url: "/experimental/capabilities"
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ExperimentalCapabilitiesGetErrors = {
|
||||||
|
/**
|
||||||
|
* Bad request
|
||||||
|
*/
|
||||||
|
400: BadRequestError
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ExperimentalCapabilitiesGetError =
|
||||||
|
ExperimentalCapabilitiesGetErrors[keyof ExperimentalCapabilitiesGetErrors]
|
||||||
|
|
||||||
|
export type ExperimentalCapabilitiesGetResponses = {
|
||||||
|
/**
|
||||||
|
* Experimental capabilities
|
||||||
|
*/
|
||||||
|
200: ExperimentalCapabilities
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ExperimentalCapabilitiesGetResponse =
|
||||||
|
ExperimentalCapabilitiesGetResponses[keyof ExperimentalCapabilitiesGetResponses]
|
||||||
|
|
||||||
export type ExperimentalConsoleGetData = {
|
export type ExperimentalConsoleGetData = {
|
||||||
body?: never
|
body?: never
|
||||||
path?: never
|
path?: never
|
||||||
|
|||||||
@@ -65,6 +65,9 @@ export const {
|
|||||||
provider_default: Record<string, string>
|
provider_default: Record<string, string>
|
||||||
provider_next: ProviderListResponse
|
provider_next: ProviderListResponse
|
||||||
console_state: ConsoleState
|
console_state: ConsoleState
|
||||||
|
capabilities: {
|
||||||
|
experimentalBackgroundSubagents: boolean
|
||||||
|
}
|
||||||
provider_auth: Record<string, ProviderAuthMethod[]>
|
provider_auth: Record<string, ProviderAuthMethod[]>
|
||||||
agent: Agent[]
|
agent: Agent[]
|
||||||
command: Command[]
|
command: Command[]
|
||||||
@@ -107,6 +110,9 @@ export const {
|
|||||||
connected: [],
|
connected: [],
|
||||||
},
|
},
|
||||||
console_state: emptyConsoleState,
|
console_state: emptyConsoleState,
|
||||||
|
capabilities: {
|
||||||
|
experimentalBackgroundSubagents: false,
|
||||||
|
},
|
||||||
provider_auth: {},
|
provider_auth: {},
|
||||||
config: {},
|
config: {},
|
||||||
status: "loading",
|
status: "loading",
|
||||||
@@ -434,6 +440,10 @@ export const {
|
|||||||
// blocking - include session.list when continuing a session
|
// blocking - include session.list when continuing a session
|
||||||
const providersPromise = sdk.client.config.providers({ workspace }, { throwOnError: true })
|
const providersPromise = sdk.client.config.providers({ workspace }, { throwOnError: true })
|
||||||
const providerListPromise = sdk.client.provider.list({ workspace }, { throwOnError: true })
|
const providerListPromise = sdk.client.provider.list({ workspace }, { throwOnError: true })
|
||||||
|
const capabilitiesPromise = sdk.client.experimental.capabilities
|
||||||
|
.get({ workspace }, { throwOnError: true })
|
||||||
|
.then((x) => x.data)
|
||||||
|
.catch(() => undefined)
|
||||||
const consoleStatePromise = sdk.client.experimental.console
|
const consoleStatePromise = sdk.client.experimental.console
|
||||||
.get({ workspace }, { throwOnError: true })
|
.get({ workspace }, { throwOnError: true })
|
||||||
.then((x) => x.data)
|
.then((x) => x.data)
|
||||||
@@ -443,6 +453,7 @@ export const {
|
|||||||
await Promise.all([
|
await Promise.all([
|
||||||
providersPromise,
|
providersPromise,
|
||||||
providerListPromise,
|
providerListPromise,
|
||||||
|
capabilitiesPromise,
|
||||||
agentsPromise,
|
agentsPromise,
|
||||||
configPromise,
|
configPromise,
|
||||||
projectPromise,
|
projectPromise,
|
||||||
@@ -451,6 +462,7 @@ export const {
|
|||||||
.then(async () => {
|
.then(async () => {
|
||||||
const providersResponse = providersPromise.then((x) => x.data!)
|
const providersResponse = providersPromise.then((x) => x.data!)
|
||||||
const providerListResponse = providerListPromise.then((x) => x.data!)
|
const providerListResponse = providerListPromise.then((x) => x.data!)
|
||||||
|
const capabilitiesResponse = capabilitiesPromise
|
||||||
const consoleStateResponse = consoleStatePromise
|
const consoleStateResponse = consoleStatePromise
|
||||||
const agentsResponse = agentsPromise.then((x) => x.data ?? [])
|
const agentsResponse = agentsPromise.then((x) => x.data ?? [])
|
||||||
const configResponse = configPromise.then((x) => x.data!)
|
const configResponse = configPromise.then((x) => x.data!)
|
||||||
@@ -459,6 +471,7 @@ export const {
|
|||||||
return Promise.all([
|
return Promise.all([
|
||||||
providersResponse,
|
providersResponse,
|
||||||
providerListResponse,
|
providerListResponse,
|
||||||
|
capabilitiesResponse,
|
||||||
consoleStateResponse,
|
consoleStateResponse,
|
||||||
agentsResponse,
|
agentsResponse,
|
||||||
configResponse,
|
configResponse,
|
||||||
@@ -466,15 +479,21 @@ export const {
|
|||||||
]).then((responses) => {
|
]).then((responses) => {
|
||||||
const providers = responses[0]
|
const providers = responses[0]
|
||||||
const providerList = responses[1]
|
const providerList = responses[1]
|
||||||
const consoleState = responses[2]
|
const capabilities = responses[2]
|
||||||
const agents = responses[3]
|
const consoleState = responses[3]
|
||||||
const config = responses[4]
|
const agents = responses[4]
|
||||||
const sessions = responses[5]
|
const config = responses[5]
|
||||||
|
const sessions = responses[6]
|
||||||
|
|
||||||
batch(() => {
|
batch(() => {
|
||||||
setStore("provider", reconcile(providers.providers))
|
setStore("provider", reconcile(providers.providers))
|
||||||
setStore("provider_default", reconcile(providers.default))
|
setStore("provider_default", reconcile(providers.default))
|
||||||
setStore("provider_next", reconcile(providerList))
|
setStore("provider_next", reconcile(providerList))
|
||||||
|
setStore(
|
||||||
|
"capabilities",
|
||||||
|
"experimentalBackgroundSubagents",
|
||||||
|
capabilities?.backgroundSubagents === true,
|
||||||
|
)
|
||||||
setStore("console_state", reconcile(consoleState))
|
setStore("console_state", reconcile(consoleState))
|
||||||
setStore("agent", reconcile(agents))
|
setStore("agent", reconcile(agents))
|
||||||
setStore("config", reconcile(config))
|
setStore("config", reconcile(config))
|
||||||
|
|||||||
@@ -206,15 +206,17 @@ export function Session() {
|
|||||||
})
|
})
|
||||||
const messages = createMemo(() => sync.data.message[route.sessionID] ?? [])
|
const messages = createMemo(() => sync.data.message[route.sessionID] ?? [])
|
||||||
const foregroundTasks = createMemo(() =>
|
const foregroundTasks = createMemo(() =>
|
||||||
messages().flatMap((message) =>
|
sync.data.capabilities.experimentalBackgroundSubagents
|
||||||
(sync.data.part[message.id] ?? []).filter(
|
? messages().flatMap((message) =>
|
||||||
(part): part is ToolPart =>
|
(sync.data.part[message.id] ?? []).filter(
|
||||||
part.type === "tool" &&
|
(part): part is ToolPart =>
|
||||||
part.tool === "task" &&
|
part.type === "tool" &&
|
||||||
part.state.status === "running" &&
|
part.tool === "task" &&
|
||||||
part.state.metadata?.background !== true,
|
part.state.status === "running" &&
|
||||||
),
|
part.state.metadata?.background !== true,
|
||||||
),
|
),
|
||||||
|
)
|
||||||
|
: [],
|
||||||
)
|
)
|
||||||
const userMessageIDs = createMemo(
|
const userMessageIDs = createMemo(
|
||||||
() =>
|
() =>
|
||||||
@@ -1510,13 +1512,16 @@ function AssistantMessage(props: { message: AssistantMessage; parts: Part[]; las
|
|||||||
{childShortcut()}
|
{childShortcut()}
|
||||||
<span style={{ fg: theme.textMuted }}> view subagents</span>
|
<span style={{ fg: theme.textMuted }}> view subagents</span>
|
||||||
<Show
|
<Show
|
||||||
when={props.parts.some(
|
when={
|
||||||
(x) =>
|
sync.data.capabilities.experimentalBackgroundSubagents &&
|
||||||
x.type === "tool" &&
|
props.parts.some(
|
||||||
x.tool === "task" &&
|
(x) =>
|
||||||
x.state.status === "running" &&
|
x.type === "tool" &&
|
||||||
x.state.metadata?.background !== true,
|
x.tool === "task" &&
|
||||||
)}
|
x.state.status === "running" &&
|
||||||
|
x.state.metadata?.background !== true,
|
||||||
|
)
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<span style={{ fg: theme.textMuted }}> · </span>
|
<span style={{ fg: theme.textMuted }}> · </span>
|
||||||
{backgroundShortcut()}
|
{backgroundShortcut()}
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ export function createFetch(override?: FetchHandler) {
|
|||||||
return json({})
|
return json({})
|
||||||
if (url.pathname === "/config/providers") return json({ providers: {}, default: {} })
|
if (url.pathname === "/config/providers") return json({ providers: {}, default: {} })
|
||||||
if (url.pathname === "/experimental/console") return json({ consoleManagedProviders: [], switchableOrgCount: 0 })
|
if (url.pathname === "/experimental/console") return json({ consoleManagedProviders: [], switchableOrgCount: 0 })
|
||||||
|
if (url.pathname === "/experimental/capabilities") return json({ backgroundSubagents: false })
|
||||||
if (url.pathname === "/path") return json({ home: "", state: "", config: "", worktree, directory })
|
if (url.pathname === "/path") return json({ home: "", state: "", config: "", worktree, directory })
|
||||||
if (url.pathname === "/api/location") return json({ directory, project: { id: "proj_test", directory: worktree } })
|
if (url.pathname === "/api/location") return json({ directory, project: { id: "proj_test", directory: worktree } })
|
||||||
if (
|
if (
|
||||||
|
|||||||
Reference in New Issue
Block a user