Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/cli/src/task-runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -419,7 +419,7 @@ export class TaskRunner {
);
const message = createUserMessage(
prompts.createSystemReminder(
"You should use tool calls to answer the question, for example, use attemptCompletion if the job is done, or use askFollowupQuestions to clarify the request.",
"You should use tool calls to answer the question, for example, use attemptCompletion if the job is done, or use askFollowupQuestion to clarify the request.",
),
);
this.chat.appendOrReplaceMessage(message);
Expand Down
99 changes: 79 additions & 20 deletions packages/common/src/base/agents/planner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,33 +11,79 @@ Examples of user requests this agent shall trigger:
- "how should we refactor the database schema"
- "design a solution for the memory leak issue"
`.trim(),
tools: ["readFile", "globFiles", "listFiles", "searchFiles", "writeToFile"],
tools: [
"readFile",
"globFiles",
"listFiles",
"searchFiles",
"writeToFile",
"askFollowupQuestion",
],
systemPrompt: `
You are the **Principal Technical Architect**. Your mission is to analyze requirements, architect robust solutions, and deliver a precise implementation strategy without modifying the codebase.

## 1. WORKFLOW
## 1. MODE

Follow this strict sequence of operations:
- You are in planning mode only.
- You must NOT implement code changes or edit repository files.
- The only file you may write is \`pochi://-/plan.md\`.
- If you need clarification from the user, you MUST use \`askFollowupQuestion\` (never plain text questions).

### Phase 1: Deep Contextual Analysis
1. **Explore**: Use \`listFiles\`, \`globFiles\` to understand the project structure.
2. **Examine**: Use \`readFile\`, \`searchFiles\` to read relevant code, configurations, and documentation.
3. **Understand**: Identify existing patterns, dependencies, and architectural constraints.
4. **Diagnose**: For bugs, identify the root cause. For features, identify integration points.
## 2. WORKFLOW

### Phase 2: Strategic Solution Design
1. **Architect**: Design a solution that ensures scalability, maintainability, and adherence to project standards.
2. **Plan**: Decompose the solution into atomic, sequential steps.
Follow this strict sequence:

### Phase 3: Plan Serialization
1. **Construct**: Create the plan content using the "Professional Plan Template" below.
2. **Save**: Write the plan to \`pochi://-/plan.md\`.
### Phase 1: Environment Grounding (Explore First)
1. **Explore**: Use \`listFiles\` and \`globFiles\` to map project structure.
2. **Inspect**: Use \`readFile\` and \`searchFiles\` to gather concrete implementation facts.
3. **Reuse-aware analysis**: Identify existing utilities, patterns, and modules that should be reused.
4. **Ask only after exploration**: Before asking the user questions, complete at least one targeted exploration pass, unless the user's prompt itself is inherently ambiguous.

### Phase 4: Completion
1. **Verify**: Ensure the file was written successfully.
2. **Report**: Call \`attemptCompletion\` with the result.
### Phase 2: Clarification (Only When Needed)
Treat unknowns as two types:
- **Discoverable facts**: resolve via tools, do not ask the user.
- **Preferences/tradeoffs**: ask the user via \`askFollowupQuestion\`.

## 2. PROFESSIONAL PLAN TEMPLATE
Ask a clarification question only when a high-impact ambiguity remains after exploration.

When using \`askFollowupQuestion\`:
- Ask exactly one question at a time.
- Provide 2-4 concrete, mutually exclusive follow-up options.
- Put the recommended default option first, prefixed with \`[Recommended]\`.
- Keep options implementation-relevant (no filler options like "other/not sure").

After asking a clarification question, stop and wait for user input.

### Phase 2.5: Ambiguity Gate (MUST PASS BEFORE DESIGN)
Before entering design, verify all of the following are known (either from repo evidence or user input):
- Target outcome and user-facing intent.
- Scope boundaries (what is in/out).
- Key constraints or acceptance criteria.
- Critical implementation choice points that cannot be inferred safely.

If any item above is missing and could materially change implementation, you MUST:
1. call \`askFollowupQuestion\`, then
2. stop and wait for the user.

You are STRICTLY FORBIDDEN from writing \`pochi://-/plan.md\` while high-impact ambiguity remains.

For requests like "add a new page", treat as ambiguous by default unless the repo context uniquely determines route, purpose, and integration points.
In that case, ask a focused clarification question first instead of drafting a checklist-style plan.

### Phase 3: Strategic Solution Design
1. **Architect**: Design a solution that is scalable, maintainable, and aligned with project conventions.
2. **Decision complete plan**: Ensure no unresolved technical decisions are left to the implementer.
3. **Plan**: Decompose into atomic, sequential implementation steps.

### Phase 4: Plan Serialization
1. **Construct**: Create plan content using the "Professional Plan Template" below.
2. **Save**: Write the plan to \`pochi://-/plan.md\`.

### Phase 5: Completion
1. **Verify**: Ensure the plan file was written successfully.
2. **Report**: Call \`attemptCompletion\` with the exact completion message.

## 3. PROFESSIONAL PLAN TEMPLATE

The plan file MUST be a high-quality Markdown document adhering to this structure:

Expand Down Expand Up @@ -85,9 +131,22 @@ The plan file MUST be a high-quality Markdown document adhering to this structur
{Potential risks (e.g., performance impact, breaking changes) and how to handle them.}
\`\`\`

## 3. COMPLETION PROTOCOL
In addition, include:
- Explicit assumptions/defaults selected for unresolved preferences.
- Public API/interface/type changes (if any).
- End-to-end verification commands and manual checks.

\`\`\`markdown
## Assumptions & Defaults
- {Assumption 1}
- {Assumption 2}
\`\`\`

## 4. COMPLETION PROTOCOL

If clarification is still needed, call \`askFollowupQuestion\` and wait.

Upon successfully writing the plan, call \`attemptCompletion\` with this EXACT message:
If and only if the plan is decision complete and saved successfully, call \`attemptCompletion\` with this EXACT message:

"Technical plan architected and saved to \`pochi://-/plan.md\`. Please start implementation using the plan"

Expand Down
2 changes: 1 addition & 1 deletion packages/tools/src/ask-followup-question.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ ${NoOtherToolsReminderPrompt}
followUp: z
.array(z.string())
.describe(
"A list of 2-4 suggested answers that logically follow from the question.",
"A list of 2-4 suggested answers that logically follow from the question. Put the recommended default answer first when applicable.",
),
}),
outputSchema: z.object({
Expand Down
7 changes: 6 additions & 1 deletion packages/tools/src/new-task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,12 @@ export const overrideCustomAgentTools = (
}

const toAddTools = ["todoWrite", "attemptCompletion", "useSkill"];
const toDeleteTools = ["askFollowupQuestion", "newTask"];
const toDeleteTools = ["newTask"];

// planner auto jump into manual run node, so it's ok to utilize askFollowupQuestion
if (customAgent.name !== "planner") {
toDeleteTools.push("askFollowupQuestion");
}

const updatedTools = customAgent.tools.filter(
(tool) => !toDeleteTools.includes(tool) && !toAddTools.includes(tool),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,7 @@ function Part({
changes={getToolCallCheckpoint(part, messages)}
messages={messages}
isSubTask={isSubTask}
isLastPart={isLastPartInMessages}
/>
);
}
Expand Down
33 changes: 32 additions & 1 deletion packages/vscode-webui/src/components/task-thread.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -130,9 +130,40 @@ export const TaskThread: React.FC<{
);
};

/**
* Filter out askFollowupQuestion if it's the last tool call in the last message.
* This prevents showing the follow-up question UI in subtasks.
*/
function filterTrailingAskFollowupQuestion(messages: Message[]): Message[] {
if (messages.length === 0) return messages;

const lastMessage = messages[messages.length - 1];
if (lastMessage.role !== "assistant") return messages;

const parts = lastMessage.parts;
if (!Array.isArray(parts) || parts.length === 0) return messages;

// Check if the last part is an askFollowupQuestion
const lastPart = parts[parts.length - 1];
if (lastPart.type === "tool-askFollowupQuestion") {
// Remove the askFollowupQuestion tool call from the message
const filteredParts = parts.slice(0, -1);
if (filteredParts.length === 0) {
// If no parts left, remove the entire message
return messages.slice(0, -1);
}
return [...messages.slice(0, -1), { ...lastMessage, parts: filteredParts }];
}

return messages;
}

function prepareForRender(messages: Message[]): Message[] {
// Remove user messages.
const filteredMessages = messages.filter((x) => x.role !== "user");
const x = formatters.ui(filteredMessages);
// Filter out trailing askFollowupQuestion tool calls
const withoutTrailingAskFollowup =
filterTrailingAskFollowupQuestion(filteredMessages);
const x = formatters.ui(withoutTrailingAskFollowup);
return x;
}
Original file line number Diff line number Diff line change
Expand Up @@ -115,9 +115,11 @@ export const ToolCallApprovalButton: React.FC<ToolCallApprovalButtonProps> = ({

const tool = tools[i];
const runManually =
!subtaskOffhand &&
// Async task cannot be run manually.
!(tool.type === "tool-newTask" && tool.input?.runAsync);
(!subtaskOffhand &&
// Async task cannot be run manually.
!(tool.type === "tool-newTask" && tool.input?.runAsync)) ||
// planner always run manually
(tool.type === "tool-newTask" && tool.input?.agentType === "planner");
if (tool.type === "tool-newTask" && runManually) {
const subtaskUid = tool.input?._meta?.uid;
if (subtaskUid) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export function useRetry({
) {
return sendMessage({
text: prompts.createSystemReminder(
"You should use tool calls to answer the question, for example, use attemptCompletion if the job is done, or use askFollowupQuestions to clarify the request.",
"You should use tool calls to answer the question, for example, use attemptCompletion if the job is done, or use askFollowupQuestion to clarify the request.",
),
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ import type { ToolProps } from "./types";

export const AskFollowupQuestionTool: React.FC<
ToolProps<"askFollowupQuestion">
> = ({ tool: toolCall, isLoading }) => {
> = ({ tool: toolCall, isLoading, isLastPart }) => {
const sendMessage = useSendMessage();
const replaceJobIdsInContent = useReplaceJobIdsInContent();
const { question, followUp } = toolCall.input || {};

const isClickable = !isLoading && isLastPart;

return (
<div className="flex flex-col gap-2">
<MessageMarkdown className="font-medium italic">
Expand All @@ -22,12 +24,13 @@ export const AskFollowupQuestionTool: React.FC<
{followUp.map((followUpText, index) => (
<li
key={index}
className={cn("text-muted-foreground hover:text-foreground", {
"cursor-pointer": !isLoading,
className={cn("text-muted-foreground", {
"cursor-pointer hover:text-foreground": isClickable,
"cursor-wait": isLoading,
"cursor-not-allowed opacity-50": !isLastPart && !isLoading,
})}
onClick={() =>
!isLoading &&
isClickable &&
sendMessage({
prompt: followUpText || "",
})
Expand Down
3 changes: 3 additions & 0 deletions packages/vscode-webui/src/features/tools/components/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,15 @@ export function ToolInvocationPart({
messages,
changes,
isSubTask,
isLastPart,
}: {
tool: ToolUIPart<UITools>;
isLoading: boolean;
messages: Message[];
className?: string;
changes?: ToolCallCheckpoint;
isSubTask?: boolean;
isLastPart?: boolean;
}) {
const toolName = getToolName(tool);
const lifecycle = useToolCallLifeCycle().getToolCallLifeCycle({
Expand All @@ -57,6 +59,7 @@ export function ToolInvocationPart({
changes={changes}
messages={messages}
isSubTask={isSubTask}
isLastPart={isLastPart}
/>
) : (
<McpToolCall
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ export interface ToolProps<T extends ToolName> {
messages: Message[];
changes?: ToolCallCheckpoint;
isSubTask?: boolean;
isLastPart?: boolean;
}