Skip to content

Commit d65e9ef

Browse files
authored
Merge pull request #868 from RooVetGit/cte/current-checkpoint-indicator
Current checkpoint indicator
2 parents 1a527fc + a0993a3 commit d65e9ef

File tree

8 files changed

+66
-26
lines changed

8 files changed

+66
-26
lines changed

src/core/Cline.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3313,6 +3313,10 @@ export class Cline {
33133313
const commit = await service.saveCheckpoint(`Task: ${this.taskId}, Time: ${Date.now()}`)
33143314

33153315
if (commit?.commit) {
3316+
await this.providerRef
3317+
.deref()
3318+
?.postMessageToWebview({ type: "currentCheckpointUpdated", text: service.currentCheckpoint })
3319+
33163320
await this.say("checkpoint_saved", commit.commit)
33173321
}
33183322
} catch (err) {
@@ -3349,6 +3353,10 @@ export class Cline {
33493353
const service = await this.getCheckpointService()
33503354
await service.restoreCheckpoint(commitHash)
33513355

3356+
await this.providerRef
3357+
.deref()
3358+
?.postMessageToWebview({ type: "currentCheckpointUpdated", text: service.currentCheckpoint })
3359+
33523360
if (mode === "restore") {
33533361
await this.overwriteApiConversationHistory(
33543362
this.apiConversationHistory.filter((m) => !m.ts || m.ts < ts),

src/services/checkpoints/CheckpointService.ts

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,6 @@ if (process.env.NODE_ENV !== "test") {
99
debug.enable("simple-git")
1010
}
1111

12-
export interface Checkpoint {
13-
hash: string
14-
message: string
15-
timestamp?: Date
16-
}
17-
1812
export type CheckpointServiceOptions = {
1913
taskId: string
2014
git?: SimpleGit
@@ -60,6 +54,16 @@ export type CheckpointServiceOptions = {
6054
*/
6155

6256
export class CheckpointService {
57+
private _currentCheckpoint?: string
58+
59+
public get currentCheckpoint() {
60+
return this._currentCheckpoint
61+
}
62+
63+
private set currentCheckpoint(value: string | undefined) {
64+
this._currentCheckpoint = value
65+
}
66+
6367
constructor(
6468
public readonly taskId: string,
6569
private readonly git: SimpleGit,
@@ -217,6 +221,8 @@ export class CheckpointService {
217221
await this.popStash()
218222
}
219223

224+
this.currentCheckpoint = commit.commit
225+
220226
return commit
221227
} catch (err) {
222228
this.log(`[saveCheckpoint] Failed to save checkpoint: ${err instanceof Error ? err.message : String(err)}`)
@@ -237,6 +243,7 @@ export class CheckpointService {
237243
await this.ensureBranch(this.mainBranch)
238244
await this.git.clean([CleanOptions.FORCE, CleanOptions.RECURSIVE])
239245
await this.git.raw(["restore", "--source", commitHash, "--worktree", "--", "."])
246+
this.currentCheckpoint = commitHash
240247
}
241248

242249
public static async create({ taskId, git, baseDir, log = console.log }: CheckpointServiceOptions) {
@@ -291,7 +298,7 @@ export class CheckpointService {
291298
// the checkpoint (i.e. the `git restore` command doesn't work
292299
// for empty commits).
293300
await fs.writeFile(path.join(baseDir, ".gitkeep"), "")
294-
await git.add(".")
301+
await git.add(".gitkeep")
295302
const commit = await git.commit("Initial commit")
296303

297304
if (!commit.commit) {

src/services/checkpoints/__tests__/CheckpointService.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,7 @@ describe("CheckpointService", () => {
291291
const baseDir = path.join(os.tmpdir(), `checkpoint-service-test2-${Date.now()}`)
292292
await fs.mkdir(baseDir)
293293
const newTestFile = path.join(baseDir, "test.txt")
294+
await fs.writeFile(newTestFile, "Hello, world!")
294295

295296
const newGit = simpleGit(baseDir)
296297
const initSpy = jest.spyOn(newGit, "init")
@@ -300,7 +301,6 @@ describe("CheckpointService", () => {
300301
expect(initSpy).toHaveBeenCalled()
301302

302303
// Save a checkpoint: Hello, world!
303-
await fs.writeFile(newTestFile, "Hello, world!")
304304
const commit1 = await newService.saveCheckpoint("Hello, world!")
305305
expect(commit1?.commit).toBeTruthy()
306306
expect(await fs.readFile(newTestFile, "utf-8")).toBe("Hello, world!")

src/shared/ExtensionMessage.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ export interface ExtensionMessage {
4242
| "autoApprovalEnabled"
4343
| "updateCustomMode"
4444
| "deleteCustomMode"
45+
| "currentCheckpointUpdated"
4546
text?: string
4647
action?:
4748
| "chatButtonClicked"

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

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ export const ChatRowContent = ({
8181
isLast,
8282
isStreaming,
8383
}: ChatRowContentProps) => {
84-
const { mcpServers, alwaysAllowMcp } = useExtensionState()
84+
const { mcpServers, alwaysAllowMcp, currentCheckpoint } = useExtensionState()
8585
const [reasoningCollapsed, setReasoningCollapsed] = useState(false)
8686

8787
// Auto-collapse reasoning when new messages arrive
@@ -757,7 +757,13 @@ export const ChatRowContent = ({
757757
</>
758758
)
759759
case "checkpoint_saved":
760-
return <CheckpointSaved ts={message.ts!} commitHash={message.text!} />
760+
return (
761+
<CheckpointSaved
762+
ts={message.ts!}
763+
commitHash={message.text!}
764+
currentCheckpointHash={currentCheckpoint}
765+
/>
766+
)
761767
default:
762768
return (
763769
<>

webview-ui/src/components/chat/checkpoints/CheckpointMenu.tsx

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,16 @@ import { Button, Popover, PopoverContent, PopoverTrigger } from "@/components/ui
88
type CheckpointMenuProps = {
99
ts: number
1010
commitHash: string
11+
currentCheckpointHash?: string
1112
}
1213

13-
export const CheckpointMenu = ({ ts, commitHash }: CheckpointMenuProps) => {
14+
export const CheckpointMenu = ({ ts, commitHash, currentCheckpointHash }: CheckpointMenuProps) => {
1415
const [portalContainer, setPortalContainer] = useState<HTMLElement>()
1516
const [isOpen, setIsOpen] = useState(false)
1617
const [isConfirming, setIsConfirming] = useState(false)
1718

19+
const isCurrent = currentCheckpointHash === commitHash
20+
1821
const onCheckpointDiff = useCallback(() => {
1922
vscode.postMessage({ type: "checkpointDiff", payload: { ts, commitHash, mode: "checkpoint" } })
2023
}, [ts, commitHash])
@@ -56,14 +59,16 @@ export const CheckpointMenu = ({ ts, commitHash }: CheckpointMenuProps) => {
5659
</PopoverTrigger>
5760
<PopoverContent align="end" container={portalContainer}>
5861
<div className="flex flex-col gap-2">
59-
<div className="flex flex-col gap-1 group hover:text-foreground">
60-
<Button variant="secondary" onClick={onPreview}>
61-
Restore Files
62-
</Button>
63-
<div className="text-muted transition-colors group-hover:text-foreground">
64-
Restores your project's files back to a snapshot taken at this point.
62+
{!isCurrent && (
63+
<div className="flex flex-col gap-1 group hover:text-foreground">
64+
<Button variant="secondary" onClick={onPreview}>
65+
Restore Files
66+
</Button>
67+
<div className="text-muted transition-colors group-hover:text-foreground">
68+
Restores your project's files back to a snapshot taken at this point.
69+
</div>
6570
</div>
66-
</div>
71+
)}
6772
<div className="flex flex-col gap-1 group hover:text-foreground">
6873
<div className="flex flex-col gap-1 group hover:text-foreground">
6974
{!isConfirming ? (

webview-ui/src/components/chat/checkpoints/CheckpointSaved.tsx

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,20 @@ import { CheckpointMenu } from "./CheckpointMenu"
33
type CheckpointSavedProps = {
44
ts: number
55
commitHash: string
6+
currentCheckpointHash?: string
67
}
78

8-
export const CheckpointSaved = (props: CheckpointSavedProps) => (
9-
<div className="flex items-center justify-between">
10-
<div className="flex items-center gap-2">
11-
<span className="codicon codicon-git-commit text-blue-400" />
12-
<span className="font-bold">Checkpoint</span>
9+
export const CheckpointSaved = (props: CheckpointSavedProps) => {
10+
const isCurrent = props.currentCheckpointHash === props.commitHash
11+
12+
return (
13+
<div className="flex items-center justify-between">
14+
<div className="flex gap-2">
15+
<span className="codicon codicon-git-commit text-blue-400" />
16+
<span className="font-bold">Checkpoint</span>
17+
{isCurrent && <span className="text-muted text-sm">Current</span>}
18+
</div>
19+
<CheckpointMenu {...props} />
1320
</div>
14-
<CheckpointMenu {...props} />
15-
</div>
16-
)
21+
)
22+
}

webview-ui/src/context/ExtensionStateContext.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export interface ExtensionStateContextType extends ExtensionState {
2626
openRouterModels: Record<string, ModelInfo>
2727
openAiModels: string[]
2828
mcpServers: McpServer[]
29+
currentCheckpoint?: string
2930
filePaths: string[]
3031
openedTabs: Array<{ label: string; isActive: boolean; path?: string }>
3132
setApiConfiguration: (config: ApiConfiguration) => void
@@ -126,6 +127,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
126127

127128
const [openAiModels, setOpenAiModels] = useState<string[]>([])
128129
const [mcpServers, setMcpServers] = useState<McpServer[]>([])
130+
const [currentCheckpoint, setCurrentCheckpoint] = useState<string>()
129131

130132
const setListApiConfigMeta = useCallback(
131133
(value: ApiConfigMeta[]) => setState((prevState) => ({ ...prevState, listApiConfigMeta: value })),
@@ -241,6 +243,10 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
241243
setMcpServers(message.mcpServers ?? [])
242244
break
243245
}
246+
case "currentCheckpointUpdated": {
247+
setCurrentCheckpoint(message.text)
248+
break
249+
}
244250
case "listApiConfig": {
245251
setListApiConfigMeta(message.listApiConfig ?? [])
246252
break
@@ -265,6 +271,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
265271
openRouterModels,
266272
openAiModels,
267273
mcpServers,
274+
currentCheckpoint,
268275
filePaths,
269276
openedTabs,
270277
soundVolume: state.soundVolume,

0 commit comments

Comments
 (0)