@tpsdev-ai/openclaw-flair 0.4.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -47,7 +47,7 @@ In your OpenClaw config (`openclaw.json`):
47
47
  "memory-flair": {
48
48
  "enabled": true,
49
49
  "config": {
50
- "url": "http://localhost:9926",
50
+ "url": "http://localhost:19926",
51
51
  "agentId": "auto",
52
52
  "autoCapture": true,
53
53
  "autoRecall": true,
package/index.ts CHANGED
@@ -34,7 +34,7 @@ interface FlairMemoryConfig {
34
34
  maxBootstrapTokens?: number;
35
35
  }
36
36
 
37
- const DEFAULT_URL = "http://127.0.0.1:9926";
37
+ const DEFAULT_URL = "http://127.0.0.1:19926";
38
38
  const DEFAULT_MAX_RECALL = 5;
39
39
  const DEFAULT_MAX_BOOTSTRAP_TOKENS = 4000;
40
40
 
@@ -107,6 +107,130 @@ function excerptForCapture(text: string, maxChars = 500): string {
107
107
  return text.length > maxChars ? `${text.slice(0, maxChars)}…` : text;
108
108
  }
109
109
 
110
+ // ─── Entity detection ────────────────────────────────────────────────────────
111
+ // Passive entity extraction from conversation content. No setup wizards,
112
+ // no "tell me about yourself" — Flair learns from natural conversation.
113
+
114
+ interface DetectedEntity {
115
+ name: string;
116
+ kind: "person" | "project" | "service" | "org" | "concept";
117
+ confidence: number;
118
+ }
119
+
120
+ interface DetectedRelationship {
121
+ subject: string;
122
+ predicate: string;
123
+ object: string;
124
+ confidence: number;
125
+ }
126
+
127
+ // Person detection: "Nathan said", "ask @Kern", "my name is X", "X is the founder"
128
+ const PERSON_PATTERNS = [
129
+ /\b([A-Z][a-z]{2,})\s+(?:said|asked|mentioned|decided|approved|rejected|thinks|wants|needs|prefers)\b/g,
130
+ /\b(?:ask|ping|tell|check with|talk to)\s+(?:@)?([A-Z][a-z]{2,})\b/g,
131
+ /\b(?:my name is|i'm|call me)\s+([A-Z][a-z]{2,})\b/ig,
132
+ /\b([A-Z][a-z]{2,})\s+(?:is the|is our|is a|was the|was our)\s+(\w+(?:\s+\w+)?)\b/g,
133
+ ];
134
+
135
+ // Project/service detection: repo references, "the X project", service names
136
+ const PROJECT_PATTERNS = [
137
+ /\b(?:tpsdev-ai|github\.com)\/([a-z0-9-]+)\b/g,
138
+ /\b(?:the|our)\s+([A-Z][a-z]+(?:\s[A-Z][a-z]+)?)\s+(?:project|repo|service|system|app|tool|plugin)\b/g,
139
+ ];
140
+
141
+ // Relationship detection: "X manages Y", "X owns Y", "X depends on Y"
142
+ const RELATIONSHIP_PATTERNS = [
143
+ { re: /\b([A-Z][a-z]{2,})\s+(?:manages|leads|runs|owns)\s+(?:the\s+)?([A-Z][a-z]+(?:\s[A-Z][a-z]+)?)\b/g, predicate: "manages" },
144
+ { re: /\b([A-Z][a-z]{2,})\s+(?:works on|is working on|maintains)\s+(?:the\s+)?([A-Z][a-z]+(?:\s[A-Z][a-z]+)?)\b/g, predicate: "works_on" },
145
+ { re: /\b([A-Z][a-z]{2,})\s+(?:reviews|reviewed)\s+(?:the\s+)?([A-Z][a-z]+(?:'s)?(?:\s\w+)?)\b/g, predicate: "reviews" },
146
+ { re: /\b([A-Z][a-z]+)\s+(?:depends on|requires|needs)\s+([A-Z][a-z]+)\b/g, predicate: "depends_on" },
147
+ { re: /\b([A-Z][a-z]+)\s+(?:replaces|supersedes)\s+([A-Z][a-z]+)\b/g, predicate: "replaces" },
148
+ ];
149
+
150
+ // Common words that look like names but aren't
151
+ const ENTITY_STOPWORDS = new Set([
152
+ "the", "this", "that", "with", "from", "into", "also", "just", "here",
153
+ "there", "what", "when", "where", "which", "while", "should", "would",
154
+ "could", "will", "does", "have", "been", "being", "make", "made",
155
+ "take", "taken", "like", "look", "good", "well", "much", "many",
156
+ "some", "each", "every", "both", "other", "such", "only", "same",
157
+ "than", "then", "now", "how", "all", "any", "few", "most", "very",
158
+ "after", "before", "between", "under", "over", "through", "during",
159
+ "about", "against", "above", "below", "off", "down", "out",
160
+ "let", "set", "get", "put", "run", "use", "try", "see", "new",
161
+ "old", "big", "end", "way", "day", "man", "did", "got", "had",
162
+ "yes", "not", "but", "for", "are", "was", "can", "may", "one",
163
+ "two", "its", "his", "her", "our", "has", "him", "her", "per",
164
+ "via", "bug", "fix", "add", "api", "url", "cli", "tcp", "ssh",
165
+ "keep", "next", "last", "best", "sure", "okay", "done", "want",
166
+ "need", "know", "think", "start", "stop", "check", "update",
167
+ "instead", "currently", "actually", "already", "however", "because",
168
+ "since", "until", "still", "right", "first", "great", "sounds",
169
+ "interesting", "important", "note", "issue", "pull", "push",
170
+ "merge", "branch", "commit", "deploy", "build", "test", "spec",
171
+ ]);
172
+
173
+ function isValidEntity(name: string): boolean {
174
+ if (name.length < 3 || name.length > 30) return false;
175
+ if (ENTITY_STOPWORDS.has(name.toLowerCase())) return false;
176
+ if (/^\d+$/.test(name)) return false;
177
+ return true;
178
+ }
179
+
180
+ function detectEntities(text: string): DetectedEntity[] {
181
+ const entities = new Map<string, DetectedEntity>();
182
+
183
+ for (const pattern of PERSON_PATTERNS) {
184
+ pattern.lastIndex = 0;
185
+ let match: RegExpExecArray | null;
186
+ while ((match = pattern.exec(text)) !== null) {
187
+ const name = match[1];
188
+ if (!isValidEntity(name)) continue;
189
+ const key = name.toLowerCase();
190
+ if (!entities.has(key) || entities.get(key)!.confidence < 0.7) {
191
+ entities.set(key, { name, kind: "person", confidence: 0.7 });
192
+ }
193
+ }
194
+ }
195
+
196
+ for (const pattern of PROJECT_PATTERNS) {
197
+ pattern.lastIndex = 0;
198
+ let match: RegExpExecArray | null;
199
+ while ((match = pattern.exec(text)) !== null) {
200
+ const name = match[1];
201
+ if (!isValidEntity(name)) continue;
202
+ const key = name.toLowerCase();
203
+ if (!entities.has(key)) {
204
+ entities.set(key, { name, kind: "project", confidence: 0.8 });
205
+ }
206
+ }
207
+ }
208
+
209
+ return [...entities.values()];
210
+ }
211
+
212
+ function detectRelationships(text: string): DetectedRelationship[] {
213
+ const relationships: DetectedRelationship[] = [];
214
+
215
+ for (const { re, predicate } of RELATIONSHIP_PATTERNS) {
216
+ re.lastIndex = 0;
217
+ let match: RegExpExecArray | null;
218
+ while ((match = re.exec(text)) !== null) {
219
+ const subject = match[1];
220
+ const object = match[2];
221
+ if (!isValidEntity(subject) || !isValidEntity(object)) continue;
222
+ relationships.push({
223
+ subject: subject.toLowerCase(),
224
+ predicate,
225
+ object: object.toLowerCase(),
226
+ confidence: 0.6,
227
+ });
228
+ }
229
+ }
230
+
231
+ return relationships;
232
+ }
233
+
110
234
  // ─── Plugin export ────────────────────────────────────────────────────────────
111
235
 
112
236
  export default {
@@ -358,16 +482,67 @@ export default {
358
482
  const client = getCurrentClient();
359
483
  const messages = (event.messages ?? []) as Array<{ role: string; content?: string }>;
360
484
  let stored = 0;
485
+ const allEntities = new Map<string, DetectedEntity>();
486
+ const allRelationships: DetectedRelationship[] = [];
487
+
361
488
  for (const msg of messages) {
362
489
  if (msg.role !== "user" && msg.role !== "assistant") continue;
363
490
  const text = typeof msg.content === "string" ? msg.content : "";
364
- if (!text || !shouldCapture(text)) continue;
365
- const excerpt = excerptForCapture(text);
366
- await client.memory.write(excerpt, { type: "session", tags: ["auto-captured"] });
367
- stored++;
368
- if (stored >= 3) break;
491
+ if (!text || text.length < MIN_CAPTURE_LENGTH) continue;
492
+
493
+ // Traditional trigger-based capture
494
+ if (shouldCapture(text) && stored < 3) {
495
+ const excerpt = excerptForCapture(text);
496
+ // Tag with detected subject if available
497
+ const entities = detectEntities(text);
498
+ const subject = entities.length > 0 ? entities[0].name.toLowerCase() : undefined;
499
+ await client.memory.write(excerpt, {
500
+ type: "session",
501
+ tags: ["auto-captured"],
502
+ subject,
503
+ });
504
+ stored++;
505
+ }
506
+
507
+ // Entity detection — accumulate across all messages
508
+ for (const entity of detectEntities(text)) {
509
+ const key = entity.name.toLowerCase();
510
+ const existing = allEntities.get(key);
511
+ if (!existing || existing.confidence < entity.confidence) {
512
+ allEntities.set(key, entity);
513
+ }
514
+ }
515
+
516
+ // Relationship detection
517
+ for (const rel of detectRelationships(text)) {
518
+ allRelationships.push(rel);
519
+ }
520
+ }
521
+
522
+ // Store detected relationships via Flair's Relationship API
523
+ let relStored = 0;
524
+ for (const rel of allRelationships) {
525
+ if (relStored >= 5) break; // cap per session
526
+ try {
527
+ await client.request("PUT", `/Relationship/${Date.now()}-${relStored}`, {
528
+ subject: rel.subject,
529
+ predicate: rel.predicate,
530
+ object: rel.object,
531
+ confidence: rel.confidence,
532
+ source: "auto-detected",
533
+ });
534
+ relStored++;
535
+ } catch {
536
+ // best effort — don't fail the session over relationship storage
537
+ }
538
+ }
539
+
540
+ const total = stored + relStored;
541
+ if (total > 0) {
542
+ api.logger.info(
543
+ `openclaw-flair: auto-captured ${stored} memories, ${relStored} relationships, ${allEntities.size} entities detected`
544
+ );
369
545
  }
370
- if (stored > 0) api.logger.info(`openclaw-flair: auto-captured ${stored} memories`);
371
546
  } catch (err: any) {
372
547
  api.logger.warn(`openclaw-flair: auto-capture failed: ${err.message}`);
373
548
  }
@@ -4,7 +4,7 @@
4
4
  "uiHints": {
5
5
  "url": {
6
6
  "label": "Flair URL",
7
- "placeholder": "http://localhost:9926",
7
+ "placeholder": "http://localhost:19926",
8
8
  "help": "Base URL for the Flair server"
9
9
  },
10
10
  "agentId": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tpsdev-ai/openclaw-flair",
3
- "version": "0.4.1",
3
+ "version": "0.5.0",
4
4
  "description": "OpenClaw memory plugin for Flair \u2014 agent identity and semantic memory",
5
5
  "type": "module",
6
6
  "main": "index.ts",
@@ -34,6 +34,9 @@
34
34
  "./index.ts"
35
35
  ]
36
36
  },
37
+ "publishConfig": {
38
+ "access": "public"
39
+ },
37
40
  "peerDependencies": {
38
41
  "openclaw": ">=2026.3.7"
39
42
  },
@@ -41,7 +44,7 @@
41
44
  "node": ">=22"
42
45
  },
43
46
  "dependencies": {
44
- "@sinclair/typebox": "^0.34.0",
45
- "@tpsdev-ai/flair-client": "^0.2.0"
47
+ "@sinclair/typebox": "0.34.48",
48
+ "@tpsdev-ai/flair-client": "0.5.0"
46
49
  }
47
50
  }