claude-code-watch 0.0.9 → 0.0.10

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-code-watch",
3
- "version": "0.0.9",
3
+ "version": "0.0.10",
4
4
  "description": "Web-based real-time monitor for Claude Code.",
5
5
  "main": "./src/server/server.js",
6
6
  "bin": {
@@ -456,8 +456,16 @@ class Watcher extends EventEmitter {
456
456
  }
457
457
 
458
458
  _handleFsWrite(p) {
459
- const ctx = this.fileContexts.get(p);
460
- if (!ctx) return;
459
+ let ctx = this.fileContexts.get(p);
460
+
461
+ // If fileContexts is missing (race condition during async session registration),
462
+ // try to infer the session context from the path
463
+ if (!ctx) {
464
+ ctx = this._inferFileContext(p);
465
+ if (!ctx) return;
466
+ // Register it so future events are found directly
467
+ this.fileContexts.set(p, ctx);
468
+ }
461
469
 
462
470
  // Debounce
463
471
  const existing = this.debounceTimers.get(p);
@@ -476,6 +484,28 @@ class Watcher extends EventEmitter {
476
484
  this.debounceTimers.set(p, timer);
477
485
  }
478
486
 
487
+ _inferFileContext(p) {
488
+ if (!p.endsWith('.jsonl')) return null;
489
+
490
+ // Subagent file: infer sessionID and agentID from path structure
491
+ if (p.includes('/subagents/')) {
492
+ const subagentsDir = path.dirname(p);
493
+ const sessionDir = path.dirname(subagentsDir);
494
+ const sessionID = path.basename(sessionDir);
495
+ const agentID = path.basename(p).replace(/^agent-/, '').replace(/\.jsonl$/, '');
496
+ const session = this.sessions.get(sessionID);
497
+ if (!session) return null;
498
+ return { sessionID, agentID };
499
+ }
500
+
501
+ // Main session file: infer sessionID from filename
502
+ const basename = path.basename(p);
503
+ const sessionID = basename.replace(/\.jsonl$/, '');
504
+ const session = this.sessions.get(sessionID);
505
+ if (!session) return null;
506
+ return { sessionID, agentID: '' };
507
+ }
508
+
479
509
  // =========================================================================
480
510
  // New session handlers
481
511
  // =========================================================================
@@ -500,6 +530,12 @@ class Watcher extends EventEmitter {
500
530
  this.emit('broadcast', 'newAgent', { sessionID: session.id, agentID, agentType });
501
531
  }
502
532
 
533
+ // Read initial data from the new session's files
534
+ if (this.useFsnotify) {
535
+ await this._skipToEndOfFiles(session);
536
+ await this._readSessionFiles(session);
537
+ }
538
+
503
539
  // Process any subagent files that arrived before the session was discovered
504
540
  const pending = this.pendingSubagents.get(session.id);
505
541
  if (pending) {
@@ -537,6 +573,13 @@ class Watcher extends EventEmitter {
537
573
 
538
574
  this._addFileWatch(p, sessionID, agentID);
539
575
  this.emit('broadcast', 'newAgent', { sessionID, agentID, agentType });
576
+
577
+ // Read initial data from the new subagent file
578
+ if (this.useFsnotify) {
579
+ const pos = await this._findPositionForLastNLines(p, KeepRecentLines);
580
+ this.filePositions.set(p, pos);
581
+ await this._readFile(p, sessionID, agentID, agentType);
582
+ }
540
583
  }
541
584
 
542
585
  async _handleNewToolResultFile(p) {
@@ -610,6 +653,8 @@ class Watcher extends EventEmitter {
610
653
 
611
654
  if (this.useFsnotify) {
612
655
  this._registerSessionWatches(c.session);
656
+ await this._skipToEndOfFiles(c.session);
657
+ await this._readSessionFiles(c.session);
613
658
  }
614
659
 
615
660
  this.emit('broadcast', 'newSession', { sessionID: c.session.id, projectPath: c.session.projectPath });
@@ -914,15 +959,16 @@ class Watcher extends EventEmitter {
914
959
  newPos = pos;
915
960
  // Read in chunks to avoid large buffer allocations for big file deltas
916
961
  let carryOver = ''; // incomplete trailing line from previous chunk
962
+ let carryOverBytes = 0; // byte length of carryOver (to avoid re-reading it)
917
963
  const buf = Buffer.alloc(MaxReadChunk);
918
964
 
919
965
  while (true) {
920
966
  const currentStats = await handle.stat();
921
- const currentPos = this.filePositions.get(filePath) || 0;
922
- if (currentPos >= currentStats.size) break;
967
+ const readFrom = newPos + carryOverBytes;
968
+ if (readFrom >= currentStats.size) break;
923
969
 
924
- const readLen = Math.min(MaxReadChunk, currentStats.size - currentPos);
925
- const { bytesRead } = await handle.read(buf, 0, readLen, currentPos);
970
+ const readLen = Math.min(MaxReadChunk, currentStats.size - readFrom);
971
+ const { bytesRead } = await handle.read(buf, 0, readLen, readFrom);
926
972
  if (bytesRead === 0) break;
927
973
 
928
974
  const chunk = bytesRead < readLen ? buf.toString('utf-8', 0, bytesRead) : buf.toString('utf-8');
@@ -939,9 +985,11 @@ class Watcher extends EventEmitter {
939
985
  // Save it as carryOver for the next chunk; don't process it yet.
940
986
  if (!chunk.endsWith('\n')) {
941
987
  carryOver = rawLines.pop();
988
+ carryOverBytes = Buffer.byteLength(carryOver, 'utf-8');
942
989
  } else {
943
990
  // chunk ends with \n — split produces a trailing empty string; clear carryOver
944
991
  carryOver = '';
992
+ carryOverBytes = 0;
945
993
  }
946
994
 
947
995
  let chunkBytes = 0;