Skip to content
Merged
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
29 changes: 20 additions & 9 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,10 @@ func (p *progress) Clear() {
var Version = "dev"

func Execute() error {
// Clear stale next action on every invocation (except check-queue which reads it)
// Clear stale queue files on every invocation (except check-queue which reads them)
if len(os.Args) < 2 || os.Args[1] != "check-queue" {
if root, err := mob.FindRepoRoot(); err == nil {
mob.ClearQueue(root)
mob.ClearAllQueues(root)
}
}

Expand Down Expand Up @@ -741,11 +741,16 @@ func cmdCheckNext(_ []string) error {
return nil // not in a repo, nothing to do
}

next, err := mob.ReadQueuedAction(root)
mobName := mob.CurrentMobName()
if mobName == "" {
return nil // not in a mob, nothing to do
}

next, err := mob.ReadQueuedAction(root, mobName)
if err != nil || next == nil {
return nil // no queued action
}
mob.ClearQueue(root)
mob.ClearQueue(root, mobName)

return executeNextAction(root, next)
}
Expand Down Expand Up @@ -891,7 +896,11 @@ func cmdWriteNext(args []string) error {
}
}

return mob.WriteQueuedAction(root, q)
currentMob := mob.CurrentMobName()
if currentMob == "" {
return fmt.Errorf("codemob queue must be run from inside a mob")
}
return mob.WriteQueuedAction(root, currentMob, q)
}

// launchAgent spawns the agent as a child process and implements the trampoline loop.
Expand All @@ -911,12 +920,13 @@ func launchAgent(root, agent, workdir string, resume bool) error {
mobStatus(fmt.Sprintf("Session ended - mob '%s'", filepath.Base(workdir)))

// Always check for queued action, regardless of how the agent exited
next, err := mob.ReadQueuedAction(root)
mobName := filepath.Base(workdir)
next, err := mob.ReadQueuedAction(root, mobName)
if err != nil || next == nil {
writeLastMob(workdir)
return nil // normal exit
}
mob.ClearQueue(root)
mob.ClearQueue(root, mobName)

newWorkdir, newAgent, newResume, err := resolveNextAction(root, next)
if err != nil {
Expand Down Expand Up @@ -1070,9 +1080,10 @@ func spawnAgent(root, binPath string, args []string, workdir string) error {
}
}()

// Watch for queue.json - auto-terminate agent when a queued action appears
// Watch for per-mob queue file - auto-terminate agent when a queued action appears
if root != "" && filepath.IsAbs(root) {
queuePath := mob.QueueFilePath(root)
mobName := filepath.Base(workdir)
queuePath := mob.QueueFilePath(root, mobName)
go func() {
ticker := time.NewTicker(500 * time.Millisecond)
defer ticker.Stop()
Expand Down
31 changes: 20 additions & 11 deletions internal/mob/next.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ var ValidQueueActions = map[string]bool{
"change-agent": true,
}

const queueFile = ".codemob/queue.json"
const queuesDir = ".codemob/queues"

// QueuedAction represents a pending action to execute after an agent exits.
type QueuedAction struct {
Expand All @@ -25,17 +25,21 @@ type QueuedAction struct {
}

// WriteQueuedAction writes an action for the trampoline to pick up.
func WriteQueuedAction(repoRoot string, action QueuedAction) error {
func WriteQueuedAction(repoRoot, mobName string, action QueuedAction) error {
data, err := json.MarshalIndent(action, "", " ")
if err != nil {
return err
}
return os.WriteFile(filepath.Join(repoRoot, queueFile), append(data, '\n'), 0644)
path := QueueFilePath(repoRoot, mobName)
if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
return err
}
return os.WriteFile(path, append(data, '\n'), 0644)
}

// ReadQueuedAction reads and returns the pending action, if any.
func ReadQueuedAction(repoRoot string) (*QueuedAction, error) {
data, err := os.ReadFile(filepath.Join(repoRoot, queueFile))
func ReadQueuedAction(repoRoot, mobName string) (*QueuedAction, error) {
data, err := os.ReadFile(QueueFilePath(repoRoot, mobName))
if err != nil {
if os.IsNotExist(err) {
return nil, nil // no file = no action
Expand All @@ -52,12 +56,17 @@ func ReadQueuedAction(repoRoot string) (*QueuedAction, error) {
return &action, nil
}

// QueueFilePath returns the absolute path to the queue file.
func QueueFilePath(repoRoot string) string {
return filepath.Join(repoRoot, queueFile)
// QueueFilePath returns the absolute path to the queue file for a given mob.
func QueueFilePath(repoRoot, mobName string) string {
return filepath.Join(repoRoot, queuesDir, mobName+".json")
}

// ClearQueue removes the queued action file for a given mob.
func ClearQueue(repoRoot, mobName string) {
os.Remove(QueueFilePath(repoRoot, mobName))
}

// ClearQueue removes the queued action file.
func ClearQueue(repoRoot string) {
os.Remove(filepath.Join(repoRoot, queueFile))
// ClearAllQueues removes all queued action files.
func ClearAllQueues(repoRoot string) {
os.RemoveAll(filepath.Join(repoRoot, queuesDir))
}
Loading