mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-08-28 19:11:03 +08:00
fix(agent-manager): continue sessions in worktrees
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Keep continued Agent Manager sessions in the selected worktree instead of moving them to Local.
|
||||
@@ -135,6 +135,7 @@ import type { KiloProviderOptions } from "./kilo-provider/options"
|
||||
import { fetchKiloEmbeddingModelCatalog } from "@kilocode/kilo-gateway"
|
||||
|
||||
type MessageLoadMode = "replace" | "prepend" | "focus" | "reconcile"
|
||||
type ContextMessage = { contextDirectory?: unknown }
|
||||
// Helper to map agent data to the subset of fields sent to the webview
|
||||
const mapAgent = (a: Agent) => ({
|
||||
name: a.name,
|
||||
@@ -634,7 +635,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
this.readyResolvers.splice(0).forEach((r) => r())
|
||||
break
|
||||
case "sendMessage": {
|
||||
const files = parseMessageFiles(message.files)
|
||||
const msg = message as typeof message & ContextMessage
|
||||
await this.handleSendMessage(
|
||||
message.text,
|
||||
typeof message.messageID === "string" ? message.messageID : undefined,
|
||||
@@ -644,13 +645,14 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
message.modelID,
|
||||
message.agent,
|
||||
message.variant,
|
||||
files,
|
||||
parseMessageFiles(message.files),
|
||||
typeof message.agentManagerContext === "string" ? message.agentManagerContext : undefined,
|
||||
typeof msg.contextDirectory === "string" ? msg.contextDirectory : undefined,
|
||||
)
|
||||
break
|
||||
}
|
||||
case "sendCommand": {
|
||||
const files = parseMessageFiles(message.files)
|
||||
const msg = message as typeof message & ContextMessage
|
||||
await this.handleSendCommand(
|
||||
message.command,
|
||||
message.arguments,
|
||||
@@ -661,8 +663,9 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
message.modelID,
|
||||
message.agent,
|
||||
message.variant,
|
||||
files,
|
||||
parseMessageFiles(message.files),
|
||||
typeof message.agentManagerContext === "string" ? message.agentManagerContext : undefined,
|
||||
typeof msg.contextDirectory === "string" ? msg.contextDirectory : undefined,
|
||||
)
|
||||
break
|
||||
}
|
||||
@@ -1068,13 +1071,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
break
|
||||
}
|
||||
case "fetchMarketplaceData": {
|
||||
const workspace = this.getProjectDirectory(this.currentSession?.id)
|
||||
const mp = this.getMarketplace()
|
||||
// Fetch skills from CLI backend (authoritative source) so the
|
||||
// marketplace doesn't need to duplicate the CLI's skill scanning.
|
||||
const skills = await this.fetchCliSkills()
|
||||
const data = await mp.fetchData(workspace, skills)
|
||||
this.postMessage({ type: "marketplaceData", ...data })
|
||||
await this.handleFetchMarketplaceData()
|
||||
break
|
||||
}
|
||||
case "filterMarketplaceItems": {
|
||||
@@ -1124,6 +1121,14 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
this.diffVirtualProvider.open(d)
|
||||
}
|
||||
|
||||
private async handleFetchMarketplaceData(): Promise<void> {
|
||||
const workspace = this.getProjectDirectory(this.currentSession?.id)
|
||||
const mp = this.getMarketplace()
|
||||
const skills = await this.fetchCliSkills()
|
||||
const data = await mp.fetchData(workspace, skills)
|
||||
this.postMessage({ type: "marketplaceData", ...data })
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize connection to the CLI backend server.
|
||||
* Subscribes to the shared KiloConnectionService.
|
||||
@@ -1522,6 +1527,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
client.session.list({ directory: dir, roots: true }, { throwOnError: true }).then(({ data }) => data)
|
||||
: null,
|
||||
sessionDirectories: this.sessionDirectories,
|
||||
worktreeDirectories: this.opts.worktreeDirectories,
|
||||
workspaceDirectory: this.getWorkspaceDirectory(),
|
||||
postMessage: (msg: unknown) => this.postMessage(msg),
|
||||
}
|
||||
@@ -2421,7 +2427,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
details: getConfigErrorDetails(error),
|
||||
})
|
||||
}
|
||||
private async resolveSession(sessionID?: string, draftID?: string, context?: string) {
|
||||
private async resolveSession(sessionID?: string, draftID?: string, context?: string, contextDirectory?: string) {
|
||||
if (!this.client) return undefined
|
||||
|
||||
const dir = resolveNewSessionDirectory({
|
||||
@@ -2429,6 +2435,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
currentSessionID: this.currentSession?.id,
|
||||
contextSessionID: this.contextSessionID,
|
||||
agentManagerContext: context,
|
||||
contextDirectory,
|
||||
sessionDirectories: this.sessionDirectories,
|
||||
workspaceDirectory: this.getRootDirectory(),
|
||||
})
|
||||
@@ -2538,6 +2545,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
variant?: string,
|
||||
files?: MessageFile[],
|
||||
context?: string,
|
||||
contextDirectory?: string,
|
||||
): Promise<void> {
|
||||
if (!this.client) {
|
||||
this.postMessage({
|
||||
@@ -2554,7 +2562,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
|
||||
let resolved: { sid: string; dir: string } | undefined
|
||||
try {
|
||||
resolved = await this.resolveSession(sessionID, draftID, context)
|
||||
resolved = await this.resolveSession(sessionID, draftID, context, contextDirectory)
|
||||
|
||||
const parts: Array<TextPartInput | FilePartInput> = []
|
||||
if (files) {
|
||||
@@ -2615,6 +2623,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
variant?: string,
|
||||
files?: MessageFile[],
|
||||
context?: string,
|
||||
contextDirectory?: string,
|
||||
): Promise<void> {
|
||||
if (!this.client) {
|
||||
this.postMessage({
|
||||
@@ -2631,7 +2640,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
|
||||
let resolved: { sid: string; dir: string } | undefined
|
||||
try {
|
||||
resolved = await this.resolveSession(sessionID, draftID, context)
|
||||
resolved = await this.resolveSession(sessionID, draftID, context, contextDirectory)
|
||||
|
||||
if (messageID) {
|
||||
this.connectionService.recordMessageSessionId(messageID, resolved!.sid)
|
||||
|
||||
@@ -181,6 +181,7 @@ export class AgentManagerProvider implements Disposable {
|
||||
this.attachPanel(
|
||||
this.host.openPanel({
|
||||
onBeforeMessage: (msg) => this.onMessage(msg),
|
||||
worktreeDirectories: () => this.getWorktreeDirectories(),
|
||||
}),
|
||||
)
|
||||
}
|
||||
@@ -378,7 +379,7 @@ export class AgentManagerProvider implements Disposable {
|
||||
if (m.type === "agentManager.deleteWorktree") return this.onDeleteWorktree(m.worktreeId)
|
||||
if (m.type === "agentManager.removeStaleWorktree") return this.onRemoveStaleWorktree(m.worktreeId)
|
||||
if (m.type === "agentManager.promoteSession") return this.onPromoteSession(m.sessionId)
|
||||
if (m.type === "agentManager.addSessionToWorktree") return this.onAddSessionToWorktree(m.worktreeId)
|
||||
if (m.type === "agentManager.addSessionToWorktree") return this.onAddSessionToWorktree(m.worktreeId, m.sessionId)
|
||||
if (m.type === "agentManager.forkSession") return this.onForkSession(m.sessionId, m.worktreeId, m.messageId)
|
||||
if (m.type === "agentManager.closeSession") return this.onCloseSession(m.sessionId)
|
||||
}
|
||||
@@ -418,6 +419,15 @@ export class AgentManagerProvider implements Disposable {
|
||||
return null
|
||||
}
|
||||
|
||||
if ((m.type === "sendMessage" || m.type === "sendCommand") && !m.sessionID) {
|
||||
const ctx = typeof m.agentManagerContext === "string" ? m.agentManagerContext : undefined
|
||||
const worktree = ctx && ctx !== "local" ? this.getStateManager()?.getWorktree(ctx) : undefined
|
||||
if (worktree) {
|
||||
this.activeSessionId = m.draftID
|
||||
return { ...msg, contextDirectory: worktree.path }
|
||||
}
|
||||
}
|
||||
|
||||
if ((m.type === "sendMessage" || m.type === "sendCommand") && m.draftID && !m.sessionID) {
|
||||
this.activeSessionId = m.draftID
|
||||
return msg
|
||||
@@ -1049,7 +1059,7 @@ export class AgentManagerProvider implements Disposable {
|
||||
}
|
||||
|
||||
/** Add a new session to an existing worktree. */
|
||||
private async onAddSessionToWorktree(worktreeId: string): Promise<null> {
|
||||
private async onAddSessionToWorktree(worktreeId: string, sessionId?: string): Promise<null> {
|
||||
let client: KiloClient
|
||||
try {
|
||||
client = this.connectionService.getClient()
|
||||
@@ -1068,6 +1078,26 @@ export class AgentManagerProvider implements Disposable {
|
||||
return null
|
||||
}
|
||||
|
||||
if (sessionId) {
|
||||
if (state.getSession(sessionId)) state.moveSession(sessionId, worktreeId)
|
||||
else state.addSession(sessionId, worktreeId)
|
||||
this.registerWorktreeSession(sessionId, worktree.path)
|
||||
this.pushState()
|
||||
this.postToWebview({
|
||||
type: "agentManager.sessionAdded",
|
||||
sessionId,
|
||||
worktreeId,
|
||||
})
|
||||
this.host.capture("Agent Manager Session Started", {
|
||||
source: PLATFORM,
|
||||
sessionId,
|
||||
worktreeId,
|
||||
existing: true,
|
||||
})
|
||||
this.log(`Added existing session ${sessionId} to worktree ${worktreeId}`)
|
||||
return null
|
||||
}
|
||||
|
||||
let session: Session
|
||||
try {
|
||||
const { data } = await client.session.create(
|
||||
@@ -1648,6 +1678,10 @@ export class AgentManagerProvider implements Disposable {
|
||||
return this.panel?.sessions.getSessionDirectories() ?? new Map()
|
||||
}
|
||||
|
||||
public getWorktreeDirectories(): string[] {
|
||||
return this.getStateManager()?.getWorktrees().map((wt) => wt.path) ?? []
|
||||
}
|
||||
|
||||
/**
|
||||
* Continue a sidebar session in a new worktree.
|
||||
* Captures git state, creates worktree, applies state, forks session.
|
||||
|
||||
@@ -101,6 +101,7 @@ function createHarness() {
|
||||
prBridge: { handleMessage: ReturnType<typeof vi.fn> }
|
||||
activeSessionId: string | undefined
|
||||
stateReady: Promise<void> | undefined
|
||||
contextTarget: ReturnType<typeof vi.fn>
|
||||
createWorktreeOnDisk: ReturnType<typeof vi.fn>
|
||||
runSetupScriptForWorktree: ReturnType<typeof vi.fn>
|
||||
createSessionInWorktree: ReturnType<typeof vi.fn>
|
||||
@@ -121,6 +122,7 @@ function createHarness() {
|
||||
manager.prBridge = { handleMessage: vi.fn().mockReturnValue(false) }
|
||||
manager.activeSessionId = undefined
|
||||
manager.stateReady = Promise.resolve()
|
||||
manager.contextTarget = vi.fn()
|
||||
manager.createWorktreeOnDisk = vi.fn()
|
||||
manager.runSetupScriptForWorktree = vi.fn().mockResolvedValue(undefined)
|
||||
manager.createSessionInWorktree = vi.fn()
|
||||
@@ -202,4 +204,28 @@ describe("AgentManagerProvider worktree creation", () => {
|
||||
|
||||
expect(result).toEqual({ type: "requestFileSearch", query: "src", requestId: "r1", sessionID: "session-wt" })
|
||||
})
|
||||
|
||||
it("resolves new sends to the selected worktree directory", async () => {
|
||||
const manager = createHarness()
|
||||
const state = {
|
||||
getWorktree: vi.fn().mockReturnValue({ id: "wt-1", path: "/repo/.kilo/worktrees/wt-1" }),
|
||||
}
|
||||
manager.getStateManager.mockReturnValue(state)
|
||||
manager.contextTarget.mockResolvedValue(undefined)
|
||||
|
||||
const result = await manager.onMessage({
|
||||
type: "sendMessage",
|
||||
text: "continue",
|
||||
agentManagerContext: "wt-1",
|
||||
draftID: "draft-1",
|
||||
})
|
||||
|
||||
expect(result).toEqual({
|
||||
type: "sendMessage",
|
||||
text: "continue",
|
||||
agentManagerContext: "wt-1",
|
||||
draftID: "draft-1",
|
||||
contextDirectory: "/repo/.kilo/worktrees/wt-1",
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -93,6 +93,7 @@ export interface Host {
|
||||
*/
|
||||
openPanel(opts: {
|
||||
onBeforeMessage: (msg: Record<string, unknown>) => Promise<Record<string, unknown> | null>
|
||||
worktreeDirectories?: () => string[]
|
||||
}): PanelContext
|
||||
|
||||
/** Get the workspace/project root path. */
|
||||
|
||||
@@ -348,6 +348,7 @@ interface OpenLocallyIn {
|
||||
interface AddSessionToWorktreeIn {
|
||||
type: "agentManager.addSessionToWorktree"
|
||||
worktreeId: string
|
||||
sessionId?: string
|
||||
}
|
||||
|
||||
interface CloseSessionIn {
|
||||
@@ -606,6 +607,7 @@ interface SendMessageIn {
|
||||
variant?: string
|
||||
files?: Array<{ mime: string; url: string; filename?: string; source?: FileSourceIn }>
|
||||
agentManagerContext?: string
|
||||
contextDirectory?: string
|
||||
}
|
||||
|
||||
interface SendCommandIn {
|
||||
@@ -621,6 +623,7 @@ interface SendCommandIn {
|
||||
variant?: string
|
||||
files?: Array<{ mime: string; url: string; filename?: string; source?: FileSourceIn }>
|
||||
agentManagerContext?: string
|
||||
contextDirectory?: string
|
||||
}
|
||||
|
||||
interface RequestTerminalContextIn {
|
||||
|
||||
@@ -35,6 +35,7 @@ export class VscodeHost implements Host {
|
||||
|
||||
openPanel(opts: {
|
||||
onBeforeMessage: (msg: Record<string, unknown>) => Promise<Record<string, unknown> | null>
|
||||
worktreeDirectories?: () => string[]
|
||||
}): PanelContext {
|
||||
const panel = vscode.window.createWebviewPanel(
|
||||
"kilo-code.new.AgentManagerPanel",
|
||||
@@ -54,6 +55,7 @@ export class VscodeHost implements Host {
|
||||
panel: vscode.WebviewPanel,
|
||||
opts: {
|
||||
onBeforeMessage: (msg: Record<string, unknown>) => Promise<Record<string, unknown> | null>
|
||||
worktreeDirectories?: () => string[]
|
||||
},
|
||||
): PanelContext {
|
||||
return this.wirePanel(panel, opts)
|
||||
@@ -63,6 +65,7 @@ export class VscodeHost implements Host {
|
||||
panel: vscode.WebviewPanel,
|
||||
opts: {
|
||||
onBeforeMessage: (msg: Record<string, unknown>) => Promise<Record<string, unknown> | null>
|
||||
worktreeDirectories?: () => string[]
|
||||
},
|
||||
): PanelContext {
|
||||
panel.webview.options = {
|
||||
@@ -86,6 +89,7 @@ export class VscodeHost implements Host {
|
||||
|
||||
const provider = new KiloProvider(this.extensionUri, this.connectionService, this.context, {
|
||||
slimEditMetadata: true,
|
||||
worktreeDirectories: () => opts.worktreeDirectories?.() ?? [],
|
||||
})
|
||||
if (this.diffVirtual) {
|
||||
provider.setDiffVirtualProvider(this.diffVirtual)
|
||||
|
||||
@@ -171,6 +171,7 @@ export function activate(context: vscode.ExtensionContext) {
|
||||
deserializeWebviewPanel(panel: vscode.WebviewPanel) {
|
||||
const ctx = agentManagerHost.wrapExistingPanel(panel, {
|
||||
onBeforeMessage: (msg) => agentManagerProvider.handleMessage(msg),
|
||||
worktreeDirectories: () => agentManagerProvider.getWorktreeDirectories(),
|
||||
})
|
||||
agentManagerProvider.deserializePanel(ctx)
|
||||
return Promise.resolve()
|
||||
|
||||
@@ -222,6 +222,7 @@ export interface SessionRefreshContext {
|
||||
connectionState: "connecting" | "connected" | "disconnected" | "error"
|
||||
listSessions: ((dir: string) => Promise<Session[]>) | null
|
||||
sessionDirectories: Map<string, string>
|
||||
worktreeDirectories?: () => string[]
|
||||
workspaceDirectory: string
|
||||
postMessage(message: unknown): void
|
||||
}
|
||||
@@ -245,7 +246,7 @@ export async function loadSessions(ctx: SessionRefreshContext): Promise<string |
|
||||
|
||||
const sessions = await list(ctx.workspaceDirectory)
|
||||
const projectID = sessions[0]?.projectID
|
||||
const worktreeDirs = new Set(ctx.sessionDirectories.values())
|
||||
const worktreeDirs = new Set([...(ctx.worktreeDirectories?.() ?? []), ...ctx.sessionDirectories.values()])
|
||||
const failed = new Set<string>()
|
||||
const extra = await Promise.all(
|
||||
[...worktreeDirs].map((dir) =>
|
||||
@@ -339,6 +340,7 @@ export function resolveNewSessionDirectory(input: {
|
||||
currentSessionID?: string
|
||||
contextSessionID?: string
|
||||
agentManagerContext?: string
|
||||
contextDirectory?: string
|
||||
sessionDirectories: Map<string, string>
|
||||
workspaceDirectory: string
|
||||
}) {
|
||||
@@ -350,6 +352,8 @@ export function resolveNewSessionDirectory(input: {
|
||||
})
|
||||
}
|
||||
|
||||
if (input.contextDirectory) return input.contextDirectory
|
||||
|
||||
return resolveContextDirectory({
|
||||
currentSessionID: input.currentSessionID,
|
||||
contextSessionID: input.contextSessionID,
|
||||
|
||||
@@ -2,4 +2,5 @@ export type KiloProviderOptions = {
|
||||
projectDirectory?: string | null
|
||||
slimEditMetadata?: boolean
|
||||
tabTitle?: (title: string) => void
|
||||
worktreeDirectories?: () => string[]
|
||||
}
|
||||
|
||||
@@ -93,6 +93,17 @@ describe("resolveNewSessionDirectory", () => {
|
||||
expect(dir).toBe("/repo/.kilo/worktrees/feature")
|
||||
})
|
||||
|
||||
it("uses explicit Agent Manager worktree context after the active session was cleared", () => {
|
||||
const dir = resolveNewSessionDirectory({
|
||||
agentManagerContext: "wt_feature",
|
||||
contextDirectory: "/repo/.kilo/worktrees/feature",
|
||||
sessionDirectories: new Map(),
|
||||
workspaceDirectory: "/repo",
|
||||
})
|
||||
|
||||
expect(dir).toBe("/repo/.kilo/worktrees/feature")
|
||||
})
|
||||
|
||||
it("creates local Agent Manager sessions in the workspace root after a worktree was selected", () => {
|
||||
const dir = resolveNewSessionDirectory({
|
||||
contextSessionID: "ses_worktree",
|
||||
|
||||
@@ -130,6 +130,7 @@ import { createMarkdownRender } from "./review-preferences"
|
||||
import { createSidebarCollapse } from "./sidebar-collapse"
|
||||
import { SidebarToggleButton } from "./SidebarToggleButton"
|
||||
import { setTabWidths } from "./tab-widths"
|
||||
import { buildShortcutCategories } from "./shortcuts"
|
||||
import "./agent-manager.css"
|
||||
import "./agent-manager-review.css"
|
||||
const REVIEW_TAB_ID = "review"
|
||||
@@ -185,76 +186,6 @@ const defaultBindings: Record<string, string> = {
|
||||
),
|
||||
}
|
||||
|
||||
/** Shortcut category definition for the keyboard shortcuts dialog */
|
||||
interface ShortcutEntry {
|
||||
label: string
|
||||
binding: string
|
||||
}
|
||||
|
||||
interface ShortcutCategory {
|
||||
title: string
|
||||
shortcuts: ShortcutEntry[]
|
||||
}
|
||||
|
||||
/** Build the categorized list of keyboard shortcuts from the current bindings */
|
||||
function buildShortcutCategories(
|
||||
bindings: Record<string, string>,
|
||||
t: (key: string, params?: Record<string, string | number>) => string,
|
||||
): ShortcutCategory[] {
|
||||
return [
|
||||
{
|
||||
title: t("agentManager.shortcuts.category.quickSwitch"),
|
||||
shortcuts: [
|
||||
{
|
||||
label: t("agentManager.shortcuts.jumpToItem"),
|
||||
binding: (() => {
|
||||
const first = bindings.jumpTo1 ?? ""
|
||||
const prefix = first.replace(/\d+$/, "")
|
||||
return prefix ? `${prefix}1-9` : ""
|
||||
})(),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: t("agentManager.shortcuts.category.sidebar"),
|
||||
shortcuts: [
|
||||
{ label: t("agentManager.shortcuts.previousItem"), binding: bindings.previousSession ?? "" },
|
||||
{ label: t("agentManager.shortcuts.nextItem"), binding: bindings.nextSession ?? "" },
|
||||
{ label: t("agentManager.shortcuts.newWorktree"), binding: bindings.newWorktree ?? "" },
|
||||
{ label: t("agentManager.shortcuts.advancedWorktree"), binding: bindings.advancedWorktree ?? "" },
|
||||
{ label: t("agentManager.shortcuts.deleteWorktree"), binding: bindings.closeWorktree ?? "" },
|
||||
{ label: t("agentManager.shortcuts.openWorktree"), binding: bindings.openWorktree ?? "" },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: t("agentManager.shortcuts.category.tabs"),
|
||||
shortcuts: [
|
||||
{ label: t("agentManager.shortcuts.previousTab"), binding: bindings.previousTab ?? "" },
|
||||
{ label: t("agentManager.shortcuts.nextTab"), binding: bindings.nextTab ?? "" },
|
||||
{ label: t("agentManager.shortcuts.newTab"), binding: bindings.newTab ?? "" },
|
||||
{ label: t("agentManager.shortcuts.closeTab"), binding: bindings.closeTab ?? "" },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: t("agentManager.shortcuts.category.terminal"),
|
||||
shortcuts: [
|
||||
{ label: t("agentManager.shortcuts.toggleTerminal"), binding: bindings.showTerminal ?? "" },
|
||||
{ label: t("agentManager.shortcuts.runScript"), binding: bindings.runScript ?? "" },
|
||||
{ label: t("agentManager.shortcuts.toggleDiff"), binding: bindings.toggleDiff ?? "" },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: t("agentManager.shortcuts.category.global"),
|
||||
shortcuts: [
|
||||
{ label: t("agentManager.shortcuts.openAgentManager"), binding: bindings.agentManagerOpen ?? "" },
|
||||
{ label: t("agentManager.shortcuts.cycleAgentMode"), binding: bindings.cycleAgentMode ?? "" },
|
||||
{ label: t("agentManager.shortcuts.cyclePreviousAgentMode"), binding: bindings.cyclePreviousAgentMode ?? "" },
|
||||
{ label: t("agentManager.shortcuts.showShortcuts"), binding: bindings.showShortcuts ?? "" },
|
||||
].filter((s) => s.binding),
|
||||
},
|
||||
].filter((c) => c.shortcuts.length > 0)
|
||||
}
|
||||
|
||||
import { parseBindingTokens } from "./keybind-tokens"
|
||||
|
||||
const AgentManagerContent: Component = () => {
|
||||
@@ -1003,6 +934,27 @@ const AgentManagerContent: Component = () => {
|
||||
setReviewActive(remembered === REVIEW_TAB_ID && reviewOpenByContext()[worktreeId] === true)
|
||||
}
|
||||
|
||||
const addSessionToCurrentWorktree = (sid: string) => {
|
||||
const sel = selection()
|
||||
if (!sel || sel === LOCAL) return false
|
||||
const current = managedSessions().find((entry) => entry.id === sid)
|
||||
if (current?.worktreeId) return focusManagedSession(current.worktreeId, sid)
|
||||
saveTabMemory()
|
||||
setHistory(false)
|
||||
setReviewActive(false)
|
||||
appendToTabOrder(sel, sid)
|
||||
evictLocal(sid)
|
||||
vscode.postMessage({ type: "agentManager.addSessionToWorktree", worktreeId: sel, sessionId: sid })
|
||||
return true
|
||||
}
|
||||
|
||||
const focusManagedSession = (worktreeId: string, sid: string) => {
|
||||
selectWorktree(worktreeId)
|
||||
setHistory(false)
|
||||
session.selectSession(sid)
|
||||
return true
|
||||
}
|
||||
|
||||
const cycleAgent = (direction: 1 | -1) => {
|
||||
const available = session.agents().filter((a) => a.mode !== "subagent" && !a.hidden)
|
||||
if (available.length <= 1) return
|
||||
@@ -1220,6 +1172,7 @@ const AgentManagerContent: Component = () => {
|
||||
saveTabMemory()
|
||||
appendToTabOrder(ev.worktreeId, ev.sessionId)
|
||||
setSelection(ev.worktreeId)
|
||||
evictLocal(ev.sessionId)
|
||||
drafts.apply(ev.worktreeId, ev.sessionId)
|
||||
session.selectSession(ev.sessionId)
|
||||
}
|
||||
@@ -2987,6 +2940,7 @@ const AgentManagerContent: Component = () => {
|
||||
<Show when={history()}>
|
||||
<HistoryView
|
||||
onSelectSession={(id) => {
|
||||
if (addSessionToCurrentWorktree(id)) return
|
||||
setHistory(false)
|
||||
if (localSessionIDs().includes(id)) {
|
||||
saveTabMemory()
|
||||
@@ -3024,6 +2978,7 @@ const AgentManagerContent: Component = () => {
|
||||
<div class="am-chat-wrapper">
|
||||
<ChatView
|
||||
onSelectSession={(id) => {
|
||||
if (addSessionToCurrentWorktree(id)) return
|
||||
if (localSessionIDs().includes(id)) {
|
||||
session.selectSession(id)
|
||||
if (selection() === null) setSelection(LOCAL)
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
export interface ShortcutEntry {
|
||||
label: string
|
||||
binding: string
|
||||
}
|
||||
|
||||
export interface ShortcutCategory {
|
||||
title: string
|
||||
shortcuts: ShortcutEntry[]
|
||||
}
|
||||
|
||||
export function buildShortcutCategories(
|
||||
bindings: Record<string, string>,
|
||||
t: (key: string, params?: Record<string, string | number>) => string,
|
||||
): ShortcutCategory[] {
|
||||
return [
|
||||
{
|
||||
title: t("agentManager.shortcuts.category.quickSwitch"),
|
||||
shortcuts: [
|
||||
{
|
||||
label: t("agentManager.shortcuts.jumpToItem"),
|
||||
binding: (() => {
|
||||
const first = bindings.jumpTo1 ?? ""
|
||||
const prefix = first.replace(/\d+$/, "")
|
||||
return prefix ? `${prefix}1-9` : ""
|
||||
})(),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: t("agentManager.shortcuts.category.sidebar"),
|
||||
shortcuts: [
|
||||
{ label: t("agentManager.shortcuts.previousItem"), binding: bindings.previousSession ?? "" },
|
||||
{ label: t("agentManager.shortcuts.nextItem"), binding: bindings.nextSession ?? "" },
|
||||
{ label: t("agentManager.shortcuts.newWorktree"), binding: bindings.newWorktree ?? "" },
|
||||
{ label: t("agentManager.shortcuts.advancedWorktree"), binding: bindings.advancedWorktree ?? "" },
|
||||
{ label: t("agentManager.shortcuts.deleteWorktree"), binding: bindings.closeWorktree ?? "" },
|
||||
{ label: t("agentManager.shortcuts.openWorktree"), binding: bindings.openWorktree ?? "" },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: t("agentManager.shortcuts.category.tabs"),
|
||||
shortcuts: [
|
||||
{ label: t("agentManager.shortcuts.previousTab"), binding: bindings.previousTab ?? "" },
|
||||
{ label: t("agentManager.shortcuts.nextTab"), binding: bindings.nextTab ?? "" },
|
||||
{ label: t("agentManager.shortcuts.newTab"), binding: bindings.newTab ?? "" },
|
||||
{ label: t("agentManager.shortcuts.closeTab"), binding: bindings.closeTab ?? "" },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: t("agentManager.shortcuts.category.terminal"),
|
||||
shortcuts: [
|
||||
{ label: t("agentManager.shortcuts.toggleTerminal"), binding: bindings.showTerminal ?? "" },
|
||||
{ label: t("agentManager.shortcuts.runScript"), binding: bindings.runScript ?? "" },
|
||||
{ label: t("agentManager.shortcuts.toggleDiff"), binding: bindings.toggleDiff ?? "" },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: t("agentManager.shortcuts.category.global"),
|
||||
shortcuts: [
|
||||
{ label: t("agentManager.shortcuts.openAgentManager"), binding: bindings.agentManagerOpen ?? "" },
|
||||
{ label: t("agentManager.shortcuts.cycleAgentMode"), binding: bindings.cycleAgentMode ?? "" },
|
||||
{ label: t("agentManager.shortcuts.cyclePreviousAgentMode"), binding: bindings.cyclePreviousAgentMode ?? "" },
|
||||
{ label: t("agentManager.shortcuts.showShortcuts"), binding: bindings.showShortcuts ?? "" },
|
||||
].filter((s) => s.binding),
|
||||
},
|
||||
].filter((c) => c.shortcuts.length > 0)
|
||||
}
|
||||
@@ -29,6 +29,7 @@ export interface SendMessageRequest {
|
||||
variant?: string
|
||||
files?: FileAttachment[]
|
||||
agentManagerContext?: string
|
||||
contextDirectory?: string
|
||||
}
|
||||
|
||||
export interface AbortRequest {
|
||||
@@ -240,6 +241,7 @@ export interface SendCommandRequest {
|
||||
variant?: string
|
||||
files?: FileAttachment[]
|
||||
agentManagerContext?: string
|
||||
contextDirectory?: string
|
||||
}
|
||||
|
||||
export interface RemoveSkillMessage {
|
||||
@@ -479,6 +481,7 @@ export interface OpenLocallyRequest {
|
||||
export interface AddSessionToWorktreeRequest {
|
||||
type: "agentManager.addSessionToWorktree"
|
||||
worktreeId: string
|
||||
sessionId?: string
|
||||
}
|
||||
|
||||
// Fork an existing session (copies conversation history)
|
||||
|
||||
Reference in New Issue
Block a user