assistme 0.2.8 → 0.2.9

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.
@@ -0,0 +1,7 @@
1
+ import {
2
+ JobRunner
3
+ } from "./chunk-UWE5WVQI.js";
4
+ import "./chunk-TTEGHE2E.js";
5
+ export {
6
+ JobRunner
7
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "assistme",
3
- "version": "0.2.8",
3
+ "version": "0.2.9",
4
4
  "description": "AssistMe CLI Agent - AI-powered assistant that controls your real browser",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -20,7 +20,6 @@
20
20
  },
21
21
  "dependencies": {
22
22
  "@anthropic-ai/claude-agent-sdk": "^0.2.63",
23
- "@supabase/supabase-js": "^2.76.0",
24
23
  "zod": "^4.0.0",
25
24
  "chalk": "^5.4.1",
26
25
  "commander": "^13.1.0",
@@ -1,4 +1,4 @@
1
- import { getSupabase } from "../db/supabase.js";
1
+ import { callMcpHandler } from "../db/api-client.js";
2
2
  import { log } from "../utils/logger.js";
3
3
 
4
4
  // ── Interfaces ─────────────────────────────────────────────────────
@@ -31,10 +31,8 @@ export interface JobRunInfo {
31
31
  // ── JobRunner ──────────────────────────────────────────────────────
32
32
 
33
33
  export class JobRunner {
34
- private userId: string;
35
-
36
- constructor(userId: string) {
37
- this.userId = userId;
34
+ constructor(_userId: string) {
35
+ // userId is no longer needed — auth is handled by the MCP token in callMcpHandler
38
36
  }
39
37
 
40
38
  /**
@@ -43,13 +41,12 @@ export class JobRunner {
43
41
  */
44
42
  async loadJob(jobName: string): Promise<JobInfo | null> {
45
43
  try {
46
- const sb = getSupabase();
47
- const { data, error } = await sb.rpc("get_job_with_skills", {
48
- p_user_id: this.userId,
49
- p_job_name: jobName,
50
- });
44
+ const data = await callMcpHandler<Array<Record<string, unknown>> | null>(
45
+ "job.get_with_skills",
46
+ { job_name: jobName },
47
+ );
51
48
 
52
- if (error || !data || (data as unknown[]).length === 0) {
49
+ if (!data || (data as unknown[]).length === 0) {
53
50
  return null;
54
51
  }
55
52
 
@@ -79,33 +76,16 @@ export class JobRunner {
79
76
  */
80
77
  async listJobs(): Promise<Array<{ id: string; name: string; description: string; skillCount: number }>> {
81
78
  try {
82
- const sb = getSupabase();
83
-
84
- const { data: jobs, error } = await sb
85
- .from("agent_jobs")
86
- .select("id, name, description")
87
- .eq("user_id", this.userId)
88
- .eq("is_active", true)
89
- .order("name");
90
-
91
- if (error || !jobs) return [];
92
-
93
- const result: Array<{ id: string; name: string; description: string; skillCount: number }> = [];
94
- for (const job of jobs) {
95
- const { count } = await sb
96
- .from("agent_job_skills")
97
- .select("id", { count: "exact", head: true })
98
- .eq("job_id", job.id);
99
-
100
- result.push({
101
- id: job.id,
102
- name: job.name,
103
- description: job.description,
104
- skillCount: count || 0,
105
- });
106
- }
107
-
108
- return result;
79
+ const data = await callMcpHandler<Array<Record<string, unknown>>>(
80
+ "job.list",
81
+ );
82
+
83
+ return (data || []).map((row) => ({
84
+ id: row.id as string,
85
+ name: row.name as string,
86
+ description: row.description as string,
87
+ skillCount: (row.skill_count as number) || 0,
88
+ }));
109
89
  } catch (err) {
110
90
  log.debug(`Failed to list jobs: ${err}`);
111
91
  return [];
@@ -122,21 +102,13 @@ export class JobRunner {
122
102
  options?: { sessionId?: string; messageId?: string; triggerType?: string }
123
103
  ): Promise<string | null> {
124
104
  try {
125
- const sb = getSupabase();
126
- const { data, error } = await sb.rpc("create_job_run", {
127
- p_user_id: this.userId,
128
- p_job_id: jobId,
129
- p_session_id: options?.sessionId || null,
130
- p_message_id: options?.messageId || null,
131
- p_trigger_type: options?.triggerType || "manual",
105
+ const data = await callMcpHandler<string>("job.create_run", {
106
+ job_id: jobId,
107
+ session_id: options?.sessionId || null,
108
+ message_id: options?.messageId || null,
109
+ trigger_type: options?.triggerType || "manual",
132
110
  });
133
-
134
- if (error) {
135
- log.debug(`Failed to create job run: ${error.message}`);
136
- return null;
137
- }
138
-
139
- return data as string;
111
+ return data;
140
112
  } catch (err) {
141
113
  log.debug(`Job run creation error: ${err}`);
142
114
  return null;
@@ -152,17 +124,11 @@ export class JobRunner {
152
124
  status: "completed" | "failed" | "cancelled" = "completed",
153
125
  summary?: string
154
126
  ): Promise<void> {
155
- const sb = getSupabase();
156
- const { error } = await sb.rpc("complete_job_run", {
157
- p_run_id: runId,
158
- p_status: status,
159
- p_summary: summary?.slice(0, 10000) || null,
160
- p_user_id: this.userId,
127
+ await callMcpHandler("job.complete_run", {
128
+ run_id: runId,
129
+ status,
130
+ summary: summary?.slice(0, 10000) || null,
161
131
  });
162
- if (error) {
163
- log.error(`Failed to mark run ${runId} as ${status}: ${error.message}`);
164
- throw new Error(`Run completion failed: ${error.message}`);
165
- }
166
132
  }
167
133
 
168
134
  /**
@@ -170,16 +136,12 @@ export class JobRunner {
170
136
  */
171
137
  async getRunHistory(jobName?: string, limit = 10): Promise<JobRunInfo[]> {
172
138
  try {
173
- const sb = getSupabase();
174
- const { data, error } = await sb.rpc("get_job_runs", {
175
- p_user_id: this.userId,
176
- p_job_name: jobName || null,
177
- p_limit: limit,
178
- });
179
-
180
- if (error || !data) return [];
139
+ const data = await callMcpHandler<Array<Record<string, unknown>>>(
140
+ "job.get_runs",
141
+ { job_name: jobName || null, limit },
142
+ );
181
143
 
182
- return (data as Array<Record<string, unknown>>).map((row) => ({
144
+ return (data || []).map((row) => ({
183
145
  runId: row.run_id as string,
184
146
  jobName: row.job_name as string,
185
147
  status: row.status as string,
@@ -8,9 +8,10 @@ import { executeTool } from "../tools/index.js";
8
8
  import { getLimiterForTool } from "../utils/rate-limiter.js";
9
9
  import { log } from "../utils/logger.js";
10
10
  import type { MemoryManager, MemoryCategory } from "./memory.js";
11
- import type { SkillManager, SkillVariable } from "./skills.js";
12
- import { substituteArguments, substituteVariables, preprocessDynamicContext } from "./skills.js";
13
- import { getSupabase, emitEvent, setActionRequest, pollActionResponse } from "../db/supabase.js";
11
+ import type { SkillManager } from "./skills.js";
12
+ import { substituteArguments, preprocessDynamicContext } from "./skills.js";
13
+ import { emitEvent, setActionRequest, pollActionResponse } from "../db/supabase.js";
14
+ import { callMcpHandler } from "../db/api-client.js";
14
15
  import { JobRunner } from "./job-runner.js";
15
16
  import {
16
17
  createScheduledTask,
@@ -214,26 +215,12 @@ export function createAgentToolsServer(deps: AgentToolsDeps): McpSdkServerConfig
214
215
  ),
215
216
  tool(
216
217
  "skill_create",
217
- "Create a new skill and add it to the user's collection. Returns the skill ID on success. " +
218
- "Use {{variable_name}} syntax in instructions for user-specific data (e.g. {{github_repos}}, {{slack_channel}}).",
218
+ "Create a new skill and add it to the user's collection. Returns the skill ID on success.",
219
219
  {
220
220
  name: z.string().describe("Skill name in kebab-case, e.g. 'flight-booking'"),
221
221
  description: z.string().describe("One-line description of what this skill does"),
222
- instructions: z.string().describe(
223
- "Markdown step-by-step instructions. Use {{variable_name}} placeholders for user-specific data " +
224
- "(e.g. {{github_repos}}, {{trello_board}}, {{slack_channel}}). These will be resolved with user's config at invoke time."
225
- ),
222
+ instructions: z.string().describe("Markdown step-by-step instructions"),
226
223
  emoji: z.string().optional().describe("Single emoji representing this skill"),
227
- variables: z.array(z.object({
228
- name: z.string().describe("Variable name matching {{name}} in instructions"),
229
- description: z.string().describe("Human-readable description of what this variable is for"),
230
- type: z.enum(["string", "string[]", "number", "boolean"]).describe("Data type"),
231
- required: z.boolean().describe("Whether the skill needs this to function"),
232
- default: z.string().optional().describe("Default value if user doesn't configure"),
233
- example: z.string().optional().describe("Example value to guide the user"),
234
- })).optional().describe(
235
- "Variables that need user-specific configuration. Define one for each {{variable}} used in instructions."
236
- ),
237
224
  },
238
225
  async (args) => {
239
226
  // Check for duplicates in user's collection
@@ -254,11 +241,7 @@ export function createAgentToolsServer(deps: AgentToolsDeps): McpSdkServerConfig
254
241
  args.name,
255
242
  args.description,
256
243
  args.instructions,
257
- {
258
- source: "manual",
259
- emoji: args.emoji,
260
- variables: args.variables as SkillVariable[] | undefined,
261
- }
244
+ { source: "manual", emoji: args.emoji }
262
245
  );
263
246
 
264
247
  if (!result) {
@@ -277,27 +260,17 @@ export function createAgentToolsServer(deps: AgentToolsDeps): McpSdkServerConfig
277
260
  source: "manual",
278
261
  emoji: args.emoji,
279
262
  sourceSkillId: result.id,
280
- variables: args.variables as SkillVariable[] | undefined,
281
263
  }
282
264
  );
283
265
 
284
- // Build response with variable info
285
- let responseText = `Skill "${args.name}" created and added to your collection (ID: ${result.id}).`;
286
- if (args.variables && args.variables.length > 0) {
287
- const requiredVars = args.variables.filter((v) => v.required);
288
- if (requiredVars.length > 0) {
289
- responseText += `\n\n**Variables that need configuration:**\n`;
290
- for (const v of requiredVars) {
291
- const example = v.example ? ` (e.g. ${v.example})` : "";
292
- responseText += `- \`{{${v.name}}}\`: ${v.description}${example}\n`;
293
- }
294
- responseText += `\nUse \`skill_configure\` to set these values for the user, or ask the user to provide them.`;
295
- }
296
- }
297
-
298
266
  log.success(`Skill "${args.name}" created and added to collection`);
299
267
  return {
300
- content: [{ type: "text", text: responseText }],
268
+ content: [
269
+ {
270
+ type: "text",
271
+ text: `Skill "${args.name}" created and added to your collection (ID: ${result.id}).`,
272
+ },
273
+ ],
301
274
  };
302
275
  }
303
276
  ),
@@ -382,9 +355,6 @@ export function createAgentToolsServer(deps: AgentToolsDeps): McpSdkServerConfig
382
355
 
383
356
  let content = skill.content;
384
357
 
385
- // Substitute {{variable}} placeholders with user config values
386
- content = substituteVariables(content, skill.config, skill.variables);
387
-
388
358
  // Substitute $ARGUMENTS placeholders
389
359
  if (args.arguments) {
390
360
  content = substituteArguments(content, args.arguments);
@@ -405,16 +375,6 @@ export function createAgentToolsServer(deps: AgentToolsDeps): McpSdkServerConfig
405
375
  response += `\n\n**Allowed tools for this skill:** ${skill.allowedTools.join(", ")}\n`;
406
376
  }
407
377
 
408
- // Warn about unconfigured required variables
409
- const unconfigured = skillManager.getUnconfiguredVariables(args.name);
410
- if (unconfigured.length > 0) {
411
- response += `\n\n**⚠ Unconfigured variables — use \`skill_configure\` to set these:**\n`;
412
- for (const v of unconfigured) {
413
- const example = v.example ? ` (e.g. ${v.example})` : "";
414
- response += `- \`{{${v.name}}}\`: ${v.description}${example}\n`;
415
- }
416
- }
417
-
418
378
  log.info(`Skill invoked: "${args.name}"`);
419
379
 
420
380
  // Log invocation to DB (fire-and-forget)
@@ -457,56 +417,6 @@ export function createAgentToolsServer(deps: AgentToolsDeps): McpSdkServerConfig
457
417
  return { content: [{ type: "text", text: response }] };
458
418
  }
459
419
  ),
460
- tool(
461
- "skill_configure",
462
- "Set user-specific variable values for a skill. Variables are defined in the skill template (e.g. {{github_repos}}) " +
463
- "and need to be configured with the user's actual data before the skill can work properly.",
464
- {
465
- name: z.string().describe("Name of the skill to configure"),
466
- config: z.record(z.unknown()).describe(
467
- "Key-value pairs of variable values. Keys must match variable names defined in the skill. " +
468
- "Example: {\"github_repos\": [\"octocat/hello-world\", \"myorg/myrepo\"], \"github_username\": \"octocat\"}"
469
- ),
470
- },
471
- async (args) => {
472
- const skill = skillManager.get(args.name);
473
- if (!skill) {
474
- const available = skillManager.getAll().map((s) => s.name).join(", ");
475
- return {
476
- content: [{
477
- type: "text",
478
- text: `Skill "${args.name}" not found. Available: ${available}`,
479
- }],
480
- };
481
- }
482
-
483
- const updated = await skillManager.updateConfig(args.name, args.config);
484
- if (!updated) {
485
- return {
486
- content: [{ type: "text", text: `Failed to update config for "${args.name}".` }],
487
- };
488
- }
489
-
490
- // Check if there are still unconfigured required variables
491
- const unconfigured = skillManager.getUnconfiguredVariables(args.name);
492
- let responseText = `Configuration updated for skill "${args.name}".`;
493
-
494
- if (unconfigured.length > 0) {
495
- responseText += `\n\n**Still needs configuration:**\n`;
496
- for (const v of unconfigured) {
497
- const example = v.example ? ` (e.g. ${v.example})` : "";
498
- responseText += `- \`{{${v.name}}}\`: ${v.description}${example}\n`;
499
- }
500
- } else {
501
- responseText += ` All required variables are configured.`;
502
- }
503
-
504
- log.info(`Config updated for skill "${args.name}": ${Object.keys(args.config).join(", ")}`);
505
- return {
506
- content: [{ type: "text", text: responseText }],
507
- };
508
- }
509
- ),
510
420
  tool(
511
421
  "skill_generate",
512
422
  "Prepare context for generating skills from a job description. Returns existing skills and job info " +
@@ -542,39 +452,15 @@ export function createAgentToolsServer(deps: AgentToolsDeps): McpSdkServerConfig
542
452
  response += `- name: kebab-case name (e.g. "slack-message-check")\n`;
543
453
  response += `- description: one-line description\n`;
544
454
  response += `- instructions: detailed step-by-step markdown instructions the agent can follow\n`;
545
- response += `- emoji: a single emoji representing the skill\n`;
546
- response += `- variables: define user-specific data needed by the skill (see below)\n\n`;
455
+ response += `- emoji: a single emoji representing the skill\n\n`;
547
456
  response += `skill_create automatically adds the skill to the user's collection — no need to call skill_add.\n\n`;
548
457
  response += `After ALL skills are created, call \`skill_link_job\` with job_name="${args.job_name}" and the list of created skill names to link them and mark the job as analyzed.\n\n`;
549
458
  response += `**Guidelines for skill instructions:**\n`;
550
459
  response += `- Write clear, actionable markdown steps\n`;
551
460
  response += `- Reference browser tools (browser_navigate, browser_click, browser_read_page, etc.) for web tasks\n`;
552
461
  response += `- Include error handling steps\n`;
553
- response += `- Each skill should be a single, well-defined workflow (10-25 steps)\n\n`;
554
- response += `**IMPORTANT Use {{variables}} for user-specific data:**\n`;
555
- response += `Skills often need user-specific information (accounts, repos, boards, channels, etc.).\n`;
556
- response += `Instead of hardcoding or asking at runtime, use \`{{variable_name}}\` placeholders in instructions.\n\n`;
557
- response += `Example: A GitHub PR review skill should use \`{{github_repos}}\` in its instructions:\n`;
558
- response += ` "Navigate to each repository in {{github_repos}} and check for open PRs..."\n\n`;
559
- response += `For each \`{{variable}}\` used in instructions, add a matching entry in the \`variables\` array:\n`;
560
- response += `\`\`\`json\n`;
561
- response += `{\n`;
562
- response += ` "name": "github_repos",\n`;
563
- response += ` "description": "GitHub repositories to monitor (owner/repo format)",\n`;
564
- response += ` "type": "string[]",\n`;
565
- response += ` "required": true,\n`;
566
- response += ` "example": "octocat/hello-world, myorg/myrepo"\n`;
567
- response += `}\n`;
568
- response += `\`\`\`\n\n`;
569
- response += `Common variables by platform:\n`;
570
- response += `- GitHub: github_repos, github_username, github_org\n`;
571
- response += `- Trello: trello_board_url, trello_list_names\n`;
572
- response += `- Slack: slack_channels, slack_workspace_url\n`;
573
- response += `- Email: email_labels, email_filters\n`;
574
- response += `- E-commerce: store_url, competitor_urls, product_categories\n`;
575
- response += `- General: timezone, language, notification_channel\n\n`;
576
- response += `After creating skills, if any have required variables, call \`skill_configure\` to set initial values `;
577
- response += `(ask the user for the values first).\n`;
462
+ response += `- Use placeholders like {query}, {date} for variable inputs\n`;
463
+ response += `- Each skill should be a single, well-defined workflow (10-25 steps)\n`;
578
464
 
579
465
  return { content: [{ type: "text", text: response }] };
580
466
  }
@@ -916,11 +802,10 @@ export function createAgentToolsServer(deps: AgentToolsDeps): McpSdkServerConfig
916
802
  const task = await createScheduledTask(userId, name, prompt, args.cron, tz);
917
803
 
918
804
  // Link the job_id to the scheduled task
919
- const sb = getSupabase();
920
- await sb
921
- .from("agent_scheduled_tasks")
922
- .update({ job_id: job.jobId })
923
- .eq("id", task.id);
805
+ await callMcpHandler("schedule.link_job", {
806
+ task_id: task.id,
807
+ job_id: job.jobId,
808
+ });
924
809
 
925
810
  const nextRun = task.next_run_at
926
811
  ? new Date(task.next_run_at).toLocaleString()
@@ -1034,27 +919,19 @@ export function createAgentToolsServer(deps: AgentToolsDeps): McpSdkServerConfig
1034
919
  // ── Helper: persist job and link skills ────────────────────────────
1035
920
 
1036
921
  async function saveJobToDb(
1037
- userId: string,
922
+ _userId: string,
1038
923
  jobName: string,
1039
924
  jobDescription: string,
1040
925
  createdSkillNames: string[]
1041
926
  ): Promise<void> {
1042
927
  try {
1043
- const sb = getSupabase();
1044
-
1045
- const { data, error } = await sb.rpc("save_job_with_skills", {
1046
- p_user_id: userId,
1047
- p_job_name: jobName,
1048
- p_job_description: jobDescription,
1049
- p_skill_names: createdSkillNames,
928
+ const data = await callMcpHandler("job.save_with_skills", {
929
+ job_name: jobName,
930
+ job_description: jobDescription,
931
+ skill_names: createdSkillNames,
1050
932
  });
1051
933
 
1052
- if (error) {
1053
- log.debug(`Failed to save job "${jobName}" via RPC: ${error.message}`);
1054
- return;
1055
- }
1056
-
1057
- log.debug(`Job "${jobName}" saved via RPC (id: ${data}), ${createdSkillNames.length} skill(s) linked`);
934
+ log.debug(`Job "${jobName}" saved via edge function (id: ${data}), ${createdSkillNames.length} skill(s) linked`);
1058
935
  } catch (err) {
1059
936
  log.debug(`saveJobToDb error: ${err}`);
1060
937
  }
@@ -1,45 +1,9 @@
1
1
  import { describe, it, expect, vi, beforeEach } from "vitest";
2
2
 
3
- // Build a fluent mock that mimics the Supabase query builder chain
4
- function createSupabaseMock() {
5
- let finalResult: Record<string, unknown> = { data: [], error: null };
6
-
7
- const chain: Record<string, unknown> = {};
8
- const methods = [
9
- "select", "insert", "update", "delete", "eq", "neq", "not",
10
- "or", "in", "order", "limit", "single", "from",
11
- ];
12
- for (const method of methods) {
13
- chain[method] = vi.fn().mockReturnValue(chain);
14
- }
15
-
16
- // Make the chain thenable (for await)
17
- chain.then = (resolve: (value: unknown) => void) => resolve(finalResult);
18
- chain.single = vi.fn().mockImplementation(() => {
19
- const singleChain = { ...chain, then: (resolve: (value: unknown) => void) => resolve(finalResult) };
20
- return singleChain;
21
- });
3
+ const mockCallMcpHandler = vi.fn();
22
4
 
23
- const mockSupabase = {
24
- from: vi.fn().mockReturnValue(chain),
25
- _chain: chain,
26
- _setResult(result: Record<string, unknown>) {
27
- finalResult = result;
28
- chain.then = (resolve: (value: unknown) => void) => resolve(result);
29
- chain.single = vi.fn().mockImplementation(() => ({
30
- ...chain,
31
- then: (resolve: (value: unknown) => void) => resolve(result),
32
- }));
33
- },
34
- };
35
-
36
- return mockSupabase;
37
- }
38
-
39
- let mockSb: ReturnType<typeof createSupabaseMock>;
40
-
41
- vi.mock("../db/supabase.js", () => ({
42
- getSupabase: () => mockSb,
5
+ vi.mock("../db/api-client.js", () => ({
6
+ callMcpHandler: (...args: unknown[]) => mockCallMcpHandler(...args),
43
7
  }));
44
8
 
45
9
  vi.mock("../utils/logger.js", () => ({
@@ -58,12 +22,11 @@ describe("MemoryManager", () => {
58
22
 
59
23
  beforeEach(() => {
60
24
  vi.clearAllMocks();
61
- mockSb = createSupabaseMock();
62
25
  manager = new MemoryManager("user-123");
63
26
  });
64
27
 
65
28
  describe("remember()", () => {
66
- it("inserts a memory with default values", async () => {
29
+ it("stores a memory with default values", async () => {
67
30
  const mockData = {
68
31
  id: "mem-1",
69
32
  user_id: "user-123",
@@ -77,16 +40,23 @@ describe("MemoryManager", () => {
77
40
  last_accessed_at: null,
78
41
  created_at: "2026-01-01",
79
42
  };
80
- mockSb._setResult({ data: mockData, error: null });
43
+ mockCallMcpHandler.mockResolvedValueOnce(mockData);
81
44
 
82
45
  const result = await manager.remember("User prefers dark mode");
83
46
 
84
- expect(mockSb.from).toHaveBeenCalledWith("agent_memories");
47
+ expect(mockCallMcpHandler).toHaveBeenCalledWith("memory.store", {
48
+ category: "general",
49
+ content: "User prefers dark mode",
50
+ importance: 5,
51
+ tags: [],
52
+ source_message_id: null,
53
+ expires_at: null,
54
+ });
85
55
  expect(result.content).toBe("User prefers dark mode");
86
56
  expect(result.category).toBe("general");
87
57
  });
88
58
 
89
- it("inserts with custom category and importance", async () => {
59
+ it("stores with custom category and importance", async () => {
90
60
  const mockData = {
91
61
  id: "mem-2",
92
62
  user_id: "user-123",
@@ -100,7 +70,7 @@ describe("MemoryManager", () => {
100
70
  last_accessed_at: null,
101
71
  created_at: "2026-01-01",
102
72
  };
103
- mockSb._setResult({ data: mockData, error: null });
73
+ mockCallMcpHandler.mockResolvedValueOnce(mockData);
104
74
 
105
75
  const result = await manager.remember("Likes TypeScript", "preference", {
106
76
  importance: 8,
@@ -112,12 +82,10 @@ describe("MemoryManager", () => {
112
82
  expect(result.importance).toBe(8);
113
83
  });
114
84
 
115
- it("throws on DB error", async () => {
116
- mockSb._setResult({ data: null, error: { message: "DB down" } });
85
+ it("throws on edge function error", async () => {
86
+ mockCallMcpHandler.mockRejectedValueOnce(new Error("Edge function error"));
117
87
 
118
- await expect(manager.remember("test")).rejects.toThrow(
119
- "Failed to store memory"
120
- );
88
+ await expect(manager.remember("test")).rejects.toThrow();
121
89
  });
122
90
 
123
91
  it("computes expiry from expiresInDays", async () => {
@@ -134,46 +102,54 @@ describe("MemoryManager", () => {
134
102
  last_accessed_at: null,
135
103
  created_at: "2026-01-01",
136
104
  };
137
- mockSb._setResult({ data: mockData, error: null });
105
+ mockCallMcpHandler.mockResolvedValueOnce(mockData);
138
106
 
139
107
  const result = await manager.remember("temp context", "context", {
140
108
  expiresInDays: 7,
141
109
  });
142
110
 
143
111
  expect(result.expires_at).not.toBeNull();
112
+ expect(mockCallMcpHandler).toHaveBeenCalledWith(
113
+ "memory.store",
114
+ expect.objectContaining({
115
+ expires_at: expect.any(String),
116
+ })
117
+ );
144
118
  });
145
119
  });
146
120
 
147
121
  describe("buildMemoryPrompt()", () => {
148
122
  it("returns empty string when no memories", async () => {
149
- mockSb._setResult({ data: [], error: null });
123
+ mockCallMcpHandler.mockResolvedValueOnce([]);
150
124
  const result = await manager.buildMemoryPrompt();
151
125
  expect(result).toBe("");
152
126
  });
153
127
  });
154
128
 
155
129
  describe("list()", () => {
156
- it("queries agent_memories with user_id filter", async () => {
157
- mockSb._setResult({ data: [], error: null });
130
+ it("calls memory.list action", async () => {
131
+ mockCallMcpHandler.mockResolvedValueOnce([]);
158
132
  await manager.list();
159
- expect(mockSb.from).toHaveBeenCalledWith("agent_memories");
160
- expect(mockSb._chain.eq).toHaveBeenCalledWith("user_id", "user-123");
133
+ expect(mockCallMcpHandler).toHaveBeenCalledWith("memory.list", {
134
+ category: null,
135
+ limit: 20,
136
+ });
161
137
  });
162
138
 
163
- it("throws on DB error", async () => {
164
- mockSb._setResult({ data: null, error: { message: "fail" } });
165
- await expect(manager.list()).rejects.toThrow("Failed to list memories");
139
+ it("returns empty array on null response", async () => {
140
+ mockCallMcpHandler.mockResolvedValueOnce(null);
141
+ const result = await manager.list();
142
+ expect(result).toEqual([]);
166
143
  });
167
144
  });
168
145
 
169
146
  describe("remove()", () => {
170
- it("calls delete on agent_memories", async () => {
171
- mockSb._setResult({ error: null });
147
+ it("calls memory.remove action", async () => {
148
+ mockCallMcpHandler.mockResolvedValueOnce(undefined);
172
149
  await manager.remove("mem-1");
173
- expect(mockSb.from).toHaveBeenCalledWith("agent_memories");
174
- expect(mockSb._chain.delete).toHaveBeenCalled();
175
- expect(mockSb._chain.eq).toHaveBeenCalledWith("id", "mem-1");
176
- expect(mockSb._chain.eq).toHaveBeenCalledWith("user_id", "user-123");
150
+ expect(mockCallMcpHandler).toHaveBeenCalledWith("memory.remove", {
151
+ memory_id: "mem-1",
152
+ });
177
153
  });
178
154
  });
179
155
  });