@oh-my-pi/pi-mnemopi 18.14.0 → 18.14.2
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/CHANGELOG.md +6 -0
- package/package.json +5 -5
- package/src/core/episodic-graph.ts +35 -16
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [18.14.2] - 2026-10-31
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Fixed heuristic episodic facts storing word fragments (`Drafting is active.` → `ctive`), truncated multiword objects, or nothing for `has an …` sentences ([#17511](https://github.com/can1357/oh-my-pi/issues/17511))
|
|
10
|
+
|
|
5
11
|
## [18.13.3] - 2026-10-28
|
|
6
12
|
|
|
7
13
|
### Changed
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/pi-mnemopi",
|
|
4
|
-
"version": "18.14.
|
|
4
|
+
"version": "18.14.2",
|
|
5
5
|
"description": "Local SQLite memory engine for omp agents",
|
|
6
6
|
"homepage": "https://omp.sh",
|
|
7
7
|
"author": {
|
|
@@ -42,10 +42,10 @@
|
|
|
42
42
|
"fmt": "oxfmt --no-error-on-unmatched-pattern 'src/**/*.{ts,tsx}' '{test,bench,examples,scripts}/**/*.ts' '*.ts'"
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@oh-my-pi/pi-ai": "18.14.
|
|
46
|
-
"@oh-my-pi/pi-catalog": "18.14.
|
|
47
|
-
"@oh-my-pi/pi-natives": "18.14.
|
|
48
|
-
"@oh-my-pi/pi-utils": "18.14.
|
|
45
|
+
"@oh-my-pi/pi-ai": "18.14.2",
|
|
46
|
+
"@oh-my-pi/pi-catalog": "18.14.2",
|
|
47
|
+
"@oh-my-pi/pi-natives": "18.14.2",
|
|
48
|
+
"@oh-my-pi/pi-utils": "18.14.2"
|
|
49
49
|
},
|
|
50
50
|
"peerDependencies": {
|
|
51
51
|
"fastembed": "3.0.0",
|
|
@@ -108,6 +108,37 @@ const MAX_FACTS_PER_MEMORY = 5;
|
|
|
108
108
|
const DEFAULT_LINK_THRESHOLD = 0.35;
|
|
109
109
|
const LIVE_MEMORY_SQL = "superseded_by IS NULL AND (valid_until IS NULL OR valid_until > ?)";
|
|
110
110
|
|
|
111
|
+
/** Clause connector that ends a fact object, so the next clause is never swallowed into it. */
|
|
112
|
+
const CLAUSE_BREAK = String.raw`(?:and|but|or|nor|yet|then|because|so|while|which|that|who|whom|whose|when|where|if|since|although|though|unless|until|whereas)\b`;
|
|
113
|
+
/** Optional leading article; must be a whole word, so `active` never loses its `a` and `an` never splits. */
|
|
114
|
+
const ARTICLE = String.raw`(?:(?:an?|the)\s+)?`;
|
|
115
|
+
/** Fact subject: from the first capitalized word up to the predicate. */
|
|
116
|
+
const SUBJECT = String.raw`\b([A-Z][a-zA-Z\s]+?)`;
|
|
117
|
+
/**
|
|
118
|
+
* Start of another extractable clause (`Bob is …`, `Bob Jones is …`, `the runtime has …`). Catches clauses
|
|
119
|
+
* joined by a connector missing from {@link CLAUSE_BREAK}, so their whole subject is left for the next match.
|
|
120
|
+
*/
|
|
121
|
+
const NEXT_CLAUSE = String.raw`${ARTICLE}(?:[A-Z][a-zA-Z\d]*[ \t]+)*[a-zA-Z\d]+\s+(?:is|has|uses?|using|used|works?)\b`;
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Captures a whole phrase of `word`s up to punctuation, a line break, a clause connector, or the
|
|
125
|
+
* subject of another clause. Connectors may start an object (for example, `that permission`) but
|
|
126
|
+
* terminate it after the first token.
|
|
127
|
+
* Each word must end on a token boundary; a phrase whose first word does not yields no match.
|
|
128
|
+
*/
|
|
129
|
+
function phrase(word: string): string {
|
|
130
|
+
const token = String.raw`${word}(?:['-]${word})*\b`;
|
|
131
|
+
return String.raw`(${token}(?:[ \t]+(?!${CLAUSE_BREAK}|${NEXT_CLAUSE})${token})*)`;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const IS_FACT = new RegExp(String.raw`${SUBJECT}\s+is\s+${ARTICLE}${phrase("[a-zA-Z]+")}`, "g");
|
|
135
|
+
const HAS_FACT = new RegExp(String.raw`${SUBJECT}\s+has\s+${ARTICLE}${phrase(String.raw`[a-zA-Z\d]+`)}`, "g");
|
|
136
|
+
const USES_FACT = new RegExp(String.raw`${SUBJECT}\s+(?:uses?|using|used)\s+${ARTICLE}${phrase("[a-zA-Z]+")}`, "g");
|
|
137
|
+
const WORKS_AT_FACT = new RegExp(
|
|
138
|
+
String.raw`${SUBJECT}\s+works?\s+(?:at|for|with)\s+([A-Z][a-zA-Z]*(?:[ \t]+[A-Z][a-zA-Z]*)*)\b`,
|
|
139
|
+
"g",
|
|
140
|
+
);
|
|
141
|
+
|
|
111
142
|
function nowIso(): string {
|
|
112
143
|
return new Date().toISOString();
|
|
113
144
|
}
|
|
@@ -363,22 +394,10 @@ export class EpisodicGraph {
|
|
|
363
394
|
});
|
|
364
395
|
};
|
|
365
396
|
|
|
366
|
-
for (const match of bounded.matchAll(
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
for (const match of bounded.matchAll(
|
|
370
|
-
pushFact(match[1] ?? "", "has", match[2] ?? "", 0.6);
|
|
371
|
-
}
|
|
372
|
-
for (const match of bounded.matchAll(
|
|
373
|
-
/\b([A-Z][a-zA-Z\s]+?)\s+(uses?|using|used)\s+(?:a|an|the)?\s*([a-zA-Z\s]+?)\b/g,
|
|
374
|
-
)) {
|
|
375
|
-
pushFact(match[1] ?? "", "uses", match[3] ?? "", 0.6);
|
|
376
|
-
}
|
|
377
|
-
for (const match of bounded.matchAll(
|
|
378
|
-
/\b([A-Z][a-zA-Z\s]+?)\s+works?\s+(?:at|for|with)\s+([A-Z][a-zA-Z\s]+?)\b/g,
|
|
379
|
-
)) {
|
|
380
|
-
pushFact(match[1] ?? "", "works_at", match[2] ?? "", 0.7);
|
|
381
|
-
}
|
|
397
|
+
for (const match of bounded.matchAll(IS_FACT)) pushFact(match[1] ?? "", "is", match[2] ?? "", 0.7);
|
|
398
|
+
for (const match of bounded.matchAll(HAS_FACT)) pushFact(match[1] ?? "", "has", match[2] ?? "", 0.6);
|
|
399
|
+
for (const match of bounded.matchAll(USES_FACT)) pushFact(match[1] ?? "", "uses", match[2] ?? "", 0.6);
|
|
400
|
+
for (const match of bounded.matchAll(WORKS_AT_FACT)) pushFact(match[1] ?? "", "works_at", match[2] ?? "", 0.7);
|
|
382
401
|
return facts;
|
|
383
402
|
}
|
|
384
403
|
storeGist(gist: Gist, memoryId: string): void {
|