pi-hermes-memory 0.6.3 → 0.6.5

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.
@@ -0,0 +1,32 @@
1
+ # v0.6.5 Changelog
2
+
3
+ ## Bug Fixes
4
+
5
+ ### Auto-review no longer blocks interactive chat (#10)
6
+
7
+ The background auto-review was `await`ing `pi.exec()` inside the `turn_end` handler, which
8
+ blocked the chat from responding while the review subprocess ran. Fixed by making the
9
+ review subprocess fire-and-forget — the turn_end handler returns immediately, and the
10
+ subprocess completes asynchronously.
11
+
12
+ - `pi.exec()` is no longer awaited — handler returns immediately
13
+ - `reviewInProgress` guard resets in `.then()` / `.catch()` callbacks
14
+ - Overlapping reviews are still prevented by the guard
15
+ - Notifications are delivered via `.then()` callback once the subprocess completes
16
+
17
+ ### Auto-review failures on Windows no longer show errors (#9)
18
+
19
+ On Windows git-bash, `pi exec` subprocesses could exit with code 1 producing
20
+ `[hermes] auto-review failed (exit=1): unknown error` messages every few turns. Fixed by
21
+ making auto-review truly best-effort — non-zero exit codes and spawn errors are silently
22
+ ignored.
23
+
24
+ - Suppressed error notifications for non-zero exit codes
25
+ - Suppressed error notifications for subprocess failures (timeout, signal, spawn)
26
+ - The next review cycle will retry automatically
27
+
28
+ ### Crash safety
29
+
30
+ - Snapshot-building code (`getBranch()`, message parsing) is wrapped in a minimal try/catch
31
+ to handle expired sessions gracefully
32
+ - Early return resets `reviewInProgress` guard to unblock future reviews
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-hermes-memory",
3
- "version": "0.6.3",
3
+ "version": "0.6.5",
4
4
  "description": "🧠 Persistent memory + 🔍 session search + 🛡️ secret scanning for Pi. SQLite FTS5 search across every conversation, auto-consolidation, memory aging, procedural skills. 272 tests. Ported from Hermes agent.",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -66,10 +66,10 @@ export function setupBackgroundReview(
66
66
  toolCallsSinceReview = 0;
67
67
  reviewInProgress = true;
68
68
 
69
+ // Build conversation snapshot from session entries (crash-safe)
70
+ let parts: string[] = [];
69
71
  try {
70
- // Build conversation snapshot from session entries
71
72
  const entries = ctx.sessionManager.getBranch();
72
- const parts: string[] = [];
73
73
 
74
74
  for (const entry of entries) {
75
75
  if (entry.type !== "message") continue;
@@ -79,56 +79,72 @@ export function setupBackgroundReview(
79
79
  const prefix = msg.role === "user" ? "[USER]" : "[ASSISTANT]";
80
80
  parts.push(`${prefix}: ${text}`);
81
81
  }
82
- if (parts.length < 4) return; // Not enough conversation to review
83
-
84
- const currentMemory = store.getMemoryEntries().join("\n§\n");
85
- const currentUser = store.getUserEntries().join("\n§\n");
86
- const currentProject = projectStore ? projectStore.getMemoryEntries().join("\n§\n") : null;
87
-
88
- const reviewPrompt = [
89
- COMBINED_REVIEW_PROMPT,
90
- "",
91
- "--- Current Memory ---",
92
- currentMemory || "(empty)",
93
- "",
94
- "--- Current User Profile ---",
95
- currentUser || "(empty)",
96
- ];
97
-
98
- if (currentProject !== null) {
99
- reviewPrompt.push(
100
- "",
101
- "--- Current Project Memory ---",
102
- currentProject || "(empty)",
103
- );
104
- }
82
+ } catch {
83
+ reviewInProgress = false;
84
+ return; // Session expired or empty — nothing to review
85
+ }
86
+ if (parts.length < 4) {
87
+ reviewInProgress = false;
88
+ return; // Not enough conversation to review
89
+ }
105
90
 
91
+ const currentMemory = store.getMemoryEntries().join("\n§\n");
92
+ const currentUser = store.getUserEntries().join("\n§\n");
93
+ const currentProject = projectStore ? projectStore.getMemoryEntries().join("\n§\n") : null;
94
+
95
+ const reviewPrompt = [
96
+ COMBINED_REVIEW_PROMPT,
97
+ "",
98
+ "--- Current Memory ---",
99
+ currentMemory || "(empty)",
100
+ "",
101
+ "--- Current User Profile ---",
102
+ currentUser || "(empty)",
103
+ ];
104
+
105
+ if (currentProject !== null) {
106
106
  reviewPrompt.push(
107
107
  "",
108
- "--- Conversation to Review ---",
109
- parts.join("\n\n"),
108
+ "--- Current Project Memory ---",
109
+ currentProject || "(empty)",
110
110
  );
111
+ }
111
112
 
112
- const result = await pi.exec("pi", ["-p", "--no-session", reviewPrompt.join("\n")], {
113
- signal: ctx.signal,
114
- timeout: 120000,
115
- });
116
-
117
- if (result.code === 0 && result.stdout) {
118
- const output = result.stdout.trim();
119
- if (output && !output.toLowerCase().includes("nothing to save")) {
120
- ctx.ui.notify("💾 Memory auto-reviewed and updated", "info");
113
+ reviewPrompt.push(
114
+ "",
115
+ "--- Conversation to Review ---",
116
+ parts.join("\n\n"),
117
+ );
118
+
119
+ // Fire-and-forget: do NOT await. The review runs in a subprocess;
120
+ // blocking turn_end would freeze the interactive chat.
121
+ // Notifications are delivered via .then() once the subprocess completes.
122
+ //
123
+ // We intentionally omit ctx.signal — the signal is tied to the turn
124
+ // lifetime and would abort the subprocess before it finishes now that
125
+ // we're not awaiting. The timeout (120s) provides its own safety net.
126
+ const reviewPromise = pi.exec("pi", ["-p", "--no-session", reviewPrompt.join("\n")], {
127
+ signal: undefined,
128
+ timeout: 120000,
129
+ });
130
+
131
+ reviewPromise
132
+ .then((result) => {
133
+ reviewInProgress = false;
134
+ if (result.code === 0 && result.stdout) {
135
+ const output = result.stdout.trim();
136
+ if (output && !output.toLowerCase().includes("nothing to save")) {
137
+ ctx.ui.notify("💾 Memory auto-reviewed and updated", "info");
138
+ }
121
139
  }
122
- } else {
123
- ctx.ui.notify(
124
- `[hermes] auto-review failed (exit=${result.code}): ${result.stderr?.slice(0, 200) || "unknown error"}`,
125
- "error",
126
- );
127
- }
128
- } catch (err) {
129
- ctx.ui.notify(`[hermes] auto-review error: ${String(err).slice(0, 200)}`, "error");
130
- } finally {
131
- reviewInProgress = false;
132
- }
140
+ // Auto-review is best-effort. Non-zero exits are silently skipped —
141
+ // common on Windows where pi CLI may resolve differently. The next
142
+ // review cycle will retry.
143
+ })
144
+ .catch(() => {
145
+ // Best-effort: subprocess failures (timeout, signal, spawn errors)
146
+ // are silently ignored. The next review cycle will retry.
147
+ reviewInProgress = false;
148
+ });
133
149
  });
134
150
  }
@@ -29,17 +29,28 @@ export function registerLearnMemoryCommand(pi: ExtensionAPI): void {
29
29
  lines.push(" ║ 📦 What Gets Saved ║");
30
30
  lines.push(" ╚══════════════════════════════════════════════╝");
31
31
  lines.push("");
32
- lines.push(" Type │ File │ Limit");
33
- lines.push(" ────────────────┼──────────────┼────────────");
34
- lines.push(" 🧠 Memory │ MEMORY.md │ 5,000 chars");
35
- lines.push(" 👤 User Profile │ USER.md │ 5,000 chars");
36
- lines.push(" 📚 Skills skills/*.md Unlimited");
37
- lines.push(" 💾 Extended sessions.db │ Unlimited");
38
- lines.push("");
39
- lines.push(" Memory: Facts — env details, project conventions, tool quirks");
40
- lines.push(" User: Who you are — name, preferences, communication style");
41
- lines.push(" Skills: Procedureshow to debug, deploy, test");
32
+ lines.push(" Type │ File │ Limit");
33
+ lines.push(" ────────────────┼───────────────┼────────────");
34
+ lines.push(" 🧠 Memory │ MEMORY.md │ 5,000 chars");
35
+ lines.push(" 👤 User Profile │ USER.md │ 5,000 chars");
36
+ lines.push(" ⚠️ Failures failures.md 10,000 chars");
37
+ lines.push(" 📚 Skills skills/*.md │ Unlimited");
38
+ lines.push(" 💾 Extended │ sessions.db │ Unlimited");
39
+ lines.push("");
40
+ lines.push(" Memory: Facts env details, project conventions, tool quirks");
41
+ lines.push(" User: Who you are name, preferences, communication style");
42
+ lines.push(" Failures: What didn't work — corrections, failures, insights");
43
+ lines.push(" Skills: Procedures — how to debug, deploy, test");
42
44
  lines.push(" Extended: Searchable memories beyond the core limit");
45
+ lines.push("");
46
+ lines.push(" Memory Categories:");
47
+ lines.push(" ─────────────────");
48
+ lines.push(" [failure] What was tried but didn't work");
49
+ lines.push(" [correction] User corrected the agent");
50
+ lines.push(" [insight] Learning from experience");
51
+ lines.push(" [preference] User preference");
52
+ lines.push(" [convention] Project convention");
53
+ lines.push(" [tool-quirk] Tool-specific knowledge");
43
54
  }
44
55
 
45
56
  if (section.startsWith("🔧")) {
@@ -50,6 +61,7 @@ export function registerLearnMemoryCommand(pi: ExtensionAPI): void {
50
61
  lines.push("");
51
62
  lines.push(" memory (add/replace/remove)");
52
63
  lines.push(" Save, update, or delete memories");
64
+ lines.push(" Targets: memory, user, failure, project");
53
65
  lines.push("");
54
66
  lines.push(" skill (create/view/patch/edit/delete)");
55
67
  lines.push(" Save reusable procedures");
@@ -59,6 +71,8 @@ export function registerLearnMemoryCommand(pi: ExtensionAPI): void {
59
71
  lines.push("");
60
72
  lines.push(" memory_search");
61
73
  lines.push(" Search extended memory store (unlimited)");
74
+ lines.push(" Filters: project, target, category");
75
+ lines.push(" Categories: failure, correction, insight, preference, convention, tool-quirk");
62
76
  }
63
77
 
64
78
  if (section.startsWith("📋")) {
@@ -86,6 +100,7 @@ export function registerLearnMemoryCommand(pi: ExtensionAPI): void {
86
100
  lines.push(" • Environment facts (\"macOS M1\", \"Node 20\")");
87
101
  lines.push(" • Corrections (\"don't use npm — use pnpm\")");
88
102
  lines.push(" • Project conventions (\"monorepo with turborepo\")");
103
+ lines.push(" • Failures (\"tried localStorage — XSS vulnerability\")");
89
104
  lines.push("");
90
105
  lines.push(" ❌ DON'T save:");
91
106
  lines.push(" • Task progress (\"finished implementing auth\")");
@@ -99,12 +114,13 @@ export function registerLearnMemoryCommand(pi: ExtensionAPI): void {
99
114
  lines.push(" ║ 🔄 How Memory Flows ║");
100
115
  lines.push(" ╚══════════════════════════════════════════════╝");
101
116
  lines.push("");
102
- lines.push(" 1. Session starts → Core memory injected");
117
+ lines.push(" 1. Session starts → Core memory + recent failures injected");
103
118
  lines.push(" 2. During conversation → Agent saves via memory tool");
104
119
  lines.push(" 3. Every 10 turns → Background review saves items");
105
- lines.push(" 4. On correction → Immediate save");
106
- lines.push(" 5. When full Auto-consolidation merges");
107
- lines.push(" 6. Session ends Final flush");
120
+ lines.push(" 4. On correction → Immediate save as [correction] category");
121
+ lines.push(" 5. On failure Saves what failed + why");
122
+ lines.push(" 6. When full Auto-consolidation merges");
123
+ lines.push(" 7. Session ends → Final flush");
108
124
  }
109
125
 
110
126
  if (section.startsWith("🏗️")) {
@@ -117,6 +133,7 @@ export function registerLearnMemoryCommand(pi: ExtensionAPI): void {
117
133
  lines.push(" ┌─────────────────────────────────────┐");
118
134
  lines.push(" │ MEMORY.md — Facts, conventions │");
119
135
  lines.push(" │ USER.md — Who you are │");
136
+ lines.push(" │ failures.md — Recent failures (7d) │");
120
137
  lines.push(" │ Project memory — When cwd matches │");
121
138
  lines.push(" └─────────────────────────────────────┘");
122
139
  lines.push("");
@@ -124,6 +141,7 @@ export function registerLearnMemoryCommand(pi: ExtensionAPI): void {
124
141
  lines.push(" ┌─────────────────────────────────────┐");
125
142
  lines.push(" │ session_search(\"auth flow\") │");
126
143
  lines.push(" │ memory_search(\"testing patterns\") │");
144
+ lines.push(" │ memory_search(\"auth\", cat:\"failure\")│");
127
145
  lines.push(" └─────────────────────────────────────┘");
128
146
  }
129
147