cte 10 месяцев назад
Родитель
Сommit
d8960788b4

+ 24 - 3
src/core/Cline.ts

@@ -370,7 +370,13 @@ export class Cline {
 		this.askResponseImages = images
 	}
 
-	async say(type: ClineSay, text?: string, images?: string[], partial?: boolean): Promise<undefined> {
+	async say(
+		type: ClineSay,
+		text?: string,
+		images?: string[],
+		partial?: boolean,
+		checkpoint?: Record<string, unknown>,
+	): Promise<undefined> {
 		if (this.abort) {
 			throw new Error("Roo Code instance aborted")
 		}
@@ -423,7 +429,7 @@ export class Cline {
 			// this is a new non-partial message, so add it like normal
 			const sayTs = Date.now()
 			this.lastMessageTs = sayTs
-			await this.addToClineMessages({ ts: sayTs, type: "say", say: type, text, images })
+			await this.addToClineMessages({ ts: sayTs, type: "say", say: type, text, images, checkpoint })
 			await this.providerRef.deref()?.postStateToWebview()
 		}
 	}
@@ -2747,6 +2753,13 @@ export class Cline {
 		// get previous api req's index to check token usage and determine if we need to truncate conversation history
 		const previousApiReqIndex = findLastIndex(this.clineMessages, (m) => m.say === "api_req_started")
 
+		// Save checkpoint if this is the first API request.
+		const isFirstRequest = this.clineMessages.filter((m) => m.say === "api_req_started").length === 0
+
+		if (isFirstRequest) {
+			await this.checkpointSave()
+		}
+
 		// getting verbose details is an expensive operation, it uses globby to top-down build file structure of project which for large projects can take a few seconds
 		// for the best UX we show a placeholder api_req_started message with a loading spinner as this happens
 		await this.say(
@@ -3299,6 +3312,7 @@ export class Cline {
 		}
 
 		try {
+			const isFirst = !this.checkpointService
 			const service = await this.getCheckpointService()
 			const commit = await service.saveCheckpoint(`Task: ${this.taskId}, Time: ${Date.now()}`)
 
@@ -3307,7 +3321,14 @@ export class Cline {
 					.deref()
 					?.postMessageToWebview({ type: "currentCheckpointUpdated", text: service.currentCheckpoint })
 
-				await this.say("checkpoint_saved", commit.commit)
+				// Checkpoint metadata required by the UI.
+				const checkpoint = {
+					isFirst,
+					from: service.baseCommitHash,
+					to: commit.commit,
+				}
+
+				await this.say("checkpoint_saved", commit.commit, undefined, undefined, checkpoint)
 			}
 		} catch (err) {
 			this.providerRef.deref()?.log("[checkpointSave] disabling checkpoints for this task")

+ 1 - 0
src/shared/ExtensionMessage.ts

@@ -139,6 +139,7 @@ export interface ClineMessage {
 	partial?: boolean
 	reasoning?: string
 	conversationHistoryIndex?: number
+	checkpoint?: Record<string, unknown>
 }
 
 export type ClineAsk =

+ 1 - 0
webview-ui/src/components/chat/ChatRow.tsx

@@ -761,6 +761,7 @@ export const ChatRowContent = ({
 						<CheckpointSaved
 							ts={message.ts!}
 							commitHash={message.text!}
+							checkpoint={message.checkpoint}
 							currentCheckpointHash={currentCheckpoint}
 						/>
 					)

+ 10 - 6
webview-ui/src/components/chat/checkpoints/CheckpointMenu.tsx

@@ -1,17 +1,19 @@
 import { useState, useEffect, useCallback } from "react"
 import { CheckIcon, Cross2Icon } from "@radix-ui/react-icons"
 
-import { vscode } from "../../../utils/vscode"
-
 import { Button, Popover, PopoverContent, PopoverTrigger } from "@/components/ui"
 
+import { vscode } from "../../../utils/vscode"
+import { Checkpoint } from "./schema"
+
 type CheckpointMenuProps = {
 	ts: number
 	commitHash: string
+	checkpoint?: Checkpoint
 	currentCheckpointHash?: string
 }
 
-export const CheckpointMenu = ({ ts, commitHash, currentCheckpointHash }: CheckpointMenuProps) => {
+export const CheckpointMenu = ({ ts, commitHash, checkpoint, currentCheckpointHash }: CheckpointMenuProps) => {
 	const [portalContainer, setPortalContainer] = useState<HTMLElement>()
 	const [isOpen, setIsOpen] = useState(false)
 	const [isConfirming, setIsConfirming] = useState(false)
@@ -43,9 +45,11 @@ export const CheckpointMenu = ({ ts, commitHash, currentCheckpointHash }: Checkp
 
 	return (
 		<div className="flex flex-row gap-1">
-			<Button variant="ghost" size="icon" onClick={onCheckpointDiff} title="View Diff">
-				<span className="codicon codicon-diff-single" />
-			</Button>
+			{!checkpoint?.isFirst && (
+				<Button variant="ghost" size="icon" onClick={onCheckpointDiff} title="View Diff">
+					<span className="codicon codicon-diff-single" />
+				</Button>
+			)}
 			<Popover
 				open={isOpen}
 				onOpenChange={(open) => {

+ 18 - 3
webview-ui/src/components/chat/checkpoints/CheckpointSaved.tsx

@@ -1,22 +1,37 @@
+import { useMemo } from "react"
+
 import { CheckpointMenu } from "./CheckpointMenu"
+import { checkpointSchema } from "./schema"
 
 type CheckpointSavedProps = {
 	ts: number
 	commitHash: string
+	checkpoint?: Record<string, unknown>
 	currentCheckpointHash?: string
 }
 
-export const CheckpointSaved = (props: CheckpointSavedProps) => {
+export const CheckpointSaved = ({ checkpoint, ...props }: CheckpointSavedProps) => {
 	const isCurrent = props.currentCheckpointHash === props.commitHash
 
+	const metadata = useMemo(() => {
+		if (!checkpoint) {
+			return undefined
+		}
+
+		const result = checkpointSchema.safeParse(checkpoint)
+		return result.success ? result.data : undefined
+	}, [checkpoint])
+
+	const isFirst = !!metadata?.isFirst
+
 	return (
 		<div className="flex items-center justify-between">
 			<div className="flex gap-2">
 				<span className="codicon codicon-git-commit text-blue-400" />
-				<span className="font-bold">Checkpoint</span>
+				<span className="font-bold">{isFirst ? "Initial Checkpoint" : "Checkpoint"}</span>
 				{isCurrent && <span className="text-muted text-sm">Current</span>}
 			</div>
-			<CheckpointMenu {...props} />
+			<CheckpointMenu {...props} checkpoint={metadata} />
 		</div>
 	)
 }

+ 9 - 0
webview-ui/src/components/chat/checkpoints/schema.ts

@@ -0,0 +1,9 @@
+import { z } from "zod"
+
+export const checkpointSchema = z.object({
+	isFirst: z.boolean(),
+	from: z.string(),
+	to: z.string(),
+})
+
+export type Checkpoint = z.infer<typeof checkpointSchema>