@pencil-agent/nano-pencil 1.4.3 → 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.
Files changed (62) hide show
  1. package/dist/core/soul-integration.js +19 -5
  2. package/dist/packages/nanomem/cli.d.ts +8 -0
  3. package/dist/packages/nanomem/cli.js +80 -0
  4. package/dist/packages/nanomem/config.d.ts +46 -0
  5. package/dist/packages/nanomem/config.js +48 -0
  6. package/dist/packages/nanomem/consolidation.d.ts +13 -0
  7. package/dist/packages/nanomem/consolidation.js +111 -0
  8. package/dist/packages/nanomem/engine.d.ts +66 -0
  9. package/dist/packages/nanomem/engine.js +486 -0
  10. package/dist/packages/nanomem/eviction.d.ts +16 -0
  11. package/dist/packages/nanomem/eviction.js +22 -0
  12. package/dist/packages/nanomem/extension.d.ts +11 -0
  13. package/dist/packages/nanomem/extension.js +215 -0
  14. package/dist/packages/nanomem/extraction.d.ts +10 -0
  15. package/dist/packages/nanomem/extraction.js +136 -0
  16. package/dist/packages/nanomem/i18n.d.ts +32 -0
  17. package/dist/packages/nanomem/i18n.js +113 -0
  18. package/dist/packages/nanomem/index.d.ts +17 -0
  19. package/dist/packages/nanomem/index.js +13 -0
  20. package/dist/packages/nanomem/insights-html.d.ts +8 -0
  21. package/dist/packages/nanomem/insights-html.js +443 -0
  22. package/dist/packages/nanomem/linking.d.ts +11 -0
  23. package/dist/packages/nanomem/linking.js +40 -0
  24. package/dist/packages/nanomem/privacy.d.ts +16 -0
  25. package/dist/packages/nanomem/privacy.js +52 -0
  26. package/dist/packages/nanomem/scoring.d.ts +25 -0
  27. package/dist/packages/nanomem/scoring.js +63 -0
  28. package/dist/packages/nanomem/store.d.ts +16 -0
  29. package/dist/packages/nanomem/store.js +68 -0
  30. package/dist/packages/nanomem/types.d.ts +126 -0
  31. package/dist/packages/nanomem/types.js +7 -0
  32. package/dist/packages/nanomem/update.d.ts +14 -0
  33. package/dist/packages/nanomem/update.js +126 -0
  34. package/dist/packages/nanosoul/config.d.ts +20 -0
  35. package/dist/packages/nanosoul/config.js +92 -0
  36. package/dist/packages/nanosoul/evolution.d.ts +73 -0
  37. package/dist/packages/nanosoul/evolution.js +246 -0
  38. package/dist/packages/nanosoul/index.d.ts +13 -0
  39. package/dist/packages/nanosoul/index.js +13 -0
  40. package/dist/packages/nanosoul/injection.d.ts +35 -0
  41. package/dist/packages/nanosoul/injection.js +298 -0
  42. package/dist/packages/nanosoul/manager.d.ts +101 -0
  43. package/dist/packages/nanosoul/manager.js +429 -0
  44. package/dist/packages/nanosoul/src/config.d.ts +20 -0
  45. package/dist/packages/nanosoul/src/config.js +92 -0
  46. package/dist/packages/nanosoul/src/evolution.d.ts +73 -0
  47. package/dist/packages/nanosoul/src/evolution.js +246 -0
  48. package/dist/packages/nanosoul/src/index.d.ts +13 -0
  49. package/dist/packages/nanosoul/src/index.js +13 -0
  50. package/dist/packages/nanosoul/src/injection.d.ts +35 -0
  51. package/dist/packages/nanosoul/src/injection.js +298 -0
  52. package/dist/packages/nanosoul/src/manager.d.ts +101 -0
  53. package/dist/packages/nanosoul/src/manager.js +429 -0
  54. package/dist/packages/nanosoul/src/store.d.ts +62 -0
  55. package/dist/packages/nanosoul/src/store.js +208 -0
  56. package/dist/packages/nanosoul/src/types.d.ts +317 -0
  57. package/dist/packages/nanosoul/src/types.js +7 -0
  58. package/dist/packages/nanosoul/store.d.ts +62 -0
  59. package/dist/packages/nanosoul/store.js +208 -0
  60. package/dist/packages/nanosoul/types.d.ts +317 -0
  61. package/dist/packages/nanosoul/types.js +7 -0
  62. package/package.json +7 -2
@@ -5,8 +5,12 @@
5
5
  */
6
6
  import { join } from "node:path";
7
7
  import { getAgentDir } from "../config.js";
8
- // Dynamic import to make nanosoul optional
9
- let SoulManagerModule = null;
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");
10
14
  /**
11
15
  * Default Soul configuration for NanoPencil
12
16
  */
@@ -43,10 +47,15 @@ export function getSoulConfig() {
43
47
  */
44
48
  export async function createSoulManager() {
45
49
  try {
46
- if (!SoulManagerModule) {
47
- SoulManagerModule = await import("nanosoul");
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
+ });
48
56
  }
49
- const { SoulManager: SM } = SoulManagerModule;
57
+ // Fall back to node_modules
58
+ const { SoulManager: SM } = await import("nanosoul");
50
59
  return new SM({
51
60
  config: getSoulConfig(),
52
61
  });
@@ -60,6 +69,11 @@ export async function createSoulManager() {
60
69
  * Check if Soul is available
61
70
  */
62
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
63
77
  try {
64
78
  require.resolve("nanosoul");
65
79
  return true;
@@ -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