mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-14 02:15:57 +00:00
A run records its terminal run.status (completed/failed/cancelled) only when the agent loop returns. If the gateway process is killed mid-run (restart, crash, or SIGKILL after a slow graceful shutdown), the terminal event is never emitted, so the run's last run.status item stays "started" forever — it shows as perpetually running in the timeline and is never counted as failed. Mirror the cron scheduler's startup reset (recomputeStaleJobs flips stale 'running' jobs to 'interrupted'): add RunTimelineStore.RecoverInterruptedRuns, invoked once on gateway startup. It finds runs that have a started run.status but no terminal sibling and appends a terminal failed run.status item, marked interrupted in metadata so it stays distinguishable from a genuine agent failure. Running only at startup means nothing from before is still executing, so there is no false-positive risk — unlike a periodic sweep (see the deliberately-disabled tracing stale-trace loop). - store: RecoverInterruptedRuns on the interface + PG and SQLite impls - cmd/gateway_managed: invoke once after the timeline recorder is built - tests: SQLite store test — orphan recovered, completed run untouched, idempotent on re-run
73 lines
3.0 KiB
Go
73 lines
3.0 KiB
Go
package store
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
const (
|
|
RunTimelineItemTypeActivity = "activity"
|
|
RunTimelineItemTypeAssistantMessage = "assistant.message"
|
|
RunTimelineItemTypeToolCall = "tool.call"
|
|
RunTimelineItemTypeToolResult = "tool.result"
|
|
RunTimelineItemTypeRunStatus = "run.status"
|
|
)
|
|
|
|
const (
|
|
RunTimelineStatusStarted = "started"
|
|
RunTimelineStatusRunning = "running"
|
|
RunTimelineStatusCompleted = "completed"
|
|
RunTimelineStatusFailed = "failed"
|
|
RunTimelineStatusCancelled = "cancelled"
|
|
)
|
|
|
|
// RunTimelineItem is a persisted, display-safe archive entry for one agent run.
|
|
type RunTimelineItem struct {
|
|
ID uuid.UUID `json:"id" db:"id"`
|
|
TenantID uuid.UUID `json:"tenant_id" db:"tenant_id"`
|
|
RunID string `json:"run_id" db:"run_id"`
|
|
SessionKey string `json:"session_key" db:"session_key"`
|
|
AgentID *uuid.UUID `json:"agent_id,omitempty" db:"agent_id"`
|
|
UserID string `json:"user_id,omitempty" db:"user_id"`
|
|
Channel string `json:"channel,omitempty" db:"channel"`
|
|
ChatID string `json:"chat_id,omitempty" db:"chat_id"`
|
|
Seq int `json:"seq" db:"seq"`
|
|
ItemType string `json:"item_type" db:"item_type"`
|
|
Status string `json:"status,omitempty" db:"status"`
|
|
Title string `json:"title,omitempty" db:"title"`
|
|
Preview string `json:"preview,omitempty" db:"preview"`
|
|
Content string `json:"content,omitempty" db:"content"`
|
|
ToolName string `json:"tool_name,omitempty" db:"tool_name"`
|
|
ToolCallID string `json:"tool_call_id,omitempty" db:"tool_call_id"`
|
|
TraceID *uuid.UUID `json:"trace_id,omitempty" db:"trace_id"`
|
|
SpanID *uuid.UUID `json:"span_id,omitempty" db:"span_id"`
|
|
Metadata json.RawMessage `json:"metadata,omitempty" db:"metadata"`
|
|
CreatedAt time.Time `json:"created_at" db:"created_at"`
|
|
}
|
|
|
|
// RunTimelineListOpts scopes a timeline read. RunID is preferred; SessionKey is
|
|
// a fallback for session archive views that need the latest runs in a session.
|
|
type RunTimelineListOpts struct {
|
|
RunID string
|
|
SessionKey string
|
|
Limit int
|
|
Offset int
|
|
}
|
|
|
|
// RunTimelineStore appends and lists archived agent run timeline entries.
|
|
type RunTimelineStore interface {
|
|
AppendRunTimelineItem(ctx context.Context, item *RunTimelineItem) error
|
|
ListRunTimelineItems(ctx context.Context, opts RunTimelineListOpts) ([]RunTimelineItem, error)
|
|
// RecoverInterruptedRuns reconciles runs left mid-execution when the gateway
|
|
// stopped: a run with a "started" run.status item but no terminal
|
|
// (completed/failed/cancelled) run.status sibling. For each, it appends a
|
|
// terminal failed run.status item so the run is no longer reported as
|
|
// perpetually running. Intended to run once on startup (cross-tenant),
|
|
// mirroring the cron scheduler's stale-'running' reset. Returns the number
|
|
// of runs recovered.
|
|
RecoverInterruptedRuns(ctx context.Context) (int64, error)
|
|
}
|