From f5138b56405725826f8b9232c2a713f6d25eda97 Mon Sep 17 00:00:00 2001 From: tomjw64 Date: Wed, 11 Feb 2026 17:13:44 -0600 Subject: [PATCH 1/8] feat(opencode): Support background subagents --- packages/opencode/src/project/bootstrap.ts | 2 + packages/opencode/src/session/background.ts | 100 ++++++++++++++++++++ packages/opencode/src/session/index.ts | 20 ++++ packages/opencode/src/session/message-v2.ts | 1 + packages/opencode/src/session/prompt.ts | 1 + packages/opencode/src/tool/task.ts | 72 +++++++++++--- packages/opencode/src/tool/task.txt | 5 + 7 files changed, 188 insertions(+), 13 deletions(-) create mode 100644 packages/opencode/src/session/background.ts diff --git a/packages/opencode/src/project/bootstrap.ts b/packages/opencode/src/project/bootstrap.ts index a2be3733f85..d2ab56d00d3 100644 --- a/packages/opencode/src/project/bootstrap.ts +++ b/packages/opencode/src/project/bootstrap.ts @@ -12,6 +12,7 @@ import { Log } from "@/util/log" import { ShareNext } from "@/share/share-next" import { Snapshot } from "../snapshot" import { Truncate } from "../tool/truncation" +import { SessionBackground } from "../session/background" export async function InstanceBootstrap() { Log.Default.info("bootstrapping", { directory: Instance.directory }) @@ -24,6 +25,7 @@ export async function InstanceBootstrap() { Vcs.init() Snapshot.init() Truncate.init() + SessionBackground.init() Bus.subscribe(Command.Event.Executed, async (payload) => { if (payload.properties.name === Command.Default.INIT) { diff --git a/packages/opencode/src/session/background.ts b/packages/opencode/src/session/background.ts new file mode 100644 index 00000000000..fbb010126cb --- /dev/null +++ b/packages/opencode/src/session/background.ts @@ -0,0 +1,100 @@ +import { Bus } from "@/bus" +import { Session } from "./index" +import { SessionStatus } from "./status" +import { Instance } from "@/project/instance" +import { Log } from "@/util/log" +import { Identifier } from "@/id/id" +import { SessionPrompt } from "./prompt" + +export namespace SessionBackground { + const log = Log.create({ service: "session.background" }) + + const state = Instance.state( + () => { + const pending = new Map() + return { pending, initialized: false } + }, + async (current) => { + current.pending.clear() + current.initialized = false + }, + ) + + export function init() { + if (state().initialized) { + return + } + state().initialized = true + + Bus.subscribe(Session.Event.BackgroundTaskCompleted, (event) => { + const sessionID = event.properties.sessionID + const task = event.properties.task + const pending = state().pending + const tasks = pending.get(sessionID) ?? [] + + tasks.push(task) + pending.set(sessionID, tasks) + + const status = SessionStatus.get(sessionID) + if (status.type !== "idle") { + return + } + + flushQueue(sessionID).catch((err) => { + log.error("failed to process background tasks", { sessionID, error: err }) + }) + }) + + Bus.subscribe(SessionStatus.Event.Status, (event) => { + if (event.properties.status.type !== "idle") { + return + } + + const sessionID = event.properties.sessionID + + flushQueue(sessionID).catch((err) => { + log.error("failed to process background tasks", { sessionID, error: err }) + }) + }) + } + + async function flushQueue(sessionID: string) { + const s = state() + const tasks = s.pending.get(sessionID) + if (!tasks || tasks.length === 0) { + return + } + + s.pending.set(sessionID, []) + + for (const task of tasks) { + const msgID = Identifier.ascending("message") + + await Session.updateMessage({ + id: msgID, + sessionID, + role: "user", + time: { created: Date.now() }, + agent: task.agent, + model: task.model, + }) + + const output = [`Session ID: ${task.sessionID}`, "", "", task.result, ""].join("\n") + const text = + task.status === "success" + ? `Background task '${task.description}' completed.\n${output}` + : `Background task '${task.description}' failed: ${task.result}` + + await Session.updatePart({ + id: Identifier.ascending("part"), + messageID: msgID, + sessionID, + type: "text", + synthetic: true, + text, + }) + } + + await SessionPrompt.loop({ sessionID }) + } +} diff --git a/packages/opencode/src/session/index.ts b/packages/opencode/src/session/index.ts index b117632051f..7398c12df34 100644 --- a/packages/opencode/src/session/index.ts +++ b/packages/opencode/src/session/index.ts @@ -178,6 +178,19 @@ export namespace Session { }) export type GlobalInfo = z.output + export const BackgroundTask = z.object({ + sessionID: z.string(), + result: z.string(), + status: z.enum(["success", "error"]), + description: z.string(), + agent: z.string(), + model: z.object({ + providerID: z.string(), + modelID: z.string(), + }), + }) + export type BackgroundTask = z.output + export const Event = { Created: BusEvent.define( "session.created", @@ -211,6 +224,13 @@ export namespace Session { error: MessageV2.Assistant.shape.error, }), ), + BackgroundTaskCompleted: BusEvent.define( + "session.background_task_completed", + z.object({ + sessionID: z.string(), + task: BackgroundTask, + }), + ), } export const create = fn( diff --git a/packages/opencode/src/session/message-v2.ts b/packages/opencode/src/session/message-v2.ts index 5b4e7bdbc04..d4e41730808 100644 --- a/packages/opencode/src/session/message-v2.ts +++ b/packages/opencode/src/session/message-v2.ts @@ -218,6 +218,7 @@ export namespace MessageV2 { }) .optional(), command: z.string().optional(), + background: z.boolean().optional(), }).meta({ ref: "SubtaskPart", }) diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 4f77920cc98..06b4e181ae1 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -402,6 +402,7 @@ export namespace SessionPrompt { description: task.description, subagent_type: task.agent, command: task.command, + background: task.background, } await Plugin.trigger( "tool.execute.before", diff --git a/packages/opencode/src/tool/task.ts b/packages/opencode/src/tool/task.ts index 8c8cf827aba..616ed0b6b93 100644 --- a/packages/opencode/src/tool/task.ts +++ b/packages/opencode/src/tool/task.ts @@ -10,6 +10,7 @@ import { iife } from "@/util/iife" import { defer } from "@/util/defer" import { Config } from "../config/config" import { PermissionNext } from "@/permission/next" +import { Bus } from "@/bus" const parameters = z.object({ description: z.string().describe("A short (3-5 words) description of the task"), @@ -22,6 +23,13 @@ const parameters = z.object({ ) .optional(), command: z.string().describe("The command that triggered this task").optional(), + background: z + .boolean() + .default(false) + .describe( + "If true, the task runs in the background and the main agent can continue working. Results will be reported back when done.", + ) + .optional(), }) export const TaskTool = Tool.define("task", async (ctx) => { @@ -117,21 +125,11 @@ export const TaskTool = Tool.define("task", async (ctx) => { }) const messageID = Identifier.ascending("message") - - function cancel() { - SessionPrompt.cancel(session.id) - } - ctx.abort.addEventListener("abort", cancel) - using _ = defer(() => ctx.abort.removeEventListener("abort", cancel)) const promptParts = await SessionPrompt.resolvePromptParts(params.prompt) - - const result = await SessionPrompt.prompt({ + const promptInput = { messageID, sessionID: session.id, - model: { - modelID: model.modelID, - providerID: model.providerID, - }, + model, agent: agent.name, tools: { todowrite: false, @@ -140,7 +138,55 @@ export const TaskTool = Tool.define("task", async (ctx) => { ...Object.fromEntries((config.experimental?.primary_tools ?? []).map((t) => [t, false])), }, parts: promptParts, - }) + } + + if (params.background) { + iife(async () => { + const result = await SessionPrompt.prompt(promptInput) + const text = result.parts.findLast((x) => x.type === "text")?.text ?? "" + + Bus.publish(Session.Event.BackgroundTaskCompleted, { + sessionID: ctx.sessionID, + task: { + sessionID: session.id, + result: text, + status: "success", + description: params.description, + agent: agent.name, + model, + }, + }) + }).catch((err) => { + Bus.publish(Session.Event.BackgroundTaskCompleted, { + sessionID: ctx.sessionID, + task: { + sessionID: session.id, + result: String(err), + status: "error", + description: params.description, + agent: agent.name, + model, + }, + }) + }) + + return { + title: params.description, + metadata: { + sessionId: session.id, + model, + }, + output: `Background task started.\nSession ID: ${session.id}\n\nYou will be notified when it completes.`, + } + } + + function cancel() { + SessionPrompt.cancel(session.id) + } + ctx.abort.addEventListener("abort", cancel) + using _ = defer(() => ctx.abort.removeEventListener("abort", cancel)) + + const result = await SessionPrompt.prompt(promptInput) const text = result.parts.findLast((x) => x.type === "text")?.text ?? "" diff --git a/packages/opencode/src/tool/task.txt b/packages/opencode/src/tool/task.txt index 585cce8f9d0..73db6623514 100644 --- a/packages/opencode/src/tool/task.txt +++ b/packages/opencode/src/tool/task.txt @@ -23,6 +23,11 @@ Usage notes: 5. Clearly tell the agent whether you expect it to write code or just to do research (search, file reads, web fetches, etc.), since it is not aware of the user's intent. Tell it how to verify its work if possible (e.g., relevant test commands). 6. If the agent description mentions that it should be used proactively, then you should try your best to use it without the user having to ask for it first. Use your judgement. +Background tasks: +- You can set background=true to run a task asynchronously if it will take a long time, or if you are requested to do so. +- Background tasks wake you upon completion, so you should wait passively and idly for output of background tasks. +- It is forbidden to track completion of tasks yourself via sleeping, or to delegate that tracking to other subagents. + Example usage (NOTE: The agents below are fictional examples for illustration only - use the actual agents listed above): From 44c57a7ba2c7997fbe14cd9e46c6383467155f04 Mon Sep 17 00:00:00 2001 From: tomjw64 Date: Thu, 12 Feb 2026 16:56:01 -0600 Subject: [PATCH 2/8] Avoid race conditions on background task completion --- packages/opencode/src/session/background.ts | 110 +++++++++++--------- packages/opencode/src/session/status.ts | 3 +- 2 files changed, 59 insertions(+), 54 deletions(-) diff --git a/packages/opencode/src/session/background.ts b/packages/opencode/src/session/background.ts index fbb010126cb..0cdc84de726 100644 --- a/packages/opencode/src/session/background.ts +++ b/packages/opencode/src/session/background.ts @@ -11,11 +11,11 @@ export namespace SessionBackground { const state = Instance.state( () => { - const pending = new Map() - return { pending, initialized: false } + const staged = new Map() + return { staged, initialized: false } }, async (current) => { - current.pending.clear() + current.staged.clear() current.initialized = false }, ) @@ -26,75 +26,81 @@ export namespace SessionBackground { } state().initialized = true - Bus.subscribe(Session.Event.BackgroundTaskCompleted, (event) => { + Bus.subscribe(Session.Event.BackgroundTaskCompleted, async (event) => { const sessionID = event.properties.sessionID const task = event.properties.task - const pending = state().pending - const tasks = pending.get(sessionID) ?? [] - tasks.push(task) - pending.set(sessionID, tasks) - - const status = SessionStatus.get(sessionID) - if (status.type !== "idle") { - return - } - - flushQueue(sessionID).catch((err) => { - log.error("failed to process background tasks", { sessionID, error: err }) + await stageTask(sessionID, task).catch((err) => { + log.error("failed to stage background task", { sessionID, error: err }) + }) + await wake(sessionID).catch((err) => { + log.error("failed to wake for staged tasks", { sessionID, error: err }) }) }) - Bus.subscribe(SessionStatus.Event.Status, (event) => { - if (event.properties.status.type !== "idle") { - return - } - + Bus.subscribe(SessionStatus.Event.Status, async (event) => { const sessionID = event.properties.sessionID - flushQueue(sessionID).catch((err) => { - log.error("failed to process background tasks", { sessionID, error: err }) + await wake(sessionID).catch((err) => { + log.error("failed to wake for staged tasks", { sessionID, error: err }) }) }) } - async function flushQueue(sessionID: string) { + async function wake(sessionID: string) { + const session = await Session.get(sessionID) + if (!session) { + return + } + const s = state() - const tasks = s.pending.get(sessionID) + const tasks = s.staged.get(sessionID) if (!tasks || tasks.length === 0) { return } - s.pending.set(sessionID, []) - - for (const task of tasks) { - const msgID = Identifier.ascending("message") + const status = SessionStatus.get(sessionID) + if (status.type !== "idle") { + return + } - await Session.updateMessage({ - id: msgID, - sessionID, - role: "user", - time: { created: Date.now() }, - agent: task.agent, - model: task.model, - }) + s.staged.delete(sessionID) + SessionStatus.set(sessionID, { type: "busy" }) + await SessionPrompt.loop({ sessionID }) + } - const output = [`Session ID: ${task.sessionID}`, "", "", task.result, ""].join("\n") - const text = - task.status === "success" - ? `Background task '${task.description}' completed.\n${output}` - : `Background task '${task.description}' failed: ${task.result}` + async function stageTask(sessionID: string, task: Session.BackgroundTask) { + await updateTaskMessage(sessionID, task) - await Session.updatePart({ - id: Identifier.ascending("part"), - messageID: msgID, - sessionID, - type: "text", - synthetic: true, - text, - }) - } + const staged = state().staged + const tasks = staged.get(sessionID) ?? [] + staged.set(sessionID, tasks) + tasks.push(task) + } - await SessionPrompt.loop({ sessionID }) + async function updateTaskMessage(sessionID: string, task: Session.BackgroundTask) { + const msgID = Identifier.ascending("message") + const output = [`Session ID: ${task.sessionID}`, "", "", task.result, ""].join("\n") + const text = + task.status === "success" + ? `Background task '${task.description}' completed.\n${output}` + : `Background task '${task.description}' failed: ${task.result}` + + await Session.updateMessage({ + id: msgID, + sessionID, + role: "user", + time: { created: Date.now() }, + agent: task.agent, + model: task.model, + }) + await Session.updatePart({ + id: Identifier.ascending("part"), + messageID: msgID, + sessionID, + type: "text", + synthetic: true, + text, + }) } } diff --git a/packages/opencode/src/session/status.ts b/packages/opencode/src/session/status.ts index 1db03b5db0d..d5af2f8b18b 100644 --- a/packages/opencode/src/session/status.ts +++ b/packages/opencode/src/session/status.ts @@ -59,6 +59,7 @@ export namespace SessionStatus { } export function set(sessionID: string, status: Info) { + state()[sessionID] = status Bus.publish(Event.Status, { sessionID, status, @@ -69,8 +70,6 @@ export namespace SessionStatus { sessionID, }) delete state()[sessionID] - return } - state()[sessionID] = status } } From 17a6c93f995622bde20c43a65ff6e77c42547e22 Mon Sep 17 00:00:00 2001 From: tomjw64 Date: Thu, 12 Feb 2026 17:03:20 -0600 Subject: [PATCH 3/8] Handle deleted session gracefully --- packages/opencode/src/session/background.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/session/background.ts b/packages/opencode/src/session/background.ts index 0cdc84de726..825dbcba880 100644 --- a/packages/opencode/src/session/background.ts +++ b/packages/opencode/src/session/background.ts @@ -48,7 +48,10 @@ export namespace SessionBackground { } async function wake(sessionID: string) { - const session = await Session.get(sessionID) + const session = await Session.get(sessionID).catch(() => { + log.warn("session not found, skipping wake", { sessionID }) + return undefined + }) if (!session) { return } From 0ce47ee4e0f6654406934ebe95ee4f70af90b3f0 Mon Sep 17 00:00:00 2001 From: tomjw64 Date: Sun, 15 Feb 2026 16:50:46 -0600 Subject: [PATCH 4/8] Use managed state cleanup --- packages/opencode/src/session/background.ts | 66 ++++++++++----------- 1 file changed, 32 insertions(+), 34 deletions(-) diff --git a/packages/opencode/src/session/background.ts b/packages/opencode/src/session/background.ts index 825dbcba880..71dce697bf0 100644 --- a/packages/opencode/src/session/background.ts +++ b/packages/opencode/src/session/background.ts @@ -11,40 +11,38 @@ export namespace SessionBackground { const state = Instance.state( () => { - const staged = new Map() - return { staged, initialized: false } + const finished = new Map() + const unsubscribes = [ + Bus.subscribe(Session.Event.BackgroundTaskCompleted, async (event) => { + const sessionID = event.properties.sessionID + const task = event.properties.task + + await addFinishedTask(sessionID, task).catch((err) => { + log.error("failed to stage background task", { sessionID, error: err }) + }) + await wake(sessionID).catch((err) => { + log.error("failed to wake for finished tasks", { sessionID, error: err }) + }) + }), + Bus.subscribe(SessionStatus.Event.Status, async (event) => { + const sessionID = event.properties.sessionID + + await wake(sessionID).catch((err) => { + log.error("failed to wake for finished tasks", { sessionID, error: err }) + }) + }), + ] + return { finished, unsubscribes } }, async (current) => { - current.staged.clear() - current.initialized = false + for (const unsubscribe of current.unsubscribes) { + unsubscribe() + } }, ) export function init() { - if (state().initialized) { - return - } - state().initialized = true - - Bus.subscribe(Session.Event.BackgroundTaskCompleted, async (event) => { - const sessionID = event.properties.sessionID - const task = event.properties.task - - await stageTask(sessionID, task).catch((err) => { - log.error("failed to stage background task", { sessionID, error: err }) - }) - await wake(sessionID).catch((err) => { - log.error("failed to wake for staged tasks", { sessionID, error: err }) - }) - }) - - Bus.subscribe(SessionStatus.Event.Status, async (event) => { - const sessionID = event.properties.sessionID - - await wake(sessionID).catch((err) => { - log.error("failed to wake for staged tasks", { sessionID, error: err }) - }) - }) + return state() } async function wake(sessionID: string) { @@ -57,7 +55,7 @@ export namespace SessionBackground { } const s = state() - const tasks = s.staged.get(sessionID) + const tasks = s.finished.get(sessionID) if (!tasks || tasks.length === 0) { return } @@ -67,17 +65,17 @@ export namespace SessionBackground { return } - s.staged.delete(sessionID) + s.finished.delete(sessionID) SessionStatus.set(sessionID, { type: "busy" }) await SessionPrompt.loop({ sessionID }) } - async function stageTask(sessionID: string, task: Session.BackgroundTask) { + async function addFinishedTask(sessionID: string, task: Session.BackgroundTask) { await updateTaskMessage(sessionID, task) - const staged = state().staged - const tasks = staged.get(sessionID) ?? [] - staged.set(sessionID, tasks) + const finished = state().finished + const tasks = finished.get(sessionID) ?? [] + finished.set(sessionID, tasks) tasks.push(task) } From 3e790e5cc01aef03c13b0e117123dc51599d7f03 Mon Sep 17 00:00:00 2001 From: tomjw64 Date: Sun, 15 Feb 2026 17:05:45 -0600 Subject: [PATCH 5/8] Abort SessionPrompt tree on session deletion --- packages/opencode/src/session/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/opencode/src/session/index.ts b/packages/opencode/src/session/index.ts index 7398c12df34..6c6811893c8 100644 --- a/packages/opencode/src/session/index.ts +++ b/packages/opencode/src/session/index.ts @@ -681,6 +681,7 @@ export namespace Session { for (const child of await children(sessionID)) { await remove(child.id) } + SessionPrompt.cancel(sessionID) await unshare(sessionID).catch(() => {}) // CASCADE delete handles messages and parts automatically Database.use((db) => { From 32c121650b583768afff177873d92c401976b4a6 Mon Sep 17 00:00:00 2001 From: tomjw64 Date: Mon, 2 Mar 2026 08:21:14 -0600 Subject: [PATCH 6/8] Normalize undefined return --- packages/opencode/src/session/background.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/opencode/src/session/background.ts b/packages/opencode/src/session/background.ts index 71dce697bf0..af54e397cee 100644 --- a/packages/opencode/src/session/background.ts +++ b/packages/opencode/src/session/background.ts @@ -48,7 +48,7 @@ export namespace SessionBackground { async function wake(sessionID: string) { const session = await Session.get(sessionID).catch(() => { log.warn("session not found, skipping wake", { sessionID }) - return undefined + return }) if (!session) { return From 28c6b7a7921372ec6974af38463b27a31c1a9266 Mon Sep 17 00:00:00 2001 From: tomjw64 Date: Mon, 2 Mar 2026 08:49:30 -0600 Subject: [PATCH 7/8] Simplify bg task session waking --- packages/opencode/src/session/background.ts | 24 +++++++-------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/packages/opencode/src/session/background.ts b/packages/opencode/src/session/background.ts index af54e397cee..201fa70bb5e 100644 --- a/packages/opencode/src/session/background.ts +++ b/packages/opencode/src/session/background.ts @@ -11,14 +11,16 @@ export namespace SessionBackground { const state = Instance.state( () => { - const finished = new Map() + const wakeable = new Set() const unsubscribes = [ Bus.subscribe(Session.Event.BackgroundTaskCompleted, async (event) => { const sessionID = event.properties.sessionID const task = event.properties.task - await addFinishedTask(sessionID, task).catch((err) => { - log.error("failed to stage background task", { sessionID, error: err }) + await updateTaskMessage(sessionID, task).then(() => { + state().wakeable.add(sessionID) + }).catch((err) => { + log.error("failed to update background task session message", { sessionID, error: err }) }) await wake(sessionID).catch((err) => { log.error("failed to wake for finished tasks", { sessionID, error: err }) @@ -32,7 +34,7 @@ export namespace SessionBackground { }) }), ] - return { finished, unsubscribes } + return { wakeable, unsubscribes } }, async (current) => { for (const unsubscribe of current.unsubscribes) { @@ -55,8 +57,7 @@ export namespace SessionBackground { } const s = state() - const tasks = s.finished.get(sessionID) - if (!tasks || tasks.length === 0) { + if (!s.wakeable.has(sessionID)) { return } @@ -65,20 +66,11 @@ export namespace SessionBackground { return } - s.finished.delete(sessionID) + s.wakeable.delete(sessionID) SessionStatus.set(sessionID, { type: "busy" }) await SessionPrompt.loop({ sessionID }) } - async function addFinishedTask(sessionID: string, task: Session.BackgroundTask) { - await updateTaskMessage(sessionID, task) - - const finished = state().finished - const tasks = finished.get(sessionID) ?? [] - finished.set(sessionID, tasks) - tasks.push(task) - } - async function updateTaskMessage(sessionID: string, task: Session.BackgroundTask) { const msgID = Identifier.ascending("message") const output = [`Session ID: ${task.sessionID}`, "", "", task.result, ""].join("\n") From a55ba8e91f428c25754eae7b8c51fcaa0cd122c6 Mon Sep 17 00:00:00 2001 From: tomjw64 Date: Sat, 7 Mar 2026 22:29:38 -0600 Subject: [PATCH 8/8] Commit generated types --- packages/sdk/js/src/v2/gen/types.gen.ts | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index 71e075b3916..5d58b377eb5 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -290,6 +290,7 @@ export type SubtaskPart = { modelID: string } command?: string + background?: boolean } export type ReasoningPart = { @@ -882,6 +883,24 @@ export type EventSessionError = { } } +export type EventSessionBackgroundTaskCompleted = { + type: "session.background_task_completed" + properties: { + sessionID: string + task: { + sessionID: string + result: string + status: "success" | "error" + description: string + agent: string + model: { + providerID: string + modelID: string + } + } + } +} + export type EventVcsBranchUpdated = { type: "vcs.branch.updated" properties: { @@ -994,6 +1013,7 @@ export type Event = | EventSessionDeleted | EventSessionDiff | EventSessionError + | EventSessionBackgroundTaskCompleted | EventVcsBranchUpdated | EventWorkspaceReady | EventWorkspaceFailed @@ -1757,6 +1777,7 @@ export type SubtaskPartInput = { modelID: string } command?: string + background?: boolean } export type ProviderAuthMethod = {