@loreai/core 0.18.0 → 0.20.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.
Files changed (88) hide show
  1. package/dist/bun/agents-file.d.ts.map +1 -1
  2. package/dist/bun/config.d.ts.map +1 -1
  3. package/dist/bun/curator.d.ts.map +1 -1
  4. package/dist/bun/db.d.ts +86 -1
  5. package/dist/bun/db.d.ts.map +1 -1
  6. package/dist/bun/distillation.d.ts +2 -13
  7. package/dist/bun/distillation.d.ts.map +1 -1
  8. package/dist/bun/embedding.d.ts +5 -1
  9. package/dist/bun/embedding.d.ts.map +1 -1
  10. package/dist/bun/git.d.ts.map +1 -1
  11. package/dist/bun/gradient.d.ts +13 -1
  12. package/dist/bun/gradient.d.ts.map +1 -1
  13. package/dist/bun/hosted.d.ts +36 -0
  14. package/dist/bun/hosted.d.ts.map +1 -0
  15. package/dist/bun/index.d.ts +3 -2
  16. package/dist/bun/index.d.ts.map +1 -1
  17. package/dist/bun/index.js +1049 -247
  18. package/dist/bun/index.js.map +4 -4
  19. package/dist/bun/lat-reader.d.ts.map +1 -1
  20. package/dist/bun/ltm.d.ts +99 -5
  21. package/dist/bun/ltm.d.ts.map +1 -1
  22. package/dist/bun/session-limiter.d.ts +26 -0
  23. package/dist/bun/session-limiter.d.ts.map +1 -0
  24. package/dist/bun/temporal.d.ts +2 -0
  25. package/dist/bun/temporal.d.ts.map +1 -1
  26. package/dist/node/agents-file.d.ts.map +1 -1
  27. package/dist/node/config.d.ts.map +1 -1
  28. package/dist/node/curator.d.ts.map +1 -1
  29. package/dist/node/db.d.ts +86 -1
  30. package/dist/node/db.d.ts.map +1 -1
  31. package/dist/node/distillation.d.ts +2 -13
  32. package/dist/node/distillation.d.ts.map +1 -1
  33. package/dist/node/embedding.d.ts +5 -1
  34. package/dist/node/embedding.d.ts.map +1 -1
  35. package/dist/node/git.d.ts.map +1 -1
  36. package/dist/node/gradient.d.ts +13 -1
  37. package/dist/node/gradient.d.ts.map +1 -1
  38. package/dist/node/hosted.d.ts +36 -0
  39. package/dist/node/hosted.d.ts.map +1 -0
  40. package/dist/node/index.d.ts +3 -2
  41. package/dist/node/index.d.ts.map +1 -1
  42. package/dist/node/index.js +1049 -247
  43. package/dist/node/index.js.map +4 -4
  44. package/dist/node/lat-reader.d.ts.map +1 -1
  45. package/dist/node/ltm.d.ts +99 -5
  46. package/dist/node/ltm.d.ts.map +1 -1
  47. package/dist/node/session-limiter.d.ts +26 -0
  48. package/dist/node/session-limiter.d.ts.map +1 -0
  49. package/dist/node/temporal.d.ts +2 -0
  50. package/dist/node/temporal.d.ts.map +1 -1
  51. package/dist/types/agents-file.d.ts.map +1 -1
  52. package/dist/types/config.d.ts.map +1 -1
  53. package/dist/types/curator.d.ts.map +1 -1
  54. package/dist/types/db.d.ts +86 -1
  55. package/dist/types/db.d.ts.map +1 -1
  56. package/dist/types/distillation.d.ts +2 -13
  57. package/dist/types/distillation.d.ts.map +1 -1
  58. package/dist/types/embedding.d.ts +5 -1
  59. package/dist/types/embedding.d.ts.map +1 -1
  60. package/dist/types/git.d.ts.map +1 -1
  61. package/dist/types/gradient.d.ts +13 -1
  62. package/dist/types/gradient.d.ts.map +1 -1
  63. package/dist/types/hosted.d.ts +36 -0
  64. package/dist/types/hosted.d.ts.map +1 -0
  65. package/dist/types/index.d.ts +3 -2
  66. package/dist/types/index.d.ts.map +1 -1
  67. package/dist/types/lat-reader.d.ts.map +1 -1
  68. package/dist/types/ltm.d.ts +99 -5
  69. package/dist/types/ltm.d.ts.map +1 -1
  70. package/dist/types/session-limiter.d.ts +26 -0
  71. package/dist/types/session-limiter.d.ts.map +1 -0
  72. package/dist/types/temporal.d.ts +2 -0
  73. package/dist/types/temporal.d.ts.map +1 -1
  74. package/package.json +3 -1
  75. package/src/agents-file.ts +12 -0
  76. package/src/config.ts +10 -5
  77. package/src/curator.ts +54 -2
  78. package/src/db.ts +386 -6
  79. package/src/distillation.ts +55 -14
  80. package/src/embedding.ts +71 -8
  81. package/src/git.ts +4 -0
  82. package/src/gradient.ts +227 -74
  83. package/src/hosted.ts +46 -0
  84. package/src/index.ts +12 -0
  85. package/src/lat-reader.ts +4 -0
  86. package/src/ltm.ts +480 -45
  87. package/src/session-limiter.ts +47 -0
  88. package/src/temporal.ts +10 -0
@@ -45,6 +45,24 @@ export declare function findFuzzyDuplicate(input: {
45
45
  title: string;
46
46
  } | null;
47
47
  export declare function forProject(projectPath: string, includeCross?: boolean): KnowledgeEntry[];
48
+ /**
49
+ * Well-known knowledge entry categories managed by the curator.
50
+ * The DB column is a free-form string, but these are the standard values.
51
+ */
52
+ export type KnowledgeCategory = "decision" | "pattern" | "preference" | "architecture" | "gotcha";
53
+ /** Options for `forSession()` to control entry selection. */
54
+ export type ForSessionOptions = {
55
+ /** Caller-provided context (e.g., user's current message) for relevance
56
+ * scoring when no session context exists in the DB yet. */
57
+ contextHint?: string;
58
+ /** Restrict to these categories (e.g., `['preference']` for turn 1). */
59
+ categories?: (KnowledgeCategory | (string & {}))[];
60
+ /** Exclude these categories (e.g., `['preference']` for context-bound
61
+ * entries when preferences are already injected in a separate block).
62
+ * Mutually exclusive with `categories` — if both are provided,
63
+ * `categories` (include) wins. */
64
+ excludeCategories?: (KnowledgeCategory | (string & {}))[];
65
+ };
48
66
  /**
49
67
  * Build a relevance-ranked, budget-capped list of knowledge entries for injection
50
68
  * into the system prompt of a live session.
@@ -52,20 +70,24 @@ export declare function forProject(projectPath: string, includeCross?: boolean):
52
70
  * Strategy:
53
71
  * 1. Both project-specific and cross-project entries are scored for relevance
54
72
  * against recent session context (last distillation + recent raw messages).
55
- * 2. Project entries get a safety net: the top PROJECT_SAFETY_NET entries by
73
+ * 2. When embeddings are available, vector cosine similarity is used for scoring
74
+ * (captures semantic matches that keyword overlap misses). Falls back to
75
+ * FTS5 BM25 when embeddings are unavailable.
76
+ * 3. Project entries get a safety net: the top PROJECT_SAFETY_NET entries by
56
77
  * confidence are always included even if they have zero relevance score.
57
78
  * This ensures the most important project knowledge is never lost to
58
- * coarse term-overlap scoring.
59
- * 3. All scored entries are merged into a single pool and greedily packed
79
+ * coarse scoring.
80
+ * 4. All scored entries are merged into a single pool and greedily packed
60
81
  * into the token budget by score descending.
61
- * 4. If there's no session context yet (first turn), fall back to top entries
82
+ * 5. If there's no session context yet (first turn), fall back to top entries
62
83
  * by confidence only (capped at NO_CONTEXT_FALLBACK_CAP per pool).
63
84
  *
64
85
  * @param projectPath Current project path
65
86
  * @param sessionID Current session ID (for context extraction)
66
87
  * @param maxTokens Hard token budget for the entire formatted block
88
+ * @param options Optional category filter and context hint
67
89
  */
68
- export declare function forSession(projectPath: string, sessionID: string | undefined, maxTokens: number): KnowledgeEntry[];
90
+ export declare function forSession(projectPath: string, sessionID: string | undefined, maxTokens: number, options?: ForSessionOptions): Promise<KnowledgeEntry[]>;
69
91
  export declare function all(): KnowledgeEntry[];
70
92
  /** Return all cross-project and global (user-level) knowledge entries. */
71
93
  export declare function crossProject(): KnowledgeEntry[];
@@ -164,9 +186,15 @@ export type DedupCluster = {
164
186
  title: string;
165
187
  }>;
166
188
  };
189
+ /** Stable pair key for two entry IDs — sorted to ensure order-independence. */
190
+ export declare function dedupPairKey(idA: string, idB: string): string;
167
191
  export type DedupResult = {
168
192
  clusters: DedupCluster[];
169
193
  totalRemoved: number;
194
+ /** Pairwise embedding cosine similarities. Key: dedupPairKey(idA, idB). */
195
+ pairSimilarities: Map<string, number>;
196
+ /** All entry titles by ID — for feedback recording after entries are deleted. */
197
+ entryTitles: Map<string, string>;
170
198
  };
171
199
  export declare function deduplicate(projectPath: string, opts?: {
172
200
  dryRun?: boolean;
@@ -175,4 +203,70 @@ export declare function deduplicate(projectPath: string, opts?: {
175
203
  export declare function deduplicateGlobal(opts?: {
176
204
  dryRun?: boolean;
177
205
  }): Promise<DedupResult>;
206
+ export type DedupFeedbackSource = "auto_dedup" | "cli_yes" | "cli_interactive";
207
+ /** Record a single dedup feedback row. */
208
+ export declare function recordDedupFeedback(input: {
209
+ projectId: string | null;
210
+ entryATitle: string;
211
+ entryBTitle: string;
212
+ similarity: number;
213
+ accepted: boolean;
214
+ source: DedupFeedbackSource;
215
+ }): void;
216
+ /**
217
+ * Bulk-record feedback for all merged pairs in a DedupResult.
218
+ * Only records pairs with embedding similarity > 0 (title-overlap-only
219
+ * matches are excluded from calibration).
220
+ */
221
+ export declare function recordDedupResultFeedback(projectId: string | null, result: DedupResult, accepted: boolean, source: DedupFeedbackSource): void;
222
+ /**
223
+ * Record automatic calibration signals from a post-curation dedup sweep.
224
+ *
225
+ * Only records **reject** signals — non-merged pairs with similarity in
226
+ * [0.80, threshold). Accept signals from auto-dedup are tautological (the
227
+ * pair was merged *because* its similarity exceeded the threshold), so they
228
+ * provide no new information and would create a self-reinforcing feedback
229
+ * loop. Manual signals (cli_yes, cli_interactive) provide the accept side.
230
+ *
231
+ * Caps at AUTO_SIGNAL_MAX_PAIRS most interesting pairs per run (closest
232
+ * to the threshold boundary) to avoid table bloat.
233
+ */
234
+ export declare function recordAutoSignals(projectId: string | null, result: DedupResult): void;
235
+ /** Get all feedback for a project (for calibration). */
236
+ export declare function getDedupFeedback(projectId: string | null): Array<{
237
+ similarity: number;
238
+ accepted: boolean;
239
+ source: string;
240
+ }>;
241
+ /** Quick count of feedback rows for a project. */
242
+ export declare function getDedupFeedbackCount(projectId: string | null): number;
243
+ /**
244
+ * Prune old feedback rows for a project, keeping the most recent
245
+ * MAX_FEEDBACK_ROWS_PER_PROJECT rows. Called from recordAutoSignals
246
+ * to prevent unbounded table growth.
247
+ */
248
+ export declare function pruneDedupFeedback(projectId: string | null): void;
249
+ /**
250
+ * Compute an optimal embedding dedup threshold from user feedback.
251
+ *
252
+ * Algorithm:
253
+ * 1. Load all (similarity, accepted) pairs for the project.
254
+ * 2. If fewer than MIN_CALIBRATION_SAMPLES, return null (use default).
255
+ * 3. If all feedback is "accept" (no rejects), return the minimum
256
+ * accepted similarity minus a small margin (0.005).
257
+ * 4. If all feedback is "reject" (no accepts), return null.
258
+ * 5. Otherwise, find the threshold that maximizes separation:
259
+ * - For each candidate threshold (midpoint between consecutive
260
+ * distinct similarity values), compute accuracy:
261
+ * correct = accepted_pairs_above + rejected_pairs_below
262
+ * accuracy = correct / total
263
+ * - Pick the threshold with highest accuracy.
264
+ * - Tie-break: prefer higher threshold (conservative).
265
+ * - Clamp to [0.85, 0.98].
266
+ */
267
+ export declare function calibrateDedupThreshold(projectId: string | null): number | null;
268
+ /** Persist the calibrated threshold for a project. */
269
+ export declare function saveCalibratedThreshold(projectId: string | null, threshold: number, sampleSize: number): void;
270
+ /** Load the calibrated threshold for a project, or null if not calibrated. */
271
+ export declare function loadCalibratedThreshold(projectId: string | null): number | null;
178
272
  //# sourceMappingURL=ltm.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ltm.d.ts","sourceRoot":"","sources":["../../src/ltm.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ltm.d.ts","sourceRoot":"","sources":["../../src/ltm.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Per-key concurrency limiter using p-limit.
3
+ *
4
+ * Each key (typically a session ID) gets its own p-limit(1) instance,
5
+ * serializing async operations on the same key while allowing different
6
+ * keys to run fully in parallel.
7
+ *
8
+ * Two independent limiter pools are provided — one for distillation and
9
+ * one for curation — so they don't block each other.
10
+ */
11
+ import pLimit from "p-limit";
12
+ type LimitFunction = ReturnType<typeof pLimit>;
13
+ /** Serializes distillation.run() and metaDistill() per session. */
14
+ export declare const distillLimiter: {
15
+ get: (key: string) => LimitFunction;
16
+ isBusy: (key: string) => boolean;
17
+ clear: () => void;
18
+ };
19
+ /** Serializes curator.run() per session with skip-if-busy semantics. */
20
+ export declare const curatorLimiter: {
21
+ get: (key: string) => LimitFunction;
22
+ isBusy: (key: string) => boolean;
23
+ clear: () => void;
24
+ };
25
+ export {};
26
+ //# sourceMappingURL=session-limiter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-limiter.d.ts","sourceRoot":"","sources":["../../src/session-limiter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,MAAM,MAAM,SAAS,CAAC;AAE7B,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,MAAM,CAAC,CAAC;AA6B/C,mEAAmE;AACnE,eAAO,MAAM,cAAc;eAxBP,MAAM,KAAG,aAAa;kBAUnB,MAAM,KAAG,OAAO;iBAMnB,IAAI;CAQyB,CAAC;AAElD,wEAAwE;AACxE,eAAO,MAAM,cAAc;eA3BP,MAAM,KAAG,aAAa;kBAUnB,MAAM,KAAG,OAAO;iBAMnB,IAAI;CAWyB,CAAC"}
@@ -84,6 +84,8 @@ export declare function searchScored(input: {
84
84
  */
85
85
  export declare function temporalCnorm(timestamps: number[], now?: number): number;
86
86
  export declare function count(projectPath: string, sessionID?: string): number;
87
+ /** Quick existence check — true if any temporal messages exist for this session. */
88
+ export declare function hasMessages(projectPath: string, sessionID: string): boolean;
87
89
  export declare function undistilledCount(projectPath: string, sessionID?: string): number;
88
90
  /** Sum of estimated tokens across undistilled messages for a project/session. */
89
91
  export declare function undistilledTokens(projectPath: string, sessionID?: string): number;
@@ -1 +1 @@
1
- {"version":3,"file":"temporal.d.ts","sourceRoot":"","sources":["../../src/temporal.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAQrD;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,gBAAgB,WAAS,CAAC;AAEvC;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,QAAQ,EAAE,GAAG,MAAM,CAarD;AAiBD,wBAAgB,KAAK,CAAC,KAAK,EAAE;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,WAAW,CAAC;IAClB,KAAK,EAAE,QAAQ,EAAE,CAAC;CACnB,QA8CA;AAED,MAAM,MAAM,eAAe,GAAG;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,wBAAgB,WAAW,CACzB,WAAW,EAAE,MAAM,EACnB,SAAS,CAAC,EAAE,MAAM,GACjB,eAAe,EAAE,CASnB;AAED,wBAAgB,SAAS,CACvB,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,GAChB,eAAe,EAAE,CAOnB;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,QAQ1C;AA6BD,wBAAgB,MAAM,CAAC,KAAK,EAAE;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,GAAG,eAAe,EAAE,CA8BpB;AAED,MAAM,MAAM,qBAAqB,GAAG,eAAe,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAEvE;;;GAGG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE;IAClC,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,GAAG,qBAAqB,EAAE,CAwB1B;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,aAAa,CAC3B,UAAU,EAAE,MAAM,EAAE,EACpB,GAAG,GAAE,MAAmB,GACvB,MAAM,CAoBR;AAED,wBAAgB,KAAK,CAAC,WAAW,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAWrE;AAED,wBAAgB,gBAAgB,CAC9B,WAAW,EAAE,MAAM,EACnB,SAAS,CAAC,EAAE,MAAM,GACjB,MAAM,CAWR;AAED,iFAAiF;AACjF,wBAAgB,iBAAiB,CAC/B,WAAW,EAAE,MAAM,EACnB,SAAS,CAAC,EAAE,MAAM,GACjB,MAAM,CAWR;AAED,MAAM,MAAM,WAAW,GAAG;IACxB,kFAAkF;IAClF,UAAU,EAAE,MAAM,CAAC;IACnB,8FAA8F;IAC9F,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,wBAAgB,KAAK,CAAC,KAAK,EAAE;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;CACtB,GAAG,WAAW,CA0Ed"}
1
+ {"version":3,"file":"temporal.d.ts","sourceRoot":"","sources":["../../src/temporal.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAQrD;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,gBAAgB,WAAS,CAAC;AAEvC;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,QAAQ,EAAE,GAAG,MAAM,CAarD;AAiBD,wBAAgB,KAAK,CAAC,KAAK,EAAE;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,WAAW,CAAC;IAClB,KAAK,EAAE,QAAQ,EAAE,CAAC;CACnB,QA8CA;AAED,MAAM,MAAM,eAAe,GAAG;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,wBAAgB,WAAW,CACzB,WAAW,EAAE,MAAM,EACnB,SAAS,CAAC,EAAE,MAAM,GACjB,eAAe,EAAE,CASnB;AAED,wBAAgB,SAAS,CACvB,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,GAChB,eAAe,EAAE,CAOnB;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,QAQ1C;AA6BD,wBAAgB,MAAM,CAAC,KAAK,EAAE;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,GAAG,eAAe,EAAE,CA8BpB;AAED,MAAM,MAAM,qBAAqB,GAAG,eAAe,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAEvE;;;GAGG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE;IAClC,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,GAAG,qBAAqB,EAAE,CAwB1B;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,aAAa,CAC3B,UAAU,EAAE,MAAM,EAAE,EACpB,GAAG,GAAE,MAAmB,GACvB,MAAM,CAoBR;AAED,wBAAgB,KAAK,CAAC,WAAW,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAWrE;AAED,oFAAoF;AACpF,wBAAgB,WAAW,CAAC,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAO3E;AAED,wBAAgB,gBAAgB,CAC9B,WAAW,EAAE,MAAM,EACnB,SAAS,CAAC,EAAE,MAAM,GACjB,MAAM,CAWR;AAED,iFAAiF;AACjF,wBAAgB,iBAAiB,CAC/B,WAAW,EAAE,MAAM,EACnB,SAAS,CAAC,EAAE,MAAM,GACjB,MAAM,CAWR;AAED,MAAM,MAAM,WAAW,GAAG;IACxB,kFAAkF;IAClF,UAAU,EAAE,MAAM,CAAC;IACnB,8FAA8F;IAC9F,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,wBAAgB,KAAK,CAAC,KAAK,EAAE;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;CACtB,GAAG,WAAW,CA0Ed"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@loreai/core",
3
- "version": "0.18.0",
3
+ "version": "0.20.0",
4
4
  "type": "module",
5
5
  "license": "FSL-1.1-Apache-2.0",
6
6
  "description": "Shared memory engine for Lore — three-tier storage, distillation, gradient context management",
@@ -8,6 +8,7 @@
8
8
  "types": "./dist/node/index.d.ts",
9
9
  "exports": {
10
10
  ".": {
11
+ "types": "./dist/types/index.d.ts",
11
12
  "bun": "./src/index.ts",
12
13
  "default": "./dist/node/index.js"
13
14
  }
@@ -26,6 +27,7 @@
26
27
  "@huggingface/hub": "2.11.0",
27
28
  "@huggingface/transformers": "^3.7.1",
28
29
  "micromark": "^4.0.0",
30
+ "p-limit": "7",
29
31
  "remark": "^15.0.1",
30
32
  "uuidv7": "^1.1.0",
31
33
  "zod": "^4.3.6"
@@ -13,6 +13,7 @@ import { dirname, join } from "path";
13
13
  import { db, ensureProject } from "./db";
14
14
  import * as ltm from "./ltm";
15
15
  import { serialize, inline, h, ul, liph, strong, t, root, unescapeMarkdown } from "./markdown";
16
+ import { isHostedMode } from "./hosted";
16
17
 
17
18
  // ---------------------------------------------------------------------------
18
19
  // Constants
@@ -334,6 +335,8 @@ export function exportToFile(input: {
334
335
  projectPath: string;
335
336
  filePath: string;
336
337
  }): void {
338
+ if (isHostedMode()) return;
339
+
337
340
  // Write the actual entries to .lore.md first.
338
341
  exportLoreFile(input.projectPath);
339
342
 
@@ -378,6 +381,7 @@ export function shouldImport(input: {
378
381
  projectPath: string;
379
382
  filePath: string;
380
383
  }): boolean {
384
+ if (isHostedMode()) return false;
381
385
  if (!existsSync(input.filePath)) return false;
382
386
 
383
387
  const fileContent = readFileSync(input.filePath, "utf8");
@@ -483,6 +487,7 @@ export function importFromFile(input: {
483
487
  projectPath: string;
484
488
  filePath: string;
485
489
  }): void {
490
+ if (isHostedMode()) return;
486
491
  if (!existsSync(input.filePath)) return;
487
492
 
488
493
  const fileContent = readFileSync(input.filePath, "utf8");
@@ -507,6 +512,7 @@ export function importFromFile(input: {
507
512
  * Returns true if a `.lore.md` file exists in the project root.
508
513
  */
509
514
  export function loreFileExists(projectPath: string): boolean {
515
+ if (isHostedMode()) return false;
510
516
  return existsSync(join(projectPath, LORE_FILE));
511
517
  }
512
518
 
@@ -519,6 +525,8 @@ export function loreFileExists(projectPath: string): boolean {
519
525
  * and mtime bumps.
520
526
  */
521
527
  export function exportLoreFile(projectPath: string): void {
528
+ if (isHostedMode()) return;
529
+
522
530
  const sectionBody = buildSection(projectPath);
523
531
  const content = LORE_FILE_HEADER + "\n" + sectionBody;
524
532
  const contentHash = hashSection(content);
@@ -545,6 +553,8 @@ export function exportLoreFile(projectPath: string): void {
545
553
  * call when the file hasn't been touched since we last processed it.
546
554
  */
547
555
  export function shouldImportLoreFile(projectPath: string): boolean {
556
+ if (isHostedMode()) return false;
557
+
548
558
  const fp = join(projectPath, LORE_FILE);
549
559
  if (!existsSync(fp)) return false;
550
560
 
@@ -579,6 +589,8 @@ export function shouldImportLoreFile(projectPath: string): boolean {
579
589
  * content hasn't changed, only the DB was updated to match it.
580
590
  */
581
591
  export function importLoreFile(projectPath: string): void {
592
+ if (isHostedMode()) return;
593
+
582
594
  const fp = join(projectPath, LORE_FILE);
583
595
  if (!existsSync(fp)) return;
584
596
 
package/src/config.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { z } from "zod";
2
2
  import { existsSync, readFileSync } from "node:fs";
3
3
  import { join } from "node:path";
4
+ import { isHostedMode } from "./hosted";
4
5
 
5
6
  export const LoreConfig = z.object({
6
7
  model: z
@@ -251,11 +252,15 @@ export function config(): LoreConfig {
251
252
  }
252
253
 
253
254
  export async function load(directory: string): Promise<LoreConfig> {
254
- const path = join(directory, ".lore.json");
255
- if (existsSync(path)) {
256
- const raw = JSON.parse(readFileSync(path, "utf8"));
257
- current = LoreConfig.parse(raw);
258
- return current;
255
+ // In hosted mode, never read config from client-controlled paths —
256
+ // a crafted .lore.json could alter gateway behavior (budget, model, thresholds).
257
+ if (!isHostedMode()) {
258
+ const path = join(directory, ".lore.json");
259
+ if (existsSync(path)) {
260
+ const raw = JSON.parse(readFileSync(path, "utf8"));
261
+ current = LoreConfig.parse(raw);
262
+ return current;
263
+ }
259
264
  }
260
265
  current = LoreConfig.parse({});
261
266
  return current;
package/src/curator.ts CHANGED
@@ -1,9 +1,11 @@
1
1
  import { config } from "./config";
2
+ import { saveSessionTracking, loadSessionTracking, ensureProject } from "./db";
2
3
  import * as temporal from "./temporal";
3
4
  import * as ltm from "./ltm";
4
5
  import * as log from "./log";
5
6
  import { CURATOR_SYSTEM, curatorUser, CONSOLIDATION_SYSTEM, consolidationUser } from "./prompt";
6
7
  import { detectAndFormat } from "./instruction-detect";
8
+ import { curatorLimiter } from "./session-limiter";
7
9
  import type { LLMClient } from "./types";
8
10
 
9
11
  /**
@@ -121,8 +123,20 @@ export function applyOps(
121
123
  // Track which messages we've already curated — per session to prevent
122
124
  // cross-session leaking (curation on session A advancing the timestamp
123
125
  // past session B's messages, causing B's curation to find < 3 recent).
126
+ // In-memory cache backed by session_state DB table so it survives restarts.
124
127
  const lastCuratedAt = new Map<string, number>();
125
128
 
129
+ /** Get the last-curated timestamp for a session, loading from DB if needed. */
130
+ function getLastCuratedAt(sessionID: string): number {
131
+ const cached = lastCuratedAt.get(sessionID);
132
+ if (cached !== undefined) return cached;
133
+ // Load from DB on first access
134
+ const persisted = loadSessionTracking(sessionID);
135
+ const ts = persisted?.lastCuratedAt ?? 0;
136
+ lastCuratedAt.set(sessionID, ts);
137
+ return ts;
138
+ }
139
+
126
140
  export async function run(input: {
127
141
  llm: LLMClient;
128
142
  projectPath: string;
@@ -132,9 +146,33 @@ export async function run(input: {
132
146
  const cfg = config();
133
147
  if (!cfg.curator.enabled) return { created: 0, updated: 0, deleted: 0 };
134
148
 
149
+ // Skip-if-busy: curation is periodic, not accumulative. If a curation is
150
+ // already running for this session, skip — the next trigger will pick up
151
+ // any new messages. Serializing would waste an LLM call.
152
+ //
153
+ // The isBusy() check and get()() enqueue are both synchronous — in Node's
154
+ // single-threaded event loop no microtask can interleave between them, so
155
+ // there is no TOCTOU race. The p-limit(1) serialization is a safety net
156
+ // if this invariant is ever violated.
157
+ if (curatorLimiter.isBusy(input.sessionID)) {
158
+ log.info(`curation skipped: already running for session ${input.sessionID.slice(0, 16)}`);
159
+ return { created: 0, updated: 0, deleted: 0 };
160
+ }
161
+
162
+ return curatorLimiter.get(input.sessionID)(() => runInner(input));
163
+ }
164
+
165
+ async function runInner(input: {
166
+ llm: LLMClient;
167
+ projectPath: string;
168
+ sessionID: string;
169
+ model?: { providerID: string; modelID: string };
170
+ }): Promise<{ created: number; updated: number; deleted: number }> {
171
+ const cfg = config();
172
+
135
173
  // Get recent messages since last curation
136
174
  const all = temporal.bySession(input.projectPath, input.sessionID);
137
- const sessionCuratedAt = lastCuratedAt.get(input.sessionID) ?? 0;
175
+ const sessionCuratedAt = getLastCuratedAt(input.sessionID);
138
176
  const recent = all.filter((m) => m.created_at > sessionCuratedAt);
139
177
  if (recent.length < 3) return { created: 0, updated: 0, deleted: 0 };
140
178
 
@@ -190,12 +228,26 @@ export async function run(input: {
190
228
  log.info(`post-curation dedup: merged ${dupes.totalRemoved} duplicate entries`);
191
229
  result.deleted += dupes.totalRemoved;
192
230
  }
231
+ // Record auto-signals for adaptive threshold calibration.
232
+ // Merged pairs → accept; non-merged high-similarity pairs → reject.
233
+ if (dupes.pairSimilarities.size > 0) {
234
+ const pid = ensureProject(input.projectPath);
235
+ ltm.recordAutoSignals(pid, dupes);
236
+ // Recalibrate if enough data has accumulated
237
+ const newThreshold = ltm.calibrateDedupThreshold(pid);
238
+ if (newThreshold !== null) {
239
+ const count = ltm.getDedupFeedbackCount(pid);
240
+ ltm.saveCalibratedThreshold(pid, newThreshold, count);
241
+ }
242
+ }
193
243
  } catch (err) {
194
244
  log.warn("post-curation dedup failed (non-fatal):", err);
195
245
  }
196
246
  }
197
247
 
198
- lastCuratedAt.set(input.sessionID, Date.now());
248
+ const now = Date.now();
249
+ lastCuratedAt.set(input.sessionID, now);
250
+ saveSessionTracking(input.sessionID, { lastCuratedAt: now });
199
251
  return result;
200
252
  }
201
253