assistme 0.2.7 → 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.
- package/dist/chunk-TTEGHE2E.js +47 -0
- package/dist/chunk-UWE5WVQI.js +289 -0
- package/dist/config-PUIS2TQL.js +12 -0
- package/dist/index.js +434 -669
- package/dist/job-runner-N4XAAWLJ.js +7 -0
- package/package.json +1 -2
- package/src/agent/job-runner.ts +33 -71
- package/src/agent/mcp-servers.ts +26 -149
- package/src/agent/memory.test.ts +41 -65
- package/src/agent/memory.ts +33 -134
- package/src/agent/scheduler.ts +47 -93
- package/src/agent/session.test.ts +8 -12
- package/src/agent/session.ts +10 -53
- package/src/agent/skills.ts +89 -488
- package/src/commands/job.ts +6 -6
- package/src/commands/status.ts +3 -10
- package/src/db/api-client.ts +68 -0
- package/src/db/supabase.test.ts +71 -184
- package/src/db/supabase.ts +140 -243
- package/dist/chunk-XY3LGAOY.js +0 -580
- package/dist/job-runner-XTGLMPZ3.js +0 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "assistme",
|
|
3
|
-
"version": "0.2.
|
|
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",
|
package/src/agent/job-runner.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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
|
-
|
|
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
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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 (
|
|
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
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
.
|
|
88
|
-
.
|
|
89
|
-
.
|
|
90
|
-
|
|
91
|
-
|
|
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
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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
|
|
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,
|
package/src/agent/mcp-servers.ts
CHANGED
|
@@ -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
|
|
12
|
-
import { substituteArguments,
|
|
13
|
-
import {
|
|
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: [
|
|
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 += `-
|
|
554
|
-
response +=
|
|
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
|
-
|
|
920
|
-
|
|
921
|
-
.
|
|
922
|
-
|
|
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
|
-
|
|
922
|
+
_userId: string,
|
|
1038
923
|
jobName: string,
|
|
1039
924
|
jobDescription: string,
|
|
1040
925
|
createdSkillNames: string[]
|
|
1041
926
|
): Promise<void> {
|
|
1042
927
|
try {
|
|
1043
|
-
const
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
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
|
-
|
|
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
|
}
|
package/src/agent/memory.test.ts
CHANGED
|
@@ -1,45 +1,9 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
24
|
-
|
|
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("
|
|
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
|
-
|
|
43
|
+
mockCallMcpHandler.mockResolvedValueOnce(mockData);
|
|
81
44
|
|
|
82
45
|
const result = await manager.remember("User prefers dark mode");
|
|
83
46
|
|
|
84
|
-
expect(
|
|
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("
|
|
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
|
-
|
|
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
|
|
116
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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("
|
|
157
|
-
|
|
130
|
+
it("calls memory.list action", async () => {
|
|
131
|
+
mockCallMcpHandler.mockResolvedValueOnce([]);
|
|
158
132
|
await manager.list();
|
|
159
|
-
expect(
|
|
160
|
-
|
|
133
|
+
expect(mockCallMcpHandler).toHaveBeenCalledWith("memory.list", {
|
|
134
|
+
category: null,
|
|
135
|
+
limit: 20,
|
|
136
|
+
});
|
|
161
137
|
});
|
|
162
138
|
|
|
163
|
-
it("
|
|
164
|
-
|
|
165
|
-
await
|
|
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
|
|
171
|
-
|
|
147
|
+
it("calls memory.remove action", async () => {
|
|
148
|
+
mockCallMcpHandler.mockResolvedValueOnce(undefined);
|
|
172
149
|
await manager.remove("mem-1");
|
|
173
|
-
expect(
|
|
174
|
-
|
|
175
|
-
|
|
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
|
});
|