You've already forked agentic-coding-workflow
bugfix
This commit is contained in:
+230
-28
@@ -36,14 +36,22 @@ type Repository interface {
|
||||
// Session
|
||||
InsertSession(ctx context.Context, s *Session) (*Session, error)
|
||||
GetSessionByID(ctx context.Context, id uuid.UUID) (*Session, error)
|
||||
// GetSessionByStepID 返回某编排器 step 最近的 session(任意状态);无则 NotFound。
|
||||
GetSessionByStepID(ctx context.Context, stepID uuid.UUID) (*Session, error)
|
||||
// GetCrashedSessionForResume 返回某编排器 step 最近一次 crashed/exited 的 session(用于 resume)。
|
||||
GetCrashedSessionForResume(ctx context.Context, stepID uuid.UUID) (*Session, error)
|
||||
// requirementID 非 nil 时仅返回关联该需求的 sessions。
|
||||
ListSessionsByUser(ctx context.Context, userID uuid.UUID, requirementID *uuid.UUID) ([]*Session, error)
|
||||
ListAllSessions(ctx context.Context, requirementID *uuid.UUID) ([]*Session, error)
|
||||
UpdateSessionRunning(ctx context.Context, id uuid.UUID, agentSessionID string, pid int32) error
|
||||
// UpdateSessionSandboxMode 记录本 session 运行所用的沙箱模式(审计/取证)。
|
||||
UpdateSessionSandboxMode(ctx context.Context, id uuid.UUID, mode string) error
|
||||
UpdateSessionFinished(ctx context.Context, id uuid.UUID, status SessionStatus, exitCode *int32, lastErr *string) error
|
||||
// MarkSessionFailedIfActive 把仍处于 starting/running 的 session 标记为 crashed
|
||||
// 并写入 lastErr;已是终态时不更新(守卫式,避免覆盖 onExit 写入的终态)。
|
||||
MarkSessionFailedIfActive(ctx context.Context, id uuid.UUID, lastErr string) (bool, error)
|
||||
// ResetSessionForResume 把崩溃/退出 session 复用为新一轮(回 starting、清运行时字段)。
|
||||
ResetSessionForResume(ctx context.Context, id uuid.UUID) error
|
||||
CountActiveSessions(ctx context.Context) (int64, error)
|
||||
CountActiveSessionsByUser(ctx context.Context, userID uuid.UUID) (int64, error)
|
||||
ResetStuckSessionsOnRestart(ctx context.Context) ([]*Session, error)
|
||||
@@ -57,6 +65,32 @@ type Repository interface {
|
||||
ListEventsSince(ctx context.Context, sessionID uuid.UUID, sinceID int64, limit int32) ([]*Event, error)
|
||||
PurgeEventsBefore(ctx context.Context, before time.Time) (int64, error)
|
||||
|
||||
// 成本核算 / 预算 / reaper
|
||||
InsertSessionUsage(ctx context.Context, r *SessionUsageRecord) (inserted bool, err error)
|
||||
AddSessionUsageTotals(ctx context.Context, sid uuid.UUID, dp, dc, dt int64, dCost float64) (totPrompt, totCompletion, totThinking int64, totCost float64, err error)
|
||||
SumProjectCostUSD(ctx context.Context, projectID uuid.UUID, since time.Time) (float64, error)
|
||||
MarkSessionTerminatedReason(ctx context.Context, sid uuid.UUID, reason string) error
|
||||
ListSessionsForReaper(ctx context.Context, wallClockStartedBefore, idleSince time.Time) ([]ReaperSession, error)
|
||||
ListSessionUsage(ctx context.Context, sessionID uuid.UUID, limit int32) ([]*SessionUsageRecord, error)
|
||||
SummarizeAcpUsageByUser(ctx context.Context, from, to time.Time) ([]AcpUsageUserRow, error)
|
||||
SummarizeAcpUsageByModel(ctx context.Context, from, to time.Time) ([]AcpUsageModelRow, error)
|
||||
SummarizeAcpUsageByDay(ctx context.Context, from, to time.Time) ([]AcpUsageDayRow, error)
|
||||
|
||||
// run-history dashboard(只读聚合)
|
||||
SessionRollup(ctx context.Context, f DashboardFilter) (SessionRollup, error)
|
||||
SessionsByDay(ctx context.Context, f DashboardFilter) ([]SessionDayRow, error)
|
||||
SessionTimeline(ctx context.Context, sessionID uuid.UUID) ([]SessionTimelineBucket, error)
|
||||
|
||||
// Turn(回合完成检测)
|
||||
InsertTurn(ctx context.Context, t *Turn) (*Turn, error)
|
||||
MarkTurnCompleted(ctx context.Context, sessionID uuid.UUID, turnIndex int, stop string, updateCount int) error
|
||||
MarkTurnAborted(ctx context.Context, sessionID uuid.UUID, turnIndex int) error
|
||||
IncrementTurnUpdateCount(ctx context.Context, sessionID uuid.UUID, turnIndex int) error
|
||||
GetLatestTurnBySession(ctx context.Context, sessionID uuid.UUID) (*Turn, error)
|
||||
ListTurnsBySession(ctx context.Context, sessionID uuid.UUID) ([]*Turn, error)
|
||||
UpdateSessionLastStopReason(ctx context.Context, sessionID uuid.UUID, reason string) error
|
||||
PurgeTurnsBefore(ctx context.Context, before time.Time) (int64, error)
|
||||
|
||||
// PermissionRequest
|
||||
InsertPermissionRequest(ctx context.Context, p *PermissionRequest) (*PermissionRequest, error)
|
||||
GetPermissionRequestByID(ctx context.Context, id uuid.UUID) (*PermissionRequest, error)
|
||||
@@ -159,6 +193,10 @@ func (r *pgRepo) CreateAgentKind(ctx context.Context, k *AgentKind) (*AgentKind,
|
||||
ToolAllowlist: normalizeStrSlice(k.ToolAllowlist),
|
||||
ClientType: string(k.ClientType),
|
||||
EncryptedMcpServers: k.EncryptedMCPServers,
|
||||
ModelID: toPgUUIDPtr(k.ModelID),
|
||||
MaxCostUsd: float64PtrToNumeric(k.MaxCostUSD),
|
||||
MaxTokens: k.MaxTokens,
|
||||
MaxWallClockSeconds: k.MaxWallClockSeconds,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errs.Wrap(err, errs.CodeInternal, "create agent kind")
|
||||
@@ -218,6 +256,10 @@ func (r *pgRepo) UpdateAgentKind(ctx context.Context, k *AgentKind) (*AgentKind,
|
||||
ToolAllowlist: normalizeStrSlice(k.ToolAllowlist),
|
||||
ClientType: string(k.ClientType),
|
||||
EncryptedMcpServers: k.EncryptedMCPServers,
|
||||
ModelID: toPgUUIDPtr(k.ModelID),
|
||||
MaxCostUsd: float64PtrToNumeric(k.MaxCostUSD),
|
||||
MaxTokens: k.MaxTokens,
|
||||
MaxWallClockSeconds: k.MaxWallClockSeconds,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, pgxNoRowsToErrNotFound(err, errs.CodeAcpAgentKindNotFound, "agent kind not found")
|
||||
@@ -300,6 +342,10 @@ func rowToAgentKind(row acpsqlc.AcpAgentKind) *AgentKind {
|
||||
CreatedBy: fromPgUUID(row.CreatedBy),
|
||||
CreatedAt: row.CreatedAt.Time,
|
||||
UpdatedAt: row.UpdatedAt.Time,
|
||||
ModelID: fromPgUUIDPtr(row.ModelID),
|
||||
MaxCostUSD: numericToFloat64Ptr(row.MaxCostUsd),
|
||||
MaxTokens: row.MaxTokens,
|
||||
MaxWallClockSeconds: row.MaxWallClockSeconds,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -328,17 +374,21 @@ func normalizeStrSlice(s []string) []string {
|
||||
|
||||
func (r *pgRepo) InsertSession(ctx context.Context, s *Session) (*Session, error) {
|
||||
row, err := r.q.InsertSession(ctx, acpsqlc.InsertSessionParams{
|
||||
ID: toPgUUID(s.ID),
|
||||
WorkspaceID: toPgUUID(s.WorkspaceID),
|
||||
ProjectID: toPgUUID(s.ProjectID),
|
||||
AgentKindID: toPgUUID(s.AgentKindID),
|
||||
UserID: toPgUUID(s.UserID),
|
||||
IssueID: toPgUUIDPtr(s.IssueID),
|
||||
RequirementID: toPgUUIDPtr(s.RequirementID),
|
||||
Branch: s.Branch,
|
||||
CwdPath: s.CwdPath,
|
||||
IsMainWorktree: s.IsMainWorktree,
|
||||
Status: string(s.Status),
|
||||
ID: toPgUUID(s.ID),
|
||||
WorkspaceID: toPgUUID(s.WorkspaceID),
|
||||
ProjectID: toPgUUID(s.ProjectID),
|
||||
AgentKindID: toPgUUID(s.AgentKindID),
|
||||
UserID: toPgUUID(s.UserID),
|
||||
IssueID: toPgUUIDPtr(s.IssueID),
|
||||
RequirementID: toPgUUIDPtr(s.RequirementID),
|
||||
Branch: s.Branch,
|
||||
CwdPath: s.CwdPath,
|
||||
IsMainWorktree: s.IsMainWorktree,
|
||||
Status: string(s.Status),
|
||||
OrchestratorStepID: toPgUUIDPtr(s.OrchestratorStepID),
|
||||
BudgetMaxCostUsd: float64PtrToNumeric(s.BudgetMaxCostUSD),
|
||||
BudgetMaxTokens: s.BudgetMaxTokens,
|
||||
BudgetMaxWallClockSeconds: s.BudgetMaxWallClockSeconds,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errs.Wrap(err, errs.CodeInternal, "insert session")
|
||||
@@ -354,6 +404,22 @@ func (r *pgRepo) GetSessionByID(ctx context.Context, id uuid.UUID) (*Session, er
|
||||
return rowToSession(row), nil
|
||||
}
|
||||
|
||||
func (r *pgRepo) GetSessionByStepID(ctx context.Context, stepID uuid.UUID) (*Session, error) {
|
||||
row, err := r.q.GetSessionByStepID(ctx, toPgUUID(stepID))
|
||||
if err != nil {
|
||||
return nil, pgxNoRowsToErrNotFound(err, errs.CodeAcpSessionNotFound, "session not found for step")
|
||||
}
|
||||
return rowToSession(row), nil
|
||||
}
|
||||
|
||||
func (r *pgRepo) GetCrashedSessionForResume(ctx context.Context, stepID uuid.UUID) (*Session, error) {
|
||||
row, err := r.q.GetCrashedSessionForResume(ctx, toPgUUID(stepID))
|
||||
if err != nil {
|
||||
return nil, pgxNoRowsToErrNotFound(err, errs.CodeAcpSessionNotFound, "no crashed session to resume")
|
||||
}
|
||||
return rowToSession(row), nil
|
||||
}
|
||||
|
||||
func (r *pgRepo) ListSessionsByUser(ctx context.Context, userID uuid.UUID, requirementID *uuid.UUID) ([]*Session, error) {
|
||||
rows, err := r.q.ListSessionsByUser(ctx, acpsqlc.ListSessionsByUserParams{
|
||||
UserID: toPgUUID(userID),
|
||||
@@ -392,6 +458,17 @@ func (r *pgRepo) UpdateSessionRunning(ctx context.Context, id uuid.UUID, agentSe
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *pgRepo) UpdateSessionSandboxMode(ctx context.Context, id uuid.UUID, mode string) error {
|
||||
m := mode
|
||||
if err := r.q.UpdateSessionSandboxMode(ctx, acpsqlc.UpdateSessionSandboxModeParams{
|
||||
ID: toPgUUID(id),
|
||||
SandboxMode: &m,
|
||||
}); err != nil {
|
||||
return errs.Wrap(err, errs.CodeInternal, "update session sandbox mode")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *pgRepo) UpdateSessionFinished(ctx context.Context, id uuid.UUID, status SessionStatus, exitCode *int32, lastErr *string) error {
|
||||
if err := r.q.UpdateSessionFinished(ctx, acpsqlc.UpdateSessionFinishedParams{
|
||||
ID: toPgUUID(id),
|
||||
@@ -415,6 +492,13 @@ func (r *pgRepo) MarkSessionFailedIfActive(ctx context.Context, id uuid.UUID, la
|
||||
return n > 0, nil
|
||||
}
|
||||
|
||||
func (r *pgRepo) ResetSessionForResume(ctx context.Context, id uuid.UUID) error {
|
||||
if err := r.q.ResetSessionForResume(ctx, toPgUUID(id)); err != nil {
|
||||
return errs.Wrap(err, errs.CodeInternal, "reset session for resume")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *pgRepo) CountActiveSessions(ctx context.Context) (int64, error) {
|
||||
n, err := r.q.CountActiveSessions(ctx)
|
||||
if err != nil {
|
||||
@@ -480,23 +564,34 @@ func rowToSession(row acpsqlc.AcpSession) *Session {
|
||||
endedAt = &t
|
||||
}
|
||||
return &Session{
|
||||
ID: fromPgUUID(row.ID),
|
||||
WorkspaceID: fromPgUUID(row.WorkspaceID),
|
||||
ProjectID: fromPgUUID(row.ProjectID),
|
||||
AgentKindID: fromPgUUID(row.AgentKindID),
|
||||
UserID: fromPgUUID(row.UserID),
|
||||
IssueID: fromPgUUIDPtr(row.IssueID),
|
||||
RequirementID: fromPgUUIDPtr(row.RequirementID),
|
||||
AgentSessionID: row.AgentSessionID,
|
||||
Branch: row.Branch,
|
||||
CwdPath: row.CwdPath,
|
||||
IsMainWorktree: row.IsMainWorktree,
|
||||
Status: SessionStatus(row.Status),
|
||||
PID: row.Pid,
|
||||
ExitCode: row.ExitCode,
|
||||
LastError: row.LastError,
|
||||
StartedAt: row.StartedAt.Time,
|
||||
EndedAt: endedAt,
|
||||
ID: fromPgUUID(row.ID),
|
||||
WorkspaceID: fromPgUUID(row.WorkspaceID),
|
||||
ProjectID: fromPgUUID(row.ProjectID),
|
||||
AgentKindID: fromPgUUID(row.AgentKindID),
|
||||
UserID: fromPgUUID(row.UserID),
|
||||
IssueID: fromPgUUIDPtr(row.IssueID),
|
||||
RequirementID: fromPgUUIDPtr(row.RequirementID),
|
||||
AgentSessionID: row.AgentSessionID,
|
||||
Branch: row.Branch,
|
||||
CwdPath: row.CwdPath,
|
||||
IsMainWorktree: row.IsMainWorktree,
|
||||
Status: SessionStatus(row.Status),
|
||||
PID: row.Pid,
|
||||
ExitCode: row.ExitCode,
|
||||
LastError: row.LastError,
|
||||
StartedAt: row.StartedAt.Time,
|
||||
EndedAt: endedAt,
|
||||
LastStopReason: row.LastStopReason,
|
||||
OrchestratorStepID: fromPgUUIDPtr(row.OrchestratorStepID),
|
||||
PromptTokens: row.PromptTokens,
|
||||
CompletionTokens: row.CompletionTokens,
|
||||
ThinkingTokens: row.ThinkingTokens,
|
||||
TotalCostUSD: numericToFloat64(row.TotalCostUsd),
|
||||
LastActivityAt: row.LastActivityAt.Time,
|
||||
BudgetMaxCostUSD: numericToFloat64Ptr(row.BudgetMaxCostUsd),
|
||||
BudgetMaxTokens: row.BudgetMaxTokens,
|
||||
BudgetMaxWallClockSeconds: row.BudgetMaxWallClockSeconds,
|
||||
TerminatedReason: row.TerminatedReason,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -556,6 +651,113 @@ func rowToEvent(row acpsqlc.AcpEvent) *Event {
|
||||
}
|
||||
}
|
||||
|
||||
// ===== Turn(回合完成检测)=====
|
||||
|
||||
func (r *pgRepo) InsertTurn(ctx context.Context, t *Turn) (*Turn, error) {
|
||||
row, err := r.q.InsertTurn(ctx, acpsqlc.InsertTurnParams{
|
||||
SessionID: toPgUUID(t.SessionID),
|
||||
TurnIndex: int32(t.TurnIndex),
|
||||
PromptRequestID: t.PromptRequestID,
|
||||
Status: string(t.Status),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errs.Wrap(err, errs.CodeInternal, "insert acp turn")
|
||||
}
|
||||
return rowToTurn(row), nil
|
||||
}
|
||||
|
||||
func (r *pgRepo) MarkTurnCompleted(ctx context.Context, sessionID uuid.UUID, turnIndex int, stop string, updateCount int) error {
|
||||
if err := r.q.MarkTurnCompleted(ctx, acpsqlc.MarkTurnCompletedParams{
|
||||
SessionID: toPgUUID(sessionID),
|
||||
TurnIndex: int32(turnIndex),
|
||||
StopReason: &stop,
|
||||
UpdateCount: int32(updateCount),
|
||||
}); err != nil {
|
||||
return errs.Wrap(err, errs.CodeInternal, "mark turn completed")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *pgRepo) MarkTurnAborted(ctx context.Context, sessionID uuid.UUID, turnIndex int) error {
|
||||
if err := r.q.MarkTurnAborted(ctx, acpsqlc.MarkTurnAbortedParams{
|
||||
SessionID: toPgUUID(sessionID),
|
||||
TurnIndex: int32(turnIndex),
|
||||
}); err != nil {
|
||||
return errs.Wrap(err, errs.CodeInternal, "mark turn aborted")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *pgRepo) IncrementTurnUpdateCount(ctx context.Context, sessionID uuid.UUID, turnIndex int) error {
|
||||
if err := r.q.IncrementTurnUpdateCount(ctx, acpsqlc.IncrementTurnUpdateCountParams{
|
||||
SessionID: toPgUUID(sessionID),
|
||||
TurnIndex: int32(turnIndex),
|
||||
}); err != nil {
|
||||
return errs.Wrap(err, errs.CodeInternal, "increment turn update count")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *pgRepo) GetLatestTurnBySession(ctx context.Context, sessionID uuid.UUID) (*Turn, error) {
|
||||
row, err := r.q.GetLatestTurnBySession(ctx, toPgUUID(sessionID))
|
||||
if err != nil {
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, errs.Wrap(err, errs.CodeInternal, "get latest turn by session")
|
||||
}
|
||||
return rowToTurn(row), nil
|
||||
}
|
||||
|
||||
func (r *pgRepo) ListTurnsBySession(ctx context.Context, sessionID uuid.UUID) ([]*Turn, error) {
|
||||
rows, err := r.q.ListTurnsBySession(ctx, toPgUUID(sessionID))
|
||||
if err != nil {
|
||||
return nil, errs.Wrap(err, errs.CodeInternal, "list turns by session")
|
||||
}
|
||||
out := make([]*Turn, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
out = append(out, rowToTurn(row))
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (r *pgRepo) UpdateSessionLastStopReason(ctx context.Context, sessionID uuid.UUID, reason string) error {
|
||||
if err := r.q.UpdateSessionLastStopReason(ctx, acpsqlc.UpdateSessionLastStopReasonParams{
|
||||
ID: toPgUUID(sessionID),
|
||||
LastStopReason: &reason,
|
||||
}); err != nil {
|
||||
return errs.Wrap(err, errs.CodeInternal, "update session last stop reason")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *pgRepo) PurgeTurnsBefore(ctx context.Context, before time.Time) (int64, error) {
|
||||
n, err := r.q.PurgeTurnsBefore(ctx, pgtype.Timestamptz{Time: before, Valid: true})
|
||||
if err != nil {
|
||||
return 0, errs.Wrap(err, errs.CodeInternal, "purge acp turns")
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func rowToTurn(row acpsqlc.AcpTurn) *Turn {
|
||||
var completedAt *time.Time
|
||||
if row.CompletedAt.Valid {
|
||||
t := row.CompletedAt.Time
|
||||
completedAt = &t
|
||||
}
|
||||
return &Turn{
|
||||
ID: row.ID,
|
||||
SessionID: fromPgUUID(row.SessionID),
|
||||
TurnIndex: int(row.TurnIndex),
|
||||
PromptRequestID: row.PromptRequestID,
|
||||
Status: TurnStatus(row.Status),
|
||||
StopReason: row.StopReason,
|
||||
UpdateCount: int(row.UpdateCount),
|
||||
StartedAt: row.StartedAt.Time,
|
||||
CompletedAt: completedAt,
|
||||
}
|
||||
}
|
||||
|
||||
// ===== PermissionRequest =====
|
||||
|
||||
func (r *pgRepo) InsertPermissionRequest(ctx context.Context, p *PermissionRequest) (*PermissionRequest, error) {
|
||||
|
||||
Reference in New Issue
Block a user