lynkr 7.2.3 → 7.2.4

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": "lynkr",
3
- "version": "7.2.3",
3
+ "version": "7.2.4",
4
4
  "description": "Self-hosted Claude Code & Cursor proxy with Databricks,AWS BedRock,Azure adapters, openrouter, Ollama,llamacpp,LM Studio, workspace tooling, and MCP integration.",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -8,9 +8,9 @@
8
8
  "lynkr-setup": "./scripts/setup.js"
9
9
  },
10
10
  "scripts": {
11
- "prestart": "docker compose --profile headroom up -d headroom 2>/dev/null || echo 'Headroom container not started (Docker may not be running)'",
11
+ "prestart": "node -e \"if(process.env.HEADROOM_ENABLED==='true'&&process.env.HEADROOM_DOCKER_ENABLED!=='false'){process.exit(0)}else{process.exit(1)}\" && docker compose --profile headroom up -d headroom 2>/dev/null || echo 'Headroom skipped (disabled or Docker not running)'",
12
12
  "start": "node index.js 2>&1 | npx pino-pretty --sync",
13
- "stop": "docker compose --profile headroom down",
13
+ "stop": "node -e \"if(process.env.HEADROOM_ENABLED==='true'&&process.env.HEADROOM_DOCKER_ENABLED!=='false'){process.exit(0)}else{process.exit(1)}\" && docker compose --profile headroom down || echo 'Headroom skipped (disabled or Docker not running)'",
14
14
  "dev": "nodemon index.js",
15
15
  "lint": "eslint src index.js",
16
16
  "test": "npm run test:unit && npm run test:performance",
@@ -10,7 +10,7 @@ const DEFAULT_CONFIG = {
10
10
  backoffMultiplier: 2,
11
11
  jitterFactor: 0.1, // 10% jitter
12
12
  retryableStatuses: [429, 500, 502, 503, 504],
13
- retryableErrors: ['ECONNRESET', 'ETIMEDOUT', 'ENOTFOUND', 'ENETUNREACH'],
13
+ retryableErrors: ['ECONNRESET', 'ETIMEDOUT', 'ENOTFOUND', 'ENETUNREACH', 'ECONNREFUSED'],
14
14
  };
15
15
 
16
16
  /**
@@ -44,6 +44,11 @@ function isRetryable(error, response, config) {
44
44
  return true;
45
45
  }
46
46
 
47
+ // Check nested cause (Node undici wraps connection errors as TypeError)
48
+ if (error && error.cause?.code && config.retryableErrors.includes(error.cause.code)) {
49
+ return true;
50
+ }
51
+
47
52
  // Check for network errors
48
53
  if (error && (error.name === 'FetchError' || error.name === 'AbortError')) {
49
54
  return true;
@@ -1694,7 +1694,33 @@ IMPORTANT TOOL USAGE RULES:
1694
1694
  });
1695
1695
  }
1696
1696
 
1697
- const databricksResponse = await invokeModel(cleanPayload);
1697
+ let databricksResponse;
1698
+ try {
1699
+ databricksResponse = await invokeModel(cleanPayload);
1700
+ } catch (modelError) {
1701
+ const isConnectionError = modelError.cause?.code === 'ECONNREFUSED'
1702
+ || modelError.message?.includes('fetch failed')
1703
+ || modelError.code === 'ECONNREFUSED';
1704
+ if (isConnectionError) {
1705
+ logger.error(`Provider ${providerType} is unreachable (connection refused). Is it running?`);
1706
+ return {
1707
+ response: {
1708
+ status: 503,
1709
+ body: {
1710
+ error: {
1711
+ type: "provider_unreachable",
1712
+ message: `Provider ${providerType} is unreachable. Is the service running?`,
1713
+ },
1714
+ },
1715
+ terminationReason: "provider_unreachable",
1716
+ },
1717
+ steps,
1718
+ durationMs: Date.now() - start,
1719
+ terminationReason: "provider_unreachable",
1720
+ };
1721
+ }
1722
+ throw modelError;
1723
+ }
1698
1724
 
1699
1725
  // Extract and log actual token usage
1700
1726
  const actualUsage = databricksResponse.ok && config.tokenTracking?.enabled !== false
@@ -9,6 +9,9 @@
9
9
 
10
10
  const logger = require('../logger');
11
11
 
12
+ // Strip system-reminder blocks injected by the CLI before classification
13
+ const SYSTEM_REMINDER_PATTERN = /<system-reminder>[\s\S]*?<\/system-reminder>/g;
14
+
12
15
  // Pre-compiled regex patterns for performance (avoid recompiling on every request)
13
16
  const GREETING_PATTERN = /^(hi|hello|hey|good morning|good afternoon|good evening|howdy|greetings|sup|yo)[\s\.\!\?]*$/i;
14
17
  const QUESTION_PATTERN = /^(what is|what's|how does|when|where|why|explain|define|tell me about|can you explain)/i;
@@ -190,7 +193,10 @@ function classifyRequestType(payload) {
190
193
  return { type: 'coding', confidence: 0.5, keywords: [] };
191
194
  }
192
195
 
193
- const content = extractContent(lastMessage);
196
+ const rawContent = extractContent(lastMessage);
197
+ // Strip <system-reminder> blocks before classification to prevent
198
+ // CLI-injected keywords (search, explain, documentation) from polluting results
199
+ const content = rawContent.replace(SYSTEM_REMINDER_PATTERN, '').trim();
194
200
  const contentLower = content.toLowerCase();
195
201
  const messageCount = payload.messages?.length ?? 0;
196
202