mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-08-28 11:05:31 +08:00
feat(agent-manager): add advanced worktree creation with base branch and custom branch name (#6201)
* feat(agent-manager): add advanced worktree creation with base branch and custom branch name * fix: remove unused CSS classes and make SendInitialMessage.text optional - Remove am-branch-group-label and am-branch-empty CSS classes (unused import tab leftovers) - Make AgentManagerSendInitialMessage.text optional to match no-prompt code path
This commit is contained in:
@@ -134,6 +134,11 @@
|
||||
"title": "Agent Manager: Close Worktree",
|
||||
"category": "Kilo Code"
|
||||
},
|
||||
{
|
||||
"command": "kilo-code.new.agentManager.advancedWorktree",
|
||||
"title": "Agent Manager: Advanced New Worktree",
|
||||
"category": "Kilo Code"
|
||||
},
|
||||
{
|
||||
"command": "kilo-code.new.generateCommitMessage",
|
||||
"title": "Generate Commit Message",
|
||||
@@ -362,6 +367,12 @@
|
||||
"key": "ctrl+shift+w",
|
||||
"mac": "cmd+shift+w",
|
||||
"when": "activeWebviewPanelId == 'kilo-code.new.AgentManagerPanel'"
|
||||
},
|
||||
{
|
||||
"command": "kilo-code.new.agentManager.advancedWorktree",
|
||||
"key": "ctrl+shift+n",
|
||||
"mac": "cmd+shift+n",
|
||||
"when": "activeWebviewPanelId == 'kilo-code.new.AgentManagerPanel'"
|
||||
}
|
||||
],
|
||||
"configuration": {
|
||||
|
||||
@@ -134,7 +134,9 @@ export class AgentManagerProvider implements vscode.Disposable {
|
||||
private async onMessage(msg: Record<string, unknown>): Promise<Record<string, unknown> | null> {
|
||||
const type = msg.type as string
|
||||
|
||||
if (type === "agentManager.createWorktree") return this.onCreateWorktree()
|
||||
if (type === "agentManager.createWorktree") {
|
||||
return this.onCreateWorktree(msg.baseBranch as string | undefined, msg.branchName as string | undefined)
|
||||
}
|
||||
if (type === "agentManager.deleteWorktree" && typeof msg.worktreeId === "string")
|
||||
return this.onDeleteWorktree(msg.worktreeId)
|
||||
if (type === "agentManager.promoteSession" && typeof msg.sessionId === "string")
|
||||
@@ -186,6 +188,10 @@ export class AgentManagerProvider implements vscode.Disposable {
|
||||
})
|
||||
return null
|
||||
}
|
||||
if (type === "agentManager.requestBranches") {
|
||||
void this.onRequestBranches()
|
||||
return null
|
||||
}
|
||||
if (type === "agentManager.setTabOrder" && typeof msg.key === "string" && Array.isArray(msg.order)) {
|
||||
this.state?.setTabOrder(msg.key as string, msg.order as string[])
|
||||
return null
|
||||
@@ -226,11 +232,14 @@ export class AgentManagerProvider implements vscode.Disposable {
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/** Create a git worktree on disk and register it in state. Returns null on failure. */
|
||||
private async createWorktreeOnDisk(
|
||||
groupId?: string,
|
||||
name?: string,
|
||||
label?: string,
|
||||
): Promise<{
|
||||
private async createWorktreeOnDisk(opts?: {
|
||||
groupId?: string
|
||||
baseBranch?: string
|
||||
branchName?: string
|
||||
existingBranch?: string
|
||||
name?: string
|
||||
label?: string
|
||||
}): Promise<{
|
||||
worktree: ReturnType<WorktreeStateManager["addWorktree"]>
|
||||
result: CreateWorktreeResult
|
||||
} | null> {
|
||||
@@ -249,7 +258,12 @@ export class AgentManagerProvider implements vscode.Disposable {
|
||||
|
||||
let result: CreateWorktreeResult
|
||||
try {
|
||||
result = await manager.createWorktree({ prompt: name || "kilo" })
|
||||
result = await manager.createWorktree({
|
||||
prompt: opts?.name || "kilo",
|
||||
baseBranch: opts?.baseBranch,
|
||||
branchName: opts?.branchName,
|
||||
existingBranch: opts?.existingBranch,
|
||||
})
|
||||
} catch (error) {
|
||||
const msg = error instanceof Error ? error.message : String(error)
|
||||
this.postToWebview({
|
||||
@@ -269,8 +283,8 @@ export class AgentManagerProvider implements vscode.Disposable {
|
||||
branch: result.branch,
|
||||
path: result.path,
|
||||
parentBranch: result.parentBranch,
|
||||
groupId,
|
||||
label,
|
||||
groupId: opts?.groupId,
|
||||
label: opts?.label,
|
||||
})
|
||||
|
||||
// Push state immediately so the sidebar shows the new worktree with a loading indicator
|
||||
@@ -363,8 +377,8 @@ export class AgentManagerProvider implements vscode.Disposable {
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/** Create a new worktree with an auto-created first session. */
|
||||
private async onCreateWorktree(): Promise<null> {
|
||||
const created = await this.createWorktreeOnDisk()
|
||||
private async onCreateWorktree(baseBranch?: string, branchName?: string): Promise<null> {
|
||||
const created = await this.createWorktreeOnDisk({ baseBranch, branchName })
|
||||
if (!created) return null
|
||||
|
||||
// Run setup script for new worktree (blocks until complete, shows in overlay)
|
||||
@@ -423,7 +437,7 @@ export class AgentManagerProvider implements vscode.Disposable {
|
||||
|
||||
/** Promote a session: create a worktree and move the session into it. */
|
||||
private async onPromoteSession(sessionId: string): Promise<null> {
|
||||
const created = await this.createWorktreeOnDisk()
|
||||
const created = await this.createWorktreeOnDisk({})
|
||||
if (!created) return null
|
||||
|
||||
// Run setup script for new worktree (blocks until complete, shows in overlay)
|
||||
@@ -509,14 +523,28 @@ export class AgentManagerProvider implements vscode.Disposable {
|
||||
return null
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Branch discovery
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
private async onRequestBranches(): Promise<void> {
|
||||
const manager = this.getWorktreeManager()
|
||||
if (!manager) return
|
||||
try {
|
||||
const data = await manager.listBranches()
|
||||
this.postToWebview({ type: "agentManager.branches", branches: data.branches, defaultBranch: data.defaultBranch })
|
||||
} catch (error) {
|
||||
this.log(`Failed to list branches: ${error}`)
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Multi-version worktree creation
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/** Create N worktree sessions for the same prompt (multi-version mode). */
|
||||
private async onCreateMultiVersion(msg: Record<string, unknown>): Promise<null> {
|
||||
const text = msg.text as string
|
||||
if (!text) return null
|
||||
const text = (msg.text as string | undefined)?.trim() || undefined
|
||||
|
||||
const versions = Math.min(Math.max(Number(msg.versions) || 1, 1), 4)
|
||||
const worktreeName = (msg.name as string | undefined)?.trim() || undefined
|
||||
@@ -524,12 +552,14 @@ export class AgentManagerProvider implements vscode.Disposable {
|
||||
const modelID = msg.modelID as string | undefined
|
||||
const agent = msg.agent as string | undefined
|
||||
const files = msg.files as Array<{ mime: string; url: string }> | undefined
|
||||
const baseBranch = msg.baseBranch as string | undefined
|
||||
const branchName = (msg.branchName as string | undefined)?.trim() || undefined
|
||||
|
||||
// Generate a shared group ID for multi-version worktrees
|
||||
const groupId = versions > 1 ? `grp-${Date.now()}` : undefined
|
||||
|
||||
this.log(
|
||||
`Creating ${versions} multi-version worktrees for: ${text.slice(0, 60)}${groupId ? ` (group=${groupId})` : ""}`,
|
||||
`Creating ${versions} worktrees${text ? ` for: ${text.slice(0, 60)}` : ""}${groupId ? ` (group=${groupId})` : ""}`,
|
||||
)
|
||||
|
||||
// Notify webview that multi-version creation has started
|
||||
@@ -553,8 +583,14 @@ export class AgentManagerProvider implements vscode.Disposable {
|
||||
for (let i = 0; i < versions; i++) {
|
||||
this.log(`Creating worktree ${i + 1}/${versions}`)
|
||||
|
||||
const version = versionedName(worktreeName, i, versions)
|
||||
const wt = await this.createWorktreeOnDisk(groupId, version.branch, version.label)
|
||||
const version = versionedName(branchName || worktreeName, i, versions)
|
||||
const wt = await this.createWorktreeOnDisk({
|
||||
groupId,
|
||||
baseBranch,
|
||||
branchName: version.branch,
|
||||
name: version.branch,
|
||||
label: version.label,
|
||||
})
|
||||
if (!wt) {
|
||||
this.log(`Failed to create worktree for version ${i + 1}`)
|
||||
continue
|
||||
@@ -607,29 +643,31 @@ export class AgentManagerProvider implements vscode.Disposable {
|
||||
})
|
||||
}
|
||||
|
||||
// Phase 2: Send the initial prompt to all sessions via the KiloProvider's
|
||||
// message handling (same path as typing in the chat). This ensures SSE
|
||||
// subscriptions and session tracking are properly set up before the message
|
||||
// is sent. We route each message through the webview→KiloProvider pipeline.
|
||||
// Phase 2: Send the initial prompt to all sessions, or clear busy state if no text
|
||||
for (let i = 0; i < created.length; i++) {
|
||||
const entry = created[i]!
|
||||
this.log(`Sending initial message to version ${i + 1} (session=${entry.sessionId})`)
|
||||
|
||||
// Tell the webview to send the message through the normal session flow
|
||||
this.postToWebview({
|
||||
type: "agentManager.sendInitialMessage",
|
||||
sessionId: entry.sessionId,
|
||||
worktreeId: entry.worktreeId,
|
||||
text,
|
||||
providerID,
|
||||
modelID,
|
||||
agent,
|
||||
files,
|
||||
})
|
||||
|
||||
// Small delay between sends to avoid overwhelming the backend
|
||||
if (i < created.length - 1) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 300))
|
||||
if (text) {
|
||||
this.log(`Sending initial message to version ${i + 1} (session=${entry.sessionId})`)
|
||||
this.postToWebview({
|
||||
type: "agentManager.sendInitialMessage",
|
||||
sessionId: entry.sessionId,
|
||||
worktreeId: entry.worktreeId,
|
||||
text,
|
||||
providerID,
|
||||
modelID,
|
||||
agent,
|
||||
files,
|
||||
})
|
||||
if (i < created.length - 1) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 300))
|
||||
}
|
||||
} else {
|
||||
// No prompt — just clear the busy state for this worktree
|
||||
this.postToWebview({
|
||||
type: "agentManager.sendInitialMessage",
|
||||
sessionId: entry.sessionId,
|
||||
worktreeId: entry.worktreeId,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,14 @@ export interface CreateWorktreeResult {
|
||||
parentBranch: string
|
||||
}
|
||||
|
||||
export interface BranchInfo {
|
||||
name: string
|
||||
isLocal: boolean
|
||||
isRemote: boolean
|
||||
lastCommitDate: number
|
||||
isDefault: boolean
|
||||
}
|
||||
|
||||
const KILOCODE_DIR = ".kilocode"
|
||||
const SESSION_ID_FILE = "session-id"
|
||||
const METADATA_FILE = "metadata.json"
|
||||
@@ -44,7 +52,12 @@ export class WorktreeManager {
|
||||
this.log = log
|
||||
}
|
||||
|
||||
async createWorktree(params: { prompt?: string; existingBranch?: string }): Promise<CreateWorktreeResult> {
|
||||
async createWorktree(params: {
|
||||
prompt?: string
|
||||
existingBranch?: string
|
||||
baseBranch?: string
|
||||
branchName?: string
|
||||
}): Promise<CreateWorktreeResult> {
|
||||
const repo = await this.git.checkIsRepo()
|
||||
if (!repo)
|
||||
throw new Error(
|
||||
@@ -54,15 +67,29 @@ export class WorktreeManager {
|
||||
await this.ensureDir()
|
||||
await this.ensureGitExclude()
|
||||
|
||||
const parent = await this.currentBranch()
|
||||
let branch = params.existingBranch ?? generateBranchName(params.prompt || "agent-task")
|
||||
const parent = params.baseBranch || (await this.currentBranch())
|
||||
|
||||
// Validate baseBranch exists if explicitly provided
|
||||
if (params.baseBranch) {
|
||||
const exists = await this.branchExists(params.baseBranch)
|
||||
if (!exists) throw new Error(`Base branch "${params.baseBranch}" does not exist`)
|
||||
// Check if the base branch is a remote-only branch and fetch it
|
||||
const branches = await this.git.branch()
|
||||
if (!branches.all.includes(params.baseBranch) && branches.all.includes(`remotes/origin/${params.baseBranch}`)) {
|
||||
await this.git.fetch("origin", params.baseBranch)
|
||||
}
|
||||
}
|
||||
|
||||
let branch = params.existingBranch ?? params.branchName ?? generateBranchName(params.prompt || "agent-task")
|
||||
|
||||
if (params.existingBranch) {
|
||||
const exists = await this.branchExists(branch)
|
||||
if (!exists) throw new Error(`Branch "${branch}" does not exist`)
|
||||
}
|
||||
|
||||
let worktreePath = path.join(this.dir, branch)
|
||||
// Sanitize directory name — replace slashes with dashes for filesystem safety
|
||||
const dirName = branch.replace(/\//g, "-")
|
||||
let worktreePath = path.join(this.dir, dirName)
|
||||
|
||||
if (fs.existsSync(worktreePath)) {
|
||||
this.log(`Worktree directory exists, cleaning up before re-creation: ${worktreePath}`)
|
||||
@@ -72,20 +99,32 @@ export class WorktreeManager {
|
||||
try {
|
||||
const args = params.existingBranch
|
||||
? ["worktree", "add", worktreePath, branch]
|
||||
: ["worktree", "add", "-b", branch, worktreePath]
|
||||
: params.baseBranch
|
||||
? ["worktree", "add", "-b", branch, worktreePath, params.baseBranch]
|
||||
: ["worktree", "add", "-b", branch, worktreePath]
|
||||
await this.git.raw(args)
|
||||
} catch (error) {
|
||||
const msg = error instanceof Error ? error.message : String(error)
|
||||
if (msg.includes("already checked out")) {
|
||||
// Extract worktree path from error like "fatal: 'branch' is already checked out at '/path'"
|
||||
const match = msg.match(/already checked out at '([^']+)'/)
|
||||
const loc = match ? match[1] : "another worktree"
|
||||
throw new Error(`Branch "${branch}" is already checked out in worktree at: ${loc}`)
|
||||
}
|
||||
if (!msg.includes("already exists") || params.existingBranch) {
|
||||
throw new Error(`Failed to create worktree: ${msg}`)
|
||||
}
|
||||
// Branch name collision -- retry with unique suffix
|
||||
branch = `${branch}-${Date.now()}`
|
||||
worktreePath = path.join(this.dir, branch)
|
||||
await this.git.raw(["worktree", "add", "-b", branch, worktreePath])
|
||||
const retryDir = branch.replace(/\//g, "-")
|
||||
worktreePath = path.join(this.dir, retryDir)
|
||||
const retryArgs = params.baseBranch
|
||||
? ["worktree", "add", "-b", branch, worktreePath, params.baseBranch]
|
||||
: ["worktree", "add", "-b", branch, worktreePath]
|
||||
await this.git.raw(retryArgs)
|
||||
}
|
||||
|
||||
this.log(`Created worktree: ${worktreePath} (branch: ${branch})`)
|
||||
this.log(`Created worktree: ${worktreePath} (branch: ${branch}, base: ${parent})`)
|
||||
return { branch, path: worktreePath, parentBranch: parent }
|
||||
}
|
||||
|
||||
@@ -176,6 +215,73 @@ export class WorktreeManager {
|
||||
return undefined
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Branch & worktree discovery
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async listBranches(): Promise<{ branches: BranchInfo[]; defaultBranch: string }> {
|
||||
const defBranch = await this.defaultBranch()
|
||||
|
||||
// Get local branches with commit dates
|
||||
const localRaw = await this.git
|
||||
.raw(["for-each-ref", "--sort=-committerdate", "--format=%(refname:short)\t%(committerdate:unix)", "refs/heads/"])
|
||||
.then((out) => out.trim())
|
||||
.catch(() => "")
|
||||
|
||||
// Get remote branches with commit dates
|
||||
const remoteRaw = await this.git
|
||||
.raw([
|
||||
"for-each-ref",
|
||||
"--sort=-committerdate",
|
||||
"--format=%(refname:short)\t%(committerdate:unix)",
|
||||
"refs/remotes/origin/",
|
||||
])
|
||||
.then((out) => out.trim())
|
||||
.catch(() => "")
|
||||
|
||||
const map = new Map<string, BranchInfo>()
|
||||
|
||||
for (const line of localRaw.split("\n").filter(Boolean)) {
|
||||
const [name, dateStr] = line.split("\t")
|
||||
if (!name) continue
|
||||
map.set(name, {
|
||||
name,
|
||||
isLocal: true,
|
||||
isRemote: false,
|
||||
lastCommitDate: parseInt(dateStr || "0", 10),
|
||||
isDefault: name === defBranch,
|
||||
})
|
||||
}
|
||||
|
||||
for (const line of remoteRaw.split("\n").filter(Boolean)) {
|
||||
const [ref, dateStr] = line.split("\t")
|
||||
if (!ref) continue
|
||||
const name = ref.replace(/^origin\//, "")
|
||||
if (name === "HEAD") continue
|
||||
const existing = map.get(name)
|
||||
if (existing) {
|
||||
existing.isRemote = true
|
||||
} else {
|
||||
map.set(name, {
|
||||
name,
|
||||
isLocal: false,
|
||||
isRemote: true,
|
||||
lastCommitDate: parseInt(dateStr || "0", 10),
|
||||
isDefault: name === defBranch,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Sort: default first, then by lastCommitDate descending
|
||||
const branches = [...map.values()].sort((a, b) => {
|
||||
if (a.isDefault && !b.isDefault) return -1
|
||||
if (!a.isDefault && b.isDefault) return 1
|
||||
return b.lastCommitDate - a.lastCommitDate
|
||||
})
|
||||
|
||||
return { branches, defaultBranch: defBranch }
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Git exclude management
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -279,7 +385,7 @@ export class WorktreeManager {
|
||||
return (await this.git.revparse(["--abbrev-ref", "HEAD"])).trim()
|
||||
}
|
||||
|
||||
private async branchExists(name: string): Promise<boolean> {
|
||||
async branchExists(name: string): Promise<boolean> {
|
||||
try {
|
||||
const branches = await this.git.branch()
|
||||
return branches.all.includes(name) || branches.all.includes(`remotes/origin/${name}`)
|
||||
@@ -288,7 +394,7 @@ export class WorktreeManager {
|
||||
}
|
||||
}
|
||||
|
||||
private async defaultBranch(): Promise<string> {
|
||||
async defaultBranch(): Promise<string> {
|
||||
try {
|
||||
const head = await this.git.raw(["symbolic-ref", "refs/remotes/origin/HEAD"])
|
||||
const match = head.trim().match(/refs\/remotes\/origin\/(.+)$/)
|
||||
|
||||
@@ -100,6 +100,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" })
|
||||
}),
|
||||
)
|
||||
|
||||
// Register autocomplete provider
|
||||
|
||||
@@ -19,6 +19,7 @@ import type {
|
||||
AgentManagerKeybindingsMessage,
|
||||
AgentManagerMultiVersionProgressMessage,
|
||||
AgentManagerSendInitialMessage,
|
||||
AgentManagerBranchInfo,
|
||||
WorktreeState,
|
||||
ManagedSessionState,
|
||||
SessionInfo,
|
||||
@@ -87,6 +88,7 @@ const defaultBindings: Record<string, string> = {
|
||||
newTab: isMac ? "⌘T" : "Ctrl+T",
|
||||
closeTab: isMac ? "⌘W" : "Ctrl+W",
|
||||
newWorktree: isMac ? "⌘N" : "Ctrl+N",
|
||||
advancedWorktree: isMac ? "⌘⇧N" : "Ctrl+Shift+N",
|
||||
closeWorktree: isMac ? "⌘⇧W" : "Ctrl+Shift+W",
|
||||
agentManagerOpen: isMac ? "⌘⇧M" : "Ctrl+Shift+M",
|
||||
focusPanel: isMac ? "⌘." : "Ctrl+.",
|
||||
@@ -177,6 +179,7 @@ function buildShortcutCategories(bindings: Record<string, string>): ShortcutCate
|
||||
{ label: "Previous item", binding: bindings.previousSession ?? "" },
|
||||
{ label: "Next item", binding: bindings.nextSession ?? "" },
|
||||
{ label: "New worktree", binding: bindings.newWorktree ?? "" },
|
||||
{ label: "Advanced worktree", binding: bindings.advancedWorktree ?? "" },
|
||||
{ label: "Delete worktree", binding: bindings.closeWorktree ?? "" },
|
||||
],
|
||||
},
|
||||
@@ -544,6 +547,7 @@ const AgentManagerContent: Component = () => {
|
||||
} else if (msg.action === "newTab") handleNewTabForCurrentSelection()
|
||||
else if (msg.action === "closeTab") closeActiveTab()
|
||||
else if (msg.action === "newWorktree") handleNewWorktreeOrPromote()
|
||||
else if (msg.action === "advancedWorktree") showAdvancedWorktreeDialog()
|
||||
else if (msg.action === "closeWorktree") closeSelectedWorktree()
|
||||
else if (msg.action === "focusInput") window.dispatchEvent(new Event("focusPrompt"))
|
||||
}
|
||||
@@ -559,8 +563,8 @@ const AgentManagerContent: Component = () => {
|
||||
if (["t", "w", "n"].includes(e.key.toLowerCase()) && !e.shiftKey) {
|
||||
e.preventDefault()
|
||||
}
|
||||
// Prevent defaults for shift variants (close worktree)
|
||||
if (e.key.toLowerCase() === "w" && e.shiftKey) {
|
||||
// Prevent defaults for shift variants (close worktree, advanced new worktree)
|
||||
if (["w", "n"].includes(e.key.toLowerCase()) && e.shiftKey) {
|
||||
e.preventDefault()
|
||||
}
|
||||
}
|
||||
@@ -721,15 +725,18 @@ const AgentManagerContent: Component = () => {
|
||||
session.setSessionAgent(ev.sessionId, ev.agent)
|
||||
}
|
||||
|
||||
vscode.postMessage({
|
||||
type: "sendMessage",
|
||||
text: ev.text,
|
||||
sessionID: ev.sessionId,
|
||||
providerID: ev.providerID,
|
||||
modelID: ev.modelID,
|
||||
agent: ev.agent,
|
||||
files: ev.files,
|
||||
})
|
||||
// Only send a message if there's text — otherwise just clear busy state
|
||||
if (ev.text) {
|
||||
vscode.postMessage({
|
||||
type: "sendMessage",
|
||||
text: ev.text,
|
||||
sessionID: ev.sessionId,
|
||||
providerID: ev.providerID,
|
||||
modelID: ev.modelID,
|
||||
agent: ev.agent,
|
||||
files: ev.files,
|
||||
})
|
||||
}
|
||||
// Clear busy state — use worktreeId from the message directly
|
||||
// to avoid race condition where managedSessions() hasn't updated yet
|
||||
if (ev.worktreeId) {
|
||||
@@ -1074,11 +1081,21 @@ const AgentManagerContent: Component = () => {
|
||||
<DropdownMenu.Content class="am-split-menu">
|
||||
<DropdownMenu.Item onSelect={handleCreateWorktree}>
|
||||
<DropdownMenu.ItemLabel>New Worktree</DropdownMenu.ItemLabel>
|
||||
<span class="am-menu-shortcut">
|
||||
{parseBindingTokens(kb().newWorktree ?? "").map((t) => (
|
||||
<kbd class="am-menu-key">{t}</kbd>
|
||||
))}
|
||||
</span>
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item onSelect={showAdvancedWorktreeDialog}>
|
||||
<Icon name="layers" size="small" />
|
||||
<DropdownMenu.ItemLabel>New with Versions...</DropdownMenu.ItemLabel>
|
||||
<Icon name="settings-gear" size="small" />
|
||||
<DropdownMenu.ItemLabel>Advanced...</DropdownMenu.ItemLabel>
|
||||
<span class="am-menu-shortcut">
|
||||
{parseBindingTokens(kb().advancedWorktree ?? "").map((t) => (
|
||||
<kbd class="am-menu-key">{t}</kbd>
|
||||
))}
|
||||
</span>
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Portal>
|
||||
@@ -1546,22 +1563,62 @@ const AgentManagerContent: Component = () => {
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Advanced "New Worktree" dialog — prompt, versions, model, mode
|
||||
// Advanced "New Worktree" dialog — prompt, versions, model, mode, advanced options
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
type VersionCount = 1 | 2 | 3 | 4
|
||||
const VERSION_OPTIONS: VersionCount[] = [1, 2, 3, 4]
|
||||
|
||||
function sanitizeSegment(text: string, maxLength = 50): string {
|
||||
return text
|
||||
.toLowerCase()
|
||||
.trim()
|
||||
.replace(/\s+/g, "-")
|
||||
.replace(/[^a-z0-9._+@-]/g, "")
|
||||
.replace(/\.{2,}/g, ".")
|
||||
.replace(/@\{/g, "@")
|
||||
.replace(/-+/g, "-")
|
||||
.replace(/^[-.]|[-.]+$/g, "")
|
||||
.replace(/\.lock$/g, "")
|
||||
.slice(0, maxLength)
|
||||
}
|
||||
|
||||
function sanitizeBranchName(name: string): string {
|
||||
return name
|
||||
.split("/")
|
||||
.map((s) => sanitizeSegment(s))
|
||||
.filter(Boolean)
|
||||
.join("/")
|
||||
}
|
||||
|
||||
function formatRelativeTime(epoch: number): string {
|
||||
const diff = Math.floor(Date.now() / 1000) - epoch
|
||||
if (diff < 60) return "now"
|
||||
if (diff < 3600) return `${Math.floor(diff / 60)}m`
|
||||
if (diff < 86400) return `${Math.floor(diff / 3600)}h`
|
||||
if (diff < 2592000) return `${Math.floor(diff / 86400)}d`
|
||||
if (diff < 31536000) return `${Math.floor(diff / 2592000)}mo`
|
||||
return `${Math.floor(diff / 31536000)}y`
|
||||
}
|
||||
|
||||
const NewWorktreeDialog: Component<{ onClose: () => void }> = (props) => {
|
||||
const vscode = useVSCode()
|
||||
const session = useSession()
|
||||
|
||||
const [name, setName] = createSignal("")
|
||||
const [prompt, setPrompt] = createSignal("")
|
||||
const [versions, setVersions] = createSignal<VersionCount>(2)
|
||||
const [versions, setVersions] = createSignal<VersionCount>(1)
|
||||
const [model, setModel] = createSignal<{ providerID: string; modelID: string } | null>(null)
|
||||
const [agent, setAgent] = createSignal(session.selectedAgent())
|
||||
const [starting, setStarting] = createSignal(false)
|
||||
const [showAdvanced, setShowAdvanced] = createSignal(false)
|
||||
const [branchName, setBranchName] = createSignal("")
|
||||
const [baseBranch, setBaseBranch] = createSignal<string | null>(null)
|
||||
const [branches, setBranches] = createSignal<AgentManagerBranchInfo[]>([])
|
||||
const [defaultBranch, setDefaultBranch] = createSignal("main")
|
||||
const [branchSearch, setBranchSearch] = createSignal("")
|
||||
const [baseBranchOpen, setBaseBranchOpen] = createSignal(false)
|
||||
const [highlightedIndex, setHighlightedIndex] = createSignal(0)
|
||||
|
||||
let textareaRef: HTMLTextAreaElement | undefined
|
||||
|
||||
@@ -1571,19 +1628,42 @@ const NewWorktreeDialog: Component<{ onClose: () => void }> = (props) => {
|
||||
textareaRef.focus()
|
||||
textareaRef.select()
|
||||
})
|
||||
vscode.postMessage({ type: "agentManager.requestBranches" })
|
||||
})
|
||||
|
||||
const canSubmit = () => prompt().trim().length > 0 && !starting()
|
||||
// Listen for branch data
|
||||
const handler = (e: MessageEvent) => {
|
||||
const msg = e.data as ExtensionMessage
|
||||
if (msg.type === "agentManager.branches") {
|
||||
setBranches(msg.branches)
|
||||
setDefaultBranch(msg.defaultBranch)
|
||||
}
|
||||
}
|
||||
window.addEventListener("message", handler)
|
||||
onCleanup(() => window.removeEventListener("message", handler))
|
||||
|
||||
const effectiveBaseBranch = () => baseBranch() ?? defaultBranch()
|
||||
|
||||
const filteredBranches = createMemo(() => {
|
||||
const search = branchSearch().toLowerCase()
|
||||
if (!search) return branches()
|
||||
return branches().filter((b) => b.name.toLowerCase().includes(search))
|
||||
})
|
||||
|
||||
const canSubmit = () => !starting()
|
||||
|
||||
const handleSubmit = () => {
|
||||
const text = prompt().trim()
|
||||
if (!text || starting()) return
|
||||
if (starting()) return
|
||||
setStarting(true)
|
||||
|
||||
const text = prompt().trim() || undefined
|
||||
const count = versions()
|
||||
const sel = model()
|
||||
const defaultAgent = session.agents()[0]?.name
|
||||
const selectedAgent = agent() !== defaultAgent ? agent() : undefined
|
||||
const advanced = showAdvanced()
|
||||
const customBranch = advanced ? branchName().trim() || undefined : undefined
|
||||
|
||||
vscode.postMessage({
|
||||
type: "agentManager.createMultiVersion",
|
||||
text,
|
||||
@@ -1592,6 +1672,8 @@ const NewWorktreeDialog: Component<{ onClose: () => void }> = (props) => {
|
||||
providerID: sel?.providerID,
|
||||
modelID: sel?.modelID,
|
||||
agent: selectedAgent,
|
||||
baseBranch: advanced ? (baseBranch() ?? undefined) : undefined,
|
||||
branchName: customBranch,
|
||||
})
|
||||
|
||||
props.onClose()
|
||||
@@ -1651,28 +1733,138 @@ const NewWorktreeDialog: Component<{ onClose: () => void }> = (props) => {
|
||||
<ModeSwitcherBase agents={session.agents()} value={agent()} onSelect={setAgent} />
|
||||
</Show>
|
||||
</div>
|
||||
<div class="prompt-input-hint-actions">
|
||||
<Tooltip value={`${isMac ? "\u2318" : "Ctrl+"}Enter`} placement="top">
|
||||
<Button variant="primary" size="small" onClick={handleSubmit} disabled={!canSubmit()}>
|
||||
<Show
|
||||
when={!starting()}
|
||||
fallback={
|
||||
<>
|
||||
<Spinner class="am-nv-spinner" />
|
||||
<span>Creating...</span>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<svg data-slot="icon-svg" width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
|
||||
<path d="M1.5 1.5L14.5 8L1.5 14.5V9L10 8L1.5 7V1.5Z" />
|
||||
</svg>
|
||||
</Show>
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div class="prompt-input-hint-actions" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Advanced options toggle */}
|
||||
<button class="am-advanced-toggle" onClick={() => setShowAdvanced(!showAdvanced())} type="button">
|
||||
<Icon name={showAdvanced() ? "chevron-down" : "chevron-right"} size="small" />
|
||||
<span>Advanced options</span>
|
||||
</button>
|
||||
|
||||
<Show when={showAdvanced()}>
|
||||
<div class="am-advanced-section">
|
||||
<div class="am-advanced-field">
|
||||
<span class="am-nv-config-label">Branch name</span>
|
||||
<input
|
||||
class="am-advanced-input"
|
||||
type="text"
|
||||
placeholder="auto-generated"
|
||||
value={branchName()}
|
||||
onInput={(e) => setBranchName(sanitizeBranchName(e.currentTarget.value))}
|
||||
/>
|
||||
</div>
|
||||
<div class="am-advanced-field">
|
||||
<span class="am-nv-config-label">Base branch</span>
|
||||
<div class="am-branch-selector-wrapper">
|
||||
<button
|
||||
class="am-branch-selector-trigger"
|
||||
onClick={() => setBaseBranchOpen(!baseBranchOpen())}
|
||||
type="button"
|
||||
>
|
||||
<Icon name="branch" size="small" />
|
||||
<span class="am-branch-selector-value">{effectiveBaseBranch()}</span>
|
||||
<Show when={!baseBranch()}>
|
||||
<span class="am-branch-badge">default</span>
|
||||
</Show>
|
||||
<Icon name="selector" size="small" />
|
||||
</button>
|
||||
<Show when={baseBranchOpen()}>
|
||||
<div class="am-branch-dropdown" onWheel={(e) => e.stopPropagation()}>
|
||||
<div class="am-branch-search">
|
||||
<Icon name="magnifying-glass" size="small" />
|
||||
<input
|
||||
class="am-branch-search-input"
|
||||
type="text"
|
||||
placeholder="Search branches..."
|
||||
value={branchSearch()}
|
||||
ref={(el) => requestAnimationFrame(() => el.focus())}
|
||||
onInput={(e) => {
|
||||
setBranchSearch(e.currentTarget.value)
|
||||
setHighlightedIndex(0)
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
const items = filteredBranches()
|
||||
if (e.key === "ArrowDown") {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
const next = Math.min(highlightedIndex() + 1, items.length - 1)
|
||||
setHighlightedIndex(next)
|
||||
requestAnimationFrame(() => {
|
||||
document
|
||||
.querySelector(`.am-branch-item[data-index="${next}"]`)
|
||||
?.scrollIntoView({ block: "nearest" })
|
||||
})
|
||||
} else if (e.key === "ArrowUp") {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
const prev = Math.max(highlightedIndex() - 1, 0)
|
||||
setHighlightedIndex(prev)
|
||||
requestAnimationFrame(() => {
|
||||
document
|
||||
.querySelector(`.am-branch-item[data-index="${prev}"]`)
|
||||
?.scrollIntoView({ block: "nearest" })
|
||||
})
|
||||
} else if (e.key === "Enter") {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
const selected = items[highlightedIndex()]
|
||||
if (selected) {
|
||||
setBaseBranch(selected.name)
|
||||
setBaseBranchOpen(false)
|
||||
setBranchSearch("")
|
||||
setHighlightedIndex(0)
|
||||
}
|
||||
} else if (e.key === "Escape") {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
setBaseBranchOpen(false)
|
||||
setBranchSearch("")
|
||||
setHighlightedIndex(0)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div class="am-branch-list">
|
||||
<For each={filteredBranches()}>
|
||||
{(branch, index) => (
|
||||
<button
|
||||
class="am-branch-item"
|
||||
classList={{
|
||||
"am-branch-item-active": effectiveBaseBranch() === branch.name,
|
||||
"am-branch-item-highlighted": highlightedIndex() === index(),
|
||||
}}
|
||||
data-index={index()}
|
||||
onClick={() => {
|
||||
setBaseBranch(branch.name)
|
||||
setBaseBranchOpen(false)
|
||||
setBranchSearch("")
|
||||
setHighlightedIndex(0)
|
||||
}}
|
||||
onMouseEnter={() => setHighlightedIndex(index())}
|
||||
type="button"
|
||||
>
|
||||
<Icon name="branch" size="small" />
|
||||
<span class="am-branch-item-name">{branch.name}</span>
|
||||
<Show when={branch.isDefault}>
|
||||
<span class="am-branch-badge">default</span>
|
||||
</Show>
|
||||
<Show when={!branch.isLocal && branch.isRemote}>
|
||||
<span class="am-branch-badge am-branch-badge-remote">remote</span>
|
||||
</Show>
|
||||
<span class="am-branch-item-time">{formatRelativeTime(branch.lastCommitDate)}</span>
|
||||
</button>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
{/* Version selector + info */}
|
||||
<div class="am-nv-version-bar">
|
||||
<span class="am-nv-config-label">Versions</span>
|
||||
@@ -1692,6 +1884,21 @@ const NewWorktreeDialog: Component<{ onClose: () => void }> = (props) => {
|
||||
<span class="am-nv-version-hint">{versions()} worktrees will run in parallel</span>
|
||||
</Show>
|
||||
</div>
|
||||
|
||||
{/* Submit button */}
|
||||
<Button variant="primary" size="large" class="am-nv-submit" onClick={handleSubmit} disabled={!canSubmit()}>
|
||||
<Show
|
||||
when={!starting()}
|
||||
fallback={
|
||||
<>
|
||||
<Spinner class="am-nv-spinner" />
|
||||
<span>Creating...</span>
|
||||
</>
|
||||
}
|
||||
>
|
||||
Create Workspace
|
||||
</Show>
|
||||
</Button>
|
||||
</div>
|
||||
</Dialog>
|
||||
)
|
||||
|
||||
@@ -795,6 +795,14 @@ button.am-section-toggle:hover .am-section-label {
|
||||
min-width: 460px;
|
||||
}
|
||||
|
||||
/* Allow dropdowns to escape the dialog bounds */
|
||||
[data-component="dialog"]:has(.am-nv-dialog) [data-slot="dialog-content"] {
|
||||
overflow: visible;
|
||||
}
|
||||
[data-component="dialog"]:has(.am-nv-dialog) [data-slot="dialog-body"] {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.am-nv-name-input {
|
||||
width: 100%;
|
||||
height: 32px;
|
||||
@@ -909,6 +917,223 @@ button.am-section-toggle:hover .am-section-label {
|
||||
height: 14px;
|
||||
}
|
||||
|
||||
.am-nv-submit {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* Advanced options toggle */
|
||||
|
||||
.am-advanced-toggle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 6px 0;
|
||||
border: none;
|
||||
background: none;
|
||||
color: var(--text-weak);
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
transition: color 100ms;
|
||||
}
|
||||
|
||||
.am-advanced-toggle:hover {
|
||||
color: var(--text-base);
|
||||
}
|
||||
|
||||
/* Advanced options section */
|
||||
|
||||
.am-advanced-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
.am-advanced-field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.am-advanced-input {
|
||||
width: 100%;
|
||||
padding: 6px 10px;
|
||||
border: 1px solid var(--border-base);
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--surface-base);
|
||||
color: var(--text-base);
|
||||
font-size: 12px;
|
||||
font-family: inherit;
|
||||
outline: none;
|
||||
transition: border-color 120ms;
|
||||
}
|
||||
|
||||
.am-advanced-input:focus {
|
||||
border-color: var(--border-focus, #007fd4);
|
||||
}
|
||||
|
||||
.am-advanced-input::placeholder {
|
||||
color: var(--text-weaker);
|
||||
}
|
||||
|
||||
/* Branch selector trigger + dropdown */
|
||||
|
||||
.am-branch-selector-wrapper {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.am-branch-selector-trigger {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
padding: 6px 10px;
|
||||
border: 1px solid var(--border-base);
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--surface-base);
|
||||
color: var(--text-base);
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
transition: border-color 120ms;
|
||||
}
|
||||
|
||||
.am-branch-selector-trigger:hover {
|
||||
border-color: var(--border-hover);
|
||||
}
|
||||
|
||||
.am-branch-selector-value {
|
||||
flex: 1;
|
||||
text-align: left;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.am-branch-badge {
|
||||
font-size: 10px;
|
||||
font-weight: 500;
|
||||
padding: 1px 6px;
|
||||
border-radius: 3px;
|
||||
background: var(--surface-raised-stronger-non-alpha);
|
||||
color: var(--text-weak);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.am-branch-badge-remote {
|
||||
background: var(--surface-raised-base);
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.am-branch-dropdown {
|
||||
position: absolute;
|
||||
top: calc(100% + 4px);
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 100;
|
||||
border: 1px solid var(--border-base);
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--surface-float-base, var(--surface-raised-base));
|
||||
box-shadow: var(--shadow-lg-border-base, 0 8px 24px rgba(0, 0, 0, 0.3));
|
||||
max-height: 280px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.am-branch-search {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 8px 10px;
|
||||
border-bottom: 1px solid var(--border-base);
|
||||
}
|
||||
|
||||
.am-branch-search-input {
|
||||
flex: 1;
|
||||
border: none;
|
||||
background: none;
|
||||
color: var(--text-base);
|
||||
font-size: 12px;
|
||||
font-family: inherit;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.am-branch-search-input::placeholder {
|
||||
color: var(--text-weaker);
|
||||
}
|
||||
|
||||
.am-branch-list {
|
||||
overflow-y: auto;
|
||||
max-height: 220px;
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
.am-branch-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
padding: 6px 10px;
|
||||
border: none;
|
||||
background: none;
|
||||
color: var(--text-base);
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
transition: background 80ms;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.am-branch-item:hover {
|
||||
background: var(--surface-raised-base-hover);
|
||||
}
|
||||
|
||||
.am-branch-item-active {
|
||||
background: var(--surface-raised-base);
|
||||
}
|
||||
|
||||
.am-branch-item-highlighted {
|
||||
background: var(--surface-raised-base-hover);
|
||||
}
|
||||
|
||||
/* Keyboard shortcut hints in dropdown menus */
|
||||
|
||||
.am-menu-shortcut {
|
||||
margin-left: auto;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.am-menu-key {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 18px;
|
||||
height: 18px;
|
||||
padding: 0 4px;
|
||||
border-radius: 3px;
|
||||
background: var(--surface-raised-stronger-non-alpha);
|
||||
color: var(--text-weak);
|
||||
font-size: 10px;
|
||||
font-family: inherit;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.am-branch-item-name {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-family: var(--font-mono, monospace);
|
||||
}
|
||||
|
||||
.am-branch-item-time {
|
||||
font-size: 11px;
|
||||
color: var(--text-weaker);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* HoverCard popover for worktree items */
|
||||
|
||||
.am-hover-card {
|
||||
|
||||
@@ -611,6 +611,22 @@ export interface AgentManagerMultiVersionProgressMessage {
|
||||
groupId?: string
|
||||
}
|
||||
|
||||
// Branch info for branch selector (extension → webview)
|
||||
export interface AgentManagerBranchInfo {
|
||||
name: string
|
||||
isLocal: boolean
|
||||
isRemote: boolean
|
||||
lastCommitDate: number
|
||||
isDefault: boolean
|
||||
}
|
||||
|
||||
// Branch list response (extension → webview)
|
||||
export interface AgentManagerBranchesMessage {
|
||||
type: "agentManager.branches"
|
||||
branches: AgentManagerBranchInfo[]
|
||||
defaultBranch: string
|
||||
}
|
||||
|
||||
// Stored variant selections loaded from extension globalState (extension → webview)
|
||||
export interface VariantsLoadedMessage {
|
||||
type: "variantsLoaded"
|
||||
@@ -622,7 +638,7 @@ export interface AgentManagerSendInitialMessage {
|
||||
type: "agentManager.sendInitialMessage"
|
||||
sessionId: string
|
||||
worktreeId: string
|
||||
text: string
|
||||
text?: string
|
||||
providerID?: string
|
||||
modelID?: string
|
||||
agent?: string
|
||||
@@ -889,6 +905,8 @@ export interface TelemetryRequest {
|
||||
// Create a new worktree (with auto-created first session)
|
||||
export interface CreateWorktreeRequest {
|
||||
type: "agentManager.createWorktree"
|
||||
baseBranch?: string
|
||||
branchName?: string
|
||||
}
|
||||
|
||||
// Delete a worktree and dissociate its sessions
|
||||
@@ -937,13 +955,14 @@ export interface ShowTerminalRequest {
|
||||
// Create multiple worktree sessions for the same prompt (multi-version mode)
|
||||
export interface CreateMultiVersionRequest {
|
||||
type: "agentManager.createMultiVersion"
|
||||
text: string
|
||||
text?: string
|
||||
versions: number
|
||||
providerID?: string
|
||||
modelID?: string
|
||||
agent?: string
|
||||
files?: FileAttachment[]
|
||||
baseBranch?: string
|
||||
branchName?: string
|
||||
}
|
||||
|
||||
// Persist tab order for a context (worktree ID or "local")
|
||||
@@ -959,6 +978,11 @@ export interface SetSessionsCollapsedRequest {
|
||||
collapsed: boolean
|
||||
}
|
||||
|
||||
// Request branch list for base branch selector
|
||||
export interface RequestBranchesMessage {
|
||||
type: "agentManager.requestBranches"
|
||||
}
|
||||
|
||||
// Variant persistence (webview → extension)
|
||||
export interface PersistVariantRequest {
|
||||
type: "persistVariant"
|
||||
@@ -1025,6 +1049,7 @@ export type WebviewMessage =
|
||||
| SetSessionsCollapsedRequest
|
||||
| PersistVariantRequest
|
||||
| RequestVariantsMessage
|
||||
| RequestBranchesMessage
|
||||
|
||||
// ============================================
|
||||
// VS Code API type
|
||||
|
||||
Reference in New Issue
Block a user