* refactor: decouple task lifecycle from issue status, add daemon health server - Remove automatic issue status changes from StartTask (in_progress), CompleteTask (in_review), and FailTask (blocked) in task service. Issue status is now fully managed by the agent via `multica issue status`. - Update agent prompt and meta skill to instruct agents to manage issue status themselves (in_progress → done/in_review/blocked). - Add daemon health HTTP server on 127.0.0.1:19514 with /health endpoint exposing pid, uptime, agents, and workspaces. Fail fast if port is taken (another daemon already running). - Update `multica status` to check both server and daemon health. - Add Save button to repos section in workspace settings UI. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor(daemon): simplify prompt, fix runtime config path, improve task error logging - Slim down BuildPrompt to a minimal hint; detailed workflow now lives in CLAUDE.md/AGENTS.md - Write CLAUDE.md to workDir root instead of .claude/CLAUDE.md - Fix git-exclude pattern (.claude → CLAUDE.md) - Decouple task queue reconciliation from issue status changes (agents manage status via CLI) - Add diagnostic logging when CompleteTask/FailTask fail due to unexpected task state Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(task): use task_completed/task_failed inbox notification types FailTask was sending "agent_blocked" which conflates agent crash with issue-level blocked status. Align notification types with the new decoupled model: task_completed and task_failed. Update frontend types and labels accordingly. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
85 lines
2.1 KiB
Go
85 lines
2.1 KiB
Go
package daemon
|
|
|
|
import (
|
|
"net/http"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestNormalizeServerBaseURL(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
got, err := NormalizeServerBaseURL("ws://localhost:8080/ws")
|
|
if err != nil {
|
|
t.Fatalf("NormalizeServerBaseURL returned error: %v", err)
|
|
}
|
|
if got != "http://localhost:8080" {
|
|
t.Fatalf("expected http://localhost:8080, got %s", got)
|
|
}
|
|
}
|
|
|
|
func TestBuildPromptContainsIssueID(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
issueID := "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
|
|
prompt := BuildPrompt(Task{
|
|
IssueID: issueID,
|
|
Agent: &AgentData{
|
|
Name: "Local Codex",
|
|
Skills: []SkillData{
|
|
{Name: "Concise", Content: "Be concise."},
|
|
},
|
|
},
|
|
})
|
|
|
|
// Prompt should contain the issue ID and CLI hint.
|
|
for _, want := range []string{
|
|
issueID,
|
|
"multica issue get",
|
|
} {
|
|
if !strings.Contains(prompt, want) {
|
|
t.Fatalf("prompt missing %q", want)
|
|
}
|
|
}
|
|
|
|
// Skills should NOT be inlined in the prompt (they're in runtime config).
|
|
for _, absent := range []string{"## Agent Skills", "Be concise."} {
|
|
if strings.Contains(prompt, absent) {
|
|
t.Fatalf("prompt should NOT contain %q (skills are in runtime config)", absent)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestBuildPromptNoIssueDetails(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
prompt := BuildPrompt(Task{
|
|
IssueID: "test-id",
|
|
Agent: &AgentData{Name: "Test"},
|
|
})
|
|
|
|
// Prompt should not contain issue title/description (agent fetches via CLI).
|
|
for _, absent := range []string{"**Issue:**", "**Summary:**"} {
|
|
if strings.Contains(prompt, absent) {
|
|
t.Fatalf("prompt should NOT contain %q — agent fetches details via CLI", absent)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestIsWorkspaceNotFoundError(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
err := &requestError{
|
|
Method: http.MethodPost,
|
|
Path: "/api/daemon/register",
|
|
StatusCode: http.StatusNotFound,
|
|
Body: `{"error":"workspace not found"}`,
|
|
}
|
|
if !isWorkspaceNotFoundError(err) {
|
|
t.Fatal("expected workspace not found error to be recognized")
|
|
}
|
|
|
|
if isWorkspaceNotFoundError(&requestError{StatusCode: http.StatusInternalServerError, Body: `{"error":"workspace not found"}`}) {
|
|
t.Fatal("did not expect 500 to be treated as workspace not found")
|
|
}
|
|
}
|