@tpsdev-ai/flair 0.3.13 → 0.3.15

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.
@@ -1,6 +1,7 @@
1
1
  import { databases } from "@harperfast/harper";
2
2
  import { patchRecord } from "./table-helpers.js";
3
3
  import { isAdmin } from "./auth-middleware.js";
4
+ import { getEmbedding } from "./embeddings-provider.js";
4
5
  export class Memory extends databases.flair.Memory {
5
6
  /**
6
7
  * Override search() to scope collection GETs by authenticated agent.
@@ -81,11 +82,23 @@ export class Memory extends databases.flair.Memory {
81
82
  const ttlHours = Number(process.env.FLAIR_EPHEMERAL_TTL_HOURS || 24);
82
83
  content.expiresAt = new Date(Date.now() + ttlHours * 3600_000).toISOString();
83
84
  }
85
+ // Generate embedding from content text
86
+ if (content.content && !content.embedding) {
87
+ const vec = await getEmbedding(content.content);
88
+ if (vec)
89
+ content.embedding = vec;
90
+ }
84
91
  return super.post(content);
85
92
  }
86
93
  async put(content) {
87
94
  const now = new Date().toISOString();
88
95
  content.updatedAt = now;
96
+ // Re-generate embedding if content changed
97
+ if (content.content && !content.embedding) {
98
+ const vec = await getEmbedding(content.content);
99
+ if (vec)
100
+ content.embedding = vec;
101
+ }
89
102
  // If archiving, record who + when
90
103
  if (content.archived === true && !content.archivedAt) {
91
104
  content.archivedAt = now;
@@ -1,6 +1,6 @@
1
1
  import { patchRecord } from "./table-helpers.js";
2
2
  import { server, databases } from "@harperfast/harper";
3
- import { initEmbeddings, getEmbedding } from "./embeddings-provider.js";
3
+ import { getEmbedding } from "./embeddings-provider.js";
4
4
  // --- Admin credentials ---
5
5
  // Admin auth is sourced exclusively from Harper's own environment variables
6
6
  // (HDB_ADMIN_PASSWORD / FLAIR_ADMIN_PASSWORD). No filesystem token file.
@@ -93,7 +93,6 @@ async function importEd25519Key(publicKeyStr) {
93
93
  keyCache.set(publicKeyStr, key);
94
94
  return key;
95
95
  }
96
- initEmbeddings().catch((err) => console.error("[embeddings] init:", err.message));
97
96
  async function backfillEmbedding(memoryId) {
98
97
  try {
99
98
  const record = await databases.flair.Memory.get(memoryId);
@@ -2,59 +2,35 @@
2
2
  * embeddings-provider.ts
3
3
  *
4
4
  * Thin wrapper around harper-fabric-embeddings for Flair resources.
5
- * harper-fabric-embeddings handles loading and running the embedding model
6
- * at the Harper sub-component level we just delegate to its exported API.
7
- *
8
- * On platforms where the native binary isn't available, getEmbedding()
9
- * returns null and semantic search falls back to keyword matching.
5
+ * harper-fabric-embeddings is loaded by Harper as a sub-component
6
+ * (declared in config.yaml). It downloads the model and initializes
7
+ * in the background. We just call embed() — if it's not ready yet,
8
+ * we return null and the caller handles it gracefully.
10
9
  */
11
10
  import * as hfe from "harper-fabric-embeddings";
12
- const SINGLETON_KEY = "__flair_hfe_provider_v1__";
13
- function getState() {
14
- if (!globalThis[SINGLETON_KEY]) {
15
- globalThis[SINGLETON_KEY] = { initialized: false, available: false };
16
- }
17
- return globalThis[SINGLETON_KEY];
18
- }
19
- export async function initEmbeddings() {
20
- const state = getState();
21
- if (state.initialized)
22
- return;
23
- // harper-fabric-embeddings is initialized by Harper as a sub-component
24
- // (declared in config.yaml). It inits in the background so it may not
25
- // be ready when resources first load. Retry a few times before giving up.
26
- for (let attempt = 1; attempt <= 10; attempt++) {
27
- try {
28
- const dims = hfe.dimensions();
29
- state.available = true;
30
- state.initialized = true;
31
- console.log(`[embeddings] ready (${dims} dims, attempt ${attempt})`);
32
- return;
33
- }
34
- catch {
35
- if (attempt < 10)
36
- await new Promise(r => setTimeout(r, 500));
37
- }
38
- }
39
- console.log("[embeddings] not available after 10 attempts — search will be keyword-only");
40
- state.available = false;
41
- state.initialized = true;
42
- }
11
+ /**
12
+ * Generate an embedding vector for the given text.
13
+ * Returns null if the embedding engine isn't ready yet (model still loading)
14
+ * or not available on this platform. Never gives up permanently — each call
15
+ * checks independently.
16
+ */
43
17
  export async function getEmbedding(text) {
44
- const state = getState();
45
- if (!state.initialized)
46
- await initEmbeddings();
47
- if (!state.available)
48
- return null;
49
18
  try {
50
- return hfe.embed(text);
19
+ return await hfe.embed(text);
51
20
  }
52
- catch (err) {
53
- console.log(`[embeddings] embed failed: ${err.message}`);
21
+ catch {
54
22
  return null;
55
23
  }
56
24
  }
25
+ /**
26
+ * Check if the embedding engine is currently available.
27
+ */
57
28
  export function getMode() {
58
- const state = getState();
59
- return state.available ? "local" : "none";
29
+ try {
30
+ hfe.dimensions();
31
+ return "local";
32
+ }
33
+ catch {
34
+ return "none";
35
+ }
60
36
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tpsdev-ai/flair",
3
- "version": "0.3.13",
3
+ "version": "0.3.15",
4
4
  "description": "Identity, memory, and soul for AI agents. Cryptographic identity (Ed25519), semantic memory with local embeddings, and persistent personality — all in a single process.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -1,6 +1,7 @@
1
1
  import { databases } from "@harperfast/harper";
2
2
  import { patchRecord } from "./table-helpers.js";
3
3
  import { isAdmin } from "./auth-middleware.js";
4
+ import { getEmbedding } from "./embeddings-provider.js";
4
5
 
5
6
  export class Memory extends (databases as any).flair.Memory {
6
7
  /**
@@ -90,6 +91,12 @@ export class Memory extends (databases as any).flair.Memory {
90
91
  content.expiresAt = new Date(Date.now() + ttlHours * 3600_000).toISOString();
91
92
  }
92
93
 
94
+ // Generate embedding from content text
95
+ if (content.content && !content.embedding) {
96
+ const vec = await getEmbedding(content.content);
97
+ if (vec) content.embedding = vec;
98
+ }
99
+
93
100
  return super.post(content);
94
101
  }
95
102
 
@@ -97,6 +104,12 @@ export class Memory extends (databases as any).flair.Memory {
97
104
  const now = new Date().toISOString();
98
105
  content.updatedAt = now;
99
106
 
107
+ // Re-generate embedding if content changed
108
+ if (content.content && !content.embedding) {
109
+ const vec = await getEmbedding(content.content);
110
+ if (vec) content.embedding = vec;
111
+ }
112
+
100
113
  // If archiving, record who + when
101
114
  if (content.archived === true && !content.archivedAt) {
102
115
  content.archivedAt = now;
@@ -1,6 +1,6 @@
1
1
  import { patchRecord } from "./table-helpers.js";
2
2
  import { server, databases } from "@harperfast/harper";
3
- import { initEmbeddings, getEmbedding } from "./embeddings-provider.js";
3
+ import { getEmbedding } from "./embeddings-provider.js";
4
4
 
5
5
  // --- Admin credentials ---
6
6
  // Admin auth is sourced exclusively from Harper's own environment variables
@@ -102,7 +102,6 @@ async function importEd25519Key(publicKeyStr: string): Promise<CryptoKey> {
102
102
  return key;
103
103
  }
104
104
 
105
- initEmbeddings().catch((err: any) => console.error("[embeddings] init:", err.message));
106
105
 
107
106
  async function backfillEmbedding(memoryId: string): Promise<void> {
108
107
  try {
@@ -2,66 +2,36 @@
2
2
  * embeddings-provider.ts
3
3
  *
4
4
  * Thin wrapper around harper-fabric-embeddings for Flair resources.
5
- * harper-fabric-embeddings handles loading and running the embedding model
6
- * at the Harper sub-component level we just delegate to its exported API.
7
- *
8
- * On platforms where the native binary isn't available, getEmbedding()
9
- * returns null and semantic search falls back to keyword matching.
5
+ * harper-fabric-embeddings is loaded by Harper as a sub-component
6
+ * (declared in config.yaml). It downloads the model and initializes
7
+ * in the background. We just call embed() — if it's not ready yet,
8
+ * we return null and the caller handles it gracefully.
10
9
  */
11
10
 
12
11
  import * as hfe from "harper-fabric-embeddings";
13
12
 
14
- const SINGLETON_KEY = "__flair_hfe_provider_v1__";
15
-
16
- interface ProviderState {
17
- initialized: boolean;
18
- available: boolean;
19
- }
20
-
21
- function getState(): ProviderState {
22
- if (!(globalThis as any)[SINGLETON_KEY]) {
23
- (globalThis as any)[SINGLETON_KEY] = { initialized: false, available: false };
24
- }
25
- return (globalThis as any)[SINGLETON_KEY];
26
- }
27
-
28
- export async function initEmbeddings(): Promise<void> {
29
- const state = getState();
30
- if (state.initialized) return;
31
-
32
- // harper-fabric-embeddings is initialized by Harper as a sub-component
33
- // (declared in config.yaml). It inits in the background so it may not
34
- // be ready when resources first load. Retry a few times before giving up.
35
- for (let attempt = 1; attempt <= 10; attempt++) {
36
- try {
37
- const dims = hfe.dimensions();
38
- state.available = true;
39
- state.initialized = true;
40
- console.log(`[embeddings] ready (${dims} dims, attempt ${attempt})`);
41
- return;
42
- } catch {
43
- if (attempt < 10) await new Promise(r => setTimeout(r, 500));
44
- }
45
- }
46
-
47
- console.log("[embeddings] not available after 10 attempts — search will be keyword-only");
48
- state.available = false;
49
- state.initialized = true;
50
- }
51
-
13
+ /**
14
+ * Generate an embedding vector for the given text.
15
+ * Returns null if the embedding engine isn't ready yet (model still loading)
16
+ * or not available on this platform. Never gives up permanently — each call
17
+ * checks independently.
18
+ */
52
19
  export async function getEmbedding(text: string): Promise<number[] | null> {
53
- const state = getState();
54
- if (!state.initialized) await initEmbeddings();
55
- if (!state.available) return null;
56
20
  try {
57
- return hfe.embed(text);
58
- } catch (err: any) {
59
- console.log(`[embeddings] embed failed: ${err.message}`);
21
+ return await hfe.embed(text);
22
+ } catch {
60
23
  return null;
61
24
  }
62
25
  }
63
26
 
27
+ /**
28
+ * Check if the embedding engine is currently available.
29
+ */
64
30
  export function getMode(): "local" | "none" {
65
- const state = getState();
66
- return state.available ? "local" : "none";
31
+ try {
32
+ hfe.dimensions();
33
+ return "local";
34
+ } catch {
35
+ return "none";
36
+ }
67
37
  }