opencode-interrupt-plugin 0.4.9 → 0.4.11

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.
@@ -37,10 +37,6 @@ export class VoiceOverlapDetector extends EventEmitter {
37
37
  }
38
38
  handleVoiceDetected(rms) {
39
39
  const ttsPlaying = this.getIsTTSPlaying();
40
- if (!ttsPlaying) {
41
- debug(`[interrupt-overlap] Voice detected (RMS=${rms.toFixed(6)}) but TTS not playing, ignoring`);
42
- return;
43
- }
44
40
  if (Date.now() < this.cooldownUntil) {
45
41
  debug(`[interrupt-overlap] In cooldown (${this.cooldownUntil - Date.now()}ms remaining), ignoring`);
46
42
  return;
@@ -48,8 +44,13 @@ export class VoiceOverlapDetector extends EventEmitter {
48
44
  const now = Date.now();
49
45
  this.cooldownUntil = now + this.COOLDOWN_MS;
50
46
  const partialContent = this.getPartialContent();
51
- debug(`[interrupt-overlap] OVERLAP! RMS=${rms.toFixed(6)}, stopping TTS, partial=${partialContent.slice(0, 80)}`);
52
- this.stopTTS();
47
+ if (ttsPlaying) {
48
+ debug(`[interrupt-overlap] OVERLAP! RMS=${rms.toFixed(6)}, stopping TTS, partial=${partialContent.slice(0, 80)}`);
49
+ this.stopTTS();
50
+ }
51
+ else {
52
+ debug(`[interrupt-overlap] Voice heard during silence RMS=${rms.toFixed(6)}, marking interrupt`);
53
+ }
53
54
  const event = {
54
55
  partialTTSContent: partialContent,
55
56
  overlapTimestamp: now,
package/dist/index.js CHANGED
@@ -8,6 +8,7 @@ import { VoiceOverlapDetector } from './audio/overlap.js';
8
8
  import { detectTextInterruption } from './detector.js';
9
9
  import { TTSStreamer } from './tts/index.js';
10
10
  let activeSessionId = null;
11
+ let pendingInterrupt = null;
11
12
  const TTS_COMMANDS = [
12
13
  { name: 'tts-on', description: 'Enable streaming TTS', template: 'TTS enabled.' },
13
14
  { name: 'tts-off', description: 'Disable streaming TTS', template: 'TTS disabled.' },
@@ -51,7 +52,8 @@ export const InterruptPlugin = (userConfig = {}) => {
51
52
  }, () => ttsStreamer.getPartialContent());
52
53
  overlapDetector.on('voice-overlap', (event) => {
53
54
  if (!activeSessionId) {
54
- debug(`[interrupt] Voice overlap event but no active session, ignoring`);
55
+ pendingInterrupt = event;
56
+ debug(`[interrupt] Voice overlap event buffered (no active session)`);
55
57
  return;
56
58
  }
57
59
  debug(`[interrupt] Voice overlap detected (RMS: ${event.rmsLevel.toFixed(4)})`);
@@ -141,6 +143,17 @@ export const InterruptPlugin = (userConfig = {}) => {
141
143
  if (!sessionId)
142
144
  return;
143
145
  activeSessionId = sessionId;
146
+ if (pendingInterrupt) {
147
+ updateSessionState(sessionId, {
148
+ wasInterrupted: true,
149
+ partialContentAtInterrupt: pendingInterrupt.partialTTSContent,
150
+ interruptTimestamp: pendingInterrupt.overlapTimestamp,
151
+ awaitingCorrection: true,
152
+ interruptSource: 'voice',
153
+ });
154
+ debug(`[interrupt] Applied buffered voice overlap to session ${sessionId}`);
155
+ pendingInterrupt = null;
156
+ }
144
157
  const sessionState = getSessionState(sessionId);
145
158
  const userMessage = extractUserMessage(input);
146
159
  if (!userMessage)
@@ -202,10 +215,40 @@ export const InterruptPlugin = (userConfig = {}) => {
202
215
  if (isTTSTool(toolName)) {
203
216
  onTTSEnd();
204
217
  }
218
+ if (config.tts && isWriteTool(toolName)) {
219
+ const text = extractToolOutput(output);
220
+ if (text) {
221
+ debug(`[interrupt] Tool "${toolName}" output captured (${text.length} chars) for TTS`);
222
+ ttsStreamer.onTextChunk({
223
+ messageID: `tool-${toolName}-${Date.now()}`,
224
+ type: 'text',
225
+ text,
226
+ });
227
+ }
228
+ }
205
229
  },
206
230
  };
207
231
  };
208
232
  };
233
+ const WRITE_TOOL_NAMES = ['write', 'create', 'edit', 'overwrite', 'file.write', 'file.create', 'file.edit', 'file.overwrite', 'append'];
234
+ function isWriteTool(name) {
235
+ return WRITE_TOOL_NAMES.includes(name.toLowerCase());
236
+ }
237
+ function extractToolOutput(output) {
238
+ if (!output)
239
+ return '';
240
+ if (typeof output === 'string')
241
+ return output;
242
+ if (typeof output.result === 'string')
243
+ return output.result;
244
+ if (output.result?.content)
245
+ return output.result.content;
246
+ if (output.content)
247
+ return output.content;
248
+ if (output.text)
249
+ return output.text;
250
+ return '';
251
+ }
209
252
  function extractUserMessage(input) {
210
253
  try {
211
254
  const messages = input.messages || [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-interrupt-plugin",
3
- "version": "0.4.9",
3
+ "version": "0.4.11",
4
4
  "description": "Streaming TTS + voice interruption for OpenCode. Speaks responses as they arrive and detects when you talk over it.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",