assistme 0.2.4 → 0.2.6
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.
|
@@ -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";
|
|
@@ -2974,7 +2976,7 @@ function createAgentToolsServer(deps) {
|
|
|
2974
2976
|
),
|
|
2975
2977
|
tool(
|
|
2976
2978
|
"skill_create",
|
|
2977
|
-
"Create a new skill
|
|
2979
|
+
"Create a new skill and add it to the user's collection. Returns the skill ID on success.",
|
|
2978
2980
|
{
|
|
2979
2981
|
name: z.string().describe("Skill name in kebab-case, e.g. 'flight-booking'"),
|
|
2980
2982
|
description: z.string().describe("One-line description of what this skill does"),
|
|
@@ -3004,12 +3006,23 @@ function createAgentToolsServer(deps) {
|
|
|
3004
3006
|
content: [{ type: "text", text: `Failed to create skill "${args.name}".` }]
|
|
3005
3007
|
};
|
|
3006
3008
|
}
|
|
3007
|
-
|
|
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`);
|
|
3008
3021
|
return {
|
|
3009
3022
|
content: [
|
|
3010
3023
|
{
|
|
3011
3024
|
type: "text",
|
|
3012
|
-
text: `Skill "${args.name}" created (ID: ${result.id})
|
|
3025
|
+
text: `Skill "${args.name}" created and added to your collection (ID: ${result.id}).`
|
|
3013
3026
|
}
|
|
3014
3027
|
]
|
|
3015
3028
|
};
|
|
@@ -3142,7 +3155,7 @@ ${content}`;
|
|
|
3142
3155
|
),
|
|
3143
3156
|
tool(
|
|
3144
3157
|
"skill_generate",
|
|
3145
|
-
"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.",
|
|
3146
3159
|
{
|
|
3147
3160
|
job_name: z.string().describe(
|
|
3148
3161
|
"Short name for this job/role. Example: '\u7535\u5546\u8FD0\u8425', 'Frontend Dev', 'Data Analyst'"
|
|
@@ -3163,7 +3176,22 @@ ${content}`;
|
|
|
3163
3176
|
|
|
3164
3177
|
`;
|
|
3165
3178
|
}
|
|
3166
|
-
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 You MUST use request_user_confirmation before creating skills:**
|
|
3183
|
+
`;
|
|
3184
|
+
response += `1. Analyze the job and draft a list of proposed skills (name, emoji, one-line description for each).
|
|
3185
|
+
`;
|
|
3186
|
+
response += `2. Call \`request_user_confirmation\` with the formatted skill list as "message" and these options:
|
|
3187
|
+
`;
|
|
3188
|
+
response += ` - options: [{label: "Approve All", action_key: "approve_all", description: "Create all proposed skills"}, {label: "Cancel", action_key: "cancel", description: "Do not create any skills"}]
|
|
3189
|
+
`;
|
|
3190
|
+
response += `3. WAIT for the response. If action_key is "approve_all", create all skills using \`skill_create\`. If "cancel", stop.
|
|
3191
|
+
`;
|
|
3192
|
+
response += `4. Do NOT ask for confirmation in text. Do NOT create skills without calling request_user_confirmation first.
|
|
3193
|
+
|
|
3194
|
+
`;
|
|
3167
3195
|
response += `For each skill, call \`skill_create\` with:
|
|
3168
3196
|
`;
|
|
3169
3197
|
response += `- name: kebab-case name (e.g. "slack-message-check")
|
|
@@ -3175,10 +3203,10 @@ ${content}`;
|
|
|
3175
3203
|
response += `- emoji: a single emoji representing the skill
|
|
3176
3204
|
|
|
3177
3205
|
`;
|
|
3178
|
-
response += `
|
|
3206
|
+
response += `skill_create automatically adds the skill to the user's collection \u2014 no need to call skill_add.
|
|
3179
3207
|
|
|
3180
3208
|
`;
|
|
3181
|
-
response += `After ALL skills are created
|
|
3209
|
+
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.
|
|
3182
3210
|
|
|
3183
3211
|
`;
|
|
3184
3212
|
response += `**Guidelines for skill instructions:**
|
|
@@ -3328,6 +3356,76 @@ ${content}`;
|
|
|
3328
3356
|
};
|
|
3329
3357
|
}
|
|
3330
3358
|
),
|
|
3359
|
+
// ── User Confirmation Tool ─────────────────────────────────
|
|
3360
|
+
tool(
|
|
3361
|
+
"request_user_confirmation",
|
|
3362
|
+
"Pause and ask the user for approval or input via the web UI. Returns the user's chosen action_key. Use this BEFORE creating skills, making irreversible changes, etc. The agent will block until the user responds or the timeout expires.",
|
|
3363
|
+
{
|
|
3364
|
+
message: z.string().describe("What to show the user (supports markdown)"),
|
|
3365
|
+
options: z.array(z.object({
|
|
3366
|
+
label: z.string().describe("Button label shown to user"),
|
|
3367
|
+
action_key: z.string().describe("Machine-readable key returned when selected"),
|
|
3368
|
+
description: z.string().optional().describe("Tooltip/description for this option")
|
|
3369
|
+
})).describe("Buttons/options to show the user"),
|
|
3370
|
+
timeout_seconds: z.number().optional().describe("How long to wait for response (default: 300)")
|
|
3371
|
+
},
|
|
3372
|
+
async (args) => {
|
|
3373
|
+
const actionId = `action_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
3374
|
+
const timeout = (args.timeout_seconds || 300) * 1e3;
|
|
3375
|
+
const actionData = {
|
|
3376
|
+
id: actionId,
|
|
3377
|
+
type: "confirmation",
|
|
3378
|
+
message: args.message,
|
|
3379
|
+
options: args.options,
|
|
3380
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
3381
|
+
};
|
|
3382
|
+
try {
|
|
3383
|
+
await setActionRequest(taskId, actionData);
|
|
3384
|
+
log.info(`Action request ${actionId} stored in metadata, waiting for user response...`);
|
|
3385
|
+
emitEvent(taskId, "user_action_request", actionData).catch(() => {
|
|
3386
|
+
});
|
|
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
|
+
const actionKey = response.action_key || response.action || "";
|
|
3393
|
+
const label = response.label || actionKey;
|
|
3394
|
+
log.info(`User responded: ${label} (${actionKey})`);
|
|
3395
|
+
return {
|
|
3396
|
+
content: [{
|
|
3397
|
+
type: "text",
|
|
3398
|
+
text: JSON.stringify({
|
|
3399
|
+
status: "responded",
|
|
3400
|
+
action_key: actionKey,
|
|
3401
|
+
label
|
|
3402
|
+
})
|
|
3403
|
+
}]
|
|
3404
|
+
};
|
|
3405
|
+
}
|
|
3406
|
+
await new Promise((resolve2) => setTimeout(resolve2, pollInterval));
|
|
3407
|
+
}
|
|
3408
|
+
log.warn(`Action request ${actionId} timed out after ${args.timeout_seconds || 300}s`);
|
|
3409
|
+
return {
|
|
3410
|
+
content: [{
|
|
3411
|
+
type: "text",
|
|
3412
|
+
text: JSON.stringify({
|
|
3413
|
+
status: "timeout",
|
|
3414
|
+
message: "User did not respond within the timeout period."
|
|
3415
|
+
})
|
|
3416
|
+
}]
|
|
3417
|
+
};
|
|
3418
|
+
} catch (err) {
|
|
3419
|
+
log.error(`request_user_confirmation failed: ${err}`);
|
|
3420
|
+
return {
|
|
3421
|
+
content: [{
|
|
3422
|
+
type: "text",
|
|
3423
|
+
text: `Failed to request user confirmation: ${err instanceof Error ? err.message : err}`
|
|
3424
|
+
}]
|
|
3425
|
+
};
|
|
3426
|
+
}
|
|
3427
|
+
}
|
|
3428
|
+
),
|
|
3331
3429
|
// ── Job Automation Tools ──────────────────────────────────────
|
|
3332
3430
|
tool(
|
|
3333
3431
|
"job_run",
|
|
@@ -3521,30 +3619,17 @@ ${content}`;
|
|
|
3521
3619
|
async function saveJobToDb(userId, jobName, jobDescription, createdSkillNames) {
|
|
3522
3620
|
try {
|
|
3523
3621
|
const sb = getSupabase();
|
|
3524
|
-
const { data
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3622
|
+
const { data, error } = await sb.rpc("save_job_with_skills", {
|
|
3623
|
+
p_user_id: userId,
|
|
3624
|
+
p_job_name: jobName,
|
|
3625
|
+
p_job_description: jobDescription,
|
|
3626
|
+
p_skill_names: createdSkillNames
|
|
3627
|
+
});
|
|
3628
|
+
if (error) {
|
|
3629
|
+
log.debug(`Failed to save job "${jobName}" via RPC: ${error.message}`);
|
|
3530
3630
|
return;
|
|
3531
3631
|
}
|
|
3532
|
-
|
|
3533
|
-
const { data: skillRows } = await sb.from("agent_skills").select("id").eq("user_id", userId).in("name", createdSkillNames);
|
|
3534
|
-
const skillIds = (skillRows || []).map((r) => r.id);
|
|
3535
|
-
if (skillIds.length > 0) {
|
|
3536
|
-
const links = skillIds.map((skillId) => ({
|
|
3537
|
-
job_id: job.id,
|
|
3538
|
-
skill_id: skillId
|
|
3539
|
-
}));
|
|
3540
|
-
const { error: linkError } = await sb.from("agent_job_skills").upsert(links, { onConflict: "job_id,skill_id" });
|
|
3541
|
-
if (linkError) {
|
|
3542
|
-
log.debug(`Failed to link skills to job: ${linkError.message}`);
|
|
3543
|
-
}
|
|
3544
|
-
}
|
|
3545
|
-
}
|
|
3546
|
-
await sb.from("agent_jobs").update({ skills_analyzed: true }).eq("id", job.id);
|
|
3547
|
-
log.debug(`Job "${jobName}" saved to DB with ${createdSkillNames.length} skill link(s), marked as analyzed`);
|
|
3632
|
+
log.debug(`Job "${jobName}" saved via RPC (id: ${data}), ${createdSkillNames.length} skill(s) linked`);
|
|
3548
3633
|
} catch (err) {
|
|
3549
3634
|
log.debug(`saveJobToDb error: ${err}`);
|
|
3550
3635
|
}
|
|
@@ -3639,7 +3724,7 @@ Available capabilities:
|
|
|
3639
3724
|
|
|
3640
3725
|
5. JOB AUTOMATION:
|
|
3641
3726
|
- When the user describes their job/role/daily work, use skill_generate to decompose it into automatable skills
|
|
3642
|
-
-
|
|
3727
|
+
- ALWAYS use request_user_confirmation to get user approval before creating skills \u2014 never create skills without approval
|
|
3643
3728
|
- Use job_run to start a job \u2014 it gives you the job's goal and available skills as capabilities
|
|
3644
3729
|
- When running a job, be AGENTIC: decide dynamically what to do based on what you discover
|
|
3645
3730
|
- Do NOT follow a fixed sequence \u2014 if checking Slack reveals a task that needs GitHub, go do GitHub immediately
|
|
@@ -3774,6 +3859,8 @@ var TaskProcessor = class {
|
|
|
3774
3859
|
"mcp__assistme-agent__skill_browse",
|
|
3775
3860
|
"mcp__assistme-agent__skill_add",
|
|
3776
3861
|
"mcp__assistme-agent__skill_publish",
|
|
3862
|
+
// User confirmation
|
|
3863
|
+
"mcp__assistme-agent__request_user_confirmation",
|
|
3777
3864
|
// Job automation tools
|
|
3778
3865
|
"mcp__assistme-agent__job_run",
|
|
3779
3866
|
"mcp__assistme-agent__job_schedule",
|
|
@@ -4446,7 +4533,7 @@ function registerJobCommands(program2) {
|
|
|
4446
4533
|
jobCmd.command("list").description("List your defined jobs").action(async () => {
|
|
4447
4534
|
try {
|
|
4448
4535
|
const userId = await getCurrentUserId();
|
|
4449
|
-
const { JobRunner: JobRunner2 } = await import("./job-runner-
|
|
4536
|
+
const { JobRunner: JobRunner2 } = await import("./job-runner-XTGLMPZ3.js");
|
|
4450
4537
|
const runner = new JobRunner2(userId);
|
|
4451
4538
|
const jobs = await runner.listJobs();
|
|
4452
4539
|
if (jobs.length === 0) {
|
|
@@ -4474,7 +4561,7 @@ function registerJobCommands(program2) {
|
|
|
4474
4561
|
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) => {
|
|
4475
4562
|
try {
|
|
4476
4563
|
const userId = await getCurrentUserId();
|
|
4477
|
-
const { JobRunner: JobRunner2 } = await import("./job-runner-
|
|
4564
|
+
const { JobRunner: JobRunner2 } = await import("./job-runner-XTGLMPZ3.js");
|
|
4478
4565
|
const runner = new JobRunner2(userId);
|
|
4479
4566
|
const runs = await runner.getRunHistory(
|
|
4480
4567
|
name,
|
|
@@ -4528,7 +4615,7 @@ Job Run History${name ? ` \u2014 ${name}` : ""}:`
|
|
|
4528
4615
|
process.exit(1);
|
|
4529
4616
|
}
|
|
4530
4617
|
const userId = await getCurrentUserId();
|
|
4531
|
-
const { JobRunner: JobRunner2 } = await import("./job-runner-
|
|
4618
|
+
const { JobRunner: JobRunner2 } = await import("./job-runner-XTGLMPZ3.js");
|
|
4532
4619
|
const runner = new JobRunner2(userId);
|
|
4533
4620
|
const job = await runner.loadJob(name);
|
|
4534
4621
|
if (!job) {
|
package/package.json
CHANGED
package/src/agent/mcp-servers.ts
CHANGED
|
@@ -10,7 +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
|
-
import { getSupabase } from "../db/supabase.js";
|
|
13
|
+
import { getSupabase, emitEvent, setActionRequest, pollActionResponse } from "../db/supabase.js";
|
|
14
14
|
import { JobRunner } from "./job-runner.js";
|
|
15
15
|
import {
|
|
16
16
|
createScheduledTask,
|
|
@@ -214,7 +214,7 @@ export function createAgentToolsServer(deps: AgentToolsDeps): McpSdkServerConfig
|
|
|
214
214
|
),
|
|
215
215
|
tool(
|
|
216
216
|
"skill_create",
|
|
217
|
-
"Create a new skill
|
|
217
|
+
"Create a new skill and add it to the user's collection. Returns the skill ID on success.",
|
|
218
218
|
{
|
|
219
219
|
name: z.string().describe("Skill name in kebab-case, e.g. 'flight-booking'"),
|
|
220
220
|
description: z.string().describe("One-line description of what this skill does"),
|
|
@@ -235,6 +235,7 @@ export function createAgentToolsServer(deps: AgentToolsDeps): McpSdkServerConfig
|
|
|
235
235
|
};
|
|
236
236
|
}
|
|
237
237
|
|
|
238
|
+
// Create in skills table (repository)
|
|
238
239
|
const result = await skillManager.create(
|
|
239
240
|
args.name,
|
|
240
241
|
args.description,
|
|
@@ -248,12 +249,25 @@ export function createAgentToolsServer(deps: AgentToolsDeps): McpSdkServerConfig
|
|
|
248
249
|
};
|
|
249
250
|
}
|
|
250
251
|
|
|
251
|
-
|
|
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`);
|
|
252
266
|
return {
|
|
253
267
|
content: [
|
|
254
268
|
{
|
|
255
269
|
type: "text",
|
|
256
|
-
text: `Skill "${args.name}" created (ID: ${result.id})
|
|
270
|
+
text: `Skill "${args.name}" created and added to your collection (ID: ${result.id}).`,
|
|
257
271
|
},
|
|
258
272
|
],
|
|
259
273
|
};
|
|
@@ -405,7 +419,7 @@ export function createAgentToolsServer(deps: AgentToolsDeps): McpSdkServerConfig
|
|
|
405
419
|
tool(
|
|
406
420
|
"skill_generate",
|
|
407
421
|
"Prepare context for generating skills from a job description. Returns existing skills and job info " +
|
|
408
|
-
"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). " +
|
|
409
423
|
"After creating all skills, call skill_link_job to link them to the job and mark it as analyzed.",
|
|
410
424
|
{
|
|
411
425
|
job_name: z.string().describe(
|
|
@@ -426,14 +440,20 @@ export function createAgentToolsServer(deps: AgentToolsDeps): McpSdkServerConfig
|
|
|
426
440
|
response += `**Existing skills (do NOT duplicate):** ${existingNames.join(", ")}\n\n`;
|
|
427
441
|
}
|
|
428
442
|
|
|
429
|
-
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 — You MUST use request_user_confirmation before creating skills:**\n`;
|
|
445
|
+
response += `1. Analyze the job and draft a list of proposed skills (name, emoji, one-line description for each).\n`;
|
|
446
|
+
response += `2. Call \`request_user_confirmation\` with the formatted skill list as "message" and these options:\n`;
|
|
447
|
+
response += ` - options: [{label: "Approve All", action_key: "approve_all", description: "Create all proposed skills"}, {label: "Cancel", action_key: "cancel", description: "Do not create any skills"}]\n`;
|
|
448
|
+
response += `3. WAIT for the response. If action_key is "approve_all", create all skills using \`skill_create\`. If "cancel", stop.\n`;
|
|
449
|
+
response += `4. Do NOT ask for confirmation in text. Do NOT create skills without calling request_user_confirmation first.\n\n`;
|
|
430
450
|
response += `For each skill, call \`skill_create\` with:\n`;
|
|
431
451
|
response += `- name: kebab-case name (e.g. "slack-message-check")\n`;
|
|
432
452
|
response += `- description: one-line description\n`;
|
|
433
453
|
response += `- instructions: detailed step-by-step markdown instructions the agent can follow\n`;
|
|
434
454
|
response += `- emoji: a single emoji representing the skill\n\n`;
|
|
435
|
-
response += `
|
|
436
|
-
response += `After ALL skills are created
|
|
455
|
+
response += `skill_create automatically adds the skill to the user's collection — no need to call skill_add.\n\n`;
|
|
456
|
+
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`;
|
|
437
457
|
response += `**Guidelines for skill instructions:**\n`;
|
|
438
458
|
response += `- Write clear, actionable markdown steps\n`;
|
|
439
459
|
response += `- Reference browser tools (browser_navigate, browser_click, browser_read_page, etc.) for web tasks\n`;
|
|
@@ -584,6 +604,89 @@ export function createAgentToolsServer(deps: AgentToolsDeps): McpSdkServerConfig
|
|
|
584
604
|
}
|
|
585
605
|
),
|
|
586
606
|
|
|
607
|
+
// ── User Confirmation Tool ─────────────────────────────────
|
|
608
|
+
|
|
609
|
+
tool(
|
|
610
|
+
"request_user_confirmation",
|
|
611
|
+
"Pause and ask the user for approval or input via the web UI. " +
|
|
612
|
+
"Returns the user's chosen action_key. Use this BEFORE creating skills, making irreversible changes, etc. " +
|
|
613
|
+
"The agent will block until the user responds or the timeout expires.",
|
|
614
|
+
{
|
|
615
|
+
message: z.string().describe("What to show the user (supports markdown)"),
|
|
616
|
+
options: z.array(z.object({
|
|
617
|
+
label: z.string().describe("Button label shown to user"),
|
|
618
|
+
action_key: z.string().describe("Machine-readable key returned when selected"),
|
|
619
|
+
description: z.string().optional().describe("Tooltip/description for this option"),
|
|
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 = `action_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
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
|
+
// Store action request in message metadata via RPC — UI reads this
|
|
637
|
+
await setActionRequest(taskId, actionData);
|
|
638
|
+
log.info(`Action request ${actionId} stored in metadata, waiting for user response...`);
|
|
639
|
+
|
|
640
|
+
// Also emit event for real-time notification (best-effort)
|
|
641
|
+
emitEvent(taskId, "user_action_request", actionData).catch(() => {});
|
|
642
|
+
|
|
643
|
+
// Poll for response
|
|
644
|
+
const startTime = Date.now();
|
|
645
|
+
const pollInterval = 2000;
|
|
646
|
+
|
|
647
|
+
while (Date.now() - startTime < timeout) {
|
|
648
|
+
const response = await pollActionResponse(taskId);
|
|
649
|
+
if (response) {
|
|
650
|
+
const actionKey = (response.action_key || response.action || "") as string;
|
|
651
|
+
const label = (response.label || actionKey) as string;
|
|
652
|
+
log.info(`User responded: ${label} (${actionKey})`);
|
|
653
|
+
return {
|
|
654
|
+
content: [{
|
|
655
|
+
type: "text",
|
|
656
|
+
text: JSON.stringify({
|
|
657
|
+
status: "responded",
|
|
658
|
+
action_key: actionKey,
|
|
659
|
+
label,
|
|
660
|
+
}),
|
|
661
|
+
}],
|
|
662
|
+
};
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
log.warn(`Action request ${actionId} timed out after ${args.timeout_seconds || 300}s`);
|
|
669
|
+
return {
|
|
670
|
+
content: [{
|
|
671
|
+
type: "text",
|
|
672
|
+
text: JSON.stringify({
|
|
673
|
+
status: "timeout",
|
|
674
|
+
message: "User did not respond within the timeout period.",
|
|
675
|
+
}),
|
|
676
|
+
}],
|
|
677
|
+
};
|
|
678
|
+
} catch (err) {
|
|
679
|
+
log.error(`request_user_confirmation failed: ${err}`);
|
|
680
|
+
return {
|
|
681
|
+
content: [{
|
|
682
|
+
type: "text",
|
|
683
|
+
text: `Failed to request user confirmation: ${err instanceof Error ? err.message : err}`,
|
|
684
|
+
}],
|
|
685
|
+
};
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
),
|
|
689
|
+
|
|
587
690
|
// ── Job Automation Tools ──────────────────────────────────────
|
|
588
691
|
|
|
589
692
|
tool(
|
|
@@ -824,54 +927,19 @@ async function saveJobToDb(
|
|
|
824
927
|
try {
|
|
825
928
|
const sb = getSupabase();
|
|
826
929
|
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
)
|
|
834
|
-
.select("id")
|
|
835
|
-
.single();
|
|
836
|
-
|
|
837
|
-
if (jobError || !job) {
|
|
838
|
-
log.debug(`Failed to save job "${jobName}": ${jobError?.message}`);
|
|
839
|
-
return;
|
|
840
|
-
}
|
|
841
|
-
|
|
842
|
-
// Query DB for skill IDs by name (can't rely on in-memory dbId due to async sync)
|
|
843
|
-
if (createdSkillNames.length > 0) {
|
|
844
|
-
const { data: skillRows } = await sb
|
|
845
|
-
.from("agent_skills")
|
|
846
|
-
.select("id")
|
|
847
|
-
.eq("user_id", userId)
|
|
848
|
-
.in("name", createdSkillNames);
|
|
849
|
-
|
|
850
|
-
const skillIds = (skillRows || []).map((r: Record<string, unknown>) => r.id as string);
|
|
930
|
+
const { data, error } = await sb.rpc("save_job_with_skills", {
|
|
931
|
+
p_user_id: userId,
|
|
932
|
+
p_job_name: jobName,
|
|
933
|
+
p_job_description: jobDescription,
|
|
934
|
+
p_skill_names: createdSkillNames,
|
|
935
|
+
});
|
|
851
936
|
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
skill_id: skillId,
|
|
856
|
-
}));
|
|
857
|
-
|
|
858
|
-
const { error: linkError } = await sb
|
|
859
|
-
.from("agent_job_skills")
|
|
860
|
-
.upsert(links, { onConflict: "job_id,skill_id" });
|
|
861
|
-
|
|
862
|
-
if (linkError) {
|
|
863
|
-
log.debug(`Failed to link skills to job: ${linkError.message}`);
|
|
864
|
-
}
|
|
865
|
-
}
|
|
937
|
+
if (error) {
|
|
938
|
+
log.debug(`Failed to save job "${jobName}" via RPC: ${error.message}`);
|
|
939
|
+
return;
|
|
866
940
|
}
|
|
867
941
|
|
|
868
|
-
|
|
869
|
-
await sb
|
|
870
|
-
.from("agent_jobs")
|
|
871
|
-
.update({ skills_analyzed: true })
|
|
872
|
-
.eq("id", job.id);
|
|
873
|
-
|
|
874
|
-
log.debug(`Job "${jobName}" saved to DB with ${createdSkillNames.length} skill link(s), marked as analyzed`);
|
|
942
|
+
log.debug(`Job "${jobName}" saved via RPC (id: ${data}), ${createdSkillNames.length} skill(s) linked`);
|
|
875
943
|
} catch (err) {
|
|
876
944
|
log.debug(`saveJobToDb error: ${err}`);
|
|
877
945
|
}
|
package/src/agent/processor.ts
CHANGED
|
@@ -68,7 +68,7 @@ Available capabilities:
|
|
|
68
68
|
|
|
69
69
|
5. JOB AUTOMATION:
|
|
70
70
|
- When the user describes their job/role/daily work, use skill_generate to decompose it into automatable skills
|
|
71
|
-
-
|
|
71
|
+
- ALWAYS use request_user_confirmation to get user approval before creating skills — never create skills without approval
|
|
72
72
|
- Use job_run to start a job — it gives you the job's goal and available skills as capabilities
|
|
73
73
|
- When running a job, be AGENTIC: decide dynamically what to do based on what you discover
|
|
74
74
|
- Do NOT follow a fixed sequence — if checking Slack reveals a task that needs GitHub, go do GitHub immediately
|
|
@@ -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",
|
package/src/db/supabase.ts
CHANGED
|
@@ -418,7 +418,8 @@ export type EventType =
|
|
|
418
418
|
| "tool_result"
|
|
419
419
|
| "thinking"
|
|
420
420
|
| "error"
|
|
421
|
-
| "status_change"
|
|
421
|
+
| "status_change"
|
|
422
|
+
| "user_action_request";
|
|
422
423
|
|
|
423
424
|
let eventSequence = 0;
|
|
424
425
|
|
|
@@ -459,3 +460,30 @@ export async function emitEvents(
|
|
|
459
460
|
});
|
|
460
461
|
if (error) log.warn(`Failed to emit events batch: ${error.message}`);
|
|
461
462
|
}
|
|
463
|
+
|
|
464
|
+
// ── Action Request Helpers ──────────────────────────────────────────
|
|
465
|
+
|
|
466
|
+
export async function setActionRequest(
|
|
467
|
+
messageId: string,
|
|
468
|
+
actionData: Record<string, unknown>
|
|
469
|
+
): Promise<void> {
|
|
470
|
+
const sb = getSupabase();
|
|
471
|
+
const { error } = await sb.rpc("mcp_set_action_request", {
|
|
472
|
+
p_token_hash: getTokenHash(),
|
|
473
|
+
p_message_id: messageId,
|
|
474
|
+
p_action_data: actionData,
|
|
475
|
+
});
|
|
476
|
+
if (error) throw new Error(`Failed to set action request: ${error.message}`);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
export async function pollActionResponse(
|
|
480
|
+
messageId: string
|
|
481
|
+
): Promise<Record<string, unknown> | null> {
|
|
482
|
+
const sb = getSupabase();
|
|
483
|
+
const { data, error } = await sb.rpc("mcp_poll_action_response", {
|
|
484
|
+
p_token_hash: getTokenHash(),
|
|
485
|
+
p_message_id: messageId,
|
|
486
|
+
});
|
|
487
|
+
if (error) throw new Error(`Failed to poll action response: ${error.message}`);
|
|
488
|
+
return data as Record<string, unknown> | null;
|
|
489
|
+
}
|