@yamo/memory-mesh 2.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/LICENSE +21 -0
  2. package/README.md +80 -0
  3. package/bin/memory_mesh.js +69 -0
  4. package/bin/scrubber.js +81 -0
  5. package/index.d.ts +111 -0
  6. package/lib/adapters/index.js +3 -0
  7. package/lib/embeddings/factory.js +150 -0
  8. package/lib/embeddings/index.js +2 -0
  9. package/lib/embeddings/service.js +586 -0
  10. package/lib/index.js +18 -0
  11. package/lib/lancedb/client.js +631 -0
  12. package/lib/lancedb/config.js +215 -0
  13. package/lib/lancedb/errors.js +144 -0
  14. package/lib/lancedb/index.js +4 -0
  15. package/lib/lancedb/schema.js +197 -0
  16. package/lib/memory/index.js +3 -0
  17. package/lib/memory/memory-context-manager.js +388 -0
  18. package/lib/memory/memory-mesh.js +910 -0
  19. package/lib/memory/memory-translator.js +130 -0
  20. package/lib/memory/migrate-memory.js +227 -0
  21. package/lib/memory/migrate-to-v2.js +120 -0
  22. package/lib/memory/scorer.js +85 -0
  23. package/lib/memory/vector-memory.js +364 -0
  24. package/lib/privacy/audit-logger.js +176 -0
  25. package/lib/privacy/dlp-redactor.js +72 -0
  26. package/lib/privacy/index.js +10 -0
  27. package/lib/reporting/skill-report-generator.js +283 -0
  28. package/lib/scrubber/.gitkeep +1 -0
  29. package/lib/scrubber/config/defaults.js +62 -0
  30. package/lib/scrubber/errors/scrubber-error.js +43 -0
  31. package/lib/scrubber/index.js +25 -0
  32. package/lib/scrubber/scrubber.js +130 -0
  33. package/lib/scrubber/stages/chunker.js +103 -0
  34. package/lib/scrubber/stages/metadata-annotator.js +74 -0
  35. package/lib/scrubber/stages/normalizer.js +59 -0
  36. package/lib/scrubber/stages/semantic-filter.js +61 -0
  37. package/lib/scrubber/stages/structural-cleaner.js +82 -0
  38. package/lib/scrubber/stages/validator.js +66 -0
  39. package/lib/scrubber/telemetry.js +66 -0
  40. package/lib/scrubber/utils/hash.js +39 -0
  41. package/lib/scrubber/utils/html-parser.js +45 -0
  42. package/lib/scrubber/utils/pattern-matcher.js +63 -0
  43. package/lib/scrubber/utils/token-counter.js +31 -0
  44. package/lib/search/filter.js +275 -0
  45. package/lib/search/hybrid.js +137 -0
  46. package/lib/search/index.js +3 -0
  47. package/lib/search/pattern-miner.js +160 -0
  48. package/lib/utils/error-sanitizer.js +84 -0
  49. package/lib/utils/handoff-validator.js +85 -0
  50. package/lib/utils/index.js +4 -0
  51. package/lib/utils/spinner.js +190 -0
  52. package/lib/utils/streaming-client.js +128 -0
  53. package/package.json +39 -0
  54. package/skills/SKILL.md +462 -0
  55. package/skills/skill-scrubber.yamo +41 -0
@@ -0,0 +1,85 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+
4
+ /**
5
+ * Handoff Package Validator
6
+ * Verifies that a handoff package is complete and valid.
7
+ */
8
+ class HandoffValidator {
9
+ constructor(packagePath) {
10
+ this.packagePath = packagePath;
11
+ this.requiredFiles = [
12
+ 'context.md',
13
+ 'memory.json',
14
+ 'metadata.json'
15
+ ];
16
+ }
17
+
18
+ validate() {
19
+ /** @type {{valid: boolean, errors: string[], warnings: string[], summary: string}} */
20
+ const result = {
21
+ valid: true,
22
+ errors: [],
23
+ warnings: [],
24
+ summary: ''
25
+ };
26
+
27
+ if (!fs.existsSync(this.packagePath)) {
28
+ result.valid = false;
29
+ result.errors.push(`Package path not found: ${this.packagePath}`);
30
+ return result;
31
+ }
32
+
33
+ // Check required files
34
+ for (const file of this.requiredFiles) {
35
+ const filePath = path.join(this.packagePath, file);
36
+ if (!fs.existsSync(filePath)) {
37
+ result.valid = false;
38
+ result.errors.push(`Missing required file: ${file}`);
39
+ }
40
+ }
41
+
42
+ // Validate metadata
43
+ const metadataPath = path.join(this.packagePath, 'metadata.json');
44
+ if (fs.existsSync(metadataPath)) {
45
+ try {
46
+ const metadata = JSON.parse(fs.readFileSync(metadataPath, 'utf8'));
47
+ if (!metadata.timestamp) result.warnings.push('Metadata missing timestamp');
48
+ if (!metadata.source) result.warnings.push('Metadata missing source');
49
+ } catch (e) {
50
+ result.valid = false;
51
+ result.errors.push('Invalid metadata.json format');
52
+ }
53
+ }
54
+
55
+ result.summary = result.valid
56
+ ? `✅ Package valid with ${result.warnings.length} warnings`
57
+ : `❌ Package invalid with ${result.errors.length} errors`;
58
+
59
+ return result;
60
+ }
61
+ }
62
+
63
+ // CLI usage
64
+ if (process.argv[1] === import.meta.url) {
65
+ const pkgPath = process.argv[2] || '.';
66
+ const validator = new HandoffValidator(pkgPath);
67
+ const result = validator.validate();
68
+
69
+ if (!result.valid) {
70
+ console.log(result.summary);
71
+ console.log('\n❌ Errors found:');
72
+ result.errors.forEach(err => console.log(` - ${err}`));
73
+ process.exit(1);
74
+ } else {
75
+ console.log(result.summary);
76
+ console.log('\n✅ Handoff package is valid!');
77
+
78
+ if (result.warnings.length > 0) {
79
+ console.log('\n⚠️ Warnings:');
80
+ result.warnings.forEach(warn => console.log(` - ${warn}`));
81
+ }
82
+ }
83
+ }
84
+
85
+ export default HandoffValidator;
@@ -0,0 +1,4 @@
1
+ export { default as HandoffValidator } from './handoff-validator.js';
2
+ export { Spinner, ProgressBar, MultiSpinner } from './spinner.js';
3
+ export { StreamingClient, StreamingLLM } from './streaming-client.js';
4
+ export { sanitizeErrorMessage, sanitizeErrorForLogging, withSanitizedErrors } from './error-sanitizer.js';
@@ -0,0 +1,190 @@
1
+ /**
2
+ * Spinner and Progress Indicators
3
+ */
4
+
5
+ import { fileURLToPath } from 'url';
6
+
7
+ class Spinner {
8
+ constructor(text = 'Loading...', options = {}) {
9
+ this.text = text;
10
+ // @ts-ignore
11
+ this.frames = options.frames || ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
12
+ // @ts-ignore
13
+ this.interval = options.interval || 80;
14
+ // @ts-ignore
15
+ this.stream = options.stream || process.stderr;
16
+ // @ts-ignore
17
+ this.color = options.color || '\x1b[36m';
18
+ this.frameIndex = 0;
19
+ this.timer = null;
20
+ this.isSpinning = false;
21
+ }
22
+
23
+ start() {
24
+ if (this.isSpinning) return this;
25
+ this.isSpinning = true;
26
+ this.frameIndex = 0;
27
+ this.stream.write('\x1B[?25l');
28
+ this.timer = setInterval(() => {
29
+ const frame = this.frames[this.frameIndex];
30
+ this.stream.write(`\r${this.color}${frame}\x1b[0m ${this.text}`);
31
+ this.frameIndex = (this.frameIndex + 1) % this.frames.length;
32
+ }, this.interval);
33
+ return this;
34
+ }
35
+
36
+ update(text) {
37
+ this.text = text;
38
+ return this;
39
+ }
40
+
41
+ succeed(text) {
42
+ return this.stop('\x1b[32m✔\x1b[0m', text);
43
+ }
44
+
45
+ fail(text) {
46
+ return this.stop('\x1b[31m✖\x1b[0m', text);
47
+ }
48
+
49
+ warn(text) {
50
+ return this.stop('\x1b[33m⚠\x1b[0m', text);
51
+ }
52
+
53
+ info(text) {
54
+ return this.stop('\x1b[36mℹ\x1b[0m', text);
55
+ }
56
+
57
+ /**
58
+ * @param {string|null} symbol
59
+ * @param {string|null} text
60
+ */
61
+ stop(symbol = null, text = null) {
62
+ if (!this.isSpinning) return this;
63
+ if (this.timer) {
64
+ // @ts-ignore
65
+ clearInterval(this.timer);
66
+ }
67
+ this.isSpinning = false;
68
+ this.stream.write('\r\x1b[K');
69
+ this.stream.write('\x1B[?25h');
70
+ if (symbol && text) {
71
+ this.stream.write(`${symbol} ${text}\n`);
72
+ } else if (text) {
73
+ this.stream.write(`${text}\n`);
74
+ }
75
+ return this;
76
+ }
77
+
78
+ clear() {
79
+ if (!this.isSpinning) return this;
80
+ if (this.timer) {
81
+ // @ts-ignore
82
+ clearInterval(this.timer);
83
+ }
84
+ this.isSpinning = false;
85
+ this.stream.write('\r\x1b[K');
86
+ this.stream.write('\x1B[?25h');
87
+ return this;
88
+ }
89
+ }
90
+
91
+ class ProgressBar {
92
+ constructor(total, options = {}) {
93
+ this.total = total;
94
+ this.current = 0;
95
+ // @ts-ignore
96
+ this.width = options.width || 40;
97
+ // @ts-ignore
98
+ this.stream = options.stream || process.stderr;
99
+ // @ts-ignore
100
+ this.format = options.format || ':bar :percent :current/:total';
101
+ // @ts-ignore
102
+ this.complete = options.complete || '█';
103
+ // @ts-ignore
104
+ this.incomplete = options.incomplete || '░';
105
+ // @ts-ignore
106
+ this.renderThrottle = options.renderThrottle || 16;
107
+ this.lastRender = 0;
108
+ }
109
+
110
+ tick(amount = 1) {
111
+ this.current = Math.min(this.current + amount, this.total);
112
+ const now = Date.now();
113
+ if (now - this.lastRender < this.renderThrottle && this.current < this.total) {
114
+ return this;
115
+ }
116
+ this.lastRender = now;
117
+ this.render();
118
+ return this;
119
+ }
120
+
121
+ render() {
122
+ const percent = Math.floor((this.current / this.total) * 100);
123
+ const completeLength = Math.floor(this.width * (this.current / this.total));
124
+ const incompleteLength = this.width - completeLength;
125
+ const bar = this.complete.repeat(completeLength) + this.incomplete.repeat(incompleteLength);
126
+ // @ts-ignore
127
+ let output = this.format.replace(':bar', bar).replace(':percent', `${percent}%`).replace(':current', String(this.current)).replace(':total', String(this.total));
128
+ this.stream.write(`\r${output}`);
129
+ if (this.current >= this.total) {
130
+ this.stream.write('\n');
131
+ }
132
+ return this;
133
+ }
134
+
135
+ update(current) {
136
+ this.current = Math.min(current, this.total);
137
+ this.render();
138
+ return this;
139
+ }
140
+ }
141
+
142
+ class MultiSpinner {
143
+ constructor() {
144
+ this.spinners = new Map();
145
+ this.stream = process.stderr;
146
+ }
147
+
148
+ add(key, text, status = 'pending') {
149
+ this.spinners.set(key, { text, status, startTime: Date.now() });
150
+ this.render();
151
+ return this;
152
+ }
153
+
154
+ update(key, status, text = null) {
155
+ const spinner = this.spinners.get(key);
156
+ if (spinner) {
157
+ spinner.status = status;
158
+ if (text) spinner.text = text;
159
+ this.render();
160
+ }
161
+ return this;
162
+ }
163
+
164
+ succeed(key, text) { return this.update(key, 'success', text); }
165
+ fail(key, text) { return this.update(key, 'fail', text); }
166
+ warn(key, text) { return this.update(key, 'warn', text); }
167
+
168
+ render() {
169
+ this.stream.write('\r\x1b[K');
170
+ const lines = [];
171
+ for (const [key, spinner] of this.spinners) {
172
+ // @ts-ignore
173
+ const symbol = { pending: '\x1b[36m⋯\x1b[0m', success: '\x1b[32m✔\x1b[0m', fail: '\x1b[31m✖\x1b[0m', warn: '\x1b[33m⚠\x1b[0m' }[spinner.status];
174
+ const duration = ((Date.now() - spinner.startTime) / 1000).toFixed(1);
175
+ lines.push(`${symbol} ${spinner.text} (${duration}s)`);
176
+ }
177
+ this.stream.write(lines.join('\n'));
178
+ if (lines.length > 1) {
179
+ this.stream.write(`\x1b[${lines.length - 1}A`);
180
+ }
181
+ return this;
182
+ }
183
+
184
+ done() {
185
+ this.stream.write('\n'.repeat(this.spinners.size));
186
+ return this;
187
+ }
188
+ }
189
+
190
+ export { Spinner, ProgressBar, MultiSpinner };
@@ -0,0 +1,128 @@
1
+ import { fileURLToPath } from 'url';
2
+ import https from "https";
3
+
4
+ /**
5
+ * Streaming LLM Client
6
+ */
7
+ class StreamingClient {
8
+ constructor(config) {
9
+ this.config = config;
10
+ this.buffer = '';
11
+ }
12
+
13
+ stream(payload, onToken, onComplete, onError) {
14
+ const url = new URL(this.config.endpoint);
15
+
16
+ /** @type {any} */
17
+ const options = {
18
+ hostname: url.hostname,
19
+ path: url.pathname,
20
+ method: 'POST',
21
+ headers: {
22
+ 'Content-Type': 'application/json',
23
+ 'Authorization': `Bearer ${this.config.apiKey}`,
24
+ 'Accept': 'text/event-stream'
25
+ },
26
+ rejectUnauthorized: true,
27
+ minVersion: 'TLSv1.2'
28
+ };
29
+
30
+ const req = https.request(options, (res) => {
31
+ let fullResponse = '';
32
+ res.on('data', (chunk) => {
33
+ this.buffer += chunk.toString();
34
+ const lines = this.buffer.split('\n');
35
+ this.buffer = lines.pop() || '';
36
+ for (const line of lines) {
37
+ if (line.startsWith('data: ')) {
38
+ const data = line.slice(6);
39
+ if (data === '[DONE]') continue;
40
+ try {
41
+ const parsed = JSON.parse(data);
42
+ const token = this.extractToken(parsed, this.config.schema);
43
+ if (token) {
44
+ fullResponse += token;
45
+ onToken(token, parsed);
46
+ }
47
+ } catch (e) { /* ignore */ }
48
+ }
49
+ }
50
+ });
51
+ res.on('end', () => { if (onComplete) onComplete(fullResponse); });
52
+ res.on('error', (e) => { if (onError) onError(e); });
53
+ });
54
+
55
+ req.on('error', (e) => { if (onError) onError(e); });
56
+ req.setTimeout(60000, () => {
57
+ req.destroy();
58
+ if (onError) onError(new Error('Timeout'));
59
+ });
60
+ req.write(JSON.stringify({ ...payload, stream: true }));
61
+ req.end();
62
+ return req;
63
+ }
64
+
65
+ extractToken(data, schema) {
66
+ switch (schema) {
67
+ case 'openai': return data.choices?.[0]?.delta?.content || '';
68
+ default: return data.choices?.[0]?.delta?.content || data.delta?.text || '';
69
+ }
70
+ }
71
+
72
+ async request(payload) {
73
+ return new Promise((resolve, reject) => {
74
+ const url = new URL(this.config.endpoint);
75
+
76
+ /** @type {any} */
77
+ const options = {
78
+ hostname: url.hostname,
79
+ path: url.pathname,
80
+ method: 'POST',
81
+ headers: {
82
+ 'Content-Type': 'application/json',
83
+ 'Authorization': `Bearer ${this.config.apiKey}`
84
+ },
85
+ rejectUnauthorized: true,
86
+ minVersion: 'TLSv1.2'
87
+ };
88
+ const req = https.request(options, (res) => {
89
+ let data = '';
90
+ res.on('data', (chunk) => data += chunk);
91
+ res.on('end', () => {
92
+ try { resolve(JSON.parse(data)); } catch (e) { reject(e); }
93
+ });
94
+ });
95
+ req.on('error', reject);
96
+ req.write(JSON.stringify(payload));
97
+ req.end();
98
+ });
99
+ }
100
+ }
101
+
102
+ class StreamingLLM {
103
+ constructor(config) {
104
+ this.client = new StreamingClient(config);
105
+ this.config = config;
106
+ }
107
+
108
+ async streamToConsole(payload, options = {}) {
109
+ // @ts-ignore
110
+ const prefix = options.prefix || '';
111
+ return new Promise((resolve, reject) => {
112
+ let tokens = [];
113
+ this.client.stream(payload, (token) => {
114
+ process.stdout.write(token);
115
+ // @ts-ignore
116
+ tokens.push(token);
117
+ }, (full) => {
118
+ process.stdout.write('\n');
119
+ resolve({ content: full, tokens: tokens.length });
120
+ }, (e) => {
121
+ process.stdout.write('\n');
122
+ reject(e);
123
+ });
124
+ });
125
+ }
126
+ }
127
+
128
+ export { StreamingClient, StreamingLLM };
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@yamo/memory-mesh",
3
+ "version": "2.0.1",
4
+ "description": "Portable semantic memory system with Layer 0 Scrubber for YAMO agents",
5
+ "type": "module",
6
+ "main": "lib/memory/index.js",
7
+ "types": "index.d.ts",
8
+ "bin": {
9
+ "memory-mesh": "./bin/memory_mesh.js",
10
+ "scrubber": "./bin/scrubber.js"
11
+ },
12
+ "files": [
13
+ "lib/",
14
+ "bin/",
15
+ "skills/",
16
+ "index.d.ts"
17
+ ],
18
+ "scripts": {
19
+ "test": "npm run type-check",
20
+ "type-check": "tsc --noEmit"
21
+ },
22
+ "dependencies": {
23
+ "@lancedb/lancedb": "^0.23.0",
24
+ "@xenova/transformers": "^2.17.0",
25
+ "apache-arrow": "^17.0.0",
26
+ "onnxruntime-node": "^1.18.0"
27
+ },
28
+ "author": "Soverane Labs",
29
+ "license": "MIT",
30
+ "engines": {
31
+ "node": ">=18.0.0"
32
+ },
33
+ "devDependencies": {
34
+ "@types/node": "^25.0.9",
35
+ "cohere-ai": "^7.20.0",
36
+ "openai": "^6.16.0",
37
+ "typescript": "^5.9.3"
38
+ }
39
+ }