lynkr 9.9.0 → 9.10.0
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/README.md +92 -23
- package/bin/cli.js +13 -7
- package/bin/lynkr-init.js +34 -12
- package/bin/lynkr-reset.js +71 -0
- package/config/model-tiers.json +199 -52
- package/package.json +3 -3
- package/scripts/build-eval-set.js +256 -0
- package/scripts/mine-difficulty-anchors.js +288 -0
- package/scripts/validate-difficulty-classifier.js +144 -0
- package/scripts/validate-intent-anchors.js +186 -0
- package/src/api/providers-handler.js +0 -1
- package/src/api/router.js +292 -160
- package/src/clients/databricks.js +95 -6
- package/src/config/index.js +3 -43
- package/src/context/tool-result-compressor.js +883 -40
- package/src/orchestrator/index.js +117 -1235
- package/src/orchestrator/passthrough-stream.js +382 -0
- package/src/orchestrator/sse-transformer.js +408 -0
- package/src/routing/affinity-store.js +17 -3
- package/src/routing/classifier-setup.js +207 -0
- package/src/routing/complexity-analyzer.js +40 -4
- package/src/routing/difficulty-classifier.js +261 -0
- package/src/routing/index.js +70 -7
- package/src/routing/intent-score.js +132 -12
- package/src/routing/session-affinity.js +8 -1
- package/src/routing/side-channel-detector.js +103 -0
- package/src/server.js +20 -46
- package/src/agents/context-manager.js +0 -236
- package/src/agents/decomposition/dispatcher.js +0 -185
- package/src/agents/decomposition/gate.js +0 -136
- package/src/agents/decomposition/index.js +0 -183
- package/src/agents/decomposition/model-call.js +0 -75
- package/src/agents/decomposition/planner.js +0 -223
- package/src/agents/decomposition/synthesizer.js +0 -89
- package/src/agents/decomposition/telemetry.js +0 -55
- package/src/agents/definitions/loader.js +0 -653
- package/src/agents/executor.js +0 -457
- package/src/agents/index.js +0 -165
- package/src/agents/parallel-coordinator.js +0 -68
- package/src/agents/reflector.js +0 -331
- package/src/agents/skillbook.js +0 -331
- package/src/agents/store.js +0 -259
- package/src/edits/index.js +0 -171
- package/src/indexer/babel-parser.js +0 -213
- package/src/indexer/index.js +0 -1629
- package/src/indexer/navigation/index.js +0 -32
- package/src/indexer/navigation/providers/treeSitter.js +0 -36
- package/src/indexer/parser.js +0 -443
- package/src/tasks/store.js +0 -349
- package/src/tests/coverage.js +0 -173
- package/src/tests/index.js +0 -171
- package/src/tests/store.js +0 -213
- package/src/tools/agent-task.js +0 -145
- package/src/tools/code-mode.js +0 -304
- package/src/tools/decompose.js +0 -91
- package/src/tools/edits.js +0 -94
- package/src/tools/execution.js +0 -171
- package/src/tools/git.js +0 -1346
- package/src/tools/index.js +0 -306
- package/src/tools/indexer.js +0 -360
- package/src/tools/lazy-loader.js +0 -366
- package/src/tools/mcp-remote.js +0 -88
- package/src/tools/mcp.js +0 -116
- package/src/tools/process.js +0 -167
- package/src/tools/smart-selection.js +0 -180
- package/src/tools/stubs.js +0 -55
- package/src/tools/tasks.js +0 -260
- package/src/tools/tests.js +0 -132
- package/src/tools/tinyfish.js +0 -358
- package/src/tools/truncate.js +0 -106
- package/src/tools/web-client.js +0 -71
- package/src/tools/web.js +0 -415
- package/src/tools/workspace.js +0 -204
|
@@ -299,8 +299,16 @@ async function invokeAzureAnthropic(body, incomingHeaders = {}) {
|
|
|
299
299
|
}
|
|
300
300
|
|
|
301
301
|
// OAuth passthrough: prefer incoming Bearer token (Claude Pro/Max subscription)
|
|
302
|
-
// over a configured API key
|
|
303
|
-
|
|
302
|
+
// over a configured API key — but ONLY when it is actually an Anthropic
|
|
303
|
+
// token (sk-ant-*). OpenAI-shape clients (Codex, goose) send their own
|
|
304
|
+
// provider's Bearer, and forwarding that to api.anthropic.com earns a
|
|
305
|
+
// guaranteed 401 "Invalid bearer token" (live incident 2026-07-21, Codex
|
|
306
|
+
// via tier fallback).
|
|
307
|
+
let incomingAuth = incomingHeaders?.authorization || incomingHeaders?.Authorization;
|
|
308
|
+
if (incomingAuth && incomingAuth.startsWith("Bearer ") && !incomingAuth.slice("Bearer ".length).startsWith("sk-ant-")) {
|
|
309
|
+
logger.debug({ tokenShape: incomingAuth.slice(7, 15) + "…" }, "Azure Anthropic: ignoring non-Anthropic bearer token from client");
|
|
310
|
+
incomingAuth = null;
|
|
311
|
+
}
|
|
304
312
|
|
|
305
313
|
// Headers Anthropic uses to verify client identity for subscription OAuth tokens.
|
|
306
314
|
// If we strip these, Anthropic returns 429 rate_limit_error with no rate-limit
|
|
@@ -1374,13 +1382,37 @@ async function invokeLlamaCpp(body, incomingHeaders = {}) {
|
|
|
1374
1382
|
headers["Authorization"] = `Bearer ${config.llamacpp.apiKey}`;
|
|
1375
1383
|
}
|
|
1376
1384
|
|
|
1377
|
-
|
|
1385
|
+
let messages = convertAnthropicMessagesToOpenRouter(body.messages || []);
|
|
1378
1386
|
|
|
1379
1387
|
if (body.system) {
|
|
1380
1388
|
messages.unshift({ role: "system", content: body.system });
|
|
1381
1389
|
}
|
|
1382
1390
|
|
|
1383
|
-
//
|
|
1391
|
+
// Fold EVERY system-role message into one leading system message. Claude
|
|
1392
|
+
// Code 2.1.216+ sends system-role entries inside `messages` (a leading one
|
|
1393
|
+
// plus a trailing billing-header-tagged one), and strict Jinja chat
|
|
1394
|
+
// templates (Qwen-family, e.g. Ornith) raise "System message must be at
|
|
1395
|
+
// the beginning" for any non-leading system message. Folding preserves
|
|
1396
|
+
// the content the old consecutive-dedup used to silently DROP.
|
|
1397
|
+
const systemParts = [];
|
|
1398
|
+
const nonSystem = [];
|
|
1399
|
+
for (const msg of messages) {
|
|
1400
|
+
if (msg.role === "system") {
|
|
1401
|
+
const text = typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content);
|
|
1402
|
+
if (text && text.trim()) systemParts.push(text);
|
|
1403
|
+
} else {
|
|
1404
|
+
nonSystem.push(msg);
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
if (systemParts.length > 0) {
|
|
1408
|
+
nonSystem.unshift({ role: "system", content: systemParts.join("\n\n") });
|
|
1409
|
+
if (systemParts.length > 1) {
|
|
1410
|
+
logger.debug({ folded: systemParts.length }, "llama.cpp: folded system messages into one leading system message");
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
messages = nonSystem;
|
|
1414
|
+
|
|
1415
|
+
// Deduplicate consecutive messages with same role (llama.cpp rejects this)
|
|
1384
1416
|
const deduplicated = [];
|
|
1385
1417
|
let lastRole = null;
|
|
1386
1418
|
for (const msg of messages) {
|
|
@@ -1460,7 +1492,20 @@ async function invokeLlamaCpp(body, incomingHeaders = {}) {
|
|
|
1460
1492
|
}))
|
|
1461
1493
|
}, "=== LLAMA.CPP REQUEST ===");
|
|
1462
1494
|
|
|
1463
|
-
|
|
1495
|
+
const result = await performJsonRequest(endpoint, { headers, body: llamacppBody }, "llama.cpp");
|
|
1496
|
+
|
|
1497
|
+
// Context overflow is a CAPACITY error, not a request error: the local
|
|
1498
|
+
// model's slot is simply too small for this conversation (llama-server
|
|
1499
|
+
// splits -c across --parallel slots). Returning the 400 as-is fails the
|
|
1500
|
+
// request AND records a provider "success" (nothing threw), so the
|
|
1501
|
+
// tier-fallback ladder never rescues it. Throw instead — same pattern as
|
|
1502
|
+
// _throwIfOllamaError — so invokeModel escalates to a bigger-context tier.
|
|
1503
|
+
const _llamaErrMsg = result?.json?.error?.message || "";
|
|
1504
|
+
if (!result?.ok && /exceeds the available context size/i.test(_llamaErrMsg)) {
|
|
1505
|
+
throw new Error(`llama.cpp context overflow: ${_llamaErrMsg}`);
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
return result;
|
|
1464
1509
|
}
|
|
1465
1510
|
|
|
1466
1511
|
async function invokeLMStudio(body, incomingHeaders = {}) {
|
|
@@ -2681,7 +2726,13 @@ async function invokeModel(body, options = {}) {
|
|
|
2681
2726
|
_queryText: body._queryText ?? null,
|
|
2682
2727
|
}
|
|
2683
2728
|
: await determineProviderSmart(body, { workspace, tenantPolicy });
|
|
2684
|
-
|
|
2729
|
+
// Canonicalize the provider name ONCE. Tier configs write z.ai as "z-ai";
|
|
2730
|
+
// every dispatch case, breaker, and health key below uses "zai". Without
|
|
2731
|
+
// this the dispatch chain falls through to the Databricks default — whose
|
|
2732
|
+
// base is a deliberate fail-fast dummy port in tier-routing setups — so
|
|
2733
|
+
// every "z-ai" tier request died with fetch's bad-port blocklist while
|
|
2734
|
+
// z.ai itself was perfectly healthy (live incident 2026-07-21/22).
|
|
2735
|
+
const initialProvider = routingResult.provider === "z-ai" ? "zai" : routingResult.provider;
|
|
2685
2736
|
const tierSelectedModel = routingResult.model;
|
|
2686
2737
|
|
|
2687
2738
|
// Inject tier-selected model into body so provider functions can use it
|
|
@@ -2984,6 +3035,21 @@ async function invokeModel(body, options = {}) {
|
|
|
2984
3035
|
healthTracker.recordFailure(initialProvider, err, err.status);
|
|
2985
3036
|
getLatencyTracker().record(initialProvider, routingDecision?.model, failLatency);
|
|
2986
3037
|
|
|
3038
|
+
// undici wraps every network failure as "TypeError: fetch failed" — the
|
|
3039
|
+
// actionable detail (ENOTFOUND vs ECONNRESET vs ETIMEDOUT vs TLS) lives
|
|
3040
|
+
// in err.cause. Without this line the z-ai breaker trips on repeated
|
|
3041
|
+
// "fetch failed" with nothing to diagnose (live incidents 2026-07-21/22:
|
|
3042
|
+
// ~5ms failures from a long-lived process while fresh processes succeed).
|
|
3043
|
+
logger.warn({
|
|
3044
|
+
provider: initialProvider,
|
|
3045
|
+
model: routingDecision?.model ?? null,
|
|
3046
|
+
error: err.message,
|
|
3047
|
+
causeCode: err.cause?.code ?? null,
|
|
3048
|
+
causeMessage: err.cause?.message ?? null,
|
|
3049
|
+
causeErrors: Array.isArray(err.cause?.errors) ? err.cause.errors.map((e) => e.code || e.message).slice(0, 4) : null,
|
|
3050
|
+
failLatencyMs: failLatency,
|
|
3051
|
+
}, "Provider invocation failed");
|
|
3052
|
+
|
|
2987
3053
|
// Tier-aware escalate-then-demote fallback (TIER_FALLBACK_ENABLED).
|
|
2988
3054
|
// On failure, try a MORE capable tier first (climb toward REASONING); only
|
|
2989
3055
|
// if every higher tier is unavailable do we fall downward to SIMPLE/local.
|
|
@@ -3011,9 +3077,29 @@ async function invokeModel(body, options = {}) {
|
|
|
3011
3077
|
_cascadeInner: true,
|
|
3012
3078
|
workspace,
|
|
3013
3079
|
tenantPolicy,
|
|
3080
|
+
// Forward the client's headers: azure-anthropic in wrap mode
|
|
3081
|
+
// authenticates with the incoming OAuth Bearer token, so a
|
|
3082
|
+
// fallback attempt without headers is guaranteed to fail with
|
|
3083
|
+
// "requires authentication".
|
|
3084
|
+
headers: incomingHeaders,
|
|
3014
3085
|
}
|
|
3015
3086
|
);
|
|
3016
3087
|
|
|
3088
|
+
// A candidate that answers with an error is a FAILED candidate,
|
|
3089
|
+
// not a serve. Provider clients return {ok:false} on 4xx instead
|
|
3090
|
+
// of throwing, and serving that here hands the client a 401/403
|
|
3091
|
+
// while healthier rungs below never get tried (live incident
|
|
3092
|
+
// 2026-07-21: Codex request served azure-anthropic's 401 while
|
|
3093
|
+
// llamacpp sat idle one rung down).
|
|
3094
|
+
if (attempt?.ok === false) {
|
|
3095
|
+
logger.warn({
|
|
3096
|
+
toProvider: cand.provider,
|
|
3097
|
+
status: attempt.status,
|
|
3098
|
+
error: attempt.json?.error?.message || attempt.text?.slice(0, 120) || null,
|
|
3099
|
+
}, "[TierFallback] Candidate answered with an error response, trying next");
|
|
3100
|
+
continue;
|
|
3101
|
+
}
|
|
3102
|
+
|
|
3017
3103
|
metricsCollector.recordFallbackAttempt(initialProvider, cand.provider, "tier_fallback");
|
|
3018
3104
|
logger.warn({
|
|
3019
3105
|
servedTier: cand.tier,
|
|
@@ -3370,6 +3456,9 @@ function destroyHttpAgents() {
|
|
|
3370
3456
|
|
|
3371
3457
|
module.exports = {
|
|
3372
3458
|
invokeModel,
|
|
3459
|
+
invokeAzureAnthropic,
|
|
3460
|
+
invokeZai,
|
|
3461
|
+
invokeOllama,
|
|
3373
3462
|
stripLynkrBadges,
|
|
3374
3463
|
destroyHttpAgents,
|
|
3375
3464
|
normalizeBodyForConverse,
|
package/src/config/index.js
CHANGED
|
@@ -179,14 +179,6 @@ if (!SUPPORTED_MODEL_PROVIDERS.has(rawFallbackProvider)) {
|
|
|
179
179
|
|
|
180
180
|
const fallbackProvider = rawFallbackProvider;
|
|
181
181
|
|
|
182
|
-
// Tool execution mode: server (default), client, or passthrough
|
|
183
|
-
const toolExecutionMode = (process.env.TOOL_EXECUTION_MODE ?? "server").toLowerCase();
|
|
184
|
-
if (!["server", "client", "passthrough"].includes(toolExecutionMode)) {
|
|
185
|
-
throw new Error(
|
|
186
|
-
"TOOL_EXECUTION_MODE must be one of: server, client, passthrough (default: server)"
|
|
187
|
-
);
|
|
188
|
-
}
|
|
189
|
-
|
|
190
182
|
// Memory system configuration (Titans-inspired long-term memory)
|
|
191
183
|
const memoryEnabled = process.env.MEMORY_ENABLED !== "false"; // default true
|
|
192
184
|
const memoryRetrievalLimit = Number.parseInt(process.env.MEMORY_RETRIEVAL_LIMIT ?? "5", 10);
|
|
@@ -201,7 +193,6 @@ const memoryDecayHalfLifeDays = Number.parseInt(process.env.MEMORY_DECAY_HALF_LI
|
|
|
201
193
|
|
|
202
194
|
// Token optimization settings
|
|
203
195
|
const tokenTrackingEnabled = process.env.TOKEN_TRACKING_ENABLED !== "false"; // default true
|
|
204
|
-
const toolTruncationEnabled = process.env.TOOL_TRUNCATION_ENABLED !== "false"; // default true
|
|
205
196
|
const memoryFormat = (process.env.MEMORY_FORMAT ?? "compact").toLowerCase();
|
|
206
197
|
const memoryDedupEnabled = process.env.MEMORY_DEDUP_ENABLED !== "false"; // default true
|
|
207
198
|
const memoryDedupLookback = Number.parseInt(process.env.MEMORY_DEDUP_LOOKBACK ?? "5", 10);
|
|
@@ -526,14 +517,9 @@ const testProfiles = parseJson(process.env.WORKSPACE_TEST_PROFILES ?? "", null);
|
|
|
526
517
|
|
|
527
518
|
// Agents configuration
|
|
528
519
|
const agentsEnabled = process.env.AGENTS_ENABLED === "true";
|
|
529
|
-
const agentsMaxConcurrent = Number.parseInt(process.env.AGENTS_MAX_CONCURRENT ?? "10", 10);
|
|
530
|
-
const agentsDefaultModel = process.env.AGENTS_DEFAULT_MODEL ?? "haiku";
|
|
531
|
-
const agentsMaxSteps = Number.parseInt(process.env.AGENTS_MAX_STEPS ?? "15", 10);
|
|
532
|
-
const agentsTimeout = Number.parseInt(process.env.AGENTS_TIMEOUT ?? "120000", 10);
|
|
533
520
|
|
|
534
521
|
// Task decomposition configuration (opt-in; builds on the agents subsystem).
|
|
535
522
|
// Single env toggle; everything else is hardcoded below.
|
|
536
|
-
const taskDecompositionEnabled = process.env.TASK_DECOMPOSITION_ENABLED === "true";
|
|
537
523
|
|
|
538
524
|
// Tier-aware fallback (escalate-then-demote) is always on (hardcoded).
|
|
539
525
|
|
|
@@ -667,7 +653,6 @@ var config = {
|
|
|
667
653
|
openRouterMaxToolsForRouting,
|
|
668
654
|
fallbackProvider,
|
|
669
655
|
},
|
|
670
|
-
toolExecutionMode,
|
|
671
656
|
toolResultCompression: {
|
|
672
657
|
enabled: true,
|
|
673
658
|
},
|
|
@@ -777,10 +762,6 @@ var config = {
|
|
|
777
762
|
manifestPath: sandboxManifestPath,
|
|
778
763
|
manifestDirs: sandboxManifestDirs,
|
|
779
764
|
},
|
|
780
|
-
codeMode: {
|
|
781
|
-
enabled: process.env.CODE_MODE_ENABLED === 'true',
|
|
782
|
-
toolListCacheTtl: parseInt(process.env.CODE_MODE_CACHE_TTL, 10) || 60_000,
|
|
783
|
-
},
|
|
784
765
|
},
|
|
785
766
|
promptCache: {
|
|
786
767
|
enabled: promptCacheEnabled,
|
|
@@ -794,25 +775,10 @@ var config = {
|
|
|
794
775
|
ttlMs: Number.parseInt(process.env.SEMANTIC_CACHE_TTL_MS ?? "300000", 10), // 5 minutes (was 1 hour)
|
|
795
776
|
},
|
|
796
777
|
agents: {
|
|
778
|
+
// Sole remaining knob: gates the agent-delegation instructions injected
|
|
779
|
+
// into the system prompt (the CLIENT's Task tool). Execution-related
|
|
780
|
+
// agent config died with server-mode tool execution.
|
|
797
781
|
enabled: agentsEnabled,
|
|
798
|
-
maxConcurrent: Number.isNaN(agentsMaxConcurrent) ? 10 : agentsMaxConcurrent,
|
|
799
|
-
defaultModel: agentsDefaultModel,
|
|
800
|
-
maxSteps: Number.isNaN(agentsMaxSteps) ? 15 : agentsMaxSteps,
|
|
801
|
-
timeout: Number.isNaN(agentsTimeout) ? 120000 : agentsTimeout,
|
|
802
|
-
},
|
|
803
|
-
taskDecomposition: {
|
|
804
|
-
enabled: taskDecompositionEnabled, // only env-driven knob (TASK_DECOMPOSITION_ENABLED)
|
|
805
|
-
// Hardcoded defaults — tune here if needed.
|
|
806
|
-
shadow: false,
|
|
807
|
-
planModel: "sonnet",
|
|
808
|
-
synthModel: "sonnet",
|
|
809
|
-
minConfidence: 0.5,
|
|
810
|
-
gate: {
|
|
811
|
-
minComplexity: 60,
|
|
812
|
-
minTokens: 3000,
|
|
813
|
-
maxSubtasks: 6,
|
|
814
|
-
minIndependentUnits: 2,
|
|
815
|
-
},
|
|
816
782
|
},
|
|
817
783
|
tierFallback: {
|
|
818
784
|
enabled: true, // always on (hardcoded): escalate-then-demote on provider failure; floor = SIMPLE
|
|
@@ -851,9 +817,6 @@ var config = {
|
|
|
851
817
|
tokenTracking: {
|
|
852
818
|
enabled: tokenTrackingEnabled,
|
|
853
819
|
},
|
|
854
|
-
toolTruncation: {
|
|
855
|
-
enabled: toolTruncationEnabled,
|
|
856
|
-
},
|
|
857
820
|
systemPrompt: {
|
|
858
821
|
mode: systemPromptMode,
|
|
859
822
|
toolDescriptions: toolDescriptions,
|
|
@@ -1092,9 +1055,6 @@ function reloadConfig() {
|
|
|
1092
1055
|
// Graphify
|
|
1093
1056
|
config.codeGraph.enabled = process.env.CODE_GRAPH_ENABLED === 'true';
|
|
1094
1057
|
|
|
1095
|
-
// Code Mode
|
|
1096
|
-
config.mcp.codeMode.enabled = process.env.CODE_MODE_ENABLED === 'true';
|
|
1097
|
-
|
|
1098
1058
|
// Log level
|
|
1099
1059
|
config.logger.level = process.env.LOG_LEVEL ?? "info";
|
|
1100
1060
|
|