feat(tui): allow backgrounding synchronous subagents (#30488)
This commit is contained in:
@@ -2,6 +2,7 @@ import { AccountID, OrgID } from "@/account/schema"
|
||||
import { MCP } from "@/mcp"
|
||||
|
||||
import { Session } from "@/session/session"
|
||||
import { SessionID } from "@/session/schema"
|
||||
import { Worktree } from "@/worktree"
|
||||
import { NonNegativeInt } from "@opencode-ai/core/schema"
|
||||
import { Schema } from "effect"
|
||||
@@ -91,6 +92,7 @@ export const ExperimentalPaths = {
|
||||
worktree: "/experimental/worktree",
|
||||
worktreeReset: "/experimental/worktree/reset",
|
||||
session: "/experimental/session",
|
||||
sessionBackground: "/experimental/session/:sessionID/background",
|
||||
resource: "/experimental/resource",
|
||||
} as const
|
||||
|
||||
@@ -215,6 +217,19 @@ export const ExperimentalApi = HttpApi.make("experimental")
|
||||
"Get a list of all OpenCode sessions across projects, sorted by most recently updated. Archived sessions are excluded by default.",
|
||||
}),
|
||||
),
|
||||
HttpApiEndpoint.post("sessionBackground", ExperimentalPaths.sessionBackground, {
|
||||
params: { sessionID: SessionID },
|
||||
query: WorkspaceRoutingQuery,
|
||||
success: described(Schema.Boolean, "Backgrounded subagents"),
|
||||
error: HttpApiError.BadRequest,
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "experimental.session.background",
|
||||
summary: "Background subagents",
|
||||
description:
|
||||
"Detach any synchronous subagents currently blocking the session and continue them in the background.",
|
||||
}),
|
||||
),
|
||||
HttpApiEndpoint.get("resource", ExperimentalPaths.resource, {
|
||||
query: WorkspaceRoutingQuery,
|
||||
success: described(Schema.Record(Schema.String, MCP.Resource), "MCP resources"),
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import { Account } from "@/account/account"
|
||||
import { Agent } from "@/agent/agent"
|
||||
import { BackgroundJob } from "@/background/job"
|
||||
import { Config } from "@/config/config"
|
||||
import { InstanceState } from "@/effect/instance-state"
|
||||
import { RuntimeFlags } from "@/effect/runtime-flags"
|
||||
import { MCP } from "@/mcp"
|
||||
import { Project } from "@/project/project"
|
||||
import { Session } from "@/session/session"
|
||||
import type { SessionID } from "@/session/schema"
|
||||
import { ToolJsonSchema } from "@/tool/json-schema"
|
||||
import { ToolRegistry } from "@/tool/registry"
|
||||
import { Worktree } from "@/worktree"
|
||||
@@ -30,6 +33,8 @@ export const experimentalHandlers = HttpApiBuilder.group(InstanceHttpApi, "exper
|
||||
const registry = yield* ToolRegistry.Service
|
||||
const worktreeSvc = yield* Worktree.Service
|
||||
const sessions = yield* Session.Service
|
||||
const background = yield* BackgroundJob.Service
|
||||
const flags = yield* RuntimeFlags.Service
|
||||
|
||||
const getConsole = Effect.fn("ExperimentalHttpApi.console")(function* () {
|
||||
const [state, groups] = yield* Effect.all(
|
||||
@@ -146,6 +151,21 @@ export const experimentalHandlers = HttpApiBuilder.group(InstanceHttpApi, "exper
|
||||
})
|
||||
})
|
||||
|
||||
const sessionBackground = Effect.fn("ExperimentalHttpApi.sessionBackground")(function* (ctx: {
|
||||
params: { sessionID: SessionID }
|
||||
}) {
|
||||
if (!flags.experimentalBackgroundSubagents) return false
|
||||
const jobs = (yield* background.list()).filter(
|
||||
(job) =>
|
||||
job.type === "task" &&
|
||||
job.status === "running" &&
|
||||
job.metadata?.parentSessionId === ctx.params.sessionID &&
|
||||
job.metadata.background !== true,
|
||||
)
|
||||
const promoted = yield* Effect.forEach(jobs, (job) => background.promote(job.id), { concurrency: "unbounded" })
|
||||
return promoted.some((job) => job !== undefined)
|
||||
})
|
||||
|
||||
const resource = Effect.fn("ExperimentalHttpApi.resource")(function* () {
|
||||
return yield* mcp.resources()
|
||||
})
|
||||
@@ -161,6 +181,7 @@ export const experimentalHandlers = HttpApiBuilder.group(InstanceHttpApi, "exper
|
||||
.handle("worktreeRemove", worktreeRemove)
|
||||
.handle("worktreeReset", worktreeReset)
|
||||
.handle("session", session)
|
||||
.handle("sessionBackground", sessionBackground)
|
||||
.handle("resource", resource)
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -13,6 +13,7 @@ import { FSUtil } from "@opencode-ai/core/fs-util"
|
||||
import { Account } from "@/account/account"
|
||||
import { Agent } from "@/agent/agent"
|
||||
import { Auth } from "@/auth"
|
||||
import { BackgroundJob } from "@/background/job"
|
||||
import { Config } from "@/config/config"
|
||||
import { Command } from "@/command"
|
||||
import * as Observability from "@opencode-ai/core/effect/observability"
|
||||
@@ -214,6 +215,7 @@ export function createRoutes(
|
||||
Account.defaultLayer,
|
||||
Agent.defaultLayer,
|
||||
Auth.defaultLayer,
|
||||
BackgroundJob.defaultLayer,
|
||||
Command.defaultLayer,
|
||||
Config.defaultLayer,
|
||||
Format.defaultLayer,
|
||||
|
||||
@@ -20,7 +20,9 @@ export function isLocalWorkspaceRoute(method: string, path: string) {
|
||||
export function getWorkspaceRouteSessionID(url: URL) {
|
||||
if (url.pathname === "/session/status") return null
|
||||
|
||||
const id = url.pathname.match(/^\/session\/([^/]+)(?:\/|$)/)?.[1]
|
||||
const id =
|
||||
url.pathname.match(/^\/session\/([^/]+)(?:\/|$)/)?.[1] ??
|
||||
url.pathname.match(/^\/experimental\/session\/([^/]+)\/background$/)?.[1]
|
||||
if (!id) return null
|
||||
|
||||
return SessionID.make(id)
|
||||
|
||||
Reference in New Issue
Block a user