@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.
Files changed (217) hide show
  1. package/README.md +2 -2
  2. package/dist/index.js +1 -7
  3. package/dist/packages/telegram-notify/client.js +2 -75
  4. package/dist/src/ambient-cli.js +7 -253
  5. package/dist/src/ambient-deps.js +1 -55
  6. package/dist/src/ambient-hooks.js +5 -278
  7. package/dist/src/ambient-ledger.js +6 -95
  8. package/dist/src/ambient-memory.js +1 -67
  9. package/dist/src/ambient-recall.js +1 -221
  10. package/dist/src/aussetzer.js +1 -82
  11. package/dist/src/auth.js +9 -140
  12. package/dist/src/autor-abzeichen.js +1 -56
  13. package/dist/src/autor.js +1 -136
  14. package/dist/src/bauabdruck.js +1 -93
  15. package/dist/src/bedeutung.js +2 -323
  16. package/dist/src/bench/cachly-bench.js +4 -249
  17. package/dist/src/bench/external-corpus.js +4 -135
  18. package/dist/src/bench/fixtures.js +1 -225
  19. package/dist/src/brain-export.js +16 -119
  20. package/dist/src/brain-watch-hook.js +8 -86
  21. package/dist/src/cachly-url.js +1 -21
  22. package/dist/src/ckg.js +1 -108
  23. package/dist/src/cls-hook.js +8 -94
  24. package/dist/src/confidence.js +1 -50
  25. package/dist/src/credentials.js +1 -85
  26. package/dist/src/deutsch.js +1 -223
  27. package/dist/src/doctor.js +2 -248
  28. package/dist/src/editor.js +1 -63
  29. package/dist/src/eingaenge-nachruesten.js +1 -290
  30. package/dist/src/eingaenge.js +2 -228
  31. package/dist/src/embeddings.js +13 -223
  32. package/dist/src/feldreparatur.js +3 -0
  33. package/dist/src/first-contact.js +4 -108
  34. package/dist/src/first-recall-proof.js +4 -41
  35. package/dist/src/funnel-milestones.js +2 -48
  36. package/dist/src/geliefert.js +1 -114
  37. package/dist/src/handlers/advanced.js +38 -537
  38. package/dist/src/handlers/brain.js +69 -4181
  39. package/dist/src/handlers/cache.js +59 -638
  40. package/dist/src/handlers/context.js +17 -182
  41. package/dist/src/handlers/fedbrain.js +40 -1436
  42. package/dist/src/handlers/instances.js +16 -201
  43. package/dist/src/handlers/roadmap.js +13 -175
  44. package/dist/src/handlers/share.js +19 -530
  45. package/dist/src/handlers/syndicate.js +38 -904
  46. package/dist/src/handlers/team.js +89 -1850
  47. package/dist/src/handlers/viz.js +3 -148
  48. package/dist/src/index.js +321 -4281
  49. package/dist/src/interferenz.js +1 -0
  50. package/dist/src/kostprobe.js +7 -125
  51. package/dist/src/lesson-preview.js +2 -133
  52. package/dist/src/namespace.js +1 -128
  53. package/dist/src/notifier.js +1 -6
  54. package/dist/src/rangfolge-stellschrauben.js +1 -0
  55. package/dist/src/rangfolge.js +1 -214
  56. package/dist/src/raumkorrektur.js +1 -197
  57. package/dist/src/recall-tiefe.js +1 -42
  58. package/dist/src/rerank.js +1 -154
  59. package/dist/src/resonanz.js +1 -0
  60. package/dist/src/search.js +1 -2104
  61. package/dist/src/seltenheitsbestand.js +1 -92
  62. package/dist/src/speicherlage.js +1 -84
  63. package/dist/src/spuren.js +1 -150
  64. package/dist/src/starter-corpus.js +1 -165
  65. package/dist/src/telemetry-types.js +0 -13
  66. package/dist/src/tools.js +10 -2465
  67. package/dist/src/toolspecs.js +1 -121
  68. package/dist/src/upgrade-nudge.js +1 -29
  69. package/dist/src/utils.js +1 -89
  70. package/dist/src/versuch.js +1 -250
  71. package/dist/src/vorspann.js +1 -146
  72. package/dist/src/wartung/vektoren-verdichten.js +1 -200
  73. package/dist/src/werkzeug-nutzung.js +1 -72
  74. package/dist/src/wertbeitrag.js +1 -73
  75. package/dist/src/wirkungsspur.js +1 -0
  76. package/glama.json +1 -1
  77. package/package.json +8 -7
  78. package/server.json +3 -3
  79. package/dist/index.d.ts +0 -1
  80. package/dist/index.js.map +0 -1
  81. package/dist/packages/telegram-notify/client.d.ts +0 -19
  82. package/dist/packages/telegram-notify/client.js.map +0 -1
  83. package/dist/src/ambient-cli.d.ts +0 -101
  84. package/dist/src/ambient-cli.js.map +0 -1
  85. package/dist/src/ambient-deps.d.ts +0 -26
  86. package/dist/src/ambient-deps.js.map +0 -1
  87. package/dist/src/ambient-hooks.d.ts +0 -137
  88. package/dist/src/ambient-hooks.js.map +0 -1
  89. package/dist/src/ambient-ledger.d.ts +0 -19
  90. package/dist/src/ambient-ledger.js.map +0 -1
  91. package/dist/src/ambient-memory.d.ts +0 -12
  92. package/dist/src/ambient-memory.js.map +0 -1
  93. package/dist/src/ambient-recall.d.ts +0 -127
  94. package/dist/src/ambient-recall.js.map +0 -1
  95. package/dist/src/aussetzer.d.ts +0 -67
  96. package/dist/src/aussetzer.js.map +0 -1
  97. package/dist/src/auth.d.ts +0 -43
  98. package/dist/src/auth.js.map +0 -1
  99. package/dist/src/autor-abzeichen.d.ts +0 -41
  100. package/dist/src/autor-abzeichen.js.map +0 -1
  101. package/dist/src/autor.d.ts +0 -101
  102. package/dist/src/autor.js.map +0 -1
  103. package/dist/src/bauabdruck-daten.generiert.json +0 -5
  104. package/dist/src/bauabdruck.d.ts +0 -38
  105. package/dist/src/bauabdruck.js.map +0 -1
  106. package/dist/src/bedeutung.d.ts +0 -168
  107. package/dist/src/bedeutung.js.map +0 -1
  108. package/dist/src/bench/cachly-bench.d.ts +0 -61
  109. package/dist/src/bench/cachly-bench.js.map +0 -1
  110. package/dist/src/bench/external-corpus.d.ts +0 -44
  111. package/dist/src/bench/external-corpus.js.map +0 -1
  112. package/dist/src/bench/fixtures.d.ts +0 -37
  113. package/dist/src/bench/fixtures.js.map +0 -1
  114. package/dist/src/brain-export.d.ts +0 -64
  115. package/dist/src/brain-export.js.map +0 -1
  116. package/dist/src/brain-watch-hook.d.ts +0 -21
  117. package/dist/src/brain-watch-hook.js.map +0 -1
  118. package/dist/src/cachly-url.d.ts +0 -68
  119. package/dist/src/cachly-url.js.map +0 -1
  120. package/dist/src/ckg.d.ts +0 -54
  121. package/dist/src/ckg.js.map +0 -1
  122. package/dist/src/cls-hook.d.ts +0 -15
  123. package/dist/src/cls-hook.js.map +0 -1
  124. package/dist/src/confidence.d.ts +0 -19
  125. package/dist/src/confidence.js.map +0 -1
  126. package/dist/src/credentials.d.ts +0 -37
  127. package/dist/src/credentials.js.map +0 -1
  128. package/dist/src/deutsch.d.ts +0 -98
  129. package/dist/src/deutsch.js.map +0 -1
  130. package/dist/src/doctor.d.ts +0 -47
  131. package/dist/src/doctor.js.map +0 -1
  132. package/dist/src/editor.d.ts +0 -9
  133. package/dist/src/editor.js.map +0 -1
  134. package/dist/src/eingaenge-nachruesten.d.ts +0 -41
  135. package/dist/src/eingaenge-nachruesten.js.map +0 -1
  136. package/dist/src/eingaenge.d.ts +0 -135
  137. package/dist/src/eingaenge.js.map +0 -1
  138. package/dist/src/embeddings.d.ts +0 -48
  139. package/dist/src/embeddings.js.map +0 -1
  140. package/dist/src/first-contact.d.ts +0 -56
  141. package/dist/src/first-contact.js.map +0 -1
  142. package/dist/src/first-recall-proof.d.ts +0 -19
  143. package/dist/src/first-recall-proof.js.map +0 -1
  144. package/dist/src/funnel-milestones.d.ts +0 -5
  145. package/dist/src/funnel-milestones.js.map +0 -1
  146. package/dist/src/geliefert.d.ts +0 -63
  147. package/dist/src/geliefert.js.map +0 -1
  148. package/dist/src/handlers/advanced.d.ts +0 -6
  149. package/dist/src/handlers/advanced.js.map +0 -1
  150. package/dist/src/handlers/brain.d.ts +0 -41
  151. package/dist/src/handlers/brain.js.map +0 -1
  152. package/dist/src/handlers/cache.d.ts +0 -6
  153. package/dist/src/handlers/cache.js.map +0 -1
  154. package/dist/src/handlers/context.d.ts +0 -6
  155. package/dist/src/handlers/context.js.map +0 -1
  156. package/dist/src/handlers/fedbrain.d.ts +0 -18
  157. package/dist/src/handlers/fedbrain.js.map +0 -1
  158. package/dist/src/handlers/instances.d.ts +0 -33
  159. package/dist/src/handlers/instances.js.map +0 -1
  160. package/dist/src/handlers/roadmap.d.ts +0 -6
  161. package/dist/src/handlers/roadmap.js.map +0 -1
  162. package/dist/src/handlers/share.d.ts +0 -8
  163. package/dist/src/handlers/share.js.map +0 -1
  164. package/dist/src/handlers/syndicate.d.ts +0 -6
  165. package/dist/src/handlers/syndicate.js.map +0 -1
  166. package/dist/src/handlers/team.d.ts +0 -45
  167. package/dist/src/handlers/team.js.map +0 -1
  168. package/dist/src/handlers/viz.d.ts +0 -6
  169. package/dist/src/handlers/viz.js.map +0 -1
  170. package/dist/src/index.d.ts +0 -93
  171. package/dist/src/index.js.map +0 -1
  172. package/dist/src/kostprobe.d.ts +0 -25
  173. package/dist/src/kostprobe.js.map +0 -1
  174. package/dist/src/lesson-preview.d.ts +0 -81
  175. package/dist/src/lesson-preview.js.map +0 -1
  176. package/dist/src/namespace.d.ts +0 -20
  177. package/dist/src/namespace.js.map +0 -1
  178. package/dist/src/notifier.d.ts +0 -5
  179. package/dist/src/notifier.js.map +0 -1
  180. package/dist/src/rangfolge.d.ts +0 -169
  181. package/dist/src/rangfolge.js.map +0 -1
  182. package/dist/src/raumkorrektur.d.ts +0 -100
  183. package/dist/src/raumkorrektur.js.map +0 -1
  184. package/dist/src/recall-tiefe.d.ts +0 -48
  185. package/dist/src/recall-tiefe.js.map +0 -1
  186. package/dist/src/rerank.d.ts +0 -48
  187. package/dist/src/rerank.js.map +0 -1
  188. package/dist/src/search.d.ts +0 -153
  189. package/dist/src/search.js.map +0 -1
  190. package/dist/src/seltenheitsbestand.d.ts +0 -53
  191. package/dist/src/seltenheitsbestand.js.map +0 -1
  192. package/dist/src/speicherlage.d.ts +0 -51
  193. package/dist/src/speicherlage.js.map +0 -1
  194. package/dist/src/spuren.d.ts +0 -97
  195. package/dist/src/spuren.js.map +0 -1
  196. package/dist/src/starter-corpus.d.ts +0 -28
  197. package/dist/src/starter-corpus.js.map +0 -1
  198. package/dist/src/telemetry-types.d.ts +0 -87
  199. package/dist/src/telemetry-types.js.map +0 -1
  200. package/dist/src/tools.d.ts +0 -2933
  201. package/dist/src/tools.js.map +0 -1
  202. package/dist/src/toolspecs.d.ts +0 -43
  203. package/dist/src/toolspecs.js.map +0 -1
  204. package/dist/src/upgrade-nudge.d.ts +0 -15
  205. package/dist/src/upgrade-nudge.js.map +0 -1
  206. package/dist/src/utils.d.ts +0 -34
  207. package/dist/src/utils.js.map +0 -1
  208. package/dist/src/versuch.d.ts +0 -179
  209. package/dist/src/versuch.js.map +0 -1
  210. package/dist/src/vorspann.d.ts +0 -73
  211. package/dist/src/vorspann.js.map +0 -1
  212. package/dist/src/wartung/vektoren-verdichten.d.ts +0 -50
  213. package/dist/src/wartung/vektoren-verdichten.js.map +0 -1
  214. package/dist/src/werkzeug-nutzung.d.ts +0 -62
  215. package/dist/src/werkzeug-nutzung.js.map +0 -1
  216. package/dist/src/wertbeitrag.d.ts +0 -57
  217. package/dist/src/wertbeitrag.js.map +0 -1
package/README.md CHANGED
@@ -24,7 +24,7 @@
24
24
  <img src="https://img.shields.io/badge/GDPR-EU%20servers-green" alt="GDPR: EU servers" />
25
25
  </a>
26
26
  &nbsp;
27
- <img src="https://img.shields.io/badge/122_MCP_tools-violet" alt="122 MCP tools" />
27
+ <img src="https://img.shields.io/badge/123_MCP_tools-violet" alt="123 MCP tools" />
28
28
  &nbsp;
29
29
  <img src="https://img.shields.io/badge/License-Apache--2.0-yellow" alt="License: Apache-2.0" />
30
30
  </p>
@@ -301,7 +301,7 @@ MCP tool — bulk-ingests past outcomes the same way `brain_from_git` ingests co
301
301
 
302
302
  ---
303
303
 
304
- ## MCP Tools (122 total)
304
+ ## MCP Tools (123 total)
305
305
 
306
306
  The full tool catalog is generated from `sdk/mcp/src/tools.ts`. Cross-surface
307
307
  coverage is tracked in [`../../docs/generated/surface-parity.md`](../../docs/generated/surface-parity.md),
package/dist/index.js CHANGED
@@ -1,7 +1 @@
1
- // Kompatibilitäts-Einstieg: Plattformen wie Glama erzeugen ihre Build-Specs
2
- // automatisch und starten mit `node dist/index.js`. tsc (rootDir ".") legt den
3
- // echten Einstieg aber unter dist/src/index.js ab. Diese Datei kompiliert nach
4
- // dist/index.js und startet dort denselben Server — der Import genügt, weil
5
- // src/index.ts auf Modul-Ebene startet.
6
- import './src/index.js';
7
- //# sourceMappingURL=index.js.map
1
+ import"./src/index.js";
@@ -1,75 +1,2 @@
1
- /**
2
- * telegram-notify — shared Telegram notification client.
3
- *
4
- * Used by cachly-mcp (TypeScript) and TCO backend (Python via client.py).
5
- * Single source of truth for: emoji map, message format, env var convention.
6
- *
7
- * Interface: notify(app, event, payload)
8
- *
9
- * Env vars:
10
- * TELEGRAM_BOT_TOKEN — bot token from @BotFather
11
- * {APP_UPPER}_TELEGRAM_CHAT_ID — app-specific channel (e.g. CACHLY_TELEGRAM_CHAT_ID)
12
- * TELEGRAM_CHAT_ID — fallback for all apps
13
- *
14
- * No-op when unconfigured. Never throws.
15
- *
16
- * Note: emoji map is kept in sync with emojis.json (Python reads the JSON file at runtime).
17
- */
18
- const EMOJI = {
19
- tool_call: '🔧',
20
- tool_error: '🔴',
21
- cache_hit: '⚡',
22
- cache_miss: '💭',
23
- auth_ok: '🔑',
24
- auth_fail: '🚫',
25
- startup: '🚀',
26
- waitlist_signup: '📬',
27
- parse_file: '📄',
28
- parse_text: '📝',
29
- parse_url: '🔗',
30
- trip_created: '🗺️',
31
- trip_deleted: '🗑️',
32
- error: '🔴',
33
- stripe_payment: '💳',
34
- stripe_checkout: '🛒',
35
- new_user: '🆕',
36
- pdf_export: '📑',
37
- trip_search: '🔍',
38
- default: '🔔',
39
- };
40
- function getToken() {
41
- return process.env.TELEGRAM_BOT_TOKEN ?? '';
42
- }
43
- function getChatId(app) {
44
- const key = `${app.toUpperCase()}_TELEGRAM_CHAT_ID`;
45
- return process.env[key] ?? process.env.TELEGRAM_CHAT_ID ?? '';
46
- }
47
- export function formatMessage(app, event, payload) {
48
- const icon = EMOJI[event] ?? EMOJI['default'];
49
- const lines = [`${icon} *${app.toUpperCase()}* · \`${event}\``];
50
- for (const [k, v] of Object.entries(payload)) {
51
- if (v !== null && v !== undefined && v !== '') {
52
- lines.push(` ${k}: \`${String(v)}\``);
53
- }
54
- }
55
- return lines.join('\n');
56
- }
57
- export async function notify(app, event, payload = {}) {
58
- const token = getToken();
59
- const chatId = getChatId(app);
60
- if (!token || !chatId)
61
- return;
62
- try {
63
- const text = formatMessage(app, event, payload);
64
- await fetch(`https://api.telegram.org/bot${token}/sendMessage`, {
65
- method: 'POST',
66
- headers: { 'Content-Type': 'application/json' },
67
- body: JSON.stringify({ chat_id: chatId, text, parse_mode: 'Markdown' }),
68
- signal: AbortSignal.timeout(5000),
69
- });
70
- }
71
- catch {
72
- // never throws
73
- }
74
- }
75
- //# sourceMappingURL=client.js.map
1
+ const i={tool_call:"\u{1F527}",tool_error:"\u{1F534}",cache_hit:"\u26A1",cache_miss:"\u{1F4AD}",auth_ok:"\u{1F511}",auth_fail:"\u{1F6AB}",startup:"\u{1F680}",waitlist_signup:"\u{1F4EC}",parse_file:"\u{1F4C4}",parse_text:"\u{1F4DD}",parse_url:"\u{1F517}",trip_created:"\u{1F5FA}\uFE0F",trip_deleted:"\u{1F5D1}\uFE0F",error:"\u{1F534}",stripe_payment:"\u{1F4B3}",stripe_checkout:"\u{1F6D2}",new_user:"\u{1F195}",pdf_export:"\u{1F4D1}",trip_search:"\u{1F50D}",default:"\u{1F514}"};function c(){return process.env.TELEGRAM_BOT_TOKEN??""}function p(t){const e=`${t.toUpperCase()}_TELEGRAM_CHAT_ID`;return process.env[e]??process.env.TELEGRAM_CHAT_ID??""}function _(t,e,r){const n=[`${i[e]??i.default} *${t.toUpperCase()}* \xB7 \`${e}\``];for(const[a,o]of Object.entries(r))o!=null&&o!==""&&n.push(` ${a}: \`${String(o)}\``);return n.join(`
2
+ `)}async function u(t,e,r={}){const s=c(),n=p(t);if(!(!s||!n))try{const a=_(t,e,r);await fetch(`https://api.telegram.org/bot${s}/sendMessage`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({chat_id:n,text:a,parse_mode:"Markdown"}),signal:AbortSignal.timeout(5e3)})}catch{}}export{_ as formatMessage,u as notify};
@@ -1,253 +1,7 @@
1
- // Ambient Recall (Phase 4) — the CLI core that the hook scripts pipe to.
2
- //
3
- // The SessionStart / UserPromptSubmit hooks (ambient-hooks.ts) pipe Claude Code's
4
- // hook payload JSON on stdin to `npx @cachly-dev/mcp-server ambient-recall`. This
5
- // module is that command's brain: it parses the payload, runs the §6.3 relevance
6
- // gate (ambient-recall.ts), and prints the `hookSpecificOutput` JSON Claude Code
7
- // injects as additionalContext — or nothing at all when the gate says skip.
8
- //
9
- // Design constraints (roadmap §6.3):
10
- // • Pure + dependency-injected (recall is passed in) so it is exhaustively
11
- // unit-tested without any network.
12
- // • Graceful: EVERY failure path returns '' (no output) — the caller exits 0 so
13
- // a crashing hook can never block or corrupt the agent's turn.
14
- // • Self-limited: recall runs under a hard timeout budget; a slow brain never
15
- // stalls the user's prompt.
16
- import { decideRecall, commitInjection, emptyMemory, isTrivialPrompt, selectInjectable, estimateTokens, } from './ambient-recall.js';
17
- /** Parse the hook payload from stdin. Never throws — returns null on any problem. */
18
- export function parseHookPayload(raw) {
19
- const trimmed = (raw ?? '').trim();
20
- if (!trimmed)
21
- return null;
22
- try {
23
- const p = JSON.parse(trimmed);
24
- if (p && typeof p === 'object')
25
- return p;
26
- return null;
27
- }
28
- catch {
29
- return null;
30
- }
31
- }
32
- // PreToolUse fires for every tool; only file-mutating tools carry a wrong-path
33
- // risk worth a recall (roadmap §6.1: file-open briefing on Edit/Write).
34
- const FILE_TOOLS = new Set(['Edit', 'Write', 'MultiEdit', 'NotebookEdit']);
35
- /**
36
- * The query to recall on for a payload, or null when recall should be skipped
37
- * before any brain call:
38
- * • UserPromptSubmit → the prompt, unless it is trivial (§6.3 guardrail 2).
39
- * • SessionStart → a fixed briefing query (there is no user prompt yet);
40
- * skipped on 'compact'/'clear' resumes where a mid-session briefing is noise.
41
- * • PreToolUse → a file-scoped query for Edit/Write-class tools only.
42
- * • Stop → never recalls (it learns instead — see stopObservation).
43
- */
44
- export function recallQueryFor(payload) {
45
- const event = payload.hook_event_name ?? 'UserPromptSubmit';
46
- if (event === 'SessionStart') {
47
- if (payload.source === 'compact' || payload.source === 'clear')
48
- return null;
49
- return 'session start: recent lessons, active pitfalls, and known failure modes for this project';
50
- }
51
- if (event === 'PreToolUse') {
52
- if (payload.tool_name && !FILE_TOOLS.has(payload.tool_name))
53
- return null;
54
- const filePath = payload.tool_input?.file_path;
55
- if (typeof filePath !== 'string' || !filePath.trim())
56
- return null;
57
- return `known pitfalls, past bugs and lessons for file ${filePath.trim()}`;
58
- }
59
- if (event === 'Stop')
60
- return null;
61
- const prompt = (payload.prompt ?? '').trim();
62
- if (!prompt || isTrivialPrompt(prompt))
63
- return null;
64
- return prompt;
65
- }
66
- // Conservative fix-signal: only turns whose final message clearly reports a
67
- // resolved problem become auto-lessons. Anything looser floods the brain with
68
- // junk on every turn-end — the exact noise §6.4 warns about.
69
- const FIX_SIGNAL_RE = /\b(fixed|resolved|behoben|gefixt|root cause|the bug was|der fehler (lag|war)|ursache (war|gefunden)|now passes|tests? (are|is|sind) (green|grün)|deployed successfully)\b/i;
70
- /**
71
- * Derive an auto-learn observation from a Stop payload, or null when the turn
72
- * carries no clear fix signal. Pure — the caller feeds it to auto_learn_session.
73
- */
74
- export function stopObservation(payload) {
75
- const msg = (payload.last_assistant_message ?? '').trim();
76
- if (msg.length < 80)
77
- return null; // too short to describe a real fix
78
- if (!FIX_SIGNAL_RE.test(msg))
79
- return null;
80
- const firstLine = msg.split('\n').find((l) => l.trim().length > 0)?.trim() ?? msg;
81
- return {
82
- action: firstLine.slice(0, 200),
83
- outcome: 'success',
84
- details: msg.slice(0, 500),
85
- severity: 'minor',
86
- };
87
- }
88
- /** Truncate text to fit an estimated token budget (~4 chars/token), on a word edge. */
89
- export function truncateToTokens(text, maxTokens) {
90
- const t = text.trim();
91
- if (estimateTokens(t) <= maxTokens)
92
- return t;
93
- const maxChars = Math.max(0, maxTokens * 4);
94
- const cut = t.slice(0, maxChars);
95
- const lastSpace = cut.lastIndexOf(' ');
96
- return (lastSpace > maxChars * 0.6 ? cut.slice(0, lastSpace) : cut).trimEnd() + '…';
97
- }
98
- /**
99
- * Tiny footer that closes the net-token loop (§6.2): the agent self-reports
100
- * prevented tokens via `ambient-credit` when a recalled lesson changed its path.
101
- * Kept to one short line — its own token cost is counted as injected.
102
- */
103
- export const CREDIT_FOOTER = '(If this memory just saved you from a wrong path, record it: `npx @cachly-dev/mcp-server@latest ambient-credit <tokens-saved>`)';
104
- /**
105
- * Der Beleg: WANN es gelernt wurde und WORAN.
106
- *
107
- * Ohne Beleg ist proaktive Erinnerung eine Behauptung — und Behauptungen einer
108
- * Maschine prueft niemand nach, man glaubt sie oder ignoriert sie. Mit Datum
109
- * und Datei wird daraus eine nachpruefbare Aussage: "am 24.07., in
110
- * infra/backup.sh — ging damals schief". Genau das macht den Unterschied
111
- * zwischen einem Hinweis, dem man folgt, und einem, den man wegklickt.
112
- *
113
- * Bewusst kurz gehalten (eine Zeile, hoechstens zwei Dateien): der Beleg wird
114
- * bei JEDER Erinnerung mitgezahlt und darf das Token-Budget nicht sprengen.
115
- */
116
- export function formatProvenance(l) {
117
- const teile = [];
118
- if (l.learnedAt) {
119
- const d = new Date(l.learnedAt);
120
- if (!Number.isNaN(d.getTime())) {
121
- teile.push(`am ${String(d.getDate()).padStart(2, "0")}.${String(d.getMonth() + 1).padStart(2, "0")}.`);
122
- }
123
- }
124
- if (l.files?.length) {
125
- const f = l.files.slice(0, 2).join(", ");
126
- teile.push(`in ${f}${l.files.length > 2 ? ` (+${l.files.length - 2})` : ""}`);
127
- }
128
- if (l.outcome === "failure")
129
- teile.push("ging damals schief");
130
- else if (l.outcome === "success")
131
- teile.push("so ging es damals gut aus");
132
- return teile.length ? ` ↳ ${teile.join(" · ")}` : "";
133
- }
134
- /**
135
- * Render the gated lessons into a context block for the agent.
136
- * • A single already-formatted briefing (multi-line summary — what smart_recall
137
- * returns) is injected verbatim; it carries its own headings.
138
- * • Multiple short lessons are rendered as a titled bullet list.
139
- * `withCreditFooter` appends the self-report line (per-prompt events only —
140
- * a SessionStart briefing is ambience, not a decision-changing recall).
141
- */
142
- export function formatContextBlock(lessons, withCreditFooter = false) {
143
- if (lessons.length === 0)
144
- return '';
145
- const body = lessons.length === 1 && lessons[0].summary.includes('\n')
146
- ? lessons[0].summary.trim()
147
- : `🧠 Relevant memory from your cachly brain (auto-recalled):\n` +
148
- lessons
149
- .map((l) => {
150
- const beleg = formatProvenance(l);
151
- return `- ${l.summary.trim()}` + (beleg ? '\n' + beleg : '');
152
- })
153
- .join('\n');
154
- return withCreditFooter ? `${body}\n${CREDIT_FOOTER}` : body;
155
- }
156
- /**
157
- * Build the JSON Claude Code expects from a hook. `additionalContext` is spliced
158
- * into the model's context for the turn. Empty context → empty string (no output),
159
- * which Claude Code treats as "hook contributed nothing".
160
- */
161
- export function buildHookOutput(event, additionalContext) {
162
- if (!additionalContext)
163
- return '';
164
- return JSON.stringify({
165
- hookSpecificOutput: {
166
- hookEventName: event,
167
- additionalContext,
168
- },
169
- });
170
- }
171
- /** Resolve a promise to a fallback if it does not settle within `ms`. */
172
- async function withTimeout(p, ms, fallback) {
173
- let timer;
174
- const timeout = new Promise((res) => {
175
- timer = setTimeout(() => res(fallback), ms);
176
- });
177
- try {
178
- return await Promise.race([p, timeout]);
179
- }
180
- finally {
181
- if (timer)
182
- clearTimeout(timer);
183
- }
184
- }
185
- /**
186
- * The whole ambient-recall CLI flow, as a pure function of (stdin, deps).
187
- * Returns the string to print on stdout — either the hookSpecificOutput JSON or
188
- * '' (nothing to inject). Never throws.
189
- */
190
- export async function runAmbient(raw, deps) {
191
- const payload = parseHookPayload(raw);
192
- if (!payload)
193
- return '';
194
- const event = payload.hook_event_name ?? 'UserPromptSubmit';
195
- const query = recallQueryFor(payload);
196
- if (query === null)
197
- return ''; // trivial / non-recallable event → skip before any brain call
198
- // Auto-backoff (§6.3 guardrail 3): when the recent net balance is red, stop
199
- // paying — checked after trivial-skip but before any brain call.
200
- try {
201
- if (deps.backoff && (await deps.backoff()))
202
- return '';
203
- }
204
- catch {
205
- // a broken backoff probe must not disable recall
206
- }
207
- let candidates = [];
208
- try {
209
- candidates = await withTimeout(deps.recall(query, event), deps.timeoutMs ?? 3000, []);
210
- }
211
- catch {
212
- return ''; // recall failed → inject nothing, never block the turn
213
- }
214
- if (!Array.isArray(candidates) || candidates.length === 0)
215
- return '';
216
- // SessionStart/PreToolUse have no user prompt, so their trivial-skip is
217
- // meaningless; pass the recall query itself so the gate's non-trivial branch runs.
218
- const gateInput = event === 'UserPromptSubmit' ? (payload.prompt ?? query) : query;
219
- // Antizipation braucht Timing, nicht nur Relevanz: dieselbe Lektion in jedem
220
- // zweiten Turn ist kein Hinweis mehr, sondern Laerm. `decideRecall` bringt
221
- // Dedupe, Ruhe-Budget und einen risikoabhaengigen Ausloesemoment mit; ohne
222
- // Gedaechtnis-Deps bleibt das Verhalten exakt wie bisher (reiner Gate).
223
- const memory = deps.loadMemory?.() ?? emptyMemory();
224
- const decision = deps.loadMemory
225
- ? decideRecall(gateInput, candidates, memory, deps.gate)
226
- : {
227
- ...selectInjectable(gateInput, candidates, deps.gate),
228
- risk: 'normal',
229
- suppressedDuplicates: [],
230
- };
231
- if (deps.saveMemory) {
232
- try {
233
- deps.saveMemory(commitInjection(memory, decision));
234
- }
235
- catch {
236
- // Gedaechtnis ist Komfort, kein Muss — ein Schreibfehler darf den Turn nie stoppen
237
- }
238
- }
239
- if (!decision.inject)
240
- return '';
241
- // Credit footer only where a recall can change a decision mid-flight.
242
- const withFooter = event === 'UserPromptSubmit' || event === 'PreToolUse';
243
- const context = formatContextBlock(decision.selected, withFooter);
244
- const injectedTokens = estimateTokens(context); // includes the footer's own cost
245
- try {
246
- deps.onInject?.(injectedTokens, event);
247
- }
248
- catch {
249
- // ledger is telemetry — never block the injection over it
250
- }
251
- return buildHookOutput(event, context);
252
- }
253
- //# sourceMappingURL=ambient-cli.js.map
1
+ import{decideRecall as p,commitInjection as h,emptyMemory as g,isTrivialPrompt as y,selectInjectable as d,estimateTokens as l}from"./ambient-recall.js";function b(e){const t=(e??"").trim();if(!t)return null;try{const r=JSON.parse(t);return r&&typeof r=="object"?r:null}catch{return null}}const v=new Set(["Edit","Write","MultiEdit","NotebookEdit"]);function S(e){const t=e.hook_event_name??"UserPromptSubmit";if(t==="SessionStart")return e.source==="compact"||e.source==="clear"?null:"session start: recent lessons, active pitfalls, and known failure modes for this project";if(t==="PreToolUse"){if(e.tool_name&&!v.has(e.tool_name))return null;const n=e.tool_input?.file_path;return typeof n!="string"||!n.trim()?null:`known pitfalls, past bugs and lessons for file ${n.trim()}`}if(t==="Stop")return null;const r=(e.prompt??"").trim();return!r||y(r)?null:r}const k=/\b(fixed|resolved|behoben|gefixt|root cause|the bug was|der fehler (lag|war)|ursache (war|gefunden)|now passes|tests? (are|is|sind) (green|grün)|deployed successfully)\b/i;function I(e){const t=(e.last_assistant_message??"").trim();return t.length<80||!k.test(t)?null:{action:(t.split(`
2
+ `).find(n=>n.trim().length>0)?.trim()??t).slice(0,200),outcome:"success",details:t.slice(0,500),severity:"minor"}}function O(e,t){const r=e.trim();if(l(r)<=t)return r;const n=Math.max(0,t*4),o=r.slice(0,n),i=o.lastIndexOf(" ");return(i>n*.6?o.slice(0,i):o).trimEnd()+"\u2026"}const w="(If this memory just saved you from a wrong path, record it: `npx @cachly-dev/mcp-server@latest ambient-credit <tokens-saved>`)";function x(e){const t=[];if(e.learnedAt){const r=new Date(e.learnedAt);Number.isNaN(r.getTime())||t.push(`am ${String(r.getDate()).padStart(2,"0")}.${String(r.getMonth()+1).padStart(2,"0")}.`)}if(e.files?.length){const r=e.files.slice(0,2).join(", ");t.push(`in ${r}${e.files.length>2?` (+${e.files.length-2})`:""}`)}return e.outcome==="failure"?t.push("ging damals schief"):e.outcome==="success"&&t.push("so ging es damals gut aus"),t.length?` \u21B3 ${t.join(" \xB7 ")}`:""}function _(e,t=!1){if(e.length===0)return"";const r=e.length===1&&e[0].summary.includes(`
3
+ `)?e[0].summary.trim():`\u{1F9E0} Relevant memory from your cachly brain (auto-recalled):
4
+ `+e.map(n=>{const o=x(n);return`- ${n.summary.trim()}`+(o?`
5
+ `+o:"")}).join(`
6
+ `);return t?`${r}
7
+ ${w}`:r}function T(e,t){return t?JSON.stringify({hookSpecificOutput:{hookEventName:e,additionalContext:t}}):""}async function P(e,t,r){let n;const o=new Promise(i=>{n=setTimeout(()=>i(r),t)});try{return await Promise.race([e,o])}finally{n&&clearTimeout(n)}}async function $(e,t){const r=b(e);if(!r)return"";const n=r.hook_event_name??"UserPromptSubmit",o=S(r);if(o===null)return"";try{if(t.backoff&&await t.backoff())return""}catch{}let i=[];try{i=await P(t.recall(o,n),t.timeoutMs??3e3,[])}catch{return""}if(!Array.isArray(i)||i.length===0)return"";const c=n==="UserPromptSubmit"?r.prompt??o:o,a=t.loadMemory?.()??g(),s=t.loadMemory?p(c,i,a,t.gate):{...d(c,i,t.gate),risk:"normal",suppressedDuplicates:[]};if(t.saveMemory)try{t.saveMemory(h(a,s))}catch{}if(!s.inject)return"";const m=n==="UserPromptSubmit"||n==="PreToolUse",u=_(s.selected,m),f=l(u);try{t.onInject?.(f,n)}catch{}return T(n,u)}export{w as CREDIT_FOOTER,T as buildHookOutput,_ as formatContextBlock,x as formatProvenance,b as parseHookPayload,S as recallQueryFor,$ as runAmbient,I as stopObservation,O as truncateToTokens};
@@ -1,55 +1 @@
1
- // Ambient Recall — the production `AmbientDeps` factory.
2
- //
3
- // ambient-cli.ts (`runAmbient`) is dependency-injected on purpose so it can be
4
- // unit-tested without a network. This module is the OTHER half of that seam:
5
- // the one place that turns a raw `smart_recall` string reply into the
6
- // `AmbientDeps.recall` candidates the gate understands — miss-detection,
7
- // truncation and content-based IDs (PR #241 regression: the id must come from
8
- // the summary, never a constant like 'ambient', or "same lesson twice" and
9
- // "different lesson" become indistinguishable in `RecallMemory`).
10
- //
11
- // Previously this mapping lived inline at the `ambient-recall` CLI call site
12
- // in index.ts. Moved here so the exact production wiring — not a hand-copied
13
- // approximation of it — is what the seam test (SDK-003) exercises across
14
- // multiple turns.
15
- import { truncateToTokens } from './ambient-cli.js';
16
- import { candidateIdFor } from './ambient-recall.js';
17
- /** A brain reply that carries no usable lesson — same heuristic as smart_recall's own callers. */
18
- const NO_LESSONS_RE = /No lessons found|no lessons|No matches found/i;
19
- /** Below this length a `smart_recall` reply is boilerplate, not a real lesson. */
20
- const MIN_LESSON_CHARS = 80;
21
- /** Hard cap on how much of one recalled lesson may be injected (§6.2 budget). */
22
- const MAX_LESSON_TOKENS = 500;
23
- /**
24
- * Builds the ONE production `AmbientDeps` used by the `ambient-recall` CLI
25
- * entrypoint. This is the exact mapping that used to be inlined at the call
26
- * site: a miss-detection heuristic, a token-truncated summary, and a
27
- * content-derived candidate id — wrapped around whatever `smartRecall`
28
- * implementation the caller passes in (production: `handleTool('smart_recall', ...)`;
29
- * tests: a stub).
30
- */
31
- export function buildAmbientDeps(opts) {
32
- const { instanceId, smartRecall, loadMemory, saveMemory, backoff, onInject, timeoutMs } = opts;
33
- return {
34
- timeoutMs: timeoutMs ?? 3000, // §6.3 guardrail 4: never stall the prompt on a slow brain
35
- // Ambient injection budget: one relevance-ranked briefing, hard-capped.
36
- // smart_recall already returns its hits best-first, so the capped head is
37
- // the high-signal part. Per-lesson structured candidates are a future slice.
38
- gate: { topK: 1, maxTokens: MAX_LESSON_TOKENS },
39
- loadMemory,
40
- saveMemory,
41
- backoff,
42
- onInject,
43
- recall: async (query) => {
44
- if (!instanceId)
45
- return [];
46
- const text = String((await smartRecall(query)) ?? '');
47
- const miss = text.length < MIN_LESSON_CHARS || NO_LESSONS_RE.test(text);
48
- if (miss)
49
- return [];
50
- const summary = truncateToTokens(text, MAX_LESSON_TOKENS);
51
- return [{ id: candidateIdFor(summary), summary, confidence: 0.9, score: 0.9 }];
52
- },
53
- };
54
- }
55
- //# sourceMappingURL=ambient-deps.js.map
1
+ import{truncateToTokens as f}from"./ambient-cli.js";import{candidateIdFor as S}from"./ambient-recall.js";const l=/No lessons found|no lessons|No matches found/i,N=80,n=500;function E(e){const{instanceId:s,smartRecall:r,loadMemory:c,saveMemory:i,backoff:a,onInject:m,timeoutMs:u}=e;return{timeoutMs:u??3e3,gate:{topK:1,maxTokens:n},loadMemory:c,saveMemory:i,backoff:a,onInject:m,recall:async d=>{if(!s)return[];const t=String(await r(d)??"");if(t.length<N||l.test(t))return[];const o=f(t,n);return[{id:S(o),summary:o,confidence:.9,score:.9}]}}}export{E as buildAmbientDeps};