@cachly-dev/mcp-server 0.10.128 → 0.10.130
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 +2 -2
- package/dist/index.js +1 -7
- package/dist/packages/telegram-notify/client.js +2 -75
- package/dist/src/ambient-cli.js +7 -253
- package/dist/src/ambient-deps.js +1 -55
- package/dist/src/ambient-hooks.js +5 -278
- package/dist/src/ambient-ledger.js +6 -95
- package/dist/src/ambient-memory.js +1 -67
- package/dist/src/ambient-recall.js +1 -221
- package/dist/src/aussetzer.js +1 -82
- package/dist/src/auth.js +9 -140
- package/dist/src/autor-abzeichen.js +1 -56
- package/dist/src/autor.js +1 -136
- package/dist/src/bauabdruck.js +1 -93
- package/dist/src/bedeutung.js +2 -323
- package/dist/src/bench/cachly-bench.js +4 -249
- package/dist/src/bench/external-corpus.js +4 -135
- package/dist/src/bench/fixtures.js +1 -225
- package/dist/src/brain-export.js +16 -119
- package/dist/src/brain-watch-hook.js +8 -86
- package/dist/src/cachly-url.js +1 -21
- package/dist/src/ckg.js +1 -108
- package/dist/src/cls-hook.js +8 -94
- package/dist/src/confidence.js +1 -50
- package/dist/src/credentials.js +1 -85
- package/dist/src/deutsch.js +1 -223
- package/dist/src/doctor.js +2 -248
- package/dist/src/editor.js +1 -63
- package/dist/src/eingaenge-nachruesten.js +1 -290
- package/dist/src/eingaenge.js +2 -228
- package/dist/src/embeddings.js +13 -223
- package/dist/src/feldreparatur.js +3 -0
- package/dist/src/first-contact.js +4 -108
- package/dist/src/first-recall-proof.js +4 -41
- package/dist/src/funnel-milestones.js +2 -48
- package/dist/src/geliefert.js +1 -114
- package/dist/src/handlers/advanced.js +38 -537
- package/dist/src/handlers/brain.js +69 -4181
- package/dist/src/handlers/cache.js +59 -638
- package/dist/src/handlers/context.js +17 -182
- package/dist/src/handlers/fedbrain.js +40 -1436
- package/dist/src/handlers/instances.js +16 -201
- package/dist/src/handlers/roadmap.js +13 -175
- package/dist/src/handlers/share.js +19 -530
- package/dist/src/handlers/syndicate.js +38 -904
- package/dist/src/handlers/team.js +89 -1850
- package/dist/src/handlers/viz.js +3 -148
- package/dist/src/index.js +321 -4281
- package/dist/src/interferenz.js +1 -0
- package/dist/src/kostprobe.js +7 -125
- package/dist/src/lesson-preview.js +2 -133
- package/dist/src/namespace.js +1 -128
- package/dist/src/notifier.js +1 -6
- package/dist/src/rangfolge-stellschrauben.js +1 -0
- package/dist/src/rangfolge.js +1 -214
- package/dist/src/raumkorrektur.js +1 -197
- package/dist/src/recall-tiefe.js +1 -42
- package/dist/src/rerank.js +1 -154
- package/dist/src/resonanz.js +1 -0
- package/dist/src/search.js +1 -2104
- package/dist/src/seltenheitsbestand.js +1 -92
- package/dist/src/speicherlage.js +1 -84
- package/dist/src/spuren.js +1 -150
- package/dist/src/starter-corpus.js +1 -165
- package/dist/src/telemetry-types.js +0 -13
- package/dist/src/tools.js +10 -2465
- package/dist/src/toolspecs.js +1 -121
- package/dist/src/upgrade-nudge.js +1 -29
- package/dist/src/utils.js +1 -89
- package/dist/src/versuch.js +1 -250
- package/dist/src/vorspann.js +1 -146
- package/dist/src/wartung/vektoren-verdichten.js +1 -200
- package/dist/src/werkzeug-nutzung.js +1 -72
- package/dist/src/wertbeitrag.js +1 -73
- package/dist/src/wirkungsspur.js +1 -0
- package/glama.json +1 -1
- package/package.json +8 -7
- package/server.json +3 -3
- package/dist/index.d.ts +0 -1
- package/dist/index.js.map +0 -1
- package/dist/packages/telegram-notify/client.d.ts +0 -19
- package/dist/packages/telegram-notify/client.js.map +0 -1
- package/dist/src/ambient-cli.d.ts +0 -101
- package/dist/src/ambient-cli.js.map +0 -1
- package/dist/src/ambient-deps.d.ts +0 -26
- package/dist/src/ambient-deps.js.map +0 -1
- package/dist/src/ambient-hooks.d.ts +0 -137
- package/dist/src/ambient-hooks.js.map +0 -1
- package/dist/src/ambient-ledger.d.ts +0 -19
- package/dist/src/ambient-ledger.js.map +0 -1
- package/dist/src/ambient-memory.d.ts +0 -12
- package/dist/src/ambient-memory.js.map +0 -1
- package/dist/src/ambient-recall.d.ts +0 -127
- package/dist/src/ambient-recall.js.map +0 -1
- package/dist/src/aussetzer.d.ts +0 -67
- package/dist/src/aussetzer.js.map +0 -1
- package/dist/src/auth.d.ts +0 -43
- package/dist/src/auth.js.map +0 -1
- package/dist/src/autor-abzeichen.d.ts +0 -41
- package/dist/src/autor-abzeichen.js.map +0 -1
- package/dist/src/autor.d.ts +0 -101
- package/dist/src/autor.js.map +0 -1
- package/dist/src/bauabdruck-daten.generiert.json +0 -5
- package/dist/src/bauabdruck.d.ts +0 -38
- package/dist/src/bauabdruck.js.map +0 -1
- package/dist/src/bedeutung.d.ts +0 -168
- package/dist/src/bedeutung.js.map +0 -1
- package/dist/src/bench/cachly-bench.d.ts +0 -61
- package/dist/src/bench/cachly-bench.js.map +0 -1
- package/dist/src/bench/external-corpus.d.ts +0 -44
- package/dist/src/bench/external-corpus.js.map +0 -1
- package/dist/src/bench/fixtures.d.ts +0 -37
- package/dist/src/bench/fixtures.js.map +0 -1
- package/dist/src/brain-export.d.ts +0 -64
- package/dist/src/brain-export.js.map +0 -1
- package/dist/src/brain-watch-hook.d.ts +0 -21
- package/dist/src/brain-watch-hook.js.map +0 -1
- package/dist/src/cachly-url.d.ts +0 -68
- package/dist/src/cachly-url.js.map +0 -1
- package/dist/src/ckg.d.ts +0 -54
- package/dist/src/ckg.js.map +0 -1
- package/dist/src/cls-hook.d.ts +0 -15
- package/dist/src/cls-hook.js.map +0 -1
- package/dist/src/confidence.d.ts +0 -19
- package/dist/src/confidence.js.map +0 -1
- package/dist/src/credentials.d.ts +0 -37
- package/dist/src/credentials.js.map +0 -1
- package/dist/src/deutsch.d.ts +0 -98
- package/dist/src/deutsch.js.map +0 -1
- package/dist/src/doctor.d.ts +0 -47
- package/dist/src/doctor.js.map +0 -1
- package/dist/src/editor.d.ts +0 -9
- package/dist/src/editor.js.map +0 -1
- package/dist/src/eingaenge-nachruesten.d.ts +0 -41
- package/dist/src/eingaenge-nachruesten.js.map +0 -1
- package/dist/src/eingaenge.d.ts +0 -135
- package/dist/src/eingaenge.js.map +0 -1
- package/dist/src/embeddings.d.ts +0 -48
- package/dist/src/embeddings.js.map +0 -1
- package/dist/src/first-contact.d.ts +0 -56
- package/dist/src/first-contact.js.map +0 -1
- package/dist/src/first-recall-proof.d.ts +0 -19
- package/dist/src/first-recall-proof.js.map +0 -1
- package/dist/src/funnel-milestones.d.ts +0 -5
- package/dist/src/funnel-milestones.js.map +0 -1
- package/dist/src/geliefert.d.ts +0 -63
- package/dist/src/geliefert.js.map +0 -1
- package/dist/src/handlers/advanced.d.ts +0 -6
- package/dist/src/handlers/advanced.js.map +0 -1
- package/dist/src/handlers/brain.d.ts +0 -41
- package/dist/src/handlers/brain.js.map +0 -1
- package/dist/src/handlers/cache.d.ts +0 -6
- package/dist/src/handlers/cache.js.map +0 -1
- package/dist/src/handlers/context.d.ts +0 -6
- package/dist/src/handlers/context.js.map +0 -1
- package/dist/src/handlers/fedbrain.d.ts +0 -18
- package/dist/src/handlers/fedbrain.js.map +0 -1
- package/dist/src/handlers/instances.d.ts +0 -33
- package/dist/src/handlers/instances.js.map +0 -1
- package/dist/src/handlers/roadmap.d.ts +0 -6
- package/dist/src/handlers/roadmap.js.map +0 -1
- package/dist/src/handlers/share.d.ts +0 -8
- package/dist/src/handlers/share.js.map +0 -1
- package/dist/src/handlers/syndicate.d.ts +0 -6
- package/dist/src/handlers/syndicate.js.map +0 -1
- package/dist/src/handlers/team.d.ts +0 -45
- package/dist/src/handlers/team.js.map +0 -1
- package/dist/src/handlers/viz.d.ts +0 -6
- package/dist/src/handlers/viz.js.map +0 -1
- package/dist/src/index.d.ts +0 -93
- package/dist/src/index.js.map +0 -1
- package/dist/src/kostprobe.d.ts +0 -25
- package/dist/src/kostprobe.js.map +0 -1
- package/dist/src/lesson-preview.d.ts +0 -81
- package/dist/src/lesson-preview.js.map +0 -1
- package/dist/src/namespace.d.ts +0 -20
- package/dist/src/namespace.js.map +0 -1
- package/dist/src/notifier.d.ts +0 -5
- package/dist/src/notifier.js.map +0 -1
- package/dist/src/rangfolge.d.ts +0 -169
- package/dist/src/rangfolge.js.map +0 -1
- package/dist/src/raumkorrektur.d.ts +0 -100
- package/dist/src/raumkorrektur.js.map +0 -1
- package/dist/src/recall-tiefe.d.ts +0 -48
- package/dist/src/recall-tiefe.js.map +0 -1
- package/dist/src/rerank.d.ts +0 -48
- package/dist/src/rerank.js.map +0 -1
- package/dist/src/search.d.ts +0 -153
- package/dist/src/search.js.map +0 -1
- package/dist/src/seltenheitsbestand.d.ts +0 -53
- package/dist/src/seltenheitsbestand.js.map +0 -1
- package/dist/src/speicherlage.d.ts +0 -51
- package/dist/src/speicherlage.js.map +0 -1
- package/dist/src/spuren.d.ts +0 -97
- package/dist/src/spuren.js.map +0 -1
- package/dist/src/starter-corpus.d.ts +0 -28
- package/dist/src/starter-corpus.js.map +0 -1
- package/dist/src/telemetry-types.d.ts +0 -87
- package/dist/src/telemetry-types.js.map +0 -1
- package/dist/src/tools.d.ts +0 -2933
- package/dist/src/tools.js.map +0 -1
- package/dist/src/toolspecs.d.ts +0 -43
- package/dist/src/toolspecs.js.map +0 -1
- package/dist/src/upgrade-nudge.d.ts +0 -15
- package/dist/src/upgrade-nudge.js.map +0 -1
- package/dist/src/utils.d.ts +0 -34
- package/dist/src/utils.js.map +0 -1
- package/dist/src/versuch.d.ts +0 -179
- package/dist/src/versuch.js.map +0 -1
- package/dist/src/vorspann.d.ts +0 -73
- package/dist/src/vorspann.js.map +0 -1
- package/dist/src/wartung/vektoren-verdichten.d.ts +0 -50
- package/dist/src/wartung/vektoren-verdichten.js.map +0 -1
- package/dist/src/werkzeug-nutzung.d.ts +0 -62
- package/dist/src/werkzeug-nutzung.js.map +0 -1
- package/dist/src/wertbeitrag.d.ts +0 -57
- package/dist/src/wertbeitrag.js.map +0 -1
|
@@ -1,221 +1 @@
|
|
|
1
|
-
|
|
2
|
-
// push-based memory economically honest.
|
|
3
|
-
//
|
|
4
|
-
// Background (docs/make_cachly_great_again.md §6.2/§6.3): pushing recall into
|
|
5
|
-
// every prompt COSTS tokens; it only pays off when the injected lesson prevents
|
|
6
|
-
// a wrong path that would have cost far more. So the whole feature hinges on a
|
|
7
|
-
// sharp relevance gate (only inject high-signal, few, small) plus honest net
|
|
8
|
-
// accounting (injected vs prevented) with auto-backoff when it goes net-negative.
|
|
9
|
-
//
|
|
10
|
-
// This module is pure and dependency-free so it is exhaustively unit-tested; the
|
|
11
|
-
// hook scripts + telemetry layer build on top of it.
|
|
12
|
-
import { createHash } from 'node:crypto';
|
|
13
|
-
// Deliberately conservative defaults: it is far cheaper to skip a marginal
|
|
14
|
-
// lesson than to erode the "−60% tokens" credibility by injecting noise.
|
|
15
|
-
export const DEFAULT_GATE = {
|
|
16
|
-
minConfidence: 0.6,
|
|
17
|
-
minScore: 0.72,
|
|
18
|
-
topK: 3,
|
|
19
|
-
maxTokens: 240,
|
|
20
|
-
};
|
|
21
|
-
/**
|
|
22
|
-
* Derives a content-based candidate ID from a summary string. Enables
|
|
23
|
-
* ID-based deduplication to suppress "the same advice" while allowing
|
|
24
|
-
* "different advice" through. See PR #241.
|
|
25
|
-
*/
|
|
26
|
-
export function candidateIdFor(summary) {
|
|
27
|
-
const hash = createHash('sha256').update(summary).digest('hex');
|
|
28
|
-
return hash.slice(0, 16);
|
|
29
|
-
}
|
|
30
|
-
/** Rough token estimate (~4 chars/token) — good enough for a per-turn budget. */
|
|
31
|
-
export function estimateTokens(text) {
|
|
32
|
-
return Math.ceil(text.trim().length / 4);
|
|
33
|
-
}
|
|
34
|
-
// Pure conversational openers with no engineering payload.
|
|
35
|
-
const TRIVIAL_RE = /^(hi|hello|hey|thanks|thank you|ok|okay|yes|no|yep|nope|ty|lol|danke|hallo|servus)\b/i;
|
|
36
|
-
// Signals that a prompt is about code/ops, where a wrong path is expensive and a
|
|
37
|
-
// relevant lesson can pay for itself. Calibrated against the golden vectors in
|
|
38
|
-
// docs/spec/ambient-gate-vectors.json (ambient-gate-vectors.test.ts): the v1
|
|
39
|
-
// list false-skipped short real requests like "curl returns 403 via the proxy"
|
|
40
|
-
// or "rename the Settings page to Preferences" (substantive-recall 0.67), so
|
|
41
|
-
// v2 adds engineering verbs, infra nouns, HTTP status codes and German
|
|
42
|
-
// question words. Stems with \w* also match inflections ("migration",
|
|
43
|
-
// "updated", "evicted"). Substantive-recall on the vector set: 1.0, with
|
|
44
|
-
// trivial-precision unchanged at 1.0.
|
|
45
|
-
const CODEY_RE = /[/\\._{}()<>;=]|\b\d{3}\b|\b(fix|bug|error|deploy|migrat\w*|test\w*|build|refactor|auth|api|db|schema|race|crash|fail\w*|revert|rollback|why|how|debug|config|hook|token|cache|add|creat\w*|implement\w*|updat\w*|remov\w*|delet\w*|renam\w*|install\w*|upgrad\w*|write|script|component|page|button|css|style\w*|log\w*|pod\w*|node\w*|docker|k8s|kubernetes|proxy|curl|http\w*|readme|lint\w*|latenc\w*|latenz|timeout\w*|evict\w*|warum|wieso|weshalb)\b/i;
|
|
46
|
-
/**
|
|
47
|
-
* A trivial prompt has ~zero expected wrong-path savings (§6.3 guardrail 2):
|
|
48
|
-
* too short to carry risk, a pure greeting, or chit-chat with no code signal.
|
|
49
|
-
* Injecting there is pure waste, so we skip recall entirely.
|
|
50
|
-
*/
|
|
51
|
-
export function isTrivialPrompt(prompt) {
|
|
52
|
-
const p = prompt.trim();
|
|
53
|
-
if (p.length < 12)
|
|
54
|
-
return true;
|
|
55
|
-
if (TRIVIAL_RE.test(p) && p.length < 40)
|
|
56
|
-
return true;
|
|
57
|
-
if (!CODEY_RE.test(p) && p.length < 60)
|
|
58
|
-
return true;
|
|
59
|
-
return false;
|
|
60
|
-
}
|
|
61
|
-
/**
|
|
62
|
-
* The §6.3 relevance gate: decide what (if anything) to push into context for a
|
|
63
|
-
* given prompt. Applies trivial-skip, a confidence AND semantic-score floor,
|
|
64
|
-
* a top-K cap, and a hard per-turn token budget — strongest candidates first.
|
|
65
|
-
*/
|
|
66
|
-
export function selectInjectable(prompt, candidates, opts = {}) {
|
|
67
|
-
const o = { ...DEFAULT_GATE, ...opts };
|
|
68
|
-
if (isTrivialPrompt(prompt)) {
|
|
69
|
-
return { inject: false, reason: 'trivial-skip', selected: [], tokens: 0 };
|
|
70
|
-
}
|
|
71
|
-
const passed = candidates
|
|
72
|
-
.filter((c) => c.confidence >= o.minConfidence && c.score >= o.minScore)
|
|
73
|
-
.sort((a, b) => b.score - a.score || b.confidence - a.confidence);
|
|
74
|
-
const selected = [];
|
|
75
|
-
let tokens = 0;
|
|
76
|
-
for (const c of passed) {
|
|
77
|
-
if (selected.length >= o.topK)
|
|
78
|
-
break;
|
|
79
|
-
const t = estimateTokens(c.summary);
|
|
80
|
-
if (tokens + t > o.maxTokens)
|
|
81
|
-
break; // hard per-turn cap — never overshoot
|
|
82
|
-
selected.push(c);
|
|
83
|
-
tokens += t;
|
|
84
|
-
}
|
|
85
|
-
if (selected.length === 0) {
|
|
86
|
-
return { inject: false, reason: 'no-candidate-passed-gate', selected: [], tokens: 0 };
|
|
87
|
-
}
|
|
88
|
-
return { inject: true, reason: 'injected', selected, tokens };
|
|
89
|
-
}
|
|
90
|
-
export function netBalance(records) {
|
|
91
|
-
const injected = records.reduce((s, r) => s + r.injected, 0);
|
|
92
|
-
const prevented = records.reduce((s, r) => s + r.prevented, 0);
|
|
93
|
-
return { injected, prevented, net: prevented - injected };
|
|
94
|
-
}
|
|
95
|
-
/**
|
|
96
|
-
* Auto-backoff (§6.3 guardrail 3): if, over the recent window, Ambient Recall is
|
|
97
|
-
* net-negative, tell the caller to tighten the gate (or pause). Only fires once
|
|
98
|
-
* there is enough signal (>= minTurns) so early noise doesn't trip it.
|
|
99
|
-
*/
|
|
100
|
-
export function shouldBackoff(records, windowN = 20, minTurns = 8) {
|
|
101
|
-
const recent = records.slice(-windowN);
|
|
102
|
-
if (recent.length < minTurns)
|
|
103
|
-
return false;
|
|
104
|
-
return netBalance(recent).net < 0;
|
|
105
|
-
}
|
|
106
|
-
// Schritte, die man nicht zurücknehmen kann: hier ist eine verpasste Lektion
|
|
107
|
-
// teuer, ein überflüssiger Hinweis dagegen billig.
|
|
108
|
-
const RISKY_RE = /\b(deploy\w*|migrat\w*|rollback|revert|drop\s+(table|database|schema)|truncate|rm\s+-rf|force[- ]push|prune|restore|rotat\w*|secret\w*|credential\w*|production|prod|live|kunde\w*|customer\w*|delete|loesch\w*|lösch\w*)\b/i;
|
|
109
|
-
// Reine Bestätigungen/Fortsetzungen: hier beginnt keine neue Arbeit.
|
|
110
|
-
const CONTINUATION_RE = /^(ok(ay)?|ja|nein|weiter|passt|danke|thanks|go|los|mach|jup|yes|no|sure|next)\b[\s.!]*$/i;
|
|
111
|
-
/**
|
|
112
|
-
* Risikoeinschätzung des Auslösemoments. Sie verschiebt die Schwellen, statt
|
|
113
|
-
* eine zweite, konkurrierende Entscheidung zu treffen — eine Stellschraube
|
|
114
|
-
* weniger, und der Gate bleibt die einzige Wahrheit über Relevanz.
|
|
115
|
-
*/
|
|
116
|
-
export function promptRisk(prompt) {
|
|
117
|
-
const p = prompt.trim();
|
|
118
|
-
if (CONTINUATION_RE.test(p))
|
|
119
|
-
return 'low';
|
|
120
|
-
if (RISKY_RE.test(p))
|
|
121
|
-
return 'high';
|
|
122
|
-
return 'normal';
|
|
123
|
-
}
|
|
124
|
-
/** Schwellen-Anpassung je Risiko (klein gehalten und bewusst begründet). */
|
|
125
|
-
export function gateForRisk(risk, base = DEFAULT_GATE) {
|
|
126
|
-
if (risk === 'high') {
|
|
127
|
-
// Vor irreversiblen Schritten darf eine etwas schwächere Übereinstimmung
|
|
128
|
-
// reichen — die vermiedene Katastrophe ist jeden Token wert.
|
|
129
|
-
return { ...base, minScore: Math.max(0, base.minScore - 0.07), topK: base.topK, maxTokens: base.maxTokens + 120 };
|
|
130
|
-
}
|
|
131
|
-
if (risk === 'low') {
|
|
132
|
-
// Im Geplauder muss es sehr deutlich sein, sonst lieber schweigen.
|
|
133
|
-
return { ...base, minScore: Math.min(1, base.minScore + 0.1), topK: 1, maxTokens: Math.min(base.maxTokens, 120) };
|
|
134
|
-
}
|
|
135
|
-
return { ...base };
|
|
136
|
-
}
|
|
137
|
-
// Ein Einwurf pro ~3 Turns im Schnitt: oft genug, um zu helfen, selten genug,
|
|
138
|
-
// dass er auffällt, wenn er kommt.
|
|
139
|
-
export const DEFAULT_TIMING = {
|
|
140
|
-
repeatCooldownTurns: 12,
|
|
141
|
-
maxInjectionsPerWindow: 3,
|
|
142
|
-
windowTurns: 10,
|
|
143
|
-
minSilenceTurns: 1,
|
|
144
|
-
};
|
|
145
|
-
export function emptyMemory() {
|
|
146
|
-
return { turn: 0, lastInjectedTurn: {}, injectionTurns: [] };
|
|
147
|
-
}
|
|
148
|
-
/**
|
|
149
|
-
* Die vollständige Entscheidung: Relevanz (Gate) UND Zeitpunkt (Timing).
|
|
150
|
-
*
|
|
151
|
-
* Reihenfolge ist Absicht: erst die billigen Ausschlüsse (Ruhe, Budget), dann
|
|
152
|
-
* Dedupe, erst zuletzt der teure Relevanz-Gate — so kostet ein unterdrückter
|
|
153
|
-
* Turn praktisch nichts.
|
|
154
|
-
*
|
|
155
|
-
* Die Funktion ist seiteneffektfrei; `commitInjection` schreibt das Gedächtnis
|
|
156
|
-
* fort, damit Aufrufer selbst entscheiden, ob ein Vorschlag wirklich gezeigt
|
|
157
|
-
* wurde (z. B. bei abgebrochenem Turn).
|
|
158
|
-
*/
|
|
159
|
-
export function decideRecall(prompt, candidates, memory, gateOverrides = {}, timingOverrides = {}) {
|
|
160
|
-
const t = { ...DEFAULT_TIMING, ...timingOverrides };
|
|
161
|
-
const risk = promptRisk(prompt);
|
|
162
|
-
const none = (reason, timingReason) => ({
|
|
163
|
-
inject: false, reason, selected: [], tokens: 0, risk,
|
|
164
|
-
suppressedDuplicates: [], timingReason,
|
|
165
|
-
});
|
|
166
|
-
if (isTrivialPrompt(prompt))
|
|
167
|
-
return none('trivial-skip');
|
|
168
|
-
// Mindestruhe nach dem letzten Einwurf.
|
|
169
|
-
const last = memory.injectionTurns[memory.injectionTurns.length - 1];
|
|
170
|
-
if (last !== undefined && memory.turn - last <= t.minSilenceTurns) {
|
|
171
|
-
return none('no-candidate-passed-gate', 'silence-window');
|
|
172
|
-
}
|
|
173
|
-
// Ruhe-Budget im gleitenden Fenster.
|
|
174
|
-
const inWindow = memory.injectionTurns.filter((n) => memory.turn - n < t.windowTurns).length;
|
|
175
|
-
if (inWindow >= t.maxInjectionsPerWindow) {
|
|
176
|
-
return none('no-candidate-passed-gate', 'quiet-budget');
|
|
177
|
-
}
|
|
178
|
-
// Dedupe: was gerade erst gesagt wurde, wird nicht wiederholt.
|
|
179
|
-
const suppressedDuplicates = [];
|
|
180
|
-
const fresh = candidates.filter((c) => {
|
|
181
|
-
const seen = memory.lastInjectedTurn[c.id];
|
|
182
|
-
if (seen !== undefined && memory.turn - seen < t.repeatCooldownTurns) {
|
|
183
|
-
suppressedDuplicates.push(c.id);
|
|
184
|
-
return false;
|
|
185
|
-
}
|
|
186
|
-
return true;
|
|
187
|
-
});
|
|
188
|
-
if (candidates.length > 0 && fresh.length === 0) {
|
|
189
|
-
return { ...none('no-candidate-passed-gate', 'all-duplicates'), suppressedDuplicates };
|
|
190
|
-
}
|
|
191
|
-
const gate = selectInjectable(prompt, fresh, { ...gateForRisk(risk), ...gateOverrides });
|
|
192
|
-
return { ...gate, risk, suppressedDuplicates };
|
|
193
|
-
}
|
|
194
|
-
/**
|
|
195
|
-
* Schreibt das Gedächtnis nach einem TATSÄCHLICH gezeigten Einwurf fort.
|
|
196
|
-
* Der Turn-Zähler wird immer erhöht — auch ohne Einwurf, sonst laufen
|
|
197
|
-
* Abklingzeit und Fenster nicht weiter.
|
|
198
|
-
*/
|
|
199
|
-
export function commitInjection(memory, decision, timingOverrides = {}) {
|
|
200
|
-
const t = { ...DEFAULT_TIMING, ...timingOverrides };
|
|
201
|
-
const next = {
|
|
202
|
-
turn: memory.turn + 1,
|
|
203
|
-
lastInjectedTurn: { ...memory.lastInjectedTurn },
|
|
204
|
-
injectionTurns: [...memory.injectionTurns],
|
|
205
|
-
};
|
|
206
|
-
if (decision.inject) {
|
|
207
|
-
for (const c of decision.selected)
|
|
208
|
-
next.lastInjectedTurn[c.id] = memory.turn;
|
|
209
|
-
next.injectionTurns.push(memory.turn);
|
|
210
|
-
}
|
|
211
|
-
// Nach Ablauf der Fenster sind Eintraege bedeutungslos — sonst waechst das
|
|
212
|
-
// Gedaechtnis in langen Sitzungen unbegrenzt mit.
|
|
213
|
-
const horizon = memory.turn - Math.max(t.windowTurns, t.repeatCooldownTurns);
|
|
214
|
-
next.injectionTurns = next.injectionTurns.filter((n) => n >= horizon);
|
|
215
|
-
for (const [id, turn] of Object.entries(next.lastInjectedTurn)) {
|
|
216
|
-
if (turn < horizon)
|
|
217
|
-
delete next.lastInjectedTurn[id];
|
|
218
|
-
}
|
|
219
|
-
return next;
|
|
220
|
-
}
|
|
221
|
-
//# sourceMappingURL=ambient-recall.js.map
|
|
1
|
+
import{createHash as T}from"node:crypto";const f={minConfidence:.6,minScore:.72,topK:3,maxTokens:240};function M(n){return T("sha256").update(n).digest("hex").slice(0,16)}function k(n){return Math.ceil(n.trim().length/4)}const g=/^(hi|hello|hey|thanks|thank you|ok|okay|yes|no|yep|nope|ty|lol|danke|hallo|servus)\b/i,j=/[/\\._{}()<>;=]|\b\d{3}\b|\b(fix|bug|error|deploy|migrat\w*|test\w*|build|refactor|auth|api|db|schema|race|crash|fail\w*|revert|rollback|why|how|debug|config|hook|token|cache|add|creat\w*|implement\w*|updat\w*|remov\w*|delet\w*|renam\w*|install\w*|upgrad\w*|write|script|component|page|button|css|style\w*|log\w*|pod\w*|node\w*|docker|k8s|kubernetes|proxy|curl|http\w*|readme|lint\w*|latenc\w*|latenz|timeout\w*|evict\w*|warum|wieso|weshalb)\b/i;function h(n){const e=n.trim();return!!(e.length<12||g.test(e)&&e.length<40||!j.test(e)&&e.length<60)}function m(n,e,t={}){const r={...f,...t};if(h(n))return{inject:!1,reason:"trivial-skip",selected:[],tokens:0};const o=e.filter(i=>i.confidence>=r.minConfidence&&i.score>=r.minScore).sort((i,u)=>u.score-i.score||u.confidence-i.confidence),c=[];let s=0;for(const i of o){if(c.length>=r.topK)break;const u=k(i.summary);if(s+u>r.maxTokens)break;c.push(i),s+=u}return c.length===0?{inject:!1,reason:"no-candidate-passed-gate",selected:[],tokens:0}:{inject:!0,reason:"injected",selected:c,tokens:s}}function x(n){const e=n.reduce((r,o)=>r+o.injected,0),t=n.reduce((r,o)=>r+o.prevented,0);return{injected:e,prevented:t,net:t-e}}function _(n,e=20,t=8){const r=n.slice(-e);return r.length<t?!1:x(r).net<0}const I=/\b(deploy\w*|migrat\w*|rollback|revert|drop\s+(table|database|schema)|truncate|rm\s+-rf|force[- ]push|prune|restore|rotat\w*|secret\w*|credential\w*|production|prod|live|kunde\w*|customer\w*|delete|loesch\w*|lösch\w*)\b/i,b=/^(ok(ay)?|ja|nein|weiter|passt|danke|thanks|go|los|mach|jup|yes|no|sure|next)\b[\s.!]*$/i;function v(n){const e=n.trim();return b.test(e)?"low":I.test(e)?"high":"normal"}function R(n,e=f){return n==="high"?{...e,minScore:Math.max(0,e.minScore-.07),topK:e.topK,maxTokens:e.maxTokens+120}:n==="low"?{...e,minScore:Math.min(1,e.minScore+.1),topK:1,maxTokens:Math.min(e.maxTokens,120)}:{...e}}const w={repeatCooldownTurns:12,maxInjectionsPerWindow:3,windowTurns:10,minSilenceTurns:1};function K(){return{turn:0,lastInjectedTurn:{},injectionTurns:[]}}function y(n,e,t,r={},o={}){const c={...w,...o},s=v(n),i=(a,l)=>({inject:!1,reason:a,selected:[],tokens:0,risk:s,suppressedDuplicates:[],timingReason:l});if(h(n))return i("trivial-skip");const u=t.injectionTurns[t.injectionTurns.length-1];if(u!==void 0&&t.turn-u<=c.minSilenceTurns)return i("no-candidate-passed-gate","silence-window");if(t.injectionTurns.filter(a=>t.turn-a<c.windowTurns).length>=c.maxInjectionsPerWindow)return i("no-candidate-passed-gate","quiet-budget");const d=[],p=e.filter(a=>{const l=t.lastInjectedTurn[a.id];return l!==void 0&&t.turn-l<c.repeatCooldownTurns?(d.push(a.id),!1):!0});return e.length>0&&p.length===0?{...i("no-candidate-passed-gate","all-duplicates"),suppressedDuplicates:d}:{...m(n,p,{...R(s),...r}),risk:s,suppressedDuplicates:d}}function A(n,e,t={}){const r={...w,...t},o={turn:n.turn+1,lastInjectedTurn:{...n.lastInjectedTurn},injectionTurns:[...n.injectionTurns]};if(e.inject){for(const s of e.selected)o.lastInjectedTurn[s.id]=n.turn;o.injectionTurns.push(n.turn)}const c=n.turn-Math.max(r.windowTurns,r.repeatCooldownTurns);o.injectionTurns=o.injectionTurns.filter(s=>s>=c);for(const[s,i]of Object.entries(o.lastInjectedTurn))i<c&&delete o.lastInjectedTurn[s];return o}export{f as DEFAULT_GATE,w as DEFAULT_TIMING,M as candidateIdFor,A as commitInjection,y as decideRecall,K as emptyMemory,k as estimateTokens,R as gateForRisk,h as isTrivialPrompt,x as netBalance,v as promptRisk,m as selectInjectable,_ as shouldBackoff};
|
package/dist/src/aussetzer.js
CHANGED
|
@@ -1,82 +1 @@
|
|
|
1
|
-
|
|
2
|
-
* Meldestelle fuer stille Aussetzer.
|
|
3
|
-
*
|
|
4
|
-
* ── Warum es das gibt ───────────────────────────────────────────────────────
|
|
5
|
-
*
|
|
6
|
-
* Am 20.08.2026 wurde am Produktionsspeicher gemessen: `cachly:lesson:vec:*`
|
|
7
|
-
* war LEER — 0 Vektoren bei 506 Lektionen. Der Recall-Pfad steigt bei
|
|
8
|
-
* `vektorbestand.groesse === 0` aus und faellt auf den reinen Wortabgleich
|
|
9
|
-
* zurueck. Er tut das STUMM.
|
|
10
|
-
*
|
|
11
|
-
* Von aussen ist dieser Zustand von "alles in Ordnung" nicht zu
|
|
12
|
-
* unterscheiden: die Suche liefert weiter Ergebnisse, nur schlechtere. Die am
|
|
13
|
-
* 19.08. gemessene Verbesserung von 21 auf 40 Prozent existierte in
|
|
14
|
-
* Produktion nie, und nichts hat es gesagt.
|
|
15
|
-
*
|
|
16
|
-
* Das ist die Fehlerklasse "Stille wird als gruen gebucht". Diese Datei ist
|
|
17
|
-
* die Gegenmassnahme an der billigsten Stelle: eine Zeile im Protokoll.
|
|
18
|
-
*
|
|
19
|
-
* ── Warum nur EINMAL je Grund ───────────────────────────────────────────────
|
|
20
|
-
*
|
|
21
|
-
* Der Recall-Pfad laeuft bei jeder Anfrage. Eine Zeile je Anfrage waere nach
|
|
22
|
-
* einer Stunde ein Protokoll, das niemand mehr liest — und ein Protokoll, das
|
|
23
|
-
* niemand liest, ist wieder Stille. Einmal je Grund und Prozess ist die
|
|
24
|
-
* Menge, die auffaellt, ohne zu ermueden.
|
|
25
|
-
*
|
|
26
|
-
* ── Warum stderr und nicht stdout ───────────────────────────────────────────
|
|
27
|
-
*
|
|
28
|
-
* Ueber stdout laeuft das MCP-Protokoll. Eine Zeile Text dort ist kein
|
|
29
|
-
* Hinweis, sondern ein Verbindungsabbruch.
|
|
30
|
-
*/
|
|
31
|
-
/** Welche Gruende schon gemeldet wurden. Je Prozess, nicht je Anfrage. */
|
|
32
|
-
const gemeldet = new Set();
|
|
33
|
-
/**
|
|
34
|
-
* Meldet einen Aussetzer genau einmal je Grund und Prozess.
|
|
35
|
-
*
|
|
36
|
-
* @param grund kurzer Schluessel, z. B. 'bedeutung-ohne-vektoren'
|
|
37
|
-
* @param text was ausgefallen ist und was das fuer das Ergebnis heisst
|
|
38
|
-
* @returns true, wenn diese Meldung wirklich hinausging
|
|
39
|
-
*/
|
|
40
|
-
export function meldeEinmal(grund, text) {
|
|
41
|
-
if (gemeldet.has(grund))
|
|
42
|
-
return false;
|
|
43
|
-
gemeldet.add(grund);
|
|
44
|
-
console.error(`[aussetzer] ${grund}: ${text}`);
|
|
45
|
-
return true;
|
|
46
|
-
}
|
|
47
|
-
/**
|
|
48
|
-
* Wurde dieser Grund in diesem Prozess schon gemeldet?
|
|
49
|
-
*
|
|
50
|
-
* Fuer `brain_doctor`: dort soll stehen, was hier passiert ist, ohne dass
|
|
51
|
-
* jemand ein Protokoll durchsuchen muss.
|
|
52
|
-
*/
|
|
53
|
-
export function schonGemeldet(grund) {
|
|
54
|
-
return gemeldet.has(grund);
|
|
55
|
-
}
|
|
56
|
-
/** Alle bisher gemeldeten Gruende, in der Reihenfolge des Auftretens. */
|
|
57
|
-
export function gemeldeteGruende() {
|
|
58
|
-
return [...gemeldet];
|
|
59
|
-
}
|
|
60
|
-
/**
|
|
61
|
-
* Setzt die Merkliste zurueck.
|
|
62
|
-
*
|
|
63
|
-
* Nur fuer Tests. Ohne das wuerde die erste Testdatei alle weiteren blind
|
|
64
|
-
* machen — genau der Fehler, gegen den diese Datei antritt.
|
|
65
|
-
*/
|
|
66
|
-
export function setzeAussetzerZurueck() {
|
|
67
|
-
gemeldet.clear();
|
|
68
|
-
}
|
|
69
|
-
/** Der Grund, um den es beim Anlass ging: Bedeutungsabgleich ohne Vektoren. */
|
|
70
|
-
export const OHNE_VEKTOREN = 'bedeutung-ohne-vektoren';
|
|
71
|
-
/** Der Nachbarfall: es gibt gar keinen Dienst zum Einbetten. */
|
|
72
|
-
export const OHNE_DIENST = 'bedeutung-ohne-dienst';
|
|
73
|
-
/**
|
|
74
|
-
* Der Bestand ist da, aber die FRAGE liess sich nicht einbetten.
|
|
75
|
-
*
|
|
76
|
-
* Eigener Grund und nicht mit OHNE_VEKTOREN zusammengelegt: hier ist der
|
|
77
|
-
* Bestand gesund und der fremde Dienst hat gerade nicht geantwortet. Das ist
|
|
78
|
-
* ein Netzproblem und keine fehlende Nachruestung — wer beides unter einem
|
|
79
|
-
* Namen fuehrt, sucht spaeter an der falschen Stelle.
|
|
80
|
-
*/
|
|
81
|
-
export const OHNE_FRAGEVEKTOR = 'bedeutung-ohne-fragevektor';
|
|
82
|
-
//# sourceMappingURL=aussetzer.js.map
|
|
1
|
+
const u=new Set;function i(e){if(e instanceof Error)return(e.message||e.name||"Error").slice(0,200);if(typeof e=="string")return e.slice(0,200)||"leerer Text geworfen";if(e===void 0)return"undefined geworfen (kein Grund mitgegeben)";if(e===null)return"null geworfen (kein Grund mitgegeben)";try{const n=JSON.stringify(e);return n&&n!=="{}"?n.slice(0,200):`${typeof e} ohne Inhalt geworfen`}catch{return`${typeof e} geworfen, nicht darstellbar`}}function c(e,n){return u.has(e)?!1:(u.add(e),console.error(`[aussetzer] ${e}: ${n}`),!0)}function l(e){return u.has(e)}function f(){return[...u]}function h(){u.clear()}const g="bedeutung-ohne-vektoren",E="bedeutung-ohne-dienst",m="bedeutung-ohne-fragevektor",p="bedeutung-abbruch",x="einbettung-beim-schreiben",d="versuch-laeuft-leer",s="cachly:aussetzer:",a=720*60*60;async function z(e,n,o){const r=c(n,o);if(!e)return r;try{const t=`${s}${n}`;await e.hincrby(t,"anzahl",1),await e.hset(t,{zuletzt:new Date().toISOString(),text:o.slice(0,500)}),await e.expire(t,a)}catch(t){c(`${n}-vermerk`,`der Aussetzer liess sich nicht vermerken (${t instanceof Error?t.message:String(t)}) \u2014 er steht nur in diesem Protokoll und ist nach einem Neustart weg`)}return r}async function k(e){if(!e)return null;try{const n=await e.keys(`${s}*`),o=[];for(const r of n){const t=await e.hgetall(r);!t||!t.anzahl||o.push({grund:r.slice(s.length),anzahl:Number(t.anzahl)||0,zuletzt:t.zuletzt??"",text:t.text??""})}return o.sort((r,t)=>t.anzahl-r.anzahl||r.grund.localeCompare(t.grund)),o}catch{return null}}export{a as AUSSETZER_TTL_SEKUNDEN,s as AUSSETZER_VORSATZ,E as OHNE_DIENST,m as OHNE_FRAGEVEKTOR,x as OHNE_SCHREIBVEKTOR,g as OHNE_VEKTOREN,p as SINNPFAD_ABBRUCH,d as VERSUCH_LEER,i as fehlerText,f as gemeldeteGruende,k as leseVermerke,c as meldeEinmal,z as meldeUndVermerke,l as schonGemeldet,h as setzeAussetzerZurueck};
|
package/dist/src/auth.js
CHANGED
|
@@ -1,140 +1,9 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
}
|
|
11
|
-
catch {
|
|
12
|
-
return null;
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
// ── Self-healing auth ─────────────────────────────────────────────────────────
|
|
16
|
-
// The silent-failure mode we guard against: a token that is missing, expired, or
|
|
17
|
-
// about to expire degrades the brain into "0 recalls" without the user ever being
|
|
18
|
-
// told why. Instead of failing silently (or only at the moment a network call
|
|
19
|
-
// finally 401s), we diagnose the credential up front and either heal it
|
|
20
|
-
// automatically (mint a fresh long-lived key while the current one is still valid)
|
|
21
|
-
// or surface a single, actionable instruction.
|
|
22
|
-
/** Long-lived API keys cannot expire silently; raw access tokens can. */
|
|
23
|
-
export const API_KEY_PREFIX = 'cky_';
|
|
24
|
-
/** A long-lived API key (cky_live_… / cky_…) has no exp and never expires silently. */
|
|
25
|
-
export function isLongLivedApiKey(token) {
|
|
26
|
-
return token.startsWith(API_KEY_PREFIX);
|
|
27
|
-
}
|
|
28
|
-
// Refresh a token this close to expiry (or already expired) the moment we touch it.
|
|
29
|
-
export const NEAR_EXPIRY_MS = 10 * 60 * 1000; // 10 minutes
|
|
30
|
-
const SETUP_URL = cachlyUrl('/setup-ai', 'auth-diagnosis');
|
|
31
|
-
/**
|
|
32
|
-
* Diagnose the current credential without making any network call. Pure +
|
|
33
|
-
* deterministic (pass `now` in tests).
|
|
34
|
-
*/
|
|
35
|
-
export function diagnoseAuth(jwt, now = Date.now()) {
|
|
36
|
-
if (!jwt) {
|
|
37
|
-
return {
|
|
38
|
-
state: 'no_jwt',
|
|
39
|
-
usable: false,
|
|
40
|
-
expiresInMs: null,
|
|
41
|
-
message: `No CACHLY_JWT set — the brain can't persist or recall anything (you'd silently get 0 recalls).\n\nGet your token at ${SETUP_URL} and add it to your MCP config, or just call any tool to start the one-time device-flow sign-in.`,
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
const expMs = jwtExpiryMs(jwt);
|
|
45
|
-
// No exp claim → long-lived API key. These are the healthy steady state.
|
|
46
|
-
if (expMs === null) {
|
|
47
|
-
return {
|
|
48
|
-
state: isLongLivedApiKey(jwt) ? 'long_lived' : 'healthy',
|
|
49
|
-
usable: true,
|
|
50
|
-
expiresInMs: null,
|
|
51
|
-
message: '',
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
const remaining = expMs - now;
|
|
55
|
-
if (remaining <= 0) {
|
|
56
|
-
return {
|
|
57
|
-
state: 'expired',
|
|
58
|
-
usable: false,
|
|
59
|
-
expiresInMs: remaining,
|
|
60
|
-
message: `CACHLY_JWT expired at ${new Date(expMs).toISOString()} — recalls will silently return nothing until you re-authenticate.\n\nGet a fresh token at ${SETUP_URL}, or call any tool to re-run the device-flow sign-in.`,
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
if (remaining <= NEAR_EXPIRY_MS) {
|
|
64
|
-
const mins = Math.max(1, Math.round(remaining / 60_000));
|
|
65
|
-
return {
|
|
66
|
-
state: 'near_expiry',
|
|
67
|
-
usable: true,
|
|
68
|
-
expiresInMs: remaining,
|
|
69
|
-
message: `CACHLY_JWT expires in ~${mins} min. cachly will refresh it automatically into a long-lived key while it's still valid.`,
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
return { state: 'healthy', usable: true, expiresInMs: remaining, message: '' };
|
|
73
|
-
}
|
|
74
|
-
/**
|
|
75
|
-
* Decide what self-healing step (if any) to take for a diagnosis. Pure so the
|
|
76
|
-
* decision is unit-testable independently of the network-bound executor.
|
|
77
|
-
*/
|
|
78
|
-
export function planAuthHeal(d) {
|
|
79
|
-
switch (d.state) {
|
|
80
|
-
case 'no_jwt':
|
|
81
|
-
case 'expired':
|
|
82
|
-
return 'reauth';
|
|
83
|
-
case 'near_expiry':
|
|
84
|
-
return 'refresh';
|
|
85
|
-
case 'healthy':
|
|
86
|
-
case 'long_lived':
|
|
87
|
-
return 'none';
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
export function checkJwt(jwt) {
|
|
91
|
-
const d = diagnoseAuth(jwt);
|
|
92
|
-
if (!d.usable) {
|
|
93
|
-
throw new McpError(ErrorCode.InvalidRequest, d.message);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
// ── M2M / headless auth (OAuth2 client_credentials) ──────────────────────────
|
|
97
|
-
// For machine-to-machine callers — CI runners, agent orchestrators, AI-to-AI
|
|
98
|
-
// pipelines — there is no human to complete a device flow. When CACHLY_CLIENT_ID
|
|
99
|
-
// and CACHLY_CLIENT_SECRET are present we run the standard OAuth2
|
|
100
|
-
// client_credentials grant against Keycloak and use the resulting access token
|
|
101
|
-
// exactly like a CACHLY_JWT. Fully non-interactive.
|
|
102
|
-
export const DEFAULT_AUTH_BASE = 'https://auth.cachly.dev/realms/cachly/protocol/openid-connect';
|
|
103
|
-
/** Read M2M client credentials from the environment, if both are present. */
|
|
104
|
-
export function readClientCredentialsFromEnv(env = process.env) {
|
|
105
|
-
const clientId = env.CACHLY_CLIENT_ID;
|
|
106
|
-
const clientSecret = env.CACHLY_CLIENT_SECRET;
|
|
107
|
-
if (!clientId || !clientSecret)
|
|
108
|
-
return null;
|
|
109
|
-
return {
|
|
110
|
-
clientId,
|
|
111
|
-
clientSecret,
|
|
112
|
-
scope: env.CACHLY_CLIENT_SCOPE || 'openid',
|
|
113
|
-
authBase: env.CACHLY_AUTH_URL || DEFAULT_AUTH_BASE,
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
/** Build the application/x-www-form-urlencoded body for a client_credentials grant. */
|
|
117
|
-
export function buildClientCredentialsBody(c) {
|
|
118
|
-
const params = new URLSearchParams({
|
|
119
|
-
grant_type: 'client_credentials',
|
|
120
|
-
client_id: c.clientId,
|
|
121
|
-
client_secret: c.clientSecret,
|
|
122
|
-
});
|
|
123
|
-
if (c.scope)
|
|
124
|
-
params.set('scope', c.scope);
|
|
125
|
-
return params.toString();
|
|
126
|
-
}
|
|
127
|
-
/** Token endpoint URL for a given credentials/realm config. */
|
|
128
|
-
export function clientCredentialsTokenUrl(c) {
|
|
129
|
-
return `${c.authBase ?? DEFAULT_AUTH_BASE}/token`;
|
|
130
|
-
}
|
|
131
|
-
export function handleApiError(status, detail) {
|
|
132
|
-
if (status === 401) {
|
|
133
|
-
throw new McpError(ErrorCode.InvalidRequest, `Authentication failed (401): ${detail}\n\nYour CACHLY_JWT may be expired or invalid. Get a fresh token at ${SETUP_URL}`);
|
|
134
|
-
}
|
|
135
|
-
if (status === 403) {
|
|
136
|
-
throw new McpError(ErrorCode.InvalidRequest, `Access denied (403): ${detail}\n\nCheck that your CACHLY_JWT belongs to an account with access to this resource.`);
|
|
137
|
-
}
|
|
138
|
-
throw new McpError(ErrorCode.InternalError, `cachly API error ${status}: ${detail}`);
|
|
139
|
-
}
|
|
140
|
-
//# sourceMappingURL=auth.js.map
|
|
1
|
+
import{ErrorCode as o,McpError as s}from"@modelcontextprotocol/sdk/types.js";import{cachlyUrl as c}from"./cachly-url.js";function u(e){try{const t=e.split(".");if(t.length!==3)return null;const n=JSON.parse(Buffer.from(t[1],"base64url").toString("utf-8"));return n.exp?n.exp*1e3:null}catch{return null}}const p="cky_";function h(e){return e.startsWith(p)}const d=600*1e3,i=c("/setup-ai","auth-diagnosis");function f(e,t=Date.now()){if(!e)return{state:"no_jwt",usable:!1,expiresInMs:null,message:`No CACHLY_JWT set \u2014 the brain can't persist or recall anything (you'd silently get 0 recalls).
|
|
2
|
+
|
|
3
|
+
Get your token at ${i} and add it to your MCP config, or just call any tool to start the one-time device-flow sign-in.`};const n=u(e);if(n===null)return{state:h(e)?"long_lived":"healthy",usable:!0,expiresInMs:null,message:""};const r=n-t;if(r<=0)return{state:"expired",usable:!1,expiresInMs:r,message:`CACHLY_JWT expired at ${new Date(n).toISOString()} \u2014 recalls will silently return nothing until you re-authenticate.
|
|
4
|
+
|
|
5
|
+
Get a fresh token at ${i}, or call any tool to re-run the device-flow sign-in.`};if(r<=d){const l=Math.max(1,Math.round(r/6e4));return{state:"near_expiry",usable:!0,expiresInMs:r,message:`CACHLY_JWT expires in ~${l} min. cachly will refresh it automatically into a long-lived key while it's still valid.`}}return{state:"healthy",usable:!0,expiresInMs:r,message:""}}function y(e){switch(e.state){case"no_jwt":case"expired":return"reauth";case"near_expiry":return"refresh";case"healthy":case"long_lived":return"none"}}function x(e){const t=f(e);if(!t.usable)throw new s(o.InvalidRequest,t.message)}const a="https://auth.cachly.dev/realms/cachly/protocol/openid-connect";function g(e=process.env){const t=e.CACHLY_CLIENT_ID,n=e.CACHLY_CLIENT_SECRET;return!t||!n?null:{clientId:t,clientSecret:n,scope:e.CACHLY_CLIENT_SCOPE||"openid",authBase:e.CACHLY_AUTH_URL||a}}function m(e){const t=new URLSearchParams({grant_type:"client_credentials",client_id:e.clientId,client_secret:e.clientSecret});return e.scope&&t.set("scope",e.scope),t.toString()}function A(e){return`${e.authBase??a}/token`}function w(e,t){throw e===401?new s(o.InvalidRequest,`Authentication failed (401): ${t}
|
|
6
|
+
|
|
7
|
+
Your CACHLY_JWT may be expired or invalid. Get a fresh token at ${i}`):e===403?new s(o.InvalidRequest,`Access denied (403): ${t}
|
|
8
|
+
|
|
9
|
+
Check that your CACHLY_JWT belongs to an account with access to this resource.`):new s(o.InternalError,`cachly API error ${e}: ${t}`)}export{p as API_KEY_PREFIX,a as DEFAULT_AUTH_BASE,d as NEAR_EXPIRY_MS,m as buildClientCredentialsBody,x as checkJwt,A as clientCredentialsTokenUrl,f as diagnoseAuth,w as handleApiError,h as isLongLivedApiKey,u as jwtExpiryMs,y as planAuthHeal,g as readClientCredentialsFromEnv};
|
|
@@ -1,56 +1 @@
|
|
|
1
|
-
|
|
2
|
-
* Wer hat diese Lektion geschrieben — ich oder jemand anderes?
|
|
3
|
-
*
|
|
4
|
-
* `session_start_summary` nimmt seit jeher einen Parameter `author` entgegen
|
|
5
|
-
* und beschreibt ihn in der Werkzeug-Spezifikation so:
|
|
6
|
-
*
|
|
7
|
-
* "Your name or handle (optional). Same as session_start — used for team
|
|
8
|
-
* lesson filtering."
|
|
9
|
-
*
|
|
10
|
-
* Die Zusage wurde nie eingeloest. Der Handler holte den Wert aus den
|
|
11
|
-
* Argumenten und benutzte ihn danach nirgends; eslint meldete ihn seit
|
|
12
|
-
* Monaten als ungenutzte Variable. Wer den Parameter setzte, bekam exakt
|
|
13
|
-
* dieselbe Ausgabe wie ohne ihn.
|
|
14
|
-
*
|
|
15
|
-
* Fehlerklasse: eine Zusage an zwei Orten — hier in der Spezifikation, dort
|
|
16
|
-
* im Code — und nur einer wurde gepflegt. Dieselbe Form wie die Kommentarzeile
|
|
17
|
-
* am Frei-Limit, die "the #1 result is always returned" versprach, waehrend
|
|
18
|
-
* der Zweig darunter null lieferte.
|
|
19
|
-
*
|
|
20
|
-
* Reines Modul, weil die Entscheidung eine Rechnung ist: dieselbe Regel, die
|
|
21
|
-
* `smart_recall` schon anwendet, an EINER Stelle statt zweimal abgeschrieben.
|
|
22
|
-
*/
|
|
23
|
-
/**
|
|
24
|
-
* Das Abzeichen hinter einer Lektion — leer, wenn sie von mir selbst stammt
|
|
25
|
-
* oder niemand als Autor eingetragen ist.
|
|
26
|
-
*
|
|
27
|
-
* Bewusst KEIN Abzeichen fuer die eigenen Lektionen: in einem Brain, das
|
|
28
|
-
* ueberwiegend einer Person gehoert, stuende sonst hinter jeder Zeile derselbe
|
|
29
|
-
* Name, und das Abzeichen verlöre genau die Aussage, für die es da ist.
|
|
30
|
-
*/
|
|
31
|
-
export function autorAbzeichen(lektionsAutor, anfragender) {
|
|
32
|
-
const autor = (lektionsAutor ?? '').trim();
|
|
33
|
-
if (!autor)
|
|
34
|
-
return '';
|
|
35
|
-
const ich = (anfragender ?? '').trim();
|
|
36
|
-
if (ich && autor === ich)
|
|
37
|
-
return '';
|
|
38
|
-
return ` · 👤 ${autor}`;
|
|
39
|
-
}
|
|
40
|
-
/**
|
|
41
|
-
* Wie viele der gezeigten Lektionen von jemand anderem stammen.
|
|
42
|
-
*
|
|
43
|
-
* Das ist der Wert, den nur ein GETEILTES Gedaechtnis liefert — und deshalb
|
|
44
|
-
* die Zahl, die es zu nennen lohnt. Ist sie null, wird auch nichts gesagt:
|
|
45
|
-
* "0 von Teamkollegen" ist keine Nachricht, sondern Rauschen.
|
|
46
|
-
*/
|
|
47
|
-
export function fremdanteil(lektionen, anfragender) {
|
|
48
|
-
const ich = (anfragender ?? '').trim();
|
|
49
|
-
if (!ich)
|
|
50
|
-
return 0;
|
|
51
|
-
return lektionen.filter(l => {
|
|
52
|
-
const autor = (l.author ?? '').trim();
|
|
53
|
-
return autor.length > 0 && autor !== ich;
|
|
54
|
-
}).length;
|
|
55
|
-
}
|
|
56
|
-
//# sourceMappingURL=autor-abzeichen.js.map
|
|
1
|
+
function o(n,e){const t=(n??"").trim();if(!t)return"";const r=(e??"").trim();return r&&t===r?"":` \xB7 \u{1F464} ${t}`}function u(n,e){const t=(e??"").trim();return t?n.filter(r=>{const i=(r.author??"").trim();return i.length>0&&i!==t}).length:0}export{o as autorAbzeichen,u as fremdanteil};
|
package/dist/src/autor.js
CHANGED
|
@@ -1,136 +1 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export const echterGitLeser = (cwd) => (schluessel) => {
|
|
3
|
-
try {
|
|
4
|
-
return execSync(`git config ${schluessel}`, {
|
|
5
|
-
cwd,
|
|
6
|
-
encoding: 'utf-8',
|
|
7
|
-
stdio: 'pipe',
|
|
8
|
-
timeout: 1500,
|
|
9
|
-
}).trim();
|
|
10
|
-
}
|
|
11
|
-
catch {
|
|
12
|
-
return '';
|
|
13
|
-
}
|
|
14
|
-
};
|
|
15
|
-
/**
|
|
16
|
-
* Einen Namen auf die Form bringen, in der Autoren gespeichert werden.
|
|
17
|
-
*
|
|
18
|
-
* Klein geschrieben und ohne Leerzeichen: "Heinrich Neb" wird zu "heinrich".
|
|
19
|
-
* Das ist keine Kosmetik — brain_who_knows, team_recall und die
|
|
20
|
-
* Wiederverwendungs-Zaehlung vergleichen Autoren als Zeichenketten. "Heinrich"
|
|
21
|
-
* und "heinrich" waeren zwei Menschen.
|
|
22
|
-
*/
|
|
23
|
-
export function normalisiere(roh) {
|
|
24
|
-
const t = (roh ?? '').trim();
|
|
25
|
-
if (!t)
|
|
26
|
-
return '';
|
|
27
|
-
// Nur der Rufname: Nachnamen aendern sich (Heirat), Rufnamen selten. Und
|
|
28
|
-
// ein Nachname im Autorenfeld ist eine Personendatei mehr, die niemand
|
|
29
|
-
// gebeten hat anzulegen.
|
|
30
|
-
const erster = t.split(/[\s.]+/)[0] ?? '';
|
|
31
|
-
return erster
|
|
32
|
-
.toLowerCase()
|
|
33
|
-
.replace(/[^a-z0-9_-]/g, '')
|
|
34
|
-
.slice(0, 32);
|
|
35
|
-
}
|
|
36
|
-
/**
|
|
37
|
-
* Ausdrueckliche Wortmeldung "ohne meinen Namen".
|
|
38
|
-
*
|
|
39
|
-
* Ohne diesen Weg gaebe es keinen mehr: seit der Name aus git hergeleitet
|
|
40
|
-
* wird, bekaeme JEDE Lektion einen Autor, auch die, bei der der Mensch das
|
|
41
|
-
* nicht will. Eine private Notiz ueber einen Kunden, ein Fund, den man nicht
|
|
42
|
-
* an sich haengen will — das muss weiterhin gehen, und zwar sichtbar statt
|
|
43
|
-
* durch Weglassen.
|
|
44
|
-
*
|
|
45
|
-
* Der Test, der beim Umbau rot wurde, hat genau diese Luecke gezeigt: er
|
|
46
|
-
* speicherte eine Lektion ohne Autor und erwartete, dass skill_gaps sie
|
|
47
|
-
* anmeckert. Das ging vorher versehentlich, jetzt geht es absichtlich.
|
|
48
|
-
*/
|
|
49
|
-
const ANONYM = new Set(['-', 'anonym', 'anonymous', 'none', 'kein', 'off']);
|
|
50
|
-
export function ermittleAutor(input) {
|
|
51
|
-
const rohUeb = (input.uebergeben ?? '').trim().toLowerCase();
|
|
52
|
-
if (ANONYM.has(rohUeb))
|
|
53
|
-
return { autor: '', quelle: 'anonym' };
|
|
54
|
-
const rohEnv = (input.umgebung ?? '').trim().toLowerCase();
|
|
55
|
-
if (ANONYM.has(rohEnv))
|
|
56
|
-
return { autor: '', quelle: 'anonym' };
|
|
57
|
-
const ueb = normalisiere(input.uebergeben ?? '');
|
|
58
|
-
if (ueb)
|
|
59
|
-
return { autor: ueb, quelle: 'uebergeben' };
|
|
60
|
-
const env = normalisiere(input.umgebung ?? '');
|
|
61
|
-
if (env)
|
|
62
|
-
return { autor: env, quelle: 'umgebung' };
|
|
63
|
-
const name = normalisiere(input.git('user.name'));
|
|
64
|
-
if (name)
|
|
65
|
-
return { autor: name, quelle: 'git-name' };
|
|
66
|
-
const mail = input.git('user.email');
|
|
67
|
-
const vorDemAt = normalisiere((mail ?? '').split('@')[0] ?? '');
|
|
68
|
-
if (vorDemAt)
|
|
69
|
-
return { autor: vorDemAt, quelle: 'git-mail' };
|
|
70
|
-
return { autor: '', quelle: 'unbekannt' };
|
|
71
|
-
}
|
|
72
|
-
/**
|
|
73
|
-
* Der Mitautor — standardmaessig AUS.
|
|
74
|
-
*
|
|
75
|
-
* Heinrich: "Wir koennen im gleichen Zuge ja auch zum Beispiel claude als
|
|
76
|
-
* Co-Author nehmen, wenn der User das moechte? Einstellungssache,
|
|
77
|
-
* standardmaessig aber aus."
|
|
78
|
-
*
|
|
79
|
-
* Aus zwei Gruenden aus: erstens gehoert die Entscheidung dem Menschen, und
|
|
80
|
-
* zweitens wuerde ein automatisch gesetzter Mitautor die Zahl, um die es hier
|
|
81
|
-
* geht — wie viele Lektionen einen MENSCHEN als Autor haben — sofort wieder
|
|
82
|
-
* unbrauchbar machen.
|
|
83
|
-
*
|
|
84
|
-
* Eingeschaltet wird er mit
|
|
85
|
-
* brain_set_pref(key="coauthor", value="claude")
|
|
86
|
-
* und wieder aus mit value="" oder "off".
|
|
87
|
-
*/
|
|
88
|
-
export const COAUTHOR_PREF_KEY = 'coauthor';
|
|
89
|
-
/** Aus dem gespeicherten Wert den Mitautor lesen — leer heisst aus. */
|
|
90
|
-
export function coautorAus(prefWert) {
|
|
91
|
-
const t = (prefWert ?? '').trim().toLowerCase();
|
|
92
|
-
if (!t || t === 'off' || t === 'false' || t === '0' || t === 'none')
|
|
93
|
-
return '';
|
|
94
|
-
return normalisiere(t);
|
|
95
|
-
}
|
|
96
|
-
/**
|
|
97
|
-
* Wie der Autor gespeichert wird, wenn ein Mitautor eingeschaltet ist.
|
|
98
|
-
*
|
|
99
|
-
* "heinrich+claude" — ein Feld, kein zweites. Ein zweites Feld haette jede
|
|
100
|
-
* Abfrage im ganzen Haus anfassen muessen (who_knows, team_recall,
|
|
101
|
-
* reuse_pairs, das Briefing), und die Zahl "wie viele Lektionen haben einen
|
|
102
|
-
* Autor" waere dabei zweideutig geworden.
|
|
103
|
-
*
|
|
104
|
-
* Der Mensch steht VORN. Wer die Zeichenkette an "+" trennt, bekommt zuerst
|
|
105
|
-
* den, der die Entscheidung getroffen hat.
|
|
106
|
-
*/
|
|
107
|
-
export function mitCoautor(autor, coautor) {
|
|
108
|
-
if (!autor)
|
|
109
|
-
return '';
|
|
110
|
-
if (!coautor || coautor === autor)
|
|
111
|
-
return autor;
|
|
112
|
-
return `${autor}+${coautor}`;
|
|
113
|
-
}
|
|
114
|
-
/** Nur der Mensch, ohne Mitautor — fuer Zaehlungen und Vergleiche. */
|
|
115
|
-
export function nurMensch(autorFeld) {
|
|
116
|
-
return (autorFeld ?? '').split('+')[0] ?? '';
|
|
117
|
-
}
|
|
118
|
-
/**
|
|
119
|
-
* Ein Satz an den Aufrufer, wenn kein Autor ermittelt werden konnte.
|
|
120
|
-
*
|
|
121
|
-
* Wieder ein HINWEIS und keine Ablehnung: eine abgelehnte Lektion ist immer
|
|
122
|
-
* schlechter als eine ohne Autor. Aber schweigen waere falsch — genau das
|
|
123
|
-
* Schweigen hat die 195 erzeugt.
|
|
124
|
-
*/
|
|
125
|
-
export function autorHinweis(h) {
|
|
126
|
-
if (h.autor)
|
|
127
|
-
return null;
|
|
128
|
-
// Wer ausdruecklich anonym speichert, bekommt keine Belehrung.
|
|
129
|
-
if (h.quelle === 'anonym')
|
|
130
|
-
return null;
|
|
131
|
-
return (`👤 **Kein Autor ermittelt** — weder \`author\`, noch \`CACHLY_AUTHOR\`, noch \`git config user.name\`. ` +
|
|
132
|
-
`Ohne Autor zählt diese Lektion nicht als Team-Wissen: \`brain_who_knows\` findet niemanden und ` +
|
|
133
|
-
`Wiederverwendung über Personengrenzen bleibt ungezählt. Ein \`git config user.name "Vorname"\` ` +
|
|
134
|
-
`im Projekt genügt für alle künftigen Lektionen.`);
|
|
135
|
-
}
|
|
136
|
-
//# sourceMappingURL=autor.js.map
|
|
1
|
+
import{execSync as c}from"node:child_process";const f=e=>n=>{try{return c(`git config ${n}`,{cwd:e,encoding:"utf-8",stdio:"pipe",timeout:1500}).trim()}catch{return""}};function t(e){const n=(e??"").trim();return n?(n.split(/[\s.]+/)[0]??"").toLowerCase().replace(/[^a-z0-9_-]/g,"").slice(0,32):""}const a=new Set(["-","anonym","anonymous","none","kein","off"]);function g(e){const n=(e.uebergeben??"").trim().toLowerCase();if(a.has(n))return{autor:"",quelle:"anonym"};const r=(e.umgebung??"").trim().toLowerCase();if(a.has(r))return{autor:"",quelle:"anonym"};const o=t(e.uebergeben??"");if(o)return{autor:o,quelle:"uebergeben"};const u=t(e.umgebung??"");if(u)return{autor:u,quelle:"umgebung"};const i=t(e.git("user.name"));if(i)return{autor:i,quelle:"git-name"};const l=e.git("user.email"),s=t((l??"").split("@")[0]??"");return s?{autor:s,quelle:"git-mail"}:{autor:"",quelle:"unbekannt"}}const b="coauthor";function h(e){const n=(e??"").trim().toLowerCase();return!n||n==="off"||n==="false"||n==="0"||n==="none"?"":t(n)}function p(e,n){return e?!n||n===e?e:`${e}+${n}`:""}function w(e){return(e??"").split("+")[0]??""}function d(e){return e.autor||e.quelle==="anonym"?null:'\u{1F464} **Kein Autor ermittelt** \u2014 weder `author`, noch `CACHLY_AUTHOR`, noch `git config user.name`. Ohne Autor z\xE4hlt diese Lektion nicht als Team-Wissen: `brain_who_knows` findet niemanden und Wiederverwendung \xFCber Personengrenzen bleibt ungez\xE4hlt. Ein `git config user.name "Vorname"` im Projekt gen\xFCgt f\xFCr alle k\xFCnftigen Lektionen.'}export{b as COAUTHOR_PREF_KEY,d as autorHinweis,h as coautorAus,f as echterGitLeser,g as ermittleAutor,p as mitCoautor,t as normalisiere,w as nurMensch};
|