fix(hooks): guard session-notification against missing ctx.$ (refs #3997)
This commit is contained in:
@@ -78,6 +78,36 @@ describe("session-notification-sender", () => {
|
||||
})
|
||||
|
||||
describe("#given sendSessionNotification", () => {
|
||||
describe("#when ctx.$ is unavailable", () => {
|
||||
test("#then it returns early without throwing when ctx has no $", async () => {
|
||||
const cmuxSpy = spyOn(utils, "getCmuxPath")
|
||||
const mockCtx = unsafeTestValue<PluginInput>({})
|
||||
|
||||
await expect(sender.sendSessionNotification(mockCtx, "darwin", "Test", "Message")).resolves.toBeUndefined()
|
||||
expect(cmuxSpy).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test("#then it returns early without throwing when ctx.$ is not a function", async () => {
|
||||
const cmuxSpy = spyOn(utils, "getCmuxPath")
|
||||
const mockCtx = unsafeTestValue<PluginInput>({
|
||||
$: "not-a-function",
|
||||
})
|
||||
|
||||
await expect(sender.sendSessionNotification(mockCtx, "darwin", "Test", "Message")).resolves.toBeUndefined()
|
||||
expect(cmuxSpy).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test("#then it remains non-throwing across sender APIs", async () => {
|
||||
const afplaySpy = spyOn(utils, "getAfplayPath")
|
||||
const mockCtx = unsafeTestValue<PluginInput>({})
|
||||
|
||||
await expect(sender.sendSessionNotification(mockCtx, "darwin", "Test", "Message")).resolves.toBeUndefined()
|
||||
await expect(sender.playSessionNotificationSound(mockCtx, "darwin", "/sound.aiff")).resolves.toBeUndefined()
|
||||
|
||||
expect(afplaySpy).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe("#when calling ctx.$ for notifications", () => {
|
||||
test("#then should call .quiet() on all shell commands to suppress stdout/stderr", async () => {
|
||||
const quietCalls: string[] = []
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { PluginInput } from "@opencode-ai/plugin"
|
||||
import { platform } from "os"
|
||||
import { log } from "../shared"
|
||||
import {
|
||||
getCmuxPath,
|
||||
getOsascriptPath,
|
||||
@@ -38,6 +39,19 @@ type ShellCommand = Promise<unknown> & {
|
||||
nothrow?: () => ShellCommand
|
||||
}
|
||||
|
||||
let hasLoggedUnavailableShellHelper = false
|
||||
|
||||
function canRunNotificationCommand(ctx: PluginInput): boolean {
|
||||
if (typeof ctx?.$ === "function") return true
|
||||
|
||||
if (!hasLoggedUnavailableShellHelper) {
|
||||
hasLoggedUnavailableShellHelper = true
|
||||
log("[session-notification] ctx.$ unavailable; skipping notification command execution")
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
async function runQuietNothrow(command: ShellCommand): Promise<void> {
|
||||
const safeCommand = typeof command.nothrow === "function" ? command.nothrow() : command
|
||||
if (typeof safeCommand.quiet === "function") {
|
||||
@@ -54,6 +68,8 @@ export async function sendSessionNotification(
|
||||
title: string,
|
||||
message: string
|
||||
): Promise<void> {
|
||||
if (!canRunNotificationCommand(ctx)) return
|
||||
|
||||
switch (platform) {
|
||||
case "darwin": {
|
||||
// Try cmux first - native UNUserNotificationCenter, properly attributed
|
||||
@@ -113,6 +129,8 @@ export async function playSessionNotificationSound(
|
||||
platform: Platform,
|
||||
soundPath: string
|
||||
): Promise<void> {
|
||||
if (!canRunNotificationCommand(ctx)) return
|
||||
|
||||
switch (platform) {
|
||||
case "darwin": {
|
||||
const afplayPath = await getAfplayPath()
|
||||
|
||||
Reference in New Issue
Block a user