smart_guide

package
v0.260806.1 Latest Latest
Warning

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

Go to latest
Published: Aug 6, 2026 License: MPL-2.0 Imports: 18 Imported by: 0

Documentation

Index

Constants

View Source
const (
	AgentTypeTinglyBox  = "tingly-box" // @tb
	AgentTypeClaudeCode = "claude"     // @cc
	AgentTypeMock       = "mock"
)

AgentType constants. Defined here (not in agentboot core) so Smart Guide can extend the agent-type space without modifying agentboot.

View Source
const SendFileMaxSize int64 = 50 * 1024 * 1024

SendFileMaxSize is the default maximum file size for outbound file sends (50MB).

Variables

View Source
var DefaultBashAllowlist = []string{
	"ls", "pwd", "cd", "cat", "tree",
	"find", "grep", "head", "tail", "wc", "sort", "uniq",
	"mkdir", "cp", "mv", "touch", "echo", "which",
	"git", "go", "npm", "pnpm", "yarn",
	"curl", "wget",
}

DefaultBashAllowlist defines the default allowed bash commands. Commands outside this list trigger the approval callback (if configured).

View Source
var PromptFS embed.FS

Functions

func BuildTools

func BuildTools(
	executor *ToolExecutor,
	chatID string,
	getStatusFunc func(chatID string) (*StatusInfo, error),
	updateProjectFunc func(chatID string, projectPath string) error,
	toolCtx *ToolContext,
	skills skill.Skills,
) []afk.Tool

BuildTools assembles the Smart Guide toolset for the ReAct engine.

The set mirrors the previous agentscope registration: bash, get_status, change_workdir, native read/write/edit, and (when a SendFile callback is available) send_file — plus activate_skill when any skills were discovered.

func CanCreateAgent

func CanCreateAgent(baseURL, apiKey, smartGuideProvider, smartGuideModel string) bool

CanCreateAgent reports whether a SmartGuide agent can be created with the given configuration.

func DefaultGreeting

func DefaultGreeting() string

DefaultGreeting returns the default greeting for new users

func DefaultSystemPrompt

func DefaultSystemPrompt() string

DefaultSystemPrompt returns the default system prompt for @tb

func DetectHandoffCommand

func DetectHandoffCommand(text string) (agentboot.AgentType, bool, string)

DetectHandoffCommand detects if text is a handoff command. Returns the target agent type, whether it's a handoff, and any remaining text after the command. Examples:

  • "@cc" -> (AgentTypeClaudeCode, true, "")
  • "@cc help me" -> (AgentTypeClaudeCode, true, "help me")
  • "hello" -> ("", false, "")

func DetectMediaType

func DetectMediaType(path string) string

DetectMediaType returns "image" for image file extensions, "document" for all others.

func GetAgentTypeString

func GetAgentTypeString(agentType agentboot.AgentType) string

GetAgentTypeString returns the string representation of an agent type

func HandoffToCCPrompt

func HandoffToCCPrompt() string

HandoffToCCPrompt returns the handoff prompt when switching to Claude Code

func HandoffToTBPrompt

func HandoffToTBPrompt() string

HandoffToTBPrompt returns the handoff prompt when returning to Smart Guide

func LoadPrompt

func LoadPrompt(name string) (string, error)

LoadPrompt reads a prompt file from the embedded filesystem

func MustLoadPrompt

func MustLoadPrompt(name string) string

MustLoadPrompt reads a prompt file or panics

func SerializeState

func SerializeState(state *HandoffState) ([]byte, error)

SerializeState serializes handoff state to JSON

Types

type AgentConfig

type AgentConfig struct {
	SmartGuideConfig *SmartGuideConfig
	ToolExecutor     *ToolExecutor

	// HTTP endpoint configuration (resolved from TBClient by caller).
	BaseURL string
	APIKey  string
	Model   string

	// Callback functions for internal tools.
	GetStatusFunc     func(chatID string) (*StatusInfo, error)
	GetProjectFunc    func(chatID string) (string, bool, error)
	UpdateProjectFunc func(chatID string, projectPath string) error

	// Approval context for non-allowlisted commands.
	Approver Approver
	ChatID   string
	Platform string
	BotUUID  string

	// ToolContext for file send capability and cross-path approval. If nil,
	// the send_file tool is not registered.
	ToolCtx *ToolContext

	// SessionLog is the chat's append-only conversation log. When set, the run
	// is checkpointed to it as each step completes, so an interrupted turn keeps
	// the work it already did. Nil disables mid-run persistence.
	SessionLog afk.Log
}

AgentConfig holds the configuration for creating a TinglyBoxAgent.

type ApprovalCallback

type ApprovalCallback func(ctx context.Context, req ApprovalRequest) (approved bool, err error)

ApprovalCallback is called when a command requires user approval Returns (approved, error) - if error is non-nil, the approval process failed

type ApprovalRequest

type ApprovalRequest struct {
	Command string   // Command to execute
	Args    []string // Command arguments
	Reason  string   // Reason for approval request
}

ApprovalRequest represents a request for user approval

type Approver

type Approver interface {
	OnApproval(ctx context.Context, req agentboot.ApprovalRequestEvent) (agentboot.ApprovalResponse, error)
}

Approver answers a permission request for a non-whitelisted command. *imchannel.IMPrompter satisfies this via its OnApproval method.

type BashTool

type BashTool struct {
	Executor        *ToolExecutor
	AllowedCommands []string
}

BashTool executes shell commands with an allowlist + approval gate.

func NewBashTool

func NewBashTool(executor *ToolExecutor, allowlist []string) *BashTool

NewBashTool creates a new bash tool bound to the given executor and allowlist.

func (*BashTool) Call

func (t *BashTool) Call(ctx context.Context, rawInput json.RawMessage) (string, error)

Call runs a bash command, gating non-allowlisted base commands behind the approval callback. On approval (or for allowlisted commands) it executes the command and tracks any working-directory change so `cd` persists within the session.

func (*BashTool) Param

func (t *BashTool) Param() anthropic.BetaToolParam

type ChangeDirTool

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

ChangeDirTool changes the bound project directory and persists it.

func NewChangeDirTool

func NewChangeDirTool(executor *ToolExecutor, chatID string, updateProjectFunc func(chatID string, projectPath string) error) *ChangeDirTool

NewChangeDirTool creates a new ChangeDirTool. chatID is injected from agent config (not taken from model input).

func (*ChangeDirTool) Call

func (t *ChangeDirTool) Call(ctx context.Context, rawInput json.RawMessage) (string, error)

func (*ChangeDirTool) Param

type CompletionResult

type CompletionResult struct {
	Success    bool
	DurationMS int64
	SessionID  string
	Error      string
}

CompletionResult reports the outcome of an ExecuteWithHandler run.

type EditFileTool

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

EditFileTool replaces an exact, unique occurrence of old_text in a file.

func NewEditFileTool

func NewEditFileTool(executor *ToolExecutor) *EditFileTool

NewEditFileTool constructs an EditFileTool bound to the given executor.

func (*EditFileTool) Call

func (t *EditFileTool) Call(ctx context.Context, rawInput json.RawMessage) (string, error)

func (*EditFileTool) Param

type GetStatusTool

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

GetStatusTool returns the current bot/session status.

func NewGetStatusTool

func NewGetStatusTool(executor *ToolExecutor, chatID string, getStatusFunc func(chatID string) (*StatusInfo, error)) *GetStatusTool

NewGetStatusTool creates a new GetStatusTool. chatID is injected from agent config (not taken from model input).

func (*GetStatusTool) Call

func (t *GetStatusTool) Call(ctx context.Context, rawInput json.RawMessage) (string, error)

func (*GetStatusTool) Param

type HandoffManager

type HandoffManager struct {
}

HandoffManager handles handoff operations between agents

func NewHandoffManager

func NewHandoffManager() *HandoffManager

NewHandoffManager creates a new handoff manager

func (*HandoffManager) ExecuteHandoff

func (hm *HandoffManager) ExecuteHandoff(ctx context.Context, state *HandoffState) *HandoffResult

ExecuteHandoff performs a handoff from one agent to another

type HandoffResult

type HandoffResult struct {
	Success   bool   `json:"success"`
	FromAgent string `json:"from_agent"`
	ToAgent   string `json:"to_agent"`
	Message   string `json:"message"`
	NextHint  string `json:"next_hint"`
	Error     string `json:"error,omitempty"`
}

HandoffResult represents the result of a handoff operation

type HandoffState

type HandoffState struct {
	FromAgent        string    `json:"from_agent"`
	ToAgent          string    `json:"to_agent"`
	Timestamp        time.Time `json:"timestamp"`
	ProjectPath      string    `json:"project_path"`
	SessionID        string    `json:"session_id"`
	ChatID           string    `json:"chat_id"`
	PreservedContext []byte    `json:"preserved_context,omitempty"`
}

HandoffState represents the state during a handoff

func DeserializeState

func DeserializeState(data []byte) (*HandoffState, error)

DeserializeState deserializes handoff state from JSON

type ModelConfig

type ModelConfig struct {
	Provider string `json:"provider"` // "openai", "anthropic", etc.
	Model    string `json:"model"`
	APIKey   string `json:"api_key"`
	BaseURL  string `json:"base_url,omitempty"`
}

ModelConfig holds the model configuration

type ReadFileTool

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

ReadFileTool reads the contents of a file, optionally limited to a line range.

func NewReadFileTool

func NewReadFileTool(executor *ToolExecutor) *ReadFileTool

NewReadFileTool constructs a ReadFileTool bound to the given executor.

func (*ReadFileTool) Call

func (t *ReadFileTool) Call(ctx context.Context, rawInput json.RawMessage) (string, error)

func (*ReadFileTool) Param

type SendFileParams

type SendFileParams struct {
	FilePath string `json:"file_path"`
	Caption  string `json:"caption,omitempty"`
}

SendFileParams defines the parameters for the send_file tool.

type SendFileTool

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

SendFileTool sends a local file to the user via the IM bot.

func NewSendFileTool

func NewSendFileTool(executor *ToolExecutor, toolCtx *ToolContext) *SendFileTool

NewSendFileTool creates a SendFileTool with the default 50MB limit.

func NewSendFileToolWithLimit

func NewSendFileToolWithLimit(executor *ToolExecutor, toolCtx *ToolContext, maxSize int64) *SendFileTool

NewSendFileToolWithLimit creates a SendFileTool with a custom size limit (for testing).

func (*SendFileTool) Call

func (t *SendFileTool) Call(ctx context.Context, rawInput json.RawMessage) (string, error)

Call executes the send_file tool.

func (*SendFileTool) Param

Param describes the send_file tool to the model.

type SessionStore

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

SessionStore persists Smart Guide conversations as append-only logs, one file per chat. We are anthropic-first, so there is no neutral message type: the stored shape is exactly what the model API consumes.

The store owns the chatID-to-path mapping and the archive semantics; the log format and its durability live in afk/session.

func NewSessionStore

func NewSessionStore(dataDir string) (*SessionStore, error)

NewSessionStore creates a session store rooted at dataDir. A blank dataDir disables persistence (returns nil, nil), mirroring the previous behavior.

func (*SessionStore) Clear

func (s *SessionStore) Clear(chatID string) error

Clear ends a chat's current Smart Guide session: the live log is archived (renamed with a timestamp suffix) rather than deleted, so /clear deactivates the conversation instead of destroying it — the same "closed, not erased" semantics remote/session.Manager.Close gives @cc sessions. The next Open for chatID sees no file and starts fresh; the archived log is left on disk.

func (*SessionStore) Open

func (s *SessionStore) Open(chatID string) (*session.Session, error)

Open returns a chat's live session log, importing a legacy whole-file session if this is the first time the chat has been opened since the format changed.

Callers that want a run to be checkpointed as it progresses hold on to this and hand it to the harness, instead of snapshotting the whole history at the end. A nil store yields a nil session, which the harness reads as "no durability wanted".

type SmartGuideConfig

type SmartGuideConfig struct {
	// Enabled determines if smart guide is active
	Enabled bool `json:"enabled"`

	// SystemPrompt is the custom system prompt (optional)
	SystemPrompt string `json:"system_prompt,omitempty"`

	// MaxIterations is the maximum number of tool use iterations
	MaxIterations int `json:"max_iterations"`

	// Temperature for LLM responses
	Temperature float64 `json:"temperature"`

	// Thinking selects the model's reasoning mode: "" (the model's own
	// default), "visible", "hidden", or "off". See afk.ThinkingMode.
	//
	// "visible" is what makes @tb render 💭 lines: reasoning only comes back
	// with content when it is asked for explicitly, and the streaming layer
	// shows it in verbose mode.
	Thinking string `json:"thinking,omitempty"`

	// Effort is how hard the model works on a turn: "" (the model's own
	// default), "low", "medium", "high", "xhigh", or "max". See
	// afk.EffortLevel. Separate axis from Thinking — that one is whether the
	// model reasons, this one is how much it spends overall.
	Effort string `json:"effort,omitempty"`

	// ToolsEnabled maps tool names to enabled state
	ToolsEnabled map[string]bool `json:"tools_enabled"`

	// HandoffCommands are the commands that trigger handoff
	HandoffCommands []string `json:"handoff_commands"`

	// Model configuration
	Model ModelConfig `json:"model"`

	// SessionTimeout is how long to remember context
	SessionTimeout time.Duration `json:"session_timeout"`
}

SmartGuideConfig holds the configuration for the smart guide agent

func DefaultSmartGuideConfig

func DefaultSmartGuideConfig() *SmartGuideConfig

DefaultSmartGuideConfig returns the default configuration

func LoadSmartGuideConfig

func LoadSmartGuideConfig() *SmartGuideConfig

LoadSmartGuideConfig loads smart guide config with custom settings For now, returns default config - settings will be loaded externally

func (*SmartGuideConfig) GetSystemPrompt

func (c *SmartGuideConfig) GetSystemPrompt() string

GetSystemPrompt returns the system prompt to use

func (*SmartGuideConfig) IsHandoffCommand

func (c *SmartGuideConfig) IsHandoffCommand(text string) bool

IsHandoffCommand checks if text is a handoff command

func (*SmartGuideConfig) IsToolEnabled

func (c *SmartGuideConfig) IsToolEnabled(toolName string) bool

IsToolEnabled checks if a tool is enabled

type StatusInfo

type StatusInfo struct {
	CurrentAgent   string `json:"current_agent"`
	SessionID      string `json:"session_id"`
	ProjectPath    string `json:"project_path"`
	WorkingDir     string `json:"working_dir"`
	HasRunningTask bool   `json:"has_running_task"`
	Whitelisted    bool   `json:"whitelisted"`
}

StatusInfo holds bot status information

type StreamHandler

type StreamHandler interface {
	OnMessage(msg any) error
	OnError(err error)
	OnComplete(result *CompletionResult)
}

StreamHandler receives streaming output and the completion signal from ExecuteWithHandler. The smart-guide agent runs an in-house ReAct loop on the Anthropic SDK (internal/afk.Engine), not agentboot's process pipeline, so it streams intermediate messages as plain maps via OnMessage and reports the final outcome via OnComplete.

type TinglyBoxAgent

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

TinglyBoxAgent is the Smart Guide agent (@tb). It runs an in-house ReAct loop (internal/afk.Engine) on the official Anthropic SDK, replacing the former tingly-agentscope runtime.

func NewTinglyBoxAgent

func NewTinglyBoxAgent(config *AgentConfig) (*TinglyBoxAgent, error)

NewTinglyBoxAgent creates a new Smart Guide agent.

func NewTinglyBoxAgentWithSession

func NewTinglyBoxAgentWithSession(config *AgentConfig, history []anthropic.BetaMessageParam) (*TinglyBoxAgent, error)

NewTinglyBoxAgentWithSession creates a Smart Guide agent seeded with prior conversation history (native Anthropic beta message params from the session store).

func (*TinglyBoxAgent) ExecuteWithHandler

func (a *TinglyBoxAgent) ExecuteWithHandler(
	ctx context.Context,
	prompt string,
	toolCtx *ToolContext,
	handler StreamHandler,
) (*agentboot.Result, error)

ExecuteWithHandler runs one user turn through the ReAct engine, streaming intermediate output to the handler and reporting completion. It returns an agentboot.Result for compatibility with the executor layer.

func (*TinglyBoxAgent) GetConfig

func (a *TinglyBoxAgent) GetConfig() *SmartGuideConfig

GetConfig returns the agent's configuration.

func (*TinglyBoxAgent) GetExecutor

func (a *TinglyBoxAgent) GetExecutor() *ToolExecutor

GetExecutor returns the tool executor.

func (*TinglyBoxAgent) GetGreeting

func (a *TinglyBoxAgent) GetGreeting() string

GetGreeting returns the default greeting for new users.

func (*TinglyBoxAgent) History

func (a *TinglyBoxAgent) History() []anthropic.BetaMessageParam

History returns the agent's current conversation history (native beta params).

func (*TinglyBoxAgent) IsAvailable

func (a *TinglyBoxAgent) IsAvailable() bool

IsAvailable returns true if the agent is available for execution.

func (*TinglyBoxAgent) IsEnabled

func (a *TinglyBoxAgent) IsEnabled() bool

IsEnabled returns whether the smart guide is enabled.

func (*TinglyBoxAgent) LastAssistantText

func (a *TinglyBoxAgent) LastAssistantText() string

LastAssistantText returns the text of the most recent assistant message in history, used by the completion callback to capture the final response.

func (*TinglyBoxAgent) Steer

func (a *TinglyBoxAgent) Steer(text string) bool

Steer delivers a message to the turn this agent is currently running, to be picked up at its next checkpoint. It reports whether there was a run to take it; false means the caller should start a normal turn.

func (*TinglyBoxAgent) Type

Type returns the agent type used by the executor routing layer.

type ToolContext

type ToolContext struct {
	ChatID      string
	ProjectPath string
	SessionID   string

	// SendFile sends a local file to the user via the IM bot.
	// Injected by the bot layer; nil if file sending is not available.
	SendFile func(ctx context.Context, filePath, caption string) error

	// RequestApproval requests explicit user approval for sensitive operations
	// (e.g. sending files outside the project path). This callback must NOT be
	// bypassed by yolo mode — it is distinct from the bash approval callback.
	// Returns (false, nil) if denied. Returns (false, err) on failure.
	RequestApproval func(ctx context.Context, prompt string) (approved bool, err error)
}

ToolContext provides context for tool execution

type ToolExecutor

type ToolExecutor struct {
	BashAllowlist map[string]struct{}
	BashCwd       string // Per-execution working directory
	// contains filtered or unexported fields
}

ToolExecutor handles tool execution with proper context

func NewToolExecutor

func NewToolExecutor(allowlist []string) *ToolExecutor

NewToolExecutor creates a new tool executor

func (*ToolExecutor) ExecuteBash

func (e *ToolExecutor) ExecuteBash(ctx context.Context, cmd string, args ...string) (string, error)

ExecuteBash executes a bash command with allowlist checking

func (*ToolExecutor) GetAllowedCommands

func (e *ToolExecutor) GetAllowedCommands() []string

GetAllowedCommands returns the list of allowed commands

func (*ToolExecutor) GetWorkingDirectory

func (e *ToolExecutor) GetWorkingDirectory() string

GetWorkingDirectory returns the current working directory

func (*ToolExecutor) HasApprovalCallback

func (e *ToolExecutor) HasApprovalCallback() bool

HasApprovalCallback returns true if an approval callback is set

func (*ToolExecutor) ResolvePath

func (e *ToolExecutor) ResolvePath(path string) string

ResolvePath resolves a path to an absolute path

func (*ToolExecutor) SetApprovalCallback

func (e *ToolExecutor) SetApprovalCallback(callback ApprovalCallback)

SetApprovalCallback sets the approval callback for non-allowlisted commands

func (*ToolExecutor) SetApprovalTimeout

func (e *ToolExecutor) SetApprovalTimeout(timeout time.Duration)

SetApprovalTimeout sets the timeout for approval requests

func (*ToolExecutor) SetWorkingDirectory

func (e *ToolExecutor) SetWorkingDirectory(cwd string)

SetWorkingDirectory sets the current working directory

type WriteFileTool

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

WriteFileTool writes content to a file, creating parent directories as needed.

func NewWriteFileTool

func NewWriteFileTool(executor *ToolExecutor) *WriteFileTool

NewWriteFileTool constructs a WriteFileTool bound to the given executor.

func (*WriteFileTool) Call

func (t *WriteFileTool) Call(ctx context.Context, rawInput json.RawMessage) (string, error)

func (*WriteFileTool) Param

Jump to

Keyboard shortcuts

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