Documentation
¶
Overview ¶
Package control is the transport-agnostic session driver. A Controller owns the agent run loop and session lifecycle, takes commands (Send/Cancel/Approve/ SetPlanMode/Compact/NewSession/…), and emits everything that happens — reasoning, tool calls, approvals, turn completion — as a typed event stream to a single event.Sink.
The point is one orchestration layer behind every frontend: a terminal TUI, a desktop webview, or an HTTP/SSE server each drive the Controller identically (issue commands, render events) and none of them re-implement turn lifecycle, cancellation, or approval. The Controller depends on no frontend.
Index ¶
- Constants
- Variables
- func FileRefLine(line string) (string, bool)
- func FormatBranchTree(branches []agent.BranchInfo, currentID string) string
- func ImageDataURL(path string) (string, error)
- func IsSyntheticUserMessage(content string) bool
- func MemoryQuickAddNote(input string) (note string, ok bool)
- func NormalizeAutoPlan(mode string) string
- func ParseBranchTarget(args string) (turn int, name string, fromTurn bool, err error)
- func RememberCommandNote(input string) (note string, ok bool)
- func SaveAttachmentDataURL(origName, dataURL string) (string, error)
- func SaveAttachmentFile(path string) (string, error)
- func SaveClipboardImage() (string, error)
- func SaveImageBytes(declaredMime string, raw []byte) (string, error)
- func SaveImageDataURL(dataURL string) (string, error)
- func SaveImageFile(path string) (string, error)
- func ShortGoalForNotice(goal string) string
- func SlashPathLikeLine(line string) bool
- func SlashPathLineRef(line, baseDir string) (string, bool)
- func StripComposePrefixes(content string) string
- func StripReferencedContextPrefix(content string) string
- type ArgData
- type Controller
- func (c *Controller) AddMCPServer(e config.PluginEntry) (int, error)
- func (c *Controller) AllSkills() []skill.Skill
- func (c *Controller) AnswerQuestion(id string, answers []event.AskAnswer)
- func (c *Controller) AppendExpertCollab(content string) error
- func (c *Controller) Approve(id string, allow, session, persist bool)
- func (c *Controller) Ask(ctx context.Context, questions []event.AskQuestion) ([]event.AskAnswer, error)
- func (c *Controller) AutoApproveTools() bool
- func (c *Controller) BeginDestroySession(sessionPath string) *SessionDestroyHandle
- func (c *Controller) Branch(name string) (string, error)
- func (c *Controller) BranchTreeText() string
- func (c *Controller) Branches() ([]agent.BranchInfo, error)
- func (c *Controller) Bypass() bool
- func (c *Controller) Cancel()
- func (c *Controller) CheckpointHasBoundary(turn int) bool
- func (c *Controller) Checkpoints() []checkpoint.Meta
- func (c *Controller) ClearGoal()
- func (c *Controller) ClearSession() error
- func (c *Controller) Close()
- func (c *Controller) Commands() []command.Command
- func (c *Controller) Compact(ctx context.Context, instructions string) error
- func (c *Controller) CompactRatio() float64
- func (c *Controller) Compose(text string) string
- func (c *Controller) ComposeSynthetic(text string) string
- func (c *Controller) ConfiguredMCPNames() []string
- func (c *Controller) ConnectCodegraphMCPServer(cfg *config.Config) (int, error)
- func (c *Controller) ConnectConfiguredMCPServer(name string) (int, error)
- func (c *Controller) ConnectMCPServer(e config.PluginEntry) (int, error)
- func (c *Controller) ContextSnapshot() (int, int)
- func (c *Controller) CustomCommand(input string) (sent string, found bool)
- func (c *Controller) DeleteExpertCollab(ordinal int) error
- func (c *Controller) DisabledSkills() []skill.Skill
- func (c *Controller) DisconnectMCPServer(name string) bool
- func (c *Controller) DisconnectedMCPNames() []string
- func (c *Controller) DreamInFlight(kind agent.DreamKind) bool
- func (c *Controller) EmitExpertCollab(collab event.Collab)
- func (c *Controller) EnableInteractiveApproval()
- func (c *Controller) ForgetMemory(name string) error
- func (c *Controller) Fork(turn int) (string, error)
- func (c *Controller) ForkNamed(turn int, name string) (string, error)
- func (c *Controller) ForkSession(turn int, name string) (string, error)
- func (c *Controller) Goal() string
- func (c *Controller) GoalStatus() string
- func (c *Controller) GoalStrict() bool
- func (c *Controller) HasRefs(line string) bool
- func (c *Controller) History() []provider.Message
- func (c *Controller) HookRunner() *hook.Runner
- func (c *Controller) Host() *plugin.Host
- func (c *Controller) ImportMCPEntries(entries []config.PluginEntry) (total, added, updated, connected, failed, skipped int, err error)
- func (c *Controller) InheritLifecycleFrom(prev *Controller)
- func (c *Controller) IsDestroyingSession(sessionPath string) bool
- func (c *Controller) Jobs() []jobs.View
- func (c *Controller) Label() string
- func (c *Controller) LastDreamRun(kind agent.DreamKind) (agent.DreamRun, bool)
- func (c *Controller) LastUsage() *provider.Usage
- func (c *Controller) MCPPrompt(ctx context.Context, input string) (sent string, found bool, err error)
- func (c *Controller) Memory() *memory.Set
- func (c *Controller) NewSession() error
- func (c *Controller) Pause()
- func (c *Controller) Paused() bool
- func (c *Controller) PlanMode() bool
- func (c *Controller) Profile() ProfileView
- func (c *Controller) QueueMemory(note string)
- func (c *Controller) QuickAdd(scope memory.Scope, note string) (string, error)
- func (c *Controller) RemoveMCPServer(name string) (disconnected bool, err error)
- func (c *Controller) ReplayPendingPrompts()
- func (c *Controller) ResolveRefs(ctx context.Context, line string) (block any, errs []string)
- func (c *Controller) Resume(s *agent.Session, path string)
- func (c *Controller) ResumeTurn()
- func (c *Controller) Rewind(turn int, scope RewindScope) error
- func (c *Controller) Run(ctx context.Context, input string) error
- func (c *Controller) RunShell(command string)
- func (c *Controller) RunSkill(input string) (sent string, found bool)
- func (c *Controller) RunTurn(ctx context.Context, input string) error
- func (c *Controller) Running() bool
- func (c *Controller) SaveDoc(path, body string) (string, error)
- func (c *Controller) Send(input string)
- func (c *Controller) SendWithRaw(input, raw string)
- func (c *Controller) SessionCache() (hit, miss int)
- func (c *Controller) SessionDir() string
- func (c *Controller) SessionPath() string
- func (c *Controller) SetAutoApproveTools(on bool)
- func (c *Controller) SetAutoPlan(mode string)
- func (c *Controller) SetBypass(on bool)
- func (c *Controller) SetContextFilter(fn func([]provider.Message) []provider.Message)
- func (c *Controller) SetDisplayRecorder(fn func(content, display string))
- func (c *Controller) SetGoal(goal string)
- func (c *Controller) SetGoalStrict(strict bool)
- func (c *Controller) SetMode(plan, autoApproveTools bool)
- func (c *Controller) SetPlanMode(v bool)
- func (c *Controller) SetRAGScope(scope string)
- func (c *Controller) SetRiskOverrides(overrides map[string]permission.RiskClass)
- func (c *Controller) SetSessionPath(p string)
- func (c *Controller) SetSkillEnabled(name string, enabled bool) error
- func (c *Controller) SetToolApprovalMode(mode string)
- func (c *Controller) SkillEnabled(name string) bool
- func (c *Controller) Skills() []skill.Skill
- func (c *Controller) Snapshot() error
- func (c *Controller) SnapshotActivity() error
- func (c *Controller) Steer(text string)
- func (c *Controller) SteerConsumed() bool
- func (c *Controller) Submit(input string)
- func (c *Controller) SubmitDisplay(display, input string)
- func (c *Controller) SummarizeFrom(ctx context.Context, turn int) error
- func (c *Controller) SummarizeUpTo(ctx context.Context, turn int) error
- func (c *Controller) SwitchBranch(ref string) (agent.BranchInfo, error)
- func (c *Controller) ToolApprovalMode() string
- func (c *Controller) TriggerDistill(ctx context.Context) (agent.DreamRun, bool)
- func (c *Controller) TriggerDream(ctx context.Context) (agent.DreamRun, bool)
- func (c *Controller) Turn() int
- func (c *Controller) WorkspaceRoot() string
- type GoalCommand
- type GoalCommandAction
- type Options
- type PlanCommand
- type ProfileView
- type ProviderAutoPlanClassifier
- type RememberResult
- type RewindScope
- type SessionDestroyHandle
- type SlashItem
Constants ¶
const ( AutoPlanOff = "off" AutoPlanOn = "on" )
const ( ToolApprovalAsk = "ask" ToolApprovalAuto = "auto" ToolApprovalYolo = "yolo" )
const ( GoalStatusRunning = "running" GoalStatusComplete = "complete" GoalStatusBlocked = "blocked" GoalStatusStopped = "stopped" )
const PlanApprovalTool = "exit_plan_mode"
PlanApprovalTool is the Tool name on the ApprovalRequest the controller emits to gate a proposed plan. Frontends key their plan-approval UI on it (the desktop renders a plan card; the chat TUI a plan banner). This is the single source of truth — frontends import this constant instead of re-declaring it.
const PlanApprovedMessage = "" /* 600-byte string literal not displayed */
PlanApprovedMessage is the follow-up turn sent once the user approves a plan — the in-context nudge to execute and keep the (already-seeded) task list honest.
const PlanModeMarker = "" /* 785-byte string literal not displayed */
PlanModeMarker is prepended to every user turn while plan mode is on. It rides in the user message (not the system prompt or tools), so the cache-stable prompt prefix is left untouched and the toggle costs nothing in cache hits. (some providers do not report cache tokens; the prefix stability still helps.)
Variables ¶
var ErrTurnRunning = errors.New("turn already running")
ErrTurnRunning reports that a caller tried to start a second foreground turn while one is already active in the same Controller.
Functions ¶
func FileRefLine ¶
FileRefLine reports whether a submitted line is nothing but a path to an existing file — a dragged or pasted file lands as its bare path, which on POSIX starts with '/' and would otherwise be misread as a slash command. The returned string is that path turned into an @reference so it attaches.
func FormatBranchTree ¶
func FormatBranchTree(branches []agent.BranchInfo, currentID string) string
func ImageDataURL ¶
func IsSyntheticUserMessage ¶
IsSyntheticUserMessage returns true if the content matches one of the known synthetic user messages injected by the controller or agent loop (plan approval, stream recovery, readiness retry, etc.). These should not be shown in the chat UI.
func MemoryQuickAddNote ¶
MemoryQuickAddNote parses the legacy "# <note>" memory shortcut. The space after "#" is intentional: "#7", "#issue", and "#标题" are ordinary user prompts, not memory writes.
func NormalizeAutoPlan ¶
NormalizeAutoPlan is the single source of truth for auto_plan normalization. Exported so boot.go and config consumers share the same semantics.
func ParseBranchTarget ¶
ParseBranchTarget parses the arguments after "/branch". A leading positive integer means "branch from displayed turn N"; otherwise the whole argument is the optional branch name for a tip branch.
func RememberCommandNote ¶
RememberCommandNote parses the explicit "/remember <note>" memory command.
func SaveAttachmentDataURL ¶
SaveAttachmentDataURL stores a non-image file (dropped/pasted in the desktop app, where the browser exposes bytes but not a real path) under .fairpeer/attachments and returns its repo-relative path for @referencing. origName supplies only the extension; the stored name is generated.
func SaveAttachmentFile ¶
func SaveClipboardImage ¶
func SaveImageDataURL ¶
func SaveImageFile ¶
func ShortGoalForNotice ¶
func SlashPathLikeLine ¶
SlashPathLikeLine reports whether a slash-prefixed line looks like a POSIX absolute path rather than a slash command. It intentionally stays conservative: unknown "/foo" remains an unknown command, while "/foo/bar..." is sent as ordinary prompt text even if the path no longer exists.
func SlashPathLineRef ¶
SlashPathLineRef reports whether a slash-prefixed line starts with a local file path, including common compiler-location suffixes like ":12" or ":12:34". It returns an @reference for the file so diagnostics that begin with an absolute path can keep their original text while also attaching file context.
func StripComposePrefixes ¶
StripComposePrefixes removes controller-injected prefixes from a composed user message so that the display text matches what the user actually typed. It strips the PlanModeMarker, <memory-update>…</memory-update>, and <background-jobs>…</background-jobs> blocks that Compose prepends to user turns. This is used as a fallback when no .display.json sidecar recording exists (e.g. sessions created before the display-recording feature, or synthetic user messages injected by the controller).
func StripReferencedContextPrefix ¶
StripReferencedContextPrefix removes a leading "Referenced context:\n\n<block>\n\n" wrapper that the controller prepends when resolving @-references, leaving only the user's actual message. The wrapper has no terminator, so this matches the prefix, skips the block up to the user-text separator, and keeps the rest. Used by exporters (e.g. /export) that want to reconstruct the original prompt.
Types ¶
type ArgData ¶
type ArgData struct {
Skills []skill.Skill
DisabledSkills []skill.Skill
ServerNames []string
ConfiguredMCP []string
DisconnectedMCP []string
ModelRefs []string
CurrentModel string
ProviderNames []string
CurrentProvider string
}
ArgData supplies the dynamic data SlashArgItems needs, so the completion logic is one shared function both frontends call with their own session data — the chat TUI (controller-free, from its cached lists) and the desktop (from the controller). This keeps the CLI and desktop sub-command hints identical.
type Controller ¶
type Controller struct {
// contains filtered or unexported fields
}
Controller drives one chat session. Construct with New; drive with the command methods; observe through the Sink passed in Options.
func New ¶
func New(opts Options) *Controller
New builds a Controller. A nil Sink is replaced with event.Discard.
func (*Controller) AddMCPServer ¶
func (c *Controller) AddMCPServer(e config.PluginEntry) (int, error)
AddMCPServer connects an MCP server live and persists it to the config file. Its tools are registered immediately and become available on the next turn (the agent reads the registry per turn). The raw entry — ${VARS} intact — is what's written to disk; the live connection uses the expanded form. Returns the number of tools the server exposed. A save failure after a successful connect is reported but non-fatal: the server still works this session.
func (*Controller) AllSkills ¶
func (c *Controller) AllSkills() []skill.Skill
AllSkills returns every discoverable skill, including disabled ones, for management surfaces that need to re-enable a hidden skill.
func (*Controller) AnswerQuestion ¶
func (c *Controller) AnswerQuestion(id string, answers []event.AskAnswer)
AnswerQuestion resolves a pending AskRequest by ID with the user's selections. Unknown/expired IDs are ignored.
func (*Controller) AppendExpertCollab ¶
func (c *Controller) AppendExpertCollab(content string) error
AppendExpertCollab persists a finished expert-team collaboration into the active session as a folded-block message (a tool message whose Content is the full collab record JSON, with the context layer projecting it down to a synthesis-only summary for the model). It then emits an ExpertCollab event so the frontend renders an expandable card, and snapshots the session so the record survives restarts.
It refuses while a turn is running (the agent's run loop reads session messages lock-free, so mid-turn mutation would race). The collab is then surfaced on the next render rather than interleaved with a live turn.
func (*Controller) Approve ¶
func (c *Controller) Approve(id string, allow, session, persist bool)
Approve answers a pending ApprovalRequest by ID: allow runs the call, session also remembers a grant for the rest of the session so the same approval scope is not re-prompted. Unknown/expired IDs are ignored.
func (*Controller) Ask ¶
func (c *Controller) Ask(ctx context.Context, questions []event.AskQuestion) ([]event.AskAnswer, error)
Ask implements agent.Asker: it emits an AskRequest and blocks until AnswerQuestion(ID, …) answers or ctx is cancelled. promptMu serialises it against tool-approval prompts so at most one user prompt is outstanding. Unlike tool-approval gates, Ask is NOT bypassed in YOLO mode — the `ask` tool exists to get a genuine user decision, and YOLO only auto-approves tool calls; it must not answer the user's questions for them.
func (*Controller) AutoApproveTools ¶
func (c *Controller) AutoApproveTools() bool
AutoApproveTools reports whether YOLO/full-access tool auto-approval is on, for status indicators and mode persistence.
func (*Controller) BeginDestroySession ¶
func (c *Controller) BeginDestroySession(sessionPath string) *SessionDestroyHandle
BeginDestroySession starts a two-phase session destroy. Call Wait() to cancel and drain background jobs, then Finish() to release resources. If the session has no background job manager, Wait is a no-op.
func (*Controller) Branch ¶
func (c *Controller) Branch(name string) (string, error)
Branch copies the current conversation into a child branch and switches to it. Unlike Fork, it branches at the current tip and does not require a checkpoint.
func (*Controller) BranchTreeText ¶
func (c *Controller) BranchTreeText() string
func (*Controller) Branches ¶
func (c *Controller) Branches() ([]agent.BranchInfo, error)
Branches lists saved conversation branches in this controller's session dir.
func (*Controller) Bypass ¶
func (c *Controller) Bypass() bool
Bypass is the legacy name for AutoApproveTools.
func (*Controller) Cancel ¶
func (c *Controller) Cancel()
Cancel aborts the in-flight turn. A goroutine blocked awaiting approval unblocks via the cancelled context.
func (*Controller) CheckpointHasBoundary ¶
func (c *Controller) CheckpointHasBoundary(turn int) bool
func (*Controller) Checkpoints ¶
func (c *Controller) Checkpoints() []checkpoint.Meta
Checkpoints lists the session's rewind points (one per user turn), oldest first.
func (*Controller) ClearGoal ¶
func (c *Controller) ClearGoal()
func (*Controller) ClearSession ¶
func (c *Controller) ClearSession() error
ClearSession discards the current conversation without preserving it in resume/history, then rotates to a clean session carrying the same system prompt.
func (*Controller) Close ¶
func (c *Controller) Close()
Close stops plugin subprocesses and releases resources. A session that ever started fires SessionEnd so a teardown hook runs.
func (*Controller) Commands ¶
func (c *Controller) Commands() []command.Command
Commands returns the loaded custom slash commands.
func (*Controller) Compact ¶
func (c *Controller) Compact(ctx context.Context, instructions string) error
Compact runs one compaction pass on the executor's session on demand. instructions is optional `/compact <focus>` guidance steering what to keep.
func (*Controller) CompactRatio ¶
func (c *Controller) CompactRatio() float64
CompactRatio returns the auto-compaction threshold as a fraction of the window (0 when the executor is unset). The status line shows headroom against it.
func (*Controller) Compose ¶
func (c *Controller) Compose(text string) string
Compose applies the plan-mode marker to a turn's text when plan mode is on, returning the message to actually send to the model. The frontend keeps showing the raw text as the user bubble.
func (*Controller) ComposeSynthetic ¶
func (c *Controller) ComposeSynthetic(text string) string
ComposeSynthetic is a lighter compose path for controller-injected messages (e.g. planApprovedMessage after plan approval). Unlike Compose, it does not re-inject plan mode markers, goals, or memory — those are already part of the session context. It applies only transformations that synthetic messages need (currently a no-op; will apply reasoning language when that feature lands).
func (*Controller) ConfiguredMCPNames ¶
func (c *Controller) ConfiguredMCPNames() []string
func (*Controller) ConnectCodegraphMCPServer ¶
func (c *Controller) ConnectCodegraphMCPServer(cfg *config.Config) (int, error)
ConnectCodegraphMCPServer connects the built-in CodeGraph server using an already-resolved config. Desktop uses this after saving user-level settings so a stale project config cannot override the just-applied choice.
func (*Controller) ConnectConfiguredMCPServer ¶
func (c *Controller) ConnectConfiguredMCPServer(name string) (int, error)
func (*Controller) ConnectMCPServer ¶
func (c *Controller) ConnectMCPServer(e config.PluginEntry) (int, error)
ConnectMCPServer connects an MCP server entry for this session without writing it to config. Desktop owns config placement so it can keep user-level settings out of project fairpeer.toml while preserving the CLI AddMCPServer semantics.
func (*Controller) ContextSnapshot ¶
func (c *Controller) ContextSnapshot() (int, int)
ContextSnapshot returns (promptTokens, contextWindow) from the most recent turn. Both zero means no data yet — a gauge hides itself.
func (*Controller) CustomCommand ¶
func (c *Controller) CustomCommand(input string) (sent string, found bool)
CustomCommand resolves a "/name args…" line against the loaded custom slash commands, returning the rendered prompt to send (found=false when no command matches). It does not apply the plan-mode marker — call Compose for that.
func (*Controller) DeleteExpertCollab ¶
func (c *Controller) DeleteExpertCollab(ordinal int) error
DeleteExpertCollab removes the Nth expert-collab message (0-based among expert_team_collab tool messages) from the session and re-persists. This is the "不采纳" affordance: a user discards a collaboration from both the stored transcript and the model's context. The ordinal is stable — deleting one doesn't shift the others' ordinals — because it's recomputed over the remaining set each call. Refused while a turn is running, like the append.
func (*Controller) DisabledSkills ¶
func (c *Controller) DisabledSkills() []skill.Skill
DisabledSkills returns all discoverable skills that are disabled in config.
func (*Controller) DisconnectMCPServer ¶
func (c *Controller) DisconnectMCPServer(name string) bool
DisconnectMCPServer disconnects a live server for this session without touching config — the connector toggle's "off". Its tools vanish next turn; it reconnects on the next session start, or now via ConnectConfiguredMCPServer (the "on"). Reports whether a live server was actually disconnected.
func (*Controller) DisconnectedMCPNames ¶
func (c *Controller) DisconnectedMCPNames() []string
func (*Controller) DreamInFlight ¶
func (c *Controller) DreamInFlight(kind agent.DreamKind) bool
DreamInFlight reports whether a run of the given kind is currently executing.
func (*Controller) EmitExpertCollab ¶
func (c *Controller) EmitExpertCollab(collab event.Collab)
EmitExpertCollab fires an ExpertCollab event on this controller's sink so the frontend renders the folded card. Kept separate from AppendExpertCollab so a caller can append-then-emit (or emit for a panel-initiated run whose data it already has) without coupling the two.
func (*Controller) EnableInteractiveApproval ¶
func (c *Controller) EnableInteractiveApproval()
EnableInteractiveApproval swaps the executor's gate for one that routes approval decisions to the frontend via ApprovalRequest events, and wires the controller in as the executor's Asker so the `ask` tool can question the user. Interactive frontends (chat, desktop) call this; the headless run keeps the silent gate and a nil asker from setup.
func (*Controller) ForgetMemory ¶
func (c *Controller) ForgetMemory(name string) error
ForgetMemory deletes a saved auto-memory by name — the panel/TUI delete action, the manual counterpart to the model's `forget` tool. It queues a turn-tail note so the deletion applies this session (the cached prefix still lists the fact until the next session re-folds the index).
func (*Controller) Fork ¶
func (c *Controller) Fork(turn int) (string, error)
Fork branches the conversation at the start of turn into a NEW session file, preserving the current one as the branch point, and switches to the branch. Code is untouched (it's a conversation operation). Like a conversation rewind it needs the live boundary, so it is unavailable for resumed-session turns and refused while a turn runs. Returns the new session path.
func (*Controller) ForkNamed ¶
func (c *Controller) ForkNamed(turn int, name string) (string, error)
func (*Controller) ForkSession ¶
func (c *Controller) ForkSession(turn int, name string) (string, error)
ForkSession copies the conversation at the start of turn into a new session file without switching this controller to it. Desktop uses this to open the branch in a new tab while the source tab keeps its current transcript.
func (*Controller) Goal ¶
func (c *Controller) Goal() string
func (*Controller) GoalStatus ¶
func (c *Controller) GoalStatus() string
func (*Controller) GoalStrict ¶
func (c *Controller) GoalStrict() bool
GoalStrict reports whether the active goal is under strict enforcement.
func (*Controller) HasRefs ¶
func (c *Controller) HasRefs(line string) bool
HasRefs reports whether a line contains any resolvable @references, so a frontend can decide to resolve off its event loop only when needed.
func (*Controller) History ¶
func (c *Controller) History() []provider.Message
History returns the executor's current message log (for repopulating a resumed frontend's view).
func (*Controller) HookRunner ¶
func (c *Controller) HookRunner() *hook.Runner
HookRunner returns the session's hook runner (nil-safe; may hold zero hooks), so a frontend can list the active hooks via `/hooks`.
func (*Controller) Host ¶
func (c *Controller) Host() *plugin.Host
Host returns the running MCP host (nil when no plugins), for frontends that list servers / resolve MCP prompts.
func (*Controller) ImportMCPEntries ¶
func (c *Controller) ImportMCPEntries(entries []config.PluginEntry) (total, added, updated, connected, failed, skipped int, err error)
ImportMCPEntries persists selected MCP entries and attempts to connect them live. A connection failure does not roll back the config import: the user can fix local dependencies and reconnect in a later session.
func (*Controller) InheritLifecycleFrom ¶
func (c *Controller) InheritLifecycleFrom(prev *Controller)
InheritLifecycleFrom carries lifecycle state from a previous controller into this one, preventing duplicate SessionStart hooks and preserving the turn counter across controller rebuilds (e.g. model switch that preserves the conversation). Call this before the first turn on the new controller.
func (*Controller) IsDestroyingSession ¶
func (c *Controller) IsDestroyingSession(sessionPath string) bool
IsDestroyingSession reports whether the given session path is currently in the destroy window (between BeginDestroySession and artifact removal).
func (*Controller) Jobs ¶
func (c *Controller) Jobs() []jobs.View
Jobs returns the still-running background jobs for the status bar (nil when background jobs are disabled).
func (*Controller) Label ¶
func (c *Controller) Label() string
Label returns the human-readable model label, e.g. "openai/gpt-4o".
func (*Controller) LastDreamRun ¶
LastDreamRun exposes the most recent recorded run of a kind for this session, for the desktop "last run" status display.
func (*Controller) LastUsage ¶
func (c *Controller) LastUsage() *provider.Usage
LastUsage returns the most recent turn's token telemetry (nil before the first turn), so frontends can derive the prompt cache-hit rate for the status line.
func (*Controller) MCPPrompt ¶
func (c *Controller) MCPPrompt(ctx context.Context, input string) (sent string, found bool, err error)
MCPPrompt resolves a "/mcp__server__prompt args…" line: it maps the positional args onto the prompt's declared arguments and fetches the rendered prompt from the MCP server (an async prompts/get). found is false when no such prompt exists; err carries a fetch failure. Honours ctx.
func (*Controller) Memory ¶
func (c *Controller) Memory() *memory.Set
Memory returns the loaded memory snapshot (nil when memory is disabled), for frontends that surface a memory panel or the /memory command. The returned *Set is immutable — mutations go through QuickAdd / SaveDoc.
func (*Controller) NewSession ¶
func (c *Controller) NewSession() error
NewSession snapshots the current conversation, rotates to a fresh file, and resets the executor to a clean session carrying the same system prompt. It ends the old session and starts the new one for lifecycle hooks.
func (*Controller) Pause ¶
func (c *Controller) Pause()
Pause requests a graceful pause of the in-flight turn. Unlike Cancel (which aborts and discards partial work), Pause lets the current step finish, then freezes the agent with full state preserved so ResumeTurn continues from the break point. No-op when no turn is running or when already paused. Safe from any goroutine; the run loop checks the pause request at the top of each step.
func (*Controller) Paused ¶
func (c *Controller) Paused() bool
Paused reports whether the in-flight turn is currently frozen on a pause (between steps, awaiting ResumeTurn). False when running normally, not running, or no executor is bound.
func (*Controller) PlanMode ¶
func (c *Controller) PlanMode() bool
PlanMode reports whether outgoing turns currently receive the plan-mode marker. Frontends use it after Compose because auto-plan may flip the mode.
func (*Controller) Profile ¶
func (c *Controller) Profile() ProfileView
Profile returns the active mode's portrait for the preference panel. Read-only — saves go through SaveDoc (the profile path is whitelisted). The panel calls this on open to populate its editor.
func (*Controller) QueueMemory ¶
func (c *Controller) QueueMemory(note string)
QueueMemory implements memory.Queue: when the model runs the remember/forget tool, the tool calls this with a note that rides the next turn so the change applies this session without touching the cache-stable prefix. It also refreshes the snapshot a memory panel reads.
func (*Controller) QuickAdd ¶
QuickAdd appends a one-line note to the doc-memory file for scope (project fairpeer.md by default) — the write side of "#<note>". Returns the file written.
func (*Controller) RemoveMCPServer ¶
func (c *Controller) RemoveMCPServer(name string) (disconnected bool, err error)
RemoveMCPServer disconnects a live MCP server — its tools vanish from the next turn — and removes it from the config file. It reports whether a live server was disconnected; an error only when the name is neither connected nor in config (or the config save fails). A server declared in .mcp.json disconnects for this session but returns on the next start, since that file isn't ours to edit.
func (*Controller) ReplayPendingPrompts ¶
func (c *Controller) ReplayPendingPrompts()
ReplayPendingPrompts re-emits the ApprovalRequest / AskRequest event for every prompt currently blocking the run loop. A frontend that reconnected or reloaded after the original event has no way to rebuild its approval/ask modal otherwise, so the blocked gate goroutine stays stuck forever while the session shows a "waiting" status with no actionable prompt. promptMu serialises Ask and requestApproval, so in practice at most one prompt is outstanding; the loops stay general so a future concurrent prompt would still replay correctly.
func (*Controller) ResolveRefs ¶
ResolveRefs resolves the @references in a line into a context block. Returns either a plain string (text-only refs) or []provider.ContentPart (when image refs are present, for multimodal vision support). Per-reference error strings are returned for any that failed. Safe to call off a frontend's event loop; honours ctx for the resource reads.
func (*Controller) Resume ¶
func (c *Controller) Resume(s *agent.Session, path string)
Resume seeds the session from a loaded transcript and pins the active file to its path so auto-save keeps appending there.
func (*Controller) ResumeTurn ¶
func (c *Controller) ResumeTurn()
ResumeTurn unblocks a paused turn. Named ResumeTurn (not Resume) to avoid a clash with the session-lifecycle Resume(session, path). No-op when not paused. Safe from any goroutine.
func (*Controller) Rewind ¶
func (c *Controller) Rewind(turn int, scope RewindScope) error
Rewind restores the session to the start of `turn`: Code reverts every file that turn (or a later one) changed to its pre-turn content; Conversation truncates the message log back to that turn; Both does both. Refused while a turn is running. Conversation rewind relies on the live boundary recorded at turn start, so it is unavailable for turns inherited from a resumed session (code rewind still works). Frontends re-render their transcript from History after the call.
func (*Controller) Run ¶
func (c *Controller) Run(ctx context.Context, input string) error
Run executes a turn synchronously, returning the agent's error. Used by the headless `fairpeer run` path, where the Sink renders to stdout and the caller just needs the exit status — no TurnDone event, no cancel bookkeeping.
func (*Controller) RunShell ¶
func (c *Controller) RunShell(command string)
RunShell executes a shell command directly (bypassing the model) and streams the output as ToolDispatch/ToolProgress/ToolResult events. It uses the same bash-tool infrastructure (shell resolution, timeout) and shares the runGuarded lock with model turns — only one can run at a time. User-invoked "!" commands run without the OS sandbox (the user typed the command explicitly).
func (*Controller) RunSkill ¶
func (c *Controller) RunSkill(input string) (sent string, found bool)
RunSkill resolves a "/<name> args…" line against the loaded skills, returning the skill's rendered body to send as a turn (found=false when no skill matches). Invoking a skill by slash always inlines its body — the model reads and follows the playbook in the main loop; a subagent skill's isolation is only engaged when the model calls it via run_skill / the dedicated tool. The caller applies Compose for plan-mode/memory framing.
func (*Controller) RunTurn ¶
func (c *Controller) RunTurn(ctx context.Context, input string) error
RunTurn executes one foreground turn synchronously through the same lifecycle used by interactive frontends: auto-plan, transient memory/background-job composition, checkpoints, hooks, and plan approval. It is for transports that need a blocking request/response boundary, such as ACP session/prompt.
func (*Controller) Running ¶
func (c *Controller) Running() bool
Running reports whether a turn is currently in flight.
func (*Controller) SaveDoc ¶
func (c *Controller) SaveDoc(path, body string) (string, error)
SaveDoc overwrites a recognized memory doc with body — the save side of the desktop panel's in-place editor. Returns the file written.
func (*Controller) Send ¶
func (c *Controller) Send(input string)
Send starts a turn with an uncomposed message. The controller applies auto-plan, plan-mode, memory, and background-job framing inside the async turn path so frontends do not block on classifier I/O.
func (*Controller) SendWithRaw ¶
func (c *Controller) SendWithRaw(input, raw string)
SendWithRaw starts a turn with separate model input and raw prompt text. The raw prompt is used only for auto-plan scoring; it deliberately excludes resolved @-reference payloads so referenced file contents cannot inflate the complexity score.
func (*Controller) SessionCache ¶
func (c *Controller) SessionCache() (hit, miss int)
SessionCache returns cumulative cache hit/miss prompt tokens for the session, so a frontend can render the aggregate (session-wide) cache-hit rate — steadier than the single-turn rate and unaffected by compaction. some providers do not report cache tokens, so this returns zeros for providers that omit cache stats.
func (*Controller) SessionDir ¶
func (c *Controller) SessionDir() string
SessionDir reports the directory new session files land in ("" disables persistence), so the caller can decide whether to mint a path.
func (*Controller) SessionPath ¶
func (c *Controller) SessionPath() string
SessionPath reports the file the current conversation auto-saves to ("" when persistence is disabled), so a history view can mark the active session.
func (*Controller) SetAutoApproveTools ¶
func (c *Controller) SetAutoApproveTools(on bool)
SetAutoApproveTools turns YOLO/full-access mode on or off for the session: while on, every tool approval request is auto-allowed (writers and bash run without asking). Ask requests and plan approval still reach the user. Deny rules still block. Runtime-only — never written to config.
func (*Controller) SetAutoPlan ¶
func (c *Controller) SetAutoPlan(mode string)
SetAutoPlan updates the interactive auto-plan gate for subsequent turns.
func (*Controller) SetBypass ¶
func (c *Controller) SetBypass(on bool)
SetBypass is the legacy name for SetAutoApproveTools. Keep it for existing desktop/serve bindings and CLI code that still uses the bypass wording.
func (*Controller) SetContextFilter ¶
func (c *Controller) SetContextFilter(fn func([]provider.Message) []provider.Message)
SetContextFilter installs a read-side transform applied to session messages before they reach the model. It is the seam the experts engine uses to keep a full-fidelity expert-collab message in the transcript while showing the model a synthesis-only projection of it. Pass nil to restore the identity filter. Safe to call before or after the executor is built; applied (re-applied) on the next executor wiring.
func (*Controller) SetDisplayRecorder ¶
func (c *Controller) SetDisplayRecorder(fn func(content, display string))
SetDisplayRecorder installs an optional hook used by frontends that persist a shorter user-facing transcript than the fully composed model prompt.
func (*Controller) SetGoal ¶
func (c *Controller) SetGoal(goal string)
SetGoal stores a session-scoped active goal. Compose injects it into outgoing user turns, not the system prompt or tool schema, so it does not disturb the cache-stable prefix.
func (*Controller) SetGoalStrict ¶
func (c *Controller) SetGoalStrict(strict bool)
SetGoalStrict toggles strict enforcement on the active goal: when on, the goal may not be declared complete unless the session produced tool-backed work, so a bare "I'm done" cannot satisfy it. Setting strict on a stopped/no goal is remembered but has no effect until a goal runs. (PR #4827 goal enforcement.)
func (*Controller) SetMode ¶
func (c *Controller) SetMode(plan, autoApproveTools bool)
SetMode applies plan (read-only) and tool auto-approval together so a turn submitted right after a composer mode switch can't observe a half-applied gate. Turning tool auto-approval on drains any pending tool approval.
func (*Controller) SetPlanMode ¶
func (c *Controller) SetPlanMode(v bool)
SetPlanMode flips the executor's read-only gate without touching the cache-stable prompt prefix, and remembers the state so Compose can prepend the plan-mode marker to outgoing turns.
func (*Controller) SetRAGScope ¶
func (c *Controller) SetRAGScope(scope string)
SetRAGScope sets the knowledge-base collection that auto-injection searches. Pass "" to disable injection for this session ("不使用" in the Composer dropdown). The scope is read at the start of each turn (under c.mu) so a mid-flight change can't race the RAG call. Mirrors the per-tab pattern of SetToolApprovalMode.
func (*Controller) SetRiskOverrides ¶
func (c *Controller) SetRiskOverrides(overrides map[string]permission.RiskClass)
SetRiskOverrides installs the per-tool risk-class map (SPEC v2 §3.2A) built from [[plugins]] risk config. Called once at boot after plugins are resolved. A nil/empty map keeps the safe default (MCP tools external-risk).
func (*Controller) SetSessionPath ¶
func (c *Controller) SetSessionPath(p string)
SetSessionPath pins where auto-save lands (a fresh session file minted by the caller when no resume path applies).
func (*Controller) SetSkillEnabled ¶
func (c *Controller) SetSkillEnabled(name string, enabled bool) error
SetSkillEnabled persists a skill enable/disable preference. The caller should rebuild the controller for the prompt/tool registry to reflect it immediately.
func (*Controller) SetToolApprovalMode ¶
func (c *Controller) SetToolApprovalMode(mode string)
SetToolApprovalMode changes the runtime approval posture for permission-gated tools. It does not answer business asks or plan approval.
func (*Controller) SkillEnabled ¶
func (c *Controller) SkillEnabled(name string) bool
SkillEnabled reports whether a discoverable skill is enabled.
func (*Controller) Skills ¶
func (c *Controller) Skills() []skill.Skill
Skills returns the discoverable skills (for the slash menu and `/skills`). When a live Store is available, scan it on demand so skills installed during this session appear without rewriting the cache-stable system prompt.
func (*Controller) Snapshot ¶
func (c *Controller) Snapshot() error
Snapshot writes the executor's conversation to the active session file. No-op when persistence is unavailable or the session has never been used (no user interaction). Called after every turn so a crash loses at most one in-flight prompt.
func (*Controller) SnapshotActivity ¶
func (c *Controller) SnapshotActivity() error
SnapshotActivity writes the active conversation and marks the session as recently active. Use it only after a real user/model turn changes the transcript; switch/close snapshots should call Snapshot so they do not reorder recent-session pickers.
func (*Controller) Steer ¶
func (c *Controller) Steer(text string)
Steer queues mid-turn guidance without interrupting the in-flight request.
func (*Controller) SteerConsumed ¶
func (c *Controller) SteerConsumed() bool
SteerConsumed returns true when the steer queue is empty after the last consume.
func (*Controller) Submit ¶
func (c *Controller) Submit(input string)
Submit is the one-call entry for a simple frontend: it takes raw user input and does everything — slash-command dispatch, @-reference expansion, plan-mode composition — emitting all output as events. The HTTP/SSE server uses this so a browser client only POSTs the typed line.
Slash commands route to the matching primitive: /compact, /new, and /clear run their session op and emit a Notice; /mcp__server__prompt and custom /commands resolve to a turn; an unknown slash emits a Notice. Anything else is a normal turn with its @-references resolved first.
func (*Controller) SubmitDisplay ¶
func (c *Controller) SubmitDisplay(display, input string)
SubmitDisplay runs input as a turn while remembering the user-facing display text for transcript replay when controller-side composition expands input.
func (*Controller) SummarizeFrom ¶
func (c *Controller) SummarizeFrom(ctx context.Context, turn int) error
SummarizeFrom compresses the conversation from turn onward into one summary; SummarizeUpTo compresses everything before it. Both are Claude Code's "summarize from/up to here" — they restructure the message log (keeping code untouched), so afterwards the per-turn boundaries no longer map and conversation rewind/fork report "unavailable" until new turns rebuild them (code rewind, file-based, is unaffected). Refused while a turn runs; need the live boundary.
func (*Controller) SummarizeUpTo ¶
func (c *Controller) SummarizeUpTo(ctx context.Context, turn int) error
func (*Controller) SwitchBranch ¶
func (c *Controller) SwitchBranch(ref string) (agent.BranchInfo, error)
func (*Controller) ToolApprovalMode ¶
func (c *Controller) ToolApprovalMode() string
func (*Controller) TriggerDistill ¶
TriggerDistill runs a Distill workflow-extraction pass on demand. See TriggerDream.
func (*Controller) TriggerDream ¶
TriggerDream runs a Dream consolidation pass on demand, blocking until it finishes or times out. Returns the run record (status/error included). A false "ran" means the run did not execute (e.g. disabled, or already running). Intended for the desktop "run now" button.
func (*Controller) Turn ¶
func (c *Controller) Turn() int
Turn returns the current turn number (0 before the first submit).
func (*Controller) WorkspaceRoot ¶
func (c *Controller) WorkspaceRoot() string
WorkspaceRoot returns the workspace root for this controller's session (the directory that file-writers and @-references are scoped to). Empty means no scoping is in effect.
type GoalCommand ¶
type GoalCommand struct {
Action GoalCommandAction
Text string
}
func ParseGoalCommand ¶
func ParseGoalCommand(input string) (GoalCommand, bool)
type GoalCommandAction ¶
type GoalCommandAction int
const ( GoalCommandStatus GoalCommandAction = iota + 1 GoalCommandSet GoalCommandClear )
type Options ¶
type Options struct {
Runner agent.Runner
Executor *agent.Agent
DreamProvider provider.Provider // lightweight model for dream/distill; nil = main
Sink event.Sink
Policy permission.Policy
Label string
SystemPrompt string
SessionDir string
SessionPath string
Host *plugin.Host
Commands []command.Command
Skills []skill.Skill
AllSkills []skill.Skill
SkillStore *skill.Store
AllSkillStore *skill.Store
Hooks *hook.Runner
Memory *memory.Set
Cleanup func()
// Jobs is the session-scoped background-job manager (nil disables background jobs).
Jobs *jobs.Manager
// Registry is the executor's live tool set, and PluginCtx the session-scoped
// context; both are needed for hot-adding MCP servers via AddMCPServer.
Registry *tool.Registry
PluginCtx context.Context
// WorkspaceRoot is the project root checkpoint restores are confined to ("" =
// no confinement). Frontends pass the cwd they launched the session in.
WorkspaceRoot string
AutoPlan string
// GoalJudge enables the independent goal judge: when the model reports
// [goal:complete], a separate LLM call verifies completion based on the
// transcript. nil disables the judge (model self-report is trusted).
GoalJudge func(ctx context.Context, prov provider.Provider, transcript []provider.Message, condition string) agent.GoalVerdict
Classifier autoPlanClassifier
// OnRemember, when set, is invoked with a new allow rule the user chose to
// persist to disk (e.g. "Bash(go test:*)"). The callback is wired into the
// permission Gate on EnableInteractiveApproval.
OnRemember func(rule string) RememberResult
// OnTurnEnd, when set, is invoked after each turn completes (both the
// interactive and headless paths). It receives the last user message and
// the final assistant reply text, enabling passive memory capture: boot
// wires this to an LLM fact extractor that saves candidate memories with
// Status "pending" for the user to confirm. The callback must be
// fire-and-forget — it runs on the turn's goroutine and any error must be
// swallowed internally so it can never stall or crash the foreground turn.
// A nil callback disables auto-capture entirely.
OnTurnEnd func(ctx context.Context, lastUserMsg, lastAssistant string)
// RAGContextFn, when set, is called with the user's message before each
// turn to auto-retrieve knowledge-base context. The returned string is
// prepended to the user's input (like @reference context). A nil callback
// or "" return disables injection for that turn. collection scopes the
// retrieval to one knowledge-base collection ("" = let the callback decide,
// typically meaning "don't inject" for the auto-injection path).
RAGContextFn func(ctx context.Context, query, collection string) string
}
Options carries the already-built pieces setup assembles. Lifecycle metadata lets the controller mint and rotate session files; Host/Commands are surfaced to frontends that resolve MCP prompts and slash commands.
type PlanCommand ¶
type PlanCommand struct {
Off bool // true for "/plan off"
Text string // the planning task for "/plan <text>"; empty for a bare toggle on
}
PlanCommand is the parsed form of a /plan slash command. /plan with no args or /plan <text> enters plan mode (text, when present, is sent as the planning turn); /plan off exits. See Controller.applyPlanCommand for the dispatch.
func ParsePlanCommand ¶
func ParsePlanCommand(input string) (PlanCommand, bool)
ParsePlanCommand recognizes "/plan", "/plan off", and "/plan <text>". Returns ok=false for anything that isn't a /plan command. Mirrors ParseGoalCommand's tolerant spacing (matches "/plan", "/plan\t…", "/plan …").
type ProfileView ¶
ProfileView is the payload the workspace preference panel reads: the path of the active mode's portrait file and its current contents. Path is "" when the user config dir is unresolvable; Content is "" when the file does not exist yet (a fresh mode the user has never written to).
type ProviderAutoPlanClassifier ¶
type ProviderAutoPlanClassifier struct {
// contains filtered or unexported fields
}
func NewProviderAutoPlanClassifier ¶
func NewProviderAutoPlanClassifier(prov provider.Provider) *ProviderAutoPlanClassifier
type RememberResult ¶
RememberResult describes what happened when an approval rule was persisted.
type RewindScope ¶
type RewindScope int
RewindScope selects what a Rewind restores.
const ( RewindCode RewindScope = iota // files only RewindConversation // message log only RewindBoth // both )
type SessionDestroyHandle ¶
type SessionDestroyHandle struct {
// contains filtered or unexported fields
}
SessionDestroyHandle separates session teardown into phases so background jobs can drain before artifacts are removed.
func (*SessionDestroyHandle) Wait ¶
func (h *SessionDestroyHandle) Wait()
Wait cancels background jobs for the session and waits for them to drain.
type SlashItem ¶
type SlashItem struct {
Label string `json:"label"`
Insert string `json:"insert"`
Hint string `json:"hint"`
Descend bool `json:"descend"`
}
SlashItem is one slash-completion suggestion. Insert is the token text placed at the current argument position (callers replace from the token's start, see SlashArgItems' returned offset); Descend hints the menu to re-open one level deeper after accepting (e.g. "/mcp " → "/mcp add ").
func SlashArgItems ¶
SlashArgItems completes the arguments of a management slash command (everything after the command word). It returns the suggestions filtered by the token being typed and the byte offset where that token begins, so a caller replaces just that token. Only structured commands participate (/mcp /model /skills /hooks /effort /auto-plan /theme /language); others yield nil. Single source of truth for CLI + desktop.