@mohak34/opencode-notifier 0.1.22-beta.1 → 0.1.22-beta.3

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 (2) hide show
  1. package/dist/index.js +111 -43
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -4127,12 +4127,26 @@ function runCommand2(config, event, message, sessionTitle, projectName) {
4127
4127
  }
4128
4128
 
4129
4129
  // src/index.ts
4130
- var recentErrors = new Map;
4130
+ var IDLE_COMPLETE_DELAY_MS = 350;
4131
+ var pendingIdleTimers = new Map;
4132
+ var sessionIdleSequence = new Map;
4133
+ var sessionErrorSuppressionAt = new Map;
4134
+ var sessionLastBusyAt = new Map;
4131
4135
  setInterval(() => {
4132
4136
  const cutoff = Date.now() - 5 * 60 * 1000;
4133
- for (const [sessionID, timestamp] of recentErrors) {
4137
+ for (const [sessionID] of sessionIdleSequence) {
4138
+ if (!pendingIdleTimers.has(sessionID)) {
4139
+ sessionIdleSequence.delete(sessionID);
4140
+ }
4141
+ }
4142
+ for (const [sessionID, timestamp] of sessionErrorSuppressionAt) {
4143
+ if (timestamp < cutoff) {
4144
+ sessionErrorSuppressionAt.delete(sessionID);
4145
+ }
4146
+ }
4147
+ for (const [sessionID, timestamp] of sessionLastBusyAt) {
4134
4148
  if (timestamp < cutoff) {
4135
- recentErrors.delete(sessionID);
4149
+ sessionLastBusyAt.delete(sessionID);
4136
4150
  }
4137
4151
  }
4138
4152
  }, 5 * 60 * 1000);
@@ -4173,6 +4187,50 @@ function getSessionIDFromEvent(event) {
4173
4187
  }
4174
4188
  return null;
4175
4189
  }
4190
+ function clearPendingIdleTimer(sessionID) {
4191
+ const timer = pendingIdleTimers.get(sessionID);
4192
+ if (!timer) {
4193
+ return;
4194
+ }
4195
+ clearTimeout(timer);
4196
+ pendingIdleTimers.delete(sessionID);
4197
+ }
4198
+ function bumpSessionIdleSequence(sessionID) {
4199
+ const nextSequence = (sessionIdleSequence.get(sessionID) ?? 0) + 1;
4200
+ sessionIdleSequence.set(sessionID, nextSequence);
4201
+ return nextSequence;
4202
+ }
4203
+ function hasCurrentSessionIdleSequence(sessionID, sequence) {
4204
+ return sessionIdleSequence.get(sessionID) === sequence;
4205
+ }
4206
+ function markSessionError(sessionID) {
4207
+ if (!sessionID) {
4208
+ return;
4209
+ }
4210
+ sessionErrorSuppressionAt.set(sessionID, Date.now());
4211
+ bumpSessionIdleSequence(sessionID);
4212
+ clearPendingIdleTimer(sessionID);
4213
+ }
4214
+ function markSessionBusy(sessionID) {
4215
+ const now = Date.now();
4216
+ sessionLastBusyAt.set(sessionID, now);
4217
+ sessionErrorSuppressionAt.delete(sessionID);
4218
+ bumpSessionIdleSequence(sessionID);
4219
+ clearPendingIdleTimer(sessionID);
4220
+ }
4221
+ function shouldSuppressSessionIdle(sessionID) {
4222
+ const errorAt = sessionErrorSuppressionAt.get(sessionID);
4223
+ if (errorAt === undefined) {
4224
+ return false;
4225
+ }
4226
+ const busyAt = sessionLastBusyAt.get(sessionID);
4227
+ if (typeof busyAt === "number" && busyAt > errorAt) {
4228
+ sessionErrorSuppressionAt.delete(sessionID);
4229
+ return false;
4230
+ }
4231
+ sessionErrorSuppressionAt.delete(sessionID);
4232
+ return true;
4233
+ }
4176
4234
  async function getElapsedSinceLastPrompt(client, sessionID, nowMs = Date.now()) {
4177
4235
  try {
4178
4236
  const response = await client.session.messages({ path: { id: sessionID } });
@@ -4203,21 +4261,50 @@ async function getSessionInfo(client, sessionID) {
4203
4261
  return { isChild: false, title: null };
4204
4262
  }
4205
4263
  }
4206
- async function handleEventWithElapsedTime(client, config, eventType, projectName, event, preloadedSessionTitle) {
4264
+ async function processSessionIdle(client, config, projectName, event, sessionID, sequence, idleReceivedAtMs) {
4265
+ if (!hasCurrentSessionIdleSequence(sessionID, sequence)) {
4266
+ return;
4267
+ }
4268
+ if (shouldSuppressSessionIdle(sessionID)) {
4269
+ return;
4270
+ }
4271
+ const sessionInfo = await getSessionInfo(client, sessionID);
4272
+ if (!hasCurrentSessionIdleSequence(sessionID, sequence)) {
4273
+ return;
4274
+ }
4275
+ if (!sessionInfo.isChild) {
4276
+ await handleEventWithElapsedTime(client, config, "complete", projectName, event, idleReceivedAtMs, sessionInfo.title);
4277
+ return;
4278
+ }
4279
+ await handleEventWithElapsedTime(client, config, "subagent_complete", projectName, event, idleReceivedAtMs, sessionInfo.title);
4280
+ }
4281
+ function scheduleSessionIdle(client, config, projectName, event, sessionID) {
4282
+ clearPendingIdleTimer(sessionID);
4283
+ const sequence = bumpSessionIdleSequence(sessionID);
4284
+ const idleReceivedAtMs = Date.now();
4285
+ const timer = setTimeout(() => {
4286
+ pendingIdleTimers.delete(sessionID);
4287
+ processSessionIdle(client, config, projectName, event, sessionID, sequence, idleReceivedAtMs).catch(() => {
4288
+ return;
4289
+ });
4290
+ }, IDLE_COMPLETE_DELAY_MS);
4291
+ pendingIdleTimers.set(sessionID, timer);
4292
+ }
4293
+ async function handleEventWithElapsedTime(client, config, eventType, projectName, event, elapsedReferenceNowMs, preloadedSessionTitle) {
4207
4294
  const sessionID = getSessionIDFromEvent(event);
4295
+ const minDuration = config.command?.minDuration;
4296
+ const shouldLookupElapsed = !!config.command?.enabled && typeof config.command?.path === "string" && config.command.path.length > 0 && typeof minDuration === "number" && Number.isFinite(minDuration) && minDuration > 0;
4208
4297
  let elapsedSeconds = null;
4209
- let sessionTitle = preloadedSessionTitle ?? null;
4210
- if (sessionID) {
4211
- const minDuration = config.command?.minDuration;
4212
- const shouldLookupElapsed = !!config.command?.enabled && typeof config.command?.path === "string" && config.command.path.length > 0 && typeof minDuration === "number" && Number.isFinite(minDuration) && minDuration > 0;
4213
- if (shouldLookupElapsed) {
4214
- elapsedSeconds = await getElapsedSinceLastPrompt(client, sessionID);
4215
- }
4216
- if (!sessionTitle && config.showSessionTitle) {
4217
- const info = await getSessionInfo(client, sessionID);
4218
- sessionTitle = info.title;
4298
+ if (shouldLookupElapsed) {
4299
+ if (sessionID) {
4300
+ elapsedSeconds = await getElapsedSinceLastPrompt(client, sessionID, elapsedReferenceNowMs);
4219
4301
  }
4220
4302
  }
4303
+ let sessionTitle = preloadedSessionTitle ?? null;
4304
+ if (sessionID && !sessionTitle && config.showSessionTitle) {
4305
+ const info = await getSessionInfo(client, sessionID);
4306
+ sessionTitle = info.title;
4307
+ }
4221
4308
  await handleEvent(config, eventType, projectName, elapsedSeconds, sessionTitle);
4222
4309
  }
4223
4310
  var NotifierPlugin = async ({ client, directory }) => {
@@ -4234,42 +4321,23 @@ var NotifierPlugin = async ({ client, directory }) => {
4234
4321
  if (event.type === "session.idle") {
4235
4322
  const sessionID = getSessionIDFromEvent(event);
4236
4323
  if (sessionID) {
4237
- await new Promise((resolve) => setTimeout(resolve, 100));
4238
- const errorTime = recentErrors.get(sessionID);
4239
- if (errorTime && Date.now() - errorTime < 500) {
4240
- recentErrors.delete(sessionID);
4241
- const sessionInfo = await getSessionInfo(client, sessionID);
4242
- await handleEventWithElapsedTime(client, config, "interrupted", projectName, event, sessionInfo.title);
4243
- } else {
4244
- const sessionInfo = await getSessionInfo(client, sessionID);
4245
- if (!sessionInfo.isChild) {
4246
- await handleEventWithElapsedTime(client, config, "complete", projectName, event, sessionInfo.title);
4247
- } else {
4248
- await handleEventWithElapsedTime(client, config, "subagent_complete", projectName, event, sessionInfo.title);
4249
- }
4250
- }
4324
+ scheduleSessionIdle(client, config, projectName, event, sessionID);
4251
4325
  } else {
4252
4326
  await handleEventWithElapsedTime(client, config, "complete", projectName, event);
4253
4327
  }
4254
4328
  }
4329
+ if (event.type === "session.status" && event.properties.status.type === "busy") {
4330
+ markSessionBusy(event.properties.sessionID);
4331
+ }
4255
4332
  if (event.type === "session.error") {
4256
4333
  const sessionID = getSessionIDFromEvent(event);
4257
- if (sessionID) {
4258
- recentErrors.set(sessionID, Date.now());
4259
- let sessionTitle = null;
4260
- if (config.showSessionTitle) {
4261
- const info = await getSessionInfo(client, sessionID);
4262
- sessionTitle = info.title;
4263
- }
4264
- setTimeout(() => {
4265
- if (recentErrors.has(sessionID)) {
4266
- recentErrors.delete(sessionID);
4267
- handleEventWithElapsedTime(client, config, "error", projectName, event, sessionTitle);
4268
- }
4269
- }, 100);
4270
- } else {
4271
- await handleEventWithElapsedTime(client, config, "error", projectName, event);
4334
+ markSessionError(sessionID);
4335
+ let sessionTitle = null;
4336
+ if (sessionID && config.showSessionTitle) {
4337
+ const info = await getSessionInfo(client, sessionID);
4338
+ sessionTitle = info.title;
4272
4339
  }
4340
+ await handleEventWithElapsedTime(client, config, "error", projectName, event, undefined, sessionTitle);
4273
4341
  }
4274
4342
  },
4275
4343
  "permission.ask": async () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mohak34/opencode-notifier",
3
- "version": "0.1.22-beta.1",
3
+ "version": "0.1.22-beta.3",
4
4
  "description": "OpenCode plugin that sends system notifications and plays sounds when permission is needed, generation completes, or errors occur",
5
5
  "author": "mohak34",
6
6
  "license": "MIT",