assistme 0.2.8 → 0.3.0
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 +818 -704
- 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 +111 -151
- package/src/agent/memory.test.ts +41 -65
- package/src/agent/memory.ts +33 -134
- package/src/agent/processor.ts +59 -17
- 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/skill-evaluator.ts +258 -0
- package/src/agent/skills.ts +191 -494
- 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.
|
|
3
|
+
"version": "0.3.0",
|
|
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, validateSkillName } 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,28 +215,25 @@ 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) => {
|
|
226
|
+
// Validate skill name format
|
|
227
|
+
const nameError = validateSkillName(args.name);
|
|
228
|
+
if (nameError) {
|
|
229
|
+
return {
|
|
230
|
+
content: [{
|
|
231
|
+
type: "text",
|
|
232
|
+
text: `Invalid skill name: ${nameError}. Use lowercase kebab-case like "flight-booking".`,
|
|
233
|
+
}],
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
239
237
|
// Check for duplicates in user's collection
|
|
240
238
|
const existing = skillManager.findSimilar(args.name);
|
|
241
239
|
if (existing) {
|
|
@@ -254,11 +252,7 @@ export function createAgentToolsServer(deps: AgentToolsDeps): McpSdkServerConfig
|
|
|
254
252
|
args.name,
|
|
255
253
|
args.description,
|
|
256
254
|
args.instructions,
|
|
257
|
-
{
|
|
258
|
-
source: "manual",
|
|
259
|
-
emoji: args.emoji,
|
|
260
|
-
variables: args.variables as SkillVariable[] | undefined,
|
|
261
|
-
}
|
|
255
|
+
{ source: "manual", emoji: args.emoji }
|
|
262
256
|
);
|
|
263
257
|
|
|
264
258
|
if (!result) {
|
|
@@ -277,27 +271,17 @@ export function createAgentToolsServer(deps: AgentToolsDeps): McpSdkServerConfig
|
|
|
277
271
|
source: "manual",
|
|
278
272
|
emoji: args.emoji,
|
|
279
273
|
sourceSkillId: result.id,
|
|
280
|
-
variables: args.variables as SkillVariable[] | undefined,
|
|
281
274
|
}
|
|
282
275
|
);
|
|
283
276
|
|
|
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
277
|
log.success(`Skill "${args.name}" created and added to collection`);
|
|
299
278
|
return {
|
|
300
|
-
content: [
|
|
279
|
+
content: [
|
|
280
|
+
{
|
|
281
|
+
type: "text",
|
|
282
|
+
text: `Skill "${args.name}" created and added to your collection (ID: ${result.id}).`,
|
|
283
|
+
},
|
|
284
|
+
],
|
|
301
285
|
};
|
|
302
286
|
}
|
|
303
287
|
),
|
|
@@ -382,9 +366,6 @@ export function createAgentToolsServer(deps: AgentToolsDeps): McpSdkServerConfig
|
|
|
382
366
|
|
|
383
367
|
let content = skill.content;
|
|
384
368
|
|
|
385
|
-
// Substitute {{variable}} placeholders with user config values
|
|
386
|
-
content = substituteVariables(content, skill.config, skill.variables);
|
|
387
|
-
|
|
388
369
|
// Substitute $ARGUMENTS placeholders
|
|
389
370
|
if (args.arguments) {
|
|
390
371
|
content = substituteArguments(content, args.arguments);
|
|
@@ -405,16 +386,6 @@ export function createAgentToolsServer(deps: AgentToolsDeps): McpSdkServerConfig
|
|
|
405
386
|
response += `\n\n**Allowed tools for this skill:** ${skill.allowedTools.join(", ")}\n`;
|
|
406
387
|
}
|
|
407
388
|
|
|
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
389
|
log.info(`Skill invoked: "${args.name}"`);
|
|
419
390
|
|
|
420
391
|
// Log invocation to DB (fire-and-forget)
|
|
@@ -457,56 +428,6 @@ export function createAgentToolsServer(deps: AgentToolsDeps): McpSdkServerConfig
|
|
|
457
428
|
return { content: [{ type: "text", text: response }] };
|
|
458
429
|
}
|
|
459
430
|
),
|
|
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
431
|
tool(
|
|
511
432
|
"skill_generate",
|
|
512
433
|
"Prepare context for generating skills from a job description. Returns existing skills and job info " +
|
|
@@ -542,39 +463,15 @@ export function createAgentToolsServer(deps: AgentToolsDeps): McpSdkServerConfig
|
|
|
542
463
|
response += `- name: kebab-case name (e.g. "slack-message-check")\n`;
|
|
543
464
|
response += `- description: one-line description\n`;
|
|
544
465
|
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`;
|
|
466
|
+
response += `- emoji: a single emoji representing the skill\n\n`;
|
|
547
467
|
response += `skill_create automatically adds the skill to the user's collection — no need to call skill_add.\n\n`;
|
|
548
468
|
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
469
|
response += `**Guidelines for skill instructions:**\n`;
|
|
550
470
|
response += `- Write clear, actionable markdown steps\n`;
|
|
551
471
|
response += `- Reference browser tools (browser_navigate, browser_click, browser_read_page, etc.) for web tasks\n`;
|
|
552
472
|
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`;
|
|
473
|
+
response += `- Use placeholders like {query}, {date} for variable inputs\n`;
|
|
474
|
+
response += `- Each skill should be a single, well-defined workflow (10-25 steps)\n`;
|
|
578
475
|
|
|
579
476
|
return { content: [{ type: "text", text: response }] };
|
|
580
477
|
}
|
|
@@ -719,7 +616,78 @@ export function createAgentToolsServer(deps: AgentToolsDeps): McpSdkServerConfig
|
|
|
719
616
|
}
|
|
720
617
|
),
|
|
721
618
|
|
|
722
|
-
// ── User
|
|
619
|
+
// ── User Interaction Tools ──────────────────────────────────
|
|
620
|
+
|
|
621
|
+
tool(
|
|
622
|
+
"request_user_input",
|
|
623
|
+
"Ask the user a clarifying question and wait for their free-text response. " +
|
|
624
|
+
"Use this when you need information that cannot be inferred from context, memory, or the workspace — " +
|
|
625
|
+
"e.g. which account to use, specific preferences, ambiguous instructions, or missing parameters for a skill. " +
|
|
626
|
+
"Do NOT use this for information you can discover yourself (git remote, file contents, etc.).",
|
|
627
|
+
{
|
|
628
|
+
question: z.string().describe("The question to ask the user (supports markdown). Be specific about what you need and why."),
|
|
629
|
+
placeholder: z.string().optional().describe("Placeholder text for the input field (e.g. 'https://github.com/owner/repo')"),
|
|
630
|
+
timeout_seconds: z.number().optional().describe("How long to wait for response (default: 300)"),
|
|
631
|
+
},
|
|
632
|
+
async (args) => {
|
|
633
|
+
const actionId = `input_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
634
|
+
const timeout = (args.timeout_seconds || 300) * 1000;
|
|
635
|
+
|
|
636
|
+
const actionData = {
|
|
637
|
+
id: actionId,
|
|
638
|
+
type: "input",
|
|
639
|
+
message: args.question,
|
|
640
|
+
placeholder: args.placeholder || "",
|
|
641
|
+
created_at: new Date().toISOString(),
|
|
642
|
+
};
|
|
643
|
+
|
|
644
|
+
try {
|
|
645
|
+
await setActionRequest(taskId, actionData);
|
|
646
|
+
log.info(`Input request ${actionId}: "${args.question.slice(0, 80)}..."`);
|
|
647
|
+
|
|
648
|
+
emitEvent(taskId, "user_action_request", actionData).catch(() => {});
|
|
649
|
+
|
|
650
|
+
const startTime = Date.now();
|
|
651
|
+
const pollInterval = 2000;
|
|
652
|
+
|
|
653
|
+
while (Date.now() - startTime < timeout) {
|
|
654
|
+
const response = await pollActionResponse(taskId);
|
|
655
|
+
// Match response to this specific request by action_id
|
|
656
|
+
if (response && (!response.action_id || response.action_id === actionId)) {
|
|
657
|
+
const text = (response.text || response.value || "") as string;
|
|
658
|
+
log.info(`User input received: "${text.slice(0, 80)}"`);
|
|
659
|
+
return {
|
|
660
|
+
content: [{
|
|
661
|
+
type: "text",
|
|
662
|
+
text: JSON.stringify({ status: "responded", text }),
|
|
663
|
+
}],
|
|
664
|
+
};
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
log.warn(`Input request ${actionId} timed out`);
|
|
671
|
+
return {
|
|
672
|
+
content: [{
|
|
673
|
+
type: "text",
|
|
674
|
+
text: JSON.stringify({
|
|
675
|
+
status: "timeout",
|
|
676
|
+
message: "User did not respond within the timeout period.",
|
|
677
|
+
}),
|
|
678
|
+
}],
|
|
679
|
+
};
|
|
680
|
+
} catch (err) {
|
|
681
|
+
log.error(`request_user_input failed: ${err}`);
|
|
682
|
+
return {
|
|
683
|
+
content: [{
|
|
684
|
+
type: "text",
|
|
685
|
+
text: `Failed to request user input: ${err instanceof Error ? err.message : err}`,
|
|
686
|
+
}],
|
|
687
|
+
};
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
),
|
|
723
691
|
|
|
724
692
|
tool(
|
|
725
693
|
"request_user_confirmation",
|
|
@@ -761,7 +729,8 @@ export function createAgentToolsServer(deps: AgentToolsDeps): McpSdkServerConfig
|
|
|
761
729
|
|
|
762
730
|
while (Date.now() - startTime < timeout) {
|
|
763
731
|
const response = await pollActionResponse(taskId);
|
|
764
|
-
|
|
732
|
+
// Match response to this specific request by action_id
|
|
733
|
+
if (response && (!response.action_id || response.action_id === actionId)) {
|
|
765
734
|
const actionKey = (response.action_key || response.action || "") as string;
|
|
766
735
|
const label = (response.label || actionKey) as string;
|
|
767
736
|
log.info(`User responded: ${label} (${actionKey})`);
|
|
@@ -916,11 +885,10 @@ export function createAgentToolsServer(deps: AgentToolsDeps): McpSdkServerConfig
|
|
|
916
885
|
const task = await createScheduledTask(userId, name, prompt, args.cron, tz);
|
|
917
886
|
|
|
918
887
|
// Link the job_id to the scheduled task
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
.
|
|
922
|
-
|
|
923
|
-
.eq("id", task.id);
|
|
888
|
+
await callMcpHandler("schedule.link_job", {
|
|
889
|
+
task_id: task.id,
|
|
890
|
+
job_id: job.jobId,
|
|
891
|
+
});
|
|
924
892
|
|
|
925
893
|
const nextRun = task.next_run_at
|
|
926
894
|
? new Date(task.next_run_at).toLocaleString()
|
|
@@ -1034,27 +1002,19 @@ export function createAgentToolsServer(deps: AgentToolsDeps): McpSdkServerConfig
|
|
|
1034
1002
|
// ── Helper: persist job and link skills ────────────────────────────
|
|
1035
1003
|
|
|
1036
1004
|
async function saveJobToDb(
|
|
1037
|
-
|
|
1005
|
+
_userId: string,
|
|
1038
1006
|
jobName: string,
|
|
1039
1007
|
jobDescription: string,
|
|
1040
1008
|
createdSkillNames: string[]
|
|
1041
1009
|
): Promise<void> {
|
|
1042
1010
|
try {
|
|
1043
|
-
const
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
p_job_name: jobName,
|
|
1048
|
-
p_job_description: jobDescription,
|
|
1049
|
-
p_skill_names: createdSkillNames,
|
|
1011
|
+
const data = await callMcpHandler("job.save_with_skills", {
|
|
1012
|
+
job_name: jobName,
|
|
1013
|
+
job_description: jobDescription,
|
|
1014
|
+
skill_names: createdSkillNames,
|
|
1050
1015
|
});
|
|
1051
1016
|
|
|
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`);
|
|
1017
|
+
log.debug(`Job "${jobName}" saved via edge function (id: ${data}), ${createdSkillNames.length} skill(s) linked`);
|
|
1058
1018
|
} catch (err) {
|
|
1059
1019
|
log.debug(`saveJobToDb error: ${err}`);
|
|
1060
1020
|
}
|