Pusti: premešta task ready→active bez pokretanja claude sesije

- handleRunTask samo premešta task iz ready/ u active/ sa timestampom
- Uklonjena zavisnost od console sesija — konzola je nezavisna
- Korisnik pokreće claude ručno iz konzole terminala
- Ažurirani testovi (6 RunTask testova prolaze)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
djuka 2026-02-21 04:09:30 +00:00
parent 932ffe5203
commit ac72ca6f52
2 changed files with 29 additions and 64 deletions

View File

@ -123,6 +123,9 @@ func (s *Server) setupRoutes() {
s.Router.GET("/console/history/:session", s.handleConsoleHistory) s.Router.GET("/console/history/:session", s.handleConsoleHistory)
s.Router.GET("/console/ws/:session", s.handleConsoleWS) s.Router.GET("/console/ws/:session", s.handleConsoleWS)
// Logs route
s.Router.GET("/api/logs/tail", s.handleLogsTail)
// Docs routes // Docs routes
s.Router.GET("/docs", s.handleDocsList) s.Router.GET("/docs", s.handleDocsList)
s.Router.GET("/docs/*path", s.handleDocsView) s.Router.GET("/docs/*path", s.handleDocsView)
@ -346,54 +349,19 @@ func (s *Server) handleRunTask(c *gin.Context) {
task.Status = "ready" task.Status = "ready"
} }
// Find free session // Move ready → active
sessionIdx := -1 if err := supervisor.MoveTask(s.Config.TasksDir, id, "ready", "active"); err != nil {
for i := 0; i < 2; i++ { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
sess := s.console.getSession(i)
sess.mu.Lock()
if sess.status == "idle" {
sessionIdx = i
sess.mu.Unlock()
break
}
sess.mu.Unlock()
}
if sessionIdx == -1 {
c.JSON(http.StatusConflict, gin.H{"error": "obe sesije su zauzete"})
return return
} }
// Build the prompt
prompt := "Pročitaj CLAUDE.md u root-u projekta. Tvoj task: TASKS/ready/" + id + ".md — Pročitaj task fajl i uradi šta piše. Prati pravila iz CLAUDE.md."
// Start in the session
session := s.console.getSession(sessionIdx)
execID := s.console.nextExecID()
session.mu.Lock()
session.status = "running"
session.execID = execID
session.taskID = id
session.output = nil
session.history = append(session.history, historyEntry{
Command: "pusti " + id,
ExecID: execID,
Timestamp: timeNow(),
Status: "running",
})
session.mu.Unlock()
// Append "Pokrenut" timestamp // Append "Pokrenut" timestamp
taskPath := filepath.Join(s.Config.TasksDir, "ready", id+".md") taskPath := filepath.Join(s.Config.TasksDir, "active", id+".md")
appendTimestamp(taskPath, "Pokrenut") appendTimestamp(taskPath, "Pokrenut (→active)")
go s.runCommand(session, prompt, execID)
c.JSON(http.StatusOK, gin.H{ c.JSON(http.StatusOK, gin.H{
"status": "started", "status": "started",
"session": sessionIdx + 1, "task": id,
"exec_id": execID,
}) })
} }

View File

@ -809,8 +809,12 @@ func TestRunTask_Ready(t *testing.T) {
if resp["status"] != "started" { if resp["status"] != "started" {
t.Errorf("expected status started, got %v", resp["status"]) t.Errorf("expected status started, got %v", resp["status"])
} }
if resp["session"] == nil { if resp["task"] != "T08" {
t.Error("expected session number in response") t.Errorf("expected task T08, got %v", resp["task"])
}
// Verify task moved to active/
if _, err := os.Stat(filepath.Join(srv.Config.TasksDir, "active", "T08.md")); err != nil {
t.Error("expected T08.md in active/")
} }
} }
@ -853,7 +857,7 @@ func TestRunTask_NotFound(t *testing.T) {
} }
} }
func TestRunTask_BothSessionsBusy(t *testing.T) { func TestRunTask_MovesToActive(t *testing.T) {
srv := setupTestServer(t) srv := setupTestServer(t)
// Move T08 to ready // Move T08 to ready
@ -862,30 +866,18 @@ func TestRunTask_BothSessionsBusy(t *testing.T) {
filepath.Join(srv.Config.TasksDir, "ready", "T08.md"), filepath.Join(srv.Config.TasksDir, "ready", "T08.md"),
) )
// Occupy both sessions
srv.console.sessions[0].mu.Lock()
srv.console.sessions[0].status = "running"
srv.console.sessions[0].mu.Unlock()
srv.console.sessions[1].mu.Lock()
srv.console.sessions[1].status = "running"
srv.console.sessions[1].mu.Unlock()
req := httptest.NewRequest(http.MethodPost, "/task/T08/run", nil) req := httptest.NewRequest(http.MethodPost, "/task/T08/run", nil)
w := httptest.NewRecorder() w := httptest.NewRecorder()
srv.Router.ServeHTTP(w, req) srv.Router.ServeHTTP(w, req)
if w.Code != http.StatusConflict { if w.Code != http.StatusOK {
t.Fatalf("expected 409 when both sessions busy, got %d: %s", w.Code, w.Body.String()) t.Fatalf("expected 200, got %d: %s", w.Code, w.Body.String())
} }
// Clean up // Verify task moved to active/
srv.console.sessions[0].mu.Lock() if _, err := os.Stat(filepath.Join(srv.Config.TasksDir, "active", "T08.md")); err != nil {
srv.console.sessions[0].status = "idle" t.Error("expected T08.md in active/ after run")
srv.console.sessions[0].mu.Unlock() }
srv.console.sessions[1].mu.Lock()
srv.console.sessions[1].status = "idle"
srv.console.sessions[1].mu.Unlock()
} }
func TestDashboardHTML_HasRunButton(t *testing.T) { func TestDashboardHTML_HasRunButton(t *testing.T) {
@ -1865,11 +1857,16 @@ func TestRunTask_AddsTimestamp(t *testing.T) {
t.Fatalf("expected 200, got %d: %s", w.Code, w.Body.String()) t.Fatalf("expected 200, got %d: %s", w.Code, w.Body.String())
} }
content, _ := os.ReadFile(filepath.Join(srv.Config.TasksDir, "ready", "T08.md")) // Task should now be in active/
content, _ := os.ReadFile(filepath.Join(srv.Config.TasksDir, "active", "T08.md"))
text := string(content) text := string(content)
if !containsStr(text, "Pokrenut") { if !containsStr(text, "Pokrenut") {
t.Error("expected 'Pokrenut' timestamp after run") t.Error("expected 'Pokrenut' timestamp after run")
} }
// Verify it's no longer in ready/
if _, err := os.Stat(filepath.Join(srv.Config.TasksDir, "ready", "T08.md")); err == nil {
t.Error("task should no longer be in ready/")
}
} }
// --- T24: PTY tests --- // --- T24: PTY tests ---