@jungjaehoon/mama-core 1.6.0 → 1.8.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.
- package/db/migrations/037-create-context-packets.sql +32 -0
- package/db/migrations/038-create-vnext-operator-contracts.sql +57 -0
- package/db/migrations/039-add-connector-event-operator-ingest-seq.sql +96 -0
- package/db/migrations/040-create-operator-memory-commit-intents.sql +19 -0
- package/db/migrations/041-enforce-operator-memory-commit-claim-invariant.sql +74 -0
- package/db/migrations/042-embedding-prefix-scheme.sql +46 -0
- package/dist/cases/wiki-page-index.js +6 -2
- package/dist/context-compile/boundary-defaults.d.ts +12 -0
- package/dist/context-compile/boundary-defaults.js +105 -0
- package/dist/context-compile/compiler-policy.d.ts +39 -0
- package/dist/context-compile/compiler-policy.js +147 -0
- package/dist/context-compile/compiler.d.ts +18 -0
- package/dist/context-compile/compiler.js +401 -0
- package/dist/context-compile/index.d.ts +9 -0
- package/dist/context-compile/index.js +25 -0
- package/dist/context-compile/packet-store.d.ts +9 -0
- package/dist/context-compile/packet-store.js +255 -0
- package/dist/context-compile/ref.d.ts +7 -0
- package/dist/context-compile/ref.js +102 -0
- package/dist/context-compile/source-readers.d.ts +69 -0
- package/dist/context-compile/source-readers.js +707 -0
- package/dist/context-compile/types.d.ts +138 -0
- package/dist/context-compile/types.js +11 -0
- package/dist/context-compile/visibility.d.ts +21 -0
- package/dist/context-compile/visibility.js +200 -0
- package/dist/db-adapter/node-sqlite-adapter.d.ts +17 -1
- package/dist/db-adapter/node-sqlite-adapter.js +399 -6
- package/dist/db-manager.d.ts +9 -10
- package/dist/db-manager.js +59 -4
- package/dist/edges/ref-validation.js +55 -16
- package/dist/edges/types.d.ts +2 -0
- package/dist/embedding-client.d.ts +3 -1
- package/dist/embedding-client.js +3 -2
- package/dist/embedding-server/index.d.ts +2 -0
- package/dist/embedding-server/index.js +10 -2
- package/dist/embeddings.d.ts +8 -3
- package/dist/embeddings.js +48 -15
- package/dist/entities/store.d.ts +4 -1
- package/dist/entities/store.js +206 -78
- package/dist/entities/types.d.ts +5 -1
- package/dist/entities/types.js +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.js +8 -4
- package/dist/mama-api.js +1 -1
- package/dist/memory/api.d.ts +6 -1
- package/dist/memory/api.js +223 -29
- package/dist/memory/provenance.d.ts +1 -0
- package/dist/memory/provenance.js +2 -0
- package/dist/memory/types.d.ts +1 -0
- package/dist/memory-inject.js +1 -1
- package/dist/provenance/source-ref.d.ts +29 -0
- package/dist/provenance/source-ref.js +204 -0
- package/package.json +4 -2
|
@@ -9,15 +9,15 @@ const TABLE_COLUMN_PRAGMAS = {
|
|
|
9
9
|
case_truth: 'PRAGMA table_info(case_truth)',
|
|
10
10
|
};
|
|
11
11
|
const tableColumnCache = new WeakMap();
|
|
12
|
+
// Keep memory-truth quarantine semantics excluded for legacy/backcompat rows even though the
|
|
13
|
+
// current decisions.status CHECK only admits superseded/contradicted/stale terminal states.
|
|
14
|
+
const EXCLUDED_MEMORY_STATUSES = new Set(['superseded', 'quarantined', 'contradicted', 'stale']);
|
|
12
15
|
function scopeKey(scope) {
|
|
13
16
|
return `${scope.kind}\0${scope.id}`;
|
|
14
17
|
}
|
|
15
18
|
function hasScopes(scopes) {
|
|
16
19
|
return Array.isArray(scopes) && scopes.length > 0;
|
|
17
20
|
}
|
|
18
|
-
function hasConnectors(connectors) {
|
|
19
|
-
return Array.isArray(connectors) && connectors.length > 0;
|
|
20
|
-
}
|
|
21
21
|
function hasProjectRefs(projectRefs) {
|
|
22
22
|
return Array.isArray(projectRefs) && projectRefs.length > 0;
|
|
23
23
|
}
|
|
@@ -27,16 +27,33 @@ function visibilityFromScopes(scopes) {
|
|
|
27
27
|
function asOfMs(visibility) {
|
|
28
28
|
return typeof visibility.asOfMs === 'number' ? visibility.asOfMs : null;
|
|
29
29
|
}
|
|
30
|
-
function
|
|
30
|
+
function startMs(visibility) {
|
|
31
|
+
return typeof visibility.startMs === 'number' ? visibility.startMs : null;
|
|
32
|
+
}
|
|
33
|
+
function isWithinVisibilityTime(value, visibility) {
|
|
34
|
+
const start = startMs(visibility);
|
|
31
35
|
const asOf = asOfMs(visibility);
|
|
32
|
-
|
|
36
|
+
if (start === null && asOf === null) {
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
return (typeof value === 'number' &&
|
|
40
|
+
(start === null || value >= start) &&
|
|
41
|
+
(asOf === null || value <= asOf));
|
|
33
42
|
}
|
|
34
43
|
function parseTimestamp(value) {
|
|
35
44
|
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
36
45
|
return Math.floor(value);
|
|
37
46
|
}
|
|
38
47
|
if (typeof value === 'string') {
|
|
39
|
-
const
|
|
48
|
+
const trimmed = value.trim();
|
|
49
|
+
if (trimmed.length === 0) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
const numeric = Number(trimmed);
|
|
53
|
+
if (Number.isFinite(numeric)) {
|
|
54
|
+
return Math.floor(numeric);
|
|
55
|
+
}
|
|
56
|
+
const parsed = Date.parse(trimmed);
|
|
40
57
|
return Number.isFinite(parsed) ? parsed : null;
|
|
41
58
|
}
|
|
42
59
|
return null;
|
|
@@ -61,9 +78,14 @@ function tableColumns(adapter, table) {
|
|
|
61
78
|
return columns;
|
|
62
79
|
}
|
|
63
80
|
function isMemoryVisible(adapter, id, visibility) {
|
|
81
|
+
const columns = tableColumns(adapter, 'decisions');
|
|
82
|
+
const statusSelect = columns.has('status') ? 'status' : 'NULL AS status';
|
|
83
|
+
const supersededBySelect = columns.has('superseded_by')
|
|
84
|
+
? 'superseded_by'
|
|
85
|
+
: 'NULL AS superseded_by';
|
|
64
86
|
const row = adapter
|
|
65
87
|
.prepare(`
|
|
66
|
-
SELECT created_at, event_datetime
|
|
88
|
+
SELECT created_at, event_datetime, ${statusSelect}, ${supersededBySelect}
|
|
67
89
|
FROM decisions
|
|
68
90
|
WHERE id = ?
|
|
69
91
|
LIMIT 1
|
|
@@ -72,7 +94,12 @@ function isMemoryVisible(adapter, id, visibility) {
|
|
|
72
94
|
if (!row) {
|
|
73
95
|
return false;
|
|
74
96
|
}
|
|
75
|
-
|
|
97
|
+
const status = typeof row.status === 'string' ? row.status : 'active';
|
|
98
|
+
if (EXCLUDED_MEMORY_STATUSES.has(status) ||
|
|
99
|
+
(typeof row.superseded_by === 'string' && row.superseded_by.length > 0)) {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
if (!isWithinVisibilityTime(row.event_datetime ?? row.created_at, visibility)) {
|
|
76
103
|
return false;
|
|
77
104
|
}
|
|
78
105
|
const scopes = visibility.scopes;
|
|
@@ -94,7 +121,6 @@ function isMemoryVisible(adapter, id, visibility) {
|
|
|
94
121
|
if (bindingRow?.ok) {
|
|
95
122
|
return true;
|
|
96
123
|
}
|
|
97
|
-
const columns = tableColumns(adapter, 'decisions');
|
|
98
124
|
if (columns.has('memory_scope_kind') && columns.has('memory_scope_id')) {
|
|
99
125
|
const row = adapter
|
|
100
126
|
.prepare(`
|
|
@@ -133,7 +159,7 @@ function isCaseVisible(adapter, id, visibility) {
|
|
|
133
159
|
return false;
|
|
134
160
|
}
|
|
135
161
|
const timestamp = parseTimestamp(row.last_activity_at) ?? parseTimestamp(row.updated_at);
|
|
136
|
-
if (!
|
|
162
|
+
if (!isWithinVisibilityTime(timestamp, visibility)) {
|
|
137
163
|
return false;
|
|
138
164
|
}
|
|
139
165
|
const scopes = visibility.scopes;
|
|
@@ -158,7 +184,7 @@ function isRawVisible(adapter, id, visibility) {
|
|
|
158
184
|
if (!row) {
|
|
159
185
|
return false;
|
|
160
186
|
}
|
|
161
|
-
if (
|
|
187
|
+
if (Array.isArray(visibility.connectors) &&
|
|
162
188
|
!visibility.connectors.includes(String(row.source_connector))) {
|
|
163
189
|
return false;
|
|
164
190
|
}
|
|
@@ -171,7 +197,12 @@ function isRawVisible(adapter, id, visibility) {
|
|
|
171
197
|
row.tenant_id !== visibility.tenantId) {
|
|
172
198
|
return false;
|
|
173
199
|
}
|
|
174
|
-
|
|
200
|
+
const rawTs = row.event_datetime ?? row.source_timestamp_ms;
|
|
201
|
+
if (rawTs === null || rawTs === undefined || (typeof rawTs === 'string' && rawTs.trim() === '')) {
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
const eventTsMs = parseTimestamp(rawTs);
|
|
205
|
+
if (eventTsMs === null || eventTsMs < 0 || !isWithinVisibilityTime(eventTsMs, visibility)) {
|
|
175
206
|
return false;
|
|
176
207
|
}
|
|
177
208
|
if (!hasScopes(visibility.scopes)) {
|
|
@@ -186,7 +217,7 @@ function isEntityVisible(adapter, id, visibility) {
|
|
|
186
217
|
if (!row) {
|
|
187
218
|
return false;
|
|
188
219
|
}
|
|
189
|
-
if (!
|
|
220
|
+
if (!isWithinVisibilityTime(row.created_at, visibility)) {
|
|
190
221
|
return false;
|
|
191
222
|
}
|
|
192
223
|
const scopes = visibility.scopes;
|
|
@@ -222,7 +253,7 @@ function isTwinRefVisible(adapter, ref, visibility, visitedEdges, edgeCache) {
|
|
|
222
253
|
if (!edge) {
|
|
223
254
|
return false;
|
|
224
255
|
}
|
|
225
|
-
if (!
|
|
256
|
+
if (!isWithinVisibilityTime(edge.created_at, visibility)) {
|
|
226
257
|
return false;
|
|
227
258
|
}
|
|
228
259
|
const pathWithCurrent = new Set(visitedEdges);
|
|
@@ -245,13 +276,21 @@ function listVisibleTwinEdgesForRefs(adapter, refs, options = {}) {
|
|
|
245
276
|
const edgeCache = new Map();
|
|
246
277
|
const edgeTypes = normalizeEdgeTypes(options.edgeTypes);
|
|
247
278
|
const edges = (0, store_js_1.listTwinEdgesForRefs)(adapter, refs).filter((edge) => (edgeTypes.size === 0 || edgeTypes.has(edge.edge_type)) &&
|
|
279
|
+
(typeof options.startMs !== 'number' || edge.created_at >= options.startMs) &&
|
|
248
280
|
(typeof options.asOfMs !== 'number' || edge.created_at <= options.asOfMs) &&
|
|
249
281
|
isTwinRefVisible(adapter, edge.subject_ref, options, new Set(), edgeCache) &&
|
|
250
282
|
isTwinRefVisible(adapter, edge.object_ref, options, new Set(), edgeCache));
|
|
251
283
|
const limit = typeof options.limit === 'number' && Number.isFinite(options.limit)
|
|
252
284
|
? Math.max(0, Math.floor(options.limit))
|
|
253
|
-
:
|
|
254
|
-
|
|
285
|
+
: null;
|
|
286
|
+
const sortedEdges = [...edges].sort((left, right) => {
|
|
287
|
+
const createdDiff = right.created_at - left.created_at;
|
|
288
|
+
return createdDiff !== 0 ? createdDiff : left.edge_id.localeCompare(right.edge_id);
|
|
289
|
+
});
|
|
290
|
+
if (limit === null) {
|
|
291
|
+
return sortedEdges;
|
|
292
|
+
}
|
|
293
|
+
return sortedEdges.slice(0, limit);
|
|
255
294
|
}
|
|
256
295
|
function normalizeEdgeTypes(edgeTypes) {
|
|
257
296
|
if (!Array.isArray(edgeTypes) || edgeTypes.length === 0) {
|
package/dist/edges/types.d.ts
CHANGED
|
@@ -27,6 +27,7 @@ export interface TwinVisibility {
|
|
|
27
27
|
connectors?: string[];
|
|
28
28
|
projectRefs?: TwinProjectRef[];
|
|
29
29
|
tenantId?: string | null;
|
|
30
|
+
startMs?: number | null;
|
|
30
31
|
asOfMs?: number | null;
|
|
31
32
|
}
|
|
32
33
|
export interface InsertTwinEdgeInput {
|
|
@@ -80,6 +81,7 @@ export interface ListVisibleTwinEdgesOptions {
|
|
|
80
81
|
projectRefs?: TwinProjectRef[];
|
|
81
82
|
tenantId?: string | null;
|
|
82
83
|
edgeTypes?: TwinEdgeType[];
|
|
84
|
+
startMs?: number | null;
|
|
83
85
|
asOfMs?: number | null;
|
|
84
86
|
limit?: number;
|
|
85
87
|
}
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
*
|
|
12
12
|
* @module embedding-client
|
|
13
13
|
*/
|
|
14
|
+
import type { EmbeddingRole } from './embeddings.js';
|
|
14
15
|
export declare const DEFAULT_PORT: number;
|
|
15
16
|
export declare const HOST = "127.0.0.1";
|
|
16
17
|
export declare const TIMEOUT_MS = 500;
|
|
@@ -33,9 +34,10 @@ export declare function isServerRunning(): Promise<boolean>;
|
|
|
33
34
|
* Generate embedding via HTTP server
|
|
34
35
|
*
|
|
35
36
|
* @param text - Text to embed
|
|
37
|
+
* @param role - e5 role: 'passage' (default, stored text) or 'query' (search)
|
|
36
38
|
* @returns Embedding or null if failed
|
|
37
39
|
*/
|
|
38
|
-
export declare function getEmbeddingFromServer(text: string): Promise<Float32Array | null>;
|
|
40
|
+
export declare function getEmbeddingFromServer(text: string, role?: EmbeddingRole): Promise<Float32Array | null>;
|
|
39
41
|
/**
|
|
40
42
|
* Get server status
|
|
41
43
|
*/
|
package/dist/embedding-client.js
CHANGED
|
@@ -76,9 +76,10 @@ async function isServerRunning() {
|
|
|
76
76
|
* Generate embedding via HTTP server
|
|
77
77
|
*
|
|
78
78
|
* @param text - Text to embed
|
|
79
|
+
* @param role - e5 role: 'passage' (default, stored text) or 'query' (search)
|
|
79
80
|
* @returns Embedding or null if failed
|
|
80
81
|
*/
|
|
81
|
-
async function getEmbeddingFromServer(text) {
|
|
82
|
+
async function getEmbeddingFromServer(text, role = 'passage') {
|
|
82
83
|
const port = getServerPort();
|
|
83
84
|
try {
|
|
84
85
|
const controller = new AbortController();
|
|
@@ -86,7 +87,7 @@ async function getEmbeddingFromServer(text) {
|
|
|
86
87
|
const response = await fetch(`http://${exports.HOST}:${port}/embed`, {
|
|
87
88
|
method: 'POST',
|
|
88
89
|
headers: { 'Content-Type': 'application/json' },
|
|
89
|
-
body: JSON.stringify({ text }),
|
|
90
|
+
body: JSON.stringify({ text, role }),
|
|
90
91
|
signal: controller.signal,
|
|
91
92
|
});
|
|
92
93
|
clearTimeout(timeout);
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import type { IncomingMessage, ServerResponse, Server as HTTPServer } from 'http';
|
|
16
16
|
export declare const SHUTDOWN_TOKEN: string;
|
|
17
|
+
import type { EmbeddingRole } from '../embeddings.js';
|
|
17
18
|
export declare const DEFAULT_PORT: number;
|
|
18
19
|
export declare const HOST = "127.0.0.1";
|
|
19
20
|
export declare const PORT_FILE: string;
|
|
@@ -42,6 +43,7 @@ export interface ServerOptions {
|
|
|
42
43
|
sessionStore?: SessionStore;
|
|
43
44
|
graphHandler?: RequestHandler;
|
|
44
45
|
}
|
|
46
|
+
export declare function resolveEmbeddingRole(raw: unknown): EmbeddingRole;
|
|
45
47
|
/**
|
|
46
48
|
* Clean up port file on exit
|
|
47
49
|
*/
|
|
@@ -18,6 +18,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
18
18
|
};
|
|
19
19
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
20
|
exports.PORT_FILE = exports.HOST = exports.DEFAULT_PORT = exports.SHUTDOWN_TOKEN = void 0;
|
|
21
|
+
exports.resolveEmbeddingRole = resolveEmbeddingRole;
|
|
21
22
|
exports.cleanupPortFile = cleanupPortFile;
|
|
22
23
|
exports.startEmbeddingServer = startEmbeddingServer;
|
|
23
24
|
exports.warmModel = warmModel;
|
|
@@ -63,6 +64,11 @@ const connections = new Set();
|
|
|
63
64
|
// Graph handler (optional, passed from standalone)
|
|
64
65
|
let graphHandler = null;
|
|
65
66
|
let _wssInstance = null;
|
|
67
|
+
// Resolve the e5 role from an HTTP body value. Absent/unknown -> passage, so old
|
|
68
|
+
// clients that never send a role keep getting passage vectors (backward compatible).
|
|
69
|
+
function resolveEmbeddingRole(raw) {
|
|
70
|
+
return raw === 'query' ? 'query' : 'passage';
|
|
71
|
+
}
|
|
66
72
|
/**
|
|
67
73
|
* Read request body as JSON
|
|
68
74
|
*/
|
|
@@ -158,7 +164,8 @@ async function handleRequest(req, res) {
|
|
|
158
164
|
res.end(JSON.stringify({ error: 'Missing or invalid "text" field' }));
|
|
159
165
|
return;
|
|
160
166
|
}
|
|
161
|
-
const
|
|
167
|
+
const role = resolveEmbeddingRole(body.role);
|
|
168
|
+
const embedding = await (0, embeddings_js_1.generateEmbedding)(body.text, role);
|
|
162
169
|
modelLoaded = true;
|
|
163
170
|
const latency = Date.now() - startTime;
|
|
164
171
|
res.writeHead(200);
|
|
@@ -186,7 +193,8 @@ async function handleRequest(req, res) {
|
|
|
186
193
|
res.end(JSON.stringify({ error: 'Missing or invalid "texts" array' }));
|
|
187
194
|
return;
|
|
188
195
|
}
|
|
189
|
-
const
|
|
196
|
+
const role = resolveEmbeddingRole(body.role);
|
|
197
|
+
const embeddings = await Promise.all(body.texts.map((text) => (0, embeddings_js_1.generateEmbedding)(text, role).then((e) => Array.from(e))));
|
|
190
198
|
modelLoaded = true;
|
|
191
199
|
const latency = Date.now() - startTime;
|
|
192
200
|
res.writeHead(200);
|
package/dist/embeddings.d.ts
CHANGED
|
@@ -11,6 +11,11 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import { embeddingCache } from './embedding-cache.js';
|
|
13
13
|
import { loadConfig, getModelName, getEmbeddingDim, getQuantized } from './config-loader.js';
|
|
14
|
+
export declare const EMBEDDING_MODEL_MAX_LENGTH = 512;
|
|
15
|
+
export declare const EMBEDDING_MAX_TOKENISH_SEGMENTS = 480;
|
|
16
|
+
export type EmbeddingRole = 'passage' | 'query';
|
|
17
|
+
export declare const EMBEDDING_PREFIX_SCHEME = "e5-prefixed-v1";
|
|
18
|
+
export declare function prepareEmbeddingText(text: string): string;
|
|
14
19
|
/**
|
|
15
20
|
* Decision object for enhanced embedding generation
|
|
16
21
|
*/
|
|
@@ -35,7 +40,7 @@ export interface DecisionForEmbedding {
|
|
|
35
40
|
* @returns Embedding vector (dimension from config)
|
|
36
41
|
* @throws Error if text is empty or embedding fails
|
|
37
42
|
*/
|
|
38
|
-
export declare function generateEmbedding(text: string): Promise<Float32Array>;
|
|
43
|
+
export declare function generateEmbedding(text: string, role?: EmbeddingRole): Promise<Float32Array>;
|
|
39
44
|
/**
|
|
40
45
|
* Generate enhanced embedding with content + metadata
|
|
41
46
|
*
|
|
@@ -45,7 +50,7 @@ export declare function generateEmbedding(text: string): Promise<Float32Array>;
|
|
|
45
50
|
* @param decision - Decision object
|
|
46
51
|
* @returns 1024-dim enhanced embedding
|
|
47
52
|
*/
|
|
48
|
-
export declare function generateEnhancedEmbedding(decision: DecisionForEmbedding): Promise<Float32Array>;
|
|
53
|
+
export declare function generateEnhancedEmbedding(decision: DecisionForEmbedding, role?: EmbeddingRole): Promise<Float32Array>;
|
|
49
54
|
/**
|
|
50
55
|
* Batch generate embeddings (optimized)
|
|
51
56
|
*
|
|
@@ -57,7 +62,7 @@ export declare function generateEnhancedEmbedding(decision: DecisionForEmbedding
|
|
|
57
62
|
* @param texts - Array of texts to embed (max 10 per batch)
|
|
58
63
|
* @returns Array of embeddings
|
|
59
64
|
*/
|
|
60
|
-
export declare function generateBatchEmbeddings(texts: string[]): Promise<Float32Array[]>;
|
|
65
|
+
export declare function generateBatchEmbeddings(texts: string[], role?: EmbeddingRole): Promise<Float32Array[]>;
|
|
61
66
|
/**
|
|
62
67
|
* Calculate cosine similarity between two embeddings
|
|
63
68
|
*
|
package/dist/embeddings.js
CHANGED
|
@@ -14,7 +14,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
14
14
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
exports.getQuantized = exports.getEmbeddingDim = exports.getModelName = exports.loadConfig = exports.MODEL_NAME = exports.EMBEDDING_DIM = exports.embeddingCache = void 0;
|
|
17
|
+
exports.getQuantized = exports.getEmbeddingDim = exports.getModelName = exports.loadConfig = exports.MODEL_NAME = exports.EMBEDDING_DIM = exports.embeddingCache = exports.EMBEDDING_PREFIX_SCHEME = exports.EMBEDDING_MAX_TOKENISH_SEGMENTS = exports.EMBEDDING_MODEL_MAX_LENGTH = void 0;
|
|
18
|
+
exports.prepareEmbeddingText = prepareEmbeddingText;
|
|
18
19
|
exports.generateEmbedding = generateEmbedding;
|
|
19
20
|
exports.generateEnhancedEmbedding = generateEnhancedEmbedding;
|
|
20
21
|
exports.generateBatchEmbeddings = generateBatchEmbeddings;
|
|
@@ -33,6 +34,13 @@ Object.defineProperty(exports, "getQuantized", { enumerable: true, get: function
|
|
|
33
34
|
// Shared cache directory (not in node_modules)
|
|
34
35
|
const DEFAULT_CACHE_DIR = path_1.default.join(os_1.default.homedir(), '.cache', 'huggingface', 'transformers');
|
|
35
36
|
const TIER3_ENV_VALUES = new Set(['1', 'true', 'yes']);
|
|
37
|
+
exports.EMBEDDING_MODEL_MAX_LENGTH = 512;
|
|
38
|
+
exports.EMBEDDING_MAX_TOKENISH_SEGMENTS = 480;
|
|
39
|
+
exports.EMBEDDING_PREFIX_SCHEME = 'e5-prefixed-v1';
|
|
40
|
+
function applyRolePrefix(preparedText, role) {
|
|
41
|
+
return `${role}: ${preparedText}`;
|
|
42
|
+
}
|
|
43
|
+
const TOKENISH_SEGMENT_PATTERN = /[\p{Script=Hangul}\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}]|\S+/gu;
|
|
36
44
|
// Singleton pattern for model loading
|
|
37
45
|
let embeddingPipeline = null;
|
|
38
46
|
let currentModelName = null;
|
|
@@ -46,6 +54,19 @@ function assertEmbeddingsEnabled() {
|
|
|
46
54
|
'Tier 3 test mode must use lexical/no-vector fallback instead of loading the embedding model.');
|
|
47
55
|
}
|
|
48
56
|
}
|
|
57
|
+
function prepareEmbeddingText(text) {
|
|
58
|
+
const trimmed = text.trim();
|
|
59
|
+
let count = 0;
|
|
60
|
+
let cutIndex = trimmed.length;
|
|
61
|
+
for (const match of trimmed.matchAll(TOKENISH_SEGMENT_PATTERN)) {
|
|
62
|
+
count++;
|
|
63
|
+
if (count > exports.EMBEDDING_MAX_TOKENISH_SEGMENTS) {
|
|
64
|
+
cutIndex = match.index ?? trimmed.length;
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return cutIndex === trimmed.length ? trimmed : trimmed.slice(0, cutIndex).trimEnd();
|
|
69
|
+
}
|
|
49
70
|
/**
|
|
50
71
|
* Load embedding model (configurable)
|
|
51
72
|
*
|
|
@@ -111,13 +132,15 @@ async function loadModel() {
|
|
|
111
132
|
* @returns Embedding vector (dimension from config)
|
|
112
133
|
* @throws Error if text is empty or embedding fails
|
|
113
134
|
*/
|
|
114
|
-
async function generateEmbedding(text) {
|
|
115
|
-
if (
|
|
135
|
+
async function generateEmbedding(text, role = 'passage') {
|
|
136
|
+
if (typeof text !== 'string' || text.trim().length === 0) {
|
|
116
137
|
throw new Error('Text cannot be empty');
|
|
117
138
|
}
|
|
118
139
|
assertEmbeddingsEnabled();
|
|
119
|
-
|
|
120
|
-
const
|
|
140
|
+
const preparedText = prepareEmbeddingText(text);
|
|
141
|
+
const modelInput = applyRolePrefix(preparedText, role); // e5 instruction prefix
|
|
142
|
+
// Cache key is the PREFIXED input: a passage vector can never be served for a query.
|
|
143
|
+
const cached = embedding_cache_js_1.embeddingCache.get(modelInput);
|
|
121
144
|
if (cached) {
|
|
122
145
|
return cached;
|
|
123
146
|
}
|
|
@@ -126,9 +149,11 @@ async function generateEmbedding(text) {
|
|
|
126
149
|
const model = await loadModel();
|
|
127
150
|
const expectedDim = (0, config_loader_js_1.getEmbeddingDim)();
|
|
128
151
|
// Generate embedding
|
|
129
|
-
const output = await model(
|
|
152
|
+
const output = await model(modelInput, {
|
|
130
153
|
pooling: 'mean', // Mean pooling over tokens
|
|
131
154
|
normalize: true, // L2 normalization
|
|
155
|
+
truncation: true,
|
|
156
|
+
max_length: exports.EMBEDDING_MODEL_MAX_LENGTH,
|
|
132
157
|
});
|
|
133
158
|
// Extract Float32Array
|
|
134
159
|
const embedding = output.data;
|
|
@@ -139,7 +164,7 @@ async function generateEmbedding(text) {
|
|
|
139
164
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
140
165
|
const _latency = Date.now() - startTime;
|
|
141
166
|
// Task 2: Store in cache (AC #3)
|
|
142
|
-
embedding_cache_js_1.embeddingCache.set(
|
|
167
|
+
embedding_cache_js_1.embeddingCache.set(modelInput, embedding);
|
|
143
168
|
return embedding;
|
|
144
169
|
}
|
|
145
170
|
catch (error) {
|
|
@@ -156,7 +181,7 @@ async function generateEmbedding(text) {
|
|
|
156
181
|
* @param decision - Decision object
|
|
157
182
|
* @returns 1024-dim enhanced embedding
|
|
158
183
|
*/
|
|
159
|
-
async function generateEnhancedEmbedding(decision) {
|
|
184
|
+
async function generateEnhancedEmbedding(decision, role = 'passage') {
|
|
160
185
|
// Construct enriched text representation with narrative fields (Story 2.2)
|
|
161
186
|
const parts = [
|
|
162
187
|
`Topic: ${decision.topic}`,
|
|
@@ -187,7 +212,7 @@ async function generateEnhancedEmbedding(decision) {
|
|
|
187
212
|
parts.push(`Risks: ${decision.risks}`);
|
|
188
213
|
}
|
|
189
214
|
const enrichedText = parts.join('\n').trim();
|
|
190
|
-
return generateEmbedding(enrichedText);
|
|
215
|
+
return generateEmbedding(enrichedText, role);
|
|
191
216
|
}
|
|
192
217
|
/**
|
|
193
218
|
* Batch generate embeddings (optimized)
|
|
@@ -200,26 +225,34 @@ async function generateEnhancedEmbedding(decision) {
|
|
|
200
225
|
* @param texts - Array of texts to embed (max 10 per batch)
|
|
201
226
|
* @returns Array of embeddings
|
|
202
227
|
*/
|
|
203
|
-
async function generateBatchEmbeddings(texts) {
|
|
204
|
-
assertEmbeddingsEnabled();
|
|
228
|
+
async function generateBatchEmbeddings(texts, role = 'passage') {
|
|
205
229
|
if (!Array.isArray(texts) || texts.length === 0) {
|
|
206
230
|
throw new Error('Texts must be a non-empty array');
|
|
207
231
|
}
|
|
208
|
-
// Validate all texts
|
|
209
232
|
for (const text of texts) {
|
|
210
|
-
if (
|
|
211
|
-
throw new Error('All texts must be non-empty');
|
|
233
|
+
if (typeof text !== 'string' || text.trim().length === 0) {
|
|
234
|
+
throw new Error('All texts must be non-empty strings');
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
assertEmbeddingsEnabled();
|
|
238
|
+
const preparedTexts = texts.map((text) => prepareEmbeddingText(text));
|
|
239
|
+
for (const text of preparedTexts) {
|
|
240
|
+
if (!text || text.length === 0) {
|
|
241
|
+
throw new Error('All texts must be non-empty strings');
|
|
212
242
|
}
|
|
213
243
|
}
|
|
244
|
+
const modelInputs = preparedTexts.map((t) => applyRolePrefix(t, role));
|
|
214
245
|
const startTime = Date.now();
|
|
215
246
|
try {
|
|
216
247
|
const model = await loadModel();
|
|
217
248
|
const expectedDim = (0, config_loader_js_1.getEmbeddingDim)();
|
|
218
249
|
// Native batch processing - single model forward pass
|
|
219
250
|
// This is significantly faster than sequential calls
|
|
220
|
-
const outputs = await model(
|
|
251
|
+
const outputs = await model(modelInputs, {
|
|
221
252
|
pooling: 'mean',
|
|
222
253
|
normalize: true,
|
|
254
|
+
truncation: true,
|
|
255
|
+
max_length: exports.EMBEDDING_MODEL_MAX_LENGTH,
|
|
223
256
|
});
|
|
224
257
|
// Extract embeddings from batch output
|
|
225
258
|
const embeddings = [];
|
package/dist/entities/store.d.ts
CHANGED
|
@@ -13,9 +13,11 @@ export declare class EntityMergeError extends Error {
|
|
|
13
13
|
readonly code: string;
|
|
14
14
|
constructor(code: string, message: string);
|
|
15
15
|
}
|
|
16
|
+
declare const OBSERVATION_CONTEXT_KEY_COLUMNS: readonly ["workspace_context_key", "channel_context_key", "thread_context_key", "actor_context_key"];
|
|
16
17
|
type CreateEntityNodeInput = Omit<EntityNode, 'created_at' | 'updated_at'>;
|
|
17
18
|
type AttachEntityAliasInput = Omit<EntityAlias, 'created_at'>;
|
|
18
|
-
type
|
|
19
|
+
type ObservationContextKeyColumn = (typeof OBSERVATION_CONTEXT_KEY_COLUMNS)[number];
|
|
20
|
+
type UpsertEntityObservationInput = Omit<EntityObservation, 'created_at'> & Partial<Record<ObservationContextKeyColumn, string | null>>;
|
|
19
21
|
type AppendEntityTimelineEventInput = {
|
|
20
22
|
event: Omit<EntityTimelineEvent, 'id' | 'created_at'> & {
|
|
21
23
|
id?: string;
|
|
@@ -27,6 +29,7 @@ export interface UpsertEntityObservationResult {
|
|
|
27
29
|
id: string;
|
|
28
30
|
created: boolean;
|
|
29
31
|
}
|
|
32
|
+
export declare function clearEntityTableColumnCache(adapter: EntityStoreAdapter): void;
|
|
30
33
|
export declare function parseObservationRow(row: Record<string, unknown>): EntityObservation;
|
|
31
34
|
export declare function createEntityNode(input: CreateEntityNodeInput): Promise<EntityNode>;
|
|
32
35
|
export declare function getEntityNode(id: string, adapter?: EntityStoreAdapter): EntityNode | null;
|