138 lines
4.2 KiB
TypeScript
138 lines
4.2 KiB
TypeScript
/**
|
|
* Detects external plugins that may conflict with oh-my-opencode features.
|
|
* Used to prevent crashes from concurrent notification plugins.
|
|
*/
|
|
|
|
import * as fs from "node:fs"
|
|
import * as path from "node:path"
|
|
import * as os from "node:os"
|
|
import { log } from "./logger"
|
|
import { parseJsoncSafe } from "./jsonc-parser"
|
|
|
|
interface OpencodeConfig {
|
|
plugin?: string[]
|
|
}
|
|
|
|
/**
|
|
* Known notification plugins that conflict with oh-my-opencode's session-notification.
|
|
* Both plugins listen to session.idle and send notifications simultaneously,
|
|
* which can cause crashes on Windows due to resource contention.
|
|
*/
|
|
const KNOWN_NOTIFICATION_PLUGINS = [
|
|
"opencode-notifier",
|
|
"@mohak34/opencode-notifier",
|
|
"mohak34/opencode-notifier",
|
|
]
|
|
|
|
function getWindowsAppdataDir(): string | null {
|
|
return process.env.APPDATA || null
|
|
}
|
|
|
|
function getConfigPaths(directory: string): string[] {
|
|
const crossPlatformDir = path.join(os.homedir(), ".config")
|
|
const paths = [
|
|
path.join(directory, ".opencode", "opencode.json"),
|
|
path.join(directory, ".opencode", "opencode.jsonc"),
|
|
path.join(crossPlatformDir, "opencode", "opencode.json"),
|
|
path.join(crossPlatformDir, "opencode", "opencode.jsonc"),
|
|
]
|
|
|
|
if (process.platform === "win32") {
|
|
const appdataDir = getWindowsAppdataDir()
|
|
if (appdataDir) {
|
|
paths.push(path.join(appdataDir, "opencode", "opencode.json"))
|
|
paths.push(path.join(appdataDir, "opencode", "opencode.jsonc"))
|
|
}
|
|
}
|
|
|
|
return paths
|
|
}
|
|
|
|
function loadOpencodePlugins(directory: string): string[] {
|
|
for (const configPath of getConfigPaths(directory)) {
|
|
try {
|
|
if (!fs.existsSync(configPath)) continue
|
|
const content = fs.readFileSync(configPath, "utf-8")
|
|
const result = parseJsoncSafe<OpencodeConfig>(content)
|
|
if (result.data) {
|
|
return result.data.plugin ?? []
|
|
}
|
|
} catch {
|
|
continue
|
|
}
|
|
}
|
|
return []
|
|
}
|
|
|
|
/**
|
|
* Check if a plugin entry matches a known notification plugin.
|
|
* Handles various formats: "name", "name@version", "npm:name", "file://path/name"
|
|
*/
|
|
function matchesNotificationPlugin(entry: string): string | null {
|
|
const normalized = entry.toLowerCase()
|
|
for (const known of KNOWN_NOTIFICATION_PLUGINS) {
|
|
// Exact match
|
|
if (normalized === known) return known
|
|
// Version suffix: "opencode-notifier@1.2.3"
|
|
if (normalized.startsWith(`${known}@`)) return known
|
|
// Scoped package: "@mohak34/opencode-notifier" or "@mohak34/opencode-notifier@1.2.3"
|
|
if (normalized === `@mohak34/${known}` || normalized.startsWith(`@mohak34/${known}@`)) return known
|
|
// npm: prefix
|
|
if (normalized === `npm:${known}` || normalized.startsWith(`npm:${known}@`)) return known
|
|
// file:// path ending exactly with package name
|
|
if (normalized.startsWith("file://") && (
|
|
normalized.endsWith(`/${known}`) ||
|
|
normalized.endsWith(`\\${known}`)
|
|
)) return known
|
|
}
|
|
return null
|
|
}
|
|
|
|
export interface ExternalNotifierResult {
|
|
detected: boolean
|
|
pluginName: string | null
|
|
allPlugins: string[]
|
|
}
|
|
|
|
/**
|
|
* Detect if any external notification plugin is configured.
|
|
* Returns information about detected plugins for logging/warning.
|
|
*/
|
|
export function detectExternalNotificationPlugin(directory: string): ExternalNotifierResult {
|
|
const plugins = loadOpencodePlugins(directory)
|
|
|
|
for (const plugin of plugins) {
|
|
const match = matchesNotificationPlugin(plugin)
|
|
if (match) {
|
|
log(`Detected external notification plugin: ${plugin}`)
|
|
return {
|
|
detected: true,
|
|
pluginName: match,
|
|
allPlugins: plugins,
|
|
}
|
|
}
|
|
}
|
|
|
|
return {
|
|
detected: false,
|
|
pluginName: null,
|
|
allPlugins: plugins,
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Generate a warning message for users with conflicting notification plugins.
|
|
*/
|
|
export function getNotificationConflictWarning(pluginName: string): string {
|
|
return `[oh-my-opencode] External notification plugin detected: ${pluginName}
|
|
|
|
Both oh-my-opencode and ${pluginName} listen to session.idle events.
|
|
Running both simultaneously can cause crashes on Windows.
|
|
|
|
oh-my-opencode's session-notification has been auto-disabled.
|
|
|
|
To use oh-my-opencode's notifications instead, either:
|
|
1. Remove ${pluginName} from your opencode.json plugins
|
|
2. Or set "notification": { "force_enable": true } in oh-my-opencode.json`
|
|
}
|