Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion packages/kilo-vscode/tests/unit/agent-manager-arch.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -360,7 +360,7 @@ describe("Agent Manager Model Picker", () => {

describe("Agent Manager Worktree Actions", () => {
it("opens the configuration dialog from the primary plus action", () => {
const source = fs.readFileSync(path.join(ROOT, "webview-ui/agent-manager/WorktreeSectionActions.tsx"), "utf-8")
const source = fs.readFileSync(path.join(ROOT, "webview-ui/agent-manager/ProjectActions.tsx"), "utf-8")
const start = source.indexOf('<div class="am-split-button">')
const end = source.indexOf("</div>", start)
const actions = source.slice(start, end)
Expand Down
3 changes: 2 additions & 1 deletion packages/kilo-vscode/tests/unit/plan-exit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ describe("planDisplayPath", () => {
describe("plan_exit renderer uses openFile not openDiff (source)", () => {
const ROOT = path.resolve(import.meta.dir, "../..")
const FILE = path.join(ROOT, "webview-ui/src/components/chat/AssistantMessage.tsx")
const TURN_FILE = path.join(ROOT, "webview-ui/src/components/chat/VscodeSessionTurn.tsx")
const TURN_FILE = path.join(ROOT, "webview-ui/src/components/chat/TranscriptRow.tsx")
const src = fs.readFileSync(FILE, "utf-8")
const turnSrc = fs.readFileSync(TURN_FILE, "utf-8")

Expand Down Expand Up @@ -76,6 +76,7 @@ describe("plan_exit renderer uses openFile not openDiff (source)", () => {
expect(src).not.toContain("Object.values(data.store.part ?? {}).flat()")
expect(src).not.toContain("[...props.parts, ...all()]")
expect(src).not.toContain("turnParts")
expect(turnSrc).toContain("parts={row().parts as unknown as SDKPart[]}")
expect(turnSrc).not.toContain("assistantMessages().flatMap")
expect(turnSrc).not.toContain("turnParts={assistantParts()}")
})
Expand Down
6 changes: 3 additions & 3 deletions packages/kilo-vscode/tests/unit/revert-checkpoints.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import fs from "node:fs"
import path from "node:path"

const ROOT = path.resolve(import.meta.dir, "../..")
const TURN_FILE = path.join(ROOT, "webview-ui/src/components/chat/VscodeSessionTurn.tsx")
const TURN_FILE = path.join(ROOT, "webview-ui/src/components/chat/TranscriptRow.tsx")
const PROVIDER_FILE = path.join(ROOT, "src/KiloProvider.ts")
const BANNER_FILE = path.join(ROOT, "webview-ui/src/components/chat/RevertBanner.tsx")
const SESSION_FILE = path.join(ROOT, "webview-ui/src/types/messages/sessions.ts")
Expand Down Expand Up @@ -32,12 +32,12 @@ function exported(name: string) {

describe("message revert checkpoints", () => {
it("keeps revert actions available after a session is already reverted", () => {
expect(src).toMatch(/onRevert=\{\s*assistantMessages\(\)\.length > 0\s*\? \(\) =>/)
expect(src).toMatch(/onRevert=\{\s*row\(\)\.answered\s*\? \(\) =>/)
expect(src).not.toMatch(/onRevert=\{[\s\S]*?&& !session\.revert\(\)[\s\S]*?\? \(\) =>/)
})

it("only marks revert disabled while the agent is busy", () => {
expect(src).toMatch(/data-revert-disabled=\{\s*assistantMessages\(\)\.length > 0 && session\.status\(\) !== "idle"/)
expect(src).toMatch(/data-revert-disabled=\{\s*row\(\)\.answered && session\.status\(\) !== "idle"/)
expect(src).not.toMatch(/data-revert-disabled=\{[\s\S]*?!session\.revert\(\)/)
})
})
Expand Down
17 changes: 17 additions & 0 deletions packages/kilo-vscode/tests/unit/review-comments.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
buildFileAnnotations,
clearReviewComposer,
createReviewComposer,
labels,
reviewAnnotationSpeechKey,
reviewComposerDraft,
reviewComposerEdit,
Expand Down Expand Up @@ -441,3 +442,19 @@ describe("getFilename", () => {
expect(getFilename("src/components/Button.tsx")).toBe("Button.tsx")
})
})

it("shares review action labels and translates line numbers", () => {
const value = labels((key, params) => (params ? `${key}:${params.line}` : key))
expect(value).toMatchObject({
placeholder: "agentManager.review.commentPlaceholder",
cancel: "common.cancel",
comment: "agentManager.review.commentAction",
send: "prompt.action.send",
save: "common.save",
sendToChat: "agentManager.review.sendToChat",
edit: "common.edit",
delete: "common.delete",
})
expect(value.commentOnLine(3)).toBe("agentManager.review.commentOnLine:3")
expect(value.editCommentOnLine(7)).toBe("agentManager.review.editCommentOnLine:7")
})
27 changes: 3 additions & 24 deletions packages/kilo-vscode/webview-ui/agent-manager/DiffPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ import {
reviewDraftSpeechKey,
reviewEditSpeechKey,
sendReviewComments,
type AnnotationLabels,
labels,
type AnnotationMeta,
type ReviewComposer,
type ReviewDraft,
Expand Down Expand Up @@ -130,18 +130,6 @@ export const DiffPanel: Component<DiffPanelProps> = (props) => {
const isMac = typeof navigator !== "undefined" && /Mac|iPhone|iPad/.test(navigator.userAgent)
const sendAllKeybind = () =>
isMac ? t("agentManager.review.sendAllShortcut.mac") : t("agentManager.review.sendAllShortcut.other")
const labels = (): AnnotationLabels => ({
commentOnLine: (line) => t("agentManager.review.commentOnLine", { line }),
editCommentOnLine: (line) => t("agentManager.review.editCommentOnLine", { line }),
placeholder: t("agentManager.review.commentPlaceholder"),
cancel: t("common.cancel"),
comment: t("agentManager.review.commentAction"),
send: t("prompt.action.send"),
save: t("common.save"),
sendToChat: t("agentManager.review.sendToChat"),
edit: t("common.edit"),
delete: t("common.delete"),
})
const localComposer = createReviewComposer()
const composer = () => props.composer ?? localComposer
const [manualOpen, setManualOpen] = createSignal<Record<string, string[]>>({})
Expand Down Expand Up @@ -437,7 +425,7 @@ export const DiffPanel: Component<DiffPanelProps> = (props) => {
updateComment,
deleteComment,
cancelDraft,
labels: labels(),
labels: labels(t),
activeTerminalId: props.activeTerminalId,
speech: reviewSpeech,
})
Expand Down Expand Up @@ -465,16 +453,7 @@ export const DiffPanel: Component<DiffPanelProps> = (props) => {
const sendAllToChat = () => {
const all = comments()
if (all.length === 0) return
window.dispatchEvent(
new MessageEvent("message", {
data: {
type: props.activeTerminalId ? "appendReviewCommentsToTerminal" : "appendReviewComments",
comments: all,
autoSend: true,
targetTerminalId: props.activeTerminalId,
},
}),
)
sendReviewComments(all, props.activeTerminalId)
preserveScroll(() => setComments([]))
props.onSendAll?.()
}
Expand Down
103 changes: 53 additions & 50 deletions packages/kilo-vscode/webview-ui/agent-manager/ProjectActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,67 +8,70 @@ import { TooltipKeybind } from "@kilocode/kilo-ui/tooltip"
import type { LanguageContextValue } from "../src/context/language"
import { parseBindingTokens } from "./keybind-tokens"

interface Props {
export interface WorktreeCreateProps {
branch: string
bindings: Record<string, string>
loaded: boolean
t: LanguageContextValue["t"]
onCreate: () => void
onNew: () => void
onSection: () => void
onSettings: () => void
}

export const ProjectActions: Component<Props> = (props) => (
<div class="am-project-actions">
<div class="am-split-button">
<TooltipKeybind
title={props.t("agentManager.shortcuts.advancedWorktree")}
keybind={props.bindings.newWorktree ?? ""}
export const WorktreeCreate: Component<WorktreeCreateProps> = (props) => (
<div class="am-split-button">
<TooltipKeybind
title={props.t("agentManager.shortcuts.advancedWorktree")}
keybind={props.bindings.newWorktree ?? ""}
>
<IconButton
icon="plus"
size="small"
variant="ghost"
label={props.t("agentManager.worktree.new")}
onClick={props.onNew}
disabled={!props.loaded}
/>
</TooltipKeybind>
<DropdownMenu gutter={4} placement="bottom-end">
<DropdownMenu.Trigger
class="am-split-arrow"
aria-label={props.t("agentManager.worktree.advancedOptions")}
disabled={!props.loaded}
>
<IconButton
icon="plus"
size="small"
variant="ghost"
label={props.t("agentManager.worktree.new")}
onClick={props.onNew}
disabled={!props.loaded}
/>
</TooltipKeybind>
<DropdownMenu gutter={4} placement="bottom-end">
<DropdownMenu.Trigger
class="am-split-arrow"
aria-label={props.t("agentManager.worktree.advancedOptions")}
disabled={!props.loaded}
>
<Icon name="chevron-down" size="small" />
</DropdownMenu.Trigger>
<DropdownMenu.Portal>
<DropdownMenu.Content class="am-split-menu">
<DropdownMenu.Item onSelect={props.onCreate}>
<span class="am-worktree-menu-gap" aria-hidden="true" />
<DropdownMenu.ItemLabel class="am-worktree-menu-label">
<span>{props.t("sidebar.session.newWorktree.from")}</span>
<span class="am-worktree-menu-branch">
<Icon name="branch" size="small" />
<strong>{props.branch}</strong>
</span>
</DropdownMenu.ItemLabel>
<span class="am-menu-shortcut">
{parseBindingTokens(props.bindings.quickWorktree ?? "").map((token) => (
<kbd class="am-menu-key">{token}</kbd>
))}
<Icon name="chevron-down" size="small" />
</DropdownMenu.Trigger>
<DropdownMenu.Portal>
<DropdownMenu.Content class="am-split-menu">
<DropdownMenu.Item onSelect={props.onCreate}>
<span class="am-worktree-menu-gap" aria-hidden="true" />
<DropdownMenu.ItemLabel class="am-worktree-menu-label">
<span>{props.t("sidebar.session.newWorktree.from")}</span>
<span class="am-worktree-menu-branch">
<Icon name="branch" size="small" />
<strong>{props.branch}</strong>
</span>
</DropdownMenu.Item>
<DropdownMenu.Separator />
<DropdownMenu.Item onSelect={props.onSection}>
<Icon name="plus" size="small" />
<DropdownMenu.ItemLabel>{props.t("agentManager.worktree.newSection")}</DropdownMenu.ItemLabel>
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Portal>
</DropdownMenu>
</div>
</DropdownMenu.ItemLabel>
<span class="am-menu-shortcut">
{parseBindingTokens(props.bindings.quickWorktree ?? "").map((token) => (
<kbd class="am-menu-key">{token}</kbd>
))}
</span>
</DropdownMenu.Item>
<DropdownMenu.Separator />
<DropdownMenu.Item onSelect={props.onSection}>
<Icon name="plus" size="small" />
<DropdownMenu.ItemLabel>{props.t("agentManager.worktree.newSection")}</DropdownMenu.ItemLabel>
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Portal>
</DropdownMenu>
</div>
)

export const ProjectActions: Component<WorktreeCreateProps & { onSettings: () => void }> = (props) => (
<div class="am-project-actions">
<WorktreeCreate {...props} />
<IconButton
icon="settings-gear"
size="small"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,19 @@

import type { Accessor, Component } from "solid-js"
import { Show } from "solid-js"
import { DropdownMenu } from "@kilocode/kilo-ui/dropdown-menu"
import { Icon } from "@kilocode/kilo-ui/icon"
import { IconButton } from "@kilocode/kilo-ui/icon-button"
import { Tooltip, TooltipKeybind } from "@kilocode/kilo-ui/tooltip"
import type { LanguageContextValue } from "../src/context/language"
import { parseBindingTokens } from "./keybind-tokens"
import { WorktreeCreate, type WorktreeCreateProps } from "./ProjectActions"
import { SidebarSearchMenu, type SidebarSearchMenuRef } from "./SidebarSearchMenu"
import type { SidebarSearchItem } from "./sidebar-search"
import { label } from "../src/utils/session-activity"

interface WorktreeSectionActionsProps {
interface WorktreeSectionActionsProps extends WorktreeCreateProps {
items: Accessor<SidebarSearchItem[]>
current: Accessor<SidebarSearchItem | undefined>
bindings: Record<string, string>
branch: string
git: boolean
loaded: boolean
t: LanguageContextValue["t"]
onRef: (ref: SidebarSearchMenuRef) => void
onSelect: (item: SidebarSearchItem) => void
onCreate: () => void
onNew: () => void
onSection: () => void
onShortcuts: () => void
onSettings: () => void
onHistory: () => void
Expand All @@ -47,54 +37,7 @@ export const WorktreeSectionActions: Component<WorktreeSectionActionsProps> = (p
onSelect={props.onSelect}
/>
<Show when={props.git}>
<div class="am-split-button">
<TooltipKeybind
title={props.t("agentManager.shortcuts.advancedWorktree")}
keybind={props.bindings.newWorktree ?? ""}
>
<IconButton
icon="plus"
size="small"
variant="ghost"
label={props.t("agentManager.worktree.new")}
onClick={props.onNew}
disabled={!props.loaded}
/>
</TooltipKeybind>
<DropdownMenu gutter={4} placement="bottom-end">
<DropdownMenu.Trigger
class="am-split-arrow"
aria-label={props.t("agentManager.worktree.advancedOptions")}
disabled={!props.loaded}
>
<Icon name="chevron-down" size="small" />
</DropdownMenu.Trigger>
<DropdownMenu.Portal>
<DropdownMenu.Content class="am-split-menu">
<DropdownMenu.Item onSelect={props.onCreate}>
<span class="am-worktree-menu-gap" aria-hidden="true" />
<DropdownMenu.ItemLabel class="am-worktree-menu-label">
<span>{props.t("sidebar.session.newWorktree.from")}</span>
<span class="am-worktree-menu-branch">
<Icon name="branch" size="small" />
<strong>{props.branch}</strong>
</span>
</DropdownMenu.ItemLabel>
<span class="am-menu-shortcut">
{parseBindingTokens(props.bindings.quickWorktree ?? "").map((token) => (
<kbd class="am-menu-key">{token}</kbd>
))}
</span>
</DropdownMenu.Item>
<DropdownMenu.Separator />
<DropdownMenu.Item onSelect={props.onSection}>
<Icon name="plus" size="small" />
<DropdownMenu.ItemLabel>{props.t("agentManager.worktree.newSection")}</DropdownMenu.ItemLabel>
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Portal>
</DropdownMenu>
</div>
<WorktreeCreate {...props} />
<TooltipKeybind
title={props.t("agentManager.shortcuts.title")}
keybind={props.bindings.showShortcuts ?? ""}
Expand Down
Loading
Loading