specmem-hardwicksoftware 3.7.32 → 3.7.33

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 CHANGED
@@ -574,6 +574,19 @@ class LocalEmbeddingProvider {
574
574
  // Reset timeout only for the specific request that received data
575
575
  if (requestId && this.pendingRequests.has(requestId)) {
576
576
  const pending = this.pendingRequests.get(requestId);
577
+ // Check hard deadline BEFORE resetting idle timeout
578
+ if (pending.hardDeadline && Date.now() >= pending.hardDeadline) {
579
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'INIT_PERSISTENT_SOCKET_HARD_DEADLINE_HIT', {
580
+ requestId,
581
+ elapsedMs: Date.now() - pending.createdAt
582
+ });
583
+ clearTimeout(pending.timeout);
584
+ if (pending.hardDeadlineTimer) clearTimeout(pending.hardDeadlineTimer);
585
+ this.pendingRequests.delete(requestId);
586
+ pending.reject(new Error(`Embedding hard deadline exceeded during message processing. ` +
587
+ `Socket: ${this.sandboxSocketPath}.`));
588
+ continue;
589
+ }
577
590
  const timeoutMs = this.getAdaptiveTimeout();
578
591
  clearTimeout(pending.timeout);
579
592
  pending.timeout = setTimeout(() => {
@@ -582,6 +595,7 @@ class LocalEmbeddingProvider {
582
595
  requestId,
583
596
  timeoutMs
584
597
  });
598
+ if (pending.hardDeadlineTimer) clearTimeout(pending.hardDeadlineTimer);
585
599
  this.pendingRequests.delete(requestId);
586
600
  pending.reject(new Error(`Embedding idle timeout after ${Math.round(timeoutMs / 1000)}s of no activity. ` +
587
601
  `Socket: ${this.sandboxSocketPath}. ` +
@@ -606,6 +620,7 @@ class LocalEmbeddingProvider {
606
620
  if (requestId && this.pendingRequests.has(requestId)) {
607
621
  const pending = this.pendingRequests.get(requestId);
608
622
  clearTimeout(pending.timeout);
623
+ if (pending.hardDeadlineTimer) clearTimeout(pending.hardDeadlineTimer);
609
624
  this.pendingRequests.delete(requestId);
610
625
  if (response.error) {
611
626
  __debugLog('[EMBEDDING DEBUG]', Date.now(), 'INIT_PERSISTENT_SOCKET_RESOLVING_ERROR', {
@@ -663,6 +678,7 @@ class LocalEmbeddingProvider {
663
678
  error: err.message
664
679
  });
665
680
  clearTimeout(pending.timeout);
681
+ if (pending.hardDeadlineTimer) clearTimeout(pending.hardDeadlineTimer);
666
682
  pending.reject(new Error(`Socket error: ${err.message}`));
667
683
  }
668
684
  this.pendingRequests.clear();
@@ -688,6 +704,7 @@ class LocalEmbeddingProvider {
688
704
  requestId
689
705
  });
690
706
  clearTimeout(pending.timeout);
707
+ if (pending.hardDeadlineTimer) clearTimeout(pending.hardDeadlineTimer);
691
708
  pending.reject(new Error('Socket closed'));
692
709
  }
693
710
  this.pendingRequests.clear();
@@ -3008,6 +3025,26 @@ class LocalEmbeddingProvider {
3008
3025
  `Cold starts may need longer timeout - set SPECMEM_EMBEDDING_TIMEOUT=60 for 60 seconds.`));
3009
3026
  }
3010
3027
  }, timeoutMs);
3028
+ // Hard deadline: 3 minutes absolute maximum, can NOT be reset by heartbeats
3029
+ const HARD_DEADLINE_MS = parseInt(process.env['SPECMEM_EMBEDDING_HARD_DEADLINE_MS'] || '180000', 10);
3030
+ const hardDeadline = Date.now() + HARD_DEADLINE_MS;
3031
+ const hardDeadlineTimer = setTimeout(() => {
3032
+ if (this.pendingRequests.has(requestId)) {
3033
+ __debugLog('[EMBEDDING DEBUG]', Date.now(), 'PERSISTENT_SOCKET_HARD_DEADLINE_EXCEEDED', {
3034
+ attempt,
3035
+ requestId,
3036
+ hardDeadlineMs: HARD_DEADLINE_MS,
3037
+ totalElapsedMs: Date.now() - methodStart
3038
+ });
3039
+ const pending = this.pendingRequests.get(requestId);
3040
+ clearTimeout(pending.timeout);
3041
+ this.pendingRequests.delete(requestId);
3042
+ reject(new Error(`Embedding hard deadline exceeded (${Math.round(HARD_DEADLINE_MS / 1000)}s). ` +
3043
+ `Heartbeats kept resetting idle timeout but no embedding was returned. ` +
3044
+ `Socket: ${this.sandboxSocketPath}. ` +
3045
+ `Increase SPECMEM_EMBEDDING_HARD_DEADLINE_MS if model genuinely needs more time.`));
3046
+ }
3047
+ }, HARD_DEADLINE_MS);
3011
3048
  // Store pending request
3012
3049
  this.pendingRequests.set(requestId, {
3013
3050
  resolve: (embedding) => {
@@ -3019,11 +3056,17 @@ class LocalEmbeddingProvider {
3019
3056
  embeddingDim: embedding.length,
3020
3057
  totalElapsedMs: Date.now() - methodStart
3021
3058
  });
3059
+ clearTimeout(hardDeadlineTimer); // Clear hard deadline on success
3022
3060
  this.recordResponseTime(responseTime);
3023
3061
  resolve(embedding);
3024
3062
  },
3025
- reject,
3063
+ reject: (err) => {
3064
+ clearTimeout(hardDeadlineTimer); // Clear hard deadline on any rejection
3065
+ reject(err);
3066
+ },
3026
3067
  timeout,
3068
+ hardDeadlineTimer,
3069
+ hardDeadline,
3027
3070
  createdAt: Date.now() // FIX Issue #6: Track creation time for stale cleanup
3028
3071
  });
3029
3072
  // Send request with ID so we can match responses
@@ -393,7 +393,10 @@ function fixProjectMcpConfigs() {
393
393
  // Write back if modified
394
394
  if (modified) {
395
395
  try {
396
- fs.writeFileSync(CLAUDE_JSON_PATH, JSON.stringify(claudeJson, null, 2));
396
+ // ATOMIC WRITE: write to temp then rename to prevent corruption
397
+ const tmpPath = CLAUDE_JSON_PATH + '.tmp.' + process.pid;
398
+ fs.writeFileSync(tmpPath, JSON.stringify(claudeJson, null, 2));
399
+ fs.renameSync(tmpPath, CLAUDE_JSON_PATH);
397
400
  logger.info({ path: CLAUDE_JSON_PATH, projectsFixed: fixed }, '[ConfigInjector] Updated project-level MCP configs');
398
401
  }
399
402
  catch (err) {
@@ -284,7 +284,10 @@ export async function configureHooks() {
284
284
  // Also create .claude.json with acceptEditsModeAccepted
285
285
  const claudeJsonPath = path.join(claudeDir, '.claude.json');
286
286
  const claudeJson = { acceptEditsModeAccepted: true };
287
- fs.writeFileSync(claudeJsonPath, JSON.stringify(claudeJson, null, 2));
287
+ // ATOMIC WRITE: write to temp then rename to prevent corruption
288
+ const tmpPath = claudeJsonPath + '.tmp.' + process.pid;
289
+ fs.writeFileSync(tmpPath, JSON.stringify(claudeJson, null, 2));
290
+ fs.renameSync(tmpPath, claudeJsonPath);
288
291
  logger.info('GOD MODE: acceptEditsModeAccepted enabled');
289
292
  // Add SpecMem tool permissions if not already present
290
293
  const specmemPermissions = [