diff --git a/packages/cli/src/lib/load-agents.ts b/packages/cli/src/lib/load-agents.ts index cda11e902..e3ce13344 100644 --- a/packages/cli/src/lib/load-agents.ts +++ b/packages/cli/src/lib/load-agents.ts @@ -7,7 +7,7 @@ import type { CustomAgentFile, ValidCustomAgentFile, } from "@getpochi/common/vscode-webui-bridge"; -import { isValidCustomAgentFile } from "@getpochi/common/vscode-webui-bridge"; +import { isValidCustomAgent } from "@getpochi/common/vscode-webui-bridge"; import type { CustomAgent } from "@getpochi/tools"; import { uniqueBy } from "remeda"; @@ -74,7 +74,7 @@ export async function loadAgents( // Filter out invalid agents for CLI usage const validAgents = uniqueBy(allAgents, (agent) => agent.name).filter( (agent): agent is ValidCustomAgentFile => { - if (isValidCustomAgentFile(agent)) { + if (isValidCustomAgent(agent)) { return true; } logger.warn( diff --git a/packages/common/src/base/built-in-custom-agents/index.ts b/packages/common/src/base/built-in-custom-agents/index.ts new file mode 100644 index 000000000..faed305bc --- /dev/null +++ b/packages/common/src/base/built-in-custom-agents/index.ts @@ -0,0 +1,3 @@ +import { plannerAgent } from "./planner-agent"; + +export const builtInCustomAgents = [plannerAgent]; diff --git a/packages/common/src/base/built-in-custom-agents/planner-agent.ts b/packages/common/src/base/built-in-custom-agents/planner-agent.ts new file mode 100644 index 000000000..e9d16a812 --- /dev/null +++ b/packages/common/src/base/built-in-custom-agents/planner-agent.ts @@ -0,0 +1,104 @@ +import type { CustomAgent } from "@getpochi/tools"; + +export const plannerAgent: CustomAgent = { + name: "planner", + description: ` +Use this agent to create detailed, actionable implementation plans for new features, refactoring, or bug fixes. +This agent ONLY produces a plan and does NOT modify any source code. Code implementation should only begin after the user approves the generated plan. +`.trim(), + tools: ["readFile", "globFiles", "listFiles", "searchFiles", "writeToFile"], + systemPrompt: ` +You are the Plan agent, specialized in architecting technical solutions and providing methodical, step-by-step implementation roadmaps. + +## Your Role + +Your SOLE goal is to transform high-level requirements into a concrete technical plan. + +**CRITICAL CONSTRAINT**: You MUST NOT modify any source code files or implement any logic. Your task is complete once the plan is architected and saved. Any actual code changes will be performed in a subsequent step ONLY after the user has reviewed and approved your plan. + +You should: + +1. **Understand Context & Constraints**: Analyze the codebase to understand how the new feature fits into the existing architecture. +2. **Design the Solution**: Determine the best technical approach, considering project conventions and best practices. +3. **Break Down Implementation**: Deconstruct the task into atomic, manageable steps. +4. **Define Verification**: Specify how each change and the overall feature should be tested. +5. **Persist the Plan**: Save the final roadmap to the designated location for future reference. + +## Planning Strategies + +### For New Feature Development + +- Identify necessary new files, components, or modules. +- Map out data flow and interface changes. +- Determine required changes to existing services or stores. +- Consider UI/UX consistency with existing patterns. + +### For Code Refactoring + +- Identify the target code and its dependencies. +- Plan incremental changes to ensure the system remains functional. +- Define clear entry and exit points for the refactored logic. +- Prioritize maintainability and testability. + +### For Bug Fixes + +- Locate the root cause and analyze the surrounding context. +- Plan the fix and any necessary regression tests. +- Consider if similar bugs exist elsewhere in the codebase. + +## Important Reminders + +- **DO NOT implement the plan.** You are a planning agent, not a coding agent. +- **DO NOT modify any source code files.** Your only write operation should be creating the plan file. +- Be specific and technical. Avoid vague instructions. +- Respect the project's existing style, libraries, and architectural patterns. +- Ensure the plan is actionable for a developer who may not have full context. +- Always include the correct file paths and line number references where applicable. + +## Output Format & Persistence + +### 1. Identify Task ID + +Find the current **Task ID** from the **System Information** section in the **system-reminder** (look for "Task ID: [UUID]"). + +### 2. Save the Plan + +**CRITICAL:** You MUST use the 'writeToFile' tool to save the plan to '.pochi/plans/{taskId}.md'. The content of the file should follow this markdown structure: + +\`\`\`markdown +# Implementation Plan - {Feature Name} + +## Problem Analysis +{Brief description of the problem and current state} + +## Proposed Solution +{High-level architectural design and technical approach} + +## Implementation Steps + +### Step 1: {Step Title} +- **Objective**: {What this step achieves} +- **Files**: + - \`path/to/file1.ts\` +- **Changes**: + - {Detailed description of changes} + +### Step 2: {Step Title} +... + +## Verification Plan +### Automated Tests +- [ ] {Test Case 1} +### Manual Verification +- [ ] {Verification Step 1} +\`\`\` + +### 3. Final Report + +In your final response via 'attemptCompletion', provide: + +- **Plan UI**: **MUST** include exactly this line at the end (not in a code block): + +- **Instruction to Main Agent**: **CRITICAL**: Explicitly state: "The plan has been created. STOP execution now and ask the user to review the plan before proceeding." +`.trim(), +}; diff --git a/packages/common/src/base/environment.ts b/packages/common/src/base/environment.ts index aefe55e8a..c5e2292f7 100644 --- a/packages/common/src/base/environment.ts +++ b/packages/common/src/base/environment.ts @@ -129,6 +129,12 @@ export const Environment = z.object({ .string() .optional() .describe("Custom rules provided by the user."), + taskId: z + .string() + .optional() + .describe( + "The task id of the current task. It is useful for tools that require task id.", + ), }) .describe("General information about the environment."), todos: z.array(Todo).optional().describe("Todos in current task"), diff --git a/packages/common/src/base/index.ts b/packages/common/src/base/index.ts index bc415f5da..a33e79d4c 100644 --- a/packages/common/src/base/index.ts +++ b/packages/common/src/base/index.ts @@ -17,6 +17,8 @@ export { WebsiteTaskCreateEvent } from "./event"; export { toErrorMessage } from "./error"; +export { builtInCustomAgents } from "./built-in-custom-agents"; + export const PochiProviderOptions = z.object({ taskId: z.string(), client: z.string(), diff --git a/packages/common/src/base/prompts/environment.ts b/packages/common/src/base/prompts/environment.ts index b57408bc9..ba23eeb0b 100644 --- a/packages/common/src/base/prompts/environment.ts +++ b/packages/common/src/base/prompts/environment.ts @@ -46,7 +46,8 @@ Operating System: ${info.os} Default Shell: ${info.shell} Home Directory: ${info.homedir} Current Working Directory: ${info.cwd} -Current Time: ${currentTime}`; +Current Time: ${currentTime} +Current Task ID: ${info.taskId ?? "N/A"}`; return prompt; } diff --git a/packages/common/src/vscode-webui-bridge/index.ts b/packages/common/src/vscode-webui-bridge/index.ts index 6eb2d2c04..cb2f32372 100644 --- a/packages/common/src/vscode-webui-bridge/index.ts +++ b/packages/common/src/vscode-webui-bridge/index.ts @@ -44,7 +44,7 @@ export type { DiffCheckpointOptions, CreateWorktreeOptions, } from "./types/git"; -export { isValidCustomAgentFile } from "./types/custom-agent"; +export { isValidCustomAgent } from "./types/custom-agent"; export { prefixTaskDisplayId, prefixWorktreeName, diff --git a/packages/common/src/vscode-webui-bridge/types/custom-agent.ts b/packages/common/src/vscode-webui-bridge/types/custom-agent.ts index e1dfbf6d4..3feb73439 100644 --- a/packages/common/src/vscode-webui-bridge/types/custom-agent.ts +++ b/packages/common/src/vscode-webui-bridge/types/custom-agent.ts @@ -31,8 +31,8 @@ export interface InvalidCustomAgentFile extends Partial { export type CustomAgentFile = ValidCustomAgentFile | InvalidCustomAgentFile; -export const isValidCustomAgentFile = ( - agent: CustomAgentFile, +export const isValidCustomAgent = ( + agent: CustomAgent | CustomAgentFile, ): agent is ValidCustomAgentFile => { return ( (agent as ValidCustomAgentFile).name !== undefined && diff --git a/packages/common/src/vscode-webui-bridge/webview-stub.ts b/packages/common/src/vscode-webui-bridge/webview-stub.ts index c21d7df86..86fe70f80 100644 --- a/packages/common/src/vscode-webui-bridge/webview-stub.ts +++ b/packages/common/src/vscode-webui-bridge/webview-stub.ts @@ -1,3 +1,4 @@ +import type { CustomAgent } from "@getpochi/tools"; import type { ThreadAbortSignalSerialization } from "@quilted/threads"; import type { ThreadSignalSerialization } from "@quilted/threads/signals"; import type { Environment } from "../base"; @@ -243,9 +244,11 @@ const VSCodeHostStub = { ); }, readCustomAgents: async (): Promise< - ThreadSignalSerialization + ThreadSignalSerialization<(CustomAgent | CustomAgentFile)[]> > => { - return Promise.resolve({} as ThreadSignalSerialization); + return Promise.resolve( + {} as ThreadSignalSerialization<(CustomAgent | CustomAgentFile)[]>, + ); }, openTaskInPanel: async (): Promise => {}, diff --git a/packages/common/src/vscode-webui-bridge/webview.ts b/packages/common/src/vscode-webui-bridge/webview.ts index 33895e7e1..90625b7f3 100644 --- a/packages/common/src/vscode-webui-bridge/webview.ts +++ b/packages/common/src/vscode-webui-bridge/webview.ts @@ -1,4 +1,4 @@ -import type { PreviewReturnType } from "@getpochi/tools"; +import type { CustomAgent, PreviewReturnType } from "@getpochi/tools"; import type { ThreadAbortSignalSerialization } from "@quilted/threads"; import type { ThreadSignalSerialization } from "@quilted/threads/signals"; import type { Environment } from "../base"; @@ -53,6 +53,7 @@ export interface VSCodeHostApi { readEnvironment(options: { isSubTask?: boolean; webviewKind: "sidebar" | "pane"; + taskId?: string; }): Promise; previewToolCall( @@ -167,7 +168,9 @@ export interface VSCodeHostApi { workspacePath: string | null; }>; - readCustomAgents(): Promise>; + readCustomAgents(): Promise< + ThreadSignalSerialization<(CustomAgent | CustomAgentFile)[]> + >; executeBashCommand: ( command: string, diff --git a/packages/vscode-webui/src/components/message/markdown.tsx b/packages/vscode-webui/src/components/message/markdown.tsx index 00dfc8f9c..9607d036a 100644 --- a/packages/vscode-webui/src/components/message/markdown.tsx +++ b/packages/vscode-webui/src/components/message/markdown.tsx @@ -1,8 +1,10 @@ +import { PlanCard, useReplaceJobIdsInContent } from "@/features/chat"; import { FileBadge, IssueBadge } from "@/features/tools"; import { CustomHtmlTags } from "@/lib/constants"; import { cn } from "@/lib/utils"; import { isKnownProgrammingLanguage } from "@/lib/utils/languages"; import { isVSCodeEnvironment, vscodeHost } from "@/lib/vscode"; +import { Bot } from "lucide-react"; import { type DetailedHTMLProps, type HTMLAttributes, @@ -20,7 +22,6 @@ import { import { CodeBlock } from "./code-block"; import { customStripTagsPlugin } from "./custom-strip-tags-plugin"; import "./markdown.css"; -import { useReplaceJobIdsInContent } from "@/features/chat"; import { useTranslation } from "react-i18next"; import type { ExtraProps, Options } from "react-markdown"; @@ -399,6 +400,7 @@ export function MessageMarkdown({ ...defaultSchema.attributes, workflow: ["path", "id"], "custom-agent": ["path", "id"], + "plan-card": ["task-id"], issue: ["id", "url", "title"], ...mathSanitizeConfig.attributes, }, @@ -422,9 +424,19 @@ export function MessageMarkdown({ }, "custom-agent": (props: WorkflowComponentProps) => { const { id, path } = props; - return ( - - ); + const cleanId = id.replaceAll("user-content-", "/"); + if (!path) { + return ( + + + {cleanId} + + ); + } + return ; + }, + "plan-card": (props: { "task-id"?: string }) => { + return ; }, issue: (props: IssueComponentProps) => { const { id, url, title } = props; diff --git a/packages/vscode-webui/src/components/prompt-form/form-editor.tsx b/packages/vscode-webui/src/components/prompt-form/form-editor.tsx index 07b85341d..34c72b6f3 100644 --- a/packages/vscode-webui/src/components/prompt-form/form-editor.tsx +++ b/packages/vscode-webui/src/components/prompt-form/form-editor.tsx @@ -38,7 +38,7 @@ import { useSelectedModels } from "@/features/settings"; import { useLatest } from "@/lib/hooks/use-latest"; import { cn } from "@/lib/utils"; import { resolveModelFromId } from "@/lib/utils/resolve-model-from-id"; -import { isValidCustomAgentFile } from "@getpochi/common/vscode-webui-bridge"; +import { isValidCustomAgent } from "@getpochi/common/vscode-webui-bridge"; import { threadSignal } from "@quilted/threads/signals"; import { type SuggestionMatch, @@ -820,7 +820,7 @@ export const debouncedListSlashCommand = debounceWithCachedValue( ]); const options: SlashCandidate[] = [ ...customAgents.value - .filter((x) => isValidCustomAgentFile(x)) + .filter((x) => isValidCustomAgent(x)) .map((x) => ({ type: "custom-agent" as const, id: x.name, diff --git a/packages/vscode-webui/src/features/chat/components/create-task-input.tsx b/packages/vscode-webui/src/features/chat/components/create-task-input.tsx index cbe8d76a3..3f5e38038 100644 --- a/packages/vscode-webui/src/features/chat/components/create-task-input.tsx +++ b/packages/vscode-webui/src/features/chat/components/create-task-input.tsx @@ -18,7 +18,7 @@ import { useTaskInputDraft } from "@/lib/hooks/use-task-input-draft"; import { useWorktrees } from "@/lib/hooks/use-worktrees"; import { vscodeHost } from "@/lib/vscode"; import type { GitWorktree } from "@getpochi/common/vscode-webui-bridge"; -import { Loader2, PaperclipIcon } from "lucide-react"; +import { ClipboardList, Loader2, PaperclipIcon } from "lucide-react"; import type React from "react"; import { useCallback, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; @@ -111,11 +111,11 @@ export const CreateTaskInput: React.FC = ({ useDebounceState(isCreatingTask, 300); const handleSubmitImpl = useCallback( - async ( - e?: React.FormEvent, - shouldCreateWorktree?: boolean, - ) => { - e?.preventDefault(); + async (options?: { + shouldCreateWorktree?: boolean; + shouldCreatePlan?: boolean; + }) => { + const { shouldCreateWorktree, shouldCreatePlan } = options || {}; if (isCreatingTask) return; @@ -125,11 +125,16 @@ export const CreateTaskInput: React.FC = ({ // If no valid model is selected, submission is not allowed. if (!selectedModel) return; - const content = input.trim(); + let content = input.trim(); // Disallow empty submissions if (content.length === 0 && files.length === 0) return; + if (shouldCreatePlan) { + // Use the planner custom agent to create a plan + content = `newTask:planner ${content}`; + } + // Set isCreatingTask state true // Show loading and freeze input setIsCreatingTask(true); @@ -208,18 +213,24 @@ export const CreateTaskInput: React.FC = ({ const handleSubmit = useCallback( async (e: React.FormEvent) => { - handleSubmitImpl(e); + e.preventDefault(); + handleSubmitImpl(); }, [handleSubmitImpl], ); const handleCtrlSubmit = useCallback( async (e: React.FormEvent) => { - handleSubmitImpl(e, true); + e.preventDefault(); + handleSubmitImpl({ shouldCreateWorktree: true }); }, [handleSubmitImpl], ); + const handleCreatePlan = useCallback(async () => { + handleSubmitImpl({ shouldCreatePlan: true }); + }, [handleSubmitImpl]); + return ( <> = ({ {t("chat.attachmentTooltip")} + + + + + + + + {t("chat.createPlanTooltip")} + + {!!debouncedIsCreatingTask && ( diff --git a/packages/vscode-webui/src/features/chat/components/plan-card.tsx b/packages/vscode-webui/src/features/chat/components/plan-card.tsx new file mode 100644 index 000000000..54702a8a8 --- /dev/null +++ b/packages/vscode-webui/src/features/chat/components/plan-card.tsx @@ -0,0 +1,69 @@ +import { Button } from "@/components/ui/button"; +import { useSendMessage } from "@/features/chat"; +import { vscodeHost } from "@/lib/vscode"; +import { FileText, Play, SquareArrowOutUpRight } from "lucide-react"; +import type React from "react"; +import { useTranslation } from "react-i18next"; + +interface PlanCardProps { + taskId: string; +} + +export const PlanCard: React.FC = ({ taskId }) => { + const { t } = useTranslation(); + const sendMessage = useSendMessage(); + + // Clean up taskId if it comes with the sanitization prefix + const cleanTaskId = (taskId || "").replace(/^user-content-/, ""); + const planPath = cleanTaskId ? `.pochi/plans/${cleanTaskId}.md` : ""; + + const openPlan = () => { + if (planPath) vscodeHost.openFile(planPath); + }; + + const executePlan = () => { + if (planPath) { + sendMessage({ + prompt: t("chat.planCard.executePrompt", { path: planPath }), + }); + } + }; + + return ( +
+
+
+
+ +
+ + {t("chat.planCard.title")} + +
+
+ +
+ + +
+
+ ); +}; diff --git a/packages/vscode-webui/src/features/chat/index.ts b/packages/vscode-webui/src/features/chat/index.ts index f2f3d211b..4acddbf1b 100644 --- a/packages/vscode-webui/src/features/chat/index.ts +++ b/packages/vscode-webui/src/features/chat/index.ts @@ -22,3 +22,4 @@ export type { ToolCallLifeCycle } from "./lib/tool-call-life-cycle"; export { ChatPage } from "./page"; export { CreateTaskInput } from "./components/create-task-input"; +export { PlanCard } from "./components/plan-card"; diff --git a/packages/vscode-webui/src/features/chat/lib/use-live-chat-kit-getters.ts b/packages/vscode-webui/src/features/chat/lib/use-live-chat-kit-getters.ts index 12f577f8e..bf5b3ba8a 100644 --- a/packages/vscode-webui/src/features/chat/lib/use-live-chat-kit-getters.ts +++ b/packages/vscode-webui/src/features/chat/lib/use-live-chat-kit-getters.ts @@ -25,10 +25,12 @@ export function useLiveChatKitGetters({ todos, isSubTask, modelOverride, + taskId, }: { todos: React.RefObject; isSubTask: boolean; modelOverride?: DisplayModel; + taskId: string; }) { const { toolset, instructions } = useMcp(); const mcpInfo = useLatest({ toolset, instructions }); @@ -42,6 +44,7 @@ export function useLiveChatKitGetters({ const environment = await vscodeHost.readEnvironment({ isSubTask, webviewKind: globalThis.POCHI_WEBVIEW_KIND, + taskId, }); let userEdits: FileDiff[] | undefined; @@ -60,7 +63,7 @@ export function useLiveChatKitGetters({ userEdits, } satisfies Environment; }, - [todos, isSubTask], + [todos, isSubTask, taskId], ); return { diff --git a/packages/vscode-webui/src/features/chat/page.tsx b/packages/vscode-webui/src/features/chat/page.tsx index 06de405b1..3cf5802ea 100644 --- a/packages/vscode-webui/src/features/chat/page.tsx +++ b/packages/vscode-webui/src/features/chat/page.tsx @@ -107,6 +107,7 @@ function Chat({ user, uid, info }: ChatProps) { const getters = useLiveChatKitGetters({ todos: todosRef, isSubTask, + taskId: uid, }); useRestoreTaskModel(task, isModelsLoading, updateSelectedModelId); diff --git a/packages/vscode-webui/src/features/settings/components/sections/custom-agent-section.tsx b/packages/vscode-webui/src/features/settings/components/sections/custom-agent-section.tsx index 30097ba20..f475f4ae6 100644 --- a/packages/vscode-webui/src/features/settings/components/sections/custom-agent-section.tsx +++ b/packages/vscode-webui/src/features/settings/components/sections/custom-agent-section.tsx @@ -9,7 +9,7 @@ import type { CustomAgentFile, InvalidCustomAgentFile, } from "@getpochi/common/vscode-webui-bridge"; -import { isValidCustomAgentFile } from "@getpochi/common/vscode-webui-bridge"; +import { isValidCustomAgent } from "@getpochi/common/vscode-webui-bridge"; import { AlertTriangle, Bot, Edit } from "lucide-react"; import { useTranslation } from "react-i18next"; import { AccordionSection } from "../ui/accordion-section"; @@ -56,7 +56,7 @@ export const CustomAgentSection: React.FC = () => { return (
{customAgents.map((agent) => { - const isValid = isValidCustomAgentFile(agent); + const isValid = isValidCustomAgent(agent); const subtitle = !isValid ? ( diff --git a/packages/vscode-webui/src/i18n/locales/en.json b/packages/vscode-webui/src/i18n/locales/en.json index 10c2897d7..79ef9b03c 100644 --- a/packages/vscode-webui/src/i18n/locales/en.json +++ b/packages/vscode-webui/src/i18n/locales/en.json @@ -27,6 +27,14 @@ }, "chat": { "attachmentTooltip": "Attach files to chat. You can also drag and drop files or paste them into the chat input box.", + "createPlanTooltip": "Create a plan for the task", + "planCard": { + "title": "Implementation Plan", + "status": "Ready for review", + "reviewButton": "Review & Comment", + "executeButton": "Execute Plan", + "executePrompt": "Please implement the plan specified in {{path}}." + }, "queuedMessages": "Queued Messages ({{count}})", "copyImage": "Copy Image", "openImage": "Open Image", diff --git a/packages/vscode-webui/src/i18n/locales/jp.json b/packages/vscode-webui/src/i18n/locales/jp.json index 40fdd2519..c29d2e47d 100644 --- a/packages/vscode-webui/src/i18n/locales/jp.json +++ b/packages/vscode-webui/src/i18n/locales/jp.json @@ -26,7 +26,15 @@ "required": "Pochi を使用するには、フォルダーまたはワークスペースを開いてください。" }, "chat": { - "attachmentTooltip": "ファイルをチャットに添付します。ファイルをドラッグ&ドロップするか、チャット入力欄に貼り付けることもできます。", + "attachmentTooltip": "ファイルをチャットに添付します。ファイ ルをドラッグ&ドロップするか、チャット入力欄に貼り付けることもできます。", + "createPlanTooltip": "タスクの計画を作成", + "planCard": { + "title": "実装計画", + "status": "レビュー待ち", + "reviewButton": "レビュー&コメント", + "executeButton": "計画を実行", + "executePrompt": "{{path}} に指定された計画を実装してください。" + }, "queuedMessages": "キュー内のメッセージ ({{count}})", "copyImage": "画像をコピー", "openImage": "画像を開く", diff --git a/packages/vscode-webui/src/i18n/locales/ko.json b/packages/vscode-webui/src/i18n/locales/ko.json index f493abbdd..85b020c05 100644 --- a/packages/vscode-webui/src/i18n/locales/ko.json +++ b/packages/vscode-webui/src/i18n/locales/ko.json @@ -27,6 +27,14 @@ }, "chat": { "attachmentTooltip": "채팅에 파일을 첨부합니다. 파일을 드래그 앤 드롭하거나 채팅 입력란에 붙여넣을 수도 있습니다.", + "createPlanTooltip": "작업 계획 생성", + "planCard": { + "title": "구현 계획", + "status": "검토 준비 완료", + "reviewButton": "검토 및 댓글", + "executeButton": "계획 실행", + "executePrompt": "{{path}}에 명시된 계획을 구현해 주세요." + }, "queuedMessages": "대기 중인 메시지 ({{count}})", "copyImage": "이미지 복사", "openImage": "이미지 열기", diff --git a/packages/vscode-webui/src/i18n/locales/zh.json b/packages/vscode-webui/src/i18n/locales/zh.json index cbbcf4053..34c239472 100644 --- a/packages/vscode-webui/src/i18n/locales/zh.json +++ b/packages/vscode-webui/src/i18n/locales/zh.json @@ -26,7 +26,15 @@ "required": "要使用 Pochi,请打开一个文件夹或工作区。" }, "chat": { - "attachmentTooltip": "附加文件到对话。您也可以拖放文件或将其粘贴到聊天输入框中。", + "attachmentTooltip": "附加文件到对话。您也可以拖放文件或将其 粘贴到聊天输入框中。", + "createPlanTooltip": "为任务创建计划", + "planCard": { + "title": "实施计划", + "status": "待审查", + "reviewButton": "审查与评论", + "executeButton": "执行计划", + "executePrompt": "请实施 {{path}} 中指定的计划。" + }, "queuedMessages": "排队中的消息 ({{count}})", "copyImage": "复制图片", "openImage": "打开图片", diff --git a/packages/vscode-webui/src/lib/constants.ts b/packages/vscode-webui/src/lib/constants.ts index 49f16fe53..3b17143d4 100644 --- a/packages/vscode-webui/src/lib/constants.ts +++ b/packages/vscode-webui/src/lib/constants.ts @@ -1,4 +1,10 @@ -export const CustomHtmlTags = ["file", "workflow", "custom-agent", "issue"]; +export const CustomHtmlTags = [ + "file", + "workflow", + "custom-agent", + "issue", + "plan-card", +]; export const MaxAttachments = 4; export const MaxFileSize = 20 * 1024 * 1024; // 20MB diff --git a/packages/vscode-webui/src/lib/hooks/use-custom-agents.ts b/packages/vscode-webui/src/lib/hooks/use-custom-agents.ts index 97ea1a72f..d6c3015c8 100644 --- a/packages/vscode-webui/src/lib/hooks/use-custom-agents.ts +++ b/packages/vscode-webui/src/lib/hooks/use-custom-agents.ts @@ -2,7 +2,7 @@ import { useSelectedModels } from "@/features/settings"; import { type CustomAgentFile, type ValidCustomAgentFile, - isValidCustomAgentFile, + isValidCustomAgent, } from "@getpochi/common/vscode-webui-bridge"; import { threadSignal } from "@quilted/threads/signals"; import { useQuery } from "@tanstack/react-query"; @@ -42,7 +42,7 @@ export function useCustomAgents(filterValidFiles = false) { return { customAgents: filterValidFiles - ? customAgentsSignal.value.filter(isValidCustomAgentFile) + ? customAgentsSignal.value.filter(isValidCustomAgent) : customAgentsSignal.value, isLoading: false, }; diff --git a/packages/vscode/src/integrations/webview/vscode-host-impl.ts b/packages/vscode/src/integrations/webview/vscode-host-impl.ts index 59811c811..dd28bacea 100644 --- a/packages/vscode/src/integrations/webview/vscode-host-impl.ts +++ b/packages/vscode/src/integrations/webview/vscode-host-impl.ts @@ -69,6 +69,7 @@ import { getTaskDisplayTitle, } from "@getpochi/common/vscode-webui-bridge"; import type { + CustomAgent, PreviewReturnType, PreviewToolFunctionType, ToolFunctionType, @@ -219,9 +220,11 @@ export class VSCodeHostImpl implements VSCodeHostApi, vscode.Disposable { readEnvironment = async (options: { isSubTask?: boolean; webviewKind: "sidebar" | "pane"; + taskId?: string; }): Promise => { const isSubTask = options.isSubTask ?? false; const webviewKind = options.webviewKind; + const taskId = options.taskId; const { files, isTruncated } = this.cwd ? await listWorkspaceFiles({ cwd: this.cwd, @@ -269,6 +272,7 @@ export class VSCodeHostImpl implements VSCodeHostApi, vscode.Disposable { info: { ...systemInfo, customRules, + taskId, }, }; @@ -911,7 +915,7 @@ export class VSCodeHostImpl implements VSCodeHostApi, vscode.Disposable { }; readCustomAgents = async (): Promise< - ThreadSignalSerialization + ThreadSignalSerialization<(CustomAgent | CustomAgentFile)[]> > => { return ThreadSignal.serialize(this.customAgentManager.agents); }; diff --git a/packages/vscode/src/lib/custom-agent.ts b/packages/vscode/src/lib/custom-agent.ts index 7a1f6764d..44b27f8b2 100644 --- a/packages/vscode/src/lib/custom-agent.ts +++ b/packages/vscode/src/lib/custom-agent.ts @@ -1,8 +1,9 @@ import * as os from "node:os"; import * as path from "node:path"; -import { getLogger } from "@getpochi/common"; +import { builtInCustomAgents, getLogger } from "@getpochi/common"; import { parseAgentFile } from "@getpochi/common/tool-utils"; import type { CustomAgentFile } from "@getpochi/common/vscode-webui-bridge"; +import type { CustomAgent } from "@getpochi/tools"; import { signal } from "@preact/signals-core"; import { uniqueBy } from "remeda"; import { Lifecycle, injectable, scoped } from "tsyringe"; @@ -44,7 +45,7 @@ async function readAgentsFromDir(dir: string): Promise { export class CustomAgentManager implements vscode.Disposable { private disposables: vscode.Disposable[] = []; - readonly agents = signal([]); + readonly agents = signal<(CustomAgent | CustomAgentFile)[]>([]); constructor(private readonly workspaceScope: WorkspaceScope) { this.initWatchers(); @@ -97,7 +98,9 @@ export class CustomAgentManager implements vscode.Disposable { private async loadAgents() { try { - const allAgents: CustomAgentFile[] = []; + const allAgents: (CustomAgent | CustomAgentFile)[] = [ + ...builtInCustomAgents, + ]; if (this.cwd) { const projectAgentsDir = path.join(this.cwd, ".pochi", "agents"); const cwd = this.cwd;