copilot

package
v1.8.2 Latest Latest
Warning

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

Go to latest
Published: Feb 22, 2026 License: MIT Imports: 60 Imported by: 0

Documentation

Overview

Package copilot – access.go implements the access control system for DevClaw.

The bot does NOT respond to everyone by default. Only explicitly authorized contacts (or groups) can interact with the assistant.

Access levels:

  • owner: Full control, can manage admins, workspaces, and settings
  • admin: Can manage users, create workspaces, and use admin commands
  • user: Can interact with the assistant normally
  • blocked: Explicitly blocked, never receives a response

Default policy: "deny" — unknown contacts are silently ignored.

Package copilot – agent.go implements the agentic loop that orchestrates LLM calls with tool execution. The agent iterates: call LLM → if tool_calls → execute tools → append results → call LLM again, until the LLM produces a final text response with no tool calls.

Architecture:

  • No fixed max turns — the loop runs until the LLM stops calling tools.
  • Single run timeout (default: 600s = 10min) controls the whole run.
  • Per-LLM-call safety timeout (5min) prevents individual hung requests.
  • Reflection nudge every 15 turns for budget awareness.
  • Auto-compaction on context overflow (up to 3 attempts).

Package copilot – agent_router.go implements agent routing based on channel, user, or group. This allows different agents with different models, instructions, and skill sets to handle messages from different sources.

Package copilot implements the main orchestrator for DevClaw. Coordinates channels, skills, scheduler, access control, workspaces, and security to process user messages and generate LLM responses.

Package copilot – block_streamer.go implements progressive message delivery for channels. Instead of waiting for the full LLM response, text is coalesced into blocks and sent as they become available, giving the user near-real-time feedback as blocks become available.

Coalescing rules:

  • Wait until at least MinChars are accumulated.
  • Flush when MaxChars is reached or the idle timer fires.
  • Always try to flush at a natural boundary (newline, sentence end).

Package copilot – browser_tool.go implements a browser automation tool using Chrome DevTools Protocol (CDP). This allows the agent to navigate web pages, take screenshots, extract content, click elements, and fill forms.

Architecture:

Agent ──browser_navigate──▶ BrowserManager ──CDP──▶ Chrome/Chromium
Agent ──browser_screenshot──▶ BrowserManager ──CDP──▶ Screenshot → base64
Agent ──browser_content──▶ BrowserManager ──CDP──▶ DOM → text
Agent ──browser_click──▶ BrowserManager ──CDP──▶ Click element

The browser is launched lazily on first use and kept alive for the session. A configurable timeout prevents runaway browser sessions.

Package copilot – canvas_host.go implements an interactive HTML/JS canvas host. Allows the agent to generate HTML/JS content and serve it via a temporary local HTTP server for the user to interact with.

Use cases:

  • Data visualization (charts, graphs)
  • Interactive prototypes
  • Mini-apps (calculators, forms)
  • Rich output that exceeds chat formatting

Architecture:

Agent ──canvas_create──▶ CanvasHost ──HTTP──▶ user browser
Agent ──canvas_update──▶ CanvasHost (live-reload via SSE)
Agent ──canvas_list──▶ list of active canvases

Package copilot – codebase_tools.go implements codebase analysis tools: file tree indexing, code search (ripgrep), symbol extraction, and Cursor rules generator.

Package copilot – commands.go implements admin commands that can be executed via chat messages (WhatsApp, Discord, etc.).

Commands are prefixed with "/" and only available to admins/owners:

/allow <phone>           - Grant user access
/block <phone>           - Block a user
/unblock <phone>         - Unblock a user
/revoke <phone>          - Revoke user access
/admin <phone>           - Promote user to admin
/users                   - List all authorized users
/ws create <id> <name>   - Create a workspace
/ws delete <id>          - Delete a workspace
/ws assign <phone> <id>  - Assign user to workspace
/ws list                 - List all workspaces
/ws info [id]            - Show workspace details
/ws set <key> <value>    - Update current workspace setting
/group allow             - Allow current group
/group block             - Block current group
/group assign <ws_id>    - Assign current group to workspace
/skills list             - List installed skills
/skills defaults         - List available default skills
/skills install <n|all>  - Install default skills
/status                  - Show bot status
/help                    - Show available commands

Package copilot – config.go defines all configuration structures for the DevClaw Copilot assistant.

Package copilot – config_watcher.go polls config.yaml for changes and triggers hot-reload of safe-to-update fields without restarting the daemon.

Package copilot – daemon_manager.go implements a process manager that lets the agent start, monitor, and control long-running background processes (dev servers, watchers, database engines, etc.) with ring-buffer output capture and health checking.

Package copilot – db.go provides the central SQLite database for DevClaw. A single devclaw.db file holds scheduler jobs, session history/meta/facts, and the audit log. The memory.db (FTS5/embeddings) and whatsapp.db (whatsmeow session) remain as separate databases.

Package copilot – db_hub_tools.go implements database hub management tools. These tools use the native Database Hub with Go drivers for better performance than the CLI-based db_tools.go tools.

Package copilot – db_migrate.go handles one-time migration of legacy JSON/JSONL/text data to the central devclaw.db SQLite database. After a successful migration, the old files are renamed to .bak.

Package copilot – db_tools.go implements database tools for querying PostgreSQL, MySQL, and SQLite databases. Uses CLI clients (psql, mysql, sqlite3) to avoid heavy driver dependencies.

Package copilot – dev_utils.go implements developer utility tools: JSON formatting, JWT decoding, regex testing, base64 encode/decode, hashing, UUID generation, URL parsing, timestamp conversion, etc.

Package copilot – docker_tools.go implements native Docker tools for container management, image operations, and compose integration.

Package copilot – env_tools.go implements system information and network diagnostic tools: port scanning, environment info, and process listing.

Package copilot – events.go implements an in-memory pub/sub event bus for agent lifecycle events. Replaces channel-based buffering with fan-out to multiple listeners, each receiving events via direct function call.

Event streams:

  • "lifecycle": run_start, run_end, abort
  • "assistant": delta (text tokens), thinking_start, thinking_delta, thinking_end
  • "tool": tool_use, tool_result
  • "error": agent errors, LLM errors

Package copilot – exec_analysis.go implements command risk analysis for bash/exec tool calls. Commands are categorized by risk level and appropriate actions are taken (allow, log, require approval, deny).

Package copilot – exec_approval.go implements interactive approval for tools that require confirmation before execution (e.g. bash, ssh, write_file).

Package copilot – git_tools.go implements native Git tools that provide structured JSON output for the agent, enabling better decision-making without parsing raw text. Uses os/exec to call git directly.

Package copilot – group_chat.go implements enhanced group chat features Activation modes, intro messages, context injection, participant tracking, and quiet hours.

Package copilot – group_policy.go implements group-specific policies including activation modes, quiet hours, and access control for groups.

Package copilot – heartbeat.go implements a periodic heartbeat that checks for pending scheduled jobs, reads HEARTBEAT.md for custom checklists, and triggers proactive agent turns that can send messages to channels.

Package copilot – hooks.go implements a lifecycle hook system. Hooks allow external code to observe and optionally modify agent behavior at well-defined points in the lifecycle.

Hook events include:

SessionStart      — A new session is created or restored.
SessionEnd        — A session is about to be pruned/deleted.
UserPromptSubmit  — User message received, before processing.
PreToolUse        — Before a tool is called (can block/modify).
PostToolUse       — After a tool returns (observe/log).
AgentStart        — Agent loop is about to begin.
AgentStop         — Agent loop finished (normal or error).
SubagentStart     — A subagent has been spawned.
SubagentStop      — A subagent has finished.
PreCompact        — Before session compaction.
PostCompact       — After session compaction.
MemorySave        — A memory was saved.
MemoryRecall      — Memories were recalled for prompt.
Notification      — An outbound notification/message is being sent.
Heartbeat         — Periodic heartbeat tick.
Error             — An unrecoverable error occurred.

Package copilot – ide_extensions.go provides configuration generators for IDE extensions: VSCode, JetBrains, and Neovim. These generate the necessary config files for connecting to DevClaw's MCP server.

Package copilot – keyring.go provides secure credential storage using the operating system's native keyring (Linux: Secret Service/GNOME Keyring, macOS: Keychain, Windows: Credential Manager).

Priority for resolving secrets:

  1. Encrypted vault (.devclaw.vault — AES-256-GCM + Argon2, requires master password)
  2. OS keyring (encrypted by the OS, requires user session)
  3. Environment variable (DEVCLAW_API_KEY, OPENAI_API_KEY, etc.)
  4. .env file (loaded by godotenv)
  5. config.yaml value (least secure — plaintext on disk)

Package copilot – lanes.go implements a lane-based concurrency system. Each lane has its own queue and concurrency limit, preventing contention between different types of work (sessions, cron, subagents).

Lane types:

  • session:{id} — one lane per session, maxConcurrent=1 (serialized)
  • global — shared lane for cross-session work, maxConcurrent=3
  • cron — scheduled jobs, maxConcurrent=2
  • subagent — subagent runs, maxConcurrent=8

Package copilot – llm.go implements the LLM client for chat completions with function calling / tool use support. Uses the OpenAI-compatible API format, which works with OpenAI, Anthropic proxies, GLM (api.z.ai), and any compatible endpoint.

Package copilot – loader.go handles loading configuration from YAML files with secure credential management via environment variables and .env files.

Package copilot – maintenance_manager.go manages maintenance mode state.

Package copilot – markdown.go converts standard Markdown to channel-specific formats. WhatsApp supports a limited subset; other channels may get plain text or passthrough.

Package copilot – mcp_manager.go implements MCP (Model Context Protocol) server management including listing, adding, editing, removing, and testing MCP connections.

Package copilot – media_enrichment.go handles extraction of content from documents (PDF, DOCX, TXT) and video frames for enriching agent prompts.

Package copilot – media_tools.go registers tools for image understanding (describe_image) and audio transcription (transcribe_audio). Also includes native media sending tools (send_image, send_audio, send_document).

Package copilot – memory_hardening.go implements security hardening for memory content that is injected into LLM prompts. Memories are treated as untrusted historical data and sanitized to prevent prompt injection.

Security pattern:

  • Escape HTML entities in memory content
  • Wrap memories in <relevant-memories> tags with untrusted data warning
  • Detect and reject auto-capture of prompt injection patterns
  • Only capture from user-role messages

Package copilot – memory_indexer.go provides background memory indexing.

Package copilot – message_queue.go handles message bursts with debouncing. When a session is already processing, incoming messages are queued and combined after a debounce period.

Package copilot – message_split.go provides splitting of long messages for channels with character limits (e.g. WhatsApp 4096).

Package copilot – metrics_collector.go provides background metrics collection.

Package copilot – model_failover.go implements automatic model failover with cooldowns and reason classification. When the primary LLM returns persistent errors, the system rotates through fallback models automatically.

Package copilot – multiuser.go implements multi-user support with user management, role-based access control, shared memory spaces, and team collaboration features.

Package copilot – ops_tools.go implements operations tools for deploy pipeline execution, server health monitoring, and tunnel management.

Package copilot – pairing.go implements the DM pairing system for secure access onboarding.

The pairing system allows admins to generate shareable tokens that new users can send to the bot to request access. Tokens can be configured for:

  • Auto-approval (immediate access) or manual approval
  • Expiration time
  • Maximum number of uses
  • Role to grant (user or admin)
  • Workspace assignment

Package copilot – plugin_system.go implements an extensible plugin system that supports HTTP-based plugins (GitHub, Jira, Sentry, etc.) with webhook integration and tool registration.

Package copilot – product_tools.go implements product management tools: project management API integration (Jira, Linear), sprint reporting, documentation sync (Notion/Confluence), and DORA metrics calculation.

Package copilot – project.go implements the ProjectManager for managing development projects. Provides project registration, activation, auto-detection of language/framework, and per-session project context.

A "project" maps to a filesystem directory (typically a git repo root) with associated metadata like language, framework, build/test/lint commands, and MCP server configurations.

Package copilot – project_adapter.go provides an adapter that bridges copilot.ProjectManager to skills.ProjectProvider, breaking the import cycle between the two packages.

The adapter converts between copilot.Project and skills.ProjectInfo structs which have the same shape but live in different packages.

Package copilot – prompt_layers.go implements the layered system prompt Each layer has a priority and contributes to the final prompt that is sent to the LLM as the system message.

Bootstrap files (SOUL.md, AGENTS.md, IDENTITY.md, USER.md, TOOLS.md) are loaded from the workspace root and injected as "Project Context". If SOUL.md is present, the agent is instructed to embody its persona.

Package copilot – queue_modes.go implements configurable queue modes that control how the agent handles incoming messages while a session is busy. Supports: collect, steer, followup, interrupt, steer-backlog.

session.go implementa o gerenciamento de sessões isoladas por chat/grupo. Cada grupo ou DM possui sua própria sessão com memória, skills ativas e configurações independentes.

session_persistence.go implements disk persistence for sessions using JSONL format.

Package copilot – session_persistence_sqlite.go implements session persistence backed by the central devclaw.db SQLite database. It is a drop-in replacement for the JSONL-based SessionPersistence.

Package copilot – skill_creator.go implements tools that allow the agent to create, edit, and manage skills via chat. Skills are created as ClawdHub-compatible SKILL.md files in the workspace skills directory.

The agent can use these tools to:

  • Initialize a new skill with a SKILL.md template
  • Edit an existing skill's instructions
  • Add scripts (Python, Node, Shell) to a skill
  • List installed skills
  • Test a skill by executing it

Package copilot provides startup verification for DevClaw. Checks vault, database, channels, and system dependencies.

Package copilot – subagent.go implements a subagent system that allows the main agent to spawn independent child agents to handle tasks concurrently.

Architecture:

Main Agent ──spawn_subagent──▶ SubagentManager ──goroutine──▶ Child AgentRun
                                   │                              │
                                   ▼                              ▼
                            SubagentRegistry           (runs with own session,
                            tracks runs + results       limited tools, separate
                                                        prompt, optional model)

Subagents:

  • Run in isolated goroutines with their own session context.
  • Cannot spawn nested subagents (no recursion).
  • Have a configurable subset of tools (deny list applied).
  • Results are collected and can be polled or waited on.
  • Are announced back to the parent session when complete.

Package copilot – techops_commands.go implements administrative commands for remote operations. These commands allow operators to manage the system via chat channels without SSH access.

Package copilot – system_tools.go registers built-in tools that are always available to the agent, independent of skills. These tools provide core capabilities like shell execution, file I/O, memory operations, and scheduling.

Package copilot – techops_types.go provides data structures for TechOps commands.

Package copilot – tailscale.go implements Tailscale Serve/Funnel integration for secure remote access to DevClaw's web services.

Tailscale Serve proxies HTTPS traffic from the Tailscale network to a local port. Tailscale Funnel extends this to the public internet with automatic TLS certificates.

Provides secure remote access without manual port forwarding, dynamic DNS, or certificate management.

Architecture:

Internet ──HTTPS──▶ Tailscale Funnel ──▶ Local DevClaw (e.g. :8085)
Tailnet  ──HTTPS──▶ Tailscale Serve  ──▶ Local DevClaw (e.g. :8085)

Package copilot – team_manager.go manages persistent agents and teams. Provides lifecycle management, heartbeats, and integration with the scheduler.

Package copilot – team_memory.go implements shared memory for team agents. Provides tasks, messages, facts, and activity tracking accessible by all team members.

Package copilot – team_tools.go implements tools for team/agent management. These tools allow agents and users to manage teams, agents, tasks, and communication.

Package copilot – team_types.go defines types for the team/agent management system. This enables persistent agents, shared memory, and inter-agent communication.

Package copilot – testing_tools.go implements a testing engine that wraps common test runners and provides tools for running tests, API testing, and generating test reports.

Package copilot – tool_executor.go manages a registry of callable tools and dispatches tool calls from the LLM to the appropriate handlers. Tools can be registered from skills, system built-ins, or plugins.

Package copilot – tool_guard.go implements a security layer that controls which tools can be used, by whom, and under what conditions.

Security features:

  • Tool-level access control (owner/admin/user)
  • Destructive command detection and blocking
  • Sensitive path protection
  • SSH host allowlist
  • Full audit logging of every tool execution
  • Configurable confirmation for dangerous operations

Package copilot – tool_guard_audit_sqlite.go provides a SQLite-backed audit logger for the ToolGuard. It writes tool execution records to the audit_log table in the central devclaw.db and auto-prunes entries older than 30 days.

Package copilot – tool_loop_detection.go detects when the agent enters a tool call loop (repeating the same call with no progress) and triggers circuit breakers to prevent infinite loops.

Four detectors:

  • Generic repeat: same tool+args hash repeated N times
  • Ping-pong: alternating between two tool calls
  • Known no-progress poll: tools that poll external state without progress
  • Global circuit breaker: total no-progress calls across all patterns

Package copilot – tool_profiles.go implements predefined tool permission profiles. Profiles simplify tool configuration by providing presets for common use cases.

Package copilot – usage_tracker.go records LLM token usage and estimated costs per session and globally.

Package copilot – vault.go provides encrypted credential storage using AES-256-GCM with Argon2id key derivation. Secrets are stored in a local file (.devclaw.vault) that is unreadable without the master password.

Even if someone has filesystem access, the vault contents remain encrypted. The master password is never stored — only a derived key is used in memory.

Package copilot – webhooks.go implements external webhook support for hooks. Webhooks allow sending hook events to external HTTP endpoints.

Package copilot – workspace.go implements the multi-tenant workspace system.

Workspaces (also called "profiles") allow multiple people to use the same WhatsApp number with completely isolated contexts:

  • Each workspace has its own system prompt, skills, model, and language
  • Each workspace has its own session store (isolated conversation memory)
  • A workspace can be assigned to specific users (JIDs) or groups
  • One user can belong to one workspace at a time
  • There is always a "default" workspace for unassigned users

Example use cases:

  • Personal workspace: your own assistant with custom instructions
  • Team workspace: shared context for a project team
  • Client workspace: different personality/language per client
  • Testing workspace: experimental settings without affecting production

Package copilot – workspace_containment.go implements workspace path containment and symlink escape protection for file operations.

All file tools (read_file, write_file, edit_file, apply_patch) must call AssertSandboxPath before performing any I/O to ensure the resolved path is within the workspace root.

Index

Constants

View Source
const (
	// DefaultRunTimeout is the maximum duration for an entire agent run.
	// Set to 20 minutes to accommodate coding tasks that invoke Claude Code CLI
	// (which itself can take 5-15 minutes for complex projects).
	// This is the PRIMARY timeout — no per-turn limit.
	DefaultRunTimeout = 1200 * time.Second

	// DefaultLLMCallTimeout is the safety-net timeout for a single LLM API call.
	// This only prevents hung HTTP connections — it should be generous enough
	// that even large contexts complete. 5 minutes covers worst-case scenarios.
	DefaultLLMCallTimeout = 5 * time.Minute

	// DefaultMaxCompactionAttempts is how many times to retry after context overflow compaction.
	DefaultMaxCompactionAttempts = 3
)
View Source
const (
	TokenNoReply     = "NO_REPLY"
	TokenHeartbeatOK = "HEARTBEAT_OK"
)

Silent token constants — used by the LLM to signal special behavior. These must be stripped from user-visible output.

View Source
const (
	// DefaultDebounceMs is the debounce delay for followup messages (session busy).
	// Kept short so followups are grouped without adding perceptible lag.
	DefaultDebounceMs = 200
	// DefaultMaxPending is the default max queued messages per session.
	DefaultMaxPending = 20
	// DedupWindowSec is the window for deduplication (skip same content).
	DedupWindowSec = 5
	// FollowupDebounceMs is used when the session is already processing.
	// Slightly longer to allow burst followup messages to be collected.
	FollowupDebounceMs = 500
)
View Source
const (
	// MaxMessageWhatsApp is WhatsApp's character limit.
	MaxMessageWhatsApp = 4096

	// MaxMessageDefault is the default max length for general channels.
	MaxMessageDefault = 4000
)
View Source
const (
	// ApprovalTimeout is how long to wait for user approval before giving up.
	// 120s gives ample time for users to read and respond via chat.
	ApprovalTimeout = 120 * time.Second
)
View Source
const DefaultMaxHistory = 100

DefaultMaxHistory é o limite padrão de entradas no histórico por sessão.

View Source
const DefaultSessionTTL = 24 * time.Hour

DefaultSessionTTL é o tempo de inatividade antes de uma sessão ser removida.

View Source
const (
	// DefaultToolTimeout is the maximum time a single tool execution can take.
	DefaultToolTimeout = 30 * time.Second
)
View Source
const HardMaxToolResultChars = 400_000

HardMaxToolResultChars is the absolute maximum size for a tool result. Results exceeding this are truncated before entering the conversation to prevent context overflow.

View Source
const (
	// VaultFile is the default vault file name.
	VaultFile = ".devclaw.vault"
)

Variables

AllHookEvents lists every supported hook event for discovery/documentation.

View Source
var BuiltInProfiles = map[string]ToolProfile{
	"minimal": {
		Name:        "minimal",
		Description: "Basic queries only - read-only access",
		Allow: []string{
			"group:web",
			"group:memory",
			"read_file",
			"list_files",
			"search_files",
			"glob_files",
		},
		Deny: []string{
			"group:runtime",
			"write_file",
			"edit_file",
			"group:skills",
			"group:scheduler",
			"group:vault",
			"group:subagents",
		},
	},
	"coding": {
		Name:        "coding",
		Description: "Software development - file access, git, docker, tests",
		Allow: []string{
			"group:fs",
			"group:web",
			"group:memory",
			"bash",
			"exec",
			"git_*",
			"docker_*",
			"test_*",
			"cron_list",
		},
		Deny: []string{
			"ssh",
			"scp",
			"cron_add",
			"cron_remove",
		},
	},
	"messaging": {
		Name:        "messaging",
		Description: "Chat channel usage - web search and memory only",
		Allow: []string{
			"group:web",
			"group:memory",
			"list_skills",
			"search_skills",
		},
		Deny: []string{
			"group:runtime",
			"group:fs",
			"group:skills",
			"group:scheduler",
			"group:vault",
			"group:subagents",
		},
	},
	"full": {
		Name:        "full",
		Description: "Full access - all tools available (respect permissions)",
		Allow:       []string{"*"},
		Deny:        []string{},
	},
}

BuiltInProfiles provides predefined tool profiles for common use cases.

View Source
var DefaultSubagentDeniedTools = []string{

	"spawn_subagent",
	"list_subagents",
	"wait_subagent",
	"stop_subagent",

	"memory_save",
	"memory_search",
	"memory_list",
	"memory_index",

	"cron_add",
	"cron_remove",

	"install_skill",
	"remove_skill",
	"init_skill",
}

DefaultSubagentDeniedTools lists tools subagents should not access. Subagents should not manage sessions, spawn recursively, or access memory/cron.

View Source
var ErrInvalidTime = errorString("invalid time format")

ErrInvalidTime is returned when a time string is invalid.

View Source
var ProviderKeyNames = map[string]string{
	"openai":      "OPENAI_API_KEY",
	"anthropic":   "ANTHROPIC_API_KEY",
	"google":      "GOOGLE_API_KEY",
	"xai":         "XAI_API_KEY",
	"groq":        "GROQ_API_KEY",
	"zai":         "ZAI_API_KEY",
	"mistral":     "MISTRAL_API_KEY",
	"openrouter":  "OPENROUTER_API_KEY",
	"cerebras":    "CEREBRAS_API_KEY",
	"minimax":     "MINIMAX_API_KEY",
	"huggingface": "HUGGINGFACE_API_KEY",
	"deepseek":    "DEEPSEEK_API_KEY",
	"custom":      "CUSTOM_API_KEY",
}

ProviderKeyNames maps provider IDs to their standard API key variable names. These follow industry conventions (OPENAI_API_KEY, ANTHROPIC_API_KEY, etc.)

View Source
var ToolGroups = map[string][]string{
	"group:memory":    {"memory_save", "memory_search", "memory_list", "memory_index"},
	"group:web":       {"web_search", "web_fetch"},
	"group:fs":        {"read_file", "write_file", "edit_file", "list_files", "search_files", "glob_files"},
	"group:runtime":   {"bash", "exec", "ssh", "scp", "set_env"},
	"group:subagents": {"spawn_subagent", "list_subagents", "wait_subagent", "stop_subagent"},
	"group:skills":    {"install_skill", "remove_skill", "search_skills", "list_skills", "test_skill", "edit_skill", "add_script", "init_skill", "skill_defaults_list", "skill_defaults_install"},
	"group:scheduler": {"cron_add", "cron_list", "cron_remove"},
	"group:vault":     {"vault_save", "vault_get", "vault_list", "vault_delete"},
	"group:media":     {"describe_image", "transcribe_audio", "image-gen_generate_image"},
}

ToolGroups maps group names to tool name lists. Allows policy management at a higher level than individual tools.

Functions

func AuditSecrets

func AuditSecrets(cfg *Config, logger *slog.Logger)

AuditSecrets checks for hardcoded secrets and logs warnings. Should be called on startup to alert the user.

func CallerJIDFromContext

func CallerJIDFromContext(ctx context.Context) string

CallerJIDFromContext extracts the caller JID from context.

func ContextWithCaller

func ContextWithCaller(ctx context.Context, level AccessLevel, jid string) context.Context

ContextWithCaller returns a new context carrying the caller's access level and JID. This replaces the global SetCallerContext/SetSessionContext pattern, making tool security checks goroutine-safe (context per request).

func ContextWithDelivery

func ContextWithDelivery(ctx context.Context, channel, chatID string) context.Context

ContextWithDelivery returns a new context carrying the delivery target. This is used by tools like cron_add to know where to deliver scheduled messages.

func ContextWithProgressSender

func ContextWithProgressSender(ctx context.Context, fn ProgressSender) context.Context

ContextWithProgressSender returns a context carrying a ProgressSender callback.

func ContextWithSession

func ContextWithSession(ctx context.Context, sessionID string) context.Context

ContextWithSession returns a new context carrying the given session ID.

func ContextWithSpawnDepth added in v1.8.0

func ContextWithSpawnDepth(ctx context.Context, depth int) context.Context

ContextWithSpawnDepth returns a new context with the spawn depth set.

func ContextWithToolProfile

func ContextWithToolProfile(ctx context.Context, profile *ToolProfile) context.Context

ContextWithToolProfile returns a new context carrying a tool profile. The profile is used for CheckWithProfile to apply allow/deny lists.

func DeleteKeyring

func DeleteKeyring(key string) error

DeleteKeyring removes a secret from the OS keyring.

func DetectInjectionPattern

func DetectInjectionPattern(text string) bool

DetectInjectionPattern checks if a text contains known prompt injection patterns. Returns true if any pattern matches (the text should NOT be auto-captured).

func ExpandProfileList

func ExpandProfileList(items []string, allTools []string) []string

ExpandProfileList expands a profile's allow/deny lists into tool names. Handles groups ("group:name") and wildcards ("git_*").

func ExpandToolGroups

func ExpandToolGroups(names []string) []string

ExpandToolGroups expands group references (e.g. "group:memory") into individual tool names. Non-group entries are passed through as-is.

func ExtractTokenFromMessage

func ExtractTokenFromMessage(content string) string

ExtractTokenFromMessage attempts to extract a pairing token from message content. Tokens are 48+ hex characters. Returns empty string if not a token attempt.

func FindConfigFile

func FindConfigFile() string

FindConfigFile searches for config files in standard locations.

func FormatCollectedMessages

func FormatCollectedMessages(msgs []*channels.IncomingMessage) string

FormatCollectedMessages combines multiple messages into a single prompt (used by QueueModeCollect).

func FormatForChannel

func FormatForChannel(text, channel string) string

FormatForChannel dispatches to the appropriate formatter based on channel. Reply tags ([[reply_to_current]], [[reply_to:<id>]]) are stripped before formatting so they never reach the user.

func FormatForPlainText

func FormatForPlainText(text string) string

FormatForPlainText strips all Markdown, leaving only plain text.

func FormatForSlack

func FormatForSlack(text string) string

FormatForSlack converts Markdown to Slack's mrkdwn format. Slack uses: *bold*, _italic_, ~strike~, `code`, ```preformatted```.

func FormatForTelegram

func FormatForTelegram(text string) string

FormatForTelegram converts Markdown to Telegram HTML. Telegram supports: <b>, <i>, <code>, <pre>, <a href="">, <s>, <u>.

func FormatForWhatsApp

func FormatForWhatsApp(text string) string

FormatForWhatsApp converts standard Markdown to WhatsApp-compatible formatting. WhatsApp supports: *bold*, _italic_, ~strikethrough~, `monospace`, ```code blocks```. Headers become bold; links are flattened; images become [Image: alt]; lists use •.

func GetKeyring

func GetKeyring(key string) string

GetKeyring retrieves a secret from the OS keyring. Returns empty string if not found.

func GetProviderKeyName added in v1.8.1

func GetProviderKeyName(provider string) string

GetProviderKeyName returns the standard API key variable name for a provider. Falls back to "API_KEY" for unknown providers.

func HookEventDescription

func HookEventDescription(ev HookEvent) string

HookEventDescription returns a human-readable description for a hook event.

func IsCommand

func IsCommand(content string) bool

IsCommand returns true if the message starts with "/".

func IsEnvReference

func IsEnvReference(s string) bool

IsEnvReference checks if a string is an environment variable reference.

func KeyringAvailable

func KeyringAvailable() bool

KeyringAvailable checks if the OS keyring is accessible.

func ListProfiles

func ListProfiles(customProfiles map[string]ToolProfile) []string

ListProfiles returns all available profile names.

func MakeSessionID

func MakeSessionID(channel, chatID string) string

MakeSessionID returns a compact hash-based session ID from channel and chatID. For backward compatibility, this is still used as the primary key.

func MatchesPattern

func MatchesPattern(toolName, pattern string) bool

MatchesPattern checks if a tool name matches a pattern. Supports glob-style wildcards: "git_*" matches "git_status", "git_commit", etc.

func MigrateKeyToKeyring

func MigrateKeyToKeyring(apiKey string, logger *slog.Logger) error

MigrateKeyToKeyring moves an API key from config/env to the OS keyring and clears it from the original location.

func MigrateToSQLite

func MigrateToSQLite(db *sql.DB, dataDir string, logger *slog.Logger)

MigrateToSQLite imports legacy JSON/JSONL data into the central database. It is safe to call multiple times: once the .bak file exists, migration is skipped for that component. Called once from assistant.Start().

func OpenDatabase

func OpenDatabase(path string) (*sql.DB, error)

OpenDatabase opens (or creates) the central devclaw.db at the given path. It enables WAL mode for concurrent read performance and creates all tables.

func ReadPassword

func ReadPassword(prompt string) (string, error)

ReadPassword reads a password from the terminal without echoing. Falls back to regular stdin reading if terminal is not available.

func RegisterBrowserTools

func RegisterBrowserTools(executor *ToolExecutor, browserMgr *BrowserManager, logger *slog.Logger)

RegisterBrowserTools registers browser automation tools in the executor.

func RegisterCanvasTools

func RegisterCanvasTools(executor *ToolExecutor, canvasHost *CanvasHost, logger *slog.Logger)

RegisterCanvasTools registers canvas tools in the executor.

func RegisterCodebaseTools

func RegisterCodebaseTools(executor *ToolExecutor)

RegisterCodebaseTools registers codebase analysis tools in the executor.

func RegisterDBHubTools added in v1.8.0

func RegisterDBHubTools(executor *ToolExecutor, hub *database.Hub)

RegisterDBHubTools registers database hub management tools. These tools operate on the internal database hub, not external databases.

func RegisterDBTools

func RegisterDBTools(executor *ToolExecutor)

RegisterDBTools registers database query and management tools.

func RegisterDaemonTools

func RegisterDaemonTools(executor *ToolExecutor, dm *DaemonManager)

RegisterDaemonTools registers daemon management tools in the executor.

func RegisterDevUtilTools

func RegisterDevUtilTools(executor *ToolExecutor)

RegisterDevUtilTools registers developer utility tools.

func RegisterDockerTools

func RegisterDockerTools(executor *ToolExecutor)

RegisterDockerTools registers Docker management tools in the executor.

func RegisterEnvTools

func RegisterEnvTools(executor *ToolExecutor)

RegisterEnvTools registers system information and diagnostic tools.

func RegisterGitTools

func RegisterGitTools(executor *ToolExecutor)

RegisterGitTools registers native Git tools in the executor.

func RegisterIDETools

func RegisterIDETools(executor *ToolExecutor)

RegisterIDETools registers IDE extension configuration tools.

func RegisterMediaTools

func RegisterMediaTools(executor *ToolExecutor, llmClient *LLMClient, cfg *Config, logger *slog.Logger)

RegisterMediaTools registers describe_image and transcribe_audio tools when the LLM client and config support them.

func RegisterMultiUserTools

func RegisterMultiUserTools(executor *ToolExecutor, um *UserManager)

RegisterMultiUserTools registers multi-user management tools.

func RegisterNativeMediaTools added in v1.8.0

func RegisterNativeMediaTools(executor *ToolExecutor, mediaSvc *media.MediaService, channelMgr *channels.Manager, logger *slog.Logger)

RegisterNativeMediaTools registers send_image, send_audio, send_document tools for the LLM to send media to users through the channel manager.

func RegisterOpsTools

func RegisterOpsTools(executor *ToolExecutor)

RegisterOpsTools registers operations and deployment tools.

func RegisterPluginTools

func RegisterPluginTools(executor *ToolExecutor, pm *PluginManager)

RegisterPluginTools registers plugin management tools in the executor.

func RegisterProductTools

func RegisterProductTools(executor *ToolExecutor)

RegisterProductTools registers product management tools.

func RegisterSessionTools

func RegisterSessionTools(executor *ToolExecutor, wm *WorkspaceManager)

RegisterSessionTools registers sessions_list and sessions_send in the executor. These tools enable multi-agent routing: agents can discover other sessions and send messages to them, enabling inter-agent communication.

func RegisterSkillCreatorTools

func RegisterSkillCreatorTools(executor *ToolExecutor, registry *skills.Registry, skillsDir string, logger *slog.Logger)

RegisterSkillCreatorTools registers skill management tools in the executor. skillsDir is the workspace-level directory where user-created skills live.

func RegisterSubagentTools

func RegisterSubagentTools(
	executor *ToolExecutor,
	manager *SubagentManager,
	llmClient *LLMClient,
	promptComposer *PromptComposer,
	logger *slog.Logger,
)

RegisterSubagentTools registers the spawn_subagent, list_subagents, wait_subagent, and stop_subagent tools in the tool executor. These allow the main agent to create and manage child agents.

func RegisterSystemTools

func RegisterSystemTools(executor *ToolExecutor, sandboxRunner *sandbox.Runner, memStore *memory.FileStore, sqliteStore *memory.SQLiteStore, memCfg MemoryConfig, sched *scheduler.Scheduler, dataDir string, ssrfGuard *security.SSRFGuard, vault *Vault, webSearchCfg WebSearchConfig)

RegisterSystemTools registers all built-in system tools in the executor. These are core tools available regardless of which skills are loaded. If ssrfGuard is non-nil, web_fetch will validate URLs against SSRF rules.

func RegisterTeamTools added in v1.8.0

func RegisterTeamTools(
	executor *ToolExecutor,
	teamMgr *TeamManager,
	db *sql.DB,
	sched *scheduler.Scheduler,
	logger *slog.Logger,
)

RegisterTeamTools registers all team management tools.

func RegisterTestingTools

func RegisterTestingTools(executor *ToolExecutor)

RegisterTestingTools registers testing engine tools.

func ReloadEnvFiles

func ReloadEnvFiles() (int, error)

ReloadEnvFiles forces a reload of .env files with override. Returns the number of variables loaded.

func ResolveProfile

func ResolveProfile(name string, customProfiles map[string]ToolProfile) (allow, deny []string)

ResolveProfile returns the allow and deny lists for a profile. Checks built-in profiles first, then custom profiles. Returns nil lists if profile not found.

func SanitizeMemoryContent

func SanitizeMemoryContent(content string) string

SanitizeMemoryContent escapes HTML entities and strips dangerous patterns from memory content before injection into prompts.

func SaveConfigToFile

func SaveConfigToFile(cfg *Config, path string) error

SaveConfigToFile writes a Config as YAML to the specified path. Secrets are replaced with environment variable references. Creates a backup (.bak) of the existing file before overwriting to prevent data loss from crashes or invalid writes.

func SessionIDFromContext

func SessionIDFromContext(ctx context.Context) string

SessionIDFromContext extracts the session ID from a context. Returns empty string if not set.

func SpawnDepthFromContext added in v1.8.0

func SpawnDepthFromContext(ctx context.Context) int

SpawnDepthFromContext retrieves the current spawn depth from context. Returns 0 if not set (main agent level).

func SplitMessage

func SplitMessage(text string, maxLen int) []string

SplitMessage splits a long message into chunks respecting maxLen. Tries to break on paragraph boundaries, then sentence boundaries, then word boundaries. Does not split inside ``` code blocks.

func StoreKeyring

func StoreKeyring(key, value string) error

StoreKeyring saves a secret to the OS keyring.

func StripExternalContentBoundaries

func StripExternalContentBoundaries(content string) string

StripExternalContentBoundaries removes the untrusted content wrappers (useful when preparing content for display to the user).

func StripInternalTags

func StripInternalTags(text string) string

StripInternalTags removes all internal control tags and sentinel tokens from LLM output so they never reach the user. Handles:

  • [[reply_to_*]] delivery tags
  • <final>...</final> and <thinking>...</thinking> XML tags
  • NO_REPLY / HEARTBEAT_OK sentinel tokens

func StripReplyTags

func StripReplyTags(text string) string

StripReplyTags is an alias for StripInternalTags for backward compatibility.

func WrapExternalContent

func WrapExternalContent(source, content string) string

WrapExternalContent wraps content from external sources (web_fetch, browser, web_search) with untrusted content boundaries to prevent prompt injection replay during compaction.

func WrapMemoriesForPrompt

func WrapMemoriesForPrompt(memories []string) string

WrapMemoriesForPrompt wraps sanitized memory entries with the untrusted data boundary so the LLM treats them as historical context, not instructions.

Types

type APIConfig

type APIConfig struct {
	// BaseURL is the API base URL (OpenAI-compatible endpoint).
	// Examples:
	//   https://api.openai.com/v1           (OpenAI)
	//   https://api.z.ai/api/anthropic      (GLM / Anthropic proxy)
	//   https://api.anthropic.com/v1        (Anthropic direct)
	BaseURL string `yaml:"base_url"`

	// APIKey is the authentication key for the provider.
	// Can also be set via the DEVCLAW_API_KEY environment variable.
	APIKey string `yaml:"api_key"`

	// Provider hints which SDK to use ("openai", "anthropic", "glm").
	// Auto-detected from base_url if omitted.
	Provider string `yaml:"provider"`

	// Params holds provider-specific parameters:
	//   context1m: true   — enable Anthropic 1M context beta for Opus/Sonnet
	//   tool_stream: true — enable real-time tool call streaming (Z.AI)
	Params map[string]any `yaml:"params"`
}

APIConfig configures the LLM provider endpoint and credentials.

type AccessConfig

type AccessConfig struct {
	// DefaultPolicy determines behavior for unknown contacts.
	// "deny" (default) = ignore unknown, "allow" = respond to all, "ask" = request access.
	DefaultPolicy AccessPolicy `yaml:"default_policy"`

	// Owners have full control (phone numbers or JIDs).
	Owners []string `yaml:"owners"`

	// Admins can manage users and workspaces.
	Admins []string `yaml:"admins"`

	// AllowedUsers can interact with the bot.
	AllowedUsers []string `yaml:"allowed_users"`

	// BlockedUsers are explicitly blocked.
	BlockedUsers []string `yaml:"blocked_users"`

	// AllowedGroups are group IDs where the bot responds.
	AllowedGroups []string `yaml:"allowed_groups"`

	// BlockedGroups are group IDs where the bot stays silent.
	BlockedGroups []string `yaml:"blocked_groups"`

	// AllowGroupAdmins grants "user" access to group admins automatically.
	AllowGroupAdmins bool `yaml:"allow_group_admins"`

	// PendingMessage is the message sent to unknown contacts when policy is "ask".
	PendingMessage string `yaml:"pending_message"`
}

AccessConfig holds the access control configuration.

func DefaultAccessConfig

func DefaultAccessConfig() AccessConfig

DefaultAccessConfig returns the default access control config.

type AccessEntry

type AccessEntry struct {
	// JID is the contact identifier (phone@server or group@server).
	JID string

	// Level is the access level.
	Level AccessLevel

	// AddedBy is the JID of the admin/owner who granted access.
	AddedBy string

	// AddedAt is when access was granted.
	AddedAt time.Time

	// Note is an optional admin note about this contact.
	Note string
}

AccessEntry represents a contact in the access list.

type AccessLevel

type AccessLevel string

AccessLevel defines the permission level of a contact.

const (
	AccessOwner   AccessLevel = "owner"
	AccessAdmin   AccessLevel = "admin"
	AccessUser    AccessLevel = "user"
	AccessBlocked AccessLevel = "blocked"
	AccessNone    AccessLevel = "none"
	AccessUnknown AccessLevel = ""
)

func CallerLevelFromContext

func CallerLevelFromContext(ctx context.Context) AccessLevel

CallerLevelFromContext extracts the caller access level from context. Falls back to AccessNone if not set.

type AccessManager

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

AccessManager handles access control for incoming messages.

func NewAccessManager

func NewAccessManager(cfg AccessConfig, logger *slog.Logger) *AccessManager

NewAccessManager creates a new access manager from config.

func (*AccessManager) ApplyConfig

func (am *AccessManager) ApplyConfig(cfg AccessConfig)

ApplyConfig updates access config from hot-reload. Re-seeds config-derived entries (owners, admins, allowed, blocked, groups). Runtime grants (AddedBy != "config") are preserved.

func (*AccessManager) Block

func (am *AccessManager) Block(jid string, blockedBy string)

Block explicitly blocks a contact.

func (*AccessManager) Check

Check evaluates whether an incoming message should be processed. This is the main entry point called before any message handling.

func (*AccessManager) GetLevel

func (am *AccessManager) GetLevel(jid string) AccessLevel

GetLevel returns the access level for a JID.

func (*AccessManager) Grant

func (am *AccessManager) Grant(jid string, level AccessLevel, grantedBy string) error

Grant gives access to a contact at the specified level.

func (*AccessManager) GrantGroup

func (am *AccessManager) GrantGroup(groupJID string, level AccessLevel, grantedBy string) error

GrantGroup gives access to a group.

func (*AccessManager) IsAdmin

func (am *AccessManager) IsAdmin(jid string) bool

IsAdmin returns true if the JID is an admin or owner.

func (*AccessManager) IsOwner

func (am *AccessManager) IsOwner(jid string) bool

IsOwner returns true if the JID is an owner.

func (*AccessManager) ListGroups

func (am *AccessManager) ListGroups() []*AccessEntry

ListGroups returns all group access entries.

func (*AccessManager) ListUsers

func (am *AccessManager) ListUsers() []*AccessEntry

ListUsers returns all access entries.

func (*AccessManager) MarkAsked

func (am *AccessManager) MarkAsked(jid string)

MarkAsked records that we sent the "pending" message to a contact.

func (*AccessManager) PendingMessage

func (am *AccessManager) PendingMessage() string

PendingMessage returns the message to send to unknown contacts.

func (*AccessManager) Revoke

func (am *AccessManager) Revoke(jid string, revokedBy string)

Revoke removes access from a contact.

func (*AccessManager) Unblock

func (am *AccessManager) Unblock(jid string, unblockedBy string)

Unblock removes a block from a contact.

type AccessPolicy

type AccessPolicy string

AccessPolicy defines how unknown contacts are handled.

const (
	// PolicyDeny silently ignores unknown contacts (default).
	PolicyDeny AccessPolicy = "deny"

	// PolicyAllow responds to everyone not explicitly blocked.
	PolicyAllow AccessPolicy = "allow"

	// PolicyAsk sends a one-time "request access" message to unknown contacts.
	PolicyAsk AccessPolicy = "ask"
)

type ActivationMode

type ActivationMode string

ActivationMode defines how the bot activates in a group.

const (
	// ActivationAlways responds to all messages.
	ActivationAlways ActivationMode = "always"
	// ActivationMention responds only when mentioned.
	ActivationMention ActivationMode = "mention"
	// ActivationReply responds only when replying to bot's messages.
	ActivationReply ActivationMode = "reply"
	// ActivationKeyword responds when keywords are detected.
	ActivationKeyword ActivationMode = "keyword"
)

type ActivityType added in v1.8.0

type ActivityType string

ActivityType represents the type of team activity.

const (
	ActivityTaskCreated     ActivityType = "task_created"
	ActivityTaskUpdated     ActivityType = "task_updated"
	ActivityTaskCompleted   ActivityType = "task_completed"
	ActivityTaskAssigned    ActivityType = "task_assigned"
	ActivityMessageSent     ActivityType = "message_sent"
	ActivityMention         ActivityType = "mention"
	ActivityFactCreated     ActivityType = "fact_created"
	ActivityAgentActive     ActivityType = "agent_active"
	ActivityAgentIdle       ActivityType = "agent_idle"
	ActivityDocumentCreated ActivityType = "document_created"
	ActivityDocumentUpdated ActivityType = "document_updated"
	ActivitySubscribed      ActivityType = "subscribed"
)

type AgentConfig

type AgentConfig struct {
	// RunTimeoutSeconds is the max seconds for the entire agent run (default: 600).
	// One timer for the whole run, not per-turn.
	RunTimeoutSeconds int `yaml:"run_timeout_seconds"`

	// LLMCallTimeoutSeconds is the safety-net timeout per individual LLM call
	// (default: 300). Only catches hung connections — not the primary timeout.
	LLMCallTimeoutSeconds int `yaml:"llm_call_timeout_seconds"`

	// MaxTurns is a soft safety limit on LLM round-trips (default: 0 = unlimited).
	// When > 0, the agent will request a summary after this many turns.
	MaxTurns int `yaml:"max_turns"`

	// MaxContinuations is how many auto-continue rounds are allowed when
	// MaxTurns is hit and the agent is still using tools.
	// Only relevant when MaxTurns > 0. Default: 2.
	MaxContinuations int `yaml:"max_continuations"`

	// ReflectionEnabled enables periodic budget awareness nudges (default: true).
	ReflectionEnabled bool `yaml:"reflection_enabled"`

	// MaxCompactionAttempts is how many times to retry after context overflow (default: 3).
	MaxCompactionAttempts int `yaml:"max_compaction_attempts"`

	// ToolLoop configures tool loop detection thresholds.
	ToolLoop ToolLoopConfig `yaml:"tool_loop"`
}

AgentConfig holds configurable agent loop parameters.

func DefaultAgentConfig

func DefaultAgentConfig() AgentConfig

DefaultAgentConfig returns sensible defaults for agent autonomy.

type AgentEvent

type AgentEvent struct {
	RunID     string    `json:"run_id"`
	SessionID string    `json:"session_id"`
	Seq       int64     `json:"seq"`
	Stream    string    `json:"stream"` // lifecycle, assistant, tool, error
	Type      string    `json:"type"`   // delta, tool_use, tool_result, done, error, run_start, etc.
	Timestamp time.Time `json:"timestamp"`
	Data      any       `json:"data"`
}

AgentEvent represents a single typed event from an agent run.

type AgentLevel added in v1.8.0

type AgentLevel string

AgentLevel defines the autonomy level of an agent.

const (
	AgentLevelIntern     AgentLevel = "intern"     // Needs approval for actions
	AgentLevelSpecialist AgentLevel = "specialist" // Autonomous in domain
	AgentLevelLead       AgentLevel = "lead"       // Can delegate to others
)

type AgentProfileConfig

type AgentProfileConfig struct {
	// ID is the unique identifier for this agent profile.
	ID string `yaml:"id"`

	// Model is the LLM model to use (e.g., "gpt-4o", "claude-sonnet-4").
	Model string `yaml:"model"`

	// Instructions override the base system prompt for this agent.
	Instructions string `yaml:"instructions"`

	// Skills are the skill names to enable for this agent.
	Skills []string `yaml:"skills"`

	// Channels route messages from these channels to this agent.
	Channels []string `yaml:"channels"`

	// Users route messages from these users to this agent.
	Users []string `yaml:"users"`

	// Groups route messages from these groups to this agent.
	Groups []string `yaml:"groups"`

	// MaxTurns is the max LLM turns for this agent (0 = unlimited).
	MaxTurns int `yaml:"max_turns"`

	// RunTimeoutSeconds is the max run time for this agent.
	RunTimeoutSeconds int `yaml:"run_timeout_seconds"`
}

AgentProfileConfig defines a specialized agent configuration.

func (*AgentProfileConfig) MergeConfig

func (p *AgentProfileConfig) MergeConfig(baseModel, baseInstructions string, baseSkills []string) (model, instructions string, skills []string)

MergeConfig merges agent profile settings with the base config. Returns the model, instructions, and skills to use.

type AgentRouter

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

AgentRouter routes messages to the appropriate agent profile.

func NewAgentRouter

func NewAgentRouter(cfg AgentsConfig, logger *slog.Logger) *AgentRouter

NewAgentRouter creates a new agent router from configuration.

func (*AgentRouter) DefaultProfileID

func (r *AgentRouter) DefaultProfileID() string

DefaultProfileID returns the default profile ID.

func (*AgentRouter) GetProfile

func (r *AgentRouter) GetProfile(id string) *AgentProfileConfig

GetProfile returns a profile by ID.

func (*AgentRouter) ListProfiles

func (r *AgentRouter) ListProfiles() []string

ListProfiles returns all profile IDs.

func (*AgentRouter) Route

func (r *AgentRouter) Route(channel string, userJID string, groupJID string) *AgentProfileConfig

Route determines which agent profile should handle a message. Priority: user > group > channel > default.

type AgentRun

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

AgentRun encapsulates a single agent execution with its dependencies.

func NewAgentRun

func NewAgentRun(llm *LLMClient, executor *ToolExecutor, logger *slog.Logger) *AgentRun

NewAgentRun creates a new agent runner.

func NewAgentRunWithConfig

func NewAgentRunWithConfig(llm *LLMClient, executor *ToolExecutor, cfg AgentConfig, logger *slog.Logger) *AgentRun

NewAgentRunWithConfig creates a new agent runner with explicit configuration.

func (*AgentRun) Run

func (a *AgentRun) Run(ctx context.Context, systemPrompt string, history []ConversationEntry, userMessage string) (string, error)

Run executes the agent loop: builds the initial message list from conversation history, then iterates LLM calls and tool executions until a final response is produced or the turn limit is exhausted.

If auto-continue is enabled and the agent is still using tools when the budget runs out, it will automatically start a continuation round.

func (*AgentRun) RunWithUsage

func (a *AgentRun) RunWithUsage(ctx context.Context, systemPrompt string, history []ConversationEntry, userMessage string) (string, *LLMUsage, error)

RunWithUsage is like Run but also returns aggregated token usage from all LLM calls.

Architecture:

  • The loop runs until the LLM produces a response with no tool calls.
  • A single run-level timeout controls the entire execution (default: 600s).
  • Individual LLM calls have a safety-net timeout (5min) to catch hung connections.
  • No fixed turn limit — the agent keeps going as long as it has tools to call.

func (*AgentRun) SetInterruptChannel

func (a *AgentRun) SetInterruptChannel(ch <-chan string)

SetInterruptChannel sets the channel for receiving follow-up user messages during agent execution. Messages received on this channel are injected into the conversation between agent turns, allowing users to steer the agent mid-run (similar to Claude Code behavior).

func (*AgentRun) SetLoopDetector

func (a *AgentRun) SetLoopDetector(d *ToolLoopDetector)

SetLoopDetector sets the tool loop detector for this run.

func (*AgentRun) SetModelOverride

func (a *AgentRun) SetModelOverride(model string)

SetModelOverride sets the model to use instead of the default. Empty string means use the LLM client's default.

func (*AgentRun) SetOnBeforeToolExec

func (a *AgentRun) SetOnBeforeToolExec(fn func())

SetOnBeforeToolExec sets a callback fired right before tool execution starts in the agent loop. Used by the block streamer to flush buffered text so the user sees intermediate reasoning before tools run.

func (*AgentRun) SetOnToolResult

func (a *AgentRun) SetOnToolResult(fn func(name string, result ToolResult))

SetOnToolResult sets a callback fired after each tool execution completes. Used to auto-send media (e.g. generated images) to the channel.

func (*AgentRun) SetStreamCallback

func (a *AgentRun) SetStreamCallback(cb StreamCallback)

SetStreamCallback sets the callback for streaming text deltas. When set, the agent uses CompleteWithToolsStream; only text content is forwarded, tool calls are accumulated silently.

func (*AgentRun) SetUsageRecorder

func (a *AgentRun) SetUsageRecorder(fn func(model string, usage LLMUsage))

SetUsageRecorder sets a callback invoked after each successful LLM response.

type AgentStatus added in v1.8.0

type AgentStatus string

AgentStatus represents the current state of a persistent agent.

const (
	AgentStatusIdle    AgentStatus = "idle"
	AgentStatusActive  AgentStatus = "active"
	AgentStatusBlocked AgentStatus = "blocked"
	AgentStatusStopped AgentStatus = "stopped"
	AgentStatusError   AgentStatus = "error"
)

type AgentWorkingState added in v1.8.0

type AgentWorkingState struct {
	// AgentID is the agent this state belongs to.
	AgentID string `json:"agent_id" yaml:"agent_id"`

	// TeamID is the team the agent belongs to.
	TeamID string `json:"team_id" yaml:"team_id"`

	// CurrentTaskID is the task the agent is currently working on.
	CurrentTaskID string `json:"current_task_id,omitempty" yaml:"current_task_id,omitempty"`

	// Status is the work status (idle, working, blocked, waiting).
	Status string `json:"status" yaml:"status"`

	// NextSteps describes what the agent plans to do next (markdown).
	NextSteps string `json:"next_steps,omitempty" yaml:"next_steps,omitempty"`

	// Context holds additional context for resuming work.
	Context string `json:"context,omitempty" yaml:"context,omitempty"`

	// UpdatedAt is when the state was last updated.
	UpdatedAt time.Time `json:"updated_at" yaml:"updated_at"`
}

AgentWorkingState represents an agent's current work state.

type AgentsConfig

type AgentsConfig struct {
	// Profiles is the list of agent profiles.
	Profiles []AgentProfileConfig `yaml:"profiles"`

	// Routing defines how messages are routed.
	Routing RoutingConfig `yaml:"routing"`
}

AgentsConfig holds all agent profiles and routing configuration.

type AnnounceCallback

type AnnounceCallback func(run *SubagentRun)

AnnounceCallback is called when a subagent completes, allowing push-style notification to the parent session. Receives the completed run so the caller can notify the user/agent.

type ApprovalManager

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

ApprovalManager manages pending tool approvals and their resolution. It also tracks session-scoped trust: once a user approves a tool in a session, subsequent uses of the same tool are auto-approved (no re-prompting).

func NewApprovalManager

func NewApprovalManager(logger *slog.Logger) *ApprovalManager

NewApprovalManager creates a new approval manager.

func (*ApprovalManager) ClearSessionTrust

func (m *ApprovalManager) ClearSessionTrust(sessionID string)

ClearSessionTrust removes all trusted tools for a session (e.g. on /new or /reset).

func (*ApprovalManager) Create

func (m *ApprovalManager) Create(sessionID, callerJID, toolName string, args map[string]any) (id string, message string)

Create creates a pending approval and returns the ID and message for the user. The caller should send the message to the chat, then call Wait to block for the result.

func (*ApprovalManager) GrantTrust

func (m *ApprovalManager) GrantTrust(sessionID, toolName string)

GrantTrust marks a tool as trusted for the given session. Future calls to Request for this tool+session will be auto-approved.

func (*ApprovalManager) IsTrusted

func (m *ApprovalManager) IsTrusted(sessionID, toolName string) bool

IsTrusted returns true if the tool has been previously approved in this session.

func (*ApprovalManager) LatestPendingForSession

func (m *ApprovalManager) LatestPendingForSession(sessionID string) string

LatestPendingForSession returns the ID of the most recent pending approval for the given session, or empty string if none. This allows "/approve" without specifying the UUID — it resolves the latest pending request.

func (*ApprovalManager) PendingCountForSession

func (m *ApprovalManager) PendingCountForSession(sessionID string) int

PendingCountForSession returns the number of pending approvals for a session.

func (*ApprovalManager) Request

func (m *ApprovalManager) Request(sessionID, callerJID, toolName string, args map[string]any, sendMsg func(msg string)) (bool, error)

Request creates a pending approval, invokes sendMsg with the approval message, then blocks until the user approves, denies, or timeout. sendMsg is called so the user sees the approval request (e.g. send to channel).

If the tool has already been approved in this session (session trust), the request is auto-approved without prompting the user.

func (*ApprovalManager) Resolve

func (m *ApprovalManager) Resolve(id, sessionID, resolverJID string, approved bool, reason string) bool

Resolve resolves a pending approval by ID. Returns true if the approval was found and resolved. resolverJID is the user resolving (must match CallerJID for "own requests only").

func (*ApprovalManager) Wait

func (m *ApprovalManager) Wait(id string) (approved bool, err error)

Wait blocks until the approval is resolved or times out. Must be called after Create. Removes the pending approval when done.

type ApprovalResult

type ApprovalResult struct {
	Approved bool
	Reason   string
}

ApprovalResult holds the outcome of an approval request.

type Assistant

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

Assistant is the main orchestrator for DevClaw. Message flow: receive → access check → command check → trigger check → workspace resolve → input validation → context build → agent → output validation → send.

func New

func New(cfg *Config, logger *slog.Logger) *Assistant

New creates a new Assistant with all dependencies.

func (*Assistant) AccessManager

func (a *Assistant) AccessManager() *AccessManager

AccessManager returns the access manager.

func (*Assistant) ApplyConfigUpdate

func (a *Assistant) ApplyConfigUpdate(newCfg *Config)

ApplyConfigUpdate applies hot-reloadable config changes. Updates: access control, instructions, tool guard, heartbeat, token budget. Does NOT update: API, channels, model, plugins (require restart).

func (*Assistant) ChannelManager

func (a *Assistant) ChannelManager() *channels.Manager

ChannelManager returns the channel manager for external registration.

func (*Assistant) ComposePrompt

func (a *Assistant) ComposePrompt(session *Session, input string) string

ComposePrompt builds a system prompt for the given session and input. Convenience method for CLI and external callers.

func (*Assistant) Config

func (a *Assistant) Config() *Config

Config returns the assistant configuration.

func (*Assistant) ExecuteAgent

func (a *Assistant) ExecuteAgent(ctx context.Context, systemPrompt string, session *Session, userMessage string) string

ExecuteAgent runs the agent loop with tools and returns the response text. Public wrapper for CLI and external callers. Uses "default" as workspace ID.

func (*Assistant) ForceCompactSession

func (a *Assistant) ForceCompactSession(session *Session) (oldLen, newLen int)

ForceCompactSession runs compaction immediately, returns old and new history length.

func (*Assistant) GetMediaService added in v1.8.0

func (a *Assistant) GetMediaService() *media.MediaService

GetMediaService returns the media service for WebUI adapter wiring. Returns nil if native media is not enabled.

func (*Assistant) HandleCommand

func (a *Assistant) HandleCommand(msg *channels.IncomingMessage) CommandResult

HandleCommand processes an admin command from a chat message. Returns handled=true if it was a valid command (even if permission denied).

func (*Assistant) HookManager

func (a *Assistant) HookManager() *HookManager

HookManager returns the lifecycle hook manager for registering plugin hooks.

func (*Assistant) InjectVaultEnvVars

func (a *Assistant) InjectVaultEnvVars()

InjectVaultEnvVars loads all vault secrets as environment variables. Key names are uppercased and prefixed if not already (e.g. "brave_api_key" → "BRAVE_API_KEY"). Existing env vars are NOT overwritten — vault only fills gaps. This allows skills/scripts to use process.env.BRAVE_API_KEY without .env files.

func (*Assistant) LLMClient

func (a *Assistant) LLMClient() *LLMClient

LLMClient returns the LLM client (for gateway chat completions).

func (*Assistant) MediaConfig

func (a *Assistant) MediaConfig() MediaConfig

MediaConfig returns the current effective media config under read lock.

func (*Assistant) MemoryEnabled

func (a *Assistant) MemoryEnabled() bool

MemoryEnabled returns true if the memory store is available.

func (*Assistant) ProjectManager

func (a *Assistant) ProjectManager() *ProjectManager

ProjectManager returns the project manager.

func (*Assistant) SQLiteMemory

func (a *Assistant) SQLiteMemory() *memory.SQLiteStore

SQLiteMemory returns the SQLite memory store (for advanced search), or nil.

func (*Assistant) Scheduler

func (a *Assistant) Scheduler() *scheduler.Scheduler

Scheduler returns the task scheduler (may be nil if not initialized).

func (*Assistant) SchedulerEnabled

func (a *Assistant) SchedulerEnabled() bool

SchedulerEnabled returns true if the scheduler is running.

func (*Assistant) SessionStore

func (a *Assistant) SessionStore() *SessionStore

SessionStore returns the session store (used by CLI chat).

func (*Assistant) SetScheduler

func (a *Assistant) SetScheduler(s *scheduler.Scheduler)

SetScheduler configures the assistant's scheduler.

func (*Assistant) SetVault

func (a *Assistant) SetVault(v *Vault)

SetVault sets the unlocked vault for the assistant (enables vault tools).

func (*Assistant) SkillRegistry

func (a *Assistant) SkillRegistry() *skills.Registry

SkillRegistry returns the skills registry.

func (*Assistant) Start

func (a *Assistant) Start(ctx context.Context) error

Start initializes and starts all subsystems.

func (*Assistant) Stop

func (a *Assistant) Stop()

Stop gracefully shuts down all subsystems.

func (*Assistant) StopActiveRun

func (a *Assistant) StopActiveRun(workspaceID, sessionID string) bool

StopActiveRun cancels the active agent run for the given workspace and session. It also signals the tool executor to abort all running tools and forces the session out of "processing" state so new messages are handled immediately. Returns true if a run was stopped, false if none was active.

func (*Assistant) ToolExecutor

func (a *Assistant) ToolExecutor() *ToolExecutor

ToolExecutor returns the tool executor for external tool registration.

func (*Assistant) UpdateMediaConfig

func (a *Assistant) UpdateMediaConfig(media MediaConfig)

UpdateMediaConfig safely updates the media configuration under lock.

func (*Assistant) UsageTracker

func (a *Assistant) UsageTracker() *UsageTracker

UsageTracker returns the usage tracker for token/cost stats.

func (*Assistant) Vault

func (a *Assistant) Vault() *Vault

Vault returns the vault instance (may be nil if unavailable).

func (*Assistant) WorkspaceManager

func (a *Assistant) WorkspaceManager() *WorkspaceManager

WorkspaceManager returns the workspace manager.

type AuditRecord

type AuditRecord struct {
	ID            int64
	Tool          string
	Caller        string
	Level         string
	Allowed       bool
	ArgsSummary   string
	ResultSummary string
	CreatedAt     string
}

AuditRecord is a structured audit log entry.

type AuditRecordShort

type AuditRecordShort struct {
	ID          int64     `json:"id"`
	Tool        string    `json:"tool"`
	Caller      string    `json:"caller"`
	Level       string    `json:"level"`
	Allowed     bool      `json:"allowed"`
	ArgsSummary string    `json:"args_summary"`
	CreatedAt   time.Time `json:"created_at"`
}

AuditRecordShort is a shortened version of AuditRecord for display.

type BlockStreamConfig

type BlockStreamConfig struct {
	// Enabled turns block streaming on/off (default: true).
	Enabled bool `yaml:"enabled"`

	// MinChars is the minimum characters to accumulate before sending a block (default: 20).
	// Kept low for near-instant first-block feedback.
	MinChars int `yaml:"min_chars"`

	// MaxChars is the maximum characters per block before a forced flush (default: 600).
	MaxChars int `yaml:"max_chars"`

	// IdleMs is the idle timeout in milliseconds: if no new tokens arrive within
	// this window, flush whatever is buffered (default: 200).
	IdleMs int `yaml:"idle_ms"`
}

BlockStreamConfig configures the progressive message streaming behavior.

func DefaultBlockStreamConfig

func DefaultBlockStreamConfig() BlockStreamConfig

DefaultBlockStreamConfig returns sensible defaults for block streaming. Tuned for WhatsApp/chat UX: each flush = a new message, so we prioritize sending coherent paragraphs over low-latency fragments. MinChars must be high enough to avoid sending single-line fragments as separate messages.

func (BlockStreamConfig) Effective

func (c BlockStreamConfig) Effective() BlockStreamConfig

Effective returns a copy with defaults filled in for zero values.

type BlockStreamer

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

BlockStreamer accumulates LLM stream tokens and sends them progressively to a channel. It is tied to a single message exchange (one user message → one agent response).

func NewBlockStreamer

func NewBlockStreamer(
	cfg BlockStreamConfig,
	channelMgr *channels.Manager,
	channel, chatID, replyTo string,
) *BlockStreamer

NewBlockStreamer creates a streamer that progressively sends blocks to the given channel.

func (*BlockStreamer) Finish

func (bs *BlockStreamer) Finish()

Finish flushes any remaining buffer and marks the streamer as done.

func (*BlockStreamer) FlushNow

func (bs *BlockStreamer) FlushNow()

FlushNow immediately sends any buffered text to the channel, regardless of MinChars threshold. Use this before tool execution to ensure the user sees the LLM's intermediate text (thoughts/reasoning) before tools start running.

func (*BlockStreamer) HasSentBlocks

func (bs *BlockStreamer) HasSentBlocks() bool

HasSentBlocks returns true if at least one block was sent progressively.

func (*BlockStreamer) StreamCallback

func (bs *BlockStreamer) StreamCallback() StreamCallback

StreamCallback returns a StreamCallback function suitable for AgentRun.SetStreamCallback.

type BrowserConfig

type BrowserConfig struct {
	// Enabled turns the browser tool on/off (default: true if Chrome is found).
	Enabled bool `yaml:"enabled"`

	// ChromePath is the path to the Chrome/Chromium binary.
	// Auto-detected if empty.
	ChromePath string `yaml:"chrome_path"`

	// Headless runs the browser without a visible window (default: true).
	Headless bool `yaml:"headless"`

	// TimeoutSeconds is the max time for a single browser operation (default: 30).
	TimeoutSeconds int `yaml:"timeout_seconds"`

	// MaxPages is the max number of simultaneous pages/tabs (default: 3).
	MaxPages int `yaml:"max_pages"`

	// ViewportWidth is the browser viewport width (default: 1280).
	ViewportWidth int `yaml:"viewport_width"`

	// ViewportHeight is the browser viewport height (default: 720).
	ViewportHeight int `yaml:"viewport_height"`
}

BrowserConfig configures the browser tool.

func DefaultBrowserConfig

func DefaultBrowserConfig() BrowserConfig

DefaultBrowserConfig returns sensible defaults.

type BrowserManager

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

BrowserManager manages a Chrome/Chromium process and CDP connections.

func NewBrowserManager

func NewBrowserManager(cfg BrowserConfig, logger *slog.Logger) *BrowserManager

NewBrowserManager creates a new browser manager.

func (*BrowserManager) ClickElement

func (bm *BrowserManager) ClickElement(ctx context.Context, selector string) error

ClickElement clicks an element matched by CSS selector.

func (*BrowserManager) FillInput

func (bm *BrowserManager) FillInput(ctx context.Context, selector, value string) error

FillInput fills a text input matched by CSS selector.

func (*BrowserManager) GetContent

func (bm *BrowserManager) GetContent(ctx context.Context) (string, error)

GetContent returns the text content of the current page.

func (*BrowserManager) Navigate

func (bm *BrowserManager) Navigate(ctx context.Context, url string) error

Navigate opens a URL in the browser.

func (*BrowserManager) Screenshot

func (bm *BrowserManager) Screenshot(ctx context.Context) (string, error)

Screenshot captures the current page as a PNG and returns base64-encoded data.

func (*BrowserManager) Start

func (bm *BrowserManager) Start(ctx context.Context) error

Start launches Chrome with CDP enabled. Called lazily on first tool use.

func (*BrowserManager) Stop

func (bm *BrowserManager) Stop()

Stop kills the Chrome process and closes connections.

func (*BrowserManager) WithSSRFGuard

func (bm *BrowserManager) WithSSRFGuard(guard *security.SSRFGuard) *BrowserManager

WithSSRFGuard attaches an SSRF guard to the browser manager. When set, Navigate() will validate URLs before loading them.

type BudgetConfig

type BudgetConfig struct {
	// MonthlyLimitUSD is the maximum monthly spend (0 = unlimited).
	MonthlyLimitUSD float64 `yaml:"monthly_limit_usd"`

	// WarnAtPercent triggers a warning when this % of budget is reached (default: 80).
	WarnAtPercent int `yaml:"warn_at_percent"`

	// ActionAtLimit defines behavior when limit is reached: "warn", "block", "fallback_local".
	ActionAtLimit string `yaml:"action_at_limit"`
}

BudgetConfig configures monthly cost tracking and limits.

func DefaultBudgetConfig

func DefaultBudgetConfig() BudgetConfig

DefaultBudgetConfig returns sensible defaults for budget tracking.

type Canvas

type Canvas struct {
	ID        string    `json:"id"`
	Title     string    `json:"title"`
	HTML      string    `json:"-"`
	Port      int       `json:"port"`
	URL       string    `json:"url"`
	CreatedAt time.Time `json:"created_at"`
	UpdatedAt time.Time `json:"updated_at"`
	// contains filtered or unexported fields
}

Canvas represents a single hosted HTML page with live-reload support.

type CanvasConfig

type CanvasConfig struct {
	// Enabled turns canvas hosting on/off (default: true).
	Enabled bool `yaml:"enabled"`

	// BasePort is the starting port for canvas servers (default: 9100).
	// Each canvas gets its own port: BasePort, BasePort+1, etc.
	BasePort int `yaml:"base_port"`

	// MaxCanvases is the max number of simultaneous canvas servers (default: 5).
	MaxCanvases int `yaml:"max_canvases"`

	// TTLMinutes is how long a canvas stays alive without updates (default: 30).
	TTLMinutes int `yaml:"ttl_minutes"`
}

CanvasConfig configures the canvas host.

func DefaultCanvasConfig

func DefaultCanvasConfig() CanvasConfig

DefaultCanvasConfig returns sensible defaults.

type CanvasHost

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

CanvasHost manages multiple canvas servers.

func NewCanvasHost

func NewCanvasHost(cfg CanvasConfig, logger *slog.Logger) *CanvasHost

NewCanvasHost creates a new canvas host.

func (*CanvasHost) CleanupStale

func (ch *CanvasHost) CleanupStale() int

CleanupStale removes canvases that haven't been updated within TTL.

func (*CanvasHost) Create

func (ch *CanvasHost) Create(id, title, html string) (*Canvas, error)

Create creates and starts a new canvas with the given HTML content.

func (*CanvasHost) List

func (ch *CanvasHost) List() []*Canvas

List returns metadata for all active canvases.

func (*CanvasHost) Stop

func (ch *CanvasHost) Stop(id string) error

Stop stops a specific canvas server.

func (*CanvasHost) StopAll

func (ch *CanvasHost) StopAll()

StopAll stops all canvas servers.

func (*CanvasHost) Update

func (ch *CanvasHost) Update(id, html string) error

Update replaces the HTML content of an existing canvas and triggers live-reload.

type ChannelDiagnostic

type ChannelDiagnostic struct {
	Name       string `json:"name"`
	Connected  bool   `json:"connected"`
	TestResult string `json:"test_result"`
	LatencyMs  int64  `json:"latency_ms"`
}

ChannelDiagnostic represents diagnostic result for a single channel.

type ChannelHealth

type ChannelHealth struct {
	Connected     bool           `json:"connected"`
	LastMessageAt time.Time      `json:"last_message_at,omitempty"`
	ErrorCount    int            `json:"error_count"`
	LatencyMs     int64          `json:"latency_ms"`
	Details       map[string]any `json:"details,omitempty"`
}

ChannelHealth represents health status of a single channel.

type ChannelsConfig

type ChannelsConfig struct {
	// WhatsApp is the WhatsApp channel config (core).
	WhatsApp whatsapp.Config `yaml:"whatsapp"`

	// Telegram is the Telegram channel config (core).
	Telegram telegram.Config `yaml:"telegram"`

	// Discord is the Discord channel config (core).
	Discord discord.Config `yaml:"discord"`

	// Slack is the Slack channel config (core).
	Slack slack.Config `yaml:"slack"`
}

ChannelsConfig holds configuration for all channels.

type CheckResult

type CheckResult struct {
	// Allowed is true if the contact can interact.
	Allowed bool

	// Level is the resolved access level.
	Level AccessLevel

	// ShouldAsk is true if we should send a "request access" message.
	ShouldAsk bool

	// Reason explains why access was denied (for logging).
	Reason string
}

CheckResult contains the result of an access check.

type CommandResult

type CommandResult struct {
	// Response is the text to send back.
	Response string

	// Handled is true if the message was a valid command.
	Handled bool
}

CommandResult contains the result of a command execution.

type Config

type Config struct {
	// Name is the assistant name shown in responses.
	Name string `yaml:"name"`

	// Trigger is the keyword that activates the bot (e.g. "@devclaw").
	Trigger string `yaml:"trigger"`

	// Model is the LLM model to use (e.g. "glm-4.7-flash").
	Model string `yaml:"model"`

	// API configures the LLM provider endpoint.
	API APIConfig `yaml:"api"`

	// Instructions are the base system prompt instructions.
	Instructions string `yaml:"instructions"`

	// Timezone is the user's timezone (e.g. "America/Sao_Paulo").
	Timezone string `yaml:"timezone"`

	// Language is the preferred response language (e.g. "pt-BR").
	Language string `yaml:"language"`

	// Access configures who can use the bot (allowlist/blocklist).
	Access AccessConfig `yaml:"access"`

	// Workspaces configures isolated profiles/contexts.
	Workspaces WorkspaceConfig `yaml:"workspaces"`

	// Channels configures communication channels.
	Channels ChannelsConfig `yaml:"channels"`

	// Memory configures the memory system.
	Memory MemoryConfig `yaml:"memory"`

	// Security configures security guardrails.
	Security SecurityConfig `yaml:"security"`

	// TokenBudget configures per-layer token limits.
	TokenBudget TokenBudgetConfig `yaml:"token_budget"`

	// Plugins configures the plugin loader.
	Plugins plugins.Config `yaml:"plugins"`

	// Sandbox configures the script sandbox.
	Sandbox sandbox.Config `yaml:"sandbox"`

	// Skills configures which skills are enabled.
	Skills SkillsConfig `yaml:"skills"`

	// Scheduler configures the task scheduler.
	Scheduler SchedulerConfig `yaml:"scheduler"`

	// Heartbeat configures the proactive heartbeat system.
	Heartbeat HeartbeatConfig `yaml:"heartbeat"`

	// Subagents configures the subagent orchestration system.
	Subagents SubagentConfig `yaml:"subagents"`

	// Agent configures the agent loop parameters (turns, timeouts, auto-continue).
	Agent AgentConfig `yaml:"agent"`

	// Fallback configures model fallback with retry and backoff.
	Fallback FallbackConfig `yaml:"fallback"`

	// Budget configures monthly cost tracking and limits.
	Budget BudgetConfig `yaml:"budget"`

	// Team configures multi-user mode.
	Team TeamConfig `yaml:"team"`

	// Media configures vision and audio transcription.
	Media MediaConfig `yaml:"media"`

	// Logging configures log output.
	Logging LoggingConfig `yaml:"logging"`

	// Queue configures message debouncing for bursts.
	Queue QueueConfig `yaml:"queue"`

	// Database configures the central SQLite database (devclaw.db).
	Database DatabaseConfig `yaml:"database"`

	// Gateway configures the HTTP API gateway.
	Gateway GatewayConfig `yaml:"gateway"`

	// BlockStream configures progressive message delivery (stream text to channel
	// in chunks instead of waiting for the complete response).
	BlockStream BlockStreamConfig `yaml:"block_stream"`

	// WebSearch configures the web search tool provider.
	WebSearch WebSearchConfig `yaml:"web_search"`

	// TTS configures text-to-speech synthesis.
	TTS TTSConfig `yaml:"tts"`

	// WebUI configures the web dashboard.
	WebUI webui.Config `yaml:"webui"`

	// Group configures group chat behavior.
	Group GroupConfig `yaml:"group"`

	// Agents configures specialized agent profiles and routing.
	Agents AgentsConfig `yaml:"agents"`

	// Groups configures group-specific policies and activation modes.
	Groups GroupsPolicyConfig `yaml:"groups"`

	// Hooks configures lifecycle hooks and webhooks.
	Hooks HooksConfig `yaml:"hooks"`

	// MCP configures Model Context Protocol servers.
	MCP MCPConfig `yaml:"mcp"`

	// Routines configures background routines (metrics, memory indexer, etc).
	Routines RoutinesConfig `yaml:"routines"`

	// NativeMedia configures the native media handling system.
	NativeMedia NativeMediaConfig `yaml:"native_media"`
}

Config holds all assistant configuration.

func DefaultConfig

func DefaultConfig() *Config

DefaultConfig returns the default assistant configuration.

func LoadConfigFromFile

func LoadConfigFromFile(path string) (*Config, error)

LoadConfigFromFile reads and parses a YAML configuration file. Automatically loads .env files and expands environment variables. Returns an error if any ${VAR:?error} pattern has its variable unset.

func ParseConfig

func ParseConfig(data []byte) (*Config, error)

ParseConfig parses YAML bytes into a Config. Starts with defaults and overlays values from the YAML.

type ConfigHealth

type ConfigHealth struct {
	Valid    bool     `json:"valid"`
	Path     string   `json:"path"`
	Errors   []string `json:"errors,omitempty"`
	Sections []string `json:"sections"`
}

ConfigHealth represents configuration health status.

type ConfigWatcher

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

ConfigWatcher monitors a config file for changes and invokes a callback when the file is modified. Uses polling (mtime + sha256) to avoid platform-specific file watchers.

func NewConfigWatcher

func NewConfigWatcher(path string, interval time.Duration, onChange func(*Config), logger *slog.Logger) *ConfigWatcher

NewConfigWatcher creates a new config watcher. interval is the polling interval (e.g. 5 * time.Second). onChange is called when a valid config change is detected.

func (*ConfigWatcher) Start

func (w *ConfigWatcher) Start(ctx context.Context)

Start begins polling in a goroutine. Exits when ctx is cancelled.

type ConversationEntry

type ConversationEntry struct {
	UserMessage       string
	AssistantResponse string
	Timestamp         time.Time
}

ConversationEntry representa uma troca de mensagem na sessão.

type CooldownConfig

type CooldownConfig struct {
	BillingBackoffHours   float64 `yaml:"billing_backoff_hours"`   // Default: 5
	BillingMaxHours       float64 `yaml:"billing_max_hours"`       // Default: 24
	FailureWindowHours    float64 `yaml:"failure_window_hours"`    // Default: 24
	InitialBackoffMinutes float64 `yaml:"initial_backoff_minutes"` // Default: 1
	MaxBackoffMinutes     float64 `yaml:"max_backoff_minutes"`     // Default: 60
}

CooldownConfig defines backoff parameters for model cooldowns.

func DefaultCooldownConfig

func DefaultCooldownConfig() CooldownConfig

DefaultCooldownConfig returns sensible defaults.

type Daemon

type Daemon struct {
	Label     string    `json:"label"`
	Command   string    `json:"command"`
	PID       int       `json:"pid"`
	Port      int       `json:"port,omitempty"`
	Status    string    `json:"status"` // running, stopped, failed
	StartedAt time.Time `json:"started_at"`
	ExitCode  int       `json:"exit_code,omitempty"`
	Error     string    `json:"error,omitempty"`
	// contains filtered or unexported fields
}

Daemon represents a managed background process.

type DaemonManager

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

DaemonManager manages a set of background daemons.

func NewDaemonManager

func NewDaemonManager() *DaemonManager

NewDaemonManager creates a new daemon manager.

func (*DaemonManager) GetLogs

func (dm *DaemonManager) GetLogs(label string, n int, filter string) (string, error)

GetLogs returns the last n lines from a daemon's output ring buffer.

func (*DaemonManager) List

func (dm *DaemonManager) List() []Daemon

List returns info about all managed daemons.

func (*DaemonManager) RestartDaemon

func (dm *DaemonManager) RestartDaemon(label string) (*Daemon, error)

RestartDaemon stops and re-starts a daemon with the same config.

func (*DaemonManager) Shutdown

func (dm *DaemonManager) Shutdown()

Shutdown stops all running daemons.

func (*DaemonManager) StartDaemon

func (dm *DaemonManager) StartDaemon(label, command string, port int, readyPattern string) (*Daemon, error)

StartDaemon starts a new background process.

func (*DaemonManager) StopDaemon

func (dm *DaemonManager) StopDaemon(label string, force bool) error

StopDaemon gracefully stops a daemon (SIGTERM). If force is true, uses SIGKILL.

type DatabaseConfig

type DatabaseConfig struct {
	// Path is the database file path for SQLite (default: "./data/devclaw.db").
	// Kept for backward compatibility with existing configs.
	Path string `yaml:"path"`

	// Hub enables the new Database Hub system with multi-backend support.
	// When Hub.Backend is not set, falls back to Path for SQLite.
	Hub database.HubConfig `yaml:"hub"`
}

DatabaseConfig configures the central database using the Database Hub. Supports SQLite (default), PostgreSQL, and MySQL backends.

func (DatabaseConfig) Effective added in v1.8.0

func (c DatabaseConfig) Effective() database.HubConfig

Effective returns the effective Hub configuration, applying defaults.

type DatabaseHealth

type DatabaseHealth struct {
	Connected bool           `json:"connected"`
	SizeMB    float64        `json:"size_mb"`
	Tables    map[string]int `json:"tables"`
	Error     string         `json:"error,omitempty"`
}

DatabaseHealth represents database health status.

type DeliveryTarget

type DeliveryTarget struct {
	Channel string
	ChatID  string
}

DeliveryTarget holds the channel and chatID for message delivery.

func DeliveryTargetFromContext

func DeliveryTargetFromContext(ctx context.Context) DeliveryTarget

DeliveryTargetFromContext extracts the delivery target from a context. Returns empty DeliveryTarget if not set.

type DiagnosticsResult

type DiagnosticsResult struct {
	Database     DatabaseHealth      `json:"database"`
	Config       ConfigHealth        `json:"config"`
	Channels     []ChannelDiagnostic `json:"channels"`
	RecentErrors []AuditRecordShort  `json:"recent_errors,omitempty"`
	Memory       MemoryStats         `json:"memory"`
	Disk         DiskStats           `json:"disk"`
}

DiagnosticsResult represents comprehensive system diagnostics.

type DiskStats

type DiskStats struct {
	TotalGB float64 `json:"total_gb"`
	FreeGB  float64 `json:"free_gb"`
	UsedPct float64 `json:"used_pct"`
}

DiskStats represents disk usage statistics.

type DocumentType added in v1.8.0

type DocumentType string

DocumentType represents the type of team document.

const (
	DocumentTypeDeliverable DocumentType = "deliverable"
	DocumentTypeResearch    DocumentType = "research"
	DocumentTypeProtocol    DocumentType = "protocol"
	DocumentTypeNotes       DocumentType = "notes"
)

type EventBus

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

EventBus is a thread-safe pub/sub hub for agent events. Subscribers receive events synchronously during Emit — callers should keep listener logic fast or dispatch to goroutines internally.

func NewEventBus

func NewEventBus() *EventBus

NewEventBus creates a new event bus.

func (*EventBus) CleanupRun

func (eb *EventBus) CleanupRun(runID string)

CleanupRun removes the sequence counter for a completed run.

func (*EventBus) Emit

func (eb *EventBus) Emit(event AgentEvent)

Emit sends an event to all registered listeners. The event's Seq field is auto-assigned using an atomic counter scoped to the run ID.

func (*EventBus) EmitDelta

func (eb *EventBus) EmitDelta(runID, sessionID, content string)

EmitDelta is a convenience method for emitting text delta events.

func (*EventBus) EmitDone

func (eb *EventBus) EmitDone(runID, sessionID string, usage any)

EmitDone emits a run completion event.

func (*EventBus) EmitError

func (eb *EventBus) EmitError(runID, sessionID, message string)

EmitError emits an error event.

func (*EventBus) EmitThinkingDelta

func (eb *EventBus) EmitThinkingDelta(runID, sessionID, content string)

EmitThinkingDelta emits a thinking_delta event with a partial reasoning token.

func (*EventBus) EmitThinkingEnd

func (eb *EventBus) EmitThinkingEnd(runID, sessionID string)

EmitThinkingEnd emits a thinking_end event. Callers are responsible for deduplication — this method emits unconditionally. The recommended pattern is to track a thinkingActive bool and only call EmitThinkingEnd once.

func (*EventBus) EmitThinkingStart

func (eb *EventBus) EmitThinkingStart(runID, sessionID string)

EmitThinkingStart emits a thinking_start event signalling the beginning of an extended thinking / reasoning block.

func (*EventBus) EmitToolResult

func (eb *EventBus) EmitToolResult(runID, sessionID, toolName, output string, isError bool)

EmitToolResult emits a tool result event.

func (*EventBus) EmitToolUse

func (eb *EventBus) EmitToolUse(runID, sessionID, toolName string, input any)

EmitToolUse emits a tool invocation event.

func (*EventBus) Subscribe

func (eb *EventBus) Subscribe(fn EventListener) func()

Subscribe registers a listener and returns an unsubscribe function. The listener is called synchronously for every emitted event.

func (*EventBus) SubscribeRun

func (eb *EventBus) SubscribeRun(runID string, fn EventListener) func()

SubscribeRun registers a listener that only receives events for a specific run. Returns an unsubscribe function.

type EventListener

type EventListener func(event AgentEvent)

EventListener is a callback that receives agent events.

type ExecAnalysisConfig

type ExecAnalysisConfig struct {
	// Enabled turns the analysis on/off.
	Enabled bool `yaml:"enabled"`

	// Categories configures each risk category.
	Categories map[RiskLevel]RiskCategoryConfig `yaml:"categories"`

	// SafeBins lists binary paths that are always safe.
	SafeBins []string `yaml:"safe_bins"`

	// Trust configures per-role trust levels.
	Trust TrustConfig `yaml:"trust"`

	// SuspiciousPatterns are regex patterns for suspicious constructs.
	SuspiciousPatterns []string `yaml:"suspicious_patterns"`

	// DefaultAction is the action for commands that don't match any category.
	DefaultAction RiskAction `yaml:"default_action"`
}

ExecAnalysisConfig configures the exec analysis system.

func DefaultExecAnalysisConfig

func DefaultExecAnalysisConfig() ExecAnalysisConfig

DefaultExecAnalysisConfig returns sensible defaults.

type ExecAnalysisResult

type ExecAnalysisResult struct {
	// Risk is the determined risk level.
	Risk RiskLevel

	// Action is the action to take.
	Action RiskAction

	// Reason explains why this risk level was assigned.
	Reason string

	// MatchedPattern is the pattern that matched (if any).
	MatchedPattern string

	// IsSuspicious indicates if suspicious constructs were found.
	IsSuspicious bool

	// SuspiciousMatches lists the suspicious patterns found.
	SuspiciousMatches []string
}

ExecAnalysisResult contains the analysis result for a command.

type ExecAnalyzer

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

ExecAnalyzer analyzes commands for risk.

func NewExecAnalyzer

func NewExecAnalyzer(cfg ExecAnalysisConfig, logger *slog.Logger) *ExecAnalyzer

NewExecAnalyzer creates a new command analyzer.

func (*ExecAnalyzer) Analyze

func (a *ExecAnalyzer) Analyze(command string) ExecAnalysisResult

Analyze analyzes a command and returns the risk assessment.

func (*ExecAnalyzer) AnalyzeForRole

func (a *ExecAnalyzer) AnalyzeForRole(command string, role string) ExecAnalysisResult

AnalyzeForRole analyzes a command considering the user's role.

type ExecQueueItem

type ExecQueueItem struct {
	ID          string    `json:"id"`
	Tool        string    `json:"tool"`
	Caller      string    `json:"caller"`
	SessionID   string    `json:"session_id"`
	Description string    `json:"description"`
	CreatedAt   time.Time `json:"created_at"`
}

ExecQueueItem represents a pending execution approval.

type ExportedMessage

type ExportedMessage struct {
	User      string    `json:"user"`
	Assistant string    `json:"assistant"`
	Timestamp time.Time `json:"timestamp"`
}

ExportedMessage is a single message in an exported session.

type FailoverReason

type FailoverReason string

FailoverReason classifies why a model failed.

const (
	FailoverBilling   FailoverReason = "billing"    // 402 Payment Required
	FailoverRateLimit FailoverReason = "rate_limit" // 429 Too Many Requests
	FailoverAuth      FailoverReason = "auth"       // 401/403
	FailoverTimeout   FailoverReason = "timeout"    // 408, ETIMEDOUT, empty chunks
	FailoverFormat    FailoverReason = "format"     // 400 Bad Request
	FailoverServer    FailoverReason = "server"     // 5xx
	FailoverUnknown   FailoverReason = "unknown"
)

func ClassifyError

func ClassifyError(statusCode int, errMsg string) FailoverReason

ClassifyError determines the failover reason from an HTTP status code and error message.

type FallbackConfig

type FallbackConfig struct {
	// Models is the ordered list of fallback models to try on failure.
	// Supports N providers: primary -> fallback1 -> fallback2 -> ... -> local.
	Models []string `yaml:"models"`

	// Chain defines provider-specific fallback with separate base_url/api_key.
	// Each entry is a complete provider config tried in order on failure.
	Chain []ProviderChainEntry `yaml:"chain"`

	// MaxRetries per model before moving to next (default: 2).
	MaxRetries int `yaml:"max_retries"`

	// InitialBackoffMs is the initial retry delay in ms (default: 1000).
	InitialBackoffMs int `yaml:"initial_backoff_ms"`

	// MaxBackoffMs caps the backoff (default: 30000).
	MaxBackoffMs int `yaml:"max_backoff_ms"`

	// RetryOnStatusCodes lists HTTP codes that trigger retry (default: [429, 500, 502, 503, 529]).
	RetryOnStatusCodes []int `yaml:"retry_on_status_codes"`
}

FallbackConfig configures model fallback and retry behavior.

func DefaultFallbackConfig

func DefaultFallbackConfig() FallbackConfig

DefaultFallbackConfig returns sensible defaults for model fallback.

func (FallbackConfig) Effective

func (f FallbackConfig) Effective() FallbackConfig

Effective returns a copy with default values filled in for zero fields.

type FunctionCall

type FunctionCall struct {
	Name      string `json:"name"`
	Arguments string `json:"arguments"`
}

FunctionCall holds the function name and serialized arguments from the LLM.

type FunctionDef

type FunctionDef struct {
	Name        string          `json:"name"`
	Description string          `json:"description"`
	Parameters  json.RawMessage `json:"parameters"`
}

FunctionDef describes a callable function exposed to the LLM.

type GatewayConfig

type GatewayConfig struct {
	// Enabled turns the gateway on/off (default: false).
	Enabled bool `yaml:"enabled"`

	// Address is the listen address (default: ":8085").
	Address string `yaml:"address"`

	// AuthToken is the Bearer token for /api/* and /v1/* auth (empty = no auth).
	AuthToken string `yaml:"auth_token"`

	// CORSOrigins lists allowed origins for CORS (empty = no CORS).
	CORSOrigins []string `yaml:"cors_origins"`
}

GatewayConfig configures the HTTP API gateway.

type GroupConfig

type GroupConfig struct {
	// ActivationMode controls when the bot responds in groups:
	//   "always"  — responds to all messages (default)
	//   "mention" — only when mentioned by name/trigger
	//   "reply"   — only when replied to directly
	ActivationMode string `yaml:"activation_mode"`

	// IntroMessage is sent when the bot joins a new group.
	// Empty = no intro. Supports template variables: {{name}}, {{trigger}}.
	IntroMessage string `yaml:"intro_message"`

	// ContextInjection adds group-specific context to the system prompt.
	// Useful for per-group instructions, rules, or personas.
	ContextInjection map[string]string `yaml:"context_injection"`

	// MaxParticipants limits context tracking for group participants.
	// Names of the last N participants are included in the prompt for
	// natural multi-party conversation (default: 20).
	MaxParticipants int `yaml:"max_participants"`

	// QuietHours defines time ranges when the bot won't respond in groups
	// (e.g. "23:00-07:00"). Empty = always active.
	QuietHours string `yaml:"quiet_hours"`

	// IgnorePatterns are regex patterns for messages the bot should ignore
	// even when activated (e.g. forwarded messages, bot commands for other bots).
	IgnorePatterns []string `yaml:"ignore_patterns"`
}

GroupConfig configures group chat behavior.

type GroupManager

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

GroupManager handles group-specific behavior and state.

func NewGroupManager

func NewGroupManager(cfg GroupConfig) *GroupManager

NewGroupManager creates a new group manager with the given config.

func (*GroupManager) BuildGroupPromptContext

func (gm *GroupManager) BuildGroupPromptContext(chatID, botName string) string

BuildGroupPromptContext builds a group-specific section for the system prompt.

func (*GroupManager) GetContextInjection

func (gm *GroupManager) GetContextInjection(chatID string) string

GetContextInjection returns any group-specific context for the given chatID.

func (*GroupManager) GetIntroMessage

func (gm *GroupManager) GetIntroMessage(chatID, botName, trigger string) string

GetIntroMessage returns the intro message for a group, or empty if already sent. Marks the group as introduced so the message is only sent once.

func (*GroupManager) GetParticipants

func (gm *GroupManager) GetParticipants(chatID string) []string

GetParticipants returns the recent participant names for a group.

func (*GroupManager) ShouldRespond

func (gm *GroupManager) ShouldRespond(chatID, senderName, messageText, botName, trigger string) (bool, string)

ShouldRespond determines if the bot should respond to a group message. Returns (respond bool, reason string).

func (*GroupManager) TrackParticipant

func (gm *GroupManager) TrackParticipant(chatID, name string)

TrackParticipant records a participant's activity in a group.

type GroupPolicy

type GroupPolicy string

GroupPolicy defines the access policy for a group.

const (
	// GroupPolicyOpen allows all group members to use the bot.
	GroupPolicyOpen GroupPolicy = "open"
	// GroupPolicyDisabled prevents the bot from responding in this group.
	GroupPolicyDisabled GroupPolicy = "disabled"
	// GroupPolicyAllowlist restricts access to allowed users only.
	GroupPolicyAllowlist GroupPolicy = "allowlist"
)

type GroupPolicyConfig

type GroupPolicyConfig struct {
	// ID is the group JID.
	ID string `yaml:"id"`
	// Name is a human-readable name for the group.
	Name string `yaml:"name"`
	// Policy is the access policy for this group.
	Policy GroupPolicy `yaml:"policy"`
	// Activation is the activation mode.
	Activation ActivationMode `yaml:"activation"`
	// Keywords trigger the bot in keyword mode.
	Keywords []string `yaml:"keywords"`
	// Workspace is the workspace to use for this group.
	Workspace string `yaml:"workspace"`
	// QuietHours defines when the bot should be silent.
	QuietHours *QuietHoursConfig `yaml:"quiet_hours"`
	// MaxParticipants ignores messages in groups larger than this (0 = unlimited).
	MaxParticipants int `yaml:"max_participants"`
	// AllowedUsers is the list of allowed user JIDs for allowlist policy.
	AllowedUsers []string `yaml:"allowed_users"`
}

GroupPolicyConfig holds configuration for a specific group's policy.

type GroupPolicyManager

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

GroupPolicyManager manages group-specific policies.

func NewGroupPolicyManager

func NewGroupPolicyManager(cfg GroupsPolicyConfig, logger *slog.Logger) *GroupPolicyManager

NewGroupPolicyManager creates a new group policy manager.

func (*GroupPolicyManager) GetGroupConfig

func (m *GroupPolicyManager) GetGroupConfig(groupJID string) *GroupPolicyConfig

GetGroupConfig returns the configuration for a group. Returns a default config if the group is not explicitly configured.

func (*GroupPolicyManager) GetWorkspace

func (m *GroupPolicyManager) GetWorkspace(groupJID string) string

GetWorkspace returns the workspace for a group, or empty string if not set.

func (*GroupPolicyManager) IsBlocked

func (m *GroupPolicyManager) IsBlocked(groupJID string) bool

IsBlocked returns true if the group is blocked.

func (*GroupPolicyManager) IsQuietHours

func (m *GroupPolicyManager) IsQuietHours(cfg *GroupPolicyConfig) bool

IsQuietHours checks if quiet hours are active for a group.

func (*GroupPolicyManager) ListBlocked

func (m *GroupPolicyManager) ListBlocked() []string

ListBlocked returns all blocked group IDs.

func (*GroupPolicyManager) ListGroups

func (m *GroupPolicyManager) ListGroups() []string

ListGroups returns all configured group IDs.

func (*GroupPolicyManager) ShouldRespond

func (m *GroupPolicyManager) ShouldRespond(groupJID, userJID string, content string, isReplyToBot bool, trigger string) bool

ShouldRespond determines if the bot should respond to a message in a group.

type GroupsPolicyConfig

type GroupsPolicyConfig struct {
	// DefaultPolicy is the policy for groups not explicitly configured.
	DefaultPolicy GroupPolicy `yaml:"default_policy"`
	// Groups is the list of group-specific configurations.
	Groups []GroupPolicyConfig `yaml:"groups"`
	// Blocked is the list of blocked group JIDs.
	Blocked []string `yaml:"blocked"`
}

GroupsPolicyConfig holds all group policy configuration.

type HandlerConfig

type HandlerConfig struct {
	// Event is the hook event to listen to.
	Event string `yaml:"event"`

	// Action is the action to take (notify_admins, send_message).
	Action string `yaml:"action"`

	// Template is a Go template for the action output.
	Template string `yaml:"template"`

	// Enabled controls whether this handler is active.
	Enabled bool `yaml:"enabled"`
}

HandlerConfig configures an internal hook handler.

type HealthCheckResult

type HealthCheckResult struct {
	Component string `json:"component"`
	Status    string `json:"status"` // "PASS", "FAIL", "WARN"
	Message   string `json:"message"`
	LatencyMs int64  `json:"latency_ms,omitempty"`
}

HealthCheckResult represents the result of a health check.

type Heartbeat

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

Heartbeat runs periodic checks and proactive behavior.

func NewHeartbeat

func NewHeartbeat(cfg HeartbeatConfig, assistant *Assistant, logger *slog.Logger) *Heartbeat

NewHeartbeat creates a new heartbeat instance.

func (*Heartbeat) Start

func (h *Heartbeat) Start(ctx context.Context)

Start begins the heartbeat loop in a background goroutine.

func (*Heartbeat) Stop

func (h *Heartbeat) Stop()

Stop shuts down the heartbeat.

func (*Heartbeat) UpdateConfig

func (h *Heartbeat) UpdateConfig(cfg HeartbeatConfig)

UpdateConfig updates the heartbeat config from hot-reload.

type HeartbeatConfig

type HeartbeatConfig struct {
	// Enabled turns the heartbeat on/off.
	Enabled bool `yaml:"enabled"`

	// Interval is the time between heartbeat ticks.
	// Default: 30 minutes.
	Interval time.Duration `yaml:"interval"`

	// ActiveStart is the earliest hour the heartbeat runs (e.g., 9 for 9 AM).
	ActiveStart int `yaml:"active_start"`

	// ActiveEnd is the latest hour the heartbeat runs (e.g., 22 for 10 PM).
	ActiveEnd int `yaml:"active_end"`

	// Channel is the default channel to send proactive messages to.
	Channel string `yaml:"channel"`

	// ChatID is the default chat to send proactive messages to.
	ChatID string `yaml:"chat_id"`

	// WorkspaceDir is the workspace directory where HEARTBEAT.md is located.
	WorkspaceDir string `yaml:"workspace_dir"`
}

HeartbeatConfig configures the heartbeat system.

func DefaultHeartbeatConfig

func DefaultHeartbeatConfig() HeartbeatConfig

DefaultHeartbeatConfig returns sensible defaults for the heartbeat.

type HookAction

type HookAction struct {
	// Block prevents the operation from proceeding (PreToolUse, UserPromptSubmit).
	Block bool

	// Reason explains why the operation was blocked.
	Reason string

	// ModifiedArgs replaces ToolArgs if non-nil (PreToolUse only).
	ModifiedArgs map[string]any

	// ModifiedMessage replaces the user message if non-empty (UserPromptSubmit).
	ModifiedMessage string
}

HookAction is the result returned by a hook handler.

type HookEvent

type HookEvent string

HookEvent identifies the lifecycle point at which a hook fires.

const (
	HookSessionStart      HookEvent = "session_start"
	HookSessionEnd        HookEvent = "session_end"
	HookUserPromptSubmit  HookEvent = "user_prompt_submit"
	HookPreToolUse        HookEvent = "pre_tool_use"
	HookPostToolUse       HookEvent = "post_tool_use"
	HookAgentStart        HookEvent = "agent_start"
	HookAgentStop         HookEvent = "agent_stop"
	HookSubagentStart     HookEvent = "subagent_start"
	HookSubagentStop      HookEvent = "subagent_stop"
	HookPreCompact        HookEvent = "pre_compact"
	HookPostCompact       HookEvent = "post_compact"
	HookMemorySave        HookEvent = "memory_save"
	HookMemoryRecall      HookEvent = "memory_recall"
	HookNotification      HookEvent = "notification"
	HookHeartbeat         HookEvent = "heartbeat"
	HookError             HookEvent = "error"
	HookUserJoin          HookEvent = "user_join"
	HookUserLeave         HookEvent = "user_leave"
	HookChannelConnect    HookEvent = "channel_connect"
	HookChannelDisconnect HookEvent = "channel_disconnect"
)

type HookHandler

type HookHandler func(ctx context.Context, payload HookPayload) HookAction

HookHandler processes a hook event and returns an action. Handlers should be fast and non-blocking. For async work, spawn a goroutine.

type HookManager

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

HookManager manages lifecycle hook registration and dispatch.

func NewHookManager

func NewHookManager(logger *slog.Logger) *HookManager

NewHookManager creates a new hook manager.

func (*HookManager) Dispatch

func (hm *HookManager) Dispatch(ctx context.Context, payload HookPayload) HookAction

Dispatch fires all hooks for the given event and returns the combined action. For blocking events (PreToolUse, UserPromptSubmit), the first Block=true stops dispatch and returns immediately. For non-blocking events, all hooks run.

func (*HookManager) DispatchAsync

func (hm *HookManager) DispatchAsync(payload HookPayload)

DispatchAsync fires all hooks for the event without waiting for them. Use for non-critical observe-only events (PostToolUse, Notification, etc.).

func (*HookManager) HasHooks

func (hm *HookManager) HasHooks(event HookEvent) bool

HasHooks returns true if any hooks are registered for the given event.

func (*HookManager) HookCount

func (hm *HookManager) HookCount() int

HookCount returns the total number of registered hooks.

func (*HookManager) ListDetailed

func (hm *HookManager) ListDetailed() []HookSummary

ListDetailed returns a deduplicated list of all registered hooks with metadata.

func (*HookManager) ListHooks

func (hm *HookManager) ListHooks() map[HookEvent][]string

ListHooks returns all registered hooks grouped by event.

func (*HookManager) Register

func (hm *HookManager) Register(hook *RegisteredHook) error

Register adds a hook handler for the specified events.

func (*HookManager) SetEnabled

func (hm *HookManager) SetEnabled(name string, enabled bool) bool

SetEnabled enables or disables a hook by name.

func (*HookManager) Unregister

func (hm *HookManager) Unregister(name string) bool

Unregister removes all registrations for a hook by name.

type HookPayload

type HookPayload struct {
	// Event is the hook event type.
	Event HookEvent

	// SessionID is the session this event relates to (if applicable).
	SessionID string

	// Channel is the originating channel (if applicable).
	Channel string

	// ToolName is the tool being called (PreToolUse/PostToolUse).
	ToolName string

	// ToolArgs are the tool arguments (PreToolUse only).
	ToolArgs map[string]any

	// ToolResult is the tool output (PostToolUse only).
	ToolResult string

	// Message is a human-readable description or the user message content.
	Message string

	// Error is set for HookError events.
	Error error

	// Extra holds arbitrary key-value data for extensibility.
	Extra map[string]any
}

HookPayload carries contextual data for a hook invocation. Fields are populated based on the event type; unused fields are zero-valued.

type HookSummary

type HookSummary struct {
	Name        string      `json:"name"`
	Description string      `json:"description"`
	Source      string      `json:"source"`
	Events      []HookEvent `json:"events"`
	Priority    int         `json:"priority"`
	Enabled     bool        `json:"enabled"`
}

HookSummary is a serializable representation of a registered hook (no handler).

type HooksConfig

type HooksConfig struct {
	// Enabled turns the hooks system on/off.
	Enabled bool `yaml:"enabled"`

	// Webhooks is the list of external webhook configurations.
	Webhooks []WebhookConfig `yaml:"webhooks"`

	// Handlers is the list of internal hook handlers.
	Handlers []HandlerConfig `yaml:"handlers"`
}

HooksConfig holds all hook configuration.

type IDEConfig

type IDEConfig struct {
	MCPPort   int    `yaml:"mcp_port" json:"mcp_port"`
	MCPHost   string `yaml:"mcp_host" json:"mcp_host"`
	Transport string `yaml:"transport" json:"transport"` // stdio, sse
}

IDEConfig holds configuration for IDE extension generation.

func DefaultIDEConfig

func DefaultIDEConfig() IDEConfig

DefaultIDEConfig returns sensible defaults.

type IndexConfig

type IndexConfig struct {
	// Auto enables automatic re-indexing on file changes (default: true).
	Auto bool `yaml:"auto"`

	// ChunkMaxTokens is the max tokens per chunk (default: 500).
	ChunkMaxTokens int `yaml:"chunk_max_tokens"`
}

IndexConfig configures automatic memory indexing.

type LLMClient

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

LLMClient handles communication with the LLM provider API.

func NewLLMClient

func NewLLMClient(cfg *Config, logger *slog.Logger) *LLMClient

NewLLMClient creates a new LLM client from config.

func (*LLMClient) Complete

func (c *LLMClient) Complete(ctx context.Context, systemPrompt string, history []ConversationEntry, userMessage string) (string, error)

Complete sends a simple chat completion request (no tools) and returns the text. Convenience wrapper around CompleteWithTools for non-agentic use cases.

func (*LLMClient) CompleteWithFallback

func (c *LLMClient) CompleteWithFallback(ctx context.Context, messages []chatMessage, tools []ToolDefinition) (*LLMResponse, error)

CompleteWithFallback tries the primary model, then fallback models, with retry and exponential backoff on retryable errors. Returns the first successful response.

func (*LLMClient) CompleteWithFallbackUsingModel

func (c *LLMClient) CompleteWithFallbackUsingModel(ctx context.Context, modelOverride string, messages []chatMessage, tools []ToolDefinition) (*LLMResponse, error)

CompleteWithFallbackUsingModel is like CompleteWithFallback but uses modelOverride as the primary model when non-empty. Empty = use c.model. Includes auto-recovery: when the primary model hits a rate limit, subsequent calls use fallback models. Near cooldown expiry, a probe is sent to the primary model to check if it recovered. On success, cooldown is cleared.

func (*LLMClient) CompleteWithTools

func (c *LLMClient) CompleteWithTools(ctx context.Context, messages []chatMessage, tools []ToolDefinition) (*LLMResponse, error)

CompleteWithTools sends a chat completion request with optional tool definitions. Delegates to CompleteWithFallback for retry and model fallback. Returns a structured response that may include tool calls the LLM wants to execute.

func (*LLMClient) CompleteWithToolsStream

func (c *LLMClient) CompleteWithToolsStream(ctx context.Context, messages []chatMessage, tools []ToolDefinition, onChunk StreamCallback) (*LLMResponse, error)

CompleteWithToolsStream sends a streaming chat completion request. For each text delta, onChunk is called. Tool calls are accumulated silently. Falls back to non-streaming if the provider does not support streaming or returns an error.

func (*LLMClient) CompleteWithToolsStreamUsingModel

func (c *LLMClient) CompleteWithToolsStreamUsingModel(ctx context.Context, modelOverride string, messages []chatMessage, tools []ToolDefinition, onChunk StreamCallback) (*LLMResponse, error)

CompleteWithToolsStreamUsingModel is like CompleteWithToolsStream but uses modelOverride when non-empty. Empty = use c.model. Includes retry for transient HTTP errors before falling back to non-streaming.

func (*LLMClient) CompleteWithToolsUsingModel

func (c *LLMClient) CompleteWithToolsUsingModel(ctx context.Context, modelOverride string, messages []chatMessage, tools []ToolDefinition) (*LLMResponse, error)

CompleteWithToolsUsingModel is like CompleteWithTools but uses modelOverride as the primary model when non-empty. Empty string means use the default config model.

func (*LLMClient) CompleteWithVision

func (c *LLMClient) CompleteWithVision(ctx context.Context, systemPrompt, imageBase64, mimeType, userPrompt, detail string, visionModel ...string) (string, error)

CompleteWithVision sends an image plus optional text to the LLM vision API and returns the model's description or response. imageBase64 is the raw base64-encoded image bytes (without data URL prefix). mimeType is e.g. "image/jpeg", "image/png". detail is "auto", "low", or "high" (empty defaults to "auto"). CompleteWithVision sends an image plus optional text to a vision-capable model. visionModel overrides the model; if empty, uses the main chat model.

func (*LLMClient) Provider

func (c *LLMClient) Provider() string

Provider returns the detected or configured provider name.

func (*LLMClient) TranscribeAudio

func (c *LLMClient) TranscribeAudio(ctx context.Context, audioData []byte, filename, model string, media ...MediaConfig) (string, error)

TranscribeAudio sends audio data to a Whisper-compatible API and returns the transcript. filename is used as the form field name (e.g. "audio.ogg", "voice.mp3"). model defaults to "whisper-1" if empty. media is optional; if provided, it may override the transcription endpoint and API key for providers that don't natively support Whisper (e.g. Z.AI/GLM, Anthropic).

type LLMErrorKind

type LLMErrorKind int

LLMErrorKind classifies API errors for retry/fallback decisions. Granular classification enables smarter retry behavior.

const (
	LLMErrorRetryable  LLMErrorKind = iota // generic retryable (transient 5xx)
	LLMErrorRateLimit                      // 429 — rate limited, should respect Retry-After
	LLMErrorOverloaded                     // 529 or "overloaded" in body
	LLMErrorTimeout                        // request timeout / deadline exceeded
	LLMErrorAuth                           // 401, 403 — invalid/expired API key
	LLMErrorBilling                        // 402 or billing-related in body
	LLMErrorContext                        // context_length_exceeded
	LLMErrorBadRequest                     // 400 — malformed request
	LLMErrorFatal                          // everything else
)

func (LLMErrorKind) IsRetryableKind

func (k LLMErrorKind) IsRetryableKind() bool

IsRetryableKind returns true if the error kind warrants retrying.

func (LLMErrorKind) String

func (k LLMErrorKind) String() string

String returns a human-readable label for the error kind.

type LLMResponse

type LLMResponse struct {
	Content      string
	ToolCalls    []ToolCall
	FinishReason string
	Usage        LLMUsage
	ModelUsed    string // The model that actually produced the response
}

LLMResponse holds the parsed response from a chat completion.

type LLMUsage

type LLMUsage struct {
	PromptTokens     int
	CompletionTokens int
	TotalTokens      int
}

LLMUsage holds token usage information from the API response.

type Lane

type Lane struct {
	Name          string
	MaxConcurrent int
	// contains filtered or unexported fields
}

Lane manages a queue with bounded concurrency.

func NewLane

func NewLane(name string, maxConcurrent int) *Lane

NewLane creates a lane with the given concurrency limit.

func (*Lane) ActiveCount

func (l *Lane) ActiveCount() int

ActiveCount returns the number of currently running tasks.

func (*Lane) Close

func (l *Lane) Close()

Close prevents new tasks from being enqueued. Active tasks finish normally.

func (*Lane) Enqueue

func (l *Lane) Enqueue(ctx context.Context, task LaneTask) error

Enqueue adds a task to the lane. If a slot is available, the task starts immediately. Otherwise, it's queued and will run when a slot opens.

func (*Lane) IsBusy

func (l *Lane) IsBusy() bool

IsBusy returns true if the lane is at capacity.

func (*Lane) QueueLen

func (l *Lane) QueueLen() int

QueueLen returns the number of tasks waiting in the queue.

type LaneConfig

type LaneConfig struct {
	SessionMax  int `yaml:"session_max"`  // Default: 1 (one agent run per session)
	GlobalMax   int `yaml:"global_max"`   // Default: 3
	CronMax     int `yaml:"cron_max"`     // Default: 2
	SubagentMax int `yaml:"subagent_max"` // Default: 8
}

LaneConfig configures default concurrency limits per lane type.

func DefaultLaneConfig

func DefaultLaneConfig() LaneConfig

DefaultLaneConfig returns sensible defaults.

type LaneManager

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

LaneManager manages multiple lanes, creating them on demand.

func NewLaneManager

func NewLaneManager(config LaneConfig, logger *slog.Logger) *LaneManager

NewLaneManager creates a lane manager with the given configuration.

func (*LaneManager) CronLane

func (lm *LaneManager) CronLane() *Lane

CronLane returns the lane for scheduled jobs.

func (*LaneManager) GetLane

func (lm *LaneManager) GetLane(name string) *Lane

GetLane returns an existing lane or creates a new one with the appropriate concurrency limit based on the lane name prefix.

func (*LaneManager) GlobalLane

func (lm *LaneManager) GlobalLane() *Lane

GlobalLane returns the shared global lane.

func (*LaneManager) SessionLane

func (lm *LaneManager) SessionLane(sessionID string) *Lane

SessionLane returns the lane for a specific session.

func (*LaneManager) SubagentLane

func (lm *LaneManager) SubagentLane() *Lane

SubagentLane returns the lane for subagent runs.

type LaneTask

type LaneTask struct {
	ID       string
	Fn       func(ctx context.Context) error
	Priority int // Lower = higher priority (0 is highest)
}

LaneTask represents a unit of work to be executed in a lane.

type LoggingConfig

type LoggingConfig struct {
	// Level is the log level ("debug", "info", "warn", "error").
	Level string `yaml:"level"`

	// Format is the log format ("json", "text").
	Format string `yaml:"format"`
}

LoggingConfig configures logging.

type LoopDetectionResult

type LoopDetectionResult struct {
	Severity LoopSeverity
	Message  string // Injected into the conversation as a system hint
	Streak   int    // Number of consecutive repeats detected
	Pattern  string // "repeat", "ping-pong", "known_poll", "global_breaker", or ""
}

LoopDetectionResult is the outcome of a loop check.

type LoopSeverity

type LoopSeverity int

LoopSeverity represents the level of loop detection.

const (
	LoopNone     LoopSeverity = iota
	LoopWarning               // Agent should be nudged
	LoopCritical              // Agent should be strongly nudged
	LoopBreaker               // Agent run should be terminated
)

type MCPConfig

type MCPConfig struct {
	// Enabled turns MCP support on/off.
	Enabled bool `yaml:"enabled" json:"enabled"`

	// Servers is the list of configured MCP servers.
	Servers []ManagedMCPServerConfig `yaml:"servers" json:"servers"`
}

MCPConfig holds all MCP configuration.

func DefaultMCPConfig

func DefaultMCPConfig() MCPConfig

DefaultMCPConfig returns sensible defaults.

type MCPManager

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

MCPManager manages MCP server lifecycle and configuration.

func NewMCPManager

func NewMCPManager(cfg MCPConfig, db *sql.DB, logger *slog.Logger) *MCPManager

NewMCPManager creates a new MCP manager.

func (*MCPManager) AddServer

func (m *MCPManager) AddServer(cfg ManagedMCPServerConfig) error

AddServer adds a new MCP server configuration.

func (*MCPManager) GetConfig

func (m *MCPManager) GetConfig() MCPConfig

GetConfig returns the current MCP configuration.

func (*MCPManager) GetServer

func (m *MCPManager) GetServer(name string) *MCPServerInfo

GetServer returns a specific MCP server by name.

func (*MCPManager) ListServers

func (m *MCPManager) ListServers() []MCPServerInfo

ListServers returns all configured MCP servers with their status.

func (*MCPManager) LoadFromDB

func (m *MCPManager) LoadFromDB() error

LoadFromDB loads the MCP configuration from the database.

func (*MCPManager) RefreshStatus

func (m *MCPManager) RefreshStatus(ctx context.Context)

RefreshStatus refreshes the status of all MCP servers.

func (*MCPManager) Reload

func (m *MCPManager) Reload(cfg MCPConfig)

Reload reloads the MCP configuration.

func (*MCPManager) RemoveServer

func (m *MCPManager) RemoveServer(name string) error

RemoveServer removes an MCP server configuration.

func (*MCPManager) SetEnabled

func (m *MCPManager) SetEnabled(name string, enabled bool) error

SetEnabled enables or disables an MCP server.

func (*MCPManager) TestServer

func (m *MCPManager) TestServer(ctx context.Context, name string) (*MCPServerStatus, error)

TestServer tests the connection to an MCP server.

func (*MCPManager) UpdateServer

func (m *MCPManager) UpdateServer(name string, cfg ManagedMCPServerConfig) error

UpdateServer updates an existing MCP server configuration.

type MCPServerConfig

type MCPServerConfig struct {
	Name      string            `yaml:"name"`
	Transport string            `yaml:"transport"` // "stdio", "sse", "streamable-http"
	Command   string            `yaml:"command,omitempty"`
	Args      []string          `yaml:"args,omitempty"`
	URL       string            `yaml:"url,omitempty"`
	Env       map[string]string `yaml:"env,omitempty"`
}

MCPServerConfig holds configuration for an MCP server associated with a project.

type MCPServerInfo

type MCPServerInfo struct {
	Config ManagedMCPServerConfig `json:"config"`
	Status MCPServerStatus        `json:"status"`
}

MCPServerInfo combines config and status for an MCP server.

type MCPServerStatus

type MCPServerStatus struct {
	Name        string    `json:"name"`
	Connected   bool      `json:"connected"`
	LastChecked time.Time `json:"last_checked"`
	Error       string    `json:"error,omitempty"`
	Tools       []string  `json:"tools,omitempty"`
}

MCPServerStatus represents the status of an MCP server.

type MCPType

type MCPType string

MCPType defines the type of MCP server connection.

const (
	MCPTypeStdio     MCPType = "stdio"
	MCPTypeSSE       MCPType = "sse"
	MCPTypeHTTP      MCPType = "http"
	MCPTypeWebSocket MCPType = "websocket"
)

type MaintenanceManager

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

MaintenanceManager manages maintenance mode state with database persistence.

func NewMaintenanceManager

func NewMaintenanceManager(db *sql.DB, logger *slog.Logger) *MaintenanceManager

NewMaintenanceManager creates a new maintenance manager.

func (*MaintenanceManager) Get

Get returns the current maintenance mode state.

func (*MaintenanceManager) IsEnabled

func (m *MaintenanceManager) IsEnabled() bool

IsEnabled returns true if maintenance mode is active.

func (*MaintenanceManager) Load

func (m *MaintenanceManager) Load() error

Load restores maintenance mode from database on startup.

func (*MaintenanceManager) Set

func (m *MaintenanceManager) Set(enabled bool, message, setBy string) error

Set enables or disables maintenance mode.

type MaintenanceMode

type MaintenanceMode struct {
	Enabled bool      `json:"enabled"`
	Message string    `json:"message"`
	SetBy   string    `json:"set_by"`
	SetAt   time.Time `json:"set_at"`
}

MaintenanceMode represents the system maintenance state.

type ManagedMCPServerConfig

type ManagedMCPServerConfig struct {
	// Name is the unique identifier for this MCP server.
	Name string `yaml:"name" json:"name"`

	// Type is the connection type (stdio, sse, http, websocket).
	Type MCPType `yaml:"type" json:"type"`

	// Command is the executable for stdio type.
	Command string `yaml:"command" json:"command,omitempty"`

	// Args are command-line arguments for stdio type.
	Args []string `yaml:"args" json:"args,omitempty"`

	// URL is the endpoint for SSE/HTTP/WebSocket types.
	URL string `yaml:"url" json:"url,omitempty"`

	// Headers are custom HTTP headers for SSE/HTTP/WebSocket types.
	Headers map[string]string `yaml:"headers" json:"headers,omitempty"`

	// Env are environment variables for stdio type.
	Env map[string]string `yaml:"env" json:"env,omitempty"`

	// Enabled controls whether this MCP server is active.
	Enabled bool `yaml:"enabled" json:"enabled"`

	// AutoStart controls whether to start on launch.
	AutoStart bool `yaml:"auto_start" json:"auto_start"`

	// Timeout is the connection timeout in seconds.
	Timeout int `yaml:"timeout" json:"timeout"`

	// Description is a human-readable description.
	Description string `yaml:"description" json:"description,omitempty"`
}

ManagedMCPServerConfig configures a managed MCP server.

type MediaConfig

type MediaConfig struct {
	// VisionEnabled enables image understanding via LLM vision (default: true).
	VisionEnabled bool `yaml:"vision_enabled"`

	// VisionModel overrides the model used for image/video understanding.
	// If empty, uses the main chat model. Examples: "glm-4.6v", "gpt-4o", "claude-sonnet-4-20250514".
	VisionModel string `yaml:"vision_model"`

	// VisionDetail controls quality: "auto", "low", "high" (default: "auto").
	VisionDetail string `yaml:"vision_detail"`

	// TranscriptionEnabled enables audio transcription (default: true).
	TranscriptionEnabled bool `yaml:"transcription_enabled"`

	// TranscriptionModel is the model for audio transcription (default: "whisper-1").
	// Examples: "whisper-1", "glm-asr-2512", "gpt-4o-transcribe", "whisper-large-v3".
	TranscriptionModel string `yaml:"transcription_model"`

	// TranscriptionBaseURL is the base URL for the transcription API.
	// Examples:
	//   Z.AI:   "https://api.z.ai/api/paas/v4"
	//   Groq:   "https://api.groq.com/openai/v1"
	//   OpenAI: "https://api.openai.com/v1" (default)
	TranscriptionBaseURL string `yaml:"transcription_base_url"`

	// TranscriptionAPIKey is the API key for the transcription provider.
	// If empty, falls back to the main API key.
	TranscriptionAPIKey string `yaml:"transcription_api_key"`

	// TranscriptionLanguage hints the expected language (ISO 639-1, e.g. "pt", "en", "es").
	// For Whisper: passed as the "language" field.
	// For Z.AI GLM-ASR: used as a prompt hint for auto-detection.
	TranscriptionLanguage string `yaml:"transcription_language"`

	// MaxImageSize is the max image size in bytes to process (default: 20MB).
	MaxImageSize int64 `yaml:"max_image_size"`

	// MaxAudioSize is the max audio size in bytes (default: 25MB).
	MaxAudioSize int64 `yaml:"max_audio_size"`
}

MediaConfig configures vision and audio transcription capabilities.

func DefaultMediaConfig

func DefaultMediaConfig() MediaConfig

DefaultMediaConfig returns sensible defaults for media processing.

func (MediaConfig) Effective

func (m MediaConfig) Effective() MediaConfig

Effective returns a copy with default values filled in for zero fields.

func (*MediaConfig) ResolveForProvider

func (m *MediaConfig) ResolveForProvider(provider, baseURL string)

ResolveForProvider fills in transcription defaults based on the main API provider so users don't have to configure transcription separately when their provider already supports it.

type MemoryChunk added in v1.8.0

type MemoryChunk struct {
	Filepath  string
	Content   string
	Hash      string
	CreatedAt time.Time
}

MemoryChunk represents a chunk of memory content for indexing.

type MemoryConfig

type MemoryConfig struct {
	// Type is the storage type ("sqlite", "file").
	// "sqlite" enables FTS5 + vector search; "file" is the legacy fallback.
	Type string `yaml:"type"`

	// Path is the database file path (for sqlite).
	Path string `yaml:"path"`

	// MaxMessages is the max messages kept per session.
	MaxMessages int `yaml:"max_messages"`

	// CompressionStrategy defines memory compression
	// ("summarize", "truncate", "semantic").
	CompressionStrategy string `yaml:"compression_strategy"`

	// Embedding configures the embedding provider for semantic search.
	Embedding memory.EmbeddingConfig `yaml:"embedding"`

	// Search configures hybrid search behavior.
	Search SearchConfig `yaml:"search"`

	// Index configures automatic indexing.
	Index IndexConfig `yaml:"index"`

	// SessionMemory configures automatic session summarization.
	SessionMemory SessionMemoryConfig `yaml:"session_memory"`
}

MemoryConfig configures the memory and persistence system.

type MemoryIndexer added in v1.8.0

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

MemoryIndexer performs incremental indexing of memory files in the background.

func NewMemoryIndexer added in v1.8.0

func NewMemoryIndexer(cfg MemoryIndexerConfig, logger *slog.Logger) *MemoryIndexer

NewMemoryIndexer creates a new memory indexer.

func (*MemoryIndexer) ForceReindex added in v1.8.0

func (m *MemoryIndexer) ForceReindex()

ForceReindex clears all stored hashes and triggers a full reindex.

func (*MemoryIndexer) IndexNow added in v1.8.0

func (m *MemoryIndexer) IndexNow()

IndexNow triggers an immediate index (useful for manual triggers).

func (*MemoryIndexer) SetDeleteFileFunc added in v1.8.0

func (m *MemoryIndexer) SetDeleteFileFunc(fn func(filepath string) error)

SetDeleteFileFunc sets the function for deleting file from index.

func (*MemoryIndexer) SetIndexChunkFunc added in v1.8.0

func (m *MemoryIndexer) SetIndexChunkFunc(fn func(chunks []MemoryChunk) error)

SetIndexChunkFunc sets the function for indexing chunks.

func (*MemoryIndexer) SetMemoryDir added in v1.8.0

func (m *MemoryIndexer) SetMemoryDir(dir string)

SetMemoryDir sets the memory directory to index.

func (*MemoryIndexer) SetSQLiteStore added in v1.8.0

func (m *MemoryIndexer) SetSQLiteStore(store SQLiteMemoryStore)

SetSQLiteStore sets the SQLite memory store for indexing.

func (*MemoryIndexer) Start added in v1.8.0

func (m *MemoryIndexer) Start(ctx context.Context) error

Start begins periodic memory indexing.

func (*MemoryIndexer) Stats added in v1.8.0

func (m *MemoryIndexer) Stats() (indexedTotal, indexedLast, deletedTotal int64, lastIndexTime time.Time)

Stats returns current indexer statistics.

func (*MemoryIndexer) Stop added in v1.8.0

func (m *MemoryIndexer) Stop()

Stop stops the memory indexer.

type MemoryIndexerConfig added in v1.8.0

type MemoryIndexerConfig struct {
	Enabled   bool          `yaml:"enabled" json:"enabled"`
	Interval  time.Duration `yaml:"interval" json:"interval"`
	MemoryDir string        `yaml:"memory_dir" json:"memory_dir"`
}

MemoryIndexerConfig configures the memory indexer.

func DefaultMemoryIndexerConfig added in v1.8.0

func DefaultMemoryIndexerConfig() MemoryIndexerConfig

DefaultMemoryIndexerConfig returns default configuration.

type MemoryStats

type MemoryStats struct {
	AllocMB      float64 `json:"alloc_mb"`
	TotalAllocMB float64 `json:"total_alloc_mb"`
	SysMB        float64 `json:"sys_mb"`
	NumGC        uint32  `json:"num_gc"`
}

MemoryStats represents runtime memory statistics.

type MessageQueue

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

MessageQueue handles message bursts with per-session debouncing.

func NewMessageQueue

func NewMessageQueue(debounceMs, maxPending int, onDrain OnDrainFunc, logger *slog.Logger) *MessageQueue

NewMessageQueue creates a new message queue. onDrain is called when the debounce timer fires with drained messages (may be nil).

func (*MessageQueue) CombineMessages

func (q *MessageQueue) CombineMessages(msgs []*channels.IncomingMessage) string

CombineMessages merges multiple messages into one prompt string.

func (*MessageQueue) Drain

func (q *MessageQueue) Drain(sessionID string) []*channels.IncomingMessage

Drain returns and clears pending messages for the session.

func (*MessageQueue) Enqueue

func (q *MessageQueue) Enqueue(sessionID string, msg *channels.IncomingMessage) bool

Enqueue adds a message to the session queue. Returns true if enqueued, false if deduplicated (same content within 5 seconds).

func (*MessageQueue) IsProcessing

func (q *MessageQueue) IsProcessing(sessionID string) bool

IsProcessing returns true if the session has an active run.

func (*MessageQueue) SetProcessing

func (q *MessageQueue) SetProcessing(sessionID string, active bool)

SetProcessing marks the session as processing or not.

func (*MessageQueue) StuckSessions

func (q *MessageQueue) StuckSessions(maxAge time.Duration) []string

StuckSessions returns session IDs that have been processing longer than maxAge.

func (*MessageQueue) TrySetProcessing

func (q *MessageQueue) TrySetProcessing(sessionID string) bool

TrySetProcessing atomically checks if the session is NOT processing and sets it to processing. Returns true if successful (caller owns the lock), false if the session was already processing (caller should enqueue). This eliminates the race window between IsProcessing() and SetProcessing().

type MetricsCollector added in v1.8.0

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

MetricsCollector collects and aggregates system metrics periodically.

func NewMetricsCollector added in v1.8.0

func NewMetricsCollector(cfg MetricsCollectorConfig, logger *slog.Logger) *MetricsCollector

NewMetricsCollector creates a new metrics collector.

func (*MetricsCollector) Latest added in v1.8.0

func (m *MetricsCollector) Latest() *MetricsSnapshot

Latest returns the most recent metrics snapshot.

func (*MetricsCollector) RecordAgentRunComplete added in v1.8.0

func (m *MetricsCollector) RecordAgentRunComplete(success bool, timedOut bool)

RecordAgentRunComplete decrements active runs and records outcome.

func (*MetricsCollector) RecordAgentRunStart added in v1.8.0

func (m *MetricsCollector) RecordAgentRunStart()

RecordAgentRunStart increments active runs.

func (*MetricsCollector) RecordDBQuery added in v1.8.0

func (m *MetricsCollector) RecordDBQuery(slow bool)

RecordDBQuery records a database query.

func (*MetricsCollector) RecordError added in v1.8.0

func (m *MetricsCollector) RecordError()

RecordError increments error counter.

func (*MetricsCollector) RecordLatency added in v1.8.0

func (m *MetricsCollector) RecordLatency(ms int64)

RecordLatency records a latency measurement in milliseconds.

func (*MetricsCollector) RecordMessage added in v1.8.0

func (m *MetricsCollector) RecordMessage()

RecordMessage increments message counter.

func (*MetricsCollector) RecordSessionCreated added in v1.8.0

func (m *MetricsCollector) RecordSessionCreated()

RecordSessionCreated records a new session.

func (*MetricsCollector) RecordSubagentComplete added in v1.8.0

func (m *MetricsCollector) RecordSubagentComplete(success bool)

RecordSubagentComplete records a subagent completion.

func (*MetricsCollector) RecordSubagentSpawn added in v1.8.0

func (m *MetricsCollector) RecordSubagentSpawn()

RecordSubagentSpawn records a subagent creation.

func (*MetricsCollector) RecordTokens added in v1.8.0

func (m *MetricsCollector) RecordTokens(count int64)

RecordTokens increments token counter.

func (*MetricsCollector) RecordToolCall added in v1.8.0

func (m *MetricsCollector) RecordToolCall(success bool)

RecordToolCall records a tool execution.

func (*MetricsCollector) SetDBSizeFunc added in v1.8.0

func (m *MetricsCollector) SetDBSizeFunc(fn func() int64)

SetDBSizeFunc sets the callback for getting database size.

func (*MetricsCollector) SetMessagesQueueFunc added in v1.8.0

func (m *MetricsCollector) SetMessagesQueueFunc(fn func() int64)

SetMessagesQueueFunc sets the callback for getting queued messages count.

func (*MetricsCollector) SetSessionsCountFunc added in v1.8.0

func (m *MetricsCollector) SetSessionsCountFunc(fn func() int64)

SetSessionsCountFunc sets the callback for getting active sessions count.

func (*MetricsCollector) SetSubagentsCountFunc added in v1.8.0

func (m *MetricsCollector) SetSubagentsCountFunc(fn func() int64)

SetSubagentsCountFunc sets the callback for getting active subagents count.

func (*MetricsCollector) Start added in v1.8.0

func (m *MetricsCollector) Start(ctx context.Context) error

Start begins periodic metrics collection.

func (*MetricsCollector) Stop added in v1.8.0

func (m *MetricsCollector) Stop()

Stop stops the metrics collector.

func (*MetricsCollector) Subscribe added in v1.8.0

func (m *MetricsCollector) Subscribe() <-chan MetricsSnapshot

Subscribe returns a channel that receives metrics snapshots.

func (*MetricsCollector) Unsubscribe added in v1.8.0

func (m *MetricsCollector) Unsubscribe(ch <-chan MetricsSnapshot)

Unsubscribe removes a subscriber.

type MetricsCollectorConfig added in v1.8.0

type MetricsCollectorConfig struct {
	Enabled  bool          `yaml:"enabled" json:"enabled"`
	Interval time.Duration `yaml:"interval" json:"interval"`
	Webhook  string        `yaml:"webhook" json:"webhook"`
}

MetricsCollectorConfig configures the metrics collector.

func DefaultMetricsCollectorConfig added in v1.8.0

func DefaultMetricsCollectorConfig() MetricsCollectorConfig

DefaultMetricsCollectorConfig returns default configuration.

type MetricsResult

type MetricsResult struct {
	Period    string                   `json:"period"`
	StartTime time.Time                `json:"start_time"`
	EndTime   time.Time                `json:"end_time"`
	Total     MetricsTotals            `json:"total"`
	ByModel   map[string]MetricsTotals `json:"by_model"`
	TopUsers  []UserUsage              `json:"top_users"`
}

MetricsResult represents usage metrics for a time period.

type MetricsSnapshot added in v1.8.0

type MetricsSnapshot struct {
	Timestamp time.Time `json:"timestamp"`

	// Message metrics
	MessagesTotal     int64 `json:"messages_total"`
	MessagesPerMinute int64 `json:"messages_per_minute"`

	// Token metrics
	TokensTotal     int64 `json:"tokens_total"`
	TokensPerMinute int64 `json:"tokens_per_minute"`

	// Agent metrics
	AgentRunsTotal   int64 `json:"agent_runs_total"`
	AgentRunsActive  int64 `json:"agent_runs_active"`
	AgentRunsSuccess int64 `json:"agent_runs_success"`
	AgentRunsFailed  int64 `json:"agent_runs_failed"`
	AgentRunsTimeout int64 `json:"agent_runs_timeout"`

	// Tool metrics
	ToolCallsTotal   int64 `json:"tool_calls_total"`
	ToolCallsSuccess int64 `json:"tool_calls_success"`
	ToolCallsFailed  int64 `json:"tool_calls_failed"`

	// Subagent metrics
	SubagentsTotal   int64 `json:"subagents_total"`
	SubagentsActive  int64 `json:"subagents_active"`
	SubagentsSuccess int64 `json:"subagents_success"`
	SubagentsFailed  int64 `json:"subagents_failed"`

	// System metrics
	Goroutines    int64 `json:"goroutines"`
	MemoryAllocMB int64 `json:"memory_alloc_mb"`
	MemorySysMB   int64 `json:"memory_sys_mb"`

	// Session metrics
	SessionsActive int64 `json:"sessions_active"`
	SessionsTotal  int64 `json:"sessions_total"`

	// Error metrics
	ErrorsTotal  int64 `json:"errors_total"`
	ErrorsRecent int64 `json:"errors_recent"` // last interval

	// Latency metrics (milliseconds)
	LatencyAvgMs int64 `json:"latency_avg_ms"`
	LatencyP50Ms int64 `json:"latency_p50_ms"`
	LatencyP99Ms int64 `json:"latency_p99_ms"`

	// Database metrics
	DBSizeMB    int64 `json:"db_size_mb"`
	DBQueries   int64 `json:"db_queries"`
	DBSlowQuery int64 `json:"db_slow_query"`

	// Uptime
	UptimeSeconds int64 `json:"uptime_seconds"`
}

MetricsSnapshot represents a point-in-time collection of system metrics.

type MetricsTotals

type MetricsTotals struct {
	PromptTokens     int64   `json:"prompt_tokens"`
	CompletionTokens int64   `json:"completion_tokens"`
	TotalTokens      int64   `json:"total_tokens"`
	Requests         int64   `json:"requests"`
	EstimatedCostUSD float64 `json:"estimated_cost_usd"`
}

MetricsTotals holds aggregated usage totals.

type ModelCooldown

type ModelCooldown struct {
	Model      string
	Until      time.Time
	Reason     FailoverReason
	ErrorCount int
	LastError  time.Time
}

ModelCooldown tracks a model's cooldown state.

type ModelCost

type ModelCost struct {
	InputPer1M  float64 `yaml:"input_per_1m"`  // USD per 1M input tokens
	OutputPer1M float64 `yaml:"output_per_1m"` // USD per 1M output tokens
}

ModelCost holds pricing per 1M tokens for a model.

type ModelFailoverManager

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

ModelFailoverManager handles automatic model rotation on failures.

func NewModelFailoverManager

func NewModelFailoverManager(config ModelFallbackConfig, logger *slog.Logger) *ModelFailoverManager

NewModelFailoverManager creates a failover manager.

func (*ModelFailoverManager) GetCooldownStatus

func (m *ModelFailoverManager) GetCooldownStatus() map[string]*ModelCooldown

GetCooldownStatus returns the current cooldown state of all models.

func (*ModelFailoverManager) ReportFailure

func (m *ModelFailoverManager) ReportFailure(model string, statusCode int, errMsg string) FailoverReason

ReportFailure records a failure for a model and applies cooldown if needed. Returns the reason classification.

func (*ModelFailoverManager) ReportSuccess

func (m *ModelFailoverManager) ReportSuccess(model string)

ReportSuccess resets the cooldown for a model after a successful call.

func (*ModelFailoverManager) SelectModel

func (m *ModelFailoverManager) SelectModel() (model string, isPrimary bool)

SelectModel returns the best available model. It checks the primary first, then iterates through fallbacks, skipping any that are in cooldown. Returns the model name and whether it's the primary.

type ModelFallbackConfig

type ModelFallbackConfig struct {
	Primary   string   `yaml:"primary"`   // e.g. "claude-sonnet-4-20250514"
	Fallbacks []string `yaml:"fallbacks"` // e.g. ["gpt-4o", "glm-5"]

	// CooldownConfig controls how long a model is disabled after failures.
	Cooldowns CooldownConfig `yaml:"cooldowns"`
}

ModelFallbackConfig defines the primary model and fallback chain.

type NativeMediaConfig added in v1.8.0

type NativeMediaConfig struct {
	// Enabled activates native media features (default: true after setup).
	Enabled bool `yaml:"enabled"`

	// Store configures media storage.
	Store NativeMediaStoreConfig `yaml:"store"`

	// Service configures the media service.
	Service NativeMediaServiceConfig `yaml:"service"`

	// Enrichment configures automatic media enrichment.
	Enrichment NativeMediaEnrichmentConfig `yaml:"enrichment"`
}

NativeMediaConfig configures the native media handling system.

func DefaultNativeMediaConfig added in v1.8.0

func DefaultNativeMediaConfig() NativeMediaConfig

DefaultNativeMediaConfig returns sensible defaults for native media. Note: The enrichment flags (AutoEnrichImages, AutoEnrichAudio) are set to true by default, but they will only work if the corresponding MediaConfig capabilities (VisionEnabled, TranscriptionEnabled) are also enabled. Documents always work as they don't depend on external APIs.

type NativeMediaEnrichmentConfig added in v1.8.0

type NativeMediaEnrichmentConfig struct {
	// AutoEnrichImages runs vision on received images.
	AutoEnrichImages bool `yaml:"auto_enrich_images"`

	// AutoEnrichAudio transcribes received audio.
	AutoEnrichAudio bool `yaml:"auto_enrich_audio"`

	// AutoEnrichDocuments extracts text from documents.
	AutoEnrichDocuments bool `yaml:"auto_enrich_documents"`
}

NativeMediaEnrichmentConfig configures automatic media enrichment.

type NativeMediaServiceConfig added in v1.8.0

type NativeMediaServiceConfig struct {
	// MaxImageSize is the maximum image size in bytes.
	MaxImageSize int64 `yaml:"max_image_size"`

	// MaxAudioSize is the maximum audio size in bytes.
	MaxAudioSize int64 `yaml:"max_audio_size"`

	// MaxDocSize is the maximum document size in bytes.
	MaxDocSize int64 `yaml:"max_doc_size"`

	// TempTTL is the time-to-live for temporary files.
	TempTTL string `yaml:"temp_ttl"`

	// CleanupEnabled enables automatic cleanup of expired files.
	CleanupEnabled bool `yaml:"cleanup_enabled"`

	// CleanupInterval is the interval between cleanup runs.
	CleanupInterval string `yaml:"cleanup_interval"`
}

NativeMediaServiceConfig configures the media service.

type NativeMediaStoreConfig added in v1.8.0

type NativeMediaStoreConfig struct {
	// BaseDir is the permanent storage directory.
	BaseDir string `yaml:"base_dir"`

	// TempDir is the temporary storage directory.
	TempDir string `yaml:"temp_dir"`

	// MaxFileSize is the maximum file size in bytes.
	MaxFileSize int64 `yaml:"max_file_size"`
}

NativeMediaStoreConfig configures media storage.

type OnDrainFunc

type OnDrainFunc func(sessionID string, msgs []*channels.IncomingMessage)

OnDrainFunc is called when the debounce timer fires with drained messages.

type PairingManager

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

PairingManager handles pairing tokens and requests.

func NewPairingManager

func NewPairingManager(db *sql.DB, accessMgr *AccessManager, wsMgr *WorkspaceManager, logger *slog.Logger) *PairingManager

NewPairingManager creates a new pairing manager.

func (*PairingManager) ApproveRequest

func (pm *PairingManager) ApproveRequest(requestID, approvedBy string) error

ApproveRequest approves a pending request and grants access.

func (*PairingManager) CreateRequest

func (pm *PairingManager) CreateRequest(tokenID, userJID, userName string) (*PairingRequest, error)

CreateRequest creates a pending pairing request.

func (*PairingManager) DenyRequest

func (pm *PairingManager) DenyRequest(requestID, deniedBy, reason string) error

DenyRequest denies a pending request.

func (*PairingManager) GenerateToken

func (pm *PairingManager) GenerateToken(createdBy string, opts TokenOptions) (*PairingToken, error)

GenerateToken creates a new pairing token.

func (*PairingManager) GetTokenByIDOrPrefix

func (pm *PairingManager) GetTokenByIDOrPrefix(idOrPrefix string) (*PairingToken, error)

GetTokenByIDOrPrefix finds a token by ID or token prefix.

func (*PairingManager) ListPendingRequests

func (pm *PairingManager) ListPendingRequests() ([]*PairingRequest, error)

ListPendingRequests returns all pending requests.

func (*PairingManager) ListTokens

func (pm *PairingManager) ListTokens(includeRevoked bool) ([]*PairingToken, error)

ListTokens returns all tokens (with optional filter).

func (*PairingManager) Load

func (pm *PairingManager) Load() error

Load restores token cache from database on startup.

func (*PairingManager) ProcessTokenRedemption

func (pm *PairingManager) ProcessTokenRedemption(tokenStr, userJID, userName string) (bool, string, error)

ProcessTokenRedemption handles a user sending a token. Returns: (approved, message, error)

func (*PairingManager) RevokeToken

func (pm *PairingManager) RevokeToken(tokenID, revokedBy string) error

RevokeToken revokes a token.

func (*PairingManager) ValidateToken

func (pm *PairingManager) ValidateToken(token string) (*PairingToken, error)

ValidateToken checks if a token is valid and returns it.

type PairingRequest

type PairingRequest struct {
	ID         string     `json:"id"`
	TokenID    string     `json:"token_id"`
	UserJID    string     `json:"user_jid"`
	UserName   string     `json:"user_name"`
	Status     string     `json:"status"` // pending, approved, denied
	ReviewedBy string     `json:"reviewed_by"`
	ReviewedAt *time.Time `json:"reviewed_at,omitempty"`
	CreatedAt  time.Time  `json:"created_at"`

	// Loaded via join for display
	TokenNote string    `json:"token_note,omitempty"`
	TokenRole TokenRole `json:"token_role,omitempty"`
}

PairingRequest represents a pending access request.

type PairingToken

type PairingToken struct {
	ID          string     `json:"id"`
	Token       string     `json:"token"`
	Role        TokenRole  `json:"role"`
	MaxUses     int        `json:"max_uses"` // 0 = unlimited
	UseCount    int        `json:"use_count"`
	AutoApprove bool       `json:"auto_approve"` // If true, grants access immediately
	WorkspaceID string     `json:"workspace_id"`
	Note        string     `json:"note"`
	CreatedBy   string     `json:"created_by"`
	CreatedAt   time.Time  `json:"created_at"`
	ExpiresAt   *time.Time `json:"expires_at,omitempty"`
	Revoked     bool       `json:"revoked"`
	RevokedAt   *time.Time `json:"revoked_at,omitempty"`
	RevokedBy   string     `json:"revoked_by"`
}

PairingToken represents a shareable invite token.

func (*PairingToken) CanUse

func (t *PairingToken) CanUse() bool

CanUse returns true if the token can still be used.

func (*PairingToken) IsExpired

func (t *PairingToken) IsExpired() bool

IsExpired returns true if the token has expired.

type PendingApproval

type PendingApproval struct {
	ID          string
	ToolName    string
	Args        map[string]any
	Description string
	SessionID   string
	CallerJID   string
	CreatedAt   time.Time
	Result      chan ApprovalResult
}

PendingApproval represents a tool call waiting for user approval.

type PendingMessage added in v1.8.0

type PendingMessage struct {
	// ID is the unique message identifier.
	ID string `json:"id" yaml:"id"`

	// ToAgent is the destination agent ID.
	ToAgent string `json:"to_agent" yaml:"to_agent"`

	// FromAgent is the source agent ID (empty = from user).
	FromAgent string `json:"from_agent,omitempty" yaml:"from_agent,omitempty"`

	// FromUser is the user JID if sent by a human.
	FromUser string `json:"from_user,omitempty" yaml:"from_user,omitempty"`

	// Content is the message text.
	Content string `json:"content" yaml:"content"`

	// ThreadID is the related task/thread ID.
	ThreadID string `json:"thread_id,omitempty" yaml:"thread_id,omitempty"`

	// CreatedAt is when the message was created.
	CreatedAt time.Time `json:"created_at" yaml:"created_at"`

	// Delivered indicates if the message was delivered.
	Delivered bool `json:"delivered" yaml:"delivered"`

	// DeliveredAt is when the message was delivered.
	DeliveredAt *time.Time `json:"delivered_at,omitempty" yaml:"delivered_at,omitempty"`
}

PendingMessage is a message waiting to be delivered to an agent.

type PersistentAgent added in v1.8.0

type PersistentAgent struct {
	// ID is the unique agent identifier (used for @mentions).
	ID string `json:"id" yaml:"id"`

	// Name is the human-readable name (e.g., "Jarvis", "Loki").
	Name string `json:"name" yaml:"name"`

	// Role describes what this agent does (e.g., "Writer", "Researcher").
	Role string `json:"role" yaml:"role"`

	// TeamID is the team this agent belongs to.
	TeamID string `json:"team_id" yaml:"team_id"`

	// Level defines the agent's autonomy level.
	Level AgentLevel `json:"level" yaml:"level"`

	// Status is the current agent state.
	Status AgentStatus `json:"status" yaml:"status"`

	// Personality is the SOUL.md content for this agent.
	Personality string `json:"personality,omitempty" yaml:"personality,omitempty"`

	// Instructions are additional operating instructions.
	Instructions string `json:"instructions,omitempty" yaml:"instructions,omitempty"`

	// Model is the LLM model for this agent (empty = team default).
	Model string `json:"model,omitempty" yaml:"model,omitempty"`

	// Skills are the skill names this agent can use.
	Skills []string `json:"skills,omitempty" yaml:"skills,omitempty"`

	// SessionID is the internal session identifier.
	SessionID string `json:"session_id,omitempty" yaml:"-"`

	// CurrentTaskID is the task this agent is currently working on.
	CurrentTaskID string `json:"current_task_id,omitempty" yaml:"-"`

	// HeartbeatSchedule is the cron expression for heartbeats (default: "*/15 * * * *").
	HeartbeatSchedule string `json:"heartbeat_schedule,omitempty" yaml:"heartbeat_schedule,omitempty"`

	// CreatedAt is when the agent was created.
	CreatedAt time.Time `json:"created_at" yaml:"created_at"`

	// LastActiveAt is when the agent was last active.
	LastActiveAt time.Time `json:"last_active_at,omitempty" yaml:"last_active_at,omitempty"`

	// LastHeartbeatAt is when the last heartbeat ran.
	LastHeartbeatAt *time.Time `json:"last_heartbeat_at,omitempty" yaml:"last_heartbeat_at,omitempty"`

	// HeartbeatJobID is the scheduler job ID for heartbeats.
	HeartbeatJobID string `json:"heartbeat_job_id,omitempty" yaml:"-"`
}

PersistentAgent represents a long-lived agent with a specific role.

type Plugin

type Plugin struct {
	Name        string         `json:"name" yaml:"name"`
	Version     string         `json:"version" yaml:"version"`
	Description string         `json:"description" yaml:"description"`
	Author      string         `json:"author" yaml:"author"`
	Enabled     bool           `json:"enabled" yaml:"enabled"`
	Config      map[string]any `json:"config" yaml:"config"`
	Tools       []PluginTool   `json:"tools" yaml:"tools"`
	Webhooks    []WebhookDef   `json:"webhooks" yaml:"webhooks"`
}

Plugin represents an installed plugin with its tools and hooks.

func GitHubPlugin

func GitHubPlugin(token string) *Plugin

GitHubPlugin returns a pre-configured GitHub integration plugin.

func JiraPlugin

func JiraPlugin(baseURL, token string) *Plugin

JiraPlugin returns a pre-configured Jira integration plugin.

func SentryPlugin

func SentryPlugin(dsn, token string) *Plugin

SentryPlugin returns a pre-configured Sentry integration plugin.

type PluginManager

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

PluginManager manages installed plugins and their lifecycle.

func NewPluginManager

func NewPluginManager() *PluginManager

NewPluginManager creates a new plugin manager.

func (*PluginManager) Disable

func (pm *PluginManager) Disable(name string) error

Disable disables a plugin.

func (*PluginManager) Enable

func (pm *PluginManager) Enable(name string) error

Enable enables a plugin.

func (*PluginManager) Get

func (pm *PluginManager) Get(name string) (*Plugin, bool)

Get returns a plugin by name.

func (*PluginManager) Install

func (pm *PluginManager) Install(p *Plugin) error

Install adds a plugin to the manager.

func (*PluginManager) List

func (pm *PluginManager) List() []*Plugin

List returns all installed plugins.

func (*PluginManager) Uninstall

func (pm *PluginManager) Uninstall(name string) error

Uninstall removes a plugin.

type PluginTool

type PluginTool struct {
	Name        string            `json:"name" yaml:"name"`
	Description string            `json:"description" yaml:"description"`
	Endpoint    string            `json:"endpoint" yaml:"endpoint"` // HTTP endpoint for the tool
	Method      string            `json:"method" yaml:"method"`     // GET, POST, etc.
	Headers     map[string]string `json:"headers" yaml:"headers"`
	Parameters  map[string]any    `json:"parameters" yaml:"parameters"`
}

PluginTool describes a tool provided by a plugin.

type ProfileChecker

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

ProfileChecker checks if tools are allowed/denied by a profile.

func NewProfileChecker

func NewProfileChecker(allow, deny []string, allTools []string) *ProfileChecker

NewProfileChecker creates a checker from allow/deny lists.

func (*ProfileChecker) Check

func (pc *ProfileChecker) Check(toolName string) (allowed bool, reason string)

Check returns whether a tool is permitted by the profile. Returns (allowed, reason) where reason explains why if not allowed.

func (*ProfileChecker) IsAllowed

func (pc *ProfileChecker) IsAllowed(toolName string) bool

IsAllowed returns true if the tool is in the allow list. If allow list is empty, all tools are allowed (respecting deny).

func (*ProfileChecker) IsDenied

func (pc *ProfileChecker) IsDenied(toolName string) bool

IsDenied returns true if the tool is in the deny list.

type ProgressSender

type ProgressSender func(ctx context.Context, message string)

ProgressSender sends intermediate progress messages to the user during long-running tool execution (e.g. claude-code). Called by tools that want to give real-time feedback without waiting for the full result.

func ProgressSenderFromContext

func ProgressSenderFromContext(ctx context.Context) ProgressSender

ProgressSenderFromContext extracts the ProgressSender from context. Returns nil if not set.

type Project

type Project struct {
	// ID is the unique project identifier (slug, e.g. "devclaw", "my-saas").
	ID string `yaml:"id"`

	// Name is the human-readable project name.
	Name string `yaml:"name"`

	// RootPath is the absolute path to the project root directory.
	RootPath string `yaml:"root_path"`

	// Language is the primary programming language (auto-detected or manual).
	Language string `yaml:"language"`

	// Framework is the detected/configured framework (e.g. "laravel", "next", "gin").
	Framework string `yaml:"framework"`

	// GitRemote is the primary git remote URL (auto-detected from origin).
	GitRemote string `yaml:"git_remote,omitempty"`

	// BuildCmd is the command to build the project.
	BuildCmd string `yaml:"build_cmd,omitempty"`

	// TestCmd is the command to run tests.
	TestCmd string `yaml:"test_cmd,omitempty"`

	// LintCmd is the command to run the linter.
	LintCmd string `yaml:"lint_cmd,omitempty"`

	// StartCmd is the command to start the dev server.
	StartCmd string `yaml:"start_cmd,omitempty"`

	// DeployCmd is the command to deploy the project.
	DeployCmd string `yaml:"deploy_cmd,omitempty"`

	// DockerCompose is the path to docker-compose.yml (if present).
	DockerCompose string `yaml:"docker_compose,omitempty"`

	// EnvFile is the path to the .env file (if present).
	EnvFile string `yaml:"env_file,omitempty"`

	// MCPServers lists MCP server configurations for this project.
	MCPServers []MCPServerConfig `yaml:"mcp_servers,omitempty"`
}

Project represents a registered development project.

type ProjectManager

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

ProjectManager manages registered projects and per-session active project.

func NewProjectManager

func NewProjectManager(dataDir string) *ProjectManager

NewProjectManager creates a new ProjectManager, loading from disk if available.

func (*ProjectManager) Activate

func (pm *ProjectManager) Activate(sessionKey, projectID string) error

Activate sets the active project for a session.

func (*ProjectManager) ActiveProject

func (pm *ProjectManager) ActiveProject(sessionKey string) *Project

ActiveProject returns the active project for a session, or nil.

func (*ProjectManager) FindByPath

func (pm *ProjectManager) FindByPath(path string) *Project

FindByPath finds a project whose root matches the given path.

func (*ProjectManager) Get

func (pm *ProjectManager) Get(id string) *Project

Get returns a project by ID.

func (*ProjectManager) List

func (pm *ProjectManager) List() []*Project

List returns all registered projects sorted by name.

func (*ProjectManager) Register

func (pm *ProjectManager) Register(p *Project) error

Register adds or updates a project.

func (*ProjectManager) Remove

func (pm *ProjectManager) Remove(id string) error

Remove removes a project by ID.

func (*ProjectManager) ScanDirectory

func (pm *ProjectManager) ScanDirectory(root string) ([]*Project, error)

ScanDirectory scans a directory for projects (subdirectories with .git or known markers).

type ProjectProviderAdapter

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

ProjectProviderAdapter adapts a copilot.ProjectManager to the skills.ProjectProvider interface so coding skills can access project context without importing the copilot package.

func NewProjectProviderAdapter

func NewProjectProviderAdapter(pm *ProjectManager) *ProjectProviderAdapter

NewProjectProviderAdapter creates a new adapter wrapping the given ProjectManager.

func (*ProjectProviderAdapter) Activate

func (a *ProjectProviderAdapter) Activate(sessionKey, projectID string) error

Activate sets the active project for a session.

func (*ProjectProviderAdapter) ActiveProject

func (a *ProjectProviderAdapter) ActiveProject(sessionKey string) *skills.ProjectInfo

ActiveProject returns the active project for a session.

func (*ProjectProviderAdapter) Get

Get returns a project by ID.

func (*ProjectProviderAdapter) List

List returns all registered projects.

func (*ProjectProviderAdapter) Register

Register adds or updates a project.

func (*ProjectProviderAdapter) Remove

func (a *ProjectProviderAdapter) Remove(id string) error

Remove removes a project by ID.

func (*ProjectProviderAdapter) ScanDirectory

func (a *ProjectProviderAdapter) ScanDirectory(root string) ([]*skills.ProjectInfo, error)

ScanDirectory scans for projects in a directory.

type PromptComposer

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

PromptComposer assembles the final system prompt from multiple layers.

func NewPromptComposer

func NewPromptComposer(config *Config) *PromptComposer

NewPromptComposer creates a new prompt composer.

func (*PromptComposer) Compose

func (p *PromptComposer) Compose(session *Session, input string) string

Compose builds the complete system prompt for a session and user input. Heavy layers (bootstrap, memory, skills, conversation) are built concurrently to minimize prompt composition latency.

func (*PromptComposer) ComposeMinimal

func (p *PromptComposer) ComposeMinimal() string

ComposeMinimal builds a lightweight system prompt for scheduled jobs and other fast-path scenarios. It includes only: Core identity, Safety, Temporal (date/time), and the user's custom instructions. It deliberately skips bootstrap files, memory search, skill instructions, and conversation history to minimize token count and latency.

func (*PromptComposer) SetMemoryStore

func (p *PromptComposer) SetMemoryStore(store *memory.FileStore)

SetMemoryStore configures the file-based memory store for the prompt composer.

func (*PromptComposer) SetSQLiteMemory

func (p *PromptComposer) SetSQLiteMemory(store *memory.SQLiteStore)

SetSQLiteMemory configures the SQLite memory store for hybrid search.

func (*PromptComposer) SetSkillGetter

func (p *PromptComposer) SetSkillGetter(getter func(name string) (interface{ SystemPrompt() string }, bool))

SetSkillGetter sets the function used to retrieve skill system prompts.

func (*PromptComposer) SetSubagentMode

func (p *PromptComposer) SetSubagentMode(isSubagent bool)

SetSubagentMode restricts bootstrap loading to AGENTS.md + TOOLS.md only.

type PromptLayer

type PromptLayer int

PromptLayer defines the priority of a prompt layer. Lower values = higher priority (never trimmed first on budget cuts).

const (
	LayerCore         PromptLayer = 0  // Base identity and tooling.
	LayerSafety       PromptLayer = 5  // Safety rules.
	LayerIdentity     PromptLayer = 10 // Custom instructions.
	LayerThinking     PromptLayer = 12 // Extended thinking level hint (from /think).
	LayerBootstrap    PromptLayer = 15 // SOUL.md, AGENTS.md, etc.
	LayerBusiness     PromptLayer = 20 // User/workspace context.
	LayerSkills       PromptLayer = 40 // Active skill instructions.
	LayerMemory       PromptLayer = 50 // Long-term memory facts.
	LayerTemporal     PromptLayer = 60 // Date/time context.
	LayerConversation PromptLayer = 70 // Recent history summary.
	LayerRuntime      PromptLayer = 80 // Runtime info (final line).
)

type ProviderChainEntry

type ProviderChainEntry struct {
	Provider string `yaml:"provider"`          // Provider name (openai, anthropic, ollama, etc.)
	BaseURL  string `yaml:"base_url"`          // API endpoint
	APIKey   string `yaml:"api_key,omitempty"` // API key (can use ${VAR} references)
	Model    string `yaml:"model"`             // Model to use from this provider
}

ProviderChainEntry defines a single provider in the fallback chain.

type QueueConfig

type QueueConfig struct {
	// DebounceMs is the debounce delay in ms before draining queued messages (default: 200).
	DebounceMs int `yaml:"debounce_ms"`

	// MaxPending is the max queued messages per session before dropping oldest (default: 20).
	MaxPending int `yaml:"max_pending"`

	// DefaultMode is the default queue mode for all channels (default: "collect").
	DefaultMode QueueMode `yaml:"default_mode"`

	// ByChannel overrides the default mode per channel name.
	ByChannel map[string]QueueMode `yaml:"by_channel"`

	// DropPolicy controls what happens when the queue exceeds MaxPending (default: "old").
	DropPolicy QueueDropPolicy `yaml:"drop_policy"`
}

QueueConfig configures the message queue for handling bursts.

type QueueDropPolicy

type QueueDropPolicy string

QueueDropPolicy defines what happens when the queue exceeds max size.

const (
	// DropOld removes the oldest messages to make room.
	DropOld QueueDropPolicy = "old"

	// DropNew rejects new messages when the queue is full.
	DropNew QueueDropPolicy = "new"

	// DropSummarize uses the LLM to summarize dropped messages.
	DropSummarize QueueDropPolicy = "summarize"
)

type QueueMode

type QueueMode string

QueueMode defines how incoming messages are handled when the session is busy.

const (
	// QueueModeCollect groups all queued messages into a single prompt.
	// Processed as one agent run after the current run completes.
	QueueModeCollect QueueMode = "collect"

	// QueueModeSteer injects messages into the active agent run via interruptCh.
	// The agent sees the message between turns and adjusts behavior.
	QueueModeSteer QueueMode = "steer"

	// QueueModeFollowup enqueues each message as a separate agent run.
	// Processed in order after the current run completes.
	QueueModeFollowup QueueMode = "followup"

	// QueueModeInterrupt aborts the current run and processes the new message.
	QueueModeInterrupt QueueMode = "interrupt"

	// QueueModeSteerBacklog tries steer first; if no active run to inject into,
	// falls back to followup.
	QueueModeSteerBacklog QueueMode = "steer-backlog"
)

func EffectiveQueueMode

func EffectiveQueueMode(qc QueueConfig, channelName string) QueueMode

EffectiveQueueMode returns the queue mode for a given channel, falling back to the default mode from QueueConfig (defined in config.go).

func ParseQueueMode

func ParseQueueMode(s string) (QueueMode, bool)

ParseQueueMode parses a string into a QueueMode. Returns (mode, true) on success, ("", false) on unknown mode.

type QuietHoursConfig

type QuietHoursConfig struct {
	// Start is the start time in HH:MM format.
	Start string `yaml:"start"`
	// End is the end time in HH:MM format.
	End string `yaml:"end"`
	// Timezone is the timezone for quiet hours (default: UTC).
	Timezone string `yaml:"timezone"`
}

QuietHoursConfig defines quiet hours for a group.

type RegisteredHook

type RegisteredHook struct {
	// Name identifies this hook for logging.
	Name string

	// Description provides a human-readable summary of this hook.
	Description string

	// Source indicates where the hook came from (e.g. "system", "plugin:github", "skill:monitor").
	Source string

	// Events lists which events this hook subscribes to.
	Events []HookEvent

	// Priority controls execution order (lower = earlier). Default: 100.
	Priority int

	// Enabled controls whether this hook is active. Default: true.
	Enabled bool

	// Handler is the callback function.
	Handler HookHandler
}

RegisteredHook associates a handler with metadata.

type ResolvedWorkspace

type ResolvedWorkspace struct {
	// Workspace is the resolved workspace.
	Workspace *Workspace

	// Session is the workspace-isolated session for this chat.
	Session *Session

	// SessionStore is the workspace session store (for pruning, etc.).
	SessionStore *SessionStore
}

ResolvedWorkspace contains the resolved workspace and session for a message.

type RiskAction

type RiskAction string

RiskAction represents the action to take for a risk level.

const (
	ActionAllow           RiskAction = "allow"
	ActionAllowLog        RiskAction = "allow_log"
	ActionRequireApproval RiskAction = "require_approval"
	ActionDeny            RiskAction = "deny"
)

type RiskCategoryConfig

type RiskCategoryConfig struct {
	// Patterns are glob-like patterns to match commands.
	Patterns []string `yaml:"patterns"`

	// Action is the action to take for this category.
	Action RiskAction `yaml:"action"`

	// Notify lists who to notify (e.g., ["owners"], ["admins"]).
	Notify []string `yaml:"notify"`

	// Message is a custom denial/approval message.
	Message string `yaml:"message"`
}

RiskCategoryConfig configures a risk category.

type RiskLevel

type RiskLevel string

RiskLevel represents the risk category of a command.

const (
	RiskSafe      RiskLevel = "safe"      // Execute immediately
	RiskModerate  RiskLevel = "moderate"  // Log and execute
	RiskDangerous RiskLevel = "dangerous" // Require approval
	RiskBlocked   RiskLevel = "blocked"   // Always deny
)

type RoutinesConfig added in v1.8.0

type RoutinesConfig struct {
	// Metrics configures the metrics collector.
	Metrics MetricsCollectorConfig `yaml:"metrics"`

	// MemoryIndexer configures the background memory indexer.
	MemoryIndexer MemoryIndexerConfig `yaml:"memory_indexer"`
}

RoutinesConfig configures background routines for metrics and memory indexing.

func DefaultRoutinesConfig added in v1.8.0

func DefaultRoutinesConfig() RoutinesConfig

DefaultRoutinesConfig returns sensible defaults for background routines.

type RoutingConfig

type RoutingConfig struct {
	// Default is the default agent ID to use when no match is found.
	Default string `yaml:"default"`
}

RoutingConfig defines how messages are routed to agents.

type SQLiteAuditLogger

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

SQLiteAuditLogger writes tool execution audit records to the devclaw.db audit_log table. It replaces the plain-text append-only file.

func NewSQLiteAuditLogger

func NewSQLiteAuditLogger(db *sql.DB, logger *slog.Logger) *SQLiteAuditLogger

NewSQLiteAuditLogger creates an audit logger backed by SQLite.

func (*SQLiteAuditLogger) Close

func (a *SQLiteAuditLogger) Close()

Close is a no-op; the shared *sql.DB is closed at the application level.

func (*SQLiteAuditLogger) Count

func (a *SQLiteAuditLogger) Count() int

Count returns the total number of audit log entries.

func (*SQLiteAuditLogger) Log

func (a *SQLiteAuditLogger) Log(toolName, caller, level string, allowed bool, argsSummary, resultSummary string)

Log records a tool execution in the audit_log table.

func (*SQLiteAuditLogger) Recent

func (a *SQLiteAuditLogger) Recent(n int) []string

Recent returns the last N audit log entries as formatted strings.

func (*SQLiteAuditLogger) RecentRecords

func (a *SQLiteAuditLogger) RecentRecords(n int) []AuditRecord

RecentRecords returns the last N audit log entries as structured records.

type SQLiteMemoryStore added in v1.8.0

type SQLiteMemoryStore interface {
	IndexChunks(chunks []MemoryChunk) error
	DeleteByFilepath(filepath string) error
	GetIndexedFiles() (map[string]string, error) // filepath -> hash
}

SQLiteMemoryStore is an interface for SQLite memory operations.

type SQLiteSessionPersistence

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

SQLiteSessionPersistence stores session data in the devclaw.db tables: session_entries, session_meta, session_facts.

func NewSQLiteSessionPersistence

func NewSQLiteSessionPersistence(db *sql.DB, logger *slog.Logger) *SQLiteSessionPersistence

NewSQLiteSessionPersistence creates a SQLite-backed session persistence. The tables must already exist (created by OpenDatabase).

func (*SQLiteSessionPersistence) Close

func (p *SQLiteSessionPersistence) Close() error

Close is a no-op; the shared *sql.DB is closed at the application level.

func (*SQLiteSessionPersistence) DeleteSession

func (p *SQLiteSessionPersistence) DeleteSession(sessionID string) error

DeleteSession removes all data for a session (entries, facts, meta).

func (*SQLiteSessionPersistence) LoadAll

func (p *SQLiteSessionPersistence) LoadAll() (map[string]*SessionData, error)

LoadAll scans all sessions from the database and returns SessionData for each.

func (*SQLiteSessionPersistence) LoadSession

func (p *SQLiteSessionPersistence) LoadSession(sessionID string) ([]ConversationEntry, []string, error)

LoadSession reads all entries and facts for a session.

func (*SQLiteSessionPersistence) Rotate

func (p *SQLiteSessionPersistence) Rotate(sessionID string, maxLines int) error

Rotate keeps only the latest maxLines entries for a session.

func (*SQLiteSessionPersistence) SaveEntry

func (p *SQLiteSessionPersistence) SaveEntry(sessionID string, entry ConversationEntry) error

SaveEntry appends a conversation entry for the given session.

func (*SQLiteSessionPersistence) SaveFacts

func (p *SQLiteSessionPersistence) SaveFacts(sessionID string, facts []string) error

SaveFacts replaces all facts for the given session.

func (*SQLiteSessionPersistence) SaveMeta

func (p *SQLiteSessionPersistence) SaveMeta(sessionID, channel, chatID string, config SessionConfig, activeSkills []string) error

SaveMeta persists session metadata (channel, chatID, config, activeSkills).

type SchedulerConfig

type SchedulerConfig struct {
	// Enabled turns the scheduler on/off.
	Enabled bool `yaml:"enabled"`

	// Storage is the path to the scheduler database.
	Storage string `yaml:"storage"`
}

SchedulerConfig configures the task scheduler.

type SchedulerStats

type SchedulerStats struct {
	Enabled bool   `json:"enabled"`
	Jobs    int    `json:"jobs"`
	NextRun string `json:"next_run,omitempty"`
	Running int    `json:"running"`
}

SchedulerStats holds scheduler statistics.

type SearchConfig

type SearchConfig struct {
	// HybridWeightVector is the weight for vector search (default: 0.7).
	HybridWeightVector float64 `yaml:"hybrid_weight_vector"`

	// HybridWeightBM25 is the weight for BM25 keyword search (default: 0.3).
	HybridWeightBM25 float64 `yaml:"hybrid_weight_bm25"`

	// MaxResults is the max results returned (default: 6).
	MaxResults int `yaml:"max_results"`

	// MinScore is the minimum score threshold (default: 0.1).
	MinScore float64 `yaml:"min_score"`
}

SearchConfig configures hybrid search behavior.

type SecurityConfig

type SecurityConfig struct {
	// MaxInputLength is the max input size in characters.
	MaxInputLength int `yaml:"max_input_length"`

	// RateLimit is max messages per minute per user.
	RateLimit int `yaml:"rate_limit"`

	// EnablePIIDetection enables PII detection in outputs.
	EnablePIIDetection bool `yaml:"enable_pii_detection"`

	// EnableURLValidation enables URL validation in outputs.
	EnableURLValidation bool `yaml:"enable_url_validation"`

	// ToolGuard configures per-tool access control, command safety,
	// path protection, SSH allowlist, and audit logging.
	ToolGuard ToolGuardConfig `yaml:"tool_guard"`

	// ToolExecutor configures parallel tool execution.
	ToolExecutor ToolExecutorConfig `yaml:"tool_executor"`

	// SSRF configures URL validation for web_fetch (private IPs, metadata, etc.).
	SSRF security.SSRFConfig `yaml:"ssrf"`

	// ExecAnalysis configures command risk analysis for bash/exec tools.
	ExecAnalysis ExecAnalysisConfig `yaml:"exec_analysis"`
}

SecurityConfig configures security guardrails.

type Session

type Session struct {
	// ID é o identificador único da sessão (combinação de channel + chatID).
	ID string

	// Channel identifica o canal de origem (ex: "whatsapp", "discord").
	Channel string

	// ChatID é o identificador do grupo ou DM.
	ChatID string

	// CreatedAt é o timestamp de criação da sessão.
	CreatedAt time.Time
	// contains filtered or unexported fields
}

Session representa uma sessão isolada de conversa para um chat/grupo específico.

func (*Session) AddFact

func (s *Session) AddFact(fact string)

AddFact adiciona um fato de longo prazo à sessão. Persiste os fatos em disco se persistence estiver configurada.

func (*Session) AddMessage

func (s *Session) AddMessage(userMsg, assistantResp string)

AddMessage adiciona uma nova entrada de conversa à sessão. Aplica o limite de maxHistory, removendo mensagens antigas quando excedido. Persiste a entrada em disco se persistence estiver configurada.

func (*Session) AddTokenUsage

func (s *Session) AddTokenUsage(promptTokens, completionTokens int)

AddTokenUsage records token usage from an LLM response. Thread-safe.

func (*Session) ClearFacts

func (s *Session) ClearFacts()

ClearFacts removes all session facts. Used by /reset.

func (*Session) ClearHistory

func (s *Session) ClearHistory()

ClearHistory limpa o histórico de conversa mantendo fatos de longo prazo.

func (*Session) CompactHistory

func (s *Session) CompactHistory(summary string, keepRecent int) []ConversationEntry

CompactHistory replaces the full history with a summary entry, keeping only the most recent entries. Returns the old entries for memory extraction.

func (*Session) GetActiveSkills

func (s *Session) GetActiveSkills() []string

GetActiveSkills retorna uma cópia thread-safe das skills ativas.

func (*Session) GetConfig

func (s *Session) GetConfig() SessionConfig

GetConfig retorna uma cópia thread-safe da configuração da sessão.

func (*Session) GetFacts

func (s *Session) GetFacts() []string

GetFacts retorna uma cópia thread-safe dos fatos da sessão.

func (*Session) GetThinkingLevel

func (s *Session) GetThinkingLevel() string

GetThinkingLevel returns the session thinking level. Thread-safe.

func (*Session) GetTokenUsage

func (s *Session) GetTokenUsage() (promptTokens, completionTokens, requests int)

GetTokenUsage returns a copy of the token usage. Thread-safe.

func (*Session) HistoryLen

func (s *Session) HistoryLen() int

HistoryLen returns the number of entries in the session history.

func (*Session) LastActiveAt

func (s *Session) LastActiveAt() time.Time

LastActiveAt retorna o timestamp da última atividade (thread-safe).

func (*Session) RecentHistory

func (s *Session) RecentHistory(maxEntries int) []ConversationEntry

RecentHistory retorna as últimas N entradas de conversa (cópia thread-safe).

func (*Session) ResetTokenUsage

func (s *Session) ResetTokenUsage()

ResetTokenUsage clears token counters. Thread-safe.

func (*Session) SetActiveSkills

func (s *Session) SetActiveSkills(skills []string)

SetActiveSkills define as skills ativas da sessão.

func (*Session) SetConfig

func (s *Session) SetConfig(cfg SessionConfig)

SetConfig atualiza a configuração da sessão.

func (*Session) SetThinkingLevel

func (s *Session) SetThinkingLevel(level string)

SetThinkingLevel sets the session thinking level. Thread-safe.

type SessionConfig

type SessionConfig struct {
	// Trigger é a palavra-chave que ativa o copilot nesta sessão.
	Trigger string `yaml:"trigger"`

	// Language é o idioma preferido nesta sessão.
	Language string `yaml:"language"`

	// MaxTokens é o budget máximo de tokens para esta sessão.
	MaxTokens int `yaml:"max_tokens"`

	// Model é o modelo LLM a ser usado nesta sessão (pode ser diferente do padrão).
	Model string `yaml:"model"`

	// BusinessContext é o contexto de negócio/usuário para esta sessão.
	BusinessContext string `yaml:"business_context"`

	// ThinkingLevel controls extended thinking: "", "off", "low", "medium", "high".
	ThinkingLevel string `yaml:"thinking_level"`

	// Verbose enables narration of tool calls and internal steps.
	Verbose bool `yaml:"verbose"`
}

SessionConfig contém configurações específicas de uma sessão.

type SessionData

type SessionData struct {
	ID           string
	Channel      string
	ChatID       string
	History      []ConversationEntry
	Facts        []string
	Config       SessionConfig
	ActiveSkills []string
}

SessionData holds all data needed to restore a session from disk.

type SessionExport

type SessionExport struct {
	ID        string            `json:"id"`
	Channel   string            `json:"channel"`
	ChatID    string            `json:"chat_id"`
	Config    SessionConfig     `json:"config"`
	Facts     []string          `json:"facts"`
	CreatedAt time.Time         `json:"created_at"`
	Messages  []ExportedMessage `json:"messages"`
}

SessionExport is a portable representation of a session for backup/export.

type SessionInfo

type SessionInfo struct {
	SessionMeta
	WorkspaceID string
}

SessionInfo holds session metadata with workspace ID for API responses.

type SessionKey

type SessionKey struct {
	Channel string // "whatsapp", "discord", "webui", "subagent", etc.
	ChatID  string // Group JID, user JID, or chat UUID.
	Branch  string // Optional: sub-session branch (e.g. "topic-research", fork ID).
}

sessionKey gera a chave única para uma sessão. MakeSessionID generates a deterministic, opaque session ID from channel and chatID. The ID is a truncated SHA-256 hash, so no PII (phone numbers, etc.) leaks into file names, logs, or persisted job data. SessionKey is a structured session identifier that preserves the original channel, chatID, and optional branch components while providing a compact string form. This enables multi-agent routing: sessions can be found by channel, user, or branch without losing context.

func ParseSessionKey

func ParseSessionKey(s string) SessionKey

ParseSessionKey parses a "channel:chatID" or "channel:chatID:branch" string.

func (SessionKey) Hash

func (sk SessionKey) Hash() string

Hash returns a compact hash suitable for map keys and persistence IDs.

func (SessionKey) String

func (sk SessionKey) String() string

String returns the canonical string form: "channel:chatID" or "channel:chatID:branch".

type SessionMemoryConfig

type SessionMemoryConfig struct {
	// Enabled turns session memory on/off (default: false).
	Enabled bool `yaml:"enabled"`

	// Messages is the number of recent messages to include in summaries (default: 15).
	Messages int `yaml:"messages"`
}

SessionMemoryConfig configures automatic session summarization.

type SessionMeta

type SessionMeta struct {
	ID           string
	Channel      string
	ChatID       string
	MessageCount int
	CreatedAt    time.Time
	LastActiveAt time.Time
}

SessionMeta holds read-only metadata for a session (for listing).

type SessionPersistence

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

SessionPersistence handles saving and loading sessions to JSONL files.

func NewSessionPersistence

func NewSessionPersistence(dir string, logger *slog.Logger) (*SessionPersistence, error)

NewSessionPersistence creates a SessionPersistence and ensures the directory exists.

func (*SessionPersistence) Close

func (p *SessionPersistence) Close() error

Close flushes any buffers. JSONL writes are unbuffered (direct write), so this is a no-op for now.

func (*SessionPersistence) DeleteSession

func (p *SessionPersistence) DeleteSession(sessionID string) error

DeleteSession removes the session's JSONL and facts files.

func (*SessionPersistence) LoadAll

func (p *SessionPersistence) LoadAll() (map[string]*SessionData, error)

LoadAll scans the directory and restores all sessions from disk.

func (*SessionPersistence) LoadSession

func (p *SessionPersistence) LoadSession(sessionID string) ([]ConversationEntry, []string, error)

LoadSession reads all entries and facts for a session.

func (*SessionPersistence) Rotate

func (p *SessionPersistence) Rotate(sessionID string, maxLines int) error

Rotate creates a .bak of the JSONL file and starts fresh.

func (*SessionPersistence) SaveEntry

func (p *SessionPersistence) SaveEntry(sessionID string, entry ConversationEntry) error

SaveEntry appends one JSONL line for the given conversation entry.

func (*SessionPersistence) SaveFacts

func (p *SessionPersistence) SaveFacts(sessionID string, facts []string) error

SaveFacts writes facts to {session_id}.facts.json.

func (*SessionPersistence) SaveMeta

func (p *SessionPersistence) SaveMeta(sessionID, channel, chatID string, config SessionConfig, activeSkills []string) error

SaveMeta persists session metadata (channel, chatID, config, activeSkills).

type SessionPersister

type SessionPersister interface {
	SaveEntry(sessionID string, entry ConversationEntry) error
	LoadSession(sessionID string) ([]ConversationEntry, []string, error)
	SaveFacts(sessionID string, facts []string) error
	SaveMeta(sessionID, channel, chatID string, config SessionConfig, activeSkills []string) error
	DeleteSession(sessionID string) error
	Rotate(sessionID string, maxLines int) error
	LoadAll() (map[string]*SessionData, error)
	Close() error
}

SessionPersister is the interface for session persistence backends (JSONL or SQLite).

type SessionStats

type SessionStats struct {
	Active      int `json:"active"`
	Total       int `json:"total"`
	WithHistory int `json:"with_history"`
}

SessionStats holds session-related statistics.

type SessionStore

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

SessionStore gerencia sessões ativas, criando e recuperando por canal e chatID. Implementa pruning automático de sessões inativas.

func NewSessionStore

func NewSessionStore(logger *slog.Logger) *SessionStore

NewSessionStore cria um novo store de sessões.

func (*SessionStore) Count

func (ss *SessionStore) Count() int

Count retorna o número de sessões ativas.

func (*SessionStore) Delete

func (ss *SessionStore) Delete(channel, chatID string) bool

Delete removes a session by channel and chatID.

func (*SessionStore) DeleteByID

func (ss *SessionStore) DeleteByID(id string) bool

DeleteByID removes a session by its hash ID.

func (*SessionStore) Export

func (ss *SessionStore) Export(id string) *SessionExport

Export returns a portable representation of a session's history and metadata.

func (*SessionStore) Get

func (ss *SessionStore) Get(channel, chatID string) *Session

Get retorna a sessão pelo canal e chatID, ou nil se não existir.

func (*SessionStore) GetByID

func (ss *SessionStore) GetByID(id string) *Session

GetByID returns a session by its raw store key. Returns nil if not found.

func (*SessionStore) GetOrCreate

func (ss *SessionStore) GetOrCreate(channel, chatID string) *Session

GetOrCreate retorna a sessão existente ou cria uma nova para o canal e chatID. Se persistence estiver configurada, tenta carregar do disco antes de criar.

func (*SessionStore) ListSessions

func (ss *SessionStore) ListSessions() []SessionMeta

ListSessions returns metadata for all sessions in the store.

func (*SessionStore) Prune

func (ss *SessionStore) Prune() int

Prune remove sessões inativas há mais tempo que o TTL configurado. Deve ser chamado periodicamente (ex: via goroutine com ticker).

func (*SessionStore) RenameSession

func (ss *SessionStore) RenameSession(oldID, newChannel, newChatID string) bool

RenameSession changes the ChatID of a session (e.g. for aliasing).

func (*SessionStore) SetPersistence

func (ss *SessionStore) SetPersistence(p SessionPersister)

SetPersistence configures disk persistence for sessions.

func (*SessionStore) StartPruner

func (ss *SessionStore) StartPruner(ctx context.Context)

StartPruner inicia uma goroutine que executa Prune periodicamente. Para quando o contexto é cancelado.

type SessionUsage

type SessionUsage struct {
	PromptTokens     int64
	CompletionTokens int64
	TotalTokens      int64
	Requests         int64
	EstimatedCostUSD float64
	FirstRequestAt   time.Time
	LastRequestAt    time.Time
}

SessionUsage holds token and cost stats for a session.

type SharedMemory

type SharedMemory struct {
	Key       string    `json:"key"`
	Value     string    `json:"value"`
	Author    string    `json:"author"` // User ID who wrote it
	UpdatedAt time.Time `json:"updated_at"`
	Tags      []string  `json:"tags,omitempty"`
}

SharedMemory represents a team-shared memory space.

type SkillsConfig

type SkillsConfig struct {
	// Builtin lists built-in skills to enable.
	Builtin []string `yaml:"builtin"`

	// Installed lists installed skill names.
	Installed []string `yaml:"installed"`

	// ClawdHubDirs lists directories with ClawdHub SKILL.md skills.
	ClawdHubDirs []string `yaml:"clawdhub_dirs"`
}

SkillsConfig configures the skills system.

type SpawnParams

type SpawnParams struct {
	Task            string
	Label           string
	Model           string
	ParentSessionID string
	TimeoutSeconds  int

	// SpawnDepth is the nesting level (1 = top-level subagent).
	// If not set, defaults to 1.
	SpawnDepth int

	// OriginChannel, OriginTo, and OriginThreadID identify where to push the
	// completion announcement. When OriginChannel is set the announce callback
	// delivers the result directly to that channel/chat in addition to injecting
	// it into the parent agent loop.
	OriginChannel  string
	OriginTo       string
	OriginThreadID string
}

SpawnParams holds parameters for spawning a subagent.

type StartupCheckResult

type StartupCheckResult struct {
	Name     string // Check name (e.g., "vault", "database", "channels")
	Status   string // "ok", "warning", "error", "skipped"
	Message  string // Human-readable message
	Required bool   // If true, failure blocks startup
}

StartupCheckResult represents the result of a single startup check.

type StartupReport

type StartupReport struct {
	Results []StartupCheckResult
	Healthy bool // true if all required checks pass
}

StartupReport contains all startup check results.

type StartupVerifier

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

StartupVerifier performs system checks at initialization.

func NewStartupVerifier

func NewStartupVerifier(cfg *Config, vault *Vault, logger *slog.Logger) *StartupVerifier

NewStartupVerifier creates a new startup verifier.

func (*StartupVerifier) PrintReport

func (sv *StartupVerifier) PrintReport(report *StartupReport)

PrintReport logs a formatted startup report.

func (*StartupVerifier) RunAll

func (sv *StartupVerifier) RunAll() *StartupReport

RunAll executes all startup checks and returns a report.

type StreamCallback

type StreamCallback func(chunk string)

StreamCallback is called for each token/chunk during streaming.

type SubagentConfig

type SubagentConfig struct {
	// Enabled turns the subagent system on/off (default: true).
	Enabled bool `yaml:"enabled"`

	// MaxConcurrent is the max number of subagents running at the same time.
	MaxConcurrent int `yaml:"max_concurrent"`

	// MaxTurns is the max agent loop turns for each subagent (default: 15).
	MaxTurns int `yaml:"max_turns"`

	// TimeoutSeconds is the max execution time per subagent (default: 300 = 5min).
	TimeoutSeconds int `yaml:"timeout_seconds"`

	// MaxSpawnDepth controls nested subagent spawning (default: 1 = no nesting).
	// Set to 2 to allow subagents to spawn their own children.
	// Higher values allow deeper nesting (e.g., 3 = sub-sub-subagents).
	MaxSpawnDepth int `yaml:"max_spawn_depth"`

	// MaxChildrenPerAgent limits how many children a single agent can spawn (default: 5).
	MaxChildrenPerAgent int `yaml:"max_children_per_agent"`

	// DeniedTools lists tool names that subagents cannot use.
	// Default: ["spawn_subagent", "list_subagents", "wait_subagent"]
	DeniedTools []string `yaml:"denied_tools"`

	// Model overrides the LLM model for subagents (empty = use parent model).
	Model string `yaml:"model"`
}

SubagentConfig configures the subagent system.

func DefaultSubagentConfig

func DefaultSubagentConfig() SubagentConfig

DefaultSubagentConfig returns safe defaults.

type SubagentManager

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

SubagentManager orchestrates subagent lifecycle: spawning, tracking, and cleanup.

func NewSubagentManager

func NewSubagentManager(cfg SubagentConfig, logger *slog.Logger) *SubagentManager

NewSubagentManager creates a new subagent manager.

func (*SubagentManager) ActiveCount

func (m *SubagentManager) ActiveCount() int

ActiveCount returns the number of currently running subagents.

func (*SubagentManager) Cleanup

func (m *SubagentManager) Cleanup(maxAge time.Duration) int

Cleanup removes completed/failed runs older than the given duration.

func (*SubagentManager) Get

func (m *SubagentManager) Get(runID string) (*SubagentRun, bool)

Get returns a subagent run by ID. Checks in-memory first, then SQLite.

func (*SubagentManager) List

func (m *SubagentManager) List() []*SubagentRun

List returns all subagent runs (active in-memory + recent from SQLite). Merges both sources, deduplicating by ID.

func (*SubagentManager) PruneOldRuns

func (m *SubagentManager) PruneOldRuns(days int) int

PruneOldRuns removes persisted runs older than the given number of days.

func (*SubagentManager) SetAnnounceCallback

func (m *SubagentManager) SetAnnounceCallback(cb AnnounceCallback)

SetAnnounceCallback registers a callback that fires when any subagent completes. This enables push-style announce: the parent is notified immediately instead of having to poll via wait_subagent.

func (*SubagentManager) SetDB

func (m *SubagentManager) SetDB(db *sql.DB)

SetDB wires the central SQLite database for persisting subagent runs. When set, completed/failed runs survive process restarts.

func (*SubagentManager) Spawn

func (m *SubagentManager) Spawn(
	parentCtx context.Context,
	params SpawnParams,
	llmClient *LLMClient,
	parentExecutor *ToolExecutor,
	promptComposer *PromptComposer,
) (*SubagentRun, error)

Spawn creates and starts a new subagent. Returns the run ID immediately. The subagent executes in a background goroutine.

func (*SubagentManager) Stop

func (m *SubagentManager) Stop(runID string) error

Stop cancels a running subagent.

func (*SubagentManager) Wait

func (m *SubagentManager) Wait(ctx context.Context, runID string) (*SubagentRun, error)

Wait blocks until the specified subagent run completes or the context is cancelled. Returns the final run state.

type SubagentRun

type SubagentRun struct {
	// ID is a unique identifier for this run.
	ID string `json:"id"`

	// Label is a human-readable label for identification.
	Label string `json:"label"`

	// Task is the original task description given to the subagent.
	Task string `json:"task"`

	// Status is the current execution status.
	Status SubagentStatus `json:"status"`

	// Result is the final text response (set on completion).
	Result string `json:"result,omitempty"`

	// Error holds the error message if the subagent failed.
	Error string `json:"error,omitempty"`

	// Model is the LLM model used for this run.
	Model string `json:"model,omitempty"`

	// ParentSessionID is the session that spawned this subagent.
	ParentSessionID string `json:"parent_session_id"`

	// SpawnDepth is the nesting level (1 = top-level subagent, 2 = child of subagent, etc.).
	SpawnDepth int `json:"spawn_depth"`

	// ChildrenCount tracks how many children this subagent has spawned.
	ChildrenCount int `json:"children_count,omitempty"`

	// OriginChannel is the channel name (e.g. "telegram", "discord") where the
	// spawn was requested. When set, the completion announcement is delivered
	// directly to that channel/chat rather than only injecting into the agent loop.
	OriginChannel string `json:"origin_channel,omitempty"`

	// OriginTo is the chat ID / recipient address in the origin channel.
	// For Telegram this may include a topic suffix (e.g. "12345678:topic:42").
	OriginTo string `json:"origin_to,omitempty"`

	// OriginThreadID is an optional thread or topic ID for threaded delivery
	// within the origin channel (e.g. a Slack thread_ts or Telegram topic ID).
	// TODO: populate from IncomingMessage.Metadata once per-message thread context
	// is propagated through the tool execution context.
	OriginThreadID string `json:"origin_thread_id,omitempty"`

	// StartedAt is when the subagent started.
	StartedAt time.Time `json:"started_at"`

	// CompletedAt is when the subagent finished (zero if still running).
	CompletedAt time.Time `json:"completed_at,omitempty"`

	// Duration is the wall-clock execution time.
	Duration time.Duration `json:"duration,omitempty"`

	// TokensUsed tracks approximate token usage.
	TokensUsed int `json:"tokens_used,omitempty"`
	// contains filtered or unexported fields
}

SubagentRun tracks a single subagent execution.

type SubagentStatus

type SubagentStatus string

SubagentStatus represents the current state of a subagent run.

const (
	SubagentStatusRunning   SubagentStatus = "running"
	SubagentStatusCompleted SubagentStatus = "completed"
	SubagentStatusFailed    SubagentStatus = "failed"
	SubagentStatusTimeout   SubagentStatus = "timeout"
)

type SubscriptionReason added in v1.8.0

type SubscriptionReason string

SubscriptionReason represents how an agent was subscribed to a thread.

const (
	SubscriptionAuto      SubscriptionReason = "auto"      // Automatic (commented)
	SubscriptionManual    SubscriptionReason = "manual"    // Explicitly subscribed
	SubscriptionMentioned SubscriptionReason = "mentioned" // Was @mentioned
	SubscriptionAssigned  SubscriptionReason = "assigned"  // Was assigned to task
)

type SystemCommands

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

SystemCommands holds system administration command handlers.

func NewSystemCommands

func NewSystemCommands(a *Assistant, configPath string, maintenanceMgr *MaintenanceManager) *SystemCommands

NewSystemCommands creates a new system command handler.

func (*SystemCommands) ChannelsCommand

func (t *SystemCommands) ChannelsCommand(args []string) string

ChannelsCommand handles /channels [connect|disconnect <name>]

func (*SystemCommands) DiagnosticsCommand

func (t *SystemCommands) DiagnosticsCommand(full bool) string

DiagnosticsCommand handles /diagnostics [--full]

func (*SystemCommands) ExecQueueCommand

func (t *SystemCommands) ExecQueueCommand() string

ExecQueueCommand handles /exec queue

func (*SystemCommands) HealthCommand

func (t *SystemCommands) HealthCommand() string

HealthCommand handles /health

func (*SystemCommands) LogsCommand

func (t *SystemCommands) LogsCommand(args []string) string

LogsCommand handles /logs [level] [lines]

func (*SystemCommands) MaintenanceCommand

func (t *SystemCommands) MaintenanceCommand(args []string, setBy string) string

MaintenanceCommand handles /maintenance [on|off] [message]

func (*SystemCommands) MetricsCommand

func (t *SystemCommands) MetricsCommand(args []string) string

MetricsCommand handles /metrics [period]

func (*SystemCommands) ReloadCommand

func (t *SystemCommands) ReloadCommand(args []string) string

ReloadCommand handles /reload [section]

func (*SystemCommands) StatusCommand

func (t *SystemCommands) StatusCommand(jsonOutput bool) string

StatusCommand handles /status [--json]

type SystemStatus

type SystemStatus struct {
	Version       string                   `json:"version"`
	Uptime        string                   `json:"uptime"`
	UptimeSeconds int64                    `json:"uptime_seconds"`
	MemoryMB      float64                  `json:"memory_mb"`
	GoRoutines    int                      `json:"goroutines"`
	Channels      map[string]ChannelHealth `json:"channels"`
	Sessions      SessionStats             `json:"sessions"`
	Scheduler     SchedulerStats           `json:"scheduler"`
	Skills        int                      `json:"skills"`
	Maintenance   *MaintenanceMode         `json:"maintenance,omitempty"`
}

SystemStatus represents comprehensive system status for TechOps commands.

type TTSConfig

type TTSConfig struct {
	// Enabled activates TTS for assistant responses.
	Enabled bool `yaml:"enabled"`

	// Provider is the TTS provider to use: "openai" (default), "edge", "auto".
	// "auto" tries OpenAI first, falls back to Edge TTS if OpenAI is unavailable.
	Provider string `yaml:"provider"`

	// Voice is the voice to use.
	//   OpenAI: alloy, echo, fable, onyx, nova, shimmer
	//   Edge: pt-BR-FranciscaNeural, en-US-JennyNeural, etc.
	Voice string `yaml:"voice"`

	// EdgeVoice is the voice to use specifically for Edge TTS (when provider is "auto").
	// If empty, falls back to Voice.
	EdgeVoice string `yaml:"edge_voice"`

	// Model is the TTS model: "tts-1" (fast) or "tts-1-hd" (high quality).
	// Only used for OpenAI provider.
	Model string `yaml:"model"`

	// AutoMode controls when TTS is used:
	//   "off"     - disabled (default)
	//   "always"  - always generate audio alongside text
	//   "inbound" - generate audio only when the user sent a voice note
	AutoMode string `yaml:"auto_mode"`
}

TTSConfig configures text-to-speech synthesis.

type TailscaleConfig

type TailscaleConfig struct {
	// Enabled turns Tailscale integration on/off.
	Enabled bool `yaml:"enabled"`

	// Serve enables Tailscale Serve (accessible within your Tailnet).
	Serve bool `yaml:"serve"`

	// Funnel enables Tailscale Funnel (accessible from the public internet).
	// Requires Tailscale Funnel to be enabled in your Tailscale admin console.
	Funnel bool `yaml:"funnel"`

	// Port is the local port to proxy (default: 8085).
	Port int `yaml:"port"`

	// Hostname is the Tailscale hostname to use (empty = auto from `tailscale status`).
	Hostname string `yaml:"hostname"`
}

TailscaleConfig configures Tailscale Serve/Funnel integration.

type TailscaleManager

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

TailscaleManager manages Tailscale Serve/Funnel lifecycle.

func NewTailscaleManager

func NewTailscaleManager(cfg TailscaleConfig, logger *slog.Logger) *TailscaleManager

NewTailscaleManager creates a new Tailscale manager.

func (*TailscaleManager) Start

func (tm *TailscaleManager) Start(ctx context.Context) error

Start sets up Tailscale Serve and/or Funnel based on config.

func (*TailscaleManager) Status

func (tm *TailscaleManager) Status() map[string]any

Status returns the current Tailscale integration status.

func (*TailscaleManager) Stop

func (tm *TailscaleManager) Stop()

Stop tears down Tailscale Serve/Funnel.

func (*TailscaleManager) URL

func (tm *TailscaleManager) URL() string

URL returns the public-facing URL if available, or empty string.

type TaskStatus added in v1.8.0

type TaskStatus string

TaskStatus represents the state of a team task.

const (
	TaskStatusInbox     TaskStatus = "inbox"
	TaskStatusAssigned  TaskStatus = "assigned"
	TaskStatusProgress  TaskStatus = "in_progress"
	TaskStatusReview    TaskStatus = "review"
	TaskStatusDone      TaskStatus = "done"
	TaskStatusBlocked   TaskStatus = "blocked"
	TaskStatusCancelled TaskStatus = "cancelled"
)

type Team added in v1.8.0

type Team struct {
	// ID is the unique team identifier.
	ID string `json:"id" yaml:"id"`

	// Name is the human-readable team name.
	Name string `json:"name" yaml:"name"`

	// Description explains the team's purpose.
	Description string `json:"description,omitempty" yaml:"description,omitempty"`

	// OwnerJID is the user who owns this team.
	OwnerJID string `json:"owner_jid" yaml:"owner_jid"`

	// DefaultModel is the default LLM model for agents in this team.
	DefaultModel string `json:"default_model,omitempty" yaml:"default_model,omitempty"`

	// WorkspacePath is the team's workspace directory for files/memory.
	WorkspacePath string `json:"workspace_path,omitempty" yaml:"workspace_path,omitempty"`

	// CreatedAt is when the team was created.
	CreatedAt time.Time `json:"created_at" yaml:"created_at"`

	// Enabled indicates if the team is active.
	Enabled bool `json:"enabled" yaml:"enabled"`
}

Team represents a group of agents working together with shared context.

type TeamActivity added in v1.8.0

type TeamActivity struct {
	// ID is the unique activity identifier.
	ID string `json:"id" yaml:"id"`

	// TeamID is the team this activity belongs to.
	TeamID string `json:"team_id" yaml:"team_id"`

	// Type is the activity type.
	Type ActivityType `json:"type" yaml:"type"`

	// AgentID is the agent that performed the activity.
	AgentID string `json:"agent_id,omitempty" yaml:"agent_id,omitempty"`

	// Message is a human-readable description.
	Message string `json:"message" yaml:"message"`

	// RelatedID is the related entity ID (task, message, etc.).
	RelatedID string `json:"related_id,omitempty" yaml:"related_id,omitempty"`

	// CreatedAt is when the activity occurred.
	CreatedAt time.Time `json:"created_at" yaml:"created_at"`
}

TeamActivity represents an entry in the activity feed.

type TeamConfig

type TeamConfig struct {
	Enabled      bool   `yaml:"enabled" json:"enabled"`
	MaxUsers     int    `yaml:"max_users" json:"max_users"`
	SharedMemory bool   `yaml:"shared_memory" json:"shared_memory"`
	AuditLog     bool   `yaml:"audit_log" json:"audit_log"`
	DefaultRole  string `yaml:"default_role" json:"default_role"`
}

TeamConfig holds multi-user configuration.

func DefaultTeamConfig

func DefaultTeamConfig() TeamConfig

DefaultTeamConfig returns sensible defaults.

type TeamDocument added in v1.8.0

type TeamDocument struct {
	// ID is the unique document identifier.
	ID string `json:"id" yaml:"id"`

	// TeamID is the team this document belongs to.
	TeamID string `json:"team_id" yaml:"team_id"`

	// TaskID is the task this document is linked to (optional).
	TaskID string `json:"task_id,omitempty" yaml:"task_id,omitempty"`

	// Title is the document title.
	Title string `json:"title" yaml:"title"`

	// DocType is the document type.
	DocType DocumentType `json:"doc_type" yaml:"doc_type"`

	// Content is the document content (markdown, code, etc.).
	Content string `json:"content" yaml:"content"`

	// Format is the content format (markdown, code, json).
	Format string `json:"format" yaml:"format"`

	// FilePath is an optional link to a workspace file.
	FilePath string `json:"file_path,omitempty" yaml:"file_path,omitempty"`

	// Version is the document version number.
	Version int `json:"version" yaml:"version"`

	// Author is the agent or user who created the document.
	Author string `json:"author" yaml:"author"`

	// CreatedAt is when the document was created.
	CreatedAt time.Time `json:"created_at" yaml:"created_at"`

	// UpdatedAt is when the document was last modified.
	UpdatedAt time.Time `json:"updated_at" yaml:"updated_at"`
}

TeamDocument represents a deliverable or artifact linked to a task.

type TeamFact added in v1.8.0

type TeamFact struct {
	// ID is the unique fact identifier.
	ID string `json:"id" yaml:"id"`

	// TeamID is the team this fact belongs to.
	TeamID string `json:"team_id" yaml:"team_id"`

	// Key is the fact key/label.
	Key string `json:"key" yaml:"key"`

	// Value is the fact content.
	Value string `json:"value" yaml:"value"`

	// Author is the agent or user who created this fact.
	Author string `json:"author" yaml:"author"`

	// CreatedAt is when the fact was created.
	CreatedAt time.Time `json:"created_at" yaml:"created_at"`

	// UpdatedAt is when the fact was last modified.
	UpdatedAt time.Time `json:"updated_at" yaml:"updated_at"`
}

TeamFact represents a shared fact in team memory.

type TeamManager added in v1.8.0

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

TeamManager manages teams and persistent agents.

func NewTeamManager added in v1.8.0

func NewTeamManager(db *sql.DB, sched *scheduler.Scheduler, logger *slog.Logger) *TeamManager

NewTeamManager creates a new team manager.

func (*TeamManager) BuildAgentSystemPrompt added in v1.8.0

func (tm *TeamManager) BuildAgentSystemPrompt(agent *PersistentAgent, teamMemory *TeamMemory) string

BuildAgentSystemPrompt builds a system prompt for a persistent agent.

func (*TeamManager) ClearAgentWorkingState added in v1.8.0

func (tm *TeamManager) ClearAgentWorkingState(agentID string) error

ClearAgentWorkingState clears an agent's working state (e.g., on task completion).

func (*TeamManager) CreateAgent added in v1.8.0

func (tm *TeamManager) CreateAgent(
	teamID, name, role, personality, instructions, model string,
	skills []string, level AgentLevel, heartbeatSchedule string,
) (*PersistentAgent, error)

CreateAgent creates a new persistent agent.

func (*TeamManager) CreateTeam added in v1.8.0

func (tm *TeamManager) CreateTeam(name, description, ownerJID, defaultModel string) (*Team, error)

CreateTeam creates a new team.

func (*TeamManager) DeleteAgent added in v1.8.0

func (tm *TeamManager) DeleteAgent(agentID string) error

DeleteAgent permanently removes an agent.

func (*TeamManager) FindAgentByName added in v1.8.0

func (tm *TeamManager) FindAgentByName(name string) (*PersistentAgent, error)

FindAgentByName finds an agent by name (case-insensitive).

func (*TeamManager) GetAgent added in v1.8.0

func (tm *TeamManager) GetAgent(agentID string) (*PersistentAgent, error)

GetAgent retrieves a persistent agent by ID.

func (*TeamManager) GetAgentWorkingState added in v1.8.0

func (tm *TeamManager) GetAgentWorkingState(agentID string) (*AgentWorkingState, error)

GetAgentWorkingState retrieves an agent's working state.

func (*TeamManager) GetTeam added in v1.8.0

func (tm *TeamManager) GetTeam(teamID string) (*Team, error)

GetTeam retrieves a team by ID.

func (*TeamManager) GetTeamMemory added in v1.8.0

func (tm *TeamManager) GetTeamMemory(teamID string) *TeamMemory

GetTeamMemory creates a TeamMemory instance for a team.

func (*TeamManager) ListAgents added in v1.8.0

func (tm *TeamManager) ListAgents(teamID string) ([]*PersistentAgent, error)

ListAgents lists all agents in a team.

func (*TeamManager) ListTeams added in v1.8.0

func (tm *TeamManager) ListTeams() ([]*Team, error)

ListTeams lists all teams.

func (*TeamManager) ParseMentions added in v1.8.0

func (tm *TeamManager) ParseMentions(text string) []string

ParseMentions extracts @mentions from text and returns agent IDs.

func (*TeamManager) RecordHeartbeat added in v1.8.0

func (tm *TeamManager) RecordHeartbeat(agentID string) error

RecordHeartbeat records that a heartbeat was executed.

func (*TeamManager) SaveAgentWorkingState added in v1.8.0

func (tm *TeamManager) SaveAgentWorkingState(state *AgentWorkingState) error

SaveAgentWorkingState saves an agent's working state.

func (*TeamManager) SendToAgent added in v1.8.0

func (tm *TeamManager) SendToAgent(ctx context.Context, toAgentID, fromAgent, message string) error

SendToAgent sends a message to an agent, triggering an immediate run.

func (*TeamManager) SetAgentCurrentTask added in v1.8.0

func (tm *TeamManager) SetAgentCurrentTask(agentID, taskID string) error

SetAgentCurrentTask sets the current task for an agent.

func (*TeamManager) SetSpawnAgentCallback added in v1.8.0

func (tm *TeamManager) SetSpawnAgentCallback(fn func(ctx context.Context, agent *PersistentAgent, task string) error)

SetSpawnAgentCallback sets the callback for spawning agent runs.

func (*TeamManager) StopAgent added in v1.8.0

func (tm *TeamManager) StopAgent(agentID string) error

StopAgent stops a persistent agent (disables heartbeats).

func (*TeamManager) UpdateAgentStatus added in v1.8.0

func (tm *TeamManager) UpdateAgentStatus(agentID string, status AgentStatus) error

UpdateAgentStatus updates an agent's status.

type TeamMemory added in v1.8.0

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

TeamMemory provides shared memory and communication for a team.

func NewTeamMemory added in v1.8.0

func NewTeamMemory(teamID string, db *sql.DB, logger *slog.Logger) *TeamMemory

NewTeamMemory creates a new team memory instance.

func (*TeamMemory) AssignTask added in v1.8.0

func (tm *TeamMemory) AssignTask(taskID string, assignees []string, assignedBy string) error

AssignTask assigns agents to a task.

func (*TeamMemory) BuildTeamContext added in v1.8.0

func (tm *TeamMemory) BuildTeamContext(agentID string) (string, error)

BuildTeamContext creates a context string for agents with current team state.

func (*TeamMemory) CreateDocument added in v1.8.0

func (tm *TeamMemory) CreateDocument(title string, docType DocumentType, content, format, taskID, author string) (*TeamDocument, error)

CreateDocument creates a new document linked to a task.

func (*TeamMemory) CreateTask added in v1.8.0

func (tm *TeamMemory) CreateTask(title, description, createdBy string, assignees []string) (*TeamTask, error)

CreateTask creates a new task in the team.

func (*TeamMemory) DeleteDocument added in v1.8.0

func (tm *TeamMemory) DeleteDocument(docID string) error

DeleteDocument removes a document.

func (*TeamMemory) DeleteFact added in v1.8.0

func (tm *TeamMemory) DeleteFact(key string) error

DeleteFact removes a fact from team memory.

func (*TeamMemory) GenerateStandup added in v1.8.0

func (tm *TeamMemory) GenerateStandup() (string, error)

GenerateStandup creates a daily standup summary.

func (*TeamMemory) GetActivities added in v1.8.0

func (tm *TeamMemory) GetActivities(limit int) ([]*TeamActivity, error)

GetActivities retrieves recent activities.

func (*TeamMemory) GetDocument added in v1.8.0

func (tm *TeamMemory) GetDocument(docID string) (*TeamDocument, error)

GetDocument retrieves a document by ID.

func (*TeamMemory) GetFacts added in v1.8.0

func (tm *TeamMemory) GetFacts() ([]*TeamFact, error)

GetFacts retrieves all facts for the team.

func (*TeamMemory) GetPendingMessages added in v1.8.0

func (tm *TeamMemory) GetPendingMessages(agentID string, markDelivered bool) ([]*PendingMessage, error)

GetPendingMessages retrieves undelivered messages for an agent.

func (*TeamMemory) GetSubscribedThreads added in v1.8.0

func (tm *TeamMemory) GetSubscribedThreads(agentID string) ([]string, error)

GetSubscribedThreads returns all threads an agent is subscribed to.

func (*TeamMemory) GetTask added in v1.8.0

func (tm *TeamMemory) GetTask(taskID string) (*TeamTask, error)

GetTask retrieves a task by ID.

func (*TeamMemory) GetTasksForAgent added in v1.8.0

func (tm *TeamMemory) GetTasksForAgent(agentID string) ([]*TeamTask, error)

GetTasksForAgent returns tasks assigned to a specific agent.

func (*TeamMemory) GetThreadMessages added in v1.8.0

func (tm *TeamMemory) GetThreadMessages(threadID string, limit int) ([]*TeamMessage, error)

GetThreadMessages returns all messages in a thread.

func (*TeamMemory) GetThreadSubscribers added in v1.8.0

func (tm *TeamMemory) GetThreadSubscribers(threadID string) ([]string, error)

GetThreadSubscribers returns all agents subscribed to a thread.

func (*TeamMemory) ListDocuments added in v1.8.0

func (tm *TeamMemory) ListDocuments(taskID string, docType DocumentType) ([]*TeamDocument, error)

ListDocuments lists documents with optional filters.

func (*TeamMemory) ListTasks added in v1.8.0

func (tm *TeamMemory) ListTasks(statusFilter TaskStatus, assigneeFilter string) ([]*TeamTask, error)

ListTasks lists tasks with optional filters.

func (*TeamMemory) NotifySubscribers added in v1.8.0

func (tm *TeamMemory) NotifySubscribers(threadID, fromAgent, content string, excludeAgents []string) error

NotifySubscribers adds pending messages for all thread subscribers.

func (*TeamMemory) PostMessage added in v1.8.0

func (tm *TeamMemory) PostMessage(threadID, fromAgent, content string, mentions []string) (*TeamMessage, error)

PostMessage posts a message to a thread/task.

func (*TeamMemory) SaveFact added in v1.8.0

func (tm *TeamMemory) SaveFact(key, value, author string) error

SaveFact saves a fact to shared team memory.

func (*TeamMemory) SearchFacts added in v1.8.0

func (tm *TeamMemory) SearchFacts(query string) ([]*TeamFact, error)

SearchFacts searches facts by key or value.

func (*TeamMemory) SetTeamManager added in v1.8.0

func (tm *TeamMemory) SetTeamManager(mgr *TeamManager)

SetTeamManager sets the team manager for notification push.

func (*TeamMemory) SubscribeToThread added in v1.8.0

func (tm *TeamMemory) SubscribeToThread(threadID, agentID string, reason SubscriptionReason) error

SubscribeToThread subscribes an agent to a thread.

func (*TeamMemory) UnsubscribeFromThread added in v1.8.0

func (tm *TeamMemory) UnsubscribeFromThread(threadID, agentID string) error

UnsubscribeFromThread removes an agent's subscription.

func (*TeamMemory) UpdateDocument added in v1.8.0

func (tm *TeamMemory) UpdateDocument(docID, content, updatedBy string) error

UpdateDocument updates document content, incrementing version.

func (*TeamMemory) UpdateTask added in v1.8.0

func (tm *TeamMemory) UpdateTask(taskID string, status TaskStatus, comment, updatedBy string) error

UpdateTask updates a task's status and optionally adds a comment.

type TeamMessage added in v1.8.0

type TeamMessage struct {
	// ID is the unique message identifier.
	ID string `json:"id" yaml:"id"`

	// TeamID is the team this message belongs to.
	TeamID string `json:"team_id" yaml:"team_id"`

	// ThreadID is the task ID or thread identifier (empty = general).
	ThreadID string `json:"thread_id,omitempty" yaml:"thread_id,omitempty"`

	// FromAgent is the agent ID that sent the message.
	FromAgent string `json:"from_agent" yaml:"from_agent"`

	// FromUser is the user JID if sent by a human (optional).
	FromUser string `json:"from_user,omitempty" yaml:"from_user,omitempty"`

	// Content is the message text.
	Content string `json:"content" yaml:"content"`

	// Mentions are agent IDs mentioned in the message (@agent_id).
	Mentions []string `json:"mentions,omitempty" yaml:"mentions,omitempty"`

	// CreatedAt is when the message was sent.
	CreatedAt time.Time `json:"created_at" yaml:"created_at"`

	// Delivered indicates if mentioned agents have received this.
	Delivered bool `json:"delivered" yaml:"delivered"`
}

TeamMessage represents a message in a task thread or general discussion.

type TeamTask added in v1.8.0

type TeamTask struct {
	// ID is the unique task identifier.
	ID string `json:"id" yaml:"id"`

	// TeamID is the team this task belongs to.
	TeamID string `json:"team_id" yaml:"team_id"`

	// Title is the short task description.
	Title string `json:"title" yaml:"title"`

	// Description is the detailed task description.
	Description string `json:"description,omitempty" yaml:"description,omitempty"`

	// Status is the current task state.
	Status TaskStatus `json:"status" yaml:"status"`

	// Assignees are the agent IDs assigned to this task.
	Assignees []string `json:"assignees,omitempty" yaml:"assignees,omitempty"`

	// Priority is the task priority (1-5, 1=highest).
	Priority int `json:"priority,omitempty" yaml:"priority,omitempty"`

	// Labels are arbitrary tags for organization.
	Labels []string `json:"labels,omitempty" yaml:"labels,omitempty"`

	// CreatedBy is the agent or user who created the task.
	CreatedBy string `json:"created_by" yaml:"created_by"`

	// CreatedAt is when the task was created.
	CreatedAt time.Time `json:"created_at" yaml:"created_at"`

	// UpdatedAt is when the task was last modified.
	UpdatedAt time.Time `json:"updated_at" yaml:"updated_at"`

	// CompletedAt is when the task was marked done.
	CompletedAt *time.Time `json:"completed_at,omitempty" yaml:"completed_at,omitempty"`

	// BlockedReason explains why the task is blocked.
	BlockedReason string `json:"blocked_reason,omitempty" yaml:"blocked_reason,omitempty"`
}

TeamTask represents a shared task that agents can work on.

type TeamUser

type TeamUser struct {
	ID        string    `json:"id"`
	Name      string    `json:"name"`
	Email     string    `json:"email,omitempty"`
	Role      UserRole  `json:"role"`
	CreatedAt time.Time `json:"created_at"`
	LastSeen  time.Time `json:"last_seen"`
	Active    bool      `json:"active"`
}

TeamUser represents a user in the multi-user system.

type ThreadSubscription added in v1.8.0

type ThreadSubscription struct {
	// ID is the unique subscription identifier.
	ID string `json:"id" yaml:"id"`

	// TeamID is the team this subscription belongs to.
	TeamID string `json:"team_id" yaml:"team_id"`

	// ThreadID is the task or thread identifier.
	ThreadID string `json:"thread_id" yaml:"thread_id"`

	// AgentID is the subscribed agent.
	AgentID string `json:"agent_id" yaml:"agent_id"`

	// SubscribedAt is when the subscription was created.
	SubscribedAt time.Time `json:"subscribed_at" yaml:"subscribed_at"`

	// Reason is how the agent was subscribed.
	Reason SubscriptionReason `json:"reason" yaml:"reason"`
}

ThreadSubscription represents an agent's subscription to a thread.

type TokenBudgetConfig

type TokenBudgetConfig struct {
	Total    int `yaml:"total"`
	Reserved int `yaml:"reserved"`
	System   int `yaml:"system"`
	Skills   int `yaml:"skills"`
	Memory   int `yaml:"memory"`
	History  int `yaml:"history"`
	Tools    int `yaml:"tools"`

	// BootstrapMaxChars is the max total characters for all bootstrap files
	// combined (SOUL.md, IDENTITY.md, etc.). Default: 20000 (~5K tokens).
	BootstrapMaxChars int `yaml:"bootstrap_max_chars"`
}

TokenBudgetConfig configures per-layer token allocation.

type TokenOptions

type TokenOptions struct {
	Role        TokenRole
	MaxUses     int           // 0 = unlimited
	ExpiresIn   time.Duration // 0 = never expires
	AutoApprove bool
	WorkspaceID string
	Note        string
}

TokenOptions configures token generation.

type TokenRole

type TokenRole string

TokenRole defines the access level granted by a pairing token.

const (
	TokenRoleUser  TokenRole = "user"
	TokenRoleAdmin TokenRole = "admin"
)

type ToolCall

type ToolCall struct {
	ID       string       `json:"id"`
	Type     string       `json:"type"`
	Function FunctionCall `json:"function"`
}

ToolCall represents a tool invocation requested by the LLM.

type ToolCheckResult

type ToolCheckResult struct {
	Allowed              bool
	Reason               string
	RequiresConfirmation bool // true if tool needs user approval before execution
}

CheckResult holds the result of a tool access check.

type ToolDefinition

type ToolDefinition struct {
	Type     string      `json:"type"`
	Function FunctionDef `json:"function"`
}

ToolDefinition is an OpenAI-compatible tool definition for function calling.

func MakeToolDefinition

func MakeToolDefinition(name, description string, params map[string]any) ToolDefinition

MakeToolDefinition creates a ToolDefinition from name, description, and a parameter schema map (matching JSON Schema format). The name is automatically sanitized to match OpenAI's pattern.

func SkillToolToDefinition

func SkillToolToDefinition(name string, tool skills.Tool) ToolDefinition

SkillToolToDefinition converts a skills.Tool into an OpenAI ToolDefinition.

type ToolExecutor

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

ToolExecutor manages tool registration and dispatches tool calls.

func NewToolExecutor

func NewToolExecutor(logger *slog.Logger) *ToolExecutor

NewToolExecutor creates a new empty tool executor.

func (*ToolExecutor) Abort

func (e *ToolExecutor) Abort()

Abort signals all running tools to stop. Safe to call multiple times.

func (*ToolExecutor) AbortCh

func (e *ToolExecutor) AbortCh() <-chan struct{}

AbortCh returns the abort channel for tools to select on.

func (*ToolExecutor) Configure

func (e *ToolExecutor) Configure(cfg ToolExecutorConfig)

Configure applies ToolExecutorConfig (parallel, max_parallel, timeouts).

func (*ToolExecutor) Execute

func (e *ToolExecutor) Execute(ctx context.Context, calls []ToolCall) []ToolResult

Execute dispatches a batch of tool calls to their registered handlers. Each tool is executed with a per-tool timeout. When Parallel is true and no sequential tools are in the batch, runs concurrently. Returns results in the same order as the input calls.

func (*ToolExecutor) Guard

func (e *ToolExecutor) Guard() *ToolGuard

Guard returns the configured ToolGuard (may be nil).

func (*ToolExecutor) HasTool

func (e *ToolExecutor) HasTool(name string) bool

HasTool checks if a tool is registered by name.

func (*ToolExecutor) IsAborted

func (e *ToolExecutor) IsAborted() bool

IsAborted returns true if an abort has been signaled.

func (*ToolExecutor) Register

func (e *ToolExecutor) Register(def ToolDefinition, handler ToolHandlerFunc)

Register adds a tool with its definition and handler. If a tool with the same name already exists, it is overwritten.

func (*ToolExecutor) RegisterHook

func (e *ToolExecutor) RegisterHook(hook *ToolHook)

RegisterHook adds a before/after tool execution hook. Hooks are called in registration order. Multiple hooks can be registered.

func (*ToolExecutor) RegisterSkillTools

func (e *ToolExecutor) RegisterSkillTools(skill skills.Skill)

RegisterSkillTools registers all tools exposed by a skill. Tool names are prefixed with the skill name to avoid collisions. Names are sanitized to match OpenAI's pattern: ^[a-zA-Z0-9_-]+$

func (*ToolExecutor) ResetAbort

func (e *ToolExecutor) ResetAbort()

ResetAbort creates a fresh abort channel for a new run.

func (*ToolExecutor) SessionContext

func (e *ToolExecutor) SessionContext() string

SessionContext returns the current session ID (format: "channel:chatID").

func (*ToolExecutor) SetCallerContext

func (e *ToolExecutor) SetCallerContext(level AccessLevel, jid string)

SetCallerContext sets the access level and JID for the current caller. Must be called before Execute() in the message handling flow.

func (*ToolExecutor) SetConfirmationRequester

func (e *ToolExecutor) SetConfirmationRequester(fn func(sessionID, callerJID, toolName string, args map[string]any) (bool, error))

SetConfirmationRequester sets the callback for tools requiring user approval. When a tool is in RequireConfirmation list, this callback is invoked.

func (*ToolExecutor) SetGuard

func (e *ToolExecutor) SetGuard(guard *ToolGuard)

SetGuard configures the security guard for tool execution.

func (*ToolExecutor) SetSessionContext

func (e *ToolExecutor) SetSessionContext(sessionID string)

SetSessionContext sets the session ID for approval matching (channel:chatID). Must be set before Execute() when using approval flow.

func (*ToolExecutor) ToolNames

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

ToolNames returns the names of all registered tools.

func (*ToolExecutor) Tools

func (e *ToolExecutor) Tools() []ToolDefinition

Tools returns all registered tool definitions for the LLM. Uses a cached slice that is rebuilt only when tools are added/removed.

func (*ToolExecutor) UpdateGuardConfig

func (e *ToolExecutor) UpdateGuardConfig(cfg ToolGuardConfig)

UpdateGuardConfig updates the tool guard config (for hot-reload).

type ToolExecutorConfig

type ToolExecutorConfig struct {
	// Parallel enables parallel execution of independent tools (default: true).
	Parallel bool `yaml:"parallel"`

	// MaxParallel is the max concurrent tool executions when parallel is enabled (default: 5).
	MaxParallel int `yaml:"max_parallel"`

	// BashTimeoutSeconds is the executor-level timeout for bash/ssh/scp/exec tools (default: 300).
	BashTimeoutSeconds int `yaml:"bash_timeout_seconds"`

	// DefaultTimeoutSeconds is the executor-level timeout for all other tools (default: 30).
	DefaultTimeoutSeconds int `yaml:"default_timeout_seconds"`
}

ToolExecutorConfig configures tool execution behavior.

type ToolGuard

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

ToolGuard enforces security policies on tool execution.

func NewToolGuard

func NewToolGuard(cfg ToolGuardConfig, logger *slog.Logger) *ToolGuard

NewToolGuard creates and initializes a tool security guard.

func (*ToolGuard) AuditLog

func (g *ToolGuard) AuditLog(toolName string, callerJID string, callerLevel AccessLevel, args map[string]any, allowed bool, result string)

AuditLog records a tool execution to the audit log.

func (*ToolGuard) Check

func (g *ToolGuard) Check(toolName string, callerLevel AccessLevel, args map[string]any) ToolCheckResult

Check evaluates whether a tool call is permitted for the given access level.

func (*ToolGuard) CheckWithProfile

func (g *ToolGuard) CheckWithProfile(toolName string, callerLevel AccessLevel, args map[string]any, profile *ToolProfile) ToolCheckResult

CheckWithProfile evaluates tool access considering a profile's allow/deny lists. The profile check runs before the standard permission checks. If no profile is provided (nil), delegates directly to Check().

func (*ToolGuard) Close

func (g *ToolGuard) Close()

Close closes the audit log file.

func (*ToolGuard) GetActiveProfile

func (g *ToolGuard) GetActiveProfile() *ToolProfile

GetActiveProfile returns the active profile based on config. Returns nil if no profile is configured or if profile is not found.

func (*ToolGuard) GetAllToolNames

func (g *ToolGuard) GetAllToolNames() []string

GetAllToolNames returns all known tool names from permissions and groups.

func (*ToolGuard) SQLiteAudit

func (g *ToolGuard) SQLiteAudit() *SQLiteAuditLogger

SQLiteAudit returns the SQLite audit logger (may be nil).

func (*ToolGuard) SetSQLiteAudit

func (g *ToolGuard) SetSQLiteAudit(a *SQLiteAuditLogger)

SetSQLiteAudit configures a SQLite-backed audit logger. When set, audit records go to the database instead of the text file.

func (*ToolGuard) UpdateConfig

func (g *ToolGuard) UpdateConfig(cfg ToolGuardConfig)

UpdateConfig updates the tool guard config from hot-reload. Re-compiles dangerous patterns and protected paths. The audit log file is not changed (requires restart to change audit log path).

type ToolGuardConfig

type ToolGuardConfig struct {
	// Enable turns on the tool security guard (default: true).
	Enabled bool `yaml:"enabled"`

	// AuditLog path for recording all tool executions.
	AuditLogPath string `yaml:"audit_log"`

	// Profile selects a predefined tool profile.
	// Options: minimal, coding, messaging, full, or custom profile name.
	// Empty = use ToolPermissions directly (backward compatibility).
	Profile string `yaml:"profile"`

	// CustomProfiles allows defining custom tool profiles.
	CustomProfiles map[string]ToolProfile `yaml:"custom_profiles"`

	// ToolPermissions overrides per-tool permission levels.
	// key = tool name, value = "owner"/"admin"/"user"/"public".
	ToolPermissions map[string]string `yaml:"tool_permissions"`

	// AllowDestructive enables destructive commands (rm -rf /, mkfs, dd, etc)
	// for the owner. When false (default), these are blocked for everyone.
	// When true, owner can run them; non-owners are still blocked.
	AllowDestructive bool `yaml:"allow_destructive"`

	// AllowSudo allows sudo commands. When false (default), sudo is blocked
	// for non-owners. When true, owner and admin can use sudo.
	AllowSudo bool `yaml:"allow_sudo"`

	// AllowReboot allows shutdown/reboot/halt commands. Default: false.
	AllowReboot bool `yaml:"allow_reboot"`

	// DangerousCommands are additional regex patterns for commands that should
	// be blocked. These are added ON TOP of the defaults (not replacing them).
	// To disable all defaults, set allow_destructive: true.
	DangerousCommands []string `yaml:"dangerous_commands"`

	// ProtectedPaths are file paths that cannot be read or written by non-owners.
	// Supports glob patterns. If empty, defaults are used.
	ProtectedPaths []string `yaml:"protected_paths"`

	// SSHAllowedHosts restricts which hosts can be connected via SSH.
	// Empty list = any host allowed (no restriction). Use "*" explicitly to allow all.
	SSHAllowedHosts []string `yaml:"ssh_allowed_hosts"`

	// BlockSudo blocks sudo commands for non-owners (default: true).
	// Deprecated: use AllowSudo instead. Kept for backward compatibility.
	BlockSudo bool `yaml:"block_sudo"`

	// AutoApprove lists tools that can execute without any permission check,
	// even for regular users. Use with caution. Example: ["web_search", "memory_search"]
	AutoApprove []string `yaml:"auto_approve"`

	// RequireConfirmation lists tools that require the user to confirm via
	// the chat before executing. The agent will ask "Confirm: <action>?" and
	// wait for approval. Example: ["bash", "ssh", "scp", "write_file"]
	RequireConfirmation []string `yaml:"require_confirmation"`
}

ToolGuardConfig configures the security guard for tools.

func DefaultToolGuardConfig

func DefaultToolGuardConfig() ToolGuardConfig

DefaultToolGuardConfig returns safe defaults for the tool security guard.

type ToolHandlerFunc

type ToolHandlerFunc func(ctx context.Context, args map[string]any) (any, error)

ToolHandlerFunc is the signature for tool execution handlers. Receives parsed arguments and returns the result or an error.

type ToolHook

type ToolHook struct {
	// Name identifies this hook for logging and debugging.
	Name string

	// BeforeToolCall is called before the tool handler executes.
	// Return modified args (or original), or an error to block execution.
	// If blocked is true, the tool is not executed and blockReason is returned.
	BeforeToolCall func(toolName string, args map[string]any) (modifiedArgs map[string]any, blocked bool, blockReason string)

	// AfterToolCall is called after the tool handler executes (success or error).
	AfterToolCall func(toolName string, args map[string]any, result string, err error)
}

ToolHook is a callback that runs before or after tool execution. Before hooks can modify args or block execution by returning an error. After hooks can observe/log the result but cannot modify it.

type ToolLoopConfig

type ToolLoopConfig struct {
	// Enabled turns loop detection on (default: true).
	Enabled bool `yaml:"enabled"`

	// HistorySize is how many recent tool calls to track (default: 30).
	HistorySize int `yaml:"history_size"`

	// WarningThreshold triggers a warning injected into the conversation (default: 8).
	WarningThreshold int `yaml:"warning_threshold"`

	// CriticalThreshold triggers a strong nudge to stop (default: 15).
	CriticalThreshold int `yaml:"critical_threshold"`

	// CircuitBreakerThreshold force-stops the agent run (default: 25).
	CircuitBreakerThreshold int `yaml:"circuit_breaker_threshold"`

	// GlobalCircuitBreaker is the max total no-progress calls before hard stop (default: 30).
	GlobalCircuitBreaker int `yaml:"global_circuit_breaker"`
}

ToolLoopConfig configures tool loop detection thresholds.

func DefaultToolLoopConfig

func DefaultToolLoopConfig() ToolLoopConfig

DefaultToolLoopConfig returns sensible defaults.

type ToolLoopDetector

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

ToolLoopDetector tracks tool call history and detects loops.

func NewToolLoopDetector

func NewToolLoopDetector(cfg ToolLoopConfig, logger *slog.Logger) *ToolLoopDetector

NewToolLoopDetector creates a new detector with the given config.

func (*ToolLoopDetector) RecordAndCheck

func (d *ToolLoopDetector) RecordAndCheck(toolName string, args map[string]any) LoopDetectionResult

RecordAndCheck records a tool call and checks for loops. Returns a result indicating the severity (if any).

func (*ToolLoopDetector) RecordToolOutcome

func (d *ToolLoopDetector) RecordToolOutcome(output string)

RecordToolOutcome records the result of a tool call for progress tracking. Call this after tool execution with the output to determine if the agent is making progress. An empty or identical output signals no progress.

func (*ToolLoopDetector) Reset

func (d *ToolLoopDetector) Reset()

Reset clears the history (e.g. for a new run).

type ToolPermission

type ToolPermission string

ToolPermission defines which access level is required for a tool.

const (
	PermOwner  ToolPermission = "owner"  // Only owner can use.
	PermAdmin  ToolPermission = "admin"  // Admin and owner.
	PermUser   ToolPermission = "user"   // Any authorized user.
	PermPublic ToolPermission = "public" // No restriction (used for read-only tools).
)

type ToolProfile

type ToolProfile struct {
	// Name is the profile identifier (e.g., "minimal", "coding", "full").
	Name string `yaml:"name"`

	// Description explains what this profile is for.
	Description string `yaml:"description"`

	// Allow lists tools and groups that are permitted.
	// Supports: tool names, "group:name", wildcards like "git_*"
	// Empty means no allow list (use permission levels).
	Allow []string `yaml:"allow"`

	// Deny lists tools and groups that are always blocked.
	// Takes precedence over Allow.
	Deny []string `yaml:"deny"`
}

ToolProfile defines a preset of allowed and denied tools.

func GetProfile

func GetProfile(name string, customProfiles map[string]ToolProfile) *ToolProfile

GetProfile returns a profile by name (built-in or custom).

func ToolProfileFromContext

func ToolProfileFromContext(ctx context.Context) *ToolProfile

ToolProfileFromContext extracts the tool profile from context. Returns nil if no profile is set.

type ToolResult

type ToolResult struct {
	ToolCallID string
	Name       string
	Content    string
	Error      error
}

ToolResult holds the output of a single tool execution.

type TrustConfig

type TrustConfig struct {
	// Owner is the maximum risk level the owner can execute without approval.
	Owner RiskLevel `yaml:"owner"`

	// Admin is the maximum risk level admins can execute without approval.
	Admin RiskLevel `yaml:"admin"`

	// User is the maximum risk level regular users can execute without approval.
	User RiskLevel `yaml:"user"`
}

TrustConfig configures trust levels per user role.

type UsageTracker

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

UsageTracker records usage per session and globally.

func NewUsageTracker

func NewUsageTracker(logger *slog.Logger) *UsageTracker

NewUsageTracker creates a new UsageTracker.

func (*UsageTracker) FormatGlobalUsage

func (u *UsageTracker) FormatGlobalUsage() string

FormatGlobalUsage returns a human-readable global usage report.

func (*UsageTracker) FormatUsage

func (u *UsageTracker) FormatUsage(sessionID string) string

FormatUsage returns a human-readable usage report for a session.

func (*UsageTracker) GetGlobal

func (u *UsageTracker) GetGlobal() *SessionUsage

GetGlobal returns a copy of global usage.

func (*UsageTracker) GetSession

func (u *UsageTracker) GetSession(sessionID string) *SessionUsage

GetSession returns a copy of the session's usage stats, or nil if not found.

func (*UsageTracker) Record

func (u *UsageTracker) Record(sessionID, model string, usage LLMUsage)

Record adds usage for a session and globally.

func (*UsageTracker) ResetSession

func (u *UsageTracker) ResetSession(sessionID string)

ResetSession clears usage for a session.

type UserManager

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

UserManager handles multi-user operations.

func NewUserManager

func NewUserManager(config TeamConfig) *UserManager

NewUserManager creates a new user manager.

func (*UserManager) AddUser

func (um *UserManager) AddUser(user *TeamUser) error

AddUser adds a new user.

func (*UserManager) CheckPermission

func (um *UserManager) CheckPermission(userID string, required UserRole) bool

CheckPermission verifies a user has the required role.

func (*UserManager) GetSharedMemory

func (um *UserManager) GetSharedMemory(key string) (*SharedMemory, bool)

GetSharedMemory retrieves a value from shared team memory.

func (*UserManager) GetUser

func (um *UserManager) GetUser(id string) (*TeamUser, bool)

GetUser returns a user by ID.

func (*UserManager) ListSharedMemory

func (um *UserManager) ListSharedMemory() []*SharedMemory

ListSharedMemory returns all shared memory entries.

func (*UserManager) ListUsers

func (um *UserManager) ListUsers() []*TeamUser

ListUsers returns all users.

func (*UserManager) RemoveUser

func (um *UserManager) RemoveUser(id string) error

RemoveUser removes a user.

func (*UserManager) SetSharedMemory

func (um *UserManager) SetSharedMemory(key, value, authorID string, tags []string)

SetSharedMemory stores a value in shared team memory.

type UserRole

type UserRole string

UserRole defines permission levels for multi-user access.

const (
	RoleOwner  UserRole = "owner"
	RoleAdmin  UserRole = "admin"
	RoleUser   UserRole = "user"
	RoleViewer UserRole = "viewer"
)

type UserUsage

type UserUsage struct {
	SessionID string  `json:"session_id"`
	Tokens    int64   `json:"tokens"`
	Requests  int64   `json:"requests"`
	CostUSD   float64 `json:"cost_usd"`
}

UserUsage represents usage for a single user/session.

type Vault

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

Vault provides encrypted secret storage backed by a local file.

func NewVault

func NewVault(path string) *Vault

NewVault creates a vault instance pointing to the given file path. The vault is not yet unlocked — call Unlock() or Create() first.

func ResolveAPIKey

func ResolveAPIKey(cfg *Config, logger *slog.Logger) *Vault

ResolveAPIKey resolves the API key using the priority chain: vault → keyring → env var → config value. Also updates the config in-place with the resolved value. If a vault exists but is locked, it prompts for the master password (or uses DEVCLAW_VAULT_PASSWORD env var for non-interactive mode). Returns the unlocked vault (or nil if unavailable) so it can be reused by the assistant for agent vault tools.

func (*Vault) ChangePassword

func (v *Vault) ChangePassword(newPassword string) error

ChangePassword re-encrypts all entries with a new master password. The vault must be unlocked.

func (*Vault) Create

func (v *Vault) Create(password string) error

Create initializes a new vault with the given master password. If the vault file already exists, it returns an error.

func (*Vault) Delete

func (v *Vault) Delete(name string) error

Delete removes a secret from the vault. The vault must be unlocked.

func (*Vault) Exists

func (v *Vault) Exists() bool

Exists returns true if the vault file exists on disk.

func (*Vault) Get

func (v *Vault) Get(name string) (string, error)

Get retrieves and decrypts a secret from the vault. Returns empty string if the key doesn't exist. The vault must be unlocked.

func (*Vault) InjectProviderKeys added in v1.8.1

func (v *Vault) InjectProviderKeys() error

InjectProviderKeys injects all secrets from the vault into environment variables. This allows LLM clients to find their API keys via standard variable names (OPENAI_API_KEY, ANTHROPIC_API_KEY, etc.) without the config needing to reference them explicitly.

The vault must be unlocked before calling this method.

func (*Vault) IsUnlocked

func (v *Vault) IsUnlocked() bool

IsUnlocked returns true if the vault has been unlocked with a password.

func (*Vault) Keys

func (v *Vault) Keys() ([]string, error)

Keys returns the names of all stored secrets (excluding internal entries).

func (*Vault) List

func (v *Vault) List() []string

List returns the names of all stored secrets (excluding internal entries). Returns an empty slice if the vault is locked or empty.

func (*Vault) Lock

func (v *Vault) Lock()

Lock clears the derived key from memory, locking the vault.

func (*Vault) Path

func (v *Vault) Path() string

Path returns the vault file path.

func (*Vault) Set

func (v *Vault) Set(name, value string) error

Set stores a secret in the vault (encrypted). The vault must be unlocked.

func (*Vault) Unlock

func (v *Vault) Unlock(password string) error

Unlock decrypts and loads the vault using the provided master password. Returns an error if the password is wrong (decryption will fail on the verification entry or the first real entry).

type VaultData

type VaultData struct {
	Version int                   `json:"version"`
	Salt    string                `json:"salt"` // base64-encoded Argon2 salt
	Entries map[string]VaultEntry `json:"entries"`
}

VaultData is the on-disk format of the vault.

type VaultEntry

type VaultEntry struct {
	Nonce      string `json:"nonce"`      // base64-encoded AES-GCM nonce
	Ciphertext string `json:"ciphertext"` // base64-encoded encrypted data
}

VaultEntry holds one encrypted secret.

type WebSearchConfig

type WebSearchConfig struct {
	// Provider is the search engine to use: "duckduckgo" (default) or "brave".
	Provider string `yaml:"provider"`

	// BraveAPIKey is the Brave Search API subscription token.
	// Can also be set via BRAVE_API_KEY env var.
	BraveAPIKey string `yaml:"brave_api_key"`

	// MaxResults is the maximum number of results to return (default: 8).
	MaxResults int `yaml:"max_results"`
}

WebSearchConfig configures the web search tool.

type WebhookConfig

type WebhookConfig struct {
	// Name identifies this webhook for logging.
	Name string `yaml:"name"`

	// URL is the webhook endpoint URL.
	URL string `yaml:"url"`

	// Events lists which events to send to this webhook.
	Events []string `yaml:"events"`

	// Secret is used to sign payloads (HMAC-SHA256).
	Secret string `yaml:"secret"`

	// Headers are additional HTTP headers to send.
	Headers map[string]string `yaml:"headers"`

	// Timeout is the request timeout in seconds (default: 10).
	Timeout int `yaml:"timeout"`

	// Enabled controls whether this webhook is active.
	Enabled bool `yaml:"enabled"`

	// RetryCount is the number of retry attempts on failure (default: 3).
	RetryCount int `yaml:"retry_count"`

	// RetryDelayMs is the delay between retries in milliseconds (default: 1000).
	RetryDelayMs int `yaml:"retry_delay_ms"`
}

WebhookConfig configures an external webhook endpoint.

type WebhookDef

type WebhookDef struct {
	Path    string            `json:"path" yaml:"path"`
	Secret  string            `json:"secret" yaml:"secret"`
	Events  []string          `json:"events" yaml:"events"`
	Headers map[string]string `json:"headers" yaml:"headers"`
}

WebhookDef describes an incoming webhook configuration.

type WebhookDispatcher

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

WebhookDispatcher handles incoming webhook events from external services.

func NewWebhookDispatcher

func NewWebhookDispatcher() *WebhookDispatcher

NewWebhookDispatcher creates a new webhook dispatcher.

func (*WebhookDispatcher) Dispatch

func (wd *WebhookDispatcher) Dispatch(ctx context.Context, event string, payload []byte) error

Dispatch sends a webhook event to all registered handlers.

func (*WebhookDispatcher) On

func (wd *WebhookDispatcher) On(event string, handler WebhookHandler)

On registers a handler for a webhook event type.

type WebhookHandler

type WebhookHandler func(ctx context.Context, event string, payload []byte) error

WebhookHandler processes a webhook event.

type WebhookManager

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

WebhookManager manages webhook delivery.

func NewWebhookManager

func NewWebhookManager(cfg WebhooksConfig, hookMgr *HookManager, logger *slog.Logger) *WebhookManager

NewWebhookManager creates a new webhook manager.

func (*WebhookManager) ForwardEvent

func (wm *WebhookManager) ForwardEvent(payload HookPayload)

ForwardEvent sends an event to all matching webhooks.

func (*WebhookManager) ListWebhooks

func (wm *WebhookManager) ListWebhooks() []WebhookConfig

ListWebhooks returns all configured webhooks.

func (*WebhookManager) Reload

func (wm *WebhookManager) Reload(cfg WebhooksConfig)

Reload updates the webhook configuration.

func (*WebhookManager) SetWebhookEnabled

func (wm *WebhookManager) SetWebhookEnabled(name string, enabled bool) bool

SetWebhookEnabled enables or disables a webhook by name.

type WebhookPayload

type WebhookPayload struct {
	// Event is the hook event name.
	Event string `json:"event"`

	// Timestamp is when the event occurred (ISO 8601).
	Timestamp string `json:"timestamp"`

	// SessionID is the session this event relates to (if applicable).
	SessionID string `json:"session_id,omitempty"`

	// Channel is the originating channel (if applicable).
	Channel string `json:"channel,omitempty"`

	// ToolName is the tool being called (for tool events).
	ToolName string `json:"tool_name,omitempty"`

	// Message is a human-readable description.
	Message string `json:"message,omitempty"`

	// Error is the error message (for error events).
	Error string `json:"error,omitempty"`

	// Extra holds arbitrary key-value data.
	Extra map[string]any `json:"extra,omitempty"`
}

WebhookPayload is the JSON payload sent to webhook endpoints.

type WebhooksConfig

type WebhooksConfig struct {
	// Enabled turns the webhook system on/off.
	Enabled bool `yaml:"enabled"`

	// Webhooks is the list of configured webhooks.
	Webhooks []WebhookConfig `yaml:"webhooks"`
}

WebhooksConfig holds all webhook configurations.

type Workspace

type Workspace struct {
	// ID is the unique workspace identifier (slug, e.g. "personal", "work").
	ID string `yaml:"id"`

	// Name is the human-readable workspace name.
	Name string `yaml:"name"`

	// Description explains the workspace purpose.
	Description string `yaml:"description"`

	// Instructions are the custom system prompt for this workspace.
	// Overrides the global instructions when set.
	Instructions string `yaml:"instructions"`

	// Model overrides the default LLM model.
	// Empty = use global default.
	Model string `yaml:"model"`

	// Language overrides the default language.
	// Empty = use global default.
	Language string `yaml:"language"`

	// Timezone overrides the default timezone.
	// Empty = use global default.
	Timezone string `yaml:"timezone"`

	// Trigger overrides the activation keyword for this workspace.
	// Empty = use global default.
	Trigger string `yaml:"trigger"`

	// Skills lists the skills available in this workspace.
	// Empty = use all globally enabled skills.
	Skills []string `yaml:"skills"`

	// TokenBudget overrides token limits for this workspace.
	// Nil = use global defaults.
	TokenBudget *TokenBudgetConfig `yaml:"token_budget,omitempty"`

	// MaxMessages overrides the session history limit.
	// 0 = use global default.
	MaxMessages int `yaml:"max_messages"`

	// ToolProfile specifies which tool profile to use for this workspace.
	// Options: minimal, coding, messaging, full, or custom profile name.
	// Empty = use global profile from tool_guard config.
	ToolProfile string `yaml:"tool_profile"`

	// Members lists the user JIDs assigned to this workspace.
	Members []string `yaml:"members"`

	// Groups lists the group JIDs assigned to this workspace.
	Groups []string `yaml:"groups"`

	// CreatedBy is the JID of whoever created this workspace.
	CreatedBy string `yaml:"created_by"`

	// CreatedAt is when the workspace was created.
	CreatedAt time.Time `yaml:"created_at"`

	// Active indicates if the workspace is enabled.
	Active bool `yaml:"active"`
}

Workspace represents an isolated assistant profile. Each workspace has its own instructions, skills, model, and memory.

type WorkspaceConfig

type WorkspaceConfig struct {
	// DefaultWorkspace is the ID of the workspace used for unassigned contacts.
	DefaultWorkspace string `yaml:"default_workspace"`

	// Workspaces is the list of defined workspaces.
	Workspaces []Workspace `yaml:"workspaces"`
}

WorkspaceConfig holds the workspaces configuration.

func DefaultWorkspaceConfig

func DefaultWorkspaceConfig() WorkspaceConfig

DefaultWorkspaceConfig returns a minimal workspace configuration.

type WorkspaceContainment

type WorkspaceContainment struct {
	// Root is the absolute path of the workspace root.
	Root string

	// Enabled toggles containment enforcement (default: true).
	Enabled bool
}

WorkspaceContainment enforces that file operations stay within the workspace root.

func NewWorkspaceContainment

func NewWorkspaceContainment(root string) *WorkspaceContainment

NewWorkspaceContainment creates a containment checker for the given root directory.

func (*WorkspaceContainment) AssertNoSymlinkEscape

func (wc *WorkspaceContainment) AssertNoSymlinkEscape(path string) error

AssertNoSymlinkEscape checks that a path does not traverse through a symlink that points outside the workspace. Unlike AssertSandboxPath, this allows symlinks WITHIN the workspace but blocks those that escape.

func (*WorkspaceContainment) AssertSandboxPath

func (wc *WorkspaceContainment) AssertSandboxPath(path string) (string, error)

AssertSandboxPath validates that the given path resolves to within the workspace root. It resolves symlinks to prevent escape attacks and checks for path traversal. Returns the resolved absolute path or an error if containment is violated.

type WorkspaceManager

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

WorkspaceManager manages workspaces and their member assignments. It provides workspace resolution for incoming messages and maintains isolated session stores per workspace.

func NewWorkspaceManager

func NewWorkspaceManager(globalCfg *Config, wsCfg WorkspaceConfig, logger *slog.Logger) *WorkspaceManager

NewWorkspaceManager creates a new workspace manager.

func (*WorkspaceManager) AssignGroup

func (wm *WorkspaceManager) AssignGroup(groupJID, wsID, assignedBy string) error

AssignGroup assigns a group to a workspace.

func (*WorkspaceManager) AssignUser

func (wm *WorkspaceManager) AssignUser(jid, wsID, assignedBy string) error

AssignUser assigns a user to a workspace.

func (*WorkspaceManager) Count

func (wm *WorkspaceManager) Count() int

Count returns the number of workspaces.

func (*WorkspaceManager) Create

func (wm *WorkspaceManager) Create(ws Workspace, createdBy string) error

Create creates a new workspace.

func (*WorkspaceManager) Delete

func (wm *WorkspaceManager) Delete(wsID, deletedBy string) error

Delete removes a workspace (members go back to default).

func (*WorkspaceManager) DeleteSessionByID

func (wm *WorkspaceManager) DeleteSessionByID(sessionID string) bool

DeleteSessionByID removes a session by ID across all workspaces.

func (*WorkspaceManager) ExportSession

func (wm *WorkspaceManager) ExportSession(id string) *SessionExport

ExportSession exports a session's history and metadata by hash ID.

func (*WorkspaceManager) FindSessionByID

func (wm *WorkspaceManager) FindSessionByID(id string) *Session

FindSessionByID searches all workspace session stores for a session by its hash ID.

func (*WorkspaceManager) Get

func (wm *WorkspaceManager) Get(wsID string) (*Workspace, bool)

Get returns a workspace by ID.

func (*WorkspaceManager) GetForUser

func (wm *WorkspaceManager) GetForUser(jid string) (*Workspace, bool)

GetForUser returns the workspace assigned to a user JID.

func (*WorkspaceManager) GetSessionByID

func (wm *WorkspaceManager) GetSessionByID(sessionID string) (*Session, *Workspace)

GetSessionByID finds a session by ID (format "channel:chatID") across all workspaces.

func (*WorkspaceManager) List

func (wm *WorkspaceManager) List() []*Workspace

List returns all workspaces.

func (*WorkspaceManager) ListAllSessions

func (wm *WorkspaceManager) ListAllSessions() []SessionInfo

ListAllSessions returns session metadata from all workspaces.

func (*WorkspaceManager) Resolve

func (wm *WorkspaceManager) Resolve(channel, chatID, senderJID string, isGroup bool) *ResolvedWorkspace

Resolve determines which workspace a message belongs to and returns the workspace along with its isolated session.

func (*WorkspaceManager) SessionCount

func (wm *WorkspaceManager) SessionCount() int

SessionCount returns the total number of sessions across all workspaces.

func (*WorkspaceManager) SetPersistence

func (wm *WorkspaceManager) SetPersistence(p SessionPersister)

SetPersistence propagates a SessionPersister to all workspace session stores and stores it for newly created workspaces.

func (*WorkspaceManager) StartPruners

func (wm *WorkspaceManager) StartPruners(ctx context.Context)

StartPruners starts session pruning for all workspace session stores.

func (*WorkspaceManager) UnassignUser

func (wm *WorkspaceManager) UnassignUser(jid string)

UnassignUser removes a user from their workspace (goes to default).

func (*WorkspaceManager) Update

func (wm *WorkspaceManager) Update(wsID string, fn func(ws *Workspace)) error

Update modifies a workspace's settings.

Directories

Path Synopsis
Package memory – embeddings.go implements embedding generation for semantic search.
Package memory – embeddings.go implements embedding generation for semantic search.
Package security implementa os guardrails de segurança do DevClaw.
Package security implementa os guardrails de segurança do DevClaw.

Jump to

Keyboard shortcuts

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