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
1 change: 1 addition & 0 deletions packages/core/src/flag/flag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ export const Flag = {
OPENCODE_SKIP_MIGRATIONS: truthy("OPENCODE_SKIP_MIGRATIONS"),
OPENCODE_STRICT_CONFIG_DEPS: truthy("OPENCODE_STRICT_CONFIG_DEPS"),

OPENCODE_DISABLE_NOTIFICATIONS: truthy("OPENCODE_DISABLE_NOTIFICATIONS"),
OPENCODE_WORKSPACE_ID: process.env["OPENCODE_WORKSPACE_ID"],
OPENCODE_EXPERIMENTAL_WORKSPACES: OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_WORKSPACES"),
OPENCODE_EXPERIMENTAL_EVENT_SYSTEM: OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_EVENT_SYSTEM"),
Expand Down
21 changes: 20 additions & 1 deletion packages/opencode/src/cli/cmd/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { Flag } from "@opencode-ai/core/flag/flag"
import { ServerAuth } from "@/server/auth"
import { EOL } from "os"
import { Filesystem } from "@/util/filesystem"
import { Notify } from "@/cli/notify"
import { createOpencodeClient, type OpencodeClient, type ToolPart } from "@opencode-ai/sdk/v2"
import { Agent } from "@/agent/agent"
import { Permission } from "@/permission"
Expand Down Expand Up @@ -601,6 +602,9 @@ export const RunCommand = effectCmd({
async function loop(client: OpencodeClient, events: Awaited<ReturnType<typeof sdk.event.subscribe>>) {
const toggles = new Map<string, boolean>()
let error: string | undefined
let lastOutput = ""
let agent = ""
let model = ""

for await (const event of events.stream) {
if (
Expand All @@ -610,8 +614,10 @@ export const RunCommand = effectCmd({
args.format !== "json" &&
toggles.get("start") !== true
) {
agent = event.properties.info.agent ?? ""
model = event.properties.info.modelID ?? ""
UI.empty()
UI.println(`> ${event.properties.info.agent} · ${event.properties.info.modelID}`)
UI.println(`> ${agent} · ${model}`)
UI.empty()
toggles.set("start", true)
}
Expand Down Expand Up @@ -653,6 +659,7 @@ export const RunCommand = effectCmd({
if (emit("text", { part })) continue
const text = part.text.trim()
if (!text) continue
lastOutput = text
if (!process.stdout.isTTY) {
process.stdout.write(text + EOL)
continue
Expand Down Expand Up @@ -694,6 +701,18 @@ export const RunCommand = effectCmd({
event.properties.sessionID === sessionID &&
event.properties.status.type === "idle"
) {
const prompt = message.slice(0, 100)
if (error) {
Notify.notify(prompt, `✗ ${error.slice(0, 200)}`)
} else if (lastOutput) {
const lines = lastOutput.split("\n").filter(Boolean).slice(0, 3)
const via = agent || model ? `${agent}${model ? ` (${model})` : ""}\n` : ""
const snippet = lines.join("\n").slice(0, 200)
Notify.notify(prompt, `${via}${snippet}`)
} else {
const via = agent || model ? `${agent}${model ? ` (${model})` : ""}` : ""
Notify.notify(prompt, via || "✓ done")
}
break
}

Expand Down
8 changes: 8 additions & 0 deletions packages/opencode/src/cli/cmd/tui/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ import { PromptRefProvider, usePromptRef } from "./context/prompt"
import { TuiConfigProvider, useTuiConfig } from "./context/tui-config"
import { TuiConfig } from "@/cli/cmd/tui/config/tui"
import { TuiPluginRuntime } from "@/cli/cmd/tui/plugin/runtime"
import { Notify } from "@/cli/notify"
import { createTuiApi } from "@/cli/cmd/tui/plugin/api"
import type { RouteMap } from "@/cli/cmd/tui/plugin/api"
import { FormatError, FormatUnknownError } from "@/cli/error"
Expand Down Expand Up @@ -869,11 +870,18 @@ function App(props: { onSnapshot?: () => Promise<string[]> }) {
}
})

event.on("session.idle", (evt) => {
const session = sync.session.get(evt.properties.sessionID)
Notify.notify("OpenCode", session?.title ?? "Task complete")
})

event.on("session.error", (evt) => {
const error = evt.properties.error
if (error && typeof error === "object" && error.name === "MessageAbortedError") return
const message = errorMessage(error)

Notify.notifyError("OpenCode Error", message)

toast.show({
variant: "error",
message,
Expand Down
20 changes: 20 additions & 0 deletions packages/opencode/src/cli/notify.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import * as Process from "@/util/process"
import { Flag } from "@opencode-ai/core/flag/flag"

const noop = () => {}

export const notify = Flag.OPENCODE_DISABLE_NOTIFICATIONS
? noop
: (title: string, message?: string) => {
const { platform } = process
if (platform === "linux") {
Process.spawn(["notify-send", title, message ?? ""])
} else if (platform === "darwin") {
const msg = (message ?? "").replace(/"/g, '\\"')
Process.spawn(["osascript", "-e", `display notification "${msg}" with title "${title}"`])
}
}

export const notifyError = notify

export * as Notify from "./notify"
Loading