@linxiraos/pi-mnemopi 1.2.1 → 1.3.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/CHANGELOG.md +8 -0
- package/THIRD-PARTY-NOTICES.txt +55 -5
- package/dist/types/cli.d.ts +1 -0
- package/dist/types/config.d.ts +3 -0
- package/dist/types/mcp-tools.d.ts +2 -0
- package/package.json +6 -6
- package/src/cli.ts +11 -6
- package/src/config.ts +5 -2
- package/src/core/beam/recall.ts +46 -15
- package/src/core/polyphonic-recall.ts +9 -3
- package/src/mcp-tools.ts +12 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [1.3.0] - 2026-10-18
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- Fixed a failing `mnemopi` CLI command or MCP memory call dropping the embeddings of memories it had just stored ([#15804](https://github.com/can1357/oh-my-pi/pull/15804) by [@yuzu-octopus](https://github.com/yuzu-octopus))
|
|
8
|
+
- Fixed polyphonic recall ranking unrelated memories above relevant ones when the query shared only a common word such as "that" with an extracted fact subject ([#15512](https://github.com/can1357/oh-my-pi/issues/15512)).
|
|
9
|
+
- Fixed `MNEMOPI_<LABEL>_WEIGHT` and `MNEMOPI_TIER<N>_WEIGHT` having no effect on recall ranking ([#13824](https://github.com/can1357/oh-my-pi/pull/13824) by [@sjawhar](https://github.com/sjawhar))
|
|
10
|
+
|
|
3
11
|
## [1.2.0] - 2026-10-12
|
|
4
12
|
|
|
5
13
|
- Fixed proactive linking freezing the host for seconds per stored memory on large banks; a memory's graph links are now written in one commit instead of one per link ([#14998](https://github.com/can1357/oh-my-pi/issues/14998)).
|
package/THIRD-PARTY-NOTICES.txt
CHANGED
|
@@ -8,8 +8,8 @@ no rights in third-party code, fonts, data, or marks identified below.
|
|
|
8
8
|
PYTHON DEPENDENCY DISTRIBUTION ROLE
|
|
9
9
|
===================================
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
Uvicorn, HTTPX, Pydantic, pydantic-settings, python-dotenv, Click, and
|
|
11
|
+
zeta-rpc has no third-party runtime dependencies. robomp declares FastAPI,
|
|
12
|
+
Uvicorn, HTTPX, Pydantic, pydantic-settings, python-dotenv, Click, and zeta-rpc
|
|
13
13
|
as ordinary installer-resolved dependencies. The Python wheels do not vendor
|
|
14
14
|
those projects; each dependency remains a separately installed distribution
|
|
15
15
|
carrying its own license metadata and files. Container/system assemblers must
|
|
@@ -79,9 +79,9 @@ uutils coreutils (https://github.com/uutils/coreutils), MIT
|
|
|
79
79
|
--------------------------------------------------------------------------------
|
|
80
80
|
Covers: base32, base64, basename, cat, cksum (shared checksum machinery),
|
|
81
81
|
b2sum, md5sum, sha1sum, sha224sum, sha256sum, sha384sum, sha512sum, comm, cp,
|
|
82
|
-
cut, date, dirname, head, hostname, ln, ls, mkdir, mktemp, mv,
|
|
83
|
-
printenv, readlink, realpath, rm, seq, sort, stat, tac, tail, tee,
|
|
84
|
-
truncate, uname, uniq, wc, whoami, yes.
|
|
82
|
+
cut, date, dirname, head, hostname, ln, ls, mkdir, mktemp, mv, nl, nproc,
|
|
83
|
+
paste, printenv, readlink, realpath, rm, seq, sort, stat, tac, tail, tee,
|
|
84
|
+
touch, tr, truncate, uname, uniq, wc, whoami, yes.
|
|
85
85
|
|
|
86
86
|
Copyright (c) uutils developers
|
|
87
87
|
|
|
@@ -824,6 +824,56 @@ public licenses.
|
|
|
824
824
|
|
|
825
825
|
Creative Commons may be contacted at creativecommons.org.
|
|
826
826
|
|
|
827
|
+
-------------------------------------------------------------------------------
|
|
828
|
+
scripts/brand/fonts/ANSI Shadow.flf, scripts/brand/fonts/mini.flf
|
|
829
|
+
|
|
830
|
+
Two FIGlet font files from the standard FIGlet font distribution — the
|
|
831
|
+
figlet.org font set shipped by figlet.js
|
|
832
|
+
(https://github.com/patorjk/figlet.js/tree/master/fonts), fetched from
|
|
833
|
+
figlet.js 1.8.0 — vendored so the welcome/splash brand art
|
|
834
|
+
(`packages/tui/src/prompt/welcome.ts`) is generated reproducibly and offline
|
|
835
|
+
by `bun scripts/brand/generate-logo-art.ts`. Both files are unmodified and
|
|
836
|
+
retain their original FIGlet headers. They are build-time inputs only: no font
|
|
837
|
+
file or font data is compiled into, or shipped with, a Zeta release artifact.
|
|
838
|
+
The fonts' own headers carry their terms:
|
|
839
|
+
|
|
840
|
+
- `ANSI Shadow.flf` — "ANSI Shadow". Author unknown ("Font Author: ?" in the
|
|
841
|
+
header); a TheDraw-archive font converted with the FIGlet font editor
|
|
842
|
+
(http://patorjk.com/figfont-editor). Distributed under the FIGlet font
|
|
843
|
+
license as carried by the standard FIGlet font set: free to use and
|
|
844
|
+
redistribute with the header intact.
|
|
845
|
+
- `mini.flf` — "mini" by Glenn Chappell (4/93), from figlet release 2.1
|
|
846
|
+
(12 Aug 1994), "Includes ISO Latin-1"; modified by Paul Burton
|
|
847
|
+
<solution@earthlink.net> 12/96. Header grant: "Permission is hereby given to
|
|
848
|
+
modify this font, as long as the modifier's name is placed on a comment
|
|
849
|
+
line."
|
|
850
|
+
|
|
851
|
+
FIGlet renderer — `figlet` 1.8.0 (https://github.com/patorjk/figlet.js), a
|
|
852
|
+
root devDependency used only to generate the art at build time; it is not
|
|
853
|
+
imported by any runtime bundle or shipped artifact.
|
|
854
|
+
|
|
855
|
+
MIT License
|
|
856
|
+
|
|
857
|
+
Copyright (C) 2014-present Patrick Gillespie and other contributors
|
|
858
|
+
|
|
859
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
860
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
861
|
+
in the Software without restriction, including without limitation the rights
|
|
862
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
863
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
864
|
+
furnished to do so, subject to the following conditions:
|
|
865
|
+
|
|
866
|
+
The above copyright notice and this permission notice shall be included in
|
|
867
|
+
all copies or substantial portions of the Software.
|
|
868
|
+
|
|
869
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
870
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
871
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
872
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
873
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
874
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
875
|
+
SOFTWARE.
|
|
876
|
+
|
|
827
877
|
BUN / NPM DEPENDENCY INVENTORY
|
|
828
878
|
==============================
|
|
829
879
|
This inventory is traversed from bun.lock, not from the host node_modules tree.
|
package/dist/types/cli.d.ts
CHANGED
|
@@ -16,6 +16,7 @@ export declare class CliError extends Error {
|
|
|
16
16
|
constructor(message: string, exitCode?: number);
|
|
17
17
|
}
|
|
18
18
|
type CommandHandler = (args: readonly string[], context?: CliContext) => number | Promise<number>;
|
|
19
|
+
export declare function withMemory<T>(context: CliContext | undefined, fn: (memory: BeamMemory) => T | Promise<T>): Promise<T>;
|
|
19
20
|
export declare function memoryStats(memory: BeamMemory, dataDir?: string): Record<string, unknown>;
|
|
20
21
|
export declare const cmdExport: CommandHandler;
|
|
21
22
|
export declare const cmdImport: CommandHandler;
|
package/dist/types/config.d.ts
CHANGED
|
@@ -56,8 +56,11 @@ export declare function scratchpadMaxItems(env?: Env): number;
|
|
|
56
56
|
export declare function recencyHalflifeHours(env?: Env): number;
|
|
57
57
|
export declare function tier2Days(env?: Env): number;
|
|
58
58
|
export declare function tier3Days(env?: Env): number;
|
|
59
|
+
/** Recall-time score multiplier for an episodic row still at degradation tier 1. */
|
|
59
60
|
export declare function tier1Weight(env?: Env): number;
|
|
61
|
+
/** Recall-time score multiplier for an episodic row degraded to tier 2. */
|
|
60
62
|
export declare function tier2Weight(env?: Env): number;
|
|
63
|
+
/** Recall-time score multiplier for an episodic row degraded to tier 3 or beyond. */
|
|
61
64
|
export declare function tier3Weight(env?: Env): number;
|
|
62
65
|
export declare function degradeBatchSize(env?: Env): number;
|
|
63
66
|
export declare function smartCompressEnabled(env?: Env): boolean;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { BeamMemory } from "./core/beam/index.js";
|
|
1
2
|
export type JsonPrimitive = string | number | boolean | null;
|
|
2
3
|
export type JsonValue = JsonPrimitive | JsonValue[] | {
|
|
3
4
|
[key: string]: JsonValue;
|
|
@@ -480,5 +481,6 @@ export declare const GRAPH_LINK_SCHEMA: {
|
|
|
480
481
|
readonly required: readonly ["source_id", "target_id", "relationship"];
|
|
481
482
|
};
|
|
482
483
|
export declare const TOOLS: readonly ToolDefinition[];
|
|
484
|
+
export declare function withBeam<T>(args: ToolArguments, fn: (beam: BeamMemory, bank: string) => T | Promise<T>): Promise<T>;
|
|
483
485
|
export declare function handleToolCall(name: string, args?: ToolArguments): Promise<ToolResult>;
|
|
484
486
|
export declare function getToolDefinitions(): readonly ToolDefinition[];
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@linxiraos/pi-mnemopi",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.3.0",
|
|
5
5
|
"description": "Local SQLite memory engine for Zeta agents",
|
|
6
6
|
"homepage": "https://linxira-os.github.io/zeta/",
|
|
7
7
|
"author": "Linxira-OS",
|
|
@@ -32,17 +32,17 @@
|
|
|
32
32
|
},
|
|
33
33
|
"scripts": {
|
|
34
34
|
"check": "oxlint . && oxfmt --check --no-error-on-unmatched-pattern 'src/**/*.{ts,tsx}' '{test,bench,examples,scripts}/**/*.ts' '*.ts' && bun run check:types",
|
|
35
|
-
"check:types": "
|
|
35
|
+
"check:types": "tsc -p tsconfig.json --noEmit",
|
|
36
36
|
"lint": "oxlint .",
|
|
37
37
|
"test": "bun test --parallel",
|
|
38
38
|
"fix": "oxlint --fix --fix-suggestions . && bun run fmt",
|
|
39
39
|
"fmt": "oxfmt --no-error-on-unmatched-pattern 'src/**/*.{ts,tsx}' '{test,bench,examples,scripts}/**/*.ts' '*.ts'"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@linxiraos/pi-ai": "1.
|
|
43
|
-
"@linxiraos/pi-catalog": "1.
|
|
44
|
-
"@linxiraos/pi-natives": "1.
|
|
45
|
-
"@linxiraos/pi-utils": "1.
|
|
42
|
+
"@linxiraos/pi-ai": "1.3.0",
|
|
43
|
+
"@linxiraos/pi-catalog": "1.3.0",
|
|
44
|
+
"@linxiraos/pi-natives": "1.3.0",
|
|
45
|
+
"@linxiraos/pi-utils": "1.3.0"
|
|
46
46
|
},
|
|
47
47
|
"peerDependencies": {
|
|
48
48
|
"fastembed": "3.0.0",
|
package/src/cli.ts
CHANGED
|
@@ -77,17 +77,22 @@ function getMemory(context?: CliContext): { memory: BeamMemory; owned: boolean }
|
|
|
77
77
|
return { memory: new BeamMemory({ dbPath: resolveDbPath(context) }), owned: true };
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
async function withMemory<T>(
|
|
80
|
+
export async function withMemory<T>(
|
|
81
|
+
context: CliContext | undefined,
|
|
82
|
+
fn: (memory: BeamMemory) => T | Promise<T>,
|
|
83
|
+
): Promise<T> {
|
|
81
84
|
const { memory, owned } = getMemory(context);
|
|
82
85
|
try {
|
|
83
|
-
|
|
86
|
+
return await fn(memory);
|
|
87
|
+
} finally {
|
|
84
88
|
// Drain background fact-extraction and embedding tasks before close so
|
|
85
89
|
// short-lived owners (e.g. `mnemopi store …` / `mnemopi sleep …`) don't
|
|
86
90
|
// race the SQLite handle shut from under in-flight `embed()` writes.
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
+
// Runs on both success and throw paths.
|
|
92
|
+
if (owned) {
|
|
93
|
+
await memory.flushExtractions();
|
|
94
|
+
memory.close();
|
|
95
|
+
}
|
|
91
96
|
}
|
|
92
97
|
}
|
|
93
98
|
|
package/src/config.ts
CHANGED
|
@@ -170,16 +170,19 @@ export function tier3Days(env: Env = process.env): number {
|
|
|
170
170
|
return envInt("MNEMOPI_TIER3_DAYS", 180, env);
|
|
171
171
|
}
|
|
172
172
|
|
|
173
|
+
/** Recall-time score multiplier for an episodic row still at degradation tier 1. */
|
|
173
174
|
export function tier1Weight(env: Env = process.env): number {
|
|
174
175
|
return envFloat("MNEMOPI_TIER1_WEIGHT", 1.0, env);
|
|
175
176
|
}
|
|
176
177
|
|
|
178
|
+
/** Recall-time score multiplier for an episodic row degraded to tier 2. */
|
|
177
179
|
export function tier2Weight(env: Env = process.env): number {
|
|
178
|
-
return envFloat("MNEMOPI_TIER2_WEIGHT", 0.
|
|
180
|
+
return envFloat("MNEMOPI_TIER2_WEIGHT", 0.85, env);
|
|
179
181
|
}
|
|
180
182
|
|
|
183
|
+
/** Recall-time score multiplier for an episodic row degraded to tier 3 or beyond. */
|
|
181
184
|
export function tier3Weight(env: Env = process.env): number {
|
|
182
|
-
return envFloat("MNEMOPI_TIER3_WEIGHT", 0.
|
|
185
|
+
return envFloat("MNEMOPI_TIER3_WEIGHT", 0.7, env);
|
|
183
186
|
}
|
|
184
187
|
|
|
185
188
|
export function degradeBatchSize(env: Env = process.env): number {
|
package/src/core/beam/recall.ts
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
import { popLoopPhase, pushLoopPhase, withLoopPhase } from "@linxiraos/pi-utils";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
importedWeight,
|
|
4
|
+
inferredWeight,
|
|
5
|
+
normalizedRecallWeights,
|
|
6
|
+
polyphonicRecallEnabled,
|
|
7
|
+
statedWeight,
|
|
8
|
+
temporalHalflifeHours,
|
|
9
|
+
tier1Weight,
|
|
10
|
+
tier2Weight,
|
|
11
|
+
tier3Weight,
|
|
12
|
+
toolWeight,
|
|
13
|
+
unknownWeight,
|
|
14
|
+
} from "../../config";
|
|
3
15
|
import { hasCjk, matchesWordForm } from "../../util/regex";
|
|
4
16
|
import { embedQuery } from "../embeddings";
|
|
5
17
|
import { mmrRerank } from "../mmr";
|
|
@@ -63,16 +75,33 @@ type RecallMmrItem = {
|
|
|
63
75
|
readonly [key: string]: unknown;
|
|
64
76
|
};
|
|
65
77
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
78
|
+
/**
|
|
79
|
+
* Score multipliers for a candidate's stored `veracity` label and, for episodic
|
|
80
|
+
* rows, its degradation tier. Resolved once per {@link recall} from the
|
|
81
|
+
* `MNEMOPI_<LABEL>_WEIGHT` and `MNEMOPI_TIER<N>_WEIGHT` readers in `config.ts`,
|
|
82
|
+
* clamped at 0 like the hybrid weights so a negative value cannot rank a row
|
|
83
|
+
* below one labelled `false`; `true`, `likely_true` and `false` have no override.
|
|
84
|
+
*/
|
|
85
|
+
interface TrustWeights {
|
|
86
|
+
readonly veracity: Readonly<Record<string, number>> & { readonly unknown: number };
|
|
87
|
+
readonly tiers: readonly [tier1: number, tier2: number, tier3: number];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function resolveTrustWeights(): TrustWeights {
|
|
91
|
+
return {
|
|
92
|
+
veracity: {
|
|
93
|
+
stated: Math.max(0, statedWeight()),
|
|
94
|
+
true: 1.0,
|
|
95
|
+
likely_true: 1.0,
|
|
96
|
+
unknown: Math.max(0, unknownWeight()),
|
|
97
|
+
inferred: Math.max(0, inferredWeight()),
|
|
98
|
+
imported: Math.max(0, importedWeight()),
|
|
99
|
+
tool: Math.max(0, toolWeight()),
|
|
100
|
+
false: 0,
|
|
101
|
+
},
|
|
102
|
+
tiers: [Math.max(0, tier1Weight()), Math.max(0, tier2Weight()), Math.max(0, tier3Weight())],
|
|
103
|
+
};
|
|
104
|
+
}
|
|
76
105
|
|
|
77
106
|
/**
|
|
78
107
|
* Default per-result content preview cap enforced by {@link recall}. Content
|
|
@@ -671,6 +700,7 @@ function scoreCandidate(
|
|
|
671
700
|
queryTokens: readonly string[],
|
|
672
701
|
queryGroups: readonly (readonly string[])[],
|
|
673
702
|
weights: readonly [number, number, number],
|
|
703
|
+
trust: TrustWeights,
|
|
674
704
|
options: RecallOptionsInternal,
|
|
675
705
|
): RecallResult | null {
|
|
676
706
|
const content = asString(candidate.row.content);
|
|
@@ -714,11 +744,11 @@ function scoreCandidate(
|
|
|
714
744
|
score *= 1 + temporalWeight * temporalScore;
|
|
715
745
|
}
|
|
716
746
|
const veracity = asString(candidate.row.veracity) || "unknown";
|
|
717
|
-
const veracityWeight =
|
|
747
|
+
const veracityWeight = trust.veracity[veracity] ?? trust.veracity.unknown;
|
|
718
748
|
const degradationTier = candidate.tierLabel === "episodic" ? asNumber(candidate.row.tier, 1) : undefined;
|
|
719
749
|
if (candidate.tierLabel === "episodic") {
|
|
720
|
-
const
|
|
721
|
-
score *=
|
|
750
|
+
const [tier1, tier2, tier3] = trust.tiers;
|
|
751
|
+
score *= degradationTier === 1 ? tier1 : degradationTier === 2 ? tier2 : tier3;
|
|
722
752
|
}
|
|
723
753
|
score *= veracityWeight * currentContentAdjustment(searchableContent, options.currentSensitive === true);
|
|
724
754
|
const preview = clipRecallContent(content, options.contentPreviewChars ?? RECALL_CONTENT_PREVIEW_CHARS);
|
|
@@ -938,9 +968,10 @@ export async function recall(
|
|
|
938
968
|
const tokens = expandedTokens(query, useSynonyms);
|
|
939
969
|
const tokenGroups = expandedTokenGroups(query, useSynonyms);
|
|
940
970
|
const candidates = collectMemoryCandidates(beam, query, topK, temporalOptions);
|
|
971
|
+
const trust = resolveTrustWeights();
|
|
941
972
|
const scored: RecallResult[] = [];
|
|
942
973
|
for (const candidate of candidates) {
|
|
943
|
-
const result = scoreCandidate(candidate, tokens, tokenGroups, weights, temporalOptions);
|
|
974
|
+
const result = scoreCandidate(candidate, tokens, tokenGroups, weights, trust, temporalOptions);
|
|
944
975
|
if (result !== null) scored.push(result);
|
|
945
976
|
}
|
|
946
977
|
scored.sort((left, right) => (right.score ?? 0) - (left.score ?? 0));
|
|
@@ -5,6 +5,7 @@ import { closeQuietly, type DatabasePath, openDatabase } from "../db";
|
|
|
5
5
|
import { backfillConsolidatedFacts, ensureVeracityConsolidator } from "./beam/consolidate";
|
|
6
6
|
import type { BeamMemoryState, JsonValue, Metadata, RecallResult, RecallTierLabel } from "./beam/types";
|
|
7
7
|
import { EpisodicGraph } from "./episodic-graph";
|
|
8
|
+
import { STOP_WORDS } from "./synonyms";
|
|
8
9
|
import { VeracityConsolidator } from "./veracity-consolidation";
|
|
9
10
|
|
|
10
11
|
export type PolyphonicVoice = "hybrid" | "vector" | "graph" | "fact" | "temporal";
|
|
@@ -229,15 +230,20 @@ function queryTokens(query: string): string[] {
|
|
|
229
230
|
return tokens;
|
|
230
231
|
}
|
|
231
232
|
|
|
232
|
-
/**
|
|
233
|
+
/**
|
|
234
|
+
* Subject candidates for the fact voice: every 3+ char query word plus adjacent word pairs.
|
|
235
|
+
* Stopwords never anchor a subject on their own — the regex extractor stores sentence-initial
|
|
236
|
+
* words like `That` as subjects, so a bare `that` in the query would pull in unrelated memories.
|
|
237
|
+
*/
|
|
233
238
|
function factSubjectCandidates(query: string): string[] {
|
|
234
239
|
const tokens = queryTokens(query);
|
|
235
240
|
const seen = new Set<string>();
|
|
236
241
|
for (let i = 0; i < tokens.length; i++) {
|
|
237
242
|
const word = tokens[i] as string;
|
|
238
|
-
|
|
243
|
+
const isContent = !STOP_WORDS.has(word);
|
|
244
|
+
if (isContent && word.length >= 3) seen.add(word);
|
|
239
245
|
const next = tokens[i + 1];
|
|
240
|
-
if (next !== undefined) seen.add(`${word} ${next}`);
|
|
246
|
+
if (next !== undefined && (isContent || !STOP_WORDS.has(next))) seen.add(`${word} ${next}`);
|
|
241
247
|
}
|
|
242
248
|
return [...seen];
|
|
243
249
|
}
|
package/src/mcp-tools.ts
CHANGED
|
@@ -447,20 +447,24 @@ function sharedBeam(): BeamMemory {
|
|
|
447
447
|
return new BeamMemory({ sessionId: "mcp_shared_surface", dbPath });
|
|
448
448
|
}
|
|
449
449
|
|
|
450
|
-
async function withBeam<T>(
|
|
450
|
+
export async function withBeam<T>(
|
|
451
|
+
args: ToolArguments,
|
|
452
|
+
fn: (beam: BeamMemory, bank: string) => T | Promise<T>,
|
|
453
|
+
): Promise<T> {
|
|
451
454
|
const bank = resolveBank(args);
|
|
452
455
|
const beam = createBeam(args, bank);
|
|
456
|
+
let result: T;
|
|
453
457
|
try {
|
|
454
|
-
|
|
458
|
+
result = await fn(beam, bank);
|
|
459
|
+
} finally {
|
|
455
460
|
// Drain background fact-extraction and embedding tasks before close so
|
|
456
461
|
// the SQLite handle stays open until in-flight `embed()` writes commit;
|
|
457
462
|
// otherwise the short-lived MCP `remember`/`update`/`sleep` paths race
|
|
458
463
|
// the close and silently drop the new dense-recall rows.
|
|
459
464
|
await beam.flushExtractions();
|
|
460
|
-
return result;
|
|
461
|
-
} finally {
|
|
462
465
|
beam.close();
|
|
463
466
|
}
|
|
467
|
+
return result;
|
|
464
468
|
}
|
|
465
469
|
function serialize(value: unknown): unknown {
|
|
466
470
|
if (value instanceof Date) return value.toISOString();
|
|
@@ -739,13 +743,14 @@ function surfaceLabel(content: string, kind: string): string {
|
|
|
739
743
|
|
|
740
744
|
async function withSharedBeam<T>(fn: (beam: BeamMemory) => T | Promise<T>): Promise<T> {
|
|
741
745
|
const beam = sharedBeam();
|
|
746
|
+
let result: T;
|
|
742
747
|
try {
|
|
743
|
-
|
|
744
|
-
await beam.flushExtractions();
|
|
745
|
-
return result;
|
|
748
|
+
result = await fn(beam);
|
|
746
749
|
} finally {
|
|
750
|
+
await beam.flushExtractions();
|
|
747
751
|
beam.close();
|
|
748
752
|
}
|
|
753
|
+
return result;
|
|
749
754
|
}
|
|
750
755
|
|
|
751
756
|
async function handleSharedRemember(args: ToolArguments): Promise<ToolResult> {
|