mirror of
https://gh-proxy.org/https://github.com/klinsmaya/multica.git
synced 2026-11-08 01:16:45 +08:00
* fix(opencode): retire future-dated resume sessions (MUL-8174) Co-authored-by: multica-agent <[email protected]> * fix(opencode): check resume clocks across CLI versions Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: Sol-Boy <[email protected]> Co-authored-by: multica-agent <[email protected]>
101 lines
4.7 KiB
Go
101 lines
4.7 KiB
Go
package handler
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
|
|
"github.com/jackc/pgx/v5/pgtype"
|
|
"github.com/multica-ai/multica/server/internal/testutil"
|
|
db "github.com/multica-ai/multica/server/pkg/db/generated"
|
|
)
|
|
|
|
func TestOpenCodeClockSkewResumeQueries(t *testing.T) {
|
|
if testHandler == nil {
|
|
t.Skip("database not available")
|
|
}
|
|
ctx := context.Background()
|
|
agentID, runtimeID, _ := createRuntimeGuardAgent(t, ctx)
|
|
issueID := dbfx.Issue(t, "clock-skew resume filtering")
|
|
chatID := dbfx.ChatSession(t, agentID)
|
|
for _, scope := range []struct{ name, column, id string }{
|
|
{"issue", "issue_id", issueID}, {"chat", "chat_session_id", chatID},
|
|
} {
|
|
t.Run(scope.name, func(t *testing.T) {
|
|
for _, history := range []struct{ session, status, reason, age string }{
|
|
{"healthy", "completed", "", "4 minutes"},
|
|
{"skewed", "completed", "", "3 minutes"},
|
|
{"skewed", "failed", "opencode_clock_skew", "2 minutes"},
|
|
} {
|
|
dbfx.Task(t, agentID, testutil.Cols{scope.column: scope.id, "runtime_id": runtimeID, "session_id": history.session, "status": history.status, "failure_reason": history.reason, "work_dir": "/clock-workdir", "completed_at": testutil.Raw("now() - interval '" + history.age + "'")})
|
|
}
|
|
if scope.name == "issue" {
|
|
got, err := testHandler.Queries.GetLastTaskSession(ctx, db.GetLastTaskSessionParams{AgentID: parseUUID(agentID), IssueID: parseUUID(issueID)})
|
|
if err != nil || got.SessionID.String != "healthy" {
|
|
t.Fatalf("issue resumed poisoned history: %+v, %v", got, err)
|
|
}
|
|
} else {
|
|
got, err := testHandler.Queries.GetLastChatTaskSession(ctx, db.GetLastChatTaskSessionParams{ChatSessionID: parseUUID(chatID)})
|
|
if err != nil || got.SessionID.String != "healthy" {
|
|
t.Fatalf("chat resumed poisoned history: %+v, %v", got, err)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestOpenCodeClockSkewFailureClearsChatPointer(t *testing.T) {
|
|
if testHandler == nil {
|
|
t.Skip("database not available")
|
|
}
|
|
ctx := context.Background()
|
|
agentID, runtimeID, _ := createRuntimeGuardAgent(t, ctx)
|
|
chatID := dbfx.ChatSession(t, agentID, testutil.Cols{"session_id": "skewed", "work_dir": "/clock-workdir", "runtime_id": runtimeID})
|
|
taskID := dbfx.Task(t, agentID, testutil.Cols{"chat_session_id": chatID, "runtime_id": runtimeID, "status": "running", "session_id": "skewed"})
|
|
_, err := testHandler.TaskService.FailTask(ctx, parseUUID(taskID), "opencode clock skew: session ordering is unreliable", "skewed", "/clock-workdir", "", "opencode_clock_skew", false, "skewed", "")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var pointer pgtype.Text
|
|
dbfx.QueryRow(t, `SELECT session_id FROM chat_session WHERE id = $1`, chatID).Scan(&pointer)
|
|
if pointer.Valid {
|
|
t.Fatalf("failed callback restored retired chat pointer: %+v", pointer)
|
|
}
|
|
}
|
|
|
|
func TestOpenCodeClockRetirementSurvivesManualRetryOfOldTask(t *testing.T) {
|
|
if testHandler == nil {
|
|
t.Skip("database not available")
|
|
}
|
|
ctx := context.Background()
|
|
agentID, runtimeID, daemonID := createRuntimeGuardAgent(t, ctx)
|
|
issueID := dbfx.Issue(t, "manual retry after clock preflight retirement")
|
|
sourceID := dbfx.Task(t, agentID, testutil.Cols{"issue_id": issueID, "runtime_id": runtimeID, "status": "completed", "session_id": "ses-old", "work_dir": "/clock-workdir"})
|
|
dbfx.Task(t, agentID, testutil.Cols{"issue_id": issueID, "runtime_id": runtimeID, "status": "completed", "session_id": "ses-new", "retired_session_id": "ses-old"})
|
|
dbfx.Task(t, agentID, testutil.Cols{"issue_id": issueID, "runtime_id": runtimeID, "rerun_of_task_id": sourceID, "force_fresh_session": true, "priority": 1000})
|
|
got := claimTaskForRuntimeGuard(t, runtimeID, daemonID)
|
|
if got.PriorSessionID != "" || got.PriorWorkDir != "/clock-workdir" {
|
|
t.Fatalf("manual retry bypassed retirement: %+v", got)
|
|
}
|
|
}
|
|
|
|
func TestOpenCodeClockFailureReleasesIssueQueue(t *testing.T) {
|
|
if testHandler == nil {
|
|
t.Skip("database not available")
|
|
}
|
|
ctx := context.Background()
|
|
agentID, runtimeID, daemonID := createRuntimeGuardAgent(t, ctx)
|
|
issueID := dbfx.Issue(t, "clock-skew terminal releases queue")
|
|
taskID := dbfx.Task(t, agentID, testutil.Cols{"issue_id": issueID, "runtime_id": runtimeID, "status": "running", "session_id": "skewed"})
|
|
nextID := dbfx.Task(t, agentID, testutil.Cols{"issue_id": issueID, "runtime_id": runtimeID, "priority": 1000})
|
|
_, err := testHandler.TaskService.FailTask(ctx, parseUUID(taskID), "fresh OpenCode launch failed", "", "/clock-workdir", "", "agent_error.process_failure", false, "skewed", "")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
got := claimTaskForRuntimeGuard(t, runtimeID, daemonID)
|
|
var status string
|
|
dbfx.QueryRow(t, `SELECT status FROM agent_task_queue WHERE id = $1`, nextID).Scan(&status)
|
|
if status != "dispatched" || got.PriorSessionID != "" {
|
|
t.Fatalf("queue did not recover with a clean session: %+v", got)
|
|
}
|
|
}
|