@serkanalgur/opencodev2-notification 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +11 -14
  2. package/package.json +1 -1
  3. package/src/index.ts +17 -54
package/README.md CHANGED
@@ -51,10 +51,9 @@ Or copy the plugin files to your `.opencode/plugins/` directory:
51
51
 
52
52
  | Event | Notifies? | Sound | Why |
53
53
  |-------|-----------|-------|-----|
54
- | Session complete | Yes | Glass | Main task done - time to review |
55
- | Session error | Yes | Basso | Something broke - needs attention |
56
- | Permission needed | Yes | Submarine | AI is blocked, waiting for you |
57
- | Question asked | Yes | Submarine (default) | Questions should always reach you promptly |
54
+ | Session complete | Yes | done | Main task done - time to review |
55
+ | Session error | Yes | error | Something broke - needs attention |
56
+ | Permission needed | Yes | permission | AI is blocked, waiting for you |
58
57
  | Sub-task complete/error | No (default) | - | Set `notifyChildSessions: true` to include child sessions |
59
58
 
60
59
  The plugin automatically:
@@ -79,10 +78,9 @@ Works out of the box. To customize, create `~/.config/opencode/opencodev2-notifi
79
78
  {
80
79
  "notifyChildSessions": false,
81
80
  "sounds": {
82
- "idle": "Glass",
83
- "error": "Basso",
84
- "permission": "Submarine",
85
- "question": "Submarine"
81
+ "idle": "done",
82
+ "error": "error",
83
+ "permission": "permission"
86
84
  },
87
85
  "quietHours": {
88
86
  "enabled": false,
@@ -97,17 +95,16 @@ Works out of the box. To customize, create `~/.config/opencode/opencodev2-notifi
97
95
  | Key | Type | Default | Description |
98
96
  |-----|------|---------|-------------|
99
97
  | `notifyChildSessions` | boolean | `false` | Include child/sub-session notifications |
100
- | `sounds.idle` | string | `"Glass"` | Sound for session complete |
101
- | `sounds.error` | string | `"Basso"` | Sound for errors |
102
- | `sounds.permission` | string | `"Submarine"` | Sound for permission requests |
103
- | `sounds.question` | string | `"Submarine"` | Sound for questions |
98
+ | `sounds.idle` | string | `"done"` | Sound for session complete |
99
+ | `sounds.error` | string | `"error"` | Sound for errors |
100
+ | `sounds.permission` | string | `"permission"` | Sound for permission requests |
104
101
  | `quietHours.enabled` | boolean | `false` | Enable quiet hours |
105
102
  | `quietHours.start` | string | `"22:00"` | Quiet hours start (HH:MM) |
106
103
  | `quietHours.end` | string | `"08:00"` | Quiet hours end (HH:MM) |
107
104
 
108
- ### Available macOS Sounds
105
+ ### Available Sound Names
109
106
 
110
- Basso, Blow, Bottle, Frog, Funk, Glass, Hero, Morse, Ping, Pop, Purr, Sosumi, Submarine, Tink
107
+ `default`, `question`, `permission`, `error`, `done`, `subagent_done`
111
108
 
112
109
  ## FAQ
113
110
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@serkanalgur/opencodev2-notification",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "Native OS notifications for OpenCode V2 - know when tasks complete, errors occur, or the AI needs your input",
5
5
  "keywords": [
6
6
  "opencode",
package/src/index.ts CHANGED
@@ -22,15 +22,16 @@ import { Plugin } from "@opencode/plugin/tui"
22
22
  // TYPES
23
23
  // ==========================================
24
24
 
25
+ type AttentionSoundName = "default" | "question" | "permission" | "error" | "done" | "subagent_done"
26
+
25
27
  interface NotifyConfig {
26
28
  /** Notify for child/sub-session events (default: false) */
27
29
  notifyChildSessions: boolean
28
30
  /** Sound configuration per event type */
29
31
  sounds: {
30
- idle: string
31
- error: string
32
- permission: string
33
- question?: string
32
+ idle: AttentionSoundName
33
+ error: AttentionSoundName
34
+ permission: AttentionSoundName
34
35
  }
35
36
  /** Quiet hours configuration */
36
37
  quietHours: {
@@ -38,8 +39,6 @@ interface NotifyConfig {
38
39
  start: string // "HH:MM" format
39
40
  end: string // "HH:MM" format
40
41
  }
41
- /** Override terminal detection (optional) */
42
- terminal?: string
43
42
  }
44
43
 
45
44
  // ==========================================
@@ -49,9 +48,9 @@ interface NotifyConfig {
49
48
  const DEFAULT_CONFIG: NotifyConfig = {
50
49
  notifyChildSessions: false,
51
50
  sounds: {
52
- idle: "Glass",
53
- error: "Basso",
54
- permission: "Submarine",
51
+ idle: "done",
52
+ error: "error",
53
+ permission: "permission",
55
54
  },
56
55
  quietHours: {
57
56
  enabled: false,
@@ -125,7 +124,6 @@ function isQuietHours(config: NotifyConfig): boolean {
125
124
 
126
125
  type RecentNotifications = Map<string, number>
127
126
 
128
- const QUESTION_DEDUPE_WINDOW_MS = 1500
129
127
  const READY_DEDUPE_WINDOW_MS = 1500
130
128
  const PERMISSION_DEDUPE_WINDOW_MS = 1500
131
129
 
@@ -169,7 +167,6 @@ export default Plugin.define({
169
167
  const config = await loadConfig()
170
168
 
171
169
  // Deduplication maps
172
- const recentQuestionNotifications: RecentNotifications = new Map()
173
170
  const recentReadyNotifications: RecentNotifications = new Map()
174
171
  const recentPermissionNotifications: RecentNotifications = new Map()
175
172
 
@@ -193,7 +190,7 @@ export default Plugin.define({
193
190
  const sendNotification = async (
194
191
  title: string,
195
192
  message: string,
196
- soundName: string
193
+ soundName: AttentionSoundName
197
194
  ): Promise<void> => {
198
195
  try {
199
196
  await context.attention.notify({
@@ -245,9 +242,9 @@ export default Plugin.define({
245
242
  })
246
243
  )
247
244
 
248
- // Session error
245
+ // Session execution failed
249
246
  unsubscribers.push(
250
- context.data.on("session.error", async (event) => {
247
+ context.data.on("session.execution.failed", async (event) => {
251
248
  const sessionID = toNonEmptyString(event.data.sessionID)
252
249
  if (!sessionID) return
253
250
 
@@ -260,12 +257,7 @@ export default Plugin.define({
260
257
  if (isQuietHours(config)) return
261
258
 
262
259
  const error = event.data.error
263
- const errorMessage =
264
- typeof error === "string"
265
- ? error.slice(0, 100)
266
- : error
267
- ? String(error).slice(0, 100)
268
- : "Something went wrong"
260
+ const errorMessage = (error.message ?? "Something went wrong").slice(0, 100)
269
261
 
270
262
  await sendNotification(
271
263
  "Something went wrong",
@@ -303,16 +295,16 @@ export default Plugin.define({
303
295
  })
304
296
  )
305
297
 
306
- // Permission updated (when user responds)
298
+ // Permission replied (when user responds)
307
299
  unsubscribers.push(
308
- context.data.on("permission.updated", async (event) => {
300
+ context.data.on("permission.replied", async (event) => {
309
301
  // Check quiet hours
310
302
  if (isQuietHours(config)) return
311
303
 
312
304
  // Deduplication
313
- const permissionKey = toNonEmptyString(event.data.id)
314
- ? `permission:updated:${event.data.id}`
315
- : `permission-update:${Date.now()}`
305
+ const permissionKey = toNonEmptyString(event.data.requestID)
306
+ ? `permission:replied:${event.data.requestID}`
307
+ : `permission-reply:${Date.now()}`
316
308
  if (
317
309
  !shouldSendDedupedNotification(
318
310
  recentPermissionNotifications,
@@ -331,35 +323,6 @@ export default Plugin.define({
331
323
  })
332
324
  )
333
325
 
334
- // Question asked (when AI asks a question)
335
- unsubscribers.push(
336
- context.data.on("question.asked", async (event) => {
337
- // Check quiet hours
338
- if (isQuietHours(config)) return
339
-
340
- // Deduplication
341
- const questionKey = toNonEmptyString(event.data.id)
342
- ? `question:request:${event.data.id}`
343
- : `question:${Date.now()}`
344
- if (
345
- !shouldSendDedupedNotification(
346
- recentQuestionNotifications,
347
- questionKey,
348
- QUESTION_DEDUPE_WINDOW_MS
349
- )
350
- ) {
351
- return
352
- }
353
-
354
- const sound = config.sounds.question ?? config.sounds.permission
355
- await sendNotification(
356
- "Question for you",
357
- "OpenCode needs your input",
358
- sound
359
- )
360
- })
361
- )
362
-
363
326
  // Return cleanup function
364
327
  return () => {
365
328
  unsubscribers.forEach((unsub) => unsub())