@muxoai/sdk 1.3.0 → 1.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/.agents/plugins/marketplace.json +2 -2
- package/.claude-plugin/marketplace.json +19 -4
- package/.codex/config.toml +9 -2
- package/ARCHITECTURE.md +16 -6
- package/README.md +69 -24
- package/dist/clerk.d.ts +35 -0
- package/dist/clerk.js +201 -0
- package/dist/cli.js +1677 -103
- package/dist/composio-catalog.d.ts +234 -0
- package/dist/composio-catalog.js +1894 -0
- package/dist/config.d.ts +16 -0
- package/dist/config.js +27 -1
- package/dist/durable-workflow.d.ts +112 -0
- package/dist/durable-workflow.js +284 -0
- package/dist/engine.d.ts +377 -6
- package/dist/engine.js +1919 -94
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/mcp-schema.d.ts +286 -0
- package/dist/mcp-schema.js +2580 -0
- package/dist/mcp.d.ts +2 -25
- package/dist/mcp.js +1008 -507
- package/dist/memory-graph.d.ts +45 -0
- package/dist/memory-graph.js +293 -0
- package/dist/nl.js +214 -35
- package/dist/oauth.d.ts +32 -0
- package/dist/oauth.js +134 -0
- package/dist/oss-suite.d.ts +217 -0
- package/dist/oss-suite.js +836 -0
- package/dist/policy.d.ts +190 -0
- package/dist/policy.js +1597 -0
- package/dist/providers/composio-instant.d.ts +4 -0
- package/dist/providers/composio-instant.js +890 -535
- package/dist/providers/composio-sdk.d.ts +126 -5
- package/dist/providers/composio-sdk.js +902 -79
- package/dist/providers/microsoft-governance.d.ts +89 -3
- package/dist/providers/microsoft-governance.js +673 -26
- package/dist/providers/stripe-billing.d.ts +42 -0
- package/dist/providers/stripe-billing.js +244 -5
- package/dist/providers/stripe-projects.d.ts +459 -4
- package/dist/providers/stripe-projects.js +2674 -122
- package/dist/providers/supermemory.d.ts +102 -6
- package/dist/providers/supermemory.js +609 -55
- package/dist/provisioning-managed.d.ts +50 -0
- package/dist/provisioning-managed.js +155 -0
- package/dist/sdk.d.ts +728 -7
- package/dist/sdk.js +539 -10
- package/dist/server.js +2744 -321
- package/dist/store.d.ts +138 -6
- package/dist/store.js +932 -58
- package/dist/types.d.ts +330 -4
- package/dist/ui/console-html.d.ts +62 -0
- package/dist/ui/console-html.js +3742 -2228
- package/dist/ui/design-tokens.d.ts +177 -0
- package/dist/ui/design-tokens.js +210 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +1 -0
- package/package.json +6 -5
- package/plugins/muxo/.app.json +1 -1
- package/plugins/muxo/.claude-plugin/plugin.json +3 -3
- package/plugins/muxo/.codex-plugin/plugin.json +19 -10
- package/plugins/muxo/.cursor-plugin/plugin.json +30 -0
- package/plugins/muxo/.mcp.json +1 -33
- package/plugins/muxo/assets/logo.png +0 -0
- package/plugins/muxo/hooks/session-start.sh +16 -4
- package/plugins/muxo/hooks/user-prompt-submit.sh +14 -1
- package/plugins/muxo/mcp.json +0 -10
- package/plugins/muxo/plugin.json +65 -8
- package/plugins/muxo/skills/muxo/SKILL.md +142 -14
- package/plugins/muxo/skills/muxo/agents/openai.yaml +12 -0
- package/plugins/muxo/skills/muxo/references/cli.md +9 -3
- package/plugins/muxo/skills/muxo/references/mcp.md +22 -10
- package/plugins/muxo/skills/muxo-activity-summary/SKILL.md +81 -0
- package/plugins/muxo/skills/setup/SKILL.md +17 -3
- package/plugins/muxo/skills/setup/agents/openai.yaml +12 -0
- package/src/catalogues/stripe-projects-catalog.json +140 -0
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
import crypto from "node:crypto";
|
|
2
2
|
import Supermemory from "supermemory";
|
|
3
|
+
import { formatHumanFriendlyExplanation } from "../policy.js";
|
|
4
|
+
// Profile buckets can hold plain strings or memory objects depending on API version.
|
|
5
|
+
function toFactTexts(items) {
|
|
6
|
+
if (!Array.isArray(items))
|
|
7
|
+
return [];
|
|
8
|
+
return items
|
|
9
|
+
.map((item) => {
|
|
10
|
+
if (item == null)
|
|
11
|
+
return "";
|
|
12
|
+
if (typeof item !== "object")
|
|
13
|
+
return String(item);
|
|
14
|
+
const o = item;
|
|
15
|
+
const text = o.memory ?? o.content ?? o.text ?? o.fact ?? o.value ?? o.summary ?? o.title;
|
|
16
|
+
return typeof text === "string" ? text : JSON.stringify(item);
|
|
17
|
+
})
|
|
18
|
+
.map((s) => s.trim())
|
|
19
|
+
.filter(Boolean);
|
|
20
|
+
}
|
|
3
21
|
/**
|
|
4
22
|
* Supermemory v5 Contextual Brain & Context-Aware Tool Call Adapter.
|
|
5
23
|
* Uses deterministic per-workspace namespaces (`/ns/muxo-v5-<hash>/...`)
|
|
@@ -8,21 +26,71 @@ import Supermemory from "supermemory";
|
|
|
8
26
|
export class SupermemoryAdapter {
|
|
9
27
|
config;
|
|
10
28
|
client;
|
|
29
|
+
byokClients = new Map();
|
|
30
|
+
unavailableUntil = 0;
|
|
11
31
|
constructor(config) {
|
|
12
32
|
this.config = config;
|
|
13
33
|
if (config.supermemoryApiKey) {
|
|
14
34
|
this.client = new Supermemory({
|
|
15
35
|
apiKey: config.supermemoryApiKey,
|
|
16
36
|
baseUrl: config.supermemoryBaseUrl,
|
|
17
|
-
maxRetries:
|
|
37
|
+
maxRetries: 0,
|
|
38
|
+
timeoutInSeconds: 2,
|
|
18
39
|
});
|
|
19
40
|
}
|
|
20
41
|
else {
|
|
21
42
|
this.client = null;
|
|
22
43
|
}
|
|
23
44
|
}
|
|
24
|
-
|
|
25
|
-
|
|
45
|
+
resolveClient(apiKeyOverride) {
|
|
46
|
+
const trimmedByok = String(apiKeyOverride || "").trim();
|
|
47
|
+
if (trimmedByok) {
|
|
48
|
+
let byokClient = this.byokClients.get(trimmedByok);
|
|
49
|
+
if (!byokClient) {
|
|
50
|
+
byokClient = new Supermemory({
|
|
51
|
+
apiKey: trimmedByok,
|
|
52
|
+
baseUrl: this.config.supermemoryBaseUrl,
|
|
53
|
+
maxRetries: 0,
|
|
54
|
+
timeoutInSeconds: 2,
|
|
55
|
+
});
|
|
56
|
+
this.byokClients.set(trimmedByok, byokClient);
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
client: byokClient,
|
|
60
|
+
fundingSource: "customer_byok_supermemory",
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
if (this.client) {
|
|
64
|
+
return {
|
|
65
|
+
client: this.client,
|
|
66
|
+
fundingSource: "muxo_funded",
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
client: null,
|
|
71
|
+
fundingSource: "local_fallback",
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
withTimeout(fn, ms = 1000) {
|
|
75
|
+
if (Date.now() < this.unavailableUntil) {
|
|
76
|
+
return Promise.resolve(null);
|
|
77
|
+
}
|
|
78
|
+
return Promise.race([
|
|
79
|
+
fn(),
|
|
80
|
+
new Promise((resolve) => {
|
|
81
|
+
const t = setTimeout(() => {
|
|
82
|
+
this.unavailableUntil = Date.now() + 30_000;
|
|
83
|
+
resolve(null);
|
|
84
|
+
}, ms);
|
|
85
|
+
t.unref?.();
|
|
86
|
+
}),
|
|
87
|
+
]).catch(() => {
|
|
88
|
+
this.unavailableUntil = Date.now() + 30_000;
|
|
89
|
+
return null;
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
isEnabled(apiKeyOverride) {
|
|
93
|
+
return Boolean(this.resolveClient(apiKeyOverride).client);
|
|
26
94
|
}
|
|
27
95
|
/**
|
|
28
96
|
* Derives a strict Supermemory v5 namespace (`^[a-zA-Z0-9_:-]+$`) scoped to a workspace.
|
|
@@ -50,21 +118,29 @@ export class SupermemoryAdapter {
|
|
|
50
118
|
}
|
|
51
119
|
/**
|
|
52
120
|
* Fetches the workspace's static + dynamic profile and optionally semantic search results
|
|
53
|
-
* in a single fail-open call.
|
|
121
|
+
* in a single fail-open call. Supports customer BYOK `supermemory` keys via `apiKeyOverride`.
|
|
54
122
|
*/
|
|
55
|
-
async getContextForWorkspace(workspaceIdOrParams, optionalQuery) {
|
|
123
|
+
async getContextForWorkspace(workspaceIdOrParams, optionalQuery, optionalApiKeyOverride) {
|
|
56
124
|
const params = typeof workspaceIdOrParams === "string"
|
|
57
|
-
? {
|
|
125
|
+
? {
|
|
126
|
+
workspaceId: workspaceIdOrParams,
|
|
127
|
+
query: optionalQuery,
|
|
128
|
+
limit: 5,
|
|
129
|
+
apiKeyOverride: optionalApiKeyOverride,
|
|
130
|
+
}
|
|
58
131
|
: workspaceIdOrParams;
|
|
59
132
|
const namespace = this.namespaceForWorkspace(params.workspaceId);
|
|
60
|
-
|
|
133
|
+
const { client, fundingSource } = this.resolveClient(params.apiKeyOverride);
|
|
134
|
+
if (!client) {
|
|
61
135
|
return {
|
|
62
136
|
enabled: false,
|
|
63
137
|
namespace,
|
|
138
|
+
fundingSource,
|
|
64
139
|
staticFacts: [],
|
|
65
140
|
dynamicContext: [],
|
|
66
141
|
recalledMemories: [],
|
|
67
142
|
formattedPromptContext: "Supermemory v5 disabled (no API key configured).",
|
|
143
|
+
profileMarkdown: "",
|
|
68
144
|
profile: { static: [], dynamic: [], preferences: [] },
|
|
69
145
|
relevantMemories: [],
|
|
70
146
|
};
|
|
@@ -74,50 +150,42 @@ export class SupermemoryAdapter {
|
|
|
74
150
|
let dynamicFacts = [];
|
|
75
151
|
let preferences = [];
|
|
76
152
|
const relevantMemories = [];
|
|
77
|
-
|
|
78
|
-
|
|
153
|
+
const [profileOutcome, searchOutcome] = await Promise.allSettled([
|
|
154
|
+
this.withTimeout(() => client.profile(namespace, {
|
|
79
155
|
...(params.query ? { q: params.query } : {}),
|
|
80
|
-
})
|
|
81
|
-
|
|
82
|
-
?
|
|
83
|
-
: [];
|
|
84
|
-
dynamicFacts = Array.isArray(profileRes?.profile?.dynamic)
|
|
85
|
-
? profileRes.profile.dynamic.map(String)
|
|
86
|
-
: [];
|
|
87
|
-
preferences = Array.isArray(profileRes?.profile?.buckets?.preferences)
|
|
88
|
-
? profileRes.profile.buckets.preferences.map(String)
|
|
89
|
-
: [];
|
|
90
|
-
}
|
|
91
|
-
catch {
|
|
92
|
-
// Fail-open: never block tool planning if profile endpoint times out
|
|
93
|
-
}
|
|
94
|
-
if (params.query && params.query.trim().length > 0) {
|
|
95
|
-
try {
|
|
96
|
-
const searchRes = await this.client.search(namespace, {
|
|
156
|
+
}), 1000),
|
|
157
|
+
params.query && params.query.trim().length > 0
|
|
158
|
+
? this.withTimeout(() => client.search(namespace, {
|
|
97
159
|
query: params.query.trim(),
|
|
98
160
|
limit,
|
|
99
161
|
searchMode: "hybrid",
|
|
100
162
|
include: { documents: true, forgotten: false, related: false },
|
|
101
163
|
rewriteQuery: false,
|
|
164
|
+
}), 1000)
|
|
165
|
+
: Promise.resolve(null),
|
|
166
|
+
]);
|
|
167
|
+
if (profileOutcome.status === "fulfilled" && profileOutcome.value) {
|
|
168
|
+
const profileRes = profileOutcome.value;
|
|
169
|
+
staticFacts = toFactTexts(profileRes?.profile?.static);
|
|
170
|
+
dynamicFacts = toFactTexts(profileRes?.profile?.dynamic);
|
|
171
|
+
preferences = toFactTexts(profileRes?.profile?.buckets?.preferences);
|
|
172
|
+
}
|
|
173
|
+
if (searchOutcome.status === "fulfilled" && searchOutcome.value) {
|
|
174
|
+
const searchRes = searchOutcome.value;
|
|
175
|
+
for (const item of (searchRes?.results || []).slice(0, limit)) {
|
|
176
|
+
const doc = item.included?.document;
|
|
177
|
+
const title = String(doc?.metadata?.title ||
|
|
178
|
+
doc?.title ||
|
|
179
|
+
item.memory ||
|
|
180
|
+
item.chunk ||
|
|
181
|
+
"Workspace Memory").slice(0, 120);
|
|
182
|
+
const snippet = String(item.memory || item.chunk || doc?.metadata?.summary || "").slice(0, 300);
|
|
183
|
+
relevantMemories.push({
|
|
184
|
+
documentId: String(doc?.id || item.id || ""),
|
|
185
|
+
title,
|
|
186
|
+
snippet,
|
|
187
|
+
similarity: Number(item.similarity || 0),
|
|
102
188
|
});
|
|
103
|
-
for (const item of (searchRes?.results || []).slice(0, limit)) {
|
|
104
|
-
const doc = item.included?.document;
|
|
105
|
-
const title = String(doc?.metadata?.title ||
|
|
106
|
-
doc?.title ||
|
|
107
|
-
item.memory ||
|
|
108
|
-
item.chunk ||
|
|
109
|
-
"Workspace Memory").slice(0, 120);
|
|
110
|
-
const snippet = String(item.memory || item.chunk || doc?.metadata?.summary || "").slice(0, 300);
|
|
111
|
-
relevantMemories.push({
|
|
112
|
-
documentId: String(doc?.id || item.id || ""),
|
|
113
|
-
title,
|
|
114
|
-
snippet,
|
|
115
|
-
similarity: Number(item.similarity || 0),
|
|
116
|
-
});
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
catch {
|
|
120
|
-
// Fail-open
|
|
121
189
|
}
|
|
122
190
|
}
|
|
123
191
|
const promptLines = [
|
|
@@ -132,52 +200,85 @@ export class SupermemoryAdapter {
|
|
|
132
200
|
if (relevantMemories.length > 0) {
|
|
133
201
|
promptLines.push(`Recalled Memories: ${relevantMemories.map((m) => `${m.title}: ${m.snippet}`).join(" | ")}`);
|
|
134
202
|
}
|
|
203
|
+
const mdLines = [`## Workspace Profile (${namespace})`];
|
|
204
|
+
if (staticFacts.length > 0) {
|
|
205
|
+
mdLines.push("### Static Facts", ...staticFacts.map((f) => `- ${f}`));
|
|
206
|
+
}
|
|
207
|
+
if (dynamicFacts.length > 0) {
|
|
208
|
+
mdLines.push("### Dynamic Context", ...dynamicFacts.map((f) => `- ${f}`));
|
|
209
|
+
}
|
|
210
|
+
if (preferences.length > 0) {
|
|
211
|
+
mdLines.push("### Preferences", ...preferences.map((f) => `- ${f}`));
|
|
212
|
+
}
|
|
213
|
+
const inferred = extractActiveMemoryDefaults({
|
|
214
|
+
preferences,
|
|
215
|
+
staticFacts: [
|
|
216
|
+
...staticFacts,
|
|
217
|
+
...dynamicFacts,
|
|
218
|
+
...relevantMemories.map((m) => `${m.title}: ${m.snippet}`),
|
|
219
|
+
],
|
|
220
|
+
});
|
|
221
|
+
if (Object.keys(inferred.activeDefaults).length > 0) {
|
|
222
|
+
promptLines.push(`Active Memory Defaults: ${Object.entries(inferred.activeDefaults)
|
|
223
|
+
.map(([k, v]) => `${k}=${String(v)}`)
|
|
224
|
+
.join(", ")}`);
|
|
225
|
+
mdLines.push("### Active Defaults (Auto-Fill)", ...Object.entries(inferred.activeDefaults).map(([k, v]) => `- **${k}**: \`${String(v)}\``));
|
|
226
|
+
}
|
|
135
227
|
return {
|
|
136
228
|
enabled: true,
|
|
137
229
|
namespace,
|
|
230
|
+
fundingSource,
|
|
138
231
|
staticFacts,
|
|
139
232
|
dynamicContext: dynamicFacts,
|
|
140
233
|
recalledMemories: relevantMemories,
|
|
141
234
|
formattedPromptContext: promptLines.join("\n"),
|
|
235
|
+
profileMarkdown: mdLines.join("\n"),
|
|
142
236
|
profile: {
|
|
143
237
|
static: staticFacts,
|
|
144
238
|
dynamic: dynamicFacts,
|
|
145
239
|
preferences,
|
|
146
240
|
},
|
|
147
241
|
relevantMemories,
|
|
242
|
+
activeDefaults: inferred.activeDefaults,
|
|
243
|
+
activeGuardrails: inferred.activeGuardrails,
|
|
148
244
|
};
|
|
149
245
|
}
|
|
150
246
|
/**
|
|
151
|
-
* Stores a governed, secret-sanitized document/memory in the workspace's Supermemory v5 namespace
|
|
247
|
+
* Stores a governed, secret-sanitized document/memory in the workspace's Supermemory v5 namespace
|
|
248
|
+
* with `dreaming: "instant"` so profile facts are synthesized immediately.
|
|
152
249
|
*/
|
|
153
250
|
async addMemory(params) {
|
|
154
251
|
const namespace = this.namespaceForWorkspace(params.workspaceId);
|
|
155
252
|
const sanitizedContent = this.sanitizeContent(params.content);
|
|
156
253
|
const docId = params.documentId || `mem_${crypto.randomBytes(8).toString("hex")}`;
|
|
157
|
-
|
|
254
|
+
const { client, fundingSource } = this.resolveClient(params.apiKeyOverride);
|
|
255
|
+
if (!client) {
|
|
158
256
|
return {
|
|
159
257
|
documentId: docId,
|
|
160
258
|
namespace,
|
|
161
259
|
status: "local_only",
|
|
162
260
|
sanitizedContent,
|
|
261
|
+
fundingSource,
|
|
163
262
|
};
|
|
164
263
|
}
|
|
165
|
-
const res = await this.client.add(namespace, {
|
|
264
|
+
const res = await this.withTimeout(() => client.add(namespace, {
|
|
166
265
|
id: docId,
|
|
167
266
|
content: `${params.title}\n\n${sanitizedContent}`,
|
|
168
267
|
taskType: "memory",
|
|
268
|
+
dreaming: params.dreaming || "instant",
|
|
169
269
|
metadata: {
|
|
170
270
|
title: params.title,
|
|
171
271
|
muxoWorkspaceId: params.workspaceId,
|
|
172
272
|
muxoKind: params.kind,
|
|
173
273
|
...(params.metadata || {}),
|
|
174
274
|
},
|
|
175
|
-
});
|
|
275
|
+
}), 1500);
|
|
176
276
|
return {
|
|
177
|
-
documentId: String(res
|
|
277
|
+
documentId: String(res?.id || docId),
|
|
178
278
|
namespace,
|
|
179
|
-
status: String(res
|
|
279
|
+
status: String(res?.status || "queued"),
|
|
180
280
|
sanitizedContent,
|
|
281
|
+
fundingSource,
|
|
181
282
|
};
|
|
182
283
|
}
|
|
183
284
|
async recordExecutionMemory(params) {
|
|
@@ -187,6 +288,7 @@ export class SupermemoryAdapter {
|
|
|
187
288
|
title: `Execution: ${params.toolSlug} (${params.status})`,
|
|
188
289
|
content: params.summary,
|
|
189
290
|
kind: "execution_outcome",
|
|
291
|
+
apiKeyOverride: params.apiKeyOverride,
|
|
190
292
|
metadata: {
|
|
191
293
|
toolSlug: params.toolSlug,
|
|
192
294
|
lane: params.lane,
|
|
@@ -194,7 +296,7 @@ export class SupermemoryAdapter {
|
|
|
194
296
|
status: params.status,
|
|
195
297
|
},
|
|
196
298
|
});
|
|
197
|
-
return added.documentId;
|
|
299
|
+
return added ? added.documentId : null;
|
|
198
300
|
}
|
|
199
301
|
catch {
|
|
200
302
|
return null;
|
|
@@ -203,15 +305,16 @@ export class SupermemoryAdapter {
|
|
|
203
305
|
/**
|
|
204
306
|
* Deletes/forgets a memory document from the workspace's Supermemory v5 namespace.
|
|
205
307
|
*/
|
|
206
|
-
async deleteMemory(workspaceId, documentId) {
|
|
308
|
+
async deleteMemory(workspaceId, documentId, apiKeyOverride) {
|
|
207
309
|
const namespace = this.namespaceForWorkspace(workspaceId);
|
|
208
|
-
|
|
310
|
+
const { client } = this.resolveClient(apiKeyOverride);
|
|
311
|
+
if (!client) {
|
|
209
312
|
return { deleted: true, namespace };
|
|
210
313
|
}
|
|
211
314
|
try {
|
|
212
|
-
const res = await this.client.documents.delete(namespace, {
|
|
315
|
+
const res = await this.withTimeout(() => client.documents.delete(namespace, {
|
|
213
316
|
ids: [documentId],
|
|
214
|
-
});
|
|
317
|
+
}), 1200);
|
|
215
318
|
return {
|
|
216
319
|
deleted: (res?.count || 0) >= 1,
|
|
217
320
|
namespace,
|
|
@@ -221,4 +324,455 @@ export class SupermemoryAdapter {
|
|
|
221
324
|
return { deleted: false, namespace };
|
|
222
325
|
}
|
|
223
326
|
}
|
|
327
|
+
/**
|
|
328
|
+
* Semantically searches and forgets/prunes outdated memories in Supermemory v5 matching a query.
|
|
329
|
+
*/
|
|
330
|
+
async forgetMatching(params) {
|
|
331
|
+
const namespace = this.namespaceForWorkspace(params.workspaceId);
|
|
332
|
+
const { client, fundingSource } = this.resolveClient(params.apiKeyOverride);
|
|
333
|
+
if (!client || !params.query.trim()) {
|
|
334
|
+
return {
|
|
335
|
+
namespace,
|
|
336
|
+
deletedRemoteIds: [],
|
|
337
|
+
fundingSource,
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
try {
|
|
341
|
+
const searchRes = await this.withTimeout(() => client.search(namespace, {
|
|
342
|
+
query: params.query.trim(),
|
|
343
|
+
limit: Math.min(10, Math.max(1, Number(params.limit || 5))),
|
|
344
|
+
searchMode: "hybrid",
|
|
345
|
+
include: { documents: true, forgotten: false, related: false },
|
|
346
|
+
rewriteQuery: false,
|
|
347
|
+
}), 1200);
|
|
348
|
+
const docIds = Array.from(new Set((searchRes?.results || [])
|
|
349
|
+
.map((r) => String(r.included?.document?.id || r.id || ""))
|
|
350
|
+
.filter(Boolean)));
|
|
351
|
+
if (docIds.length > 0) {
|
|
352
|
+
await this.withTimeout(() => client.documents.delete(namespace, { ids: docIds }), 1200);
|
|
353
|
+
return {
|
|
354
|
+
namespace,
|
|
355
|
+
deletedRemoteIds: docIds,
|
|
356
|
+
fundingSource,
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
catch {
|
|
361
|
+
// Fail-open on remote pruning errors
|
|
362
|
+
}
|
|
363
|
+
return {
|
|
364
|
+
namespace,
|
|
365
|
+
deletedRemoteIds: [],
|
|
366
|
+
fundingSource,
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
export function extractActiveMemoryDefaults(params) {
|
|
371
|
+
const activeDefaults = {};
|
|
372
|
+
const activeGuardrails = [];
|
|
373
|
+
const activeFacts = (params.temporalFacts || []).filter((f) => f.validTo === null);
|
|
374
|
+
for (const fact of activeFacts) {
|
|
375
|
+
const subj = String(fact.subject || "").toLowerCase();
|
|
376
|
+
const pred = String(fact.predicate || "").toLowerCase();
|
|
377
|
+
const obj = String(fact.object || "").trim();
|
|
378
|
+
if (!obj)
|
|
379
|
+
continue;
|
|
380
|
+
if (subj === "workspace_guardrail" || pred === "enforces_rule") {
|
|
381
|
+
if (!activeGuardrails.includes(obj))
|
|
382
|
+
activeGuardrails.push(obj);
|
|
383
|
+
continue;
|
|
384
|
+
}
|
|
385
|
+
if ((subj.includes("region") || pred.includes("region")) &&
|
|
386
|
+
!activeDefaults.defaultRegion) {
|
|
387
|
+
const regToken = obj.match(/\b((?:aws-)?(?:us|eu|ap|sa|ca|me|af)-(?:east|west|central|north|south|northeast|southeast)-[1-9])\b/i);
|
|
388
|
+
if (regToken) {
|
|
389
|
+
activeDefaults.defaultRegion = regToken[1].toLowerCase();
|
|
390
|
+
}
|
|
391
|
+
else if (!/\s/.test(obj)) {
|
|
392
|
+
activeDefaults.defaultRegion = obj;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
if ((subj.includes("github") || subj.includes("repo") || pred.includes("repo")) &&
|
|
396
|
+
obj.includes("/") &&
|
|
397
|
+
!activeDefaults.defaultGithubFullRepo) {
|
|
398
|
+
const [owner, repo] = obj.split("/");
|
|
399
|
+
if (owner && repo) {
|
|
400
|
+
activeDefaults.defaultGithubOwner = owner.trim();
|
|
401
|
+
activeDefaults.defaultGithubRepo = repo.trim();
|
|
402
|
+
activeDefaults.defaultGithubFullRepo = `${owner.trim()}/${repo.trim()}`;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
if ((subj.includes("slack") || pred.includes("channel")) &&
|
|
406
|
+
!activeDefaults.defaultSlackChannel) {
|
|
407
|
+
activeDefaults.defaultSlackChannel = obj.replace(/^#/, "").trim();
|
|
408
|
+
}
|
|
409
|
+
if ((subj.includes("email") || pred.includes("email")) &&
|
|
410
|
+
obj.includes("@") &&
|
|
411
|
+
!activeDefaults.defaultEmail) {
|
|
412
|
+
activeDefaults.defaultEmail = obj;
|
|
413
|
+
}
|
|
414
|
+
if ((subj.includes("domain") || pred.includes("domain")) &&
|
|
415
|
+
!activeDefaults.defaultDomain) {
|
|
416
|
+
activeDefaults.defaultDomain = obj.replace(/^https?:\/\//i, "").replace(/\/.*$/, "");
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
const textCorpus = [
|
|
420
|
+
...(params.memories || []).map((m) => `${m.title}: ${m.content}`),
|
|
421
|
+
...(params.preferences || []),
|
|
422
|
+
...(params.staticFacts || []),
|
|
423
|
+
];
|
|
424
|
+
for (const rawText of textCorpus) {
|
|
425
|
+
const text = String(rawText || "");
|
|
426
|
+
if (!activeDefaults.defaultRegion) {
|
|
427
|
+
const regMatch = text.match(/\b((?:aws-)?(?:us|eu|ap|sa|ca|me|af)-(?:east|west|central|north|south|northeast|southeast)-[1-9])\b/i);
|
|
428
|
+
if (regMatch) {
|
|
429
|
+
activeDefaults.defaultRegion = regMatch[1].toLowerCase();
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
if (!activeDefaults.defaultGithubFullRepo) {
|
|
433
|
+
const repoMatch = text.match(/(?:default\s+(?:github\s+)?repo(?:sitory)?|github\s+repo(?:sitory)?|primary\s+repo(?:sitory)?|repo\s+is)\s*(?:is|:|=)?\s*["'`]?([A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+)["'`]?/i) ||
|
|
434
|
+
text.match(/\bgithub\.com\/([A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+)\b/i);
|
|
435
|
+
if (repoMatch) {
|
|
436
|
+
const [owner, repo] = repoMatch[1].split("/");
|
|
437
|
+
if (owner && repo) {
|
|
438
|
+
activeDefaults.defaultGithubOwner = owner;
|
|
439
|
+
activeDefaults.defaultGithubRepo = repo;
|
|
440
|
+
activeDefaults.defaultGithubFullRepo = `${owner}/${repo}`;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
if (!activeDefaults.defaultSlackChannel) {
|
|
445
|
+
const slackMatch = text.match(/(?:default\s+(?:slack\s+)?channel|slack\s+channel|post\s+to\s+channel)\s*(?:is|:|=)?\s*#?([a-z0-9_-]{2,40})\b/i) || text.match(/\B#([a-z][a-z0-9_-]{2,35})\b/);
|
|
446
|
+
if (slackMatch) {
|
|
447
|
+
activeDefaults.defaultSlackChannel = slackMatch[1].toLowerCase();
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
if (!activeDefaults.defaultEmail) {
|
|
451
|
+
const emailMatch = text.match(/(?:default\s+(?:recipient\s+|alert\s+)?email|send\s+emails?\s+to|notify\s+email|recipient_email)\s*(?:is|:|=)?\s*([A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,})/i);
|
|
452
|
+
if (emailMatch) {
|
|
453
|
+
activeDefaults.defaultEmail = emailMatch[1];
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
if (!activeDefaults.defaultDomain) {
|
|
457
|
+
const domainMatch = text.match(/(?:default\s+domain|primary\s+domain|company\s+domain)\s*(?:is|:|=)?\s*(?:https?:\/\/)?([a-z0-9.-]+\.[a-z]{2,})\b/i);
|
|
458
|
+
if (domainMatch) {
|
|
459
|
+
activeDefaults.defaultDomain = domainMatch[1].toLowerCase();
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
if (/workspace\s+guardrail/i.test(text)) {
|
|
463
|
+
const cleaned = text.replace(/^.*?Workspace Guardrail:\s*/i, "").trim();
|
|
464
|
+
if (cleaned && !activeGuardrails.includes(cleaned)) {
|
|
465
|
+
activeGuardrails.push(cleaned);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
return {
|
|
470
|
+
activeDefaults,
|
|
471
|
+
activeGuardrails,
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
export function applyActiveMemoryToToolInput(params) {
|
|
475
|
+
const mergedInput = { ...(params.input || {}) };
|
|
476
|
+
const autoFilledInput = {};
|
|
477
|
+
const explanations = [];
|
|
478
|
+
const defaults = params.activeDefaults || {};
|
|
479
|
+
const slug = String(params.toolSlug || "").toLowerCase();
|
|
480
|
+
// 1. Cloud provisioning tools: auto-fill region if not explicitly provided
|
|
481
|
+
if (slug.startsWith("resource.") && defaults.defaultRegion) {
|
|
482
|
+
if (!mergedInput.region || String(mergedInput.region).trim() === "") {
|
|
483
|
+
mergedInput.region = defaults.defaultRegion;
|
|
484
|
+
autoFilledInput.region = defaults.defaultRegion;
|
|
485
|
+
explanations.push(`Auto-filled region="${defaults.defaultRegion}" from Supermemory`);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
// 2. GitHub tools: auto-fill owner & repo if missing or generic placeholder
|
|
489
|
+
if ((slug.startsWith("app.github.") || slug === "instant.github.repo_intel") &&
|
|
490
|
+
defaults.defaultGithubOwner &&
|
|
491
|
+
defaults.defaultGithubRepo) {
|
|
492
|
+
const isGenericPlaceholder = mergedInput.owner === "muxo-ai" && mergedInput.repo === "platform";
|
|
493
|
+
if (!mergedInput.owner || isGenericPlaceholder) {
|
|
494
|
+
mergedInput.owner = defaults.defaultGithubOwner;
|
|
495
|
+
autoFilledInput.owner = defaults.defaultGithubOwner;
|
|
496
|
+
}
|
|
497
|
+
if (!mergedInput.repo || isGenericPlaceholder) {
|
|
498
|
+
mergedInput.repo = defaults.defaultGithubRepo;
|
|
499
|
+
autoFilledInput.repo = defaults.defaultGithubRepo;
|
|
500
|
+
}
|
|
501
|
+
if (autoFilledInput.owner || autoFilledInput.repo) {
|
|
502
|
+
explanations.push(`Auto-filled GitHub repo "${defaults.defaultGithubOwner}/${defaults.defaultGithubRepo}" from Supermemory`);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
// 3. Slack tools: auto-fill channel if missing or generic placeholder ("general")
|
|
506
|
+
if (slug.startsWith("app.slack.") && defaults.defaultSlackChannel) {
|
|
507
|
+
if (!mergedInput.channel || mergedInput.channel === "general") {
|
|
508
|
+
mergedInput.channel = defaults.defaultSlackChannel;
|
|
509
|
+
autoFilledInput.channel = defaults.defaultSlackChannel;
|
|
510
|
+
explanations.push(`Auto-filled Slack channel "#${defaults.defaultSlackChannel}" from Supermemory`);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
// 4. Gmail send_email: auto-fill recipient_email if missing or placeholder
|
|
514
|
+
if (slug === "app.gmail.send_email" && defaults.defaultEmail) {
|
|
515
|
+
if (!mergedInput.recipient_email ||
|
|
516
|
+
mergedInput.recipient_email === "team@example.com") {
|
|
517
|
+
mergedInput.recipient_email = defaults.defaultEmail;
|
|
518
|
+
autoFilledInput.recipient_email = defaults.defaultEmail;
|
|
519
|
+
explanations.push(`Auto-filled recipient_email="${defaults.defaultEmail}" from Supermemory`);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
// 5. DNS / Domain tools: auto-fill domain if missing or example.com
|
|
523
|
+
if ((slug === "instant.dns.resolve" || slug === "instant.dns.lookup") &&
|
|
524
|
+
defaults.defaultDomain) {
|
|
525
|
+
if (!mergedInput.domain || mergedInput.domain === "example.com") {
|
|
526
|
+
mergedInput.domain = defaults.defaultDomain;
|
|
527
|
+
autoFilledInput.domain = defaults.defaultDomain;
|
|
528
|
+
explanations.push(`Auto-filled domain="${defaults.defaultDomain}" from Supermemory`);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
// 6. SEO / Web tools: auto-fill url from defaultDomain if missing or generic placeholder
|
|
532
|
+
if ((slug === "instant.seo.audit" ||
|
|
533
|
+
slug === "instant.web.scrape" ||
|
|
534
|
+
slug === "instant.web.crawl") &&
|
|
535
|
+
defaults.defaultDomain) {
|
|
536
|
+
if (!mergedInput.url ||
|
|
537
|
+
mergedInput.url === "https://example.com") {
|
|
538
|
+
const normalizedUrl = defaults.defaultDomain.startsWith("http")
|
|
539
|
+
? defaults.defaultDomain
|
|
540
|
+
: `https://${defaults.defaultDomain}`;
|
|
541
|
+
mergedInput.url = normalizedUrl;
|
|
542
|
+
autoFilledInput.url = normalizedUrl;
|
|
543
|
+
explanations.push(`Auto-filled url="${normalizedUrl}" from Supermemory`);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
return {
|
|
547
|
+
mergedInput,
|
|
548
|
+
autoFilledInput,
|
|
549
|
+
explanations,
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
export function parseNaturalLanguageMemoryInstruction(prompt) {
|
|
553
|
+
const cleanPrompt = String(prompt || "").trim();
|
|
554
|
+
const isMemoryInstruction = /^(?:hey\s+muxo[,:\s]*)?(?:please\s+)?(?:remember(?:\s+that)?|save\s+to\s+memory|store\s+in\s+memory|note\s+that|set\s+(?:our\s+|my\s+)?default|our\s+default\s+(?:github\s+repo|repo|region|slack\s+channel|email|domain)\s+is|my\s+default\s+(?:github\s+repo|repo|region|slack\s+channel|email|domain)\s+is)\b/i.test(cleanPrompt);
|
|
555
|
+
const normalizedBody = cleanPrompt
|
|
556
|
+
.replace(/^(?:hey\s+muxo[,:\s]*)?(?:please\s+)?(?:remember(?:\s+that)?|save\s+to\s+memory|store\s+in\s+memory|note\s+that)[:\s]*/i, "")
|
|
557
|
+
.trim();
|
|
558
|
+
const { activeDefaults } = extractActiveMemoryDefaults({
|
|
559
|
+
preferences: [cleanPrompt],
|
|
560
|
+
staticFacts: [normalizedBody],
|
|
561
|
+
});
|
|
562
|
+
const temporalFacts = [];
|
|
563
|
+
const learnedParts = [];
|
|
564
|
+
if (activeDefaults.defaultRegion) {
|
|
565
|
+
temporalFacts.push({
|
|
566
|
+
subject: "workspace:default_region",
|
|
567
|
+
predicate: "prefers_region",
|
|
568
|
+
object: activeDefaults.defaultRegion,
|
|
569
|
+
summary: `Default cloud region is ${activeDefaults.defaultRegion}`,
|
|
570
|
+
});
|
|
571
|
+
learnedParts.push(`default cloud region = ${activeDefaults.defaultRegion}`);
|
|
572
|
+
}
|
|
573
|
+
if (activeDefaults.defaultGithubFullRepo) {
|
|
574
|
+
temporalFacts.push({
|
|
575
|
+
subject: "workspace:default_github_repo",
|
|
576
|
+
predicate: "prefers_repo",
|
|
577
|
+
object: activeDefaults.defaultGithubFullRepo,
|
|
578
|
+
summary: `Default GitHub repository is ${activeDefaults.defaultGithubFullRepo}`,
|
|
579
|
+
});
|
|
580
|
+
learnedParts.push(`default GitHub repo = ${activeDefaults.defaultGithubFullRepo}`);
|
|
581
|
+
}
|
|
582
|
+
if (activeDefaults.defaultSlackChannel) {
|
|
583
|
+
temporalFacts.push({
|
|
584
|
+
subject: "workspace:default_slack_channel",
|
|
585
|
+
predicate: "prefers_channel",
|
|
586
|
+
object: activeDefaults.defaultSlackChannel,
|
|
587
|
+
summary: `Default Slack channel is #${activeDefaults.defaultSlackChannel}`,
|
|
588
|
+
});
|
|
589
|
+
learnedParts.push(`default Slack channel = #${activeDefaults.defaultSlackChannel}`);
|
|
590
|
+
}
|
|
591
|
+
if (activeDefaults.defaultEmail) {
|
|
592
|
+
temporalFacts.push({
|
|
593
|
+
subject: "workspace:default_email",
|
|
594
|
+
predicate: "prefers_email",
|
|
595
|
+
object: activeDefaults.defaultEmail,
|
|
596
|
+
summary: `Default notification email is ${activeDefaults.defaultEmail}`,
|
|
597
|
+
});
|
|
598
|
+
learnedParts.push(`default email = ${activeDefaults.defaultEmail}`);
|
|
599
|
+
}
|
|
600
|
+
if (activeDefaults.defaultDomain) {
|
|
601
|
+
temporalFacts.push({
|
|
602
|
+
subject: "workspace:default_domain",
|
|
603
|
+
predicate: "prefers_domain",
|
|
604
|
+
object: activeDefaults.defaultDomain,
|
|
605
|
+
summary: `Default workspace domain is ${activeDefaults.defaultDomain}`,
|
|
606
|
+
});
|
|
607
|
+
learnedParts.push(`default domain = ${activeDefaults.defaultDomain}`);
|
|
608
|
+
}
|
|
609
|
+
if (temporalFacts.length === 0 && isMemoryInstruction && normalizedBody) {
|
|
610
|
+
temporalFacts.push({
|
|
611
|
+
subject: "workspace:operator_preference",
|
|
612
|
+
predicate: "remembers",
|
|
613
|
+
object: normalizedBody.slice(0, 140),
|
|
614
|
+
summary: normalizedBody.slice(0, 180),
|
|
615
|
+
});
|
|
616
|
+
}
|
|
617
|
+
const title = learnedParts.length > 0
|
|
618
|
+
? `Learned Default: ${learnedParts.join(", ").slice(0, 80)}`
|
|
619
|
+
: `Remembered Preference: ${(normalizedBody || cleanPrompt).slice(0, 72)}`;
|
|
620
|
+
const humanSummary = learnedParts.length > 0
|
|
621
|
+
? `Remembered in Supermemory and enabled auto-fill for: ${learnedParts.join(" · ")}.`
|
|
622
|
+
: `Saved to Supermemory: "${normalizedBody || cleanPrompt}"`;
|
|
623
|
+
return {
|
|
624
|
+
isMemoryInstruction,
|
|
625
|
+
title,
|
|
626
|
+
content: normalizedBody || cleanPrompt,
|
|
627
|
+
kind: "preference",
|
|
628
|
+
extractedDefaults: activeDefaults,
|
|
629
|
+
temporalFacts,
|
|
630
|
+
humanSummary,
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
export function explainMemoryContext(params) {
|
|
634
|
+
const mem = params.memory;
|
|
635
|
+
const defaultsEntries = Object.entries(mem.activeDefaults || {});
|
|
636
|
+
const defaultsPlain = defaultsEntries.length > 0
|
|
637
|
+
? `Learned defaults active (${defaultsEntries.map(([k, v]) => `${k}: ${String(v)}`).join(", ")}) and automatically fill into matching tool calls.`
|
|
638
|
+
: "No learned defaults set yet (say 'Remember our default region is us-east-1' or 'Remember our default repo is owner/repo' to set one).";
|
|
639
|
+
const factsCount = (mem.staticFacts?.length || 0) +
|
|
640
|
+
(mem.dynamicContext?.length || 0) +
|
|
641
|
+
(params.localCount || 0);
|
|
642
|
+
const scanSafe = mem.contextPoisoningScan?.safe !== false;
|
|
643
|
+
const headline = defaultsEntries.length > 0
|
|
644
|
+
? `Muxo Memory active with ${defaultsEntries.length} auto-fill default${defaultsEntries.length === 1 ? "" : "s"} and ${factsCount} stored workspace item${factsCount === 1 ? "" : "s"}.`
|
|
645
|
+
: `Muxo Memory active with ${factsCount} stored workspace item${factsCount === 1 ? "" : "s"}.`;
|
|
646
|
+
const nonTechnical = [
|
|
647
|
+
headline,
|
|
648
|
+
defaultsPlain,
|
|
649
|
+
mem.recalledMemories && mem.recalledMemories.length > 0
|
|
650
|
+
? `Recalled ${mem.recalledMemories.length} relevant memory note${mem.recalledMemories.length === 1 ? "" : "s"} for this context.`
|
|
651
|
+
: "All stored memories are secret-scrubbed so API keys and passwords are never saved.",
|
|
652
|
+
scanSafe
|
|
653
|
+
? "Memory safety check: Clean (no prompt-injection or poisoning patterns detected)."
|
|
654
|
+
: `Memory safety warning: ${mem.contextPoisoningScan?.findingsCount || 1} suspicious pattern(s) flagged (${mem.contextPoisoningScan?.riskLevel || "medium"}).`,
|
|
655
|
+
].join(" ");
|
|
656
|
+
const technical = `namespace=${mem.namespace} | enabled=${mem.enabled} | fundingSource=${mem.fundingSource || "local_fallback"} | staticFacts=${mem.staticFacts?.length || 0} | dynamicContext=${mem.dynamicContext?.length || 0} | recalledMemories=${mem.recalledMemories?.length || 0} | localRecords=${params.localCount ?? 0} | temporalGraph=(active=${params.activeTemporalCount ?? (mem.temporalFacts?.length || 0)}, superseded=${params.historicalTemporalCount ?? 0}) | activeDefaults=${JSON.stringify(mem.activeDefaults || {})} | contextPoisoning=(safe=${scanSafe}, risk=${mem.contextPoisoningScan?.riskLevel || "none"}, findings=${mem.contextPoisoningScan?.findingsCount || 0})`;
|
|
657
|
+
return formatHumanFriendlyExplanation({
|
|
658
|
+
headline,
|
|
659
|
+
nonTechnical,
|
|
660
|
+
technical,
|
|
661
|
+
policy: params.policy,
|
|
662
|
+
languageMode: params.languageMode,
|
|
663
|
+
});
|
|
664
|
+
}
|
|
665
|
+
/**
|
|
666
|
+
* Deterministic 64-dimensional semantic feature-hashing embedding vector
|
|
667
|
+
* compatible with Cloudflare Vectorize / Workers AI (`@cf/baai/bge-base-en-v1.5`) hybrid recall.
|
|
668
|
+
*/
|
|
669
|
+
export function computeSemanticEmbedding(text, dimensions = 64) {
|
|
670
|
+
const vec = new Array(dimensions).fill(0);
|
|
671
|
+
const normalized = String(text || "")
|
|
672
|
+
.toLowerCase()
|
|
673
|
+
.replace(/[^a-z0-9_./#@-\s]/g, " ");
|
|
674
|
+
const tokens = normalized
|
|
675
|
+
.split(/\s+/)
|
|
676
|
+
.map((t) => t.trim())
|
|
677
|
+
.filter((t) => t.length >= 2);
|
|
678
|
+
if (tokens.length === 0)
|
|
679
|
+
return vec;
|
|
680
|
+
// Semantic concept synonym expansion so related queries ("db"/"postgres", "mail"/"email", "repo"/"github") map to shared vector dimensions
|
|
681
|
+
const expandedTokens = [...tokens];
|
|
682
|
+
for (const tok of tokens) {
|
|
683
|
+
if (/^(postgres|postgresql|neon|supabase|sql|db|database)$/.test(tok)) {
|
|
684
|
+
expandedTokens.push("concept_database", "concept_postgres");
|
|
685
|
+
}
|
|
686
|
+
if (/^(redis|upstash|cache|kv)$/.test(tok)) {
|
|
687
|
+
expandedTokens.push("concept_cache", "concept_redis");
|
|
688
|
+
}
|
|
689
|
+
if (/^(repo|repository|github|git|pr|issue)$/.test(tok)) {
|
|
690
|
+
expandedTokens.push("concept_github", "concept_repo");
|
|
691
|
+
}
|
|
692
|
+
if (/^(region|us-east-1|us-west-2|eu-central-1|zone|datacenter)$/.test(tok)) {
|
|
693
|
+
expandedTokens.push("concept_region", "concept_cloud");
|
|
694
|
+
}
|
|
695
|
+
if (/^(slack|channel|chat|alert|incident)$/.test(tok)) {
|
|
696
|
+
expandedTokens.push("concept_slack", "concept_messaging");
|
|
697
|
+
}
|
|
698
|
+
if (/^(email|gmail|inbox|recipient|mail)$/.test(tok)) {
|
|
699
|
+
expandedTokens.push("concept_email", "concept_communication");
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
for (let i = 0; i < expandedTokens.length; i++) {
|
|
703
|
+
const feature = expandedTokens[i];
|
|
704
|
+
const digest = crypto.createHash("md5").update(feature).digest();
|
|
705
|
+
const dimIdx = digest[0] % dimensions;
|
|
706
|
+
const sign = (digest[1] & 1) === 0 ? 1 : -1;
|
|
707
|
+
const weight = feature.startsWith("concept_") ? 1.5 : 1.0;
|
|
708
|
+
vec[dimIdx] += sign * weight;
|
|
709
|
+
// Character trigram projection for sub-word resilience
|
|
710
|
+
for (let c = 0; c <= feature.length - 3; c++) {
|
|
711
|
+
const tri = feature.slice(c, c + 3);
|
|
712
|
+
const triDigest = crypto.createHash("md5").update(`tri:${tri}`).digest();
|
|
713
|
+
const triIdx = triDigest[0] % dimensions;
|
|
714
|
+
const triSign = (triDigest[1] & 1) === 0 ? 0.35 : -0.35;
|
|
715
|
+
vec[triIdx] += triSign;
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
const norm = Math.sqrt(vec.reduce((acc, v) => acc + v * v, 0));
|
|
719
|
+
if (norm > 0) {
|
|
720
|
+
for (let d = 0; d < dimensions; d++) {
|
|
721
|
+
vec[d] = Number((vec[d] / norm).toFixed(6));
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
return vec;
|
|
725
|
+
}
|
|
726
|
+
export function cosineSimilarity(vecA, vecB) {
|
|
727
|
+
const len = Math.min(vecA.length, vecB.length);
|
|
728
|
+
if (len === 0)
|
|
729
|
+
return 0;
|
|
730
|
+
let dot = 0;
|
|
731
|
+
let normA = 0;
|
|
732
|
+
let normB = 0;
|
|
733
|
+
for (let i = 0; i < len; i++) {
|
|
734
|
+
dot += vecA[i] * vecB[i];
|
|
735
|
+
normA += vecA[i] * vecA[i];
|
|
736
|
+
normB += vecB[i] * vecB[i];
|
|
737
|
+
}
|
|
738
|
+
if (normA === 0 || normB === 0)
|
|
739
|
+
return 0;
|
|
740
|
+
return Math.max(0, Math.min(1, dot / (Math.sqrt(normA) * Math.sqrt(normB))));
|
|
741
|
+
}
|
|
742
|
+
export function rankMemoriesHybrid(params) {
|
|
743
|
+
const q = String(params.query || "").trim();
|
|
744
|
+
const qVec = computeSemanticEmbedding(q);
|
|
745
|
+
const qTokens = q
|
|
746
|
+
.toLowerCase()
|
|
747
|
+
.split(/[^a-z0-9_./#@-]+/)
|
|
748
|
+
.filter((t) => t.length >= 2);
|
|
749
|
+
const nowMs = Date.now();
|
|
750
|
+
const scored = (params.memories || []).map((mem) => {
|
|
751
|
+
const corpus = `${mem.title} ${mem.content} ${mem.kind}`;
|
|
752
|
+
const docVec = computeSemanticEmbedding(corpus);
|
|
753
|
+
const vectorSim = q ? cosineSimilarity(qVec, docVec) : 0.5;
|
|
754
|
+
const lowerCorpus = corpus.toLowerCase();
|
|
755
|
+
const matchedTokens = qTokens.length > 0
|
|
756
|
+
? qTokens.filter((tok) => lowerCorpus.includes(tok)).length
|
|
757
|
+
: 1;
|
|
758
|
+
const lexicalOverlap = qTokens.length > 0 ? matchedTokens / qTokens.length : 0.5;
|
|
759
|
+
const ageHours = Math.max(0, (nowMs - new Date(mem.createdAt || nowMs).getTime()) / 3_600_000);
|
|
760
|
+
const recencyWeight = 1 / (1 + ageHours / 168); // 1-week half-life
|
|
761
|
+
const hybridScore = Number((0.5 * vectorSim + 0.35 * lexicalOverlap + 0.15 * recencyWeight).toFixed(4));
|
|
762
|
+
return {
|
|
763
|
+
...mem,
|
|
764
|
+
hybridScore,
|
|
765
|
+
vectorSimilarity: Number(vectorSim.toFixed(4)),
|
|
766
|
+
lexicalOverlap: Number(lexicalOverlap.toFixed(4)),
|
|
767
|
+
};
|
|
768
|
+
});
|
|
769
|
+
scored.sort((a, b) => b.hybridScore - a.hybridScore);
|
|
770
|
+
const limit = Math.max(1, Math.min(50, params.limit || 10));
|
|
771
|
+
const top = scored.slice(0, limit);
|
|
772
|
+
return {
|
|
773
|
+
rankedMemories: top,
|
|
774
|
+
vectorDimensions: 64,
|
|
775
|
+
embeddingModel: "@cf/baai/bge-base-en-v1.5 (cloudflare-vectorize-hybrid)",
|
|
776
|
+
topScore: top[0]?.hybridScore || 0,
|
|
777
|
+
};
|
|
224
778
|
}
|