@muxoai/platform 1.3.1 → 1.3.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.
@@ -4,6 +4,22 @@ import path from "node:path";
4
4
  import { promisify } from "node:util";
5
5
  import { ROOT_DIR } from "../config.js";
6
6
  const execFileAsync = promisify(execFile);
7
+ export function isPaidCloudProvisioning(tool, input) {
8
+ if (tool.lane !== "stripe_projects")
9
+ return false;
10
+ if (Boolean(tool.metadata?.isPaidService) || tool.creditCost >= 15) {
11
+ return true;
12
+ }
13
+ const requestedPlan = String(input?.plan || "")
14
+ .trim()
15
+ .toLowerCase();
16
+ if (!requestedPlan)
17
+ return false;
18
+ const planLeaf = requestedPlan.includes("/")
19
+ ? requestedPlan.split("/").pop() || requestedPlan
20
+ : requestedPlan;
21
+ return /^(pro|scaler|paid|scale|business|team|grow|credits|launch|enterprise|plus)$/i.test(planLeaf);
22
+ }
7
23
  export class StripeProjectsAdapter {
8
24
  config;
9
25
  tools = new Map();
@@ -152,6 +168,13 @@ export class StripeProjectsAdapter {
152
168
  const hasConfigProps = svc.configuration_schema &&
153
169
  typeof svc.configuration_schema === "object" &&
154
170
  Object.keys(svc.configuration_schema.properties || {}).length > 0;
171
+ const pricingType = String(svc.pricing?.type || "")
172
+ .trim()
173
+ .toLowerCase();
174
+ const isPaidService = (svc.kind === "plan" &&
175
+ ((pricingType.length > 0 && pricingType !== "free") ||
176
+ /^(pro|scaler|paid|scale|business|team|grow|credits|launch|enterprise|plus)$/i.test(svc.service_id))) ||
177
+ /^(credits|workers_paid)$/i.test(svc.service_id);
155
178
  const tool = {
156
179
  slug: muxoSlug,
157
180
  upstreamSlug,
@@ -164,7 +187,7 @@ export class StripeProjectsAdapter {
164
187
  categories: Array.from(new Set([...(svc.categories || []), "infrastructure"])),
165
188
  riskLevel: "provisioning",
166
189
  requiresAuth: false,
167
- creditCost: 5,
190
+ creditCost: isPaidService ? 25 : 5,
168
191
  billingMode: "resource_provisioning",
169
192
  inputSchema: {
170
193
  type: "object",
@@ -203,6 +226,8 @@ export class StripeProjectsAdapter {
203
226
  metadata: {
204
227
  kind: svc.kind,
205
228
  serviceId: svc.service_id,
229
+ isPaidService,
230
+ pricingType: pricingType || (isPaidService ? "paid" : "free"),
206
231
  parentPlans,
207
232
  configurationSchema: svc.configuration_schema || null,
208
233
  providerUrl: matchedProvider?.url || null,
@@ -375,9 +400,13 @@ export class StripeProjectsAdapter {
375
400
  : `Provider ${slug} is linked and ready in Stripe Projects.`,
376
401
  };
377
402
  }
378
- async pullEnvVarsFromWorkdir() {
379
- await this.runStripeProjectsCli(["env", "--pull"], 20000).catch(() => null);
403
+ async pullEnvVarsFromWorkdir(syncFromCli = false) {
404
+ if (!this.hasLocalProjectsWorkdir)
405
+ return {};
380
406
  const envPath = path.join(this.config.stripeProjectsWorkdir, ".env");
407
+ if (syncFromCli || !fs.existsSync(envPath)) {
408
+ await this.runStripeProjectsCli(["env", "--pull"], 10000).catch(() => null);
409
+ }
381
410
  if (!fs.existsSync(envPath))
382
411
  return {};
383
412
  const raw = fs.readFileSync(envPath, "utf8");
@@ -403,6 +432,15 @@ export class StripeProjectsAdapter {
403
432
  }
404
433
  async executeTool(params) {
405
434
  const { tool, input } = params;
435
+ const isPaidPlan = isPaidCloudProvisioning(tool, input);
436
+ const requestedPlanRaw = input.plan ? String(input.plan).trim() : "";
437
+ const hasCustomerStripeByok = Boolean(params.stripeApiKey && params.stripeApiKey.trim());
438
+ const isMasterWalletEmpty = Boolean(params.masterWalletDepleted) ||
439
+ process.env.MUXO_MASTER_WALLET_DEPLETED === "true" ||
440
+ input._simulateMasterWalletEmpty === true;
441
+ if (isPaidPlan && !hasCustomerStripeByok && isMasterWalletEmpty) {
442
+ throw new Error(`MUXO_MASTER_WALLET_DEPLETED: Cannot provision paid cloud plan '${requestedPlanRaw || tool.upstreamSlug}' via Muxo Master Wallet because master supplier funds are depleted. 0 credits were charged to your workspace. Configure your own Stripe key via 'muxo byok set stripe <rk_live_...>' to provision paid plans at 0 Muxo credits, or select a 'free' plan.`);
443
+ }
406
444
  if (tool.slug === "resource.projects.status") {
407
445
  const status = await this.getLiveStatus();
408
446
  return {
@@ -440,7 +478,7 @@ export class StripeProjectsAdapter {
440
478
  };
441
479
  }
442
480
  const res = await this.runStripeProjectsCli(["rotate", resourceName, "--yes"], 45000);
443
- const secrets = await this.pullEnvVarsFromWorkdir();
481
+ const secrets = await this.pullEnvVarsFromWorkdir(true);
444
482
  return {
445
483
  output: {
446
484
  provider: "stripe_projects",
@@ -523,6 +561,8 @@ export class StripeProjectsAdapter {
523
561
  [`${prefix}_URL`]: `https://${resourceName}.${tool.providerSlug}.muxo.cloud`,
524
562
  [`${prefix}_API_KEY`]: `muxo_secret_${tool.providerSlug}_${Date.now()}`,
525
563
  };
564
+ const resolvedPlanSlug = requestedPlanRaw ||
565
+ (isPaidPlan ? serviceId : tool.metadata?.parentPlans?.[0] || "free");
526
566
  return {
527
567
  output: {
528
568
  provider: "stripe_projects",
@@ -530,7 +570,9 @@ export class StripeProjectsAdapter {
530
570
  tool: tool.slug,
531
571
  upstreamService: tool.upstreamSlug,
532
572
  resourceName,
533
- provisionedPlan: tool.metadata?.parentPlans?.[0] || "free",
573
+ provisionedPlan: resolvedPlanSlug,
574
+ billingTier: isPaidPlan ? "paid" : "free",
575
+ fundingSource: hasCustomerStripeByok ? "customer_byok_stripe" : "muxo_master_wallet",
534
576
  envVarKeys: Object.keys(secrets),
535
577
  details: {
536
578
  id: `ext_${tool.providerSlug}_${Date.now()}`,
@@ -543,7 +585,7 @@ export class StripeProjectsAdapter {
543
585
  providerSlug: tool.providerSlug,
544
586
  providerName: tool.providerName,
545
587
  serviceSlug: serviceId,
546
- planSlug: tool.metadata?.parentPlans?.[0] || "free",
588
+ planSlug: resolvedPlanSlug,
547
589
  status: "active",
548
590
  secrets,
549
591
  externalResourceId: `ext_${tool.providerSlug}_${Date.now()}`,
@@ -552,7 +594,7 @@ export class StripeProjectsAdapter {
552
594
  }
553
595
  // Check if a parent plan is required first
554
596
  const parentPlans = tool.metadata?.parentPlans || [];
555
- let provisionedPlan = null;
597
+ let provisionedPlan = requestedPlanRaw || null;
556
598
  // Auto-fill required config fields (e.g. name, location/region) from configurationSchema if omitted
557
599
  const configSchema = (tool.metadata?.configurationSchema || {});
558
600
  const mergedConfig = input.config && typeof input.config === "object"
@@ -608,6 +650,11 @@ export class StripeProjectsAdapter {
608
650
  const existingSvc = liveAfter?.services?.find((s) => String(s.name || "").toLowerCase() === resourceName.toLowerCase());
609
651
  if (!existingSvc &&
610
652
  !/already exists|conflict|duplicate|already tracked|already added|in use/i.test(msg)) {
653
+ if (isPaidPlan &&
654
+ !hasCustomerStripeByok &&
655
+ /payment_method_required|insufficient_funds|card_declined|402|billing_required|master_wallet_depleted/i.test(msg)) {
656
+ throw new Error(`MUXO_MASTER_WALLET_DEPLETED: Upstream supplier billing failed for paid plan '${provisionedPlan || tool.upstreamSlug}' (${msg}). 0 credits were charged to your workspace. Configure your own Stripe key via 'muxo byok set stripe <rk_live_...>' to provision paid plans at 0 Muxo credits.`);
657
+ }
611
658
  // Fallback to Muxo Cloud Infrastructure managed provisioning when local CLI requires browser auth or provider account linking
612
659
  const prefix = tool.providerSlug.toUpperCase().replace(/[^A-Z0-9]+/g, "_");
613
660
  const fallbackSecrets = tool.providerSlug === "turso"
@@ -623,6 +670,7 @@ export class StripeProjectsAdapter {
623
670
  };
624
671
  const authUrlMatch = msg.match(/https?:\/\/[^\s"'<>]+/i);
625
672
  const fallbackKeys = Object.keys(fallbackSecrets);
673
+ const resolvedFallbackPlan = provisionedPlan || (isPaidPlan ? serviceId : parentPlans[0] || "free");
626
674
  return {
627
675
  output: {
628
676
  provider: "stripe_projects",
@@ -630,7 +678,11 @@ export class StripeProjectsAdapter {
630
678
  tool: tool.slug,
631
679
  upstreamService: tool.upstreamSlug,
632
680
  resourceName,
633
- provisionedPlan: provisionedPlan || parentPlans[0] || "free",
681
+ provisionedPlan: resolvedFallbackPlan,
682
+ billingTier: isPaidPlan ? "paid" : "free",
683
+ fundingSource: hasCustomerStripeByok
684
+ ? "customer_byok_stripe"
685
+ : "muxo_master_wallet",
634
686
  envVarKeys: fallbackKeys,
635
687
  vaultSavedKeys: fallbackKeys,
636
688
  secretKeysSaved: fallbackKeys,
@@ -646,7 +698,7 @@ export class StripeProjectsAdapter {
646
698
  providerSlug: tool.providerSlug,
647
699
  providerName: tool.providerName,
648
700
  serviceSlug: serviceId,
649
- planSlug: provisionedPlan || parentPlans[0] || "free",
701
+ planSlug: resolvedFallbackPlan,
650
702
  status: "active",
651
703
  secrets: fallbackSecrets,
652
704
  externalResourceId: `ext_${tool.providerSlug}_${Date.now()}`,
@@ -672,7 +724,9 @@ export class StripeProjectsAdapter {
672
724
  tool: tool.slug,
673
725
  upstreamService: tool.upstreamSlug,
674
726
  resourceName,
675
- provisionedPlan,
727
+ provisionedPlan: provisionedPlan || (isPaidPlan ? serviceId : parentPlans[0] || "free"),
728
+ billingTier: isPaidPlan ? "paid" : "free",
729
+ fundingSource: hasCustomerStripeByok ? "customer_byok_stripe" : "muxo_master_wallet",
676
730
  envVarKeys,
677
731
  vaultSavedKeys: envVarKeys,
678
732
  secretKeysSaved: envVarKeys,
@@ -8,10 +8,12 @@ import type { MemoryContextSummary, MemoryRecordKind } from "../types.js";
8
8
  export declare class SupermemoryAdapter {
9
9
  private readonly config;
10
10
  private readonly client;
11
+ private readonly byokClients;
11
12
  private unavailableUntil;
12
13
  constructor(config: MuxoConfig);
14
+ private resolveClient;
13
15
  private withTimeout;
14
- isEnabled(): boolean;
16
+ isEnabled(apiKeyOverride?: string | null): boolean;
15
17
  /**
16
18
  * Derives a strict Supermemory v5 namespace (`^[a-zA-Z0-9_:-]+$`) scoped to a workspace.
17
19
  */
@@ -24,15 +26,17 @@ export declare class SupermemoryAdapter {
24
26
  sanitizeContent(raw: string): string;
25
27
  /**
26
28
  * Fetches the workspace's static + dynamic profile and optionally semantic search results
27
- * in a single fail-open call.
29
+ * in a single fail-open call. Supports customer BYOK `supermemory` keys via `apiKeyOverride`.
28
30
  */
29
31
  getContextForWorkspace(workspaceIdOrParams: string | {
30
32
  workspaceId: string;
31
33
  query?: string;
32
34
  limit?: number;
33
- }, optionalQuery?: string): Promise<MemoryContextSummary>;
35
+ apiKeyOverride?: string | null;
36
+ }, optionalQuery?: string, optionalApiKeyOverride?: string | null): Promise<MemoryContextSummary>;
34
37
  /**
35
- * Stores a governed, secret-sanitized document/memory in the workspace's Supermemory v5 namespace.
38
+ * Stores a governed, secret-sanitized document/memory in the workspace's Supermemory v5 namespace
39
+ * with `dreaming: "instant"` so profile facts are synthesized immediately.
36
40
  */
37
41
  addMemory(params: {
38
42
  workspaceId: string;
@@ -41,11 +45,14 @@ export declare class SupermemoryAdapter {
41
45
  content: string;
42
46
  kind: MemoryRecordKind;
43
47
  metadata?: Record<string, unknown>;
48
+ apiKeyOverride?: string | null;
49
+ dreaming?: "auto" | "instant";
44
50
  }): Promise<{
45
51
  documentId: string;
46
52
  namespace: string;
47
53
  status: string;
48
54
  sanitizedContent: string;
55
+ fundingSource: "muxo_funded" | "customer_byok_supermemory" | "local_fallback";
49
56
  }>;
50
57
  recordExecutionMemory(params: {
51
58
  workspaceId: string;
@@ -55,12 +62,26 @@ export declare class SupermemoryAdapter {
55
62
  reason: string;
56
63
  status: string;
57
64
  summary: string;
65
+ apiKeyOverride?: string | null;
58
66
  }): Promise<string | null>;
59
67
  /**
60
68
  * Deletes/forgets a memory document from the workspace's Supermemory v5 namespace.
61
69
  */
62
- deleteMemory(workspaceId: string, documentId: string): Promise<{
70
+ deleteMemory(workspaceId: string, documentId: string, apiKeyOverride?: string | null): Promise<{
63
71
  deleted: boolean;
64
72
  namespace: string;
65
73
  }>;
74
+ /**
75
+ * Semantically searches and forgets/prunes outdated memories in Supermemory v5 matching a query.
76
+ */
77
+ forgetMatching(params: {
78
+ workspaceId: string;
79
+ query: string;
80
+ limit?: number;
81
+ apiKeyOverride?: string | null;
82
+ }): Promise<{
83
+ namespace: string;
84
+ deletedRemoteIds: string[];
85
+ fundingSource: "muxo_funded" | "customer_byok_supermemory" | "local_fallback";
86
+ }>;
66
87
  }
@@ -1,5 +1,22 @@
1
1
  import crypto from "node:crypto";
2
2
  import Supermemory from "supermemory";
3
+ // Profile buckets can hold plain strings or memory objects depending on API version.
4
+ function toFactTexts(items) {
5
+ if (!Array.isArray(items))
6
+ return [];
7
+ return items
8
+ .map((item) => {
9
+ if (item == null)
10
+ return "";
11
+ if (typeof item !== "object")
12
+ return String(item);
13
+ const o = item;
14
+ const text = o.memory ?? o.content ?? o.text ?? o.fact ?? o.value ?? o.summary ?? o.title;
15
+ return typeof text === "string" ? text : JSON.stringify(item);
16
+ })
17
+ .map((s) => s.trim())
18
+ .filter(Boolean);
19
+ }
3
20
  /**
4
21
  * Supermemory v5 Contextual Brain & Context-Aware Tool Call Adapter.
5
22
  * Uses deterministic per-workspace namespaces (`/ns/muxo-v5-<hash>/...`)
@@ -8,6 +25,7 @@ import Supermemory from "supermemory";
8
25
  export class SupermemoryAdapter {
9
26
  config;
10
27
  client;
28
+ byokClients = new Map();
11
29
  unavailableUntil = 0;
12
30
  constructor(config) {
13
31
  this.config = config;
@@ -16,19 +34,48 @@ export class SupermemoryAdapter {
16
34
  apiKey: config.supermemoryApiKey,
17
35
  baseUrl: config.supermemoryBaseUrl,
18
36
  maxRetries: 0,
37
+ timeoutInSeconds: 2,
19
38
  });
20
39
  }
21
40
  else {
22
41
  this.client = null;
23
42
  }
24
43
  }
25
- withTimeout(p, ms = 1000) {
44
+ resolveClient(apiKeyOverride) {
45
+ const trimmedByok = String(apiKeyOverride || "").trim();
46
+ if (trimmedByok) {
47
+ let byokClient = this.byokClients.get(trimmedByok);
48
+ if (!byokClient) {
49
+ byokClient = new Supermemory({
50
+ apiKey: trimmedByok,
51
+ baseUrl: this.config.supermemoryBaseUrl,
52
+ maxRetries: 0,
53
+ timeoutInSeconds: 2,
54
+ });
55
+ this.byokClients.set(trimmedByok, byokClient);
56
+ }
57
+ return {
58
+ client: byokClient,
59
+ fundingSource: "customer_byok_supermemory",
60
+ };
61
+ }
62
+ if (this.client) {
63
+ return {
64
+ client: this.client,
65
+ fundingSource: "muxo_funded",
66
+ };
67
+ }
68
+ return {
69
+ client: null,
70
+ fundingSource: "local_fallback",
71
+ };
72
+ }
73
+ withTimeout(fn, ms = 1000) {
26
74
  if (Date.now() < this.unavailableUntil) {
27
- p.catch(() => { });
28
75
  return Promise.resolve(null);
29
76
  }
30
77
  return Promise.race([
31
- p,
78
+ fn(),
32
79
  new Promise((resolve) => {
33
80
  const t = setTimeout(() => {
34
81
  this.unavailableUntil = Date.now() + 30_000;
@@ -41,8 +88,8 @@ export class SupermemoryAdapter {
41
88
  return null;
42
89
  });
43
90
  }
44
- isEnabled() {
45
- return Boolean(this.client);
91
+ isEnabled(apiKeyOverride) {
92
+ return Boolean(this.resolveClient(apiKeyOverride).client);
46
93
  }
47
94
  /**
48
95
  * Derives a strict Supermemory v5 namespace (`^[a-zA-Z0-9_:-]+$`) scoped to a workspace.
@@ -70,21 +117,29 @@ export class SupermemoryAdapter {
70
117
  }
71
118
  /**
72
119
  * Fetches the workspace's static + dynamic profile and optionally semantic search results
73
- * in a single fail-open call.
120
+ * in a single fail-open call. Supports customer BYOK `supermemory` keys via `apiKeyOverride`.
74
121
  */
75
- async getContextForWorkspace(workspaceIdOrParams, optionalQuery) {
122
+ async getContextForWorkspace(workspaceIdOrParams, optionalQuery, optionalApiKeyOverride) {
76
123
  const params = typeof workspaceIdOrParams === "string"
77
- ? { workspaceId: workspaceIdOrParams, query: optionalQuery, limit: 5 }
124
+ ? {
125
+ workspaceId: workspaceIdOrParams,
126
+ query: optionalQuery,
127
+ limit: 5,
128
+ apiKeyOverride: optionalApiKeyOverride,
129
+ }
78
130
  : workspaceIdOrParams;
79
131
  const namespace = this.namespaceForWorkspace(params.workspaceId);
80
- if (!this.client) {
132
+ const { client, fundingSource } = this.resolveClient(params.apiKeyOverride);
133
+ if (!client) {
81
134
  return {
82
135
  enabled: false,
83
136
  namespace,
137
+ fundingSource,
84
138
  staticFacts: [],
85
139
  dynamicContext: [],
86
140
  recalledMemories: [],
87
141
  formattedPromptContext: "Supermemory v5 disabled (no API key configured).",
142
+ profileMarkdown: "",
88
143
  profile: { static: [], dynamic: [], preferences: [] },
89
144
  relevantMemories: [],
90
145
  };
@@ -95,11 +150,11 @@ export class SupermemoryAdapter {
95
150
  let preferences = [];
96
151
  const relevantMemories = [];
97
152
  const [profileOutcome, searchOutcome] = await Promise.allSettled([
98
- this.withTimeout(this.client.profile(namespace, {
153
+ this.withTimeout(() => client.profile(namespace, {
99
154
  ...(params.query ? { q: params.query } : {}),
100
155
  }), 1000),
101
156
  params.query && params.query.trim().length > 0
102
- ? this.withTimeout(this.client.search(namespace, {
157
+ ? this.withTimeout(() => client.search(namespace, {
103
158
  query: params.query.trim(),
104
159
  limit,
105
160
  searchMode: "hybrid",
@@ -110,15 +165,9 @@ export class SupermemoryAdapter {
110
165
  ]);
111
166
  if (profileOutcome.status === "fulfilled" && profileOutcome.value) {
112
167
  const profileRes = profileOutcome.value;
113
- staticFacts = Array.isArray(profileRes?.profile?.static)
114
- ? profileRes.profile.static.map(String)
115
- : [];
116
- dynamicFacts = Array.isArray(profileRes?.profile?.dynamic)
117
- ? profileRes.profile.dynamic.map(String)
118
- : [];
119
- preferences = Array.isArray(profileRes?.profile?.buckets?.preferences)
120
- ? profileRes.profile.buckets.preferences.map(String)
121
- : [];
168
+ staticFacts = toFactTexts(profileRes?.profile?.static);
169
+ dynamicFacts = toFactTexts(profileRes?.profile?.dynamic);
170
+ preferences = toFactTexts(profileRes?.profile?.buckets?.preferences);
122
171
  }
123
172
  if (searchOutcome.status === "fulfilled" && searchOutcome.value) {
124
173
  const searchRes = searchOutcome.value;
@@ -150,13 +199,25 @@ export class SupermemoryAdapter {
150
199
  if (relevantMemories.length > 0) {
151
200
  promptLines.push(`Recalled Memories: ${relevantMemories.map((m) => `${m.title}: ${m.snippet}`).join(" | ")}`);
152
201
  }
202
+ const mdLines = [`## Workspace Profile (${namespace})`];
203
+ if (staticFacts.length > 0) {
204
+ mdLines.push("### Static Facts", ...staticFacts.map((f) => `- ${f}`));
205
+ }
206
+ if (dynamicFacts.length > 0) {
207
+ mdLines.push("### Dynamic Context", ...dynamicFacts.map((f) => `- ${f}`));
208
+ }
209
+ if (preferences.length > 0) {
210
+ mdLines.push("### Preferences", ...preferences.map((f) => `- ${f}`));
211
+ }
153
212
  return {
154
213
  enabled: true,
155
214
  namespace,
215
+ fundingSource,
156
216
  staticFacts,
157
217
  dynamicContext: dynamicFacts,
158
218
  recalledMemories: relevantMemories,
159
219
  formattedPromptContext: promptLines.join("\n"),
220
+ profileMarkdown: mdLines.join("\n"),
160
221
  profile: {
161
222
  static: staticFacts,
162
223
  dynamic: dynamicFacts,
@@ -166,24 +227,28 @@ export class SupermemoryAdapter {
166
227
  };
167
228
  }
168
229
  /**
169
- * Stores a governed, secret-sanitized document/memory in the workspace's Supermemory v5 namespace.
230
+ * Stores a governed, secret-sanitized document/memory in the workspace's Supermemory v5 namespace
231
+ * with `dreaming: "instant"` so profile facts are synthesized immediately.
170
232
  */
171
233
  async addMemory(params) {
172
234
  const namespace = this.namespaceForWorkspace(params.workspaceId);
173
235
  const sanitizedContent = this.sanitizeContent(params.content);
174
236
  const docId = params.documentId || `mem_${crypto.randomBytes(8).toString("hex")}`;
175
- if (!this.client) {
237
+ const { client, fundingSource } = this.resolveClient(params.apiKeyOverride);
238
+ if (!client) {
176
239
  return {
177
240
  documentId: docId,
178
241
  namespace,
179
242
  status: "local_only",
180
243
  sanitizedContent,
244
+ fundingSource,
181
245
  };
182
246
  }
183
- const res = await this.withTimeout(this.client.add(namespace, {
247
+ const res = await this.withTimeout(() => client.add(namespace, {
184
248
  id: docId,
185
249
  content: `${params.title}\n\n${sanitizedContent}`,
186
250
  taskType: "memory",
251
+ dreaming: params.dreaming || "instant",
187
252
  metadata: {
188
253
  title: params.title,
189
254
  muxoWorkspaceId: params.workspaceId,
@@ -196,6 +261,7 @@ export class SupermemoryAdapter {
196
261
  namespace,
197
262
  status: String(res?.status || "queued"),
198
263
  sanitizedContent,
264
+ fundingSource,
199
265
  };
200
266
  }
201
267
  async recordExecutionMemory(params) {
@@ -205,6 +271,7 @@ export class SupermemoryAdapter {
205
271
  title: `Execution: ${params.toolSlug} (${params.status})`,
206
272
  content: params.summary,
207
273
  kind: "execution_outcome",
274
+ apiKeyOverride: params.apiKeyOverride,
208
275
  metadata: {
209
276
  toolSlug: params.toolSlug,
210
277
  lane: params.lane,
@@ -221,13 +288,14 @@ export class SupermemoryAdapter {
221
288
  /**
222
289
  * Deletes/forgets a memory document from the workspace's Supermemory v5 namespace.
223
290
  */
224
- async deleteMemory(workspaceId, documentId) {
291
+ async deleteMemory(workspaceId, documentId, apiKeyOverride) {
225
292
  const namespace = this.namespaceForWorkspace(workspaceId);
226
- if (!this.client) {
293
+ const { client } = this.resolveClient(apiKeyOverride);
294
+ if (!client) {
227
295
  return { deleted: true, namespace };
228
296
  }
229
297
  try {
230
- const res = await this.withTimeout(this.client.documents.delete(namespace, {
298
+ const res = await this.withTimeout(() => client.documents.delete(namespace, {
231
299
  ids: [documentId],
232
300
  }), 1200);
233
301
  return {
@@ -239,4 +307,46 @@ export class SupermemoryAdapter {
239
307
  return { deleted: false, namespace };
240
308
  }
241
309
  }
310
+ /**
311
+ * Semantically searches and forgets/prunes outdated memories in Supermemory v5 matching a query.
312
+ */
313
+ async forgetMatching(params) {
314
+ const namespace = this.namespaceForWorkspace(params.workspaceId);
315
+ const { client, fundingSource } = this.resolveClient(params.apiKeyOverride);
316
+ if (!client || !params.query.trim()) {
317
+ return {
318
+ namespace,
319
+ deletedRemoteIds: [],
320
+ fundingSource,
321
+ };
322
+ }
323
+ try {
324
+ const searchRes = await this.withTimeout(() => client.search(namespace, {
325
+ query: params.query.trim(),
326
+ limit: Math.min(10, Math.max(1, Number(params.limit || 5))),
327
+ searchMode: "hybrid",
328
+ include: { documents: true, forgotten: false, related: false },
329
+ rewriteQuery: false,
330
+ }), 1200);
331
+ const docIds = Array.from(new Set((searchRes?.results || [])
332
+ .map((r) => String(r.included?.document?.id || r.id || ""))
333
+ .filter(Boolean)));
334
+ if (docIds.length > 0) {
335
+ await this.withTimeout(() => client.documents.delete(namespace, { ids: docIds }), 1200);
336
+ return {
337
+ namespace,
338
+ deletedRemoteIds: docIds,
339
+ fundingSource,
340
+ };
341
+ }
342
+ }
343
+ catch {
344
+ // Fail-open on remote pruning errors
345
+ }
346
+ return {
347
+ namespace,
348
+ deletedRemoteIds: [],
349
+ fundingSource,
350
+ };
351
+ }
242
352
  }