Skip to content

Commit 36a7e99

Browse files
committed
feat: add ability to edit subtask instructions
- Added edit button to subtask instructions in orchestrator mode - Users can now modify subtask content before approval - Reduces unnecessary LLM calls and token usage - Implements inline editing similar to user message editing Fixes #9593
1 parent f173c9c commit 36a7e99

File tree

3 files changed

+137
-4
lines changed

3 files changed

+137
-4
lines changed

src/core/webview/webviewMessageHandler.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1554,6 +1554,56 @@ export const webviewMessageHandler = async (
15541554
}
15551555
break
15561556
}
1557+
case "submitEditedSubtask": {
1558+
// Handle editing of subtask instructions
1559+
const currentCline = provider.getCurrentTask()
1560+
if (currentCline && message.messageTs && message.editedSubtaskContent) {
1561+
// Find the message with the newTask tool
1562+
const messageIndex = currentCline.clineMessages.findIndex(
1563+
(msg: ClineMessage) => msg.ts === message.messageTs,
1564+
)
1565+
1566+
if (messageIndex !== -1) {
1567+
const targetMessage = currentCline.clineMessages[messageIndex]
1568+
1569+
// Parse the tool content to update it
1570+
if (targetMessage.ask === "tool" && targetMessage.text) {
1571+
try {
1572+
const tool = JSON.parse(targetMessage.text)
1573+
if (tool.tool === "newTask") {
1574+
// Update the content with the edited subtask instructions
1575+
tool.content = message.editedSubtaskContent
1576+
1577+
// Update the message with the new content
1578+
currentCline.clineMessages[messageIndex].text = JSON.stringify(tool)
1579+
1580+
// Save the updated messages
1581+
await saveTaskMessages({
1582+
messages: currentCline.clineMessages,
1583+
taskId: currentCline.taskId,
1584+
globalStoragePath: provider.contextProxy.globalStorageUri.fsPath,
1585+
})
1586+
1587+
// Update the UI to reflect the changes
1588+
await provider.postStateToWebview()
1589+
1590+
// Now approve the subtask with the edited content
1591+
// This simulates the user clicking "Approve" after editing
1592+
currentCline.handleWebviewAskResponse("yesButtonClicked")
1593+
}
1594+
} catch (error) {
1595+
console.error("Error updating subtask instructions:", error)
1596+
vscode.window.showErrorMessage(
1597+
t("common:errors.message.error_editing_subtask", {
1598+
error: error instanceof Error ? error.message : String(error),
1599+
}),
1600+
)
1601+
}
1602+
}
1603+
}
1604+
}
1605+
break
1606+
}
15571607

15581608
case "hasOpenedModeSelector":
15591609
await updateGlobalState("hasOpenedModeSelector", message.bool ?? true)

src/shared/WebviewMessage.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ export interface WebviewMessage {
9393
| "deleteMessageConfirm"
9494
| "submitEditedMessage"
9595
| "editMessageConfirm"
96+
| "submitEditedSubtask"
9697
| "enableMcpServerCreation"
9798
| "remoteControlEnabled"
9899
| "taskSyncEnabled"
@@ -175,6 +176,7 @@ export interface WebviewMessage {
175176
| "browserPanelDidLaunch"
176177
text?: string
177178
editedMessageContent?: string
179+
editedSubtaskContent?: string
178180
tab?: "settings" | "history" | "mcp" | "modes" | "chat" | "marketplace" | "cloud"
179181
disabled?: boolean
180182
context?: string

webview-ui/src/components/chat/ChatRow.tsx

Lines changed: 85 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,10 @@ export const ChatRowContent = ({
171171
const [editMode, setEditMode] = useState<Mode>(mode || "code")
172172
const [editImages, setEditImages] = useState<string[]>([])
173173

174+
// State for editing subtask instructions
175+
const [isEditingSubtask, setIsEditingSubtask] = useState(false)
176+
const [editedSubtaskContent, setEditedSubtaskContent] = useState("")
177+
174178
// Handle message events for image selection during edit mode
175179
useEffect(() => {
176180
const handleMessage = (event: MessageEvent) => {
@@ -815,6 +819,30 @@ export const ChatRowContent = ({
815819
</>
816820
)
817821
case "newTask":
822+
// Handle edit button click for subtask
823+
const handleEditSubtaskClick = () => {
824+
setIsEditingSubtask(true)
825+
setEditedSubtaskContent(tool.content || "")
826+
}
827+
828+
// Handle cancel edit for subtask
829+
const handleCancelEditSubtask = () => {
830+
setIsEditingSubtask(false)
831+
setEditedSubtaskContent(tool.content || "")
832+
}
833+
834+
// Handle save edit for subtask
835+
const handleSaveEditSubtask = () => {
836+
setIsEditingSubtask(false)
837+
// Send edited subtask content to backend
838+
vscode.postMessage({
839+
type: "submitEditedSubtask",
840+
messageTs: message.ts,
841+
editedSubtaskContent: editedSubtaskContent,
842+
mode: tool.mode,
843+
})
844+
}
845+
818846
return (
819847
<>
820848
<div style={headerStyle}>
@@ -828,13 +856,15 @@ export const ChatRowContent = ({
828856
</span>
829857
</div>
830858
<div
859+
className="group"
831860
style={{
832861
marginTop: "4px",
833862
backgroundColor: "var(--vscode-badge-background)",
834863
border: "1px solid var(--vscode-badge-background)",
835864
borderRadius: "4px 4px 0 0",
836865
overflow: "hidden",
837866
marginBottom: "2px",
867+
position: "relative",
838868
}}>
839869
<div
840870
style={{
@@ -846,13 +876,64 @@ export const ChatRowContent = ({
846876
color: "var(--vscode-badge-foreground)",
847877
display: "flex",
848878
alignItems: "center",
849-
gap: "6px",
879+
justifyContent: "space-between",
850880
}}>
851-
<span className="codicon codicon-arrow-right"></span>
852-
{t("chat:subtasks.newTaskContent")}
881+
<div style={{ display: "flex", alignItems: "center", gap: "6px" }}>
882+
<span className="codicon codicon-arrow-right"></span>
883+
{t("chat:subtasks.newTaskContent")}
884+
</div>
885+
{/* Edit button for subtask instructions */}
886+
{message.type === "ask" && !isStreaming && !isEditingSubtask && (
887+
<div
888+
className="cursor-pointer opacity-0 group-hover:opacity-100 transition-opacity"
889+
onClick={(e) => {
890+
e.stopPropagation()
891+
handleEditSubtaskClick()
892+
}}
893+
title={t("chat:editSubtaskInstructions")}
894+
style={{
895+
padding: "2px 4px",
896+
borderRadius: "3px",
897+
display: "flex",
898+
alignItems: "center",
899+
gap: "4px",
900+
}}>
901+
<Edit className="w-4 h-4" aria-label="Edit subtask instructions" />
902+
</div>
903+
)}
853904
</div>
854905
<div style={{ padding: "12px 16px", backgroundColor: "var(--vscode-editor-background)" }}>
855-
<MarkdownBlock markdown={tool.content} />
906+
{isEditingSubtask ? (
907+
<div className="flex flex-col gap-2">
908+
<textarea
909+
value={editedSubtaskContent}
910+
onChange={(e) => setEditedSubtaskContent(e.target.value)}
911+
className="w-full p-2 border border-vscode-input-border bg-vscode-input-background text-vscode-input-foreground rounded"
912+
style={{
913+
minHeight: "200px",
914+
resize: "vertical",
915+
fontFamily: "var(--vscode-editor-font-family)",
916+
fontSize: "var(--vscode-editor-font-size)",
917+
}}
918+
placeholder={t("chat:editSubtaskInstructions.placeholder")}
919+
autoFocus
920+
/>
921+
<div className="flex gap-2 justify-end">
922+
<button
923+
onClick={handleCancelEditSubtask}
924+
className="px-3 py-1 rounded bg-vscode-button-secondaryBackground text-vscode-button-secondaryForeground hover:bg-vscode-button-secondaryHoverBackground">
925+
{t("chat:cancel")}
926+
</button>
927+
<button
928+
onClick={handleSaveEditSubtask}
929+
className="px-3 py-1 rounded bg-vscode-button-background text-vscode-button-foreground hover:bg-vscode-button-hoverBackground">
930+
{t("chat:save")}
931+
</button>
932+
</div>
933+
</div>
934+
) : (
935+
<MarkdownBlock markdown={tool.content} />
936+
)}
856937
</div>
857938
</div>
858939
</>

0 commit comments

Comments
 (0)