@magic-ingredients/tiny-brain-local 0.3.10
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/LICENSE +21 -0
- package/README.md +81 -0
- package/dist/core/console-logger.d.ts +30 -0
- package/dist/core/console-logger.d.ts.map +1 -0
- package/dist/core/console-logger.js +101 -0
- package/dist/core/console-logger.js.map +1 -0
- package/dist/core/file-logger.d.ts +40 -0
- package/dist/core/file-logger.d.ts.map +1 -0
- package/dist/core/file-logger.js +223 -0
- package/dist/core/file-logger.js.map +1 -0
- package/dist/core/mcp-server.d.ts +54 -0
- package/dist/core/mcp-server.d.ts.map +1 -0
- package/dist/core/mcp-server.js +295 -0
- package/dist/core/mcp-server.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +37 -0
- package/dist/index.js.map +1 -0
- package/dist/prompts/index.d.ts +39 -0
- package/dist/prompts/index.d.ts.map +1 -0
- package/dist/prompts/index.js +5 -0
- package/dist/prompts/index.js.map +1 -0
- package/dist/prompts/memory/memory.prompt.d.ts +32 -0
- package/dist/prompts/memory/memory.prompt.d.ts.map +1 -0
- package/dist/prompts/memory/memory.prompt.js +204 -0
- package/dist/prompts/memory/memory.prompt.js.map +1 -0
- package/dist/prompts/persona/persona.prompt.d.ts +27 -0
- package/dist/prompts/persona/persona.prompt.d.ts.map +1 -0
- package/dist/prompts/persona/persona.prompt.js +592 -0
- package/dist/prompts/persona/persona.prompt.js.map +1 -0
- package/dist/prompts/planning/planning.prompt.d.ts +56 -0
- package/dist/prompts/planning/planning.prompt.d.ts.map +1 -0
- package/dist/prompts/planning/planning.prompt.js +1016 -0
- package/dist/prompts/planning/planning.prompt.js.map +1 -0
- package/dist/prompts/prompt-registry.d.ts +25 -0
- package/dist/prompts/prompt-registry.d.ts.map +1 -0
- package/dist/prompts/prompt-registry.js +68 -0
- package/dist/prompts/prompt-registry.js.map +1 -0
- package/dist/prompts/thinking/thinking.prompt.d.ts +29 -0
- package/dist/prompts/thinking/thinking.prompt.d.ts.map +1 -0
- package/dist/prompts/thinking/thinking.prompt.js +171 -0
- package/dist/prompts/thinking/thinking.prompt.js.map +1 -0
- package/dist/services/UpdateService.d.ts +29 -0
- package/dist/services/UpdateService.d.ts.map +1 -0
- package/dist/services/UpdateService.js +132 -0
- package/dist/services/UpdateService.js.map +1 -0
- package/dist/services/plan-watcher.service.d.ts +143 -0
- package/dist/services/plan-watcher.service.d.ts.map +1 -0
- package/dist/services/plan-watcher.service.js +914 -0
- package/dist/services/plan-watcher.service.js.map +1 -0
- package/dist/storage/local-filesystem-adapter.d.ts +39 -0
- package/dist/storage/local-filesystem-adapter.d.ts.map +1 -0
- package/dist/storage/local-filesystem-adapter.js +208 -0
- package/dist/storage/local-filesystem-adapter.js.map +1 -0
- package/dist/storage/storage-path-builder.d.ts +14 -0
- package/dist/storage/storage-path-builder.d.ts.map +1 -0
- package/dist/storage/storage-path-builder.js +43 -0
- package/dist/storage/storage-path-builder.js.map +1 -0
- package/dist/test-setup.d.ts +2 -0
- package/dist/test-setup.d.ts.map +1 -0
- package/dist/test-setup.js +12 -0
- package/dist/test-setup.js.map +1 -0
- package/dist/tools/analyse-request/analyse-request.tool.d.ts +8 -0
- package/dist/tools/analyse-request/analyse-request.tool.d.ts.map +1 -0
- package/dist/tools/analyse-request/analyse-request.tool.js +120 -0
- package/dist/tools/analyse-request/analyse-request.tool.js.map +1 -0
- package/dist/tools/index.d.ts +69 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +24 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/memory/memory.tool.d.ts +15 -0
- package/dist/tools/memory/memory.tool.d.ts.map +1 -0
- package/dist/tools/memory/memory.tool.js +110 -0
- package/dist/tools/memory/memory.tool.js.map +1 -0
- package/dist/tools/persona/as.tool.d.ts +25 -0
- package/dist/tools/persona/as.tool.d.ts.map +1 -0
- package/dist/tools/persona/as.tool.js +294 -0
- package/dist/tools/persona/as.tool.js.map +1 -0
- package/dist/tools/persona/persona.tool.d.ts +8 -0
- package/dist/tools/persona/persona.tool.d.ts.map +1 -0
- package/dist/tools/persona/persona.tool.js +193 -0
- package/dist/tools/persona/persona.tool.js.map +1 -0
- package/dist/tools/plan/plan.tool.d.ts +18 -0
- package/dist/tools/plan/plan.tool.d.ts.map +1 -0
- package/dist/tools/plan/plan.tool.js +643 -0
- package/dist/tools/plan/plan.tool.js.map +1 -0
- package/dist/tools/strategy/strategy.tool.d.ts +13 -0
- package/dist/tools/strategy/strategy.tool.d.ts.map +1 -0
- package/dist/tools/strategy/strategy.tool.js +199 -0
- package/dist/tools/strategy/strategy.tool.js.map +1 -0
- package/dist/tools/thinking/thinking.tool.d.ts +13 -0
- package/dist/tools/thinking/thinking.tool.d.ts.map +1 -0
- package/dist/tools/thinking/thinking.tool.js +226 -0
- package/dist/tools/thinking/thinking.tool.js.map +1 -0
- package/dist/tools/tool-registry.d.ts +20 -0
- package/dist/tools/tool-registry.d.ts.map +1 -0
- package/dist/tools/tool-registry.js +61 -0
- package/dist/tools/tool-registry.js.map +1 -0
- package/dist/tools/update/update.tool.d.ts +15 -0
- package/dist/tools/update/update.tool.d.ts.map +1 -0
- package/dist/tools/update/update.tool.js +86 -0
- package/dist/tools/update/update.tool.js.map +1 -0
- package/dist/tools/validate-response/validate-response.tool.d.ts +13 -0
- package/dist/tools/validate-response/validate-response.tool.d.ts.map +1 -0
- package/dist/tools/validate-response/validate-response.tool.js +142 -0
- package/dist/tools/validate-response/validate-response.tool.js.map +1 -0
- package/dist/types/request-context.d.ts +7 -0
- package/dist/types/request-context.d.ts.map +1 -0
- package/dist/types/request-context.js +7 -0
- package/dist/types/request-context.js.map +1 -0
- package/package.json +77 -0
|
@@ -0,0 +1,643 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { createSuccessResult, createErrorResult } from '../index.js';
|
|
3
|
+
import { PlanningService } from '@magic-ingredients/tiny-brain-core';
|
|
4
|
+
import { PlanWatcherService } from '../../services/plan-watcher.service.js';
|
|
5
|
+
const PlanArgsSchema = z.object({
|
|
6
|
+
operation: z.enum(['accept', 'update', 'status', 'report', 'list', 'archive', 'switch', 'watch', 'stop-watch']),
|
|
7
|
+
// For 'accept' operation
|
|
8
|
+
planName: z.string().optional(),
|
|
9
|
+
planDetails: z.string().optional(),
|
|
10
|
+
// For 'update' operation
|
|
11
|
+
planId: z.string().optional(),
|
|
12
|
+
updates: z
|
|
13
|
+
.object({
|
|
14
|
+
overview: z.string().optional(),
|
|
15
|
+
updateBlockers: z
|
|
16
|
+
.array(z.object({
|
|
17
|
+
description: z.string(),
|
|
18
|
+
severity: z.enum(['low', 'medium', 'high']),
|
|
19
|
+
todoId: z.string().optional(),
|
|
20
|
+
}))
|
|
21
|
+
.optional(),
|
|
22
|
+
addPhase: z
|
|
23
|
+
.object({
|
|
24
|
+
title: z.string(),
|
|
25
|
+
description: z.string(),
|
|
26
|
+
})
|
|
27
|
+
.optional(),
|
|
28
|
+
updatePhase: z
|
|
29
|
+
.object({
|
|
30
|
+
phaseNumber: z.number(),
|
|
31
|
+
status: z.enum(['pending', 'in_progress', 'completed']).optional(),
|
|
32
|
+
addTodo: z.string().optional(),
|
|
33
|
+
completeTodo: z.string().optional(),
|
|
34
|
+
// Bulk operations
|
|
35
|
+
addTodos: z.array(z.string()).optional(),
|
|
36
|
+
completeTodos: z.array(z.string()).optional(),
|
|
37
|
+
})
|
|
38
|
+
.optional(),
|
|
39
|
+
// Support for updating multiple phases
|
|
40
|
+
updatePhases: z
|
|
41
|
+
.array(z.object({
|
|
42
|
+
phaseNumber: z.number(),
|
|
43
|
+
status: z.enum(['pending', 'in_progress', 'completed']).optional(),
|
|
44
|
+
addTodo: z.string().optional(),
|
|
45
|
+
completeTodo: z.string().optional(),
|
|
46
|
+
// Bulk operations
|
|
47
|
+
addTodos: z.array(z.string()).optional(),
|
|
48
|
+
completeTodos: z.array(z.string()).optional(),
|
|
49
|
+
}))
|
|
50
|
+
.optional(),
|
|
51
|
+
})
|
|
52
|
+
.optional(),
|
|
53
|
+
// For 'status' operation
|
|
54
|
+
verbose: z.boolean().optional(),
|
|
55
|
+
// For 'report' operation
|
|
56
|
+
saveToFile: z.boolean().optional(),
|
|
57
|
+
// For 'list' operation
|
|
58
|
+
listStatus: z.enum(['active', 'completed', 'archived', 'all']).optional(),
|
|
59
|
+
// For 'archive' operation
|
|
60
|
+
reason: z.string().optional(),
|
|
61
|
+
// For 'switch' operation
|
|
62
|
+
// planId is reused from update operation
|
|
63
|
+
// Note: switchToPlan automatically unarchives if needed
|
|
64
|
+
// For 'watch' operation
|
|
65
|
+
port: z.number().optional().default(8765),
|
|
66
|
+
autoOpen: z.boolean().optional().default(true),
|
|
67
|
+
showAll: z.boolean().optional().default(false),
|
|
68
|
+
});
|
|
69
|
+
// Singleton watcher instance
|
|
70
|
+
let watcherInstance = null;
|
|
71
|
+
// For testing - clear the singleton
|
|
72
|
+
export function clearWatcherInstance() {
|
|
73
|
+
watcherInstance = null;
|
|
74
|
+
}
|
|
75
|
+
export class PlanTool {
|
|
76
|
+
static getToolDefinition() {
|
|
77
|
+
return {
|
|
78
|
+
name: 'plan',
|
|
79
|
+
description: `📋 LIVING DOCUMENT PLANNER 📋
|
|
80
|
+
|
|
81
|
+
⚡ COLLABORATIVE PLANNING: Captures user-AI planning discussions and creates structured, trackable plans.
|
|
82
|
+
|
|
83
|
+
✅ OPERATIONS:
|
|
84
|
+
• accept: Create plan from collaborative discussion (planName + planDetails)
|
|
85
|
+
• update: Add phases, todos, or update status (usually automatic)
|
|
86
|
+
• status: Show current plan progress and summary
|
|
87
|
+
• report: Generate and display comprehensive markdown report
|
|
88
|
+
• list: List all plans (active, completed, archived)
|
|
89
|
+
• archive: Archive a completed plan
|
|
90
|
+
• switch: Switch to a different plan (automatically unarchives if needed)
|
|
91
|
+
• watch: Launch web dashboard to monitor plans in real-time
|
|
92
|
+
• stop-watch: Stop the running plan watcher dashboard
|
|
93
|
+
|
|
94
|
+
💡 WORKFLOW:
|
|
95
|
+
1. User & AI collaborate on planning in chat
|
|
96
|
+
2. AI invokes 'accept' with planName + full planning discussion
|
|
97
|
+
3. Tool extracts phases, todos, and structure automatically
|
|
98
|
+
4. Creates persona-specific JSON plan document
|
|
99
|
+
5. Tracks updates and progress across conversations
|
|
100
|
+
|
|
101
|
+
🎯 FEATURES:
|
|
102
|
+
• Automatic extraction of phases and todos from discussions
|
|
103
|
+
• Smart parsing of collaborative planning conversations
|
|
104
|
+
• Cross-chat plan continuity and progress tracking
|
|
105
|
+
• Living documents that evolve with your project
|
|
106
|
+
|
|
107
|
+
📝 UPDATE BEST PRACTICES:
|
|
108
|
+
• After accepting a plan, use the returned JSON structure to make precise updates
|
|
109
|
+
• Match todo content exactly as shown in the structure
|
|
110
|
+
• Update phases based on actual progress made
|
|
111
|
+
• Complete todos by referencing their exact content from the plan structure
|
|
112
|
+
• Use bulk operations to update multiple items at once:
|
|
113
|
+
- completeTodos: ["todo1", "todo2"] to complete multiple todos
|
|
114
|
+
- addTodos: ["task1", "task2"] to add multiple todos
|
|
115
|
+
- updatePhases: [{...}, {...}] to update multiple phases`,
|
|
116
|
+
inputSchema: {
|
|
117
|
+
type: 'object',
|
|
118
|
+
properties: {
|
|
119
|
+
operation: {
|
|
120
|
+
type: 'string',
|
|
121
|
+
enum: ['accept', 'update', 'status', 'report', 'list', 'archive', 'switch', 'watch', 'stop-watch'],
|
|
122
|
+
description: 'The planning operation to perform',
|
|
123
|
+
},
|
|
124
|
+
// For 'accept' operation
|
|
125
|
+
planName: {
|
|
126
|
+
type: 'string',
|
|
127
|
+
description: 'Name/title for the new plan (required for accept)',
|
|
128
|
+
},
|
|
129
|
+
planDetails: {
|
|
130
|
+
type: 'string',
|
|
131
|
+
description: 'Large chunk of text containing the collaborative planning discussion between user and AI (required for accept)',
|
|
132
|
+
},
|
|
133
|
+
// For 'update' operation
|
|
134
|
+
planId: {
|
|
135
|
+
type: 'string',
|
|
136
|
+
description: 'Plan ID to update (optional, uses current active if not provided)',
|
|
137
|
+
},
|
|
138
|
+
updates: {
|
|
139
|
+
type: 'object',
|
|
140
|
+
properties: {
|
|
141
|
+
overview: { type: 'string' },
|
|
142
|
+
updateBlockers: {
|
|
143
|
+
type: 'array',
|
|
144
|
+
items: {
|
|
145
|
+
type: 'object',
|
|
146
|
+
properties: {
|
|
147
|
+
description: { type: 'string' },
|
|
148
|
+
severity: { type: 'string', enum: ['low', 'medium', 'high'] },
|
|
149
|
+
todoId: { type: 'string' },
|
|
150
|
+
},
|
|
151
|
+
required: ['description', 'severity'],
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
addPhase: {
|
|
155
|
+
type: 'object',
|
|
156
|
+
properties: {
|
|
157
|
+
title: { type: 'string' },
|
|
158
|
+
description: { type: 'string' },
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
updatePhase: {
|
|
162
|
+
type: 'object',
|
|
163
|
+
properties: {
|
|
164
|
+
phaseNumber: { type: 'number' },
|
|
165
|
+
status: { type: 'string', enum: ['pending', 'in_progress', 'completed'] },
|
|
166
|
+
addTodo: { type: 'string' },
|
|
167
|
+
completeTodo: { type: 'string' },
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
description: 'Updates to apply to the plan. Use exact todo content from plan structure for completeTodo. Update phases based on actual work completed.',
|
|
172
|
+
},
|
|
173
|
+
// For 'status' operation
|
|
174
|
+
verbose: {
|
|
175
|
+
type: 'boolean',
|
|
176
|
+
description: 'Show detailed todo lists in status',
|
|
177
|
+
},
|
|
178
|
+
// For 'report' operation
|
|
179
|
+
saveToFile: {
|
|
180
|
+
type: 'boolean',
|
|
181
|
+
description: 'Save markdown report to file (optional for report)',
|
|
182
|
+
},
|
|
183
|
+
// For 'list' operation
|
|
184
|
+
listStatus: {
|
|
185
|
+
type: 'string',
|
|
186
|
+
enum: ['active', 'completed', 'archived', 'all'],
|
|
187
|
+
description: 'Filter plans by status',
|
|
188
|
+
},
|
|
189
|
+
// For 'archive' operation
|
|
190
|
+
reason: {
|
|
191
|
+
type: 'string',
|
|
192
|
+
description: 'Reason for archiving',
|
|
193
|
+
},
|
|
194
|
+
// For 'switch' operation
|
|
195
|
+
// planId is reused from update operation
|
|
196
|
+
force: {
|
|
197
|
+
type: 'boolean',
|
|
198
|
+
description: 'Force switch to archived plans (optional)',
|
|
199
|
+
},
|
|
200
|
+
// For 'watch' operation
|
|
201
|
+
port: {
|
|
202
|
+
type: 'number',
|
|
203
|
+
description: 'Port number for the dashboard (default: 8765)',
|
|
204
|
+
},
|
|
205
|
+
autoOpen: {
|
|
206
|
+
type: 'boolean',
|
|
207
|
+
description: 'Auto-open browser when dashboard starts',
|
|
208
|
+
},
|
|
209
|
+
showAll: {
|
|
210
|
+
type: 'boolean',
|
|
211
|
+
description: 'Show all plans including archived (default: false)',
|
|
212
|
+
},
|
|
213
|
+
},
|
|
214
|
+
required: ['operation'],
|
|
215
|
+
},
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
static async execute(args, context) {
|
|
219
|
+
try {
|
|
220
|
+
const validatedArgs = PlanArgsSchema.parse(args);
|
|
221
|
+
// All plan operations except watch/stop-watch require an active persona
|
|
222
|
+
if (validatedArgs.operation !== 'watch' && validatedArgs.operation !== 'stop-watch' && !context.activePersona?.id) {
|
|
223
|
+
return createErrorResult('No active persona found. Plan operations require an active persona. Use the "as" tool to switch to a persona first.');
|
|
224
|
+
}
|
|
225
|
+
switch (validatedArgs.operation) {
|
|
226
|
+
case 'accept':
|
|
227
|
+
return await PlanTool.handleAccept(validatedArgs, context);
|
|
228
|
+
case 'update':
|
|
229
|
+
return await PlanTool.handleUpdate(validatedArgs, context);
|
|
230
|
+
case 'status':
|
|
231
|
+
return await PlanTool.handleStatus(validatedArgs, context);
|
|
232
|
+
case 'report':
|
|
233
|
+
return await PlanTool.handleReport(validatedArgs, context);
|
|
234
|
+
case 'list':
|
|
235
|
+
return await PlanTool.handleList(validatedArgs, context);
|
|
236
|
+
case 'archive':
|
|
237
|
+
return await PlanTool.handleArchive(validatedArgs, context);
|
|
238
|
+
case 'switch':
|
|
239
|
+
return await PlanTool.handleSwitch(validatedArgs, context);
|
|
240
|
+
case 'watch':
|
|
241
|
+
return await PlanTool.handleWatch(validatedArgs, context);
|
|
242
|
+
case 'stop-watch':
|
|
243
|
+
return await PlanTool.handleStopWatch(validatedArgs, context);
|
|
244
|
+
default:
|
|
245
|
+
return createErrorResult(`Unknown operation: ${validatedArgs.operation}`);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
catch (error) {
|
|
249
|
+
if (error instanceof z.ZodError) {
|
|
250
|
+
return createErrorResult(`Invalid arguments: ${error.errors.map((e) => e.message).join(', ')}`);
|
|
251
|
+
}
|
|
252
|
+
return createErrorResult(error instanceof Error ? error.message : 'Unknown error occurred');
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
static async handleAccept(args, context) {
|
|
256
|
+
if (!args.planName || !args.planDetails) {
|
|
257
|
+
return createErrorResult('Plan name and plan details are required to accept a new plan.');
|
|
258
|
+
}
|
|
259
|
+
try {
|
|
260
|
+
// Create the plan using service
|
|
261
|
+
if (!context.activePersona) {
|
|
262
|
+
return createErrorResult('Active persona is required');
|
|
263
|
+
}
|
|
264
|
+
// Create context with plan input including planDetails for phase extraction
|
|
265
|
+
// Create service with RequestContext
|
|
266
|
+
const planningService = new PlanningService(context);
|
|
267
|
+
const plan = await planningService.createPlan({
|
|
268
|
+
title: args.planName,
|
|
269
|
+
overview: args.planDetails,
|
|
270
|
+
planDetails: args.planDetails, // Service will extract phases from this
|
|
271
|
+
});
|
|
272
|
+
// Set the new plan as active
|
|
273
|
+
const setActiveResult = await planningService.setActivePlan({ planId: plan.id });
|
|
274
|
+
if (!setActiveResult) {
|
|
275
|
+
console.warn(`Warning: Failed to set plan ${plan.id} as active for persona ${context.activePersona?.id}`);
|
|
276
|
+
}
|
|
277
|
+
// Format response using service method
|
|
278
|
+
const planStructureJson = await planningService.formatPlanStructure(plan);
|
|
279
|
+
const planStructure = JSON.parse(planStructureJson);
|
|
280
|
+
const response = [
|
|
281
|
+
`✅ Created plan: "${plan.title}"`,
|
|
282
|
+
`Plan ID: ${plan.id}`,
|
|
283
|
+
`Persona: ${context.activePersona?.id || 'unknown'}`,
|
|
284
|
+
'',
|
|
285
|
+
`Extracted ${plan.phases.length} phases:`,
|
|
286
|
+
...plan.phases.map((phase, i) => `${i + 1}. ${phase.title}`),
|
|
287
|
+
'',
|
|
288
|
+
'## Plan Structure (for AI reference)',
|
|
289
|
+
'```json',
|
|
290
|
+
JSON.stringify(planStructure, null, 2),
|
|
291
|
+
'```',
|
|
292
|
+
'',
|
|
293
|
+
'## How to Update This Plan',
|
|
294
|
+
'When calling plan update, reference the structure above to:',
|
|
295
|
+
'1. Complete todos by exact content match: `completeTodo: "exact todo text"`',
|
|
296
|
+
'2. Update phase status: `updatePhase: { phaseNumber: 1, status: "completed" }`',
|
|
297
|
+
'3. Add new todos: `updatePhase: { phaseNumber: 1, addTodo: "new task" }`',
|
|
298
|
+
'',
|
|
299
|
+
'Example update calls:',
|
|
300
|
+
'',
|
|
301
|
+
'**Single operations (backward compatible):**',
|
|
302
|
+
'```json',
|
|
303
|
+
JSON.stringify({
|
|
304
|
+
operation: 'update',
|
|
305
|
+
updates: {
|
|
306
|
+
updatePhase: {
|
|
307
|
+
phaseNumber: 1,
|
|
308
|
+
status: 'in_progress',
|
|
309
|
+
completeTodo: 'First todo content from structure above',
|
|
310
|
+
},
|
|
311
|
+
},
|
|
312
|
+
}, null, 2),
|
|
313
|
+
'```',
|
|
314
|
+
'',
|
|
315
|
+
'**Bulk operations (NEW):**',
|
|
316
|
+
'```json',
|
|
317
|
+
JSON.stringify({
|
|
318
|
+
operation: 'update',
|
|
319
|
+
updates: {
|
|
320
|
+
updatePhase: {
|
|
321
|
+
phaseNumber: 1,
|
|
322
|
+
status: 'completed',
|
|
323
|
+
completeTodos: ['First todo content', 'Second todo content', 'Third todo content'],
|
|
324
|
+
},
|
|
325
|
+
},
|
|
326
|
+
}, null, 2),
|
|
327
|
+
'```',
|
|
328
|
+
'',
|
|
329
|
+
'**Multiple phase updates:**',
|
|
330
|
+
'```json',
|
|
331
|
+
JSON.stringify({
|
|
332
|
+
operation: 'update',
|
|
333
|
+
updates: {
|
|
334
|
+
updatePhases: [
|
|
335
|
+
{
|
|
336
|
+
phaseNumber: 1,
|
|
337
|
+
status: 'completed',
|
|
338
|
+
completeTodos: ['Todo 1', 'Todo 2'],
|
|
339
|
+
},
|
|
340
|
+
{
|
|
341
|
+
phaseNumber: 2,
|
|
342
|
+
status: 'in_progress',
|
|
343
|
+
addTodos: ['New task 1', 'New task 2'],
|
|
344
|
+
},
|
|
345
|
+
],
|
|
346
|
+
},
|
|
347
|
+
}, null, 2),
|
|
348
|
+
'```',
|
|
349
|
+
'',
|
|
350
|
+
'Use "plan operation=status" to see the current status.',
|
|
351
|
+
].join('\n');
|
|
352
|
+
return createSuccessResult(response);
|
|
353
|
+
}
|
|
354
|
+
catch (error) {
|
|
355
|
+
return createErrorResult(`Failed to create plan: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
static async handleUpdate(args, context) {
|
|
359
|
+
try {
|
|
360
|
+
// Get the plan to update
|
|
361
|
+
let plan;
|
|
362
|
+
// Create service with RequestContext
|
|
363
|
+
const planningService = new PlanningService(context);
|
|
364
|
+
if (args.planId) {
|
|
365
|
+
if (!context.activePersona) {
|
|
366
|
+
return createErrorResult('Active persona is required');
|
|
367
|
+
}
|
|
368
|
+
plan = await planningService.loadPlan({ planId: args.planId });
|
|
369
|
+
if (!plan) {
|
|
370
|
+
return createErrorResult(`Plan not found: ${args.planId}`);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
else {
|
|
374
|
+
if (!context.activePersona) {
|
|
375
|
+
return createErrorResult('Active persona is required');
|
|
376
|
+
}
|
|
377
|
+
plan = await planningService.getActivePlan();
|
|
378
|
+
if (!plan) {
|
|
379
|
+
return createErrorResult('No active plan found. Specify a planId or accept a new plan.');
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
// Apply updates using service
|
|
383
|
+
const updatedPlan = await planningService.updatePlan({
|
|
384
|
+
planId: plan.id,
|
|
385
|
+
updates: args.updates || {},
|
|
386
|
+
});
|
|
387
|
+
// Generate status report using service
|
|
388
|
+
const statusReport = await planningService.generateStatusReport(updatedPlan);
|
|
389
|
+
const response = [
|
|
390
|
+
`✅ Updated plan: "${updatedPlan.title}"`,
|
|
391
|
+
'',
|
|
392
|
+
'Plan updated successfully.',
|
|
393
|
+
'',
|
|
394
|
+
statusReport,
|
|
395
|
+
].join('\n');
|
|
396
|
+
return createSuccessResult(response);
|
|
397
|
+
}
|
|
398
|
+
catch (error) {
|
|
399
|
+
return createErrorResult(`Failed to update plan: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
static async handleStatus(args, context) {
|
|
403
|
+
try {
|
|
404
|
+
if (!context.activePersona) {
|
|
405
|
+
return createErrorResult('No active persona found. Please select a persona first.');
|
|
406
|
+
}
|
|
407
|
+
// For non-verbose, use the summary method for better performance
|
|
408
|
+
if (!args.verbose) {
|
|
409
|
+
// Create service with RequestContext
|
|
410
|
+
const planningService = new PlanningService(context);
|
|
411
|
+
const planSummary = await planningService.getActivePlanSummary();
|
|
412
|
+
if (!planSummary) {
|
|
413
|
+
return createErrorResult(`No active plan found for persona "${context.activePersona.id}". Use "plan operation=list" to see all plans.`);
|
|
414
|
+
}
|
|
415
|
+
// Generate status report using a temporary plan object from summary
|
|
416
|
+
const tempPlan = {
|
|
417
|
+
...planSummary,
|
|
418
|
+
overview: '',
|
|
419
|
+
phases: [],
|
|
420
|
+
metadata: {
|
|
421
|
+
totalPhases: planSummary.currentState.overallProgress.totalPhases,
|
|
422
|
+
completedPhases: planSummary.currentState.overallProgress.completedPhases,
|
|
423
|
+
totalTodos: planSummary.currentState.overallProgress.totalTodos,
|
|
424
|
+
completedTodos: planSummary.currentState.overallProgress.completedTodos,
|
|
425
|
+
lastChanges: [],
|
|
426
|
+
contributors: [],
|
|
427
|
+
tags: [],
|
|
428
|
+
},
|
|
429
|
+
created: '',
|
|
430
|
+
};
|
|
431
|
+
const statusReport = await planningService.generateStatusReport(tempPlan);
|
|
432
|
+
return createSuccessResult(statusReport);
|
|
433
|
+
}
|
|
434
|
+
else {
|
|
435
|
+
// For verbose, load the full plan
|
|
436
|
+
// Create service with RequestContext
|
|
437
|
+
const planningService = new PlanningService(context);
|
|
438
|
+
const plan = await planningService.getActivePlan();
|
|
439
|
+
if (!plan) {
|
|
440
|
+
return createErrorResult(`No active plan found for persona "${context.activePersona.id}". Use "plan operation=list" to see all plans.`);
|
|
441
|
+
}
|
|
442
|
+
const formattedPlan = await planningService.formatPlan(plan, true);
|
|
443
|
+
return createSuccessResult(formattedPlan);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
catch (error) {
|
|
447
|
+
return createErrorResult(`Failed to get plan status: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
static async handleReport(args, context) {
|
|
451
|
+
try {
|
|
452
|
+
if (!context.activePersona) {
|
|
453
|
+
return createErrorResult('No active persona found. Please select a persona first.');
|
|
454
|
+
}
|
|
455
|
+
// Create service with RequestContext
|
|
456
|
+
const planningService = new PlanningService(context);
|
|
457
|
+
const plan = await planningService.getActivePlan();
|
|
458
|
+
if (!plan) {
|
|
459
|
+
return createErrorResult('No active plan found.');
|
|
460
|
+
}
|
|
461
|
+
const report = await planningService.formatPlan(plan, true);
|
|
462
|
+
if (args.saveToFile) {
|
|
463
|
+
// Save report to storage
|
|
464
|
+
const filename = `plan-report-${plan.id}-${new Date().toISOString().split('T')[0]}.md`;
|
|
465
|
+
const path = `reports/${filename}`;
|
|
466
|
+
await context.storage?.storeUserData(path, report, context.userId);
|
|
467
|
+
const response = [`Report saved to: ${path}`, '', report].join('\n');
|
|
468
|
+
return createSuccessResult(response);
|
|
469
|
+
}
|
|
470
|
+
const response = report;
|
|
471
|
+
return createSuccessResult(response);
|
|
472
|
+
}
|
|
473
|
+
catch (error) {
|
|
474
|
+
return createErrorResult(`Failed to generate report: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
static async handleList(args, context) {
|
|
478
|
+
try {
|
|
479
|
+
if (!context.activePersona) {
|
|
480
|
+
return createErrorResult('No active persona found. Please select a persona first.');
|
|
481
|
+
}
|
|
482
|
+
const status = args.listStatus || 'active';
|
|
483
|
+
// Create service with RequestContext
|
|
484
|
+
const planningService = new PlanningService(context);
|
|
485
|
+
// Map 'completed' to 'all' since we need to load all plans to filter by status
|
|
486
|
+
const serviceStatus = status === 'completed' ? 'all' : status;
|
|
487
|
+
const allPlans = await planningService.listPlans({
|
|
488
|
+
type: serviceStatus === 'all' ? undefined : serviceStatus
|
|
489
|
+
});
|
|
490
|
+
// Filter by actual plan status if we're looking for completed plans
|
|
491
|
+
const plans = status === 'completed'
|
|
492
|
+
? allPlans.filter(plan => plan.status === 'complete')
|
|
493
|
+
: allPlans;
|
|
494
|
+
if (plans.length === 0) {
|
|
495
|
+
return createSuccessResult(`No ${status} plans found for persona "${context.activePersona.id}".`);
|
|
496
|
+
}
|
|
497
|
+
// Get the active plan to mark it
|
|
498
|
+
const activePlan = await planningService.getActivePlan();
|
|
499
|
+
const activePlanId = activePlan?.id;
|
|
500
|
+
// Format the plan list with active plan indicator
|
|
501
|
+
const lines = [`📋 Plans for persona "${context.activePersona?.id || 'unknown'}":`];
|
|
502
|
+
lines.push('');
|
|
503
|
+
if (plans.length === 0) {
|
|
504
|
+
lines.push('No plans found. Create one with: plan operation="accept"');
|
|
505
|
+
}
|
|
506
|
+
else {
|
|
507
|
+
lines.push('Available plans:');
|
|
508
|
+
lines.push('');
|
|
509
|
+
for (const plan of plans) {
|
|
510
|
+
const isActive = plan.id === activePlanId;
|
|
511
|
+
const statusEmoji = plan.type === 'active' ? '🟢' : plan.status === 'complete' ? '✅' : '📦';
|
|
512
|
+
const activeMarker = isActive ? ' ⭐ [ACTIVE]' : '';
|
|
513
|
+
lines.push(`${statusEmoji} **${plan.title}** (${plan.id})${activeMarker}`);
|
|
514
|
+
lines.push(` Progress: ${plan.currentState.overallProgress.percentComplete}% - ${plan.currentState.overallProgress.completedPhases}/${plan.phases.length} phases, ${plan.currentState.overallProgress.completedTodos}/${plan.currentState.overallProgress.totalTodos} tasks`);
|
|
515
|
+
lines.push(` Last updated: ${new Date(plan.lastUpdated).toLocaleDateString()}`);
|
|
516
|
+
lines.push('');
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
return createSuccessResult(lines.join('\n'));
|
|
520
|
+
}
|
|
521
|
+
catch (error) {
|
|
522
|
+
return createErrorResult(`Failed to list plans: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
static async handleArchive(args, context) {
|
|
526
|
+
try {
|
|
527
|
+
if (!context.activePersona) {
|
|
528
|
+
return createErrorResult('No active persona found. Please select a persona first.');
|
|
529
|
+
}
|
|
530
|
+
// planId is required for archive operation
|
|
531
|
+
if (!args.planId) {
|
|
532
|
+
return createErrorResult('Plan ID is required for archive operation. Use "plan operation=list" to see available plans.');
|
|
533
|
+
}
|
|
534
|
+
// Create service with RequestContext
|
|
535
|
+
const planningService = new PlanningService(context);
|
|
536
|
+
// Load the specific plan to archive
|
|
537
|
+
const plan = await planningService.loadPlan({ planId: args.planId });
|
|
538
|
+
if (!plan) {
|
|
539
|
+
return createErrorResult(`Plan not found: ${args.planId}. Use "plan operation=list" to see available plans.`);
|
|
540
|
+
}
|
|
541
|
+
// Archive the plan with explicit args
|
|
542
|
+
const archived = await planningService.archivePlan({
|
|
543
|
+
planId: plan.id,
|
|
544
|
+
reason: args.reason
|
|
545
|
+
});
|
|
546
|
+
if (!archived) {
|
|
547
|
+
return createErrorResult('Failed to archive plan.');
|
|
548
|
+
}
|
|
549
|
+
return createSuccessResult(`✅ Archived plan: "${plan.title}"\nReason: ${args.reason || 'Completed'}`);
|
|
550
|
+
}
|
|
551
|
+
catch (error) {
|
|
552
|
+
return createErrorResult(`Failed to archive plan: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
static async handleSwitch(args, context) {
|
|
556
|
+
try {
|
|
557
|
+
if (!context.activePersona) {
|
|
558
|
+
return createErrorResult('No active persona found. Please select a persona first.');
|
|
559
|
+
}
|
|
560
|
+
if (!args.planId) {
|
|
561
|
+
return createErrorResult('Plan ID is required for switch operation.');
|
|
562
|
+
}
|
|
563
|
+
// Create service with RequestContext
|
|
564
|
+
const planningService = new PlanningService(context);
|
|
565
|
+
// Use the new switchToPlan method which handles both active and archived plans
|
|
566
|
+
// It will automatically unarchive if needed
|
|
567
|
+
const switchedPlan = await planningService.switchToPlan(args.planId);
|
|
568
|
+
// Generate a success message with plan summary
|
|
569
|
+
const state = switchedPlan.currentState;
|
|
570
|
+
const response = [
|
|
571
|
+
`✅ Switched active plan to: "${switchedPlan.title}"`,
|
|
572
|
+
`Plan ID: ${switchedPlan.id}`,
|
|
573
|
+
`Status: ${switchedPlan.status}`,
|
|
574
|
+
`Progress: ${state.overallProgress.percentComplete}% (${state.overallProgress.completedPhases}/${state.overallProgress.totalPhases} phases, ${state.overallProgress.completedTodos}/${state.overallProgress.totalTodos} tasks)`,
|
|
575
|
+
'',
|
|
576
|
+
'Use "plan operation=status" to see the current status.',
|
|
577
|
+
].join('\n');
|
|
578
|
+
return createSuccessResult(response);
|
|
579
|
+
}
|
|
580
|
+
catch (error) {
|
|
581
|
+
return createErrorResult(`Failed to switch plan: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
static async handleWatch(args, context) {
|
|
585
|
+
try {
|
|
586
|
+
// Check if we have an existing watcher
|
|
587
|
+
if (watcherInstance) {
|
|
588
|
+
const isRunning = watcherInstance.isRunning();
|
|
589
|
+
if (isRunning) {
|
|
590
|
+
// Stop the old watcher before creating a new one
|
|
591
|
+
await watcherInstance.stop();
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
// Always create a new instance with the current context
|
|
595
|
+
// This ensures we get the latest activePersona from the MCP server
|
|
596
|
+
watcherInstance = new PlanWatcherService(context);
|
|
597
|
+
// Start the watcher with provided options
|
|
598
|
+
const options = {
|
|
599
|
+
port: args.port,
|
|
600
|
+
autoOpen: args.autoOpen,
|
|
601
|
+
showAll: args.showAll,
|
|
602
|
+
};
|
|
603
|
+
const result = await watcherInstance.start(options);
|
|
604
|
+
if (!result.success) {
|
|
605
|
+
return createErrorResult(result.error.message);
|
|
606
|
+
}
|
|
607
|
+
const { url, port, autoOpen, plansLoaded } = result.data;
|
|
608
|
+
const response = [
|
|
609
|
+
'🔍 Plan watcher started successfully!',
|
|
610
|
+
'',
|
|
611
|
+
`📊 Dashboard URL: ${url}`,
|
|
612
|
+
`🔌 Port: ${port}`,
|
|
613
|
+
`📂 Plans loaded: ${plansLoaded}`,
|
|
614
|
+
autoOpen ? '🌐 Browser will open automatically' : '🔗 Open the URL in your browser',
|
|
615
|
+
'',
|
|
616
|
+
'The dashboard will auto-refresh when plans change.',
|
|
617
|
+
'Use "plan operation=stop-watch" to stop the dashboard.',
|
|
618
|
+
].join('\n');
|
|
619
|
+
return createSuccessResult(response);
|
|
620
|
+
}
|
|
621
|
+
catch (error) {
|
|
622
|
+
return createErrorResult(`Failed to start plan watcher: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
static async handleStopWatch(_args, _context) {
|
|
626
|
+
try {
|
|
627
|
+
if (!watcherInstance) {
|
|
628
|
+
return createErrorResult('Plan watcher is not running');
|
|
629
|
+
}
|
|
630
|
+
const result = await watcherInstance.stop();
|
|
631
|
+
if (!result.success) {
|
|
632
|
+
return createErrorResult(result.error.message);
|
|
633
|
+
}
|
|
634
|
+
// Clear the singleton instance
|
|
635
|
+
watcherInstance = null;
|
|
636
|
+
return createSuccessResult('✅ Plan watcher stopped successfully');
|
|
637
|
+
}
|
|
638
|
+
catch (error) {
|
|
639
|
+
return createErrorResult(`Failed to stop plan watcher: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
//# sourceMappingURL=plan.tool.js.map
|