assistme 0.2.3 → 0.2.5
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-GHMZQ2UA.js → chunk-XY3LGAOY.js} +20 -0
- package/dist/index.js +121 -35
- package/dist/{job-runner-BH5MDQX3.js → job-runner-XTGLMPZ3.js} +1 -1
- package/package.json +1 -1
- package/src/agent/mcp-servers.ts +119 -55
- package/src/agent/processor.test.ts +1 -4
- package/src/agent/processor.ts +2 -0
- package/src/agent/skill-extractor.ts +0 -459
- package/src/agent/skills.ts +7 -3
- package/src/db/supabase.ts +29 -1
- package/src/agent/memory-extractor.ts +0 -128
- package/src/utils/validation.test.ts +0 -153
- package/src/utils/validation.ts +0 -101
|
@@ -340,6 +340,24 @@ async function emitEvent(messageId, eventType, eventData) {
|
|
|
340
340
|
});
|
|
341
341
|
if (error) log.warn(`Failed to emit event: ${error.message}`);
|
|
342
342
|
}
|
|
343
|
+
async function setActionRequest(messageId, actionData) {
|
|
344
|
+
const sb = getSupabase();
|
|
345
|
+
const { error } = await sb.rpc("mcp_set_action_request", {
|
|
346
|
+
p_token_hash: getTokenHash(),
|
|
347
|
+
p_message_id: messageId,
|
|
348
|
+
p_action_data: actionData
|
|
349
|
+
});
|
|
350
|
+
if (error) throw new Error(`Failed to set action request: ${error.message}`);
|
|
351
|
+
}
|
|
352
|
+
async function pollActionResponse(messageId) {
|
|
353
|
+
const sb = getSupabase();
|
|
354
|
+
const { data, error } = await sb.rpc("mcp_poll_action_response", {
|
|
355
|
+
p_token_hash: getTokenHash(),
|
|
356
|
+
p_message_id: messageId
|
|
357
|
+
});
|
|
358
|
+
if (error) throw new Error(`Failed to poll action response: ${error.message}`);
|
|
359
|
+
return data;
|
|
360
|
+
}
|
|
343
361
|
|
|
344
362
|
// src/agent/job-runner.ts
|
|
345
363
|
var JobRunner = class {
|
|
@@ -556,5 +574,7 @@ export {
|
|
|
556
574
|
getConversationHistory,
|
|
557
575
|
resetEventSequence,
|
|
558
576
|
emitEvent,
|
|
577
|
+
setActionRequest,
|
|
578
|
+
pollActionResponse,
|
|
559
579
|
JobRunner
|
|
560
580
|
};
|
package/dist/index.js
CHANGED
|
@@ -19,15 +19,17 @@ import {
|
|
|
19
19
|
loginWithToken,
|
|
20
20
|
logout,
|
|
21
21
|
newCorrelationId,
|
|
22
|
+
pollActionResponse,
|
|
22
23
|
pollAndClaimJobRun,
|
|
23
24
|
pollAndClaimTask,
|
|
24
25
|
resetEventSequence,
|
|
26
|
+
setActionRequest,
|
|
25
27
|
setConfig,
|
|
26
28
|
setCorrelationId,
|
|
27
29
|
setLogLevel,
|
|
28
30
|
setSessionBusy,
|
|
29
31
|
updateHeartbeat
|
|
30
|
-
} from "./chunk-
|
|
32
|
+
} from "./chunk-XY3LGAOY.js";
|
|
31
33
|
|
|
32
34
|
// src/index.ts
|
|
33
35
|
import { Command } from "commander";
|
|
@@ -1985,8 +1987,10 @@ _(${skills.length - included} additional skills available \u2014 use skill_invok
|
|
|
1985
1987
|
log.debug(`Skill create returned no data for "${name}"`);
|
|
1986
1988
|
return null;
|
|
1987
1989
|
}
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
+
const id = row.out_id || row.id;
|
|
1991
|
+
const skillName = row.out_name || row.name;
|
|
1992
|
+
log.info(`Skill "${skillName}" created in skills table (pending approval)`);
|
|
1993
|
+
return { id, name: skillName };
|
|
1990
1994
|
} catch (err) {
|
|
1991
1995
|
log.debug(`Skill create error: ${err}`);
|
|
1992
1996
|
return null;
|
|
@@ -2972,7 +2976,7 @@ function createAgentToolsServer(deps) {
|
|
|
2972
2976
|
),
|
|
2973
2977
|
tool(
|
|
2974
2978
|
"skill_create",
|
|
2975
|
-
"Create a new skill
|
|
2979
|
+
"Create a new skill and add it to the user's collection. Returns the skill ID on success.",
|
|
2976
2980
|
{
|
|
2977
2981
|
name: z.string().describe("Skill name in kebab-case, e.g. 'flight-booking'"),
|
|
2978
2982
|
description: z.string().describe("One-line description of what this skill does"),
|
|
@@ -3002,12 +3006,23 @@ function createAgentToolsServer(deps) {
|
|
|
3002
3006
|
content: [{ type: "text", text: `Failed to create skill "${args.name}".` }]
|
|
3003
3007
|
};
|
|
3004
3008
|
}
|
|
3005
|
-
|
|
3009
|
+
await skillManager.syncToAgentSkills(
|
|
3010
|
+
args.name,
|
|
3011
|
+
args.description,
|
|
3012
|
+
args.instructions,
|
|
3013
|
+
"1.0.0",
|
|
3014
|
+
{
|
|
3015
|
+
source: "manual",
|
|
3016
|
+
emoji: args.emoji,
|
|
3017
|
+
sourceSkillId: result.id
|
|
3018
|
+
}
|
|
3019
|
+
);
|
|
3020
|
+
log.success(`Skill "${args.name}" created and added to collection`);
|
|
3006
3021
|
return {
|
|
3007
3022
|
content: [
|
|
3008
3023
|
{
|
|
3009
3024
|
type: "text",
|
|
3010
|
-
text: `Skill "${args.name}" created (ID: ${result.id})
|
|
3025
|
+
text: `Skill "${args.name}" created and added to your collection (ID: ${result.id}).`
|
|
3011
3026
|
}
|
|
3012
3027
|
]
|
|
3013
3028
|
};
|
|
@@ -3140,7 +3155,7 @@ ${content}`;
|
|
|
3140
3155
|
),
|
|
3141
3156
|
tool(
|
|
3142
3157
|
"skill_generate",
|
|
3143
|
-
"Prepare context for generating skills from a job description. Returns existing skills and job info so you can analyze the job and create skills using skill_create
|
|
3158
|
+
"Prepare context for generating skills from a job description. Returns existing skills and job info so you can analyze the job and create skills using skill_create (which auto-adds to user's collection). After creating all skills, call skill_link_job to link them to the job and mark it as analyzed.",
|
|
3144
3159
|
{
|
|
3145
3160
|
job_name: z.string().describe(
|
|
3146
3161
|
"Short name for this job/role. Example: '\u7535\u5546\u8FD0\u8425', 'Frontend Dev', 'Data Analyst'"
|
|
@@ -3161,7 +3176,24 @@ ${content}`;
|
|
|
3161
3176
|
|
|
3162
3177
|
`;
|
|
3163
3178
|
}
|
|
3164
|
-
response += `**Your task:** Analyze this job description and decompose it into 4-10 automatable skills.
|
|
3179
|
+
response += `**Your task:** Analyze this job description and decompose it into 4-10 automatable skills.
|
|
3180
|
+
|
|
3181
|
+
`;
|
|
3182
|
+
response += `**IMPORTANT \u2014 Confirmation workflow:**
|
|
3183
|
+
`;
|
|
3184
|
+
response += `1. First, analyze the job and draft a list of proposed skills (name, emoji, one-line description for each).
|
|
3185
|
+
`;
|
|
3186
|
+
response += `2. Then call \`request_user_confirmation\` to present these skills to the user for approval BEFORE creating any.
|
|
3187
|
+
`;
|
|
3188
|
+
response += ` - message: show a formatted list of all proposed skills
|
|
3189
|
+
`;
|
|
3190
|
+
response += ` - options: [{label: "Approve All", value: "approve_all"}, {label: "Cancel", value: "cancel"}]
|
|
3191
|
+
`;
|
|
3192
|
+
response += `3. If the user approves, create all skills using \`skill_create\` for each.
|
|
3193
|
+
`;
|
|
3194
|
+
response += `4. If the user cancels, stop without creating anything.
|
|
3195
|
+
|
|
3196
|
+
`;
|
|
3165
3197
|
response += `For each skill, call \`skill_create\` with:
|
|
3166
3198
|
`;
|
|
3167
3199
|
response += `- name: kebab-case name (e.g. "slack-message-check")
|
|
@@ -3173,10 +3205,10 @@ ${content}`;
|
|
|
3173
3205
|
response += `- emoji: a single emoji representing the skill
|
|
3174
3206
|
|
|
3175
3207
|
`;
|
|
3176
|
-
response += `
|
|
3208
|
+
response += `skill_create automatically adds the skill to the user's collection \u2014 no need to call skill_add.
|
|
3177
3209
|
|
|
3178
3210
|
`;
|
|
3179
|
-
response += `After ALL skills are created
|
|
3211
|
+
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.
|
|
3180
3212
|
|
|
3181
3213
|
`;
|
|
3182
3214
|
response += `**Guidelines for skill instructions:**
|
|
@@ -3326,6 +3358,71 @@ ${content}`;
|
|
|
3326
3358
|
};
|
|
3327
3359
|
}
|
|
3328
3360
|
),
|
|
3361
|
+
// ── User Confirmation Tool ─────────────────────────────────
|
|
3362
|
+
tool(
|
|
3363
|
+
"request_user_confirmation",
|
|
3364
|
+
"Pause and ask the user for approval or input via the web UI. Returns the user's response. Use this before creating skills, making irreversible changes, etc.",
|
|
3365
|
+
{
|
|
3366
|
+
message: z.string().describe("What to show the user (supports markdown)"),
|
|
3367
|
+
options: z.array(z.object({
|
|
3368
|
+
label: z.string(),
|
|
3369
|
+
value: z.string(),
|
|
3370
|
+
description: z.string().optional()
|
|
3371
|
+
})).describe("Buttons/options to show the user"),
|
|
3372
|
+
timeout_seconds: z.number().optional().describe("How long to wait for response (default: 300)")
|
|
3373
|
+
},
|
|
3374
|
+
async (args) => {
|
|
3375
|
+
const actionId = crypto.randomUUID();
|
|
3376
|
+
const timeout = (args.timeout_seconds || 300) * 1e3;
|
|
3377
|
+
const actionData = {
|
|
3378
|
+
id: actionId,
|
|
3379
|
+
type: "confirmation",
|
|
3380
|
+
message: args.message,
|
|
3381
|
+
options: args.options,
|
|
3382
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
3383
|
+
};
|
|
3384
|
+
try {
|
|
3385
|
+
await setActionRequest(taskId, actionData);
|
|
3386
|
+
await emitEvent(taskId, "user_action_request", actionData);
|
|
3387
|
+
const startTime = Date.now();
|
|
3388
|
+
const pollInterval = 2e3;
|
|
3389
|
+
while (Date.now() - startTime < timeout) {
|
|
3390
|
+
const response = await pollActionResponse(taskId);
|
|
3391
|
+
if (response) {
|
|
3392
|
+
log.info(`User responded to action request ${actionId}: ${JSON.stringify(response)}`);
|
|
3393
|
+
return {
|
|
3394
|
+
content: [{
|
|
3395
|
+
type: "text",
|
|
3396
|
+
text: JSON.stringify({
|
|
3397
|
+
status: "responded",
|
|
3398
|
+
action: response.action || response.value,
|
|
3399
|
+
response
|
|
3400
|
+
})
|
|
3401
|
+
}]
|
|
3402
|
+
};
|
|
3403
|
+
}
|
|
3404
|
+
await new Promise((resolve2) => setTimeout(resolve2, pollInterval));
|
|
3405
|
+
}
|
|
3406
|
+
log.warn(`Action request ${actionId} timed out after ${args.timeout_seconds || 300}s`);
|
|
3407
|
+
return {
|
|
3408
|
+
content: [{
|
|
3409
|
+
type: "text",
|
|
3410
|
+
text: JSON.stringify({
|
|
3411
|
+
status: "timeout",
|
|
3412
|
+
message: "User did not respond within the timeout period."
|
|
3413
|
+
})
|
|
3414
|
+
}]
|
|
3415
|
+
};
|
|
3416
|
+
} catch (err) {
|
|
3417
|
+
return {
|
|
3418
|
+
content: [{
|
|
3419
|
+
type: "text",
|
|
3420
|
+
text: `Failed to request user confirmation: ${err instanceof Error ? err.message : err}`
|
|
3421
|
+
}]
|
|
3422
|
+
};
|
|
3423
|
+
}
|
|
3424
|
+
}
|
|
3425
|
+
),
|
|
3329
3426
|
// ── Job Automation Tools ──────────────────────────────────────
|
|
3330
3427
|
tool(
|
|
3331
3428
|
"job_run",
|
|
@@ -3519,30 +3616,17 @@ ${content}`;
|
|
|
3519
3616
|
async function saveJobToDb(userId, jobName, jobDescription, createdSkillNames) {
|
|
3520
3617
|
try {
|
|
3521
3618
|
const sb = getSupabase();
|
|
3522
|
-
const { data
|
|
3523
|
-
|
|
3524
|
-
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3619
|
+
const { data, error } = await sb.rpc("save_job_with_skills", {
|
|
3620
|
+
p_user_id: userId,
|
|
3621
|
+
p_job_name: jobName,
|
|
3622
|
+
p_job_description: jobDescription,
|
|
3623
|
+
p_skill_names: createdSkillNames
|
|
3624
|
+
});
|
|
3625
|
+
if (error) {
|
|
3626
|
+
log.debug(`Failed to save job "${jobName}" via RPC: ${error.message}`);
|
|
3528
3627
|
return;
|
|
3529
3628
|
}
|
|
3530
|
-
|
|
3531
|
-
const { data: skillRows } = await sb.from("agent_skills").select("id").eq("user_id", userId).in("name", createdSkillNames);
|
|
3532
|
-
const skillIds = (skillRows || []).map((r) => r.id);
|
|
3533
|
-
if (skillIds.length > 0) {
|
|
3534
|
-
const links = skillIds.map((skillId) => ({
|
|
3535
|
-
job_id: job.id,
|
|
3536
|
-
skill_id: skillId
|
|
3537
|
-
}));
|
|
3538
|
-
const { error: linkError } = await sb.from("agent_job_skills").upsert(links, { onConflict: "job_id,skill_id" });
|
|
3539
|
-
if (linkError) {
|
|
3540
|
-
log.debug(`Failed to link skills to job: ${linkError.message}`);
|
|
3541
|
-
}
|
|
3542
|
-
}
|
|
3543
|
-
}
|
|
3544
|
-
await sb.from("agent_jobs").update({ skills_analyzed: true }).eq("id", job.id);
|
|
3545
|
-
log.debug(`Job "${jobName}" saved to DB with ${createdSkillNames.length} skill link(s), marked as analyzed`);
|
|
3629
|
+
log.debug(`Job "${jobName}" saved via RPC (id: ${data}), ${createdSkillNames.length} skill(s) linked`);
|
|
3546
3630
|
} catch (err) {
|
|
3547
3631
|
log.debug(`saveJobToDb error: ${err}`);
|
|
3548
3632
|
}
|
|
@@ -3772,6 +3856,8 @@ var TaskProcessor = class {
|
|
|
3772
3856
|
"mcp__assistme-agent__skill_browse",
|
|
3773
3857
|
"mcp__assistme-agent__skill_add",
|
|
3774
3858
|
"mcp__assistme-agent__skill_publish",
|
|
3859
|
+
// User confirmation
|
|
3860
|
+
"mcp__assistme-agent__request_user_confirmation",
|
|
3775
3861
|
// Job automation tools
|
|
3776
3862
|
"mcp__assistme-agent__job_run",
|
|
3777
3863
|
"mcp__assistme-agent__job_schedule",
|
|
@@ -4444,7 +4530,7 @@ function registerJobCommands(program2) {
|
|
|
4444
4530
|
jobCmd.command("list").description("List your defined jobs").action(async () => {
|
|
4445
4531
|
try {
|
|
4446
4532
|
const userId = await getCurrentUserId();
|
|
4447
|
-
const { JobRunner: JobRunner2 } = await import("./job-runner-
|
|
4533
|
+
const { JobRunner: JobRunner2 } = await import("./job-runner-XTGLMPZ3.js");
|
|
4448
4534
|
const runner = new JobRunner2(userId);
|
|
4449
4535
|
const jobs = await runner.listJobs();
|
|
4450
4536
|
if (jobs.length === 0) {
|
|
@@ -4472,7 +4558,7 @@ function registerJobCommands(program2) {
|
|
|
4472
4558
|
jobCmd.command("status [name]").description("Show run history for a job (or all jobs)").option("-l, --limit <number>", "Max runs to show (default: 5)").action(async (name, opts) => {
|
|
4473
4559
|
try {
|
|
4474
4560
|
const userId = await getCurrentUserId();
|
|
4475
|
-
const { JobRunner: JobRunner2 } = await import("./job-runner-
|
|
4561
|
+
const { JobRunner: JobRunner2 } = await import("./job-runner-XTGLMPZ3.js");
|
|
4476
4562
|
const runner = new JobRunner2(userId);
|
|
4477
4563
|
const runs = await runner.getRunHistory(
|
|
4478
4564
|
name,
|
|
@@ -4526,7 +4612,7 @@ Job Run History${name ? ` \u2014 ${name}` : ""}:`
|
|
|
4526
4612
|
process.exit(1);
|
|
4527
4613
|
}
|
|
4528
4614
|
const userId = await getCurrentUserId();
|
|
4529
|
-
const { JobRunner: JobRunner2 } = await import("./job-runner-
|
|
4615
|
+
const { JobRunner: JobRunner2 } = await import("./job-runner-XTGLMPZ3.js");
|
|
4530
4616
|
const runner = new JobRunner2(userId);
|
|
4531
4617
|
const job = await runner.loadJob(name);
|
|
4532
4618
|
if (!job) {
|
package/package.json
CHANGED
package/src/agent/mcp-servers.ts
CHANGED
|
@@ -10,9 +10,7 @@ import { log } from "../utils/logger.js";
|
|
|
10
10
|
import type { MemoryManager, MemoryCategory } from "./memory.js";
|
|
11
11
|
import type { SkillManager } from "./skills.js";
|
|
12
12
|
import { substituteArguments, preprocessDynamicContext } from "./skills.js";
|
|
13
|
-
|
|
14
|
-
// job analysis and skill generation directly (no separate LLM API calls needed)
|
|
15
|
-
import { getSupabase } from "../db/supabase.js";
|
|
13
|
+
import { getSupabase, emitEvent, setActionRequest, pollActionResponse } from "../db/supabase.js";
|
|
16
14
|
import { JobRunner } from "./job-runner.js";
|
|
17
15
|
import {
|
|
18
16
|
createScheduledTask,
|
|
@@ -216,7 +214,7 @@ export function createAgentToolsServer(deps: AgentToolsDeps): McpSdkServerConfig
|
|
|
216
214
|
),
|
|
217
215
|
tool(
|
|
218
216
|
"skill_create",
|
|
219
|
-
"Create a new skill
|
|
217
|
+
"Create a new skill and add it to the user's collection. Returns the skill ID on success.",
|
|
220
218
|
{
|
|
221
219
|
name: z.string().describe("Skill name in kebab-case, e.g. 'flight-booking'"),
|
|
222
220
|
description: z.string().describe("One-line description of what this skill does"),
|
|
@@ -237,6 +235,7 @@ export function createAgentToolsServer(deps: AgentToolsDeps): McpSdkServerConfig
|
|
|
237
235
|
};
|
|
238
236
|
}
|
|
239
237
|
|
|
238
|
+
// Create in skills table (repository)
|
|
240
239
|
const result = await skillManager.create(
|
|
241
240
|
args.name,
|
|
242
241
|
args.description,
|
|
@@ -250,12 +249,25 @@ export function createAgentToolsServer(deps: AgentToolsDeps): McpSdkServerConfig
|
|
|
250
249
|
};
|
|
251
250
|
}
|
|
252
251
|
|
|
253
|
-
|
|
252
|
+
// Auto-add to user's collection via upsert_agent_skill RPC
|
|
253
|
+
await skillManager.syncToAgentSkills(
|
|
254
|
+
args.name,
|
|
255
|
+
args.description,
|
|
256
|
+
args.instructions,
|
|
257
|
+
"1.0.0",
|
|
258
|
+
{
|
|
259
|
+
source: "manual",
|
|
260
|
+
emoji: args.emoji,
|
|
261
|
+
sourceSkillId: result.id,
|
|
262
|
+
}
|
|
263
|
+
);
|
|
264
|
+
|
|
265
|
+
log.success(`Skill "${args.name}" created and added to collection`);
|
|
254
266
|
return {
|
|
255
267
|
content: [
|
|
256
268
|
{
|
|
257
269
|
type: "text",
|
|
258
|
-
text: `Skill "${args.name}" created (ID: ${result.id})
|
|
270
|
+
text: `Skill "${args.name}" created and added to your collection (ID: ${result.id}).`,
|
|
259
271
|
},
|
|
260
272
|
],
|
|
261
273
|
};
|
|
@@ -407,7 +419,7 @@ export function createAgentToolsServer(deps: AgentToolsDeps): McpSdkServerConfig
|
|
|
407
419
|
tool(
|
|
408
420
|
"skill_generate",
|
|
409
421
|
"Prepare context for generating skills from a job description. Returns existing skills and job info " +
|
|
410
|
-
"so you can analyze the job and create skills using skill_create
|
|
422
|
+
"so you can analyze the job and create skills using skill_create (which auto-adds to user's collection). " +
|
|
411
423
|
"After creating all skills, call skill_link_job to link them to the job and mark it as analyzed.",
|
|
412
424
|
{
|
|
413
425
|
job_name: z.string().describe(
|
|
@@ -428,14 +440,21 @@ export function createAgentToolsServer(deps: AgentToolsDeps): McpSdkServerConfig
|
|
|
428
440
|
response += `**Existing skills (do NOT duplicate):** ${existingNames.join(", ")}\n\n`;
|
|
429
441
|
}
|
|
430
442
|
|
|
431
|
-
response += `**Your task:** Analyze this job description and decompose it into 4-10 automatable skills
|
|
443
|
+
response += `**Your task:** Analyze this job description and decompose it into 4-10 automatable skills.\n\n`;
|
|
444
|
+
response += `**IMPORTANT — Confirmation workflow:**\n`;
|
|
445
|
+
response += `1. First, analyze the job and draft a list of proposed skills (name, emoji, one-line description for each).\n`;
|
|
446
|
+
response += `2. Then call \`request_user_confirmation\` to present these skills to the user for approval BEFORE creating any.\n`;
|
|
447
|
+
response += ` - message: show a formatted list of all proposed skills\n`;
|
|
448
|
+
response += ` - options: [{label: "Approve All", value: "approve_all"}, {label: "Cancel", value: "cancel"}]\n`;
|
|
449
|
+
response += `3. If the user approves, create all skills using \`skill_create\` for each.\n`;
|
|
450
|
+
response += `4. If the user cancels, stop without creating anything.\n\n`;
|
|
432
451
|
response += `For each skill, call \`skill_create\` with:\n`;
|
|
433
452
|
response += `- name: kebab-case name (e.g. "slack-message-check")\n`;
|
|
434
453
|
response += `- description: one-line description\n`;
|
|
435
454
|
response += `- instructions: detailed step-by-step markdown instructions the agent can follow\n`;
|
|
436
455
|
response += `- emoji: a single emoji representing the skill\n\n`;
|
|
437
|
-
response += `
|
|
438
|
-
response += `After ALL skills are created
|
|
456
|
+
response += `skill_create automatically adds the skill to the user's collection — no need to call skill_add.\n\n`;
|
|
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`;
|
|
439
458
|
response += `**Guidelines for skill instructions:**\n`;
|
|
440
459
|
response += `- Write clear, actionable markdown steps\n`;
|
|
441
460
|
response += `- Reference browser tools (browser_navigate, browser_click, browser_read_page, etc.) for web tasks\n`;
|
|
@@ -586,6 +605,86 @@ export function createAgentToolsServer(deps: AgentToolsDeps): McpSdkServerConfig
|
|
|
586
605
|
}
|
|
587
606
|
),
|
|
588
607
|
|
|
608
|
+
// ── User Confirmation Tool ─────────────────────────────────
|
|
609
|
+
|
|
610
|
+
tool(
|
|
611
|
+
"request_user_confirmation",
|
|
612
|
+
"Pause and ask the user for approval or input via the web UI. " +
|
|
613
|
+
"Returns the user's response. Use this before creating skills, making irreversible changes, etc.",
|
|
614
|
+
{
|
|
615
|
+
message: z.string().describe("What to show the user (supports markdown)"),
|
|
616
|
+
options: z.array(z.object({
|
|
617
|
+
label: z.string(),
|
|
618
|
+
value: z.string(),
|
|
619
|
+
description: z.string().optional(),
|
|
620
|
+
})).describe("Buttons/options to show the user"),
|
|
621
|
+
timeout_seconds: z.number().optional().describe("How long to wait for response (default: 300)"),
|
|
622
|
+
},
|
|
623
|
+
async (args) => {
|
|
624
|
+
const actionId = crypto.randomUUID();
|
|
625
|
+
const timeout = (args.timeout_seconds || 300) * 1000;
|
|
626
|
+
|
|
627
|
+
const actionData = {
|
|
628
|
+
id: actionId,
|
|
629
|
+
type: "confirmation",
|
|
630
|
+
message: args.message,
|
|
631
|
+
options: args.options,
|
|
632
|
+
created_at: new Date().toISOString(),
|
|
633
|
+
};
|
|
634
|
+
|
|
635
|
+
try {
|
|
636
|
+
// 1. Store action request in message metadata via RPC
|
|
637
|
+
await setActionRequest(taskId, actionData);
|
|
638
|
+
|
|
639
|
+
// 2. Emit user_action_request event so web UI renders buttons
|
|
640
|
+
await emitEvent(taskId, "user_action_request", actionData);
|
|
641
|
+
|
|
642
|
+
// 3. Poll for response
|
|
643
|
+
const startTime = Date.now();
|
|
644
|
+
const pollInterval = 2000;
|
|
645
|
+
|
|
646
|
+
while (Date.now() - startTime < timeout) {
|
|
647
|
+
const response = await pollActionResponse(taskId);
|
|
648
|
+
if (response) {
|
|
649
|
+
log.info(`User responded to action request ${actionId}: ${JSON.stringify(response)}`);
|
|
650
|
+
return {
|
|
651
|
+
content: [{
|
|
652
|
+
type: "text",
|
|
653
|
+
text: JSON.stringify({
|
|
654
|
+
status: "responded",
|
|
655
|
+
action: response.action || response.value,
|
|
656
|
+
response,
|
|
657
|
+
}),
|
|
658
|
+
}],
|
|
659
|
+
};
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
// Wait before next poll
|
|
663
|
+
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
// Timeout
|
|
667
|
+
log.warn(`Action request ${actionId} timed out after ${args.timeout_seconds || 300}s`);
|
|
668
|
+
return {
|
|
669
|
+
content: [{
|
|
670
|
+
type: "text",
|
|
671
|
+
text: JSON.stringify({
|
|
672
|
+
status: "timeout",
|
|
673
|
+
message: "User did not respond within the timeout period.",
|
|
674
|
+
}),
|
|
675
|
+
}],
|
|
676
|
+
};
|
|
677
|
+
} catch (err) {
|
|
678
|
+
return {
|
|
679
|
+
content: [{
|
|
680
|
+
type: "text",
|
|
681
|
+
text: `Failed to request user confirmation: ${err instanceof Error ? err.message : err}`,
|
|
682
|
+
}],
|
|
683
|
+
};
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
),
|
|
687
|
+
|
|
589
688
|
// ── Job Automation Tools ──────────────────────────────────────
|
|
590
689
|
|
|
591
690
|
tool(
|
|
@@ -826,54 +925,19 @@ async function saveJobToDb(
|
|
|
826
925
|
try {
|
|
827
926
|
const sb = getSupabase();
|
|
828
927
|
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
)
|
|
836
|
-
.select("id")
|
|
837
|
-
.single();
|
|
838
|
-
|
|
839
|
-
if (jobError || !job) {
|
|
840
|
-
log.debug(`Failed to save job "${jobName}": ${jobError?.message}`);
|
|
841
|
-
return;
|
|
842
|
-
}
|
|
843
|
-
|
|
844
|
-
// Query DB for skill IDs by name (can't rely on in-memory dbId due to async sync)
|
|
845
|
-
if (createdSkillNames.length > 0) {
|
|
846
|
-
const { data: skillRows } = await sb
|
|
847
|
-
.from("agent_skills")
|
|
848
|
-
.select("id")
|
|
849
|
-
.eq("user_id", userId)
|
|
850
|
-
.in("name", createdSkillNames);
|
|
851
|
-
|
|
852
|
-
const skillIds = (skillRows || []).map((r: Record<string, unknown>) => r.id as string);
|
|
928
|
+
const { data, error } = await sb.rpc("save_job_with_skills", {
|
|
929
|
+
p_user_id: userId,
|
|
930
|
+
p_job_name: jobName,
|
|
931
|
+
p_job_description: jobDescription,
|
|
932
|
+
p_skill_names: createdSkillNames,
|
|
933
|
+
});
|
|
853
934
|
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
skill_id: skillId,
|
|
858
|
-
}));
|
|
859
|
-
|
|
860
|
-
const { error: linkError } = await sb
|
|
861
|
-
.from("agent_job_skills")
|
|
862
|
-
.upsert(links, { onConflict: "job_id,skill_id" });
|
|
863
|
-
|
|
864
|
-
if (linkError) {
|
|
865
|
-
log.debug(`Failed to link skills to job: ${linkError.message}`);
|
|
866
|
-
}
|
|
867
|
-
}
|
|
935
|
+
if (error) {
|
|
936
|
+
log.debug(`Failed to save job "${jobName}" via RPC: ${error.message}`);
|
|
937
|
+
return;
|
|
868
938
|
}
|
|
869
939
|
|
|
870
|
-
|
|
871
|
-
await sb
|
|
872
|
-
.from("agent_jobs")
|
|
873
|
-
.update({ skills_analyzed: true })
|
|
874
|
-
.eq("id", job.id);
|
|
875
|
-
|
|
876
|
-
log.debug(`Job "${jobName}" saved to DB with ${createdSkillNames.length} skill link(s), marked as analyzed`);
|
|
940
|
+
log.debug(`Job "${jobName}" saved via RPC (id: ${data}), ${createdSkillNames.length} skill(s) linked`);
|
|
877
941
|
} catch (err) {
|
|
878
942
|
log.debug(`saveJobToDb error: ${err}`);
|
|
879
943
|
}
|
|
@@ -92,11 +92,8 @@ vi.mock("./skills.js", () => ({
|
|
|
92
92
|
},
|
|
93
93
|
}));
|
|
94
94
|
|
|
95
|
-
vi.mock("./memory-extractor.js", () => ({
|
|
96
|
-
extractMemoriesWithLLM: vi.fn().mockResolvedValue([]),
|
|
97
|
-
}));
|
|
98
95
|
vi.mock("./skill-extractor.js", () => ({
|
|
99
|
-
// Only ToolCallRecord type is used
|
|
96
|
+
// Only ToolCallRecord type is used
|
|
100
97
|
}));
|
|
101
98
|
|
|
102
99
|
vi.mock("../utils/rate-limiter.js", () => ({
|
package/src/agent/processor.ts
CHANGED
|
@@ -237,6 +237,8 @@ export class TaskProcessor {
|
|
|
237
237
|
"mcp__assistme-agent__skill_browse",
|
|
238
238
|
"mcp__assistme-agent__skill_add",
|
|
239
239
|
"mcp__assistme-agent__skill_publish",
|
|
240
|
+
// User confirmation
|
|
241
|
+
"mcp__assistme-agent__request_user_confirmation",
|
|
240
242
|
// Job automation tools
|
|
241
243
|
"mcp__assistme-agent__job_run",
|
|
242
244
|
"mcp__assistme-agent__job_schedule",
|