@jhizzard/termdeck 0.4.2 → 0.4.3
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/README.md
CHANGED
|
@@ -139,7 +139,7 @@ Restart Claude Code. Six MCP tools appear: `memory_remember`, `memory_recall`, `
|
|
|
139
139
|
|
|
140
140
|
### Tier 3 — Add Rumen for async learning
|
|
141
141
|
|
|
142
|
-
Rumen is a separate npm package — `@jhizzard/rumen@0.4.
|
|
142
|
+
Rumen is a separate npm package — `@jhizzard/rumen@0.4.3` — that ships as a Supabase Edge Function designed to run on a 15-minute `pg_cron` schedule. It's the async reflection layer over Mnestra: it reads recent session memories, cross-references them with your entire historical corpus via hybrid search, synthesizes insights via Claude Haiku, and writes the results back into `rumen_insights` (a new table alongside Mnestra's `memory_items`). TermDeck's Flashback and Claude Code's `memory_recall` both automatically benefit because insights flow back into the same database.
|
|
143
143
|
|
|
144
144
|
**Rumen is live.** First full-kickstart run against a production Mnestra store on 2026-04-15 19:47 UTC: **111 sessions processed, 111 insights generated** in one pass. Insights surfaced patterns like "the error detection regex in Flashback misses `No such file or directory` — same class of blind spot as X" and "Practice sessions exist as a separate model but frontend components were built and never wired into the schedule view." The cognitive loop is closed.
|
|
145
145
|
|
|
@@ -163,7 +163,7 @@ Honest limits, stated upfront so the skeptic has nothing to chase:
|
|
|
163
163
|
- **Not a replacement for reading docs.** It's the shortest path to a memory you already wrote. If the memory isn't there, the feature does nothing.
|
|
164
164
|
- **Not fully local by default.** Tier 2+ reaches out to Supabase for storage and OpenAI for embeddings. Tier 1 is fully local. A fully-local Tier 2 (local Postgres + local embeddings) is on the roadmap.
|
|
165
165
|
- **Not free forever.** Tier 2+ pays OpenAI fractions of a cent per memory for embeddings. Self-hosted embeddings via Ollama are on the roadmap.
|
|
166
|
-
- **Not proven at scale.** v0.4.
|
|
166
|
+
- **Not proven at scale.** v0.4.3, validated against 3,527 memories in one developer's production store. First full Rumen kickstart on 2026-04-15 processed 111 sessions into 111 insights in one pass. No multi-user data yet. Bug reports and issues welcome.
|
|
167
167
|
|
|
168
168
|
---
|
|
169
169
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jhizzard/termdeck",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.3",
|
|
4
4
|
"description": "Browser-based terminal multiplexer with metadata overlays, panel flashback memory recall, and AI-aware session management",
|
|
5
5
|
"bin": {
|
|
6
6
|
"termdeck": "./packages/cli/src/index.js"
|
|
@@ -375,6 +375,7 @@ function createServer(config) {
|
|
|
375
375
|
// configured, regardless of the push-loop flag.
|
|
376
376
|
session.onErrorDetected = (sess, ctx) => {
|
|
377
377
|
const question = `${sess.meta.type} error ${ctx.lastCommand || ''} ${ctx.tail || ''}`.trim();
|
|
378
|
+
console.log(`[flashback] error detected in session ${sess.id} (type=${sess.meta.type}, project=${sess.meta.project || 'none'}), querying Mnestra via ${mnestraBridge.mode}…`);
|
|
378
379
|
mnestraBridge.queryMnestra({
|
|
379
380
|
question,
|
|
380
381
|
project: sess.meta.project,
|
|
@@ -386,16 +387,26 @@ function createServer(config) {
|
|
|
386
387
|
status: 'errored'
|
|
387
388
|
}
|
|
388
389
|
}).then((result) => {
|
|
390
|
+
const count = (result.memories || []).length;
|
|
391
|
+
console.log(`[flashback] query returned ${count} matches for session ${sess.id}`);
|
|
389
392
|
const hit = (result.memories || [])[0];
|
|
390
|
-
if (!hit)
|
|
393
|
+
if (!hit) {
|
|
394
|
+
console.log(`[flashback] no matches — skipping proactive_memory send for session ${sess.id}`);
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
391
397
|
if (sess.ws && sess.ws.readyState === 1) {
|
|
392
398
|
try {
|
|
393
399
|
sess.ws.send(JSON.stringify({ type: 'proactive_memory', hit }));
|
|
400
|
+
console.log(`[flashback] proactive_memory sent to session ${sess.id} (source_type=${hit.source_type}, project=${hit.project})`);
|
|
394
401
|
} catch (err) {
|
|
402
|
+
console.error('[flashback] proactive_memory send failed:', err);
|
|
395
403
|
console.error('[ws] proactive_memory send failed:', err);
|
|
396
404
|
}
|
|
405
|
+
} else {
|
|
406
|
+
console.log(`[flashback] ws not open for session ${sess.id} (readyState=${sess.ws ? sess.ws.readyState : 'null'}) — dropped hit`);
|
|
397
407
|
}
|
|
398
408
|
}).catch((err) => {
|
|
409
|
+
console.error(`[flashback] query failed for session ${sess.id}: ${err.message}`);
|
|
399
410
|
console.warn('[mnestra-bridge] proactive query failed:', err.message);
|
|
400
411
|
});
|
|
401
412
|
};
|
|
@@ -47,6 +47,24 @@ function createBridge(config) {
|
|
|
47
47
|
const embeddingData = await embeddingRes.json();
|
|
48
48
|
const embedding = embeddingData.data[0].embedding;
|
|
49
49
|
|
|
50
|
+
// NOTE: memory_hybrid_search (migrations/004) accepts exactly 8 named params:
|
|
51
|
+
// query_text, query_embedding, match_count, full_text_weight,
|
|
52
|
+
// semantic_weight, rrf_k, filter_project, filter_source_type.
|
|
53
|
+
// PostgREST matches RPC functions by the set of JSON keys in the body — any
|
|
54
|
+
// extra key (e.g. recency_weight, decay_days) makes it fail to resolve the
|
|
55
|
+
// overload and return 404 "Could not find the function". That was silently
|
|
56
|
+
// killing every Flashback query for 15 sprints.
|
|
57
|
+
const rpcBody = {
|
|
58
|
+
query_text: question,
|
|
59
|
+
query_embedding: `[${embedding.join(',')}]`,
|
|
60
|
+
match_count: 10,
|
|
61
|
+
full_text_weight: 1.0,
|
|
62
|
+
semantic_weight: 1.0,
|
|
63
|
+
rrf_k: 60,
|
|
64
|
+
filter_project: searchAll ? null : (project || null),
|
|
65
|
+
filter_source_type: null
|
|
66
|
+
};
|
|
67
|
+
console.log(`[flashback] direct RPC → memory_hybrid_search project=${rpcBody.filter_project ?? 'ALL'} q="${question.slice(0, 60)}"`);
|
|
50
68
|
const searchRes = await fetch(`${supabaseUrl}/rest/v1/rpc/memory_hybrid_search`, {
|
|
51
69
|
method: 'POST',
|
|
52
70
|
headers: {
|
|
@@ -54,31 +72,23 @@ function createBridge(config) {
|
|
|
54
72
|
'apikey': supabaseKey,
|
|
55
73
|
'Authorization': `Bearer ${supabaseKey}`
|
|
56
74
|
},
|
|
57
|
-
body: JSON.stringify(
|
|
58
|
-
query_text: question,
|
|
59
|
-
query_embedding: `[${embedding.join(',')}]`,
|
|
60
|
-
match_count: 10,
|
|
61
|
-
full_text_weight: 1.0,
|
|
62
|
-
semantic_weight: 1.0,
|
|
63
|
-
rrf_k: 60,
|
|
64
|
-
filter_project: searchAll ? null : (project || null),
|
|
65
|
-
filter_source_type: null,
|
|
66
|
-
recency_weight: 0.15,
|
|
67
|
-
decay_days: 30.0
|
|
68
|
-
})
|
|
75
|
+
body: JSON.stringify(rpcBody)
|
|
69
76
|
});
|
|
70
77
|
if (!searchRes.ok) {
|
|
71
78
|
const err = await searchRes.text();
|
|
79
|
+
console.error(`[flashback] direct RPC failed ${searchRes.status}:`, err);
|
|
72
80
|
console.error('[mnestra-bridge:direct] supabase search failed:', err);
|
|
73
|
-
throw new Error(
|
|
81
|
+
throw new Error(`Memory search failed (${searchRes.status})`);
|
|
74
82
|
}
|
|
75
83
|
const rows = await searchRes.json();
|
|
84
|
+
console.log(`[flashback] direct RPC returned ${rows.length} rows`);
|
|
76
85
|
return {
|
|
77
86
|
memories: rows.map((m) => ({
|
|
78
87
|
content: m.content,
|
|
79
88
|
source_type: m.source_type,
|
|
80
89
|
project: m.project,
|
|
81
|
-
|
|
90
|
+
// memory_hybrid_search returns `score`, not `similarity`.
|
|
91
|
+
similarity: m.similarity ?? m.score ?? null,
|
|
82
92
|
created_at: m.created_at
|
|
83
93
|
})),
|
|
84
94
|
total: rows.length
|
|
@@ -322,7 +322,10 @@ class Session {
|
|
|
322
322
|
|
|
323
323
|
// Server-side rate limit: at most one error_detected event every 30s per session
|
|
324
324
|
const now = Date.now();
|
|
325
|
-
if (now - this._lastErrorFireAt < 30000)
|
|
325
|
+
if (now - this._lastErrorFireAt < 30000) {
|
|
326
|
+
console.log(`[flashback] error detected in session ${this.id} but rate-limited (${Math.round((30000 - (now - this._lastErrorFireAt)) / 1000)}s left)`);
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
326
329
|
this._lastErrorFireAt = now;
|
|
327
330
|
|
|
328
331
|
if (this.onErrorDetected) {
|
|
@@ -333,8 +336,11 @@ class Session {
|
|
|
333
336
|
try {
|
|
334
337
|
this.onErrorDetected(this, { lastCommand, tail });
|
|
335
338
|
} catch (err) {
|
|
339
|
+
console.error('[flashback] onErrorDetected handler threw:', err);
|
|
336
340
|
console.error('[session] onErrorDetected handler error:', err);
|
|
337
341
|
}
|
|
342
|
+
} else {
|
|
343
|
+
console.log(`[flashback] error detected in session ${this.id} but no onErrorDetected handler wired`);
|
|
338
344
|
}
|
|
339
345
|
}
|
|
340
346
|
|