@unclick/mcp-server 0.3.0 → 0.3.1

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 (131) hide show
  1. package/README.md +34 -13
  2. package/dist/abn-tool.js +1 -1
  3. package/dist/bgg-tool.js +1 -1
  4. package/dist/carboninterface-tool.js +1 -1
  5. package/dist/cards/card.d.ts +9 -0
  6. package/dist/cards/card.d.ts.map +1 -0
  7. package/dist/cards/card.js +4 -0
  8. package/dist/cards/card.js.map +1 -0
  9. package/dist/cards/search-memory-card.d.ts +11 -0
  10. package/dist/cards/search-memory-card.d.ts.map +1 -0
  11. package/dist/cards/search-memory-card.js +75 -0
  12. package/dist/cards/search-memory-card.js.map +1 -0
  13. package/dist/cards/search-memory-card.test.d.ts +2 -0
  14. package/dist/cards/search-memory-card.test.d.ts.map +1 -0
  15. package/dist/cards/search-memory-card.test.js +59 -0
  16. package/dist/cards/search-memory-card.test.js.map +1 -0
  17. package/dist/catalog.js +36 -36
  18. package/dist/catalog.js.map +1 -1
  19. package/dist/client.d.ts.map +1 -1
  20. package/dist/client.js +96 -6
  21. package/dist/client.js.map +1 -1
  22. package/dist/converter-tools.js +1 -1
  23. package/dist/crews-tool.d.ts +12 -0
  24. package/dist/crews-tool.d.ts.map +1 -0
  25. package/dist/crews-tool.js +125 -0
  26. package/dist/crews-tool.js.map +1 -0
  27. package/dist/gdelt-tool.js +4 -4
  28. package/dist/hackernews-tool.js +1 -1
  29. package/dist/index.js +0 -0
  30. package/dist/line-tool.js +1 -1
  31. package/dist/local-catalog-handlers.js +1 -1
  32. package/dist/local-catalog-handlers.js.map +1 -1
  33. package/dist/local-tools.js +7 -7
  34. package/dist/local-tools.js.map +1 -1
  35. package/dist/memory/__tests__/bitemporal.test.d.ts +8 -0
  36. package/dist/memory/__tests__/bitemporal.test.d.ts.map +1 -0
  37. package/dist/memory/__tests__/bitemporal.test.js +148 -0
  38. package/dist/memory/__tests__/bitemporal.test.js.map +1 -0
  39. package/dist/memory/__tests__/hybrid-search.test.d.ts +14 -0
  40. package/dist/memory/__tests__/hybrid-search.test.d.ts.map +1 -0
  41. package/dist/memory/__tests__/hybrid-search.test.js +304 -0
  42. package/dist/memory/__tests__/hybrid-search.test.js.map +1 -0
  43. package/dist/memory/agent.d.ts +34 -0
  44. package/dist/memory/agent.d.ts.map +1 -0
  45. package/dist/memory/agent.js +69 -0
  46. package/dist/memory/agent.js.map +1 -0
  47. package/dist/memory/conflicts.d.ts +48 -0
  48. package/dist/memory/conflicts.d.ts.map +1 -0
  49. package/dist/memory/conflicts.js +209 -0
  50. package/dist/memory/conflicts.js.map +1 -0
  51. package/dist/memory/db.d.ts +18 -3
  52. package/dist/memory/db.d.ts.map +1 -1
  53. package/dist/memory/db.js +133 -11
  54. package/dist/memory/db.js.map +1 -1
  55. package/dist/memory/device.d.ts +20 -0
  56. package/dist/memory/device.d.ts.map +1 -0
  57. package/dist/memory/device.js +48 -0
  58. package/dist/memory/device.js.map +1 -0
  59. package/dist/memory/embeddings.d.ts +10 -0
  60. package/dist/memory/embeddings.d.ts.map +1 -0
  61. package/dist/memory/embeddings.js +40 -0
  62. package/dist/memory/embeddings.js.map +1 -0
  63. package/dist/memory/handlers.d.ts.map +1 -1
  64. package/dist/memory/handlers.js +98 -4
  65. package/dist/memory/handlers.js.map +1 -1
  66. package/dist/memory/instrumentation.d.ts +38 -0
  67. package/dist/memory/instrumentation.d.ts.map +1 -0
  68. package/dist/memory/instrumentation.js +97 -0
  69. package/dist/memory/instrumentation.js.map +1 -0
  70. package/dist/memory/load-events.d.ts +18 -0
  71. package/dist/memory/load-events.d.ts.map +1 -0
  72. package/dist/memory/load-events.js +61 -0
  73. package/dist/memory/load-events.js.map +1 -0
  74. package/dist/memory/local.d.ts +4 -1
  75. package/dist/memory/local.d.ts.map +1 -1
  76. package/dist/memory/local.js +14 -0
  77. package/dist/memory/local.js.map +1 -1
  78. package/dist/memory/session-state.d.ts +37 -0
  79. package/dist/memory/session-state.d.ts.map +1 -0
  80. package/dist/memory/session-state.js +82 -0
  81. package/dist/memory/session-state.js.map +1 -0
  82. package/dist/memory/supabase.d.ts +75 -5
  83. package/dist/memory/supabase.d.ts.map +1 -1
  84. package/dist/memory/supabase.js +584 -83
  85. package/dist/memory/supabase.js.map +1 -1
  86. package/dist/memory/tenant-settings.d.ts +33 -0
  87. package/dist/memory/tenant-settings.d.ts.map +1 -0
  88. package/dist/memory/tenant-settings.js +79 -0
  89. package/dist/memory/tenant-settings.js.map +1 -0
  90. package/dist/memory/tool-awareness.d.ts +66 -0
  91. package/dist/memory/tool-awareness.d.ts.map +1 -0
  92. package/dist/memory/tool-awareness.js +307 -0
  93. package/dist/memory/tool-awareness.js.map +1 -0
  94. package/dist/memory/types.d.ts +18 -2
  95. package/dist/memory/types.d.ts.map +1 -1
  96. package/dist/numbers-tool.js +2 -2
  97. package/dist/openfoodfacts-tool.js +1 -1
  98. package/dist/openmeteo-tool.js +1 -1
  99. package/dist/radiobrowser-tool.js +2 -2
  100. package/dist/server.d.ts.map +1 -1
  101. package/dist/server.js +759 -55
  102. package/dist/server.js.map +1 -1
  103. package/dist/signals/emit.d.ts +11 -0
  104. package/dist/signals/emit.d.ts.map +1 -0
  105. package/dist/signals/emit.js +26 -0
  106. package/dist/signals/emit.js.map +1 -0
  107. package/dist/testpass-tool.d.ts +12 -0
  108. package/dist/testpass-tool.d.ts.map +1 -0
  109. package/dist/testpass-tool.js +121 -0
  110. package/dist/testpass-tool.js.map +1 -0
  111. package/dist/tool-wiring.d.ts +320 -4
  112. package/dist/tool-wiring.d.ts.map +1 -1
  113. package/dist/tool-wiring.js +246 -5
  114. package/dist/tool-wiring.js.map +1 -1
  115. package/dist/trivia-tool.js +5 -5
  116. package/dist/usgs-tool.js +1 -1
  117. package/dist/uxpass-tool.d.ts +24 -0
  118. package/dist/uxpass-tool.d.ts.map +1 -0
  119. package/dist/uxpass-tool.js +165 -0
  120. package/dist/uxpass-tool.js.map +1 -0
  121. package/dist/vault-bridge.js +7 -7
  122. package/dist/vercel-tool.d.ts +3 -0
  123. package/dist/vercel-tool.d.ts.map +1 -1
  124. package/dist/vercel-tool.js +198 -7
  125. package/dist/vercel-tool.js.map +1 -1
  126. package/dist/web-tools.d.ts +62 -0
  127. package/dist/web-tools.d.ts.map +1 -0
  128. package/dist/web-tools.js +271 -0
  129. package/dist/web-tools.js.map +1 -0
  130. package/package.json +6 -3
  131. package/server.json +1 -1
@@ -0,0 +1,148 @@
1
+ /**
2
+ * Acceptance tests for Chunk 2: bi-temporal schema, provenance, and blob preservation.
3
+ *
4
+ * These tests are skipped unless SUPABASE_URL + SUPABASE_SERVICE_ROLE_KEY are present.
5
+ * Run with: tsx --test src/memory/__tests__/bitemporal.test.ts
6
+ */
7
+ import { describe, it, before, after } from "node:test";
8
+ import assert from "node:assert/strict";
9
+ import { createHash } from "node:crypto";
10
+ // ---- Pure unit helpers (no I/O) ----
11
+ function contentHash(text) {
12
+ return createHash("sha256").update(text.trim().toLowerCase()).digest("hex");
13
+ }
14
+ function cosine(a, b) {
15
+ let dot = 0, na = 0, nb = 0;
16
+ for (let i = 0; i < a.length; i++) {
17
+ dot += a[i] * b[i];
18
+ na += a[i] * a[i];
19
+ nb += b[i] * b[i];
20
+ }
21
+ return na === 0 || nb === 0 ? 0 : dot / (Math.sqrt(na) * Math.sqrt(nb));
22
+ }
23
+ // Near-dup collapse: given scored candidates with embeddings, return de-duped set.
24
+ // Mirrors the SQL dominated CTE logic.
25
+ function nearDupCollapse(candidates, threshold = 0.9) {
26
+ const dominated = new Set();
27
+ for (let i = 0; i < candidates.length; i++) {
28
+ for (let j = 0; j < candidates.length; j++) {
29
+ if (i === j)
30
+ continue;
31
+ const sim = cosine(candidates[i].embedding, candidates[j].embedding);
32
+ if (sim >= threshold) {
33
+ // The lower-scored one is dominated; ties broken by id (LEAST)
34
+ if (candidates[j].score < candidates[i].score ||
35
+ (candidates[j].score === candidates[i].score && candidates[j].id > candidates[i].id)) {
36
+ dominated.add(candidates[j].id);
37
+ }
38
+ }
39
+ }
40
+ }
41
+ return candidates.filter((c) => !dominated.has(c.id)).map((c) => c.id);
42
+ }
43
+ // ---- Unit tests (always run) ----
44
+ describe("contentHash", () => {
45
+ it("is stable for identical text", () => {
46
+ const h1 = contentHash("User prefers TypeScript");
47
+ const h2 = contentHash("User prefers TypeScript");
48
+ assert.equal(h1, h2);
49
+ });
50
+ it("normalises whitespace and case", () => {
51
+ const h1 = contentHash(" USER PREFERS TYPESCRIPT ");
52
+ const h2 = contentHash("user prefers typescript");
53
+ assert.equal(h1, h2);
54
+ });
55
+ it("differs for distinct text", () => {
56
+ assert.notEqual(contentHash("fact A"), contentHash("fact B"));
57
+ });
58
+ });
59
+ describe("nearDupCollapse (pure)", () => {
60
+ it("keeps both candidates when cosine < threshold", () => {
61
+ // Orthogonal vectors - cosine = 0
62
+ const a = { id: "a", score: 0.9, embedding: [1, 0, 0] };
63
+ const b = { id: "b", score: 0.8, embedding: [0, 1, 0] };
64
+ const kept = nearDupCollapse([a, b]);
65
+ assert.deepEqual(kept.sort(), ["a", "b"]);
66
+ });
67
+ it("drops the lower-scored duplicate when cosine >= 0.9", () => {
68
+ // Nearly identical vectors
69
+ const base = [0.9, 0.1, 0.05];
70
+ const near = [0.91, 0.1, 0.04];
71
+ const a = { id: "a", score: 0.9, embedding: base };
72
+ const b = { id: "b", score: 0.5, embedding: near };
73
+ const kept = nearDupCollapse([a, b]);
74
+ assert.deepEqual(kept, ["a"]);
75
+ });
76
+ it("breaks ties by id (LEAST wins)", () => {
77
+ const v = [1, 0];
78
+ const a = { id: "aaa", score: 0.7, embedding: v };
79
+ const b = { id: "zzz", score: 0.7, embedding: v };
80
+ const kept = nearDupCollapse([a, b]);
81
+ assert.deepEqual(kept, ["aaa"]);
82
+ });
83
+ it("handles single candidate", () => {
84
+ const kept = nearDupCollapse([{ id: "x", score: 1.0, embedding: [1, 0] }]);
85
+ assert.deepEqual(kept, ["x"]);
86
+ });
87
+ });
88
+ // ---- Integration tests (skipped without live creds) ----
89
+ const LIVE = !!(process.env.SUPABASE_URL && process.env.SUPABASE_SERVICE_ROLE_KEY);
90
+ describe("Supabase bi-temporal integration", { skip: !LIVE }, () => {
91
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
92
+ let _db;
93
+ let testSession;
94
+ before(async () => {
95
+ const { SupabaseBackend } = await import("../supabase.js");
96
+ _db = new SupabaseBackend();
97
+ testSession = `test-chunk2-${Date.now()}`;
98
+ });
99
+ it("exact-hash dedup: inserting same fact twice returns same id", async () => {
100
+ const { SupabaseBackend } = await import("../supabase.js");
101
+ const backend = new SupabaseBackend();
102
+ const factText = `dedup-test-${Date.now()}`;
103
+ const r1 = await backend.addFact({ fact: factText, category: "test", confidence: 0.9, source_session_id: testSession });
104
+ const r2 = await backend.addFact({ fact: factText, category: "test", confidence: 0.9, source_session_id: testSession });
105
+ assert.equal(r1.id, r2.id, "second insert should return existing fact id");
106
+ });
107
+ it("bi-temporal: invalidated fact excluded from search, visible via as_of", async () => {
108
+ const { SupabaseBackend } = await import("../supabase.js");
109
+ const backend = new SupabaseBackend();
110
+ const factText = `invalidate-test-${Date.now()}`;
111
+ // Insert fact and record timestamp
112
+ const { id } = await backend.addFact({ fact: factText, category: "test", confidence: 0.9, source_session_id: testSession });
113
+ const afterInsert = new Date().toISOString();
114
+ // Invalidate it
115
+ await backend.invalidateFact({ fact_id: id, reason: "test cleanup", session_id: testSession });
116
+ // Search now - should NOT appear
117
+ const nowResults = await backend.searchFacts(factText);
118
+ const ids = nowResults.map((r) => r.id);
119
+ assert.ok(!ids.includes(id), "invalidated fact should not appear in current search");
120
+ // Search as_of before invalidation - should appear
121
+ const pastResults = await backend.searchMemory(factText, 10, afterInsert);
122
+ const pastIds = pastResults.map((r) => r.id);
123
+ assert.ok(pastIds.includes(id), "invalidated fact should appear in as_of query before invalidation");
124
+ });
125
+ it("preserve_as_blob: stores canonical doc and returns fact_ids", async () => {
126
+ if (!process.env.OPENAI_API_KEY) {
127
+ console.log(" skipping preserve_as_blob test (no OPENAI_API_KEY)");
128
+ return;
129
+ }
130
+ const { SupabaseBackend } = await import("../supabase.js");
131
+ const backend = new SupabaseBackend();
132
+ const blob = `User works at Acme Corp. They prefer TypeScript. They live in Seattle. Session: ${testSession}`;
133
+ const result = await backend.addFact({
134
+ fact: blob,
135
+ category: "blob",
136
+ confidence: 0.9,
137
+ source_session_id: testSession,
138
+ preserve_as_blob: true,
139
+ });
140
+ assert.ok(result.id, "should return canonical doc id");
141
+ assert.ok(Array.isArray(result.fact_ids), "should return extracted fact_ids array");
142
+ assert.ok(result.fact_ids.length >= 1, "should have extracted at least one atomic fact");
143
+ });
144
+ after(async () => {
145
+ // Best-effort: nothing to clean up since invalidated facts stay in DB
146
+ });
147
+ });
148
+ //# sourceMappingURL=bitemporal.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bitemporal.test.js","sourceRoot":"","sources":["../../../src/memory/__tests__/bitemporal.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Tests for hybrid RRF search logic.
3
+ *
4
+ * Run: npx tsx --test src/memory/__tests__/hybrid-search.test.ts
5
+ * (from packages/mcp-server directory)
6
+ *
7
+ * Tests:
8
+ * 1. RRF scoring math (pure function, no DB required)
9
+ * 2. embedText graceful null return when OPENAI_API_KEY absent
10
+ * 3. Acceptance test: semantic query finds fact that keyword search misses
11
+ * (requires SUPABASE_URL + SUPABASE_SERVICE_ROLE_KEY + OPENAI_API_KEY)
12
+ */
13
+ export {};
14
+ //# sourceMappingURL=hybrid-search.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hybrid-search.test.d.ts","sourceRoot":"","sources":["../../../src/memory/__tests__/hybrid-search.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG"}
@@ -0,0 +1,304 @@
1
+ /**
2
+ * Tests for hybrid RRF search logic.
3
+ *
4
+ * Run: npx tsx --test src/memory/__tests__/hybrid-search.test.ts
5
+ * (from packages/mcp-server directory)
6
+ *
7
+ * Tests:
8
+ * 1. RRF scoring math (pure function, no DB required)
9
+ * 2. embedText graceful null return when OPENAI_API_KEY absent
10
+ * 3. Acceptance test: semantic query finds fact that keyword search misses
11
+ * (requires SUPABASE_URL + SUPABASE_SERVICE_ROLE_KEY + OPENAI_API_KEY)
12
+ */
13
+ import { test, describe } from "node:test";
14
+ import assert from "node:assert/strict";
15
+ function computeRRFScore(input) {
16
+ const k = input.k ?? 60;
17
+ const kwContrib = input.kwRank !== undefined ? 1 / (k + input.kwRank) : 0;
18
+ const vecContrib = input.vecRank !== undefined ? 1 / (k + input.vecRank) : 0;
19
+ const rrf = kwContrib + vecContrib;
20
+ const recency = Math.exp(-input.ageDays / 90);
21
+ const final = rrf * input.confidence * recency;
22
+ return { rrf, final };
23
+ }
24
+ describe("RRF scoring math", () => {
25
+ test("row in both lanes scores higher than row in one lane", () => {
26
+ const both = computeRRFScore({ kwRank: 1, vecRank: 1, confidence: 1, ageDays: 0 });
27
+ const kwOnly = computeRRFScore({ kwRank: 1, confidence: 1, ageDays: 0 });
28
+ const vecOnly = computeRRFScore({ vecRank: 1, confidence: 1, ageDays: 0 });
29
+ assert.ok(both.rrf > kwOnly.rrf, "both > kw-only");
30
+ assert.ok(both.rrf > vecOnly.rrf, "both > vec-only");
31
+ });
32
+ test("RRF rank 1 beats rank 50 in same lane", () => {
33
+ const top = computeRRFScore({ kwRank: 1, confidence: 1, ageDays: 0 });
34
+ const bottom = computeRRFScore({ kwRank: 50, confidence: 1, ageDays: 0 });
35
+ assert.ok(top.rrf > bottom.rrf);
36
+ });
37
+ test("recency decay: 0-day-old > 90-day-old > 180-day-old", () => {
38
+ const base = { kwRank: 1, confidence: 1, k: 60 };
39
+ const fresh = computeRRFScore({ ...base, ageDays: 0 });
40
+ const mid = computeRRFScore({ ...base, ageDays: 90 });
41
+ const old = computeRRFScore({ ...base, ageDays: 180 });
42
+ assert.ok(fresh.final > mid.final, "fresh > mid");
43
+ assert.ok(mid.final > old.final, "mid > old");
44
+ // 90-day decay factor should be ~1/e ≈ 0.368
45
+ assert.ok(Math.abs(mid.final / fresh.final - Math.exp(-1)) < 0.001, "90d decay ≈ 1/e");
46
+ });
47
+ test("confidence 0.9 row can beat confidence 1.0 row with better RRF", () => {
48
+ // A result ranked 1st in both lanes at 0.9 confidence should beat a result
49
+ // ranked 50th in one lane at 1.0 confidence with same recency.
50
+ const highRRF = computeRRFScore({ kwRank: 1, vecRank: 1, confidence: 0.9, ageDays: 0 });
51
+ const lowRRF = computeRRFScore({ kwRank: 50, confidence: 1.0, ageDays: 0 });
52
+ assert.ok(highRRF.final > lowRRF.final);
53
+ });
54
+ test("RRF k=60 exact values for rank 1", () => {
55
+ // 1/(60+1) = 0.016393...
56
+ const score = computeRRFScore({ kwRank: 1, confidence: 1, ageDays: 0 });
57
+ assert.ok(Math.abs(score.rrf - 1 / 61) < 1e-10);
58
+ });
59
+ test("row absent from both lanes scores 0", () => {
60
+ const score = computeRRFScore({ confidence: 1, ageDays: 0 });
61
+ assert.equal(score.rrf, 0);
62
+ assert.equal(score.final, 0);
63
+ });
64
+ });
65
+ // ─── 2. embedText returns null when key absent ────────────────────────────────
66
+ describe("embedText", () => {
67
+ test("returns null when OPENAI_API_KEY is not set", async () => {
68
+ // Ensure the env var is absent for this test
69
+ const saved = process.env.OPENAI_API_KEY;
70
+ delete process.env.OPENAI_API_KEY;
71
+ try {
72
+ // Dynamic import so it picks up the (now absent) env var at call time
73
+ const { embedText } = await import("../embeddings.js");
74
+ const result = await embedText("test query");
75
+ assert.equal(result, null);
76
+ }
77
+ finally {
78
+ if (saved !== undefined)
79
+ process.env.OPENAI_API_KEY = saved;
80
+ }
81
+ });
82
+ });
83
+ // ─── 2b. Keyword-fallback regression (P0) ─────────────────────────────────────
84
+ //
85
+ // Reproduces the production bug: a fact with NULL embedding whose proper-noun
86
+ // content does not survive plainto_tsquery('english', ...) tokenization.
87
+ // Hybrid returns []. ILIKE fallback must surface the row anyway.
88
+ describe("acceptance: keyword fallback restores search when hybrid returns []", () => {
89
+ test("ILIKE fallback finds proper-noun fact with NULL embedding", async () => {
90
+ const url = process.env.TEST_SUPABASE_URL ?? process.env.SUPABASE_URL;
91
+ const key = process.env.TEST_SUPABASE_SERVICE_ROLE_KEY ?? process.env.SUPABASE_SERVICE_ROLE_KEY;
92
+ if (!url || !key) {
93
+ console.log(" [skipped] set SUPABASE_URL + SUPABASE_SERVICE_ROLE_KEY to run");
94
+ return;
95
+ }
96
+ const { createClient } = await import("@supabase/supabase-js");
97
+ const supabase = createClient(url, key, { auth: { persistSession: false, autoRefreshToken: false } });
98
+ const factText = "Test owner is Chris Byrne for P0 search-memory regression";
99
+ const { data: inserted, error: insertErr } = await supabase
100
+ .from("extracted_facts")
101
+ .insert({
102
+ fact: factText,
103
+ category: "test",
104
+ confidence: 0.9,
105
+ status: "active",
106
+ source_type: "test",
107
+ decay_tier: "hot",
108
+ // Deliberately leave embedding NULL to simulate legacy / un-backfilled rows
109
+ })
110
+ .select("id")
111
+ .single();
112
+ assert.ok(!insertErr, `insert failed: ${insertErr?.message}`);
113
+ const factId = inserted.id;
114
+ try {
115
+ // Force the keyword path by removing OPENAI_API_KEY: searchMemory will
116
+ // skip the hybrid lane and exercise keywordFallback directly.
117
+ const savedOpenAI = process.env.OPENAI_API_KEY;
118
+ delete process.env.OPENAI_API_KEY;
119
+ try {
120
+ const { SupabaseBackend } = await import("../supabase.js");
121
+ const backend = new SupabaseBackend({
122
+ url,
123
+ serviceRoleKey: key,
124
+ tenancy: { mode: "byod" },
125
+ });
126
+ const results = (await backend.searchMemory("Chris", 10));
127
+ assert.ok(Array.isArray(results), "fallback should return an array");
128
+ const ids = results.map((r) => r.id);
129
+ assert.ok(ids.includes(factId), `Expected fact ${factId} via ILIKE fallback. Got ${ids.length} rows: ${JSON.stringify(results.slice(0, 3))}`);
130
+ console.log(` [passed] keyword fallback surfaced fact at position ${ids.indexOf(factId) + 1}`);
131
+ }
132
+ finally {
133
+ if (savedOpenAI !== undefined)
134
+ process.env.OPENAI_API_KEY = savedOpenAI;
135
+ }
136
+ }
137
+ finally {
138
+ await supabase.from("extracted_facts").delete().eq("id", factId);
139
+ }
140
+ });
141
+ // Phrase-query regression: a multi-word query like "active Fishbowl topic"
142
+ // used to ILIKE the literal whole string and miss any fact whose words were
143
+ // present but interleaved with other words. The fix tokenizes the query and
144
+ // ANDs each token; if AND returns nothing it degrades to OR-of-tokens.
145
+ test("multi-word query finds facts containing all tokens in any order (AND mode)", async () => {
146
+ const url = process.env.TEST_SUPABASE_URL ?? process.env.SUPABASE_URL;
147
+ const key = process.env.TEST_SUPABASE_SERVICE_ROLE_KEY ?? process.env.SUPABASE_SERVICE_ROLE_KEY;
148
+ if (!url || !key) {
149
+ console.log(" [skipped] set SUPABASE_URL + SUPABASE_SERVICE_ROLE_KEY to run");
150
+ return;
151
+ }
152
+ const { createClient } = await import("@supabase/supabase-js");
153
+ const supabase = createClient(url, key, { auth: { persistSession: false, autoRefreshToken: false } });
154
+ const factText = "Current Fishbowl thread tracks the active topic for Phase 1.1 hotfix";
155
+ const { data: inserted, error: insertErr } = await supabase
156
+ .from("extracted_facts")
157
+ .insert({
158
+ fact: factText,
159
+ category: "test",
160
+ confidence: 0.9,
161
+ status: "active",
162
+ source_type: "test",
163
+ decay_tier: "hot",
164
+ })
165
+ .select("id")
166
+ .single();
167
+ assert.ok(!insertErr, `insert failed: ${insertErr?.message}`);
168
+ const factId = inserted.id;
169
+ try {
170
+ const savedOpenAI = process.env.OPENAI_API_KEY;
171
+ delete process.env.OPENAI_API_KEY;
172
+ try {
173
+ const { SupabaseBackend } = await import("../supabase.js");
174
+ const backend = new SupabaseBackend({ url, serviceRoleKey: key, tenancy: { mode: "byod" } });
175
+ // All three tokens appear in the fact, but not as a contiguous phrase.
176
+ const results = (await backend.searchMemory("active Fishbowl topic", 10));
177
+ const ids = results.map((r) => r.id);
178
+ assert.ok(ids.includes(factId), `Expected fact ${factId} via tokenized AND. Got: ${JSON.stringify(results.slice(0, 3))}`);
179
+ }
180
+ finally {
181
+ if (savedOpenAI !== undefined)
182
+ process.env.OPENAI_API_KEY = savedOpenAI;
183
+ }
184
+ }
185
+ finally {
186
+ await supabase.from("extracted_facts").delete().eq("id", factId);
187
+ }
188
+ });
189
+ test("OR fallback surfaces best partial match when no fact contains every token", async () => {
190
+ const url = process.env.TEST_SUPABASE_URL ?? process.env.SUPABASE_URL;
191
+ const key = process.env.TEST_SUPABASE_SERVICE_ROLE_KEY ?? process.env.SUPABASE_SERVICE_ROLE_KEY;
192
+ if (!url || !key) {
193
+ console.log(" [skipped] set SUPABASE_URL + SUPABASE_SERVICE_ROLE_KEY to run");
194
+ return;
195
+ }
196
+ const { createClient } = await import("@supabase/supabase-js");
197
+ const supabase = createClient(url, key, { auth: { persistSession: false, autoRefreshToken: false } });
198
+ // Fact contains "architecture" but not "thread". Query is "architecture
199
+ // thread nonexistent" so AND-of-tokens returns []; OR-of-tokens should
200
+ // still surface this row because it matches one token.
201
+ const factText = "Memory architecture uses six layers including bitemporal facts";
202
+ const { data: inserted, error: insertErr } = await supabase
203
+ .from("extracted_facts")
204
+ .insert({
205
+ fact: factText,
206
+ category: "test",
207
+ confidence: 0.8,
208
+ status: "active",
209
+ source_type: "test",
210
+ decay_tier: "hot",
211
+ })
212
+ .select("id")
213
+ .single();
214
+ assert.ok(!insertErr, `insert failed: ${insertErr?.message}`);
215
+ const factId = inserted.id;
216
+ try {
217
+ const savedOpenAI = process.env.OPENAI_API_KEY;
218
+ delete process.env.OPENAI_API_KEY;
219
+ try {
220
+ const { SupabaseBackend } = await import("../supabase.js");
221
+ const backend = new SupabaseBackend({ url, serviceRoleKey: key, tenancy: { mode: "byod" } });
222
+ const results = (await backend.searchMemory("architecture thread zzznonexistentzzz", 10));
223
+ const ids = results.map((r) => r.id);
224
+ assert.ok(ids.includes(factId), `Expected fact ${factId} via OR-of-tokens fallback. Got: ${JSON.stringify(results.slice(0, 3))}`);
225
+ }
226
+ finally {
227
+ if (savedOpenAI !== undefined)
228
+ process.env.OPENAI_API_KEY = savedOpenAI;
229
+ }
230
+ }
231
+ finally {
232
+ await supabase.from("extracted_facts").delete().eq("id", factId);
233
+ }
234
+ });
235
+ });
236
+ // ─── 3. Acceptance test (LOCOMO-style) ────────────────────────────────────────
237
+ //
238
+ // Requires real credentials. Skipped automatically when env vars are absent.
239
+ // This is the key regression test: a query that previously returned [] should
240
+ // now return the expected fact in top-5 after backfill.
241
+ describe("acceptance: hybrid search finds semantically similar fact", () => {
242
+ test("'preference for functional programming' found by 'avoids OOP' query", async () => {
243
+ const url = process.env.TEST_SUPABASE_URL ?? process.env.SUPABASE_URL;
244
+ const key = process.env.TEST_SUPABASE_SERVICE_ROLE_KEY ?? process.env.SUPABASE_SERVICE_ROLE_KEY;
245
+ const openaiKey = process.env.OPENAI_API_KEY;
246
+ if (!url || !key || !openaiKey) {
247
+ console.log(" [skipped] set SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY, OPENAI_API_KEY to run");
248
+ return;
249
+ }
250
+ const { createClient } = await import("@supabase/supabase-js");
251
+ const { embedText, EMBEDDING_MODEL } = await import("../embeddings.js");
252
+ const supabase = createClient(url, key, {
253
+ auth: { persistSession: false, autoRefreshToken: false },
254
+ });
255
+ // Insert a test fact
256
+ const factText = "User strongly prefers functional programming over object-oriented patterns";
257
+ const { data: inserted, error: insertErr } = await supabase
258
+ .from("extracted_facts")
259
+ .insert({
260
+ fact: factText,
261
+ category: "preference",
262
+ confidence: 0.95,
263
+ status: "active",
264
+ source_type: "test",
265
+ decay_tier: "hot",
266
+ })
267
+ .select("id")
268
+ .single();
269
+ assert.ok(!insertErr, `insert failed: ${insertErr?.message}`);
270
+ const factId = inserted.id;
271
+ try {
272
+ // Embed the fact
273
+ const factEmbedding = await embedText(factText);
274
+ assert.ok(factEmbedding, "failed to embed fact");
275
+ await supabase
276
+ .from("extracted_facts")
277
+ .update({
278
+ embedding: JSON.stringify(factEmbedding),
279
+ embedding_model: EMBEDDING_MODEL,
280
+ embedding_created_at: new Date().toISOString(),
281
+ })
282
+ .eq("id", factId);
283
+ // Search with a semantically related but keyword-different query
284
+ const query = "avoids class hierarchies and OOP";
285
+ const queryEmbedding = await embedText(query);
286
+ assert.ok(queryEmbedding, "failed to embed query");
287
+ const { data: results, error: searchErr } = await supabase.rpc("search_memory_hybrid", {
288
+ search_query: query,
289
+ query_embedding: queryEmbedding,
290
+ max_results: 5,
291
+ });
292
+ assert.ok(!searchErr, `hybrid search failed: ${searchErr?.message}`);
293
+ assert.ok(Array.isArray(results), "results should be array");
294
+ const ids = results.map((r) => r.id);
295
+ assert.ok(ids.includes(factId), `Expected fact ${factId} in top-5. Got: ${JSON.stringify(results)}`);
296
+ console.log(` [passed] fact found at position ${ids.indexOf(factId) + 1} of ${ids.length}`);
297
+ }
298
+ finally {
299
+ // Clean up
300
+ await supabase.from("extracted_facts").delete().eq("id", factId);
301
+ }
302
+ });
303
+ });
304
+ //# sourceMappingURL=hybrid-search.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hybrid-search.test.js","sourceRoot":"","sources":["../../../src/memory/__tests__/hybrid-search.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Agent profile resolver for the MCP server.
3
+ *
4
+ * Calls the central UnClick API (admin_agent_resolve) to fetch the active
5
+ * agent's persona, scoped tools, and scoped memory layers. When no agent
6
+ * exists for the user, returns null and the server falls back to default
7
+ * behaviour (all tools, all memory layers).
8
+ */
9
+ export type MemoryLayerKey = "business_context" | "extracted_facts" | "session_summaries" | "knowledge_library" | "conversation_log" | "code_dumps";
10
+ export interface AgentProfile {
11
+ id: string;
12
+ name: string;
13
+ slug: string;
14
+ role: string;
15
+ description: string | null;
16
+ system_prompt: string | null;
17
+ is_default: boolean;
18
+ }
19
+ export interface ResolvedAgent {
20
+ agent: AgentProfile;
21
+ enabled_tools: string[];
22
+ enabled_memory_layers: MemoryLayerKey[];
23
+ }
24
+ export declare function resolveAgent(opts: {
25
+ agent_slug?: string;
26
+ agent_id?: string;
27
+ }): Promise<ResolvedAgent | null>;
28
+ /**
29
+ * Strip memory layer keys from a startup-context response that the active
30
+ * agent isn't allowed to see. When enabledLayers is empty we treat that as
31
+ * "all layers" (backward compatible default).
32
+ */
33
+ export declare function filterContextByLayers(context: unknown, enabledLayers: MemoryLayerKey[]): unknown;
34
+ //# sourceMappingURL=agent.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../../src/memory/agent.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH,MAAM,MAAM,cAAc,GACtB,kBAAkB,GAClB,iBAAiB,GACjB,mBAAmB,GACnB,mBAAmB,GACnB,kBAAkB,GAClB,YAAY,CAAC;AAEjB,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,UAAU,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,YAAY,CAAC;IACpB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,qBAAqB,EAAE,cAAc,EAAE,CAAC;CACzC;AAQD,wBAAsB,YAAY,CAAC,IAAI,EAAE;IACvC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAoChC;AAWD;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,OAAO,EAChB,aAAa,EAAE,cAAc,EAAE,GAC9B,OAAO,CAQT"}
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Agent profile resolver for the MCP server.
3
+ *
4
+ * Calls the central UnClick API (admin_agent_resolve) to fetch the active
5
+ * agent's persona, scoped tools, and scoped memory layers. When no agent
6
+ * exists for the user, returns null and the server falls back to default
7
+ * behaviour (all tools, all memory layers).
8
+ */
9
+ const MEMORY_API_BASE = process.env.UNCLICK_MEMORY_BASE_URL || process.env.UNCLICK_SITE_URL || "https://unclick.world";
10
+ export async function resolveAgent(opts) {
11
+ const apiKey = process.env.UNCLICK_API_KEY;
12
+ if (!apiKey)
13
+ return null;
14
+ const params = new URLSearchParams();
15
+ if (opts.agent_slug)
16
+ params.set("agent_slug", opts.agent_slug);
17
+ if (opts.agent_id)
18
+ params.set("agent_id", opts.agent_id);
19
+ const qs = params.toString();
20
+ const url = `${MEMORY_API_BASE}/api/memory-admin?action=admin_agent_resolve${qs ? `&${qs}` : ""}`;
21
+ try {
22
+ const res = await fetch(url, {
23
+ headers: { Authorization: `Bearer ${apiKey}` },
24
+ });
25
+ if (!res.ok)
26
+ return null;
27
+ const data = (await res.json());
28
+ if (!data.agent)
29
+ return null;
30
+ const enabledTools = (data.tools ?? [])
31
+ .filter((t) => t.is_enabled)
32
+ .map((t) => t.connector_id);
33
+ const enabledLayers = (data.memory_scope ?? [])
34
+ .filter((l) => l.is_enabled)
35
+ .map((l) => l.memory_layer);
36
+ return {
37
+ agent: data.agent,
38
+ enabled_tools: enabledTools,
39
+ enabled_memory_layers: enabledLayers,
40
+ };
41
+ }
42
+ catch {
43
+ return null;
44
+ }
45
+ }
46
+ const ALL_LAYERS = [
47
+ "business_context",
48
+ "extracted_facts",
49
+ "session_summaries",
50
+ "knowledge_library",
51
+ "conversation_log",
52
+ "code_dumps",
53
+ ];
54
+ /**
55
+ * Strip memory layer keys from a startup-context response that the active
56
+ * agent isn't allowed to see. When enabledLayers is empty we treat that as
57
+ * "all layers" (backward compatible default).
58
+ */
59
+ export function filterContextByLayers(context, enabledLayers) {
60
+ if (enabledLayers.length === 0 || !context || typeof context !== "object")
61
+ return context;
62
+ const blocked = ALL_LAYERS.filter((l) => !enabledLayers.includes(l));
63
+ const out = { ...context };
64
+ for (const layer of blocked) {
65
+ delete out[layer];
66
+ }
67
+ return out;
68
+ }
69
+ //# sourceMappingURL=agent.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent.js","sourceRoot":"","sources":["../../src/memory/agent.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,eAAe,GACnB,OAAO,CAAC,GAAG,CAAC,uBAAuB,IAAI,OAAO,CAAC,GAAG,CAAC,gBAAgB,IAAI,uBAAuB,CAAC;AAgCjG,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,IAGlC;IACC,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC;IAC3C,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAC;IAEzB,MAAM,MAAM,GAAG,IAAI,eAAe,EAAE,CAAC;IACrC,IAAI,IAAI,CAAC,UAAU;QAAE,MAAM,CAAC,GAAG,CAAC,YAAY,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;IAC/D,IAAI,IAAI,CAAC,QAAQ;QAAE,MAAM,CAAC,GAAG,CAAC,UAAU,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;IACzD,MAAM,EAAE,GAAG,MAAM,CAAC,QAAQ,EAAE,CAAC;IAC7B,MAAM,GAAG,GAAG,GAAG,eAAe,+CAC5B,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,EAClB,EAAE,CAAC;IAEH,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;YAC3B,OAAO,EAAE,EAAE,aAAa,EAAE,UAAU,MAAM,EAAE,EAAE;SAC/C,CAAC,CAAC;QACH,IAAI,CAAC,GAAG,CAAC,EAAE;YAAE,OAAO,IAAI,CAAC;QACzB,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAoB,CAAC;QACnD,IAAI,CAAC,IAAI,CAAC,KAAK;YAAE,OAAO,IAAI,CAAC;QAE7B,MAAM,YAAY,GAAG,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC;aACpC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC;aAC3B,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC;QAE9B,MAAM,aAAa,GAAG,CAAC,IAAI,CAAC,YAAY,IAAI,EAAE,CAAC;aAC5C,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC;aAC3B,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,YAA8B,CAAC,CAAC;QAEhD,OAAO;YACL,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,aAAa,EAAE,YAAY;YAC3B,qBAAqB,EAAE,aAAa;SACrC,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,UAAU,GAAqB;IACnC,kBAAkB;IAClB,iBAAiB;IACjB,mBAAmB;IACnB,mBAAmB;IACnB,kBAAkB;IAClB,YAAY;CACb,CAAC;AAEF;;;;GAIG;AACH,MAAM,UAAU,qBAAqB,CACnC,OAAgB,EAChB,aAA+B;IAE/B,IAAI,aAAa,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,OAAO,IAAI,OAAO,OAAO,KAAK,QAAQ;QAAE,OAAO,OAAO,CAAC;IAC1F,MAAM,OAAO,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;IACrE,MAAM,GAAG,GAA4B,EAAE,GAAI,OAAmC,EAAE,CAAC;IACjF,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,OAAO,GAAG,CAAC,KAAK,CAAC,CAAC;IACpB,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC"}