fix(session-notification): defer platform detection and background checks
This commit is contained in:
@@ -0,0 +1,31 @@
|
|||||||
|
import type { Platform } from "./session-notification-sender"
|
||||||
|
import * as sessionNotificationSender from "./session-notification-sender"
|
||||||
|
import { startBackgroundCheck } from "./session-notification-utils"
|
||||||
|
|
||||||
|
export function createSessionNotificationInit() {
|
||||||
|
let platform: Platform | null = null
|
||||||
|
let defaultSoundPath: string | null = null
|
||||||
|
let started = false
|
||||||
|
|
||||||
|
function initialize(): { platform: Platform; defaultSoundPath: string } {
|
||||||
|
if (!platform) {
|
||||||
|
platform = sessionNotificationSender.detectPlatform()
|
||||||
|
}
|
||||||
|
if (!defaultSoundPath) {
|
||||||
|
defaultSoundPath = sessionNotificationSender.getDefaultSoundPath(platform)
|
||||||
|
}
|
||||||
|
if (!started) {
|
||||||
|
startBackgroundCheck(platform)
|
||||||
|
started = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
platform,
|
||||||
|
defaultSoundPath,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
initialize,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,20 +1,12 @@
|
|||||||
import type { PluginInput } from "@opencode-ai/plugin"
|
import type { PluginInput } from "@opencode-ai/plugin"
|
||||||
import { subagentSessions, getMainSessionID } from "../features/claude-code-session-state"
|
import { subagentSessions, getMainSessionID } from "../features/claude-code-session-state"
|
||||||
import {
|
|
||||||
startBackgroundCheck,
|
|
||||||
} from "./session-notification-utils"
|
|
||||||
import { buildReadyNotificationContent } from "./session-notification-content"
|
import { buildReadyNotificationContent } from "./session-notification-content"
|
||||||
import {
|
import { type Platform } from "./session-notification-sender"
|
||||||
type Platform,
|
|
||||||
} from "./session-notification-sender"
|
|
||||||
import * as sessionNotificationSender from "./session-notification-sender"
|
import * as sessionNotificationSender from "./session-notification-sender"
|
||||||
import {
|
import { getEventToolName, getQuestionText, getSessionID } from "./session-notification-event-properties"
|
||||||
getEventToolName,
|
|
||||||
getQuestionText,
|
|
||||||
getSessionID,
|
|
||||||
} from "./session-notification-event-properties"
|
|
||||||
import { hasIncompleteTodos } from "./session-todo-status"
|
import { hasIncompleteTodos } from "./session-todo-status"
|
||||||
import { createIdleNotificationScheduler } from "./session-notification-scheduler"
|
import { createIdleNotificationScheduler } from "./session-notification-scheduler"
|
||||||
|
import { createSessionNotificationInit } from "./session-notification-init"
|
||||||
|
|
||||||
interface SessionNotificationConfig {
|
interface SessionNotificationConfig {
|
||||||
title?: string
|
title?: string
|
||||||
@@ -33,22 +25,15 @@ interface SessionNotificationConfig {
|
|||||||
/** Grace period in ms to ignore late-arriving activity events after scheduling (default: 100) */
|
/** Grace period in ms to ignore late-arriving activity events after scheduling (default: 100) */
|
||||||
activityGracePeriodMs?: number
|
activityGracePeriodMs?: number
|
||||||
}
|
}
|
||||||
export function createSessionNotification(
|
|
||||||
ctx: PluginInput,
|
|
||||||
config: SessionNotificationConfig = {}
|
|
||||||
) {
|
|
||||||
const currentPlatform: Platform = sessionNotificationSender.detectPlatform()
|
|
||||||
const defaultSoundPath = sessionNotificationSender.getDefaultSoundPath(currentPlatform)
|
|
||||||
|
|
||||||
startBackgroundCheck(currentPlatform)
|
|
||||||
|
|
||||||
|
export function createSessionNotification(ctx: PluginInput, config: SessionNotificationConfig = {}) {
|
||||||
const mergedConfig = {
|
const mergedConfig = {
|
||||||
title: "OpenCode",
|
title: "OpenCode",
|
||||||
message: "Agent is ready for input",
|
message: "Agent is ready for input",
|
||||||
questionMessage: "Agent is asking a question",
|
questionMessage: "Agent is asking a question",
|
||||||
permissionMessage: "Agent needs permission to continue",
|
permissionMessage: "Agent needs permission to continue",
|
||||||
playSound: false,
|
playSound: false,
|
||||||
soundPath: defaultSoundPath,
|
soundPath: "",
|
||||||
idleConfirmationDelay: 1500,
|
idleConfirmationDelay: 1500,
|
||||||
skipIfIncompleteTodos: true,
|
skipIfIncompleteTodos: true,
|
||||||
maxTrackedSessions: 100,
|
maxTrackedSessions: 100,
|
||||||
@@ -56,22 +41,18 @@ export function createSessionNotification(
|
|||||||
...config,
|
...config,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const sessionNotificationInit = createSessionNotificationInit()
|
||||||
|
let currentPlatform: Platform | null = null
|
||||||
|
let defaultSoundPath = mergedConfig.soundPath
|
||||||
|
|
||||||
const scheduler = createIdleNotificationScheduler({
|
const scheduler = createIdleNotificationScheduler({
|
||||||
ctx,
|
ctx,
|
||||||
platform: currentPlatform,
|
platform: "unsupported",
|
||||||
config: mergedConfig,
|
config: mergedConfig,
|
||||||
hasIncompleteTodos,
|
hasIncompleteTodos,
|
||||||
send: async (hookCtx, platform, sessionID) => {
|
send: async (hookCtx, platform, sessionID) => {
|
||||||
if (
|
if (typeof hookCtx.client.session.get !== "function" && typeof hookCtx.client.session.messages !== "function") {
|
||||||
typeof hookCtx.client.session.get !== "function"
|
await sessionNotificationSender.sendSessionNotification(hookCtx, platform, mergedConfig.title, mergedConfig.message)
|
||||||
&& typeof hookCtx.client.session.messages !== "function"
|
|
||||||
) {
|
|
||||||
await sessionNotificationSender.sendSessionNotification(
|
|
||||||
hookCtx,
|
|
||||||
platform,
|
|
||||||
mergedConfig.title,
|
|
||||||
mergedConfig.message,
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,6 +71,15 @@ export function createSessionNotification(
|
|||||||
const PERMISSION_EVENTS = new Set(["permission.ask", "permission.asked", "permission.updated", "permission.requested"])
|
const PERMISSION_EVENTS = new Set(["permission.ask", "permission.asked", "permission.updated", "permission.requested"])
|
||||||
const PERMISSION_HINT_PATTERN = /\b(permission|approve|approval|allow|deny|consent)\b/i
|
const PERMISSION_HINT_PATTERN = /\b(permission|approve|approval|allow|deny|consent)\b/i
|
||||||
|
|
||||||
|
const ensureNotificationPlatform = (): Platform => {
|
||||||
|
if (currentPlatform) return currentPlatform
|
||||||
|
|
||||||
|
const initialized = sessionNotificationInit.initialize()
|
||||||
|
currentPlatform = initialized.platform
|
||||||
|
defaultSoundPath = initialized.defaultSoundPath || mergedConfig.soundPath
|
||||||
|
return currentPlatform
|
||||||
|
}
|
||||||
|
|
||||||
const shouldNotifyForSession = (sessionID: string): boolean => {
|
const shouldNotifyForSession = (sessionID: string): boolean => {
|
||||||
if (subagentSessions.has(sessionID)) return false
|
if (subagentSessions.has(sessionID)) return false
|
||||||
|
|
||||||
@@ -102,16 +92,12 @@ export function createSessionNotification(
|
|||||||
}
|
}
|
||||||
|
|
||||||
return async ({ event }: { event: { type: string; properties?: unknown } }) => {
|
return async ({ event }: { event: { type: string; properties?: unknown } }) => {
|
||||||
if (currentPlatform === "unsupported") return
|
|
||||||
|
|
||||||
const props = event.properties as Record<string, unknown> | undefined
|
const props = event.properties as Record<string, unknown> | undefined
|
||||||
|
|
||||||
if (event.type === "session.created") {
|
if (event.type === "session.created") {
|
||||||
const info = props?.info as Record<string, unknown> | undefined
|
const info = props?.info as Record<string, unknown> | undefined
|
||||||
const sessionID = info?.id as string | undefined
|
const sessionID = info?.id as string | undefined
|
||||||
if (sessionID) {
|
if (sessionID) scheduler.markSessionActivity(sessionID)
|
||||||
scheduler.markSessionActivity(sessionID)
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,6 +105,8 @@ export function createSessionNotification(
|
|||||||
const sessionID = getSessionID(props)
|
const sessionID = getSessionID(props)
|
||||||
if (!sessionID) return
|
if (!sessionID) return
|
||||||
|
|
||||||
|
const platform = ensureNotificationPlatform()
|
||||||
|
if (platform === "unsupported") return
|
||||||
if (!shouldNotifyForSession(sessionID)) return
|
if (!shouldNotifyForSession(sessionID)) return
|
||||||
|
|
||||||
scheduler.scheduleIdleNotification(sessionID)
|
scheduler.scheduleIdleNotification(sessionID)
|
||||||
@@ -128,26 +116,22 @@ export function createSessionNotification(
|
|||||||
if (event.type === "message.updated") {
|
if (event.type === "message.updated") {
|
||||||
const info = props?.info as Record<string, unknown> | undefined
|
const info = props?.info as Record<string, unknown> | undefined
|
||||||
const sessionID = getSessionID({ ...props, info })
|
const sessionID = getSessionID({ ...props, info })
|
||||||
if (sessionID) {
|
if (sessionID) scheduler.markSessionActivity(sessionID)
|
||||||
scheduler.markSessionActivity(sessionID)
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (PERMISSION_EVENTS.has(event.type)) {
|
if (PERMISSION_EVENTS.has(event.type)) {
|
||||||
const sessionID = getSessionID(props)
|
const sessionID = getSessionID(props)
|
||||||
if (!sessionID) return
|
if (!sessionID) return
|
||||||
|
|
||||||
|
const platform = ensureNotificationPlatform()
|
||||||
|
if (platform === "unsupported") return
|
||||||
if (!shouldNotifyForSession(sessionID)) return
|
if (!shouldNotifyForSession(sessionID)) return
|
||||||
|
|
||||||
scheduler.markSessionActivity(sessionID)
|
scheduler.markSessionActivity(sessionID)
|
||||||
await sessionNotificationSender.sendSessionNotification(
|
await sessionNotificationSender.sendSessionNotification(ctx, platform, mergedConfig.title, mergedConfig.permissionMessage)
|
||||||
ctx,
|
if (mergedConfig.playSound && defaultSoundPath) {
|
||||||
currentPlatform,
|
await sessionNotificationSender.playSessionNotificationSound(ctx, platform, defaultSoundPath)
|
||||||
mergedConfig.title,
|
|
||||||
mergedConfig.permissionMessage,
|
|
||||||
)
|
|
||||||
if (mergedConfig.playSound && mergedConfig.soundPath) {
|
|
||||||
await sessionNotificationSender.playSessionNotificationSound(ctx, currentPlatform, mergedConfig.soundPath)
|
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -160,16 +144,16 @@ export function createSessionNotification(
|
|||||||
if (event.type === "tool.execute.before") {
|
if (event.type === "tool.execute.before") {
|
||||||
const toolName = getEventToolName(props)?.toLowerCase()
|
const toolName = getEventToolName(props)?.toLowerCase()
|
||||||
if (toolName && QUESTION_TOOLS.has(toolName)) {
|
if (toolName && QUESTION_TOOLS.has(toolName)) {
|
||||||
|
const platform = ensureNotificationPlatform()
|
||||||
|
if (platform === "unsupported") return
|
||||||
if (!shouldNotifyForSession(sessionID)) return
|
if (!shouldNotifyForSession(sessionID)) return
|
||||||
|
|
||||||
const questionText = getQuestionText(props)
|
const questionText = getQuestionText(props)
|
||||||
const message = PERMISSION_HINT_PATTERN.test(questionText)
|
const message = PERMISSION_HINT_PATTERN.test(questionText) ? mergedConfig.permissionMessage : mergedConfig.questionMessage
|
||||||
? mergedConfig.permissionMessage
|
|
||||||
: mergedConfig.questionMessage
|
|
||||||
|
|
||||||
await sessionNotificationSender.sendSessionNotification(ctx, currentPlatform, mergedConfig.title, message)
|
await sessionNotificationSender.sendSessionNotification(ctx, platform, mergedConfig.title, message)
|
||||||
if (mergedConfig.playSound && mergedConfig.soundPath) {
|
if (mergedConfig.playSound && defaultSoundPath) {
|
||||||
await sessionNotificationSender.playSessionNotificationSound(ctx, currentPlatform, mergedConfig.soundPath)
|
await sessionNotificationSender.playSessionNotificationSound(ctx, platform, defaultSoundPath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -179,9 +163,7 @@ export function createSessionNotification(
|
|||||||
|
|
||||||
if (event.type === "session.deleted") {
|
if (event.type === "session.deleted") {
|
||||||
const sessionInfo = props?.info as { id?: string } | undefined
|
const sessionInfo = props?.info as { id?: string } | undefined
|
||||||
if (sessionInfo?.id) {
|
if (sessionInfo?.id) scheduler.deleteSession(sessionInfo.id)
|
||||||
scheduler.deleteSession(sessionInfo.id)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user