馃惐 Medium-horizon agent planning MCP server
1/**
2 * 9plan_queue_pull tool
3 *
4 * Removes the front plan from queue and marks it active.
5 */
6
7import type { SessionStoreInterface } from "../types.js";
8import { formatResponse, NinePlanError } from "../types.js";
9
10// Input schema (no required inputs)
11export const queuePullInputSchema = {};
12
13// Input type
14export type QueuePullInput = Record<string, never>;
15
16/**
17 * Handle queue pull tool call
18 */
19export function handleQueuePull(
20 store: SessionStoreInterface,
21 sessionName: string,
22 _args: QueuePullInput,
23): { content: { type: "text"; text: string }[] } {
24 try {
25 const plan = store.pullPlan();
26 const planPath = store.getSessionPath() + `/plans/${plan.id}.txt`;
27
28 // Format plan content for display
29 const planContent = `## Context
30${plan.context ?? "(none)"}
31
32## Goal
33${plan.goal}
34
35## Inputs
36${plan.inputs ?? "(none)"}
37
38## Outputs
39${plan.outputs ?? "(none)"}
40
41## Approach
42${plan.approach ?? "(none)"}
43
44## Testing
45${plan.testing ?? "(none)"}
46
47## Success Criteria
48${plan.successCriteria ?? "(none)"}
49
50## Notes
51${plan.notes ?? "(none)"}`;
52
53 const response = formatResponse(
54 sessionName,
55 `Active plan: ${plan.id}
56Path: ${planPath}
57
58${planContent}
59
60---
61Review for any ambiguities before starting execution.
62If the plan has inputs from other plans, use 9plan_history_search to find their outputs.
63If the plan's Notes indicate it was previously decomposed, use 9plan_history_get to retrieve child outcomes.`,
64 );
65
66 return {
67 content: [{ type: "text", text: response }],
68 };
69 } catch (error) {
70 if (error instanceof NinePlanError) {
71 // Special handling for queue empty (not really an error)
72 if (error.category === "QUEUE_EMPTY") {
73 const completedCount = store.getCompletedCount();
74 const response = formatResponse(
75 sessionName,
76 `Queue is empty. Task complete!
77
78Completed plans: ${String(completedCount)}
79
80Use 9plan_history_search to review what was accomplished.`,
81 );
82 return {
83 content: [{ type: "text", text: response }],
84 };
85 }
86
87 // Special handling for plan already active
88 if (error.category === "PLAN_ALREADY_ACTIVE") {
89 const active = store.getActivePlan();
90 if (active) {
91 const activePath = store.getSessionPath() + `/plans/${active.id}.txt`;
92 const response = formatResponse(
93 sessionName,
94 `Error: Cannot pull - a plan is already active
95
96Active plan: ${active.id}
97Path: ${activePath}
98
99Complete, defer, or discard the active plan before pulling another.`,
100 );
101 return {
102 content: [{ type: "text", text: response }],
103 };
104 }
105 }
106
107 return {
108 content: [{ type: "text", text: error.format(sessionName) }],
109 };
110 }
111 throw error;
112 }
113}
114
115// Tool configuration for registration
116export const queuePullToolConfig = {
117 title: "Pull Plan from Queue",
118 description:
119 "Removes the front plan from queue and marks it as active. Returns the plan ID and file path. Only one plan can be active at a time.",
120 inputSchema: queuePullInputSchema,
121};