memhouse 0.4.3 → 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.
Files changed (64) hide show
  1. package/README.md +244 -114
  2. package/bin/memhouse.js +1169 -233
  3. package/editors/base.js +6 -0
  4. package/editors/claude.js +44 -9
  5. package/editors/goose.js +4 -0
  6. package/editors/index.js +0 -0
  7. package/editors/opencode.js +3 -0
  8. package/{mem-house → memhouse}/COMPETITION.md +5 -5
  9. package/{mem-house → memhouse}/DESIGN.md +18 -12
  10. package/memhouse/RESUME-AND-UPDATE.md +185 -0
  11. package/{mem-house → memhouse}/delivery/AGENT-INSTALL-PROMPT.md +1 -1
  12. package/{mem-house → memhouse}/delivery/AGENT-INSTALL.md +9 -7
  13. package/{mem-house → memhouse}/delivery/PROMPT.md +8 -8
  14. package/{mem-house → memhouse}/delivery/kernel-install.md +3 -3
  15. package/{mem-house → memhouse}/delivery/plugin/.claude-plugin/plugin.json +1 -1
  16. package/{mem-house/delivery/skills/memhouse-search → memhouse/delivery/plugin/skills/search}/SKILL.md +60 -18
  17. package/memhouse/delivery/plugin/skills/sessions/SKILL.md +177 -0
  18. package/memhouse/delivery/plugin/skills/sql/SKILL.md +206 -0
  19. package/memhouse/host.js +101 -0
  20. package/{mem-house → memhouse}/per-member/INSTALL.md +59 -14
  21. package/{mem-house → memhouse}/per-member/SCHEMA.md +47 -0
  22. package/memhouse/per-member/provision.js +265 -0
  23. package/memhouse/per-member/rooms.js +235 -0
  24. package/memhouse/per-member/schema-member.sql.tpl +125 -0
  25. package/{mem-house → memhouse}/per-member/schema-merge.sql.tpl +3 -3
  26. package/memhouse/resume.js +150 -0
  27. package/memhouse/self-update.js +150 -0
  28. package/{mem-house → memhouse}/server/queries.js +80 -34
  29. package/memhouse/server/server.js +451 -0
  30. package/{mem-house → memhouse}/service.js +48 -29
  31. package/memhouse/shipper/ship.js +949 -0
  32. package/package.json +5 -20
  33. package/public/assets/{index-CtH-5i95.js → index-CnWzrjF3.js} +10 -7
  34. package/public/index.html +2 -2
  35. package/ui/index.html +1 -1
  36. package/ui/src/App.jsx +1 -1
  37. package/ui/src/lib/api.js +29 -13
  38. package/ui/src/pages/Dashboard.jsx +8 -5
  39. package/ui/src/pages/Sessions.jsx +3 -1
  40. package/ui/src/pages/SqlViewer.jsx +6 -23
  41. package/cache.js +0 -850
  42. package/ch-ingest.js +0 -329
  43. package/ch-schema.js +0 -152
  44. package/clickhouse.js +0 -87
  45. package/index.js +0 -425
  46. package/mcp-server.js +0 -294
  47. package/mem-house/delivery/install.sh +0 -183
  48. package/mem-house/delivery/skills/memhouse-sessions/SKILL.md +0 -125
  49. package/mem-house/delivery/skills/memhouse-sql/SKILL.md +0 -146
  50. package/mem-house/per-member/provision.js +0 -142
  51. package/mem-house/per-member/rooms.js +0 -132
  52. package/mem-house/per-member/schema-member.sql.tpl +0 -85
  53. package/mem-house/server/server.js +0 -228
  54. package/mem-house/shipper/ship.js +0 -504
  55. package/relay-client.js +0 -306
  56. package/relay-server.js +0 -577
  57. package/server.js +0 -860
  58. package/share-image.js +0 -367
  59. /package/{mem-house → memhouse}/deploy.js +0 -0
  60. /package/{mem-house → memhouse}/envfile.js +0 -0
  61. /package/{mem-house → memhouse}/per-member/PLAN.md +0 -0
  62. /package/{mem-house → memhouse}/per-member/PROVISIONING.md +0 -0
  63. /package/{mem-house → memhouse}/per-member/README.md +0 -0
  64. /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>Agentlytics — AI Agent Analytics</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-CtH-5i95.js"></script>
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>Agentlytics — AI Agent Analytics</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 mem-house/delivery/kernel-install.md.
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
- export function refetchAgents(onProgress) {
109
- return new Promise((resolve, reject) => {
110
- const es = new EventSource(`${BASE}/api/refetch`);
111
- es.onmessage = (evt) => {
112
- try {
113
- const data = JSON.parse(evt.data);
114
- if (data.done) { es.close(); resolve(data); }
115
- else if (data.error) { es.close(); reject(new Error(data.error)); }
116
- else if (onProgress) onProgress(data);
117
- } catch { /* ignore parse errors */ }
118
- };
119
- es.onerror = () => { es.close(); reject(new Error('SSE error')); };
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
- const avgMsgsPerSession = tk && tk.sessions > 0 ? (depthData ? (Object.values(stats.depthBuckets).reduce((s, v, i) => {
176
- const labels = Object.keys(stats.depthBuckets)
177
- const midpoints = [1, 3.5, 8, 15.5, 35.5, 75.5, 150]
178
- return s + v * midpoints[i]
179
- }, 0) / tk.sessions).toFixed(1) : '—') : '—'
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
- {c.cost > 0 ? formatCost(c.cost) : ''}
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 mem-house house (sessions / messages /
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
- const tables = s?.tables
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 || prev === LEGACY_EXAMPLES[0].sql) ? ex[0].sql : prev)
74
+ setSql(prev => (prev === '' || prev === TYPED_EXAMPLES[0].sql) ? ex[0].sql : prev)
92
75
  }).catch(() => setExamples(null))
93
76
  }, [])
94
77