mirror of
https://gh-proxy.org/https://github.com/klinsmaya/multica.git
synced 2026-11-08 01:16:45 +08:00
* fix(agent): join Pi cancellation cleanup and guard continuation Co-authored-by: multica-agent <[email protected]> * fix(agent): preserve Pi completion and negotiate cleanup Co-authored-by: multica-agent <[email protected]> * perf(agent): avoid Pi exit delay and index cleanup guards Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: Multica Eve <[email protected]> Co-authored-by: multica-agent <[email protected]>
188 lines
7.3 KiB
Go
188 lines
7.3 KiB
Go
// Code generated by sqlc. DO NOT EDIT.
|
|
// versions:
|
|
// sqlc v1.31.1
|
|
// source: task_cleanup.sql
|
|
|
|
package db
|
|
|
|
import (
|
|
"context"
|
|
|
|
"github.com/jackc/pgx/v5/pgtype"
|
|
)
|
|
|
|
const clearTaskProcessCleanupPending = `-- name: ClearTaskProcessCleanupPending :exec
|
|
UPDATE agent_task_queue
|
|
SET result = result - 'process_cleanup_pending'
|
|
WHERE id = $1 AND status IN ('cancelled', 'failed')
|
|
AND result->>'process_cleanup_pending' = 'true'
|
|
`
|
|
|
|
func (q *Queries) ClearTaskProcessCleanupPending(ctx context.Context, id pgtype.UUID) error {
|
|
_, err := q.db.Exec(ctx, clearTaskProcessCleanupPending, id)
|
|
return err
|
|
}
|
|
|
|
const failCancelledTaskProcessCleanup = `-- name: FailCancelledTaskProcessCleanup :one
|
|
UPDATE agent_task_queue
|
|
SET status = 'failed',
|
|
error = concat_ws('; ', NULLIF(error, ''), $1::text),
|
|
failure_reason = 'cleanup_unconfirmed',
|
|
completed_at = now(),
|
|
chat_finalize_deferred_at = NULL
|
|
WHERE id = $2 AND status = 'cancelled'
|
|
RETURNING id, agent_id, issue_id, status, priority, dispatched_at, started_at, completed_at, result, error, created_at, context, runtime_id, session_id, work_dir, trigger_comment_id, chat_session_id, autopilot_run_id, attempt, max_attempts, parent_task_id, failure_reason, trigger_summary, force_fresh_session, is_leader_task, wait_reason, initiator_user_id, handoff_note, prepare_lease_expires_at, squad_id, runtime_mcp_overlay, escalation_for_task_id, fire_at, originator_user_id, runtime_connected_apps, coalesced_comment_ids, delivered_comment_ids, chat_input_task_id, chat_finalize_deferred_at, originator_source, delegated_from_task_id, retry_of_task_id, rerun_of_task_id, rule_version_id, trigger_evidence_kind, trigger_evidence_ref_id, accountable_user_id, session_rollout_missing, retired_session_id, quick_actions_disabled, regenerate_quick_actions_for, branch_name, durable_work_dir, channel_context_revision, comment_thread_id, cancelled_by_type, cancelled_by_id, cancelled_by_name, issue_snapshot, workflow_step
|
|
`
|
|
|
|
type FailCancelledTaskProcessCleanupParams struct {
|
|
ErrorMessage string `json:"error_message"`
|
|
ID pgtype.UUID `json:"id"`
|
|
}
|
|
|
|
// The daemon's cancellation acknowledgement can discover that local work did
|
|
// NOT stop. Preserve the cancelled run's metadata but surface this as failure.
|
|
// The status CAS makes replay harmless and leaves completed/failed rows alone.
|
|
func (q *Queries) FailCancelledTaskProcessCleanup(ctx context.Context, arg FailCancelledTaskProcessCleanupParams) (AgentTaskQueue, error) {
|
|
row := q.db.QueryRow(ctx, failCancelledTaskProcessCleanup, arg.ErrorMessage, arg.ID)
|
|
var i AgentTaskQueue
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.AgentID,
|
|
&i.IssueID,
|
|
&i.Status,
|
|
&i.Priority,
|
|
&i.DispatchedAt,
|
|
&i.StartedAt,
|
|
&i.CompletedAt,
|
|
&i.Result,
|
|
&i.Error,
|
|
&i.CreatedAt,
|
|
&i.Context,
|
|
&i.RuntimeID,
|
|
&i.SessionID,
|
|
&i.WorkDir,
|
|
&i.TriggerCommentID,
|
|
&i.ChatSessionID,
|
|
&i.AutopilotRunID,
|
|
&i.Attempt,
|
|
&i.MaxAttempts,
|
|
&i.ParentTaskID,
|
|
&i.FailureReason,
|
|
&i.TriggerSummary,
|
|
&i.ForceFreshSession,
|
|
&i.IsLeaderTask,
|
|
&i.WaitReason,
|
|
&i.InitiatorUserID,
|
|
&i.HandoffNote,
|
|
&i.PrepareLeaseExpiresAt,
|
|
&i.SquadID,
|
|
&i.RuntimeMcpOverlay,
|
|
&i.EscalationForTaskID,
|
|
&i.FireAt,
|
|
&i.OriginatorUserID,
|
|
&i.RuntimeConnectedApps,
|
|
&i.CoalescedCommentIds,
|
|
&i.DeliveredCommentIds,
|
|
&i.ChatInputTaskID,
|
|
&i.ChatFinalizeDeferredAt,
|
|
&i.OriginatorSource,
|
|
&i.DelegatedFromTaskID,
|
|
&i.RetryOfTaskID,
|
|
&i.RerunOfTaskID,
|
|
&i.RuleVersionID,
|
|
&i.TriggerEvidenceKind,
|
|
&i.TriggerEvidenceRefID,
|
|
&i.AccountableUserID,
|
|
&i.SessionRolloutMissing,
|
|
&i.RetiredSessionID,
|
|
&i.QuickActionsDisabled,
|
|
&i.RegenerateQuickActionsFor,
|
|
&i.BranchName,
|
|
&i.DurableWorkDir,
|
|
&i.ChannelContextRevision,
|
|
&i.CommentThreadID,
|
|
&i.CancelledByType,
|
|
&i.CancelledByID,
|
|
&i.CancelledByName,
|
|
&i.IssueSnapshot,
|
|
&i.WorkflowStep,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const markPiTaskProcessCleanupPending = `-- name: MarkPiTaskProcessCleanupPending :exec
|
|
UPDATE agent_task_queue task
|
|
SET result = COALESCE(task.result, '{}'::jsonb) || '{"process_cleanup_pending":true}'::jsonb
|
|
WHERE task.id = $1 AND task.status = 'cancelled' AND task.started_at IS NOT NULL
|
|
AND task.result->>'process_cleanup_capability' = 'pi-process-cleanup-v1'
|
|
AND EXISTS (SELECT 1 FROM agent_runtime runtime WHERE runtime.id = task.runtime_id AND runtime.provider IN ('pi', 'omp'))
|
|
`
|
|
|
|
// Cancellation makes the task terminal before the daemon finishes stopping it.
|
|
// Only a run that negotiated confirmation at start holds a fence until ack.
|
|
func (q *Queries) MarkPiTaskProcessCleanupPending(ctx context.Context, id pgtype.UUID) error {
|
|
_, err := q.db.Exec(ctx, markPiTaskProcessCleanupPending, id)
|
|
return err
|
|
}
|
|
|
|
const taskBlockedByProcessCleanup = `-- name: TaskBlockedByProcessCleanup :one
|
|
SELECT EXISTS (
|
|
SELECT 1
|
|
FROM agent_task_queue candidate
|
|
JOIN LATERAL (
|
|
SELECT CASE WHEN unsafe.status = 'cancelled'
|
|
AND unsafe.result->>'process_cleanup_pending' = 'true'
|
|
THEN unsafe.completed_at + interval '30 seconds'
|
|
ELSE COALESCE(unsafe.completed_at, unsafe.created_at)
|
|
END AS completed_at
|
|
FROM agent_task_queue unsafe
|
|
WHERE unsafe.agent_id = candidate.agent_id
|
|
AND unsafe.runtime_id = candidate.runtime_id
|
|
AND (unsafe.failure_reason = 'cleanup_unconfirmed'
|
|
OR (unsafe.status = 'cancelled' AND unsafe.result->>'process_cleanup_pending' = 'true'
|
|
AND unsafe.completed_at <= now() - interval '30 seconds'))
|
|
ORDER BY unsafe.completed_at DESC, unsafe.id DESC
|
|
LIMIT 1
|
|
) unsafe ON true
|
|
WHERE candidate.id = $1
|
|
AND candidate.status IN ('queued', 'dispatched', 'waiting_local_directory')
|
|
AND NOT EXISTS (
|
|
SELECT 1 FROM agent_task_queue manual
|
|
WHERE manual.agent_id = candidate.agent_id
|
|
AND manual.runtime_id = candidate.runtime_id
|
|
AND manual.rerun_of_task_id IS NOT NULL
|
|
AND manual.retry_of_task_id IS NULL
|
|
AND manual.created_at > unsafe.completed_at
|
|
AND (manual.id = candidate.id OR manual.status = 'completed')
|
|
)
|
|
)
|
|
`
|
|
|
|
// A cleanup failure fences this agent on this runtime, including already queued
|
|
// work and future scheduled runs. Only a NEW explicit manual rerun can cross
|
|
// the fence; its successful completion allows later automated work again.
|
|
// Existing failure and result fields are the durable safety markers.
|
|
func (q *Queries) TaskBlockedByProcessCleanup(ctx context.Context, taskID pgtype.UUID) (bool, error) {
|
|
row := q.db.QueryRow(ctx, taskBlockedByProcessCleanup, taskID)
|
|
var exists bool
|
|
err := row.Scan(&exists)
|
|
return exists, err
|
|
}
|
|
|
|
const taskWaitingForProcessCleanup = `-- name: TaskWaitingForProcessCleanup :one
|
|
SELECT EXISTS (
|
|
SELECT 1 FROM agent_task_queue candidate
|
|
JOIN agent_task_queue stopping ON stopping.agent_id = candidate.agent_id AND stopping.runtime_id = candidate.runtime_id
|
|
WHERE candidate.id = $1 AND candidate.status IN ('dispatched', 'waiting_local_directory')
|
|
AND stopping.status = 'cancelled' AND stopping.result->>'process_cleanup_pending' = 'true'
|
|
AND stopping.completed_at > now() - interval '30 seconds'
|
|
)
|
|
`
|
|
|
|
func (q *Queries) TaskWaitingForProcessCleanup(ctx context.Context, id pgtype.UUID) (bool, error) {
|
|
row := q.db.QueryRow(ctx, taskWaitingForProcessCleanup, id)
|
|
var exists bool
|
|
err := row.Scan(&exists)
|
|
return exists, err
|
|
}
|