Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/core/src/flag/flag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ export const Flag = {
OPENCODE_EXPERIMENTAL_LSP_TOOL: OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_LSP_TOOL"),
OPENCODE_EXPERIMENTAL_PLAN_MODE: OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_PLAN_MODE"),
OPENCODE_EXPERIMENTAL_SCOUT: OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_SCOUT"),
OPENCODE_EXPERIMENTAL_BACKGROUND_AGENTS:
OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_BACKGROUND_AGENTS"),
OPENCODE_EXPERIMENTAL_MARKDOWN: !falsy("OPENCODE_EXPERIMENTAL_MARKDOWN"),
OPENCODE_ENABLE_PARALLEL: truthy("OPENCODE_ENABLE_PARALLEL") || truthy("OPENCODE_EXPERIMENTAL_PARALLEL"),
OPENCODE_MODELS_URL: process.env["OPENCODE_MODELS_URL"],
Expand Down
10 changes: 8 additions & 2 deletions packages/opencode/src/cli/cmd/tui/routes/session/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2028,7 +2028,9 @@ function Task(props: ToolProps<typeof TaskTool>) {

const content = createMemo(() => {
if (!props.input.description) return ""
let content = [`${Locale.titlecase(props.input.subagent_type ?? "General")} Task — ${props.input.description}`]
const description =
props.metadata.background === true ? `${props.input.description} (background)` : props.input.description
let content = [`${Locale.titlecase(props.input.subagent_type ?? "General")} Task — ${description}`]

if (isRunning() && tools().length > 0) {
// content[0] += ` · ${tools().length} toolcalls`
Expand All @@ -2040,7 +2042,11 @@ function Task(props: ToolProps<typeof TaskTool>) {
}

if (props.part.state.status === "completed") {
content.push(`└ ${tools().length} toolcalls · ${Locale.duration(duration())}`)
content.push(
props.metadata.background === true
? `└ ${tools().length} toolcalls`
: `└ ${tools().length} toolcalls · ${Locale.duration(duration())}`,
)
}

return content.join("\n")
Expand Down
1 change: 1 addition & 0 deletions packages/opencode/src/session/prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ export const layer = Layer.effect(
cancel: (sessionID: SessionID) => cancel(sessionID),
resolvePromptParts: (template: string) => resolvePromptParts(template),
prompt: (input: PromptInput) => prompt(input),
loop: (input: LoopInput) => loop(input),
} satisfies TaskPromptOps
})

Expand Down
39 changes: 38 additions & 1 deletion packages/opencode/src/session/run-state.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { InstanceState } from "@/effect/instance-state"
import { Runner } from "@/effect/runner"
import { BackgroundJob } from "@/background/job"
import { Effect, Latch, Layer, Scope, Context } from "effect"
import * as Session from "./session"
import { MessageV2 } from "./message-v2"
Expand Down Expand Up @@ -27,6 +28,7 @@ export class Service extends Context.Service<Service, Interface>()("@opencode/Se
export const layer = Layer.effect(
Service,
Effect.gen(function* () {
const background = yield* BackgroundJob.Service
const status = yield* SessionStatus.Service

const state = yield* InstanceState.make(
Expand Down Expand Up @@ -75,6 +77,7 @@ export const layer = Layer.effect(
})

const cancel = Effect.fn("SessionRunState.cancel")(function* (sessionID: SessionID) {
yield* cancelBackgroundJobs(background, sessionID)
const data = yield* InstanceState.get(state)
const existing = data.runners.get(sessionID)
if (!existing || !existing.busy) {
Expand Down Expand Up @@ -105,6 +108,40 @@ export const layer = Layer.effect(
}),
)

export const defaultLayer = layer.pipe(Layer.provide(SessionStatus.defaultLayer))
export const defaultLayer = layer.pipe(Layer.provide(BackgroundJob.defaultLayer), Layer.provide(SessionStatus.defaultLayer))

const cancelBackgroundJobs = Effect.fn("SessionRunState.cancelBackgroundJobs")(function* (
background: BackgroundJob.Interface,
sessionID: SessionID,
) {
const jobs = yield* background.list()
const pending = new Set<string>([sessionID])
const cancelled = new Set<string>()
const matches = (job: BackgroundJob.Info) => {
if (job.status !== "running") return false
if (cancelled.has(job.id)) return false
if (pending.has(job.id)) return true
if (typeof job.metadata?.sessionId === "string" && pending.has(job.metadata.sessionId)) return true
return typeof job.metadata?.parentSessionId === "string" && pending.has(job.metadata.parentSessionId)
}
let batch = jobs.filter(matches)
while (batch.length > 0) {
yield* Effect.forEach(
batch,
(job) =>
background.cancel(job.id).pipe(
Effect.tap(() =>
Effect.sync(() => {
cancelled.add(job.id)
pending.add(job.id)
if (typeof job.metadata?.sessionId === "string") pending.add(job.metadata.sessionId)
}),
),
),
{ concurrency: "unbounded", discard: true },
)
batch = jobs.filter(matches)
}
})

export * as SessionRunState from "./run-state"
23 changes: 22 additions & 1 deletion packages/opencode/src/session/session.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Slug } from "@opencode-ai/core/util/slug"
import path from "path"
import { BackgroundJob } from "@/background/job"
import { BusEvent } from "@/bus/bus-event"
import { Bus } from "@/bus"
import { Decimal } from "decimal.js"
Expand Down Expand Up @@ -503,9 +504,10 @@ export type Patch = Types.DeepMutable<SyncEvent.Event<typeof Event.Updated>["dat
const db = <T>(fn: (d: Parameters<typeof Database.use>[0] extends (trx: infer D) => any ? D : never) => T) =>
Effect.sync(() => Database.use(fn))

export const layer: Layer.Layer<Service, never, Bus.Service | Storage.Service | SyncEvent.Service> = Layer.effect(
export const layer: Layer.Layer<Service, never, BackgroundJob.Service | Bus.Service | Storage.Service | SyncEvent.Service> = Layer.effect(
Service,
Effect.gen(function* () {
const background = yield* BackgroundJob.Service
const bus = yield* Bus.Service
const storage = yield* Storage.Service
const sync = yield* SyncEvent.Service
Expand Down Expand Up @@ -583,6 +585,7 @@ export const layer: Layer.Layer<Service, never, Bus.Service | Storage.Service |
const remove: Interface["remove"] = Effect.fnUntraced(function* (sessionID: SessionID) {
const session = yield* get(sessionID)
try {
yield* cancelBackgroundJobs(background, sessionID)
const kids = yield* children(sessionID)
for (const child of kids) {
yield* remove(child.id)
Expand Down Expand Up @@ -833,11 +836,29 @@ export const layer: Layer.Layer<Service, never, Bus.Service | Storage.Service |
)

export const defaultLayer = layer.pipe(
Layer.provide(BackgroundJob.defaultLayer),
Layer.provide(Bus.layer),
Layer.provide(Storage.defaultLayer),
Layer.provide(SyncEvent.defaultLayer),
)

const cancelBackgroundJobs = Effect.fn("Session.cancelBackgroundJobs")(function* (
background: BackgroundJob.Interface,
sessionID: SessionID,
) {
const jobs = yield* background.list()
yield* Effect.forEach(
jobs.filter((job) => {
if (job.status !== "running") return false
if (job.id === sessionID) return true
if (job.metadata?.sessionId === sessionID) return true
return job.metadata?.parentSessionId === sessionID
}),
(job) => background.cancel(job.id),
{ concurrency: "unbounded", discard: true },
)
})

function* listByProject(
input: ListInput & {
projectID: ProjectID
Expand Down
9 changes: 9 additions & 0 deletions packages/opencode/src/tool/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { GlobTool } from "./glob"
import { GrepTool } from "./grep"
import { ReadTool } from "./read"
import { TaskTool } from "./task"
import { TaskStatusTool } from "./task_status"
import { TodoWriteTool } from "./todo"
import { WebFetchTool } from "./webfetch"
import { WriteTool } from "./write"
Expand Down Expand Up @@ -50,6 +51,8 @@ import { Git } from "@/git"
import { Skill } from "../skill"
import { Permission } from "@/permission"
import { Reference } from "@/reference/reference"
import { BackgroundJob } from "@/background/job"
import { SessionStatus } from "@/session/status"

const log = Log.create({ service: "tool.registry" })

Expand Down Expand Up @@ -89,6 +92,8 @@ export const layer: Layer.Layer<
| Agent.Service
| Skill.Service
| Session.Service
| SessionStatus.Service
| BackgroundJob.Service
| Provider.Service
| Git.Service
| Reference.Service
Expand All @@ -112,6 +117,7 @@ export const layer: Layer.Layer<

const invalid = yield* InvalidTool
const task = yield* TaskTool
const taskStatus = yield* TaskStatusTool
const read = yield* ReadTool
const question = yield* QuestionTool
const todo = yield* TodoWriteTool
Expand Down Expand Up @@ -219,6 +225,7 @@ export const layer: Layer.Layer<
edit: Tool.init(edit),
write: Tool.init(writetool),
task: Tool.init(task),
task_status: Tool.init(taskStatus),
fetch: Tool.init(webfetch),
todo: Tool.init(todo),
search: Tool.init(websearch),
Expand All @@ -243,6 +250,7 @@ export const layer: Layer.Layer<
tool.edit,
tool.write,
tool.task,
...(Flag.OPENCODE_EXPERIMENTAL_BACKGROUND_AGENTS ? [tool.task_status] : []),
tool.fetch,
tool.todo,
tool.search,
Expand Down Expand Up @@ -366,6 +374,7 @@ export const defaultLayer = Layer.suspend(() =>
Layer.provide(Skill.defaultLayer),
Layer.provide(Agent.defaultLayer),
Layer.provide(Session.defaultLayer),
Layer.provide(Layer.mergeAll(SessionStatus.defaultLayer, BackgroundJob.defaultLayer)),
Layer.provide(Provider.defaultLayer),
Layer.provide(Git.defaultLayer),
Layer.provide(Reference.defaultLayer),
Expand Down
Loading
Loading