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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions cmd/task/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -524,7 +524,7 @@ Examples:
}

// Validate executor if provided
validExecutors := []string{db.ExecutorClaude, db.ExecutorCodex, db.ExecutorGemini, db.ExecutorPi, db.ExecutorOpenCode, db.ExecutorOpenClaw}
validExecutors := []string{db.ExecutorClaude, db.ExecutorCodex, db.ExecutorGemini, db.ExecutorPi, db.ExecutorOpenCode, db.ExecutorOpenClaw, db.ExecutorVibe}
if taskExecutor != "" {
validExecutor := false
for _, e := range validExecutors {
Expand Down Expand Up @@ -626,7 +626,7 @@ Examples:
createCmd.Flags().String("body", "", "Task body/description (if no title, AI generates from body)")
createCmd.Flags().StringP("type", "t", "", "Task type: code, writing, thinking (default: code)")
createCmd.Flags().StringP("project", "p", "", "Project name (auto-detected from cwd if not specified)")
createCmd.Flags().StringP("executor", "e", "", "Task executor: claude, codex, gemini, pi, opencode, openclaw (default: claude)")
createCmd.Flags().StringP("executor", "e", "", "Task executor: claude, codex, gemini, pi, opencode, openclaw, vibe (default: claude)")
createCmd.Flags().BoolP("execute", "x", false, "Queue task for immediate execution")
createCmd.Flags().String("tags", "", "Task tags (comma-separated)")
createCmd.Flags().Bool("pinned", false, "Pin the task to the top of its column")
Expand Down Expand Up @@ -1210,7 +1210,7 @@ Examples:

// Validate executor if provided
if taskExecutor != "" {
validExecutors := []string{db.ExecutorClaude, db.ExecutorCodex, db.ExecutorGemini, db.ExecutorPi, db.ExecutorOpenCode, db.ExecutorOpenClaw}
validExecutors := []string{db.ExecutorClaude, db.ExecutorCodex, db.ExecutorGemini, db.ExecutorPi, db.ExecutorOpenCode, db.ExecutorOpenClaw, db.ExecutorVibe}
validExecutor := false
for _, e := range validExecutors {
if e == taskExecutor {
Expand Down Expand Up @@ -1269,7 +1269,7 @@ Examples:
updateCmd.Flags().String("body", "", "Update task body/description")
updateCmd.Flags().StringP("type", "t", "", "Update task type: code, writing, thinking")
updateCmd.Flags().StringP("project", "p", "", "Update project name")
updateCmd.Flags().StringP("executor", "e", "", "Update task executor: claude, codex, gemini, pi, opencode, openclaw")
updateCmd.Flags().StringP("executor", "e", "", "Update task executor: claude, codex, gemini, pi, opencode, openclaw, vibe")
updateCmd.Flags().String("tags", "", "Update task tags (comma-separated)")
updateCmd.Flags().Bool("pinned", false, "Pin or unpin the task")
rootCmd.AddCommand(updateCmd)
Expand Down
1 change: 1 addition & 0 deletions internal/db/tasks.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ const (
ExecutorOpenClaw = "openclaw" // OpenClaw AI assistant (https://openclaw.ai)
ExecutorOpenCode = "opencode" // OpenCode AI assistant (https://opencode.ai)
ExecutorPi = "pi" // Pi coding agent (https://github.com/mariozechner/pi-coding-agent)
ExecutorVibe = "vibe" // Mistral Vibe executor
)

// DefaultExecutor returns the default executor if none is specified.
Expand Down
9 changes: 8 additions & 1 deletion internal/executor/dangerous_mode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,13 @@ func TestExecutorInterfaceImplementation(t *testing.T) {
supportsDangerousMode: false, // Pi does not support dangerous mode
dangerousFlag: "",
},
{
name: "Vibe executor",
executorName: db.ExecutorVibe,
supportsSessionResume: false, // Vibe does not support session resume
supportsDangerousMode: true, // Vibe supports --dangerous flag
dangerousFlag: "--dangerous",
},
}

for _, tt := range tests {
Expand Down Expand Up @@ -1021,7 +1028,7 @@ func TestBuildCommandIncludesEnvironmentVariables(t *testing.T) {
WorktreePath: "/home/user/projects/myapp/.task-worktrees/42-fix-bug",
}

executors := []string{db.ExecutorClaude, db.ExecutorCodex, db.ExecutorGemini, db.ExecutorOpenClaw, db.ExecutorOpenCode}
executors := []string{db.ExecutorClaude, db.ExecutorCodex, db.ExecutorGemini, db.ExecutorOpenClaw, db.ExecutorOpenCode, db.ExecutorVibe}

for _, name := range executors {
t.Run(name, func(t *testing.T) {
Expand Down
86 changes: 86 additions & 0 deletions internal/executor/executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ func New(database *db.DB, cfg *config.Config) *Executor {
e.executorFactory.Register(NewOpenClawExecutor(e))
e.executorFactory.Register(NewOpenCodeExecutor(e))
e.executorFactory.Register(NewPiExecutor(e))
e.executorFactory.Register(NewVibeExecutor(e))

return e
}
Expand Down Expand Up @@ -207,6 +208,7 @@ func NewWithLogging(database *db.DB, cfg *config.Config, w io.Writer) *Executor
e.executorFactory.Register(NewOpenClawExecutor(e))
e.executorFactory.Register(NewOpenCodeExecutor(e))
e.executorFactory.Register(NewPiExecutor(e))
e.executorFactory.Register(NewVibeExecutor(e))

return e
}
Expand Down Expand Up @@ -2994,6 +2996,90 @@ func (e *Executor) resumeGeminiWithMode(task *db.Task, workDir string, dangerous
return true
}

// resumeVibeWithMode kills the current Vibe process and restarts with the specified mode.
// If dangerousMode is true, uses --dangerous flag.
func (e *Executor) resumeVibeWithMode(task *db.Task, workDir string, dangerousMode bool) bool {
taskID := task.ID
paths := e.claudePathsForProject(task.Project)

modeStr := "safe"
if dangerousMode {
modeStr = "dangerous"
}
e.logLine(taskID, "system", fmt.Sprintf("Restarting Vibe in %s mode", modeStr))

if _, err := exec.LookPath("tmux"); err != nil {
e.logLine(taskID, "system", "Tmux not available - cannot resume")
return false
}

windowName := TmuxWindowName(taskID)
// Kill ALL existing windows with this name (handles duplicates)
killAllWindowsByNameAllSessions(windowName)

daemonSession, err := ensureTmuxDaemon()
if err != nil {
e.logger.Warn("could not create task-daemon session", "error", err)
return false
}

windowTarget := fmt.Sprintf("%s:%s", daemonSession, windowName)

taskSessionID := os.Getenv("WORKTREE_SESSION_ID")
if taskSessionID == "" {
taskSessionID = fmt.Sprintf("%d", os.Getpid())
}

// Build dangerous flag
dangerousFlag := ""
if dangerousMode {
flag := strings.TrimSpace(os.Getenv("VIBE_DANGEROUS_ARGS"))
if flag == "" {
flag = "--dangerous"
}
dangerousFlag = flag + " "
}

// Build script - Vibe doesn't support session resume, so we start fresh
envPrefix := claudeEnvPrefix(paths.configDir)
script := fmt.Sprintf(`WORKTREE_TASK_ID=%d WORKTREE_SESSION_ID=%s WORKTREE_PORT=%d WORKTREE_PATH=%q %svibe %s`,
taskID, taskSessionID, task.Port, task.WorktreePath, envPrefix, dangerousFlag)

actualSession, tmuxErr := createTmuxWindow(daemonSession, windowName, workDir, script, e.getProjectDir(task.Project))
if tmuxErr != nil {
e.logger.Warn("tmux failed to create window", "error", tmuxErr, "session", daemonSession)
return false
}

if actualSession != daemonSession {
windowTarget = fmt.Sprintf("%s:%s", actualSession, windowName)
daemonSession = actualSession
}

time.Sleep(200 * time.Millisecond)

if err := e.db.UpdateTaskDaemonSession(taskID, daemonSession); err != nil {
e.logger.Warn("failed to save daemon session", "task", taskID, "error", err)
}

if windowID := getWindowID(daemonSession, windowName); windowID != "" {
if err := e.db.UpdateTaskWindowID(taskID, windowID); err != nil {
e.logger.Warn("failed to save window ID", "task", taskID, "error", err)
}
}

e.ensureShellPane(windowTarget, workDir, task.ID, task.Port, task.WorktreePath, paths.configDir)
e.configureTmuxWindow(windowTarget)

if err := e.db.UpdateTaskDangerousMode(taskID, dangerousMode); err != nil {
e.logger.Warn("could not update task dangerous mode", "error", err)
}

e.logLine(taskID, "system", fmt.Sprintf("Vibe restarted in %s mode", modeStr))

return true
}

// FindClaudeSessionID finds the most recent claude session ID for a workDir using the default config dir.
// Exported for use by the UI to check for resumable sessions.
func FindClaudeSessionID(workDir string) string {
Expand Down
Loading
Loading