Add orch strict worktree dispatch
This commit is contained in:
+95
-31
@@ -98,11 +98,12 @@ type ListReadyInput struct {
|
||||
}
|
||||
|
||||
type DispatchInput struct {
|
||||
RunID string
|
||||
TaskID string
|
||||
ToAgent string
|
||||
Body string
|
||||
BaseRef string
|
||||
RunID string
|
||||
TaskID string
|
||||
ToAgent string
|
||||
Body string
|
||||
BaseRef string
|
||||
PrepareWorkspace DispatchWorkspacePreparer
|
||||
}
|
||||
|
||||
type DispatchResult struct {
|
||||
@@ -118,6 +119,16 @@ type ReconcileResult struct {
|
||||
UpdatedTasks []Task `json:"updated_tasks"`
|
||||
}
|
||||
|
||||
type DispatchWorkspace struct {
|
||||
BaseRef string `json:"base_ref,omitempty"`
|
||||
BaseCommit string `json:"base_commit,omitempty"`
|
||||
BranchName string `json:"branch_name,omitempty"`
|
||||
WorktreePath string `json:"worktree_path,omitempty"`
|
||||
WorkspaceStatus string `json:"workspace_status,omitempty"`
|
||||
}
|
||||
|
||||
type DispatchWorkspacePreparer func(task Task, attemptNo int) (DispatchWorkspace, func(), error)
|
||||
|
||||
type BlockedTask struct {
|
||||
Task Task `json:"task"`
|
||||
Attempt TaskAttempt `json:"attempt"`
|
||||
@@ -473,9 +484,29 @@ func (s *OrchStore) DispatchTask(ctx context.Context, input DispatchInput) (Disp
|
||||
}
|
||||
|
||||
attemptNo := task.LatestAttemptNo + 1
|
||||
workspace := DispatchWorkspace{
|
||||
BaseRef: strings.TrimSpace(input.BaseRef),
|
||||
}
|
||||
cleanupWorkspace := func() {}
|
||||
workspaceCommitted := false
|
||||
if input.PrepareWorkspace != nil {
|
||||
workspace, cleanupWorkspace, err = input.PrepareWorkspace(task, attemptNo)
|
||||
if err != nil {
|
||||
return DispatchResult{}, err
|
||||
}
|
||||
if cleanupWorkspace == nil {
|
||||
cleanupWorkspace = func() {}
|
||||
}
|
||||
defer func() {
|
||||
if !workspaceCommitted {
|
||||
cleanupWorkspace()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
threadID := newID("thr")
|
||||
messageID := newID("msg")
|
||||
payloadJSON := buildDispatchPayload(task, attemptNo, input.BaseRef)
|
||||
payloadJSON := buildDispatchPayload(task, attemptNo, workspace)
|
||||
thread := Thread{
|
||||
ThreadID: threadID,
|
||||
RunID: task.RunID,
|
||||
@@ -541,15 +572,19 @@ func (s *OrchStore) DispatchTask(ctx context.Context, input DispatchInput) (Disp
|
||||
}
|
||||
|
||||
attempt := TaskAttempt{
|
||||
RunID: task.RunID,
|
||||
TaskID: task.TaskID,
|
||||
AttemptNo: attemptNo,
|
||||
AssignedTo: assignedTo,
|
||||
ThreadID: threadID,
|
||||
BaseRef: strings.TrimSpace(input.BaseRef),
|
||||
Status: "dispatched",
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
RunID: task.RunID,
|
||||
TaskID: task.TaskID,
|
||||
AttemptNo: attemptNo,
|
||||
AssignedTo: assignedTo,
|
||||
ThreadID: threadID,
|
||||
BaseRef: workspace.BaseRef,
|
||||
BaseCommit: workspace.BaseCommit,
|
||||
BranchName: workspace.BranchName,
|
||||
WorktreePath: workspace.WorktreePath,
|
||||
WorkspaceStatus: workspace.WorkspaceStatus,
|
||||
Status: "dispatched",
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
_, err = tx.ExecContext(
|
||||
ctx,
|
||||
@@ -564,10 +599,10 @@ func (s *OrchStore) DispatchTask(ctx context.Context, input DispatchInput) (Disp
|
||||
attempt.AssignedTo,
|
||||
attempt.ThreadID,
|
||||
nullIfEmpty(attempt.BaseRef),
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nullIfEmpty(attempt.BaseCommit),
|
||||
nullIfEmpty(attempt.BranchName),
|
||||
nullIfEmpty(attempt.WorktreePath),
|
||||
nullIfEmpty(attempt.WorkspaceStatus),
|
||||
nil,
|
||||
attempt.Status,
|
||||
formatTime(attempt.CreatedAt),
|
||||
@@ -613,6 +648,7 @@ func (s *OrchStore) DispatchTask(ctx context.Context, input DispatchInput) (Disp
|
||||
if err := tx.Commit(); err != nil {
|
||||
return DispatchResult{}, fmt.Errorf("commit dispatch transaction: %w", err)
|
||||
}
|
||||
workspaceCommitted = true
|
||||
|
||||
task.Status = "dispatched"
|
||||
task.LatestAttemptNo = attempt.AttemptNo
|
||||
@@ -704,9 +740,10 @@ func (s *OrchStore) ReconcileRun(ctx context.Context, runID string) (ReconcileRe
|
||||
_, err = tx.ExecContext(
|
||||
ctx,
|
||||
`UPDATE task_attempts
|
||||
SET status = ?, updated_at = ?
|
||||
SET status = ?, workspace_status = COALESCE(?, workspace_status), updated_at = ?
|
||||
WHERE run_id = ? AND task_id = ? AND attempt_no = ?`,
|
||||
nextStatus,
|
||||
nullIfEmpty(reconcileWorkspaceStatus(threadStatus)),
|
||||
formatTime(now),
|
||||
runID,
|
||||
taskID,
|
||||
@@ -1116,14 +1153,14 @@ func scanTask(scanner threadScanner) (Task, error) {
|
||||
|
||||
func scanAttempt(scanner threadScanner) (TaskAttempt, error) {
|
||||
var (
|
||||
attempt TaskAttempt
|
||||
baseRef sql.NullString
|
||||
baseCommit sql.NullString
|
||||
branchName sql.NullString
|
||||
worktreePath sql.NullString
|
||||
workspaceStatus sql.NullString
|
||||
resultCommit sql.NullString
|
||||
createdAt, updated string
|
||||
attempt TaskAttempt
|
||||
baseRef sql.NullString
|
||||
baseCommit sql.NullString
|
||||
branchName sql.NullString
|
||||
worktreePath sql.NullString
|
||||
workspaceStatus sql.NullString
|
||||
resultCommit sql.NullString
|
||||
createdAt, updated string
|
||||
)
|
||||
|
||||
if err := scanner.Scan(
|
||||
@@ -1580,7 +1617,7 @@ func validateAndNormalizeJSONDefault(fieldName, value, defaultValue string) (str
|
||||
return compact.String(), nil
|
||||
}
|
||||
|
||||
func buildDispatchPayload(task Task, attemptNo int, baseRef string) string {
|
||||
func buildDispatchPayload(task Task, attemptNo int, workspace DispatchWorkspace) string {
|
||||
payload := map[string]any{
|
||||
"run_id": task.RunID,
|
||||
"task_id": task.TaskID,
|
||||
@@ -1596,8 +1633,20 @@ func buildDispatchPayload(task Task, attemptNo int, baseRef string) string {
|
||||
payload["acceptance"] = acceptance
|
||||
}
|
||||
}
|
||||
if strings.TrimSpace(baseRef) != "" {
|
||||
payload["base_ref"] = strings.TrimSpace(baseRef)
|
||||
if strings.TrimSpace(workspace.BaseRef) != "" {
|
||||
payload["base_ref"] = strings.TrimSpace(workspace.BaseRef)
|
||||
}
|
||||
if strings.TrimSpace(workspace.BaseCommit) != "" {
|
||||
payload["base_commit"] = strings.TrimSpace(workspace.BaseCommit)
|
||||
}
|
||||
if strings.TrimSpace(workspace.BranchName) != "" {
|
||||
payload["branch_name"] = strings.TrimSpace(workspace.BranchName)
|
||||
}
|
||||
if strings.TrimSpace(workspace.WorktreePath) != "" {
|
||||
payload["worktree_path"] = strings.TrimSpace(workspace.WorktreePath)
|
||||
}
|
||||
if strings.TrimSpace(workspace.WorkspaceStatus) != "" {
|
||||
payload["workspace_status"] = strings.TrimSpace(workspace.WorkspaceStatus)
|
||||
}
|
||||
|
||||
return marshalJSON(payload)
|
||||
@@ -1634,6 +1683,21 @@ func summarizeAnswer(body string) string {
|
||||
return line
|
||||
}
|
||||
|
||||
func reconcileWorkspaceStatus(threadStatus string) string {
|
||||
switch threadStatus {
|
||||
case "pending":
|
||||
return "created"
|
||||
case "claimed", "in_progress", "blocked":
|
||||
return "active"
|
||||
case "done", "failed":
|
||||
return "completed"
|
||||
case "cancelled":
|
||||
return "abandoned"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func isUniqueConstraintError(err error) bool {
|
||||
return strings.Contains(strings.ToLower(err.Error()), "unique constraint failed")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user