feat: add git worktree session support with improved UI

Implement comprehensive worktree session support allowing users to spawn sessions in temporary git worktrees. Includes backend worktree management, full-stack integration, and refined UI for session type selection.

Backend:
- Add worktree creation/removal utilities with branch management
- Track worktree metadata (basePath, branch, name, path) in session metadata
- Automatic cleanup of worktrees when sessions fail or exit
- Enhanced error handling with stderr tail logging

UI improvements:
- Redesign session type toggle with improved alignment and spacing
- Move worktree description inline with label for cleaner layout
- Add branch name input field that appears when worktree mode selected
- Auto-focus on worktree input when switching modes
- Reduce gap between radio options from gap-3 to gap-1.5
- Update descriptive text and placeholders for clarity

Integration:
- Thread worktree parameters through API client, RPC handlers, and daemon
- Add worktreeEnv utility to read worktree info from environment
- Update session spawning to support both simple and worktree modes
This commit is contained in:
weishu
2025-12-28 13:17:58 +08:00
parent 0eab45c49c
commit c35a5ce827
21 changed files with 605 additions and 69 deletions
+4 -2
View File
@@ -70,7 +70,7 @@ export class ApiMachineClient {
setRPCHandlers({ spawnSession, stopSession, requestShutdown }: MachineRpcHandlers): void {
this.rpcHandlerManager.registerHandler('spawn-happy-session', async (params: any) => {
const { directory, sessionId, machineId, approvedNewDirectoryCreation, agent, yolo, token } = params || {}
const { directory, sessionId, machineId, approvedNewDirectoryCreation, agent, yolo, token, sessionType, worktreeName } = params || {}
if (!directory) {
throw new Error('Directory is required')
@@ -83,7 +83,9 @@ export class ApiMachineClient {
approvedNewDirectoryCreation,
agent,
yolo,
token
token,
sessionType,
worktreeName
})
switch (result.type) {
+4 -2
View File
@@ -51,7 +51,10 @@ export class RpcHandlerManager {
const result = await handler(params as any)
return JSON.stringify(result)
} catch (error) {
this.logger('[RPC] [ERROR] Error handling request', { error })
const details = error instanceof Error
? { message: error.message, stack: error.stack }
: { error: String(error) }
this.logger('[RPC] [ERROR] Error handling request', details)
return JSON.stringify({
error: error instanceof Error ? error.message : 'Unknown error'
})
@@ -91,4 +94,3 @@ export class RpcHandlerManager {
export function createRpcHandlerManager(config: RpcHandlerConfig): RpcHandlerManager {
return new RpcHandlerManager(config)
}
+15 -1
View File
@@ -41,6 +41,13 @@ export type Metadata = {
archivedBy?: string
archiveReason?: string
flavor?: string
worktree?: {
basePath: string
branch: string
name: string
worktreePath?: string
createdAt?: number
}
}
export const MetadataSchema = z.object({
@@ -69,7 +76,14 @@ export const MetadataSchema = z.object({
lifecycleStateSince: z.number().optional(),
archivedBy: z.string().optional(),
archiveReason: z.string().optional(),
flavor: z.string().optional()
flavor: z.string().optional(),
worktree: z.object({
basePath: z.string(),
branch: z.string(),
name: z.string(),
worktreePath: z.string().optional(),
createdAt: z.number().optional()
}).optional()
}).passthrough()
export type AgentState = {