blun-king-cli 9.0.0 → 9.0.1

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 (55) hide show
  1. package/LIESMICH.txt +1 -7
  2. package/README.md +4 -16
  3. package/bin/blun.js +248 -160
  4. package/bin/core-bootstrap.js +47 -0
  5. package/bin/king.js +277 -1
  6. package/bin/launcher-mode.js +2 -1
  7. package/bin/launcher-runtime.js +221 -0
  8. package/bin/plugin-bootstrap.js +0 -0
  9. package/bin/private-paths.js +0 -0
  10. package/bin/update-lease.js +399 -0
  11. package/bin/update-notice.js +1094 -0
  12. package/blun.mjs +4060 -6667
  13. package/package.json +3 -10
  14. package/skills/screenshot-lesen/SKILL.md +0 -1
  15. package/skills/web-lesen/SKILL.md +0 -1
  16. package/telegram-plugin/dist/bridge.mjs +1 -21
  17. package/mnemo/access_routes.js +0 -692
  18. package/mnemo/agent_governance.js +0 -4242
  19. package/mnemo/agent_mail.js +0 -901
  20. package/mnemo/bootstrap_auto.js +0 -137
  21. package/mnemo/brief_coordination.js +0 -226
  22. package/mnemo/code_read_tools.js +0 -375
  23. package/mnemo/context_preview_tools.js +0 -603
  24. package/mnemo/embeddings.js +0 -66
  25. package/mnemo/external_repo_ops.js +0 -575
  26. package/mnemo/facts/example-project-rules.json +0 -90
  27. package/mnemo/facts/example.json +0 -34
  28. package/mnemo/identity_schema.sql +0 -139
  29. package/mnemo/journal_schema.js +0 -561
  30. package/mnemo/loop_doctor_tools.js +0 -661
  31. package/mnemo/mail_secret_refs.js +0 -150
  32. package/mnemo/mcp.js +0 -9309
  33. package/mnemo/memory_consolidation.js +0 -1914
  34. package/mnemo/memory_health_tools.js +0 -165
  35. package/mnemo/package.json +0 -79
  36. package/mnemo/protected_scope_gate.js +0 -627
  37. package/mnemo/resource_access_control.js +0 -684
  38. package/mnemo/runtime_governance.js +0 -1256
  39. package/mnemo/runtime_turn_gate.js +0 -862
  40. package/mnemo/sandbox.js +0 -143
  41. package/mnemo/schema.sql +0 -389
  42. package/mnemo/shared_utils.js +0 -763
  43. package/mnemo/skills/agent-auto-resume/SKILL.md +0 -56
  44. package/mnemo/skills/agent_hand/SKILL.md +0 -43
  45. package/mnemo/skills/agent_hand/run.js +0 -63
  46. package/mnemo/skills/book_flight/SKILL.md +0 -34
  47. package/mnemo/skills/external_repo_review/SKILL.md +0 -43
  48. package/mnemo/skills/external_repo_review/run.js +0 -73
  49. package/mnemo/skills/pay_invoice/SKILL.md +0 -34
  50. package/mnemo/team_quality_ops.js +0 -944
  51. package/mnemo/timeline_report_tools.js +0 -810
  52. package/mnemo/write_gate_risk.js +0 -80
  53. package/mnemo/writer_health.js +0 -152
  54. package/skills/doku-ingestion/SKILL.md +0 -48
  55. package/skills/doku-ingestion/ingest_docs.py +0 -133
@@ -1,603 +0,0 @@
1
- "use strict";
2
-
3
- const CONTEXT_PREVIEW_TOOL_DEFS = {
4
- mem_context_preview: {
5
- description: "Build a token-budgeted preview of the Mnemo context an agent should load before work: rules, contracts, findings, claims, handoffs, memories, and exact follow-up tool calls.",
6
- inputSchema: {
7
- type: "object",
8
- properties: {
9
- agent_name: { type: "string" },
10
- project: { type: "string" },
11
- task: { type: "string" },
12
- files: { type: "array", items: { type: "string" } },
13
- topics: { type: "array", items: { type: "string" } },
14
- memory_kind: { type: "string", default: "handoff" },
15
- token_budget: { type: "integer", default: 1800 },
16
- max_items: { type: "integer", default: 8 },
17
- },
18
- },
19
- },
20
- };
21
-
22
- function handleContextPreviewTool(db, name, args, options) {
23
- if (!CONTEXT_PREVIEW_TOOL_DEFS[name]) return { handled: false };
24
- return { handled: true, result: contextPreview(db, args || {}, options || {}) };
25
- }
26
-
27
- function contextPreview(db, args, options = {}) {
28
- const agentName = clean(args.agent_name, 80).toLowerCase();
29
- const project = clean(args.project, 140);
30
- const task = clean(args.task || args.summary || args.query, 800);
31
- const files = cleanArray(args.files || args.file_paths, 25, 300);
32
- const explicitTopics = cleanArray(args.topics, 20, 120);
33
- const tokenBudget = clampInt(args.token_budget, 1800, 300, 20000);
34
- const maxItems = clampInt(args.max_items, 8, 3, 30);
35
- const memoryKind = clean(args.memory_kind, 80).toLowerCase() || "handoff";
36
- const inferredTopics = inferTopics([project, task, ...explicitTopics, ...files].join(" "));
37
- const isWebsiteTask = looksLikeWebsiteTask(task, files);
38
- const isCodeTask = looksLikeCodeTask(task, files);
39
- const sections = [];
40
-
41
- addSection(sections, {
42
- key: "session_brief",
43
- title: "Session identity and current focus",
44
- required: true,
45
- selected: true,
46
- priority: 10,
47
- estimated_tokens: 250,
48
- why: "Keeps identity, owner preferences, current task, and continuity loaded before action.",
49
- command: toolCall("mem_session_brief", compactArgs({ agent_name: agentName, project, task, token_budget: 250 })),
50
- });
51
-
52
- const registry = project ? safeGet(db, "SELECT name, domain, live_status, live_url, staging_url, updated_at FROM project_registry WHERE name=?", [project]) : null;
53
- addSection(sections, {
54
- key: "project_registry",
55
- title: "Project registry",
56
- selected: !!registry,
57
- priority: 20,
58
- estimated_tokens: registry ? estimateRowTokens(registry, 220) : 0,
59
- count: registry ? 1 : 0,
60
- why: registry ? "Canonical project coordinates, live/staging URLs, status, and deploy hints exist." : "No registry row found for this project.",
61
- command: project ? toolCall("mem_project_registry_get", { name: project }) : null,
62
- preview: registry ? [redactRow(registry)] : [],
63
- });
64
-
65
- const rules = project ? safeGet(db, "SELECT project, updated_at, required_gates, canonical_nav, design_rules, deploy_rules, notes FROM project_rules WHERE project=?", [project]) : null;
66
- addSection(sections, {
67
- key: "project_rules",
68
- title: "Project rules",
69
- required: !!rules,
70
- selected: !!rules,
71
- priority: 25,
72
- estimated_tokens: rules ? estimateRowTokens(rules, 420) : 0,
73
- count: rules ? 1 : 0,
74
- why: rules ? "Project-specific gates, navigation, design, deploy, and owner rules must constrain the work." : "No project rules row found.",
75
- command: project ? toolCall("mem_project_rules_get", { project }) : null,
76
- preview: rules ? [redactRow(rules)] : [],
77
- });
78
-
79
- const siteContract = project ? safeGet(db, "SELECT project, canonical_url, target_urls, paths, forbidden_hosts, required_locales, header_rules, menu_rules, footer_rules, logo_rules, auth_rules, pricing_rules, checkout_rules, required_checks, updated_at FROM site_contract WHERE project=?", [project]) : null;
80
- addSection(sections, {
81
- key: "site_contract",
82
- title: "Website contract",
83
- required: !!siteContract && isWebsiteTask,
84
- selected: !!siteContract && (isWebsiteTask || project),
85
- priority: 30,
86
- estimated_tokens: siteContract ? estimateRowTokens(siteContract, 520) : 0,
87
- count: siteContract ? 1 : 0,
88
- why: siteContract ? "Canonical website source, target URLs, forbidden hosts, locales, header/menu/footer/logo/auth/pricing/checkout rules." : "No website contract stored for this project.",
89
- command: project ? toolCall("mem_site_contract_get", { project }) : null,
90
- preview: siteContract ? [contractPreview(siteContract)] : [],
91
- });
92
-
93
- const training = selectTrainingRules(db, agentName, project, maxItems);
94
- addSection(sections, {
95
- key: "training_rules",
96
- title: "Active training rules",
97
- required: training.length > 0,
98
- selected: training.length > 0,
99
- priority: 35,
100
- estimated_tokens: estimateRowsTokens(training, 480),
101
- count: training.length,
102
- why: training.length ? "Owner/reviewer corrections that must prevent repeated mistakes." : "No active training rules found for this agent/project.",
103
- command: toolCall("mem_agent_training_rules", compactArgs({ agent_name: agentName, project, limit: maxItems })),
104
- preview: training.map(redactRow),
105
- });
106
-
107
- const openFindings = selectOpenFindings(db, project, maxItems);
108
- addSection(sections, {
109
- key: "open_findings",
110
- title: "Open quality findings",
111
- selected: openFindings.length > 0,
112
- priority: 45,
113
- estimated_tokens: estimateRowsTokens(openFindings, 420),
114
- count: openFindings.length,
115
- why: openFindings.length ? "Known defects and regressions must be considered before new work." : "No open findings found for this project.",
116
- command: toolCall("mem_quality_finding_list", compactArgs({ project, status: "open", limit: maxItems })),
117
- preview: openFindings.map(redactRow),
118
- });
119
-
120
- const claims = selectActiveClaims(db, project, maxItems);
121
- addSection(sections, {
122
- key: "active_claims",
123
- title: "Active work claims",
124
- selected: claims.length > 0,
125
- priority: 50,
126
- estimated_tokens: estimateRowsTokens(claims, 300),
127
- count: claims.length,
128
- why: claims.length ? "Avoids overlapping edits and duplicate work." : "No active claims found for this project.",
129
- command: toolCall("mem_work_active", compactArgs({ project, limit: maxItems })),
130
- preview: claims.map(redactRow),
131
- });
132
-
133
- const handoffOverride = options.recentHandoffs && Array.isArray(options.recentHandoffs.rows)
134
- ? options.recentHandoffs
135
- : null;
136
- const handoffs = handoffOverride
137
- ? handoffOverride.rows.slice(0, maxItems)
138
- : selectRecentHandoffs(db, agentName, project, maxItems);
139
- const handoffSource = handoffOverride
140
- ? clean(handoffOverride.source, 160) || "hub-primary:mem_recall_ids"
141
- : "local-sqlite";
142
- addSection(sections, {
143
- key: "recent_handoffs",
144
- title: "Recent handoffs",
145
- selected: handoffs.length > 0,
146
- priority: 60,
147
- estimated_tokens: estimateRowsTokens(handoffs, 520),
148
- count: handoffs.length,
149
- why: handoffs.length ? "Shows what was changed, tested, deployed, blocked, and left open across sessions." : "No recent handoffs found.",
150
- command: toolCall("mem_recall_ids", {
151
- query: recallQuery(project, task, inferredTopics),
152
- kind: memoryKind,
153
- include_journal: false,
154
- include_superseded: false,
155
- limit: Math.min(maxItems, 5),
156
- }),
157
- preview: handoffs.map(redactRow),
158
- source: handoffSource,
159
- });
160
-
161
- const decisions = selectDecisions(db, project, task, maxItems);
162
- addSection(sections, {
163
- key: "decisions",
164
- title: "Active decisions",
165
- selected: decisions.length > 0,
166
- priority: 65,
167
- estimated_tokens: estimateRowsTokens(decisions, 520),
168
- count: decisions.length,
169
- why: decisions.length ? "Existing architectural or product decisions prevent silent drift." : "No matching active decisions found.",
170
- command: toolCall("mem_decision_get", compactArgs({ scope: project || undefined, status: "active", limit: Math.min(maxItems, 5) })),
171
- preview: decisions.map(redactRow),
172
- });
173
-
174
- const briefs = selectPendingBriefs(db, agentName, maxItems);
175
- addSection(sections, {
176
- key: "pending_briefs",
177
- title: "Pending briefs",
178
- required: briefs.length > 0,
179
- selected: briefs.length > 0,
180
- priority: 70,
181
- estimated_tokens: estimateRowsTokens(briefs, 420),
182
- count: briefs.length,
183
- why: briefs.length ? "Direct instructions waiting in the agent inbox." : "No pending brief found for this agent.",
184
- command: toolCall("mem_brief_list", compactArgs({ agent_name: agentName, status: "pending", limit: maxItems })),
185
- preview: briefs.map(redactRow),
186
- });
187
-
188
- const actions = selectRecentActions(db, agentName, project, maxItems);
189
- addSection(sections, {
190
- key: "recent_actions",
191
- title: "Recent actions",
192
- selected: actions.length > 0,
193
- priority: 80,
194
- estimated_tokens: estimateRowsTokens(actions, 380),
195
- count: actions.length,
196
- why: actions.length ? "Prevents repeating already-finished work or missing recent failures." : "No recent actions found for this agent/project.",
197
- command: toolCall("mem_actions_recent", compactArgs({ agent_name: agentName, limit: maxItems })),
198
- preview: actions.map(redactRow),
199
- });
200
-
201
- const memoryOverride = options.memoryCandidates && Array.isArray(options.memoryCandidates.rows)
202
- ? options.memoryCandidates
203
- : null;
204
- const memories = memoryOverride
205
- ? memoryOverride.rows.slice(0, maxItems)
206
- : selectMemoryCandidates(db, project, inferredTopics, task, maxItems);
207
- const memorySource = memoryOverride
208
- ? clean(memoryOverride.source, 160) || "hub-primary:mem_recall_ids"
209
- : "local-sqlite";
210
- addSection(sections, {
211
- key: "memory_candidates",
212
- title: "Relevant memory candidates",
213
- selected: memories.length > 0,
214
- priority: 90,
215
- estimated_tokens: estimateRowsTokens(memories, 520),
216
- count: memories.length,
217
- why: memories.length ? "Small snippets point to exact memory IDs; fetch full rows only when needed." : "No matching memory snippets found.",
218
- command: toolCall("mem_recall_ids", compactArgs({
219
- query: recallQuery(project, task, inferredTopics),
220
- kind: memoryKind,
221
- include_journal: false,
222
- include_superseded: false,
223
- limit: maxItems,
224
- })),
225
- preview: memories.map(redactRow),
226
- source: memorySource,
227
- });
228
-
229
- addSection(sections, {
230
- key: "smart_code_read",
231
- title: "Smart code read plan",
232
- required: files.length > 0 && isCodeTask,
233
- selected: files.length > 0 || isCodeTask,
234
- priority: 95,
235
- estimated_tokens: files.length ? 120 + files.length * 90 : 180,
236
- count: files.length,
237
- why: files.length ? "Outlines named files before any full read, then unfolds only needed symbols/ranges." : "Use outlines before opening large code files when code changes start.",
238
- command: files.length
239
- ? files.slice(0, maxItems).map((filePath) => toolCall("mem_code_outline", { file_path: filePath, query: task || project || "task", max_symbols: 25 }))
240
- : toolCall("mem_code_outline", { file_path: "<relevant-file>", query: task || project || "task", max_symbols: 25 }),
241
- preview: files.slice(0, maxItems).map((filePath) => ({ file_path: filePath, next: "mem_code_outline, then mem_code_unfold for the needed symbol/range" })),
242
- });
243
-
244
- const warnings = applyBudget(sections, tokenBudget);
245
- const selected = sections.filter((section) => section.selected);
246
- const estimatedSelectedTokens = selected.reduce((sum, section) => sum + section.estimated_tokens, 0);
247
-
248
- return {
249
- agent_name: agentName || null,
250
- project: project || null,
251
- task: task || null,
252
- token_budget: tokenBudget,
253
- estimated_selected_tokens: estimatedSelectedTokens,
254
- topics: inferredTopics,
255
- context_sources: {
256
- preview_store: clean(options.previewSource, 160) || "local-sqlite",
257
- recent_handoffs: handoffSource,
258
- memory_candidates: memorySource,
259
- },
260
- sections: sections.map(publicSection),
261
- recommended_order: selected.sort((a, b) => a.priority - b.priority).map((section) => section.key),
262
- fetch_plan: selected.sort((a, b) => a.priority - b.priority).map((section) => ({ key: section.key, command: section.command })).filter((item) => item.command),
263
- warnings,
264
- next_step: "Fetch only the selected sections. Use mem_get only for exact IDs, mem_code_unfold only for needed symbols/ranges, then write the pre-work guard before editing.",
265
- };
266
- }
267
-
268
- function addSection(sections, section) {
269
- sections.push(Object.assign({
270
- key: "",
271
- title: "",
272
- required: false,
273
- selected: false,
274
- priority: 100,
275
- estimated_tokens: 0,
276
- count: 0,
277
- why: "",
278
- command: null,
279
- preview: [],
280
- }, section));
281
- }
282
-
283
- function applyBudget(sections, tokenBudget) {
284
- const warnings = [];
285
- let total = selectedTotal(sections);
286
- if (total <= tokenBudget) return warnings;
287
- const droppable = sections
288
- .filter((section) => section.selected && !section.required)
289
- .sort((a, b) => b.priority - a.priority);
290
- for (const section of droppable) {
291
- if (total <= tokenBudget) break;
292
- section.selected = false;
293
- section.deferred_reason = `Deferred to stay inside token_budget=${tokenBudget}. Fetch only if pre-work needs it.`;
294
- total -= section.estimated_tokens;
295
- }
296
- if (total > tokenBudget) {
297
- warnings.push(`Required context is estimated at ${total} tokens, above token_budget=${tokenBudget}. Keep summaries tight and fetch details only by exact ID.`);
298
- } else {
299
- warnings.push(`Some optional context was deferred to stay inside token_budget=${tokenBudget}.`);
300
- }
301
- return warnings;
302
- }
303
-
304
- function selectedTotal(sections) {
305
- return sections.filter((section) => section.selected).reduce((sum, section) => sum + section.estimated_tokens, 0);
306
- }
307
-
308
- function publicSection(section) {
309
- return {
310
- key: section.key,
311
- title: section.title,
312
- selected: !!section.selected,
313
- required: !!section.required,
314
- estimated_tokens: section.estimated_tokens,
315
- count: section.count || 0,
316
- why: section.why,
317
- source: section.source || undefined,
318
- deferred_reason: section.deferred_reason || undefined,
319
- preview: section.preview || [],
320
- };
321
- }
322
-
323
- function selectTrainingRules(db, agentName, project, limit) {
324
- if (!tableExists(db, "agent_training_rule")) return [];
325
- return safeAll(db, `
326
- SELECT id, agent_name, scope, project, rule_kind, title, severity, updated_at
327
- FROM agent_training_rule
328
- WHERE status='active'
329
- AND (?='' OR agent_name IS NULL OR lower(agent_name)=lower(?))
330
- AND (?='' OR project IS NULL OR project=?)
331
- ORDER BY CASE severity WHEN 'H' THEN 0 WHEN 'M' THEN 1 ELSE 2 END, updated_at DESC
332
- LIMIT ?`, [agentName, agentName, project, project, limit]);
333
- }
334
-
335
- function selectOpenFindings(db, project, limit) {
336
- if (!tableExists(db, "quality_finding")) return [];
337
- return safeAll(db, `
338
- SELECT id, project, category, severity, title, url, status, updated_at
339
- FROM quality_finding
340
- WHERE COALESCE(status,'open')='open'
341
- AND (?='' OR project=?)
342
- ORDER BY CASE severity WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 ELSE 3 END, updated_at DESC
343
- LIMIT ?`, [project, project, limit]);
344
- }
345
-
346
- function selectActiveClaims(db, project, limit) {
347
- if (!tableExists(db, "work_claim")) return [];
348
- return safeAll(db, `
349
- SELECT id, project, file_path, agent_name, summary, expires_at
350
- FROM work_claim
351
- WHERE status='active'
352
- AND expires_at > strftime('%Y-%m-%dT%H:%M:%fZ','now')
353
- AND (?='' OR project=?)
354
- ORDER BY expires_at ASC
355
- LIMIT ?`, [project, project, limit]);
356
- }
357
-
358
- function selectRecentHandoffs(db, agentName, project, limit) {
359
- if (!tableExists(db, "session_handoff")) return [];
360
- return safeAll(db, `
361
- SELECT id, agent_name, project, summary, changed_files, tests, deploys, blockers, next_actions, created_at
362
- FROM session_handoff
363
- WHERE (?='' OR lower(agent_name)=lower(?))
364
- AND (?='' OR project=?)
365
- ORDER BY created_at DESC
366
- LIMIT ?`, [agentName, agentName, project, project, Math.min(limit, 5)]);
367
- }
368
-
369
- function selectDecisions(db, project, task, limit) {
370
- if (!tableExists(db, "decision_log")) return [];
371
- const terms = [project, ...inferTopics(task)].filter(Boolean).slice(0, 4);
372
- if (!terms.length) {
373
- return safeAll(db, `
374
- SELECT id, scope, title, substr(body,1,220) AS body, decided_by, decided_at, status
375
- FROM decision_log
376
- WHERE status='active'
377
- ORDER BY decided_at DESC
378
- LIMIT ?`, [Math.min(limit, 5)]);
379
- }
380
- const where = terms.map(() => "(scope LIKE ? OR title LIKE ? OR body LIKE ?)").join(" OR ");
381
- const params = [];
382
- for (const term of terms) params.push(like(term), like(term), like(term));
383
- params.push(Math.min(limit, 5));
384
- return safeAll(db, `
385
- SELECT id, scope, title, substr(body,1,220) AS body, decided_by, decided_at, status
386
- FROM decision_log
387
- WHERE status='active' AND (${where})
388
- ORDER BY decided_at DESC
389
- LIMIT ?`, params);
390
- }
391
-
392
- function selectPendingBriefs(db, agentName, limit) {
393
- if (!agentName || !tableExists(db, "agent_brief")) return [];
394
- return safeAll(db, `
395
- SELECT id, agent_name, source_agent, substr(content,1,260) AS preview, created_at
396
- FROM agent_brief
397
- WHERE status='pending'
398
- AND lower(agent_name)=lower(?)
399
- ORDER BY created_at DESC
400
- LIMIT ?`, [agentName, limit]);
401
- }
402
-
403
- function selectRecentActions(db, agentName, project, limit) {
404
- if (!tableExists(db, "agent_action")) return [];
405
- return safeAll(db, `
406
- SELECT id, agent_name, action_kind, target, status, topic, started_at
407
- FROM agent_action
408
- WHERE (?='' OR lower(agent_name)=lower(?))
409
- AND (?='' OR lower(target)=lower(?) OR lower(topic)=lower(?))
410
- ORDER BY started_at DESC
411
- LIMIT ?`, [agentName, agentName, project, project, project, limit]);
412
- }
413
-
414
- function selectMemoryCandidates(db, project, topics, task, limit) {
415
- if (!tableExists(db, "memory")) return [];
416
- const terms = [project, ...topics, ...inferTopics(task)].filter(Boolean).slice(0, 5);
417
- if (!terms.length) return [];
418
- const where = terms.map(() => "(topic LIKE ? OR text LIKE ?)").join(" OR ");
419
- const params = [];
420
- for (const term of terms) params.push(like(term), like(term));
421
- params.push(limit);
422
- return safeAll(db, `
423
- SELECT id, kind, actor, topic, importance, occurred_at, substr(text,1,260) AS preview
424
- FROM memory
425
- WHERE COALESCE(status, 'active') = 'active'
426
- AND (${where})
427
- ORDER BY importance DESC, occurred_at DESC
428
- LIMIT ?`, params);
429
- }
430
-
431
- function contractPreview(row) {
432
- const out = redactRow(row);
433
- for (const key of ["target_urls", "paths", "forbidden_hosts", "required_locales", "required_checks"]) {
434
- out[key] = safeJsonSummary(row[key], 6);
435
- }
436
- for (const key of ["header_rules", "menu_rules", "footer_rules", "logo_rules", "auth_rules", "pricing_rules", "checkout_rules"]) {
437
- out[key] = safeJsonSummary(row[key], 4);
438
- }
439
- return out;
440
- }
441
-
442
- function safeJsonSummary(value, maxItems) {
443
- if (!value) return null;
444
- const text = String(value);
445
- try {
446
- const parsed = JSON.parse(text);
447
- if (Array.isArray(parsed)) return parsed.slice(0, maxItems);
448
- if (parsed && typeof parsed === "object") {
449
- const keys = Object.keys(parsed).slice(0, maxItems);
450
- const out = {};
451
- for (const key of keys) out[key] = parsed[key];
452
- return out;
453
- }
454
- return parsed;
455
- } catch {
456
- return redact(text, 260);
457
- }
458
- }
459
-
460
- function toolCall(tool, args) {
461
- return { tool, args: compactArgs(args || {}) };
462
- }
463
-
464
- function compactArgs(args) {
465
- const out = {};
466
- for (const [key, value] of Object.entries(args || {})) {
467
- if (value === undefined || value === null || value === "") continue;
468
- out[key] = value;
469
- }
470
- return out;
471
- }
472
-
473
- function safeGet(db, sql, params) {
474
- try { return db.prepare(sql).get(...(params || [])) || null; } catch { return null; }
475
- }
476
-
477
- function safeAll(db, sql, params) {
478
- try { return db.prepare(sql).all(...(params || [])); } catch { return []; }
479
- }
480
-
481
- function tableExists(db, name) {
482
- try {
483
- return !!db.prepare("SELECT name FROM sqlite_master WHERE type IN ('table','view') AND name=?").get(name);
484
- } catch {
485
- return false;
486
- }
487
- }
488
-
489
- function clean(value, max) {
490
- const text = String(value || "").replace(/\s+/g, " ").trim();
491
- if (!text) return "";
492
- return text.length <= max ? text : text.slice(0, max - 1).trimEnd() + "...";
493
- }
494
-
495
- function cleanArray(value, maxItems, maxLen) {
496
- const items = Array.isArray(value) ? value : (typeof value === "string" ? value.split(/[,\n]/) : []);
497
- const seen = new Set();
498
- const out = [];
499
- for (const item of items) {
500
- const cleanItem = clean(item, maxLen);
501
- const key = cleanItem.toLowerCase();
502
- if (!cleanItem || seen.has(key)) continue;
503
- seen.add(key);
504
- out.push(cleanItem);
505
- if (out.length >= maxItems) break;
506
- }
507
- return out;
508
- }
509
-
510
- function clampInt(value, fallback, min, max) {
511
- const parsed = Number.parseInt(value, 10);
512
- if (!Number.isFinite(parsed)) return fallback;
513
- return Math.max(min, Math.min(max, parsed));
514
- }
515
-
516
- function inferTopics(text) {
517
- const normalized = String(text || "").toLowerCase();
518
- const stop = new Set(["the","and","for","with","that","this","from","eine","einen","einer","oder","aber","auch","alle","alles","muss","soll","sind","ist","nicht","noch","jetzt","dann","bitte","wenn","werden","wurde","wird","das","der","die","den","dem","auf","aus","bei","was","wie","wir","ihr"]);
519
- const words = normalized.match(/[a-z0-9][a-z0-9._-]{2,}/g) || [];
520
- const seen = new Set();
521
- const out = [];
522
- for (const word of words) {
523
- const compact = word.replace(/^[._-]+|[._-]+$/g, "");
524
- if (!compact || stop.has(compact) || seen.has(compact)) continue;
525
- seen.add(compact);
526
- out.push(compact);
527
- if (out.length >= 8) break;
528
- }
529
- return out;
530
- }
531
-
532
- function recallQuery(project, task, topics) {
533
- const parts = [project, ...topics.slice(0, 5), clean(task, 120)].filter(Boolean);
534
- return parts.join(" ").slice(0, 500);
535
- }
536
-
537
- function contextPreviewRecallInput(args = {}) {
538
- const project = clean(args.project, 140);
539
- const task = clean(args.task || args.summary || args.query, 800);
540
- const files = cleanArray(args.files || args.file_paths, 25, 300);
541
- const explicitTopics = cleanArray(args.topics, 20, 120);
542
- const inferredTopics = inferTopics([project, task, ...explicitTopics, ...files].join(" "));
543
- return {
544
- query: recallQuery(project, task, inferredTopics),
545
- kind: clean(args.memory_kind, 80).toLowerCase() || "handoff",
546
- limit: clampInt(args.max_items, 8, 3, 30),
547
- include_journal: false,
548
- include_superseded: false,
549
- mode: "hybrid",
550
- };
551
- }
552
-
553
- function looksLikeWebsiteTask(task, files) {
554
- const haystack = [task, ...(files || [])].join(" ").toLowerCase();
555
- return /(website|landing|seite|seiten|header|footer|menu|menue|menü|logo|link|impressum|sprache|language|locale|darkmode|pricing|price|checkout|vat|oss|login|auth|mobile|responsive|domain)/i.test(haystack);
556
- }
557
-
558
- function looksLikeCodeTask(task, files) {
559
- const haystack = [task, ...(files || [])].join(" ").toLowerCase();
560
- return files.length > 0 || /(code|coding|programm|fix|bug|api|endpoint|schema|test|repo|file|datei|function|class|component|css|js|ts|tsx|py|php|html)/i.test(haystack);
561
- }
562
-
563
- function like(term) {
564
- return `%${String(term || "").replace(/[%_]/g, "")}%`;
565
- }
566
-
567
- function estimateRowTokens(row, cap) {
568
- return Math.min(cap, Math.max(80, estimateTokens(JSON.stringify(redactRow(row)))));
569
- }
570
-
571
- function estimateRowsTokens(rows, cap) {
572
- if (!rows || !rows.length) return 0;
573
- return Math.min(cap, 80 + rows.reduce((sum, row) => sum + estimateTokens(JSON.stringify(redactRow(row))), 0));
574
- }
575
-
576
- function estimateTokens(text) {
577
- return Math.ceil(String(text || "").length / 4);
578
- }
579
-
580
- function redactRow(row) {
581
- const out = {};
582
- for (const [key, value] of Object.entries(row || {})) {
583
- out[key] = typeof value === "string" ? redact(value, 260) : value;
584
- }
585
- return out;
586
- }
587
-
588
- function redact(value, max) {
589
- let text = String(value || "");
590
- text = text.replace(/<private>[\s\S]*?<\/private>/gi, "[private]");
591
- text = text.replace(/\b(sk|pk|rk|ghp|gho|github_pat)_[A-Za-z0-9_=-]{12,}\b/g, "[secret]");
592
- text = text.replace(/\b(password|passwd|token|secret|api[_-]?key)\s*[:=]\s*[^,\s;}]+/gi, "$1=[secret]");
593
- text = text.replace(/\s+/g, " ").trim();
594
- if (!text || text.length <= max) return text;
595
- return text.slice(0, max - 1).trimEnd() + "...";
596
- }
597
-
598
- module.exports = {
599
- CONTEXT_PREVIEW_TOOL_DEFS,
600
- handleContextPreviewTool,
601
- contextPreview,
602
- contextPreviewRecallInput,
603
- };
@@ -1,66 +0,0 @@
1
- "use strict";
2
- /**
3
- * embeddings.js — Mnemo embedding layer.
4
- *
5
- * Backend: @xenova/transformers (Xenova/all-MiniLM-L6-v2, 384 dim, ONNX)
6
- * runs in pure JS, no Python, no GPU. ~30MB model auto-downloaded on first use.
7
- *
8
- * Provides:
9
- * - embedText(text) -> Float32Array (length 384)
10
- * - bufFromVector(vec) -> Buffer (4*N bytes, little-endian)
11
- * - vectorFromBuf(buf) -> Float32Array
12
- * - cosine(a, b) -> number in [-1, 1]
13
- */
14
-
15
- const MODEL_NAME = process.env.MNEMO_EMBED_MODEL || "Xenova/all-MiniLM-L6-v2";
16
- const DIM = 384;
17
- const MODEL_TAG = "all-MiniLM-L6-v2";
18
-
19
- let _pipeline = null;
20
- let _loadingPromise = null;
21
-
22
- async function getPipeline() {
23
- if (_pipeline) return _pipeline;
24
- if (_loadingPromise) return _loadingPromise;
25
- _loadingPromise = (async () => {
26
- const { pipeline, env } = await import("@xenova/transformers");
27
- env.cacheDir = process.env.MNEMO_MODEL_CACHE || "/root/mnemo/.models";
28
- _pipeline = await pipeline("feature-extraction", MODEL_NAME, { quantized: true });
29
- return _pipeline;
30
- })();
31
- return _loadingPromise;
32
- }
33
-
34
- async function embedText(text) {
35
- const pipe = await getPipeline();
36
- const out = await pipe(text, { pooling: "mean", normalize: true });
37
- return new Float32Array(out.data);
38
- }
39
-
40
- function bufFromVector(vec) {
41
- const buf = Buffer.alloc(vec.length * 4);
42
- for (let i = 0; i < vec.length; i++) buf.writeFloatLE(vec[i], i * 4);
43
- return buf;
44
- }
45
-
46
- function vectorFromBuf(buf) {
47
- const n = buf.length / 4;
48
- const vec = new Float32Array(n);
49
- for (let i = 0; i < n; i++) vec[i] = buf.readFloatLE(i * 4);
50
- return vec;
51
- }
52
-
53
- function cosine(a, b) {
54
- if (a.length !== b.length) throw new Error("dim mismatch");
55
- let dot = 0, na = 0, nb = 0;
56
- for (let i = 0; i < a.length; i++) {
57
- dot += a[i] * b[i]; na += a[i] * a[i]; nb += b[i] * b[i];
58
- }
59
- const denom = Math.sqrt(na) * Math.sqrt(nb);
60
- return denom ? dot / denom : 0;
61
- }
62
-
63
- module.exports = {
64
- MODEL_TAG, DIM,
65
- embedText, bufFromVector, vectorFromBuf, cosine,
66
- };