vibeaudio 0.11.0 → 0.11.1

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/package.json +1 -1
  2. package/src/hooks.js +39 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibeaudio",
3
- "version": "0.11.0",
3
+ "version": "0.11.1",
4
4
  "description": "Procedural focus music while your AI coding tools (Claude Code, Codex, Cursor, Grok, Gemini, Copilot) think — a different arrangement per project.",
5
5
  "bin": {
6
6
  "vibeaudio": "bin/vibeaudio.js",
package/src/hooks.js CHANGED
@@ -450,7 +450,44 @@ function fileSize(file) {
450
450
  function newTurn(raw) {
451
451
  const payload = parsePayload(raw);
452
452
  const transcript = typeof payload.transcript_path === "string" ? payload.transcript_path : "";
453
- return { session: String(payloadSession(payload) || ""), transcript, offset: transcript ? fileSize(transcript) : 0 };
453
+ const offset = transcript ? fileSize(transcript) : 0;
454
+ return { session: String(payloadSession(payload) || ""), transcript, offset, blocked: blockedJustBefore(transcript, offset) };
455
+ }
456
+
457
+ const BLOCK_LOOKBACK_MS = 3000;
458
+
459
+ /**
460
+ * Whether another hook has already rejected this very prompt. Hooks run side
461
+ * by side and a native blocker is faster to start than we are, so its transcript
462
+ * entry can land *before* the offset taken above - where the daemon, which reads
463
+ * only what follows, would never see it. Looked at the tail instead, and only
464
+ * for an entry stamped within the last moments, so an earlier blocked prompt
465
+ * does not silence this one.
466
+ */
467
+ function blockedJustBefore(transcript, offset) {
468
+ if (!transcript || !offset) return false;
469
+ try {
470
+ const len = Math.min(offset, 16384);
471
+ const fd = fs.openSync(transcript, "r");
472
+ const buf = Buffer.alloc(len);
473
+ try {
474
+ fs.readSync(fd, buf, 0, len, offset - len);
475
+ } finally {
476
+ fs.closeSync(fd);
477
+ }
478
+ const lines = buf.toString("utf8").split("\n");
479
+ if (len < offset) lines.shift(); // Began mid-line.
480
+ return lines.some((line) => {
481
+ if (!isInterruptEntry(line)) return false;
482
+ try {
483
+ return Date.now() - Date.parse(JSON.parse(line).timestamp) <= BLOCK_LOOKBACK_MS;
484
+ } catch (e) {
485
+ return false;
486
+ }
487
+ });
488
+ } catch (e) {
489
+ return false;
490
+ }
454
491
  }
455
492
 
456
493
  // The id ends up in a file name.
@@ -653,6 +690,7 @@ function spawnDaemon(genre, volume, reactive) {
653
690
  * music carries on: restarting it would cut every other session's stream.
654
691
  */
655
692
  function hookStart(genre, volume, { reactive = false, turn = null } = {}) {
693
+ if (turn && turn.blocked) return null; // Rejected before it began: nothing to play for.
656
694
  const id = sessionId(turn && turn.session);
657
695
  // Before the spawn: the daemon reads it on startup.
658
696
  writeSession(id, { transcript: (turn && turn.transcript) || "", offset: (turn && turn.offset) || 0, waiting: null, started: Date.now(), blockedMs: 0 });