@pcircle/memesh 4.8.1 → 4.8.2

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 (71) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/AGENTS.md +7 -1
  4. package/README.de.md +1 -1
  5. package/README.md +1 -1
  6. package/README.zh-TW.md +1 -1
  7. package/dashboard/dist/index.html +9 -9
  8. package/dist/core/agent-message-inbox.d.ts +9 -0
  9. package/dist/core/agent-message-inbox.d.ts.map +1 -0
  10. package/dist/core/agent-message-inbox.js +26 -0
  11. package/dist/core/agent-message-inbox.js.map +1 -0
  12. package/dist/core/briefing.d.ts.map +1 -1
  13. package/dist/core/briefing.js +5 -1
  14. package/dist/core/briefing.js.map +1 -1
  15. package/dist/core/config.d.ts +4 -1
  16. package/dist/core/config.d.ts.map +1 -1
  17. package/dist/core/config.js +12 -3
  18. package/dist/core/config.js.map +1 -1
  19. package/dist/core/doctor.d.ts.map +1 -1
  20. package/dist/core/doctor.js +5 -2
  21. package/dist/core/doctor.js.map +1 -1
  22. package/dist/core/dreamer.d.ts +2 -0
  23. package/dist/core/dreamer.d.ts.map +1 -1
  24. package/dist/core/dreamer.js +7 -1
  25. package/dist/core/dreamer.js.map +1 -1
  26. package/dist/core/lesson-engine.d.ts +2 -1
  27. package/dist/core/lesson-engine.d.ts.map +1 -1
  28. package/dist/core/lesson-engine.js +5 -2
  29. package/dist/core/lesson-engine.js.map +1 -1
  30. package/dist/core/lesson-slug.d.ts +2 -0
  31. package/dist/core/lesson-slug.d.ts.map +1 -0
  32. package/dist/core/lesson-slug.js +12 -0
  33. package/dist/core/lesson-slug.js.map +1 -0
  34. package/dist/core/llm-client.js +1 -1
  35. package/dist/core/llm-client.js.map +1 -1
  36. package/dist/core/llm-validator.d.ts +4 -1
  37. package/dist/core/llm-validator.d.ts.map +1 -1
  38. package/dist/core/llm-validator.js +59 -30
  39. package/dist/core/llm-validator.js.map +1 -1
  40. package/dist/core/operations.d.ts +9 -3
  41. package/dist/core/operations.d.ts.map +1 -1
  42. package/dist/core/operations.js +18 -14
  43. package/dist/core/operations.js.map +1 -1
  44. package/dist/core/paths.d.ts.map +1 -1
  45. package/dist/core/paths.js +2 -3
  46. package/dist/core/paths.js.map +1 -1
  47. package/dist/core/schema-export.js +2 -2
  48. package/dist/core/schema-export.js.map +1 -1
  49. package/dist/core/transcript-extractor.d.ts.map +1 -1
  50. package/dist/core/transcript-extractor.js +2 -2
  51. package/dist/core/transcript-extractor.js.map +1 -1
  52. package/dist/db.d.ts.map +1 -1
  53. package/dist/db.js +12 -0
  54. package/dist/db.js.map +1 -1
  55. package/dist/skills-manifest.json +14 -9
  56. package/dist/storage/graph-repairs.d.ts +12 -0
  57. package/dist/storage/graph-repairs.d.ts.map +1 -0
  58. package/dist/storage/graph-repairs.js +194 -0
  59. package/dist/storage/graph-repairs.js.map +1 -0
  60. package/dist/transports/http/server.d.ts.map +1 -1
  61. package/dist/transports/http/server.js +117 -7
  62. package/dist/transports/http/server.js.map +1 -1
  63. package/dist/transports/mcp/handlers.d.ts +1 -1
  64. package/dist/transports/mcp/handlers.js +1 -1
  65. package/dist/transports/mcp/handlers.js.map +1 -1
  66. package/package.json +13 -3
  67. package/scripts/hooks/_generated/agent-message-inbox.js +33 -0
  68. package/scripts/hooks/_generated/core-paths.js +2 -3
  69. package/scripts/hooks/session-start.js +34 -7
  70. package/scripts/hooks/session-summary.js +40 -1
  71. package/skills/memesh/SKILL.md +17 -1
@@ -3,7 +3,7 @@
3
3
  "entries": [
4
4
  {
5
5
  "path": ".claude-plugin/plugin.json",
6
- "sha256": "1b9d1a3e0dd305a7914b5ddc16809713fbdc0760bf8bdcb16de18a534e92472b",
6
+ "sha256": "3af9a5e8c6a08ef45c179f98040b79b46efb73d1c5696825dc3bb49cdcc0fd8e",
7
7
  "bytes": 527
8
8
  },
9
9
  {
@@ -16,6 +16,11 @@
16
16
  "sha256": "f39efb39a90594141cf7b5e79062b42f7376f7e0007307040b4fed77022fd024",
17
17
  "bytes": 2083
18
18
  },
19
+ {
20
+ "path": "scripts/hooks/_generated/agent-message-inbox.js",
21
+ "sha256": "32eefdac2b8699bf33ed0b111fad5e374cc64dba0f17b95d4475f30243cef290",
22
+ "bytes": 1394
23
+ },
19
24
  {
20
25
  "path": "scripts/hooks/_generated/capture-flag.js",
21
26
  "sha256": "926c85a60b4ff4ac391666dad392f16358e875a14f2dda66edf99b1d636a0a38",
@@ -28,8 +33,8 @@
28
33
  },
29
34
  {
30
35
  "path": "scripts/hooks/_generated/core-paths.js",
31
- "sha256": "6c867a9765e3e469fd539cc002179ff4fe3b79a59fc2b8b9f7a10d126c621531",
32
- "bytes": 5123
36
+ "sha256": "d9aaba0b0a33b701bc05db9a4b73fe42715279796a77db563767c224c35986c3",
37
+ "bytes": 5244
33
38
  },
34
39
  {
35
40
  "path": "scripts/hooks/_generated/fts-index.js",
@@ -103,13 +108,13 @@
103
108
  },
104
109
  {
105
110
  "path": "scripts/hooks/session-start.js",
106
- "sha256": "3f0dd2bad818c621ad8f9ef6992146e0dbcb8afa89289c038699273a83a3dcff",
107
- "bytes": 58386
111
+ "sha256": "9dd8010551deb8a4016d2963f83a1699313f40302bd714b082b5ec82aa71c467",
112
+ "bytes": 60042
108
113
  },
109
114
  {
110
115
  "path": "scripts/hooks/session-summary.js",
111
- "sha256": "50d2d9ee1a65e19f43d296891ffcb8222eb7b02d162c0f99f4d883d7cb9872d1",
112
- "bytes": 47381
116
+ "sha256": "71c9a38f7b1c0b3f84ec8f51d5b5c79e9cca5bb6ae84b72d2b6bccc77f0fd38d",
117
+ "bytes": 49691
113
118
  },
114
119
  {
115
120
  "path": "scripts/hooks/user-prompt-intent.js",
@@ -123,8 +128,8 @@
123
128
  },
124
129
  {
125
130
  "path": "skills/memesh/SKILL.md",
126
- "sha256": "e669c0cbaa80d6f53b2a0ccf48c459c64bfafc5f9775acd3bf22db6be4d462b1",
127
- "bytes": 13982
131
+ "sha256": "56f4a8352f75526a57fc3aa3b58bbd7ba1ef63c289a150cc842c182a055b3d2d",
132
+ "bytes": 15089
128
133
  }
129
134
  ]
130
135
  }
@@ -0,0 +1,12 @@
1
+ import type { MemeshDatabase } from './sqlite.js';
2
+ export declare const SESSION_DEDUPE_KEY = "session_observation_dedupe";
3
+ export declare const ZERO_EDIT_RETRACT_KEY = "session_zero_edit_retract";
4
+ export declare const FUSED_LESSON_SPLIT_KEY = "fused_lesson_split";
5
+ export declare function dedupeSessionObservations(db: MemeshDatabase): number;
6
+ export declare function bashWritesFiles(command: string): boolean;
7
+ export declare function retractZeroEditClaims(db: MemeshDatabase): number;
8
+ export declare function splitFusedLessons(db: MemeshDatabase, deps: {
9
+ deriveTitle: (type: string, observations: string[]) => string | null;
10
+ markReindexOwed: (conn: MemeshDatabase) => void;
11
+ }): number;
12
+ //# sourceMappingURL=graph-repairs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"graph-repairs.d.ts","sourceRoot":"","sources":["../../src/storage/graph-repairs.ts"],"names":[],"mappings":"AAwDA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAKlD,eAAO,MAAM,kBAAkB,+BAA+B,CAAC;AAC/D,eAAO,MAAM,qBAAqB,8BAA8B,CAAC;AACjE,eAAO,MAAM,sBAAsB,uBAAuB,CAAC;AAyB3D,wBAAgB,yBAAyB,CAAC,EAAE,EAAE,cAAc,GAAG,MAAM,CA2BpE;AAoBD,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAUxD;AAgBD,wBAAgB,qBAAqB,CAAC,EAAE,EAAE,cAAc,GAAG,MAAM,CAgChE;AA4CD,wBAAgB,iBAAiB,CAC/B,EAAE,EAAE,cAAc,EAClB,IAAI,EAAE;IACJ,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,KAAK,MAAM,GAAG,IAAI,CAAC;IACrE,eAAe,EAAE,CAAC,IAAI,EAAE,cAAc,KAAK,IAAI,CAAC;CACjD,GACA,MAAM,CA0HR"}
@@ -0,0 +1,194 @@
1
+ import { rebuildFtsIndex, runOnceMigration } from './schema.js';
2
+ import { lessonSlug } from '../core/lesson-slug.js';
3
+ import { computeSignalScore } from '../core/signal-scorer.js';
4
+ export const SESSION_DEDUPE_KEY = 'session_observation_dedupe';
5
+ export const ZERO_EDIT_RETRACT_KEY = 'session_zero_edit_retract';
6
+ export const FUSED_LESSON_SPLIT_KEY = 'fused_lesson_split';
7
+ const ZERO_EDITS = ', 0 files edited';
8
+ const ZERO_EDITS_RETRACTED = ', files edited through Bash (count not recorded before 4.8.2)';
9
+ function note(line) {
10
+ process.stderr.write(`MeMesh: ${line}\n`);
11
+ }
12
+ export function dedupeSessionObservations(db) {
13
+ let removed = -1;
14
+ runOnceMigration(db, {
15
+ key: SESSION_DEDUPE_KEY,
16
+ version: 1,
17
+ describe: 'session observation dedupe',
18
+ migrate: (conn) => {
19
+ const affected = conn
20
+ .prepare(`SELECT DISTINCT e.id FROM entities e JOIN observations o ON o.entity_id = e.id
21
+ WHERE e.name LIKE 'session-%'
22
+ GROUP BY e.id, o.content HAVING COUNT(o.id) > 1`)
23
+ .all();
24
+ removed = 0;
25
+ const del = conn.prepare(`DELETE FROM observations WHERE entity_id = ? AND id NOT IN (
26
+ SELECT MIN(id) FROM observations WHERE entity_id = ? GROUP BY content)`);
27
+ for (const row of affected)
28
+ removed += Number(del.run(row.id, row.id).changes);
29
+ if (removed > 0) {
30
+ rebuildFtsIndex(conn);
31
+ note(`removed ${removed} duplicate observation(s) from ${affected.length} session entit${affected.length === 1 ? 'y' : 'ies'} (written by 4.8.1 hooks).`);
32
+ }
33
+ },
34
+ });
35
+ return removed;
36
+ }
37
+ const BASH_WRITE_SHAPES = [
38
+ /(?:^|[^<])>\s*"?([^\s"'>|&;]+)"?\s*<<\s*['"]?\w+['"]?/,
39
+ /\bcat\s*>\s*"?([^\s"'>|&;]+)"?/,
40
+ /\btee\s+(?:-a\s+)?"?([^\s"'>|&;]+)"?/,
41
+ /\bsed\s+-i(?:\s+'')?\s+(?:'[^']*'|"[^"]*")\s+"?([^\s"'>|&;]+)"?/,
42
+ /Path\(\s*['"]([^'"]+)['"]\s*\)\s*\.write_text\(/,
43
+ /writeFileSync\(\s*['"]([^'"]+)['"]/,
44
+ ];
45
+ export function bashWritesFiles(command) {
46
+ for (const re of BASH_WRITE_SHAPES) {
47
+ for (const m of command.matchAll(new RegExp(re.source, 'g'))) {
48
+ if (m[1] && !m[1].startsWith('/dev/') && !m[1].startsWith('/tmp/'))
49
+ return true;
50
+ }
51
+ }
52
+ return false;
53
+ }
54
+ export function retractZeroEditClaims(db) {
55
+ let rewritten = -1;
56
+ runOnceMigration(db, {
57
+ key: ZERO_EDIT_RETRACT_KEY,
58
+ version: 1,
59
+ describe: 'session zero-edit retraction',
60
+ migrate: (conn) => {
61
+ const candidates = conn
62
+ .prepare(`SELECT o.id, o.content, o.entity_id FROM observations o JOIN entities e ON e.id = o.entity_id
63
+ WHERE e.name LIKE 'session-%-summary'
64
+ AND o.content LIKE 'Significant session:%${ZERO_EDITS}%'`)
65
+ .all();
66
+ const commands = conn.prepare("SELECT content FROM observations WHERE entity_id = ? AND content LIKE 'Command:%'");
67
+ const update = conn.prepare('UPDATE observations SET content = ? WHERE id = ?');
68
+ rewritten = 0;
69
+ for (const row of candidates) {
70
+ const cmds = commands.all(row.entity_id);
71
+ if (!cmds.some((c) => bashWritesFiles(c.content)))
72
+ continue;
73
+ update.run(row.content.replace(ZERO_EDITS, ZERO_EDITS_RETRACTED), row.id);
74
+ rewritten += 1;
75
+ }
76
+ if (rewritten > 0) {
77
+ rebuildFtsIndex(conn);
78
+ note(`retracted "0 files edited" on ${rewritten} session summar${rewritten === 1 ? 'y' : 'ies'} that recorded a Bash write.`);
79
+ }
80
+ },
81
+ });
82
+ return rewritten;
83
+ }
84
+ function groupLessons(rows) {
85
+ const groups = [];
86
+ for (const row of rows) {
87
+ if (row.content.startsWith('Error: ')) {
88
+ groups.push({ error: row.content.slice('Error: '.length), rows: [row] });
89
+ }
90
+ else if (groups.length > 0) {
91
+ groups[groups.length - 1].rows.push(row);
92
+ }
93
+ }
94
+ return groups;
95
+ }
96
+ export function splitFusedLessons(db, deps) {
97
+ let moved = -1;
98
+ runOnceMigration(db, {
99
+ key: FUSED_LESSON_SPLIT_KEY,
100
+ version: 1,
101
+ describe: 'fused lesson split',
102
+ migrate: (conn) => {
103
+ const buckets = conn
104
+ .prepare(`SELECT e.id, e.name, e.type, e.namespace, e.confidence, e.metadata
105
+ FROM entities e
106
+ WHERE e.type = 'lesson_learned' AND e.name LIKE 'lesson-%-other'
107
+ AND EXISTS (SELECT 1 FROM tags t WHERE t.entity_id = e.id AND t.tag = 'source:explicit')`)
108
+ .all();
109
+ const tagsOf = conn.prepare('SELECT tag FROM tags WHERE entity_id = ?');
110
+ const obsOf = conn.prepare('SELECT id, content, created_at FROM observations WHERE entity_id = ? ORDER BY id');
111
+ const findTarget = conn.prepare('SELECT id, status FROM entities WHERE name = ?');
112
+ const revive = conn.prepare("UPDATE entities SET status = 'active' WHERE id = ?");
113
+ const insertEntity = conn.prepare(`INSERT INTO entities (name, type, created_at, metadata, status, confidence, namespace, title)
114
+ VALUES (?, ?, ?, ?, 'active', ?, ?, ?)`);
115
+ const insertTag = conn.prepare('INSERT OR IGNORE INTO tags (entity_id, tag) VALUES (?, ?)');
116
+ const moveRow = conn.prepare('UPDATE observations SET entity_id = ? WHERE id = ?');
117
+ const emptied = 'NOT EXISTS (SELECT 1 FROM observations WHERE entity_id = ?)';
118
+ const dropExplicit = conn.prepare(`DELETE FROM tags WHERE entity_id = ? AND tag = 'source:explicit' AND ${emptied}`);
119
+ const archive = conn.prepare(`UPDATE entities SET status = 'archived' WHERE id = ? AND ${emptied}`);
120
+ moved = 0;
121
+ let bucketsTouched = 0;
122
+ for (const bucket of buckets) {
123
+ const groups = groupLessons(obsOf.all(bucket.id));
124
+ if (groups.length === 0)
125
+ continue;
126
+ const tags = tagsOf.all(bucket.id).map((t) => t.tag);
127
+ const project = tags.find((t) => t.startsWith('project:'))?.slice('project:'.length) ??
128
+ bucket.name.slice('lesson-'.length, -'-other'.length);
129
+ let inherited = {};
130
+ try {
131
+ const parsed = bucket.metadata ? JSON.parse(bucket.metadata) : {};
132
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed))
133
+ inherited = parsed;
134
+ }
135
+ catch { }
136
+ const severities = tags.filter((t) => t.startsWith('severity:'));
137
+ const carried = tags.filter((t) => !t.startsWith('severity:') && !t.startsWith('source:'));
138
+ if (severities.length === 1)
139
+ carried.push(severities[0]);
140
+ if (!tags.includes('source:auto-learned'))
141
+ carried.push('source:explicit');
142
+ let movedFromBucket = 0;
143
+ for (const group of groups) {
144
+ const slug = lessonSlug(group.error);
145
+ if (slug === 'other')
146
+ continue;
147
+ const name = `lesson-${project}-${slug}`;
148
+ if (name === bucket.name)
149
+ continue;
150
+ let target = findTarget.get(name);
151
+ if (target) {
152
+ if (target.status !== 'active')
153
+ revive.run(target.id);
154
+ }
155
+ else {
156
+ const contents = group.rows.map((o) => o.content);
157
+ const title = deps.deriveTitle(bucket.type, contents);
158
+ const metadata = {
159
+ ...inherited,
160
+ split_from: bucket.name,
161
+ signal_score: computeSignalScore({ type: bucket.type, name, observations: contents, tags: carried }),
162
+ };
163
+ if (title)
164
+ metadata.title_source = 'heuristic';
165
+ else
166
+ delete metadata.title_source;
167
+ delete metadata.guard;
168
+ delete metadata.evidence_for;
169
+ delete metadata.previous_namespace;
170
+ const inserted = insertEntity.run(name, bucket.type, group.rows[0].created_at, JSON.stringify(metadata), bucket.confidence, bucket.namespace, title);
171
+ target = { id: Number(inserted.lastInsertRowid), status: 'active' };
172
+ for (const tag of carried)
173
+ insertTag.run(target.id, tag);
174
+ }
175
+ for (const row of group.rows)
176
+ moveRow.run(target.id, row.id);
177
+ moved += 1;
178
+ movedFromBucket += 1;
179
+ }
180
+ dropExplicit.run(bucket.id, bucket.id);
181
+ archive.run(bucket.id, bucket.id);
182
+ if (movedFromBucket > 0)
183
+ bucketsTouched += 1;
184
+ }
185
+ if (moved > 0) {
186
+ rebuildFtsIndex(conn);
187
+ deps.markReindexOwed(conn);
188
+ note(`moved ${moved} lesson(s) out of ${bucketsTouched} "-other" bucket(s) into their own entities; run 'memesh reindex' to refresh their vectors.`);
189
+ }
190
+ },
191
+ });
192
+ return moved;
193
+ }
194
+ //# sourceMappingURL=graph-repairs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"graph-repairs.js","sourceRoot":"","sources":["../../src/storage/graph-repairs.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../../src/transports/http/server.ts"],"names":[],"mappings":";AA2BA,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAkB,MAAM,6BAA6B,CAAC;AAiBjG,QAAA,MAAM,GAAG,6CAAY,CAAC;AA8CtB,wBAAgB,iBAAiB,CAAC,GAAG,EAAE;IAAE,EAAE,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAG/D;AAwuCD,wBAAgB,WAAW,CACzB,IAAI,SAAO,EACX,IAAI,SAAO,EACX,IAAI,CAAC,EAAE;IACL,WAAW,CAAC,EAAE,OAAO,CAAC;IAUtB,eAAe,CAAC,EAAE,OAAO,CAAC;IAE1B,eAAe,CAAC,EAAE,OAAO,cAAc,CAAC;IACxC,mBAAmB,CAAC,EAAE,OAAO,kBAAkB,CAAC;CACjD,GACA,UAAU,CAAC,OAAO,GAAG,CAAC,MAAM,CAAC,CA8I/B;AAID,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAElE;AAwBD,OAAO,EAAE,GAAG,EAAE,CAAC"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../../src/transports/http/server.ts"],"names":[],"mappings":";AAmDA,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAkB,MAAM,6BAA6B,CAAC;AAiBjG,QAAA,MAAM,GAAG,6CAAY,CAAC;AA8CtB,wBAAgB,iBAAiB,CAAC,GAAG,EAAE;IAAE,EAAE,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAG/D;AA82CD,wBAAgB,WAAW,CACzB,IAAI,SAAO,EACX,IAAI,SAAO,EACX,IAAI,CAAC,EAAE;IACL,WAAW,CAAC,EAAE,OAAO,CAAC;IAUtB,eAAe,CAAC,EAAE,OAAO,CAAC;IAE1B,eAAe,CAAC,EAAE,OAAO,cAAc,CAAC;IACxC,mBAAmB,CAAC,EAAE,OAAO,kBAAkB,CAAC;CACjD,GACA,UAAU,CAAC,OAAO,GAAG,CAAC,MAAM,CAAC,CA8I/B;AAID,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAElE;AAwBD,OAAO,EAAE,GAAG,EAAE,CAAC"}
@@ -3,10 +3,10 @@ import express from 'express';
3
3
  import { rateLimit } from 'express-rate-limit';
4
4
  import { z } from 'zod';
5
5
  import { randomBytes, timingSafeEqual } from 'crypto';
6
- import { openDatabase, closeDatabase, getDatabase } from '../../db.js';
7
- import { remember, recallWithConflicts, forget, exportMemories, importMemories, learn } from '../../core/operations.js';
6
+ import { openDatabase, closeDatabase, getDatabase, getStoredEmbeddingDimension, getPendingReindexInfo, readVectorGeneration, } from '../../db.js';
7
+ import { remember, recallWithConflicts, forget, exportMemories, importMemories, learn, reindex, countMissingVectors, } from '../../core/operations.js';
8
8
  import { KnowledgeGraph } from '../../knowledge-graph.js';
9
- import { logCapabilities, readConfig, updateConfig, detectCapabilities } from '../../core/config.js';
9
+ import { logCapabilities, readConfig, updateConfig, detectCapabilities, getEmbeddingDimension, } from '../../core/config.js';
10
10
  import { languageValueError } from '../../core/output-language.js';
11
11
  import { computePatterns } from '../../core/patterns.js';
12
12
  import { computeAnalytics, computePmAnalytics } from '../../core/analytics.js';
@@ -241,8 +241,21 @@ app.get('/dashboard', (_req, res) => {
241
241
  app.get('/v1/health', (_req, res) => {
242
242
  try {
243
243
  const db = getDatabase();
244
- const count = db.prepare('SELECT COUNT(*) as c FROM entities').get();
245
- res.json({ success: true, data: { status: 'ok', version: packageVersion, entity_count: count.c } });
244
+ const count = db.prepare(`
245
+ SELECT
246
+ COUNT(*) AS c,
247
+ SUM(CASE WHEN json_extract(metadata, '$.demo') = 1 THEN 1 ELSE 0 END) AS demo_c
248
+ FROM entities
249
+ `).get();
250
+ res.json({
251
+ success: true,
252
+ data: {
253
+ status: 'ok',
254
+ version: packageVersion,
255
+ entity_count: count.c,
256
+ demo_entity_count: count.demo_c ?? 0,
257
+ },
258
+ });
246
259
  }
247
260
  catch (err) {
248
261
  const message = err instanceof Error ? err.message : String(err);
@@ -439,6 +452,10 @@ const ConfigBody = z.object({
439
452
  apiKey: z.string().optional(),
440
453
  keepKeyFrom: z.number().int().nonnegative().nullable().optional(),
441
454
  })).optional(),
455
+ embedder: z.union([
456
+ z.object({ provider: z.enum(['openai', 'ollama']) }),
457
+ z.null(),
458
+ ]).optional(),
442
459
  autoCapture: z.boolean().optional(),
443
460
  sessionLimit: z.number().int().min(1).max(100).optional(),
444
461
  autoUpdate: z.enum(['off', 'patch', 'minor', 'major']).optional(),
@@ -451,6 +468,9 @@ const ConfigBody = z.object({
451
468
  }).strict();
452
469
  app.post('/v1/config', (req, res) => handlePost(ConfigBody, req, res, (data) => {
453
470
  const before = readConfig();
471
+ if (data.embedder !== undefined && reindexJob?.state === 'running') {
472
+ throw new Error('The search index is being rebuilt. Wait for it to finish before changing the embedding provider.');
473
+ }
454
474
  if (data.llm && data.llm.apiKey === API_KEY_MASK) {
455
475
  if (before.llm && before.llm.provider === data.llm.provider && before.llm.apiKey) {
456
476
  data.llm.apiKey = before.llm.apiKey;
@@ -463,17 +483,107 @@ app.post('/v1/config', (req, res) => handlePost(ConfigBody, req, res, (data) =>
463
483
  data.llmFallbacks = preserveFallbackApiKeys(data.llmFallbacks, before.llmFallbacks);
464
484
  }
465
485
  const updated = updateConfig(data);
486
+ if (data.embedder && before.embedder?.provider !== data.embedder.provider) {
487
+ reindexJob = null;
488
+ }
466
489
  return maskLlmSecrets(updated);
467
490
  }));
491
+ let reindexJob = null;
492
+ function safeReindexDiagnostic(message) {
493
+ return redactUserPaths(redactSecrets(message));
494
+ }
495
+ function reindexStatus() {
496
+ const config = readConfig();
497
+ const embeddings = detectCapabilities(config).embeddings;
498
+ const configuredProvider = embeddings === 'openai' || embeddings === 'ollama'
499
+ ? embeddings
500
+ : null;
501
+ const pendingReindex = getPendingReindexInfo();
502
+ const generationRead = readVectorGeneration();
503
+ const generation = generationRead.state === 'unreadable'
504
+ ? { ...generationRead, detail: safeReindexDiagnostic(generationRead.detail) }
505
+ : generationRead;
506
+ const configuredDimension = getEmbeddingDimension(config);
507
+ const storedDimension = getStoredEmbeddingDimension();
508
+ const missingVectors = countMissingVectors(getDatabase());
509
+ const retryNeeded = pendingReindex !== null
510
+ || generation.state !== 'none'
511
+ || configuredDimension !== storedDimension
512
+ || missingVectors > 0;
513
+ const status = reindexJob?.state === 'running'
514
+ ? 'running'
515
+ : reindexJob?.state === 'failed'
516
+ ? 'failed'
517
+ : retryNeeded
518
+ ? 'retry-needed'
519
+ : reindexJob?.state === 'succeeded'
520
+ ? 'succeeded'
521
+ : 'idle';
522
+ return {
523
+ status,
524
+ job: reindexJob === null ? null : {
525
+ id: reindexJob.id,
526
+ state: reindexJob.state,
527
+ processed: reindexJob.processed,
528
+ total: reindexJob.total,
529
+ startedAt: reindexJob.startedAt,
530
+ finishedAt: reindexJob.finishedAt,
531
+ },
532
+ configuredProvider,
533
+ configuredDimension,
534
+ storedDimension,
535
+ pendingReindex,
536
+ missingVectors,
537
+ generation,
538
+ result: reindexJob?.result ?? null,
539
+ error: reindexJob?.error ?? null,
540
+ };
541
+ }
542
+ app.get('/v1/reindex', (_req, res) => handleGet(res, reindexStatus));
543
+ app.post('/v1/reindex', (_req, res) => {
544
+ if (reindexJob?.state !== 'running') {
545
+ reindexJob = {
546
+ id: randomBytes(8).toString('hex'),
547
+ state: 'running',
548
+ processed: 0,
549
+ total: 0,
550
+ startedAt: new Date().toISOString(),
551
+ finishedAt: null,
552
+ result: null,
553
+ error: null,
554
+ };
555
+ const job = reindexJob;
556
+ void reindex({
557
+ onProgress: ({ processed, total }) => {
558
+ job.processed = processed;
559
+ job.total = total;
560
+ },
561
+ }).then((result) => {
562
+ job.result = result;
563
+ const incomplete = result.failed > 0 || result.generationSwapped === false;
564
+ job.state = incomplete ? 'failed' : 'succeeded';
565
+ if (incomplete) {
566
+ job.error = 'The new search index is incomplete. The previous index is still active; retry the rebuild.';
567
+ }
568
+ job.finishedAt = new Date().toISOString();
569
+ }).catch((err) => {
570
+ job.state = 'failed';
571
+ job.error = safeReindexDiagnostic(err instanceof Error ? err.message : 'The rebuild failed.');
572
+ job.finishedAt = new Date().toISOString();
573
+ });
574
+ }
575
+ res.status(202).json({ success: true, data: reindexStatus() });
576
+ });
468
577
  const ConfigTestBody = z.object({
469
578
  provider: z.enum(['anthropic', 'openai', 'ollama']),
470
579
  apiKey: z.string().max(500).optional(),
471
580
  host: z.string().max(500).optional(),
581
+ model: z.string().min(1).max(200).optional(),
472
582
  fallbackIndex: z.number().int().nonnegative().optional(),
473
583
  });
474
584
  app.post('/v1/config/test', (req, res) => handlePost(ConfigTestBody, req, res, async (data) => {
475
585
  const { probeProvider } = await import('../../core/llm-validator.js');
476
- const { provider, host, fallbackIndex } = data;
586
+ const { provider, host, model, fallbackIndex } = data;
477
587
  let { apiKey } = data;
478
588
  if (!apiKey && (provider === 'anthropic' || provider === 'openai')) {
479
589
  const existing = readConfig();
@@ -486,7 +596,7 @@ app.post('/v1/config/test', (req, res) => handlePost(ConfigTestBody, req, res, a
486
596
  apiKey = existing.llm.apiKey;
487
597
  }
488
598
  }
489
- return probeProvider(provider, apiKey, host);
599
+ return probeProvider(provider, apiKey, host, model);
490
600
  }, { errorStatus: 500, errorCode: 'server.internal' }));
491
601
  app.get('/v1/update-status', (req, res) => handleGet(res, async () => {
492
602
  const cached = req.query.cached === '1' || req.query.cached === 'true';