@mohak34/opencode-notifier 0.1.22-beta.1 → 0.1.22-beta.2
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.
- package/dist/index.js +112 -75
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3894,9 +3894,6 @@ function getMessage(config, event) {
|
|
|
3894
3894
|
function getSoundPath(config, event) {
|
|
3895
3895
|
return config.sounds[event];
|
|
3896
3896
|
}
|
|
3897
|
-
function getSoundVolume(config, event) {
|
|
3898
|
-
return config.volumes[event];
|
|
3899
|
-
}
|
|
3900
3897
|
function getIconPath(config) {
|
|
3901
3898
|
if (!config.showIcon) {
|
|
3902
3899
|
return;
|
|
@@ -3911,16 +3908,6 @@ function getIconPath(config) {
|
|
|
3911
3908
|
} catch {}
|
|
3912
3909
|
return;
|
|
3913
3910
|
}
|
|
3914
|
-
function interpolateMessage(message, context) {
|
|
3915
|
-
let result = message;
|
|
3916
|
-
const sessionTitle = context.sessionTitle || "";
|
|
3917
|
-
result = result.replaceAll("{sessionTitle}", sessionTitle);
|
|
3918
|
-
const projectName = context.projectName || "";
|
|
3919
|
-
result = result.replaceAll("{projectName}", projectName);
|
|
3920
|
-
result = result.replace(/\s*[:\-|]\s*$/, "").trim();
|
|
3921
|
-
result = result.replace(/\s{2,}/g, " ");
|
|
3922
|
-
return result;
|
|
3923
|
-
}
|
|
3924
3911
|
|
|
3925
3912
|
// src/notify.ts
|
|
3926
3913
|
var import_node_notifier = __toESM(require_node_notifier(), 1);
|
|
@@ -4127,12 +4114,26 @@ function runCommand2(config, event, message, sessionTitle, projectName) {
|
|
|
4127
4114
|
}
|
|
4128
4115
|
|
|
4129
4116
|
// src/index.ts
|
|
4130
|
-
var
|
|
4117
|
+
var IDLE_COMPLETE_DELAY_MS = 350;
|
|
4118
|
+
var pendingIdleTimers = new Map;
|
|
4119
|
+
var sessionIdleSequence = new Map;
|
|
4120
|
+
var sessionErrorSuppressionAt = new Map;
|
|
4121
|
+
var sessionLastBusyAt = new Map;
|
|
4131
4122
|
setInterval(() => {
|
|
4132
4123
|
const cutoff = Date.now() - 5 * 60 * 1000;
|
|
4133
|
-
for (const [sessionID
|
|
4124
|
+
for (const [sessionID] of sessionIdleSequence) {
|
|
4125
|
+
if (!pendingIdleTimers.has(sessionID)) {
|
|
4126
|
+
sessionIdleSequence.delete(sessionID);
|
|
4127
|
+
}
|
|
4128
|
+
}
|
|
4129
|
+
for (const [sessionID, timestamp] of sessionErrorSuppressionAt) {
|
|
4134
4130
|
if (timestamp < cutoff) {
|
|
4135
|
-
|
|
4131
|
+
sessionErrorSuppressionAt.delete(sessionID);
|
|
4132
|
+
}
|
|
4133
|
+
}
|
|
4134
|
+
for (const [sessionID, timestamp] of sessionLastBusyAt) {
|
|
4135
|
+
if (timestamp < cutoff) {
|
|
4136
|
+
sessionLastBusyAt.delete(sessionID);
|
|
4136
4137
|
}
|
|
4137
4138
|
}
|
|
4138
4139
|
}, 5 * 60 * 1000);
|
|
@@ -4142,13 +4143,9 @@ function getNotificationTitle(config, projectName) {
|
|
|
4142
4143
|
}
|
|
4143
4144
|
return "OpenCode";
|
|
4144
4145
|
}
|
|
4145
|
-
async function handleEvent(config, eventType, projectName, elapsedSeconds
|
|
4146
|
+
async function handleEvent(config, eventType, projectName, elapsedSeconds) {
|
|
4146
4147
|
const promises = [];
|
|
4147
|
-
const
|
|
4148
|
-
const message = interpolateMessage(rawMessage, {
|
|
4149
|
-
sessionTitle: config.showSessionTitle ? sessionTitle : null,
|
|
4150
|
-
projectName
|
|
4151
|
-
});
|
|
4148
|
+
const message = getMessage(config, eventType);
|
|
4152
4149
|
if (isEventNotificationEnabled(config, eventType)) {
|
|
4153
4150
|
const title = getNotificationTitle(config, projectName);
|
|
4154
4151
|
const iconPath = getIconPath(config);
|
|
@@ -4156,13 +4153,12 @@ async function handleEvent(config, eventType, projectName, elapsedSeconds, sessi
|
|
|
4156
4153
|
}
|
|
4157
4154
|
if (isEventSoundEnabled(config, eventType)) {
|
|
4158
4155
|
const customSoundPath = getSoundPath(config, eventType);
|
|
4159
|
-
|
|
4160
|
-
promises.push(playSound(eventType, customSoundPath, soundVolume));
|
|
4156
|
+
promises.push(playSound(eventType, customSoundPath, 1));
|
|
4161
4157
|
}
|
|
4162
4158
|
const minDuration = config.command?.minDuration;
|
|
4163
4159
|
const shouldSkipCommand = typeof minDuration === "number" && Number.isFinite(minDuration) && minDuration > 0 && typeof elapsedSeconds === "number" && Number.isFinite(elapsedSeconds) && elapsedSeconds < minDuration;
|
|
4164
4160
|
if (!shouldSkipCommand) {
|
|
4165
|
-
runCommand2(config, eventType, message
|
|
4161
|
+
runCommand2(config, eventType, message);
|
|
4166
4162
|
}
|
|
4167
4163
|
await Promise.allSettled(promises);
|
|
4168
4164
|
}
|
|
@@ -4173,6 +4169,50 @@ function getSessionIDFromEvent(event) {
|
|
|
4173
4169
|
}
|
|
4174
4170
|
return null;
|
|
4175
4171
|
}
|
|
4172
|
+
function clearPendingIdleTimer(sessionID) {
|
|
4173
|
+
const timer = pendingIdleTimers.get(sessionID);
|
|
4174
|
+
if (!timer) {
|
|
4175
|
+
return;
|
|
4176
|
+
}
|
|
4177
|
+
clearTimeout(timer);
|
|
4178
|
+
pendingIdleTimers.delete(sessionID);
|
|
4179
|
+
}
|
|
4180
|
+
function bumpSessionIdleSequence(sessionID) {
|
|
4181
|
+
const nextSequence = (sessionIdleSequence.get(sessionID) ?? 0) + 1;
|
|
4182
|
+
sessionIdleSequence.set(sessionID, nextSequence);
|
|
4183
|
+
return nextSequence;
|
|
4184
|
+
}
|
|
4185
|
+
function hasCurrentSessionIdleSequence(sessionID, sequence) {
|
|
4186
|
+
return sessionIdleSequence.get(sessionID) === sequence;
|
|
4187
|
+
}
|
|
4188
|
+
function markSessionError(sessionID) {
|
|
4189
|
+
if (!sessionID) {
|
|
4190
|
+
return;
|
|
4191
|
+
}
|
|
4192
|
+
sessionErrorSuppressionAt.set(sessionID, Date.now());
|
|
4193
|
+
bumpSessionIdleSequence(sessionID);
|
|
4194
|
+
clearPendingIdleTimer(sessionID);
|
|
4195
|
+
}
|
|
4196
|
+
function markSessionBusy(sessionID) {
|
|
4197
|
+
const now = Date.now();
|
|
4198
|
+
sessionLastBusyAt.set(sessionID, now);
|
|
4199
|
+
sessionErrorSuppressionAt.delete(sessionID);
|
|
4200
|
+
bumpSessionIdleSequence(sessionID);
|
|
4201
|
+
clearPendingIdleTimer(sessionID);
|
|
4202
|
+
}
|
|
4203
|
+
function shouldSuppressSessionIdle(sessionID) {
|
|
4204
|
+
const errorAt = sessionErrorSuppressionAt.get(sessionID);
|
|
4205
|
+
if (errorAt === undefined) {
|
|
4206
|
+
return false;
|
|
4207
|
+
}
|
|
4208
|
+
const busyAt = sessionLastBusyAt.get(sessionID);
|
|
4209
|
+
if (typeof busyAt === "number" && busyAt > errorAt) {
|
|
4210
|
+
sessionErrorSuppressionAt.delete(sessionID);
|
|
4211
|
+
return false;
|
|
4212
|
+
}
|
|
4213
|
+
sessionErrorSuppressionAt.delete(sessionID);
|
|
4214
|
+
return true;
|
|
4215
|
+
}
|
|
4176
4216
|
async function getElapsedSinceLastPrompt(client, sessionID, nowMs = Date.now()) {
|
|
4177
4217
|
try {
|
|
4178
4218
|
const response = await client.session.messages({ path: { id: sessionID } });
|
|
@@ -4192,33 +4232,55 @@ async function getElapsedSinceLastPrompt(client, sessionID, nowMs = Date.now())
|
|
|
4192
4232
|
} catch {}
|
|
4193
4233
|
return null;
|
|
4194
4234
|
}
|
|
4195
|
-
async function
|
|
4235
|
+
async function isChildSession(client, sessionID) {
|
|
4196
4236
|
try {
|
|
4197
4237
|
const response = await client.session.get({ path: { id: sessionID } });
|
|
4198
|
-
|
|
4199
|
-
|
|
4200
|
-
title: response.data?.title ?? null
|
|
4201
|
-
};
|
|
4238
|
+
const parentID = response.data?.parentID;
|
|
4239
|
+
return !!parentID;
|
|
4202
4240
|
} catch {
|
|
4203
|
-
return
|
|
4241
|
+
return false;
|
|
4242
|
+
}
|
|
4243
|
+
}
|
|
4244
|
+
async function processSessionIdle(client, config, projectName, event, sessionID, sequence, idleReceivedAtMs) {
|
|
4245
|
+
if (!hasCurrentSessionIdleSequence(sessionID, sequence)) {
|
|
4246
|
+
return;
|
|
4204
4247
|
}
|
|
4248
|
+
if (shouldSuppressSessionIdle(sessionID)) {
|
|
4249
|
+
return;
|
|
4250
|
+
}
|
|
4251
|
+
const isChild = await isChildSession(client, sessionID);
|
|
4252
|
+
if (!hasCurrentSessionIdleSequence(sessionID, sequence)) {
|
|
4253
|
+
return;
|
|
4254
|
+
}
|
|
4255
|
+
if (!isChild) {
|
|
4256
|
+
await handleEventWithElapsedTime(client, config, "complete", projectName, event, idleReceivedAtMs);
|
|
4257
|
+
return;
|
|
4258
|
+
}
|
|
4259
|
+
await handleEventWithElapsedTime(client, config, "subagent_complete", projectName, event, idleReceivedAtMs);
|
|
4260
|
+
}
|
|
4261
|
+
function scheduleSessionIdle(client, config, projectName, event, sessionID) {
|
|
4262
|
+
clearPendingIdleTimer(sessionID);
|
|
4263
|
+
const sequence = bumpSessionIdleSequence(sessionID);
|
|
4264
|
+
const idleReceivedAtMs = Date.now();
|
|
4265
|
+
const timer = setTimeout(() => {
|
|
4266
|
+
pendingIdleTimers.delete(sessionID);
|
|
4267
|
+
processSessionIdle(client, config, projectName, event, sessionID, sequence, idleReceivedAtMs).catch(() => {
|
|
4268
|
+
return;
|
|
4269
|
+
});
|
|
4270
|
+
}, IDLE_COMPLETE_DELAY_MS);
|
|
4271
|
+
pendingIdleTimers.set(sessionID, timer);
|
|
4205
4272
|
}
|
|
4206
|
-
async function handleEventWithElapsedTime(client, config, eventType, projectName, event,
|
|
4207
|
-
const
|
|
4273
|
+
async function handleEventWithElapsedTime(client, config, eventType, projectName, event, elapsedReferenceNowMs) {
|
|
4274
|
+
const minDuration = config.command?.minDuration;
|
|
4275
|
+
const shouldLookupElapsed = !!config.command?.enabled && typeof config.command?.path === "string" && config.command.path.length > 0 && typeof minDuration === "number" && Number.isFinite(minDuration) && minDuration > 0;
|
|
4208
4276
|
let elapsedSeconds = null;
|
|
4209
|
-
|
|
4210
|
-
|
|
4211
|
-
|
|
4212
|
-
|
|
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;
|
|
4277
|
+
if (shouldLookupElapsed) {
|
|
4278
|
+
const sessionID = getSessionIDFromEvent(event);
|
|
4279
|
+
if (sessionID) {
|
|
4280
|
+
elapsedSeconds = await getElapsedSinceLastPrompt(client, sessionID, elapsedReferenceNowMs);
|
|
4219
4281
|
}
|
|
4220
4282
|
}
|
|
4221
|
-
await handleEvent(config, eventType, projectName, elapsedSeconds
|
|
4283
|
+
await handleEvent(config, eventType, projectName, elapsedSeconds);
|
|
4222
4284
|
}
|
|
4223
4285
|
var NotifierPlugin = async ({ client, directory }) => {
|
|
4224
4286
|
const config = loadConfig();
|
|
@@ -4234,42 +4296,17 @@ var NotifierPlugin = async ({ client, directory }) => {
|
|
|
4234
4296
|
if (event.type === "session.idle") {
|
|
4235
4297
|
const sessionID = getSessionIDFromEvent(event);
|
|
4236
4298
|
if (sessionID) {
|
|
4237
|
-
|
|
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
|
-
}
|
|
4299
|
+
scheduleSessionIdle(client, config, projectName, event, sessionID);
|
|
4251
4300
|
} else {
|
|
4252
4301
|
await handleEventWithElapsedTime(client, config, "complete", projectName, event);
|
|
4253
4302
|
}
|
|
4254
4303
|
}
|
|
4304
|
+
if (event.type === "session.status" && event.properties.status.type === "busy") {
|
|
4305
|
+
markSessionBusy(event.properties.sessionID);
|
|
4306
|
+
}
|
|
4255
4307
|
if (event.type === "session.error") {
|
|
4256
|
-
|
|
4257
|
-
|
|
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);
|
|
4272
|
-
}
|
|
4308
|
+
markSessionError(getSessionIDFromEvent(event));
|
|
4309
|
+
await handleEventWithElapsedTime(client, config, "error", projectName, event);
|
|
4273
4310
|
}
|
|
4274
4311
|
},
|
|
4275
4312
|
"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.
|
|
3
|
+
"version": "0.1.22-beta.2",
|
|
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",
|