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
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,
|
|
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): **
|
|
284
|
-
|
|
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
|
-
>
|
|
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
|
|
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
|
|
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 |
|
|
480
|
+
| Router | Routing-correct | Notes |
|
|
441
481
|
|---|---|---|
|
|
442
|
-
| **Lynkr** | **11/11
|
|
443
|
-
| LiteLLM v2 — heuristic (default) | 4/11 |
|
|
444
|
-
| LiteLLM v2 — LLM classifier | 6–8/11 (non-deterministic
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 work — a 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
|
-
| **
|
|
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** | ✅
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
|
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(['
|
|
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'
|
|
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);
|