@pencil-agent/nano-pencil 1.4.2 → 1.4.4
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/dist/core/sdk.js +9 -3
- package/dist/core/soul-integration.d.ts +7 -3
- package/dist/core/soul-integration.js +49 -9
- package/dist/packages/nanomem/cli.d.ts +8 -0
- package/dist/packages/nanomem/cli.js +80 -0
- package/dist/packages/nanomem/config.d.ts +46 -0
- package/dist/packages/nanomem/config.js +48 -0
- package/dist/packages/nanomem/consolidation.d.ts +13 -0
- package/dist/packages/nanomem/consolidation.js +111 -0
- package/dist/packages/nanomem/engine.d.ts +66 -0
- package/dist/packages/nanomem/engine.js +486 -0
- package/dist/packages/nanomem/eviction.d.ts +16 -0
- package/dist/packages/nanomem/eviction.js +22 -0
- package/dist/packages/nanomem/extension.d.ts +11 -0
- package/dist/packages/nanomem/extension.js +215 -0
- package/dist/packages/nanomem/extraction.d.ts +10 -0
- package/dist/packages/nanomem/extraction.js +136 -0
- package/dist/packages/nanomem/i18n.d.ts +32 -0
- package/dist/packages/nanomem/i18n.js +113 -0
- package/dist/packages/nanomem/index.d.ts +17 -0
- package/dist/packages/nanomem/index.js +13 -0
- package/dist/packages/nanomem/insights-html.d.ts +8 -0
- package/dist/packages/nanomem/insights-html.js +443 -0
- package/dist/packages/nanomem/linking.d.ts +11 -0
- package/dist/packages/nanomem/linking.js +40 -0
- package/dist/packages/nanomem/privacy.d.ts +16 -0
- package/dist/packages/nanomem/privacy.js +52 -0
- package/dist/packages/nanomem/scoring.d.ts +25 -0
- package/dist/packages/nanomem/scoring.js +63 -0
- package/dist/packages/nanomem/store.d.ts +16 -0
- package/dist/packages/nanomem/store.js +68 -0
- package/dist/packages/nanomem/types.d.ts +126 -0
- package/dist/packages/nanomem/types.js +7 -0
- package/dist/packages/nanomem/update.d.ts +14 -0
- package/dist/packages/nanomem/update.js +126 -0
- package/dist/packages/nanosoul/config.d.ts +20 -0
- package/dist/packages/nanosoul/config.js +92 -0
- package/dist/packages/nanosoul/evolution.d.ts +73 -0
- package/dist/packages/nanosoul/evolution.js +246 -0
- package/dist/packages/nanosoul/index.d.ts +13 -0
- package/dist/packages/nanosoul/index.js +13 -0
- package/dist/packages/nanosoul/injection.d.ts +35 -0
- package/dist/packages/nanosoul/injection.js +298 -0
- package/dist/packages/nanosoul/manager.d.ts +101 -0
- package/dist/packages/nanosoul/manager.js +429 -0
- package/dist/packages/nanosoul/src/config.d.ts +20 -0
- package/dist/packages/nanosoul/src/config.js +92 -0
- package/dist/packages/nanosoul/src/evolution.d.ts +73 -0
- package/dist/packages/nanosoul/src/evolution.js +246 -0
- package/dist/packages/nanosoul/src/index.d.ts +13 -0
- package/dist/packages/nanosoul/src/index.js +13 -0
- package/dist/packages/nanosoul/src/injection.d.ts +35 -0
- package/dist/packages/nanosoul/src/injection.js +298 -0
- package/dist/packages/nanosoul/src/manager.d.ts +101 -0
- package/dist/packages/nanosoul/src/manager.js +429 -0
- package/dist/packages/nanosoul/src/store.d.ts +62 -0
- package/dist/packages/nanosoul/src/store.js +208 -0
- package/dist/packages/nanosoul/src/types.d.ts +317 -0
- package/dist/packages/nanosoul/src/types.js +7 -0
- package/dist/packages/nanosoul/store.d.ts +62 -0
- package/dist/packages/nanosoul/store.js +208 -0
- package/dist/packages/nanosoul/types.d.ts +317 -0
- package/dist/packages/nanosoul/types.js +7 -0
- package/package.json +7 -4
package/dist/core/sdk.js
CHANGED
|
@@ -252,9 +252,15 @@ export async function createAgentSession(options = {}) {
|
|
|
252
252
|
let soulManager;
|
|
253
253
|
if (isSoulEnabled(options)) {
|
|
254
254
|
try {
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
255
|
+
const soulMgr = await createSoulManager();
|
|
256
|
+
if (soulMgr) {
|
|
257
|
+
soulManager = soulMgr;
|
|
258
|
+
await soulManager.initialize();
|
|
259
|
+
time("soul.initialize");
|
|
260
|
+
}
|
|
261
|
+
else {
|
|
262
|
+
console.warn("Soul not available (nanosoul package not installed). Skipping...");
|
|
263
|
+
}
|
|
258
264
|
}
|
|
259
265
|
catch (error) {
|
|
260
266
|
console.warn(`Failed to initialize Soul: ${error}`);
|
|
@@ -4,8 +4,7 @@
|
|
|
4
4
|
* [POS]: Integration layer - bridges Soul and NanoPencil
|
|
5
5
|
*/
|
|
6
6
|
import type { CreateAgentSessionOptions } from "../core/sdk.js";
|
|
7
|
-
import type { InteractionContext } from "nanosoul";
|
|
8
|
-
import { SoulManager } from "nanosoul";
|
|
7
|
+
import type { InteractionContext, SoulManager } from "nanosoul";
|
|
9
8
|
/**
|
|
10
9
|
* Default Soul configuration for NanoPencil
|
|
11
10
|
*/
|
|
@@ -36,8 +35,13 @@ export declare function getSoulConfig(): {
|
|
|
36
35
|
};
|
|
37
36
|
/**
|
|
38
37
|
* Create a SoulManager instance for NanoPencil
|
|
38
|
+
* Returns null if nanosoul is not installed
|
|
39
39
|
*/
|
|
40
|
-
export declare function createSoulManager(): SoulManager
|
|
40
|
+
export declare function createSoulManager(): Promise<SoulManager | null>;
|
|
41
|
+
/**
|
|
42
|
+
* Check if Soul is available
|
|
43
|
+
*/
|
|
44
|
+
export declare function isSoulAvailable(): boolean;
|
|
41
45
|
/**
|
|
42
46
|
* Convert NanoPencil context to Soul InteractionContext
|
|
43
47
|
*/
|
|
@@ -3,9 +3,14 @@
|
|
|
3
3
|
* [OUTPUT]: Configured SoulManager with NanoPencil defaults
|
|
4
4
|
* [POS]: Integration layer - bridges Soul and NanoPencil
|
|
5
5
|
*/
|
|
6
|
-
import { SoulManager } from "nanosoul";
|
|
7
6
|
import { join } from "node:path";
|
|
8
7
|
import { getAgentDir } from "../config.js";
|
|
8
|
+
import { existsSync } from "node:fs";
|
|
9
|
+
import { fileURLToPath } from "node:url";
|
|
10
|
+
import { dirname } from "node:path";
|
|
11
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
// Try to load from bundled packages first (dist/packages/nanosoul)
|
|
13
|
+
const BUNDLED_SOUL = join(__dirname, "packages", "nanosoul");
|
|
9
14
|
/**
|
|
10
15
|
* Default Soul configuration for NanoPencil
|
|
11
16
|
*/
|
|
@@ -38,11 +43,44 @@ export function getSoulConfig() {
|
|
|
38
43
|
}
|
|
39
44
|
/**
|
|
40
45
|
* Create a SoulManager instance for NanoPencil
|
|
46
|
+
* Returns null if nanosoul is not installed
|
|
41
47
|
*/
|
|
42
|
-
export function createSoulManager() {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
48
|
+
export async function createSoulManager() {
|
|
49
|
+
try {
|
|
50
|
+
// Try bundled package first (dist/packages/nanosoul)
|
|
51
|
+
if (existsSync(BUNDLED_SOUL)) {
|
|
52
|
+
const { SoulManager: SM } = await import(join(BUNDLED_SOUL, "index.js"));
|
|
53
|
+
return new SM({
|
|
54
|
+
config: getSoulConfig(),
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
// Fall back to node_modules
|
|
58
|
+
const { SoulManager: SM } = await import("nanosoul");
|
|
59
|
+
return new SM({
|
|
60
|
+
config: getSoulConfig(),
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
// nanosoul not installed or failed to load
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Check if Soul is available
|
|
70
|
+
*/
|
|
71
|
+
export function isSoulAvailable() {
|
|
72
|
+
// Check bundled version first
|
|
73
|
+
if (existsSync(BUNDLED_SOUL)) {
|
|
74
|
+
return existsSync(join(BUNDLED_SOUL, "index.js"));
|
|
75
|
+
}
|
|
76
|
+
// Fall back to checking node_modules
|
|
77
|
+
try {
|
|
78
|
+
require.resolve("nanosoul");
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
46
84
|
}
|
|
47
85
|
/**
|
|
48
86
|
* Convert NanoPencil context to Soul InteractionContext
|
|
@@ -53,10 +91,12 @@ export function toSoulContext(project, tags, complexity, toolUsage, userFeedback
|
|
|
53
91
|
tags,
|
|
54
92
|
complexity,
|
|
55
93
|
toolUsage,
|
|
56
|
-
userFeedback: userFeedback
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
94
|
+
userFeedback: userFeedback
|
|
95
|
+
? {
|
|
96
|
+
rating: userFeedback.rating || 5,
|
|
97
|
+
comment: userFeedback.comment,
|
|
98
|
+
}
|
|
99
|
+
: undefined,
|
|
60
100
|
timestamp: new Date(),
|
|
61
101
|
};
|
|
62
102
|
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* [INPUT]: process.argv
|
|
4
|
+
* [OUTPUT]: stats | search <query> | forget <id> | export | insights — terminal output or JSON or HTML
|
|
5
|
+
* [POS]: Standalone CLI for NanoMem — no host dependency
|
|
6
|
+
*/
|
|
7
|
+
export {};
|
|
8
|
+
//# sourceMappingURL=cli.d.ts.map
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* [INPUT]: process.argv
|
|
4
|
+
* [OUTPUT]: stats | search <query> | forget <id> | export | insights — terminal output or JSON or HTML
|
|
5
|
+
* [POS]: Standalone CLI for NanoMem — no host dependency
|
|
6
|
+
*/
|
|
7
|
+
import { writeFileSync } from "node:fs";
|
|
8
|
+
import { NanoMemEngine } from "./engine.js";
|
|
9
|
+
import { renderInsightsHtml } from "./insights-html.js";
|
|
10
|
+
const args = process.argv.slice(2);
|
|
11
|
+
const sub = args[0];
|
|
12
|
+
const engine = new NanoMemEngine();
|
|
13
|
+
async function main() {
|
|
14
|
+
if (!sub || sub === "help" || sub === "-h" || sub === "--help") {
|
|
15
|
+
console.log(`nanomem — NanoMem memory CLI
|
|
16
|
+
|
|
17
|
+
Usage:
|
|
18
|
+
nanomem stats Show memory counts (sessions, knowledge, lessons, preferences, work, episodes, facets)
|
|
19
|
+
nanomem search <query> Search memories by query text
|
|
20
|
+
nanomem forget <id> Remove a memory entry by ID
|
|
21
|
+
nanomem export Export all memories as JSON to stdout
|
|
22
|
+
nanomem insights [--output <path>] Generate HTML insights report (default: ./nanomem-insights.html)
|
|
23
|
+
nanomem help Show this help
|
|
24
|
+
`);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
if (sub === "stats") {
|
|
28
|
+
const s = await engine.getStats();
|
|
29
|
+
console.log(`Sessions: ${s.totalSessions}`);
|
|
30
|
+
console.log(`Knowledge: ${s.knowledge}`);
|
|
31
|
+
console.log(`Lessons: ${s.lessons}`);
|
|
32
|
+
console.log(`Preferences: ${s.preferences}`);
|
|
33
|
+
console.log(`Work: ${s.work}`);
|
|
34
|
+
console.log(`Episodes: ${s.episodes}`);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
if (sub === "search") {
|
|
38
|
+
const query = args.slice(1).join(" ").trim() || " ";
|
|
39
|
+
const results = await engine.searchEntries(query);
|
|
40
|
+
if (!results.length) {
|
|
41
|
+
console.log("No matching memories.");
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
for (const e of results) {
|
|
45
|
+
console.log(`[${e.type}] ${e.id} — ${e.content.slice(0, 100)}`);
|
|
46
|
+
}
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
if (sub === "forget") {
|
|
50
|
+
const id = args[1];
|
|
51
|
+
if (!id) {
|
|
52
|
+
console.error("Usage: nanomem forget <id>");
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
const ok = await engine.forgetEntry(id);
|
|
56
|
+
console.log(ok ? `Removed entry ${id}` : `Entry ${id} not found`);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
if (sub === "export") {
|
|
60
|
+
const data = await engine.exportAll();
|
|
61
|
+
console.log(JSON.stringify(data, null, 2));
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
if (sub === "insights") {
|
|
65
|
+
const outputIdx = args.indexOf("--output");
|
|
66
|
+
const outputPath = outputIdx >= 0 && args[outputIdx + 1] ? args[outputIdx + 1] : "./nanomem-insights.html";
|
|
67
|
+
const report = await engine.generateInsights();
|
|
68
|
+
const html = renderInsightsHtml(report, engine.cfg.locale);
|
|
69
|
+
writeFileSync(outputPath, html, "utf-8");
|
|
70
|
+
console.log(`Insights report written to: ${outputPath}`);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
console.error(`Unknown command: ${sub}. Run 'nanomem help' for usage.`);
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
main().catch((err) => {
|
|
77
|
+
console.error(err);
|
|
78
|
+
process.exit(1);
|
|
79
|
+
});
|
|
80
|
+
//# sourceMappingURL=cli.js.map
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* [INPUT]: process.env, optional overrides
|
|
3
|
+
* [OUTPUT]: NanomemConfig — memory dir, token budget, scoring weights, etc.
|
|
4
|
+
* [POS]: Shared by engine and adapters; host products configure via this
|
|
5
|
+
*/
|
|
6
|
+
import type { MemoryScope } from "./types.js";
|
|
7
|
+
export interface NanomemConfig {
|
|
8
|
+
memoryDir: string;
|
|
9
|
+
tokenBudget: number;
|
|
10
|
+
budget: {
|
|
11
|
+
lessons: number;
|
|
12
|
+
knowledge: number;
|
|
13
|
+
episodes: number;
|
|
14
|
+
preferences: number;
|
|
15
|
+
work: number;
|
|
16
|
+
facets: number;
|
|
17
|
+
};
|
|
18
|
+
halfLife: Record<string, number>;
|
|
19
|
+
maxEntries: {
|
|
20
|
+
knowledge: number;
|
|
21
|
+
lessons: number;
|
|
22
|
+
preferences: number;
|
|
23
|
+
work: number;
|
|
24
|
+
facets: number;
|
|
25
|
+
};
|
|
26
|
+
consolidationThreshold: number;
|
|
27
|
+
/** Stanford-style retrieval scoring weights */
|
|
28
|
+
scoreWeights: {
|
|
29
|
+
recency: number;
|
|
30
|
+
importance: number;
|
|
31
|
+
relevance: number;
|
|
32
|
+
};
|
|
33
|
+
/** Utility-weighted eviction: access frequency vs base impact */
|
|
34
|
+
evictionWeights: {
|
|
35
|
+
accessFrequency: number;
|
|
36
|
+
baseImpact: number;
|
|
37
|
+
};
|
|
38
|
+
/** Default scope for all operations */
|
|
39
|
+
defaultScope?: MemoryScope;
|
|
40
|
+
/** Locale for LLM prompts and injection templates */
|
|
41
|
+
locale: "en" | "zh";
|
|
42
|
+
/** Strength growth factor on each successful recall (spaced repetition) */
|
|
43
|
+
strengthGrowthFactor: number;
|
|
44
|
+
}
|
|
45
|
+
export declare function getConfig(overrides?: Partial<NanomemConfig>): NanomemConfig;
|
|
46
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* [INPUT]: process.env, optional overrides
|
|
3
|
+
* [OUTPUT]: NanomemConfig — memory dir, token budget, scoring weights, etc.
|
|
4
|
+
* [POS]: Shared by engine and adapters; host products configure via this
|
|
5
|
+
*/
|
|
6
|
+
import { homedir } from "node:os";
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
const DEFAULT_BUDGET = {
|
|
9
|
+
lessons: 0.2,
|
|
10
|
+
knowledge: 0.2,
|
|
11
|
+
episodes: 0.18,
|
|
12
|
+
preferences: 0.1,
|
|
13
|
+
work: 0.2,
|
|
14
|
+
facets: 0.12,
|
|
15
|
+
};
|
|
16
|
+
const DEFAULT_HALF_LIFE = {
|
|
17
|
+
lesson: 90,
|
|
18
|
+
fact: 60,
|
|
19
|
+
episode: 14,
|
|
20
|
+
preference: 120,
|
|
21
|
+
decision: 45,
|
|
22
|
+
entity: 30,
|
|
23
|
+
work: 45,
|
|
24
|
+
pattern: 180,
|
|
25
|
+
struggle: 120,
|
|
26
|
+
};
|
|
27
|
+
const DEFAULT_MAX_ENTRIES = { knowledge: 200, lessons: 100, preferences: 50, work: 80, facets: 80 };
|
|
28
|
+
const DEFAULT_SCORE_WEIGHTS = { recency: 1, importance: 1, relevance: 1 };
|
|
29
|
+
const DEFAULT_EVICTION_WEIGHTS = { accessFrequency: 0.4, baseImpact: 0.6 };
|
|
30
|
+
export function getConfig(overrides) {
|
|
31
|
+
const tokenBudget = Number(process.env.NANOMEM_TOKEN_BUDGET) || 6000;
|
|
32
|
+
const memoryDir = process.env.NANOMEM_MEMORY_DIR || overrides?.memoryDir || join(homedir(), ".nanomem", "memory");
|
|
33
|
+
const locale = process.env.NANOMEM_LOCALE || overrides?.locale || "en";
|
|
34
|
+
return {
|
|
35
|
+
memoryDir,
|
|
36
|
+
tokenBudget: overrides?.tokenBudget ?? tokenBudget,
|
|
37
|
+
budget: overrides?.budget ?? { ...DEFAULT_BUDGET },
|
|
38
|
+
halfLife: overrides?.halfLife ?? { ...DEFAULT_HALF_LIFE },
|
|
39
|
+
maxEntries: overrides?.maxEntries ?? { ...DEFAULT_MAX_ENTRIES },
|
|
40
|
+
consolidationThreshold: overrides?.consolidationThreshold ?? 10,
|
|
41
|
+
scoreWeights: overrides?.scoreWeights ?? { ...DEFAULT_SCORE_WEIGHTS },
|
|
42
|
+
evictionWeights: overrides?.evictionWeights ?? { ...DEFAULT_EVICTION_WEIGHTS },
|
|
43
|
+
defaultScope: overrides?.defaultScope,
|
|
44
|
+
locale,
|
|
45
|
+
strengthGrowthFactor: overrides?.strengthGrowthFactor ?? 1.5,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* [INPUT]: unconsolidated episodes, LlmFn (optional), config
|
|
3
|
+
* [OUTPUT]: newly extracted MemoryEntries (facts + lessons) promoted to long-term storage
|
|
4
|
+
* [POS]: Episodic→Semantic consolidation — heart of multi-store memory model
|
|
5
|
+
*
|
|
6
|
+
* Two modes:
|
|
7
|
+
* LLM-powered (preferred): produces high-quality semantic extraction
|
|
8
|
+
* Heuristic fallback: frequency-based file/error extraction
|
|
9
|
+
*/
|
|
10
|
+
import type { NanomemConfig } from "./config.js";
|
|
11
|
+
import type { Episode, LlmFn, MemoryEntry } from "./types.js";
|
|
12
|
+
export declare function consolidateEpisodes(episodes: Episode[], cfg: NanomemConfig, llmFn?: LlmFn): Promise<MemoryEntry[]>;
|
|
13
|
+
//# sourceMappingURL=consolidation.d.ts.map
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* [INPUT]: unconsolidated episodes, LlmFn (optional), config
|
|
3
|
+
* [OUTPUT]: newly extracted MemoryEntries (facts + lessons) promoted to long-term storage
|
|
4
|
+
* [POS]: Episodic→Semantic consolidation — heart of multi-store memory model
|
|
5
|
+
*
|
|
6
|
+
* Two modes:
|
|
7
|
+
* LLM-powered (preferred): produces high-quality semantic extraction
|
|
8
|
+
* Heuristic fallback: frequency-based file/error extraction
|
|
9
|
+
*/
|
|
10
|
+
import { PROMPTS } from "./i18n.js";
|
|
11
|
+
import { extractTags } from "./scoring.js";
|
|
12
|
+
function makeId() {
|
|
13
|
+
return `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
14
|
+
}
|
|
15
|
+
export async function consolidateEpisodes(episodes, cfg, llmFn) {
|
|
16
|
+
const unconsolidated = episodes.filter((ep) => !ep.consolidated);
|
|
17
|
+
if (unconsolidated.length < cfg.consolidationThreshold)
|
|
18
|
+
return [];
|
|
19
|
+
let newEntries;
|
|
20
|
+
if (llmFn) {
|
|
21
|
+
newEntries = await llmConsolidation(unconsolidated, cfg, llmFn);
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
newEntries = heuristicConsolidation(unconsolidated, cfg);
|
|
25
|
+
}
|
|
26
|
+
for (const ep of unconsolidated)
|
|
27
|
+
ep.consolidated = true;
|
|
28
|
+
return newEntries;
|
|
29
|
+
}
|
|
30
|
+
async function llmConsolidation(episodes, cfg, llmFn) {
|
|
31
|
+
const p = PROMPTS[cfg.locale] ?? PROMPTS.en;
|
|
32
|
+
const summary = episodes
|
|
33
|
+
.map((ep) => `[${ep.date}] ${ep.project}: ${ep.summary}\nFiles: ${ep.filesModified.join(", ")}\nErrors: ${ep.errors.join("; ") || "none"}`)
|
|
34
|
+
.join("\n\n");
|
|
35
|
+
const raw = await llmFn(p.consolidationSystem, summary);
|
|
36
|
+
try {
|
|
37
|
+
const items = JSON.parse(raw);
|
|
38
|
+
const now = new Date().toISOString();
|
|
39
|
+
return items.map((item) => {
|
|
40
|
+
const type = item.type === "lesson" ? "lesson" : "fact";
|
|
41
|
+
return {
|
|
42
|
+
id: makeId(),
|
|
43
|
+
type,
|
|
44
|
+
content: item.content,
|
|
45
|
+
tags: extractTags(item.content),
|
|
46
|
+
project: episodes[0]?.project ?? "unknown",
|
|
47
|
+
importance: item.importance ?? 6,
|
|
48
|
+
strength: cfg.halfLife[type] ?? 30,
|
|
49
|
+
created: now,
|
|
50
|
+
eventTime: now,
|
|
51
|
+
accessCount: 0,
|
|
52
|
+
relatedIds: [],
|
|
53
|
+
scope: cfg.defaultScope,
|
|
54
|
+
};
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return heuristicConsolidation(episodes, cfg);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function heuristicConsolidation(episodes, cfg) {
|
|
62
|
+
const now = new Date().toISOString();
|
|
63
|
+
const result = [];
|
|
64
|
+
const fileCounts = new Map();
|
|
65
|
+
for (const ep of episodes) {
|
|
66
|
+
for (const f of ep.filesModified)
|
|
67
|
+
fileCounts.set(f, (fileCounts.get(f) ?? 0) + 1);
|
|
68
|
+
}
|
|
69
|
+
const hotFiles = [...fileCounts.entries()]
|
|
70
|
+
.filter(([, c]) => c >= 3)
|
|
71
|
+
.sort((a, b) => b[1] - a[1])
|
|
72
|
+
.slice(0, 5);
|
|
73
|
+
if (hotFiles.length) {
|
|
74
|
+
const content = `Frequently modified files: ${hotFiles.map(([f, c]) => `${f} (${c}x)`).join(", ")}`;
|
|
75
|
+
result.push({
|
|
76
|
+
id: makeId(),
|
|
77
|
+
type: "fact",
|
|
78
|
+
content,
|
|
79
|
+
tags: extractTags(content),
|
|
80
|
+
project: episodes[0]?.project ?? "unknown",
|
|
81
|
+
importance: 5,
|
|
82
|
+
strength: cfg.halfLife.fact ?? 60,
|
|
83
|
+
created: now,
|
|
84
|
+
eventTime: now,
|
|
85
|
+
accessCount: 0,
|
|
86
|
+
relatedIds: [],
|
|
87
|
+
scope: cfg.defaultScope,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
const allErrors = episodes.flatMap((ep) => ep.errors).filter(Boolean);
|
|
91
|
+
if (allErrors.length) {
|
|
92
|
+
const errorSet = [...new Set(allErrors)].slice(0, 5);
|
|
93
|
+
const content = `Recurring issues: ${errorSet.join("; ")}`;
|
|
94
|
+
result.push({
|
|
95
|
+
id: makeId(),
|
|
96
|
+
type: "lesson",
|
|
97
|
+
content,
|
|
98
|
+
tags: extractTags(content),
|
|
99
|
+
project: episodes[0]?.project ?? "unknown",
|
|
100
|
+
importance: 7,
|
|
101
|
+
strength: cfg.halfLife.lesson ?? 90,
|
|
102
|
+
created: now,
|
|
103
|
+
eventTime: now,
|
|
104
|
+
accessCount: 0,
|
|
105
|
+
relatedIds: [],
|
|
106
|
+
scope: cfg.defaultScope,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
return result;
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=consolidation.js.map
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* [INPUT]: NanomemConfig, optional LlmFn
|
|
3
|
+
* [OUTPUT]: NanoMemEngine — unified API for memory CRUD, injection, consolidation
|
|
4
|
+
* [POS]: Facade layer — composes store, scoring, eviction, update, linking, privacy, extraction, consolidation
|
|
5
|
+
*
|
|
6
|
+
* Host products create an engine instance and call its methods.
|
|
7
|
+
* No dependency on any specific AI framework — LLM is pluggable.
|
|
8
|
+
*/
|
|
9
|
+
import { type NanomemConfig } from "./config.js";
|
|
10
|
+
import type { Episode, ExtractedItem, InsightsReport, LlmFn, MemoryEntry, MemoryScope, Meta, WorkEntry } from "./types.js";
|
|
11
|
+
export declare class NanoMemEngine {
|
|
12
|
+
readonly cfg: NanomemConfig;
|
|
13
|
+
private llmFn?;
|
|
14
|
+
private knowledgePath;
|
|
15
|
+
private lessonsPath;
|
|
16
|
+
private preferencesPath;
|
|
17
|
+
private facetsPath;
|
|
18
|
+
private workPath;
|
|
19
|
+
private metaPath;
|
|
20
|
+
private episodesDir;
|
|
21
|
+
constructor(overrides?: Partial<NanomemConfig>, llmFn?: LlmFn);
|
|
22
|
+
setLlmFn(fn: LlmFn): void;
|
|
23
|
+
extractAndStore(conversation: string, project: string): Promise<ExtractedItem[]>;
|
|
24
|
+
extractAndStoreWork(conversation: string, project: string, sessionGoal?: string): Promise<void>;
|
|
25
|
+
saveEpisode(ep: Episode): Promise<void>;
|
|
26
|
+
consolidate(): Promise<MemoryEntry[]>;
|
|
27
|
+
getMemoryInjection(project: string, contextTags: string[], scope?: MemoryScope): Promise<string>;
|
|
28
|
+
getStats(): Promise<{
|
|
29
|
+
knowledge: number;
|
|
30
|
+
lessons: number;
|
|
31
|
+
preferences: number;
|
|
32
|
+
facets: number;
|
|
33
|
+
episodes: number;
|
|
34
|
+
work: number;
|
|
35
|
+
totalSessions: number;
|
|
36
|
+
}>;
|
|
37
|
+
getAllEntries(): Promise<{
|
|
38
|
+
knowledge: MemoryEntry[];
|
|
39
|
+
lessons: MemoryEntry[];
|
|
40
|
+
preferences: MemoryEntry[];
|
|
41
|
+
facets: MemoryEntry[];
|
|
42
|
+
}>;
|
|
43
|
+
getAllWork(): Promise<WorkEntry[]>;
|
|
44
|
+
getAllEpisodes(): Promise<Episode[]>;
|
|
45
|
+
searchEntries(query: string, scope?: MemoryScope): Promise<MemoryEntry[]>;
|
|
46
|
+
forgetEntry(id: string): Promise<boolean>;
|
|
47
|
+
exportAll(): Promise<{
|
|
48
|
+
knowledge: MemoryEntry[];
|
|
49
|
+
lessons: MemoryEntry[];
|
|
50
|
+
preferences: MemoryEntry[];
|
|
51
|
+
facets: MemoryEntry[];
|
|
52
|
+
work: WorkEntry[];
|
|
53
|
+
episodes: Episode[];
|
|
54
|
+
meta: Meta;
|
|
55
|
+
}>;
|
|
56
|
+
generateInsights(): Promise<InsightsReport>;
|
|
57
|
+
private generateRecommendations;
|
|
58
|
+
private generateRulesBasedRecommendations;
|
|
59
|
+
private filterAndCleanEntries;
|
|
60
|
+
private filterAndCleanWork;
|
|
61
|
+
private reinforceEntries;
|
|
62
|
+
private reinforceWork;
|
|
63
|
+
private reconsolidateIfNeeded;
|
|
64
|
+
private buildInjectionText;
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=engine.d.ts.map
|