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.
Files changed (73) hide show
  1. package/README.md +92 -23
  2. package/bin/cli.js +13 -7
  3. package/bin/lynkr-init.js +34 -12
  4. package/bin/lynkr-reset.js +71 -0
  5. package/config/model-tiers.json +199 -52
  6. package/package.json +3 -3
  7. package/scripts/build-eval-set.js +256 -0
  8. package/scripts/mine-difficulty-anchors.js +288 -0
  9. package/scripts/validate-difficulty-classifier.js +144 -0
  10. package/scripts/validate-intent-anchors.js +186 -0
  11. package/src/api/providers-handler.js +0 -1
  12. package/src/api/router.js +292 -160
  13. package/src/clients/databricks.js +95 -6
  14. package/src/config/index.js +3 -43
  15. package/src/context/tool-result-compressor.js +883 -40
  16. package/src/orchestrator/index.js +117 -1235
  17. package/src/orchestrator/passthrough-stream.js +382 -0
  18. package/src/orchestrator/sse-transformer.js +408 -0
  19. package/src/routing/affinity-store.js +17 -3
  20. package/src/routing/classifier-setup.js +207 -0
  21. package/src/routing/complexity-analyzer.js +40 -4
  22. package/src/routing/difficulty-classifier.js +261 -0
  23. package/src/routing/index.js +70 -7
  24. package/src/routing/intent-score.js +132 -12
  25. package/src/routing/session-affinity.js +8 -1
  26. package/src/routing/side-channel-detector.js +103 -0
  27. package/src/server.js +20 -46
  28. package/src/agents/context-manager.js +0 -236
  29. package/src/agents/decomposition/dispatcher.js +0 -185
  30. package/src/agents/decomposition/gate.js +0 -136
  31. package/src/agents/decomposition/index.js +0 -183
  32. package/src/agents/decomposition/model-call.js +0 -75
  33. package/src/agents/decomposition/planner.js +0 -223
  34. package/src/agents/decomposition/synthesizer.js +0 -89
  35. package/src/agents/decomposition/telemetry.js +0 -55
  36. package/src/agents/definitions/loader.js +0 -653
  37. package/src/agents/executor.js +0 -457
  38. package/src/agents/index.js +0 -165
  39. package/src/agents/parallel-coordinator.js +0 -68
  40. package/src/agents/reflector.js +0 -331
  41. package/src/agents/skillbook.js +0 -331
  42. package/src/agents/store.js +0 -259
  43. package/src/edits/index.js +0 -171
  44. package/src/indexer/babel-parser.js +0 -213
  45. package/src/indexer/index.js +0 -1629
  46. package/src/indexer/navigation/index.js +0 -32
  47. package/src/indexer/navigation/providers/treeSitter.js +0 -36
  48. package/src/indexer/parser.js +0 -443
  49. package/src/tasks/store.js +0 -349
  50. package/src/tests/coverage.js +0 -173
  51. package/src/tests/index.js +0 -171
  52. package/src/tests/store.js +0 -213
  53. package/src/tools/agent-task.js +0 -145
  54. package/src/tools/code-mode.js +0 -304
  55. package/src/tools/decompose.js +0 -91
  56. package/src/tools/edits.js +0 -94
  57. package/src/tools/execution.js +0 -171
  58. package/src/tools/git.js +0 -1346
  59. package/src/tools/index.js +0 -306
  60. package/src/tools/indexer.js +0 -360
  61. package/src/tools/lazy-loader.js +0 -366
  62. package/src/tools/mcp-remote.js +0 -88
  63. package/src/tools/mcp.js +0 -116
  64. package/src/tools/process.js +0 -167
  65. package/src/tools/smart-selection.js +0 -180
  66. package/src/tools/stubs.js +0 -55
  67. package/src/tools/tasks.js +0 -260
  68. package/src/tools/tests.js +0 -132
  69. package/src/tools/tinyfish.js +0 -358
  70. package/src/tools/truncate.js +0 -106
  71. package/src/tools/web-client.js +0 -71
  72. package/src/tools/web.js +0 -415
  73. package/src/tools/workspace.js +0 -204
package/README.md CHANGED
@@ -68,7 +68,7 @@ The fastest path is the interactive wizard:
68
68
  lynkr init
69
69
  ```
70
70
 
71
- It asks four questions — usage mode (Claude Pro/Max via wrap, or direct API keys), tier picks for SIMPLE/MEDIUM/COMPLEX/REASONING across the 12 supported providers, credentials for what you chose, and a few routing-intelligence knobs — then writes a fully-populated `.env` with sensible production defaults for everything else (caching, compression, policy budgets, MCP sandbox, agents, rate limiting).
71
+ It asks four questions — usage mode (Claude Pro/Max via wrap, or direct API keys), tier picks for SIMPLE/MEDIUM/COMPLEX/REASONING across the 12 supported providers, credentials for what you chose, and a few routing-intelligence knobs — then writes a fully-populated `.env` with sensible production defaults for everything else (caching, compression, policy budgets, MCP sandbox, rate limiting).
72
72
 
73
73
  Useful flags:
74
74
 
@@ -246,9 +246,9 @@ POLICY_MAX_STEPS=50
246
246
  POLICY_MAX_TOOL_CALLS=100
247
247
  ```
248
248
 
249
- Lynkr analyzes each request and routes it to the appropriate tier. Simple questions use fast models. Complex refactoring uses powerful models.
249
+ Lynkr analyzes each request and routes it to the appropriate tier. Simple questions use fast models. Complex refactoring uses powerful models. The scorer combines anchor-embedding classification (WS7, payload-invariant) with an LLM difficulty classifier (Phase 6, added 2026-07-19) that catches topic-vs-difficulty confounding — `list the exports from this file` correctly routes to MEDIUM instead of getting boosted to REASONING by "technical vocabulary" alone.
250
250
 
251
- **Result:** 70-90% of requests use cheaper/faster models. Only hard problems hit expensive models.
251
+ **Result:** 70-90% of requests use cheaper/faster models. Only hard problems hit expensive models. **15× reduction in expensive-tier over-routing** vs the anchor-only baseline on the 381-prompt eval set (0.6% vs ~15%).
252
252
 
253
253
  Tier configuration is strictly authoritative — bandit exploration is constrained to the models you've listed in `TIER_*`, and multi-turn conversations score with a recency-weighted sliding window so context isn't lost on short follow-ups. Conversations get a content-fingerprint session id (clients like Claude Code send none), the decision pins for the session, and a guarded escape ladder (risk keywords, force phrases, score drift, context overflow) re-escalates the moment a task outgrows its model. Full pipeline: [`docs/routing-intelligence.md`](docs/routing-intelligence.md) · intent scorer: [`docs/intent-window-routing.md`](docs/intent-window-routing.md) · verify any change: [`docs/benchmarking.md`](docs/benchmarking.md).
254
254
 
@@ -271,17 +271,39 @@ Tier configuration is strictly authoritative — bandit exploration is constrain
271
271
 
272
272
  ## Advanced Features
273
273
 
274
+ ### Real-time SSE streaming (all tiers)
275
+
276
+ Responses stream token-by-token instead of arriving all at once — including
277
+ through the tier router. Two mechanisms, both on by default:
278
+
279
+ - **Native passthrough**: when the upstream already speaks Anthropic SSE
280
+ (Anthropic endpoints, Z.AI, Ollama v0.14+), Lynkr pipes the bytes straight
281
+ through with backpressure. Kill switch: `LYNKR_NATIVE_PASSTHROUGH=false`.
282
+ - **Cross-format transform**: OpenAI-format upstreams (OpenAI, Azure OpenAI,
283
+ OpenRouter, Databricks, llama.cpp, LM Studio) are reshaped into Anthropic
284
+ events in flight — including reassembling tool-call argument fragments into
285
+ clean `tool_use` blocks. Kill switch: `LYNKR_STREAM_TRANSFORM=false`.
286
+
287
+ The `LYNKR_VISIBLE_ROUTING` badge streams too (injected as the first content
288
+ block), and telemetry is recorded on stream close with real token counts.
289
+ Fallback safety: if an upstream fails before the first byte, the request
290
+ falls back to the buffered path automatically.
291
+
292
+ ```bash
293
+ # Ollama thinking models (MiniMax): streaming skips the <think>-leak repair.
294
+ # If you see raw <think> text in responses, buffer that provider instead:
295
+ LYNKR_OLLAMA_BUFFER_RESPONSES=true # default true; false = stream Ollama
296
+ ```
297
+
274
298
  ### Token Optimization (60-80% savings)
275
299
  ```bash
276
300
  # Enable all optimizations
277
301
  PROMPT_CACHE_ENABLED=true
278
302
  SEMANTIC_CACHE_ENABLED=true
279
- TOOL_INJECTION_ENABLED=false
280
- CODE_MODE_ENABLED=true
281
303
  ```
282
304
 
283
- Always-on (no config): **smart tool selection** (server mode), **RTK tool-result
284
- compression** (test/git/grep/lint/build/JSON output), **MCP tool dedup** (drops
305
+ Always-on (no config): **RTK tool-result compression**
306
+ (test/git/grep/lint/build/JSON output), **MCP tool dedup** (drops
285
307
  built-in WebSearch/WebFetch when an Exa/Tavily MCP tool is present), and
286
308
  **request bypass** (Claude CLI Warmup / title-extraction calls are answered
287
309
  locally, never hitting a provider).
@@ -292,6 +314,25 @@ CAVEMAN_ENABLED=true # off by default — nudges the model to be concise
292
314
  CAVEMAN_LEVEL=lite # lite | full | ultra
293
315
  ```
294
316
 
317
+ ### Built-in dashboard
318
+
319
+ Open `http://localhost:8081/dashboard` while Lynkr is running. No setup, reads
320
+ the local telemetry store:
321
+
322
+ - **Spend & savings** — actual cost vs what the same traffic would have cost
323
+ on the flagship model (the counterfactual no pass-through gateway can show,
324
+ since they neither pick your model nor see local-model traffic)
325
+ - **Tier mix** — daily request breakdown across SIMPLE/MEDIUM/COMPLEX/REASONING
326
+ - **Routing accuracy** — over-/under-provisioned request counts, a self-audit
327
+ of the tier router's decisions
328
+ - **Request logs** — filterable by provider, tier, and errors, with latency,
329
+ tokens, and cost per request
330
+ - **Provider health** — configured providers, credential warnings, circuit
331
+ breaker states
332
+
333
+ JSON APIs behind it (`/dashboard/api/overview|usage|routing|logs`) if you want
334
+ the raw numbers.
335
+
295
336
  ### Cost tracking & model pricing
296
337
  Per-request cost is computed from a model-pricing registry (LiteLLM → models.dev,
297
338
  cached 24h) and recorded in telemetry. Models the registry doesn't know record
@@ -413,16 +454,15 @@ npm start
413
454
 
414
455
  Head-to-head against **LiteLLM** on the **same backends** (Ollama `minimax-m2.5`, Moonshot, Azure OpenAI), 9 scenarios across 4 feature categories. Apples-to-apples comparison is Lynkr vs LiteLLM **billed tokens on the same scenario**. Run with `node benchmark-tier-routing.js`.
415
456
 
416
- > _Run: June 5, 2026 · Lynkr v9.3.2 · LiteLLM v1.87.1 · macOS, Apple Silicon._
457
+ > _Runs: token benchmarks — June 5, 2026 (Lynkr v9.3.2 · LiteLLM v1.87.1). Tier routing head-to-head — **re-run July 19, 2026** (Lynkr working tree w/ Phase-6 classifier + config B · LiteLLM v1.94.0.dev1 Auto Router v2). macOS, Apple Silicon._
417
458
 
418
459
  ### Token reduction (vs LiteLLM, same model & prompt)
419
460
 
420
461
  | Mechanism | Lynkr | LiteLLM | Result |
421
462
  |---|---|---|---|
422
- | Smart tool selection (14 tools) | **959** tokens · $0.0044 | 2,085 tokens · $0.0091 | **53% fewer tokens, 52% cheaper** |
423
463
  | TOON compression (60-item grep JSON) | **427** tokens · $0.009 | 3,458 tokens · $0.018 | **87.6% fewer tokens, 50% cheaper** |
424
464
 
425
- Lynkr strips irrelevant tool schemas (smart tool selection) and binary-compresses large JSON tool results (TOON) — both in-process, no added latency.
465
+ Lynkr binary-compresses large JSON tool results (TOON) in-process, with no added latency.
426
466
 
427
467
  ### Semantic cache
428
468
 
@@ -433,28 +473,56 @@ Lynkr strips irrelevant tool schemas (smart tool selection) and binary-compresse
433
473
 
434
474
  Near-identical prompts return cached responses in 171ms. Zero model tokens billed on a cache hit.
435
475
 
436
- ### Tier routing — vs LiteLLM Auto Router v2 (July 15, 2026)
476
+ ### Tier routing — vs LiteLLM Auto Router v2 (re-run July 19, 2026)
437
477
 
438
- LiteLLM v1.94 shipped a native complexity router (`auto_router/complexity_router`) with the same four tier names Lynkr uses. Head-to-head on the **same backends** with identical prompts, both proxies judged against the same acceptable-tier sets (11 routing scenarios, `MODE=routing node benchmark-tier-routing.js`, LiteLLM config in `litellm-autorouter-v2.yaml`):
478
+ LiteLLM v1.94 shipped a native complexity router (`auto_router/complexity_router`) with the same four tier names Lynkr uses. Head-to-head on the **same backends** with identical prompts, both proxies live, both judged against the same acceptable-tier sets (11 routing scenarios, `MODE=routing node benchmark-tier-routing.js`, LiteLLM config in `litellm-autorouter-v2.yaml`):
439
479
 
440
- | Router | Routing-correct | Routing overhead |
480
+ | Router | Routing-correct | Notes |
441
481
  |---|---|---|
442
- | **Lynkr** | **11/11** | local embedding, ~0 marginal cost/latency after cache |
443
- | LiteLLM v2 — heuristic (default) | 4/11 | <1ms, free — but every miss under-routed hard work (banking security analysis, autonomous agentic loop) to the free local model |
444
- | LiteLLM v2 — LLM classifier | 6–8/11 (non-deterministic across runs) | a paid GPT-5.2 call + ~2–3s on **every** request; fails outright with local classifier models (structured-output errors → silent heuristic fallback) |
482
+ | **Lynkr (Phase-6 classifier + config B)** | **11/11 ✅** | anchor embedding + local LLM classifier reconcile + FORCE_REASONING patterns + risk-remap; ~500ms warm classifier call, cached |
483
+ | LiteLLM v2 — heuristic (default) | **4/11** | every miss *under-routed* banking security analysis, whole-pipeline refactor, prod auth-file fix, autonomous agentic loop all sent to a 7B local model |
484
+ | LiteLLM v2 — LLM classifier | 6–8/11 (non-deterministic) | paid GPT-5.2 call + ~2–3s on **every** request; fails outright with local classifier models |
485
+
486
+ **The 7 requests LiteLLM v2 misroutes to a 7B ollama model (should be COMPLEX or REASONING):**
487
+
488
+ | What you typed | Lynkr sends to | LiteLLM sends to |
489
+ |---|---|---|
490
+ | "Analyse security trade-offs of JWT vs httpOnly cookies for a banking app" | Claude Opus (top) | **ollama 7B** ❌ |
491
+ | "Refactor the entire ingestion pipeline and give me the plan" | z.ai GLM-5.2 (mid) | **ollama 7B** ❌ |
492
+ | "Fix the null-check bug in `src/auth/middleware.ts`" | Claude Opus (risk-remap) | **ollama 7B** ❌ |
493
+ | "Figure out why this test is flaky. You have full autonomy — iterate until 10 runs pass" | Claude Opus (autonomous) | **ollama 7B** ❌ |
494
+ | "please summarize the exports of this file quickly" | ollama minimax (correct — MEDIUM) | ollama minimax — but for the wrong reason (LiteLLM under-routed to SIMPLE) |
495
+
496
+ Lynkr layers the following, in decreasing priority, to catch every miss LiteLLM leaks:
445
497
 
446
- Lynkr scores cleaned user-authored text against embedding anchors plus 13 weighted heuristic dimensions, detects agentic workflows, and strips harness-injected noise before scoring. LiteLLM's router reads the raw last message and has no verify-then-escalate cascade — its fallbacks trigger only on HTTP errors, never on a bad answer.
498
+ - FORCE_REASONING regex (`ultrathink`, `prove`, `security audit`, `from first principles`) deterministic top-tier
499
+ - Risk classifier (auth/middleware/credentials paths) → REASONING under config B
500
+ - Agentic detector (`AUTONOMOUS` workflows) → REASONING minimum
501
+ - Anchor-embedding classifier over 4 difficulty classes + FRONTIER_MIN_SIM floor
502
+ - Local LLM classifier (qwen2.5:3b) reconciles borderline anchor calls
503
+ - Envelope invariance — scores cleaned user text only, ignoring tool schemas / history / system-reminders
447
504
 
448
- _Fairness notes: the 11 scenarios derive from Lynkr's own regression suite, so Lynkr has home-field advantage — the transferable finding is the direction of LiteLLM's failures (systematic under-routing on defaults; per-request cost and instability with the LLM classifier), not the exact scores. Lynkr's top tiers used Azure gpt-5.2-chat / Z.ai GLM-5.2; LiteLLM was given the identical tier targets._
505
+ LiteLLM's router reads the raw last message with no envelope-stripping, no verify-then-escalate cascade, no risk classifier, no agentic detection its fallbacks trigger only on HTTP errors, never on a bad answer.
506
+
507
+ _Fairness notes: the 11 scenarios derive from Lynkr's own regression suite, so Lynkr has home-field advantage — the transferable finding is the **direction** of LiteLLM's failures (systematic under-routing to cheap on hard prompts), not the exact scores. Lynkr's top tiers used Claude Opus 4.8 / z.ai GLM-5.2; LiteLLM was given the identical tier targets._
449
508
 
450
509
  ### Cost projection (100,000 requests/month, same backend)
451
510
 
511
+ **Direct cost (raw meter):**
512
+
452
513
  | | Monthly cost | vs LiteLLM |
453
514
  |---|---|---|
454
- | LiteLLM | ~$818 | baseline |
455
- | **Lynkr** | **~$409** | **~50% cheaper** |
515
+ | LiteLLM (TOON tool-heavy scenario) | ~$818 | baseline |
516
+ | **Lynkr (TOON tool-heavy)** | **~$409** | **~50% cheaper** via token optimization |
517
+
518
+ **Effective cost per correct-tier answer** (July 19 head-to-head, all 11 routing scenarios):
519
+
520
+ | | Direct $/mo | Effective $/mo if you re-issue misroutes at correct tier |
521
+ |---|---|---|
522
+ | **Lynkr** | ~$5.50 (spends where it should, doesn't where it shouldn't) | ~$5.50 |
523
+ | LiteLLM v2 heuristic | $0 (under-routes everything to ollama) | ~$89 (7 misroutes need re-issue on Moonshot/COMPLEX) |
456
524
 
457
- _Based on a tool-heavy agentic session (TOON scenario). On equal footing same provider, same modelLynkr is cheaper due to token optimization._
525
+ The story isn't "Lynkr always spends less." It's **Lynkr spends where it should and doesn't where it shouldn't**. LiteLLM's zero-dollar cost on complex prompts is bought with wrong-tier answers to hard worka 7B model answering "prove this rate limiter is fair under concurrent refill" is silent quality collapse, not a savings.
458
526
 
459
527
  → [Full benchmark report with methodology](BENCHMARK_REPORT.md)
460
528
 
@@ -481,10 +549,11 @@ With tier routing + token optimization: **additional 50-87% savings** on cloud p
481
549
  | **Local models** | Ollama, llama.cpp, LM Studio | Ollama only | ❌ | ❌ |
482
550
  | **Automatic tier routing** | ✅ embedding intent + 13-dimension scorer, verified cascade | ⚠️ Auto Router v2 (v1.94): heuristic under-routes, LLM classifier billed per request | ❌ | ❌ Manual metadata |
483
551
  | **TOON JSON compression** | ✅ up to 87.6% | ❌ | ❌ | ❌ |
484
- | **Smart tool selection** | ✅ up to 60% token reduction | | | |
552
+ | **Upstream SSE streaming** | ✅ native passthrough + cross-format transform | ⚠️ passthrough only | | ⚠️ |
485
553
  | **Semantic cache** | ✅ 171ms hits, 0 tokens | ❌ | ❌ | ✅ Prompt cache only |
554
+ | **Savings & routing dashboard** | ✅ spend, savings vs flagship, tier mix, routing accuracy, request logs | ⚠️ spend UI only | ⚠️ usage page | ✅ observability suite (no routing accuracy) |
486
555
  | **Long-term memory** | ✅ SQLite, per-session | ❌ | ❌ | ❌ |
487
- | **MCP integration** | ✅ + Code Mode (96% reduction) | ❌ | ❌ | ❌ |
556
+ | **MCP integration** | ✅ | ❌ | ❌ | ❌ |
488
557
  | **Self-hosted** | ✅ Node.js only | ✅ Python stack | ❌ SaaS | ✅ Docker |
489
558
  | **Dependencies** | Node.js 20+ | Python, Prisma, PostgreSQL | None | Docker, Python |
490
559
 
package/bin/cli.js CHANGED
@@ -11,24 +11,27 @@ const SUBCOMMANDS = {
11
11
  trajectory: path.join(__dirname, "lynkr-trajectory.js"),
12
12
  wrap: path.join(__dirname, "wrap.js"),
13
13
  init: path.join(__dirname, "lynkr-init.js"),
14
+ reset: path.join(__dirname, "lynkr-reset.js"),
14
15
  };
15
16
 
16
17
  const sub = process.argv[2];
17
- if (sub && Object.prototype.hasOwnProperty.call(SUBCOMMANDS, sub)) {
18
+ // `lynkr start` is an alias for `lynkr` (start the proxy). Drop the token
19
+ // and fall through so users can use whichever spelling they prefer.
20
+ if (sub === 'start') {
21
+ process.argv.splice(2, 1);
22
+ } else if (sub && Object.prototype.hasOwnProperty.call(SUBCOMMANDS, sub)) {
18
23
  process.argv.splice(2, 1); // drop the subcommand token so the script's own arg parser is happy
19
24
  // Subcommand scripts check this to decide whether to invoke their main()
20
25
  // when they're require()'d (vs being loaded by a test for unit-checking).
21
26
  process.env._LYNKR_SUBCMD = sub;
22
27
  require(SUBCOMMANDS[sub]);
23
28
  return;
24
- }
25
-
26
- // Unknown positional commands must error loudly, not silently boot the
27
- // server (`lynkr codex` used to start a bare gateway and never say why).
28
- if (sub && !sub.startsWith('-')) {
29
+ } else if (sub && !sub.startsWith('-')) {
30
+ // Unknown positional commands must error loudly, not silently boot the
31
+ // server (`lynkr codex` used to start a bare gateway and never say why).
29
32
  console.error(`Error: unknown command '${sub}'.`);
30
33
  console.error(`Did you mean: lynkr wrap ${sub}`);
31
- console.error(`Known commands: ${Object.keys(SUBCOMMANDS).join(', ')} (or no command to start the server)`);
34
+ console.error(`Known commands: ${Object.keys(SUBCOMMANDS).join(', ')}, start (or no command to start the server)`);
32
35
  process.exit(1);
33
36
  }
34
37
 
@@ -45,10 +48,13 @@ ${pkg.description}
45
48
 
46
49
  Usage:
47
50
  lynkr [options] Start the proxy server (default)
51
+ lynkr start [options] Alias for the above
52
+ lynkr init [options] Interactive setup wizard (writes .env, pulls classifier model)
48
53
  lynkr wrap <target> [options] Wrap CLI tools through Lynkr proxy
49
54
  lynkr usage [options] Show AI spend report and tier-routing savings
50
55
  lynkr stats [options] Shareable savings-receipt card (also: lynkr usage --card)
51
56
  lynkr trajectory [options] Export agent trajectories as JSONL training data
57
+ lynkr reset <resource> Clear a stored resource (e.g. session_pins, all)
52
58
 
53
59
  Options:
54
60
  -h, --help Show this help message
package/bin/lynkr-init.js CHANGED
@@ -197,12 +197,9 @@ const BASELINE_ENV = {
197
197
  LYNKR_KNN_CONFIDENCE_HIGH: '0.55',
198
198
  LYNKR_KNN_CONFIDENCE_LOW: '0.30',
199
199
 
200
- // ── Tool execution ────────────────────────────────────────────────────
201
- TOOL_EXECUTION_MODE: 'client',
202
- TOOL_INJECTION_ENABLED: 'false',
200
+ // ── Tool selection (routing signals) ──────────────────────────────────
203
201
  SMART_TOOL_SELECTION_MODE: 'disabled',
204
202
  SMART_TOOL_SELECTION_TOKEN_BUDGET: '2500',
205
- CODE_MODE_ENABLED: 'true',
206
203
 
207
204
  // ── Caching ───────────────────────────────────────────────────────────
208
205
  PROMPT_CACHE_ENABLED: 'true',
@@ -253,7 +250,6 @@ const BASELINE_ENV = {
253
250
  MEMORY_DEDUP_LOOKBACK: '5',
254
251
  MEMORY_TTL: '3600000',
255
252
  TOKEN_TRACKING_ENABLED: 'true',
256
- TOOL_TRUNCATION_ENABLED: 'true',
257
253
 
258
254
  // ── Prompt/output shaping ─────────────────────────────────────────────
259
255
  SYSTEM_PROMPT_MODE: 'dynamic',
@@ -280,12 +276,8 @@ const BASELINE_ENV = {
280
276
  POLICY_FILE_BLOCKED_PATHS: '/.env,.env,/etc/passwd,/etc/shadow',
281
277
  POLICY_SAFE_COMMANDS_ENABLED: 'true',
282
278
 
283
- // ── Agents ────────────────────────────────────────────────────────────
279
+ // ── Agents (delegation prompt injection; execution is client-side) ────
284
280
  AGENTS_ENABLED: 'true',
285
- AGENTS_MAX_CONCURRENT: '10',
286
- AGENTS_DEFAULT_MODEL: 'haiku',
287
- AGENTS_MAX_STEPS: '15',
288
- AGENTS_TIMEOUT: '300000',
289
281
 
290
282
  // ── Rate limiting ─────────────────────────────────────────────────────
291
283
  RATE_LIMIT_ENABLED: 'true',
@@ -550,6 +542,36 @@ async function runInteractive(opts) {
550
542
  close();
551
543
  console.log('');
552
544
  writeEnvFile(buildEnvContent(env, isWrap, tierConfig), opts);
545
+
546
+ // ── Classifier bootstrap: detect ollama, pull model, warm-up ──
547
+ // Runs AFTER .env is written so the classifier can read tier config
548
+ // (currently uses hardcoded qwen2.5:3b, but leaves room for the future
549
+ // fine-tuned model to plug in via SIMPLE tier or an env override).
550
+ if (!opts.dryRun) {
551
+ console.log('');
552
+ console.log('Setting up the difficulty classifier…');
553
+ try {
554
+ const { ensureClassifierReady } = require('../src/routing/classifier-setup');
555
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
556
+ const prompt = (q) => new Promise((res) => rl.question(q, (a) => res(a)));
557
+ const result = await ensureClassifierReady({
558
+ mode: 'interactive',
559
+ log: (m) => console.log(m),
560
+ warn: (m) => console.warn(m),
561
+ prompt,
562
+ });
563
+ rl.close();
564
+ if (result.ready) {
565
+ console.log('✓ Classifier ready.');
566
+ } else if (result.reason === 'ollama_missing') {
567
+ console.log('(Install Ollama and re-run `lynkr init` to enable the classifier — Lynkr will still start without it, using anchor-only scoring.)');
568
+ } else {
569
+ console.log(`(Classifier disabled: ${result.reason}. Lynkr will fall back to anchor-only scoring.)`);
570
+ }
571
+ } catch (err) {
572
+ console.warn(`⚠ Classifier setup skipped: ${err.message}`);
573
+ }
574
+ }
553
575
  } catch (err) {
554
576
  close();
555
577
  throw err;
@@ -576,12 +598,12 @@ function buildEnvContent(env, isWrap, tierConfig) {
576
598
  // Group output by section in the order it appears in the generated file.
577
599
  // Mirrors the layout of the .env.example reference doc.
578
600
  const SERVER_KEYS = new Set(['PORT', 'NODE_ENV', 'REQUEST_JSON_LIMIT', 'SESSION_DB_PATH', 'WORKSPACE_ROOT', 'ENABLE_TOOL_SEARCH']);
579
- const TOOL_EXEC_KEYS = new Set(['TOOL_EXECUTION_MODE', 'TOOL_INJECTION_ENABLED', 'SMART_TOOL_SELECTION_MODE', 'SMART_TOOL_SELECTION_TOKEN_BUDGET', 'CODE_MODE_ENABLED']);
601
+ const TOOL_EXEC_KEYS = new Set(['SMART_TOOL_SELECTION_MODE', 'SMART_TOOL_SELECTION_TOKEN_BUDGET']);
580
602
  const CACHE_KEYS = new Set([
581
603
  'PROMPT_CACHE_ENABLED', 'PROMPT_CACHE_MAX_ENTRIES', 'PROMPT_CACHE_TTL_MS',
582
604
  'SEMANTIC_CACHE_ENABLED', 'SEMANTIC_CACHE_THRESHOLD', 'SEMANTIC_CACHE_MAX_ENTRIES', 'SEMANTIC_CACHE_TTL_MS',
583
605
  ]);
584
- const MEMORY_KEYS = new Set(Object.keys(merged).filter((k) => k.startsWith('MEMORY_') || k === 'TOKEN_TRACKING_ENABLED' || k === 'TOOL_TRUNCATION_ENABLED'));
606
+ const MEMORY_KEYS = new Set(Object.keys(merged).filter((k) => k.startsWith('MEMORY_') || k === 'TOKEN_TRACKING_ENABLED'));
585
607
  const SHAPING_KEYS = new Set([
586
608
  'SYSTEM_PROMPT_MODE', 'TOOL_DESCRIPTIONS',
587
609
  'HISTORY_COMPRESSION_ENABLED', 'HISTORY_KEEP_RECENT_TURNS', 'HISTORY_SUMMARIZE_OLDER',
@@ -0,0 +1,71 @@
1
+ #!/usr/bin/env node
2
+
3
+ const path = require("path");
4
+ const fs = require("fs");
5
+
6
+ const DB_PATH =
7
+ process.env.LYNKR_TELEMETRY_DB ||
8
+ path.join(__dirname, "..", ".lynkr", "telemetry.db");
9
+
10
+ const RESOURCES = {
11
+ session_pins: {
12
+ describe: "Session → provider affinity pins",
13
+ table: "session_pins",
14
+ },
15
+ };
16
+
17
+ function usage(code = 0) {
18
+ const lines = Object.entries(RESOURCES).map(
19
+ ([k, v]) => ` ${k.padEnd(16)} ${v.describe}`
20
+ );
21
+ console.log(
22
+ `Usage: lynkr reset <resource>
23
+
24
+ Resources:
25
+ ${lines.join("\n")}
26
+ all Clear every resource above
27
+
28
+ DB: ${DB_PATH}
29
+ `
30
+ );
31
+ process.exit(code);
32
+ }
33
+
34
+ const target = process.argv[2];
35
+ if (!target || target === "-h" || target === "--help") usage(0);
36
+
37
+ if (target !== "all" && !RESOURCES[target]) {
38
+ console.error(`Error: unknown resource '${target}'.`);
39
+ usage(1);
40
+ }
41
+
42
+ if (!fs.existsSync(DB_PATH)) {
43
+ console.error(`Error: telemetry DB not found at ${DB_PATH}`);
44
+ process.exit(1);
45
+ }
46
+
47
+ let Database;
48
+ try {
49
+ Database = require("better-sqlite3");
50
+ } catch (err) {
51
+ console.error("Error: better-sqlite3 not installed. Run `npm install` in ~/claude-code.");
52
+ process.exit(1);
53
+ }
54
+
55
+ const db = new Database(DB_PATH);
56
+ const targets = target === "all" ? Object.keys(RESOURCES) : [target];
57
+
58
+ let hadError = false;
59
+ for (const t of targets) {
60
+ const { table } = RESOURCES[t];
61
+ try {
62
+ const before = db.prepare(`SELECT COUNT(*) as n FROM ${table}`).get().n;
63
+ db.prepare(`DELETE FROM ${table}`).run();
64
+ console.log(`✓ cleared ${t} (${before} rows)`);
65
+ } catch (err) {
66
+ hadError = true;
67
+ console.error(`✗ ${t}: ${err.message}`);
68
+ }
69
+ }
70
+ db.close();
71
+ process.exit(hadError ? 1 : 0);