agent-planner-mcp 0.8.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENT_GUIDE.md +58 -243
- package/README.md +30 -0
- package/SKILL.md +111 -418
- package/package.json +3 -1
- package/src/tools/bdi/_shared.js +29 -0
- package/src/tools/bdi/beliefs.js +451 -0
- package/src/tools/bdi/desires.js +163 -0
- package/src/tools/bdi/index.js +46 -0
- package/src/tools/bdi/intentions.js +532 -0
- package/src/tools/bdi/utility.js +73 -0
- package/src/tools.js +34 -2629
package/src/tools.js
CHANGED
|
@@ -1,2659 +1,64 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* MCP Tools
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* -
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* - Rich context retrieval
|
|
9
|
-
* - Text responses for Claude Desktop compatibility
|
|
2
|
+
* MCP Tools — v0.9.0 BDI-aligned surface.
|
|
3
|
+
*
|
|
4
|
+
* 15 tools across Belief / Desire / Intention / Utility namespaces. The legacy
|
|
5
|
+
* 63-tool CRUD-shaped surface was removed in v0.9.0 — see ../docs/MIGRATION_v0.9.md
|
|
6
|
+
* for the mapping from old tool names to new ones, and ../docs/MCP_REDESIGN_PLAN.md
|
|
7
|
+
* for the design rationale.
|
|
10
8
|
*/
|
|
11
9
|
|
|
12
10
|
const { ListToolsRequestSchema, CallToolRequestSchema } = require('@modelcontextprotocol/sdk/types.js');
|
|
13
11
|
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}`; }
|
|
12
|
+
const { bdiToolDefinitions, bdiToolHandler, bdiToolNames } = require('./tools/bdi');
|
|
18
13
|
|
|
19
14
|
/**
|
|
20
|
-
*
|
|
21
|
-
*/
|
|
22
|
-
function formatResponse(data) {
|
|
23
|
-
// If data is an error object with a message, return just the message
|
|
24
|
-
if (data && data.error) {
|
|
25
|
-
return {
|
|
26
|
-
isError: true,
|
|
27
|
-
content: [
|
|
28
|
-
{
|
|
29
|
-
type: "text",
|
|
30
|
-
text: data.error
|
|
31
|
-
}
|
|
32
|
-
]
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// For successful responses, stringify the data
|
|
37
|
-
return {
|
|
38
|
-
content: [
|
|
39
|
-
{
|
|
40
|
-
type: "text",
|
|
41
|
-
text: JSON.stringify(data, null, 2)
|
|
42
|
-
}
|
|
43
|
-
]
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Setup tools for the MCP server
|
|
15
|
+
* Wire BDI tools into an MCP server.
|
|
49
16
|
* @param {Server} server - MCP server instance
|
|
50
|
-
* @param {Object} [apiClientOverride] - Per-session API client (HTTP mode)
|
|
17
|
+
* @param {Object} [apiClientOverride] - Per-session API client (HTTP mode); falls back to default (stdio mode)
|
|
51
18
|
*/
|
|
52
19
|
function setupTools(server, apiClientOverride) {
|
|
53
20
|
const apiClient = apiClientOverride || defaultApiClient;
|
|
54
|
-
// Suppress console logs when not in debug mode
|
|
55
|
-
if (process.env.NODE_ENV !== 'development') {
|
|
56
|
-
// Silent mode for production
|
|
57
|
-
} else {
|
|
58
|
-
console.error('Setting up MCP tools...');
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// Handler for listing available tools
|
|
62
|
-
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
63
|
-
return {
|
|
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
|
-
// COHERENCE - Check alignment across goals/plans/knowledge
|
|
183
|
-
// ========================================
|
|
184
|
-
{
|
|
185
|
-
name: "check_coherence_pending",
|
|
186
|
-
description: "Check what needs coherence review. Returns stale plans and goals that have changed since their last coherence check. Call this at the start of a maintenance cycle to discover what needs attention. Uses timestamp comparison (updated_at vs coherence_checked_at) — no expensive processing.",
|
|
187
|
-
inputSchema: {
|
|
188
|
-
type: "object",
|
|
189
|
-
properties: {}
|
|
190
|
-
}
|
|
191
|
-
},
|
|
192
|
-
{
|
|
193
|
-
name: "run_coherence_check",
|
|
194
|
-
description: "Run a coherence check on a specific plan. Evaluates quality (coverage, specificity, ordering, knowledge completeness), flags contradictions, and stamps the plan as checked. Returns quality breakdown with sub-scores and rationale.",
|
|
195
|
-
inputSchema: {
|
|
196
|
-
type: "object",
|
|
197
|
-
properties: {
|
|
198
|
-
plan_id: { type: "string", description: "Plan ID to check" },
|
|
199
|
-
goal_id: { type: "string", description: "Optional goal ID to evaluate coverage against" }
|
|
200
|
-
},
|
|
201
|
-
required: ["plan_id"]
|
|
202
|
-
}
|
|
203
|
-
},
|
|
204
|
-
{
|
|
205
|
-
name: "assess_goal_quality",
|
|
206
|
-
description: "Assess how well-defined a goal is. Evaluates 5 dimensions: clarity (title+description), measurability (success criteria), actionability (linked plans), knowledge grounding (related facts), and commitment (desire vs intention, deadline). Returns score, dimension breakdown, and specific improvement suggestions. Use this when helping users define or refine goals.",
|
|
207
|
-
inputSchema: {
|
|
208
|
-
type: "object",
|
|
209
|
-
properties: {
|
|
210
|
-
goal_id: { type: "string", description: "Goal ID to assess" }
|
|
211
|
-
},
|
|
212
|
-
required: ["goal_id"]
|
|
213
|
-
}
|
|
214
|
-
},
|
|
215
|
-
|
|
216
|
-
// ========================================
|
|
217
|
-
// CONTEXT LOADING - Get everything you need
|
|
218
|
-
// Use before starting work on a plan/goal
|
|
219
|
-
// ========================================
|
|
220
|
-
{
|
|
221
|
-
name: "get_my_tasks",
|
|
222
|
-
description: "Get tasks that need attention - blocked tasks, in-progress tasks, and next tasks to start. Perfect for status check-ins.",
|
|
223
|
-
inputSchema: {
|
|
224
|
-
type: "object",
|
|
225
|
-
properties: {
|
|
226
|
-
plan_id: { type: "string", description: "Specific plan to check (optional - checks all if not provided)" },
|
|
227
|
-
status: {
|
|
228
|
-
type: "array",
|
|
229
|
-
items: { type: "string" },
|
|
230
|
-
default: ["blocked", "in_progress"],
|
|
231
|
-
description: "Task statuses to include"
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
},
|
|
236
|
-
|
|
237
|
-
// ========================================
|
|
238
|
-
// MARKDOWN EXPORT/IMPORT - Filesystem pattern
|
|
239
|
-
// ========================================
|
|
240
|
-
{
|
|
241
|
-
name: "export_plan_markdown",
|
|
242
|
-
description: "Export a plan as markdown text. Useful for reviewing plans in text format or saving to files.",
|
|
243
|
-
inputSchema: {
|
|
244
|
-
type: "object",
|
|
245
|
-
properties: {
|
|
246
|
-
plan_id: { type: "string", description: "Plan to export" },
|
|
247
|
-
include_descriptions: { type: "boolean", default: true, description: "Include task descriptions" },
|
|
248
|
-
include_status: { type: "boolean", default: true, description: "Include status indicators" }
|
|
249
|
-
},
|
|
250
|
-
required: ["plan_id"]
|
|
251
|
-
}
|
|
252
|
-
},
|
|
253
|
-
{
|
|
254
|
-
name: "import_plan_markdown",
|
|
255
|
-
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.",
|
|
256
|
-
inputSchema: {
|
|
257
|
-
type: "object",
|
|
258
|
-
properties: {
|
|
259
|
-
markdown: {
|
|
260
|
-
type: "string",
|
|
261
|
-
description: "Markdown text to parse. Use # for title, ## for phases, - for tasks"
|
|
262
|
-
},
|
|
263
|
-
title: {
|
|
264
|
-
type: "string",
|
|
265
|
-
description: "Plan title (optional - extracted from first # heading if not provided)"
|
|
266
|
-
},
|
|
267
|
-
goal_id: { type: "string", description: "Optionally link to a goal" }
|
|
268
|
-
},
|
|
269
|
-
required: ["markdown"]
|
|
270
|
-
}
|
|
271
|
-
},
|
|
272
|
-
|
|
273
|
-
// ===== UNIFIED SEARCH TOOL =====
|
|
274
|
-
{
|
|
275
|
-
name: "search",
|
|
276
|
-
description: "Universal search tool for plans, nodes, and content",
|
|
277
|
-
inputSchema: {
|
|
278
|
-
type: "object",
|
|
279
|
-
properties: {
|
|
280
|
-
scope: {
|
|
281
|
-
type: "string",
|
|
282
|
-
description: "Search scope",
|
|
283
|
-
enum: ["global", "plans", "plan", "node"],
|
|
284
|
-
default: "global"
|
|
285
|
-
},
|
|
286
|
-
scope_id: {
|
|
287
|
-
type: "string",
|
|
288
|
-
description: "Plan ID (if scope is 'plan') or Node ID (if scope is 'node')"
|
|
289
|
-
},
|
|
290
|
-
query: {
|
|
291
|
-
type: "string",
|
|
292
|
-
description: "Search query"
|
|
293
|
-
},
|
|
294
|
-
filters: {
|
|
295
|
-
type: "object",
|
|
296
|
-
description: "Optional filters",
|
|
297
|
-
properties: {
|
|
298
|
-
status: {
|
|
299
|
-
type: "string",
|
|
300
|
-
description: "Filter by status",
|
|
301
|
-
enum: ["draft", "active", "completed", "archived", "not_started", "in_progress", "blocked"]
|
|
302
|
-
},
|
|
303
|
-
type: {
|
|
304
|
-
type: "string",
|
|
305
|
-
description: "Filter by type",
|
|
306
|
-
enum: ["plan", "node", "phase", "task", "milestone", "log"]
|
|
307
|
-
},
|
|
308
|
-
limit: {
|
|
309
|
-
type: "integer",
|
|
310
|
-
description: "Maximum number of results",
|
|
311
|
-
default: 20
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
},
|
|
316
|
-
required: ["query"]
|
|
317
|
-
}
|
|
318
|
-
},
|
|
319
|
-
|
|
320
|
-
// ===== PLAN MANAGEMENT TOOLS =====
|
|
321
|
-
{
|
|
322
|
-
name: "list_plans",
|
|
323
|
-
description: "List plans. By default excludes completed/archived plans — set include_completed to true to see all.",
|
|
324
|
-
inputSchema: {
|
|
325
|
-
type: "object",
|
|
326
|
-
properties: {
|
|
327
|
-
status: {
|
|
328
|
-
type: "string",
|
|
329
|
-
description: "Optional filter by plan status",
|
|
330
|
-
enum: ["draft", "active", "completed", "archived"]
|
|
331
|
-
},
|
|
332
|
-
include_completed: {
|
|
333
|
-
type: "boolean",
|
|
334
|
-
description: "If true, include completed and archived plans (default: false)",
|
|
335
|
-
default: false
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
},
|
|
340
|
-
{
|
|
341
|
-
name: "create_plan",
|
|
342
|
-
description: "Create a new plan",
|
|
343
|
-
inputSchema: {
|
|
344
|
-
type: "object",
|
|
345
|
-
properties: {
|
|
346
|
-
title: { type: "string", description: "Plan title" },
|
|
347
|
-
description: { type: "string", description: "Plan description" },
|
|
348
|
-
status: {
|
|
349
|
-
type: "string",
|
|
350
|
-
description: "Plan status",
|
|
351
|
-
enum: ["draft", "active", "completed", "archived"],
|
|
352
|
-
default: "draft"
|
|
353
|
-
}
|
|
354
|
-
},
|
|
355
|
-
required: ["title"]
|
|
356
|
-
}
|
|
357
|
-
},
|
|
358
|
-
{
|
|
359
|
-
name: "update_plan",
|
|
360
|
-
description: "Update an existing plan",
|
|
361
|
-
inputSchema: {
|
|
362
|
-
type: "object",
|
|
363
|
-
properties: {
|
|
364
|
-
plan_id: { type: "string", description: "Plan ID" },
|
|
365
|
-
title: { type: "string", description: "New plan title" },
|
|
366
|
-
description: { type: "string", description: "New plan description" },
|
|
367
|
-
status: {
|
|
368
|
-
type: "string",
|
|
369
|
-
description: "New plan status",
|
|
370
|
-
enum: ["draft", "active", "completed", "archived"]
|
|
371
|
-
}
|
|
372
|
-
},
|
|
373
|
-
required: ["plan_id"]
|
|
374
|
-
}
|
|
375
|
-
},
|
|
376
|
-
{
|
|
377
|
-
name: "delete_plan",
|
|
378
|
-
description: "Delete a plan",
|
|
379
|
-
inputSchema: {
|
|
380
|
-
type: "object",
|
|
381
|
-
properties: {
|
|
382
|
-
plan_id: { type: "string", description: "Plan ID to delete" }
|
|
383
|
-
},
|
|
384
|
-
required: ["plan_id"]
|
|
385
|
-
}
|
|
386
|
-
},
|
|
387
|
-
{
|
|
388
|
-
name: "share_plan",
|
|
389
|
-
description: "Share a plan by making it public or private. Public plans can be viewed by anyone with the link.",
|
|
390
|
-
inputSchema: {
|
|
391
|
-
type: "object",
|
|
392
|
-
properties: {
|
|
393
|
-
plan_id: { type: "string", description: "Plan ID to share" },
|
|
394
|
-
visibility: {
|
|
395
|
-
type: "string",
|
|
396
|
-
description: "Plan visibility setting",
|
|
397
|
-
enum: ["public", "private"],
|
|
398
|
-
default: "public"
|
|
399
|
-
},
|
|
400
|
-
github_repo_owner: {
|
|
401
|
-
type: "string",
|
|
402
|
-
description: "GitHub repository owner (optional, for linking public plans to a repo)"
|
|
403
|
-
},
|
|
404
|
-
github_repo_name: {
|
|
405
|
-
type: "string",
|
|
406
|
-
description: "GitHub repository name (optional, for linking public plans to a repo)"
|
|
407
|
-
}
|
|
408
|
-
},
|
|
409
|
-
required: ["plan_id"]
|
|
410
|
-
}
|
|
411
|
-
},
|
|
412
|
-
|
|
413
|
-
// ===== NODE MANAGEMENT TOOLS =====
|
|
414
|
-
{
|
|
415
|
-
name: "create_node",
|
|
416
|
-
description: "Create a new node in a plan",
|
|
417
|
-
inputSchema: {
|
|
418
|
-
type: "object",
|
|
419
|
-
properties: {
|
|
420
|
-
plan_id: { type: "string", description: "Plan ID" },
|
|
421
|
-
parent_id: { type: "string", description: "Parent node ID (optional, defaults to root)" },
|
|
422
|
-
node_type: {
|
|
423
|
-
type: "string",
|
|
424
|
-
description: "Node type",
|
|
425
|
-
enum: ["phase", "task", "milestone"]
|
|
426
|
-
},
|
|
427
|
-
title: { type: "string", description: "Node title" },
|
|
428
|
-
description: { type: "string", description: "Node description" },
|
|
429
|
-
status: {
|
|
430
|
-
type: "string",
|
|
431
|
-
description: "Node status",
|
|
432
|
-
enum: ["not_started", "in_progress", "completed", "blocked", "plan_ready"],
|
|
433
|
-
default: "not_started"
|
|
434
|
-
},
|
|
435
|
-
context: { type: "string", description: "Additional context for the node" },
|
|
436
|
-
agent_instructions: { type: "string", description: "Instructions for AI agents working on this node" },
|
|
437
|
-
acceptance_criteria: { type: "string", description: "Criteria for node completion" },
|
|
438
|
-
due_date: { type: "string", description: "Due date (ISO format)" },
|
|
439
|
-
metadata: { type: "object", description: "Additional metadata" },
|
|
440
|
-
task_mode: {
|
|
441
|
-
type: "string",
|
|
442
|
-
description: "RPI workflow mode for the node",
|
|
443
|
-
enum: ["research", "plan", "implement", "free"],
|
|
444
|
-
default: "free"
|
|
445
|
-
}
|
|
446
|
-
},
|
|
447
|
-
required: ["plan_id", "node_type", "title"]
|
|
448
|
-
}
|
|
449
|
-
},
|
|
450
|
-
{
|
|
451
|
-
name: "update_node",
|
|
452
|
-
description: "Update a node's properties",
|
|
453
|
-
inputSchema: {
|
|
454
|
-
type: "object",
|
|
455
|
-
properties: {
|
|
456
|
-
plan_id: { type: "string", description: "Plan ID" },
|
|
457
|
-
node_id: { type: "string", description: "Node ID" },
|
|
458
|
-
title: { type: "string", description: "New node title" },
|
|
459
|
-
description: { type: "string", description: "New node description" },
|
|
460
|
-
status: {
|
|
461
|
-
type: "string",
|
|
462
|
-
description: "New node status",
|
|
463
|
-
enum: ["not_started", "in_progress", "completed", "blocked", "plan_ready"]
|
|
464
|
-
},
|
|
465
|
-
context: { type: "string", description: "New context" },
|
|
466
|
-
agent_instructions: { type: "string", description: "New agent instructions" },
|
|
467
|
-
acceptance_criteria: { type: "string", description: "New acceptance criteria" },
|
|
468
|
-
due_date: { type: "string", description: "New due date (ISO format)" },
|
|
469
|
-
metadata: { type: "object", description: "New metadata" },
|
|
470
|
-
task_mode: {
|
|
471
|
-
type: "string",
|
|
472
|
-
description: "RPI workflow mode for the node",
|
|
473
|
-
enum: ["research", "plan", "implement", "free"]
|
|
474
|
-
}
|
|
475
|
-
},
|
|
476
|
-
required: ["plan_id", "node_id"]
|
|
477
|
-
}
|
|
478
|
-
},
|
|
479
|
-
{
|
|
480
|
-
name: "delete_node",
|
|
481
|
-
description: "Delete a node and all its children",
|
|
482
|
-
inputSchema: {
|
|
483
|
-
type: "object",
|
|
484
|
-
properties: {
|
|
485
|
-
plan_id: { type: "string", description: "Plan ID" },
|
|
486
|
-
node_id: { type: "string", description: "Node ID to delete" }
|
|
487
|
-
},
|
|
488
|
-
required: ["plan_id", "node_id"]
|
|
489
|
-
}
|
|
490
|
-
},
|
|
491
|
-
{
|
|
492
|
-
name: "move_node",
|
|
493
|
-
description: "Move a node to a different parent or position",
|
|
494
|
-
inputSchema: {
|
|
495
|
-
type: "object",
|
|
496
|
-
properties: {
|
|
497
|
-
plan_id: { type: "string", description: "Plan ID" },
|
|
498
|
-
node_id: { type: "string", description: "Node ID to move" },
|
|
499
|
-
parent_id: { type: "string", description: "New parent node ID" },
|
|
500
|
-
order_index: { type: "integer", description: "New position index" }
|
|
501
|
-
},
|
|
502
|
-
required: ["plan_id", "node_id"]
|
|
503
|
-
}
|
|
504
|
-
},
|
|
505
|
-
{
|
|
506
|
-
name: "get_node_ancestry",
|
|
507
|
-
description: "Get the path from root to a specific node",
|
|
508
|
-
inputSchema: {
|
|
509
|
-
type: "object",
|
|
510
|
-
properties: {
|
|
511
|
-
plan_id: { type: "string", description: "Plan ID" },
|
|
512
|
-
node_id: { type: "string", description: "Node ID" }
|
|
513
|
-
},
|
|
514
|
-
required: ["plan_id", "node_id"]
|
|
515
|
-
}
|
|
516
|
-
},
|
|
517
|
-
|
|
518
|
-
// ===== DEPENDENCY TOOLS =====
|
|
519
|
-
{
|
|
520
|
-
name: "create_dependency",
|
|
521
|
-
description: "Create a dependency edge between two nodes in a plan. Source 'blocks' target by default.",
|
|
522
|
-
inputSchema: {
|
|
523
|
-
type: "object",
|
|
524
|
-
properties: {
|
|
525
|
-
plan_id: { type: "string", description: "Plan ID" },
|
|
526
|
-
source_node_id: { type: "string", description: "Source node ID (the blocker)" },
|
|
527
|
-
target_node_id: { type: "string", description: "Target node ID (the blocked)" },
|
|
528
|
-
dependency_type: {
|
|
529
|
-
type: "string",
|
|
530
|
-
description: "Type of dependency",
|
|
531
|
-
enum: ["blocks", "requires", "relates_to"],
|
|
532
|
-
default: "blocks"
|
|
533
|
-
},
|
|
534
|
-
weight: { type: "integer", description: "Edge weight (default 1)", default: 1 },
|
|
535
|
-
metadata: { type: "object", description: "Additional metadata" }
|
|
536
|
-
},
|
|
537
|
-
required: ["plan_id", "source_node_id", "target_node_id"]
|
|
538
|
-
}
|
|
539
|
-
},
|
|
540
|
-
{
|
|
541
|
-
name: "delete_dependency",
|
|
542
|
-
description: "Delete a dependency edge",
|
|
543
|
-
inputSchema: {
|
|
544
|
-
type: "object",
|
|
545
|
-
properties: {
|
|
546
|
-
plan_id: { type: "string", description: "Plan ID" },
|
|
547
|
-
dependency_id: { type: "string", description: "Dependency edge ID" }
|
|
548
|
-
},
|
|
549
|
-
required: ["plan_id", "dependency_id"]
|
|
550
|
-
}
|
|
551
|
-
},
|
|
552
|
-
{
|
|
553
|
-
name: "list_dependencies",
|
|
554
|
-
description: "List all dependency edges in a plan",
|
|
555
|
-
inputSchema: {
|
|
556
|
-
type: "object",
|
|
557
|
-
properties: {
|
|
558
|
-
plan_id: { type: "string", description: "Plan ID" }
|
|
559
|
-
},
|
|
560
|
-
required: ["plan_id"]
|
|
561
|
-
}
|
|
562
|
-
},
|
|
563
|
-
{
|
|
564
|
-
name: "get_node_dependencies",
|
|
565
|
-
description: "Get upstream and downstream dependencies for a node",
|
|
566
|
-
inputSchema: {
|
|
567
|
-
type: "object",
|
|
568
|
-
properties: {
|
|
569
|
-
plan_id: { type: "string", description: "Plan ID" },
|
|
570
|
-
node_id: { type: "string", description: "Node ID" },
|
|
571
|
-
direction: {
|
|
572
|
-
type: "string",
|
|
573
|
-
description: "Direction to query",
|
|
574
|
-
enum: ["upstream", "downstream", "both"],
|
|
575
|
-
default: "both"
|
|
576
|
-
}
|
|
577
|
-
},
|
|
578
|
-
required: ["plan_id", "node_id"]
|
|
579
|
-
}
|
|
580
|
-
},
|
|
581
|
-
|
|
582
|
-
// ===== RPI WORKFLOW =====
|
|
583
|
-
{
|
|
584
|
-
name: "create_rpi_chain",
|
|
585
|
-
description: "Create a Research→Plan→Implement task chain with automatic dependency edges. The three tasks are linked: Research blocks Plan, Plan blocks Implement.",
|
|
586
|
-
inputSchema: {
|
|
587
|
-
type: "object",
|
|
588
|
-
properties: {
|
|
589
|
-
plan_id: { type: "string", description: "Plan ID" },
|
|
590
|
-
title: { type: "string", description: "Base title for the chain (e.g. 'Auth refactor')" },
|
|
591
|
-
description: { type: "string", description: "Description for the research task" },
|
|
592
|
-
parent_id: { type: "string", description: "Parent node ID (optional, defaults to root)" }
|
|
593
|
-
},
|
|
594
|
-
required: ["plan_id", "title"]
|
|
595
|
-
}
|
|
596
|
-
},
|
|
597
|
-
|
|
598
|
-
// ===== ANALYSIS TOOLS =====
|
|
599
|
-
{
|
|
600
|
-
name: "analyze_impact",
|
|
601
|
-
description: "Analyze what happens if a node is delayed, blocked, or removed. Shows directly and transitively affected nodes.",
|
|
602
|
-
inputSchema: {
|
|
603
|
-
type: "object",
|
|
604
|
-
properties: {
|
|
605
|
-
plan_id: { type: "string", description: "Plan ID" },
|
|
606
|
-
node_id: { type: "string", description: "Node ID to analyze" },
|
|
607
|
-
scenario: {
|
|
608
|
-
type: "string",
|
|
609
|
-
description: "Impact scenario",
|
|
610
|
-
enum: ["delay", "block", "remove"],
|
|
611
|
-
default: "block"
|
|
612
|
-
}
|
|
613
|
-
},
|
|
614
|
-
required: ["plan_id", "node_id"]
|
|
615
|
-
}
|
|
616
|
-
},
|
|
617
|
-
{
|
|
618
|
-
name: "get_critical_path",
|
|
619
|
-
description: "Find the critical path (longest dependency chain) through incomplete tasks in a plan",
|
|
620
|
-
inputSchema: {
|
|
621
|
-
type: "object",
|
|
622
|
-
properties: {
|
|
623
|
-
plan_id: { type: "string", description: "Plan ID" }
|
|
624
|
-
},
|
|
625
|
-
required: ["plan_id"]
|
|
626
|
-
}
|
|
627
|
-
},
|
|
628
|
-
|
|
629
|
-
// ===== PROGRESSIVE CONTEXT TOOLS =====
|
|
630
|
-
{
|
|
631
|
-
name: "get_task_context",
|
|
632
|
-
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.",
|
|
633
|
-
inputSchema: {
|
|
634
|
-
type: "object",
|
|
635
|
-
properties: {
|
|
636
|
-
node_id: { type: "string", description: "Task/node ID to get context for" },
|
|
637
|
-
depth: {
|
|
638
|
-
type: "integer",
|
|
639
|
-
description: "Context depth 1-4 (default 2). Start with 2, go deeper if needed.",
|
|
640
|
-
minimum: 1,
|
|
641
|
-
maximum: 4,
|
|
642
|
-
default: 2
|
|
643
|
-
},
|
|
644
|
-
token_budget: {
|
|
645
|
-
type: "integer",
|
|
646
|
-
description: "Max estimated tokens (0 = unlimited). Use to stay within context window limits.",
|
|
647
|
-
default: 0
|
|
648
|
-
},
|
|
649
|
-
log_limit: {
|
|
650
|
-
type: "integer",
|
|
651
|
-
description: "Max recent logs to include per node",
|
|
652
|
-
default: 10
|
|
653
|
-
},
|
|
654
|
-
include_research: {
|
|
655
|
-
type: "boolean",
|
|
656
|
-
description: "Include research outputs from RPI chain siblings (for implement tasks)",
|
|
657
|
-
default: true
|
|
658
|
-
}
|
|
659
|
-
},
|
|
660
|
-
required: ["node_id"]
|
|
661
|
-
}
|
|
662
|
-
},
|
|
663
|
-
{
|
|
664
|
-
name: "suggest_next_tasks",
|
|
665
|
-
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.",
|
|
666
|
-
inputSchema: {
|
|
667
|
-
type: "object",
|
|
668
|
-
properties: {
|
|
669
|
-
plan_id: { type: "string", description: "Plan ID" },
|
|
670
|
-
limit: {
|
|
671
|
-
type: "integer",
|
|
672
|
-
description: "Maximum suggestions to return",
|
|
673
|
-
default: 5
|
|
674
|
-
}
|
|
675
|
-
},
|
|
676
|
-
required: ["plan_id"]
|
|
677
|
-
}
|
|
678
|
-
},
|
|
679
|
-
|
|
680
|
-
// ===== LOGGING TOOLS (Replaces Comments) =====
|
|
681
|
-
{
|
|
682
|
-
name: "add_log",
|
|
683
|
-
description: "Add a log entry to a node (replaces comments)",
|
|
684
|
-
inputSchema: {
|
|
685
|
-
type: "object",
|
|
686
|
-
properties: {
|
|
687
|
-
plan_id: { type: "string", description: "Plan ID" },
|
|
688
|
-
node_id: { type: "string", description: "Node ID" },
|
|
689
|
-
content: { type: "string", description: "Log content" },
|
|
690
|
-
log_type: {
|
|
691
|
-
type: "string",
|
|
692
|
-
description: "Type of log entry",
|
|
693
|
-
enum: ["progress", "reasoning", "challenge", "decision", "comment"],
|
|
694
|
-
default: "comment"
|
|
695
|
-
},
|
|
696
|
-
tags: {
|
|
697
|
-
type: "array",
|
|
698
|
-
description: "Tags for categorizing the log entry",
|
|
699
|
-
items: { type: "string" }
|
|
700
|
-
}
|
|
701
|
-
},
|
|
702
|
-
required: ["plan_id", "node_id", "content"]
|
|
703
|
-
}
|
|
704
|
-
},
|
|
705
|
-
{
|
|
706
|
-
name: "get_logs",
|
|
707
|
-
description: "Get log entries for a node",
|
|
708
|
-
inputSchema: {
|
|
709
|
-
type: "object",
|
|
710
|
-
properties: {
|
|
711
|
-
plan_id: { type: "string", description: "Plan ID" },
|
|
712
|
-
node_id: { type: "string", description: "Node ID" },
|
|
713
|
-
log_type: {
|
|
714
|
-
type: "string",
|
|
715
|
-
description: "Filter by log type",
|
|
716
|
-
enum: ["progress", "reasoning", "challenge", "decision", "comment"]
|
|
717
|
-
},
|
|
718
|
-
limit: {
|
|
719
|
-
type: "integer",
|
|
720
|
-
description: "Maximum number of logs to return",
|
|
721
|
-
default: 50
|
|
722
|
-
}
|
|
723
|
-
},
|
|
724
|
-
required: ["plan_id", "node_id"]
|
|
725
|
-
}
|
|
726
|
-
},
|
|
727
|
-
|
|
728
|
-
// ===== BATCH OPERATIONS =====
|
|
729
|
-
{
|
|
730
|
-
name: "batch_update_nodes",
|
|
731
|
-
description: "Update multiple nodes at once",
|
|
732
|
-
inputSchema: {
|
|
733
|
-
type: "object",
|
|
734
|
-
properties: {
|
|
735
|
-
plan_id: { type: "string", description: "Plan ID" },
|
|
736
|
-
updates: {
|
|
737
|
-
type: "array",
|
|
738
|
-
description: "List of node updates",
|
|
739
|
-
items: {
|
|
740
|
-
type: "object",
|
|
741
|
-
properties: {
|
|
742
|
-
node_id: { type: "string", description: "Node ID" },
|
|
743
|
-
status: {
|
|
744
|
-
type: "string",
|
|
745
|
-
enum: ["not_started", "in_progress", "completed", "blocked", "plan_ready"]
|
|
746
|
-
},
|
|
747
|
-
title: { type: "string" },
|
|
748
|
-
description: { type: "string" }
|
|
749
|
-
},
|
|
750
|
-
required: ["node_id"]
|
|
751
|
-
}
|
|
752
|
-
}
|
|
753
|
-
},
|
|
754
|
-
required: ["plan_id", "updates"]
|
|
755
|
-
}
|
|
756
|
-
},
|
|
757
|
-
|
|
758
|
-
// ===== PLAN STRUCTURE & SUMMARY =====
|
|
759
|
-
{
|
|
760
|
-
name: "get_plan_structure",
|
|
761
|
-
description: "Get the hierarchical structure of a plan with minimal fields (id, parent_id, node_type, title, status, order_index). Use get_task_context for detailed information about specific nodes.",
|
|
762
|
-
inputSchema: {
|
|
763
|
-
type: "object",
|
|
764
|
-
properties: {
|
|
765
|
-
plan_id: { type: "string", description: "Plan ID" },
|
|
766
|
-
include_details: {
|
|
767
|
-
type: "boolean",
|
|
768
|
-
description: "Include full node details (description, context, agent_instructions, etc.). Default is false for efficient context usage.",
|
|
769
|
-
default: false
|
|
770
|
-
}
|
|
771
|
-
},
|
|
772
|
-
required: ["plan_id"]
|
|
773
|
-
}
|
|
774
|
-
},
|
|
775
|
-
{
|
|
776
|
-
name: "get_plan_summary",
|
|
777
|
-
description: "Get a comprehensive summary with statistics",
|
|
778
|
-
inputSchema: {
|
|
779
|
-
type: "object",
|
|
780
|
-
properties: {
|
|
781
|
-
plan_id: { type: "string", description: "Plan ID" }
|
|
782
|
-
},
|
|
783
|
-
required: ["plan_id"]
|
|
784
|
-
}
|
|
785
|
-
},
|
|
786
|
-
|
|
787
|
-
// ===== AGENT CONTEXT TOOLS (Leaf-up context loading) =====
|
|
788
|
-
{
|
|
789
|
-
name: "get_plan_context",
|
|
790
|
-
description: "Get plan-level context overview. Returns plan details, phase summaries (not full tree), linked goals, and organization. Use get_task_context for task-focused work.",
|
|
791
|
-
inputSchema: {
|
|
792
|
-
type: "object",
|
|
793
|
-
properties: {
|
|
794
|
-
plan_id: {
|
|
795
|
-
type: "string",
|
|
796
|
-
description: "Plan ID"
|
|
797
|
-
},
|
|
798
|
-
include_knowledge: {
|
|
799
|
-
type: "boolean",
|
|
800
|
-
description: "Include knowledge entries",
|
|
801
|
-
default: true
|
|
802
|
-
}
|
|
803
|
-
},
|
|
804
|
-
required: ["plan_id"]
|
|
805
|
-
}
|
|
806
|
-
},
|
|
807
|
-
|
|
808
|
-
// ===== ORGANIZATION TOOLS =====
|
|
809
|
-
{
|
|
810
|
-
name: "list_organizations",
|
|
811
|
-
description: "List all organizations the user is a member of",
|
|
812
|
-
inputSchema: {
|
|
813
|
-
type: "object",
|
|
814
|
-
properties: {}
|
|
815
|
-
}
|
|
816
|
-
},
|
|
817
|
-
{
|
|
818
|
-
name: "get_organization",
|
|
819
|
-
description: "Get organization details including member count and plan count",
|
|
820
|
-
inputSchema: {
|
|
821
|
-
type: "object",
|
|
822
|
-
properties: {
|
|
823
|
-
organization_id: { type: "string", description: "Organization ID" }
|
|
824
|
-
},
|
|
825
|
-
required: ["organization_id"]
|
|
826
|
-
}
|
|
827
|
-
},
|
|
828
|
-
{
|
|
829
|
-
name: "create_organization",
|
|
830
|
-
description: "Create a new organization. You become the owner.",
|
|
831
|
-
inputSchema: {
|
|
832
|
-
type: "object",
|
|
833
|
-
properties: {
|
|
834
|
-
name: { type: "string", description: "Organization name" },
|
|
835
|
-
description: { type: "string", description: "Organization description" },
|
|
836
|
-
slug: { type: "string", description: "URL-friendly slug (auto-generated if not provided)" }
|
|
837
|
-
},
|
|
838
|
-
required: ["name"]
|
|
839
|
-
}
|
|
840
|
-
},
|
|
841
|
-
{
|
|
842
|
-
name: "update_organization",
|
|
843
|
-
description: "Update organization details (owner only)",
|
|
844
|
-
inputSchema: {
|
|
845
|
-
type: "object",
|
|
846
|
-
properties: {
|
|
847
|
-
organization_id: { type: "string", description: "Organization ID" },
|
|
848
|
-
name: { type: "string", description: "New name" },
|
|
849
|
-
description: { type: "string", description: "New description" }
|
|
850
|
-
},
|
|
851
|
-
required: ["organization_id"]
|
|
852
|
-
}
|
|
853
|
-
},
|
|
854
|
-
|
|
855
|
-
// ===== GOAL TOOLS =====
|
|
856
|
-
{
|
|
857
|
-
name: "list_goals",
|
|
858
|
-
description: "List goals. By default returns only active goals — set include_inactive to true to see all.",
|
|
859
|
-
inputSchema: {
|
|
860
|
-
type: "object",
|
|
861
|
-
properties: {
|
|
862
|
-
organization_id: { type: "string", description: "Filter by organization ID" },
|
|
863
|
-
status: {
|
|
864
|
-
type: "string",
|
|
865
|
-
description: "Filter by status",
|
|
866
|
-
enum: ["active", "achieved", "at_risk", "abandoned"]
|
|
867
|
-
},
|
|
868
|
-
include_inactive: {
|
|
869
|
-
type: "boolean",
|
|
870
|
-
description: "If true, include achieved/paused/abandoned goals (default: false)",
|
|
871
|
-
default: false
|
|
872
|
-
}
|
|
873
|
-
}
|
|
874
|
-
}
|
|
875
|
-
},
|
|
876
|
-
{
|
|
877
|
-
name: "get_goal",
|
|
878
|
-
description: "Get goal details including linked plans",
|
|
879
|
-
inputSchema: {
|
|
880
|
-
type: "object",
|
|
881
|
-
properties: {
|
|
882
|
-
goal_id: { type: "string", description: "Goal ID" }
|
|
883
|
-
},
|
|
884
|
-
required: ["goal_id"]
|
|
885
|
-
}
|
|
886
|
-
},
|
|
887
|
-
{
|
|
888
|
-
name: "create_goal",
|
|
889
|
-
description: "Create a new goal within an organization",
|
|
890
|
-
inputSchema: {
|
|
891
|
-
type: "object",
|
|
892
|
-
properties: {
|
|
893
|
-
organization_id: { type: "string", description: "Organization ID" },
|
|
894
|
-
title: { type: "string", description: "Goal title" },
|
|
895
|
-
description: { type: "string", description: "Goal description" },
|
|
896
|
-
type: { type: "string", enum: ["outcome", "constraint", "metric", "principle"], description: "Goal type (default: outcome)" },
|
|
897
|
-
success_criteria: { type: "object", description: "Success criteria as JSON" },
|
|
898
|
-
priority: { type: "number", description: "Priority (higher = more important, default: 0)" },
|
|
899
|
-
parent_goal_id: { type: "string", description: "Parent goal ID for hierarchy" }
|
|
900
|
-
},
|
|
901
|
-
required: ["organization_id", "title"]
|
|
902
|
-
}
|
|
903
|
-
},
|
|
904
|
-
{
|
|
905
|
-
name: "update_goal",
|
|
906
|
-
description: "Update goal details or status",
|
|
907
|
-
inputSchema: {
|
|
908
|
-
type: "object",
|
|
909
|
-
properties: {
|
|
910
|
-
goal_id: { type: "string", description: "Goal ID" },
|
|
911
|
-
title: { type: "string", description: "New title" },
|
|
912
|
-
description: { type: "string", description: "New description" },
|
|
913
|
-
type: { type: "string", enum: ["outcome", "constraint", "metric", "principle"], description: "Goal type" },
|
|
914
|
-
status: {
|
|
915
|
-
type: "string",
|
|
916
|
-
description: "New status",
|
|
917
|
-
enum: ["active", "achieved", "paused", "abandoned"]
|
|
918
|
-
},
|
|
919
|
-
success_criteria: { type: "object", description: "Success criteria as JSON" },
|
|
920
|
-
priority: { type: "number", description: "Priority (higher = more important)" },
|
|
921
|
-
parent_goal_id: { type: "string", description: "Parent goal ID for hierarchy" }
|
|
922
|
-
},
|
|
923
|
-
required: ["goal_id"]
|
|
924
|
-
}
|
|
925
|
-
},
|
|
926
|
-
{
|
|
927
|
-
name: "link_plan_to_goal",
|
|
928
|
-
description: "Link a plan to a goal (shows the plan contributes to this goal)",
|
|
929
|
-
inputSchema: {
|
|
930
|
-
type: "object",
|
|
931
|
-
properties: {
|
|
932
|
-
goal_id: { type: "string", description: "Goal ID" },
|
|
933
|
-
plan_id: { type: "string", description: "Plan ID to link" }
|
|
934
|
-
},
|
|
935
|
-
required: ["goal_id", "plan_id"]
|
|
936
|
-
}
|
|
937
|
-
},
|
|
938
|
-
{
|
|
939
|
-
name: "unlink_plan_from_goal",
|
|
940
|
-
description: "Remove a plan-goal link",
|
|
941
|
-
inputSchema: {
|
|
942
|
-
type: "object",
|
|
943
|
-
properties: {
|
|
944
|
-
goal_id: { type: "string", description: "Goal ID" },
|
|
945
|
-
plan_id: { type: "string", description: "Plan ID to unlink" }
|
|
946
|
-
},
|
|
947
|
-
required: ["goal_id", "plan_id"]
|
|
948
|
-
}
|
|
949
|
-
},
|
|
950
|
-
|
|
951
|
-
// ===== CROSS-PLAN & EXTERNAL DEPENDENCY TOOLS =====
|
|
952
|
-
{
|
|
953
|
-
name: "create_cross_plan_dependency",
|
|
954
|
-
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.",
|
|
955
|
-
inputSchema: {
|
|
956
|
-
type: "object",
|
|
957
|
-
properties: {
|
|
958
|
-
source_node_id: { type: "string", description: "Source node ID (the blocker/prerequisite)" },
|
|
959
|
-
target_node_id: { type: "string", description: "Target node ID (the blocked/dependent task)" },
|
|
960
|
-
dependency_type: { type: "string", enum: ["blocks", "requires", "relates_to"], default: "blocks", description: "Edge type (default: blocks)" },
|
|
961
|
-
weight: { type: "number", description: "Edge weight (default 1)" }
|
|
962
|
-
},
|
|
963
|
-
required: ["source_node_id", "target_node_id"]
|
|
964
|
-
}
|
|
965
|
-
},
|
|
966
|
-
{
|
|
967
|
-
name: "list_cross_plan_dependencies",
|
|
968
|
-
description: "List all dependency edges that cross plan boundaries between specified plans.",
|
|
969
|
-
inputSchema: {
|
|
970
|
-
type: "object",
|
|
971
|
-
properties: {
|
|
972
|
-
plan_ids: {
|
|
973
|
-
type: "array",
|
|
974
|
-
items: { type: "string" },
|
|
975
|
-
description: "Plan IDs to check for cross-plan edges (at least 2)"
|
|
976
|
-
}
|
|
977
|
-
},
|
|
978
|
-
required: ["plan_ids"]
|
|
979
|
-
}
|
|
980
|
-
},
|
|
981
|
-
{
|
|
982
|
-
name: "create_external_dependency",
|
|
983
|
-
description: "Create an external dependency node representing a blocker outside the system (vendor API, legal approval, etc.). Optionally blocks a target task.",
|
|
984
|
-
inputSchema: {
|
|
985
|
-
type: "object",
|
|
986
|
-
properties: {
|
|
987
|
-
plan_id: { type: "string", description: "Plan to add the external dependency to" },
|
|
988
|
-
title: { type: "string", description: "External dependency title (e.g., 'Waiting for vendor API access')" },
|
|
989
|
-
description: { type: "string", description: "Details about the external dependency" },
|
|
990
|
-
url: { type: "string", description: "URL reference (ticket, docs, etc.)" },
|
|
991
|
-
blocks_node_id: { type: "string", description: "Node ID that this external dep blocks" }
|
|
992
|
-
},
|
|
993
|
-
required: ["plan_id", "title"]
|
|
994
|
-
}
|
|
995
|
-
},
|
|
996
|
-
|
|
997
|
-
// ===== GOAL-DEPENDENCY TOOLS =====
|
|
998
|
-
{
|
|
999
|
-
name: "goal_path",
|
|
1000
|
-
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.",
|
|
1001
|
-
inputSchema: {
|
|
1002
|
-
type: "object",
|
|
1003
|
-
properties: {
|
|
1004
|
-
goal_id: { type: "string", description: "Goal ID" },
|
|
1005
|
-
max_depth: { type: "number", description: "Max traversal depth (default 20)" }
|
|
1006
|
-
},
|
|
1007
|
-
required: ["goal_id"]
|
|
1008
|
-
}
|
|
1009
|
-
},
|
|
1010
|
-
{
|
|
1011
|
-
name: "goal_progress",
|
|
1012
|
-
description: "Get goal progress calculated from its dependency graph. Returns overall completion percentage and direct achiever progress.",
|
|
1013
|
-
inputSchema: {
|
|
1014
|
-
type: "object",
|
|
1015
|
-
properties: {
|
|
1016
|
-
goal_id: { type: "string", description: "Goal ID" }
|
|
1017
|
-
},
|
|
1018
|
-
required: ["goal_id"]
|
|
1019
|
-
}
|
|
1020
|
-
},
|
|
1021
|
-
{
|
|
1022
|
-
name: "add_achiever",
|
|
1023
|
-
description: "Link a task to a goal via an 'achieves' dependency edge. This declares that completing this task contributes to achieving the goal.",
|
|
1024
|
-
inputSchema: {
|
|
1025
|
-
type: "object",
|
|
1026
|
-
properties: {
|
|
1027
|
-
goal_id: { type: "string", description: "Goal ID" },
|
|
1028
|
-
node_id: { type: "string", description: "Task/node ID that achieves this goal" },
|
|
1029
|
-
weight: { type: "number", description: "Edge weight for critical path (default 1)" }
|
|
1030
|
-
},
|
|
1031
|
-
required: ["goal_id", "node_id"]
|
|
1032
|
-
}
|
|
1033
|
-
},
|
|
1034
|
-
{
|
|
1035
|
-
name: "remove_achiever",
|
|
1036
|
-
description: "Remove an achieves edge between a task and a goal",
|
|
1037
|
-
inputSchema: {
|
|
1038
|
-
type: "object",
|
|
1039
|
-
properties: {
|
|
1040
|
-
goal_id: { type: "string", description: "Goal ID" },
|
|
1041
|
-
dependency_id: { type: "string", description: "Dependency edge ID to remove" }
|
|
1042
|
-
},
|
|
1043
|
-
required: ["goal_id", "dependency_id"]
|
|
1044
|
-
}
|
|
1045
|
-
},
|
|
1046
|
-
{
|
|
1047
|
-
name: "goal_knowledge_gaps",
|
|
1048
|
-
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.",
|
|
1049
|
-
inputSchema: {
|
|
1050
|
-
type: "object",
|
|
1051
|
-
properties: {
|
|
1052
|
-
goal_id: { type: "string", description: "Goal ID" }
|
|
1053
|
-
},
|
|
1054
|
-
required: ["goal_id"]
|
|
1055
|
-
}
|
|
1056
|
-
},
|
|
1057
|
-
|
|
1058
|
-
// ===== GRAPHITI KNOWLEDGE GRAPH TOOLS =====
|
|
1059
|
-
{
|
|
1060
|
-
name: "add_learning",
|
|
1061
|
-
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.",
|
|
1062
|
-
inputSchema: {
|
|
1063
|
-
type: "object",
|
|
1064
|
-
properties: {
|
|
1065
|
-
content: { type: "string", description: "The knowledge content — be detailed. Include context, reasoning, and conclusions." },
|
|
1066
|
-
title: { type: "string", description: "Short title/name for the episode" },
|
|
1067
|
-
entry_type: { type: "string", enum: ["decision", "learning", "context", "constraint"], description: "Type of knowledge" },
|
|
1068
|
-
plan_id: { type: "string", description: "Plan ID this knowledge relates to (optional)" },
|
|
1069
|
-
node_id: { type: "string", description: "Node/task ID this knowledge relates to (optional)" }
|
|
1070
|
-
},
|
|
1071
|
-
required: ["content"]
|
|
1072
|
-
}
|
|
1073
|
-
},
|
|
1074
|
-
{
|
|
1075
|
-
name: "recall_knowledge",
|
|
1076
|
-
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.",
|
|
1077
|
-
inputSchema: {
|
|
1078
|
-
type: "object",
|
|
1079
|
-
properties: {
|
|
1080
|
-
query: { type: "string", description: "What to search for — be specific" },
|
|
1081
|
-
max_results: { type: "number", description: "Maximum results (default 10)", default: 10 }
|
|
1082
|
-
},
|
|
1083
|
-
required: ["query"]
|
|
1084
|
-
}
|
|
1085
|
-
},
|
|
1086
|
-
{
|
|
1087
|
-
name: "find_entities",
|
|
1088
|
-
description: "Search for entities (technologies, people, patterns, constraints) in the knowledge graph. Returns entity nodes with their relationships.",
|
|
1089
|
-
inputSchema: {
|
|
1090
|
-
type: "object",
|
|
1091
|
-
properties: {
|
|
1092
|
-
query: { type: "string", description: "Entity search query" },
|
|
1093
|
-
max_results: { type: "number", description: "Maximum results (default 10)", default: 10 }
|
|
1094
|
-
},
|
|
1095
|
-
required: ["query"]
|
|
1096
|
-
}
|
|
1097
|
-
},
|
|
1098
21
|
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
inputSchema: {
|
|
1103
|
-
type: "object",
|
|
1104
|
-
properties: {
|
|
1105
|
-
query: { type: "string", description: "Topic to check for contradictions" },
|
|
1106
|
-
max_results: { type: "number", description: "Maximum results (default 10)", default: 10 }
|
|
1107
|
-
},
|
|
1108
|
-
required: ["query"]
|
|
1109
|
-
}
|
|
1110
|
-
},
|
|
1111
|
-
{
|
|
1112
|
-
name: "get_recent_episodes",
|
|
1113
|
-
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.",
|
|
1114
|
-
inputSchema: {
|
|
1115
|
-
type: "object",
|
|
1116
|
-
properties: {
|
|
1117
|
-
max_episodes: { type: "number", description: "Maximum episodes to return (default 20)", default: 20 }
|
|
1118
|
-
}
|
|
1119
|
-
}
|
|
1120
|
-
},
|
|
22
|
+
if (process.env.NODE_ENV === 'development') {
|
|
23
|
+
console.error(`Setting up MCP tools (${bdiToolDefinitions.length} BDI tools)`);
|
|
24
|
+
}
|
|
1121
25
|
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
name: "get_started",
|
|
1125
|
-
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.",
|
|
1126
|
-
inputSchema: {
|
|
1127
|
-
type: "object",
|
|
1128
|
-
properties: {
|
|
1129
|
-
topic: {
|
|
1130
|
-
type: "string",
|
|
1131
|
-
enum: ["overview", "planning", "execution", "knowledge", "collaboration"],
|
|
1132
|
-
description: "Specific topic to learn about: 'overview' (system intro), 'planning' (creating plans), 'execution' (working through tasks), 'knowledge' (storing decisions/learnings), 'collaboration' (working with others)",
|
|
1133
|
-
default: "overview"
|
|
1134
|
-
}
|
|
1135
|
-
}
|
|
1136
|
-
}
|
|
1137
|
-
},
|
|
1138
|
-
]
|
|
1139
|
-
};
|
|
26
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
27
|
+
return { tools: bdiToolDefinitions };
|
|
1140
28
|
});
|
|
1141
|
-
|
|
1142
|
-
// Handler for calling tools
|
|
29
|
+
|
|
1143
30
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
1144
31
|
const { name, arguments: args } = request.params;
|
|
1145
|
-
|
|
1146
|
-
// Only log in development mode
|
|
32
|
+
|
|
1147
33
|
if (process.env.NODE_ENV === 'development') {
|
|
1148
|
-
console.error(`Calling tool: ${name}
|
|
34
|
+
console.error(`Calling tool: ${name}`);
|
|
1149
35
|
}
|
|
1150
|
-
|
|
1151
|
-
try {
|
|
1152
|
-
// ========================================
|
|
1153
|
-
// QUICK ACTIONS IMPLEMENTATIONS
|
|
1154
|
-
// ========================================
|
|
1155
|
-
|
|
1156
|
-
if (name === "quick_plan") {
|
|
1157
|
-
const { title, description, tasks, goal_id, organization_id } = args;
|
|
1158
|
-
|
|
1159
|
-
if (!tasks || tasks.length === 0) {
|
|
1160
|
-
return formatResponse({
|
|
1161
|
-
error: "At least one task is required",
|
|
1162
|
-
suggestion: "Provide an array of task titles in the 'tasks' parameter"
|
|
1163
|
-
});
|
|
1164
|
-
}
|
|
1165
|
-
|
|
1166
|
-
// Create the plan
|
|
1167
|
-
const planData = { title };
|
|
1168
|
-
if (description) planData.description = description;
|
|
1169
|
-
if (organization_id) planData.organization_id = organization_id;
|
|
1170
|
-
|
|
1171
|
-
const plan = await apiClient.plans.createPlan(planData);
|
|
1172
|
-
|
|
1173
|
-
// Get the root node
|
|
1174
|
-
const nodes = await apiClient.nodes.getNodes(plan.id);
|
|
1175
|
-
const rootNode = nodes.find(n => n.node_type === 'root') || nodes[0];
|
|
1176
|
-
|
|
1177
|
-
// Create a phase for the tasks
|
|
1178
|
-
const phase = await apiClient.nodes.createNode(plan.id, {
|
|
1179
|
-
parent_id: rootNode.id,
|
|
1180
|
-
node_type: 'phase',
|
|
1181
|
-
title: 'Tasks',
|
|
1182
|
-
status: 'not_started',
|
|
1183
|
-
order_index: 0
|
|
1184
|
-
});
|
|
1185
|
-
|
|
1186
|
-
// Create tasks
|
|
1187
|
-
const createdTasks = [];
|
|
1188
|
-
for (let i = 0; i < tasks.length; i++) {
|
|
1189
|
-
const task = await apiClient.nodes.createNode(plan.id, {
|
|
1190
|
-
parent_id: phase.id,
|
|
1191
|
-
node_type: 'task',
|
|
1192
|
-
title: tasks[i],
|
|
1193
|
-
status: 'not_started',
|
|
1194
|
-
order_index: i
|
|
1195
|
-
});
|
|
1196
|
-
createdTasks.push({ id: task.id, title: task.title });
|
|
1197
|
-
}
|
|
1198
|
-
|
|
1199
|
-
// Link to goal if provided
|
|
1200
|
-
if (goal_id) {
|
|
1201
|
-
try {
|
|
1202
|
-
await apiClient.goals.linkPlan(goal_id, plan.id);
|
|
1203
|
-
} catch (e) {
|
|
1204
|
-
// Goal linking failed, continue anyway
|
|
1205
|
-
}
|
|
1206
|
-
}
|
|
1207
|
-
|
|
1208
|
-
return formatResponse({
|
|
1209
|
-
success: true,
|
|
1210
|
-
message: `Plan "${title}" created with ${tasks.length} tasks`,
|
|
1211
|
-
plan_id: plan.id,
|
|
1212
|
-
plan_url: buildPlanUrl(plan.id),
|
|
1213
|
-
phase_id: phase.id,
|
|
1214
|
-
task_ids: createdTasks.map(t => t.id),
|
|
1215
|
-
tasks: createdTasks,
|
|
1216
|
-
next_steps: [
|
|
1217
|
-
"Use quick_status to update task progress",
|
|
1218
|
-
"Use quick_log to document your work",
|
|
1219
|
-
"Use get_plan_context to load full plan details"
|
|
1220
|
-
]
|
|
1221
|
-
});
|
|
1222
|
-
}
|
|
1223
|
-
|
|
1224
|
-
if (name === "quick_task") {
|
|
1225
|
-
const { plan_id, title, description, phase_id, agent_instructions } = args;
|
|
1226
|
-
|
|
1227
|
-
// Get plan structure to find the phase
|
|
1228
|
-
const nodes = await apiClient.nodes.getNodes(plan_id);
|
|
1229
|
-
const flatNodes = flattenNodes(nodes);
|
|
1230
|
-
|
|
1231
|
-
// Find target phase
|
|
1232
|
-
let targetPhaseId = phase_id;
|
|
1233
|
-
if (!targetPhaseId) {
|
|
1234
|
-
// Find first phase
|
|
1235
|
-
const phases = flatNodes.filter(n => n.node_type === 'phase');
|
|
1236
|
-
if (phases.length > 0) {
|
|
1237
|
-
targetPhaseId = phases[0].id;
|
|
1238
|
-
} else {
|
|
1239
|
-
// No phase exists, create one
|
|
1240
|
-
const rootNode = flatNodes.find(n => n.node_type === 'root') || nodes[0];
|
|
1241
|
-
const newPhase = await apiClient.nodes.createNode(plan_id, {
|
|
1242
|
-
parent_id: rootNode.id,
|
|
1243
|
-
node_type: 'phase',
|
|
1244
|
-
title: 'Tasks',
|
|
1245
|
-
status: 'not_started',
|
|
1246
|
-
order_index: 0
|
|
1247
|
-
});
|
|
1248
|
-
targetPhaseId = newPhase.id;
|
|
1249
|
-
}
|
|
1250
|
-
}
|
|
1251
|
-
|
|
1252
|
-
// Get task count in phase for order_index
|
|
1253
|
-
const phaseTasks = flatNodes.filter(n => n.parent_id === targetPhaseId && n.node_type === 'task');
|
|
1254
|
-
|
|
1255
|
-
// Create the task
|
|
1256
|
-
const taskData = {
|
|
1257
|
-
parent_id: targetPhaseId,
|
|
1258
|
-
node_type: 'task',
|
|
1259
|
-
title,
|
|
1260
|
-
status: 'not_started',
|
|
1261
|
-
order_index: phaseTasks.length
|
|
1262
|
-
};
|
|
1263
|
-
if (description) taskData.description = description;
|
|
1264
|
-
if (agent_instructions) taskData.agent_instructions = agent_instructions;
|
|
1265
|
-
|
|
1266
|
-
const task = await apiClient.nodes.createNode(plan_id, taskData);
|
|
1267
|
-
|
|
1268
|
-
return formatResponse({
|
|
1269
|
-
success: true,
|
|
1270
|
-
message: `Task "${title}" added to plan`,
|
|
1271
|
-
task_id: task.id,
|
|
1272
|
-
plan_id: plan_id,
|
|
1273
|
-
phase_id: targetPhaseId,
|
|
1274
|
-
task_url: buildTaskUrl(plan_id, task.id),
|
|
1275
|
-
next_steps: [
|
|
1276
|
-
"Use quick_status to mark as in_progress when you start",
|
|
1277
|
-
"Use quick_log to document progress"
|
|
1278
|
-
]
|
|
1279
|
-
});
|
|
1280
|
-
}
|
|
1281
|
-
|
|
1282
|
-
if (name === "quick_status") {
|
|
1283
|
-
const { task_id, plan_id, status, note } = args;
|
|
1284
|
-
|
|
1285
|
-
// Update the task status
|
|
1286
|
-
const updateData = { status };
|
|
1287
|
-
const updated = await apiClient.nodes.updateNode(plan_id, task_id, updateData);
|
|
1288
|
-
|
|
1289
|
-
// Add a log entry if note provided or if blocking
|
|
1290
|
-
if (note || status === 'blocked') {
|
|
1291
|
-
const logMessage = note || (status === 'blocked' ? 'Task blocked - needs attention' : `Status changed to ${status}`);
|
|
1292
|
-
try {
|
|
1293
|
-
await apiClient.logs.addLogEntry(plan_id, task_id, {
|
|
1294
|
-
type: status === 'blocked' ? 'blocker' : 'progress',
|
|
1295
|
-
content: logMessage
|
|
1296
|
-
});
|
|
1297
|
-
} catch (e) {
|
|
1298
|
-
// Log failed, continue
|
|
1299
|
-
}
|
|
1300
|
-
}
|
|
1301
|
-
|
|
1302
|
-
// Get next tasks for suggestion
|
|
1303
|
-
let nextTasks = [];
|
|
1304
|
-
try {
|
|
1305
|
-
const nodes = await apiClient.nodes.getNodes(plan_id);
|
|
1306
|
-
const flatNodes = flattenNodes(nodes);
|
|
1307
|
-
nextTasks = flatNodes
|
|
1308
|
-
.filter(n => n.node_type === 'task' && n.status === 'not_started')
|
|
1309
|
-
.slice(0, 3)
|
|
1310
|
-
.map(n => ({ id: n.id, title: n.title }));
|
|
1311
|
-
} catch (e) {
|
|
1312
|
-
// Failed to get next tasks, continue
|
|
1313
|
-
}
|
|
1314
|
-
|
|
1315
|
-
const response = {
|
|
1316
|
-
success: true,
|
|
1317
|
-
message: `Task status updated to "${status}"`,
|
|
1318
|
-
task_id,
|
|
1319
|
-
plan_id,
|
|
1320
|
-
new_status: status
|
|
1321
|
-
};
|
|
1322
|
-
|
|
1323
|
-
if (status === 'completed' && nextTasks.length > 0) {
|
|
1324
|
-
response.next_tasks = nextTasks;
|
|
1325
|
-
response.suggestion = "Here are the next tasks to work on";
|
|
1326
|
-
} else if (status === 'blocked') {
|
|
1327
|
-
response.suggestion = "Task marked as blocked. A human will be notified to help unblock.";
|
|
1328
|
-
}
|
|
1329
|
-
|
|
1330
|
-
return formatResponse(response);
|
|
1331
|
-
}
|
|
1332
|
-
|
|
1333
|
-
if (name === "quick_log") {
|
|
1334
|
-
const { task_id, plan_id, message, log_type = 'progress' } = args;
|
|
1335
|
-
|
|
1336
|
-
const logEntry = await apiClient.logs.addLogEntry(plan_id, task_id, {
|
|
1337
|
-
type: log_type,
|
|
1338
|
-
content: message
|
|
1339
|
-
});
|
|
1340
|
-
|
|
1341
|
-
return formatResponse({
|
|
1342
|
-
success: true,
|
|
1343
|
-
message: "Progress logged",
|
|
1344
|
-
log_id: logEntry.id,
|
|
1345
|
-
task_id,
|
|
1346
|
-
plan_id,
|
|
1347
|
-
logged: message,
|
|
1348
|
-
tip: "Good practice! Logging helps humans follow your work."
|
|
1349
|
-
});
|
|
1350
|
-
}
|
|
1351
|
-
|
|
1352
|
-
// ========================================
|
|
1353
|
-
// CONTEXT LOADING IMPLEMENTATIONS
|
|
1354
|
-
// ========================================
|
|
1355
|
-
|
|
1356
|
-
if (name === "get_my_tasks") {
|
|
1357
|
-
const { plan_id, status = ["blocked", "in_progress"] } = args;
|
|
1358
|
-
|
|
1359
|
-
const tasks = {
|
|
1360
|
-
retrieved_at: new Date().toISOString(),
|
|
1361
|
-
needs_attention: [],
|
|
1362
|
-
ready_to_start: []
|
|
1363
|
-
};
|
|
1364
|
-
|
|
1365
|
-
// Get plans to check
|
|
1366
|
-
let plansToCheck = [];
|
|
1367
|
-
if (plan_id) {
|
|
1368
|
-
plansToCheck = [{ id: plan_id }];
|
|
1369
|
-
} else {
|
|
1370
|
-
plansToCheck = await apiClient.plans.getPlans();
|
|
1371
|
-
}
|
|
1372
|
-
|
|
1373
|
-
for (const plan of plansToCheck.slice(0, 10)) { // Limit to 10 plans
|
|
1374
|
-
try {
|
|
1375
|
-
const nodes = await apiClient.nodes.getNodes(plan.id);
|
|
1376
|
-
const flatNodes = flattenNodes(nodes);
|
|
1377
|
-
|
|
1378
|
-
const matchingTasks = flatNodes
|
|
1379
|
-
.filter(n => n.node_type === 'task' && status.includes(n.status))
|
|
1380
|
-
.map(n => ({
|
|
1381
|
-
id: n.id,
|
|
1382
|
-
title: n.title,
|
|
1383
|
-
status: n.status,
|
|
1384
|
-
plan_id: plan.id,
|
|
1385
|
-
plan_title: plan.title
|
|
1386
|
-
}));
|
|
1387
|
-
|
|
1388
|
-
tasks.needs_attention.push(...matchingTasks);
|
|
1389
|
-
|
|
1390
|
-
// Also get a few ready-to-start tasks
|
|
1391
|
-
const readyTasks = flatNodes
|
|
1392
|
-
.filter(n => n.node_type === 'task' && n.status === 'not_started')
|
|
1393
|
-
.slice(0, 3)
|
|
1394
|
-
.map(n => ({
|
|
1395
|
-
id: n.id,
|
|
1396
|
-
title: n.title,
|
|
1397
|
-
plan_id: plan.id,
|
|
1398
|
-
plan_title: plan.title
|
|
1399
|
-
}));
|
|
1400
|
-
|
|
1401
|
-
tasks.ready_to_start.push(...readyTasks);
|
|
1402
|
-
} catch (e) {
|
|
1403
|
-
// Skip this plan
|
|
1404
|
-
}
|
|
1405
|
-
}
|
|
1406
|
-
|
|
1407
|
-
tasks.summary = {
|
|
1408
|
-
blocked: tasks.needs_attention.filter(t => t.status === 'blocked').length,
|
|
1409
|
-
in_progress: tasks.needs_attention.filter(t => t.status === 'in_progress').length,
|
|
1410
|
-
ready: tasks.ready_to_start.length
|
|
1411
|
-
};
|
|
1412
|
-
|
|
1413
|
-
return formatResponse(tasks);
|
|
1414
|
-
}
|
|
1415
|
-
|
|
1416
|
-
// add_learning handled in GRAPHITI KNOWLEDGE GRAPH HANDLERS section below
|
|
1417
|
-
|
|
1418
|
-
// ========================================
|
|
1419
|
-
// MARKDOWN EXPORT
|
|
1420
|
-
// ========================================
|
|
1421
|
-
|
|
1422
|
-
if (name === "export_plan_markdown") {
|
|
1423
|
-
const { plan_id, include_descriptions = true, include_status = true } = args;
|
|
1424
|
-
|
|
1425
|
-
const plan = await apiClient.plans.getPlan(plan_id);
|
|
1426
|
-
const nodes = await apiClient.nodes.getNodes(plan_id);
|
|
1427
|
-
|
|
1428
|
-
let markdown = `# ${plan.title}\n\n`;
|
|
1429
|
-
if (plan.description) {
|
|
1430
|
-
markdown += `${plan.description}\n\n`;
|
|
1431
|
-
}
|
|
1432
|
-
|
|
1433
|
-
const statusEmoji = {
|
|
1434
|
-
not_started: '⬜',
|
|
1435
|
-
in_progress: '🔄',
|
|
1436
|
-
completed: '✅',
|
|
1437
|
-
blocked: '🚫',
|
|
1438
|
-
cancelled: '❌'
|
|
1439
|
-
};
|
|
1440
|
-
|
|
1441
|
-
// Process nodes recursively
|
|
1442
|
-
const processNode = (node, depth = 0) => {
|
|
1443
|
-
const indent = ' '.repeat(depth);
|
|
1444
|
-
const status = include_status ? (statusEmoji[node.status] || '⬜') + ' ' : '';
|
|
1445
|
-
|
|
1446
|
-
if (node.node_type === 'phase') {
|
|
1447
|
-
markdown += `\n${indent}## ${node.title}\n`;
|
|
1448
|
-
if (include_descriptions && node.description) {
|
|
1449
|
-
markdown += `${indent}${node.description}\n`;
|
|
1450
|
-
}
|
|
1451
|
-
} else if (node.node_type === 'task') {
|
|
1452
|
-
markdown += `${indent}- ${status}${node.title}\n`;
|
|
1453
|
-
if (include_descriptions && node.description) {
|
|
1454
|
-
markdown += `${indent} _${node.description}_\n`;
|
|
1455
|
-
}
|
|
1456
|
-
} else if (node.node_type === 'milestone') {
|
|
1457
|
-
markdown += `${indent}- 🎯 ${status}**${node.title}**\n`;
|
|
1458
|
-
}
|
|
1459
|
-
|
|
1460
|
-
if (node.children) {
|
|
1461
|
-
node.children.forEach(child => processNode(child, depth + 1));
|
|
1462
|
-
}
|
|
1463
|
-
};
|
|
1464
|
-
|
|
1465
|
-
// Start from root's children
|
|
1466
|
-
if (nodes.length > 0 && nodes[0].children) {
|
|
1467
|
-
nodes[0].children.forEach(child => processNode(child, 0));
|
|
1468
|
-
}
|
|
1469
|
-
|
|
1470
|
-
return formatResponse({
|
|
1471
|
-
plan_id,
|
|
1472
|
-
title: plan.title,
|
|
1473
|
-
markdown,
|
|
1474
|
-
tip: "You can save this to a file or share it as text"
|
|
1475
|
-
});
|
|
1476
|
-
}
|
|
1477
|
-
|
|
1478
|
-
if (name === "import_plan_markdown") {
|
|
1479
|
-
const { markdown, title: providedTitle, goal_id } = args;
|
|
1480
|
-
|
|
1481
|
-
// Parse markdown
|
|
1482
|
-
const lines = markdown.split('\n').map(l => l.trim()).filter(l => l);
|
|
1483
|
-
|
|
1484
|
-
let planTitle = providedTitle;
|
|
1485
|
-
let planDescription = '';
|
|
1486
|
-
const phases = [];
|
|
1487
|
-
let currentPhase = null;
|
|
1488
|
-
|
|
1489
|
-
for (const line of lines) {
|
|
1490
|
-
// H1 = Plan title
|
|
1491
|
-
if (line.startsWith('# ')) {
|
|
1492
|
-
if (!planTitle) {
|
|
1493
|
-
planTitle = line.slice(2).trim();
|
|
1494
|
-
}
|
|
1495
|
-
}
|
|
1496
|
-
// H2 = Phase
|
|
1497
|
-
else if (line.startsWith('## ')) {
|
|
1498
|
-
const phaseTitle = line.slice(3).trim();
|
|
1499
|
-
currentPhase = { title: phaseTitle, tasks: [] };
|
|
1500
|
-
phases.push(currentPhase);
|
|
1501
|
-
}
|
|
1502
|
-
// List item = Task (with optional status emoji)
|
|
1503
|
-
else if (line.startsWith('- ') || line.startsWith('* ')) {
|
|
1504
|
-
let taskTitle = line.slice(2).trim();
|
|
1505
|
-
let taskStatus = 'not_started';
|
|
1506
|
-
|
|
1507
|
-
// Parse status from emoji
|
|
1508
|
-
if (taskTitle.startsWith('✅')) {
|
|
1509
|
-
taskStatus = 'completed';
|
|
1510
|
-
taskTitle = taskTitle.slice(1).trim();
|
|
1511
|
-
} else if (taskTitle.startsWith('🔄')) {
|
|
1512
|
-
taskStatus = 'in_progress';
|
|
1513
|
-
taskTitle = taskTitle.slice(1).trim();
|
|
1514
|
-
} else if (taskTitle.startsWith('🚫')) {
|
|
1515
|
-
taskStatus = 'blocked';
|
|
1516
|
-
taskTitle = taskTitle.slice(1).trim();
|
|
1517
|
-
} else if (taskTitle.startsWith('⬜')) {
|
|
1518
|
-
taskTitle = taskTitle.slice(1).trim();
|
|
1519
|
-
}
|
|
1520
|
-
|
|
1521
|
-
// Skip milestone markers for now
|
|
1522
|
-
if (taskTitle.startsWith('🎯')) {
|
|
1523
|
-
taskTitle = taskTitle.slice(1).trim().replace(/\*\*/g, '');
|
|
1524
|
-
}
|
|
1525
|
-
|
|
1526
|
-
if (currentPhase) {
|
|
1527
|
-
currentPhase.tasks.push({ title: taskTitle, status: taskStatus });
|
|
1528
|
-
} else {
|
|
1529
|
-
// No phase yet, create a default one
|
|
1530
|
-
currentPhase = { title: 'Tasks', tasks: [{ title: taskTitle, status: taskStatus }] };
|
|
1531
|
-
phases.push(currentPhase);
|
|
1532
|
-
}
|
|
1533
|
-
}
|
|
1534
|
-
// Regular text after title = description
|
|
1535
|
-
else if (planTitle && !currentPhase && !line.startsWith('#')) {
|
|
1536
|
-
planDescription += (planDescription ? ' ' : '') + line;
|
|
1537
|
-
}
|
|
1538
|
-
}
|
|
1539
|
-
|
|
1540
|
-
if (!planTitle) {
|
|
1541
|
-
return formatResponse({
|
|
1542
|
-
error: "Could not extract plan title",
|
|
1543
|
-
suggestion: "Add a '# Title' heading at the start, or provide the 'title' parameter"
|
|
1544
|
-
});
|
|
1545
|
-
}
|
|
1546
|
-
|
|
1547
|
-
if (phases.length === 0) {
|
|
1548
|
-
return formatResponse({
|
|
1549
|
-
error: "No phases or tasks found",
|
|
1550
|
-
suggestion: "Use '## Phase Name' for phases and '- Task name' for tasks"
|
|
1551
|
-
});
|
|
1552
|
-
}
|
|
1553
|
-
|
|
1554
|
-
// Create the plan
|
|
1555
|
-
const planData = { title: planTitle };
|
|
1556
|
-
if (planDescription) planData.description = planDescription;
|
|
1557
|
-
|
|
1558
|
-
const plan = await apiClient.plans.createPlan(planData);
|
|
1559
|
-
|
|
1560
|
-
// Get root node
|
|
1561
|
-
const nodes = await apiClient.nodes.getNodes(plan.id);
|
|
1562
|
-
const rootNode = nodes.find(n => n.node_type === 'root') || nodes[0];
|
|
1563
|
-
|
|
1564
|
-
// Create phases and tasks
|
|
1565
|
-
const createdPhases = [];
|
|
1566
|
-
const createdTasks = [];
|
|
1567
|
-
|
|
1568
|
-
for (let i = 0; i < phases.length; i++) {
|
|
1569
|
-
const phaseData = phases[i];
|
|
1570
|
-
|
|
1571
|
-
const phase = await apiClient.nodes.createNode(plan.id, {
|
|
1572
|
-
parent_id: rootNode.id,
|
|
1573
|
-
node_type: 'phase',
|
|
1574
|
-
title: phaseData.title,
|
|
1575
|
-
status: 'not_started',
|
|
1576
|
-
order_index: i
|
|
1577
|
-
});
|
|
1578
|
-
createdPhases.push({ id: phase.id, title: phase.title });
|
|
1579
|
-
|
|
1580
|
-
for (let j = 0; j < phaseData.tasks.length; j++) {
|
|
1581
|
-
const taskData = phaseData.tasks[j];
|
|
1582
|
-
|
|
1583
|
-
const task = await apiClient.nodes.createNode(plan.id, {
|
|
1584
|
-
parent_id: phase.id,
|
|
1585
|
-
node_type: 'task',
|
|
1586
|
-
title: taskData.title,
|
|
1587
|
-
status: taskData.status,
|
|
1588
|
-
order_index: j
|
|
1589
|
-
});
|
|
1590
|
-
createdTasks.push({ id: task.id, title: task.title, status: task.status });
|
|
1591
|
-
}
|
|
1592
|
-
}
|
|
1593
|
-
|
|
1594
|
-
// Link to goal if provided
|
|
1595
|
-
if (goal_id) {
|
|
1596
|
-
try {
|
|
1597
|
-
await apiClient.goals.linkPlan(goal_id, plan.id);
|
|
1598
|
-
} catch (e) {}
|
|
1599
|
-
}
|
|
1600
|
-
|
|
1601
|
-
return formatResponse({
|
|
1602
|
-
success: true,
|
|
1603
|
-
message: `Plan "${planTitle}" created from markdown with ${phases.length} phases and ${createdTasks.length} tasks`,
|
|
1604
|
-
plan_id: plan.id,
|
|
1605
|
-
plan_url: buildPlanUrl(plan.id),
|
|
1606
|
-
phases: createdPhases,
|
|
1607
|
-
tasks: createdTasks,
|
|
1608
|
-
next_steps: [
|
|
1609
|
-
"Use get_plan_context to review the imported plan",
|
|
1610
|
-
"Use quick_status to update task progress"
|
|
1611
|
-
]
|
|
1612
|
-
});
|
|
1613
|
-
}
|
|
1614
|
-
|
|
1615
|
-
// ===== UNIFIED SEARCH TOOL =====
|
|
1616
|
-
if (name === "search") {
|
|
1617
|
-
const { scope, scope_id, query, filters = {} } = args;
|
|
1618
|
-
|
|
1619
|
-
let results = [];
|
|
1620
|
-
|
|
1621
|
-
switch (scope) {
|
|
1622
|
-
case "global":
|
|
1623
|
-
// Global search across all plans
|
|
1624
|
-
const searchWrapper = require('./tools/search-wrapper');
|
|
1625
|
-
results = await searchWrapper.globalSearch(query);
|
|
1626
|
-
break;
|
|
1627
|
-
|
|
1628
|
-
case "plans":
|
|
1629
|
-
// Search only in plan titles/descriptions
|
|
1630
|
-
const plans = await apiClient.plans.getPlans();
|
|
1631
|
-
|
|
1632
|
-
// Handle wildcard queries
|
|
1633
|
-
if (query === '*' || query === '' || !query) {
|
|
1634
|
-
// Return all plans (with optional status filter)
|
|
1635
|
-
results = plans.filter(plan =>
|
|
1636
|
-
!filters.status || plan.status === filters.status
|
|
1637
|
-
);
|
|
1638
|
-
} else {
|
|
1639
|
-
// Normal search
|
|
1640
|
-
const queryLower = query.toLowerCase();
|
|
1641
|
-
results = plans.filter(plan => {
|
|
1642
|
-
const titleMatch = plan.title.toLowerCase().includes(queryLower);
|
|
1643
|
-
const descMatch = plan.description?.toLowerCase().includes(queryLower);
|
|
1644
|
-
const statusMatch = !filters.status || plan.status === filters.status;
|
|
1645
|
-
return (titleMatch || descMatch) && statusMatch;
|
|
1646
|
-
});
|
|
1647
|
-
}
|
|
1648
|
-
break;
|
|
1649
|
-
|
|
1650
|
-
case "plan":
|
|
1651
|
-
// Search within a specific plan
|
|
1652
|
-
if (!scope_id) {
|
|
1653
|
-
throw new Error("scope_id (plan_id) is required when scope is 'plan'");
|
|
1654
|
-
}
|
|
1655
|
-
const searchWrapperPlan = require('./tools/search-wrapper');
|
|
1656
|
-
results = await searchWrapperPlan.searchPlan(scope_id, query);
|
|
1657
|
-
break;
|
|
1658
|
-
|
|
1659
|
-
case "node":
|
|
1660
|
-
// Search within a specific node's children
|
|
1661
|
-
if (!scope_id) {
|
|
1662
|
-
throw new Error("scope_id (node_id) is required when scope is 'node'");
|
|
1663
|
-
}
|
|
1664
|
-
// This would need a specific implementation
|
|
1665
|
-
results = [];
|
|
1666
|
-
break;
|
|
1667
|
-
|
|
1668
|
-
default:
|
|
1669
|
-
// Default to global search
|
|
1670
|
-
const searchWrapperDefault = require('./tools/search-wrapper');
|
|
1671
|
-
results = await searchWrapperDefault.globalSearch(query);
|
|
1672
|
-
}
|
|
1673
|
-
|
|
1674
|
-
// Apply filters
|
|
1675
|
-
if (filters.type) {
|
|
1676
|
-
results = results.filter(item => item.type === filters.type);
|
|
1677
|
-
}
|
|
1678
|
-
if (filters.limit) {
|
|
1679
|
-
results = results.slice(0, filters.limit);
|
|
1680
|
-
}
|
|
1681
|
-
|
|
1682
|
-
return formatResponse({
|
|
1683
|
-
query,
|
|
1684
|
-
scope,
|
|
1685
|
-
scope_id,
|
|
1686
|
-
filters,
|
|
1687
|
-
count: results.length,
|
|
1688
|
-
results
|
|
1689
|
-
});
|
|
1690
|
-
}
|
|
1691
|
-
|
|
1692
|
-
// ===== PLAN MANAGEMENT =====
|
|
1693
|
-
if (name === "list_plans") {
|
|
1694
|
-
const { status, include_completed } = args;
|
|
1695
|
-
const plans = await apiClient.plans.getPlans();
|
|
1696
|
-
let filteredPlans;
|
|
1697
|
-
if (status) {
|
|
1698
|
-
filteredPlans = plans.filter(p => p.status === status);
|
|
1699
|
-
} else if (!include_completed) {
|
|
1700
|
-
filteredPlans = plans.filter(p => p.status !== 'completed' && p.status !== 'archived');
|
|
1701
|
-
} else {
|
|
1702
|
-
filteredPlans = plans;
|
|
1703
|
-
}
|
|
1704
|
-
return formatResponse(filteredPlans);
|
|
1705
|
-
}
|
|
1706
|
-
|
|
1707
|
-
if (name === "create_plan") {
|
|
1708
|
-
const result = await apiClient.plans.createPlan(args);
|
|
1709
|
-
return formatResponse(result);
|
|
1710
|
-
}
|
|
1711
|
-
|
|
1712
|
-
if (name === "update_plan") {
|
|
1713
|
-
const { plan_id, ...planData } = args;
|
|
1714
|
-
const result = await apiClient.plans.updatePlan(plan_id, planData);
|
|
1715
|
-
return formatResponse(result);
|
|
1716
|
-
}
|
|
1717
|
-
|
|
1718
|
-
if (name === "delete_plan") {
|
|
1719
|
-
const { plan_id } = args;
|
|
1720
|
-
await apiClient.plans.deletePlan(plan_id);
|
|
1721
|
-
return formatResponse({
|
|
1722
|
-
success: true,
|
|
1723
|
-
message: `Plan ${plan_id} deleted successfully`
|
|
1724
|
-
});
|
|
1725
|
-
}
|
|
1726
|
-
|
|
1727
|
-
if (name === "share_plan") {
|
|
1728
|
-
const { plan_id, visibility = "public", github_repo_owner, github_repo_name } = args;
|
|
1729
|
-
|
|
1730
|
-
const visibilityData = { visibility };
|
|
1731
|
-
if (github_repo_owner) visibilityData.github_repo_owner = github_repo_owner;
|
|
1732
|
-
if (github_repo_name) visibilityData.github_repo_name = github_repo_name;
|
|
1733
|
-
|
|
1734
|
-
const result = await apiClient.plans.updateVisibility(plan_id, visibilityData);
|
|
1735
|
-
|
|
1736
|
-
const shareUrl = visibility === "public"
|
|
1737
|
-
? buildPlanUrl(plan_id)
|
|
1738
|
-
: null;
|
|
1739
|
-
|
|
1740
|
-
return formatResponse({
|
|
1741
|
-
success: true,
|
|
1742
|
-
plan_id: plan_id,
|
|
1743
|
-
visibility: result.visibility,
|
|
1744
|
-
is_public: result.is_public,
|
|
1745
|
-
share_url: shareUrl,
|
|
1746
|
-
github_repo_owner: result.github_repo_owner,
|
|
1747
|
-
github_repo_name: result.github_repo_name,
|
|
1748
|
-
message: visibility === "public"
|
|
1749
|
-
? `Plan is now public. Share URL: ${shareUrl}`
|
|
1750
|
-
: `Plan is now private.`
|
|
1751
|
-
});
|
|
1752
|
-
}
|
|
1753
|
-
|
|
1754
|
-
// ===== NODE MANAGEMENT =====
|
|
1755
|
-
if (name === "create_node") {
|
|
1756
|
-
const { plan_id, ...nodeData } = args;
|
|
1757
|
-
const result = await apiClient.nodes.createNode(plan_id, nodeData);
|
|
1758
|
-
return formatResponse(result);
|
|
1759
|
-
}
|
|
1760
|
-
|
|
1761
|
-
if (name === "update_node") {
|
|
1762
|
-
const { plan_id, node_id, ...nodeData } = args;
|
|
1763
|
-
const result = await apiClient.nodes.updateNode(plan_id, node_id, nodeData);
|
|
1764
|
-
return formatResponse(result);
|
|
1765
|
-
}
|
|
1766
|
-
|
|
1767
|
-
if (name === "delete_node") {
|
|
1768
|
-
const { plan_id, node_id } = args;
|
|
1769
|
-
await apiClient.nodes.deleteNode(plan_id, node_id);
|
|
1770
|
-
return formatResponse({
|
|
1771
|
-
success: true,
|
|
1772
|
-
message: `Node ${node_id} and its children deleted successfully`
|
|
1773
|
-
});
|
|
1774
|
-
}
|
|
1775
|
-
|
|
1776
|
-
if (name === "move_node") {
|
|
1777
|
-
const { plan_id, node_id, parent_id, order_index } = args;
|
|
1778
|
-
|
|
1779
|
-
try {
|
|
1780
|
-
// Build request body with only provided fields - don't send nulls
|
|
1781
|
-
const body = {};
|
|
1782
|
-
if (parent_id) body.parent_id = parent_id;
|
|
1783
|
-
if (order_index !== undefined) body.order_index = order_index;
|
|
1784
|
-
|
|
1785
|
-
// Call the move endpoint - using POST as per API definition
|
|
1786
|
-
const response = await apiClient.axiosInstance.post(
|
|
1787
|
-
`/plans/${plan_id}/nodes/${node_id}/move`,
|
|
1788
|
-
body
|
|
1789
|
-
);
|
|
1790
|
-
|
|
1791
|
-
return formatResponse(response.data);
|
|
1792
|
-
} catch (error) {
|
|
1793
|
-
// If endpoint still doesn't work, try updating the node directly
|
|
1794
|
-
if (error.response && error.response.status === 404) {
|
|
1795
|
-
console.error('Move endpoint not found, trying direct update');
|
|
1796
|
-
// Fallback to updating the node's parent_id via regular update
|
|
1797
|
-
const updateData = {};
|
|
1798
|
-
if (parent_id) updateData.parent_id = parent_id;
|
|
1799
|
-
if (order_index !== undefined) updateData.order_index = order_index;
|
|
1800
|
-
const updateResponse = await apiClient.nodes.updateNode(plan_id, node_id, updateData);
|
|
1801
|
-
return formatResponse(updateResponse);
|
|
1802
|
-
}
|
|
1803
|
-
throw error;
|
|
1804
|
-
}
|
|
1805
|
-
}
|
|
1806
|
-
|
|
1807
|
-
if (name === "get_node_ancestry") {
|
|
1808
|
-
const { plan_id, node_id } = args;
|
|
1809
|
-
|
|
1810
|
-
// Get node ancestry
|
|
1811
|
-
const response = await apiClient.axiosInstance.get(
|
|
1812
|
-
`/plans/${plan_id}/nodes/${node_id}/ancestry`
|
|
1813
|
-
);
|
|
1814
|
-
|
|
1815
|
-
return formatResponse(response.data);
|
|
1816
|
-
}
|
|
1817
|
-
|
|
1818
|
-
// ===== DEPENDENCIES =====
|
|
1819
|
-
if (name === "create_dependency") {
|
|
1820
|
-
const { plan_id, source_node_id, target_node_id, dependency_type, weight, metadata } = args;
|
|
1821
|
-
const response = await apiClient.axiosInstance.post(
|
|
1822
|
-
`/plans/${plan_id}/dependencies`,
|
|
1823
|
-
{ source_node_id, target_node_id, dependency_type, weight, metadata }
|
|
1824
|
-
);
|
|
1825
|
-
return formatResponse(response.data);
|
|
1826
|
-
}
|
|
1827
|
-
|
|
1828
|
-
if (name === "delete_dependency") {
|
|
1829
|
-
const { plan_id, dependency_id } = args;
|
|
1830
|
-
const response = await apiClient.axiosInstance.delete(
|
|
1831
|
-
`/plans/${plan_id}/dependencies/${dependency_id}`
|
|
1832
|
-
);
|
|
1833
|
-
return formatResponse(response.data);
|
|
1834
|
-
}
|
|
1835
|
-
|
|
1836
|
-
if (name === "list_dependencies") {
|
|
1837
|
-
const { plan_id } = args;
|
|
1838
|
-
const response = await apiClient.axiosInstance.get(
|
|
1839
|
-
`/plans/${plan_id}/dependencies`
|
|
1840
|
-
);
|
|
1841
|
-
return formatResponse(response.data);
|
|
1842
|
-
}
|
|
1843
|
-
|
|
1844
|
-
if (name === "get_node_dependencies") {
|
|
1845
|
-
const { plan_id, node_id, direction = 'both' } = args;
|
|
1846
|
-
const response = await apiClient.axiosInstance.get(
|
|
1847
|
-
`/plans/${plan_id}/nodes/${node_id}/dependencies`,
|
|
1848
|
-
{ params: { direction } }
|
|
1849
|
-
);
|
|
1850
|
-
return formatResponse(response.data);
|
|
1851
|
-
}
|
|
1852
|
-
|
|
1853
|
-
// ===== RPI WORKFLOW =====
|
|
1854
|
-
if (name === "create_rpi_chain") {
|
|
1855
|
-
const { plan_id, title, description, parent_id } = args;
|
|
1856
|
-
const response = await apiClient.axiosInstance.post(
|
|
1857
|
-
`/plans/${plan_id}/nodes/rpi-chain`,
|
|
1858
|
-
{ title, description, parent_id }
|
|
1859
|
-
);
|
|
1860
|
-
return formatResponse(response.data);
|
|
1861
|
-
}
|
|
1862
|
-
|
|
1863
|
-
// ===== ANALYSIS =====
|
|
1864
|
-
if (name === "analyze_impact") {
|
|
1865
|
-
const { plan_id, node_id, scenario = 'block' } = args;
|
|
1866
|
-
const response = await apiClient.axiosInstance.get(
|
|
1867
|
-
`/plans/${plan_id}/nodes/${node_id}/impact`,
|
|
1868
|
-
{ params: { scenario } }
|
|
1869
|
-
);
|
|
1870
|
-
return formatResponse(response.data);
|
|
1871
|
-
}
|
|
1872
|
-
|
|
1873
|
-
if (name === "get_critical_path") {
|
|
1874
|
-
const { plan_id } = args;
|
|
1875
|
-
const response = await apiClient.axiosInstance.get(
|
|
1876
|
-
`/plans/${plan_id}/critical-path`
|
|
1877
|
-
);
|
|
1878
|
-
return formatResponse(response.data);
|
|
1879
|
-
}
|
|
1880
|
-
|
|
1881
|
-
// ===== PROGRESSIVE CONTEXT =====
|
|
1882
|
-
if (name === "get_task_context") {
|
|
1883
|
-
const { node_id, depth = 2, token_budget = 0, log_limit = 10, include_research = true } = args;
|
|
1884
|
-
const params = new URLSearchParams({
|
|
1885
|
-
node_id,
|
|
1886
|
-
depth: String(depth),
|
|
1887
|
-
token_budget: String(token_budget),
|
|
1888
|
-
log_limit: String(log_limit),
|
|
1889
|
-
include_research: String(include_research),
|
|
1890
|
-
});
|
|
1891
|
-
const response = await apiClient.axiosInstance.get(`/context/progressive?${params.toString()}`);
|
|
1892
|
-
return formatResponse(response.data);
|
|
1893
|
-
}
|
|
1894
|
-
|
|
1895
|
-
if (name === "suggest_next_tasks") {
|
|
1896
|
-
const { plan_id, limit = 5 } = args;
|
|
1897
|
-
const params = new URLSearchParams({ plan_id, limit: String(limit) });
|
|
1898
|
-
const response = await apiClient.axiosInstance.get(`/context/suggest?${params.toString()}`);
|
|
1899
|
-
return formatResponse(response.data);
|
|
1900
|
-
}
|
|
1901
|
-
|
|
1902
|
-
// ===== LOGGING =====
|
|
1903
|
-
if (name === "add_log") {
|
|
1904
|
-
const { plan_id, node_id, content, log_type = "comment", tags } = args;
|
|
1905
|
-
|
|
1906
|
-
const logData = {
|
|
1907
|
-
content,
|
|
1908
|
-
log_type,
|
|
1909
|
-
tags
|
|
1910
|
-
};
|
|
1911
|
-
|
|
1912
|
-
const result = await apiClient.logs.addLogEntry(plan_id, node_id, logData);
|
|
1913
|
-
return formatResponse(result);
|
|
1914
|
-
}
|
|
1915
|
-
|
|
1916
|
-
if (name === "get_logs") {
|
|
1917
|
-
const { plan_id, node_id, log_type, limit = 50 } = args;
|
|
1918
|
-
|
|
1919
|
-
let logs = await apiClient.logs.getLogs(plan_id, node_id);
|
|
1920
|
-
|
|
1921
|
-
// Apply filters
|
|
1922
|
-
if (log_type) {
|
|
1923
|
-
logs = logs.filter(log => log.log_type === log_type);
|
|
1924
|
-
}
|
|
1925
|
-
|
|
1926
|
-
// Apply limit
|
|
1927
|
-
logs = logs.slice(0, limit);
|
|
1928
|
-
|
|
1929
|
-
return formatResponse(logs);
|
|
1930
|
-
}
|
|
1931
|
-
|
|
1932
|
-
// ===== BATCH OPERATIONS =====
|
|
1933
|
-
if (name === "batch_update_nodes") {
|
|
1934
|
-
const { plan_id, updates } = args;
|
|
1935
|
-
|
|
1936
|
-
const results = [];
|
|
1937
|
-
const errors = [];
|
|
1938
|
-
|
|
1939
|
-
for (const update of updates) {
|
|
1940
|
-
const { node_id, ...updateData } = update;
|
|
1941
|
-
try {
|
|
1942
|
-
const result = await apiClient.nodes.updateNode(plan_id, node_id, updateData);
|
|
1943
|
-
results.push({ node_id, success: true, data: result });
|
|
1944
|
-
} catch (error) {
|
|
1945
|
-
errors.push({ node_id, success: false, error: error.message });
|
|
1946
|
-
}
|
|
1947
|
-
}
|
|
1948
|
-
|
|
1949
|
-
return formatResponse({
|
|
1950
|
-
total: updates.length,
|
|
1951
|
-
successful: results.length,
|
|
1952
|
-
failed: errors.length,
|
|
1953
|
-
results,
|
|
1954
|
-
errors
|
|
1955
|
-
});
|
|
1956
|
-
}
|
|
1957
|
-
|
|
1958
|
-
// ===== PLAN STRUCTURE & SUMMARY =====
|
|
1959
|
-
if (name === "get_plan_structure") {
|
|
1960
|
-
const { plan_id, include_details = false } = args;
|
|
1961
|
-
|
|
1962
|
-
const plan = await apiClient.plans.getPlan(plan_id);
|
|
1963
|
-
// Pass include_details to the API - defaults to minimal fields
|
|
1964
|
-
const nodes = await apiClient.nodes.getNodes(plan_id, {
|
|
1965
|
-
include_details: include_details
|
|
1966
|
-
});
|
|
1967
|
-
|
|
1968
|
-
// The API already returns a tree structure, not a flat list
|
|
1969
|
-
// If it's already hierarchical, use it directly
|
|
1970
|
-
let structure;
|
|
1971
|
-
if (Array.isArray(nodes) && nodes.length > 0 && nodes[0].children !== undefined) {
|
|
1972
|
-
// Already hierarchical - use directly
|
|
1973
|
-
structure = nodes;
|
|
1974
|
-
} else {
|
|
1975
|
-
// Flat list - build hierarchy
|
|
1976
|
-
structure = buildNodeHierarchy(nodes, include_details);
|
|
1977
|
-
}
|
|
1978
|
-
|
|
1979
|
-
return formatResponse({
|
|
1980
|
-
plan: {
|
|
1981
|
-
id: plan.id,
|
|
1982
|
-
title: plan.title,
|
|
1983
|
-
status: plan.status,
|
|
1984
|
-
description: plan.description
|
|
1985
|
-
},
|
|
1986
|
-
structure
|
|
1987
|
-
});
|
|
1988
|
-
}
|
|
1989
|
-
|
|
1990
|
-
if (name === "get_plan_summary") {
|
|
1991
|
-
const { plan_id } = args;
|
|
1992
|
-
|
|
1993
|
-
const plan = await apiClient.plans.getPlan(plan_id);
|
|
1994
|
-
const nodes = await apiClient.nodes.getNodes(plan_id);
|
|
1995
|
-
|
|
1996
|
-
// Calculate statistics
|
|
1997
|
-
const stats = calculatePlanStatistics(nodes);
|
|
1998
|
-
|
|
1999
|
-
return formatResponse({
|
|
2000
|
-
plan: {
|
|
2001
|
-
id: plan.id,
|
|
2002
|
-
title: plan.title,
|
|
2003
|
-
status: plan.status,
|
|
2004
|
-
description: plan.description,
|
|
2005
|
-
created_at: plan.created_at,
|
|
2006
|
-
updated_at: plan.updated_at
|
|
2007
|
-
},
|
|
2008
|
-
statistics: stats,
|
|
2009
|
-
progress_percentage: stats.total > 0
|
|
2010
|
-
? ((stats.status_counts.completed / stats.total) * 100).toFixed(1)
|
|
2011
|
-
: 0
|
|
2012
|
-
});
|
|
2013
|
-
}
|
|
2014
|
-
|
|
2015
|
-
// ===== AGENT CONTEXT TOOLS =====
|
|
2016
|
-
if (name === "get_plan_context") {
|
|
2017
|
-
const { plan_id, include_knowledge = true } = args;
|
|
2018
|
-
|
|
2019
|
-
const result = await apiClient.context.getPlanContext(plan_id, {
|
|
2020
|
-
include_knowledge
|
|
2021
|
-
});
|
|
2022
|
-
|
|
2023
|
-
return formatResponse(result);
|
|
2024
|
-
}
|
|
2025
|
-
|
|
2026
|
-
// ===== ORGANIZATION TOOLS =====
|
|
2027
|
-
if (name === "list_organizations") {
|
|
2028
|
-
const result = await apiClient.organizations.list();
|
|
2029
|
-
return formatResponse(result);
|
|
2030
|
-
}
|
|
2031
|
-
|
|
2032
|
-
if (name === "get_organization") {
|
|
2033
|
-
const { organization_id } = args;
|
|
2034
|
-
const result = await apiClient.organizations.get(organization_id);
|
|
2035
|
-
return formatResponse(result);
|
|
2036
|
-
}
|
|
2037
|
-
|
|
2038
|
-
if (name === "create_organization") {
|
|
2039
|
-
const { name, description, slug } = args;
|
|
2040
|
-
const result = await apiClient.organizations.create({ name, description, slug });
|
|
2041
|
-
return formatResponse(result);
|
|
2042
|
-
}
|
|
2043
|
-
|
|
2044
|
-
if (name === "update_organization") {
|
|
2045
|
-
const { organization_id, ...updateData } = args;
|
|
2046
|
-
const result = await apiClient.organizations.update(organization_id, updateData);
|
|
2047
|
-
return formatResponse(result);
|
|
2048
|
-
}
|
|
2049
|
-
|
|
2050
|
-
// ===== GOAL TOOLS =====
|
|
2051
|
-
if (name === "list_goals") {
|
|
2052
|
-
const { organization_id, status, include_inactive } = args;
|
|
2053
|
-
const effectiveStatus = status || (!include_inactive ? 'active' : undefined);
|
|
2054
|
-
const result = await apiClient.goals.list({ organization_id, status: effectiveStatus });
|
|
2055
|
-
return formatResponse(result);
|
|
2056
|
-
}
|
|
2057
|
-
|
|
2058
|
-
if (name === "get_goal") {
|
|
2059
|
-
const { goal_id } = args;
|
|
2060
|
-
const result = await apiClient.goals.get(goal_id);
|
|
2061
|
-
return formatResponse(result);
|
|
2062
|
-
}
|
|
2063
|
-
|
|
2064
|
-
if (name === "create_goal") {
|
|
2065
|
-
const { organization_id, title, description, type, success_criteria, priority, parent_goal_id } = args;
|
|
2066
|
-
const result = await apiClient.goals.create({
|
|
2067
|
-
organization_id,
|
|
2068
|
-
title,
|
|
2069
|
-
description,
|
|
2070
|
-
type: type || 'outcome',
|
|
2071
|
-
successCriteria: success_criteria || null,
|
|
2072
|
-
priority: priority || 0,
|
|
2073
|
-
parentGoalId: parent_goal_id || null,
|
|
2074
|
-
});
|
|
2075
|
-
return formatResponse(result);
|
|
2076
|
-
}
|
|
2077
|
-
|
|
2078
|
-
if (name === "update_goal") {
|
|
2079
|
-
const { goal_id, parent_goal_id, success_criteria, ...rest } = args;
|
|
2080
|
-
const updateData = { ...rest };
|
|
2081
|
-
if (parent_goal_id !== undefined) updateData.parentGoalId = parent_goal_id;
|
|
2082
|
-
if (success_criteria !== undefined) updateData.successCriteria = success_criteria;
|
|
2083
|
-
const result = await apiClient.goals.update(goal_id, updateData);
|
|
2084
|
-
return formatResponse(result);
|
|
2085
|
-
}
|
|
2086
|
-
|
|
2087
|
-
if (name === "link_plan_to_goal") {
|
|
2088
|
-
const { goal_id, plan_id } = args;
|
|
2089
|
-
const result = await apiClient.goals.linkPlan(goal_id, plan_id);
|
|
2090
|
-
return formatResponse({
|
|
2091
|
-
success: true,
|
|
2092
|
-
message: `Plan ${plan_id} linked to goal ${goal_id}`,
|
|
2093
|
-
...result
|
|
2094
|
-
});
|
|
2095
|
-
}
|
|
2096
|
-
|
|
2097
|
-
if (name === "unlink_plan_from_goal") {
|
|
2098
|
-
const { goal_id, plan_id } = args;
|
|
2099
|
-
const result = await apiClient.goals.unlinkPlan(goal_id, plan_id);
|
|
2100
|
-
return formatResponse({
|
|
2101
|
-
success: true,
|
|
2102
|
-
message: `Plan ${plan_id} unlinked from goal ${goal_id}`,
|
|
2103
|
-
...result
|
|
2104
|
-
});
|
|
2105
|
-
}
|
|
2106
|
-
|
|
2107
|
-
// ===== CROSS-PLAN & EXTERNAL DEPENDENCY HANDLERS =====
|
|
2108
|
-
if (name === "create_cross_plan_dependency") {
|
|
2109
|
-
const { source_node_id, target_node_id, dependency_type, weight } = args;
|
|
2110
|
-
const result = await apiClient.dependencies.createCrossPlan({
|
|
2111
|
-
source_node_id, target_node_id, dependency_type, weight
|
|
2112
|
-
});
|
|
2113
|
-
return formatResponse(result);
|
|
2114
|
-
}
|
|
2115
|
-
|
|
2116
|
-
if (name === "list_cross_plan_dependencies") {
|
|
2117
|
-
const { plan_ids } = args;
|
|
2118
|
-
const result = await apiClient.dependencies.listCrossPlan(plan_ids);
|
|
2119
|
-
return formatResponse(result);
|
|
2120
|
-
}
|
|
2121
|
-
|
|
2122
|
-
if (name === "create_external_dependency") {
|
|
2123
|
-
const { plan_id, title, description, url, blocks_node_id } = args;
|
|
2124
|
-
const result = await apiClient.dependencies.createExternal({
|
|
2125
|
-
plan_id, title, description, url, blocks_node_id
|
|
2126
|
-
});
|
|
2127
|
-
return formatResponse(result);
|
|
2128
|
-
}
|
|
2129
|
-
|
|
2130
|
-
// ===== GOAL-DEPENDENCY HANDLERS =====
|
|
2131
|
-
if (name === "goal_path") {
|
|
2132
|
-
const { goal_id, max_depth } = args;
|
|
2133
|
-
const result = await apiClient.goals.getPath(goal_id, max_depth);
|
|
2134
|
-
return formatResponse(result);
|
|
2135
|
-
}
|
|
2136
|
-
|
|
2137
|
-
if (name === "goal_progress") {
|
|
2138
|
-
const { goal_id } = args;
|
|
2139
|
-
const result = await apiClient.goals.getProgress(goal_id);
|
|
2140
|
-
return formatResponse(result);
|
|
2141
|
-
}
|
|
2142
|
-
|
|
2143
|
-
if (name === "add_achiever") {
|
|
2144
|
-
const { goal_id, node_id, weight } = args;
|
|
2145
|
-
const result = await apiClient.goals.addAchiever(goal_id, node_id, weight);
|
|
2146
|
-
return formatResponse({
|
|
2147
|
-
...result,
|
|
2148
|
-
message: `Task ${node_id} now achieves goal ${goal_id}`,
|
|
2149
|
-
});
|
|
2150
|
-
}
|
|
2151
|
-
|
|
2152
|
-
if (name === "remove_achiever") {
|
|
2153
|
-
const { goal_id, dependency_id } = args;
|
|
2154
|
-
const result = await apiClient.goals.removeAchiever(goal_id, dependency_id);
|
|
2155
|
-
return formatResponse(result);
|
|
2156
|
-
}
|
|
2157
|
-
|
|
2158
|
-
if (name === "goal_knowledge_gaps") {
|
|
2159
|
-
const { goal_id } = args;
|
|
2160
|
-
const result = await apiClient.goals.getKnowledgeGaps(goal_id);
|
|
2161
|
-
return formatResponse(result);
|
|
2162
|
-
}
|
|
2163
|
-
|
|
2164
|
-
// ===== GRAPHITI KNOWLEDGE GRAPH HANDLERS =====
|
|
2165
|
-
if (name === "add_learning") {
|
|
2166
|
-
const { content, title, entry_type, plan_id, node_id } = args;
|
|
2167
|
-
|
|
2168
|
-
// Add to Graphiti temporal knowledge graph
|
|
2169
|
-
const result = await apiClient.graphiti.addEpisode({
|
|
2170
|
-
content,
|
|
2171
|
-
name: title,
|
|
2172
|
-
plan_id,
|
|
2173
|
-
node_id,
|
|
2174
|
-
metadata: { entry_type: entry_type || 'learning' },
|
|
2175
|
-
});
|
|
2176
|
-
return formatResponse({
|
|
2177
|
-
...result,
|
|
2178
|
-
message: 'Knowledge recorded in temporal graph',
|
|
2179
|
-
tip: 'This is now searchable via recall_knowledge across all plans'
|
|
2180
|
-
});
|
|
2181
|
-
}
|
|
2182
|
-
|
|
2183
|
-
if (name === "recall_knowledge") {
|
|
2184
|
-
const { query, max_results = 10 } = args;
|
|
2185
|
-
|
|
2186
|
-
// Try Graphiti first (temporal, cross-plan)
|
|
2187
|
-
try {
|
|
2188
|
-
const graphResult = await apiClient.graphiti.graphSearch({ query, max_results });
|
|
2189
|
-
if (graphResult?.results) {
|
|
2190
|
-
return formatResponse({
|
|
2191
|
-
...graphResult,
|
|
2192
|
-
source: 'graphiti_temporal_graph'
|
|
2193
|
-
});
|
|
2194
|
-
}
|
|
2195
|
-
} catch (err) {
|
|
2196
|
-
return formatResponse({
|
|
2197
|
-
results: [],
|
|
2198
|
-
source: 'graphiti_temporal_graph',
|
|
2199
|
-
error: 'Knowledge graph not available: ' + err.message,
|
|
2200
|
-
});
|
|
2201
|
-
}
|
|
2202
|
-
}
|
|
2203
|
-
|
|
2204
|
-
if (name === "find_entities") {
|
|
2205
|
-
const { query, max_results = 10 } = args;
|
|
2206
|
-
|
|
2207
|
-
try {
|
|
2208
|
-
const result = await apiClient.graphiti.searchEntities({ query, max_results });
|
|
2209
|
-
return formatResponse(result);
|
|
2210
|
-
} catch (err) {
|
|
2211
|
-
return formatResponse({
|
|
2212
|
-
error: 'Entity search requires the temporal knowledge graph (Graphiti)',
|
|
2213
|
-
detail: err.message
|
|
2214
|
-
});
|
|
2215
|
-
}
|
|
2216
|
-
}
|
|
2217
|
-
|
|
2218
|
-
if (name === "check_contradictions") {
|
|
2219
|
-
const { query, max_results = 10 } = args;
|
|
2220
|
-
|
|
2221
|
-
try {
|
|
2222
|
-
const result = await apiClient.graphiti.detectContradictions({ query, max_results });
|
|
2223
|
-
if (result.contradictions_found) {
|
|
2224
|
-
return formatResponse({
|
|
2225
|
-
...result,
|
|
2226
|
-
warning: 'Some knowledge has been superseded. Review the "superseded" facts before proceeding.',
|
|
2227
|
-
});
|
|
2228
|
-
}
|
|
2229
|
-
return formatResponse({
|
|
2230
|
-
...result,
|
|
2231
|
-
message: 'No contradictions found — all facts are current.',
|
|
2232
|
-
});
|
|
2233
|
-
} catch (err) {
|
|
2234
|
-
return formatResponse({
|
|
2235
|
-
error: 'Contradiction detection requires the temporal knowledge graph (Graphiti)',
|
|
2236
|
-
detail: err.message,
|
|
2237
|
-
});
|
|
2238
|
-
}
|
|
2239
|
-
}
|
|
2240
|
-
|
|
2241
|
-
if (name === "get_recent_episodes") {
|
|
2242
|
-
const { max_episodes = 20 } = args || {};
|
|
2243
|
-
|
|
2244
|
-
try {
|
|
2245
|
-
const result = await apiClient.graphiti.getEpisodes({ max_episodes });
|
|
2246
|
-
return formatResponse(result);
|
|
2247
|
-
} catch (err) {
|
|
2248
|
-
return formatResponse({
|
|
2249
|
-
error: 'Episodic memory requires the temporal knowledge graph (Graphiti)',
|
|
2250
|
-
detail: err.message
|
|
2251
|
-
});
|
|
2252
|
-
}
|
|
2253
|
-
}
|
|
2254
|
-
|
|
2255
|
-
// ===== HELPER TOOLS =====
|
|
2256
|
-
if (name === "get_started") {
|
|
2257
|
-
const { topic = "overview" } = args || {};
|
|
2258
|
-
|
|
2259
|
-
const guides = {
|
|
2260
|
-
overview: {
|
|
2261
|
-
title: "AgentPlanner Overview",
|
|
2262
|
-
description: "AgentPlanner is a collaborative planning system for AI agents and humans to work together on structured plans.",
|
|
2263
|
-
key_concepts: [
|
|
2264
|
-
"Organizations - Groups of users, goals, and resources",
|
|
2265
|
-
"Goals - High-level objectives with success metrics that plans work toward",
|
|
2266
|
-
"Plans - Hierarchical structures with phases, tasks, and milestones",
|
|
2267
|
-
"Nodes - Individual items in a plan (phases contain tasks and milestones)",
|
|
2268
|
-
"Knowledge - Persistent storage for decisions, context, constraints, and learnings"
|
|
2269
|
-
],
|
|
2270
|
-
recommended_workflow: [
|
|
2271
|
-
"1. PREFLIGHT: check_coherence_pending to see if any plans/goals need alignment review",
|
|
2272
|
-
" → If stale items found, run_coherence_check on each before starting task work",
|
|
2273
|
-
"2. Check list_goals to understand current objectives",
|
|
2274
|
-
"3. Use list_plans to see existing plans",
|
|
2275
|
-
"4. Before working on a plan, use get_plan_context to get the full picture",
|
|
2276
|
-
"5. Update task statuses as you work (update_node with status)",
|
|
2277
|
-
"6. Store important decisions and learnings using add_learning",
|
|
2278
|
-
"7. Check recall_knowledge before making decisions to see past context"
|
|
2279
|
-
],
|
|
2280
|
-
quick_tips: [
|
|
2281
|
-
"Always capture WHY decisions were made, not just WHAT",
|
|
2282
|
-
"Mark tasks 'blocked' with notes when stuck - this helps humans help you",
|
|
2283
|
-
"Use logs to document progress so others can follow your work"
|
|
2284
|
-
]
|
|
2285
|
-
},
|
|
2286
|
-
planning: {
|
|
2287
|
-
title: "Planning Best Practices",
|
|
2288
|
-
description: "How to create and structure effective plans.",
|
|
2289
|
-
structure: [
|
|
2290
|
-
"Plans have a hierarchical structure: Plan → Phases → Tasks/Milestones",
|
|
2291
|
-
"Phases are major stages or milestones of work",
|
|
2292
|
-
"Tasks are actionable work items within phases",
|
|
2293
|
-
"Milestones mark significant checkpoints"
|
|
2294
|
-
],
|
|
2295
|
-
tips: [
|
|
2296
|
-
"Break work into phases (major stages)",
|
|
2297
|
-
"Each phase should contain 3-7 tasks (not too granular, not too big)",
|
|
2298
|
-
"Add clear acceptance_criteria to tasks so completion is unambiguous",
|
|
2299
|
-
"Use agent_instructions to guide how AI agents should approach tasks",
|
|
2300
|
-
"Link plans to goals to track how work contributes to objectives"
|
|
2301
|
-
],
|
|
2302
|
-
tools_to_use: ["create_plan", "create_node", "get_plan_structure", "link_plan_to_goal"]
|
|
2303
|
-
},
|
|
2304
|
-
execution: {
|
|
2305
|
-
title: "Executing Plans",
|
|
2306
|
-
description: "How to work through plans effectively.",
|
|
2307
|
-
workflow: [
|
|
2308
|
-
"1. Use get_plan_structure to see the full plan",
|
|
2309
|
-
"2. Find tasks with status 'not_started' or 'in_progress'",
|
|
2310
|
-
"3. Before starting a task, check recall_knowledge for relevant context",
|
|
2311
|
-
"4. Update task status to 'in_progress' when you begin",
|
|
2312
|
-
"5. Add logs to document what you're doing",
|
|
2313
|
-
"6. Mark 'completed' when done, or 'blocked' if stuck"
|
|
2314
|
-
],
|
|
2315
|
-
status_values: {
|
|
2316
|
-
not_started: "Work hasn't begun",
|
|
2317
|
-
in_progress: "Currently being worked on",
|
|
2318
|
-
completed: "Finished and verified",
|
|
2319
|
-
blocked: "Cannot proceed - add notes explaining why",
|
|
2320
|
-
cancelled: "No longer needed"
|
|
2321
|
-
},
|
|
2322
|
-
tips: [
|
|
2323
|
-
"Check get_plan_summary for current progress and blockers",
|
|
2324
|
-
"When blocked, clearly document what's blocking you",
|
|
2325
|
-
"Store learnings as you go - don't wait until the end"
|
|
2326
|
-
],
|
|
2327
|
-
tools_to_use: ["get_plan_structure", "update_node", "add_log", "recall_knowledge"]
|
|
2328
|
-
},
|
|
2329
|
-
knowledge: {
|
|
2330
|
-
title: "Knowledge Management",
|
|
2331
|
-
description: "How to capture and use organizational knowledge effectively.",
|
|
2332
|
-
entry_types: {
|
|
2333
|
-
decision: "Choices made and their rationale - ALWAYS capture WHY",
|
|
2334
|
-
context: "Background information needed to understand something",
|
|
2335
|
-
constraint: "Rules, limitations, or requirements that must be respected",
|
|
2336
|
-
learning: "Insights gained from experience - what worked, what didn't",
|
|
2337
|
-
reference: "Links to external resources or documentation",
|
|
2338
|
-
note: "General notes that don't fit other categories"
|
|
2339
|
-
},
|
|
2340
|
-
best_practices: [
|
|
2341
|
-
"ALWAYS capture significant decisions with reasoning",
|
|
2342
|
-
"Search knowledge BEFORE making decisions (check for constraints)",
|
|
2343
|
-
"Add learnings when you discover something useful",
|
|
2344
|
-
"Tag entries well for easier retrieval later",
|
|
2345
|
-
"Include enough context that future-you can understand"
|
|
2346
|
-
],
|
|
2347
|
-
when_to_create_entries: [
|
|
2348
|
-
"When a decision is made (especially if non-obvious)",
|
|
2349
|
-
"When you learn something that might be useful later",
|
|
2350
|
-
"When you discover a constraint or rule",
|
|
2351
|
-
"When you find a useful resource or reference"
|
|
2352
|
-
],
|
|
2353
|
-
tools_to_use: ["add_learning", "recall_knowledge", "find_entities", "check_contradictions"]
|
|
2354
|
-
},
|
|
2355
|
-
collaboration: {
|
|
2356
|
-
title: "Collaboration",
|
|
2357
|
-
description: "Working with humans and other agents.",
|
|
2358
|
-
tips: [
|
|
2359
|
-
"Plans can be shared with collaborators (viewer, editor, admin roles)",
|
|
2360
|
-
"Use logs to document progress so others can follow your work",
|
|
2361
|
-
"Knowledge stores are shared within their scope (org/goal/plan)",
|
|
2362
|
-
"When stuck, mark tasks as 'blocked' with clear notes - humans will see this"
|
|
2363
|
-
],
|
|
2364
|
-
communication: [
|
|
2365
|
-
"Logs are visible to all plan collaborators",
|
|
2366
|
-
"Knowledge entries persist and are searchable by others",
|
|
2367
|
-
"Clear status updates help humans understand where things stand"
|
|
2368
|
-
],
|
|
2369
|
-
tools_to_use: ["list_organizations", "list_goals", "add_log"]
|
|
2370
|
-
}
|
|
2371
|
-
};
|
|
2372
|
-
|
|
2373
|
-
return formatResponse(guides[topic] || guides.overview);
|
|
2374
|
-
}
|
|
2375
|
-
|
|
2376
|
-
// ===== GOALS HEALTH DASHBOARD =====
|
|
2377
|
-
if (name === "check_goals_health") {
|
|
2378
|
-
const { status_filter } = args || {};
|
|
2379
|
-
const result = await apiClient.goals.getDashboard();
|
|
2380
|
-
|
|
2381
|
-
let goals = result.goals || result;
|
|
2382
|
-
if (status_filter && Array.isArray(goals)) {
|
|
2383
|
-
goals = goals.filter(g => g.health_status === status_filter || g.status === status_filter);
|
|
2384
|
-
}
|
|
2385
|
-
|
|
2386
|
-
return formatResponse({
|
|
2387
|
-
...result,
|
|
2388
|
-
goals,
|
|
2389
|
-
tip: "Prioritize: stale goals first, then at_risk, then on_track."
|
|
2390
|
-
});
|
|
2391
|
-
}
|
|
2392
|
-
|
|
2393
|
-
// ===== TASK CLAIMING =====
|
|
2394
|
-
if (name === "claim_task") {
|
|
2395
|
-
const { task_id, plan_id, ttl_minutes = 30 } = args;
|
|
2396
|
-
const result = await apiClient.nodes.claimTask(plan_id, task_id, 'mcp-agent', ttl_minutes);
|
|
2397
|
-
return formatResponse({
|
|
2398
|
-
success: true,
|
|
2399
|
-
message: `Task ${task_id} claimed for ${ttl_minutes} minutes`,
|
|
2400
|
-
...result,
|
|
2401
|
-
tip: "Remember to release the task when done, or it will auto-expire."
|
|
2402
|
-
});
|
|
2403
|
-
}
|
|
2404
36
|
|
|
2405
|
-
|
|
2406
|
-
const { task_id, plan_id } = args;
|
|
2407
|
-
const result = await apiClient.nodes.releaseTask(plan_id, task_id, 'mcp-agent');
|
|
2408
|
-
return formatResponse({
|
|
2409
|
-
success: true,
|
|
2410
|
-
message: `Task ${task_id} released`,
|
|
2411
|
-
...result
|
|
2412
|
-
});
|
|
2413
|
-
}
|
|
2414
|
-
|
|
2415
|
-
// ===== COHERENCE =====
|
|
2416
|
-
if (name === "check_coherence_pending") {
|
|
2417
|
-
const result = await apiClient.coherence.getPending();
|
|
2418
|
-
const totalStale = (result.stale_plans?.length || 0) + (result.stale_goals?.length || 0);
|
|
2419
|
-
return formatResponse({
|
|
2420
|
-
...result,
|
|
2421
|
-
tip: totalStale > 0
|
|
2422
|
-
? "Review stale items. For each stale plan, call run_coherence_check to evaluate quality and stamp as checked."
|
|
2423
|
-
: "Everything is up to date. No coherence review needed."
|
|
2424
|
-
});
|
|
2425
|
-
}
|
|
2426
|
-
|
|
2427
|
-
if (name === "assess_goal_quality") {
|
|
2428
|
-
const { goal_id } = args;
|
|
2429
|
-
const result = await apiClient.goals.getQuality(goal_id);
|
|
2430
|
-
const lowDims = Object.entries(result.dimensions || {})
|
|
2431
|
-
.filter(([, v]) => v.score < 0.5)
|
|
2432
|
-
.map(([k]) => k);
|
|
2433
|
-
return formatResponse({
|
|
2434
|
-
...result,
|
|
2435
|
-
tip: result.suggestions?.length > 0
|
|
2436
|
-
? `Goal needs improvement in: ${lowDims.join(', ') || 'minor areas'}. Follow the suggestions to strengthen it.`
|
|
2437
|
-
: 'Goal is well-defined. Ready for agent execution.'
|
|
2438
|
-
});
|
|
2439
|
-
}
|
|
2440
|
-
|
|
2441
|
-
if (name === "run_coherence_check") {
|
|
2442
|
-
const { plan_id, goal_id } = args;
|
|
2443
|
-
const result = await apiClient.coherence.runCheck(plan_id, goal_id);
|
|
2444
|
-
return formatResponse({
|
|
2445
|
-
...result,
|
|
2446
|
-
tip: result.coherence_issues_count > 0
|
|
2447
|
-
? `${result.coherence_issues_count} coherence issues found. Review tasks with stale_beliefs or contradiction_detected status.`
|
|
2448
|
-
: "Plan is coherent. Quality score and checked_at timestamp updated."
|
|
2449
|
-
});
|
|
2450
|
-
}
|
|
2451
|
-
|
|
2452
|
-
// Tool not found
|
|
2453
|
-
throw new Error(`Unknown tool: ${name}`);
|
|
2454
|
-
} catch (error) {
|
|
2455
|
-
if (process.env.NODE_ENV === 'development') {
|
|
2456
|
-
console.error(`Error calling tool ${name}:`, error);
|
|
2457
|
-
}
|
|
37
|
+
if (!bdiToolNames.has(name)) {
|
|
2458
38
|
return {
|
|
2459
39
|
isError: true,
|
|
2460
|
-
content: [
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
}
|
|
2465
|
-
]
|
|
40
|
+
content: [{
|
|
41
|
+
type: 'text',
|
|
42
|
+
text: `Unknown tool: ${name}. v0.9.0 ships 15 BDI tools. Run get_started to see them, or check ../docs/MIGRATION_v0.9.md for the legacy → BDI mapping.`,
|
|
43
|
+
}],
|
|
2466
44
|
};
|
|
2467
45
|
}
|
|
2468
|
-
});
|
|
2469
|
-
|
|
2470
|
-
if (process.env.NODE_ENV === 'development') {
|
|
2471
|
-
console.error('Tools setup complete');
|
|
2472
|
-
}
|
|
2473
|
-
}
|
|
2474
46
|
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
return [];
|
|
2481
|
-
}
|
|
2482
|
-
|
|
2483
|
-
// Debug logging to understand the structure
|
|
2484
|
-
if (process.env.NODE_ENV === 'development') {
|
|
2485
|
-
console.error('Building hierarchy for nodes:', nodes.length);
|
|
2486
|
-
if (nodes[0]) {
|
|
2487
|
-
console.error('Sample node:', {
|
|
2488
|
-
id: nodes[0].id,
|
|
2489
|
-
parent_id: nodes[0].parent_id,
|
|
2490
|
-
node_type: nodes[0].node_type
|
|
2491
|
-
});
|
|
2492
|
-
}
|
|
2493
|
-
}
|
|
2494
|
-
|
|
2495
|
-
const nodeMap = new Map();
|
|
2496
|
-
const rootNodes = [];
|
|
2497
|
-
|
|
2498
|
-
// First pass: create all nodes in the map
|
|
2499
|
-
nodes.forEach(node => {
|
|
2500
|
-
const nodeData = includeDetails ? { ...node } : {
|
|
2501
|
-
id: node.id,
|
|
2502
|
-
title: node.title,
|
|
2503
|
-
node_type: node.node_type,
|
|
2504
|
-
status: node.status,
|
|
2505
|
-
parent_id: node.parent_id,
|
|
2506
|
-
order_index: node.order_index
|
|
2507
|
-
};
|
|
2508
|
-
|
|
2509
|
-
// Initialize with empty children array
|
|
2510
|
-
nodeMap.set(node.id, {
|
|
2511
|
-
...nodeData,
|
|
2512
|
-
children: []
|
|
2513
|
-
});
|
|
2514
|
-
});
|
|
2515
|
-
|
|
2516
|
-
// Second pass: build parent-child relationships
|
|
2517
|
-
nodes.forEach(node => {
|
|
2518
|
-
const currentNode = nodeMap.get(node.id);
|
|
2519
|
-
|
|
2520
|
-
if (node.parent_id) {
|
|
2521
|
-
const parent = nodeMap.get(node.parent_id);
|
|
2522
|
-
if (parent) {
|
|
2523
|
-
// Add as child to parent
|
|
2524
|
-
parent.children.push(currentNode);
|
|
2525
|
-
} else {
|
|
2526
|
-
// Parent not found, treat as root
|
|
2527
|
-
if (process.env.NODE_ENV === 'development') {
|
|
2528
|
-
console.error(`Parent ${node.parent_id} not found for node ${node.id}`);
|
|
2529
|
-
}
|
|
2530
|
-
rootNodes.push(currentNode);
|
|
47
|
+
try {
|
|
48
|
+
return await bdiToolHandler(name, args, apiClient);
|
|
49
|
+
} catch (err) {
|
|
50
|
+
if (process.env.NODE_ENV === 'development') {
|
|
51
|
+
console.error(`Tool ${name} threw:`, err);
|
|
2531
52
|
}
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
53
|
+
return {
|
|
54
|
+
isError: true,
|
|
55
|
+
content: [{
|
|
56
|
+
type: 'text',
|
|
57
|
+
text: `Tool ${name} failed: ${err.message || String(err)}`,
|
|
58
|
+
}],
|
|
59
|
+
};
|
|
2535
60
|
}
|
|
2536
61
|
});
|
|
2537
|
-
|
|
2538
|
-
// Special case: if we have a single root node of type 'root', return its children
|
|
2539
|
-
if (rootNodes.length === 1 && rootNodes[0].node_type === 'root') {
|
|
2540
|
-
// Return the root node itself with its children
|
|
2541
|
-
const rootNode = rootNodes[0];
|
|
2542
|
-
|
|
2543
|
-
// Sort children by order_index
|
|
2544
|
-
const sortNodes = (nodeArray) => {
|
|
2545
|
-
nodeArray.sort((a, b) => {
|
|
2546
|
-
const orderA = a.order_index ?? 999;
|
|
2547
|
-
const orderB = b.order_index ?? 999;
|
|
2548
|
-
return orderA - orderB;
|
|
2549
|
-
});
|
|
2550
|
-
|
|
2551
|
-
nodeArray.forEach(node => {
|
|
2552
|
-
if (node.children && node.children.length > 0) {
|
|
2553
|
-
sortNodes(node.children);
|
|
2554
|
-
}
|
|
2555
|
-
});
|
|
2556
|
-
};
|
|
2557
|
-
|
|
2558
|
-
sortNodes(rootNode.children);
|
|
2559
|
-
return [rootNode]; // Return root with its properly sorted children
|
|
2560
|
-
}
|
|
2561
|
-
|
|
2562
|
-
// Sort all root nodes and their children
|
|
2563
|
-
const sortNodes = (nodeArray) => {
|
|
2564
|
-
nodeArray.sort((a, b) => {
|
|
2565
|
-
const orderA = a.order_index ?? 999;
|
|
2566
|
-
const orderB = b.order_index ?? 999;
|
|
2567
|
-
return orderA - orderB;
|
|
2568
|
-
});
|
|
2569
|
-
|
|
2570
|
-
nodeArray.forEach(node => {
|
|
2571
|
-
if (node.children && node.children.length > 0) {
|
|
2572
|
-
sortNodes(node.children);
|
|
2573
|
-
}
|
|
2574
|
-
});
|
|
2575
|
-
};
|
|
2576
|
-
|
|
2577
|
-
sortNodes(rootNodes);
|
|
2578
|
-
|
|
2579
|
-
return rootNodes;
|
|
2580
|
-
}
|
|
2581
|
-
|
|
2582
|
-
/**
|
|
2583
|
-
* Flatten a hierarchical node structure into a flat array
|
|
2584
|
-
*/
|
|
2585
|
-
function flattenNodes(nodes) {
|
|
2586
|
-
const flat = [];
|
|
2587
|
-
|
|
2588
|
-
const processNode = (node) => {
|
|
2589
|
-
flat.push(node);
|
|
2590
|
-
if (node.children && node.children.length > 0) {
|
|
2591
|
-
node.children.forEach(processNode);
|
|
2592
|
-
}
|
|
2593
|
-
};
|
|
2594
|
-
|
|
2595
|
-
if (Array.isArray(nodes)) {
|
|
2596
|
-
nodes.forEach(processNode);
|
|
2597
|
-
}
|
|
2598
|
-
|
|
2599
|
-
return flat;
|
|
2600
|
-
}
|
|
2601
|
-
|
|
2602
|
-
/**
|
|
2603
|
-
* Calculate plan statistics
|
|
2604
|
-
*/
|
|
2605
|
-
function calculatePlanStatistics(nodes) {
|
|
2606
|
-
const stats = {
|
|
2607
|
-
total: 0,
|
|
2608
|
-
type_counts: {
|
|
2609
|
-
root: 0,
|
|
2610
|
-
phase: 0,
|
|
2611
|
-
task: 0,
|
|
2612
|
-
milestone: 0
|
|
2613
|
-
},
|
|
2614
|
-
status_counts: {
|
|
2615
|
-
not_started: 0,
|
|
2616
|
-
in_progress: 0,
|
|
2617
|
-
completed: 0,
|
|
2618
|
-
blocked: 0
|
|
2619
|
-
},
|
|
2620
|
-
in_progress_nodes: [],
|
|
2621
|
-
blocked_nodes: []
|
|
2622
|
-
};
|
|
2623
|
-
|
|
2624
|
-
const processNode = (node) => {
|
|
2625
|
-
stats.total++;
|
|
2626
|
-
|
|
2627
|
-
if (node.node_type && stats.type_counts[node.node_type] !== undefined) {
|
|
2628
|
-
stats.type_counts[node.node_type]++;
|
|
2629
|
-
}
|
|
2630
|
-
|
|
2631
|
-
if (node.status && stats.status_counts[node.status] !== undefined) {
|
|
2632
|
-
stats.status_counts[node.status]++;
|
|
2633
|
-
|
|
2634
|
-
if (node.status === 'in_progress') {
|
|
2635
|
-
stats.in_progress_nodes.push({
|
|
2636
|
-
id: node.id,
|
|
2637
|
-
title: node.title,
|
|
2638
|
-
type: node.node_type
|
|
2639
|
-
});
|
|
2640
|
-
} else if (node.status === 'blocked') {
|
|
2641
|
-
stats.blocked_nodes.push({
|
|
2642
|
-
id: node.id,
|
|
2643
|
-
title: node.title,
|
|
2644
|
-
type: node.node_type
|
|
2645
|
-
});
|
|
2646
|
-
}
|
|
2647
|
-
}
|
|
2648
|
-
|
|
2649
|
-
if (node.children && node.children.length > 0) {
|
|
2650
|
-
node.children.forEach(processNode);
|
|
2651
|
-
}
|
|
2652
|
-
};
|
|
2653
|
-
|
|
2654
|
-
nodes.forEach(processNode);
|
|
2655
|
-
|
|
2656
|
-
return stats;
|
|
2657
62
|
}
|
|
2658
63
|
|
|
2659
64
|
module.exports = { setupTools };
|