Add orch strict worktree dispatch

This commit is contained in:
2026-03-19 13:52:51 +08:00
parent 07f4a6fdae
commit 1b0cd723d7
9 changed files with 776 additions and 48 deletions
+95 -31
View File
@@ -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")
}