memhouse 0.4.4 → 0.7.0
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 +244 -114
- package/bin/memhouse.js +1162 -230
- package/editors/base.js +6 -0
- package/editors/claude.js +44 -9
- package/editors/goose.js +4 -0
- package/editors/index.js +0 -0
- package/editors/opencode.js +3 -0
- package/{mem-house → memhouse}/COMPETITION.md +5 -5
- package/{mem-house → memhouse}/DESIGN.md +18 -12
- package/memhouse/RESUME-AND-UPDATE.md +185 -0
- package/{mem-house → memhouse}/delivery/AGENT-INSTALL-PROMPT.md +1 -1
- package/{mem-house → memhouse}/delivery/AGENT-INSTALL.md +9 -7
- package/{mem-house → memhouse}/delivery/PROMPT.md +8 -8
- package/{mem-house → memhouse}/delivery/kernel-install.md +3 -3
- package/{mem-house → memhouse}/delivery/plugin/.claude-plugin/plugin.json +1 -1
- package/{mem-house/delivery/skills/memhouse-search → memhouse/delivery/plugin/skills/search}/SKILL.md +60 -18
- package/memhouse/delivery/plugin/skills/sessions/SKILL.md +177 -0
- package/memhouse/delivery/plugin/skills/sql/SKILL.md +206 -0
- package/memhouse/host.js +101 -0
- package/{mem-house → memhouse}/per-member/INSTALL.md +59 -14
- package/{mem-house → memhouse}/per-member/SCHEMA.md +47 -0
- package/memhouse/per-member/provision.js +265 -0
- package/memhouse/per-member/rooms.js +235 -0
- package/{mem-house → memhouse}/per-member/schema-member.sql.tpl +18 -14
- package/{mem-house → memhouse}/per-member/schema-merge.sql.tpl +3 -3
- package/memhouse/resume.js +150 -0
- package/memhouse/self-update.js +150 -0
- package/{mem-house → memhouse}/server/queries.js +80 -34
- package/memhouse/server/server.js +451 -0
- package/{mem-house → memhouse}/service.js +48 -29
- package/memhouse/shipper/ship.js +949 -0
- package/package.json +5 -20
- package/public/assets/{index-CtH-5i95.js → index-CnWzrjF3.js} +10 -7
- package/public/index.html +2 -2
- package/ui/index.html +1 -1
- package/ui/src/App.jsx +1 -1
- package/ui/src/lib/api.js +29 -13
- package/ui/src/pages/Dashboard.jsx +8 -5
- package/ui/src/pages/Sessions.jsx +3 -1
- package/ui/src/pages/SqlViewer.jsx +6 -23
- package/cache.js +0 -850
- package/ch-ingest.js +0 -329
- package/ch-schema.js +0 -152
- package/clickhouse.js +0 -87
- package/index.js +0 -425
- package/mcp-server.js +0 -294
- package/mem-house/delivery/install.sh +0 -183
- package/mem-house/delivery/skills/memhouse-sessions/SKILL.md +0 -125
- package/mem-house/delivery/skills/memhouse-sql/SKILL.md +0 -146
- package/mem-house/per-member/provision.js +0 -142
- package/mem-house/per-member/rooms.js +0 -132
- package/mem-house/server/server.js +0 -228
- package/mem-house/shipper/ship.js +0 -566
- package/relay-client.js +0 -306
- package/relay-server.js +0 -577
- package/server.js +0 -860
- package/share-image.js +0 -367
- /package/{mem-house → memhouse}/deploy.js +0 -0
- /package/{mem-house → memhouse}/envfile.js +0 -0
- /package/{mem-house → memhouse}/per-member/PLAN.md +0 -0
- /package/{mem-house → memhouse}/per-member/PROVISIONING.md +0 -0
- /package/{mem-house → memhouse}/per-member/README.md +0 -0
- /package/{mem-house → memhouse}/per-member/SHARING.md +0 -0
package/public/index.html
CHANGED
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
-
<title>
|
|
6
|
+
<title>memhouse — agent conversation memory</title>
|
|
7
7
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
8
8
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
9
9
|
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@300;400;500;600;700&display=swap" rel="stylesheet" />
|
|
10
|
-
<script type="module" crossorigin src="/assets/index-
|
|
10
|
+
<script type="module" crossorigin src="/assets/index-CnWzrjF3.js"></script>
|
|
11
11
|
<link rel="stylesheet" crossorigin href="/assets/index-DV6ONi_F.css">
|
|
12
12
|
</head>
|
|
13
13
|
<body>
|
package/ui/index.html
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
-
<title>
|
|
6
|
+
<title>memhouse — agent conversation memory</title>
|
|
7
7
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
8
8
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
9
9
|
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@300;400;500;600;700&display=swap" rel="stylesheet" />
|
package/ui/src/App.jsx
CHANGED
|
@@ -351,7 +351,7 @@ export default function App() {
|
|
|
351
351
|
<div className="text-[10px] mb-1" style={{ color: 'var(--c-text3)' }}>
|
|
352
352
|
Relay join is not supported in memhouse — team sharing is kernel-based. Each
|
|
353
353
|
member owns their own rooms and shares by granting SELECT on them.
|
|
354
|
-
See
|
|
354
|
+
See memhouse/delivery/kernel-install.md.
|
|
355
355
|
</div>
|
|
356
356
|
<pre
|
|
357
357
|
className="text-[11px] px-3 py-2 overflow-x-auto"
|
package/ui/src/lib/api.js
CHANGED
|
@@ -105,19 +105,35 @@ export async function fetchDeepAnalytics(params = {}) {
|
|
|
105
105
|
return res.json();
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
108
|
+
// POST, read as a stream. EventSource can only issue GET, and a full re-ship behind a GET
|
|
109
|
+
// is the easiest request on a machine to make by accident — a prefetch, a link checker, a
|
|
110
|
+
// curl in a log file. The wire format is unchanged (`data: {...}` frames); only the verb
|
|
111
|
+
// and the reader are.
|
|
112
|
+
export async function refetchAgents(onProgress) {
|
|
113
|
+
const res = await fetch(`${BASE}/api/refetch`, { method: 'POST' });
|
|
114
|
+
if (!res.ok || !res.body) throw new Error(`refetch failed (${res.status})`);
|
|
115
|
+
const reader = res.body.getReader();
|
|
116
|
+
const decoder = new TextDecoder();
|
|
117
|
+
let buf = '';
|
|
118
|
+
let last = null;
|
|
119
|
+
for (;;) {
|
|
120
|
+
const { value, done } = await reader.read();
|
|
121
|
+
if (done) break;
|
|
122
|
+
buf += decoder.decode(value, { stream: true });
|
|
123
|
+
const frames = buf.split('\n\n');
|
|
124
|
+
buf = frames.pop() || '';
|
|
125
|
+
for (const frame of frames) {
|
|
126
|
+
const line = frame.split('\n').find((l) => l.startsWith('data:'));
|
|
127
|
+
if (!line) continue;
|
|
128
|
+
let data;
|
|
129
|
+
try { data = JSON.parse(line.slice(5).trim()); } catch { continue; }
|
|
130
|
+
if (data.error) throw new Error(data.error);
|
|
131
|
+
if (data.done) { last = data; }
|
|
132
|
+
else if (onProgress) onProgress(data);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (!last) throw new Error('refetch ended without completing');
|
|
136
|
+
return last;
|
|
121
137
|
}
|
|
122
138
|
|
|
123
139
|
export async function fetchDashboardStats(params = {}) {
|
|
@@ -172,11 +172,14 @@ export default function Dashboard({ overview }) {
|
|
|
172
172
|
const totalInputAll = tk ? tk.input + tk.cacheRead + (tk.cacheWrite || 0) : 0
|
|
173
173
|
const cacheHitRate = totalInputAll > 0 ? ((tk.cacheRead / totalInputAll) * 100).toFixed(1) : 0
|
|
174
174
|
const outputInputRatio = totalInputAll > 0 ? (tk.output / totalInputAll).toFixed(3) : 0
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
175
|
+
// Messages divided by sessions. This used to sum the depth HISTOGRAM against hardcoded
|
|
176
|
+
// bucket midpoints — reading 4.2 where the truth was 26/8 = 3.25, +29% — and it mixed
|
|
177
|
+
// denominators too: the numerator came from depthBuckets, whose query excludes sessions
|
|
178
|
+
// with no messages, while the divisor counted all of them. The exact value is one
|
|
179
|
+
// division away and both numbers were already on the page.
|
|
180
|
+
const avgMsgsPerSession = tk && tk.sessions > 0 && tk.messages != null
|
|
181
|
+
? (tk.messages / tk.sessions).toFixed(1)
|
|
182
|
+
: '—'
|
|
180
183
|
|
|
181
184
|
|
|
182
185
|
return (
|
|
@@ -287,7 +287,9 @@ export default function Sessions({ overview }) {
|
|
|
287
287
|
)}
|
|
288
288
|
</td>
|
|
289
289
|
<td className="py-2 px-3 text-[12px] font-mono text-right" style={{ color: c.cost > 0 ? 'var(--c-text2)' : 'var(--c-text3)' }}>
|
|
290
|
-
{
|
|
290
|
+
{/* costPartial: the session used a model with no price, so this number omits part
|
|
291
|
+
of itself. The API has carried the flag since 0.4.6 and nothing rendered it. */}
|
|
292
|
+
{c.cost > 0 ? <span title={c.costPartial ? 'partial — this session used a model with no price' : undefined}>{formatCost(c.cost)}{c.costPartial ? '*' : ''}</span> : ''}
|
|
291
293
|
</td>
|
|
292
294
|
<td className="py-2 px-3 text-[12px] whitespace-nowrap" style={{ color: 'var(--c-text3)' }}>
|
|
293
295
|
{formatDate(c.lastUpdatedAt || c.createdAt)}
|
|
@@ -8,7 +8,7 @@ import PageHeader from '../components/PageHeader'
|
|
|
8
8
|
|
|
9
9
|
ChartJS.register(ArcElement, Tooltip, Legend, CategoryScale, LinearScale, BarElement, PointElement, LineElement, Filler)
|
|
10
10
|
|
|
11
|
-
// Two backends share this SPA: the typed
|
|
11
|
+
// Two backends share this SPA: the typed memhouse house (sessions / messages /
|
|
12
12
|
// tool_calls + the session rollup) and the legacy agentlytics cache (chats /
|
|
13
13
|
// chat_stats). Both are ClickHouse, and the flavor comes from /api/schema.
|
|
14
14
|
//
|
|
@@ -33,16 +33,6 @@ const TYPED_EXAMPLES = [
|
|
|
33
33
|
// arbitrary member's.
|
|
34
34
|
{ label: 'Full-text search', sql: `SELECT session_id, user_id, any(source) AS source, count() AS hits FROM {{messages}} WHERE text_ngram LIKE '%clickhouse%' GROUP BY session_id, user_id ORDER BY hits DESC LIMIT 10` },
|
|
35
35
|
]
|
|
36
|
-
const LEGACY_EXAMPLES = [
|
|
37
|
-
{ label: 'Sessions per editor', sql: `SELECT source, count() AS count FROM chats GROUP BY source ORDER BY count DESC` },
|
|
38
|
-
{ label: 'Top 10 projects', sql: `SELECT folder, count() AS sessions, sum(bubble_count) AS messages FROM chats WHERE folder IS NOT NULL GROUP BY folder ORDER BY sessions DESC LIMIT 10` },
|
|
39
|
-
{ label: 'Messages per day', sql: `SELECT toDate(toDateTime(intDiv(created_at, 1000))) AS day, count() AS count FROM chats WHERE created_at IS NOT NULL GROUP BY day ORDER BY day` },
|
|
40
|
-
{ label: 'Top models', sql: `SELECT model, count() AS count FROM messages WHERE model IS NOT NULL GROUP BY model ORDER BY count DESC LIMIT 10` },
|
|
41
|
-
{ label: 'Top tools', sql: `SELECT tool_name, count() AS count FROM tool_calls GROUP BY tool_name ORDER BY count DESC LIMIT 15` },
|
|
42
|
-
{ label: 'Token usage by editor', sql: `SELECT c.source AS source, sum(cs.total_input_tokens) AS input_tokens, sum(cs.total_output_tokens) AS output_tokens FROM chat_stats AS cs JOIN chats AS c ON c.id = cs.chat_id GROUP BY c.source ORDER BY input_tokens DESC` },
|
|
43
|
-
{ label: 'Sessions by mode', sql: `SELECT mode, count() AS count FROM chats WHERE mode IS NOT NULL GROUP BY mode ORDER BY count DESC` },
|
|
44
|
-
{ label: 'Hourly distribution', sql: `SELECT toHour(toDateTime(intDiv(created_at, 1000))) AS hour, count() AS count FROM chats WHERE created_at IS NOT NULL GROUP BY hour ORDER BY hour` },
|
|
45
|
-
]
|
|
46
36
|
|
|
47
37
|
// `{{room}}` -> the resolved name (or, for the rollup on a per-member house, the whole
|
|
48
38
|
// subquery). Same token shape the server's own query layer uses.
|
|
@@ -71,24 +61,17 @@ export default function SqlViewer() {
|
|
|
71
61
|
useEffect(() => {
|
|
72
62
|
fetchSchema().then(s => {
|
|
73
63
|
setSchema(s)
|
|
74
|
-
// Three outcomes, not two. The typed house is whichever one /api/schema resolved
|
|
75
|
-
// rooms for; the legacy one has `chats`. Anything else — an error object with no
|
|
76
|
-
// `tables`, a half-applied schema with neither marker — is indeterminate, and
|
|
77
|
-
// offering either set would hand the user queries that error.
|
|
78
|
-
//
|
|
79
64
|
// `rooms` is the authority, not a table name: a per-member house has no
|
|
80
65
|
// `sessions_v` object, so the old `tables.includes('sessions_v')` test called a
|
|
81
|
-
// perfectly healthy typed house indeterminate.
|
|
82
|
-
|
|
66
|
+
// perfectly healthy typed house indeterminate. Anything without resolved rooms —
|
|
67
|
+
// an error object, a half-applied schema — stays indeterminate and offers nothing,
|
|
68
|
+
// because offering queries against tables that do not exist is worse than silence.
|
|
83
69
|
const rooms = s?.rooms
|
|
84
|
-
const ex = rooms?.sessions ? TYPED_EXAMPLES.map(e => ({ ...e, sql: applyRooms(e.sql, rooms) }))
|
|
85
|
-
: !Array.isArray(tables) ? null
|
|
86
|
-
: tables.includes('chats') ? LEGACY_EXAMPLES
|
|
87
|
-
: null
|
|
70
|
+
const ex = rooms?.sessions ? TYPED_EXAMPLES.map(e => ({ ...e, sql: applyRooms(e.sql, rooms) })) : null
|
|
88
71
|
setExamples(ex)
|
|
89
72
|
if (!ex) return
|
|
90
73
|
// Swap the prefilled query only if the user hasn't edited it yet.
|
|
91
|
-
setSql(prev => (prev === '' || prev === TYPED_EXAMPLES[0].sql
|
|
74
|
+
setSql(prev => (prev === '' || prev === TYPED_EXAMPLES[0].sql) ? ex[0].sql : prev)
|
|
92
75
|
}).catch(() => setExamples(null))
|
|
93
76
|
}, [])
|
|
94
77
|
|