thread

package
v0.19.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 30, 2026 License: AGPL-3.0 Imports: 17 Imported by: 0

Documentation

Index

Constants

View Source
const (
	TypeChat              = "chat"
	TypeSchedule          = "schedule"
	TypeSubagent          = "subagent"
	TypeDiscuss           = "discuss"
	TypeACPAgent          = "acp_agent"
	RuntimeModel          = "model"
	RuntimeACPAgent       = "acp_agent"
	DefaultACPProjectMode = "project"
	DefaultACPProjectPath = "/data"
)

Variables

View Source
var (
	ErrACPAgentIDRequired     = errors.New("acp_agent_id is required for acp_agent sessions")
	ErrACPProjectPathMissing  = errors.New("project_path is required for acp_agent sessions")
	ErrACPUnknownAgent        = errors.New("unknown ACP agent")
	ErrACPAgentNotEnabled     = errors.New("ACP agent is not enabled for this bot")
	ErrACPAgentNotConfigured  = errors.New("ACP agent is not configured for this bot")
	ErrACPRuntimeOwnerMissing = errors.New("runtime_owner_account_id is required for acp_agent sessions")
	ErrACPProjectModeInvalid  = errors.New("unknown ACP project mode")
	ErrForkSourceNotFound     = errors.New("fork source session not found")
	ErrForkSourceNotReply     = errors.New("fork source must be a visible assistant reply")
	ErrForkSourceNotChat      = errors.New("fork source must be a chat session")
	ErrSessionHasMessages     = errors.New("session has visible messages")
)

Functions

func AllSessionTypes

func AllSessionTypes() []string

AllSessionTypes returns every known session type. The paged list queries always demand an explicit type filter; callers that filter by visibility instead pass this list to make the type predicate a no-op.

func ApplyACPMetadataDefaults

func ApplyACPMetadataDefaults(meta map[string]any) map[string]any

ApplyACPMetadataDefaults fills omitted ACP session project fields.

func DescriptorFromLegacyType

func DescriptorFromLegacyType(typ string) (string, string)

DescriptorFromLegacyType maps the legacy single type column to the split session mode/runtime descriptor used by new code paths.

func IsACPRuntime

func IsACPRuntime(thread Thread) bool

IsACPRuntime reports whether a session is backed by an ACP runtime. It keeps legacy chat ACP sessions (`type=acp_agent`) working while allowing newer descriptors such as `session_mode=discuss` + `runtime_type=acp_agent`.

func IsKnownRuntimeType

func IsKnownRuntimeType(runtimeType string) bool

func IsKnownSessionMode

func IsKnownSessionMode(mode string) bool

func IsKnownType

func IsKnownType(typ string) bool

func IsUserFacingType

func IsUserFacingType(typ string) bool

IsUserFacingType reports whether a session type is one that user-facing session list endpoints should return by default.

func LegacyTypeForDescriptor

func LegacyTypeForDescriptor(sessionMode, runtimeType string) string

LegacyTypeForDescriptor returns the compatibility type value for a split session descriptor.

func ResolveDescriptor

func ResolveDescriptor(legacyType, sessionMode, runtimeType string) (string, string, string, error)

ResolveDescriptor returns the normalized compatibility type plus split session-mode/runtime descriptor without applying metadata side effects.

func SupportsSkillActivation

func SupportsSkillActivation(sessionMode, legacyType, runtimeType string) bool

SupportsSkillActivation reports whether a session shape can accept user-requested skill activation: chat mode (with the usual legacy-type fallback) on the built-in model runtime. This is the single definition for the rule — the web WS pre-check, the channel inbound gate, and the flow resolver's final guard all call it, so the three surfaces cannot drift.

func UserFacingSessionTypes

func UserFacingSessionTypes() []string

UserFacingSessionTypes returns a fresh copy of the user-facing session type list so callers can read or mutate it without disturbing the package-level source of truth.

Types

type ACPSetupValidation

type ACPSetupValidation struct {
	Known                 bool
	Enabled               bool
	MissingManagedFieldID string
}

ACPSetupValidation is the channel- and runtime-independent policy result needed before a thread can persist an ACP runtime descriptor.

type ACPSetupValidator

type ACPSetupValidator interface {
	ValidateACPSetup(agentID string, botMetadata map[string]any) ACPSetupValidation
}

ACPSetupValidator is implemented by an Agent adapter. Thread owns this port so chat persistence never imports an Agent runtime implementation.

type CreateInput

type CreateInput struct {
	BotID           string
	BotAgentID      string
	RouteID         string
	ChannelType     string
	Type            string
	SessionMode     string
	RuntimeType     string
	Title           string
	Metadata        map[string]any
	RuntimeMetadata map[string]any
	ParentThreadID  string
	CreatedByUserID string
	// WorkdirID immutably binds the thread to a bot workdir. The caller
	// (handler layer) validates the workdir; thread persistence only stores
	// the reference so this package never imports the workdir domain.
	WorkdirID string
	// WorkdirPath is the resolved workdir directory, set if and only if
	// WorkdirID is set. For ACP threads it overrides the metadata
	// project_path so the runtime works in that directory; it also
	// leaves a creation-time path snapshot in the metadata for history.
	WorkdirPath string
	// Visibility overrides the default visibility derived from the session
	// mode. Schedule-created sessions use this to surface in user-facing
	// session lists while keeping session_mode='schedule' for prompt and
	// tool gating. Empty means "derive from mode".
	Visibility Visibility
}

CreateInput holds input for creating a new thread.

type CreateSubagentInput

type CreateSubagentInput struct {
	Thread       CreateInput
	ModelUUID    string
	ModelID      string
	ProviderName string
	Forked       bool
	ForkContext  []SubagentForkContextMessage
}

type Cursor

type Cursor struct {
	UpdatedAt time.Time
	ID        string
}

Cursor identifies the position in a thread listing for keyset pagination. The zero value means "start from the head".

func (Cursor) IsZero

func (c Cursor) IsZero() bool

IsZero reports whether the cursor carries neither half — the start-of-list signal that pagedCursorParams maps to "no cursor predicate". A partially-populated cursor (only the timestamp or only the id) is not zero; pagedCursorParams rejects it as a programmer error so we never send malformed bindings down to SQL.

type ForkFromAssistantInput

type ForkFromAssistantInput struct {
	BotID    string
	ThreadID string
	// TurnID names the round the fork inherits through. A turn is the identity
	// a client holds while the round is still live, and the cut is turn-level
	// anyway, so the fork point is named by turn rather than by stored message.
	TurnID string
	// MessageID is the pre-turn spelling of TurnID.
	//
	// Deprecated: accepted so a client shipped against the message-id contract
	// keeps working after a server upgrade. It is resolved to the round that
	// contains it. Remove once the compatibility window closes.
	MessageID       string
	Title           string
	CreatedByUserID string
}

ForkFromAssistantInput creates a new chat thread from the source thread's visible history through the assistant message's turn.

type ListFilter

type ListFilter struct {
	ParentThreadID string
	// WorkdirID filters to sessions bound to one workdir. Mutually
	// exclusive with WorkdirUnassigned.
	WorkdirID string
	// WorkdirUnassigned filters to sessions with no workdir binding — the
	// sidebar's ungrouped bucket.
	WorkdirUnassigned bool
	// Visibility filters to sessions with the given stored visibility.
	// Empty means no visibility predicate. The default session listing
	// passes VisibilityUser so schedule-created sessions surface by
	// visibility rather than by widening the legacy type filter.
	Visibility Visibility
}

type Queries

type Queries interface {
	CountMessagesBySession(context.Context, pgtype.UUID) (int64, error)
	CreateSession(context.Context, sqlc.CreateSessionParams) (sqlc.BotSession, error)
	CreateSubagentConfig(context.Context, sqlc.CreateSubagentConfigParams) (sqlc.SubagentConfig, error)
	CreateSubagentForkContext(context.Context, sqlc.CreateSubagentForkContextParams) (sqlc.CreateSubagentForkContextRow, error)
	ForkSessionFromAssistantTurn(context.Context, sqlc.ForkSessionFromAssistantTurnParams) (sqlc.ForkSessionFromAssistantTurnRow, error)
	GetVisibleHistoryTurnByMessage(context.Context, sqlc.GetVisibleHistoryTurnByMessageParams) (dbstore.HistoryTurn, error)
	GetBotByID(context.Context, pgtype.UUID) (sqlc.GetBotByIDRow, error)
	GetSessionByID(context.Context, pgtype.UUID) (sqlc.BotSession, error)
	GetSubagentConfig(context.Context, pgtype.UUID) (sqlc.SubagentConfig, error)
	ListSessionsByBot(context.Context, pgtype.UUID) ([]sqlc.ListSessionsByBotRow, error)
	ListSessionsByBotAndCreatedByUser(context.Context, sqlc.ListSessionsByBotAndCreatedByUserParams) ([]sqlc.ListSessionsByBotAndCreatedByUserRow, error)
	ListSessionsByBotAndCreatedByUserPaged(context.Context, sqlc.ListSessionsByBotAndCreatedByUserPagedParams) ([]sqlc.ListSessionsByBotAndCreatedByUserPagedRow, error)
	ListSessionsByBotPaged(context.Context, sqlc.ListSessionsByBotPagedParams) ([]sqlc.ListSessionsByBotPagedRow, error)
	ListSessionsByRoute(context.Context, pgtype.UUID) ([]sqlc.BotSession, error)
	ListSubagentForkContext(context.Context, pgtype.UUID) ([]sqlc.ListSubagentForkContextRow, error)
	ListSubagentSessionsByParent(context.Context, pgtype.UUID) ([]sqlc.BotSession, error)
	SoftDeleteSession(context.Context, pgtype.UUID) error
	TouchSession(context.Context, pgtype.UUID) error
	UpdateSessionMetadata(context.Context, sqlc.UpdateSessionMetadataParams) (sqlc.BotSession, error)
	UpdateSessionTitle(context.Context, sqlc.UpdateSessionTitleParams) (sqlc.BotSession, error)
	UpdateSessionTypeAndMetadata(context.Context, sqlc.UpdateSessionTypeAndMetadataParams) (sqlc.BotSession, error)
}

Queries is the storage surface owned by the Thread domain. Route lookup and route activation intentionally stay outside this contract.

type Service

type Service struct {
	// contains filtered or unexported fields
}

Service manages bot chat threads.

func NewService

func NewService(log *slog.Logger, queries Queries, publisher event.Publisher) *Service

NewService creates a thread service. publisher may be nil — thread creation still succeeds when there is no event hub wired in (tests, or any caller that doesn't surface activity events).

func (*Service) Create

func (s *Service) Create(ctx context.Context, input CreateInput) (Thread, error)

Create creates a new thread.

func (*Service) CreateSubagent

func (s *Service) CreateSubagent(ctx context.Context, input CreateSubagentInput) (Thread, SubagentConfig, error)

CreateSubagent atomically creates the hidden child thread and its pinned model/fork configuration when the backing store supports transactions.

func (*Service) ForkFromAssistantTurn

func (s *Service) ForkFromAssistantTurn(ctx context.Context, input ForkFromAssistantInput) (Thread, error)

ForkFromAssistantTurn creates a new chat thread containing the source thread's visible linear history through the selected assistant turn.

func (*Service) Get

func (s *Service) Get(ctx context.Context, sessionID string) (Thread, error)

Get returns a session by ID.

func (*Service) GetSubagentConfig

func (s *Service) GetSubagentConfig(ctx context.Context, sessionID string) (SubagentConfig, error)

func (*Service) ListByBot

func (s *Service) ListByBot(ctx context.Context, botID string) ([]Thread, error)

ListByBot returns all active sessions for a bot.

func (*Service) ListByBotAndCreatedByUser

func (s *Service) ListByBotAndCreatedByUser(ctx context.Context, botID, userID string) ([]Thread, error)

ListByBotAndCreatedByUser returns all active sessions for a bot created by a user.

func (*Service) ListByBotAndCreatedByUserPaged

func (s *Service) ListByBotAndCreatedByUserPaged(ctx context.Context, botID, userID string, types []string, cursor Cursor, limit int64) ([]Thread, error)

ListByBotAndCreatedByUserPaged is the paged variant scoped to a single user.

func (*Service) ListByBotAndCreatedByUserPagedWithFilter

func (s *Service) ListByBotAndCreatedByUserPagedWithFilter(ctx context.Context, botID, userID string, types []string, cursor Cursor, limit int64, filter ListFilter) ([]Thread, error)

func (*Service) ListByBotPaged

func (s *Service) ListByBotPaged(ctx context.Context, botID string, types []string, cursor Cursor, limit int64) ([]Thread, error)

ListByBotPaged returns one page of sessions for a bot, filtered to the given types and starting after the given cursor. Callers that want a "has more" signal pass limit+1 and look for an extra row.

func (*Service) ListByBotPagedWithFilter

func (s *Service) ListByBotPagedWithFilter(ctx context.Context, botID string, types []string, cursor Cursor, limit int64, filter ListFilter) ([]Thread, error)

func (*Service) ListByRoute

func (s *Service) ListByRoute(ctx context.Context, routeID string) ([]Thread, error)

ListByRoute returns all active sessions for a route.

func (*Service) ListSubagentForkContext

func (s *Service) ListSubagentForkContext(ctx context.Context, sessionID string) ([]SubagentForkContextMessage, error)

ListSubagentForkContext loads only the hidden rows reserved for a forked subagent's inherited model context. Other invisible history rows are ignored.

func (*Service) ListSubagentsByParent

func (s *Service) ListSubagentsByParent(ctx context.Context, parentSessionID string) ([]Thread, error)

ListSubagentsByParent returns active subagent sessions created under a parent session.

func (*Service) MessageCount

func (s *Service) MessageCount(ctx context.Context, sessionID string) (int64, error)

func (*Service) SetACPSetupValidator

func (s *Service) SetACPSetupValidator(validator ACPSetupValidator)

func (*Service) SetHookService

func (s *Service) SetHookService(h *hooks.Service)

func (*Service) SoftDelete

func (s *Service) SoftDelete(ctx context.Context, sessionID string) error

SoftDelete marks a session as deleted.

func (*Service) Touch

func (s *Service) Touch(ctx context.Context, sessionID string) error

Touch updates a session's updated_at timestamp.

func (*Service) UpdateDescriptorAndMetadataWithOwner

func (s *Service) UpdateDescriptorAndMetadataWithOwner(ctx context.Context, sessionID, typ, sessionMode, runtimeType string, metadata, runtimeMetadata map[string]any, botAgentID *string, runtimeOwnerUserID string) (Thread, error)

UpdateDescriptorAndMetadataWithOwner updates the session mode/runtime descriptor directly. Callers that only patch metadata for a Phase 3 session must pass the existing descriptor so discuss+ACP sessions keep their runtime.

func (*Service) UpdateEmptyDescriptorAndMetadataWithOwner

func (s *Service) UpdateEmptyDescriptorAndMetadataWithOwner(ctx context.Context, sessionID, typ, sessionMode, runtimeType string, metadata, runtimeMetadata map[string]any, botAgentID *string, runtimeOwnerUserID string) (Thread, error)

UpdateEmptyDescriptorAndMetadataWithOwner updates a session runtime identity only while its visible history is still empty. PostgreSQL-backed stores lock the bot and session in the same transaction as the message check, descriptor update, runtime-fence bump, and ACP snapshot invalidation. The lock ordering matches runtime activation, so an already-running turn linearizes before the empty-history check and an old runtime token cannot write after the update.

func (*Service) UpdateMetadata

func (s *Service) UpdateMetadata(ctx context.Context, sessionID string, metadata map[string]any) (Thread, error)

UpdateMetadata updates a session's metadata.

func (*Service) UpdateTitle

func (s *Service) UpdateTitle(ctx context.Context, sessionID, title string) (Thread, error)

UpdateTitle updates a session's title.

func (*Service) UpdateTypeAndMetadata

func (s *Service) UpdateTypeAndMetadata(ctx context.Context, sessionID, typ string, metadata map[string]any) (Thread, error)

UpdateTypeAndMetadata updates a session's runtime type and metadata in one statement so callers don't expose a half-updated agent selection.

func (*Service) UpdateTypeAndMetadataWithOwner

func (s *Service) UpdateTypeAndMetadataWithOwner(ctx context.Context, sessionID, typ string, metadata map[string]any, runtimeOwnerUserID string) (Thread, error)

UpdateTypeAndMetadataWithOwner updates a session descriptor and binds any ACP runtime ownership to a server-confirmed account id. The metadata owner field is never trusted from callers.

type SubagentConfig

type SubagentConfig struct {
	ThreadID     string    `json:"session_id"`
	ModelUUID    string    `json:"model_uuid,omitempty"`
	ModelID      string    `json:"model_id"`
	ProviderName string    `json:"provider"`
	Forked       bool      `json:"fork"`
	CreatedAt    time.Time `json:"created_at"`
	UpdatedAt    time.Time `json:"updated_at"`
}

SubagentConfig is the persisted runtime selection for a managed subagent.

type SubagentForkContextMessage

type SubagentForkContextMessage struct {
	SourceMessageID string          `json:"source_message_id,omitempty"`
	Role            string          `json:"role"`
	Message         json.RawMessage `json:"message"`
}

SubagentForkContextMessage is one hidden history message copied or materialized into a forked subagent session.

type Thread

type Thread struct {
	ID                    string         `json:"id"`
	BotID                 string         `json:"bot_id"`
	BotAgentID            string         `json:"bot_agent_id,omitempty"`
	RouteID               string         `json:"route_id,omitempty"`
	ChannelType           string         `json:"channel_type,omitempty"`
	Type                  string         `json:"type"`
	SessionMode           string         `json:"session_mode"`
	RuntimeType           string         `json:"runtime_type"`
	RuntimeMetadata       map[string]any `json:"runtime_metadata,omitempty"`
	Title                 string         `json:"title"`
	Metadata              map[string]any `json:"metadata,omitempty"`
	ParentThreadID        string         `json:"parent_session_id,omitempty"`
	CreatedByUserID       string         `json:"created_by_user_id,omitempty"`
	WorkdirID             string         `json:"workdir_id,omitempty"`
	CreatedAt             time.Time      `json:"created_at"`
	UpdatedAt             time.Time      `json:"updated_at"`
	RouteMetadata         map[string]any `json:"route_metadata,omitempty"`
	RouteConversationType string         `json:"route_conversation_type,omitempty"`
	Visibility            Visibility     `json:"-"`

} // @name session.Session

Thread represents a chat thread within a bot.

type Visibility

type Visibility string

Visibility describes whether a thread belongs in user-facing history or is reserved for an internal agent workflow.

const (
	VisibilityUser     Visibility = "user"
	VisibilityInternal Visibility = "internal"
)

func NormalizeVisibility

func NormalizeVisibility(stored string, mode string) Visibility

NormalizeVisibility resolves a possibly-absent stored visibility value against the session mode's default. Exported for restore paths that replay archived session rows which may predate the visibility column.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL