@pencil-agent/nano-pencil 1.11.21 → 1.11.23

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.
@@ -16,6 +16,7 @@ const BUNDLED_SIMPLIFY_EXTENSION = join(__dirname, "extensions", "optional", "si
16
16
  const BUNDLED_LINK_WORLD_EXTENSION = join(__dirname, "extensions", "defaults", "link-world", "index.js");
17
17
  const BUNDLED_SECURITY_AUDIT_EXTENSION = join(__dirname, "extensions", "defaults", "security-audit", "index.js");
18
18
  const BUNDLED_SOUL_EXTENSION = join(__dirname, "extensions", "defaults", "soul", "index.js");
19
+ const BUNDLED_PRESENCE_EXTENSION = join(__dirname, "extensions", "defaults", "presence", "index.js");
19
20
  const BUNDLED_INTERVIEW_EXTENSION = join(__dirname, "extensions", "defaults", "interview", "index.js");
20
21
  const BUNDLED_LOOP_EXTENSION = join(__dirname, "extensions", "defaults", "loop", "index.js");
21
22
  const BUNDLED_TEAM_EXTENSION = join(__dirname, "extensions", "defaults", "team", "index.js");
@@ -119,6 +120,14 @@ export function getBuiltinExtensionPaths() {
119
120
  if (existsSync(soulTs))
120
121
  paths.push(soulTs);
121
122
  }
123
+ if (existsSync(BUNDLED_PRESENCE_EXTENSION)) {
124
+ paths.push(BUNDLED_PRESENCE_EXTENSION);
125
+ }
126
+ else {
127
+ const presenceTs = join(__dirname, "extensions", "defaults", "presence", "index.ts");
128
+ if (existsSync(presenceTs))
129
+ paths.push(presenceTs);
130
+ }
122
131
  // === Interview 扩展(需求澄清)===
123
132
  // Placed after Soul to ensure Interview probe sees both Mem + Soul style/systemPrompt injections.
124
133
  if (existsSync(BUNDLED_INTERVIEW_EXTENSION)) {
@@ -10,6 +10,7 @@ export declare const COMPACTION_SUMMARY_PREFIX = "The conversation history befor
10
10
  export declare const COMPACTION_SUMMARY_SUFFIX = "\n</summary>";
11
11
  export declare const BRANCH_SUMMARY_PREFIX = "The following is a summary of a branch that this conversation came back from:\n\n<summary>\n";
12
12
  export declare const BRANCH_SUMMARY_SUFFIX = "</summary>";
13
+ export declare const CUSTOM_MESSAGE_TYPES_EXCLUDED_FROM_CONTEXT: Set<string>;
13
14
  /**
14
15
  * Message type for bash executions via the ! command.
15
16
  */
@@ -9,6 +9,7 @@ export const BRANCH_SUMMARY_PREFIX = `The following is a summary of a branch tha
9
9
  <summary>
10
10
  `;
11
11
  export const BRANCH_SUMMARY_SUFFIX = `</summary>`;
12
+ export const CUSTOM_MESSAGE_TYPES_EXCLUDED_FROM_CONTEXT = new Set(["presence"]);
12
13
  /**
13
14
  * Convert a BashExecutionMessage to user message text for LLM context.
14
15
  */
@@ -81,6 +82,9 @@ export function convertToLlm(messages) {
81
82
  timestamp: m.timestamp,
82
83
  };
83
84
  case "custom": {
85
+ if (CUSTOM_MESSAGE_TYPES_EXCLUDED_FROM_CONTEXT.has(m.customType)) {
86
+ return undefined;
87
+ }
84
88
  const content = typeof m.content === "string" ? [{ type: "text", text: m.content }] : m.content;
85
89
  return {
86
90
  role: "user",
@@ -22,8 +22,12 @@ Usage:
22
22
  nanomem search-v2 <query> Semantic search across V2 episode/facet/procedure memory
23
23
  nanomem forget <id> Remove a memory entry by ID
24
24
  nanomem dedup Deduplicate all memories (merge similar entries, keep best)
25
+ nanomem archive Archive stale low-value memories into _archive/
26
+ nanomem restore <id> Restore one archived memory item by ID
25
27
  nanomem export Export all memories as JSON to stdout
26
28
  nanomem export-v2 Export NanoMem v2 episodic bridge data as JSON to stdout
29
+ nanomem export-archive Export archived memories as JSON to stdout
30
+ nanomem inspect-v2 Inspect V2 memory chains and conflict signals
27
31
  nanomem sync-v2-embeddings Sync the V2 embedding index
28
32
  nanomem insights [--output <path>] Generate full HTML insights report (default: ./nanomem-insights.html)
29
33
  nanomem insights --simple [--output <path>] Generate simple insights report (rules-only, no LLM)
@@ -38,11 +42,19 @@ Usage:
38
42
  console.log(`Lessons: ${s.lessons}`);
39
43
  console.log(`Preferences: ${s.preferences}`);
40
44
  console.log(`Work: ${s.work}`);
45
+ console.log(`Archived Knowledge: ${s.archivedKnowledge}`);
46
+ console.log(`Archived Lessons: ${s.archivedLessons}`);
47
+ console.log(`Archived Events: ${s.archivedEvents}`);
48
+ console.log(`Archived Preferences: ${s.archivedPreferences}`);
49
+ console.log(`Archived Facets: ${s.archivedFacets}`);
50
+ console.log(`Archived Work: ${s.archivedWork}`);
41
51
  console.log(`Episodes: ${s.episodes}`);
42
52
  console.log(`V2 Episodes: ${v2.episodes}`);
43
53
  console.log(`V2 Episode Facets: ${v2.facets}`);
44
54
  console.log(`V2 Semantic: ${v2.semantic}`);
45
55
  console.log(`V2 Procedures: ${v2.procedural}`);
56
+ console.log(`Archived V2 Semantic: ${v2.archivedSemantic}`);
57
+ console.log(`Archived V2 Procedures: ${v2.archivedProcedural}`);
46
58
  console.log(`V2 Links: ${v2.links}`);
47
59
  console.log(`V2 Embeddings: ${v2.embeddings}`);
48
60
  if (v2.lastEmbeddingSyncAt)
@@ -109,16 +121,88 @@ Usage:
109
121
  }
110
122
  return;
111
123
  }
124
+ if (sub === "archive") {
125
+ const result = await engine.archiveStaleMemories();
126
+ if (result.total === 0) {
127
+ console.log("No stale memories were archived.");
128
+ return;
129
+ }
130
+ console.log(`Archived ${result.total} stale memory item(s):`);
131
+ if (result.knowledge)
132
+ console.log(` knowledge: ${result.knowledge}`);
133
+ if (result.lessons)
134
+ console.log(` lessons: ${result.lessons}`);
135
+ if (result.events)
136
+ console.log(` events: ${result.events}`);
137
+ if (result.preferences)
138
+ console.log(` preferences: ${result.preferences}`);
139
+ if (result.facets)
140
+ console.log(` facets: ${result.facets}`);
141
+ if (result.work)
142
+ console.log(` work: ${result.work}`);
143
+ if (result.semantic)
144
+ console.log(` semantic: ${result.semantic}`);
145
+ if (result.procedural)
146
+ console.log(` procedural: ${result.procedural}`);
147
+ return;
148
+ }
149
+ if (sub === "restore") {
150
+ const id = args[1];
151
+ if (!id) {
152
+ console.error("Usage: nanomem restore <id>");
153
+ process.exit(1);
154
+ }
155
+ const result = await engine.restoreArchivedEntry(id);
156
+ console.log(result.ok ? `Restored archived ${result.location} entry ${id}` : `Archived entry ${id} not found`);
157
+ return;
158
+ }
112
159
  if (sub === "export") {
113
160
  const data = await engine.exportAll();
114
161
  console.log(JSON.stringify(data, null, 2));
115
162
  return;
116
163
  }
164
+ if (sub === "export-archive") {
165
+ const data = await engine.exportArchive();
166
+ console.log(JSON.stringify(data, null, 2));
167
+ return;
168
+ }
117
169
  if (sub === "export-v2") {
118
170
  const data = await engine.exportAllV2();
119
171
  console.log(JSON.stringify(data, null, 2));
120
172
  return;
121
173
  }
174
+ if (sub === "inspect-v2") {
175
+ const data = await engine.inspectV2Memory();
176
+ console.log(`Episodes: ${data.counts.episodes}`);
177
+ console.log(`Facets: ${data.counts.facets}`);
178
+ console.log(`Semantic: ${data.counts.semantic}`);
179
+ console.log(`Procedural: ${data.counts.procedural}`);
180
+ console.log(`Active Procedural: ${data.counts.activeProcedural}`);
181
+ console.log(`Superseded Procedural: ${data.counts.supersededProcedural}`);
182
+ console.log(`Procedure Chains: ${data.counts.procedureChains}`);
183
+ console.log(`Procedural Conflicts: ${data.counts.proceduralConflicts}`);
184
+ console.log(`Semantic Conflicts: ${data.counts.semanticConflicts}`);
185
+ if (data.procedureChains.length) {
186
+ console.log("\nProcedure Version Chains:");
187
+ for (const chain of data.procedureChains) {
188
+ console.log(`- ${chain.name} [${chain.status}] depth=${chain.versionDepth} root=${chain.rootId}`);
189
+ console.log(` ${chain.ids.join(" -> ")}`);
190
+ }
191
+ }
192
+ if (data.proceduralConflicts.length) {
193
+ console.log("\nProcedural Conflict Signals:");
194
+ for (const conflict of data.proceduralConflicts.slice(0, 20)) {
195
+ console.log(`- ${conflict.aName} (${conflict.aId}) <-> ${conflict.bName} (${conflict.bId}) score=${conflict.score} — ${conflict.reason}`);
196
+ }
197
+ }
198
+ if (data.semanticConflicts.length) {
199
+ console.log("\nSemantic Conflict Signals:");
200
+ for (const conflict of data.semanticConflicts.slice(0, 20)) {
201
+ console.log(`- ${conflict.aName} (${conflict.aId}) <-> ${conflict.bName} (${conflict.bId}) — ${conflict.reason}`);
202
+ }
203
+ }
204
+ return;
205
+ }
122
206
  if (sub === "sync-v2-embeddings") {
123
207
  const count = await engine.syncV2Embeddings();
124
208
  if (count === 0) {
@@ -70,6 +70,7 @@ export interface NanomemConfig {
70
70
  forgetting: {
71
71
  ambientTtlDays: number;
72
72
  workTtlDays: number;
73
+ reviveCooldownDays: number;
73
74
  };
74
75
  /** Embedding configuration for semantic episodic/procedural recall */
75
76
  embeddings: EmbeddingConfig;
@@ -41,6 +41,7 @@ const DEFAULT_PROGRESSIVE_RECALL = {
41
41
  const DEFAULT_FORGETTING = {
42
42
  ambientTtlDays: 45,
43
43
  workTtlDays: 21,
44
+ reviveCooldownDays: 7,
44
45
  };
45
46
  const DEFAULT_EMBEDDINGS = {
46
47
  enabled: true,
@@ -6,11 +6,13 @@
6
6
  */
7
7
  import { type NanomemConfig } from "./config.js";
8
8
  import type { DeveloperPersona, Episode, ExtractedItem, FullInsightsReport, HumanInsight, InsightsReport, AlignmentSnapshot, LlmFn, MemoryEntry, MemoryScope, Meta, RootCauseInsight, WorkEntry } from "./types.js";
9
- import type { EmbeddingFn, EpisodeFacet, EpisodeMemory, MemoryLink, ProceduralMemory } from "./types-v2.js";
9
+ import type { EmbeddingFn, EpisodeFacet, EpisodeMemory, MemoryLink, ProceduralMemory, SemanticMemory } from "./types-v2.js";
10
10
  export declare class NanoMemEngine {
11
11
  readonly cfg: NanomemConfig;
12
12
  private llmFn?;
13
13
  private embeddingFn?;
14
+ private static readonly AUTO_V2_LINK_PREFIX;
15
+ private static readonly AUTO_REVIVE_MAX_ITEMS;
14
16
  private knowledgePath;
15
17
  private lessonsPath;
16
18
  private eventsPath;
@@ -20,6 +22,13 @@ export declare class NanoMemEngine {
20
22
  private metaPath;
21
23
  private episodesDir;
22
24
  private v2Paths;
25
+ private archiveKnowledgePath;
26
+ private archiveLessonsPath;
27
+ private archiveEventsPath;
28
+ private archivePreferencesPath;
29
+ private archiveFacetsPath;
30
+ private archiveWorkPath;
31
+ private archiveV2Paths;
23
32
  constructor(overrides?: Partial<NanomemConfig>, llmFn?: LlmFn);
24
33
  setLlmFn(fn: LlmFn): void;
25
34
  setEmbeddingFn(fn: EmbeddingFn): void;
@@ -76,6 +85,12 @@ export declare class NanoMemEngine {
76
85
  facets: number;
77
86
  episodes: number;
78
87
  work: number;
88
+ archivedKnowledge: number;
89
+ archivedLessons: number;
90
+ archivedEvents: number;
91
+ archivedPreferences: number;
92
+ archivedFacets: number;
93
+ archivedWork: number;
79
94
  totalSessions: number;
80
95
  }>;
81
96
  getV2Stats(): Promise<{
@@ -83,6 +98,8 @@ export declare class NanoMemEngine {
83
98
  facets: number;
84
99
  semantic: number;
85
100
  procedural: number;
101
+ archivedSemantic: number;
102
+ archivedProcedural: number;
86
103
  links: number;
87
104
  embeddings: number;
88
105
  lastEmbeddingSyncAt?: string;
@@ -95,6 +112,12 @@ export declare class NanoMemEngine {
95
112
  preferences: MemoryEntry[];
96
113
  facets: MemoryEntry[];
97
114
  }>;
115
+ getRuntimeIdentityEntries(): Promise<{
116
+ knowledge: MemoryEntry[];
117
+ lessons: MemoryEntry[];
118
+ preferences: MemoryEntry[];
119
+ facets: MemoryEntry[];
120
+ }>;
98
121
  getAllWork(): Promise<WorkEntry[]>;
99
122
  getAllEpisodes(): Promise<Episode[]>;
100
123
  runStartupMaintenance(maintenanceVersion?: number): Promise<{
@@ -110,6 +133,17 @@ export declare class NanoMemEngine {
110
133
  total: number;
111
134
  };
112
135
  migratedEpisodesToV2: number;
136
+ archived: {
137
+ knowledge: number;
138
+ lessons: number;
139
+ events: number;
140
+ preferences: number;
141
+ facets: number;
142
+ work: number;
143
+ semantic: number;
144
+ procedural: number;
145
+ total: number;
146
+ };
113
147
  }>;
114
148
  private createMaintenanceBackup;
115
149
  private syncEpisodeToV2;
@@ -126,6 +160,14 @@ export declare class NanoMemEngine {
126
160
  total: number;
127
161
  }>;
128
162
  searchEntries(query: string, scope?: MemoryScope): Promise<MemoryEntry[]>;
163
+ autoReviveRelevantArchive(query: string, scope?: MemoryScope, options?: {
164
+ maxItems?: number;
165
+ legacyThreshold?: number;
166
+ v2Threshold?: number;
167
+ }): Promise<Array<{
168
+ id: string;
169
+ location: "knowledge" | "lessons" | "events" | "preferences" | "facets" | "semantic" | "procedural" | "work";
170
+ }>>;
129
171
  getAlignmentSnapshot(): Promise<AlignmentSnapshot>;
130
172
  forgetEntry(id: string): Promise<boolean>;
131
173
  exportAll(): Promise<{
@@ -138,6 +180,16 @@ export declare class NanoMemEngine {
138
180
  episodes: Episode[];
139
181
  meta: Meta;
140
182
  }>;
183
+ exportArchive(): Promise<{
184
+ knowledge: MemoryEntry[];
185
+ lessons: MemoryEntry[];
186
+ events: MemoryEntry[];
187
+ preferences: MemoryEntry[];
188
+ facets: MemoryEntry[];
189
+ work: WorkEntry[];
190
+ semantic: SemanticMemory[];
191
+ procedural: ProceduralMemory[];
192
+ }>;
141
193
  exportAllV2(): Promise<{
142
194
  episodes: EpisodeMemory[];
143
195
  facets: EpisodeFacet[];
@@ -145,6 +197,60 @@ export declare class NanoMemEngine {
145
197
  procedural: ProceduralMemory[];
146
198
  links: MemoryLink[];
147
199
  }>;
200
+ rebuildV2Links(): Promise<{
201
+ total: number;
202
+ auto: number;
203
+ }>;
204
+ archiveStaleMemories(now?: string): Promise<{
205
+ knowledge: number;
206
+ lessons: number;
207
+ events: number;
208
+ preferences: number;
209
+ facets: number;
210
+ work: number;
211
+ semantic: number;
212
+ procedural: number;
213
+ total: number;
214
+ }>;
215
+ restoreArchivedEntry(id: string): Promise<{
216
+ ok: boolean;
217
+ location?: "knowledge" | "lessons" | "events" | "preferences" | "facets" | "work" | "semantic" | "procedural";
218
+ }>;
219
+ inspectV2Memory(project?: string, scope?: MemoryScope): Promise<{
220
+ counts: {
221
+ episodes: number;
222
+ facets: number;
223
+ semantic: number;
224
+ procedural: number;
225
+ activeProcedural: number;
226
+ supersededProcedural: number;
227
+ semanticConflicts: number;
228
+ proceduralConflicts: number;
229
+ procedureChains: number;
230
+ };
231
+ procedureChains: Array<{
232
+ rootId: string;
233
+ name: string;
234
+ status: ProceduralMemory["status"];
235
+ versionDepth: number;
236
+ ids: string[];
237
+ }>;
238
+ proceduralConflicts: Array<{
239
+ aId: string;
240
+ bId: string;
241
+ aName: string;
242
+ bName: string;
243
+ score: number;
244
+ reason: string;
245
+ }>;
246
+ semanticConflicts: Array<{
247
+ aId: string;
248
+ bId: string;
249
+ aName: string;
250
+ bName: string;
251
+ reason: string;
252
+ }>;
253
+ }>;
148
254
  generateFullInsights(): Promise<FullInsightsReport>;
149
255
  /**
150
256
  * 生成增强版洞察报告(包含大白话洞察 + 开发者画像 + 根因分析)
@@ -167,6 +273,30 @@ export declare class NanoMemEngine {
167
273
  private filterAndCleanV2Episodes;
168
274
  private filterAndCleanV2Facets;
169
275
  private filterAndCleanV2Semantic;
276
+ private mergeArchivedEntries;
277
+ private mergeArchivedWork;
278
+ private mergeArchivedV2;
279
+ private partitionArchivedEntries;
280
+ private partitionArchivedWork;
281
+ private partitionArchivedSemantic;
282
+ private partitionArchivedProcedural;
283
+ private getLegacyArchiveReason;
284
+ private inferSemanticRetention;
285
+ private inferSemanticStability;
286
+ private inferSemanticImportance;
287
+ private mapExtractedItemToSemanticType;
288
+ private upsertSemanticFromExtractedItem;
289
+ private semanticKindToLegacyType;
290
+ private semanticToRuntimeEntry;
291
+ private proceduralToRuntimeEntry;
292
+ private buildRuntimeMemoryView;
293
+ private getWorkArchiveReason;
294
+ private getSemanticArchiveReason;
295
+ private getProceduralArchiveReason;
296
+ private buildProceduralChains;
297
+ private materializeV2Links;
298
+ private detectProceduralConflicts;
299
+ private detectSemanticConflicts;
170
300
  private buildEmbeddingSourceItems;
171
301
  private querySemanticCandidates;
172
302
  private scoreEpisodeMemory;