services

package
v1.44.0 Latest Latest
Warning

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

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

Documentation

Overview

Package services provides the server-side service implementations.

Package services provides the server-side service implementations.

Index

Constants

View Source
const MaxWebhookBodyBytes = 5 << 20 // 5 MiB

MaxWebhookBodyBytes bounds the inbound webhook request body for both GitHubWebhookHandler and GenericWebhookHandler — a DoS guard (webhook-triggers plan.md pitfalls §1.3).

Variables

View Source
var (
	// ErrSettingsNotFound is returned when the Claude settings file is not found.
	ErrSettingsNotFound = fmt.Errorf("settings file not found")
)

Functions

func ExpandPath added in v1.1.2

func ExpandPath(arg string) string

ExpandPath expands tilde and environment variables in a single path token and normalises absolute paths with filepath.Clean.

  • "~" → home directory
  • "~/foo" → filepath.Join(home, "foo")
  • anything else → os.ExpandEnv (handles $HOME, $TMPDIR, $VAR, …)

After expansion, absolute paths are passed through filepath.Clean to remove redundant separators and ".." components (e.g. "/foo/../bar" → "/bar").

func ExtractDomainsFromCommand

func ExtractDomainsFromCommand(cmd string) []string

ExtractDomainsFromCommand parses network-relevant Bash commands and returns the eTLD+1 (registered domain) for each URL found, deduplicated.

func FormatSecretDenyMessage

func FormatSecretDenyMessage(patternName string) string

FormatSecretDenyMessage returns a user-facing denial message for a secret scan hit.

func HasActiveReviewSession added in v1.41.0

func HasActiveReviewSession(priorSessions []session.ItemSessionSummary) bool

HasActiveReviewSession reports whether any of the provided ItemSessions is an open (not yet ended) review-role session. Mirrors hasActiveWorkSession; used by AutoRespawnReview to avoid double-spawning a review pass that is already running, and by server/mcp's request_review handler to refuse re-routing a pr_pending item out from under a running reviewer (FR2).

func InjectHookConfig

func InjectHookConfig(rootDir, sessionTitle string) error

InjectHookConfig writes (or merges) the stapler-squad PermissionRequest HTTP hook into <rootDir>/.claude/settings.local.json.

If the file already contains a hook pointing to hookApprovalURL(), it is left unchanged. If the file exists but lacks our hook, the hook is prepended to PermissionRequest. If the file does not exist, it is created with just our hook config.

func InjectHooksConfig added in v1.17.0

func InjectHooksConfig(rootDir, sessionTitle string, hooks []HookName) error

InjectHooksConfig writes (or merges) hook entries into <rootDir>/.claude/settings.local.json.

  • HookPermissionApproval is always injected regardless of the hooks slice.
  • Each hook entry is a curl command POSTing to the server endpoint with X-CS-Session-ID set to sessionTitle.
  • The write is atomic (temp file + rename).
  • Idempotent: existing entries pointing to our URL are preserved.

func InjectMCPConfig added in v1.17.0

func InjectMCPConfig(rootDir, binaryPath string) error

InjectMCPConfig writes (or updates) the stapler-squad MCP server entry into <rootDir>/.mcp.json (the Claude Code project-scope MCP file).

Behavior:

  • If the file already contains our entry pointing to the same binary, it is a no-op.
  • If the file exists without our entry, the entry is merged in.
  • If the file does not exist, it is created.
  • The write is atomic (temp file + rename).

binaryPath should be the absolute path to the stapler-squad binary (use os.Executable()).

Note: sessions spawned by stapler-squad also receive a per-session --mcp-config flag via buildClaudeCommand/claudeMCPConfigArgs, which is the primary MCP injection path. InjectMCPConfig serves as a fallback for tools that read .mcp.json directly (e.g. the MCP tools_lifecycle inject_mcp_config tool).

func LoadClaudeSettingsRules

func LoadClaudeSettingsRules(projectDir string) []classifier.Rule

LoadClaudeSettingsRules parses both the global and project-level Claude settings and returns AutoAllow rules derived from their permissions.allow lists.

Search order:

  1. ~/.claude/settings.json (global)
  2. ~/.claude/settings.local.json (global local overrides)
  3. <projectDir>/.claude/settings.json (project)
  4. <projectDir>/.claude/settings.local.json (project local)

Project settings take precedence: if both define the same tool pattern, the project rule will be checked first due to higher priority.

func RemoveHooksConfig added in v1.41.0

func RemoveHooksConfig(rootDir string, hooks []HookName) error

RemoveHooksConfig strips any previously-injected entries for the given hooks from <rootDir>/.claude/settings.local.json, leaving every other hook (ours or the user's own) untouched. It is the inverse of InjectHooksConfig, needed because a backlog work session's worktree/branch is reused across reopen cycles (see spawnSessionAfterGates in backlog_service_triage.go — same "backlog/<item>" branch every revision): without an explicit removal step, a hook injected while an item was spawned autonomously would otherwise persist in that worktree's settings file forever, even after a later manual ("Reopen for Revision") respawn on the same worktree — silently violating the "never inject into a human-driven session" scoping requirement HookGitDriftCheck depends on. Call this whenever spawning a session in a mode that must NOT have a given hook, symmetrically with the InjectHooksConfig call used for the mode that must.

No-op (not an error) if the settings file doesn't exist or doesn't reference the hook — safe to call unconditionally on every spawn.

func RemoveMCPConfig added in v1.17.0

func RemoveMCPConfig(rootDir string) error

RemoveMCPConfig removes the stapler-squad entry from <rootDir>/.mcp.json. If the file is missing or has no entry for stapler-squad, it is a no-op.

func SetHookBaseURLFn added in v1.37.0

func SetHookBaseURLFn(fn func() string)

SetHookBaseURLFn overrides the base URL function used when building hook endpoint URLs via hookEndpoints. Call once during server wiring; passing nil is a no-op.

func StartBacklogGitHubForwardSyncSubscriber added in v1.41.0

func StartBacklogGitHubForwardSyncSubscriber(ctx context.Context, bus *events.EventBus, registry *session.PluginRegistry, syncLoop *session.SyncLoop, storage *session.Storage)

StartBacklogGitHubForwardSyncSubscriber subscribes to the EventBus and closes the GitHub issue linked to a backlog item when that item transitions to done, if the item's source has ForwardSyncEnabled. Mirrors analytics.StartAnalyticsSubscriber's skeleton (server/analytics/subscriber.go).

registry and syncLoop are threaded in as separate parameters — rather than deriving registry from syncLoop.Registry(), as plan.md's original sketch assumed — because *session.SyncLoop has no exported registry accessor and deps.SyncLoop (server/dependencies.go) is always nil in the current dependency graph (the live periodic SyncLoop is owned internally by session.BacklogController). Callers should pass deps.BacklogService.Registry() and deps.BacklogService.SyncLoopForForwardSync() instead — see server.go's wiring — which share the same plugin registry and key provider TriggerSync already uses.

func StartExpirationCleanup

func StartExpirationCleanup(ctx context.Context, store *ApprovalStore)

StartExpirationCleanup starts a background goroutine that periodically removes expired approvals. The goroutine stops when ctx is canceled.

func ValidateCallbackURL added in v1.43.0

func ValidateCallbackURL(ctx context.Context, rawURL string) error

ValidateCallbackURL rejects an outbound-callback target that could be used for SSRF: non-http(s) schemes, and any resolved IP that is loopback, link-local, or private-range (including the cloud-metadata address).

Called from two places (AC11's two halves): CallbackConfigService.UpdateCallbackConfig at config-save time, and CallbackDispatcher inside its per-attempt retry loop at send time. A single check at save time is not sufficient — DNS can change between save and any later delivery attempt (TOCTOU / DNS-rebinding, plan.md pitfalls §5) — so this function must be called again on every delivery attempt, not cached from the save-time check.

Deliberately does not echo rawURL (or the parsed host) back into its error messages beyond what's needed for an operator to understand a save-time rejection — callers that log a validation failure (CallbackDispatcher) must still avoid logging the URL itself per the redaction requirement; this function keeps that easy by never constructing an error that contains the full URL (which could carry embedded credentials).

func VerifyGitHubSignature added in v1.43.0

func VerifyGitHubSignature(secret string, body []byte, sigHeader string) bool

VerifyGitHubSignature reports whether sigHeader (the raw X-Hub-Signature-256 header value, e.g. "sha256=<hex>") is a valid HMAC-SHA256 signature of body computed with secret. Comparison is constant-time via hmac.Equal — never == or bytes.Equal, which leak timing information about how many leading bytes matched (webhook-triggers Epic 2.1).

func VerifyWebhookSecret added in v1.43.0

func VerifyWebhookSecret(secret string, body []byte, sigHeader string) bool

VerifyWebhookSecret reports whether sigHeader (e.g. "sha256=<hex>") is a valid HMAC-SHA256 signature of body computed with secret. Same scheme as VerifyGitHubSignature, exposed under its own name for the generic `webhook` trigger type's configurable signature header (X-Webhook-Signature) — kept as a distinct function (not an alias) so the two trigger types' verification call sites can diverge independently if a future provider needs a different scheme.

func WriteSnapshot

func WriteSnapshot(snap *DebugSnapshot, dir string) (string, error)

WriteSnapshot serializes the snapshot to a JSON file in the given directory. Returns the absolute path of the written file.

Types

type AIClient added in v1.35.0

type AIClient interface {
	Complete(ctx context.Context, systemPrompt, userPrompt string) (string, error)
}

AIClient sends assembled prompts to an AI backend and returns the raw response. ctx cancellation must abort the outbound request.

func NewBestAvailableAIClient added in v1.35.0

func NewBestAvailableAIClient(anthropicAPIKey string, specs []CLIAgentSpec) (AIClient, string)

NewBestAvailableAIClient returns the highest-priority available AIClient and a string identifying the backend selected. Returns (nil, "") when no backend is available.

specs is the ordered list of CLIAgentSpec entries to probe; pass knownCLIAgents at production call sites. Tests may pass a custom slice to avoid PATH lookups.

Priority order:

  1. First matching CLI agent from specs (handles its own auth)
  2. Anthropic HTTP API — fallback if anthropicAPIKey is non-empty

CLI agents are preferred because they manage their own authentication and model selection, requiring no extra configuration in stapler-squad.

type AgyCredentialSource added in v1.35.0

type AgyCredentialSource struct {
	// HomeDirOverride overrides os.UserHomeDir() — used in tests.
	HomeDirOverride string
}

AgyCredentialSource resolves Google/Gemini credentials for Antigravity users. It tries sources in this order:

  1. ~/.gemini/oauth_creds.json (written by `agy auth login`)
  2. ~/.config/gcloud/application_default_credentials.json (gcloud ADC)

When the ADC path is selected, Credential.IsADC is set to true — the caller must use golang.org/x/oauth2/google.FindDefaultCredentials rather than setting an Authorization header manually.

func (*AgyCredentialSource) Name added in v1.35.0

func (s *AgyCredentialSource) Name() string

func (*AgyCredentialSource) Resolve added in v1.35.0

func (s *AgyCredentialSource) Resolve(_ context.Context, provider string) (Credential, bool, error)

type AnalyticsEntry

type AnalyticsEntry struct {
	ID             string    `json:"id"`
	Timestamp      time.Time `json:"timestamp"`
	SessionID      string    `json:"session_id"`
	ToolName       string    `json:"tool_name"`
	CommandPreview string    `json:"command_preview"` // first 200 chars
	Cwd            string    `json:"cwd"`
	// Decision: "auto_allow" | "auto_deny" | "escalate" | "manual_allow" | "manual_deny"
	Decision    string `json:"decision"`
	RiskLevel   string `json:"risk_level"`
	RuleID      string `json:"rule_id,omitempty"`
	RuleName    string `json:"rule_name,omitempty"`
	Reason      string `json:"reason,omitempty"`
	Alternative string `json:"alternative,omitempty"`
	DurationMs  int64  `json:"duration_ms"`
	ApprovalID  string `json:"approval_id,omitempty"`

	// AST-derived command categorization (Bash tool only).
	// CommandProgram is the primary executable being called (e.g., "git", "npm").
	CommandProgram string `json:"command_program,omitempty"`
	// CommandCategory groups CommandProgram into a high-level category (e.g., "vcs", "node").
	CommandCategory string `json:"command_category,omitempty"`
	// CommandSubcategory is the first positional subcommand (e.g., "commit" for "git commit").
	CommandSubcategory string `json:"command_subcommand,omitempty"`
	// PythonImports lists top-level module names imported in inline Python (-c) invocations.
	PythonImports []string `json:"python_imports,omitempty"`
}

AnalyticsEntry records a single classification decision.

func ReclassifyGaps

func ReclassifyGaps(entries []AnalyticsEntry, c classifier.Classifier) []AnalyticsEntry

ReclassifyGaps re-runs the current classifier against entries that were previously escalated with no matching rule (coverage gaps). Any entry that the current rules would now auto-allow or auto-deny has its Decision and RuleID updated in the returned copy — the underlying JSONL file is unchanged.

Call this before ComputeSummary when you want coverage-gap metrics to reflect the CURRENT rule set rather than the rules that were active when the entry was recorded. This prevents historical gaps from artificially inflating the gap rate after new rules are added.

type AnalyticsGap added in v1.35.0

type AnalyticsGap struct {
	ToolName           string
	Program            string
	Count              int
	RepresentativeCmds []string // up to 5 truncated command previews
}

AnalyticsGap groups escalated, rule-less analytics entries by (ToolName, Program).

type AnalyticsStore

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

AnalyticsStore writes AnalyticsEntry records asynchronously to SQLite via session.Storage and provides aggregations.

func NewAnalyticsStore

func NewAnalyticsStore(storage *session.Storage) *AnalyticsStore

NewAnalyticsStore creates an AnalyticsStore backed by the given storage. Call Start() to begin the background flush goroutine.

func (*AnalyticsStore) DroppedCount

func (s *AnalyticsStore) DroppedCount() int64

DroppedCount returns the number of entries dropped due to buffer overflow.

func (*AnalyticsStore) GetSubcommandBreakdown added in v1.35.0

func (s *AnalyticsStore) GetSubcommandBreakdown(ctx context.Context, program string, since time.Time) ([]session.SubcommandDecisionCount, error)

GetSubcommandBreakdown returns per-(subcommand, decision) aggregate counts.

func (*AnalyticsStore) ListRecentCommands added in v1.35.0

func (s *AnalyticsStore) ListRecentCommands(ctx context.Context, program, subcommand string, since time.Time, n int) ([]string, error)

ListRecentCommands returns up to n command_preview strings for (program, subcommand). Pass subcommand="" to match all subcommands.

func (*AnalyticsStore) LoadProgramWindow added in v1.35.0

func (s *AnalyticsStore) LoadProgramWindow(ctx context.Context, program string, since time.Time) ([]AnalyticsEntry, error)

LoadProgramWindow loads analytics entries for a specific program in the given window. Used by GetProgramAnalytics RPC for trend computation.

func (*AnalyticsStore) LoadWindow

func (s *AnalyticsStore) LoadWindow(since time.Time) ([]AnalyticsEntry, error)

LoadWindow reads entries from DB with timestamps >= since. Uses a DB-level WHERE clause via ListAnalyticsSince (AC-1).

func (*AnalyticsStore) Record

func (s *AnalyticsStore) Record(entry AnalyticsEntry)

Record enqueues an analytics entry for async write. Non-blocking. If the buffer is full, the entry is dropped and the dropped counter incremented.

func (*AnalyticsStore) RecordFromResult

func (s *AnalyticsStore) RecordFromResult(payload classifier.PermissionRequestPayload, result classifier.ClassificationResult, sessionID, approvalID string, durationMs int64)

RecordFromResult builds and records an AnalyticsEntry from classification output.

func (*AnalyticsStore) RecordManualDecision

func (s *AnalyticsStore) RecordManualDecision(approvalID, sessionID, toolName, cwd, decision string)

RecordManualDecision records a manual approve/deny decision for an approval.

func (*AnalyticsStore) Start

func (s *AnalyticsStore) Start(ctx context.Context)

Start launches the background goroutine that flushes entries to disk. It stops when Stop is called or ctx is canceled.

func (*AnalyticsStore) Stop added in v1.41.0

func (s *AnalyticsStore) Stop()

Stop cancels the background flush goroutine and waits for it to drain and exit. Idempotent — safe to call multiple times or before Start.

type AnalyticsSummary

type AnalyticsSummary struct {
	TotalDecisions    int            `json:"total_decisions"`
	DecisionCounts    map[string]int `json:"decision_counts"`
	TopTools          []ToolStat     `json:"top_tools"`
	TopDeniedCommands []CommandStat  `json:"top_denied_commands"`
	TopTriggeredRules []RuleStat     `json:"top_triggered_rules"`
	// TopCommandPrograms lists the most frequently invoked programs via the Bash tool.
	TopCommandPrograms []ProgramStat `json:"top_command_programs"`
	// TopPythonImports lists the most frequently imported Python modules from inline (-c) invocations.
	TopPythonImports []ImportStat `json:"top_python_imports"`
	AutoApproveRate  float64      `json:"auto_approve_rate"`
	ManualReviewRate float64      `json:"manual_review_rate"`
	WindowStart      time.Time    `json:"window_start"`
	WindowEnd        time.Time    `json:"window_end"`

	// Coverage gap: decisions that escaped all rules (escalated with no rule_id).
	// These are prime candidates for new rules to reduce manual review.
	CoverageGapCount     int           `json:"coverage_gap_count"`
	CoverageGapRate      float64       `json:"coverage_gap_rate"` // percentage 0–100
	TopUncoveredTools    []ToolStat    `json:"top_uncovered_tools"`
	TopUncoveredPrograms []ProgramStat `json:"top_uncovered_programs"`

	// CommandSubcommandStats is the complete (program, subcommand) distribution — not
	// truncated to top-N. Use this for drill-down analysis such as "which gh subcommands
	// does Claude use most?" or "what sed patterns need rules?".
	CommandSubcommandStats []SubcommandStat `json:"command_subcommand_stats"`

	// EscalationReasonCounts breaks down escalations by category (classifier.EscalationCategory
	// string values) — no-match, explicit-rule, domain-age, secret-scan, unclassifiable.
	EscalationReasonCounts map[string]int `json:"escalation_reason_counts"`

	// RiskLevelCounts breaks down escalations by classifier.RiskLevel string value
	// ("low"/"medium"/"high"/"critical"), scoped to escalated decisions only — same scope as
	// EscalationReasonCounts, so the two breakdowns share a denominator.
	RiskLevelCounts map[string]int `json:"risk_level_counts"`
}

AnalyticsSummary aggregates decisions over a time window.

func ComputeSummary

func ComputeSummary(entries []AnalyticsEntry) AnalyticsSummary

ComputeSummary aggregates a slice of entries into an AnalyticsSummary. Pure function -- no I/O.

type AnthropicAIClient added in v1.35.0

type AnthropicAIClient struct {
	OnResponseHeaders func(http.Header)
	// contains filtered or unexported fields
}

AnthropicAIClient implements AIClient using the Anthropic Messages API. It accepts a Credential rather than a raw API key string so that both API-key users and Claude subscription (OAuth) users are supported.

func NewAnthropicAIClient added in v1.35.0

func NewAnthropicAIClient(cred Credential) (*AnthropicAIClient, error)

NewAnthropicAIClient creates an AnthropicAIClient from a resolved Credential. Returns an error if the credential is not valid for Anthropic.

func NewAnthropicAIClientFromKey added in v1.35.0

func NewAnthropicAIClientFromKey(apiKey string) (*AnthropicAIClient, error)

NewAnthropicAIClientFromKey is a convenience constructor for callers that already have a raw API key string (e.g. tests, legacy wiring). Prefer NewAnthropicAIClient + CredentialChain for new code.

func (*AnthropicAIClient) Complete added in v1.35.0

func (c *AnthropicAIClient) Complete(ctx context.Context, systemPrompt, userPrompt string) (string, error)

Complete sends systemPrompt and userPrompt to the Anthropic API and returns the response text. ctx cancellation aborts the outbound HTTP request.

type AnthropicLimitsClient added in v1.35.0

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

func NewAnthropicLimitsClient added in v1.35.0

func NewAnthropicLimitsClient(chain *CredentialChain, model string) *AnthropicLimitsClient

func (*AnthropicLimitsClient) ModelContextWindow added in v1.35.0

func (c *AnthropicLimitsClient) ModelContextWindow(model string) int

func (*AnthropicLimitsClient) Provider added in v1.35.0

func (c *AnthropicLimitsClient) Provider() string

func (*AnthropicLimitsClient) QueryLimits added in v1.35.0

func (c *AnthropicLimitsClient) QueryLimits(ctx context.Context) (ProviderLimits, error)

func (*AnthropicLimitsClient) UpdateFromResponseHeaders added in v1.35.0

func (c *AnthropicLimitsClient) UpdateFromResponseHeaders(h http.Header, current ProviderLimits) ProviderLimits

type ApprovalDecision

type ApprovalDecision struct {
	Behavior string // "allow" or "deny"
	Message  string // Optional reason shown to Claude on deny
}

ApprovalDecision is the user's response to a pending approval.

type ApprovalDetail

type ApprovalDetail struct {
	ID              string                 `json:"id"`
	SessionID       string                 `json:"session_id"`
	ClaudeSessionID string                 `json:"claude_session_id"`
	ToolName        string                 `json:"tool_name"`
	ToolInput       map[string]interface{} `json:"tool_input,omitempty"`
	Cwd             string                 `json:"cwd"`
	PermissionMode  string                 `json:"permission_mode"`
	CreatedAt       time.Time              `json:"created_at"`
	ExpiresAt       time.Time              `json:"expires_at"`
}

ApprovalDetail captures the fields of a single pending approval.

type ApprovalHandler

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

ApprovalHandler handles Claude Code HTTP hooks for PermissionRequest events. It blocks the HTTP connection open while waiting for the user's decision, then returns the decision in the hookSpecificOutput JSON format.

func NewApprovalHandler

func NewApprovalHandler(store *ApprovalStore, storage *session.Storage, eventBus *events.EventBus) *ApprovalHandler

NewApprovalHandler creates a new ApprovalHandler.

func (*ApprovalHandler) HandlePermissionRequest

func (h *ApprovalHandler) HandlePermissionRequest(w http.ResponseWriter, r *http.Request)

HandlePermissionRequest handles POST /api/hooks/permission-request. This endpoint is configured as an HTTP hook in Claude Code's settings. It blocks until the user approves/denies or the context is canceled.

func (*ApprovalHandler) SetAnalyticsStore

func (h *ApprovalHandler) SetAnalyticsStore(a *AnalyticsStore)

SetAnalyticsStore injects an AnalyticsStore for recording classification decisions.

func (*ApprovalHandler) SetAutoApprovalLogger added in v1.35.0

func (h *ApprovalHandler) SetAutoApprovalLogger(l autoApprovalLogger)

SetAutoApprovalLogger injects a logger for writing silent auto-approval records to notification history. When set, AutoAllow and AutoDeny decisions are recorded without triggering toasts or push notifications, giving users a reviewable log of what the classifier handled automatically.

func (*ApprovalHandler) SetAutonomousChecker added in v1.35.0

func (h *ApprovalHandler) SetAutonomousChecker(fn func(string) bool)

SetAutonomousChecker injects a function that returns true when the given session ID is an autonomous session. Injected from server.go to avoid a construction-time circular dependency.

func (*ApprovalHandler) SetClassifier

func (h *ApprovalHandler) SetClassifier(c classifier.Classifier)

SetClassifier injects a Classifier for auto-approving/denying tool use requests before they reach the manual review queue.

func (*ApprovalHandler) SetDashboardBaseURLFn added in v1.44.0

func (h *ApprovalHandler) SetDashboardBaseURLFn(fn func() string)

SetDashboardBaseURLFn wires the lazily-read dashboard-base-URL fallback (see dashboardBaseURLFn's doc comment) used when building Slack "view in dashboard" links for approval-pending notifications. nil-safe: when never called, broadcastApprovalNotification falls back to whatever cfg.Slack.DashboardBaseURL is (possibly empty, omitting the link).

func (*ApprovalHandler) SetDomainChecker

func (h *ApprovalHandler) SetDomainChecker(d *DomainAgeChecker)

SetDomainChecker injects a DomainAgeChecker for escalating requests to newly-registered domains.

func (*ApprovalHandler) SetHeadlessPool added in v1.35.0

func (h *ApprovalHandler) SetHeadlessPool(pool headlessPoolApprover)

SetHeadlessPool injects a headless LLM pool for autonomous session approval. When set and autonomousChecker returns true for a session, risky tool calls are sent to the LLM for approval instead of the human review queue.

func (*ApprovalHandler) SetLiveInstanceFinder added in v1.41.0

func (h *ApprovalHandler) SetLiveInstanceFinder(f LiveInstanceFinder)

SetLiveInstanceFinder wires the live in-memory instance lookup used to populate ClassificationContext.CIStatus. GitHubCheckConclusion/LastPRStatusCheck are not persisted in the ent schema (see Storage.UpdateInstancePRStatus) — they only live on the in-memory Instance the PRStatusPoller keeps fresh — so a *session.Storage lookup cannot see them; this must be the live registry (typically *SessionService).

func (*ApprovalHandler) SetNotificationStamper

func (h *ApprovalHandler) SetNotificationStamper(s approvalNotificationStamper)

SetNotificationStamper injects a stamper for persisting approval outcomes on notification records. When set, resolved and timed-out approvals are stamped with approval_decision in their metadata so the notification panel can show a persistent badge after page refresh.

func (*ApprovalHandler) SetPollInterval added in v1.41.0

func (h *ApprovalHandler) SetPollInterval(d time.Duration)

SetPollInterval overrides the interval used to bound CI-status staleness (Task 1.1.2b). Callers should pass the live PRStatusPoller's configured interval so the guard can't silently desync from the poller if it's ever tuned.

func (*ApprovalHandler) SetQueueChecker

func (h *ApprovalHandler) SetQueueChecker(checker ReviewQueueChecker)

SetQueueChecker injects a ReviewQueueChecker for triggering immediate review queue updates when a new approval is created. This provides <100ms feedback instead of waiting for the next 2-second poll cycle.

func (*ApprovalHandler) SetSlackNotifier added in v1.44.0

func (h *ApprovalHandler) SetSlackNotifier(n *SlackNotifier)

SetSlackNotifier injects the Slack notifier used to notify a configured webhook about new pending approvals (see broadcastApprovalNotification). nil-safe: when never called, h.slackNotifier stays nil and Slack notification is silently skipped — matching every other optional Set* dependency in this file.

func (*ApprovalHandler) SlackNotifierForTest added in v1.44.0

func (h *ApprovalHandler) SlackNotifierForTest() *SlackNotifier

SlackNotifierForTest returns the wired SlackNotifier instance. Exported only so cross-package wiring regression tests (server package) can assert pointer identity against the other consumers (ReactiveQueueManager, SessionService) without restructuring production code — not intended for any non-test caller.

type ApprovalService

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

ApprovalService handles Claude Code hook approval RPCs.

func NewApprovalService

func NewApprovalService(store *ApprovalStore) *ApprovalService

NewApprovalService creates an ApprovalService with the given ApprovalStore.

func (*ApprovalService) ListPendingApprovals

ListPendingApprovals returns all pending approval requests, optionally filtered by session ID.

func (*ApprovalService) ResolveApproval

ResolveApproval sends the user's decision to the blocked HTTP hook handler.

func (*ApprovalService) SetEventBus added in v1.35.0

func (as *ApprovalService) SetEventBus(bus *events.EventBus)

SetEventBus wires in the event bus so that resolved approvals are broadcast to all connected clients via the watchSessions stream. Without this, Device B has no real-time signal when Device A resolves an approval.

func (*ApprovalService) SetLiveInstanceFinder added in v1.41.0

func (as *ApprovalService) SetLiveInstanceFinder(f LiveInstanceFinder)

SetLiveInstanceFinder wires the live in-memory instance lookup used by the block-on-red-CI guard (AC5). See ApprovalHandler.SetLiveInstanceFinder for why this must be the live registry rather than *session.Storage.

func (*ApprovalService) SetNotificationStore

func (as *ApprovalService) SetNotificationStore(store approvalNotificationStamper)

SetNotificationStore wires in the notification history store so that resolved approvals are stamped with their decision in the notification metadata.

type ApprovalSnapshot

type ApprovalSnapshot struct {
	PendingCount int              `json:"pending_count"`
	Pending      []ApprovalDetail `json:"pending,omitempty"`
}

ApprovalSnapshot captures the pending approvals state.

type ApprovalStore

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

ApprovalStore manages pending approval requests with thread-safe access.

func NewApprovalStore

func NewApprovalStore(filePath string) *ApprovalStore

NewApprovalStore creates a new ApprovalStore. If filePath is non-empty, persisted approvals are loaded from disk and marked as orphaned.

func (*ApprovalStore) CancelSession

func (s *ApprovalStore) CancelSession(sessionID string) []string

CancelSession denies all pending approvals for a session (e.g., on restart).

func (*ApprovalStore) CleanupExpired

func (s *ApprovalStore) CleanupExpired() []string

CleanupExpired removes approvals past their ExpiresAt and denies them with a timeout message. Also removes orphaned approvals older than orphanedCleanupThreshold (4 hours). Returns the IDs of cleaned-up approvals.

func (*ApprovalStore) Create

func (s *ApprovalStore) Create(a *PendingApproval) error

Create adds a new pending approval to the store and initializes its decision channel.

func (*ApprovalStore) Get

func (s *ApprovalStore) Get(id string) (*PendingApproval, bool)

Get retrieves a pending approval by ID.

func (*ApprovalStore) GetApprovalMetadataBySession

func (s *ApprovalStore) GetApprovalMetadataBySession(sessionID string) []session.ApprovalMetadata

GetApprovalMetadataBySession implements session.ApprovalMetadataProvider. Returns approval metadata for all pending approvals matching the given session ID.

func (*ApprovalStore) GetBySession

func (s *ApprovalStore) GetBySession(sessionID string) []*PendingApproval

GetBySession returns all pending approvals for a given session.

func (*ApprovalStore) GetFilePath

func (s *ApprovalStore) GetFilePath() string

GetFilePath returns the file path used for persistence (for testing/wiring).

func (*ApprovalStore) ListAll

func (s *ApprovalStore) ListAll() []*PendingApproval

ListAll returns all currently pending approvals.

func (*ApprovalStore) Remove

func (s *ApprovalStore) Remove(id string)

Remove removes an approval from the store without sending a decision. The pending HTTP handler will detect context cancellation or its own timeout.

func (*ApprovalStore) Resolve

func (s *ApprovalStore) Resolve(id string, decision ApprovalDecision) error

Resolve sends a decision to the pending approval and removes it from the store. Returns an error if the approval doesn't exist or was already resolved. For orphaned approvals (loaded from disk after restart), the record is simply removed since there is no live HTTP connection to send the decision to.

type AutonomousDriverStarter added in v1.35.0

type AutonomousDriverStarter interface {
	StartAutonomousDriverForInstance(inst *session.Instance)
	StartAutonomousDriverWithTimeout(inst *session.Instance, startupTimeout time.Duration)
}

AutonomousDriverStarter allows BacklogService to start an AutonomousDriver on an existing instance. Wired via SetAutonomousDriverStarter from server.go after both services are constructed.

type AutonomousOrchestrationService added in v1.35.0

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

AutonomousOrchestrationService manages the lifecycle of AutonomousDriver instances: registering them on session creation, stopping them on deletion/hibernate, and handling their completion callbacks.

func NewAutonomousOrchestrationService added in v1.35.0

func NewAutonomousOrchestrationService(pool *headless.Pool, bus *events.EventBus) *AutonomousOrchestrationService

NewAutonomousOrchestrationService creates a new service. pool may be nil when the claude binary is not found; methods degrade gracefully.

func (*AutonomousOrchestrationService) SetAutonomousStuckRespawner added in v1.39.0

func (a *AutonomousOrchestrationService) SetAutonomousStuckRespawner(r AutonomousStuckRespawner)

SetAutonomousStuckRespawner wires the respawner (typically BacklogService) used to retry a turn-cap-stopped autonomous work session instead of forcing it to review.

func (*AutonomousOrchestrationService) SetInstanceFinder added in v1.35.0

func (a *AutonomousOrchestrationService) SetInstanceFinder(fn func(string) *session.Instance)

SetInstanceFinder wires a function for resolving live instances by title.

func (*AutonomousOrchestrationService) SetLifecycleContext added in v1.35.0

func (a *AutonomousOrchestrationService) SetLifecycleContext(ctx context.Context)

SetLifecycleContext binds the server's root context. Must be called once during server startup, before any sessions are created.

func (*AutonomousOrchestrationService) SetPool added in v1.35.0

func (a *AutonomousOrchestrationService) SetPool(pool *headless.Pool)

SetPool updates the headless pool after construction. Called from SessionService.SetHeadlessPool so the two stay in sync.

func (*AutonomousOrchestrationService) SetReviewGateTrigger added in v1.37.0

func (a *AutonomousOrchestrationService) SetReviewGateTrigger(t ReviewGateTrigger)

SetReviewGateTrigger wires the review gate trigger (typically BacklogLifecycleListener).

func (*AutonomousOrchestrationService) SetStorageGetter added in v1.35.0

func (a *AutonomousOrchestrationService) SetStorageGetter(fn func() *session.Storage)

SetStorageGetter wires a function for getting the concrete storage.

func (*AutonomousOrchestrationService) StartAutonomousDriverForInstance added in v1.35.0

func (a *AutonomousOrchestrationService) StartAutonomousDriverForInstance(inst *session.Instance)

StartAutonomousDriverForInstance starts an AutonomousDriver on inst if the pool is available. Satisfies the AutonomousDriverStarter interface (via SessionService delegate).

func (*AutonomousOrchestrationService) StartAutonomousDriverWithTimeout added in v1.35.0

func (a *AutonomousOrchestrationService) StartAutonomousDriverWithTimeout(inst *session.Instance, startupTimeout time.Duration)

StartAutonomousDriverWithTimeout is like StartAutonomousDriverForInstance but uses a configurable startup timeout for sessions that need a longer warm-up (e.g. triage sessions that spawn parallel subagents).

func (*AutonomousOrchestrationService) StopDriverForSession added in v1.35.0

func (a *AutonomousOrchestrationService) StopDriverForSession(sessionTitle string)

StopDriverForSession stops the AutonomousDriver registered under sessionTitle. Used by MCP handlers as a belt-and-suspenders stop after task completion. Satisfies mcp.ReviewCompletionSignaler.

func (*AutonomousOrchestrationService) TriggerReviewForSession added in v1.37.0

func (a *AutonomousOrchestrationService) TriggerReviewForSession(sessionUUID string)

TriggerReviewForSession is a public passthrough to the wired ReviewGateTrigger, used by the request_review MCP tool to spawn a review gate immediately instead of waiting for the next ReconcileStuck tick. No-op if no trigger is wired.

type AutonomousStuckRespawner added in v1.39.0

type AutonomousStuckRespawner interface {
	AutoRespawnAutonomousWork(ctx context.Context, itemID string) error
}

AutonomousStuckRespawner is implemented by BacklogService to give an in_progress backlog item a fresh work-session turn budget after an autonomous work session hits its turn cap without a DONE signal, instead of forcing the item into review against work the driver itself flagged incomplete (see onAutonomousDriverComplete's SessionRoleWork case). Gated by the same rework cap AutoReopenAfterFailedReview uses, so this respawn loop can't run forever either.

type BacklogAttachmentUploadHandler added in v1.39.0

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

BacklogAttachmentUploadHandler saves uploaded images for backlog item descriptions to a durable directory, independent of any session.

ponytail: uploads aren't tracked by item ID, so a file can outlive its backlog item (upload succeeds, item creation fails/is deleted) and become an orphan on disk. Accepted as YAGNI until proven — add a tracked attachment list + delete-on-item-delete wiring if orphan growth becomes a real problem.

func NewBacklogAttachmentUploadHandler added in v1.39.0

func NewBacklogAttachmentUploadHandler(dir string) (*BacklogAttachmentUploadHandler, error)

NewBacklogAttachmentUploadHandler creates a handler that saves into dir, creating it if necessary. Returns an error if dir cannot be created so the caller can fail registration instead of installing a handler backed by a possibly-missing directory.

func (*BacklogAttachmentUploadHandler) HandleUpload added in v1.39.0

+http: POST /api/v1/upload-backlog-attachment upload:backlog-attachment HandleUpload processes a multipart/form-data POST with a "file" field. Unlike the session upload handler, this validates the file is a real raster image via magic bytes — not just the declared MIME type or filename extension — since these attachments are embedded directly into markdown and rendered without further review.

type BacklogDebugMutateHandler added in v1.41.0

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

BacklogDebugMutateHandler mutates BacklogItem rows directly for the e2e suite.

func NewBacklogDebugMutateHandler added in v1.41.0

func NewBacklogDebugMutateHandler(storage *session.Storage) *BacklogDebugMutateHandler

NewBacklogDebugMutateHandler constructs the handler. storage may be nil in which case every request 503s.

func (*BacklogDebugMutateHandler) RegisterRoutes added in v1.41.0

func (h *BacklogDebugMutateHandler) RegisterRoutes(mux *http.ServeMux)

RegisterRoutes registers the debug mutate endpoints on the given mux. Callers MUST only invoke this when running as the e2e-local instance.

type BacklogDebugSeedHandler added in v1.38.0

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

BacklogDebugSeedHandler seeds BacklogStuckState rows for the e2e suite.

func NewBacklogDebugSeedHandler added in v1.38.0

func NewBacklogDebugSeedHandler(storage *session.Storage) *BacklogDebugSeedHandler

NewBacklogDebugSeedHandler constructs the handler. storage may be nil in which case every request 503s.

func (*BacklogDebugSeedHandler) RegisterRoutes added in v1.38.0

func (h *BacklogDebugSeedHandler) RegisterRoutes(mux *http.ServeMux)

RegisterRoutes registers the debug seed endpoints on the given mux. Callers MUST only invoke this when running as the e2e-local instance.

type BacklogItemEventPublisher added in v1.41.0

type BacklogItemEventPublisher struct {
	Bus *events.EventBus
}

BacklogItemEventPublisher adapts an *events.EventBus to session.ItemChangePublisher. The session package cannot import pkg/events directly (pkg/events imports session, so the reverse import would be a cycle), so this adapter lives here instead and is wired in via Storage.SetItemChangePublisher (session/storage.go), mirroring EventBusNotifier's adapter pattern for session.Notifier.

func (*BacklogItemEventPublisher) PublishItemChanged added in v1.41.0

func (p *BacklogItemEventPublisher) PublishItemChanged(item *session.BacklogItemData, change session.BacklogItemChange)

PublishItemChanged implements session.ItemChangePublisher. The entire body is wrapped in its own recover() so a panic anywhere inside it (payload construction, an unmapped BacklogChangeKind, bus.Publish itself) can never propagate into the repository method that called it — the same "best-effort side channel must not take down the caller" idiom this codebase already uses at runStuckDetector (session/backlog_lifecycle.go) and around PTY forwarding (server/services/session_service.go). Because the recover happens inside the adapter itself, session.ItemChangePublisher. PublishItemChanged deliberately keeps its no-error-return signature: there is never an error for a repository caller to log or swallow, because a panic can never reach it in the first place.

type BacklogService added in v1.35.0

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

BacklogService handles Backlog RPCs.

func NewBacklogService added in v1.35.0

func NewBacklogService(storage *session.Storage, creator SessionCreator, cfg *config.Config, engine session.WorkflowEngine, pipelineEngine session.PipelineEngine, pipelineModeRepo session.PipelineModeRepository) *BacklogService

NewBacklogService creates a BacklogService with all optional dependencies. storage and sourceBackend are typically the same (*session.Storage). sessionCreator and cfg may be nil; handlers degrade gracefully when absent.

Degradation contract: If creator is nil, RPCs that spawn sessions will return CodeUnimplemented. This is expected in test environments where a real session manager is unavailable.

func (*BacklogService) AddBacklogItemDependency added in v1.43.0

AddBacklogItemDependency marks BlockedItemId as depending on (blocked by) BlockerItemId, so DequeueNextQueuedItems skips it until the blocker resolves (reaches done or archived status, or is deleted). +api: backlog:add-item-dependency

func (*BacklogService) Admit added in v1.43.0

func (s *BacklogService) Admit(ctx context.Context) (bool, error)

Admit reports whether a new trigger-fired session may be created right now, per the same MaxConcurrentBacklogWorkItems WIP cap SpawnSessionFromItem's own gate enforces (backlog_service_triage.go). Implements server/workflows.AdmissionGate — wired into Scheduler at construction (server/dependencies.go) so Scheduler.FireNow/FireTrigger can no longer bypass this cap (webhook-triggers Epic 1.3, closing the collateral debt the 2026-07-12 OOM incident's WIP limit was meant to prevent everywhere).

func (*BacklogService) ApprovePlan added in v1.35.0

ApprovePlan marks the planning artifacts for an item as approved. +api: backlog:approve-plan

func (*BacklogService) ArchiveBacklogItem added in v1.35.0

ArchiveBacklogItem soft-deletes an item by setting its archived_at timestamp. +api: backlog:archive-item

func (*BacklogService) AttachSessionToItem added in v1.35.0

func (*BacklogService) AutoReopenAfterFailedReview added in v1.37.0

func (s *BacklogService) AutoReopenAfterFailedReview(ctx context.Context, itemID string) error

AutoReopenAfterFailedReview implements session.AutoReopenSpawner. It always transitions the item from review back to in_progress (required for request_review to become callable again — see its hardcoded ExpectedStatus precondition), then either spawns a new work session or, if one is already alive for this item, leaves it in place to continue and re-request review — so the review→rework cycle runs without manual intervention either way.

func (*BacklogService) AutoReopenForPRFix added in v1.37.0

func (s *BacklogService) AutoReopenForPRFix(ctx context.Context, itemID string, fixContext string) error

AutoReopenForPRFix implements session.PRFixSpawner. It transitions the item from pr_pending back to in_progress and spawns a new autonomous work session pre-loaded with the CI/review failure context so the agent can fix and push.

func (*BacklogService) AutoRespawnAutonomousWork added in v1.39.0

func (s *BacklogService) AutoRespawnAutonomousWork(ctx context.Context, itemID string) error

AutoRespawnAutonomousWork implements the AutonomousStuckRespawner interface consumed by AutonomousOrchestrationService. It gives an in_progress item a fresh autonomous work-session turn budget after a work session hits its turn cap without a DONE signal, instead of forcing the item through a review cycle against known-incomplete work (see onAutonomousDriverComplete's SessionRoleWork case in autonomous_orchestration_service.go, and docs/tasks/backlog-feature-improvement.md, 2026-07-19 update, for the bounce loop this closes). No status transition is needed — the item is already in_progress — so this mirrors AutoReopenAfterFailedReview's guard and cap checks without the review→in_progress transition step.

func (*BacklogService) AutoRespawnReview added in v1.39.0

func (s *BacklogService) AutoRespawnReview(ctx context.Context, itemID string) error

AutoRespawnReview implements session.ReviewRespawner. It re-triggers the review gate for a backlog item abandoned in review with no active session — closing the gap where StuckReasonAbandonedReview was previously only detected and notified, never acted on, which let real backlog items sit stuck for days (docs/tasks/backlog-feature-improvement.md).

Unlike AutoReopenAfterFailedReview/AutoReopenForPRFix, this does NOT transition the item's status: the item is already "review" (TriggerReReview requires exactly that status) and the underlying work may well already be complete — the whole point of re-review is to find out, not to force another work session. See TriggerReReview for why this is likely the right respawn mechanism over spawning a fresh work session: a live audit found several abandoned-review items with nearly all acceptance criteria already marked complete, just never actually reviewed.

Deliberately NOT gated by maxConcurrentBacklogWorkItems: that cap bounds concurrent "in_progress" items, and this path never transitions the item out of "review" (a manual TriggerReReview call doesn't check that cap either — this preserves existing behavior rather than introducing a new restriction). Concurrency is instead bounded by the caller (markAbandonedReview), which dispatches under l.reviewSem — the same limiter ReconcileStuck's sibling review-gate-respawn path already uses.

func (*BacklogService) AutoRespawnTriage added in v1.41.0

func (s *BacklogService) AutoRespawnTriage(ctx context.Context, itemID string) error

AutoRespawnTriage implements session.TriageRespawner. It re-triggers triage for an idea-status item whose most recent triage session orphaned — closing the gap where StuckReasonOrphanedTriage was previously only detected and notified, never acted on (its own doc comment in session/backlog_lifecycle.go used to read "no resolve pass needed here... once the item leaves 'idea'", which was true for resolution but left nothing driving the item TOWARD leaving idea in the first place). Confirmed live 2026-07-27 (docs/tasks/backlog-feature-improvement.md): items 4f03de7b and 505fb733 sat stuck in "idea" for 2 days, only recovering once a human noticed the one-time notification and manually re-triggered triage.

Delegates entirely to TriggerTriage, which already tombstones any still-open triage session and handles the ready->idea/idea status guard itself — reconcileOrphanedTriageItems (the caller's caller, via the backoff gate) already ended the orphaned session before marking the item stuck, so by the time this runs there is normally nothing left for TriggerTriage's own tombstone step to do; it is still safe to call unconditionally.

Generalized 2026-08-03 (docs/tasks/backlog-feature-improvement.md, item be676dab) to also handle a queued item: TriggerTriage only ever accepts idea/ready, so a queued item gated on plan approval with no usable triage result first needs the same reset-to-idea step the manual "Return to Triage" escape hatch performs (ActionsSection.tsx's send_back_idea action, session/domain's queued->idea "backward: re-triage from scratch" transition) before triage can run again — this mirrors the manual recovery already performed for be676dab exactly, just automated.

func (*BacklogService) BulkResetStuckRemediation added in v1.39.0

BulkResetStuckRemediation applies ResetStuckRemediation's reset to every open stuck row matching the optional reason filter — see only_parked_explicitly_set's doc comment in the proto for why only_parked defaults to true (the safer, more targeted reset) rather than proto3's natural false zero value. +api: backlog:bulk-reset-stuck-remediation

func (*BacklogService) CancelTriage added in v1.35.0

CancelTriage stops a running triage session for a backlog item. +api: backlog:cancel-triage

func (*BacklogService) ConfigMu added in v1.41.0

func (s *BacklogService) ConfigMu() *sync.RWMutex

ConfigMu exposes the mutex guarding cfg's backlog-concurrency fields so server/dependencies.go can wire the exact same mutex into DefaultsService.SetSharedBacklogConfig — see cfgMu's doc comment on the BacklogService struct for why this must be shared, not merely the same *config.Config pointer.

func (*BacklogService) CreateBacklogItem added in v1.35.0

CreateBacklogItem adds a new item to the backlog. +api: backlog:create-item

func (*BacklogService) CreateBacklogItemFromChat added in v1.43.0

CreateBacklogItemFromChat handles a single turn of natural-language chat. When ExistingItemId is empty, this is the first turn of a new chat-originated item: it synthesizes a minimal BacklogItem from the message and delegates to CreateBacklogItem. When ExistingItemId is set, this is a refinement turn: it delegates to TriggerTriage with Feedback = message, reusing the existing feedback/iteration mechanism. +api: backlog:create-item-from-chat

func (*BacklogService) CreateItemSource added in v1.35.0

CreateItemSource registers a new external plugin source. +api: backlog:create-source

func (*BacklogService) CreatePipelineMode added in v1.38.0

CreatePipelineMode registers a new runtime-definable pipeline mode. +api: backlog:create-pipeline-mode

func (*BacklogService) DeleteBacklogItem added in v1.35.0

DeleteBacklogItem permanently removes an item and all its child records. +api: backlog:delete-item

func (*BacklogService) DeleteItemSource added in v1.35.0

DeleteItemSource removes an external item source registration. +api: backlog:delete-source

func (*BacklogService) DeletePipelineMode added in v1.38.0

DeletePipelineMode removes a pipeline mode definition. Does NOT block on existing BacklogItemData.PipelineMode references — per the plan's Unresolved Questions default, a deleted-but-still-referenced mode relies on PipelineEngine's fail-closed resolution (Story 1.3.3: an unresolved slug falls back to the default pipeline with a Warn log, it never errors). +api: backlog:delete-pipeline-mode

func (*BacklogService) DequeueNextQueuedItems added in v1.41.0

func (s *BacklogService) DequeueNextQueuedItems(ctx context.Context) error

DequeueNextQueuedItems implements session.QueueDequeuer. It claims and spawns as many queued items as there are free WIP slots, highest-priority (P1) first, oldest (by queued time, or created time for a "ready" candidate that was never explicitly queued) as the tiebreaker. When autoSpawnReadyItemsEnabled (default true — config.Config.AutoSpawnReadyItemsOrDefault) is set, "ready" items are eligible candidates too, not just ones already sitting in "queued" — this is what makes auto-implementation the default: an item reaching "ready" no longer needs either a human to click "Spawn Session" or an explicit AutoSpawnSession flag to eventually get worked, it just needs a free WIP slot and the highest priority among what's waiting. Called from BacklogLifecycleListener.onSessionExited (immediate dequeue the moment a slot frees up) and the periodic ReconcileStuck sweep (safety net for a missed hook, a concurrency limit raised while items were waiting, or an item that reached "ready" between ticks) — see session/backlog_lifecycle.go.

Each candidate is claimed via a SQL-level compare-and-swap (queued->in_progress or ready->in_progress, ExpectedStatus set to whichever status the candidate was found in) before spawning, so concurrent callers (this method running from both the exit hook and the sweep, or multiple server processes sharing one DB) cannot double-claim the SAME item — see TransitionBacklogItemStatus's doc comment. That per-item CAS alone does not prevent two concurrent calls to this method from each computing their own freeSlots from an unsynchronized snapshot and jointly claiming DIFFERENT candidates past the cap, so dequeueMu additionally serializes the whole method body, making this method single-flight system-wide (PR #199 review F2 — the exact "uncontrolled concurrency overshoot" class of bug the WIP cap feature exists to prevent).

The claim itself now goes through transitionWithGuard (PR #199 review F4), so an item without an approved plan (SkipPlanning=false, PlanApproved=false) cannot be claimed at all — defense-in-depth against F3, on top of SpawnSessionFromItem's own planning gate now running before the WIP-cap queue gate. This is also what makes a "ready" candidate safe to auto-claim directly: ready->in_progress carries the exact same ErrPlanRequired/ErrPlanArtifactsRequired guard as queued->in_progress (see domain.TransitionGuard), so an unapproved-plan item is silently skipped (left at ready) rather than auto-spawned without review.

If the claim succeeds but the spawn itself fails (missing repo_path, stale plan approval, SessionCreator error), the item is rolled back to whichever status it was claimed from (queued or ready) rather than left stranded in_progress with no session.

func (*BacklogService) GetBacklogItem added in v1.35.0

GetBacklogItem retrieves a single backlog item by ID. +api: backlog:get-item

func (*BacklogService) GetBacklogItemCost added in v1.37.0

GetBacklogItemCost returns estimated token costs for all sessions linked to an item.

func (*BacklogService) GetBacklogItemDiff added in v1.37.0

func (*BacklogService) GetBacklogItemShipStatus added in v1.39.0

GetBacklogItemShipStatus reports whether itemID's code actually landed on main, plus the branch's position relative to main when the branch still exists. +api: backlog:get-item-ship-status

func (*BacklogService) GetPipelineMode added in v1.38.0

GetPipelineMode retrieves a single pipeline mode by slug. +api: backlog:get-pipeline-mode

func (*BacklogService) GetSessionBacklogIndex added in v1.37.0

GetSessionBacklogIndex returns a flat list of all item sessions with their parent backlog item metadata, keyed by session UUID. Used by the Insights dashboard to annotate sessions.

func (*BacklogService) GetSyncHistory added in v1.35.0

GetSyncHistory returns the sync event history for an item source, most recent first. +api: backlog:get-sync-history

func (*BacklogService) ImportGitHubIssue added in v1.35.0

ImportGitHubIssue creates a backlog item pre-populated from a GitHub issue. +api: ImportGitHubIssue

func (*BacklogService) IsTriageLive added in v1.41.0

func (s *BacklogService) IsTriageLive(itemID string) bool

IsTriageLive reports whether this process itself still has a headless triage call genuinely in flight for itemID, per the triageInFlight field's doc comment. This is the single source of truth both tombstoneOrphanTriageSessions (in this file) and session.BacklogLifecycleListener's periodic staleness sweep (reconcileOrphanedTriageItems, via the TriageRespawner interface this method satisfies) consult — see BUG-055: before this method existed, that sweep had its own separate, liveness-blind staleness-only gate that could tombstone a call genuinely still running past maxHeadlessTriageSessionStaleness.

func (*BacklogService) ListBacklogItems added in v1.35.0

ListBacklogItems returns backlog items with optional filtering and sorting. +api: backlog:list-items

func (*BacklogService) ListGitHubIssues added in v1.35.0

func (*BacklogService) ListItemSources added in v1.35.0

ListItemSources returns all registered external item sources. +api: backlog:list-sources

func (*BacklogService) ListPipelineModes added in v1.38.0

ListPipelineModes returns all pipeline modes, including disabled ones — the management UI must be able to see (and re-enable) disabled modes, unlike PipelineEngine's cache, which is backed by ListEnabled only. +api: backlog:list-pipeline-modes

func (*BacklogService) ListStuckBacklogItems added in v1.38.0

ListStuckBacklogItems returns open (unresolved, un-snoozed) stuck backlog items — items that have stopped progressing toward merge, with a reason, since-when, and PR context. +api: backlog:list-stuck

func (*BacklogService) MaybeTriggerTriage added in v1.41.0

func (s *BacklogService) MaybeTriggerTriage(ctx context.Context, itemID string, skipTriage bool, repoPath string) bool

MaybeTriggerTriage is the single "should this newly created item get auto-triaged" decision, shared by every backlog-item creation entry point: RPC CreateBacklogItem and ImportGitHubIssue (backlog_service_lifecycle.go, backlog_service_sync.go), and the create_backlog_item/import_github_issue MCP tools (server/mcp/tools_backlog.go). Before this helper existed, the MCP tools called storage.CreateBacklogItem directly and skipped this gate entirely — every backlog item self-filed by an agent session via those tools sat in "idea" with zero triage attempts until (at best) a human noticed and manually re-triggered triage, since reconcileOrphanedTriageItems (session/backlog_lifecycle.go) only ever detects items that already have a prior triage-role ItemSession and cannot originate the first attempt.

Mirrors the RPC handlers' existing inline gate exactly: skip if the caller asked to, if the item has no repo_path (nothing to run triage against), or if no headless pool is wired (e.g. claude binary unavailable). Best-effort — a failure to trigger is logged and never fails item creation. Returns whether triage was actually triggered, for callers that surface it back to the client (e.g. CreateBacklogItemResponse.TriageTriggered).

func (*BacklogService) OverrideVerdict added in v1.35.0

OverrideVerdict manually overrides a review verdict for an item session. +api: backlog:override-verdict

func (*BacklogService) PipelineEngine added in v1.38.0

func (s *BacklogService) PipelineEngine() session.PipelineEngine

PipelineEngine returns the PipelineEngine injected at construction (nil if none was wired). Exported for the pointer-equality integration test proving BacklogService and BacklogLifecycleListener share a single PipelineEngine instance (Story 1.5.1).

func (*BacklogService) PreviewBackwardSyncImpact added in v1.41.0

PreviewBackwardSyncImpact reports how many already-imported items for a source would immediately transition to archived if backward sync were enabled for it right now. Mirrors TriggerSync's SyncLoop construction and preconditions (same plugin registry, same encryption key provider — not a second credential flow), but is read-only: it does not advance the source's sync cursor and does not record a SourceSyncEvent. +api: backlog:preview-backward-sync-impact

func (*BacklogService) Registry added in v1.41.0

func (s *BacklogService) Registry() *session.PluginRegistry

Registry returns the plugin registry backing TriggerSync, or nil if none is wired. Exposed so server.go can wire the GitHub forward-sync EventBus subscriber (server/services/backlog_github_forward_sync.go) with the same registry TriggerSync uses, without needing its own copy of the dependency graph that builds it (see server/dependencies.go's syncRegistry).

func (*BacklogService) RejectPlan added in v1.42.0

RejectPlan records a rejection reason for the item's current plan artifacts and clears any existing approval. Does not itself trigger regeneration — see project_plans/plan-approval-ux/decisions/ADR-002. +api: backlog:reject-plan

func (*BacklogService) RemediateStaleWorkSession added in v1.39.0

func (s *BacklogService) RemediateStaleWorkSession(ctx context.Context, itemID string) error

RemediateStaleWorkSession implements session.StaleWorkRemediator, consumed by BacklogLifecycleListener's remediateStaleWorkWithBackoffGate (session/backlog_lifecycle.go). It closes out a work session that has gone stale (no progress reported for over session.maxWorkSessionStaleness) even though the underlying tmux session and pane process are still alive (session.Instance.TmuxAlive/PaneProcessDead) — a genuinely stale session is NOT a zombie the generic tmux health check would ever catch: the agent inside finished its own work and is idle at an interactive prompt waiting on a human, rather than crashed or hung (live repro 2026-07-20, item 9264efe7-b4c2-455a-9e2a-ab0196a63ecd, rework suffix -r14 — 14 prior rework rounds with nothing ever unsticking it, since detection existed but no remediation action did). Trusts the caller's staleness signal plus RemediationDue's own backoff gate rather than adding a second, possibly- conflicting liveness heuristic here — see StaleWorkRemediator's doc comment in session/backlog_lifecycle.go.

Ends the stale ItemSession and delegates the actual respawn to AutoRespawnAutonomousWork, which already implements exactly the "in_progress item, no active work session, needs a fresh turn budget" case this produces — including the rework-cap check, so a stale-work loop is bounded by whichever of the rework cap or MaxRemediationAttempts (session/ backlog_remediation.go) is tighter, never solely by a rework cap an operator may have set to 0 (unlimited) for a different reason.

BUG-064: UpdateItemSessionEnded runs BEFORE KillTmuxPaneOnly, deliberately — not just cosmetically — ordered this way. Killing the tmux pane fires the Instance's EventStopped lifecycle notification, which session.BacklogLifecycleListener.onSessionExited (session/ backlog_lifecycle.go) handles in its own goroutine by unconditionally transitioning any in_progress item straight to "review" the moment a work ItemSession's EndedAt is observed set. Live evidence (backlog item 2d7fac56, 2026-08-06): that goroutine's transition raced ahead of this function's own AutoRespawnAutonomousWork call below — which no-ops once item.Status is no longer in_progress ("already moved on ... nothing to do") — so the stale session was killed but no fresh work session was ever spawned, and the item silently went straight back into review carrying the exact same (already twice-PARTIAL) diff instead of getting a fresh turn budget. Ending the session here first, before the kill, guarantees (program-order happens-before, not a race) that whenever onSessionExited's goroutine eventually runs, it observes EndedAt already non-nil for this session and skips its own transition (see onSessionExited's own guard), leaving this function's AutoRespawnAutonomousWork call as the sole decider of what happens next.

func (*BacklogService) ResetStuckRemediation added in v1.39.0

ResetStuckRemediation clears the automated-remediation counters on a single open stuck row (docs/tasks/backlog-stuck-item-auto-remediation.md Phase A) — the per-item admin escape hatch for an attempt budget spuriously consumed by e.g. an OOM-restart storm. Never itself invokes a remediation action; it only un-parks the row for the NEXT automated (or TriggerRemediationNow-triggered) attempt. +api: backlog:reset-stuck-remediation

func (*BacklogService) ResolveReworkBlockedStaleIfRecovered added in v1.41.0

func (s *BacklogService) ResolveReworkBlockedStaleIfRecovered(ctx context.Context, itemID string) error

ResolveReworkBlockedStaleIfRecovered implements session.ReworkBlockStaleResolver — the resolve-side counterpart to notifyIfActiveWorkSessionStale above, called from BacklogLifecycleListener.reconcileReworkBlockedStaleResolution once per open StuckReasonReworkBlockedStale row per reconcile tick. Re-checks the item's active work session's current staleness and clears the row (storage.ResolveStuck) if any of three conditions hold: the session is producing output again (recovered), it no longer has an active work session, or the item has left review status — the last two are belt-and-suspenders alongside selfHealStuck's own status-anchored clear, matching reconcileStaleWorkSessions' identical justification for its own resolve pass (same-status clears are invisible to status-anchored self-heal). No-op (nil error) if still stale. Best-effort: a ResolveStuck error is logged and swallowed here (not returned) so one item's storage hiccup can't abort the tick for every other open row — mirroring resolveStuckLogged's established style.

func (*BacklogService) SearchGitHubRepos added in v1.35.0

func (*BacklogService) SetAutonomousDriverStarter added in v1.35.0

func (s *BacklogService) SetAutonomousDriverStarter(starter AutonomousDriverStarter)

SetAutonomousDriverStarter wires the optional autonomous driver starter. When set, SpawnSessionFromItem with autonomous=true will start an AutonomousDriver on the spawned instance.

func (*BacklogService) SetCapabilityCheck added in v1.38.0

func (s *BacklogService) SetCapabilityCheck(c *headless.CodebaseReadCapabilitySelfCheck)

SetCapabilityCheck overrides the codebase-read capability self-check instance. Exposed for tests, which need a fresh (non-shared) instance to avoid the package-level singleton's sync.Once making later tests observe an earlier test's cached result. Production callers should rely on the default.

func (*BacklogService) SetEventBus added in v1.37.0

func (s *BacklogService) SetEventBus(b *events.EventBus)

SetEventBus wires in the event bus used to publish operator-facing notifications.

func (*BacklogService) SetGitHubResolver added in v1.35.0

func (s *BacklogService) SetGitHubResolver(fn func(input string) (string, *session.GitHubRef, error))

SetGitHubResolver overrides how GitHub URLs are resolved to local clone paths. Used by tests to avoid real network/git access; production wiring uses the session.ResolveGitHubInput default set in NewBacklogService.

func (*BacklogService) SetHeadlessPool added in v1.35.0

func (s *BacklogService) SetHeadlessPool(pool headless.PoolClient)

SetHeadlessPool wires the headless pool for autonomous triage calls.

func (*BacklogService) SetOneShotRunner added in v1.39.0

func (s *BacklogService) SetOneShotRunner(r PRRunner)

SetOneShotRunner wires the one-shot PR-creation runner used by TriggerShipPR. Called post-construction from server/dependencies.go once SessionService is available — mirrors SetSessionStopper/SetAutonomousDriverStarter's setter-injection pattern used elsewhere in this file's wiring. nil (the default) makes TriggerShipPR return CodeUnimplemented.

func (*BacklogService) SetPluginRegistry added in v1.35.0

func (s *BacklogService) SetPluginRegistry(registry *session.PluginRegistry)

SetPluginRegistry wires the item-source plugin registry, enabling TriggerSync.

func (*BacklogService) SetRepoWatchRemover added in v1.41.0

func (s *BacklogService) SetRepoWatchRemover(remover RepoWatchRemover)

SetRepoWatchRemover wires the optional unfinished-changes scanner hook used to stop watching a worktree path once it's cleaned up (BUG-034).

func (*BacklogService) SetScrollbackManager added in v1.38.0

func (s *BacklogService) SetScrollbackManager(sm *scrollback.ScrollbackManager)

SetScrollbackManager wires in the scrollback manager used to write a searchable session transcript file on the empty-diff codebase-read re-review path. Optional — nil (the default) simply omits the "## Session Transcript" prompt section. Safe to call concurrently with RPC handlers that read the scrollback manager.

func (*BacklogService) SetSessionStopper added in v1.35.0

func (s *BacklogService) SetSessionStopper(stopper SessionStopper)

SetSessionStopper wires the optional session stopper used to kill orphaned sessions on re-triage.

func (*BacklogService) SetSyncFeatureEnabledCheck added in v1.35.0

func (s *BacklogService) SetSyncFeatureEnabledCheck(check func() bool)

SetSyncFeatureEnabledCheck wires a callback TriggerSync uses to refuse running while the backlog feature is disabled. Pass nil (the default) to leave TriggerSync ungated.

func (*BacklogService) SetSyncKeyFunc added in v1.35.0

func (s *BacklogService) SetSyncKeyFunc(keyFunc func() ([]byte, error))

SetSyncKeyFunc wires the encryption key provider used to decrypt item source tokens during a manual sync. May be left nil if no sources use encrypted tokens; SyncByID degrades gracefully (see session.SyncLoop.decryptConfigToken).

func (*BacklogService) SetTokenStore added in v1.37.0

func (s *BacklogService) SetTokenStore(ts tokens.TokenStoreReader, pt *tokens.PricingTable)

SetTokenStore wires cost-estimation data. Optional: if not set, cost fields remain 0.

func (*BacklogService) SetTriageCleanupTimeout added in v1.35.0

func (s *BacklogService) SetTriageCleanupTimeout(d time.Duration)

SetTriageCleanupTimeout overrides the default timeout for TriggerTriage's post-LLM-call DB writes. Exposed for tests; production callers should rely on the default.

func (*BacklogService) Shutdown added in v1.35.0

func (s *BacklogService) Shutdown()

Shutdown cancels the service's background context, unblocking any goroutines waiting on the triage semaphore.

func (*BacklogService) SnoozeStuckItem added in v1.38.0

SnoozeStuckItem suppresses a stuck row from the active view and from re-notification until the given time. +api: backlog:snooze-stuck

func (*BacklogService) SpawnSessionFromItem added in v1.35.0

func (*BacklogService) SubmitManualReview added in v1.37.0

SubmitManualReview allows a user to submit a review verdict directly, without running an AI review session. +api: backlog:submit-manual-review

func (*BacklogService) SuggestNextItem added in v1.35.0

SuggestNextItem recommends the highest-priority ready backlog item. +api: backlog:suggest-next

func (*BacklogService) SyncLoopForForwardSync added in v1.41.0

func (s *BacklogService) SyncLoopForForwardSync() *session.SyncLoop

SyncLoopForForwardSync returns a *session.SyncLoop sharing this service's plugin registry and encryption key provider — mirrors TriggerSync's own inline SyncLoop construction below, but exposed for the GitHub forward-sync EventBus subscriber, which only needs DecryptConfigToken from it (registry access goes through Registry() above). Returns nil if no plugin registry is wired, matching TriggerSync's CodeUnimplemented guard.

func (*BacklogService) TransitionBacklogItemStatus added in v1.35.0

TransitionBacklogItemStatus moves an item through the status state machine. +api: backlog:transition-status

func (*BacklogService) TriggerReReview added in v1.35.0

TriggerReReview re-runs the review gate for a backlog item. +api: backlog:trigger-re-review

func (*BacklogService) TriggerRemediationNow added in v1.39.0

TriggerRemediationNow immediately runs the reason-specific remediation action for a single open stuck row (docs/tasks/backlog-stuck-item-auto-remediation.md addendum) — the operator "Retry now" escape hatch. Bypasses only the next_remediation_at backoff timer: RecordManualRemediationAttempt still rejects a parked row (ErrRemediationParked) rather than un-parking it, and this attempt still increments remediation_attempts exactly like a normal dispatcher-triggered one, so it counts toward the same 5-attempt cap. The wrapped action's own circuit breaker (IsRepeatedFailure/ IsRepeatedNoVerdictFailure inside AutoReopenAfterFailedReview, for example) still applies — this RPC does not bypass it. +api: backlog:trigger-remediation-now

func (*BacklogService) TriggerShipPR added in v1.39.0

TriggerShipPR manually runs the same one-shot PR-creation prompt the opt-in AutoCreatePR policy uses automatically, for a backlog item that has no PR yet. This is the self-service "Ship PR" action on the item detail page — closing the gap where the only way to ask the agent to ship a PR was the unrelated Review Queue page, or waiting on AutoCreatePR (opt-in, default off) to fire on its own. +api: backlog:trigger-ship-pr

func (*BacklogService) TriggerSync added in v1.35.0

TriggerSync initiates a synchronous, on-demand sync run for an external item source, regardless of its Enabled flag. Runs inline (not backgrounded like TriggerTriage) because a single external-API fetch is expected to complete in seconds, not the 7-15 minutes a headless LLM triage call takes. +api: backlog:trigger-sync

func (*BacklogService) TriggerTriage added in v1.35.0

TriggerTriage kicks off a headless triage planning call for a backlog item. Returns immediately after creating an ItemSession; actual triage runs in a goroutine. +api: backlog:trigger-triage

func (*BacklogService) UnarchiveBacklogItem added in v1.44.0

UnarchiveBacklogItem clears archived_at and restores the item to "idea". It does not attempt to recreate worktrees deleted at archive time. +api: backlog:unarchive-item

func (*BacklogService) UpdateBacklogItem added in v1.35.0

UpdateBacklogItem modifies the properties of an existing backlog item. +api: backlog:update-item

func (*BacklogService) UpdateItemSource added in v1.35.0

UpdateItemSource modifies configuration for an existing item source. +api: backlog:update-source

func (*BacklogService) UpdatePipelineMode added in v1.38.0

UpdatePipelineMode modifies an existing pipeline mode's fields via partial update. +api: backlog:update-pipeline-mode

func (*BacklogService) WatchBacklogItems added in v1.41.0

WatchBacklogItems streams real-time backlog item events. Sends an initial snapshot (or, on reconnect via after_seq, a replay of buffered events) followed by live fan-out, filtered by status_filter/category_filter. +api: backlog:watch

type BulkUpsertResult added in v1.35.0

type BulkUpsertResult struct {
	Created int
	Updated int
	Skipped int
	Errors  []string
}

BulkUpsertResult holds the outcome counts for a BulkUpsert operation.

type CDPStreamHandler added in v1.35.0

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

CDPStreamHandler handles WebSocket connections for CDP browser streaming. It delivers JPEG screencast frames from Chrome (via the per-session CDP manager) to the browser client and forwards input events in the opposite direction.

func NewCDPStreamHandler added in v1.35.0

func NewCDPStreamHandler(finder InstanceFinder) *CDPStreamHandler

NewCDPStreamHandler creates a new CDPStreamHandler backed by the given InstanceFinder. Pass a *session.ReviewQueuePoller (which implements InstanceFinder) so that each WebSocket upgrade performs an O(1) in-memory lookup rather than a full SQLite read.

func (*CDPStreamHandler) HandleWebSocket added in v1.35.0

func (h *CDPStreamHandler) HandleWebSocket(w http.ResponseWriter, r *http.Request)

+http: GET /api/sessions/{id}/cdp-stream browser:cdp-stream HandleWebSocket upgrades an HTTP request to WebSocket and streams JPEG frames from the session's Chrome CDP screencast to the client.

Route: GET /api/sessions/{id}/cdp-stream (WebSocket upgrade)

type CLIAIClient added in v1.35.0

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

CLIAIClient implements AIClient by shelling out to a locally installed AI agent CLI. The agent runs in one-shot mode with the prompt on stdin. executor.ShortLivedCmd provides context cancellation, timeout, and audit logging.

func NewCLIAIClient added in v1.35.0

func NewCLIAIClient(spec CLIAgentSpec) (*CLIAIClient, error)

NewCLIAIClient resolves spec.Binary in PATH and returns a CLIAIClient. Returns an error if the binary is not found.

func (*CLIAIClient) Complete added in v1.35.0

func (c *CLIAIClient) Complete(ctx context.Context, systemPrompt, userPrompt string) (string, error)

Complete delivers the combined prompt to the CLI and returns stdout. A 55-second timeout is applied so the caller's 60-second deadline has headroom.

type CLIAgentSpec added in v1.35.0

type CLIAgentSpec struct {
	// Name is the human-readable identifier used in logs and error messages.
	Name string
	// Binary is the executable name resolved via exec.LookPath.
	Binary string
	// Args returns the argv slice (excluding binary name) for one-shot mode.
	Args func() []string
	// PromptSeparator is inserted between system and user prompts. Defaults to "\n\n---\n\n".
	PromptSeparator string
	// PromptAsArg delivers the combined prompt as the last positional argument to Args()
	// instead of writing it to stdin. Required for CLIs like agy (--print "msg") and
	// opencode (run "msg") that take the prompt inline rather than from stdin.
	PromptAsArg bool
}

CLIAgentSpec describes how to invoke an AI agent CLI in one-shot mode.

type CallbackConfigService added in v1.43.0

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

CallbackConfigService handles the GetCallbackConfig/UpdateCallbackConfig RPCs (webhook-triggers Phase 5, FR7). Delegated to from SessionService exactly like DefaultsService — a config-backed handler with no second implementation, so a concrete type per .claude/rules/interface-pollution-checklist.md.

func NewCallbackConfigService added in v1.43.0

func NewCallbackConfigService() *CallbackConfigService

NewCallbackConfigService creates a CallbackConfigService.

func (*CallbackConfigService) GetCallbackConfig added in v1.43.0

GetCallbackConfig reports which of the three outbound-callback URLs are configured. The URLs themselves are never returned (AC4 partial — config side).

func (*CallbackConfigService) SetSharedCallbackConfig added in v1.43.0

func (c *CallbackConfigService) SetSharedCallbackConfig(cfg *config.Config, mu *sync.RWMutex)

SetSharedCallbackConfig wires the live *config.Config instance (and its guarding mutex) that CallbackDispatcher.Dispatch reads callback URLs from — see sharedCfg's doc comment for why this is needed. Called once from server/dependencies.go with the exact same *config.Config pointer and *sync.RWMutex passed to services.NewCallbackDispatcher / CallbackDispatcher.ConfigMu.

func (*CallbackConfigService) UpdateCallbackConfig added in v1.43.0

UpdateCallbackConfig sets one or more outbound-callback URLs. Each provided (non-nil) URL is SSRF-validated via ValidateCallbackURL before being persisted (AC11, config-save half) — an empty string clears/disables that callback; an unset field leaves the existing value unchanged.

type CallbackDispatcher added in v1.43.0

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

CallbackDispatcher fires outbound HTTP callbacks for the three lifecycle events FR7 covers: "session_complete", "session_stale", "queue_item_created". Built fresh from stdlib net/http — same shape as the (unimplemented) SlackNotifier design from project_plans/slack-review-notifications, which has no shipped code to reuse (0 matches repo-wide, verified). Concrete type, not an interface — one implementation, per .claude/rules/interface-pollution-checklist.md; the session package's CallbackDispatcher interface (session/callback_dispatcher.go) exists only because session cannot import this package.

Dispatch is always non-blocking (FR8): a non-blocking select on a semaphore-sized channel either reserves a slot immediately or drops the dispatch and logs a warning (AC10) — it never queues beyond the cap and never makes the caller wait. Actual delivery (up to callbackRetryAttempts attempts, each independently timeout-bounded and SSRF-revalidated via ValidateCallbackURL — send-time half of AC11) happens in a goroutine launched after the slot is reserved.

Delivery failures and dropped dispatches are logged with the event type only — never the target URL — per the redaction requirement (a URL can carry embedded credentials in its userinfo component).

func NewCallbackDispatcher added in v1.43.0

func NewCallbackDispatcher(cfg *config.Config) *CallbackDispatcher

NewCallbackDispatcher creates a CallbackDispatcher reading callback URLs and the webhook_triggers feature flag from cfg (the live, shared *config.Config instance — Dispatch re-reads cfg.Callbacks on every call, so a config update via CallbackConfigService takes effect on the very next dispatch without a process restart).

func (*CallbackDispatcher) ConfigMu added in v1.43.0

func (d *CallbackDispatcher) ConfigMu() *sync.RWMutex

ConfigMu exposes the mutex guarding cfg.Callbacks so CallbackConfigService.UpdateCallbackConfig can write a saved callback URL directly into this dispatcher's live *config.Config instance without a process restart — wired via SetSharedCallbackConfig/server/dependencies.go. See cfgMu's doc comment.

func (*CallbackDispatcher) Dispatch added in v1.43.0

func (d *CallbackDispatcher) Dispatch(eventType string, payload any)

Dispatch fires eventType's configured callback URL with payload as a JSON POST body. A no-op when: d is nil, the webhook_triggers feature flag is off (Task 8.2.1b — defense in depth beyond route-registration gating), eventType has no URL configured, or the in-flight semaphore is already at maxInFlightCallbacks (dropped + logged, AC10). Never blocks the caller.

type CapacityMonitor added in v1.35.0

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

func NewCapacityMonitor added in v1.35.0

func NewCapacityMonitor(
	cfg config.CapacityConfig,
	eventBus *events.EventBus,
	poller InstancePoller,
	tokenStore tokens.TokenStoreReader,
	switcher SessionSwitcher,
) *CapacityMonitor

func (*CapacityMonitor) GetCurrentLimits added in v1.35.0

func (m *CapacityMonitor) GetCurrentLimits() map[string]ProviderLimits

func (*CapacityMonitor) GetSessionLimits added in v1.35.0

func (m *CapacityMonitor) GetSessionLimits(title string) (ProviderLimits, bool)

func (*CapacityMonitor) RegisterClient added in v1.35.0

func (m *CapacityMonitor) RegisterClient(name string, client ProviderLimitsClient)

func (*CapacityMonitor) Start added in v1.35.0

func (m *CapacityMonitor) Start(ctx context.Context)

func (*CapacityMonitor) UpdateFromResponseHeaders added in v1.35.0

func (m *CapacityMonitor) UpdateFromResponseHeaders(provider string, headers http.Header)

type CheckpointService added in v1.35.0

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

CheckpointService handles checkpoint creation, listing, and conversation-state management RPCs. Logic moved verbatim from SessionService (ADR-001).

func NewCheckpointService added in v1.35.0

func NewCheckpointService(storage session.InstanceStore, eventBus *events.EventBus) *CheckpointService

NewCheckpointService creates a CheckpointService with the given storage and event bus. Call SetPoller, SetExternalDiscovery, SetScrollbackMgr, and SetLoadInstancesFn after construction to complete wiring (matching the pattern of project_service.go).

func (*CheckpointService) ClearConversationState added in v1.35.0

ClearConversationState removes the stored Claude conversation UUID from a session so that the next Resume starts a fresh conversation instead of attempting --resume with a stale or path-mismatched UUID.

func (*CheckpointService) CreateCheckpoint added in v1.35.0

CreateCheckpoint creates a new named checkpoint for the specified session.

func (*CheckpointService) ListCheckpoints added in v1.35.0

ListCheckpoints returns all checkpoints for the specified session.

func (*CheckpointService) SetExternalDiscovery added in v1.35.0

func (cs *CheckpointService) SetExternalDiscovery(d *session.ExternalSessionDiscovery)

SetExternalDiscovery wires external session discovery (mux-enabled sessions).

func (*CheckpointService) SetLoadInstancesFn added in v1.35.0

func (cs *CheckpointService) SetLoadInstancesFn(fn func() ([]*session.Instance, error))

SetLoadInstancesFn wires the fallback function used when the target session is not found in the live poller. Should close over SessionService.loadInstancesWithWiring.

func (*CheckpointService) SetPoller added in v1.35.0

func (cs *CheckpointService) SetPoller(p *session.ReviewQueuePoller)

SetPoller wires the live-instance poller for fast instance lookup.

func (*CheckpointService) SetScrollbackMgr added in v1.35.0

func (cs *CheckpointService) SetScrollbackMgr(mgr ScrollbackSequencer)

SetScrollbackMgr wires the scrollback sequence provider for CreateCheckpoint.

type CircuitBreakerHandler

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

CircuitBreakerHandler provides a debug endpoint to inspect circuit breaker state.

func NewCircuitBreakerHandler

func NewCircuitBreakerHandler() *CircuitBreakerHandler

NewCircuitBreakerHandler creates a new handler using the global registry.

func (*CircuitBreakerHandler) HandleCircuitBreakers

func (h *CircuitBreakerHandler) HandleCircuitBreakers(w http.ResponseWriter, r *http.Request)

HandleCircuitBreakers returns the current state of all circuit breakers. GET /api/debug/circuit-breakers

func (*CircuitBreakerHandler) RegisterRoutes

func (h *CircuitBreakerHandler) RegisterRoutes(mux *http.ServeMux)

RegisterRoutes registers the circuit breaker debug routes on the given mux.

type ClaudeOAuthCredentialSource added in v1.35.0

type ClaudeOAuthCredentialSource struct {
	// HomeDirOverride overrides os.UserHomeDir() — used in tests.
	HomeDirOverride string
}

ClaudeOAuthCredentialSource reads the OAuth token written by Claude Code at ~/.claude/.credentials.json. This is the credential used by users with a Claude subscription who have never set ANTHROPIC_API_KEY.

func (*ClaudeOAuthCredentialSource) Name added in v1.35.0

func (*ClaudeOAuthCredentialSource) Resolve added in v1.35.0

type ClaudePermissions

type ClaudePermissions struct {
	Allow []string `json:"allow"` // tool patterns, e.g. "Bash(git log*)"
	Deny  []string `json:"deny,omitempty"`
}

ClaudePermissions mirrors the "permissions" key in ~/.claude/settings.json.

func ParseClaudeSettings

func ParseClaudeSettings(path string) (*ClaudePermissions, error)

ParseClaudeSettings reads a Claude settings.json file and extracts permissions. Returns nil permissions (no error) if the file does not exist or has no permissions key.

type CommandStat

type CommandStat struct {
	Preview  string `json:"preview"`
	ToolName string `json:"tool_name"`
	Count    int    `json:"count"`
}

CommandStat is a command preview with a count.

type ConfigFileCredentialSource added in v1.35.0

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

ConfigFileCredentialSource reads API keys explicitly set in config.json. Uses the existing config.AnthropicAPIKey field.

func (*ConfigFileCredentialSource) Name added in v1.35.0

func (*ConfigFileCredentialSource) Resolve added in v1.35.0

type ConfigFileRulesRepository added in v1.35.0

type ConfigFileRulesRepository interface {
	GetRules(ctx context.Context) (rules []RuleSpec, filePath string, err error)
	SaveRules(ctx context.Context, ruleIDs []string, rule *sessionv1.ApprovalRuleProto) (filePath string, err error)
}

ConfigFileRulesRepository is the seam for config-file rule persistence. Implementations are registered via NewRulesService; nil means the feature is unavailable.

type ConfigService

type ConfigService struct{}

ConfigService handles Claude configuration file RPC methods.

It is dependency-light: each call creates a fresh ClaudeConfigManager so there is no shared state to synchronise.

func NewConfigService

func NewConfigService() *ConfigService

NewConfigService creates a ConfigService.

func (*ConfigService) GetClaudeConfig

GetClaudeConfig retrieves a Claude configuration file by name.

func (*ConfigService) ListClaudeConfigs

ListClaudeConfigs returns all configuration files in the ~/.claude directory.

func (*ConfigService) UpdateClaudeConfig

UpdateClaudeConfig updates a Claude configuration file with atomic write and backup.

type ConnectRPCWebSocketHandler

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

func NewConnectRPCWebSocketHandler

func NewConnectRPCWebSocketHandler(sessionService *SessionService, scrollbackManager *scrollback.ScrollbackManager, tmuxStreamerManager *session.ExternalTmuxStreamerManager) *ConnectRPCWebSocketHandler

NewConnectRPCWebSocketHandler creates a new ConnectRPC WebSocket handler tmuxStreamerManager is required for ALL sessions (managed and external) since they all use tmux capture-pane polling

func (*ConnectRPCWebSocketHandler) HandleWebSocket

func (h *ConnectRPCWebSocketHandler) HandleWebSocket(w http.ResponseWriter, r *http.Request)

HandleWebSocket upgrades HTTP connection to WebSocket and handles ConnectRPC protocol

func (*ConnectRPCWebSocketHandler) SetExternalSessionSupport

func (h *ConnectRPCWebSocketHandler) SetExternalSessionSupport(
	discovery *session.ExternalSessionDiscovery,
)

SetExternalSessionSupport configures external session discovery support This enables the handler to discover and stream external sessions (via mux socket monitoring) Note: tmuxStreamerManager is already set in constructor since ALL sessions use it

type Credential added in v1.35.0

type Credential struct {
	// Provider identifies the AI provider: "anthropic", "google", "openai".
	Provider string

	// APIKey is a long-lived API key (set via developer console or env var).
	// Used in Anthropic's x-api-key header or as a query param for Gemini.
	APIKey string

	// BearerToken is a short-lived OAuth access token obtained via CLI login.
	// Used in Authorization: Bearer <token> headers.
	BearerToken string

	// ExpiresAt is when BearerToken expires. Zero means unknown / no expiry.
	ExpiresAt time.Time

	// IsADC signals that the caller should use Google Application Default
	// Credentials (golang.org/x/oauth2/google.FindDefaultCredentials) rather
	// than setting an Authorization header directly. Only set by
	// AgyCredentialSource when falling back to gcloud ADC.
	IsADC bool

	// Source records where this credential came from, for logs and diagnostics.
	// E.g. "env:ANTHROPIC_API_KEY", "cli_oauth:~/.claude/.credentials.json".
	Source string
}

Credential holds resolved auth material for one provider. Exactly one of APIKey or BearerToken will be non-empty for a valid credential.

func (Credential) AnthropicAuthHeader added in v1.35.0

func (c Credential) AnthropicAuthHeader() (key, value string, ok bool)

AnthropicAuthHeader returns the value of the x-api-key header for Anthropic API calls. Returns ("", false) when the credential cannot be used directly as an API key (e.g. OAuth tokens use a Bearer header instead).

func (Credential) GoogleAuthHeader added in v1.35.0

func (c Credential) GoogleAuthHeader() (key, value string, ok bool)

GoogleAuthHeader returns the Authorization header value for Google/Gemini REST calls. Returns ("", false) when the credential is ADC-based (the caller must obtain a token via the Google SDK instead).

func (Credential) IsValid added in v1.35.0

func (c Credential) IsValid() bool

IsValid returns true when the credential carries usable auth material.

type CredentialChain added in v1.35.0

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

CredentialChain tries each source in order, returning the first valid credential. It is the central entry point used by ProviderLimitsClient and all AI clients.

func NewChain added in v1.35.0

func NewChain(sources ...CredentialSource) *CredentialChain

NewChain creates a chain from an explicit ordered list of sources. Useful in tests and for custom priority overrides.

func NewDefaultChain added in v1.35.0

func NewDefaultChain(cfg *config.Config) *CredentialChain

NewDefaultChain returns a chain with the standard priority order:

  1. Environment variable (highest priority — always wins)
  2. Config file explicit entry
  3. Claude CLI OAuth file (~/.claude/.credentials.json)
  4. Antigravity OAuth file (~/.gemini/oauth_creds.json)
  5. gcloud Application Default Credentials

func (*CredentialChain) Resolve added in v1.35.0

func (c *CredentialChain) Resolve(ctx context.Context, provider string) (Credential, error)

Resolve walks the chain and returns the first valid credential for provider. Returns an error only when no source produced a valid credential.

type CredentialSource added in v1.35.0

type CredentialSource interface {
	// Name returns a human-readable identifier used in log and error messages.
	Name() string

	// Resolve attempts to load a credential for the given provider.
	// Returns (zero, false, nil) when this source has no credential —
	// not an error. Returns (zero, false, err) on an unexpected I/O failure.
	Resolve(ctx context.Context, provider string) (Credential, bool, error)
}

CredentialSource resolves auth material for a named provider. Implementations must be safe to call concurrently.

type DailyBucket

type DailyBucket struct {
	Date        string `json:"date"` // "2006-01-02" in local time
	AutoAllow   int    `json:"auto_allow"`
	AutoDeny    int    `json:"auto_deny"`
	Escalate    int    `json:"escalate"`
	ManualAllow int    `json:"manual_allow"`
	ManualDeny  int    `json:"manual_deny"`
	Total       int    `json:"total"`
}

DailyBucket aggregates classification decisions for a single calendar day.

func ComputeDailyBuckets

func ComputeDailyBuckets(entries []AnalyticsEntry) []DailyBucket

ComputeDailyBuckets groups entries by calendar day (local time) sorted ascending. Pure function — no I/O.

func (DailyBucket) AutoApproveRate

func (b DailyBucket) AutoApproveRate() float64

AutoApproveRate returns the fraction of decisions that were auto-allowed.

type DatabaseService

type DatabaseService struct{}

DatabaseService implements the database/workspace switcher RPC methods. It is stateless: each call reads the filesystem directly.

func NewDatabaseService

func NewDatabaseService() *DatabaseService

NewDatabaseService creates a DatabaseService.

func (*DatabaseService) GetCurrentDatabase

GetCurrentDatabase returns metadata for the currently active workspace database.

func (*DatabaseService) ListDatabases

ListDatabases returns all discovered workspace databases with metadata.

func (*DatabaseService) MergeDatabase

MergeDatabase copies sessions from a source workspace into the current one. Uses INSERT OR IGNORE so existing sessions (matched by title) are never overwritten.

func (*DatabaseService) SwitchDatabase

SwitchDatabase writes a preference file and triggers an exec-based server self-restart. The client should poll until the server is back up, then reload the page.

type DebugSnapshot

type DebugSnapshot struct {
	Version    int                `json:"version"`
	Timestamp  time.Time          `json:"timestamp"`
	Note       string             `json:"note,omitempty"`
	Server     ServerInfo         `json:"server"`
	Sessions   []SessionSnapshot  `json:"sessions"`
	Tmux       TmuxSnapshot       `json:"tmux"`
	Approvals  ApprovalSnapshot   `json:"approvals"`
	RecentLogs RecentLogsSnapshot `json:"recent_logs"`
	Errors     []string           `json:"errors,omitempty"`
}

DebugSnapshot is the top-level JSON structure written to disk.

func CollectSnapshot

func CollectSnapshot(ctx context.Context, note string, instances []*session.Instance, approvalStore *ApprovalStore, logLines int) *DebugSnapshot

CollectSnapshot gathers all diagnostic data into a DebugSnapshot. Individual subsystem failures are recorded in Errors and do not abort the collection.

type DefaultRulePromptBuilder added in v1.35.0

type DefaultRulePromptBuilder struct{}

DefaultRulePromptBuilder implements RulePromptBuilder. It is a pure function — no I/O or external calls.

func (*DefaultRulePromptBuilder) BuildSystemPrompt added in v1.35.0

func (b *DefaultRulePromptBuilder) BuildSystemPrompt(ctx RulePromptContext) string

BuildSystemPrompt returns the system prompt for rule suggestion. Includes a JSON schema of SuggestedRuleProto fields, existing rules as JSON, 5 seed examples, pattern priority instructions, and a priority tier legend.

func (*DefaultRulePromptBuilder) BuildUserPrompt added in v1.35.0

func (b *DefaultRulePromptBuilder) BuildUserPrompt(ctx RulePromptContext) string

BuildUserPrompt returns the user prompt, formatted by source type. Before including any CommandPreview, it applies a second-pass secret scan and replaces positives with [REDACTED] (defense-in-depth, per FLAG-1).

type DefaultsService added in v1.12.0

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

DefaultsService handles session defaults RPC methods.

func NewDefaultsService added in v1.12.0

func NewDefaultsService() *DefaultsService

NewDefaultsService creates a DefaultsService.

func (*DefaultsService) DeleteAlias added in v1.35.0

DeleteAlias removes an alias preset by name.

func (*DefaultsService) DeleteDirectoryRule added in v1.12.0

DeleteDirectoryRule removes a directory rule by path.

func (*DefaultsService) DeleteProfile added in v1.12.0

DeleteProfile removes a named profile by name.

func (*DefaultsService) GetSessionDefaults added in v1.12.0

GetSessionDefaults returns the full session defaults configuration.

func (*DefaultsService) ListAliases added in v1.35.0

ListAliases returns all configured aliases.

func (*DefaultsService) ResolveDefaults added in v1.12.0

ResolveDefaults merges all default layers for the given working directory and profile.

func (*DefaultsService) SetOnGlobalDefaultsUpdated added in v1.41.0

func (d *DefaultsService) SetOnGlobalDefaultsUpdated(fn func())

SetOnGlobalDefaultsUpdated wires in the callback invoked after every successful UpdateGlobalDefaults save.

func (*DefaultsService) SetSharedBacklogConfig added in v1.41.0

func (d *DefaultsService) SetSharedBacklogConfig(cfg *config.Config, mu *sync.RWMutex)

SetSharedBacklogConfig wires the live *config.Config instance (and its guarding mutex) that BacklogService reads MaxConcurrentBacklogWorkItems / MaxAutoReworkIterations from — see sharedBacklogCfg's doc comment for why this is needed and what it does and does not change about UpdateGlobalDefaults's existing behavior. Called once from server/dependencies.go with the exact same *config.Config pointer and *sync.RWMutex passed to services.NewBacklogService / BacklogService.ConfigMu.

func (*DefaultsService) UpdateGlobalDefaults added in v1.12.0

UpdateGlobalDefaults replaces the global default fields and persists them.

func (*DefaultsService) UpsertAlias added in v1.35.0

UpsertAlias creates or updates a named alias preset (matched by name). NOTE: like all other config-write handlers, this follows the lock-free load-modify-save pattern. Concurrent writes are last-write-wins — the accepted project tradeoff; see DefaultsService for context.

func (*DefaultsService) UpsertDirectoryRule added in v1.12.0

UpsertDirectoryRule creates or updates a directory rule (matched by path).

func (*DefaultsService) UpsertProfile added in v1.12.0

UpsertProfile creates or updates a named profile.

type DirCache added in v1.13.0

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

DirCache is a thread-safe, mtime+TTL-invalidated cache of os.DirEntry slices keyed by directory path. It is intended to reduce repeated os.ReadDir calls for the same directory within a short window (e.g., repeated Omnibar opens).

Invalidation policy:

  • An entry is stale if time.Since(cachedAt) > ttl.
  • An entry is stale if the directory's mtime has advanced since the entry was stored.
  • Eviction is LRU by insertion order: when len(entries) >= maxSize, the oldest entry (earliest cachedAt) is removed before storing a new one.

No background goroutines are used; all operations are on-demand.

func NewDirCache added in v1.13.0

func NewDirCache(maxSize int, ttl time.Duration) *DirCache

NewDirCache creates a DirCache with the given capacity and TTL.

func (*DirCache) Get added in v1.13.0

func (c *DirCache) Get(path string) ([]os.DirEntry, bool)

Get returns the cached DirEntry slice for path if the entry is still valid.

Validity requires both:

  1. time.Since(entry.cachedAt) <= c.ttl (TTL not expired)
  2. os.Stat(path).ModTime() == entry.dirMtime (directory unchanged)

Returns (nil, false) on any miss: entry absent, TTL expired, or mtime changed. A stale-mtime entry is removed from the cache under a write lock so the next Put does not exceed maxSize unnecessarily.

func (*DirCache) Put added in v1.13.0

func (c *DirCache) Put(path string, entries []os.DirEntry, dirMtime time.Time)

Put stores entries for path with the provided dirMtime. If the cache is at capacity, the entry with the oldest cachedAt is evicted first.

type DomainAgeChecker

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

DomainAgeChecker extracts domains from Bash commands and checks their registration age using RDAP (Registration Data Access Protocol). Results are cached for 24h.

A domain is considered "new" if its registration date is within the configured threshold (default 30 days). New domains from network-oriented commands are escalated for review.

func NewDomainAgeChecker

func NewDomainAgeChecker(enabled bool) *DomainAgeChecker

NewDomainAgeChecker creates a DomainAgeChecker with sensible defaults. Set enabled=false to disable RDAP lookups (no-op mode).

func (*DomainAgeChecker) IsNewlyRegistered

func (d *DomainAgeChecker) IsNewlyRegistered(ctx context.Context, domain string) (bool, error)

IsNewlyRegistered returns true if the domain was registered within the threshold and the check is enabled. Returns (false, nil) for any lookup failure, to avoid blocking legitimate operations on RDAP outages.

func (*DomainAgeChecker) NewDomainThreshold

func (d *DomainAgeChecker) NewDomainThreshold() time.Duration

NewDomainThreshold returns how old a domain must be to be considered "established".

type EnvVarCredentialSource added in v1.35.0

type EnvVarCredentialSource struct{}

EnvVarCredentialSource reads API keys from environment variables. This is the highest-priority source in the default chain.

func (*EnvVarCredentialSource) Name added in v1.35.0

func (s *EnvVarCredentialSource) Name() string

func (*EnvVarCredentialSource) Resolve added in v1.35.0

func (s *EnvVarCredentialSource) Resolve(_ context.Context, provider string) (Credential, bool, error)

type ErrorRegistry added in v1.35.0

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

ErrorRegistry deduplicates RPC errors and persists them to SQLite. Each unique (message, procedure) pair is stored once and occurrence_count is incremented on every subsequent hit.

func NewErrorRegistry added in v1.35.0

func NewErrorRegistry(entClient *ent.Client, enabled bool) *ErrorRegistry

NewErrorRegistry creates an ErrorRegistry backed by entClient. Pass enabled=false (or a nil entClient) to make every call a no-op.

func (*ErrorRegistry) Acknowledge added in v1.35.0

func (r *ErrorRegistry) Acknowledge(ctx context.Context, fingerprint string) error

Acknowledge marks a single error event as acknowledged.

func (*ErrorRegistry) List added in v1.35.0

func (r *ErrorRegistry) List(ctx context.Context, includeAcknowledged bool) ([]*ent.ErrorEvent, error)

List returns error events ordered by last_seen desc. When includeAcknowledged is false only unacknowledged events are returned.

func (*ErrorRegistry) Record added in v1.35.0

func (r *ErrorRegistry) Record(ctx context.Context, errVal error, procedure string)

Record deduplicates the error by fingerprint and upserts into SQLite. Silently drops the event when disabled or when the client is nil. Implements the ErrorRecorder interface consumed by the interceptor.

type EscapeCodeHandler

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

EscapeCodeHandler provides REST endpoints for escape code analytics

func NewEscapeCodeHandler

func NewEscapeCodeHandler() *EscapeCodeHandler

NewEscapeCodeHandler creates a new handler using the global store

func (*EscapeCodeHandler) HandleClear

func (h *EscapeCodeHandler) HandleClear(w http.ResponseWriter, r *http.Request)

HandleClear clears all recorded escape codes DELETE /api/debug/escape-codes

func (*EscapeCodeHandler) HandleExport

func (h *EscapeCodeHandler) HandleExport(w http.ResponseWriter, r *http.Request)

HandleExport exports all data as JSON GET /api/debug/escape-codes/export

func (*EscapeCodeHandler) HandleGetAll

func (h *EscapeCodeHandler) HandleGetAll(w http.ResponseWriter, r *http.Request)

HandleGetAll returns all escape code entries GET /api/debug/escape-codes

func (*EscapeCodeHandler) HandleGetByCategory

func (h *EscapeCodeHandler) HandleGetByCategory(w http.ResponseWriter, r *http.Request)

HandleGetByCategory returns entries for a specific category GET /api/debug/escape-codes/category/{category}

func (*EscapeCodeHandler) HandleGetBySession

func (h *EscapeCodeHandler) HandleGetBySession(w http.ResponseWriter, r *http.Request)

HandleGetBySession returns entries for a specific session GET /api/debug/escape-codes/session/{sessionId}

func (*EscapeCodeHandler) HandleGetStats

func (h *EscapeCodeHandler) HandleGetStats(w http.ResponseWriter, r *http.Request)

HandleGetStats returns aggregated statistics GET /api/debug/escape-codes/stats

func (*EscapeCodeHandler) HandleStatus

func (h *EscapeCodeHandler) HandleStatus(w http.ResponseWriter, r *http.Request)

HandleStatus returns the current tracking status GET /api/debug/escape-codes/status

func (*EscapeCodeHandler) HandleToggle

func (h *EscapeCodeHandler) HandleToggle(w http.ResponseWriter, r *http.Request)

HandleToggle enables or disables escape code tracking POST /api/debug/escape-codes/toggle Body: {"enabled": true/false}

func (*EscapeCodeHandler) RegisterRoutes

func (h *EscapeCodeHandler) RegisterRoutes(mux *http.ServeMux)

RegisterRoutes registers all escape code handler routes on the given mux

type EventBusNotifier added in v1.37.0

type EventBusNotifier struct {
	Bus *events.EventBus
}

EventBusNotifier adapts an *events.EventBus to session.Notifier. The session package cannot import pkg/events directly (pkg/events imports session, so the reverse import would be a cycle), so this adapter lives here instead and is wired in via BacklogLifecycleListener.SetNotifier / BacklogService.SetEventBus.

func (*EventBusNotifier) Notify added in v1.37.0

func (n *EventBusNotifier) Notify(itemID, title, message string, notificationType, priority int32)

Notify implements session.Notifier.

type ExternalWebSocketHandler

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

ExternalWebSocketHandler handles approval monitoring for external mux sessions. Terminal streaming has been migrated to the unified ConnectRPC WebSocket handler.

func NewExternalWebSocketHandler

func NewExternalWebSocketHandler(
	discovery *session.ExternalSessionDiscovery,
	tmuxStreamerManager *session.ExternalTmuxStreamerManager,
	approvalMonitor *session.ExternalApprovalMonitor,
	eventBus *events.EventBus,
) *ExternalWebSocketHandler

NewExternalWebSocketHandler creates a new handler for external session approval monitoring. Note: tmuxStreamerManager parameter is kept for backward compatibility but is no longer used since terminal streaming has been migrated to the unified ConnectRPC WebSocket handler.

func (*ExternalWebSocketHandler) HandleApprovalResponse

func (h *ExternalWebSocketHandler) HandleApprovalResponse(w http.ResponseWriter, r *http.Request)

HandleApprovalResponse handles user response to an approval request

func (*ExternalWebSocketHandler) HandleApprovals

func (h *ExternalWebSocketHandler) HandleApprovals(w http.ResponseWriter, r *http.Request)

HandleApprovals returns pending approvals for an external session

type FeatureController added in v1.35.0

type FeatureController interface {
	Enable(ctx context.Context) error
	Disable() error
	IsEnabled() bool
}

FeatureController is implemented by components that can be enabled/disabled at runtime. Used by GetFeatureFlags/UpdateFeatureFlag to toggle named subsystems.

type FeatureFlagService added in v1.35.0

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

FeatureFlagService handles GetFeatureFlags and UpdateFeatureFlag RPCs. It owns the knownFeatureFlags registry and the per-name FeatureController map. Extracted from SessionService per ADR-001 (UpdateFeatureFlag exceeds the 30-line threshold).

func NewFeatureFlagService added in v1.35.0

func NewFeatureFlagService() *FeatureFlagService

NewFeatureFlagService creates a FeatureFlagService. Call SetFeatureController for each flag that has an in-process runtime component.

func (*FeatureFlagService) GetFeatureFlags added in v1.35.0

+api: feature-flags:list GetFeatureFlags returns all known feature flags and their current state.

func (*FeatureFlagService) SetFeatureController added in v1.35.0

func (f *FeatureFlagService) SetFeatureController(name string, c FeatureController)

SetFeatureController wires a runtime controller for the named feature flag. When UpdateFeatureFlag is called for this name, the controller's Enable/Disable methods are invoked in addition to persisting the flag to config.

func (*FeatureFlagService) SetStatusDetailProvider added in v1.42.0

func (f *FeatureFlagService) SetStatusDetailProvider(name string, fn func() string)

SetStatusDetailProvider wires an optional status-detail provider for the named feature flag. GetFeatureFlags calls fn on every request and populates FeatureFlag.StatusDetail with its result (empty string when fn returns "").

func (*FeatureFlagService) UpdateFeatureFlag added in v1.35.0

+api: feature-flags:update UpdateFeatureFlag enables or disables a named feature flag and persists the change.

type FileService added in v1.11.0

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

FileService handles ListFiles and GetFileContent RPCs.

func NewFileService added in v1.11.0

func NewFileService(workspace WorkspaceProvider) *FileService

NewFileService creates a FileService with the given workspace provider.

func (*FileService) GetFileContent added in v1.11.0

GetFileContent retrieves the content of a file in the session's worktree.

func (*FileService) ListFiles added in v1.11.0

ListFiles returns the immediate children of the given directory in the session's worktree.

func (*FileService) SearchFiles added in v1.12.0

SearchFiles performs a recursive name-substring search in the session's worktree.

func (*FileService) ServeFileRaw added in v1.35.0

func (fs *FileService) ServeFileRaw(w http.ResponseWriter, r *http.Request)

ServeFileRaw serves a file's raw bytes over HTTP, with optional Content-Disposition for browser-triggered downloads. It validates the path against the session's worktree root to prevent path traversal.

Query parameters:

sessionId - required session identifier
path      - required relative path within the session worktree
download  - optional; set to "true" to force Content-Disposition: attachment

type FileUploadHandler added in v1.35.0

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

FileUploadHandler saves uploaded files to a temp directory and returns the absolute path so the terminal process can reference the file.

func NewFileUploadHandler added in v1.35.0

func NewFileUploadHandler(dir string) *FileUploadHandler

func (*FileUploadHandler) HandleUpload added in v1.35.0

func (h *FileUploadHandler) HandleUpload(w http.ResponseWriter, r *http.Request)

type GeminiLimitsClient added in v1.35.0

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

func NewGeminiLimitsClient added in v1.35.0

func NewGeminiLimitsClient(chain *CredentialChain, model string) *GeminiLimitsClient

func (*GeminiLimitsClient) ModelContextWindow added in v1.35.0

func (c *GeminiLimitsClient) ModelContextWindow(model string) int

func (*GeminiLimitsClient) Provider added in v1.35.0

func (c *GeminiLimitsClient) Provider() string

func (*GeminiLimitsClient) QueryLimits added in v1.35.0

func (c *GeminiLimitsClient) QueryLimits(ctx context.Context) (ProviderLimits, error)

func (*GeminiLimitsClient) UpdateFromResponseHeaders added in v1.35.0

func (c *GeminiLimitsClient) UpdateFromResponseHeaders(h http.Header, current ProviderLimits) ProviderLimits

type GenericWebhookHandler added in v1.43.0

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

GenericWebhookHandler handles POST /webhooks/{slug}: generic `webhook`-type Workflow triggers, matched by event/label_filter, rendered against arbitrary JSON. Concrete type, not an interface — one implementation, per .claude/rules/interface-pollution-checklist.md.

func NewGenericWebhookHandler added in v1.43.0

func NewGenericWebhookHandler(repo session.WorkflowRepository, scheduler *workflows.Scheduler, fireEvents session.TriggerFireEventRepository, cfg *config.Config) *GenericWebhookHandler

NewGenericWebhookHandler constructs a GenericWebhookHandler.

func (*GenericWebhookHandler) Handle added in v1.43.0

Handle processes an inbound generic webhook delivery: resolves the Workflow by slug, verifies HMAC signature, dedups by a SHA-256 digest of the raw body (generic webhooks have no provider-assigned delivery ID), matches event/label_filter, then renders and fires.

func (*GenericWebhookHandler) RegisterRoutes added in v1.43.0

func (h *GenericWebhookHandler) RegisterRoutes(mux *http.ServeMux)

RegisterRoutes registers the generic webhook endpoint on mux.

type GitHubService

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

GitHubService handles all GitHub PR RPC methods.

These methods shell out to the `gh` CLI and have no dependency on review queue, terminal streaming, or search. They only need to look up a session by ID and call PR operations on it.

func NewGitHubService

func NewGitHubService(storage *session.Storage) *GitHubService

NewGitHubService creates a GitHubService backed by the given storage.

func (*GitHubService) ClosePR

ClosePR closes the PR without merging for a session.

func (*GitHubService) GetPRComments

GetPRComments retrieves all comments on the PR for a session.

func (*GitHubService) GetPRInfo

GetPRInfo retrieves the latest PR information for a session.

func (*GitHubService) MergePR

MergePR merges the PR for a session using the specified merge method.

func (*GitHubService) PostPRComment

PostPRComment posts a new comment to the PR for a session.

type GitHubUserService added in v1.35.0

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

GitHubUserService implements the ConnectRPC GitHubUserServiceHandler.

func NewGitHubUserService added in v1.35.0

func NewGitHubUserService(cache *githubpkg.UserPRCache, enterpriseHosts []config.GitHubEnterpriseHost) *GitHubUserService

NewGitHubUserService creates a new service backed by the given cache.

func (*GitHubUserService) AddGitHubAccountFromCLI added in v1.41.0

+api: github-user:add-account-from-cli AddGitHubAccountFromCLI fetches the token gh CLI already holds for a host (discovered via ListGitHubCLIHosts), validates it, and stores it in the keychain — same outcome as AddGitHubAccountWithToken without manual paste.

func (*GitHubUserService) AddGitHubAccountWithToken added in v1.41.0

+api: github-user:add-account-with-token AddGitHubAccountWithToken validates a personal access token against the host's /user endpoint and stores it in the keychain on success. Use this for hosts that don't support OAuth Device Flow (e.g. some GHES instances).

func (*GitHubUserService) GetGitHubAuthState added in v1.35.0

+api: github-user:get-auth-state GetGitHubAuthState returns the current GitHub authentication status.

func (*GitHubUserService) ListGitHubAccounts added in v1.35.0

+api: github-user:list-accounts ListGitHubAccounts returns all connected GitHub accounts.

func (*GitHubUserService) ListGitHubCLIHosts added in v1.41.0

+api: github-user:list-cli-hosts ListGitHubCLIHosts discovers hosts the local gh CLI is already authenticated to, so the UI can offer one-click imports.

func (*GitHubUserService) ListUserPRs added in v1.35.0

+api: github-user:list-prs ListUserPRs returns the current cached snapshot of open PRs.

func (*GitHubUserService) PollGitHubDeviceAuth added in v1.35.0

+api: github-user:poll-device-auth PollGitHubDeviceAuth polls GitHub's token endpoint once. The frontend calls this on an interval until status is COMPLETE or EXPIRED.

func (*GitHubUserService) RevokeGitHubToken added in v1.35.0

+api: github-user:revoke-token RevokeGitHubToken removes a per-account keychain token (or the legacy slot) and resets auth state.

func (*GitHubUserService) StartGitHubDeviceAuth added in v1.35.0

+api: github-user:start-device-auth StartGitHubDeviceAuth initiates the GitHub Device Flow OAuth.

func (*GitHubUserService) WatchUserPRs added in v1.35.0

+api: github-user:watch-prs WatchUserPRs streams PR snapshot updates. Pattern: send initial snapshot → register callback → forward events until disconnect.

type GitHubWebhookHandler added in v1.43.0

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

GitHubWebhookHandler handles POST /webhooks/github: GitHub push-event deliveries matched against enabled github_push-type Workflow rows. Concrete type, not an interface — one implementation, per .claude/rules/interface-pollution-checklist.md.

func NewGitHubWebhookHandler added in v1.43.0

func NewGitHubWebhookHandler(repo session.WorkflowRepository, scheduler *workflows.Scheduler, fireEvents session.TriggerFireEventRepository, cfg *config.Config) *GitHubWebhookHandler

NewGitHubWebhookHandler constructs a GitHubWebhookHandler.

func (*GitHubWebhookHandler) Handle added in v1.43.0

Handle processes an inbound GitHub webhook delivery: verifies HMAC signature per matching-repo candidate, matches repo/branch across all enabled github_push-type Workflow rows, dedups and fires each fresh match.

func (*GitHubWebhookHandler) RegisterRoutes added in v1.43.0

func (h *GitHubWebhookHandler) RegisterRoutes(mux *http.ServeMux)

RegisterRoutes registers the GitHub webhook endpoint on mux.

type GitignoreCache added in v1.35.0

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

GitignoreCache is a thread-safe TTL cache of gitignore.Pattern slices keyed by a composite path string. It mirrors the design of DirCache.

Invalidation policy:

  • An entry is stale if time.Since(cachedAt) > ttl.
  • Eviction is LRU by insertion order: when len(entries) >= maxSize, the oldest entry (earliest cachedAt) is removed before storing a new one.

No background goroutines are used; all operations are on-demand.

func NewGitignoreCache added in v1.35.0

func NewGitignoreCache(maxSize int, ttl time.Duration) GitignoreCache

NewGitignoreCache creates a GitignoreCache with the given capacity and TTL.

func (*GitignoreCache) Get added in v1.35.0

func (c *GitignoreCache) Get(key string) ([]gitignore.Pattern, bool)

Get returns the cached pattern slice for key if the entry is still valid. Validity requires time.Since(entry.cachedAt) <= c.ttl. Returns (nil, false) on any miss: entry absent or TTL expired.

func (*GitignoreCache) Put added in v1.35.0

func (c *GitignoreCache) Put(key string, patterns []gitignore.Pattern, mtime time.Time)

Put stores patterns for key with the provided mtime (stored for future reference). If the cache is at capacity, the entry with the oldest cachedAt is evicted first.

type HeadlessService added in v1.35.0

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

HeadlessService implements the RunHeadlessCall streaming RPC.

func NewHeadlessService added in v1.35.0

func NewHeadlessService(pool *headless.Pool) *HeadlessService

NewHeadlessService creates a HeadlessService backed by the given pool. pool may be nil; in that case RunHeadlessCall returns CodeUnavailable.

func (*HeadlessService) RunHeadlessCall added in v1.35.0

RunHeadlessCall streams LLM output chunks back to the caller. It validates the feature_key and prompt sizes, applies a timeout, then drains the pool channel.

type HeadroomEstimate added in v1.42.0

type HeadroomEstimate struct {
	WindowStart   time.Time
	WindowEnd     time.Time
	TokensUsed    int64
	AssumedBudget int64
	PctRemaining  float64
	// Valid is false when AssumedBudget<=0 (uncalibrated) or the token store is
	// still loading — both cases mean this estimate must never trigger a pause.
	Valid bool
}

HeadroomEstimate is the soft/proactive quota signal's output: an estimate of remaining session-quota headroom over the trailing 5h window, derived from observed token usage against an operator-supplied assumed budget.

type HookName added in v1.17.0

type HookName string

HookName is a typed constant for the built-in hooks that can be injected.

const (
	HookPermissionApproval HookName = "permission_approval" // maps to PermissionRequest event
	HookStopNotification   HookName = "stop_notification"   // maps to Stop event
	HookPreToolLogging     HookName = "pre_tool_logging"    // maps to PreToolUse event
	HookPostToolLogging    HookName = "post_tool_logging"   // maps to PostToolUse event
	HookPromptSubmit       HookName = "prompt_submit"       // maps to UserPromptSubmit event

	// HookGitDriftCheck also maps to PostToolUse, but on its own dedicated endpoint
	// (distinct from HookPostToolLogging's) so it can be injected independently of
	// the generic hooks a manually-created session opts into. Scoped strictly to
	// autonomous backlog work sessions — see spawnSessionAfterGates in
	// server/services/backlog_service_triage.go, the only call site that injects
	// it, and hook_receiver_drift.go for the receiver. Fires the same branch-drift
	// check that gates review (BUG-044) right after every git commit/push, so an
	// autonomous session notices and can self-correct immediately instead of only
	// learning about drift from a review verdict hours or days later.
	HookGitDriftCheck HookName = "git_drift_check" // maps to PostToolUse event
)

type HookReceiver added in v1.17.0

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

HookReceiver handles inbound Claude Code hook callbacks for non-approval events. These are fire-and-forget: Claude does not block on the response.

func NewHookReceiver added in v1.17.0

func NewHookReceiver() *HookReceiver

NewHookReceiver creates a HookReceiver.

func (*HookReceiver) HandlePostToolUse added in v1.17.0

func (h *HookReceiver) HandlePostToolUse(w http.ResponseWriter, r *http.Request)

HandlePostToolUse receives the Claude Code PostToolUse hook.

func (*HookReceiver) HandlePostToolUseDriftCheck added in v1.41.0

func (h *HookReceiver) HandlePostToolUseDriftCheck(w http.ResponseWriter, r *http.Request)

HandlePostToolUseDriftCheck receives the Claude Code PostToolUse hook, wired only into autonomous backlog work sessions (see HookGitDriftCheck's doc comment and spawnSessionAfterGates — the sole call site that injects this hook). It is a steering hook, not a gate: it never blocks, never merges, never modifies the worktree. On every git commit/push it re-runs the same branch-drift detection BUG-044's review-gate precondition uses (git.BehindOriginMain) and, only once past git.SteeringBranchDriftThreshold commits behind main, feeds an explanatory nudge back into the calling agent's own context via additionalContext — so the agent notices and can proactively sync while it's still working, instead of only finding out from a review verdict hours or days later.

Deliberately silent (no additionalContext) on every non-actionable path: not a Bash tool call, not a git commit/push command, under threshold, rate-limited, or a detection error (fails open, matching every other best-effort git check in this codebase — see git.EnsureBranchSyncedWithMain's identical fail-open rationale). Firing "you're fine" context on every ordinary commit would just be noise the agent has to read past; this only ever speaks up when there's something to act on.

func (*HookReceiver) HandlePreToolUse added in v1.17.0

func (h *HookReceiver) HandlePreToolUse(w http.ResponseWriter, r *http.Request)

HandlePreToolUse receives the Claude Code PreToolUse hook.

func (*HookReceiver) HandlePromptSubmit added in v1.17.0

func (h *HookReceiver) HandlePromptSubmit(w http.ResponseWriter, r *http.Request)

HandlePromptSubmit receives the Claude Code UserPromptSubmit hook.

func (*HookReceiver) HandleStop added in v1.17.0

func (h *HookReceiver) HandleStop(w http.ResponseWriter, r *http.Request)

HandleStop receives the Claude Code Stop hook.

func (*HookReceiver) RegisterRoutes added in v1.17.0

func (h *HookReceiver) RegisterRoutes(mux *http.ServeMux)

RegisterRoutes registers the non-approval hook endpoints on mux.

func (*HookReceiver) SetDriftCheckFn added in v1.41.0

func (h *HookReceiver) SetDriftCheckFn(fn func(worktreePath, mainBranch string) (int, error))

SetDriftCheckFn overrides the function used to compute how many commits a worktree is behind origin/mainBranch. Test-only — lets tests exercise HandlePostToolUseDriftCheck without a real git repo or network fetch.

func (*HookReceiver) SetDriftCheckMinInterval added in v1.41.0

func (h *HookReceiver) SetDriftCheckMinInterval(d time.Duration)

SetDriftCheckMinInterval overrides defaultDriftCheckMinInterval. Zero restores the default. Test-only; not called during normal server wiring.

func (*HookReceiver) SetDriftThreshold added in v1.41.0

func (h *HookReceiver) SetDriftThreshold(n int)

SetDriftThreshold overrides git.SteeringBranchDriftThreshold. Zero restores the default. Test-only; not called during normal server wiring.

type ImportService added in v1.42.0

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

ImportService implements the ImportService RPC surface for import-external-session (Story 1.1.3 onward). All three mutating RPCs are no-ops behind the STAPLER_SQUAD_ENABLE_SESSION_IMPORT feature flag, enforced by an interceptor registered in server.go (Story 1.3.2) rather than duplicated per-method here.

func NewImportService added in v1.42.0

func NewImportService(detector *session.HistoryFileDetector, inspector ProcessCreateTimeReader) *ImportService

NewImportService creates an ImportService. inspector may be nil in environments where process inspection isn't available (e.g. unsupported OS); PreviewImportExternalSession still functions but never populates pid_identity.

func NewImportServiceWithRealInspector added in v1.42.0

func NewImportServiceWithRealInspector(
	storage session.InstanceStore,
	registry *session.Registry,
	linker *session.HistoryLinker,
	suspended *session.SuspendedProcessStore,
) *ImportService

NewImportServiceWithRealInspector creates an ImportService wired to the real OS-backed HistoryFileDetector and ProcessInspector, plus the dependencies needed by the three mutating RPCs.

func (*ImportService) CancelPendingKill added in v1.42.0

CancelPendingKill abandons the entire import: deletes the committed Instance and, only if that succeeds, resumes the original process (Story 1.3.3). See session.CancelPendingKill's doc comment for why deletion must happen strictly before resumption.

+api: import:cancel_pending_kill

func (*ImportService) CommitImportExternalSession added in v1.42.0

CommitImportExternalSession persists a managed Instance for the candidate, starts it resumed, and SIGSTOPs the original process (Story 1.2.1-1.2.4). Correlation drift (the candidate's history file(s) changed since preview) is surfaced as connect.CodeFailedPrecondition per import_commit.go's doc comment on ErrCorrelationDrifted, asking the client to re-preview. Every other domain-level failure (ambiguous choice, path collision, start failure) is reported inside the response body via status=FAILED so the client always gets a structured result to update UI state from.

+api: import:commit

func (*ImportService) ConfirmKillExternalSession added in v1.42.0

ConfirmKillExternalSession re-verifies the original process's identity and kills its tmux session (Story 1.3.1). The tmux session name is not part of the request (import.proto's ConfirmKillExternalSessionRequest carries only instance_id + pid_identity) so it's recovered from the SuspendedProcessRecord persisted by CommitImportExternalSession.

+api: import:confirm_kill

func (*ImportService) PreviewImportExternalSession added in v1.42.0

PreviewImportExternalSession runs correlation against the candidate and reports what an import WOULD do. Side-effect-free: no process signaling, no persistence.

+api: import:preview

type ImportStat

type ImportStat struct {
	Module string `json:"module"`
	Count  int    `json:"count"`
}

ImportStat is a Python module import with its usage count.

type InsightsService added in v1.35.0

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

InsightsService implements the ConnectRPC InsightsServiceHandler. It reads from a TokenStoreReader to serve token usage analytics.

func NewInsightsService added in v1.35.0

func NewInsightsService(
	store tokens.TokenStoreReader,
	pricing *tokens.PricingTable,
	associator *tokens.Associator,
) *InsightsService

NewInsightsService creates a new InsightsService.

func (*InsightsService) GetInsightsSummary added in v1.35.0

GetInsightsSummary returns aggregated token and cost data for a time range.

func (*InsightsService) GetSessionTurnTimeline added in v1.41.0

GetSessionTurnTimeline returns per-turn token stats for one session, fetched on-demand when the session detail drawer opens. +api: GetSessionTurnTimeline

func (*InsightsService) ListSessionTokens added in v1.35.0

ListSessionTokens returns per-session token summaries with pagination.

func (*InsightsService) WatchInsights added in v1.35.0

WatchInsights streams summary updates when new JSONL data is parsed. Sends an initial "parse_complete" event (or "loading" if still parsing), then pushes an "update" event each time the TokenStore processes a new file. +api: WatchInsights

type InstanceFinder added in v1.35.0

type InstanceFinder interface {
	FindInstance(sessionID string) *session.Instance
}

InstanceFinder provides a targeted in-memory session lookup for the VNC proxy. Implemented by session.ReviewQueuePoller — avoids a full SQLite deserialise on every WebSocket upgrade (contrast with session.InstanceStore.LoadInstances).

type InstancePoller added in v1.35.0

type InstancePoller interface {
	GetInstances() []*session.Instance
}

type LauncherPresetsService added in v1.42.0

type LauncherPresetsService struct{}

LauncherPresetsService handles the GetLauncherPresets RPC.

func NewLauncherPresetsService added in v1.42.0

func NewLauncherPresetsService() *LauncherPresetsService

NewLauncherPresetsService creates a LauncherPresetsService.

func (*LauncherPresetsService) GetLauncherPresets added in v1.42.0

+api: launcher_presets:get

GetLauncherPresets reads and validates ~/.stapler-squad/launcher-presets.json fresh on every call (no caching) — a missing file returns an empty, error-free response, while a malformed file returns an empty response with load_error populated rather than a Connect error, so the frontend can render a specific, diagnosable message.

type LiveInstanceFinder added in v1.35.0

type LiveInstanceFinder interface {
	FindLiveInstance(id string) *session.Instance
}

LiveInstanceFinder is satisfied by SessionService. It returns the live in-memory instance by scanning the poller's tracked sessions (O(N)) or nil if the session is not yet in the poller. WorkspaceService uses this as a fast path to avoid calling LoadInstances() — which re-hydrates all sessions from disk and spawns PTY/tmux subprocesses — on every read-only RPC call.

type LocalFileService added in v1.35.0

type LocalFileService struct{}

LocalFileService serves arbitrary local filesystem paths over HTTP. Authentication is handled by the server middleware chain — local HTTP has no auth, remote HTTPS requires WebAuthn.

func NewLocalFileService added in v1.35.0

func NewLocalFileService() *LocalFileService

NewLocalFileService creates a LocalFileService.

func (*LocalFileService) ListLocalDirectory added in v1.35.0

func (s *LocalFileService) ListLocalDirectory(w http.ResponseWriter, r *http.Request)

ListLocalDirectory handles GET /api/local/files/list?path=/some/dir Defaults to the user home directory when path is omitted.

func (*LocalFileService) ServeLocalFile added in v1.35.0

func (s *LocalFileService) ServeLocalFile(w http.ResponseWriter, r *http.Request)

ServeLocalFile handles GET /api/local/serve/<absolute-path>. The path arrives after StripPrefix removes "/api/local/serve", leaving the absolute filesystem path (double leading slash is normalised by filepath.Clean on Unix).

type LogLevelHandler added in v1.35.0

type LogLevelHandler struct{}

LogLevelHandler exposes a simple REST endpoint for adjusting the server log level at runtime without restart. Intended for the debug menu in the web UI.

func NewLogLevelHandler added in v1.35.0

func NewLogLevelHandler() *LogLevelHandler

func (*LogLevelHandler) HandleGet added in v1.35.0

func (h *LogLevelHandler) HandleGet(w http.ResponseWriter, _ *http.Request)

HandleGet returns the current runtime log level.

func (*LogLevelHandler) HandleSet added in v1.35.0

func (h *LogLevelHandler) HandleSet(w http.ResponseWriter, r *http.Request)

HandleSet sets the runtime log level. Body: {"level":"DEBUG"|"INFO"|"WARNING"|"ERROR"}

func (*LogLevelHandler) RegisterRoutes added in v1.35.0

func (h *LogLevelHandler) RegisterRoutes(mux *http.ServeMux)

RegisterRoutes wires the handler into mux.

type NotificationRateLimiter

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

NotificationRateLimiter provides per-session rate limiting for notifications. Prevents notification flooding from individual sessions while allowing legitimate notification volumes across all sessions.

func NewNotificationRateLimiter

func NewNotificationRateLimiter(r float64, b int) *NotificationRateLimiter

NewNotificationRateLimiter creates a rate limiter. rate: notifications per second (e.g., 10) burst: max burst size (e.g., 20)

func (*NotificationRateLimiter) Allow

func (rl *NotificationRateLimiter) Allow(sessionID string) bool

Allow checks if a notification is allowed for the given session. Returns true if the notification should be processed, false if rate limited.

func (*NotificationRateLimiter) Cleanup

func (rl *NotificationRateLimiter) Cleanup(activeSessions []string)

Cleanup removes rate limiters for sessions that are no longer active. Should be called periodically to prevent memory leaks.

func (*NotificationRateLimiter) Count

func (rl *NotificationRateLimiter) Count() int

Count returns the number of active rate limiters (for monitoring).

func (*NotificationRateLimiter) Reset

func (rl *NotificationRateLimiter) Reset(sessionID string)

Reset removes the rate limiter for a specific session. Useful for testing or when a session is recreated.

type NotificationService

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

NotificationService handles notification sending and history RPCs.

Dependencies:

  • notificationStore: persists notification history
  • notificationRateLimiter: rate-limits per-session notification sends
  • eventBus: broadcasts notification events to connected clients
  • reviewQueuePoller: late-wired; used to resolve session names

func NewNotificationService

func NewNotificationService(
	rateLimiter *NotificationRateLimiter,
	eventBus *events.EventBus,
) *NotificationService

NewNotificationService creates a NotificationService with the given dependencies.

func (*NotificationService) ClearNotificationHistory

ClearNotificationHistory removes notifications from the history.

func (*NotificationService) GetNotificationHistory

GetNotificationHistory returns persisted notification history with optional filtering.

func (*NotificationService) GetNotificationStore

func (ns *NotificationService) GetNotificationStore() *notifications.NotificationHistoryStore

GetNotificationStore returns the notification history store.

func (*NotificationService) MarkNotificationRead

MarkNotificationRead marks specific notifications as read. If notification_ids is empty, marks all notifications as read.

func (*NotificationService) SendNotification

SendNotification allows tmux sessions and external Claude processes to send notifications. Enforces localhost-only restriction and rate limiting. Accepts both managed sessions and external sessions (e.g., Claude running in IntelliJ, VS Code, or other terminals).

func (*NotificationService) SetNotificationStore

func (ns *NotificationService) SetNotificationStore(store *notifications.NotificationHistoryStore)

SetNotificationStore sets the notification history store (late-wired).

func (*NotificationService) SetReviewQueuePoller

func (ns *NotificationService) SetReviewQueuePoller(poller *session.ReviewQueuePoller)

SetReviewQueuePoller sets the review queue poller for resolving session names.

type PRRunner added in v1.39.0

type PRRunner interface {
	RunOneShotForSession(ctx context.Context, sessionID, prompt string, timeoutSeconds int32) (string, error)
}

PRRunner runs a one-shot LLM prompt against a session's worktree, returning the PR URL the prompt produced (or "" if none was created). Defined here — the consumer — rather than in the session-management layer, per this repo's anti-interface-pollution convention; *services.SessionService satisfies it via RunOneShotForSession. Mirrors server.OneShotPRCreator, which the same method also satisfies for the review-queue's AutoCreatePR trigger.

type PathClassification added in v1.1.2

type PathClassification uint

PathClassification is a bitmask that categorises a resolved filesystem path. Multiple bits may be set simultaneously (e.g. a path can be both PathSystemDir and PathGitRepo if the repository lives under /usr/local/src).

const (
	// PathRoot matches a path that is exactly the filesystem root ("/").
	PathRoot PathClassification = 1 << iota
	// PathHome matches a path that is exactly the current user's home directory.
	PathHome
	// PathSystemDir matches a path that is, or is inside, one of the conventional
	// Unix system directories: /etc, /usr, /bin, /sbin, /lib, /lib64, /boot,
	// /dev, /sys, /proc, /run.
	PathSystemDir
	// PathTempDir matches a path that is, or is inside, a temporary directory
	// (/tmp, /var/tmp, or the value of $TMPDIR).
	PathTempDir
	// PathCwd matches a path that is, or is inside, the current working directory
	// as reported by classifier.ClassificationContext.Cwd.
	PathCwd
	// PathGitRepo matches a path that is, or is inside, the git repository root
	// as reported by classifier.ClassificationContext.RepoRoot.
	PathGitRepo
)

func ClassifyPath added in v1.1.2

ClassifyPath returns a PathClassification bitmask for path. path should already be expanded (via ExpandPath) before calling this function.

type PathCompletionService added in v1.10.0

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

PathCompletionService handles RPC methods for filesystem path completion. It caches directory listings in a DirCache to avoid repeated os.ReadDir calls.

func NewPathCompletionService added in v1.10.0

func NewPathCompletionService() *PathCompletionService

NewPathCompletionService creates a PathCompletionService with a DirCache.

func (*PathCompletionService) ListPathCompletions added in v1.10.0

ListPathCompletions returns filesystem entries matching the given path prefix.

func (*PathCompletionService) ListWorktrees added in v1.12.0

ListWorktrees returns the git worktrees for a given repository path.

type PathMatcher added in v1.1.2

type PathMatcher struct {
	// ArgIndex selects which non-flag argument to evaluate.
	// -1 (the default) means any non-flag argument may satisfy the match.
	// 0 means the first non-flag argument, 1 the second, and so on.
	ArgIndex int

	// MatchIf: at least one selected argument must match one of these
	// classifications for Matches to return true.
	// Zero means this check is skipped (always passes).
	MatchIf PathClassification

	// RejectIf: if any selected argument matches any of these classifications,
	// Matches returns false immediately regardless of MatchIf.
	// Zero means this check is skipped (never rejects).
	RejectIf PathClassification
}

PathMatcher provides structured path-based matching for Bash command arguments. It operates on the expanded (tilde/env-resolved) arguments of a parsed command and can be used alongside CommandPattern and Criteria on a Rule — all set fields must match (AND semantics).

Example — block rm on root or home:

PathMatcher: &PathMatcher{ArgIndex: -1, MatchIf: PathRoot | PathHome}

Example — reject if path is inside /tmp (allow-list inversion):

PathMatcher: &PathMatcher{ArgIndex: 0, RejectIf: PathTempDir}

func (*PathMatcher) Matches added in v1.1.2

func (pm *PathMatcher) Matches(expandedArgs []string, ctx classifier.ClassificationContext) bool

Matches returns true when the path arguments in expandedArgs satisfy the PathMatcher criteria against ctx.

type PendingApproval

type PendingApproval struct {
	ID              string
	SessionID       string // stapler-squad session title (mapped from hook)
	ClaudeSessionID string // Claude Code's internal session_id
	ToolName        string
	ToolInput       map[string]interface{}
	Cwd             string
	PermissionMode  string
	CreatedAt       time.Time
	ExpiresAt       time.Time

	// EscalationReason and EscalationCategory capture why this request was escalated
	// for manual review, set once at creation from classifier.EscalationReasonText /
	// classifier.CategorizeEscalationRuleID. Empty for approvals created before this
	// field existed (loaded from disk) — never re-derived after creation.
	EscalationReason   string
	EscalationCategory string

	// RiskLevel is the classifier-assigned risk level ("low"/"medium"/"high"/"critical"),
	// captured once at creation via riskLevelString(escalation.RiskLevel) — never re-derived
	// after creation (matches EscalationReason/EscalationCategory). "" means "not recorded"
	// (approvals created before this field existed, or when the classifier was unreachable) —
	// never treated as a fallback to "low".
	RiskLevel string

	// Orphaned is true for approvals loaded from disk after a server restart.
	// These have no live HTTP connection, so they cannot be resolved via the decision channel.
	Orphaned bool
	// contains filtered or unexported fields
}

PendingApproval represents an in-flight hook approval waiting for a user decision.

type PersistedApproval

type PersistedApproval struct {
	ID                 string                 `json:"id"`
	SessionID          string                 `json:"session_id"`
	ClaudeSessionID    string                 `json:"claude_session_id"`
	ToolName           string                 `json:"tool_name"`
	ToolInput          map[string]interface{} `json:"tool_input"`
	Cwd                string                 `json:"cwd"`
	PermissionMode     string                 `json:"permission_mode"`
	CreatedAt          time.Time              `json:"created_at"`
	ExpiresAt          time.Time              `json:"expires_at"`
	EscalationReason   string                 `json:"escalation_reason,omitempty"`
	EscalationCategory string                 `json:"escalation_category,omitempty"`
	RiskLevel          string                 `json:"risk_level,omitempty"`
	Orphaned           bool                   `json:"orphaned"`
}

PersistedApproval is the JSON-serializable representation of a PendingApproval for disk storage.

type ProcessCreateTimeReader added in v1.42.0

type ProcessCreateTimeReader interface {
	CreateTime(pid int32) (int64, error)
}

ProcessCreateTimeReader is the subset of procinfo.ProcessInspector needed to mint a PIDIdentity at preview time. Scoped to the one method this service actually calls (see .claude/rules/interface-pollution-checklist.md smell #1 -- this is deliberately narrow, not a general process-inspection interface).

type ProgramStat

type ProgramStat struct {
	Program  string `json:"program"`
	Category string `json:"category"`
	Count    int    `json:"count"`
}

ProgramStat is a command program with its category and usage count.

type ProjectService added in v1.23.0

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

ProjectService handles Project CRUD RPCs.

func NewProjectService added in v1.23.0

func NewProjectService(storage *session.Storage) *ProjectService

NewProjectService creates a ProjectService backed by Storage. Returns nil if storage is nil (test environments).

func (*ProjectService) AssignSessionsToProject added in v1.23.0

AssignSessionsToProject links sessions to a project.

func (*ProjectService) CreateProject added in v1.23.0

CreateProject creates a new project.

func (*ProjectService) DeleteProject added in v1.23.0

DeleteProject removes a project; sessions are unassigned.

func (*ProjectService) ListProjects added in v1.23.0

ListProjects returns all projects.

func (*ProjectService) UpdateProject added in v1.23.0

UpdateProject modifies an existing project.

type ProviderLimits added in v1.35.0

type ProviderLimits struct {
	Provider string `json:"provider"` // "anthropic", "google", "openai"
	Model    string `json:"model"`

	// Rate limits for requests
	RequestsLimit     int       `json:"requests_limit"`
	RequestsRemaining int       `json:"requests_remaining"`
	RequestsReset     time.Time `json:"requests_reset"`

	// Rate limits for tokens (total, or input/output combined)
	TokensLimit     int       `json:"tokens_limit"`
	TokensRemaining int       `json:"tokens_remaining"`
	TokensReset     time.Time `json:"tokens_reset"`

	// Detailed token rate limits (some providers separate input and output tokens)
	InputTokensLimit      int       `json:"input_tokens_limit"`
	InputTokensRemaining  int       `json:"input_tokens_remaining"`
	InputTokensReset      time.Time `json:"input_tokens_reset"`
	OutputTokensLimit     int       `json:"output_tokens_limit"`
	OutputTokensRemaining int       `json:"output_tokens_remaining"`
	OutputTokensReset     time.Time `json:"output_tokens_reset"`

	// Context window token usage (session-level)
	ContextTokensUsed int `json:"context_tokens_used"`
	ContextTokensMax  int `json:"context_tokens_max"`

	// Cumulative token usage for this session
	SessionInputTokens  int     `json:"session_input_tokens"`
	SessionOutputTokens int     `json:"session_output_tokens"`
	EstimatedCostUSD    float64 `json:"estimated_cost_usd"`

	// Provider status/health
	Available     bool      `json:"available"`
	LastErrorCode string    `json:"last_error_code"`
	FetchedAt     time.Time `json:"fetched_at"`
}

ProviderLimits represents a snapshot of the rate limit and usage state for a provider/model. All integer values should be -1 if they are unknown or not supported by the provider.

type ProviderLimitsClient added in v1.35.0

type ProviderLimitsClient interface {
	// Provider returns the provider identifier (e.g. "anthropic", "google").
	Provider() string

	// QueryLimits makes an API call to get current limits.
	// E.g. a lightweight probe call for Anthropic, or model info query for Gemini.
	QueryLimits(ctx context.Context) (ProviderLimits, error)

	// UpdateFromResponseHeaders extracts limit headers from an API call response
	// and returns an updated ProviderLimits.
	UpdateFromResponseHeaders(headers http.Header, current ProviderLimits) ProviderLimits

	// ModelContextWindow returns the max context tokens for a model.
	ModelContextWindow(model string) int
}

ProviderLimitsClient queries a single provider for current capacity.

type PushHandler added in v1.12.0

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

PushHandler handles HTTP endpoints for push notifications

func NewPushHandler added in v1.12.0

func NewPushHandler(pushService *PushService) *PushHandler

NewPushHandler creates a new push notification handler

func (*PushHandler) RegisterRoutes added in v1.12.0

func (h *PushHandler) RegisterRoutes(mux *http.ServeMux)

RegisterRoutes registers the HTTP endpoints for push notifications

type PushNotification added in v1.12.0

type PushNotification struct {
	Title              string                 `json:"title"`
	Body               string                 `json:"body"`
	Icon               string                 `json:"icon,omitempty"`
	Tag                string                 `json:"tag,omitempty"`
	Data               map[string]interface{} `json:"data,omitempty"`
	RequireInteraction bool                   `json:"requireInteraction,omitempty"`
	Renotify           bool                   `json:"renotify,omitempty"`
}

type PushService added in v1.12.0

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

func NewPushService added in v1.12.0

func NewPushService(configDir string) *PushService

func (*PushService) GetSubscriptions added in v1.12.0

func (ps *PushService) GetSubscriptions() []PushSubscription

func (*PushService) GetVapidPublicKey added in v1.12.0

func (ps *PushService) GetVapidPublicKey() string

func (*PushService) SendNotification added in v1.12.0

func (ps *PushService) SendNotification(notif PushNotification) int

func (*PushService) Subscribe added in v1.12.0

func (ps *PushService) Subscribe(sub PushSubscription) string

func (*PushService) Unsubscribe added in v1.12.0

func (ps *PushService) Unsubscribe(endpoint string) bool

type PushSubscription added in v1.12.0

type PushSubscription struct {
	Endpoint string `json:"endpoint"`
	Keys     struct {
		P256dh string `json:"p256dh"`
		Auth   string `json:"auth"`
	} `json:"keys"`
}

type QuotaGate added in v1.42.0

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

QuotaGate owns the account-wide Claude Code session-quota headroom decision: reads both the soft (percentage-heuristic) and hard (reactive-rate-limit) signals, applies hysteresis, and drives BacklogController.Enable/Disable — without ever becoming a second independent writer racing the manual Settings toggle. Also owns the foreground-session dispatch throttle (requirement 2), enforced through a separate, lighter-weight seam (ShouldThrottleForeground) rather than Disable/Enable.

func NewQuotaGate added in v1.42.0

func NewQuotaGate(
	cfgFn func() config.QuotaConfig,
	tokenStore tokens.TokenStoreReader,
	poller InstancePoller,
	backlogCtrl FeatureController,
	eventBus *events.EventBus,
) *QuotaGate

NewQuotaGate constructs a QuotaGate. cfgFn is consulted fresh on every Reconcile tick so config.json edits take effect without a restart.

func (*QuotaGate) IsPausedByQuota added in v1.42.0

func (g *QuotaGate) IsPausedByQuota() bool

IsPausedByQuota reports whether QuotaGate itself is the reason backlog is currently disabled.

func (*QuotaGate) Reconcile added in v1.42.0

func (g *QuotaGate) Reconcile(ctx context.Context)

Reconcile is QuotaGate's single per-tick decision method: evaluates the foreground throttle, both quota signals, hysteresis, and provenance, then drives BacklogController.Enable/Disable and fires notifications. Called from exactly one place in production: the shared 60s reconcile ticker (plus once synchronously at boot). A no-op entirely when QuotaConfig.Enabled is false.

func (*QuotaGate) ShouldThrottleForeground added in v1.42.0

func (g *QuotaGate) ShouldThrottleForeground() bool

ShouldThrottleForeground reports whether new backlog dispatch should be delayed because a human-driven session was recently observed active. Consulted by the composed SyncFeatureEnabledCheck closure, not by Disable/Enable — a full stop is disproportionate to "someone has a terminal open."

func (*QuotaGate) StatusDetail added in v1.42.0

func (g *QuotaGate) StatusDetail() string

StatusDetail returns a human-readable current-state string for the Settings > Feature Flags "backlog" row, or "" when there's nothing to say.

type RateLimitAggregate added in v1.42.0

type RateLimitAggregate struct {
	LastEventAt time.Time
}

RateLimitAggregate tracks the hard/reactive override signal: whether any tracked session has recently hit a rate limit. Always accessed under QuotaGate.mu via the locking wrapper (*QuotaGate).recordRateLimitEvent — this is a named, non-embedded field on QuotaGate, so Go does not promote these methods, and callers outside this file cannot reach them directly.

type ReactiveQueueManager

type ReactiveQueueManager interface {
	AddStreamClient(ctx context.Context, filters interface{}) (<-chan *sessionv1.ReviewQueueEvent, string)
	RemoveStreamClient(clientID string)
	OnControllerStatusChange(inst *session.Instance, newStatus detection.DetectedStatus)
}

ReactiveQueueManager is an interface to avoid circular dependencies. The actual implementation is in server/review_queue_manager.go

type RecentLogsSnapshot

type RecentLogsSnapshot struct {
	LogFilePath string   `json:"log_file_path"`
	LineCount   int      `json:"line_count"`
	Lines       []string `json:"lines"`
}

RecentLogsSnapshot contains the most recent log lines.

type RepoWatchRemover added in v1.41.0

type RepoWatchRemover interface {
	RemoveRepo(repoPath string)
}

RepoWatchRemover lets BacklogService tell the background unfinished-changes scanner (session/unfinished.Scanner) to stop watching a repo path once its worktree has been removed from disk — see BUG-034. Without this, the scanner's watch list only ever grows: every worktree it was ever told about (via session auto-spider) keeps getting rescanned on every tick forever, even long after the session/item that created it finished and its worktree was deleted. Nil-safe: BacklogService degrades gracefully (the repo just stays watched a little longer, until the scanner's own self-pruning backstop catches it) when not wired.

type ResyncOptions added in v1.44.0

type ResyncOptions struct {
	// SkipStaleDimensionSlowPath, when true, skips the resize+SIGWINCH+verify
	// block below entirely whenever the request also has StaleDimensions set
	// (Epic 4.1, terminal:resync-skip-stale-dimension-slowpath).
	SkipStaleDimensionSlowPath bool
	// UseFastLane, when true, routes capture/refresh calls through the
	// exec-gate fast lane (Epic 4.2, terminal:resync-exec-gate-fast-lane) via
	// target.CapturePaneContentPriority()/RefreshTmuxClientPriority() instead
	// of the plain CapturePaneContent()/RefreshTmuxClient().
	UseFastLane bool
	// EchoResyncID, when true, echoes the incoming request's ResyncId back on the
	// TerminalOutput reply (Task 3.2.1.1, terminal:resync-correlation-id). When
	// false, a request that set a resync_id gets the pre-project empty ResyncId
	// back instead.
	EchoResyncID bool
}

ResyncOptions bundles per-request behavior flags for handleCurrentPaneRequest. It exists so Epic 4.1 (stale-dimension slow-path skip) and Epic 4.2 (exec-gate fast lane) can each add their flag as a named field here instead of accreting another positional bool parameter onto handleCurrentPaneRequest's signature — see .claude/rules/primitive-obsession-checklist.md. Callers resolve the corresponding feature flag (config.LoadConfig().GetFeatureFlag(...)) and pass the result in; handleCurrentPaneRequest itself stays free of feature-flag lookups, so unit tests can exercise both branches by constructing ResyncOptions directly instead of mutating global config state.

type ReviewGateTrigger added in v1.37.0

type ReviewGateTrigger interface {
	TriggerReviewForSession(workSessionUUID string)
}

ReviewGateTrigger is implemented by BacklogLifecycleListener to fire an immediate headless review when an autonomous work session completes.

type ReviewQueueChecker

type ReviewQueueChecker interface {
	FindInstance(sessionID string) *session.Instance
	CheckSession(inst *session.Instance)
}

ReviewQueueChecker is an interface for triggering immediate review queue checks. This avoids importing the session package's concrete ReviewQueuePoller type directly.

type ReviewQueueService

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

ReviewQueueService handles all review-queue-related RPC methods, extracted from the monolithic SessionService for separation of concerns.

Dependencies it owns (moved out of SessionService):

  • reviewQueue: stateful queue managed by ReviewQueuePoller
  • reactiveQueueMgr: streams live review queue events to clients

Dependencies it borrows (still on SessionService, passed via setters):

  • storage: needed by AcknowledgeSession to persist ack timestamps
  • reviewQueuePoller: needed by AcknowledgeSession to refresh poller refs
  • eventBus: needed by AcknowledgeSession and LogUserInteraction

func NewReviewQueueService

func NewReviewQueueService(
	reviewQueue *session.ReviewQueue,
	storage *session.Storage,
	eventBus *events.EventBus,
) *ReviewQueueService

NewReviewQueueService creates a ReviewQueueService with the required state.

func (*ReviewQueueService) AcknowledgeSession

AcknowledgeSession marks a session as acknowledged in the review queue. The session won't reappear in the queue until it receives an update.

func (*ReviewQueueService) GetQueue

func (rqs *ReviewQueueService) GetQueue() *session.ReviewQueue

GetQueue returns the underlying ReviewQueue for wiring reactive components.

func (*ReviewQueueService) GetReactiveQueueManager added in v1.35.0

func (rqs *ReviewQueueService) GetReactiveQueueManager() ReactiveQueueManager

GetReactiveQueueManager returns the injected ReactiveQueueManager, or nil if not set.

func (*ReviewQueueService) GetReviewQueue

GetReviewQueue returns sessions needing user attention with priority ordering. Uses the global stateful queue managed by ReviewQueuePoller, with optional filtering.

func (*ReviewQueueService) LogUserInteraction

LogUserInteraction logs a user interaction event for audit trail and analytics.

func (*ReviewQueueService) SetApprovalStore

func (rqs *ReviewQueueService) SetApprovalStore(store *ApprovalStore)

SetApprovalStore injects the ApprovalStore for enriching APPROVAL_PENDING items with their pending_approval_id metadata.

func (*ReviewQueueService) SetReactiveQueueManager

func (rqs *ReviewQueueService) SetReactiveQueueManager(mgr ReactiveQueueManager)

SetReactiveQueueManager injects the ReactiveQueueManager (dependency injection). Must be called before WatchReviewQueue is used.

func (*ReviewQueueService) SetReviewQueuePoller

func (rqs *ReviewQueueService) SetReviewQueuePoller(poller *session.ReviewQueuePoller)

SetReviewQueuePoller injects the ReviewQueuePoller used to refresh instance references after acknowledgement.

func (*ReviewQueueService) WatchReviewQueue

WatchReviewQueue streams real-time review queue events.

type RulePromptBuilder added in v1.35.0

type RulePromptBuilder interface {
	BuildSystemPrompt(ctx RulePromptContext) string
	BuildUserPrompt(ctx RulePromptContext) string
}

RulePromptBuilder assembles system and user prompt strings from domain context. Implementations are pure functions — no I/O, no external calls. Fully testable.

type RulePromptContext added in v1.35.0

type RulePromptContext struct {
	ExistingRules  []RuleSpec     // user + seed + claude-settings rules
	AnalyticsGaps  []AnalyticsGap // unmatched commands grouped by tool/program
	CommandSample  string         // for COMMAND_SAMPLE source
	ToolNameFilter string         // optional single-tool scope
	ProgramFilter  string         // optional single-program scope
	WindowDays     int
}

RulePromptContext carries all domain data needed to build a suggestion prompt. Assembled by RulesService before passing to a RulePromptBuilder.

type RuleSpec

type RuleSpec struct {
	ID             string    `json:"id"`
	Name           string    `json:"name"`
	ToolName       string    `json:"tool_name,omitempty"`
	ToolPattern    string    `json:"tool_pattern,omitempty"`
	ToolCategory   string    `json:"tool_category,omitempty"`
	CommandPattern string    `json:"command_pattern,omitempty"`
	FilePattern    string    `json:"file_pattern,omitempty"`
	Decision       string    `json:"decision"`   // "auto_allow" | "auto_deny" | "escalate"
	RiskLevel      string    `json:"risk_level"` // "low" | "medium" | "high" | "critical"
	Reason         string    `json:"reason,omitempty"`
	Alternative    string    `json:"alternative,omitempty"`
	Priority       int       `json:"priority"`
	Enabled        bool      `json:"enabled"`
	Source         string    `json:"source"` // "user" | "seed" | "claude-settings"
	CreatedAt      time.Time `json:"created_at"`

	// Structured CommandCriteria fields — mutually exclusive with commandPattern.
	Programs              []string `json:"programs,omitempty"`
	Subcommands           []string `json:"subcommands,omitempty"`
	BlockedSubcommands    []string `json:"blocked_subcommands,omitempty"`
	RequiredFlags         []string `json:"required_flags,omitempty"`
	ForbiddenFlags        []string `json:"forbidden_flags,omitempty"`
	RequiredFlagPrefixes  []string `json:"required_flag_prefixes,omitempty"`
	PythonModes           []string `json:"python_modes,omitempty"`
	SafePythonImportsOnly bool     `json:"safe_python_imports_only,omitempty"`
	RequireCIPassing      bool     `json:"require_ci_passing,omitempty"`
	MinSessionIdleMinutes int32    `json:"min_session_idle_minutes,omitempty"`
}

RuleSpec is the JSON-serializable form of a Rule. CommandPattern and FilePattern are stored as strings (compiled on load).

type RuleStat

type RuleStat struct {
	RuleID   string `json:"rule_id"`
	RuleName string `json:"rule_name"`
	Count    int    `json:"count"`
}

RuleStat is a rule with its trigger count.

type RulesFile

type RulesFile struct {
	Version int        `json:"version"`
	Rules   []RuleSpec `json:"rules"`
}

RulesFile is the top-level structure of auto_approve_rules.json.

type RulesService

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

RulesService handles auto-approval rule management and analytics RPCs.

func NewRulesService

func NewRulesService(rulesStore *RulesStore, configStore ConfigFileRulesRepository, analyticsStore *AnalyticsStore, classifier *classifier.RuleBasedClassifier, promptBuilder RulePromptBuilder, aiClient AIClient) *RulesService

NewRulesService creates a RulesService. configStore, promptBuilder, and aiClient may be nil; nil means that capability is unavailable.

func (*RulesService) BulkUpsertRules added in v1.35.0

BulkUpsertRules creates or updates multiple rules in one call. Rebuilds the in-memory classifier exactly once at the end (not per-rule). Security: client-supplied id and source fields are always discarded. +api: rules:bulk-upsert

func (*RulesService) DeleteApprovalRule

DeleteApprovalRule removes a user rule by ID.

func (*RulesService) ExportRules added in v1.35.0

ExportRules serializes user-authored rules to YAML for download. Only source="user" rules are exported; seed and claude-settings rules are excluded. +api: rules:export

func (*RulesService) GenerateSuggestedRule added in v1.35.0

GenerateSuggestedRule asks an AI to propose new auto-approval rules. It is read-only — it never calls rulesStore.Upsert. +api: rules:generate-suggested

func (*RulesService) GetApprovalAnalytics

GetApprovalAnalytics returns aggregated analytics for the requested time window.

func (*RulesService) GetConfigFileRules added in v1.35.0

GetConfigFileRules returns rules from the shared YAML config file.

func (*RulesService) GetProgramAnalytics added in v1.35.0

GetProgramAnalytics returns drill-down analytics for a single program. Implements AC-7 (subcommand breakdown, examples, trend).

func (*RulesService) ListApprovalRules

ListApprovalRules returns all rules: user + seed + claude-settings.

func (*RulesService) SaveRulesToConfigFile added in v1.35.0

SaveRulesToConfigFile exports rules to the shared YAML config file.

func (*RulesService) UpsertApprovalRule

UpsertApprovalRule creates or updates a user rule.

func (*RulesService) ValidateRules added in v1.35.0

ValidateRules parses and validates a YAML rules file without persisting anything. Returns per-rule results; does not short-circuit on first error. +api: rules:validate

type RulesStore

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

RulesStore manages user-defined rules persisted to SQLite. Thread-safe for concurrent reads.

func NewRulesStore

func NewRulesStore(storage *session.Storage) (*RulesStore, error)

NewRulesStore creates a RulesStore backed by the given storage.

func (*RulesStore) All

func (s *RulesStore) All() []RuleSpec

All returns user rules as compiled Rules (source="user" only).

func (*RulesStore) BulkUpsert added in v1.35.0

func (s *RulesStore) BulkUpsert(ctx context.Context, specs []RuleSpec, overwriteDuplicates bool) BulkUpsertResult

BulkUpsert creates or updates multiple user rules in one transaction. If overwriteDuplicates is false, rules whose name already exists are skipped. If overwriteDuplicates is true, rules whose name already exists are updated. This method does NOT call rebuildClassifier -- that is RulesService's responsibility. Lock ordering: BulkUpsert holds s.mu.Lock for the full operation, then calls s.storage.UpsertRule while holding the lock. This is safe because storage is independent of the in-memory lock (no recursive locking).

func (*RulesStore) Delete

func (s *RulesStore) Delete(id string) error

Delete removes a user rule by ID. Returns error if not found or not a user rule.

func (*RulesStore) ToRules

func (s *RulesStore) ToRules() []classifier.Rule

ToRules converts specs to compiled Rules, skipping specs with invalid regex.

func (*RulesStore) Upsert

func (s *RulesStore) Upsert(spec RuleSpec) (RuleSpec, error)

Upsert creates or updates a user rule. Source must be "user". Returns the upserted spec.

func (*RulesStore) WatchAndReload

func (s *RulesStore) WatchAndReload(ctx context.Context)

WatchAndReload is now a no-op as we use shared DB.

type ScrollbackSequencer added in v1.35.0

type ScrollbackSequencer interface {
	CurrentSequence(sessionID string) uint64
}

ScrollbackSequencer is the minimal interface SessionService needs from ScrollbackManager. Exported so server/dependencies.go can use warren.Set to validate this wiring at startup.

type SearchService

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

SearchService handles all Claude history and full-text search RPC methods.

It owns the history cache and search engine state that were previously scattered across SessionService.

Concurrency model: atomic.Value (COW) + singleflight for the history cache; sync.Map for the per-path branch cache. No mutexes held across I/O.

func NewSearchService

func NewSearchService(
	searchEngine *search.SearchEngine,
	snippetGenerator *search.SnippetGenerator,
	historyCacheTTL time.Duration,
) *SearchService

NewSearchService creates a SearchService with the given search components.

func (*SearchService) GetClaudeHistoryDetail

GetClaudeHistoryDetail retrieves detailed information for a specific history entry, including lazily-fetched VCS status for the project directory.

VCS status reuses the same vc.VCSProvider + vcsStatusToProto path that GetVCSStatus uses for running sessions, so the logic is not duplicated.

func (*SearchService) GetClaudeHistoryMessages

GetClaudeHistoryMessages retrieves messages from a specific conversation.

func (*SearchService) ListClaudeHistory

ListClaudeHistory returns Claude session history entries with optional filtering and cursor-based pagination.

Pagination rules:

  • page_size controls how many entries are returned per page (default 100, max 500).
  • page_token, when set, resumes from the position after the last entry on the previous page. Leave it empty for the first page.
  • next_page_token in the response is non-empty when more pages exist; pass it as page_token in the next request.
  • The legacy limit field is honoured when page_size is zero.
  • Filters (project, search_query) must be identical across all pages of a paginated sequence.

func (*SearchService) SearchClaudeHistory

SearchClaudeHistory performs full-text search across Claude conversation history.

func (*SearchService) SetInstanceProvider added in v1.35.0

func (ss *SearchService) SetInstanceProvider(fn func() []*session.Instance)

SetInstanceProvider wires the live-instance provider after SessionService is fully constructed. Must be called before the first ListClaudeHistory.

func (*SearchService) SetResolveConversationUUID added in v1.37.0

func (ss *SearchService) SetResolveConversationUUID(fn func(ctx context.Context, tmuxUUID string) (string, error))

SetResolveConversationUUID wires the tmux-UUID → Claude-UUID resolver.

type SecretScanResult

type SecretScanResult struct {
	Found       bool
	PatternName string // name of the first matching pattern
}

SecretScanResult holds the result of scanning for secrets.

func ScanForSecrets

func ScanForSecrets(text string) SecretScanResult

ScanForSecrets checks text for known secret patterns. Returns the first match found, or an empty result if none. Only the first 4096 bytes are scanned to bound performance on very long commands.

type ServerInfo

type ServerInfo struct {
	PID           int    `json:"pid"`
	UptimeSeconds int64  `json:"uptime_seconds"`
	GoVersion     string `json:"go_version"`
	OS            string `json:"os"`
	Arch          string `json:"arch"`
}

ServerInfo contains runtime metadata for the server process.

type SessionCreator added in v1.35.0

type SessionCreator interface {
	CreateDirectorySession(ctx context.Context, title, path, prompt string, tags []string, oneShot bool, hidden bool) (*session.Instance, error)
	// CreateWorktreeSession spawns a session inside an already-created git worktree at
	// worktreePath. repoPath is the parent repo used for program resolution; worktreePath
	// must already exist on disk before this is called.
	CreateWorktreeSession(ctx context.Context, title, repoPath, worktreePath, prompt string, tags []string, oneShot bool, hidden bool) (*session.Instance, error)
}

SessionCreator allows BacklogService to spawn sessions without importing handler internals.

type SessionImageUploadHandler added in v1.35.0

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

SessionImageUploadHandler saves uploaded files to a session's uploads/ directory and returns the absolute path so the terminal process can reference the file.

func NewSessionImageUploadHandler added in v1.35.0

func NewSessionImageUploadHandler(storage session.InstanceStore, finder instanceFinder) *SessionImageUploadHandler

NewSessionImageUploadHandler creates a new handler backed by the given InstanceStore. Pass the ReviewQueuePoller as finder to avoid the LoadInstances() restart side-effect. The handler accepts any file type (not limited to images) up to 10 MB.

func (*SessionImageUploadHandler) HandleUpload added in v1.35.0

func (h *SessionImageUploadHandler) HandleUpload(w http.ResponseWriter, r *http.Request)

+http: POST /api/v1/upload-image upload:file HandleUpload processes a multipart/form-data POST with fields "session_id" and "file", saves the file to <session_path>/uploads/ and returns the absolute path as JSON. Any file type is accepted (not limited to images); the per-file size cap is 10 MB.

type SessionRetentionSweeper added in v1.41.0

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

SessionRetentionSweeper periodically deletes archived sessions that have passed the configured retention window, reusing SessionService.DeleteSession's existing tmux/worktree/DB cleanup path so deletion logic lives in exactly one place.

Scope (deliberate, see project_plans/session-retention-cleanup): only sessions with ArchivedAt set are considered. Sessions that reached a terminal Stopped status without ever being archived are NOT swept by this pass — there is no reliable "how long has this been stopped" timestamp to anchor a retention window on for that case (UpdatedAt is touched by more than just the stop transition). Follow-up, not built here.

func NewSessionRetentionSweeper added in v1.41.0

func NewSessionRetentionSweeper(storage *session.Storage, cfg *config.Config, svc *SessionService) *SessionRetentionSweeper

NewSessionRetentionSweeper constructs a sweeper. svc is used to perform the actual deletion via its existing DeleteSession RPC handler, so tmux/worktree/DB cleanup logic is not duplicated here.

func (*SessionRetentionSweeper) Start added in v1.41.0

func (s *SessionRetentionSweeper) Start(ctx context.Context)

Start runs the periodic sweep loop. Blocks until ctx is cancelled.

type SessionService

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

SessionService implements the SessionServiceHandler interface for ConnectRPC.

func NewSessionService

func NewSessionService(storage session.InstanceStore, eventBus *events.EventBus) *SessionService

NewSessionService creates a new SessionService with the given storage and event bus, using a disk-backed search engine (or an in-memory one under config.IsTestMode(), to keep the ~78 existing test call sites working without change — see NewSessionServiceWithSearchEngine's doc comment for why IsTestMode() is only the default, not the seam itself). NOTE: Instances are NOT loaded here to prevent double-loading and initialization timing issues. Instances will be loaded in server.go after dependencies (statusManager, reviewQueue) are wired.

func NewSessionServiceFromConfig

func NewSessionServiceFromConfig() (*SessionService, error)

NewSessionServiceFromConfig creates a SessionService using EntRepository as storage backend. On first startup, if the legacy state.json exists and Ent DB is empty, sessions are auto-migrated from JSON to Ent.

func NewSessionServiceWithEntClient added in v1.35.0

func NewSessionServiceWithEntClient(entClient *ent.Client) (*SessionService, error)

NewSessionServiceWithEntClient creates a SessionService from a pre-existing *ent.Client. Use this when the caller already opened a database (e.g. in tests or when sharing a connection) and wants to bypass the config-based path discovery in NewSessionServiceFromConfig.

func NewSessionServiceWithSearchEngine added in v1.42.0

func NewSessionServiceWithSearchEngine(storage session.InstanceStore, eventBus *events.EventBus, searchEngine *search.SearchEngine) *SessionService

NewSessionServiceWithSearchEngine is the dependency-injection seam for the search engine: pass an explicit *search.SearchEngine (e.g. search.NewSearchEngine() for in-memory) instead of relying on NewSessionService's config.IsTestMode() default. Full migration of the ~78 existing NewSessionService(storage, eventBus) call sites to explicit injection is a separate, larger mechanical refactor — out of scope here; this seam exists so new or updated tests can opt in without waiting on that migration.

func (*SessionService) AcknowledgeError added in v1.35.0

+api: errors:acknowledge AcknowledgeError marks a persisted error event as acknowledged.

func (*SessionService) AcknowledgeSession

AcknowledgeSession marks a session as acknowledged in the review queue. The session won't reappear in the queue until it receives an update.

func (*SessionService) ArchiveSession added in v1.35.0

+api: session:archive ArchiveSession soft-archives a session by setting archived_at. Archived sessions are excluded from the default ListSessions response.

func (*SessionService) ArchiveSessionByUUID added in v1.39.0

func (s *SessionService) ArchiveSessionByUUID(ctx context.Context, sessionUUID string) error

ArchiveSessionByUUID satisfies the BacklogService.SessionStopper interface and the session.SessionArchiver interface (implemented here so both BacklogService and session.BacklogLifecycleListener can soft-archive backlog work sessions without reinventing the ArchiveSession RPC's logic — see ArchiveSession above). No-op (not an error) if the session isn't tracked live or is already archived, so callers can invoke this unconditionally from a sweep without extra existence checks.

func (*SessionService) ArchiveWorkflowSessions added in v1.35.0

+api: session:archive-workflow-sessions ArchiveWorkflowSessions delegates to WorkflowService.

func (*SessionService) AssignSessionsToProject added in v1.23.0

+api: project:assign-sessions AssignSessionsToProject assigns one or more sessions to a project.

func (*SessionService) BatchCreateSessions added in v1.23.0

+api: session:batch-create BatchCreateSessions creates multiple sessions with bounded concurrency (max 3) and per-repo serialization to prevent git worktree races.

func (*SessionService) BulkUpsertRules added in v1.35.0

BulkUpsertRules creates or updates multiple user-defined rules in one call.

func (*SessionService) ClearConversationState added in v1.35.0

ClearConversationState removes the stored Claude conversation UUID from a session so that the next Resume starts a fresh conversation instead of attempting --resume with a stale or path-mismatched UUID.

func (*SessionService) ClearNotificationHistory

ClearNotificationHistory removes notifications from the history.

func (*SessionService) ClosePR

ClosePR closes the PR without merging for a session.

func (*SessionService) CreateCheckpoint

CreateCheckpoint captures the current state of a session as a named bookmark.

func (*SessionService) CreateDebugSnapshot

CreateDebugSnapshot captures diagnostic information and writes a JSON file to the log directory.

func (*SessionService) CreateDirectorySession added in v1.35.0

func (s *SessionService) CreateDirectorySession(ctx context.Context, title, path, prompt string, tags []string, oneShot bool, hidden bool) (*session.Instance, error)

CreateDirectorySession satisfies the services.SessionCreator interface so that BacklogService can spawn sessions without importing SessionService directly. It creates a directory-type session with the given title, path, initial prompt, tags, and oneShot flag, wires it into the live poller, and returns the Instance.

func (*SessionService) CreateProject added in v1.23.0

+api: project:create CreateProject creates a new project for grouping sessions.

func (*SessionService) CreateSession

CreateSession initializes a new AI agent session with tmux and git worktree. +api: session:create

func (*SessionService) CreateWorkflow added in v1.35.0

+api: workflow:create CreateWorkflow delegates to WorkflowService.

func (*SessionService) CreateWorktreeSession added in v1.37.0

func (s *SessionService) CreateWorktreeSession(ctx context.Context, title, repoPath, worktreePath, prompt string, tags []string, oneShot bool, hidden bool) (*session.Instance, error)

CreateWorktreeSession satisfies the services.SessionCreator interface. It spawns a session that uses an already-created git worktree at worktreePath. repoPath is the parent repo (for program resolution). worktreePath must exist on disk.

func (*SessionService) DeleteAlias added in v1.35.0

DeleteAlias removes an alias preset by name.

func (*SessionService) DeleteApprovalRule

DeleteApprovalRule removes a user-defined auto-approval rule by ID.

func (*SessionService) DeleteDirectoryRule added in v1.12.0

DeleteDirectoryRule removes a directory rule by path.

func (*SessionService) DeleteProfile added in v1.12.0

DeleteProfile removes a named profile by name.

func (*SessionService) DeleteProject added in v1.23.0

+api: project:delete DeleteProject removes a project (sessions are unassigned, not deleted).

func (*SessionService) DeletePromptHistory added in v1.23.0

+api: session:delete-prompt-history DeletePromptHistory removes a saved prompt from history.

func (*SessionService) DeleteSession

DeleteSession stops and removes a session, cleaning up resources. +api: session:delete

func (*SessionService) DeleteShell added in v1.35.0

DeleteShell stops a shell and removes it from storage.

func (*SessionService) DeleteWorkflow added in v1.35.0

+api: workflow:delete DeleteWorkflow delegates to WorkflowService.

func (*SessionService) DeleteWorkflowFailedSessions added in v1.35.0

+api: session:delete-workflow-failed-sessions DeleteWorkflowFailedSessions delegates to WorkflowService.

func (*SessionService) ExportRules added in v1.35.0

ExportRules serializes user-authored rules to YAML format for download.

func (*SessionService) FindLiveInstance added in v1.18.0

func (s *SessionService) FindLiveInstance(id string) *session.Instance

FindLiveInstance returns the live in-memory instance held by the ReviewQueuePoller, or nil if the poller is not wired or the session is not found. Use this instead of LoadInstances() for read-only and mutation operations that need the live instance (with its PTY handles and controller state).

func (*SessionService) FocusWindow

FocusWindow activates a window for the specified application.

func (*SessionService) ForkSession

ForkSession creates a new independent session branched from a checkpoint on an existing session.

func (*SessionService) GenerateSuggestedRule added in v1.35.0

GenerateSuggestedRule asks an AI to propose new auto-approval rules.

func (*SessionService) GetAnalyticsStore

func (s *SessionService) GetAnalyticsStore() *AnalyticsStore

GetAnalyticsStore returns the analytics store for wiring up the ApprovalHandler.

func (*SessionService) GetApprovalAnalytics

GetApprovalAnalytics returns aggregated analytics for classification decisions.

func (*SessionService) GetApprovalStore

func (s *SessionService) GetApprovalStore() *ApprovalStore

GetApprovalStore returns the approval store for wiring up the HTTP hook handler.

func (*SessionService) GetBacklogLifecycleListener added in v1.38.0

func (s *SessionService) GetBacklogLifecycleListener() *session.BacklogLifecycleListener

GetBacklogLifecycleListener returns the wired BacklogLifecycleListener (nil if SetBacklogLifecycleListener was never called). Exported for the pointer-equality integration test proving BacklogService and BacklogLifecycleListener share a single PipelineEngine instance (Story 1.5.1) — see server/dependencies_test.go.

func (*SessionService) GetCallbackConfig added in v1.43.0

+api: callback-config:get GetCallbackConfig reports which outbound-callback URLs are configured.

func (*SessionService) GetClassifier

func (s *SessionService) GetClassifier() *classifier.RuleBasedClassifier

GetClassifier returns the rule-based classifier for wiring up the ApprovalHandler.

func (*SessionService) GetClaudeConfig

GetClaudeConfig retrieves a Claude configuration file by name.

func (*SessionService) GetClaudeHistoryDetail

GetClaudeHistoryDetail retrieves detailed information for a specific history entry.

func (*SessionService) GetClaudeHistoryMessages

GetClaudeHistoryMessages retrieves messages from a specific conversation.

func (*SessionService) GetConfigFileRules added in v1.35.0

GetConfigFileRules delegates to RulesService.

func (*SessionService) GetCurrentDatabase

GetCurrentDatabase returns metadata for the currently active workspace database.

func (*SessionService) GetDetectionEvents added in v1.35.0

GetDetectionEvents returns recent status-detection events for a session's Claude controller. Used by the debug panel (FR-8) — returns an empty list when the session has no active controller.

func (*SessionService) GetEscapeAnalyticsGlobalSummary added in v1.42.0

GetEscapeAnalyticsGlobalSummary returns aggregate escape sequence statistics across all sessions, plus a per-session breakdown to spot outliers. +api: analytics:get-escape-global-summary

func (*SessionService) GetEscapeAnalyticsSummary added in v1.35.0

GetEscapeAnalyticsSummary returns aggregate escape sequence statistics for a session. +api: escape:summary

func (*SessionService) GetEventBus

func (s *SessionService) GetEventBus() *events.EventBus

GetEventBus returns the event bus instance for wiring up reactive components.

func (*SessionService) GetFeatureFlags added in v1.35.0

+api: feature-flags:list GetFeatureFlags returns all known feature flags and their current state.

func (*SessionService) GetFileContent added in v1.11.0

GetFileContent retrieves the text content of a file in a session's worktree.

func (*SessionService) GetFileService added in v1.35.0

func (s *SessionService) GetFileService() *FileService

GetFileService returns the underlying FileService so callers can register additional HTTP handlers (e.g. the raw file download endpoint).

func (*SessionService) GetHookStatus added in v1.35.0

+api: hooks:status GetHookStatus reports whether the global Claude Code hooks are installed and whether the binaries needed to install them are available.

func (*SessionService) GetInstanceStore added in v1.1.0

func (s *SessionService) GetInstanceStore() session.InstanceStore

GetInstanceStore returns the InstanceStore interface, suitable for both production and test code.

func (*SessionService) GetInstances added in v1.35.0

func (s *SessionService) GetInstances() []*session.Instance

GetInstances returns all managed (poller-tracked) live instances, satisfying InstancePoller.

func (*SessionService) GetLauncherPresets added in v1.42.0

GetLauncherPresets returns the hand-authored launcher presets, freshly read on every call.

func (*SessionService) GetLogs

GetLogs retrieves application logs with optional filtering and search.

func (*SessionService) GetNotificationHistory

GetNotificationHistory returns persisted notification history with optional filtering.

func (*SessionService) GetNotificationStore

func (s *SessionService) GetNotificationStore() *notifications.NotificationHistoryStore

GetNotificationStore returns the notification history store.

func (*SessionService) GetPRComments

GetPRComments retrieves all comments on the PR for a session.

func (*SessionService) GetPRInfo

GetPRInfo retrieves the latest PR information for a session.

func (*SessionService) GetProgramAnalytics added in v1.35.0

GetProgramAnalytics returns drill-down analytics for a single command program.

func (*SessionService) GetProviderLimits added in v1.35.0

GetProviderLimits returns the rate limit and usage details for a session.

func (*SessionService) GetReviewQueue

GetReviewQueue returns sessions needing user attention with priority ordering.

func (*SessionService) GetReviewQueueInstance

func (s *SessionService) GetReviewQueueInstance() *session.ReviewQueue

GetReviewQueueInstance returns the review queue instance for wiring up reactive components.

func (*SessionService) GetSession

GetSession retrieves a specific session by ID (Title).

func (*SessionService) GetSessionDefaults added in v1.12.0

GetSessionDefaults returns the full session defaults configuration.

func (*SessionService) GetSessionDiff

GetSessionDiff retrieves the current git diff for a session.

func (*SessionService) GetSlackConfig added in v1.44.0

GetSlackConfig returns the current Slack notification configuration.

func (*SessionService) GetStorage

func (s *SessionService) GetStorage() *session.Storage

GetStorage returns the concrete *session.Storage for components that haven't migrated to InstanceStore yet. Returns nil when SessionService was constructed with a fake InstanceStore (e.g., in unit tests). Prefer using the session.InstanceStore interface via GetInstanceStore() for new code.

func (*SessionService) GetTerminalSnapshot added in v1.14.0

GetTerminalSnapshot returns the last N lines of terminal output for a session. Uses inst.Preview() for a read-only snapshot without requiring an active stream.

func (*SessionService) GetVCSStatus

GetVCSStatus retrieves the current version control status for a session.

func (*SessionService) GetWorkspaceInfo

GetWorkspaceInfo retrieves VCS and workspace information for a session.

func (*SessionService) HibernateSession added in v1.35.0

HibernateSession checkpoints the session state, kills the AI process, and transitions the session to Hibernated status. +api: session:hibernate

func (*SessionService) InstallHooks added in v1.35.0

+api: hooks:install InstallHooks installs the requested global hooks into Claude, Antigravity, and Gemini settings. A requested hook whose binary is unavailable is reported in messages with a manual fallback rather than failing the whole call.

func (*SessionService) IsSessionLive added in v1.35.0

func (s *SessionService) IsSessionLive(sessionUUID string) bool

IsSessionLive satisfies the BacklogService.SessionStopper interface. It returns true if the session UUID is currently tracked in the live in-memory poller.

func (*SessionService) KillTmuxPaneOnly added in v1.39.0

func (s *SessionService) KillTmuxPaneOnly(ctx context.Context, sessionUUID string) error

KillTmuxPaneOnly satisfies the BacklogService.SessionStopper interface. It closes the tmux pane only (Instance.KillSession), leaving the worktree intact — unlike StopSessionByUUID (Instance.Kill/Destroy), which also runs CleanupWorktree and would delete a worktree still in use by the next rework round. Best-effort: errors are logged, not returned, since this runs as cleanup alongside a new spawn that should proceed regardless.

func (*SessionService) KillTmuxSessionByTitle added in v1.35.0

func (s *SessionService) KillTmuxSessionByTitle(ctx context.Context, title string) error

KillTmuxSessionByTitle satisfies the BacklogService.SessionStopper interface. It kills the tmux session whose name is derived from title using the same sanitization as initTmuxSession (whitespace stripped, "." and ":" replaced with "_", "staplersquad_" prefix). This handles the case where the Instance is no longer tracked in memory but the underlying tmux session is still alive.

func (*SessionService) ListAliases added in v1.35.0

ListAliases returns all configured alias presets.

func (*SessionService) ListApprovalRules

ListApprovalRules returns all auto-approval rules (user, seed, and claude-settings).

func (*SessionService) ListBranches added in v1.17.0

ListBranches returns the git branches for a given repository path. Results are cached per repo path with a 5-minute TTL. ADR-002. Delegates to WorkspaceService (Story 1.4).

func (*SessionService) ListCheckpoints

ListCheckpoints returns all checkpoints for the specified session.

func (*SessionService) ListClaudeConfigs

ListClaudeConfigs returns all configuration files in the ~/.claude directory.

func (*SessionService) ListClaudeHistory

ListClaudeHistory returns Claude session history entries with optional filtering.

func (*SessionService) ListDatabases

ListDatabases returns all discovered workspace databases with metadata.

func (*SessionService) ListErrors added in v1.35.0

+api: errors:list ListErrors returns persisted RPC error events from SQLite, ordered by last_seen desc.

func (*SessionService) ListFiles added in v1.11.0

ListFiles returns the immediate children of a directory in a session's worktree.

func (*SessionService) ListPathCompletions added in v1.10.0

ListPathCompletions returns filesystem entries matching the given path prefix.

func (*SessionService) ListPendingApprovals

ListPendingApprovals returns all pending Claude Code tool approval requests.

func (*SessionService) ListProjects added in v1.23.0

+api: project:list ListProjects returns all projects.

func (*SessionService) ListPromptHistory added in v1.23.0

+api: session:list-prompt-history ListPromptHistory returns saved prompt history entries.

func (*SessionService) ListSessions

ListSessions returns all sessions with optional filtering. This includes both managed sessions and external mux-enabled sessions. +api: session:list

func (*SessionService) ListShells added in v1.35.0

ListShells returns all custom shells for a session, sorted by order_index.

func (*SessionService) ListSlashCommands added in v1.35.0

func (*SessionService) ListTriggerFireEvents added in v1.43.0

+api: workflow:list-trigger-fire-events ListTriggerFireEvents delegates to WorkflowService.

func (*SessionService) ListWorkflows added in v1.35.0

+api: workflow:list ListWorkflows delegates to WorkflowService.

func (*SessionService) ListWorkspaceTargets

ListWorkspaceTargets returns available switch targets for a session. +api: workspace:list-targets

func (*SessionService) ListWorktrees added in v1.12.0

ListWorktrees returns the git worktrees for a given repository path.

func (*SessionService) LogClientEvents added in v1.35.0

LogClientEvents receives batched browser console log entries from the web UI. Used for remote debugging of mobile browser sessions where DevTools are unavailable. Never returns an error — malformed or oversized entries are silently discarded.

func (*SessionService) LogUserInteraction

LogUserInteraction logs a user interaction event for audit trail and analytics.

func (*SessionService) MarkNotificationRead

MarkNotificationRead marks specific notifications as read.

func (*SessionService) MergeDatabase

MergeDatabase copies sessions from a source workspace into the current database.

func (*SessionService) MergePR

MergePR merges the PR for a session using the specified merge method.

func (*SessionService) PostPRComment

PostPRComment posts a new comment to the PR for a session.

func (*SessionService) PreviewDestinationPath added in v1.41.0

PreviewDestinationPath computes where a session's checkout/worktree would land without performing any git or filesystem mutation. Used by the Omnibar to show a live destination hint before the user submits session creation. +api: session:preview-destination-path

func (*SessionService) QueryEscapeAnalytics added in v1.35.0

QueryEscapeAnalytics returns paginated escape event records for a session. +api: escape:query

func (*SessionService) ReapPausedTmuxSessions added in v1.35.0

func (s *SessionService) ReapPausedTmuxSessions()

ReapPausedTmuxSessions kills any tmux session that is still running for a paused Instance. This is a safety net for sessions paused before the kill-on-pause change, or for cases where the initial kill attempt fell back to detach.

func (*SessionService) RemoveFromAllPollers added in v1.18.0

func (s *SessionService) RemoveFromAllPollers(id string)

RemoveFromAllPollers is the exported version for use by MCP tools and other callers outside the services package that need to clean up after deletion.

func (*SessionService) RenameSession

RenameSession changes the title of an existing session. Validates that the new title doesn't conflict with existing sessions.

func (*SessionService) ResolveApproval

ResolveApproval allows the web UI to approve or deny a pending Claude Code tool use request.

func (*SessionService) ResolveDefaults added in v1.12.0

ResolveDefaults merges all default layers for the given working directory and profile.

func (*SessionService) RestartSession

RestartSession restarts a session by killing and recreating the tmux session. Optionally preserves terminal output for debugging purposes.

func (*SessionService) RestartShell added in v1.35.0

RestartShell stops a shell (if running) and relaunches it with the same command.

func (*SessionService) ResumeCrashedSession added in v1.41.0

ResumeCrashedSession re-launches the AI process for a Crashed session (dead tmux pane detected by SessionHealthChecker, session/health.go), transitioning it back to Active status. The tmux session was already killed when the instance was marked Crashed, so Start(false) takes the cold-restore path and threads --resume automatically when a conversation UUID is known. +api: session:resume_crashed

func (*SessionService) ResumeHibernatedSession added in v1.35.0

ResumeHibernatedSession re-launches the AI process for a Hibernated session, transitioning it back to Active status. +api: session:resume_hibernated

func (*SessionService) RunOneShot added in v1.23.0

+api: session:run-one-shot RunOneShot executes `claude -p <prompt>` in the session's worktree and returns the combined output along with an extracted PR URL and branch divergence status.

func (*SessionService) RunOneShotForSession added in v1.39.0

func (s *SessionService) RunOneShotForSession(ctx context.Context, sessionID, prompt string, timeoutSeconds int32) (string, error)

RunOneShotForSession runs a one-shot prompt against a session's worktree without the ConnectRPC request/response wrapper, for automation callers. It reuses RunOneShot's exact logic (same PR-URL extraction, same PR persistence) so automated and manual PR creation share one code path — currently used by the opt-in AutoCreatePR review-queue policy (server.ReactiveQueueManager). Returns the extracted PR URL, or an error if the prompt failed.

func (*SessionService) RunWorkflow added in v1.35.0

+api: workflow:run RunWorkflow delegates to WorkflowService.

func (*SessionService) SaveRulesToConfigFile added in v1.35.0

SaveRulesToConfigFile delegates to RulesService.

func (*SessionService) SearchClaudeHistory

SearchClaudeHistory performs full-text search across Claude conversation history. +api: history:search

func (*SessionService) SearchFiles added in v1.12.0

SearchFiles performs a recursive name-substring search in a session's worktree.

func (*SessionService) SendNotification

SendNotification allows tmux sessions and external Claude processes to send notifications.

func (*SessionService) SetAnalyticsClient added in v1.35.0

func (s *SessionService) SetAnalyticsClient(c *ent.Client)

SetAnalyticsClient wires the ent client used for escape analytics queries. Must be called before the first QueryEscapeAnalytics or GetEscapeAnalyticsSummary RPC.

func (*SessionService) SetAutonomousStuckRespawner added in v1.39.0

func (s *SessionService) SetAutonomousStuckRespawner(r AutonomousStuckRespawner)

SetAutonomousStuckRespawner wires the respawner into the autonomous orchestration service so a turn-cap-stopped work session gets a fresh turn budget instead of being forced into review.

func (*SessionService) SetBacklogLifecycleListener added in v1.35.0

func (s *SessionService) SetBacklogLifecycleListener(l *session.BacklogLifecycleListener)

SetBacklogLifecycleListener wires the listener to all sessions created via CreateDirectorySession so that backlog state transitions fire on session exit.

func (*SessionService) SetConfigService

func (s *SessionService) SetConfigService(svc *ConfigService)

SetConfigService wires the ConfigService for delegating config RPCs.

func (*SessionService) SetErrorRegistry added in v1.35.0

func (s *SessionService) SetErrorRegistry(r *ErrorRegistry)

SetErrorRegistry wires the ErrorRegistry so the service can expose ListErrors and AcknowledgeError RPCs. Must be called before the first RPC request.

func (*SessionService) SetExternalDiscovery

func (s *SessionService) SetExternalDiscovery(discovery *session.ExternalSessionDiscovery)

SetExternalDiscovery sets the external session discovery for accessing mux-enabled sessions.

func (*SessionService) SetFeatureController added in v1.35.0

func (s *SessionService) SetFeatureController(name string, c FeatureController)

SetFeatureController wires a runtime controller for the named feature flag. Delegates to FeatureFlagService which owns the controller registry.

func (*SessionService) SetHeadlessPool added in v1.35.0

func (s *SessionService) SetHeadlessPool(pool *headless.Pool)

SetHeadlessPool wires the headless LLM pool for use by RunOneShot and other AI features.

func (*SessionService) SetHistoryLinker added in v1.23.1

func (s *SessionService) SetHistoryLinker(hl *session.HistoryLinker)

SetHistoryLinker wires the HistoryLinker so deleted sessions are also removed from it and cannot be re-persisted by the shutdown hook.

func (*SessionService) SetLifecycleContext added in v1.35.0

func (s *SessionService) SetLifecycleContext(ctx context.Context)

SetLifecycleContext binds the server's root context to the service. Must be called once during server startup, before any sessions are created.

func (*SessionService) SetMCPServerURL added in v1.17.0

func (s *SessionService) SetMCPServerURL(fn func() string)

SetMCPServerURL configures a lazily-invoked provider for the HTTP MCP endpoint URL passed to new sessions. Unlike a stored string, fn is called fresh at each point of use, so it can be wired up during server construction (before the listener has bound a real address) and still always observe the real bound address once Start() has resolved it, even under PORT=0.

func (*SessionService) SetMemoryCacheReader added in v1.35.0

func (s *SessionService) SetMemoryCacheReader(r session.MemoryCacheReader)

SetMemoryCacheReader wires the HibernationSweeper so that ListSessions can populate memory_rss_mb, estimated_savings_mb, and system_memory_pct fields.

func (*SessionService) SetNotificationStore

func (s *SessionService) SetNotificationStore(store *notifications.NotificationHistoryStore)

SetNotificationStore sets the notification history store for the notification history RPCs and wires it into the approval service so resolved approvals are stamped with their decision.

func (*SessionService) SetOnGlobalDefaultsUpdated added in v1.41.0

func (s *SessionService) SetOnGlobalDefaultsUpdated(fn func())

SetOnGlobalDefaultsUpdated wires in the callback invoked after every successful UpdateGlobalDefaults save (server/dependencies.go uses this to trigger an immediate backlog-queue dequeue sweep when the concurrency limit is raised).

func (*SessionService) SetQuotaGate added in v1.42.0

func (s *SessionService) SetQuotaGate(g *QuotaGate)

SetQuotaGate wires the account-wide quota gate so onRateLimitDetected can feed it the hard/reactive override signal.

func (*SessionService) SetReactiveQueueManager

func (s *SessionService) SetReactiveQueueManager(mgr ReactiveQueueManager)

SetReactiveQueueManager sets the ReactiveQueueManager (dependency injection). This must be called before WatchReviewQueue is used.

func (*SessionService) SetRegistry added in v1.35.0

func (s *SessionService) SetRegistry(r *session.Registry)

SetRegistry wires the Registry into this service. Called during server startup after the Registry is constructed in BuildServiceDeps.

func (*SessionService) SetResolveConversationUUID added in v1.37.0

func (s *SessionService) SetResolveConversationUUID(fn func(ctx context.Context, tmuxUUID string) (string, error))

SetResolveConversationUUID wires the tmux-UUID → Claude-UUID resolver into the search service.

func (*SessionService) SetReviewGateTrigger added in v1.37.0

func (s *SessionService) SetReviewGateTrigger(t ReviewGateTrigger)

SetReviewGateTrigger wires the review gate trigger into the autonomous orchestration service so that completed work sessions immediately kick off headless review.

func (*SessionService) SetReviewQueuePoller

func (s *SessionService) SetReviewQueuePoller(poller *session.ReviewQueuePoller)

SetReviewQueuePoller wires the ReviewQueuePoller so new/deleted sessions are added/removed from the poller and AcknowledgeSession updates poller references. Must be called during server startup before any session mutation RPCs are used.

func (*SessionService) SetScrollbackManager

func (s *SessionService) SetScrollbackManager(mgr ScrollbackSequencer)

SetScrollbackManager wires a scrollback sequence provider for checkpoint creation.

func (*SessionService) SetSessionSummaryGenerator added in v1.41.0

func (s *SessionService) SetSessionSummaryGenerator(g *session.SessionSummaryGenerator)

SetSessionSummaryGenerator wires the generator to all sessions created via CreateSession/CreateDirectorySession/CreateWorktreeSession after this call, mirroring SetBacklogLifecycleListener's wiring pattern (see the WireToInstance call sites alongside session.WireSessionSummaryListener below).

func (*SessionService) SetSharedBacklogConfig added in v1.41.0

func (s *SessionService) SetSharedBacklogConfig(cfg *config.Config, mu *sync.RWMutex)

SetSharedBacklogConfig wires the *config.Config instance (and its guarding mutex) BacklogService reads its concurrency fields from into this SessionService's DefaultsService, so UpdateGlobalDefaults can propagate a Settings change into BacklogService's live view without a process restart (PR #199 review F1). See DefaultsService.SetSharedBacklogConfig.

func (*SessionService) SetSharedCallbackConfig added in v1.43.0

func (s *SessionService) SetSharedCallbackConfig(cfg *config.Config, mu *sync.RWMutex)

SetSharedCallbackConfig wires the *config.Config instance (and its guarding mutex) CallbackDispatcher reads callback URLs from into this SessionService's CallbackConfigService, so UpdateCallbackConfig can propagate a saved URL into CallbackDispatcher's live view without a process restart. See CallbackConfigService.SetSharedCallbackConfig.

func (*SessionService) SetSlackNotifier added in v1.44.0

func (s *SessionService) SetSlackNotifier(n *SlackNotifier)

SetSlackNotifier rewires slackConfigSvc onto the given SlackNotifier instance — intended for server/dependencies.go to call with the SAME *services.SlackNotifier wired into ReactiveQueueManager/ApprovalHandler, so GetSlackConfig's last_delivery reflects real production sends from those trigger points, not only sends made via TestSlackWebhook. Optional: if never called, slackConfigSvc keeps the private SlackNotifier instance NewSessionService constructed for it.

func (*SessionService) SetStatusDetailProvider added in v1.42.0

func (s *SessionService) SetStatusDetailProvider(name string, fn func() string)

SetStatusDetailProvider wires an optional status-detail provider for the named feature flag. Delegates to FeatureFlagService.

func (*SessionService) SetStatusManager

func (s *SessionService) SetStatusManager(mgr *session.InstanceStatusManager)

SetStatusManager wires the InstanceStatusManager so that instances loaded via loadInstancesWithWiring (e.g., fallback path in ListSessions) receive status tracking. Must be called during server startup.

func (*SessionService) SetTmuxStreamerManager added in v1.41.0

func (s *SessionService) SetTmuxStreamerManager(mgr *session.ExternalTmuxStreamerManager)

SetTmuxStreamerManager wires the shared ExternalTmuxStreamerManager so StopShell can evict a shell's streamer when the shell closes. Must be called during server startup with the same instance passed to NewConnectRPCWebSocketHandler.

func (*SessionService) SetTokenStoreReader added in v1.35.0

func (s *SessionService) SetTokenStoreReader(store tokens.TokenStoreReader)

SetTokenStoreReader wires the global parsed token store into the capacity monitor.

func (*SessionService) SetUserPRCache added in v1.41.0

func (s *SessionService) SetUserPRCache(cache *githubpkg.UserPRCache)

SetUserPRCache wires the shared UserPRCache so CreateSession's GitHub URL detection recognizes enterprise hosts from dynamically-added accounts, not just hosts with a statically configured OAuth App in config.json.

func (*SessionService) SetWorkflowRepository added in v1.35.0

func (s *SessionService) SetWorkflowRepository(repo session.WorkflowRepository)

SetWorkflowRepository injects the workflow repository used to populate the meta cache. Must be called after both SessionService and WorkflowRepository are constructed.

func (*SessionService) SetWorkflowService added in v1.35.0

func (s *SessionService) SetWorkflowService(svc *WorkflowService)

SetWorkflowService injects the workflow sub-service using deferred setter injection. Must be called after both SessionService and WorkflowService are constructed.

func (*SessionService) Shutdown added in v1.41.0

func (s *SessionService) Shutdown()

Shutdown stops background goroutines owned by SessionService (currently the AnalyticsStore flush loop started in NewSessionService). Idempotent — safe to call multiple times (AnalyticsStore.Stop is itself sync.Once-guarded).

func (*SessionService) SlackNotifierForTest added in v1.44.0

func (s *SessionService) SlackNotifierForTest() *SlackNotifier

SlackNotifierForTest returns the SlackNotifier instance currently wired into slackConfigSvc. Exported only so cross-package wiring regression tests (server package) can assert pointer identity against the other consumers (ReactiveQueueManager, ApprovalHandler) without restructuring production code — not intended for any non-test caller.

func (*SessionService) SpawnReviewSession added in v1.35.0

func (s *SessionService) SpawnReviewSession(ctx context.Context, item *session.BacklogItemData, itemSessionID string, prompt string) (*session.Instance, error)

SpawnReviewSession satisfies the session.ReviewGateSpawner interface so that BacklogLifecycleListener can spawn one-shot review sessions automatically when a work session exits. The session is tagged "backlog:review" and runs one-shot.

func (*SessionService) SpawnShell added in v1.35.0

SpawnShell creates and starts a new custom shell attached to a session. +api: SpawnShell

func (*SessionService) StartAutonomousDriverForInstance added in v1.35.0

func (s *SessionService) StartAutonomousDriverForInstance(inst *session.Instance)

StartAutonomousDriverForInstance satisfies the AutonomousDriverStarter interface. Delegates to the autonomous orchestration service.

func (*SessionService) StartAutonomousDriverWithTimeout added in v1.35.0

func (s *SessionService) StartAutonomousDriverWithTimeout(inst *session.Instance, startupTimeout time.Duration)

StartAutonomousDriverWithTimeout is like StartAutonomousDriverForInstance but uses a configurable startup timeout. Delegates to the autonomous orchestration service.

func (*SessionService) StopDriverForSession added in v1.35.0

func (s *SessionService) StopDriverForSession(sessionTitle string)

StopDriverForSession stops the AutonomousDriver registered under sessionTitle. Used by MCP handlers as a belt-and-suspenders stop after task completion. Satisfies mcp.ReviewCompletionSignaler.

func (*SessionService) StopSessionByUUID added in v1.35.0

func (s *SessionService) StopSessionByUUID(ctx context.Context, sessionUUID string) error

StopSessionByUUID satisfies the BacklogService.SessionStopper interface. It kills the live tmux session identified by UUID (best-effort; errors are non-fatal).

func (*SessionService) StopShell added in v1.35.0

StopShell stops a running custom shell.

func (*SessionService) StreamTerminal

StreamTerminal provides bidirectional streaming for terminal I/O. Implements bidirectional streaming where: - Client sends: terminal input and resize events - Server sends: raw terminal output

NOTE: browser clients never reach this method directly — the WebSocket handler (connectrpc_websocket.go) intercepts StreamTerminal calls made over its custom websocket transport before they reach here. This handler exists to satisfy the ConnectRPC service interface and could be used by non-browser gRPC/Connect clients.

func (*SessionService) SwitchDatabase

SwitchDatabase switches to a different workspace database and restarts the server.

func (*SessionService) SwitchWorkspace

SwitchWorkspace switches a session's workspace to a different branch, revision, or worktree. +api: workspace:switch

func (*SessionService) TestSlackWebhook added in v1.44.0

TestSlackWebhook sends a synchronous test message and reports the outcome.

func (*SessionService) TimeSinceLastMeaningfulOutput added in v1.41.0

func (s *SessionService) TimeSinceLastMeaningfulOutput(sessionUUID string) (time.Duration, bool)

TimeSinceLastMeaningfulOutput satisfies the BacklogService.SessionStopper interface. It reports how long it has been since sessionUUID's live Instance last produced meaningful terminal output. ok is false when the session isn't currently tracked live (mirrors IsSessionLive's "not found" case) — callers must not use dur in that case.

func (*SessionService) TriggerReviewForSession added in v1.37.0

func (s *SessionService) TriggerReviewForSession(sessionUUID string)

TriggerReviewForSession is a public passthrough to the wired ReviewGateTrigger. Satisfies mcp.ReviewTrigger so request_review can spawn a review gate immediately instead of waiting for the next ReconcileStuck tick.

func (*SessionService) UnarchiveSession added in v1.35.0

+api: session:unarchive UnarchiveSession clears archived_at, restoring the session to the default list.

func (*SessionService) UpdateCallbackConfig added in v1.43.0

+api: callback-config:update UpdateCallbackConfig sets one or more outbound-callback URLs.

func (*SessionService) UpdateClaudeConfig

UpdateClaudeConfig updates a Claude configuration file with atomic write and backup.

func (*SessionService) UpdateFeatureFlag added in v1.35.0

+api: feature-flags:update UpdateFeatureFlag enables or disables a named feature flag and persists the change.

func (*SessionService) UpdateGlobalDefaults added in v1.12.0

UpdateGlobalDefaults replaces the global default fields.

func (*SessionService) UpdateProject added in v1.23.0

+api: project:update UpdateProject updates an existing project's metadata.

func (*SessionService) UpdateSession

UpdateSession modifies session properties (pause/resume, category, title). +api: session:update

func (*SessionService) UpdateSessionProgram added in v1.35.0

func (s *SessionService) UpdateSessionProgram(ctx context.Context, sessionID string, newProgram string) error

UpdateSessionProgram handles switching programs for a session, doing the history porting, DB save, and PTY restart. Shares its implementation with the UpdateSession RPC handler via Instance.SwitchProgram (see session/instance_program.go) and publishSessionUpdatedEvent above so the two program-switch entry points — this auto-fallback path and the manual RPC — can't drift.

func (*SessionService) UpdateSlackConfig added in v1.44.0

UpdateSlackConfig updates the Slack notification configuration.

func (*SessionService) UpdateWorkflow added in v1.35.0

+api: workflow:update UpdateWorkflow delegates to WorkflowService.

func (*SessionService) UpsertAlias added in v1.35.0

UpsertAlias creates or updates a named alias preset.

func (*SessionService) UpsertApprovalRule

UpsertApprovalRule creates or updates a user-defined auto-approval rule.

func (*SessionService) UpsertDirectoryRule added in v1.12.0

UpsertDirectoryRule creates or updates a directory rule.

func (*SessionService) UpsertProfile added in v1.12.0

UpsertProfile creates or updates a named profile.

func (*SessionService) ValidateRules added in v1.35.0

ValidateRules parses and validates a YAML rules file without applying it.

func (*SessionService) WatchReviewQueue

WatchReviewQueue streams real-time review queue events.

func (*SessionService) WatchSessions

WatchSessions streams real-time session events (created/updated/deleted). Sends initial snapshot of all sessions, then subscribes to real-time updates. +api: session:watch

func (*SessionService) WireInstanceCallbacks added in v1.35.0

func (s *SessionService) WireInstanceCallbacks(inst *session.LiveInstance)

WireInstanceCallbacks is the onConstruct hook for Registry.Acquire. It wires all per-session callbacks (review queue, status manager, rate limit, etc.) onto a freshly constructed LiveInstance. Called exactly once per genuine construction in Acquire — never on refcount++ hits, never on Register (CreateSession wires callbacks explicitly).

func (*SessionService) WriteToSession added in v1.35.0

+api: session:log-client-events +api: session:write-to-session WriteToSession sends raw text input to a running session's PTY.

type SessionSnapshot

type SessionSnapshot struct {
	Title                string    `json:"title"`
	Status               string    `json:"status"`
	Program              string    `json:"program"`
	Path                 string    `json:"path"`
	Branch               string    `json:"branch"`
	SessionType          string    `json:"session_type"`
	Category             string    `json:"category"`
	Tags                 []string  `json:"tags"`
	CreatedAt            time.Time `json:"created_at"`
	UpdatedAt            time.Time `json:"updated_at"`
	LastTerminalUpdate   time.Time `json:"last_terminal_update,omitempty"`
	LastMeaningfulOutput time.Time `json:"last_meaningful_output,omitempty"`
	LastOutputSignature  string    `json:"last_output_signature,omitempty"`
	PaneContent          string    `json:"pane_content,omitempty"`
	PaneContentRaw       string    `json:"pane_content_raw,omitempty"`
	PaneContentTruncated bool      `json:"pane_content_truncated,omitempty"`
	InstanceType         string    `json:"instance_type"`
	GitHubPRNumber       int       `json:"github_pr_number,omitempty"`
}

SessionSnapshot captures the state of a single session at snapshot time.

type SessionStopper added in v1.35.0

type SessionStopper interface {
	StopSessionByUUID(ctx context.Context, sessionUUID string) error
	// KillTmuxSessionByTitle kills a tmux session by its title, regardless of
	// whether the Instance is still tracked in memory. Used to clear stale tmux
	// sessions before re-triggering so the fresh session gets its --append-system-prompt.
	KillTmuxSessionByTitle(ctx context.Context, title string) error
	// IsSessionLive returns true if the session UUID is currently tracked in the
	// live in-memory poller. Used to distinguish genuinely-running sessions from
	// sessions that exited but whose DB records were not closed (e.g. after a
	// server restart that killed the underlying process).
	IsSessionLive(sessionUUID string) bool
	// KillTmuxPaneOnly closes the tmux pane for sessionUUID without touching its
	// worktree — unlike StopSessionByUUID/Instance.Kill, which also runs
	// CleanupWorktree. Rework rounds share one worktree/branch across their "-rN"
	// revisions (see buildRevisionTitle), so tearing down a finished round's
	// worktree would destroy the next round's checkout. No-op if the session
	// isn't tracked live (already gone).
	KillTmuxPaneOnly(ctx context.Context, sessionUUID string) error
	// ArchiveSessionByUUID soft-archives a session so it stops accumulating in
	// the default session list once its backlog item is done/superseded. No-op
	// (not an error) if the session isn't tracked live or is already archived.
	ArchiveSessionByUUID(ctx context.Context, sessionUUID string) error
	// TimeSinceLastMeaningfulOutput returns how long it has been since the live
	// Instance for sessionUUID last produced meaningful terminal output, backed
	// by the same Instance.GetTimeSinceLastMeaningfulOutput signal
	// review_queue_determiner.go's staleness detector uses — so "is this
	// session stale" has exactly one definition across the codebase instead of
	// each call site re-deriving its own. ok is false if the session isn't
	// currently tracked live (same "not live" cases as IsSessionLive); dur is
	// meaningless when ok is false.
	TimeSinceLastMeaningfulOutput(sessionUUID string) (dur time.Duration, ok bool)
}

SessionStopper allows BacklogService to kill live sessions. It is nil-safe: BacklogService degrades gracefully when not wired.

type SessionStreamer added in v1.15.0

type SessionStreamer interface {
	StartControlMode() error
	StopControlMode() error
	SubscribeControlModeUpdates() (string, <-chan []byte)
	UnsubscribeControlModeUpdates(id string)
}

SessionStreamer is the interface the WebSocket streaming handler requires from a session. Defined in the consumer package (server/services) to prevent import cycles and to keep the interface minimal — only what this package legitimately needs for terminal streaming.

*session.Instance satisfies this interface via delegation methods.

type SessionSummaryService added in v1.41.0

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

SessionSummaryService implements the ConnectRPC SessionSummaryServiceHandler. Reads/writes the SessionSummary ent table directly, via generator.FindRowBySessionID (never via SessionService's live-instance machinery), so a summary remains retrievable after its Session row is gone (AC-3). Queries are delegated to SessionSummaryGenerator rather than issuing them here because server/services must not import session/ent's query/error-handling helpers directly (.golangci.yml's no_ent_in_services/forbidigo rules) — ent access stays confined to the session package, translated to the session.ErrNotFound sentinel at the boundary.

func NewSessionSummaryService added in v1.41.0

func NewSessionSummaryService(generator *session.SessionSummaryGenerator, instances liveInstanceFinder) *SessionSummaryService

NewSessionSummaryService creates a SessionSummaryService.

func (*SessionSummaryService) GetSessionSummary added in v1.41.0

GetSessionSummary returns the current summary for a session, if one exists. The response's summary field is unset (nil) when no row exists yet — e.g. the session is still running, or was never eligible for summary generation — which is a valid state, not an error.

GetSessionSummary is a read RPC that may perform a side-effecting write: on a stale GENERATING row (see SessionSummaryGenerator.ReconcileStaleness), it upserts the row to ERROR before returning it, as a lazy restart-recovery mechanism (deliberately chosen over a background sweep — see plan.md's Pattern Decisions table). A caller polling this every 2s otherwise has no way to discover from the method's name/contract alone that it can mutate state. +api: GetSessionSummary

func (*SessionSummaryService) RegenerateSessionSummary added in v1.41.0

RegenerateSessionSummary triggers regeneration of a session's summary and returns the resulting summary. The regeneration pipeline runs asynchronously — this method dispatches it and returns the current (possibly stale/still-generating) row immediately rather than waiting for the pipeline to finish; the client is expected to poll GetSessionSummary until status leaves GENERATING (Story 3.1.1).

The dedup guard inside SessionSummaryGenerator.GenerateAndPersist (not this handler) is what prevents a second overlapping pipeline when one is already in flight for this session (AC-8) — this handler always dispatches unconditionally and lets that guard reject the duplicate. +api: RegenerateSessionSummary

type SessionSwitcher added in v1.35.0

type SessionSwitcher interface {
	UpdateSessionProgram(ctx context.Context, sessionID string, newProgram string) error
}

type SlackConfigService added in v1.44.0

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

SlackConfigService handles the GetSlackConfig/UpdateSlackConfig/ TestSlackWebhook RPCs. Delegated to from SessionService exactly like DefaultsService/CallbackConfigService — a config-backed handler with no second implementation, so a concrete type per .claude/rules/interface-pollution-checklist.md.

func NewSlackConfigService added in v1.44.0

func NewSlackConfigService(n *SlackNotifier) *SlackConfigService

NewSlackConfigService creates a SlackConfigService backed by the given SlackNotifier (used for GetDeliveryStatus and, for TestSlackWebhook, the shared postToSlack helper — both types live in package services).

func (*SlackConfigService) GetSlackConfig added in v1.44.0

GetSlackConfig returns the current Slack notification configuration. webhook_configured/signing_secret_configured reflect ciphertext (or env override) *presence*, not decrypt success — see plan.md's Task 1.4.2a decrypt-health note for why that's an accepted Phase 1 gap. +api: slack-config:get

func (*SlackConfigService) TestSlackWebhook added in v1.44.0

TestSlackWebhook sends a canned test message synchronously (not via dispatchAsync — the whole point is the caller waits for a real result) and reports the outcome. If req.Msg.WebhookUrl is non-empty, it's used directly (already plaintext from the settings form, tested before any save); a blank webhook_url falls back to the currently-saved config. Testing an in-form URL never persists it. +api: slack-config:test-webhook

func (*SlackConfigService) UpdateSlackConfig added in v1.44.0

UpdateSlackConfig updates the Slack notification configuration. See UpdateSlackConfigRequest's doc comment for the empty-string-vs-clear-bool precedence semantics applied to both secret fields. +api: slack-config:update

type SlackInteractiveHandler added in v1.44.0

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

SlackInteractiveHandler handles POST /api/hooks/slack-interactive: Slack's interactive-component callback for the Approve/Deny buttons added to outbound approval-pending messages (Phase 2, Story 2.1.2). Only registered at all when cfg.Slack.ApprovalEnabled (server.go, Story 2.1.3).

func NewSlackInteractiveHandler added in v1.44.0

func NewSlackInteractiveHandler(resolver approvalResolver) *SlackInteractiveHandler

NewSlackInteractiveHandler constructs a SlackInteractiveHandler. The signing secret is resolved live from config.LoadConfig() on every request (via resolveSlackSigningSecret) rather than captured once at construction time, matching this package's established "read live config, don't snapshot it" convention (see slack_notifier.go's resolveSlackWebhookURL call sites and hookBaseURLFn's doc comment in server/server.go).

func (*SlackInteractiveHandler) Handle added in v1.44.0

Handle reads the raw body exactly once and reuses it for both signature verification and payload parsing — never calling r.ParseForm() first, which would verify against a different/already-consumed body than the bytes Slack signed (research/pitfalls.md §5). Any verification failure gets a generic 401; on success it resolves the clicked button's approval.

type SlackInteractivePayload added in v1.44.0

type SlackInteractivePayload struct {
	User struct {
		ID       string `json:"id"`
		Username string `json:"username"`
	} `json:"user"`
	Actions []struct {
		ActionID string `json:"action_id"`
		Value    string `json:"value"`
	} `json:"actions"`
}

SlackInteractivePayload is the subset of Slack's interactive-component callback payload this handler needs: which button was clicked and who clicked it (User, for audit logging -- see Handle's success-path log line).

type SlackNotifier added in v1.44.0

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

SlackNotifier formats Block Kit messages and delivers them to a configured Slack Incoming Webhook. It is the single implementation of this concern in the codebase (no interface — see .claude/rules/interface-pollution-checklist.md). All Notify*/MaybeNotify* methods are non-blocking: they dispatch the actual HTTP POST on an internal goroutine (dispatchAsync) and return immediately.

func NewSlackNotifier added in v1.44.0

func NewSlackNotifier() *SlackNotifier

NewSlackNotifier constructs a SlackNotifier with a 5-second-timeout HTTP client, mirroring domain_checker.go's http.Client{Timeout: 3 * time.Second} shape.

func (*SlackNotifier) GetDeliveryStatus added in v1.44.0

func (n *SlackNotifier) GetDeliveryStatus() (attempted, success bool, errMsg string, at time.Time)

GetDeliveryStatus returns a thread-safe snapshot of the notifier's most recent send outcome: attempted is false until the first postToSlack call ever completes.

func (*SlackNotifier) MaybeNotifyQueueDepthThreshold added in v1.44.0

func (n *SlackNotifier) MaybeNotifyQueueDepthThreshold(ctx context.Context, cfg *config.Config, depth, threshold int, dashboardURL string) (fired bool)

MaybeNotifyQueueDepthThreshold implements the edge-triggered digest latch: fires exactly one Slack digest per crossing above threshold, resetting when depth drops back below it. threshold <= 0 always returns false. depth and threshold are both plain ints — do not swap them, it silently inverts the crossing logic with no compiler error (primitive-obsession checklist).

func (*SlackNotifier) NotifyApprovalPending added in v1.44.0

func (n *SlackNotifier) NotifyApprovalPending(ctx context.Context, cfg *config.Config, approval *PendingApproval, sessionName, dashboardURL string)

NotifyApprovalPending sends a Slack notification for a new pending approval. No-ops cleanly when no webhook is configured. Non-blocking; any send failure is logged and swallowed.

func (*SlackNotifier) NotifyReviewQueueItem added in v1.44.0

func (n *SlackNotifier) NotifyReviewQueueItem(ctx context.Context, cfg *config.Config, item *session.ReviewItem, dashboardURL string)

NotifyReviewQueueItem sends a Slack notification for a new review-queue item. No-ops cleanly (no HTTP call) when no webhook is configured. The call itself is non-blocking; any send failure is logged and swallowed — this method never returns an error to the caller.

type SlashCommandService added in v1.35.0

type SlashCommandService struct{}

SlashCommandService resolves slash commands from disk and built-ins.

func NewSlashCommandService added in v1.35.0

func NewSlashCommandService() *SlashCommandService

NewSlashCommandService returns an initialized SlashCommandService.

func (*SlashCommandService) ListSlashCommands added in v1.35.0

ListSlashCommands walks ~/.claude/commands/ and target_directory/.claude/commands/, merging results with built-in Claude Code commands. Project commands take precedence over user commands; built-ins fill any remaining gaps.

type StaleSessionNotifier added in v1.44.0

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

StaleSessionNotifier is a small, independent, periodic sweeper that fires an edge-triggered, self-clearing notification the first time an ACTIVE session crosses the configured stale threshold (config.StaleSessionConfig), and re-arms after recovery so a later episode of staleness on the same session notifies again.

Deliberately separate from the three pre-existing, independently-tuned staleness detectors elsewhere in this codebase (the review queue's 5-minute ReasonStale badge in session/review_queue_determiner.go, the rework-block gate's 15-minute check, and the 2-hour stuck-backlog-item detector's maxWorkSessionStaleness in session/backlog_lifecycle_stale.go) -- this sweeper does not read, modify, or share dedup state with any of them. It exists purely to raise an operator-facing notification event, not to change queue membership or backlog lifecycle state.

func NewStaleSessionNotifier added in v1.44.0

func NewStaleSessionNotifier(poller *session.ReviewQueuePoller, eventBus *events.EventBus) *StaleSessionNotifier

NewStaleSessionNotifier constructs a notifier. poller supplies the live set of instances to evaluate (via GetInstances()); eventBus receives the notification events. Neither dependency is optional in production, but a nil eventBus is tolerated (notify becomes a no-op) so this can be safely constructed before the event bus exists during startup wiring.

func (*StaleSessionNotifier) Start added in v1.44.0

func (n *StaleSessionNotifier) Start(ctx context.Context)

Start runs the periodic check loop. Blocks until ctx is cancelled. Mirrors SessionRetentionSweeper.Start's shape: run once immediately, then on every tick.

type StreamingWSBridge added in v1.35.0

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

StreamingWSBridge wraps a Connect HTTP handler to also accept WebSocket connections for server-streaming RPCs. This avoids browser HTTP/1.1 connection limits (6 per origin) for long-lived streaming calls like WatchSessions and WatchReviewQueue.

Protocol: the client sends exactly one WebSocket binary message containing a Connect request envelope (5-byte header + protobuf body). The server responds with one WebSocket message per stream event (each a Connect response envelope), and a final message containing the Connect end-stream envelope.

This is compatible with createWebsocketBasedTransport in the frontend.

func NewStreamingWSBridge added in v1.35.0

func NewStreamingWSBridge(handler http.Handler) *StreamingWSBridge

NewStreamingWSBridge creates a bridge around the given Connect handler. The handler should be the raw sessionv1connect handler (before StripPrefix).

func (*StreamingWSBridge) Handler added in v1.35.0

func (b *StreamingWSBridge) Handler(apiPrefix string) http.Handler

Handler returns an http.Handler that serves WebSocket connections for the streaming RPC at the given path, and falls back to the wrapped HTTP handler for non-WebSocket requests (so the same path serves both transports).

apiPrefix is the prefix (e.g. "/api") added to RPC paths at the mux level. The Connect handler expects paths WITHOUT this prefix.

type SubcommandStat

type SubcommandStat struct {
	Program    string `json:"program"`
	Subcommand string `json:"subcommand"`
	Category   string `json:"category"`
	Count      int    `json:"count"`
}

SubcommandStat is a (program, subcommand) pair with its usage count. Subcommand may contain a space for two-level CLIs (e.g., "pr create" for gh).

type TerminalService added in v1.35.0

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

TerminalService handles GetTerminalSnapshot and WriteToSession RPCs. Both methods share the same instance-lookup fallback chain (poller → external discovery → not-found error), which is factored into the private findInstance helper below. Extracted from SessionService per ADR-001 (both methods individually exceed the 30-line threshold and are cohesive as a pair).

func NewTerminalService added in v1.35.0

func NewTerminalService() *TerminalService

NewTerminalService creates a TerminalService. Wire poller and externalDiscovery after construction via SetPoller and SetExternalDiscovery.

func (*TerminalService) GetTerminalSnapshot added in v1.35.0

GetTerminalSnapshot returns the last N lines of terminal output for a session. Uses inst.Preview() for a read-only snapshot without requiring an active stream.

func (*TerminalService) SetExternalDiscovery added in v1.35.0

func (ts *TerminalService) SetExternalDiscovery(d *session.ExternalSessionDiscovery)

SetExternalDiscovery wires the external session discovery (mux-enabled sessions).

func (*TerminalService) SetPoller added in v1.35.0

func (ts *TerminalService) SetPoller(p *session.ReviewQueuePoller)

SetPoller wires the live-instance poller for instance lookup.

func (*TerminalService) WriteToSession added in v1.35.0

+api: session:log-client-events +api: session:write-to-session WriteToSession sends raw text input to a running session's PTY.

type TerminalWebSocketHandler

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

TerminalWebSocketHandler handles WebSocket connections for terminal streaming

func NewTerminalWebSocketHandler

func NewTerminalWebSocketHandler(storage session.Storage, eventBus *events.EventBus) *TerminalWebSocketHandler

NewTerminalWebSocketHandler creates a new WebSocket handler for terminal streaming

func (*TerminalWebSocketHandler) HandleWebSocket

func (h *TerminalWebSocketHandler) HandleWebSocket(w http.ResponseWriter, r *http.Request)

HandleWebSocket upgrades HTTP connection to WebSocket and handles terminal streaming

type TmuxSessionDetail

type TmuxSessionDetail struct {
	TmuxSessionName string `json:"tmux_session_name"`
	ListPanesOutput string `json:"list_panes_output,omitempty"`
	PaneContent     string `json:"pane_content,omitempty"`
}

TmuxSessionDetail captures per-tmux-session diagnostic info.

type TmuxSnapshot

type TmuxSnapshot struct {
	ListSessionsOutput string              `json:"list_sessions_output"`
	PerSession         []TmuxSessionDetail `json:"per_session"`
}

TmuxSnapshot captures global tmux state.

type ToolStat

type ToolStat struct {
	ToolName string `json:"tool_name"`
	Count    int    `json:"count"`
}

ToolStat is a tool name with a count.

type TriggerRateLimiter added in v1.43.0

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

TriggerRateLimiter enforces a per-Workflow rate limit on trigger fires (webhook- triggers Epic 2.4.2), guarding against a noisy or malicious webhook source spawning unbounded sessions. Concrete type, not an interface — one implementation, per .claude/rules/interface-pollution-checklist.md. server/workflows.Scheduler consumes it through its own narrow triggerRateLimiterGate interface (defined in scheduler.go), to avoid a server/workflows -> server/services import.

func NewTriggerRateLimiter added in v1.43.0

func NewTriggerRateLimiter() *TriggerRateLimiter

NewTriggerRateLimiter creates a TriggerRateLimiter using the default rate (10/min, burst 10).

func (*TriggerRateLimiter) Allow added in v1.43.0

func (t *TriggerRateLimiter) Allow(workflowID uuid.UUID) bool

Allow reports whether a fire for workflowID is permitted right now, consuming one token from that workflow's bucket if so. Each Workflow gets its own independent token bucket, created lazily on first use.

type UnfinishedWorkService added in v1.22.0

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

UnfinishedWorkService implements the ConnectRPC UnfinishedWorkServiceHandler.

func NewUnfinishedWorkService added in v1.22.0

func NewUnfinishedWorkService(
	scanner *unfinished.Scanner,
	stateStore *unfinished.StateStore,
	eventBus *events.EventBus,
	storage *session.Storage,
) *UnfinishedWorkService

NewUnfinishedWorkService creates a new service instance.

func (*UnfinishedWorkService) DismissWorktree added in v1.22.0

DismissWorktree permanently hides a worktree from results.

func (*UnfinishedWorkService) GetUnfinishedWorkConfig added in v1.22.0

GetUnfinishedWorkConfig returns the current source configuration.

func (*UnfinishedWorkService) GetWorktreeAISummary added in v1.22.0

GetWorktreeAISummary generates or returns a cached AI summary.

func (*UnfinishedWorkService) GetWorktreeDiff added in v1.35.0

GetWorktreeDiff returns the full unified git diff for an unfinished worktree. It compares the working tree against the remote default branch so the caller can display the diff without opening a session.

func (*UnfinishedWorkService) ListUnfinishedWork added in v1.22.0

ListUnfinishedWork returns the current snapshot of all unfinished worktrees.

func (*UnfinishedWorkService) QuickCommitPush added in v1.22.0

QuickCommitPush stages all changes, commits, and pushes.

func (*UnfinishedWorkService) ScanUnfinishedWork added in v1.22.0

ScanUnfinishedWork triggers an immediate scan.

func (*UnfinishedWorkService) SnoozeWorktree added in v1.22.0

SnoozeWorktree hides a worktree until the next HEAD SHA change.

func (*UnfinishedWorkService) UndismissWorktree added in v1.22.0

UndismissWorktree removes the dismiss record.

func (*UnfinishedWorkService) UpdateUnfinishedWorkConfig added in v1.22.0

UpdateUnfinishedWorkConfig replaces the source configuration.

func (*UnfinishedWorkService) WatchUnfinishedWork added in v1.22.0

WatchUnfinishedWork streams real-time updates to connected clients. Pattern: send initial snapshot → subscribe to EventBus → forward events until disconnect.

type UtilityService

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

UtilityService handles miscellaneous utility RPCs: GetLogs, FocusWindow, and CreateDebugSnapshot.

Dependencies:

  • approvalStore: needed by CreateDebugSnapshot to capture pending approvals
  • reviewQueuePoller: late-wired; needed by CreateDebugSnapshot for live instances

func NewUtilityService

func NewUtilityService(approvalStore *ApprovalStore) *UtilityService

NewUtilityService creates a UtilityService with the given dependencies.

func (*UtilityService) CreateDebugSnapshot

CreateDebugSnapshot captures diagnostic information and writes a JSON file to the log directory.

func (*UtilityService) FocusWindow

FocusWindow activates a window for the specified application. Uses AppleScript on macOS to bring the application to front.

func (*UtilityService) GetLogs

GetLogs retrieves application logs with optional filtering and search.

func (*UtilityService) SetReviewQueuePoller

func (us *UtilityService) SetReviewQueuePoller(poller *session.ReviewQueuePoller)

SetReviewQueuePoller sets the review queue poller (late-wired).

type VNCProxyHandler added in v1.35.0

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

VNCProxyHandler handles WebSocket connections for VNC browser passthrough. It tunnels raw bytes between the browser's noVNC client and the per-session x11vnc TCP server bound to localhost.

func NewVNCProxyHandler added in v1.35.0

func NewVNCProxyHandler(finder InstanceFinder) *VNCProxyHandler

NewVNCProxyHandler creates a new VNCProxyHandler backed by the given InstanceFinder. Pass a *session.ReviewQueuePoller (which implements InstanceFinder) so that each WebSocket upgrade performs an O(1) in-memory lookup rather than a full SQLite read.

func (*VNCProxyHandler) HandleWebSocket added in v1.35.0

func (h *VNCProxyHandler) HandleWebSocket(w http.ResponseWriter, r *http.Request)

+http: GET /api/sessions/{id}/vnc browser:proxy HandleWebSocket upgrades an HTTP request to WebSocket and proxies bytes between the client and the session's local x11vnc port.

Route: GET /api/sessions/{id}/vnc (WebSocket upgrade)

type WatchReviewQueueFilters

type WatchReviewQueueFilters struct {
	PriorityFilter    []session.Priority
	ReasonFilter      []session.AttentionReason
	SessionIDs        []string
	IncludeStatistics bool
	InitialSnapshot   bool
}

WatchReviewQueueFilters contains filters for review queue event streaming.

func (*WatchReviewQueueFilters) GetIncludeStatistics

func (f *WatchReviewQueueFilters) GetIncludeStatistics() bool

func (*WatchReviewQueueFilters) GetInitialSnapshot

func (f *WatchReviewQueueFilters) GetInitialSnapshot() bool

func (*WatchReviewQueueFilters) GetPriorityFilter

func (f *WatchReviewQueueFilters) GetPriorityFilter() []session.Priority

Implement FilterProvider interface for type-safe conversion.

func (*WatchReviewQueueFilters) GetReasonFilter

func (f *WatchReviewQueueFilters) GetReasonFilter() []session.AttentionReason

func (*WatchReviewQueueFilters) GetSessionIDs

func (f *WatchReviewQueueFilters) GetSessionIDs() []string

type WorkflowSchedulerInterface added in v1.35.0

type WorkflowSchedulerInterface interface {
	Reload(ctx context.Context, wf *ent.Workflow) error
	Remove(workflowID string) error
	// FireNow immediately fires a workflow job, returning the created session ID.
	FireNow(ctx context.Context, wf *ent.Workflow, arg string) (string, error)
}

WorkflowSchedulerInterface is the interface WorkflowService uses to interact with the scheduler. Defined in this package to avoid a circular import with server/workflows.

type WorkflowService added in v1.35.0

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

WorkflowService implements the workflow-related RPCs on SessionService.

func NewWorkflowService added in v1.35.0

func NewWorkflowService(repo session.WorkflowRepository, scheduler WorkflowSchedulerInterface, storage session.InstanceStore) *WorkflowService

NewWorkflowService creates a new WorkflowService. storage is required for ArchiveWorkflowSessions / DeleteWorkflowFailedSessions; pass nil to disable those RPCs (they will return CodeUnavailable).

func (*WorkflowService) ArchiveWorkflowSessions added in v1.35.0

+api: session:archive-workflow-sessions ArchiveWorkflowSessions archives all non-active sessions for a given workflow. Active, Creating, and Paused sessions are silently skipped. Extracted from SessionService per ADR-001.

func (*WorkflowService) CreateWorkflow added in v1.35.0

CreateWorkflow handles the CreateWorkflow RPC.

func (*WorkflowService) DeleteWorkflow added in v1.35.0

DeleteWorkflow handles the DeleteWorkflow RPC.

func (*WorkflowService) DeleteWorkflowFailedSessions added in v1.35.0

+api: session:delete-workflow-failed-sessions DeleteWorkflowFailedSessions archives (soft-deletes) sessions that appear to have failed — Stopped sessions with no meaningful terminal output for the given workflow. Extracted from SessionService per ADR-001.

func (*WorkflowService) ListTriggerFireEvents added in v1.43.0

+api: workflow:list-trigger-fire-events ListTriggerFireEvents returns the trigger-fire audit trail for a workflow, newest first (Epic 1.2, Task 1.2.1d). Query-only, shipped ahead of the Phase 7 UI so existing cron-workflow users can observe fired_failed rejections from the Epic 1.3 admission-gate fix.

func (*WorkflowService) ListWorkflows added in v1.35.0

ListWorkflows handles the ListWorkflows RPC.

func (*WorkflowService) RunWorkflow added in v1.35.0

RunWorkflow handles the RunWorkflow RPC. Delegates to scheduler.FireNow to avoid circular dependency with SessionService.

func (*WorkflowService) SetPoller added in v1.35.0

func (ws *WorkflowService) SetPoller(p *session.ReviewQueuePoller)

SetPoller wires the live-instance poller so ArchiveWorkflowSessions can update in-memory instance state. Forwarded from SessionService.SetReviewQueuePoller.

func (*WorkflowService) SetTriggerFireEventRepo added in v1.43.0

func (ws *WorkflowService) SetTriggerFireEventRepo(repo session.TriggerFireEventRepository)

SetTriggerFireEventRepo wires the trigger-fire audit trail repository used by ListTriggerFireEvents. Optional — see fireEventRepo's doc comment.

func (*WorkflowService) UpdateWorkflow added in v1.35.0

UpdateWorkflow handles the UpdateWorkflow RPC.

type WorkspaceProvider added in v1.12.0

type WorkspaceProvider interface {
	GetWorkspace(sessionID string) (session.Workspace, error)
}

WorkspaceProvider resolves workspace path information for a session. Inject this interface into services that need path resolution instead of accessing session.Instance.Path directly.

type WorkspaceService

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

WorkspaceService handles all VCS/workspace RPC methods.

These methods operate on session workspace state (git/jj status, branch switching, worktrees) and may emit events after state-modifying operations.

func NewWorkspaceService

func NewWorkspaceService(storage *session.Storage, eventBus *events.EventBus) *WorkspaceService

NewWorkspaceService creates a WorkspaceService with the given dependencies.

func (*WorkspaceService) GetVCSStatus

GetVCSStatus retrieves the current version control status for a session.

func (*WorkspaceService) GetWorkspace added in v1.12.0

func (ws *WorkspaceService) GetWorkspace(sessionID string) (session.Workspace, error)

GetWorkspace implements WorkspaceProvider.

func (*WorkspaceService) GetWorkspaceInfo

GetWorkspaceInfo retrieves VCS and workspace information for a session.

func (*WorkspaceService) ListBranches added in v1.35.0

ListBranches returns the git branches for a given repository path. Results are cached per repo path with a 5-minute TTL. ADR-002. Moved from SessionService (Story 1.4 — was the odd one out next to the four workspace methods that already delegated here).

func (*WorkspaceService) ListWorkspaceTargets

ListWorkspaceTargets returns available switch targets for a session.

func (*WorkspaceService) SetLiveFinder added in v1.35.0

func (ws *WorkspaceService) SetLiveFinder(f LiveInstanceFinder)

SetLiveFinder wires the fast-path instance lookup. Call this after constructing SessionService so that read-only RPCs bypass LoadInstances().

func (*WorkspaceService) SwitchWorkspace

SwitchWorkspace switches a session's workspace to a different branch, revision, or worktree.

Source Files

Jump to

Keyboard shortcuts

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