Skip to content

Commit be2e0b7

Browse files
authored
Merge pull request #946 from RooVetGit/cte/store-initial-checkpoint
Store initial checkpoint
2 parents 37c2a17 + d896078 commit be2e0b7

File tree

6 files changed

+63
-12
lines changed

6 files changed

+63
-12
lines changed

src/core/Cline.ts

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -370,7 +370,13 @@ export class Cline {
370370
this.askResponseImages = images
371371
}
372372

373-
async say(type: ClineSay, text?: string, images?: string[], partial?: boolean): Promise<undefined> {
373+
async say(
374+
type: ClineSay,
375+
text?: string,
376+
images?: string[],
377+
partial?: boolean,
378+
checkpoint?: Record<string, unknown>,
379+
): Promise<undefined> {
374380
if (this.abort) {
375381
throw new Error("Roo Code instance aborted")
376382
}
@@ -423,7 +429,7 @@ export class Cline {
423429
// this is a new non-partial message, so add it like normal
424430
const sayTs = Date.now()
425431
this.lastMessageTs = sayTs
426-
await this.addToClineMessages({ ts: sayTs, type: "say", say: type, text, images })
432+
await this.addToClineMessages({ ts: sayTs, type: "say", say: type, text, images, checkpoint })
427433
await this.providerRef.deref()?.postStateToWebview()
428434
}
429435
}
@@ -2747,6 +2753,13 @@ export class Cline {
27472753
// get previous api req's index to check token usage and determine if we need to truncate conversation history
27482754
const previousApiReqIndex = findLastIndex(this.clineMessages, (m) => m.say === "api_req_started")
27492755

2756+
// Save checkpoint if this is the first API request.
2757+
const isFirstRequest = this.clineMessages.filter((m) => m.say === "api_req_started").length === 0
2758+
2759+
if (isFirstRequest) {
2760+
await this.checkpointSave()
2761+
}
2762+
27502763
// 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
27512764
// for the best UX we show a placeholder api_req_started message with a loading spinner as this happens
27522765
await this.say(
@@ -3299,6 +3312,7 @@ export class Cline {
32993312
}
33003313

33013314
try {
3315+
const isFirst = !this.checkpointService
33023316
const service = await this.getCheckpointService()
33033317
const commit = await service.saveCheckpoint(`Task: ${this.taskId}, Time: ${Date.now()}`)
33043318

@@ -3307,7 +3321,14 @@ export class Cline {
33073321
.deref()
33083322
?.postMessageToWebview({ type: "currentCheckpointUpdated", text: service.currentCheckpoint })
33093323

3310-
await this.say("checkpoint_saved", commit.commit)
3324+
// Checkpoint metadata required by the UI.
3325+
const checkpoint = {
3326+
isFirst,
3327+
from: service.baseCommitHash,
3328+
to: commit.commit,
3329+
}
3330+
3331+
await this.say("checkpoint_saved", commit.commit, undefined, undefined, checkpoint)
33113332
}
33123333
} catch (err) {
33133334
this.providerRef.deref()?.log("[checkpointSave] disabling checkpoints for this task")

src/shared/ExtensionMessage.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ export interface ClineMessage {
139139
partial?: boolean
140140
reasoning?: string
141141
conversationHistoryIndex?: number
142+
checkpoint?: Record<string, unknown>
142143
}
143144

144145
export type ClineAsk =

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -761,6 +761,7 @@ export const ChatRowContent = ({
761761
<CheckpointSaved
762762
ts={message.ts!}
763763
commitHash={message.text!}
764+
checkpoint={message.checkpoint}
764765
currentCheckpointHash={currentCheckpoint}
765766
/>
766767
)

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

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
import { useState, useEffect, useCallback } from "react"
22
import { CheckIcon, Cross2Icon } from "@radix-ui/react-icons"
33

4-
import { vscode } from "../../../utils/vscode"
5-
64
import { Button, Popover, PopoverContent, PopoverTrigger } from "@/components/ui"
75

6+
import { vscode } from "../../../utils/vscode"
7+
import { Checkpoint } from "./schema"
8+
89
type CheckpointMenuProps = {
910
ts: number
1011
commitHash: string
12+
checkpoint?: Checkpoint
1113
currentCheckpointHash?: string
1214
}
1315

14-
export const CheckpointMenu = ({ ts, commitHash, currentCheckpointHash }: CheckpointMenuProps) => {
16+
export const CheckpointMenu = ({ ts, commitHash, checkpoint, currentCheckpointHash }: CheckpointMenuProps) => {
1517
const [portalContainer, setPortalContainer] = useState<HTMLElement>()
1618
const [isOpen, setIsOpen] = useState(false)
1719
const [isConfirming, setIsConfirming] = useState(false)
@@ -43,9 +45,11 @@ export const CheckpointMenu = ({ ts, commitHash, currentCheckpointHash }: Checkp
4345

4446
return (
4547
<div className="flex flex-row gap-1">
46-
<Button variant="ghost" size="icon" onClick={onCheckpointDiff} title="View Diff">
47-
<span className="codicon codicon-diff-single" />
48-
</Button>
48+
{!checkpoint?.isFirst && (
49+
<Button variant="ghost" size="icon" onClick={onCheckpointDiff} title="View Diff">
50+
<span className="codicon codicon-diff-single" />
51+
</Button>
52+
)}
4953
<Popover
5054
open={isOpen}
5155
onOpenChange={(open) => {
Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,37 @@
1+
import { useMemo } from "react"
2+
13
import { CheckpointMenu } from "./CheckpointMenu"
4+
import { checkpointSchema } from "./schema"
25

36
type CheckpointSavedProps = {
47
ts: number
58
commitHash: string
9+
checkpoint?: Record<string, unknown>
610
currentCheckpointHash?: string
711
}
812

9-
export const CheckpointSaved = (props: CheckpointSavedProps) => {
13+
export const CheckpointSaved = ({ checkpoint, ...props }: CheckpointSavedProps) => {
1014
const isCurrent = props.currentCheckpointHash === props.commitHash
1115

16+
const metadata = useMemo(() => {
17+
if (!checkpoint) {
18+
return undefined
19+
}
20+
21+
const result = checkpointSchema.safeParse(checkpoint)
22+
return result.success ? result.data : undefined
23+
}, [checkpoint])
24+
25+
const isFirst = !!metadata?.isFirst
26+
1227
return (
1328
<div className="flex items-center justify-between">
1429
<div className="flex gap-2">
1530
<span className="codicon codicon-git-commit text-blue-400" />
16-
<span className="font-bold">Checkpoint</span>
31+
<span className="font-bold">{isFirst ? "Initial Checkpoint" : "Checkpoint"}</span>
1732
{isCurrent && <span className="text-muted text-sm">Current</span>}
1833
</div>
19-
<CheckpointMenu {...props} />
34+
<CheckpointMenu {...props} checkpoint={metadata} />
2035
</div>
2136
)
2237
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { z } from "zod"
2+
3+
export const checkpointSchema = z.object({
4+
isFirst: z.boolean(),
5+
from: z.string(),
6+
to: z.string(),
7+
})
8+
9+
export type Checkpoint = z.infer<typeof checkpointSchema>

0 commit comments

Comments
 (0)