agent-planner-mcp 0.3.1 → 0.5.1
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/AGENT_GUIDE.md +257 -0
- package/LICENSE +21 -0
- package/README.md +128 -247
- package/SKILL.md +438 -0
- package/package.json +24 -7
- package/src/api-client.js +506 -115
- package/src/index.js +60 -27
- package/src/integrations/search-integration.js +3 -5
- package/src/server-http.js +569 -0
- package/src/session-manager.js +223 -0
- package/src/setup.js +1 -1
- package/src/tools/search-wrapper.js +12 -6
- package/src/tools.js +1983 -159
- package/claude-code/AUTONOMOUS_EXECUTION_GUIDE.md +0 -335
- package/claude-code/commands/README.md +0 -112
- package/claude-code/commands/create-plan.md +0 -174
- package/claude-code/commands/execute-plan.md +0 -202
- package/claude-code/commands/plan-status.md +0 -145
- package/claude-code/settings.template.json +0 -12
package/src/tools.js
CHANGED
|
@@ -10,7 +10,11 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
const { ListToolsRequestSchema, CallToolRequestSchema } = require('@modelcontextprotocol/sdk/types.js');
|
|
13
|
-
const
|
|
13
|
+
const defaultApiClient = require('./api-client');
|
|
14
|
+
|
|
15
|
+
const APP_URL = (process.env.APP_URL || 'https://agentplanner.io').replace(/\/$/, '');
|
|
16
|
+
function buildPlanUrl(planId) { return `${APP_URL}/app/plans/${planId}`; }
|
|
17
|
+
function buildTaskUrl(planId, nodeId) { return `${APP_URL}/app/plans/${planId}?node=${nodeId}`; }
|
|
14
18
|
|
|
15
19
|
/**
|
|
16
20
|
* Format JSON data as text for Claude Desktop
|
|
@@ -43,8 +47,10 @@ function formatResponse(data) {
|
|
|
43
47
|
/**
|
|
44
48
|
* Setup tools for the MCP server
|
|
45
49
|
* @param {Server} server - MCP server instance
|
|
50
|
+
* @param {Object} [apiClientOverride] - Per-session API client (HTTP mode). Falls back to default (stdio mode).
|
|
46
51
|
*/
|
|
47
|
-
function setupTools(server) {
|
|
52
|
+
function setupTools(server, apiClientOverride) {
|
|
53
|
+
const apiClient = apiClientOverride || defaultApiClient;
|
|
48
54
|
// Suppress console logs when not in debug mode
|
|
49
55
|
if (process.env.NODE_ENV !== 'development') {
|
|
50
56
|
// Silent mode for production
|
|
@@ -56,6 +62,191 @@ function setupTools(server) {
|
|
|
56
62
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
57
63
|
return {
|
|
58
64
|
tools: [
|
|
65
|
+
// ========================================
|
|
66
|
+
// QUICK ACTIONS - Low friction entry points
|
|
67
|
+
// Use these for common operations
|
|
68
|
+
// ========================================
|
|
69
|
+
{
|
|
70
|
+
name: "quick_plan",
|
|
71
|
+
description: "Create a plan quickly from a title and list of tasks. Perfect for getting started fast - just provide a title and task names. Returns plan URL and task IDs for immediate use. Tip: provide a goal_id to automatically link this plan to a goal.",
|
|
72
|
+
inputSchema: {
|
|
73
|
+
type: "object",
|
|
74
|
+
properties: {
|
|
75
|
+
title: { type: "string", description: "Plan title" },
|
|
76
|
+
description: { type: "string", description: "Optional plan description" },
|
|
77
|
+
tasks: {
|
|
78
|
+
type: "array",
|
|
79
|
+
items: { type: "string" },
|
|
80
|
+
description: "List of task titles (simple strings). A phase will be created automatically."
|
|
81
|
+
},
|
|
82
|
+
goal_id: { type: "string", description: "Optionally link this plan to a goal. Recommended: always link plans to goals for tracking." },
|
|
83
|
+
organization_id: { type: "string", description: "Organization ID (uses default if not provided)" }
|
|
84
|
+
},
|
|
85
|
+
required: ["title", "tasks"]
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
name: "quick_task",
|
|
90
|
+
description: "Add a single task to an existing plan. Minimal parameters - just plan_id and title. Automatically adds to the first phase or creates one.",
|
|
91
|
+
inputSchema: {
|
|
92
|
+
type: "object",
|
|
93
|
+
properties: {
|
|
94
|
+
plan_id: { type: "string", description: "Plan to add task to" },
|
|
95
|
+
title: { type: "string", description: "Task title" },
|
|
96
|
+
description: { type: "string", description: "Optional task description" },
|
|
97
|
+
phase_id: { type: "string", description: "Specific phase to add to (optional - uses first phase if not provided)" },
|
|
98
|
+
agent_instructions: { type: "string", description: "Instructions for AI agents working on this task" }
|
|
99
|
+
},
|
|
100
|
+
required: ["plan_id", "title"]
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
name: "quick_status",
|
|
105
|
+
description: "Update a task's status. The most common operation - made simple. Returns what to do next.",
|
|
106
|
+
inputSchema: {
|
|
107
|
+
type: "object",
|
|
108
|
+
properties: {
|
|
109
|
+
task_id: { type: "string", description: "Task ID to update" },
|
|
110
|
+
plan_id: { type: "string", description: "Plan ID (required for API)" },
|
|
111
|
+
status: {
|
|
112
|
+
type: "string",
|
|
113
|
+
enum: ["not_started", "in_progress", "completed", "blocked", "plan_ready"],
|
|
114
|
+
description: "New status"
|
|
115
|
+
},
|
|
116
|
+
note: { type: "string", description: "Optional note explaining the status change (especially useful for 'blocked')" }
|
|
117
|
+
},
|
|
118
|
+
required: ["task_id", "plan_id", "status"]
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
name: "quick_log",
|
|
123
|
+
description: "Add a progress note to a task. Use this to document work as you go - helps humans follow along and other agents understand what happened.",
|
|
124
|
+
inputSchema: {
|
|
125
|
+
type: "object",
|
|
126
|
+
properties: {
|
|
127
|
+
task_id: { type: "string", description: "Task ID" },
|
|
128
|
+
plan_id: { type: "string", description: "Plan ID" },
|
|
129
|
+
message: { type: "string", description: "What you did or learned" },
|
|
130
|
+
log_type: {
|
|
131
|
+
type: "string",
|
|
132
|
+
enum: ["progress", "decision", "blocker", "completion"],
|
|
133
|
+
default: "progress",
|
|
134
|
+
description: "Type of log entry"
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
required: ["task_id", "plan_id", "message"]
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
|
|
141
|
+
{
|
|
142
|
+
name: "check_goals_health",
|
|
143
|
+
description: "Check the health of all your goals. Returns per-goal health status (on_track/at_risk/stale), bottleneck summaries, knowledge gaps, and pending decisions. Call this FIRST in the autonomous loop to identify which goals need attention.",
|
|
144
|
+
inputSchema: {
|
|
145
|
+
type: "object",
|
|
146
|
+
properties: {
|
|
147
|
+
status_filter: { type: "string", description: "Filter by health status (e.g. 'on_track', 'at_risk', 'stale')" }
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
|
|
152
|
+
// ========================================
|
|
153
|
+
// TASK CLAIMING - Prevent agent collisions
|
|
154
|
+
// ========================================
|
|
155
|
+
{
|
|
156
|
+
name: "claim_task",
|
|
157
|
+
description: "Claim exclusive ownership of a task before starting work. Prevents other agents from working on the same task. Claims expire after ttl_minutes (default 30). Always claim before starting work on a task.",
|
|
158
|
+
inputSchema: {
|
|
159
|
+
type: "object",
|
|
160
|
+
properties: {
|
|
161
|
+
task_id: { type: "string", description: "Task ID to claim" },
|
|
162
|
+
plan_id: { type: "string", description: "Plan ID" },
|
|
163
|
+
ttl_minutes: { type: "integer", description: "Claim duration in minutes (default 30)", default: 30 }
|
|
164
|
+
},
|
|
165
|
+
required: ["task_id", "plan_id"]
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
name: "release_task",
|
|
170
|
+
description: "Release a previously claimed task. Called automatically when you complete a task, but use this if you need to abandon work early.",
|
|
171
|
+
inputSchema: {
|
|
172
|
+
type: "object",
|
|
173
|
+
properties: {
|
|
174
|
+
task_id: { type: "string", description: "Task ID to release" },
|
|
175
|
+
plan_id: { type: "string", description: "Plan ID" }
|
|
176
|
+
},
|
|
177
|
+
required: ["task_id", "plan_id"]
|
|
178
|
+
}
|
|
179
|
+
},
|
|
180
|
+
|
|
181
|
+
// ========================================
|
|
182
|
+
// CONTEXT LOADING - Get everything you need
|
|
183
|
+
// Use before starting work on a plan/goal
|
|
184
|
+
// ========================================
|
|
185
|
+
{
|
|
186
|
+
name: "get_context",
|
|
187
|
+
description: "Load EVERYTHING you need to work on a plan or goal: structure, status, progress, blocked tasks, recent activity, and relevant knowledge. Call this FIRST before starting work.",
|
|
188
|
+
inputSchema: {
|
|
189
|
+
type: "object",
|
|
190
|
+
properties: {
|
|
191
|
+
plan_id: { type: "string", description: "Plan to get context for" },
|
|
192
|
+
goal_id: { type: "string", description: "Goal to get context for" },
|
|
193
|
+
include_knowledge: { type: "boolean", default: true, description: "Include relevant knowledge entries" }
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
name: "get_my_tasks",
|
|
199
|
+
description: "Get tasks that need attention - blocked tasks, in-progress tasks, and next tasks to start. Perfect for status check-ins.",
|
|
200
|
+
inputSchema: {
|
|
201
|
+
type: "object",
|
|
202
|
+
properties: {
|
|
203
|
+
plan_id: { type: "string", description: "Specific plan to check (optional - checks all if not provided)" },
|
|
204
|
+
status: {
|
|
205
|
+
type: "array",
|
|
206
|
+
items: { type: "string" },
|
|
207
|
+
default: ["blocked", "in_progress"],
|
|
208
|
+
description: "Task statuses to include"
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
},
|
|
213
|
+
|
|
214
|
+
// ========================================
|
|
215
|
+
// MARKDOWN EXPORT/IMPORT - Filesystem pattern
|
|
216
|
+
// ========================================
|
|
217
|
+
{
|
|
218
|
+
name: "export_plan_markdown",
|
|
219
|
+
description: "Export a plan as markdown text. Useful for reviewing plans in text format or saving to files.",
|
|
220
|
+
inputSchema: {
|
|
221
|
+
type: "object",
|
|
222
|
+
properties: {
|
|
223
|
+
plan_id: { type: "string", description: "Plan to export" },
|
|
224
|
+
include_descriptions: { type: "boolean", default: true, description: "Include task descriptions" },
|
|
225
|
+
include_status: { type: "boolean", default: true, description: "Include status indicators" }
|
|
226
|
+
},
|
|
227
|
+
required: ["plan_id"]
|
|
228
|
+
}
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
name: "import_plan_markdown",
|
|
232
|
+
description: "Create a new plan from markdown text. Parses headings as phases and list items as tasks. Great for converting text plans into structured AgentPlanner plans.",
|
|
233
|
+
inputSchema: {
|
|
234
|
+
type: "object",
|
|
235
|
+
properties: {
|
|
236
|
+
markdown: {
|
|
237
|
+
type: "string",
|
|
238
|
+
description: "Markdown text to parse. Use # for title, ## for phases, - for tasks"
|
|
239
|
+
},
|
|
240
|
+
title: {
|
|
241
|
+
type: "string",
|
|
242
|
+
description: "Plan title (optional - extracted from first # heading if not provided)"
|
|
243
|
+
},
|
|
244
|
+
goal_id: { type: "string", description: "Optionally link to a goal" }
|
|
245
|
+
},
|
|
246
|
+
required: ["markdown"]
|
|
247
|
+
}
|
|
248
|
+
},
|
|
249
|
+
|
|
59
250
|
// ===== UNIFIED SEARCH TOOL =====
|
|
60
251
|
{
|
|
61
252
|
name: "search",
|
|
@@ -89,7 +280,7 @@ function setupTools(server) {
|
|
|
89
280
|
type: {
|
|
90
281
|
type: "string",
|
|
91
282
|
description: "Filter by type",
|
|
92
|
-
enum: ["plan", "node", "phase", "task", "milestone", "
|
|
283
|
+
enum: ["plan", "node", "phase", "task", "milestone", "log"]
|
|
93
284
|
},
|
|
94
285
|
limit: {
|
|
95
286
|
type: "integer",
|
|
@@ -165,6 +356,31 @@ function setupTools(server) {
|
|
|
165
356
|
required: ["plan_id"]
|
|
166
357
|
}
|
|
167
358
|
},
|
|
359
|
+
{
|
|
360
|
+
name: "share_plan",
|
|
361
|
+
description: "Share a plan by making it public or private. Public plans can be viewed by anyone with the link.",
|
|
362
|
+
inputSchema: {
|
|
363
|
+
type: "object",
|
|
364
|
+
properties: {
|
|
365
|
+
plan_id: { type: "string", description: "Plan ID to share" },
|
|
366
|
+
visibility: {
|
|
367
|
+
type: "string",
|
|
368
|
+
description: "Plan visibility setting",
|
|
369
|
+
enum: ["public", "private"],
|
|
370
|
+
default: "public"
|
|
371
|
+
},
|
|
372
|
+
github_repo_owner: {
|
|
373
|
+
type: "string",
|
|
374
|
+
description: "GitHub repository owner (optional, for linking public plans to a repo)"
|
|
375
|
+
},
|
|
376
|
+
github_repo_name: {
|
|
377
|
+
type: "string",
|
|
378
|
+
description: "GitHub repository name (optional, for linking public plans to a repo)"
|
|
379
|
+
}
|
|
380
|
+
},
|
|
381
|
+
required: ["plan_id"]
|
|
382
|
+
}
|
|
383
|
+
},
|
|
168
384
|
|
|
169
385
|
// ===== NODE MANAGEMENT TOOLS =====
|
|
170
386
|
{
|
|
@@ -185,14 +401,20 @@ function setupTools(server) {
|
|
|
185
401
|
status: {
|
|
186
402
|
type: "string",
|
|
187
403
|
description: "Node status",
|
|
188
|
-
enum: ["not_started", "in_progress", "completed", "blocked"],
|
|
404
|
+
enum: ["not_started", "in_progress", "completed", "blocked", "plan_ready"],
|
|
189
405
|
default: "not_started"
|
|
190
406
|
},
|
|
191
407
|
context: { type: "string", description: "Additional context for the node" },
|
|
192
408
|
agent_instructions: { type: "string", description: "Instructions for AI agents working on this node" },
|
|
193
409
|
acceptance_criteria: { type: "string", description: "Criteria for node completion" },
|
|
194
410
|
due_date: { type: "string", description: "Due date (ISO format)" },
|
|
195
|
-
metadata: { type: "object", description: "Additional metadata" }
|
|
411
|
+
metadata: { type: "object", description: "Additional metadata" },
|
|
412
|
+
task_mode: {
|
|
413
|
+
type: "string",
|
|
414
|
+
description: "RPI workflow mode for the node",
|
|
415
|
+
enum: ["research", "plan", "implement", "free"],
|
|
416
|
+
default: "free"
|
|
417
|
+
}
|
|
196
418
|
},
|
|
197
419
|
required: ["plan_id", "node_type", "title"]
|
|
198
420
|
}
|
|
@@ -210,13 +432,18 @@ function setupTools(server) {
|
|
|
210
432
|
status: {
|
|
211
433
|
type: "string",
|
|
212
434
|
description: "New node status",
|
|
213
|
-
enum: ["not_started", "in_progress", "completed", "blocked"]
|
|
435
|
+
enum: ["not_started", "in_progress", "completed", "blocked", "plan_ready"]
|
|
214
436
|
},
|
|
215
437
|
context: { type: "string", description: "New context" },
|
|
216
438
|
agent_instructions: { type: "string", description: "New agent instructions" },
|
|
217
439
|
acceptance_criteria: { type: "string", description: "New acceptance criteria" },
|
|
218
440
|
due_date: { type: "string", description: "New due date (ISO format)" },
|
|
219
|
-
metadata: { type: "object", description: "New metadata" }
|
|
441
|
+
metadata: { type: "object", description: "New metadata" },
|
|
442
|
+
task_mode: {
|
|
443
|
+
type: "string",
|
|
444
|
+
description: "RPI workflow mode for the node",
|
|
445
|
+
enum: ["research", "plan", "implement", "free"]
|
|
446
|
+
}
|
|
220
447
|
},
|
|
221
448
|
required: ["plan_id", "node_id"]
|
|
222
449
|
}
|
|
@@ -249,7 +476,7 @@ function setupTools(server) {
|
|
|
249
476
|
},
|
|
250
477
|
{
|
|
251
478
|
name: "get_node_context",
|
|
252
|
-
description: "Get comprehensive context for a node including children
|
|
479
|
+
description: "Get comprehensive context for a node including children and logs",
|
|
253
480
|
inputSchema: {
|
|
254
481
|
type: "object",
|
|
255
482
|
properties: {
|
|
@@ -272,6 +499,168 @@ function setupTools(server) {
|
|
|
272
499
|
}
|
|
273
500
|
},
|
|
274
501
|
|
|
502
|
+
// ===== DEPENDENCY TOOLS =====
|
|
503
|
+
{
|
|
504
|
+
name: "create_dependency",
|
|
505
|
+
description: "Create a dependency edge between two nodes in a plan. Source 'blocks' target by default.",
|
|
506
|
+
inputSchema: {
|
|
507
|
+
type: "object",
|
|
508
|
+
properties: {
|
|
509
|
+
plan_id: { type: "string", description: "Plan ID" },
|
|
510
|
+
source_node_id: { type: "string", description: "Source node ID (the blocker)" },
|
|
511
|
+
target_node_id: { type: "string", description: "Target node ID (the blocked)" },
|
|
512
|
+
dependency_type: {
|
|
513
|
+
type: "string",
|
|
514
|
+
description: "Type of dependency",
|
|
515
|
+
enum: ["blocks", "requires", "relates_to"],
|
|
516
|
+
default: "blocks"
|
|
517
|
+
},
|
|
518
|
+
weight: { type: "integer", description: "Edge weight (default 1)", default: 1 },
|
|
519
|
+
metadata: { type: "object", description: "Additional metadata" }
|
|
520
|
+
},
|
|
521
|
+
required: ["plan_id", "source_node_id", "target_node_id"]
|
|
522
|
+
}
|
|
523
|
+
},
|
|
524
|
+
{
|
|
525
|
+
name: "delete_dependency",
|
|
526
|
+
description: "Delete a dependency edge",
|
|
527
|
+
inputSchema: {
|
|
528
|
+
type: "object",
|
|
529
|
+
properties: {
|
|
530
|
+
plan_id: { type: "string", description: "Plan ID" },
|
|
531
|
+
dependency_id: { type: "string", description: "Dependency edge ID" }
|
|
532
|
+
},
|
|
533
|
+
required: ["plan_id", "dependency_id"]
|
|
534
|
+
}
|
|
535
|
+
},
|
|
536
|
+
{
|
|
537
|
+
name: "list_dependencies",
|
|
538
|
+
description: "List all dependency edges in a plan",
|
|
539
|
+
inputSchema: {
|
|
540
|
+
type: "object",
|
|
541
|
+
properties: {
|
|
542
|
+
plan_id: { type: "string", description: "Plan ID" }
|
|
543
|
+
},
|
|
544
|
+
required: ["plan_id"]
|
|
545
|
+
}
|
|
546
|
+
},
|
|
547
|
+
{
|
|
548
|
+
name: "get_node_dependencies",
|
|
549
|
+
description: "Get upstream and downstream dependencies for a node",
|
|
550
|
+
inputSchema: {
|
|
551
|
+
type: "object",
|
|
552
|
+
properties: {
|
|
553
|
+
plan_id: { type: "string", description: "Plan ID" },
|
|
554
|
+
node_id: { type: "string", description: "Node ID" },
|
|
555
|
+
direction: {
|
|
556
|
+
type: "string",
|
|
557
|
+
description: "Direction to query",
|
|
558
|
+
enum: ["upstream", "downstream", "both"],
|
|
559
|
+
default: "both"
|
|
560
|
+
}
|
|
561
|
+
},
|
|
562
|
+
required: ["plan_id", "node_id"]
|
|
563
|
+
}
|
|
564
|
+
},
|
|
565
|
+
|
|
566
|
+
// ===== RPI WORKFLOW =====
|
|
567
|
+
{
|
|
568
|
+
name: "create_rpi_chain",
|
|
569
|
+
description: "Create a Research→Plan→Implement task chain with automatic dependency edges. The three tasks are linked: Research blocks Plan, Plan blocks Implement.",
|
|
570
|
+
inputSchema: {
|
|
571
|
+
type: "object",
|
|
572
|
+
properties: {
|
|
573
|
+
plan_id: { type: "string", description: "Plan ID" },
|
|
574
|
+
title: { type: "string", description: "Base title for the chain (e.g. 'Auth refactor')" },
|
|
575
|
+
description: { type: "string", description: "Description for the research task" },
|
|
576
|
+
parent_id: { type: "string", description: "Parent node ID (optional, defaults to root)" }
|
|
577
|
+
},
|
|
578
|
+
required: ["plan_id", "title"]
|
|
579
|
+
}
|
|
580
|
+
},
|
|
581
|
+
|
|
582
|
+
// ===== ANALYSIS TOOLS =====
|
|
583
|
+
{
|
|
584
|
+
name: "analyze_impact",
|
|
585
|
+
description: "Analyze what happens if a node is delayed, blocked, or removed. Shows directly and transitively affected nodes.",
|
|
586
|
+
inputSchema: {
|
|
587
|
+
type: "object",
|
|
588
|
+
properties: {
|
|
589
|
+
plan_id: { type: "string", description: "Plan ID" },
|
|
590
|
+
node_id: { type: "string", description: "Node ID to analyze" },
|
|
591
|
+
scenario: {
|
|
592
|
+
type: "string",
|
|
593
|
+
description: "Impact scenario",
|
|
594
|
+
enum: ["delay", "block", "remove"],
|
|
595
|
+
default: "block"
|
|
596
|
+
}
|
|
597
|
+
},
|
|
598
|
+
required: ["plan_id", "node_id"]
|
|
599
|
+
}
|
|
600
|
+
},
|
|
601
|
+
{
|
|
602
|
+
name: "get_critical_path",
|
|
603
|
+
description: "Find the critical path (longest dependency chain) through incomplete tasks in a plan",
|
|
604
|
+
inputSchema: {
|
|
605
|
+
type: "object",
|
|
606
|
+
properties: {
|
|
607
|
+
plan_id: { type: "string", description: "Plan ID" }
|
|
608
|
+
},
|
|
609
|
+
required: ["plan_id"]
|
|
610
|
+
}
|
|
611
|
+
},
|
|
612
|
+
|
|
613
|
+
// ===== PROGRESSIVE CONTEXT TOOLS =====
|
|
614
|
+
{
|
|
615
|
+
name: "get_task_context",
|
|
616
|
+
description: "Get progressive context for a task at adjustable depth. This is the PRIMARY way to load context before starting work on a task.\n\nDepth levels:\n- 1: Task focus — node details + recent logs\n- 2: Local neighborhood — adds parent, siblings, direct dependencies\n- 3: Knowledge — adds plan-scoped knowledge entries\n- 4: Extended — adds plan overview, ancestry, goals, transitive dependencies\n\nFor RPI implement tasks, automatically includes research/plan outputs from the chain.",
|
|
617
|
+
inputSchema: {
|
|
618
|
+
type: "object",
|
|
619
|
+
properties: {
|
|
620
|
+
node_id: { type: "string", description: "Task/node ID to get context for" },
|
|
621
|
+
depth: {
|
|
622
|
+
type: "integer",
|
|
623
|
+
description: "Context depth 1-4 (default 2). Start with 2, go deeper if needed.",
|
|
624
|
+
minimum: 1,
|
|
625
|
+
maximum: 4,
|
|
626
|
+
default: 2
|
|
627
|
+
},
|
|
628
|
+
token_budget: {
|
|
629
|
+
type: "integer",
|
|
630
|
+
description: "Max estimated tokens (0 = unlimited). Use to stay within context window limits.",
|
|
631
|
+
default: 0
|
|
632
|
+
},
|
|
633
|
+
log_limit: {
|
|
634
|
+
type: "integer",
|
|
635
|
+
description: "Max recent logs to include per node",
|
|
636
|
+
default: 10
|
|
637
|
+
},
|
|
638
|
+
include_research: {
|
|
639
|
+
type: "boolean",
|
|
640
|
+
description: "Include research outputs from RPI chain siblings (for implement tasks)",
|
|
641
|
+
default: true
|
|
642
|
+
}
|
|
643
|
+
},
|
|
644
|
+
required: ["node_id"]
|
|
645
|
+
}
|
|
646
|
+
},
|
|
647
|
+
{
|
|
648
|
+
name: "suggest_next_tasks",
|
|
649
|
+
description: "Suggest the next actionable tasks for a plan based on dependency analysis. Returns tasks where all upstream blockers are completed, prioritized by: RPI research tasks first, then by how many downstream tasks each unblocks.",
|
|
650
|
+
inputSchema: {
|
|
651
|
+
type: "object",
|
|
652
|
+
properties: {
|
|
653
|
+
plan_id: { type: "string", description: "Plan ID" },
|
|
654
|
+
limit: {
|
|
655
|
+
type: "integer",
|
|
656
|
+
description: "Maximum suggestions to return",
|
|
657
|
+
default: 5
|
|
658
|
+
}
|
|
659
|
+
},
|
|
660
|
+
required: ["plan_id"]
|
|
661
|
+
}
|
|
662
|
+
},
|
|
663
|
+
|
|
275
664
|
// ===== LOGGING TOOLS (Replaces Comments) =====
|
|
276
665
|
{
|
|
277
666
|
name: "add_log",
|
|
@@ -320,30 +709,6 @@ function setupTools(server) {
|
|
|
320
709
|
}
|
|
321
710
|
},
|
|
322
711
|
|
|
323
|
-
// ===== ARTIFACT MANAGEMENT =====
|
|
324
|
-
{
|
|
325
|
-
name: "manage_artifact",
|
|
326
|
-
description: "Add, get, or search for artifacts",
|
|
327
|
-
inputSchema: {
|
|
328
|
-
type: "object",
|
|
329
|
-
properties: {
|
|
330
|
-
action: {
|
|
331
|
-
type: "string",
|
|
332
|
-
description: "Action to perform",
|
|
333
|
-
enum: ["add", "get", "search", "list"]
|
|
334
|
-
},
|
|
335
|
-
plan_id: { type: "string", description: "Plan ID" },
|
|
336
|
-
node_id: { type: "string", description: "Node ID" },
|
|
337
|
-
artifact_id: { type: "string", description: "Artifact ID (for 'get' action)" },
|
|
338
|
-
name: { type: "string", description: "Artifact name (for 'add' or 'search')" },
|
|
339
|
-
content_type: { type: "string", description: "Content MIME type (for 'add')" },
|
|
340
|
-
url: { type: "string", description: "URL where artifact can be accessed (for 'add')" },
|
|
341
|
-
metadata: { type: "object", description: "Additional metadata (for 'add')" }
|
|
342
|
-
},
|
|
343
|
-
required: ["action", "plan_id", "node_id"]
|
|
344
|
-
}
|
|
345
|
-
},
|
|
346
|
-
|
|
347
712
|
// ===== BATCH OPERATIONS =====
|
|
348
713
|
{
|
|
349
714
|
name: "batch_update_nodes",
|
|
@@ -361,7 +726,7 @@ function setupTools(server) {
|
|
|
361
726
|
node_id: { type: "string", description: "Node ID" },
|
|
362
727
|
status: {
|
|
363
728
|
type: "string",
|
|
364
|
-
enum: ["not_started", "in_progress", "completed", "blocked"]
|
|
729
|
+
enum: ["not_started", "in_progress", "completed", "blocked", "plan_ready"]
|
|
365
730
|
},
|
|
366
731
|
title: { type: "string" },
|
|
367
732
|
description: { type: "string" }
|
|
@@ -373,41 +738,18 @@ function setupTools(server) {
|
|
|
373
738
|
required: ["plan_id", "updates"]
|
|
374
739
|
}
|
|
375
740
|
},
|
|
376
|
-
{
|
|
377
|
-
name: "batch_get_artifacts",
|
|
378
|
-
description: "Get multiple artifacts at once",
|
|
379
|
-
inputSchema: {
|
|
380
|
-
type: "object",
|
|
381
|
-
properties: {
|
|
382
|
-
plan_id: { type: "string", description: "Plan ID" },
|
|
383
|
-
artifact_requests: {
|
|
384
|
-
type: "array",
|
|
385
|
-
description: "List of artifact requests",
|
|
386
|
-
items: {
|
|
387
|
-
type: "object",
|
|
388
|
-
properties: {
|
|
389
|
-
node_id: { type: "string", description: "Node ID" },
|
|
390
|
-
artifact_id: { type: "string", description: "Artifact ID" }
|
|
391
|
-
},
|
|
392
|
-
required: ["node_id", "artifact_id"]
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
},
|
|
396
|
-
required: ["plan_id", "artifact_requests"]
|
|
397
|
-
}
|
|
398
|
-
},
|
|
399
741
|
|
|
400
742
|
// ===== PLAN STRUCTURE & SUMMARY =====
|
|
401
743
|
{
|
|
402
744
|
name: "get_plan_structure",
|
|
403
|
-
description: "Get the
|
|
745
|
+
description: "Get the hierarchical structure of a plan with minimal fields (id, parent_id, node_type, title, status, order_index). Use get_node_context for detailed information about specific nodes.",
|
|
404
746
|
inputSchema: {
|
|
405
747
|
type: "object",
|
|
406
748
|
properties: {
|
|
407
749
|
plan_id: { type: "string", description: "Plan ID" },
|
|
408
|
-
include_details: {
|
|
409
|
-
type: "boolean",
|
|
410
|
-
description: "Include full node details",
|
|
750
|
+
include_details: {
|
|
751
|
+
type: "boolean",
|
|
752
|
+
description: "Include full node details (description, context, agent_instructions, etc.). Default is false for efficient context usage.",
|
|
411
753
|
default: false
|
|
412
754
|
}
|
|
413
755
|
},
|
|
@@ -424,13 +766,405 @@ function setupTools(server) {
|
|
|
424
766
|
},
|
|
425
767
|
required: ["plan_id"]
|
|
426
768
|
}
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
769
|
+
},
|
|
770
|
+
|
|
771
|
+
// ===== AGENT CONTEXT TOOLS (Leaf-up context loading) =====
|
|
772
|
+
{
|
|
773
|
+
name: "get_agent_context",
|
|
774
|
+
description: "Get focused context for a specific task/node. Uses leaf-up traversal - returns only the relevant path from the node to root, not the entire plan tree. Best for agents starting work on a specific task.",
|
|
775
|
+
inputSchema: {
|
|
776
|
+
type: "object",
|
|
777
|
+
properties: {
|
|
778
|
+
node_id: {
|
|
779
|
+
type: "string",
|
|
780
|
+
description: "Task or phase node ID to get context for"
|
|
781
|
+
},
|
|
782
|
+
include_knowledge: {
|
|
783
|
+
type: "boolean",
|
|
784
|
+
description: "Include knowledge entries from relevant scopes (plan, goals, org)",
|
|
785
|
+
default: true
|
|
786
|
+
},
|
|
787
|
+
include_siblings: {
|
|
788
|
+
type: "boolean",
|
|
789
|
+
description: "Include sibling tasks in the same phase",
|
|
790
|
+
default: false
|
|
791
|
+
}
|
|
792
|
+
},
|
|
793
|
+
required: ["node_id"]
|
|
794
|
+
}
|
|
795
|
+
},
|
|
796
|
+
{
|
|
797
|
+
name: "get_plan_context",
|
|
798
|
+
description: "Get plan-level context overview. Returns plan details, phase summaries (not full tree), linked goals, and organization. Use get_agent_context for task-focused work.",
|
|
799
|
+
inputSchema: {
|
|
800
|
+
type: "object",
|
|
801
|
+
properties: {
|
|
802
|
+
plan_id: {
|
|
803
|
+
type: "string",
|
|
804
|
+
description: "Plan ID"
|
|
805
|
+
},
|
|
806
|
+
include_knowledge: {
|
|
807
|
+
type: "boolean",
|
|
808
|
+
description: "Include knowledge entries",
|
|
809
|
+
default: true
|
|
810
|
+
}
|
|
811
|
+
},
|
|
812
|
+
required: ["plan_id"]
|
|
813
|
+
}
|
|
814
|
+
},
|
|
815
|
+
|
|
816
|
+
// ===== ORGANIZATION TOOLS =====
|
|
817
|
+
{
|
|
818
|
+
name: "list_organizations",
|
|
819
|
+
description: "List all organizations the user is a member of",
|
|
820
|
+
inputSchema: {
|
|
821
|
+
type: "object",
|
|
822
|
+
properties: {}
|
|
823
|
+
}
|
|
824
|
+
},
|
|
825
|
+
{
|
|
826
|
+
name: "get_organization",
|
|
827
|
+
description: "Get organization details including member count and plan count",
|
|
828
|
+
inputSchema: {
|
|
829
|
+
type: "object",
|
|
830
|
+
properties: {
|
|
831
|
+
organization_id: { type: "string", description: "Organization ID" }
|
|
832
|
+
},
|
|
833
|
+
required: ["organization_id"]
|
|
834
|
+
}
|
|
835
|
+
},
|
|
836
|
+
{
|
|
837
|
+
name: "create_organization",
|
|
838
|
+
description: "Create a new organization. You become the owner.",
|
|
839
|
+
inputSchema: {
|
|
840
|
+
type: "object",
|
|
841
|
+
properties: {
|
|
842
|
+
name: { type: "string", description: "Organization name" },
|
|
843
|
+
description: { type: "string", description: "Organization description" },
|
|
844
|
+
slug: { type: "string", description: "URL-friendly slug (auto-generated if not provided)" }
|
|
845
|
+
},
|
|
846
|
+
required: ["name"]
|
|
847
|
+
}
|
|
848
|
+
},
|
|
849
|
+
{
|
|
850
|
+
name: "update_organization",
|
|
851
|
+
description: "Update organization details (owner only)",
|
|
852
|
+
inputSchema: {
|
|
853
|
+
type: "object",
|
|
854
|
+
properties: {
|
|
855
|
+
organization_id: { type: "string", description: "Organization ID" },
|
|
856
|
+
name: { type: "string", description: "New name" },
|
|
857
|
+
description: { type: "string", description: "New description" }
|
|
858
|
+
},
|
|
859
|
+
required: ["organization_id"]
|
|
860
|
+
}
|
|
861
|
+
},
|
|
862
|
+
|
|
863
|
+
// ===== GOAL TOOLS =====
|
|
864
|
+
{
|
|
865
|
+
name: "list_goals",
|
|
866
|
+
description: "List goals, optionally filtered by organization or status",
|
|
867
|
+
inputSchema: {
|
|
868
|
+
type: "object",
|
|
869
|
+
properties: {
|
|
870
|
+
organization_id: { type: "string", description: "Filter by organization ID" },
|
|
871
|
+
status: {
|
|
872
|
+
type: "string",
|
|
873
|
+
description: "Filter by status",
|
|
874
|
+
enum: ["active", "achieved", "at_risk", "abandoned"]
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
},
|
|
879
|
+
{
|
|
880
|
+
name: "get_goal",
|
|
881
|
+
description: "Get goal details including linked plans",
|
|
882
|
+
inputSchema: {
|
|
883
|
+
type: "object",
|
|
884
|
+
properties: {
|
|
885
|
+
goal_id: { type: "string", description: "Goal ID" }
|
|
886
|
+
},
|
|
887
|
+
required: ["goal_id"]
|
|
888
|
+
}
|
|
889
|
+
},
|
|
890
|
+
{
|
|
891
|
+
name: "create_goal",
|
|
892
|
+
description: "Create a new goal within an organization",
|
|
893
|
+
inputSchema: {
|
|
894
|
+
type: "object",
|
|
895
|
+
properties: {
|
|
896
|
+
organization_id: { type: "string", description: "Organization ID" },
|
|
897
|
+
title: { type: "string", description: "Goal title" },
|
|
898
|
+
description: { type: "string", description: "Goal description" },
|
|
899
|
+
success_metrics: {
|
|
900
|
+
type: "array",
|
|
901
|
+
description: "Success metrics array [{metric, target, current, unit}]",
|
|
902
|
+
items: {
|
|
903
|
+
type: "object",
|
|
904
|
+
properties: {
|
|
905
|
+
metric: { type: "string" },
|
|
906
|
+
target: { type: "number" },
|
|
907
|
+
current: { type: "number" },
|
|
908
|
+
unit: { type: "string" }
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
},
|
|
912
|
+
time_horizon: { type: "string", description: "Target date (ISO format)" },
|
|
913
|
+
github_repo_url: { type: "string", description: "Related GitHub repo URL" }
|
|
914
|
+
},
|
|
915
|
+
required: ["organization_id", "title"]
|
|
916
|
+
}
|
|
917
|
+
},
|
|
918
|
+
{
|
|
919
|
+
name: "update_goal",
|
|
920
|
+
description: "Update goal details or status",
|
|
921
|
+
inputSchema: {
|
|
922
|
+
type: "object",
|
|
923
|
+
properties: {
|
|
924
|
+
goal_id: { type: "string", description: "Goal ID" },
|
|
925
|
+
title: { type: "string", description: "New title" },
|
|
926
|
+
description: { type: "string", description: "New description" },
|
|
927
|
+
status: {
|
|
928
|
+
type: "string",
|
|
929
|
+
description: "New status",
|
|
930
|
+
enum: ["active", "achieved", "at_risk", "abandoned"]
|
|
931
|
+
},
|
|
932
|
+
success_metrics: { type: "array", description: "Updated metrics" },
|
|
933
|
+
time_horizon: { type: "string", description: "New target date" }
|
|
934
|
+
},
|
|
935
|
+
required: ["goal_id"]
|
|
936
|
+
}
|
|
937
|
+
},
|
|
938
|
+
{
|
|
939
|
+
name: "link_plan_to_goal",
|
|
940
|
+
description: "Link a plan to a goal (shows the plan contributes to this goal)",
|
|
941
|
+
inputSchema: {
|
|
942
|
+
type: "object",
|
|
943
|
+
properties: {
|
|
944
|
+
goal_id: { type: "string", description: "Goal ID" },
|
|
945
|
+
plan_id: { type: "string", description: "Plan ID to link" }
|
|
946
|
+
},
|
|
947
|
+
required: ["goal_id", "plan_id"]
|
|
948
|
+
}
|
|
949
|
+
},
|
|
950
|
+
{
|
|
951
|
+
name: "unlink_plan_from_goal",
|
|
952
|
+
description: "Remove a plan-goal link",
|
|
953
|
+
inputSchema: {
|
|
954
|
+
type: "object",
|
|
955
|
+
properties: {
|
|
956
|
+
goal_id: { type: "string", description: "Goal ID" },
|
|
957
|
+
plan_id: { type: "string", description: "Plan ID to unlink" }
|
|
958
|
+
},
|
|
959
|
+
required: ["goal_id", "plan_id"]
|
|
960
|
+
}
|
|
961
|
+
},
|
|
962
|
+
|
|
963
|
+
// ===== CROSS-PLAN & EXTERNAL DEPENDENCY TOOLS =====
|
|
964
|
+
{
|
|
965
|
+
name: "create_cross_plan_dependency",
|
|
966
|
+
description: "Create a dependency edge between nodes in different plans. Use when a task in one plan blocks or requires a task in another plan.",
|
|
967
|
+
inputSchema: {
|
|
968
|
+
type: "object",
|
|
969
|
+
properties: {
|
|
970
|
+
source_node_id: { type: "string", description: "Source node ID (the blocker/prerequisite)" },
|
|
971
|
+
target_node_id: { type: "string", description: "Target node ID (the blocked/dependent task)" },
|
|
972
|
+
dependency_type: { type: "string", enum: ["blocks", "requires", "relates_to"], default: "blocks", description: "Edge type (default: blocks)" },
|
|
973
|
+
weight: { type: "number", description: "Edge weight (default 1)" }
|
|
974
|
+
},
|
|
975
|
+
required: ["source_node_id", "target_node_id"]
|
|
976
|
+
}
|
|
977
|
+
},
|
|
978
|
+
{
|
|
979
|
+
name: "list_cross_plan_dependencies",
|
|
980
|
+
description: "List all dependency edges that cross plan boundaries between specified plans.",
|
|
981
|
+
inputSchema: {
|
|
982
|
+
type: "object",
|
|
983
|
+
properties: {
|
|
984
|
+
plan_ids: {
|
|
985
|
+
type: "array",
|
|
986
|
+
items: { type: "string" },
|
|
987
|
+
description: "Plan IDs to check for cross-plan edges (at least 2)"
|
|
988
|
+
}
|
|
989
|
+
},
|
|
990
|
+
required: ["plan_ids"]
|
|
991
|
+
}
|
|
992
|
+
},
|
|
993
|
+
{
|
|
994
|
+
name: "create_external_dependency",
|
|
995
|
+
description: "Create an external dependency node representing a blocker outside the system (vendor API, legal approval, etc.). Optionally blocks a target task.",
|
|
996
|
+
inputSchema: {
|
|
997
|
+
type: "object",
|
|
998
|
+
properties: {
|
|
999
|
+
plan_id: { type: "string", description: "Plan to add the external dependency to" },
|
|
1000
|
+
title: { type: "string", description: "External dependency title (e.g., 'Waiting for vendor API access')" },
|
|
1001
|
+
description: { type: "string", description: "Details about the external dependency" },
|
|
1002
|
+
url: { type: "string", description: "URL reference (ticket, docs, etc.)" },
|
|
1003
|
+
blocks_node_id: { type: "string", description: "Node ID that this external dep blocks" }
|
|
1004
|
+
},
|
|
1005
|
+
required: ["plan_id", "title"]
|
|
1006
|
+
}
|
|
1007
|
+
},
|
|
1008
|
+
|
|
1009
|
+
// ===== GOAL-DEPENDENCY TOOLS =====
|
|
1010
|
+
{
|
|
1011
|
+
name: "goal_path",
|
|
1012
|
+
description: "Get the full dependency path to a goal — all tasks that contribute to achieving it (direct achievers + their upstream blockers). Shows completion stats and which tasks are blocking progress.",
|
|
1013
|
+
inputSchema: {
|
|
1014
|
+
type: "object",
|
|
1015
|
+
properties: {
|
|
1016
|
+
goal_id: { type: "string", description: "Goal ID" },
|
|
1017
|
+
max_depth: { type: "number", description: "Max traversal depth (default 20)" }
|
|
1018
|
+
},
|
|
1019
|
+
required: ["goal_id"]
|
|
1020
|
+
}
|
|
1021
|
+
},
|
|
1022
|
+
{
|
|
1023
|
+
name: "goal_progress",
|
|
1024
|
+
description: "Get goal progress calculated from its dependency graph. Returns overall completion percentage and direct achiever progress.",
|
|
1025
|
+
inputSchema: {
|
|
1026
|
+
type: "object",
|
|
1027
|
+
properties: {
|
|
1028
|
+
goal_id: { type: "string", description: "Goal ID" }
|
|
1029
|
+
},
|
|
1030
|
+
required: ["goal_id"]
|
|
1031
|
+
}
|
|
1032
|
+
},
|
|
1033
|
+
{
|
|
1034
|
+
name: "add_achiever",
|
|
1035
|
+
description: "Link a task to a goal via an 'achieves' dependency edge. This declares that completing this task contributes to achieving the goal.",
|
|
1036
|
+
inputSchema: {
|
|
1037
|
+
type: "object",
|
|
1038
|
+
properties: {
|
|
1039
|
+
goal_id: { type: "string", description: "Goal ID" },
|
|
1040
|
+
node_id: { type: "string", description: "Task/node ID that achieves this goal" },
|
|
1041
|
+
weight: { type: "number", description: "Edge weight for critical path (default 1)" }
|
|
1042
|
+
},
|
|
1043
|
+
required: ["goal_id", "node_id"]
|
|
1044
|
+
}
|
|
1045
|
+
},
|
|
1046
|
+
{
|
|
1047
|
+
name: "remove_achiever",
|
|
1048
|
+
description: "Remove an achieves edge between a task and a goal",
|
|
1049
|
+
inputSchema: {
|
|
1050
|
+
type: "object",
|
|
1051
|
+
properties: {
|
|
1052
|
+
goal_id: { type: "string", description: "Goal ID" },
|
|
1053
|
+
dependency_id: { type: "string", description: "Dependency edge ID to remove" }
|
|
1054
|
+
},
|
|
1055
|
+
required: ["goal_id", "dependency_id"]
|
|
1056
|
+
}
|
|
1057
|
+
},
|
|
1058
|
+
{
|
|
1059
|
+
name: "goal_knowledge_gaps",
|
|
1060
|
+
description: "Detect knowledge gaps for a goal — checks which tasks on the goal's dependency path lack relevant knowledge in the temporal knowledge graph. Useful for identifying where research is needed before implementation.",
|
|
1061
|
+
inputSchema: {
|
|
1062
|
+
type: "object",
|
|
1063
|
+
properties: {
|
|
1064
|
+
goal_id: { type: "string", description: "Goal ID" }
|
|
1065
|
+
},
|
|
1066
|
+
required: ["goal_id"]
|
|
1067
|
+
}
|
|
1068
|
+
},
|
|
1069
|
+
|
|
1070
|
+
// ===== GRAPHITI KNOWLEDGE GRAPH TOOLS =====
|
|
1071
|
+
{
|
|
1072
|
+
name: "add_learning",
|
|
1073
|
+
description: "Record a knowledge episode to the temporal knowledge graph. Use this after research, when making decisions, or discovering important context. Graphiti automatically extracts entities and relationships. The knowledge persists across plans and sessions.",
|
|
1074
|
+
inputSchema: {
|
|
1075
|
+
type: "object",
|
|
1076
|
+
properties: {
|
|
1077
|
+
content: { type: "string", description: "The knowledge content — be detailed. Include context, reasoning, and conclusions." },
|
|
1078
|
+
title: { type: "string", description: "Short title/name for the episode" },
|
|
1079
|
+
entry_type: { type: "string", enum: ["decision", "learning", "context", "constraint"], description: "Type of knowledge" },
|
|
1080
|
+
plan_id: { type: "string", description: "Plan ID this knowledge relates to (optional)" },
|
|
1081
|
+
node_id: { type: "string", description: "Node/task ID this knowledge relates to (optional)" }
|
|
1082
|
+
},
|
|
1083
|
+
required: ["content"]
|
|
1084
|
+
}
|
|
1085
|
+
},
|
|
1086
|
+
{
|
|
1087
|
+
name: "recall_knowledge",
|
|
1088
|
+
description: "Search the temporal knowledge graph for relevant facts, decisions, and learnings. Searches across ALL plans in the organization. Use before starting work or making decisions.",
|
|
1089
|
+
inputSchema: {
|
|
1090
|
+
type: "object",
|
|
1091
|
+
properties: {
|
|
1092
|
+
query: { type: "string", description: "What to search for — be specific" },
|
|
1093
|
+
max_results: { type: "number", description: "Maximum results (default 10)", default: 10 }
|
|
1094
|
+
},
|
|
1095
|
+
required: ["query"]
|
|
1096
|
+
}
|
|
1097
|
+
},
|
|
1098
|
+
{
|
|
1099
|
+
name: "find_entities",
|
|
1100
|
+
description: "Search for entities (technologies, people, patterns, constraints) in the knowledge graph. Returns entity nodes with their relationships.",
|
|
1101
|
+
inputSchema: {
|
|
1102
|
+
type: "object",
|
|
1103
|
+
properties: {
|
|
1104
|
+
query: { type: "string", description: "Entity search query" },
|
|
1105
|
+
max_results: { type: "number", description: "Maximum results (default 10)", default: 10 }
|
|
1106
|
+
},
|
|
1107
|
+
required: ["query"]
|
|
1108
|
+
}
|
|
1109
|
+
},
|
|
1110
|
+
|
|
1111
|
+
{
|
|
1112
|
+
name: "check_contradictions",
|
|
1113
|
+
description: "Check if knowledge about a topic has changed over time. Returns current facts and any superseded (outdated) facts. Useful before making decisions based on past knowledge — ensures you're working with the latest information.",
|
|
1114
|
+
inputSchema: {
|
|
1115
|
+
type: "object",
|
|
1116
|
+
properties: {
|
|
1117
|
+
query: { type: "string", description: "Topic to check for contradictions" },
|
|
1118
|
+
max_results: { type: "number", description: "Maximum results (default 10)", default: 10 }
|
|
1119
|
+
},
|
|
1120
|
+
required: ["query"]
|
|
1121
|
+
}
|
|
1122
|
+
},
|
|
1123
|
+
{
|
|
1124
|
+
name: "get_recent_episodes",
|
|
1125
|
+
description: "Get recent knowledge episodes from the temporal graph. Returns the latest episodes (learnings, decisions, context) across all plans. Useful to understand what has been learned recently or to review your own work session history.",
|
|
1126
|
+
inputSchema: {
|
|
1127
|
+
type: "object",
|
|
1128
|
+
properties: {
|
|
1129
|
+
max_episodes: { type: "number", description: "Maximum episodes to return (default 20)", default: 20 }
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
},
|
|
1133
|
+
|
|
1134
|
+
// ===== HELPER / GUIDANCE TOOLS =====
|
|
1135
|
+
{
|
|
1136
|
+
name: "get_started",
|
|
1137
|
+
description: "Get guidance on how to use AgentPlanner effectively. Returns an overview of the system and recommended workflows for common tasks. Call this when you're new to AgentPlanner or need to understand how to approach a task.",
|
|
1138
|
+
inputSchema: {
|
|
1139
|
+
type: "object",
|
|
1140
|
+
properties: {
|
|
1141
|
+
topic: {
|
|
1142
|
+
type: "string",
|
|
1143
|
+
enum: ["overview", "planning", "execution", "knowledge", "collaboration"],
|
|
1144
|
+
description: "Specific topic to learn about: 'overview' (system intro), 'planning' (creating plans), 'execution' (working through tasks), 'knowledge' (storing decisions/learnings), 'collaboration' (working with others)",
|
|
1145
|
+
default: "overview"
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
},
|
|
1150
|
+
{
|
|
1151
|
+
name: "understand_context",
|
|
1152
|
+
description: "Get comprehensive context about a plan or goal - its purpose, current state, recent activity, blocked tasks, and relevant knowledge. Use this BEFORE starting work to understand the full situation.",
|
|
1153
|
+
inputSchema: {
|
|
1154
|
+
type: "object",
|
|
1155
|
+
properties: {
|
|
1156
|
+
plan_id: { type: "string", description: "Plan ID to understand" },
|
|
1157
|
+
goal_id: { type: "string", description: "Goal ID to understand" },
|
|
1158
|
+
include_knowledge: { type: "boolean", description: "Include relevant knowledge entries", default: true }
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
]
|
|
1163
|
+
};
|
|
1164
|
+
});
|
|
1165
|
+
|
|
1166
|
+
// Handler for calling tools
|
|
1167
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
434
1168
|
const { name, arguments: args } = request.params;
|
|
435
1169
|
|
|
436
1170
|
// Only log in development mode
|
|
@@ -439,6 +1173,550 @@ function setupTools(server) {
|
|
|
439
1173
|
}
|
|
440
1174
|
|
|
441
1175
|
try {
|
|
1176
|
+
// ========================================
|
|
1177
|
+
// QUICK ACTIONS IMPLEMENTATIONS
|
|
1178
|
+
// ========================================
|
|
1179
|
+
|
|
1180
|
+
if (name === "quick_plan") {
|
|
1181
|
+
const { title, description, tasks, goal_id, organization_id } = args;
|
|
1182
|
+
|
|
1183
|
+
if (!tasks || tasks.length === 0) {
|
|
1184
|
+
return formatResponse({
|
|
1185
|
+
error: "At least one task is required",
|
|
1186
|
+
suggestion: "Provide an array of task titles in the 'tasks' parameter"
|
|
1187
|
+
});
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
// Create the plan
|
|
1191
|
+
const planData = { title };
|
|
1192
|
+
if (description) planData.description = description;
|
|
1193
|
+
if (organization_id) planData.organization_id = organization_id;
|
|
1194
|
+
|
|
1195
|
+
const plan = await apiClient.plans.createPlan(planData);
|
|
1196
|
+
|
|
1197
|
+
// Get the root node
|
|
1198
|
+
const nodes = await apiClient.nodes.getNodes(plan.id);
|
|
1199
|
+
const rootNode = nodes.find(n => n.node_type === 'root') || nodes[0];
|
|
1200
|
+
|
|
1201
|
+
// Create a phase for the tasks
|
|
1202
|
+
const phase = await apiClient.nodes.createNode(plan.id, {
|
|
1203
|
+
parent_id: rootNode.id,
|
|
1204
|
+
node_type: 'phase',
|
|
1205
|
+
title: 'Tasks',
|
|
1206
|
+
status: 'not_started',
|
|
1207
|
+
order_index: 0
|
|
1208
|
+
});
|
|
1209
|
+
|
|
1210
|
+
// Create tasks
|
|
1211
|
+
const createdTasks = [];
|
|
1212
|
+
for (let i = 0; i < tasks.length; i++) {
|
|
1213
|
+
const task = await apiClient.nodes.createNode(plan.id, {
|
|
1214
|
+
parent_id: phase.id,
|
|
1215
|
+
node_type: 'task',
|
|
1216
|
+
title: tasks[i],
|
|
1217
|
+
status: 'not_started',
|
|
1218
|
+
order_index: i
|
|
1219
|
+
});
|
|
1220
|
+
createdTasks.push({ id: task.id, title: task.title });
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
// Link to goal if provided
|
|
1224
|
+
if (goal_id) {
|
|
1225
|
+
try {
|
|
1226
|
+
await apiClient.goals.linkPlan(goal_id, plan.id);
|
|
1227
|
+
} catch (e) {
|
|
1228
|
+
// Goal linking failed, continue anyway
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
return formatResponse({
|
|
1233
|
+
success: true,
|
|
1234
|
+
message: `Plan "${title}" created with ${tasks.length} tasks`,
|
|
1235
|
+
plan_id: plan.id,
|
|
1236
|
+
plan_url: buildPlanUrl(plan.id),
|
|
1237
|
+
phase_id: phase.id,
|
|
1238
|
+
task_ids: createdTasks.map(t => t.id),
|
|
1239
|
+
tasks: createdTasks,
|
|
1240
|
+
next_steps: [
|
|
1241
|
+
"Use quick_status to update task progress",
|
|
1242
|
+
"Use quick_log to document your work",
|
|
1243
|
+
"Use get_context to load full plan details"
|
|
1244
|
+
]
|
|
1245
|
+
});
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
if (name === "quick_task") {
|
|
1249
|
+
const { plan_id, title, description, phase_id, agent_instructions } = args;
|
|
1250
|
+
|
|
1251
|
+
// Get plan structure to find the phase
|
|
1252
|
+
const nodes = await apiClient.nodes.getNodes(plan_id);
|
|
1253
|
+
const flatNodes = flattenNodes(nodes);
|
|
1254
|
+
|
|
1255
|
+
// Find target phase
|
|
1256
|
+
let targetPhaseId = phase_id;
|
|
1257
|
+
if (!targetPhaseId) {
|
|
1258
|
+
// Find first phase
|
|
1259
|
+
const phases = flatNodes.filter(n => n.node_type === 'phase');
|
|
1260
|
+
if (phases.length > 0) {
|
|
1261
|
+
targetPhaseId = phases[0].id;
|
|
1262
|
+
} else {
|
|
1263
|
+
// No phase exists, create one
|
|
1264
|
+
const rootNode = flatNodes.find(n => n.node_type === 'root') || nodes[0];
|
|
1265
|
+
const newPhase = await apiClient.nodes.createNode(plan_id, {
|
|
1266
|
+
parent_id: rootNode.id,
|
|
1267
|
+
node_type: 'phase',
|
|
1268
|
+
title: 'Tasks',
|
|
1269
|
+
status: 'not_started',
|
|
1270
|
+
order_index: 0
|
|
1271
|
+
});
|
|
1272
|
+
targetPhaseId = newPhase.id;
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
// Get task count in phase for order_index
|
|
1277
|
+
const phaseTasks = flatNodes.filter(n => n.parent_id === targetPhaseId && n.node_type === 'task');
|
|
1278
|
+
|
|
1279
|
+
// Create the task
|
|
1280
|
+
const taskData = {
|
|
1281
|
+
parent_id: targetPhaseId,
|
|
1282
|
+
node_type: 'task',
|
|
1283
|
+
title,
|
|
1284
|
+
status: 'not_started',
|
|
1285
|
+
order_index: phaseTasks.length
|
|
1286
|
+
};
|
|
1287
|
+
if (description) taskData.description = description;
|
|
1288
|
+
if (agent_instructions) taskData.agent_instructions = agent_instructions;
|
|
1289
|
+
|
|
1290
|
+
const task = await apiClient.nodes.createNode(plan_id, taskData);
|
|
1291
|
+
|
|
1292
|
+
return formatResponse({
|
|
1293
|
+
success: true,
|
|
1294
|
+
message: `Task "${title}" added to plan`,
|
|
1295
|
+
task_id: task.id,
|
|
1296
|
+
plan_id: plan_id,
|
|
1297
|
+
phase_id: targetPhaseId,
|
|
1298
|
+
task_url: buildTaskUrl(plan_id, task.id),
|
|
1299
|
+
next_steps: [
|
|
1300
|
+
"Use quick_status to mark as in_progress when you start",
|
|
1301
|
+
"Use quick_log to document progress"
|
|
1302
|
+
]
|
|
1303
|
+
});
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
if (name === "quick_status") {
|
|
1307
|
+
const { task_id, plan_id, status, note } = args;
|
|
1308
|
+
|
|
1309
|
+
// Update the task status
|
|
1310
|
+
const updateData = { status };
|
|
1311
|
+
const updated = await apiClient.nodes.updateNode(plan_id, task_id, updateData);
|
|
1312
|
+
|
|
1313
|
+
// Add a log entry if note provided or if blocking
|
|
1314
|
+
if (note || status === 'blocked') {
|
|
1315
|
+
const logMessage = note || (status === 'blocked' ? 'Task blocked - needs attention' : `Status changed to ${status}`);
|
|
1316
|
+
try {
|
|
1317
|
+
await apiClient.logs.addLogEntry(plan_id, task_id, {
|
|
1318
|
+
type: status === 'blocked' ? 'blocker' : 'progress',
|
|
1319
|
+
content: logMessage
|
|
1320
|
+
});
|
|
1321
|
+
} catch (e) {
|
|
1322
|
+
// Log failed, continue
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
// Get next tasks for suggestion
|
|
1327
|
+
let nextTasks = [];
|
|
1328
|
+
try {
|
|
1329
|
+
const nodes = await apiClient.nodes.getNodes(plan_id);
|
|
1330
|
+
const flatNodes = flattenNodes(nodes);
|
|
1331
|
+
nextTasks = flatNodes
|
|
1332
|
+
.filter(n => n.node_type === 'task' && n.status === 'not_started')
|
|
1333
|
+
.slice(0, 3)
|
|
1334
|
+
.map(n => ({ id: n.id, title: n.title }));
|
|
1335
|
+
} catch (e) {
|
|
1336
|
+
// Failed to get next tasks, continue
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
const response = {
|
|
1340
|
+
success: true,
|
|
1341
|
+
message: `Task status updated to "${status}"`,
|
|
1342
|
+
task_id,
|
|
1343
|
+
plan_id,
|
|
1344
|
+
new_status: status
|
|
1345
|
+
};
|
|
1346
|
+
|
|
1347
|
+
if (status === 'completed' && nextTasks.length > 0) {
|
|
1348
|
+
response.next_tasks = nextTasks;
|
|
1349
|
+
response.suggestion = "Here are the next tasks to work on";
|
|
1350
|
+
} else if (status === 'blocked') {
|
|
1351
|
+
response.suggestion = "Task marked as blocked. A human will be notified to help unblock.";
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
return formatResponse(response);
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1357
|
+
if (name === "quick_log") {
|
|
1358
|
+
const { task_id, plan_id, message, log_type = 'progress' } = args;
|
|
1359
|
+
|
|
1360
|
+
const logEntry = await apiClient.logs.addLogEntry(plan_id, task_id, {
|
|
1361
|
+
type: log_type,
|
|
1362
|
+
content: message
|
|
1363
|
+
});
|
|
1364
|
+
|
|
1365
|
+
return formatResponse({
|
|
1366
|
+
success: true,
|
|
1367
|
+
message: "Progress logged",
|
|
1368
|
+
log_id: logEntry.id,
|
|
1369
|
+
task_id,
|
|
1370
|
+
plan_id,
|
|
1371
|
+
logged: message,
|
|
1372
|
+
tip: "Good practice! Logging helps humans follow your work."
|
|
1373
|
+
});
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
// ========================================
|
|
1377
|
+
// CONTEXT LOADING IMPLEMENTATIONS
|
|
1378
|
+
// ========================================
|
|
1379
|
+
|
|
1380
|
+
if (name === "get_context") {
|
|
1381
|
+
// Redirect to understand_context implementation (same functionality)
|
|
1382
|
+
const { plan_id, goal_id, include_knowledge = true } = args;
|
|
1383
|
+
|
|
1384
|
+
if (!plan_id && !goal_id) {
|
|
1385
|
+
return formatResponse({
|
|
1386
|
+
error: "Provide either plan_id or goal_id to get context",
|
|
1387
|
+
suggestion: "Use list_plans or list_goals to find IDs"
|
|
1388
|
+
});
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1391
|
+
const context = {
|
|
1392
|
+
retrieved_at: new Date().toISOString()
|
|
1393
|
+
};
|
|
1394
|
+
|
|
1395
|
+
// Get goal context
|
|
1396
|
+
if (goal_id) {
|
|
1397
|
+
try {
|
|
1398
|
+
context.goal = await apiClient.goals.get(goal_id);
|
|
1399
|
+
if (include_knowledge) {
|
|
1400
|
+
try {
|
|
1401
|
+
const graphResult = await apiClient.graphiti.graphSearch({ query: context.goal?.title || '', max_results: 10 });
|
|
1402
|
+
context.goal_knowledge = graphResult?.results?.facts || [];
|
|
1403
|
+
} catch (e) {}
|
|
1404
|
+
}
|
|
1405
|
+
} catch (e) {
|
|
1406
|
+
context.goal_error = e.message;
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1410
|
+
// Get plan context
|
|
1411
|
+
if (plan_id) {
|
|
1412
|
+
try {
|
|
1413
|
+
context.plan = await apiClient.plans.getPlan(plan_id);
|
|
1414
|
+
context.plan_url = buildPlanUrl(plan_id);
|
|
1415
|
+
|
|
1416
|
+
const nodes = await apiClient.nodes.getNodes(plan_id);
|
|
1417
|
+
context.statistics = calculatePlanStatistics(nodes);
|
|
1418
|
+
context.progress_percentage = context.statistics.total > 0
|
|
1419
|
+
? ((context.statistics.status_counts.completed / context.statistics.total) * 100).toFixed(1) + '%'
|
|
1420
|
+
: '0%';
|
|
1421
|
+
|
|
1422
|
+
const flatNodes = flattenNodes(nodes);
|
|
1423
|
+
context.needs_attention = {
|
|
1424
|
+
blocked: flatNodes.filter(n => n.status === 'blocked').map(n => ({
|
|
1425
|
+
id: n.id, title: n.title, type: n.node_type
|
|
1426
|
+
})),
|
|
1427
|
+
in_progress: flatNodes.filter(n => n.status === 'in_progress').map(n => ({
|
|
1428
|
+
id: n.id, title: n.title, type: n.node_type
|
|
1429
|
+
})),
|
|
1430
|
+
ready_to_start: flatNodes
|
|
1431
|
+
.filter(n => n.node_type === 'task' && n.status === 'not_started')
|
|
1432
|
+
.slice(0, 5)
|
|
1433
|
+
.map(n => ({ id: n.id, title: n.title }))
|
|
1434
|
+
};
|
|
1435
|
+
|
|
1436
|
+
try {
|
|
1437
|
+
const activity = await apiClient.activity.getPlanActivity(plan_id);
|
|
1438
|
+
context.recent_activity = (activity || []).slice(0, 5);
|
|
1439
|
+
} catch (e) {}
|
|
1440
|
+
|
|
1441
|
+
if (include_knowledge) {
|
|
1442
|
+
try {
|
|
1443
|
+
const graphResult = await apiClient.graphiti.graphSearch({ query: context.plan?.title || '', max_results: 10 });
|
|
1444
|
+
context.plan_knowledge = graphResult?.results?.facts || [];
|
|
1445
|
+
} catch (e) {}
|
|
1446
|
+
}
|
|
1447
|
+
} catch (e) {
|
|
1448
|
+
context.plan_error = e.message;
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1452
|
+
context.recommendation = context.needs_attention?.blocked?.length > 0
|
|
1453
|
+
? "⚠️ There are blocked tasks that need attention first"
|
|
1454
|
+
: context.needs_attention?.in_progress?.length > 0
|
|
1455
|
+
? "Continue working on the in-progress tasks"
|
|
1456
|
+
: "Pick a task from ready_to_start to begin";
|
|
1457
|
+
|
|
1458
|
+
return formatResponse(context);
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1461
|
+
if (name === "get_my_tasks") {
|
|
1462
|
+
const { plan_id, status = ["blocked", "in_progress"] } = args;
|
|
1463
|
+
|
|
1464
|
+
const tasks = {
|
|
1465
|
+
retrieved_at: new Date().toISOString(),
|
|
1466
|
+
needs_attention: [],
|
|
1467
|
+
ready_to_start: []
|
|
1468
|
+
};
|
|
1469
|
+
|
|
1470
|
+
// Get plans to check
|
|
1471
|
+
let plansToCheck = [];
|
|
1472
|
+
if (plan_id) {
|
|
1473
|
+
plansToCheck = [{ id: plan_id }];
|
|
1474
|
+
} else {
|
|
1475
|
+
plansToCheck = await apiClient.plans.getPlans();
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1478
|
+
for (const plan of plansToCheck.slice(0, 10)) { // Limit to 10 plans
|
|
1479
|
+
try {
|
|
1480
|
+
const nodes = await apiClient.nodes.getNodes(plan.id);
|
|
1481
|
+
const flatNodes = flattenNodes(nodes);
|
|
1482
|
+
|
|
1483
|
+
const matchingTasks = flatNodes
|
|
1484
|
+
.filter(n => n.node_type === 'task' && status.includes(n.status))
|
|
1485
|
+
.map(n => ({
|
|
1486
|
+
id: n.id,
|
|
1487
|
+
title: n.title,
|
|
1488
|
+
status: n.status,
|
|
1489
|
+
plan_id: plan.id,
|
|
1490
|
+
plan_title: plan.title
|
|
1491
|
+
}));
|
|
1492
|
+
|
|
1493
|
+
tasks.needs_attention.push(...matchingTasks);
|
|
1494
|
+
|
|
1495
|
+
// Also get a few ready-to-start tasks
|
|
1496
|
+
const readyTasks = flatNodes
|
|
1497
|
+
.filter(n => n.node_type === 'task' && n.status === 'not_started')
|
|
1498
|
+
.slice(0, 3)
|
|
1499
|
+
.map(n => ({
|
|
1500
|
+
id: n.id,
|
|
1501
|
+
title: n.title,
|
|
1502
|
+
plan_id: plan.id,
|
|
1503
|
+
plan_title: plan.title
|
|
1504
|
+
}));
|
|
1505
|
+
|
|
1506
|
+
tasks.ready_to_start.push(...readyTasks);
|
|
1507
|
+
} catch (e) {
|
|
1508
|
+
// Skip this plan
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
tasks.summary = {
|
|
1513
|
+
blocked: tasks.needs_attention.filter(t => t.status === 'blocked').length,
|
|
1514
|
+
in_progress: tasks.needs_attention.filter(t => t.status === 'in_progress').length,
|
|
1515
|
+
ready: tasks.ready_to_start.length
|
|
1516
|
+
};
|
|
1517
|
+
|
|
1518
|
+
return formatResponse(tasks);
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
// add_learning handled in GRAPHITI KNOWLEDGE GRAPH HANDLERS section below
|
|
1522
|
+
|
|
1523
|
+
// ========================================
|
|
1524
|
+
// MARKDOWN EXPORT
|
|
1525
|
+
// ========================================
|
|
1526
|
+
|
|
1527
|
+
if (name === "export_plan_markdown") {
|
|
1528
|
+
const { plan_id, include_descriptions = true, include_status = true } = args;
|
|
1529
|
+
|
|
1530
|
+
const plan = await apiClient.plans.getPlan(plan_id);
|
|
1531
|
+
const nodes = await apiClient.nodes.getNodes(plan_id);
|
|
1532
|
+
|
|
1533
|
+
let markdown = `# ${plan.title}\n\n`;
|
|
1534
|
+
if (plan.description) {
|
|
1535
|
+
markdown += `${plan.description}\n\n`;
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1538
|
+
const statusEmoji = {
|
|
1539
|
+
not_started: '⬜',
|
|
1540
|
+
in_progress: '🔄',
|
|
1541
|
+
completed: '✅',
|
|
1542
|
+
blocked: '🚫',
|
|
1543
|
+
cancelled: '❌'
|
|
1544
|
+
};
|
|
1545
|
+
|
|
1546
|
+
// Process nodes recursively
|
|
1547
|
+
const processNode = (node, depth = 0) => {
|
|
1548
|
+
const indent = ' '.repeat(depth);
|
|
1549
|
+
const status = include_status ? (statusEmoji[node.status] || '⬜') + ' ' : '';
|
|
1550
|
+
|
|
1551
|
+
if (node.node_type === 'phase') {
|
|
1552
|
+
markdown += `\n${indent}## ${node.title}\n`;
|
|
1553
|
+
if (include_descriptions && node.description) {
|
|
1554
|
+
markdown += `${indent}${node.description}\n`;
|
|
1555
|
+
}
|
|
1556
|
+
} else if (node.node_type === 'task') {
|
|
1557
|
+
markdown += `${indent}- ${status}${node.title}\n`;
|
|
1558
|
+
if (include_descriptions && node.description) {
|
|
1559
|
+
markdown += `${indent} _${node.description}_\n`;
|
|
1560
|
+
}
|
|
1561
|
+
} else if (node.node_type === 'milestone') {
|
|
1562
|
+
markdown += `${indent}- 🎯 ${status}**${node.title}**\n`;
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
if (node.children) {
|
|
1566
|
+
node.children.forEach(child => processNode(child, depth + 1));
|
|
1567
|
+
}
|
|
1568
|
+
};
|
|
1569
|
+
|
|
1570
|
+
// Start from root's children
|
|
1571
|
+
if (nodes.length > 0 && nodes[0].children) {
|
|
1572
|
+
nodes[0].children.forEach(child => processNode(child, 0));
|
|
1573
|
+
}
|
|
1574
|
+
|
|
1575
|
+
return formatResponse({
|
|
1576
|
+
plan_id,
|
|
1577
|
+
title: plan.title,
|
|
1578
|
+
markdown,
|
|
1579
|
+
tip: "You can save this to a file or share it as text"
|
|
1580
|
+
});
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
if (name === "import_plan_markdown") {
|
|
1584
|
+
const { markdown, title: providedTitle, goal_id } = args;
|
|
1585
|
+
|
|
1586
|
+
// Parse markdown
|
|
1587
|
+
const lines = markdown.split('\n').map(l => l.trim()).filter(l => l);
|
|
1588
|
+
|
|
1589
|
+
let planTitle = providedTitle;
|
|
1590
|
+
let planDescription = '';
|
|
1591
|
+
const phases = [];
|
|
1592
|
+
let currentPhase = null;
|
|
1593
|
+
|
|
1594
|
+
for (const line of lines) {
|
|
1595
|
+
// H1 = Plan title
|
|
1596
|
+
if (line.startsWith('# ')) {
|
|
1597
|
+
if (!planTitle) {
|
|
1598
|
+
planTitle = line.slice(2).trim();
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1601
|
+
// H2 = Phase
|
|
1602
|
+
else if (line.startsWith('## ')) {
|
|
1603
|
+
const phaseTitle = line.slice(3).trim();
|
|
1604
|
+
currentPhase = { title: phaseTitle, tasks: [] };
|
|
1605
|
+
phases.push(currentPhase);
|
|
1606
|
+
}
|
|
1607
|
+
// List item = Task (with optional status emoji)
|
|
1608
|
+
else if (line.startsWith('- ') || line.startsWith('* ')) {
|
|
1609
|
+
let taskTitle = line.slice(2).trim();
|
|
1610
|
+
let taskStatus = 'not_started';
|
|
1611
|
+
|
|
1612
|
+
// Parse status from emoji
|
|
1613
|
+
if (taskTitle.startsWith('✅')) {
|
|
1614
|
+
taskStatus = 'completed';
|
|
1615
|
+
taskTitle = taskTitle.slice(1).trim();
|
|
1616
|
+
} else if (taskTitle.startsWith('🔄')) {
|
|
1617
|
+
taskStatus = 'in_progress';
|
|
1618
|
+
taskTitle = taskTitle.slice(1).trim();
|
|
1619
|
+
} else if (taskTitle.startsWith('🚫')) {
|
|
1620
|
+
taskStatus = 'blocked';
|
|
1621
|
+
taskTitle = taskTitle.slice(1).trim();
|
|
1622
|
+
} else if (taskTitle.startsWith('⬜')) {
|
|
1623
|
+
taskTitle = taskTitle.slice(1).trim();
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1626
|
+
// Skip milestone markers for now
|
|
1627
|
+
if (taskTitle.startsWith('🎯')) {
|
|
1628
|
+
taskTitle = taskTitle.slice(1).trim().replace(/\*\*/g, '');
|
|
1629
|
+
}
|
|
1630
|
+
|
|
1631
|
+
if (currentPhase) {
|
|
1632
|
+
currentPhase.tasks.push({ title: taskTitle, status: taskStatus });
|
|
1633
|
+
} else {
|
|
1634
|
+
// No phase yet, create a default one
|
|
1635
|
+
currentPhase = { title: 'Tasks', tasks: [{ title: taskTitle, status: taskStatus }] };
|
|
1636
|
+
phases.push(currentPhase);
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
// Regular text after title = description
|
|
1640
|
+
else if (planTitle && !currentPhase && !line.startsWith('#')) {
|
|
1641
|
+
planDescription += (planDescription ? ' ' : '') + line;
|
|
1642
|
+
}
|
|
1643
|
+
}
|
|
1644
|
+
|
|
1645
|
+
if (!planTitle) {
|
|
1646
|
+
return formatResponse({
|
|
1647
|
+
error: "Could not extract plan title",
|
|
1648
|
+
suggestion: "Add a '# Title' heading at the start, or provide the 'title' parameter"
|
|
1649
|
+
});
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1652
|
+
if (phases.length === 0) {
|
|
1653
|
+
return formatResponse({
|
|
1654
|
+
error: "No phases or tasks found",
|
|
1655
|
+
suggestion: "Use '## Phase Name' for phases and '- Task name' for tasks"
|
|
1656
|
+
});
|
|
1657
|
+
}
|
|
1658
|
+
|
|
1659
|
+
// Create the plan
|
|
1660
|
+
const planData = { title: planTitle };
|
|
1661
|
+
if (planDescription) planData.description = planDescription;
|
|
1662
|
+
|
|
1663
|
+
const plan = await apiClient.plans.createPlan(planData);
|
|
1664
|
+
|
|
1665
|
+
// Get root node
|
|
1666
|
+
const nodes = await apiClient.nodes.getNodes(plan.id);
|
|
1667
|
+
const rootNode = nodes.find(n => n.node_type === 'root') || nodes[0];
|
|
1668
|
+
|
|
1669
|
+
// Create phases and tasks
|
|
1670
|
+
const createdPhases = [];
|
|
1671
|
+
const createdTasks = [];
|
|
1672
|
+
|
|
1673
|
+
for (let i = 0; i < phases.length; i++) {
|
|
1674
|
+
const phaseData = phases[i];
|
|
1675
|
+
|
|
1676
|
+
const phase = await apiClient.nodes.createNode(plan.id, {
|
|
1677
|
+
parent_id: rootNode.id,
|
|
1678
|
+
node_type: 'phase',
|
|
1679
|
+
title: phaseData.title,
|
|
1680
|
+
status: 'not_started',
|
|
1681
|
+
order_index: i
|
|
1682
|
+
});
|
|
1683
|
+
createdPhases.push({ id: phase.id, title: phase.title });
|
|
1684
|
+
|
|
1685
|
+
for (let j = 0; j < phaseData.tasks.length; j++) {
|
|
1686
|
+
const taskData = phaseData.tasks[j];
|
|
1687
|
+
|
|
1688
|
+
const task = await apiClient.nodes.createNode(plan.id, {
|
|
1689
|
+
parent_id: phase.id,
|
|
1690
|
+
node_type: 'task',
|
|
1691
|
+
title: taskData.title,
|
|
1692
|
+
status: taskData.status,
|
|
1693
|
+
order_index: j
|
|
1694
|
+
});
|
|
1695
|
+
createdTasks.push({ id: task.id, title: task.title, status: task.status });
|
|
1696
|
+
}
|
|
1697
|
+
}
|
|
1698
|
+
|
|
1699
|
+
// Link to goal if provided
|
|
1700
|
+
if (goal_id) {
|
|
1701
|
+
try {
|
|
1702
|
+
await apiClient.goals.linkPlan(goal_id, plan.id);
|
|
1703
|
+
} catch (e) {}
|
|
1704
|
+
}
|
|
1705
|
+
|
|
1706
|
+
return formatResponse({
|
|
1707
|
+
success: true,
|
|
1708
|
+
message: `Plan "${planTitle}" created from markdown with ${phases.length} phases and ${createdTasks.length} tasks`,
|
|
1709
|
+
plan_id: plan.id,
|
|
1710
|
+
plan_url: buildPlanUrl(plan.id),
|
|
1711
|
+
phases: createdPhases,
|
|
1712
|
+
tasks: createdTasks,
|
|
1713
|
+
next_steps: [
|
|
1714
|
+
"Use get_context to review the imported plan",
|
|
1715
|
+
"Use quick_status to update task progress"
|
|
1716
|
+
]
|
|
1717
|
+
});
|
|
1718
|
+
}
|
|
1719
|
+
|
|
442
1720
|
// ===== UNIFIED SEARCH TOOL =====
|
|
443
1721
|
if (name === "search") {
|
|
444
1722
|
const { scope, scope_id, query, filters = {} } = args;
|
|
@@ -544,6 +1822,33 @@ function setupTools(server) {
|
|
|
544
1822
|
});
|
|
545
1823
|
}
|
|
546
1824
|
|
|
1825
|
+
if (name === "share_plan") {
|
|
1826
|
+
const { plan_id, visibility = "public", github_repo_owner, github_repo_name } = args;
|
|
1827
|
+
|
|
1828
|
+
const visibilityData = { visibility };
|
|
1829
|
+
if (github_repo_owner) visibilityData.github_repo_owner = github_repo_owner;
|
|
1830
|
+
if (github_repo_name) visibilityData.github_repo_name = github_repo_name;
|
|
1831
|
+
|
|
1832
|
+
const result = await apiClient.plans.updateVisibility(plan_id, visibilityData);
|
|
1833
|
+
|
|
1834
|
+
const shareUrl = visibility === "public"
|
|
1835
|
+
? buildPlanUrl(plan_id)
|
|
1836
|
+
: null;
|
|
1837
|
+
|
|
1838
|
+
return formatResponse({
|
|
1839
|
+
success: true,
|
|
1840
|
+
plan_id: plan_id,
|
|
1841
|
+
visibility: result.visibility,
|
|
1842
|
+
is_public: result.is_public,
|
|
1843
|
+
share_url: shareUrl,
|
|
1844
|
+
github_repo_owner: result.github_repo_owner,
|
|
1845
|
+
github_repo_name: result.github_repo_name,
|
|
1846
|
+
message: visibility === "public"
|
|
1847
|
+
? `Plan is now public. Share URL: ${shareUrl}`
|
|
1848
|
+
: `Plan is now private.`
|
|
1849
|
+
});
|
|
1850
|
+
}
|
|
1851
|
+
|
|
547
1852
|
// ===== NODE MANAGEMENT =====
|
|
548
1853
|
if (name === "create_node") {
|
|
549
1854
|
const { plan_id, ...nodeData } = args;
|
|
@@ -568,27 +1873,29 @@ function setupTools(server) {
|
|
|
568
1873
|
|
|
569
1874
|
if (name === "move_node") {
|
|
570
1875
|
const { plan_id, node_id, parent_id, order_index } = args;
|
|
571
|
-
|
|
1876
|
+
|
|
572
1877
|
try {
|
|
1878
|
+
// Build request body with only provided fields - don't send nulls
|
|
1879
|
+
const body = {};
|
|
1880
|
+
if (parent_id) body.parent_id = parent_id;
|
|
1881
|
+
if (order_index !== undefined) body.order_index = order_index;
|
|
1882
|
+
|
|
573
1883
|
// Call the move endpoint - using POST as per API definition
|
|
574
1884
|
const response = await apiClient.axiosInstance.post(
|
|
575
1885
|
`/plans/${plan_id}/nodes/${node_id}/move`,
|
|
576
|
-
|
|
577
|
-
parent_id: parent_id || null,
|
|
578
|
-
order_index: order_index !== undefined ? order_index : null
|
|
579
|
-
}
|
|
1886
|
+
body
|
|
580
1887
|
);
|
|
581
|
-
|
|
1888
|
+
|
|
582
1889
|
return formatResponse(response.data);
|
|
583
1890
|
} catch (error) {
|
|
584
1891
|
// If endpoint still doesn't work, try updating the node directly
|
|
585
1892
|
if (error.response && error.response.status === 404) {
|
|
586
1893
|
console.error('Move endpoint not found, trying direct update');
|
|
587
1894
|
// Fallback to updating the node's parent_id via regular update
|
|
588
|
-
const
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
1895
|
+
const updateData = {};
|
|
1896
|
+
if (parent_id) updateData.parent_id = parent_id;
|
|
1897
|
+
if (order_index !== undefined) updateData.order_index = order_index;
|
|
1898
|
+
const updateResponse = await apiClient.nodes.updateNode(plan_id, node_id, updateData);
|
|
592
1899
|
return formatResponse(updateResponse);
|
|
593
1900
|
}
|
|
594
1901
|
throw error;
|
|
@@ -617,6 +1924,90 @@ function setupTools(server) {
|
|
|
617
1924
|
return formatResponse(response.data);
|
|
618
1925
|
}
|
|
619
1926
|
|
|
1927
|
+
// ===== DEPENDENCIES =====
|
|
1928
|
+
if (name === "create_dependency") {
|
|
1929
|
+
const { plan_id, source_node_id, target_node_id, dependency_type, weight, metadata } = args;
|
|
1930
|
+
const response = await apiClient.axiosInstance.post(
|
|
1931
|
+
`/plans/${plan_id}/dependencies`,
|
|
1932
|
+
{ source_node_id, target_node_id, dependency_type, weight, metadata }
|
|
1933
|
+
);
|
|
1934
|
+
return formatResponse(response.data);
|
|
1935
|
+
}
|
|
1936
|
+
|
|
1937
|
+
if (name === "delete_dependency") {
|
|
1938
|
+
const { plan_id, dependency_id } = args;
|
|
1939
|
+
const response = await apiClient.axiosInstance.delete(
|
|
1940
|
+
`/plans/${plan_id}/dependencies/${dependency_id}`
|
|
1941
|
+
);
|
|
1942
|
+
return formatResponse(response.data);
|
|
1943
|
+
}
|
|
1944
|
+
|
|
1945
|
+
if (name === "list_dependencies") {
|
|
1946
|
+
const { plan_id } = args;
|
|
1947
|
+
const response = await apiClient.axiosInstance.get(
|
|
1948
|
+
`/plans/${plan_id}/dependencies`
|
|
1949
|
+
);
|
|
1950
|
+
return formatResponse(response.data);
|
|
1951
|
+
}
|
|
1952
|
+
|
|
1953
|
+
if (name === "get_node_dependencies") {
|
|
1954
|
+
const { plan_id, node_id, direction = 'both' } = args;
|
|
1955
|
+
const response = await apiClient.axiosInstance.get(
|
|
1956
|
+
`/plans/${plan_id}/nodes/${node_id}/dependencies`,
|
|
1957
|
+
{ params: { direction } }
|
|
1958
|
+
);
|
|
1959
|
+
return formatResponse(response.data);
|
|
1960
|
+
}
|
|
1961
|
+
|
|
1962
|
+
// ===== RPI WORKFLOW =====
|
|
1963
|
+
if (name === "create_rpi_chain") {
|
|
1964
|
+
const { plan_id, title, description, parent_id } = args;
|
|
1965
|
+
const response = await apiClient.axiosInstance.post(
|
|
1966
|
+
`/plans/${plan_id}/nodes/rpi-chain`,
|
|
1967
|
+
{ title, description, parent_id }
|
|
1968
|
+
);
|
|
1969
|
+
return formatResponse(response.data);
|
|
1970
|
+
}
|
|
1971
|
+
|
|
1972
|
+
// ===== ANALYSIS =====
|
|
1973
|
+
if (name === "analyze_impact") {
|
|
1974
|
+
const { plan_id, node_id, scenario = 'block' } = args;
|
|
1975
|
+
const response = await apiClient.axiosInstance.get(
|
|
1976
|
+
`/plans/${plan_id}/nodes/${node_id}/impact`,
|
|
1977
|
+
{ params: { scenario } }
|
|
1978
|
+
);
|
|
1979
|
+
return formatResponse(response.data);
|
|
1980
|
+
}
|
|
1981
|
+
|
|
1982
|
+
if (name === "get_critical_path") {
|
|
1983
|
+
const { plan_id } = args;
|
|
1984
|
+
const response = await apiClient.axiosInstance.get(
|
|
1985
|
+
`/plans/${plan_id}/critical-path`
|
|
1986
|
+
);
|
|
1987
|
+
return formatResponse(response.data);
|
|
1988
|
+
}
|
|
1989
|
+
|
|
1990
|
+
// ===== PROGRESSIVE CONTEXT =====
|
|
1991
|
+
if (name === "get_task_context") {
|
|
1992
|
+
const { node_id, depth = 2, token_budget = 0, log_limit = 10, include_research = true } = args;
|
|
1993
|
+
const params = new URLSearchParams({
|
|
1994
|
+
node_id,
|
|
1995
|
+
depth: String(depth),
|
|
1996
|
+
token_budget: String(token_budget),
|
|
1997
|
+
log_limit: String(log_limit),
|
|
1998
|
+
include_research: String(include_research),
|
|
1999
|
+
});
|
|
2000
|
+
const response = await apiClient.axiosInstance.get(`/context/progressive?${params.toString()}`);
|
|
2001
|
+
return formatResponse(response.data);
|
|
2002
|
+
}
|
|
2003
|
+
|
|
2004
|
+
if (name === "suggest_next_tasks") {
|
|
2005
|
+
const { plan_id, limit = 5 } = args;
|
|
2006
|
+
const params = new URLSearchParams({ plan_id, limit: String(limit) });
|
|
2007
|
+
const response = await apiClient.axiosInstance.get(`/context/suggest?${params.toString()}`);
|
|
2008
|
+
return formatResponse(response.data);
|
|
2009
|
+
}
|
|
2010
|
+
|
|
620
2011
|
// ===== LOGGING =====
|
|
621
2012
|
if (name === "add_log") {
|
|
622
2013
|
const { plan_id, node_id, content, log_type = "comment", tags } = args;
|
|
@@ -647,48 +2038,6 @@ function setupTools(server) {
|
|
|
647
2038
|
return formatResponse(logs);
|
|
648
2039
|
}
|
|
649
2040
|
|
|
650
|
-
// ===== ARTIFACT MANAGEMENT =====
|
|
651
|
-
if (name === "manage_artifact") {
|
|
652
|
-
const { action, plan_id, node_id, ...params } = args;
|
|
653
|
-
|
|
654
|
-
switch (action) {
|
|
655
|
-
case "add":
|
|
656
|
-
const { name, content_type, url, metadata } = params;
|
|
657
|
-
const newArtifact = await apiClient.artifacts.addArtifact(plan_id, node_id, {
|
|
658
|
-
name,
|
|
659
|
-
content_type,
|
|
660
|
-
url,
|
|
661
|
-
metadata
|
|
662
|
-
});
|
|
663
|
-
return formatResponse(newArtifact);
|
|
664
|
-
|
|
665
|
-
case "get":
|
|
666
|
-
const { artifact_id } = params;
|
|
667
|
-
const artifact = await apiClient.artifacts.getArtifact(plan_id, node_id, artifact_id);
|
|
668
|
-
const content = await apiClient.artifacts.getArtifactContent(plan_id, node_id, artifact_id);
|
|
669
|
-
return formatResponse({
|
|
670
|
-
...artifact,
|
|
671
|
-
content
|
|
672
|
-
});
|
|
673
|
-
|
|
674
|
-
case "search":
|
|
675
|
-
const { name: searchName } = params;
|
|
676
|
-
const artifacts = await apiClient.artifacts.getArtifacts(plan_id, node_id);
|
|
677
|
-
const searchLower = searchName.toLowerCase();
|
|
678
|
-
const matches = artifacts.filter(a =>
|
|
679
|
-
a.name.toLowerCase().includes(searchLower)
|
|
680
|
-
);
|
|
681
|
-
return formatResponse(matches);
|
|
682
|
-
|
|
683
|
-
case "list":
|
|
684
|
-
const allArtifacts = await apiClient.artifacts.getArtifacts(plan_id, node_id);
|
|
685
|
-
return formatResponse(allArtifacts);
|
|
686
|
-
|
|
687
|
-
default:
|
|
688
|
-
throw new Error(`Unknown artifact action: ${action}`);
|
|
689
|
-
}
|
|
690
|
-
}
|
|
691
|
-
|
|
692
2041
|
// ===== BATCH OPERATIONS =====
|
|
693
2042
|
if (name === "batch_update_nodes") {
|
|
694
2043
|
const { plan_id, updates } = args;
|
|
@@ -715,49 +2064,16 @@ function setupTools(server) {
|
|
|
715
2064
|
});
|
|
716
2065
|
}
|
|
717
2066
|
|
|
718
|
-
if (name === "batch_get_artifacts") {
|
|
719
|
-
const { plan_id, artifact_requests } = args;
|
|
720
|
-
|
|
721
|
-
const results = [];
|
|
722
|
-
const errors = [];
|
|
723
|
-
|
|
724
|
-
for (const request of artifact_requests) {
|
|
725
|
-
const { node_id, artifact_id } = request;
|
|
726
|
-
try {
|
|
727
|
-
const artifact = await apiClient.artifacts.getArtifact(plan_id, node_id, artifact_id);
|
|
728
|
-
const content = await apiClient.artifacts.getArtifactContent(plan_id, node_id, artifact_id);
|
|
729
|
-
results.push({
|
|
730
|
-
node_id,
|
|
731
|
-
artifact_id,
|
|
732
|
-
success: true,
|
|
733
|
-
data: { ...artifact, content }
|
|
734
|
-
});
|
|
735
|
-
} catch (error) {
|
|
736
|
-
errors.push({
|
|
737
|
-
node_id,
|
|
738
|
-
artifact_id,
|
|
739
|
-
success: false,
|
|
740
|
-
error: error.message
|
|
741
|
-
});
|
|
742
|
-
}
|
|
743
|
-
}
|
|
744
|
-
|
|
745
|
-
return formatResponse({
|
|
746
|
-
total: artifact_requests.length,
|
|
747
|
-
successful: results.length,
|
|
748
|
-
failed: errors.length,
|
|
749
|
-
results,
|
|
750
|
-
errors
|
|
751
|
-
});
|
|
752
|
-
}
|
|
753
|
-
|
|
754
2067
|
// ===== PLAN STRUCTURE & SUMMARY =====
|
|
755
2068
|
if (name === "get_plan_structure") {
|
|
756
2069
|
const { plan_id, include_details = false } = args;
|
|
757
|
-
|
|
2070
|
+
|
|
758
2071
|
const plan = await apiClient.plans.getPlan(plan_id);
|
|
759
|
-
|
|
760
|
-
|
|
2072
|
+
// Pass include_details to the API - defaults to minimal fields
|
|
2073
|
+
const nodes = await apiClient.nodes.getNodes(plan_id, {
|
|
2074
|
+
include_details: include_details
|
|
2075
|
+
});
|
|
2076
|
+
|
|
761
2077
|
// The API already returns a tree structure, not a flat list
|
|
762
2078
|
// If it's already hierarchical, use it directly
|
|
763
2079
|
let structure;
|
|
@@ -768,7 +2084,7 @@ function setupTools(server) {
|
|
|
768
2084
|
// Flat list - build hierarchy
|
|
769
2085
|
structure = buildNodeHierarchy(nodes, include_details);
|
|
770
2086
|
}
|
|
771
|
-
|
|
2087
|
+
|
|
772
2088
|
return formatResponse({
|
|
773
2089
|
plan: {
|
|
774
2090
|
id: plan.id,
|
|
@@ -805,6 +2121,494 @@ function setupTools(server) {
|
|
|
805
2121
|
});
|
|
806
2122
|
}
|
|
807
2123
|
|
|
2124
|
+
// ===== AGENT CONTEXT TOOLS =====
|
|
2125
|
+
if (name === "get_agent_context") {
|
|
2126
|
+
const { node_id, include_knowledge = true, include_siblings = false } = args;
|
|
2127
|
+
|
|
2128
|
+
const result = await apiClient.context.getNodeContext(node_id, {
|
|
2129
|
+
include_knowledge,
|
|
2130
|
+
include_siblings
|
|
2131
|
+
});
|
|
2132
|
+
|
|
2133
|
+
return formatResponse(result);
|
|
2134
|
+
}
|
|
2135
|
+
|
|
2136
|
+
if (name === "get_plan_context") {
|
|
2137
|
+
const { plan_id, include_knowledge = true } = args;
|
|
2138
|
+
|
|
2139
|
+
const result = await apiClient.context.getPlanContext(plan_id, {
|
|
2140
|
+
include_knowledge
|
|
2141
|
+
});
|
|
2142
|
+
|
|
2143
|
+
return formatResponse(result);
|
|
2144
|
+
}
|
|
2145
|
+
|
|
2146
|
+
// ===== ORGANIZATION TOOLS =====
|
|
2147
|
+
if (name === "list_organizations") {
|
|
2148
|
+
const result = await apiClient.organizations.list();
|
|
2149
|
+
return formatResponse(result);
|
|
2150
|
+
}
|
|
2151
|
+
|
|
2152
|
+
if (name === "get_organization") {
|
|
2153
|
+
const { organization_id } = args;
|
|
2154
|
+
const result = await apiClient.organizations.get(organization_id);
|
|
2155
|
+
return formatResponse(result);
|
|
2156
|
+
}
|
|
2157
|
+
|
|
2158
|
+
if (name === "create_organization") {
|
|
2159
|
+
const { name, description, slug } = args;
|
|
2160
|
+
const result = await apiClient.organizations.create({ name, description, slug });
|
|
2161
|
+
return formatResponse(result);
|
|
2162
|
+
}
|
|
2163
|
+
|
|
2164
|
+
if (name === "update_organization") {
|
|
2165
|
+
const { organization_id, ...updateData } = args;
|
|
2166
|
+
const result = await apiClient.organizations.update(organization_id, updateData);
|
|
2167
|
+
return formatResponse(result);
|
|
2168
|
+
}
|
|
2169
|
+
|
|
2170
|
+
// ===== GOAL TOOLS =====
|
|
2171
|
+
if (name === "list_goals") {
|
|
2172
|
+
const { organization_id, status } = args;
|
|
2173
|
+
const result = await apiClient.goals.list({ organization_id, status });
|
|
2174
|
+
return formatResponse(result);
|
|
2175
|
+
}
|
|
2176
|
+
|
|
2177
|
+
if (name === "get_goal") {
|
|
2178
|
+
const { goal_id } = args;
|
|
2179
|
+
const result = await apiClient.goals.get(goal_id);
|
|
2180
|
+
return formatResponse(result);
|
|
2181
|
+
}
|
|
2182
|
+
|
|
2183
|
+
if (name === "create_goal") {
|
|
2184
|
+
const { organization_id, title, description, success_metrics, time_horizon, github_repo_url } = args;
|
|
2185
|
+
const result = await apiClient.goals.create({
|
|
2186
|
+
organization_id,
|
|
2187
|
+
title,
|
|
2188
|
+
description,
|
|
2189
|
+
success_metrics,
|
|
2190
|
+
time_horizon,
|
|
2191
|
+
github_repo_url
|
|
2192
|
+
});
|
|
2193
|
+
return formatResponse(result);
|
|
2194
|
+
}
|
|
2195
|
+
|
|
2196
|
+
if (name === "update_goal") {
|
|
2197
|
+
const { goal_id, ...updateData } = args;
|
|
2198
|
+
const result = await apiClient.goals.update(goal_id, updateData);
|
|
2199
|
+
return formatResponse(result);
|
|
2200
|
+
}
|
|
2201
|
+
|
|
2202
|
+
if (name === "link_plan_to_goal") {
|
|
2203
|
+
const { goal_id, plan_id } = args;
|
|
2204
|
+
const result = await apiClient.goals.linkPlan(goal_id, plan_id);
|
|
2205
|
+
return formatResponse({
|
|
2206
|
+
success: true,
|
|
2207
|
+
message: `Plan ${plan_id} linked to goal ${goal_id}`,
|
|
2208
|
+
...result
|
|
2209
|
+
});
|
|
2210
|
+
}
|
|
2211
|
+
|
|
2212
|
+
if (name === "unlink_plan_from_goal") {
|
|
2213
|
+
const { goal_id, plan_id } = args;
|
|
2214
|
+
const result = await apiClient.goals.unlinkPlan(goal_id, plan_id);
|
|
2215
|
+
return formatResponse({
|
|
2216
|
+
success: true,
|
|
2217
|
+
message: `Plan ${plan_id} unlinked from goal ${goal_id}`,
|
|
2218
|
+
...result
|
|
2219
|
+
});
|
|
2220
|
+
}
|
|
2221
|
+
|
|
2222
|
+
// ===== CROSS-PLAN & EXTERNAL DEPENDENCY HANDLERS =====
|
|
2223
|
+
if (name === "create_cross_plan_dependency") {
|
|
2224
|
+
const { source_node_id, target_node_id, dependency_type, weight } = args;
|
|
2225
|
+
const result = await apiClient.dependencies.createCrossPlan({
|
|
2226
|
+
source_node_id, target_node_id, dependency_type, weight
|
|
2227
|
+
});
|
|
2228
|
+
return formatResponse(result);
|
|
2229
|
+
}
|
|
2230
|
+
|
|
2231
|
+
if (name === "list_cross_plan_dependencies") {
|
|
2232
|
+
const { plan_ids } = args;
|
|
2233
|
+
const result = await apiClient.dependencies.listCrossPlan(plan_ids);
|
|
2234
|
+
return formatResponse(result);
|
|
2235
|
+
}
|
|
2236
|
+
|
|
2237
|
+
if (name === "create_external_dependency") {
|
|
2238
|
+
const { plan_id, title, description, url, blocks_node_id } = args;
|
|
2239
|
+
const result = await apiClient.dependencies.createExternal({
|
|
2240
|
+
plan_id, title, description, url, blocks_node_id
|
|
2241
|
+
});
|
|
2242
|
+
return formatResponse(result);
|
|
2243
|
+
}
|
|
2244
|
+
|
|
2245
|
+
// ===== GOAL-DEPENDENCY HANDLERS =====
|
|
2246
|
+
if (name === "goal_path") {
|
|
2247
|
+
const { goal_id, max_depth } = args;
|
|
2248
|
+
const result = await apiClient.goals.getPath(goal_id, max_depth);
|
|
2249
|
+
return formatResponse(result);
|
|
2250
|
+
}
|
|
2251
|
+
|
|
2252
|
+
if (name === "goal_progress") {
|
|
2253
|
+
const { goal_id } = args;
|
|
2254
|
+
const result = await apiClient.goals.getProgress(goal_id);
|
|
2255
|
+
return formatResponse(result);
|
|
2256
|
+
}
|
|
2257
|
+
|
|
2258
|
+
if (name === "add_achiever") {
|
|
2259
|
+
const { goal_id, node_id, weight } = args;
|
|
2260
|
+
const result = await apiClient.goals.addAchiever(goal_id, node_id, weight);
|
|
2261
|
+
return formatResponse({
|
|
2262
|
+
...result,
|
|
2263
|
+
message: `Task ${node_id} now achieves goal ${goal_id}`,
|
|
2264
|
+
});
|
|
2265
|
+
}
|
|
2266
|
+
|
|
2267
|
+
if (name === "remove_achiever") {
|
|
2268
|
+
const { goal_id, dependency_id } = args;
|
|
2269
|
+
const result = await apiClient.goals.removeAchiever(goal_id, dependency_id);
|
|
2270
|
+
return formatResponse(result);
|
|
2271
|
+
}
|
|
2272
|
+
|
|
2273
|
+
if (name === "goal_knowledge_gaps") {
|
|
2274
|
+
const { goal_id } = args;
|
|
2275
|
+
const result = await apiClient.goals.getKnowledgeGaps(goal_id);
|
|
2276
|
+
return formatResponse(result);
|
|
2277
|
+
}
|
|
2278
|
+
|
|
2279
|
+
// ===== GRAPHITI KNOWLEDGE GRAPH HANDLERS =====
|
|
2280
|
+
if (name === "add_learning") {
|
|
2281
|
+
const { content, title, entry_type, plan_id, node_id } = args;
|
|
2282
|
+
|
|
2283
|
+
// Add to Graphiti temporal knowledge graph
|
|
2284
|
+
const result = await apiClient.graphiti.addEpisode({
|
|
2285
|
+
content,
|
|
2286
|
+
name: title,
|
|
2287
|
+
plan_id,
|
|
2288
|
+
node_id,
|
|
2289
|
+
metadata: { entry_type: entry_type || 'learning' },
|
|
2290
|
+
});
|
|
2291
|
+
return formatResponse({
|
|
2292
|
+
...result,
|
|
2293
|
+
message: 'Knowledge recorded in temporal graph',
|
|
2294
|
+
tip: 'This is now searchable via recall_knowledge across all plans'
|
|
2295
|
+
});
|
|
2296
|
+
}
|
|
2297
|
+
|
|
2298
|
+
if (name === "recall_knowledge") {
|
|
2299
|
+
const { query, max_results = 10 } = args;
|
|
2300
|
+
|
|
2301
|
+
// Try Graphiti first (temporal, cross-plan)
|
|
2302
|
+
try {
|
|
2303
|
+
const graphResult = await apiClient.graphiti.graphSearch({ query, max_results });
|
|
2304
|
+
if (graphResult?.results) {
|
|
2305
|
+
return formatResponse({
|
|
2306
|
+
...graphResult,
|
|
2307
|
+
source: 'graphiti_temporal_graph'
|
|
2308
|
+
});
|
|
2309
|
+
}
|
|
2310
|
+
} catch (err) {
|
|
2311
|
+
return formatResponse({
|
|
2312
|
+
results: [],
|
|
2313
|
+
source: 'graphiti_temporal_graph',
|
|
2314
|
+
error: 'Knowledge graph not available: ' + err.message,
|
|
2315
|
+
});
|
|
2316
|
+
}
|
|
2317
|
+
}
|
|
2318
|
+
|
|
2319
|
+
if (name === "find_entities") {
|
|
2320
|
+
const { query, max_results = 10 } = args;
|
|
2321
|
+
|
|
2322
|
+
try {
|
|
2323
|
+
const result = await apiClient.graphiti.searchEntities({ query, max_results });
|
|
2324
|
+
return formatResponse(result);
|
|
2325
|
+
} catch (err) {
|
|
2326
|
+
return formatResponse({
|
|
2327
|
+
error: 'Entity search requires the temporal knowledge graph (Graphiti)',
|
|
2328
|
+
detail: err.message
|
|
2329
|
+
});
|
|
2330
|
+
}
|
|
2331
|
+
}
|
|
2332
|
+
|
|
2333
|
+
if (name === "check_contradictions") {
|
|
2334
|
+
const { query, max_results = 10 } = args;
|
|
2335
|
+
|
|
2336
|
+
try {
|
|
2337
|
+
const result = await apiClient.graphiti.detectContradictions({ query, max_results });
|
|
2338
|
+
if (result.contradictions_found) {
|
|
2339
|
+
return formatResponse({
|
|
2340
|
+
...result,
|
|
2341
|
+
warning: 'Some knowledge has been superseded. Review the "superseded" facts before proceeding.',
|
|
2342
|
+
});
|
|
2343
|
+
}
|
|
2344
|
+
return formatResponse({
|
|
2345
|
+
...result,
|
|
2346
|
+
message: 'No contradictions found — all facts are current.',
|
|
2347
|
+
});
|
|
2348
|
+
} catch (err) {
|
|
2349
|
+
return formatResponse({
|
|
2350
|
+
error: 'Contradiction detection requires the temporal knowledge graph (Graphiti)',
|
|
2351
|
+
detail: err.message,
|
|
2352
|
+
});
|
|
2353
|
+
}
|
|
2354
|
+
}
|
|
2355
|
+
|
|
2356
|
+
if (name === "get_recent_episodes") {
|
|
2357
|
+
const { max_episodes = 20 } = args || {};
|
|
2358
|
+
|
|
2359
|
+
try {
|
|
2360
|
+
const result = await apiClient.graphiti.getEpisodes({ max_episodes });
|
|
2361
|
+
return formatResponse(result);
|
|
2362
|
+
} catch (err) {
|
|
2363
|
+
return formatResponse({
|
|
2364
|
+
error: 'Episodic memory requires the temporal knowledge graph (Graphiti)',
|
|
2365
|
+
detail: err.message
|
|
2366
|
+
});
|
|
2367
|
+
}
|
|
2368
|
+
}
|
|
2369
|
+
|
|
2370
|
+
// ===== HELPER TOOLS =====
|
|
2371
|
+
if (name === "get_started") {
|
|
2372
|
+
const { topic = "overview" } = args || {};
|
|
2373
|
+
|
|
2374
|
+
const guides = {
|
|
2375
|
+
overview: {
|
|
2376
|
+
title: "AgentPlanner Overview",
|
|
2377
|
+
description: "AgentPlanner is a collaborative planning system for AI agents and humans to work together on structured plans.",
|
|
2378
|
+
key_concepts: [
|
|
2379
|
+
"Organizations - Groups of users, goals, and resources",
|
|
2380
|
+
"Goals - High-level objectives with success metrics that plans work toward",
|
|
2381
|
+
"Plans - Hierarchical structures with phases, tasks, and milestones",
|
|
2382
|
+
"Nodes - Individual items in a plan (phases contain tasks and milestones)",
|
|
2383
|
+
"Knowledge - Persistent storage for decisions, context, constraints, and learnings"
|
|
2384
|
+
],
|
|
2385
|
+
recommended_workflow: [
|
|
2386
|
+
"1. Check list_goals to understand current objectives",
|
|
2387
|
+
"2. Use list_plans to see existing plans",
|
|
2388
|
+
"3. Before working on a plan, use understand_context to get the full picture",
|
|
2389
|
+
"4. Update task statuses as you work (update_node with status)",
|
|
2390
|
+
"5. Store important decisions and learnings using add_learning",
|
|
2391
|
+
"6. Check recall_knowledge before making decisions to see past context"
|
|
2392
|
+
],
|
|
2393
|
+
quick_tips: [
|
|
2394
|
+
"Always capture WHY decisions were made, not just WHAT",
|
|
2395
|
+
"Mark tasks 'blocked' with notes when stuck - this helps humans help you",
|
|
2396
|
+
"Use logs to document progress so others can follow your work"
|
|
2397
|
+
]
|
|
2398
|
+
},
|
|
2399
|
+
planning: {
|
|
2400
|
+
title: "Planning Best Practices",
|
|
2401
|
+
description: "How to create and structure effective plans.",
|
|
2402
|
+
structure: [
|
|
2403
|
+
"Plans have a hierarchical structure: Plan → Phases → Tasks/Milestones",
|
|
2404
|
+
"Phases are major stages or milestones of work",
|
|
2405
|
+
"Tasks are actionable work items within phases",
|
|
2406
|
+
"Milestones mark significant checkpoints"
|
|
2407
|
+
],
|
|
2408
|
+
tips: [
|
|
2409
|
+
"Break work into phases (major stages)",
|
|
2410
|
+
"Each phase should contain 3-7 tasks (not too granular, not too big)",
|
|
2411
|
+
"Add clear acceptance_criteria to tasks so completion is unambiguous",
|
|
2412
|
+
"Use agent_instructions to guide how AI agents should approach tasks",
|
|
2413
|
+
"Link plans to goals to track how work contributes to objectives"
|
|
2414
|
+
],
|
|
2415
|
+
tools_to_use: ["create_plan", "create_node", "get_plan_structure", "link_plan_to_goal"]
|
|
2416
|
+
},
|
|
2417
|
+
execution: {
|
|
2418
|
+
title: "Executing Plans",
|
|
2419
|
+
description: "How to work through plans effectively.",
|
|
2420
|
+
workflow: [
|
|
2421
|
+
"1. Use get_plan_structure to see the full plan",
|
|
2422
|
+
"2. Find tasks with status 'not_started' or 'in_progress'",
|
|
2423
|
+
"3. Before starting a task, check recall_knowledge for relevant context",
|
|
2424
|
+
"4. Update task status to 'in_progress' when you begin",
|
|
2425
|
+
"5. Add logs to document what you're doing",
|
|
2426
|
+
"6. Mark 'completed' when done, or 'blocked' if stuck"
|
|
2427
|
+
],
|
|
2428
|
+
status_values: {
|
|
2429
|
+
not_started: "Work hasn't begun",
|
|
2430
|
+
in_progress: "Currently being worked on",
|
|
2431
|
+
completed: "Finished and verified",
|
|
2432
|
+
blocked: "Cannot proceed - add notes explaining why",
|
|
2433
|
+
cancelled: "No longer needed"
|
|
2434
|
+
},
|
|
2435
|
+
tips: [
|
|
2436
|
+
"Check get_plan_summary for current progress and blockers",
|
|
2437
|
+
"When blocked, clearly document what's blocking you",
|
|
2438
|
+
"Store learnings as you go - don't wait until the end"
|
|
2439
|
+
],
|
|
2440
|
+
tools_to_use: ["get_plan_structure", "update_node", "add_log", "recall_knowledge"]
|
|
2441
|
+
},
|
|
2442
|
+
knowledge: {
|
|
2443
|
+
title: "Knowledge Management",
|
|
2444
|
+
description: "How to capture and use organizational knowledge effectively.",
|
|
2445
|
+
entry_types: {
|
|
2446
|
+
decision: "Choices made and their rationale - ALWAYS capture WHY",
|
|
2447
|
+
context: "Background information needed to understand something",
|
|
2448
|
+
constraint: "Rules, limitations, or requirements that must be respected",
|
|
2449
|
+
learning: "Insights gained from experience - what worked, what didn't",
|
|
2450
|
+
reference: "Links to external resources or documentation",
|
|
2451
|
+
note: "General notes that don't fit other categories"
|
|
2452
|
+
},
|
|
2453
|
+
best_practices: [
|
|
2454
|
+
"ALWAYS capture significant decisions with reasoning",
|
|
2455
|
+
"Search knowledge BEFORE making decisions (check for constraints)",
|
|
2456
|
+
"Add learnings when you discover something useful",
|
|
2457
|
+
"Tag entries well for easier retrieval later",
|
|
2458
|
+
"Include enough context that future-you can understand"
|
|
2459
|
+
],
|
|
2460
|
+
when_to_create_entries: [
|
|
2461
|
+
"When a decision is made (especially if non-obvious)",
|
|
2462
|
+
"When you learn something that might be useful later",
|
|
2463
|
+
"When you discover a constraint or rule",
|
|
2464
|
+
"When you find a useful resource or reference"
|
|
2465
|
+
],
|
|
2466
|
+
tools_to_use: ["add_learning", "recall_knowledge", "find_entities", "check_contradictions"]
|
|
2467
|
+
},
|
|
2468
|
+
collaboration: {
|
|
2469
|
+
title: "Collaboration",
|
|
2470
|
+
description: "Working with humans and other agents.",
|
|
2471
|
+
tips: [
|
|
2472
|
+
"Plans can be shared with collaborators (viewer, editor, admin roles)",
|
|
2473
|
+
"Use logs to document progress so others can follow your work",
|
|
2474
|
+
"Knowledge stores are shared within their scope (org/goal/plan)",
|
|
2475
|
+
"When stuck, mark tasks as 'blocked' with clear notes - humans will see this"
|
|
2476
|
+
],
|
|
2477
|
+
communication: [
|
|
2478
|
+
"Logs are visible to all plan collaborators",
|
|
2479
|
+
"Knowledge entries persist and are searchable by others",
|
|
2480
|
+
"Clear status updates help humans understand where things stand"
|
|
2481
|
+
],
|
|
2482
|
+
tools_to_use: ["list_organizations", "list_goals", "add_log"]
|
|
2483
|
+
}
|
|
2484
|
+
};
|
|
2485
|
+
|
|
2486
|
+
return formatResponse(guides[topic] || guides.overview);
|
|
2487
|
+
}
|
|
2488
|
+
|
|
2489
|
+
if (name === "understand_context") {
|
|
2490
|
+
const { plan_id, goal_id, include_knowledge = true } = args;
|
|
2491
|
+
|
|
2492
|
+
if (!plan_id && !goal_id) {
|
|
2493
|
+
return formatResponse({
|
|
2494
|
+
error: "Provide either plan_id or goal_id to get context"
|
|
2495
|
+
});
|
|
2496
|
+
}
|
|
2497
|
+
|
|
2498
|
+
const context = {
|
|
2499
|
+
retrieved_at: new Date().toISOString()
|
|
2500
|
+
};
|
|
2501
|
+
|
|
2502
|
+
// Get goal context
|
|
2503
|
+
if (goal_id) {
|
|
2504
|
+
try {
|
|
2505
|
+
context.goal = await apiClient.goals.get(goal_id);
|
|
2506
|
+
|
|
2507
|
+
// Get related knowledge if available
|
|
2508
|
+
if (include_knowledge) {
|
|
2509
|
+
try {
|
|
2510
|
+
const graphResult = await apiClient.graphiti.graphSearch({ query: context.goal?.title || '', max_results: 10 });
|
|
2511
|
+
context.goal_knowledge = graphResult?.results?.facts || [];
|
|
2512
|
+
} catch (e) {
|
|
2513
|
+
// Knowledge fetch failed, continue without it
|
|
2514
|
+
}
|
|
2515
|
+
}
|
|
2516
|
+
} catch (e) {
|
|
2517
|
+
context.goal_error = e.message;
|
|
2518
|
+
}
|
|
2519
|
+
}
|
|
2520
|
+
|
|
2521
|
+
// Get plan context
|
|
2522
|
+
if (plan_id) {
|
|
2523
|
+
try {
|
|
2524
|
+
context.plan = await apiClient.plans.getPlan(plan_id);
|
|
2525
|
+
|
|
2526
|
+
const nodes = await apiClient.nodes.getNodes(plan_id);
|
|
2527
|
+
context.statistics = calculatePlanStatistics(nodes);
|
|
2528
|
+
context.progress_percentage = context.statistics.total > 0
|
|
2529
|
+
? ((context.statistics.status_counts.completed / context.statistics.total) * 100).toFixed(1) + '%'
|
|
2530
|
+
: '0%';
|
|
2531
|
+
|
|
2532
|
+
// Get blocked and in-progress tasks for attention
|
|
2533
|
+
const flatNodes = flattenNodes(nodes);
|
|
2534
|
+
context.needs_attention = {
|
|
2535
|
+
blocked: flatNodes.filter(n => n.status === 'blocked').map(n => ({
|
|
2536
|
+
id: n.id,
|
|
2537
|
+
title: n.title,
|
|
2538
|
+
type: n.node_type
|
|
2539
|
+
})),
|
|
2540
|
+
in_progress: flatNodes.filter(n => n.status === 'in_progress').map(n => ({
|
|
2541
|
+
id: n.id,
|
|
2542
|
+
title: n.title,
|
|
2543
|
+
type: n.node_type
|
|
2544
|
+
}))
|
|
2545
|
+
};
|
|
2546
|
+
|
|
2547
|
+
// Get recent activity
|
|
2548
|
+
try {
|
|
2549
|
+
const activity = await apiClient.activity.getPlanActivity(plan_id);
|
|
2550
|
+
context.recent_activity = (activity || []).slice(0, 5);
|
|
2551
|
+
} catch (e) {
|
|
2552
|
+
// Activity fetch failed, continue without it
|
|
2553
|
+
}
|
|
2554
|
+
|
|
2555
|
+
// Get related knowledge if available
|
|
2556
|
+
if (include_knowledge) {
|
|
2557
|
+
try {
|
|
2558
|
+
const graphResult = await apiClient.graphiti.graphSearch({ query: context.plan?.title || '', max_results: 10 });
|
|
2559
|
+
context.plan_knowledge = graphResult?.results?.facts || [];
|
|
2560
|
+
} catch (e) {
|
|
2561
|
+
// Knowledge fetch failed, continue without it
|
|
2562
|
+
}
|
|
2563
|
+
}
|
|
2564
|
+
} catch (e) {
|
|
2565
|
+
context.plan_error = e.message;
|
|
2566
|
+
}
|
|
2567
|
+
}
|
|
2568
|
+
|
|
2569
|
+
context.recommendation = "Review the statistics, needs_attention, and knowledge entries before starting work.";
|
|
2570
|
+
return formatResponse(context);
|
|
2571
|
+
}
|
|
2572
|
+
|
|
2573
|
+
// ===== GOALS HEALTH DASHBOARD =====
|
|
2574
|
+
if (name === "check_goals_health") {
|
|
2575
|
+
const { status_filter } = args || {};
|
|
2576
|
+
const result = await apiClient.goals.getDashboard();
|
|
2577
|
+
|
|
2578
|
+
let goals = result.goals || result;
|
|
2579
|
+
if (status_filter && Array.isArray(goals)) {
|
|
2580
|
+
goals = goals.filter(g => g.health_status === status_filter || g.status === status_filter);
|
|
2581
|
+
}
|
|
2582
|
+
|
|
2583
|
+
return formatResponse({
|
|
2584
|
+
...result,
|
|
2585
|
+
goals,
|
|
2586
|
+
tip: "Prioritize: stale goals first, then at_risk, then on_track."
|
|
2587
|
+
});
|
|
2588
|
+
}
|
|
2589
|
+
|
|
2590
|
+
// ===== TASK CLAIMING =====
|
|
2591
|
+
if (name === "claim_task") {
|
|
2592
|
+
const { task_id, plan_id, ttl_minutes = 30 } = args;
|
|
2593
|
+
const result = await apiClient.nodes.claimTask(plan_id, task_id, 'mcp-agent', ttl_minutes);
|
|
2594
|
+
return formatResponse({
|
|
2595
|
+
success: true,
|
|
2596
|
+
message: `Task ${task_id} claimed for ${ttl_minutes} minutes`,
|
|
2597
|
+
...result,
|
|
2598
|
+
tip: "Remember to release the task when done, or it will auto-expire."
|
|
2599
|
+
});
|
|
2600
|
+
}
|
|
2601
|
+
|
|
2602
|
+
if (name === "release_task") {
|
|
2603
|
+
const { task_id, plan_id } = args;
|
|
2604
|
+
const result = await apiClient.nodes.releaseTask(plan_id, task_id, 'mcp-agent');
|
|
2605
|
+
return formatResponse({
|
|
2606
|
+
success: true,
|
|
2607
|
+
message: `Task ${task_id} released`,
|
|
2608
|
+
...result
|
|
2609
|
+
});
|
|
2610
|
+
}
|
|
2611
|
+
|
|
808
2612
|
// Tool not found
|
|
809
2613
|
throw new Error(`Unknown tool: ${name}`);
|
|
810
2614
|
} catch (error) {
|
|
@@ -935,6 +2739,26 @@ function buildNodeHierarchy(nodes, includeDetails = false) {
|
|
|
935
2739
|
return rootNodes;
|
|
936
2740
|
}
|
|
937
2741
|
|
|
2742
|
+
/**
|
|
2743
|
+
* Flatten a hierarchical node structure into a flat array
|
|
2744
|
+
*/
|
|
2745
|
+
function flattenNodes(nodes) {
|
|
2746
|
+
const flat = [];
|
|
2747
|
+
|
|
2748
|
+
const processNode = (node) => {
|
|
2749
|
+
flat.push(node);
|
|
2750
|
+
if (node.children && node.children.length > 0) {
|
|
2751
|
+
node.children.forEach(processNode);
|
|
2752
|
+
}
|
|
2753
|
+
};
|
|
2754
|
+
|
|
2755
|
+
if (Array.isArray(nodes)) {
|
|
2756
|
+
nodes.forEach(processNode);
|
|
2757
|
+
}
|
|
2758
|
+
|
|
2759
|
+
return flat;
|
|
2760
|
+
}
|
|
2761
|
+
|
|
938
2762
|
/**
|
|
939
2763
|
* Calculate plan statistics
|
|
940
2764
|
*/
|