mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-08-28 19:11:03 +08:00
fix(vscode): polish sidebar worktree actions
This commit is contained in:
@@ -1543,6 +1543,16 @@ export class AgentManagerProvider implements Disposable {
|
||||
await this.onCreateWorktree(baseBranch, branchName)
|
||||
}
|
||||
|
||||
public async openAdvancedWorktree(): Promise<void> {
|
||||
this.openPanel()
|
||||
const panel = this.panel
|
||||
if (!panel) return
|
||||
await panel.waitForActive()
|
||||
await panel.waitForReady()
|
||||
await this.waitForStateReady("openAdvancedWorktree")
|
||||
queueMicrotask(() => this.postToWebview({ type: "action", action: "advancedWorktree" }))
|
||||
}
|
||||
|
||||
private handleSection(m: AgentManagerInMessage): boolean {
|
||||
return handleSection(this.state, m, () => this.pushState())
|
||||
}
|
||||
|
||||
@@ -56,6 +56,12 @@ export interface PanelContext {
|
||||
/** Send a message to the webview. */
|
||||
postMessage(msg: unknown): void
|
||||
|
||||
/** Resolve once the panel webview is ready to receive messages. */
|
||||
waitForReady(): Promise<void>
|
||||
|
||||
/** Resolve once the panel is the active editor tab. */
|
||||
waitForActive(): Promise<void>
|
||||
|
||||
/** Reveal the panel. */
|
||||
reveal(preserveFocus?: boolean): void
|
||||
|
||||
|
||||
@@ -110,6 +110,19 @@ export class VscodeHost implements Host {
|
||||
postMessage(msg) {
|
||||
void panel.webview.postMessage(msg)
|
||||
},
|
||||
waitForReady() {
|
||||
return provider.waitForReady()
|
||||
},
|
||||
waitForActive() {
|
||||
if (panel.active) return Promise.resolve()
|
||||
return new Promise((resolve) => {
|
||||
const sub = panel.onDidChangeViewState((e) => {
|
||||
if (!e.webviewPanel.active) return
|
||||
sub.dispose()
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
},
|
||||
reveal(preserveFocus) {
|
||||
panel.reveal(vscode.ViewColumn.One, preserveFocus ?? false)
|
||||
},
|
||||
|
||||
@@ -338,9 +338,9 @@ export function activate(context: vscode.ExtensionContext) {
|
||||
vscode.commands.registerCommand("kilo-code.new.agentManager.closeWorktree", () => {
|
||||
agentManagerProvider.postMessage({ type: "action", action: "closeWorktree" })
|
||||
}),
|
||||
vscode.commands.registerCommand("kilo-code.new.agentManager.advancedWorktree", () => {
|
||||
agentManagerProvider.postMessage({ type: "action", action: "advancedWorktree" })
|
||||
}),
|
||||
vscode.commands.registerCommand("kilo-code.new.agentManager.advancedWorktree", () =>
|
||||
agentManagerProvider.openAdvancedWorktree(),
|
||||
),
|
||||
...Array.from({ length: 9 }, (_, i) =>
|
||||
vscode.commands.registerCommand(`kilo-code.new.agentManager.jumpTo${i + 1}`, () => {
|
||||
agentManagerProvider.postMessage({ type: "action", action: `jumpTo${i + 1}` })
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
// Agent Manager root component
|
||||
|
||||
/** @jsxImportSource solid-js */
|
||||
|
||||
import {
|
||||
Component,
|
||||
For,
|
||||
Show,
|
||||
createSignal,
|
||||
@@ -11,6 +12,7 @@ import {
|
||||
onMount,
|
||||
onCleanup,
|
||||
type Accessor,
|
||||
type Component,
|
||||
} from "solid-js"
|
||||
import type {
|
||||
ExtensionMessage,
|
||||
@@ -2285,7 +2287,7 @@ const AgentManagerContent: Component = () => {
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item onSelect={showAdvancedWorktreeDialog}>
|
||||
<Icon name="settings-gear" size="small" />
|
||||
<DropdownMenu.ItemLabel>{t("agentManager.dialog.advanced")}</DropdownMenu.ItemLabel>
|
||||
<DropdownMenu.ItemLabel>{t("agentManager.dialog.configureWorktree")}</DropdownMenu.ItemLabel>
|
||||
<span class="am-menu-shortcut">
|
||||
{parseBindingTokens(kb().advancedWorktree ?? "").map((token) => (
|
||||
<kbd class="am-menu-key">{token}</kbd>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// New Worktree dialog — prompt, versions, model, mode, import tab
|
||||
|
||||
import { Component, For, Show, createSignal, createEffect, createMemo, onMount, onCleanup } from "solid-js"
|
||||
/** @jsxImportSource solid-js */
|
||||
|
||||
import { type Component, For, Show, createSignal, createEffect, createMemo, onMount, onCleanup } from "solid-js"
|
||||
import type { AgentManagerBranchesMessage, AgentManagerImportResultMessage, BranchInfo } from "../src/types/messages"
|
||||
import { Dialog } from "@kilocode/kilo-ui/dialog"
|
||||
import { showToast } from "@kilocode/kilo-ui/toast"
|
||||
@@ -171,6 +173,17 @@ export const NewWorktreeDialog: Component<{ onClose: () => void; defaultBaseBran
|
||||
vscode.postMessage({ type: "agentManager.requestBranches" })
|
||||
// Resize textarea if restoring a cached prompt
|
||||
if (prompt()) adjustHeight()
|
||||
const focus = () => {
|
||||
textareaRef?.focus({ preventScroll: true })
|
||||
const end = textareaRef?.value.length ?? 0
|
||||
textareaRef?.setSelectionRange(end, end)
|
||||
}
|
||||
requestAnimationFrame(() => {
|
||||
focus()
|
||||
requestAnimationFrame(focus)
|
||||
setTimeout(focus, 0)
|
||||
setTimeout(focus, 50)
|
||||
})
|
||||
})
|
||||
|
||||
const effectiveBaseBranch = () => baseBranch() ?? defaultBranch()
|
||||
|
||||
@@ -77,7 +77,7 @@ export const dict = {
|
||||
"agentManager.shortcuts.nextItem": "Next item",
|
||||
"agentManager.shortcuts.newWorktree": "New worktree",
|
||||
"agentManager.shortcuts.openWorktree": "Open worktree",
|
||||
"agentManager.shortcuts.advancedWorktree": "Advanced worktree",
|
||||
"agentManager.shortcuts.advancedWorktree": "Configure new worktree",
|
||||
"agentManager.shortcuts.deleteWorktree": "Delete worktree",
|
||||
"agentManager.shortcuts.previousTab": "Previous tab",
|
||||
"agentManager.shortcuts.nextTab": "Next tab",
|
||||
@@ -122,6 +122,7 @@ export const dict = {
|
||||
"agentManager.dialog.createWorktree": "Create Worktree",
|
||||
"agentManager.dialog.removeImage": "Remove image",
|
||||
"agentManager.dialog.advanced": "Advanced...",
|
||||
"agentManager.dialog.configureWorktree": "Configure New Worktree...",
|
||||
|
||||
"agentManager.diff.toggle": "Toggle diff",
|
||||
"agentManager.diff.openFile": "Open file",
|
||||
|
||||
@@ -54,6 +54,7 @@ export const ChatView: Component<ChatViewProps> = (props) => {
|
||||
const [transferring, setTransferring] = createSignal(false)
|
||||
const [transferDetail, setTransferDetail] = createSignal("")
|
||||
const [repoBranch, setRepoBranch] = createSignal<string>()
|
||||
let worktreeRef: HTMLDivElement | undefined
|
||||
|
||||
// Permissions and questions scoped to this session's family (self + subagents).
|
||||
// Each ChatView only sees its own session tree — no cross-session leakage.
|
||||
@@ -146,7 +147,10 @@ export const ChatView: Component<ChatViewProps> = (props) => {
|
||||
|
||||
const openAgentManager = () => vscode.postMessage({ type: "openAgentManager" })
|
||||
|
||||
const openChanges = () => vscode.postMessage({ type: "openChanges" })
|
||||
|
||||
const moveToWorktree = () => {
|
||||
if (transferring()) return
|
||||
const sid = id()
|
||||
if (!sid) return
|
||||
setTransferring(true)
|
||||
@@ -159,6 +163,19 @@ export const ChatView: Component<ChatViewProps> = (props) => {
|
||||
|
||||
const advancedTooltip = "Open the Agent Manager worktree dialog to configure a new worktree before creating it."
|
||||
|
||||
const moveTooltip = () => {
|
||||
const stats = session.worktreeStats()
|
||||
if (!stats?.files)
|
||||
return "Move this conversation and your current local changes into a dedicated worktree for isolated follow-up work."
|
||||
return `Move this conversation and ${stats.files} changed file${stats.files > 1 ? "s" : ""} into a dedicated worktree for isolated follow-up work.`
|
||||
}
|
||||
|
||||
const changesTooltip = () => {
|
||||
const stats = session.worktreeStats()
|
||||
if (!stats?.files) return "Open the changes view to inspect the current working tree."
|
||||
return `${stats.files} file${stats.files > 1 ? "s" : ""} changed · +${stats.additions} -${stats.deletions}. Open the changes view.`
|
||||
}
|
||||
|
||||
const showAdvancedWorktree = () => vscode.postMessage({ type: "openAdvancedWorktree" })
|
||||
|
||||
createEffect(() => {
|
||||
@@ -169,13 +186,27 @@ export const ChatView: Component<ChatViewProps> = (props) => {
|
||||
const renderActions = (hasChat: boolean) => (
|
||||
<div class="new-task-button-wrapper" classList={{ "new-task-button-wrapper--empty": !hasChat }}>
|
||||
<div class="session-actions-row">
|
||||
<Tooltip value="Start a fresh conversation while keeping the current session intact." placement="top">
|
||||
<Button variant="secondary" size="small" onClick={startSession} aria-label="New Session">
|
||||
<Tooltip
|
||||
value={
|
||||
hasChat
|
||||
? "Start a fresh conversation while keeping the current session intact."
|
||||
: "This session is already new. Start chatting or create a worktree instead."
|
||||
}
|
||||
placement="top"
|
||||
>
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="small"
|
||||
class="session-new-button"
|
||||
onClick={startSession}
|
||||
disabled={!hasChat}
|
||||
aria-label="New Session"
|
||||
>
|
||||
New Session
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Show when={isSidebar() && server.gitInstalled()}>
|
||||
<div class="session-worktree-split">
|
||||
<div class="session-worktree-split" ref={worktreeRef}>
|
||||
<Tooltip value={worktreeTooltip} placement="top">
|
||||
<Button
|
||||
variant="secondary"
|
||||
@@ -187,7 +218,7 @@ export const ChatView: Component<ChatViewProps> = (props) => {
|
||||
New Worktree
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<DropdownMenu gutter={4} placement="top-end">
|
||||
<DropdownMenu gutter={4} placement="top-start" getAnchorRect={() => worktreeRef?.getBoundingClientRect()}>
|
||||
<Tooltip value={advancedTooltip} placement="top">
|
||||
<DropdownMenu.Trigger class="session-worktree-split-arrow" aria-label="Advanced worktree options">
|
||||
<Icon name="chevron-down" size="small" />
|
||||
@@ -207,7 +238,9 @@ export const ChatView: Component<ChatViewProps> = (props) => {
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item onSelect={showAdvancedWorktree}>
|
||||
<Icon name="settings-gear" size="small" />
|
||||
<DropdownMenu.ItemLabel>Configure New Worktree...</DropdownMenu.ItemLabel>
|
||||
<DropdownMenu.ItemLabel>
|
||||
{language.t("agentManager.dialog.configureWorktree")}
|
||||
</DropdownMenu.ItemLabel>
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Portal>
|
||||
@@ -216,42 +249,42 @@ export const ChatView: Component<ChatViewProps> = (props) => {
|
||||
</Show>
|
||||
<Show when={!hasChat}></Show>
|
||||
<Show when={hasChat && canContinueInWorktree() && server.gitInstalled()}>
|
||||
<Tooltip
|
||||
value={
|
||||
session.worktreeStats()?.files
|
||||
? `Move this conversation and ${session.worktreeStats()!.files} changed file${session.worktreeStats()!.files > 1 ? "s" : ""} into a dedicated worktree for isolated follow-up work.`
|
||||
: "Move this conversation and your current local changes into a dedicated worktree for isolated follow-up work."
|
||||
}
|
||||
placement="top"
|
||||
>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="small"
|
||||
class="session-move-button"
|
||||
classList={{
|
||||
"session-move-button--empty": !session.worktreeStats()?.files,
|
||||
"session-move-button--has-changes": !!session.worktreeStats()?.files,
|
||||
}}
|
||||
disabled={transferring()}
|
||||
onClick={moveToWorktree}
|
||||
aria-label="Move to Worktree"
|
||||
>
|
||||
<Show when={transferring()} fallback={<Icon name="branch" size="small" />}>
|
||||
<Spinner class="chat-spinner-small" />
|
||||
</Show>
|
||||
<span class="session-move-label">{transferring() ? transferDetail() : "Move to Worktree"}</span>
|
||||
<Show when={!transferring() && session.worktreeStats()?.files}>
|
||||
<span class="session-move-tail" aria-hidden="true">
|
||||
<span class="session-move-divider" />
|
||||
<span class="session-move-stats">
|
||||
<Icon name="layers" size="small" />
|
||||
<span class="session-diff-add">+{session.worktreeStats()!.additions}</span>
|
||||
<span class="session-diff-del">-{session.worktreeStats()!.deletions}</span>
|
||||
</span>
|
||||
</span>
|
||||
</Show>
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<div class="session-move-group">
|
||||
<Tooltip value={moveTooltip()} placement="top">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="small"
|
||||
class="session-move-action"
|
||||
aria-disabled={transferring()}
|
||||
onClick={moveToWorktree}
|
||||
aria-label="Move to Worktree"
|
||||
>
|
||||
<Show when={transferring()} fallback={<Icon name="branch" size="small" />}>
|
||||
<Spinner class="chat-spinner-small" />
|
||||
</Show>
|
||||
<span class="session-move-label">{transferring() ? transferDetail() : "Move to Worktree"}</span>
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Tooltip value={changesTooltip()} placement="top">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="small"
|
||||
class="session-move-changes"
|
||||
classList={{
|
||||
"session-move-changes--empty": !session.worktreeStats()?.files,
|
||||
"session-move-changes--has-changes": !!session.worktreeStats()?.files,
|
||||
}}
|
||||
onClick={openChanges}
|
||||
aria-label={language.t("command.session.show.changes")}
|
||||
>
|
||||
<Icon name="layers" size="small" />
|
||||
<Show when={session.worktreeStats()?.files}>
|
||||
<span class="session-diff-add">+{session.worktreeStats()!.additions}</span>
|
||||
<span class="session-diff-del">-{session.worktreeStats()!.deletions}</span>
|
||||
</Show>
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</Show>
|
||||
<div class="session-agent-manager-slot">
|
||||
<Tooltip
|
||||
|
||||
@@ -945,7 +945,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
variant="ghost"
|
||||
size="small"
|
||||
onClick={() => void handleSend()}
|
||||
disabled={!canSend()}
|
||||
aria-disabled={!canSend()}
|
||||
aria-label={language.t("prompt.action.send")}
|
||||
>
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
|
||||
|
||||
@@ -63,6 +63,10 @@
|
||||
color: var(--vscode-foreground);
|
||||
}
|
||||
|
||||
.session-actions-row > [data-component="tooltip-trigger"] > [data-component="button"].session-new-button:disabled {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.session-worktree-split {
|
||||
display: flex;
|
||||
flex: 0 0 auto;
|
||||
@@ -177,22 +181,57 @@
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Diff wrapper also stretches evenly (same flex as buttons) */
|
||||
.session-move-button {
|
||||
.session-move-group {
|
||||
display: flex;
|
||||
flex: 0 0 auto;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
border-radius: var(--session-action-radius);
|
||||
color: var(--vscode-descriptionForeground);
|
||||
transition:
|
||||
background-color 150ms ease,
|
||||
color 150ms ease;
|
||||
}
|
||||
|
||||
.session-move-group > [data-component="tooltip-trigger"] {
|
||||
display: flex;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.session-move-group:hover,
|
||||
.session-move-group:has(.session-move-action:focus-visible),
|
||||
.session-move-group:has(.session-move-changes:focus-visible) {
|
||||
color: var(--vscode-foreground);
|
||||
}
|
||||
|
||||
.session-move-group:hover .session-move-action,
|
||||
.session-move-group:hover .session-move-changes,
|
||||
.session-move-group:has(.session-move-action:focus-visible) .session-move-action,
|
||||
.session-move-group:has(.session-move-action:focus-visible) .session-move-changes,
|
||||
.session-move-group:has(.session-move-changes:focus-visible) .session-move-action,
|
||||
.session-move-group:has(.session-move-changes:focus-visible) .session-move-changes {
|
||||
background: var(--surface-base-hover);
|
||||
}
|
||||
|
||||
.session-move-action {
|
||||
display: flex;
|
||||
flex: 0 0 auto;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 5px;
|
||||
border-radius: var(--session-action-radius);
|
||||
color: var(--vscode-descriptionForeground);
|
||||
border-radius: var(--session-action-radius) 0 0 var(--session-action-radius);
|
||||
color: inherit;
|
||||
padding: 4px 6px !important;
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.session-move-button:hover:not(:disabled),
|
||||
.session-move-button:focus-visible:not(:disabled),
|
||||
.session-move-button:active:not(:disabled) {
|
||||
color: var(--vscode-foreground);
|
||||
.session-move-action:hover:not(:disabled),
|
||||
.session-move-action:focus-visible:not(:disabled),
|
||||
.session-move-action:active:not(:disabled) {
|
||||
color: inherit;
|
||||
background: inherit;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.session-move-label {
|
||||
@@ -202,31 +241,41 @@
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.session-move-tail {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
flex-shrink: 0;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
.session-move-divider {
|
||||
width: 1px;
|
||||
height: 14px;
|
||||
background: color-mix(in srgb, currentColor 25%, transparent);
|
||||
.session-move-action [data-component="icon"],
|
||||
.session-move-changes [data-component="icon"] {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.session-move-stats {
|
||||
.session-move-changes {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 3px;
|
||||
flex-shrink: 0;
|
||||
flex: 0 0 auto;
|
||||
border-radius: 0 var(--session-action-radius) var(--session-action-radius) 0;
|
||||
color: inherit;
|
||||
padding: 4px 6px !important;
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.session-move-changes:hover:not(:disabled),
|
||||
.session-move-changes:focus-visible:not(:disabled),
|
||||
.session-move-changes:active:not(:disabled) {
|
||||
color: inherit;
|
||||
background: inherit;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.session-move-changes {
|
||||
font-family: var(--font-family-sans);
|
||||
font-size: 12px;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.session-move-changes--empty {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.session-agent-manager-slot {
|
||||
flex: 0 0 auto;
|
||||
margin-left: auto;
|
||||
@@ -254,10 +303,6 @@
|
||||
color: var(--vscode-foreground);
|
||||
}
|
||||
|
||||
.session-move-stats [data-component="icon"] {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
/* Diff stats badge styles retained for the inline move button stats */
|
||||
.session-diff-badge {
|
||||
display: flex;
|
||||
@@ -315,21 +360,16 @@
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.session-move-divider,
|
||||
.session-diff-add,
|
||||
.session-diff-del {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.session-move-tail {
|
||||
padding-left: 4px;
|
||||
}
|
||||
|
||||
.session-move-stats {
|
||||
.session-move-changes {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.session-move-button--has-changes .session-move-stats [data-component="icon"]::after {
|
||||
.session-move-changes--has-changes [data-component="icon"]::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: -2px;
|
||||
|
||||
Reference in New Issue
Block a user