Documentation
¶
Overview ¶
* ChatCLI - Command Line Interface for LLM interaction * Copyright (c) 2024 Edilson Freitas * License: Apache-2.0
* ChatCLI - Command Line Interface for LLM interaction * Copyright (c) 2024 Edilson Freitas * License: Apache-2.0
* ChatCLI - Command Line Interface for LLM interaction * Copyright (c) 2024 Edilson Freitas * License: Apache-2.0
* ChatCLI - Command Line Interface for LLM interaction * Copyright (c) 2024 Edilson Freitas * License: Apache-2.0
* ChatCLI - Context Overflow Recovery * Copyright (c) 2024 Edilson Freitas * License: Apache-2.0 * * Provides recovery strategies when API calls fail due to context window overflow * (prompt too long) or max output token limits being hit. * * Recovery strategy for context overflow: * 1. Aggressive tool result budget enforcement (halved limits) * 2. Tool result pairing cleanup * 3. Emergency history truncation (keep system + last N messages) * * Recovery strategy for max output tokens: * 1. Escalate max_tokens (double, up to provider cap) * 2. Inject continuation message * 3. Track escalation count to prevent infinite loops
* ChatCLI - File Staleness Tracker * Copyright (c) 2024 Edilson Freitas * License: Apache-2.0 * * Tracks file state (mtime + content hash) between read and write operations * to detect when a file has been modified externally. This prevents the LLM * from silently overwriting changes made by the user or other processes. * * Inspired by openclaude's file staleness detection with mtime + hash fallback. * * Usage: * tracker.RecordRead("/path/to/file") // after successful read * stale, diff := tracker.CheckStaleness(path) // before write/patch
* ChatCLI - Microcompact for Tool Results * Copyright (c) 2024 Edilson Freitas * License: Apache-2.0 * * Progressively compacts old tool results in the conversation history to * reduce context usage without losing critical information. * * Inspired by openclaude's microCompact.ts which selectively compacts * specific tools (FILE_READ, BASH, GREP, etc.) based on age. * * Strategy: * - Tool results from recent turns: unchanged * - Tool results 2+ turns old: truncated to head+tail preview * - Tool results 4+ turns old: replaced with one-line summary * * Only applies to read-only tool results (file reads, search, git status, etc.). * Write/exec results are preserved as they contain critical error information.
* ChatCLI - Quote Normalization * Copyright (c) 2024 Edilson Freitas * License: Apache-2.0 * * Normalizes curly/smart quotes to straight quotes before write/patch operations. * LLMs frequently generate curly quotes (', ', ", ") which cause compilation * errors in source code. * * Inspired by openclaude's quote normalization in FileEditTool which preserves * file typography style. Here we take a simpler approach: always normalize to * straight quotes for code files, preserving curly quotes only in documentation.
* ChatCLI - Command Line Interface for LLM interaction * Copyright (c) 2024 Edilson Freitas * License: Apache-2.0
* ChatCLI - Unified response envelope rendering * Copyright (c) 2024 Edilson Freitas * License: Apache-2.0 * * Renders the assistant's final reply in the "sóbrio" treatment: a * bilateral titled rule opens the reply (model on the left, latency and * tokens on the right), the body sits on a two-space indent wrapped to the * live terminal width with ANSI preserved, and a single dim telemetry line * closes it. The envelope stays the single source of truth for chat, coder * and agent modes: callers supply pre-formatted labels and a body; an * optional typewriter effect plays the body progressively.
* ChatCLI - Session Workspace * Copyright (c) 2024 Edilson Freitas * License: Apache-2.0 * * Per-session scratch directory that the agent can read/write freely without * touching the project tree. Created once per CLI startup, cleaned up on exit. * * Layout: * $TMPDIR/chatcli-agent-<random>/ * ├── scratch/ -> agent-writable: temp scripts, intermediate files * └── tool-results/ -> overflow from EnforceToolResultBudget (replaces * the old global /tmp/chatcli-tool-results/) * * The session workspace registers both subdirs with: * - pkg/coder/engine (write allowlist) * - cli/agent.SensitiveReadPaths (read allowlist) * - tool_result_budget (writes overflow here instead of the global dir) * * Env vars: * CHATCLI_AGENT_TMPDIR -> absolute path of scratch dir; exported for * child processes spawned by run_command / exec. * CHATCLI_AGENT_KEEP_TMPDIR=true -> skip cleanup (debugging).
* ChatCLI - Tool Result Budget Enforcement * Copyright (c) 2024 Edilson Freitas * License: Apache-2.0 * * Enforces aggregate size limits on tool results before sending to the API. * Large results are persisted to disk and replaced with compact references, * preventing context window saturation. * * Inspired by openclaude's tool result budget enforcement and * MAX_TOOL_RESULTS_PER_MESSAGE_CHARS threshold.
* ChatCLI - Tool Result Pairing Validator * Copyright (c) 2024 Edilson Freitas * License: Apache-2.0 * * Ensures every tool_use block in the conversation history has a matching * tool_result, and every tool_result references a valid tool_use. * * Pairing is POSITIONAL (block-scoped), not global: each assistant message * that carries tool_calls owns the stretch of history up to the next * assistant message, and its results are matched only inside that stretch. * This is required for providers whose tool_call IDs are NOT globally * unique — Moonshot/Kimi K3 emits deterministic per-turn IDs such as * "web_fetch:0"/"web_fetch:1" that repeat on every turn, so a global * id→result map silently pairs an old dangling call with a newer turn's * result and ships an invalid history (API 400: "tool_call_ids did not * have response messages").
* ChatCLI - Command Line Interface for LLM interaction * Copyright (c) 2024 Edilson Freitas * License: Apache-2.0
* ChatCLI - Adaptive typewriter pacing * Copyright (c) 2024 Edilson Freitas * License: Apache-2.0 * * The reply card uses a typewriter effect to make the model's answer * feel alive. A naive fixed per-rune delay (2ms) works fine for a * handful of sentences but compounds badly on long replies — a 3 000 * rune reply at 2ms is 6 seconds of cursor-sweeping-across-borders, * which feels broken instead of alive. * * PaceText adapts the cadence: * * - Short replies keep the caller-requested delay (the effect * reads as animation). * - Long replies have their delay scaled DOWN so the whole * animation completes within a target budget (default 800ms). * - Replies above a hard threshold (8 000 visible runes) skip the * animation entirely — painting a giant code block one rune at a * time is never the right call. * * Three environment variables let advanced users tune the behavior * without rebuilding: * * CHATCLI_NO_TYPEWRITER=1 skip animation entirely * CHATCLI_TYPEWRITER_BUDGET_MS=N override the total budget in ms * (0 disables the budget; caller * delay is used verbatim) * CHATCLI_TYPEWRITER_DELAY_MS=N override the per-rune base delay * * The pacing is centralized here so every surface that types out * model output (chat envelope, agent RESPOSTA card, coder summary, * /command relay) converges on the same UX.
* ChatCLI - Command Line Interface for LLM interaction * Copyright (c) 2024 Edilson Freitas * License: Apache-2.0
Index ¶
- Constants
- Variables
- func AuxAllowedPaths() []string
- func BuildSuggestions(command string) []string
- func ClassifyErrorCode(err error) string
- func CleanupBudgetFiles()
- func CompactToolLabel(subcmd string, rawArgs string) string
- func ContinuationMessage() models.Message
- func CountPendingToolCalls(history []models.Message) int
- func DefaultAllowedCommands() map[string]string
- func DetectCurlyQuotes(content string) (count int, positions []int)
- func EnvelopeWidth() int
- func GenerateToolCallID(turn, callIndex int) string
- func HasSuspiciousUnicode(content string) bool
- func IntegrateTaskTracking(tracker *TaskTracker, reasoningText string, logger *zap.Logger)
- func IsContextTooLongError(err error) bool
- func IsLikelyPayloadProblem(err error, historyChars int) bool
- func IsPayloadTooLargeError(err error) bool
- func IsProxyWAFRejection(err error) bool
- func LocalizedStatusPhrases() []string
- func MarkTaskCompleted(tracker *TaskTracker)
- func MarkTaskFailed(tracker *TaskTracker, errorMsg string)
- func MarkTaskInProgress(tracker *TaskTracker)
- func MaxToolConcurrency() int
- func NormalizeQuotes(content, filePath string) string
- func NormalizeQuotesAlways(content string) string
- func NormalizeToolArgs(toolName, raw string) (string, bool)
- func PaceText(text string, requested time.Duration)
- func ParallelToolsEnabled() bool
- func RegisterAuxReadPath(path string)
- func RegisterResultDirSetter(fn ResultDirSetter)
- func SetBudgetResultDir(dir string)
- func SetProcessGroup(cmd *exec.Cmd)
- func TelemetrySafe(err error) string
- func TerminalWidth() int
- func UnregisterAuxReadPath(path string)
- func ValidateToolResultPairing(history []models.Message) bool
- func VisibleLen(s string) int
- func WrapPlainStringForTool(toolName, value string) string
- type BatchOptions
- type BudgetReport
- type CommandAllowlist
- type CommandBlock
- type CommandContextInfo
- type CommandExecutor
- type CommandOutput
- type CommandValidator
- type ContextManager
- func (cm *ContextManager) CreateExecutionContext() (context.Context, context.CancelFunc)
- func (cm *ContextManager) GetDefaultTimeout() time.Duration
- func (cm *ContextManager) RequestLLMContinuation(ctx context.Context, llmClient interface{}, history []models.Message, ...) (string, error)
- func (cm *ContextManager) RequestLLMWithPreExecutionContext(ctx context.Context, llmClient interface{}, history []models.Message, ...) (string, error)
- func (cm *ContextManager) SetDefaultTimeout(timeout time.Duration)
- type ContextRecovery
- func (cr *ContextRecovery) CanEscalateMaxTokens() bool
- func (cr *ContextRecovery) CanRecoverContextOverflow() bool
- func (cr *ContextRecovery) MaxTokensEscalation(currentMaxTokens, providerCap int) (int, bool)
- func (cr *ContextRecovery) RecoverContextOverflow(history []models.Message) ([]models.Message, bool)
- type ContextRecoveryConfig
- type EnhancedExecutionResult
- type ExecuteFunc
- type ExecutionResult
- type FileStalenessTracker
- type FileState
- type InlineCodeRisk
- type InlineCodeRiskAnalyzer
- type MicrocompactConfig
- type MicrocompactReport
- type PairingRepairReport
- type PartitionPolicy
- type PartitionPolicyFunc
- type PathValidator
- type PatternRule
- type ProcessManager
- type ResponseEnvelopeOptions
- type ResultDirSetter
- type SafetyConfig
- type SecurityMode
- type SensitiveReadPaths
- type SessionWorkspace
- type ShellSegment
- type SourceType
- type StalenessResult
- type Task
- type TaskPlan
- type TaskSpec
- type TaskStatus
- type TaskTracker
- func (t *TaskTracker) FormatProgress() string
- func (t *TaskTracker) GetCurrentTask() *Task
- func (t *TaskTracker) GetPlan() *TaskPlan
- func (t *TaskTracker) MarkByID(id int, status TaskStatus, errorMsg string) bool
- func (t *TaskTracker) MarkCurrentAs(status TaskStatus, errorMsg string)
- func (t *TaskTracker) NeedsReplanning() bool
- func (t *TaskTracker) ParseReasoning(reasoningText string) error
- func (t *TaskTracker) ResetPlan()
- func (t *TaskTracker) ResetPlanFromReasoning(reasoningText string, preserveCompleted bool) error
- func (t *TaskTracker) SetTasks(specs []TaskSpec)
- type ToolBatch
- type ToolCall
- type ToolContext
- func (c *ToolContext) ApplyMutations(results []ToolResult)
- func (c *ToolContext) GetExtra(key string) (any, bool)
- func (c *ToolContext) Merge(other *ToolContext)
- func (c *ToolContext) PutExtra(key string, value any)
- func (c *ToolContext) RecordFileRead(path string)
- func (c *ToolContext) RecordFileWrite(path string)
- func (c *ToolContext) RecordHostContacted(host string)
- type ToolResult
- type UIRenderer
- func (r *UIRenderer) ClearScreen()
- func (r *UIRenderer) Colorize(text string, color string) string
- func (r *UIRenderer) CompactAssistantText(text string)
- func (r *UIRenderer) CompactBatchSummary(successCount, total int, hasError bool)
- func (r *UIRenderer) CompactError(msg string)
- func (r *UIRenderer) CompactLine(icon, label, text string, color string)
- func (r *UIRenderer) CompactMultiLine(icon, label, text string, color string, maxLines int)
- func (r *UIRenderer) CompactToolDone(toolLabel string, duration string, isError bool)
- func (r *UIRenderer) CompactToolStart(toolLabel string)
- func (r *UIRenderer) EchoUserInput(text string)
- func (r *UIRenderer) IsCompact() bool
- func (r *UIRenderer) IsFull() bool
- func (r *UIRenderer) IsMinimal() bool
- func (r *UIRenderer) PrintHeader()
- func (r *UIRenderer) PrintLastResult(outputs []*CommandOutput, lastIdx int)
- func (r *UIRenderer) PrintMenu()
- func (r *UIRenderer) PrintPlanCompact(blocks []CommandBlock, outputs []*CommandOutput)
- func (r *UIRenderer) PrintPlanFull(blocks []CommandBlock, outputs []*CommandOutput, validator *CommandValidator)
- func (r *UIRenderer) PrintPrompt() string
- func (r *UIRenderer) RenderAssistantResponseTimelineEvent(icon, title, content, color string)
- func (r *UIRenderer) RenderBatchHeader(totalActions int)
- func (r *UIRenderer) RenderBatchSummary(successCount, total int, hasError bool)
- func (r *UIRenderer) RenderMarkdownTimelineEvent(icon, title, renderedMarkdownANSI, color string)
- func (r *UIRenderer) RenderModeBanner(icon, title string, color string, fields [][2]string)
- func (r *UIRenderer) RenderResponseEnvelope(opts ResponseEnvelopeOptions)
- func (r *UIRenderer) RenderStreamBoxEnd(color string)
- func (r *UIRenderer) RenderStreamBoxStart(icon, title, color string)
- func (r *UIRenderer) RenderThinking(thought string)
- func (r *UIRenderer) RenderTimelineEvent(icon, title, content, color string)
- func (r *UIRenderer) RenderToolCall(toolName, rawArgs string)
- func (r *UIRenderer) RenderToolCallMinimal(toolName, rawArgs string, current, total int)
- func (r *UIRenderer) RenderToolCallWithProgress(toolName, rawArgs string, current, total int)
- func (r *UIRenderer) RenderToolResult(output string, isError bool)
- func (r *UIRenderer) RenderToolResultMinimal(output string, isError bool)
- func (r *UIRenderer) SetSkipClearOnNextDraw(skip bool)
- func (r *UIRenderer) ShowInPager(text string) error
- func (r *UIRenderer) StreamOutput(line string)
- func (r *UIRenderer) Style() UIStyle
- type UIStyle
- type ValidationResult
Constants ¶
const ( // DefaultMaxOutputBytes is the maximum output size before truncation. DefaultMaxOutputBytes int64 = 1 << 20 // 1MB // DefaultTerminateTimeout is how long to wait for graceful shutdown. DefaultTerminateTimeout = 5 * time.Second )
const ( LeftSingleCurly = '\u2018' // ' RightSingleCurly = '\u2019' // ' LeftDoubleCurly = '\u201C' // " RightDoubleCurly = '\u201D' // " LeftAngleDouble = '\u00AB' // « RightAngleDouble = '\u00BB' // » PrimeSingle = '\u2032' // ′ PrimeDouble = '\u2033' // ″ )
Curly/smart quote Unicode characters
const ( // SyntheticToolResultContent is injected when a tool_use has no matching tool_result. SyntheticToolResultContent = "[Tool result missing — the tool execution was interrupted or failed silently. " + "Do NOT retry this tool call. Analyze what went wrong and try a different approach.]" // SyntheticToolResultErrorCode marks injected synthetic results so // providers surface them as errors (OpenAI-family [ERROR:...] marker, // Anthropic is_error) instead of apparent successes, and so downstream // consumers can distinguish repair synthetics from real outputs. SyntheticToolResultErrorCode = "missing_result" // OrphanToolResultContent is kept for API compatibility. // // Deprecated: orphaned tool results are REMOVED from history, never // replaced with this placeholder. No code references it. OrphanToolResultContent = "[Orphaned tool result — no matching tool call found. This result has been discarded.]" )
const ( ColorReset = "\033[0m" ColorGreen = "\033[32m" ColorLime = "\033[92m" ColorCyan = "\033[36m" ColorGray = "\033[90m" ColorPurple = "\033[35m" ColorBold = "\033[1m" ColorYellow = "\033[33m" ColorRed = "\033[31m" ColorBlue = "\033[34m" )
ANSI Color codes exportados
Legacy path: hue constants (duplicated from cli/colors.go — the duplication is exactly what ui/kit exists to end). They remain because they are exported and load-bearing across cli, but new and migrated code styles through kit.Colorize / kit.Style with a theme.Role.
Variables ¶
var ( // DefaultTurnBudgetChars is the maximum aggregate size of all tool results // in a single conversation turn (assistant→tool_results group). // Tool results exceeding this are persisted to disk and replaced with previews. // Override via CHATCLI_TOOL_RESULT_BUDGET_CHARS. DefaultTurnBudgetChars = 200_000 // DefaultPerResultMaxChars is the maximum size of a single tool result. // Override via CHATCLI_TOOL_RESULT_MAX_CHARS. DefaultPerResultMaxChars = 20_000 // PreviewHeadChars is how much of a large result to keep as inline preview. PreviewHeadChars = 4_000 // PreviewTailChars is how much of the end to keep for context. PreviewTailChars = 1_000 )
Budget configuration — configurable via environment variables.
var StatusPhrases = []string{
"Thinking...",
"Analyzing...",
"Processing...",
"Reasoning...",
"Planning...",
"Exploring...",
"Connecting dots...",
"Crafting solution...",
"Reading code...",
"Mapping structure...",
}
StatusPhrases is the original English fallback list, retained for backward-compat with code outside this package that imported the package-level slice. New code should call LocalizedStatusPhrases() so locale changes are honored at call time.
Deprecated: use LocalizedStatusPhrases().
Functions ¶
func AuxAllowedPaths ¶ added in v1.102.0
func AuxAllowedPaths() []string
AuxAllowedPaths returns paths the engine and read validator should treat as inside the boundary, in addition to the workspace root. Includes the session workspace (if initialized).
func BuildSuggestions ¶ added in v1.66.0
BuildSuggestions returns alternative commands based on the denied command.
func ClassifyErrorCode ¶ added in v1.118.0
ClassifyErrorCode maps a Go error into a stable, locale-independent short code suitable for telemetry, log fields, and the wire-level IsError marker. The returned string is bounded (≤32 chars) and contains only ASCII so it round-trips through any provider format.
Mapping rules:
*os.PathError → ENOENT / EACCES / EISDIR / EEXIST / ENOSPC / ETOOMANY... *fs.PathError → same syscall.Errno → the errno name (ENOENT, ECONNREFUSED, etc.) *exec.ExitError → "ExitCode:<N>" *net.OpError → "NetworkError" (further classified by the wrapped err) context.Canceled → "Canceled" context.DeadlineExceeded → "Timeout" Anything else → "UnknownError"
Callers should not parse this string back; it's an opaque sentinel for telemetry and conditional UI ("retry on Timeout, not on EACCES").
func CleanupBudgetFiles ¶ added in v1.99.0
func CleanupBudgetFiles()
CleanupBudgetFiles removes temporary budget enforcement files.
func CompactToolLabel ¶ added in v1.97.0
CompactToolLabel builds a compact label from a subcmd + args. Examples: "Read(main.go)", "Write(pkg/handler.go)", "Exec(go test ./...)", "Patch(3 edits)"
func ContinuationMessage ¶ added in v1.99.0
ContinuationMessage returns the message to inject when max_tokens is hit.
func CountPendingToolCalls ¶ added in v1.99.0
CountPendingToolCalls returns how many tool calls in the last assistant message don't yet have a tool result. Used to detect incomplete tool execution.
func DefaultAllowedCommands ¶ added in v1.97.0
DefaultAllowedCommands returns the categorized command allowlist.
func DetectCurlyQuotes ¶ added in v1.99.0
DetectCurlyQuotes checks if the content contains any curly/smart quotes. Returns the count and positions for diagnostics.
func EnvelopeWidth ¶ added in v1.119.2
func EnvelopeWidth() int
EnvelopeWidth returns the width to use for a response envelope on the current terminal. Delegates to kit.ContentWidth: right-edge margin so native scrollbars never clip the border, clamped to a minimum so the box never collapses on tiny terminals; no upper cap (full-screen terminals use their full width — a direct user preference).
func GenerateToolCallID ¶ added in v1.99.0
GenerateToolCallID creates a deterministic tool call ID for XML-parsed tool calls that don't have native IDs. Uses the turn number and call index for uniqueness.
func HasSuspiciousUnicode ¶ added in v1.99.0
HasSuspiciousUnicode checks for Unicode characters that look like ASCII but aren't. Returns true if any lookalike characters are found.
func IntegrateTaskTracking ¶ added in v1.47.0
func IntegrateTaskTracking(tracker *TaskTracker, reasoningText string, logger *zap.Logger)
IntegrateTaskTracking processa o reasoning e atualiza o plano de tarefas
func IsContextTooLongError ¶ added in v1.99.0
IsContextTooLongError checks if an error is a context overflow error.
func IsLikelyPayloadProblem ¶ added in v1.104.0
IsLikelyPayloadProblem combines strict payload-size errors with ambiguous network errors that become highly suspect when the history is large. Proxies occasionally close the TCP connection mid-POST when the body exceeds a limit instead of returning a proper 413, which surfaces as EOF / connection reset / broken pipe — indistinguishable from transient network issues unless you know the request was huge.
The historyChars threshold avoids false positives on small requests where EOF is almost certainly a genuine transient failure.
func IsPayloadTooLargeError ¶ added in v1.104.0
IsPayloadTooLargeError detects HTTP 413 responses and proxy-level body size rejections — common in corporate environments where the egress proxy or API gateway caps POST bodies at 1-5 MB, independently of the model's context window. These are recoverable by compacting history more aggressively and retrying, so we distinguish them from generic "context too long" model errors (which typically return 400).
func IsProxyWAFRejection ¶ added in v1.104.0
IsProxyWAFRejection detects 403 Forbidden responses that come from a corporate proxy / WAF / gateway rather than from the LLM provider's own auth layer. LLM auth 403s say things like "permission_denied" or "invalid_api_key"; WAF 403s cite firewall / policy / size / security rules. Distinguishing them matters: auth 403 needs a token refresh, WAF 403 needs a smaller payload. Never flags a plain unqualified 403.
Also catches a second flavor: SDK-level decode failures where the upstream returned HTML (a proxy block page) instead of JSON. The AWS SDK, for example, surfaces this as "StatusCode: 403 ... deserialization failed ... invalid character '<' looking for beginning of value" — a legitimate AWS 403 would return well-formed JSON, so this pattern is an unambiguous middlebox fingerprint.
func LocalizedStatusPhrases ¶ added in v1.119.0
func LocalizedStatusPhrases() []string
LocalizedStatusPhrases returns the rotating "thinking" messages in the currently active locale. Resolved at call time (not at package init) because i18n.Init may not have run yet when var-level initializers fire — calling it eagerly would freeze the slice to the raw keys.
func MarkTaskCompleted ¶ added in v1.47.0
func MarkTaskCompleted(tracker *TaskTracker)
MarkTaskCompleted marca a tarefa atual como concluída
func MarkTaskFailed ¶ added in v1.47.0
func MarkTaskFailed(tracker *TaskTracker, errorMsg string)
MarkTaskFailed marca a tarefa atual como falhada
func MarkTaskInProgress ¶ added in v1.47.0
func MarkTaskInProgress(tracker *TaskTracker)
MarkTaskInProgress marca a tarefa atual como em andamento
func MaxToolConcurrency ¶ added in v1.118.0
func MaxToolConcurrency() int
MaxToolConcurrency returns the active concurrency budget, reading the CHATCLI_AGENT_MAX_TOOL_CONCURRENCY environment variable on every call so a /config security mutation takes effect immediately. Values <=0 fall back to the default; the upper bound is intentionally not enforced — operators who want 64 concurrent fetches can have them.
func NormalizeQuotes ¶ added in v1.99.0
NormalizeQuotes replaces curly/smart quotes with straight ASCII quotes. Only applies to code files — documentation files (.md, .txt, .rst) are unchanged.
func NormalizeQuotesAlways ¶ added in v1.99.0
NormalizeQuotesAlways replaces curly quotes regardless of file type. Use this for tool call arguments where curly quotes are always wrong.
func NormalizeToolArgs ¶ added in v1.99.0
NormalizeToolArgs attempts multiple recovery strategies to parse malformed JSON from LLM tool call arguments. This handles common issues like:
- Single quotes instead of double quotes
- Unquoted keys: {cmd: "read", file: "main.go"}
- Plain string values that should be wrapped: "main.go" → {"file":"main.go"}
- Completely unstructured text: "read --file main.go"
Returns the normalized JSON string and true if recovery succeeded.
func PaceText ¶ added in v1.119.3
PaceText prints text with an adaptive typewriter cadence. Short bodies use rune-by-rune animation at the requested delay (the effect reads as animation); long bodies switch to a chunked mode where multiple runes paint per ~10ms tick so the total animation completes within the configured budget; very long bodies skip the animation entirely.
Why two modes instead of one: a naive "scale down per-rune delay" approach degenerates below the OS scheduler's granularity (~1-2ms on Linux/macOS). With 2 000 runes and an 800ms budget the per-rune math says 400μs/rune but the actual wall-clock can balloon to 4s on CI under noise. Chunking sidesteps that: each sleep is a full 10ms, well above scheduler granularity, and we just emit more runes per tick to hit the same total time. The result is deterministically bounded by the budget regardless of how the host schedules sleeps.
ANSI escape sequences embedded in text are emitted as part of the chunk they land in — they don't trigger sleeps or count toward the printable budget so color transitions never pause the eye.
func ParallelToolsEnabled ¶ added in v1.118.0
func ParallelToolsEnabled() bool
ParallelToolsEnabled reports whether the orchestrator should partition the batch and run concurrency-safe tools in parallel. Opt-in via CHATCLI_AGENT_PARALLEL_TOOLS=true while the feature bakes in production usage. When false, every tool runs sequentially regardless of its capability flags — preserving the legacy behavior bit-for-bit.
Default ON for new installs after Fase 7 acceptance; until then the env var gates the rollout.
func RegisterAuxReadPath ¶ added in v1.102.0
func RegisterAuxReadPath(path string)
RegisterAuxReadPath adds a directory that SensitiveReadPaths.IsReadAllowed will always permit. Typically called by the session workspace.
func RegisterResultDirSetter ¶ added in v1.102.0
func RegisterResultDirSetter(fn ResultDirSetter)
RegisterResultDirSetter adds a callback that will be invoked with the session tool-results directory during InitSessionWorkspace (and with "" on Cleanup).
func SetBudgetResultDir ¶ added in v1.102.0
func SetBudgetResultDir(dir string)
SetBudgetResultDir overrides the directory where EnforceToolResultBudget persists overflow files. Pass an empty string to reset to default. This is wired by cli/agent.InitSessionWorkspace.
func SetProcessGroup ¶ added in v1.66.0
SetProcessGroup configures the command to create a new process group. Must be called before cmd.Start().
func TelemetrySafe ¶ added in v1.118.0
TelemetrySafe returns a short, locale-independent description suitable for log fields. Sanitizes file paths and quotation marks so the output is reliably greppable.
func TerminalWidth ¶ added in v1.119.2
func TerminalWidth() int
TerminalWidth reports the live terminal width in columns. Delegates to kit.TermWidth — the single width helper (fallback 100 when stdout is not a TTY).
func UnregisterAuxReadPath ¶ added in v1.102.0
func UnregisterAuxReadPath(path string)
UnregisterAuxReadPath removes a previously registered aux read path.
func ValidateToolResultPairing ¶ added in v1.99.0
ValidateToolResultPairing checks if the history has pairing issues without repairing. Returns true if the history is valid (no repairs needed).
func VisibleLen ¶
VisibleLen calcula comprimento visível em colunas do terminal (sem ANSI codes). Delegates to kit.VisibleLen (lipgloss.Width) — one measurement path for wrap math and border math keeps them in agreement when content has emoji presentation sequences.
func WrapPlainStringForTool ¶ added in v1.99.0
WrapPlainStringForTool wraps a plain string value into the appropriate JSON structure for the given tool. This handles cases where the LLM returns just the value instead of a proper JSON object.
This function is conservative — it only wraps values that look like a single argument (e.g., a file path or search term). It does NOT wrap CLI-style args like "read --file main.go" because those are handled by the CLI parser.
Examples:
WrapPlainStringForTool("read_file", "main.go") → `{"file":"main.go"}`
WrapPlainStringForTool("run_command", "ls -la") → `{"cmd":"ls -la"}`
WrapPlainStringForTool("search_files", "TODO") → `{"term":"TODO"}`
Types ¶
type BatchOptions ¶ added in v1.118.0
type BatchOptions struct {
// MaxConcurrency caps parallelism for this batch. Zero or negative
// falls back to MaxToolConcurrency().
MaxConcurrency int
// CancelSiblings, when true, cancels in-flight siblings via the
// batch ctx when any tool returns an infra error. The legacy serial
// loop fails fast on first error; we mirror that for the concurrent
// path so users don't pay for trailing operations after one already
// signals "stop".
CancelSiblings bool
// Logger is used for batch-level diagnostics. Nil → no-op.
Logger *zap.Logger
}
BatchOptions configures a single RunBatch invocation.
type BudgetReport ¶ added in v1.99.0
type BudgetReport struct {
TotalToolResults int
TotalOriginalChars int64
TotalFinalChars int64
ResultsTruncated int
ResultsPersistedDisk int
BytesSavedToDisk int64
}
BudgetReport describes what the budget enforcement did.
func EnforceToolResultBudget ¶ added in v1.99.0
func EnforceToolResultBudget(history []models.Message, logger *zap.Logger) ([]models.Message, *BudgetReport)
EnforceToolResultBudget scans the conversation history and truncates oversized tool results in-place. Large results are persisted to temporary files and replaced with compact previews containing a file reference.
The function works in two passes:
- Per-result enforcement: any single result exceeding DefaultPerResultMaxChars is truncated with a preview.
- Per-turn enforcement: if the aggregate of all tool results in a turn exceeds DefaultTurnBudgetChars, the largest results are progressively truncated until the turn fits within budget.
Returns the (possibly modified) history and a report.
type CommandAllowlist ¶ added in v1.97.0
type CommandAllowlist struct {
// contains filtered or unexported fields
}
CommandAllowlist validates commands against a categorized allowlist. In strict mode, only allowed commands can execute. In permissive mode, unknown commands fall back to the legacy denylist validator.
func NewCommandAllowlist ¶ added in v1.97.0
func NewCommandAllowlist() *CommandAllowlist
NewCommandAllowlist creates a new allowlist validator configured from environment.
func (*CommandAllowlist) GetMode ¶ added in v1.97.0
func (al *CommandAllowlist) GetMode() SecurityMode
GetMode returns the current security mode.
type CommandBlock ¶
type CommandBlock struct {
Description string
Commands []string
Language string
ContextInfo CommandContextInfo
}
CommandBlock representa um bloco de comandos executáveis
type CommandContextInfo ¶
type CommandContextInfo struct {
SourceType SourceType
FileExtension string
IsScript bool
ScriptType string // shell, python, etc.
}
CommandContextInfo contém metadados sobre a origem e natureza de um comando
type CommandExecutor ¶
type CommandExecutor struct {
// contains filtered or unexported fields
}
CommandExecutor executa comandos do sistema de forma segura
func NewCommandExecutor ¶
func NewCommandExecutor(logger *zap.Logger) *CommandExecutor
NewCommandExecutor cria uma nova instância do executor
func (*CommandExecutor) CaptureOutput ¶
func (e *CommandExecutor) CaptureOutput(ctx context.Context, shell string, args []string) ([]byte, error)
CaptureOutput executa comando e captura apenas a saída (para uso interno)
func (*CommandExecutor) Execute ¶
func (e *CommandExecutor) Execute(ctx context.Context, command string, interactive bool) (*ExecutionResult, error)
Execute executa um comando e retorna o resultado
type CommandOutput ¶
type CommandOutput struct {
CommandBlock CommandBlock
Output string
ErrorMsg string
}
CommandOutput representa o resultado da execução de um comando
type CommandValidator ¶
type CommandValidator struct {
// contains filtered or unexported fields
}
CommandValidator validates shell-level requests before execution.
Layered strategy (cheapest to most expensive):
- inlineCodeAnalyzer — for invocations like `python -c <code>`, `node -e <code>`, etc., dynamically classifies the inline source. Replaces the older `\bpython[23]?\s+-c\b` regex that produced false positives on benign one-liners like `python -c "print(1)"`.
- dangerousPatterns — traditional regex over the full line. Catch-all for patterns that span multiple segments (curl|sh, base64|bash) or that flag a single dangerous invocation (rm -rf, mkfs, sudo, etc).
- extraDenyPatterns — user-supplied denylist via CHATCLI_AGENT_DENYLIST.
The shell parsing layer (ShellSegment) feeds (1). The legacy full-line regex pass in (2) is preserved verbatim for zero behavioral regression on the existing dangerous-command corpus.
func NewCommandValidator ¶
func NewCommandValidator(logger *zap.Logger) *CommandValidator
NewCommandValidator builds a validator with default rules.
func (*CommandValidator) IsDangerous ¶
func (v *CommandValidator) IsDangerous(cmd string) bool
IsDangerous checks whether a request is potentially harmful.
Evaluation pipeline:
- Inline-code analysis — for each shell segment that invokes an interpreter via -c/-e/-r, classify the inline source. RiskHigh short-circuits to dangerous immediately. RiskSafe does not make this segment dangerous (the rest of the line is still scored by the next layers).
- dangerousPatterns — traditional regex over the full line.
- extraDenyPatterns — user-supplied denylist.
- Sudo guard.
The "safe inline -c suppresses the false positive" property holds because the `\bpython[23]?\s+-c\b` family was removed from layer 2: the classifier is the single source of truth for that class.
func (*CommandValidator) IsLikelyInteractive ¶
func (v *CommandValidator) IsLikelyInteractive(cmd string) bool
IsLikelyInteractive verifica se um comando provavelmente é interativo
func (*CommandValidator) ValidateCommand ¶
func (v *CommandValidator) ValidateCommand(cmd string) error
ValidateCommand valida um comando antes da execução
type ContextManager ¶
type ContextManager struct {
// contains filtered or unexported fields
}
ContextManager gerencia contexto de execução para o agente
func NewContextManager ¶
func NewContextManager(logger *zap.Logger) *ContextManager
NewContextManager cria uma nova instância do gerenciador de contexto
func (*ContextManager) CreateExecutionContext ¶
func (cm *ContextManager) CreateExecutionContext() (context.Context, context.CancelFunc)
CreateExecutionContext cria um contexto com timeout para execução
func (*ContextManager) GetDefaultTimeout ¶
func (cm *ContextManager) GetDefaultTimeout() time.Duration
GetDefaultTimeout retorna o timeout padrão configurado
func (*ContextManager) RequestLLMContinuation ¶
func (cm *ContextManager) RequestLLMContinuation( ctx context.Context, llmClient interface{}, history []models.Message, previousCommand string, output string, stderr string, userContext string, ) (string, error)
RequestLLMContinuation solicita continuação à LLM com contexto adicional
func (*ContextManager) RequestLLMWithPreExecutionContext ¶
func (cm *ContextManager) RequestLLMWithPreExecutionContext( ctx context.Context, llmClient interface{}, history []models.Message, originalCommand string, userContext string, ) (string, error)
RequestLLMWithPreExecutionContext solicita refinamento antes da execução
func (*ContextManager) SetDefaultTimeout ¶
func (cm *ContextManager) SetDefaultTimeout(timeout time.Duration)
SetDefaultTimeout atualiza o timeout padrão
type ContextRecovery ¶ added in v1.99.0
type ContextRecovery struct {
// contains filtered or unexported fields
}
ContextRecovery manages recovery state across a session.
func NewContextRecovery ¶ added in v1.99.0
func NewContextRecovery(config ContextRecoveryConfig, logger *zap.Logger) *ContextRecovery
NewContextRecovery creates a recovery manager.
func (*ContextRecovery) CanEscalateMaxTokens ¶ added in v1.99.0
func (cr *ContextRecovery) CanEscalateMaxTokens() bool
CanEscalateMaxTokens returns true if more escalation attempts are available.
func (*ContextRecovery) CanRecoverContextOverflow ¶ added in v1.99.0
func (cr *ContextRecovery) CanRecoverContextOverflow() bool
CanRecoverContextOverflow returns true if more recovery attempts are available.
func (*ContextRecovery) MaxTokensEscalation ¶ added in v1.99.0
func (cr *ContextRecovery) MaxTokensEscalation(currentMaxTokens, providerCap int) (int, bool)
MaxTokensEscalation computes the escalated max_tokens value. Returns (newMaxTokens, shouldEscalate).
func (*ContextRecovery) RecoverContextOverflow ¶ added in v1.99.0
func (cr *ContextRecovery) RecoverContextOverflow(history []models.Message) ([]models.Message, bool)
RecoverContextOverflow applies increasingly aggressive recovery strategies to reduce the conversation history size.
Strategy progression:
- First attempt: aggressive budget enforcement + pairing cleanup
- Second attempt: emergency truncation (keep only recent messages)
- Third attempt: nuclear truncation (keep only system + last 2 messages)
Returns the recovered history and true if recovery was applied.
type ContextRecoveryConfig ¶ added in v1.99.0
type ContextRecoveryConfig struct {
// MaxRecoveryAttempts is the maximum number of context-too-long recoveries per session.
MaxRecoveryAttempts int
// MaxTokenEscalations is the maximum number of max_tokens escalations per session.
MaxTokenEscalations int
// EmergencyKeepMessages is how many recent messages to keep during emergency truncation.
// System messages are always preserved.
EmergencyKeepMessages int
// AggressiveBudgetRatio reduces the tool result budget to this fraction during recovery.
// 0.5 means half the normal budget.
AggressiveBudgetRatio float64
}
ContextRecoveryConfig controls recovery behavior.
func DefaultContextRecoveryConfig ¶ added in v1.99.0
func DefaultContextRecoveryConfig() ContextRecoveryConfig
DefaultContextRecoveryConfig returns the default recovery configuration.
type EnhancedExecutionResult ¶ added in v1.66.0
type EnhancedExecutionResult struct {
Command string
Output string
Error string
ExitCode int
Duration time.Duration
WasKilled bool
WasTruncated bool
OriginalSize int64
Severity string
}
EnhancedExecutionResult extends ExecutionResult with truncation info.
type ExecuteFunc ¶ added in v1.118.0
type ExecuteFunc func(ctx context.Context, call ToolCall) (ToolResult, error)
ExecuteFunc is the callback the orchestrator invokes per tool call. It returns the structured ToolResult; the second return is an infrastructure error that aborts the batch (context.Canceled, network down, plugin binary missing) — business errors stay inside the result with IsError=true.
type ExecutionResult ¶
type ExecutionResult struct {
Command string
Output string
Error string
ExitCode int
Duration time.Duration
WasKilled bool
}
ExecutionResult contém o resultado de uma execução
type FileStalenessTracker ¶ added in v1.99.0
type FileStalenessTracker struct {
// contains filtered or unexported fields
}
FileStalenessTracker tracks file states across read/write operations.
func NewFileStalenessTracker ¶ added in v1.99.0
func NewFileStalenessTracker() *FileStalenessTracker
NewFileStalenessTracker creates a new tracker.
func (*FileStalenessTracker) CheckStaleness ¶ added in v1.99.0
func (t *FileStalenessTracker) CheckStaleness(path string) StalenessResult
CheckStaleness checks if a file has been modified since the last recorded read. Returns a StalenessResult. If the file was never read, it's not considered stale.
func (*FileStalenessTracker) Clear ¶ added in v1.99.0
func (t *FileStalenessTracker) Clear(path string)
Clear removes the recorded state for a file (e.g., after a successful write).
func (*FileStalenessTracker) ClearAll ¶ added in v1.99.0
func (t *FileStalenessTracker) ClearAll()
ClearAll removes all tracked file states.
func (*FileStalenessTracker) RecordRead ¶ added in v1.99.0
func (t *FileStalenessTracker) RecordRead(path string) error
RecordRead records the current state of a file after a successful read. Should be called after every read tool execution.
func (*FileStalenessTracker) TrackedFiles ¶ added in v1.99.0
func (t *FileStalenessTracker) TrackedFiles() []string
TrackedFiles returns the list of currently tracked file paths.
type FileState ¶ added in v1.99.0
type FileState struct {
Path string `json:"path"`
ModTime time.Time `json:"mod_time"`
ContentHash string `json:"content_hash"` // SHA-256 hex
Size int64 `json:"size"`
ReadAt time.Time `json:"read_at"`
}
FileState records the state of a file at the time it was read.
type InlineCodeRisk ¶ added in v1.118.0
type InlineCodeRisk int
InlineCodeRisk is the verdict returned by the inline-code classifier for `python -c '<code>'`, `node -e '<code>'`, and friends. The middle level `Unknown` is reserved for code we can't confidently classify (parse failure, exotic syntax) — callers should treat Unknown as "elevate to confirmation" in strict mode and "allow" in lenient mode.
const ( // RiskSafe — inline source contains only read-only operations: stdlib // pretty-printing, JSON encoding, simple math. No imports of os / // subprocess / network libraries, no file writes, no eval/exec. RiskSafe InlineCodeRisk = iota // RiskUnknown — we couldn't determine risk (mixed signals, unknown // builtins, dynamic indirection). Conservative callers should treat // this as RiskHigh. RiskUnknown // RiskHigh — inline source uses primitives that can execute arbitrary // commands, write files, open network connections, or otherwise escape // the language sandbox. RiskHigh )
func (InlineCodeRisk) String ¶ added in v1.118.0
func (r InlineCodeRisk) String() string
String renders the risk level for log/telemetry purposes.
type InlineCodeRiskAnalyzer ¶ added in v1.118.0
type InlineCodeRiskAnalyzer struct {
// contains filtered or unexported fields
}
InlineCodeRiskAnalyzer classifies inline source code passed to a language interpreter via the standard exec flags. It uses substring matching (regex) rather than per-language ASTs because:
- Shipping a Python+JS+Perl+Ruby+PHP+Lua parser bundle would inflate the binary and create a maintenance nightmare.
- Inline scripts on the command line are short by definition — a regex pass over a few hundred bytes is reliably fast.
- The false-negative rate of "obfuscated inline malware" is bounded by what fits on one CLI line, and most real attacks cited in security literature use the same vocabulary (os.system, child_process, etc.).
The analyzer is goroutine-safe: all state is precompiled at construction time and Analyze is a pure function thereafter.
func NewInlineCodeRiskAnalyzer ¶ added in v1.118.0
func NewInlineCodeRiskAnalyzer() *InlineCodeRiskAnalyzer
NewInlineCodeRiskAnalyzer builds the default analyzer. Pass strict=true to elevate RiskUnknown to RiskHigh, useful for compliance-sensitive deployments. The environment variable CHATCLI_AGENT_INLINE_CODE_STRICT also forces strict mode.
func (*InlineCodeRiskAnalyzer) Analyze ¶ added in v1.118.0
func (a *InlineCodeRiskAnalyzer) Analyze(lang, source string) InlineCodeRisk
Analyze returns the risk level for the given inline source string. `lang` should be a normalized interpreter name (python, node, perl, ruby, php, lua); other values return RiskUnknown.
func (*InlineCodeRiskAnalyzer) IsHighRisk ¶ added in v1.118.0
func (a *InlineCodeRiskAnalyzer) IsHighRisk(lang, source string) bool
IsHighRisk is a convenience wrapper for callers that only care about the strict-elevation decision.
type MicrocompactConfig ¶ added in v1.99.0
type MicrocompactConfig struct {
// TurnsBeforeTruncate is the age (in turns) after which tool results
// start being truncated. Default: 2.
TurnsBeforeTruncate int
// TurnsBeforeSummarize is the age (in turns) after which tool results
// are replaced with a one-line summary. Default: 4.
TurnsBeforeSummarize int
// TruncateHeadChars is how many chars to keep at the head during truncation.
TruncateHeadChars int
// TruncateTailChars is how many chars to keep at the tail during truncation.
TruncateTailChars int
// MinContentSize is the minimum content size (chars) to trigger compaction.
// Small tool results are never compacted.
MinContentSize int
// CCR, when set, preserves every byte microcompact drops: the original
// tool result is archived in the CCR store and the preview/summary stub
// embeds a <<ccr:KEY>> marker the model can expand with @recall. Nil
// keeps the legacy lossy behavior.
CCR *compress.Layer
}
MicrocompactConfig controls progressive compaction behavior.
func DefaultMicrocompactConfig ¶ added in v1.99.0
func DefaultMicrocompactConfig() MicrocompactConfig
DefaultMicrocompactConfig returns the default configuration.
Defaults are intentionally gentle — two turns of full-fidelity tool output, four turns before falling back to a one-line summary, and generous head+tail previews. This protects multi-turn, cross-file workflows (large refactors, review sessions, long debugging arcs) where the model may need to re-consult content it read several turns ago. Users chasing maximum token frugality can tighten every knob via the CHATCLI_MICROCOMPACT_* env vars below.
type MicrocompactReport ¶ added in v1.99.0
MicrocompactReport describes what the microcompact did.
func ApplyMicrocompact ¶ added in v1.99.0
func ApplyMicrocompact(history []models.Message, currentTurn int, config MicrocompactConfig, logger *zap.Logger) ([]models.Message, *MicrocompactReport)
ApplyMicrocompact progressively compacts old tool results in the history. currentTurn is the 0-based index of the current turn in the agent loop.
The function identifies "turns" by counting assistant messages. Tool results following each assistant message belong to that turn. Results from older turns are progressively compacted.
type PairingRepairReport ¶ added in v1.99.0
type PairingRepairReport struct {
SyntheticResultsInjected int // tool_use blocks without matching tool_result
OrphanResultsRemoved int // tool_result blocks without matching tool_use
DuplicateToolUsePruned int // duplicate or empty tool_use IDs within a single assistant message
ResultsRelocated int // results moved next to their tool_call past interposed messages
MissingToolUseIDs []string // IDs of tool calls that had no result
OrphanToolResultIDs []string // IDs of tool results that had no call
}
PairingRepairReport describes what the pairing validator repaired.
func EnsureToolResultPairing ¶ added in v1.99.0
func EnsureToolResultPairing(history []models.Message, logger *zap.Logger) ([]models.Message, *PairingRepairReport)
EnsureToolResultPairing validates and repairs the conversation history so that every assistant message carrying ToolCalls is immediately followed by exactly one tool result message per call — the shape every native tool API requires.
Scope rules (per assistant block, in order):
- Results are claimed from the stretch between the assistant message and the NEXT assistant message. A matching result that sits past interposed user/system messages is relocated to be adjacent to its call.
- Calls with no claimable result get a synthetic error result injected directly after the assistant message.
- Duplicate IDs WITHIN one assistant message are pruned (all but the first), and calls with an empty ID are pruned outright — they can never be paired, and serializing them ships an invalid empty id. The same ID reappearing in a LATER assistant message is legal — providers like Moonshot/Kimi reuse deterministic per-turn IDs.
- Tool result messages not claimed by any block are orphans and removed.
Returns the repaired history and a report of what was changed. If no repairs are needed, returns the original slice unchanged — the common case is allocation-free via a validation prepass, since this runs on the persistent history every agent turn.
func (*PairingRepairReport) HasRepairs ¶ added in v1.99.0
func (r *PairingRepairReport) HasRepairs() bool
HasRepairs returns true if any repairs were made.
type PartitionPolicy ¶ added in v1.118.0
type PartitionPolicy interface {
// IsConcurrencySafe returns true when the given tool call can run
// in parallel with other safe calls. Implementations should be
// pure functions over the (name, args) inputs.
IsConcurrencySafe(call ToolCall) bool
}
PartitionPolicy lets the caller inject capability lookups without pulling cli/plugins or cli/mcp into the agent package (cycle). The orchestrator asks: "is this call safe to parallelize?". The caller implements that by inspecting its plugin / MCP registry.
type PartitionPolicyFunc ¶ added in v1.118.0
PartitionPolicyFunc adapts a plain function into the PartitionPolicy interface for callers that don't want to define a type.
func (PartitionPolicyFunc) IsConcurrencySafe ¶ added in v1.118.0
func (f PartitionPolicyFunc) IsConcurrencySafe(call ToolCall) bool
IsConcurrencySafe satisfies PartitionPolicy.
type PathValidator ¶ added in v1.66.0
type PathValidator struct {
// contains filtered or unexported fields
}
PathValidator validates file paths against workspace boundaries.
func NewPathValidator ¶ added in v1.66.0
func NewPathValidator(workspace string, logger *zap.Logger) *PathValidator
NewPathValidator creates a new path validator.
func (*PathValidator) DetectPathTraversal ¶ added in v1.66.0
func (pv *PathValidator) DetectPathTraversal(command string) (bool, string)
DetectPathTraversal checks if a command contains path traversal attempts.
func (*PathValidator) IsWithinWorkspace ¶ added in v1.66.0
func (pv *PathValidator) IsWithinWorkspace(targetPath string) bool
IsWithinWorkspace checks if a path is within the workspace boundary.
func (*PathValidator) SetWorkspaceBoundary ¶ added in v1.66.0
func (pv *PathValidator) SetWorkspaceBoundary(path string)
SetWorkspaceBoundary updates the workspace boundary.
func (*PathValidator) ValidateFilePaths ¶ added in v1.66.0
func (pv *PathValidator) ValidateFilePaths(command string) ValidationResult
ValidateFilePaths validates all paths in a command against safety rules.
type PatternRule ¶ added in v1.66.0
type PatternRule struct {
Pattern string `json:"pattern"`
Description string `json:"description"`
Severity string `json:"severity"` // "critical", "high", "medium", "low"
// contains filtered or unexported fields
}
PatternRule is a configurable deny/allow pattern.
func (*PatternRule) Compile ¶ added in v1.66.0
func (pr *PatternRule) Compile() error
Compile compiles the regex pattern.
func (*PatternRule) Matches ¶ added in v1.66.0
func (pr *PatternRule) Matches(command string) bool
Matches checks if the command matches this pattern.
type ProcessManager ¶ added in v1.66.0
type ProcessManager struct {
// contains filtered or unexported fields
}
ProcessManager handles process lifecycle management.
func NewProcessManager ¶ added in v1.66.0
func NewProcessManager(logger *zap.Logger) *ProcessManager
NewProcessManager creates a new process manager.
func (*ProcessManager) TerminateProcessTree ¶ added in v1.66.0
TerminateProcessTree sends SIGTERM, waits for timeout, then SIGKILL. Platform-specific implementation in process_manager_unix.go / process_manager_windows.go.
func (*ProcessManager) TruncateOutput ¶ added in v1.66.0
func (pm *ProcessManager) TruncateOutput(output string, maxBytes int64) (string, bool)
TruncateOutput truncates output keeping head + tail with a marker.
type ResponseEnvelopeOptions ¶ added in v1.119.2
type ResponseEnvelopeOptions struct {
// HeaderLeft is the visible label on the top border's left side.
// Conventionally the icon + title (e.g. " 💬 RESPOSTA ") in color.
HeaderLeft string
// HeaderRight is the visible label on the top border's right side.
// Conventionally the metrics block (e.g. " 1.4s · 312↑ 1.8k↓ ").
// Pass an empty string to draw only the left label.
HeaderRight string
// FooterLeft and FooterRight compose the dim telemetry line that
// closes the reply. Most callers leave them empty (the body's
// terminal punctuation closes the thought).
// Body is the message content to render inside the box. Typically
// glamour-rendered markdown (ANSI escapes preserved); the envelope
// wraps it to the resolved inner width.
Body string
// Color is kept for API stability; the sóbrio treatment draws dim
// chrome and expects labels to arrive pre-colored.
Color string
// Typewriter enables progressive rune-by-rune painting of the body
// for the "alive" reply feel. ANSI escapes flush instantly so
// colors never pause the eye.
Typewriter bool
// TypewriterDelay overrides the per-rune delay. Zero uses the
// default of 2ms — fast enough for long replies, slow enough to
// register as animation. Set to a positive value to slow down or
// to a negative value (caller-side check) to disable.
TypewriterDelay time.Duration
// Width pins the card width in columns. Zero asks the envelope to
// pick EnvelopeWidth() automatically — the right choice for almost
// every caller. Tests and special UIs (split-pane reports) can
// override this.
Width int
}
ResponseEnvelopeOptions configures the unified reply rendering. All label fields are PRE-FORMATTED: callers own colorization and any leading/trailing spaces they want carved out of the dash fill. Empty fields are omitted (no extra space reserved).
type ResultDirSetter ¶ added in v1.102.0
type ResultDirSetter func(dir string)
ResultDirSetter is a callback other packages (workers, plugins) can register so the session workspace can wire the overflow dir without creating import cycles.
type SafetyConfig ¶ added in v1.66.0
type SafetyConfig struct {
Version int `json:"version"`
DenyPatterns []PatternRule `json:"deny_patterns,omitempty"`
AllowPatterns []PatternRule `json:"allow_patterns,omitempty"`
WorkspaceBoundary string `json:"workspace_boundary,omitempty"`
AllowSudo bool `json:"allow_sudo"`
MaxOutputBytes int64 `json:"max_output_bytes,omitempty"`
}
SafetyConfig holds configurable safety rules.
func DefaultSafetyConfig ¶ added in v1.66.0
func DefaultSafetyConfig() *SafetyConfig
DefaultSafetyConfig returns sensible defaults.
func LoadSafetyConfig ¶ added in v1.66.0
func LoadSafetyConfig(globalPath, localPath string) (*SafetyConfig, error)
LoadSafetyConfig loads and merges global + local safety configs.
func MergeSafetyConfigs ¶ added in v1.66.0
func MergeSafetyConfigs(global, local *SafetyConfig) *SafetyConfig
MergeSafetyConfigs merges local into global. Local can only ADD deny patterns, not remove global ones. Local can add allow patterns for project-specific commands.
type SecurityMode ¶ added in v1.97.0
type SecurityMode string
SecurityMode determines how command validation works.
const ( // SecurityModeStrict only allows commands in the allowlist (default). SecurityModeStrict SecurityMode = "strict" // SecurityModePermissive uses allowlist + falls back to denylist for unknown commands. SecurityModePermissive SecurityMode = "permissive" )
type SensitiveReadPaths ¶ added in v1.97.0
type SensitiveReadPaths struct {
// contains filtered or unexported fields
}
SensitiveReadPaths enforces read access control in agent mode. By default, only files within the workspace are readable, with specific sensitive paths blocked even within the workspace.
func NewSensitiveReadPaths ¶ added in v1.97.0
func NewSensitiveReadPaths() *SensitiveReadPaths
NewSensitiveReadPaths creates a read path validator configured from environment.
func (*SensitiveReadPaths) IsReadAllowed ¶ added in v1.97.0
func (s *SensitiveReadPaths) IsReadAllowed(path, workspace string) (bool, string)
IsReadAllowed checks whether the given path is safe to read in agent mode. workspace is the current working directory / project root. Returns (allowed, reason).
type SessionWorkspace ¶ added in v1.102.0
type SessionWorkspace struct {
Root string // e.g. /tmp/chatcli-agent-abc123
ScratchDir string // Root/scratch -> CHATCLI_AGENT_TMPDIR
ToolResultsDir string // Root/tool-results
// contains filtered or unexported fields
}
SessionWorkspace is a per-CLI-session scratch directory the agent is unconditionally allowed to read and write under.
func GetSessionWorkspace ¶ added in v1.102.0
func GetSessionWorkspace() *SessionWorkspace
GetSessionWorkspace returns the active session workspace or nil if InitSessionWorkspace was never called.
func InitSessionWorkspace ¶ added in v1.102.0
func InitSessionWorkspace(logger *zap.Logger) (*SessionWorkspace, error)
InitSessionWorkspace creates the per-session workspace and registers its subdirs with the engine + read validators. Idempotent: calling it twice returns the existing workspace.
The returned workspace should have Cleanup() called at CLI shutdown.
func (*SessionWorkspace) Cleanup ¶ added in v1.102.0
func (ws *SessionWorkspace) Cleanup()
Cleanup removes the session workspace unless CHATCLI_AGENT_KEEP_TMPDIR=true. Safe to call multiple times.
type ShellSegment ¶ added in v1.118.0
type ShellSegment struct {
Cmd string
Args []string
Full string
HasPipe bool // true if this segment is part of a pipeline
Position int // 0-based position in the top-level command sequence
}
ShellSegment represents one shell command — a single invocation that is not itself a pipeline or compound statement. Two `cat` commands separated by `|`, `&&`, `||`, or `;` produce two ShellSegments. Each segment is the granularity at which we apply per-command dangerous-pattern matching and inline-code classification.
The Full string is the literal text of just this segment (no operators); Cmd is the first word (the program name), and Args is everything after (including flags). Quoting is preserved in Full but stripped from Args so callers can pattern-match flag values directly.
func ParseShellSegments ¶ added in v1.118.0
func ParseShellSegments(line string) []ShellSegment
ParseShellSegments splits a shell command line into segments using a real shell parser (mvdan.cc/sh/v3/syntax). Compared to splitting by "|" or strings.Fields, this is robust against:
- Quoted operators: `echo "a | b" | grep a` → 2 segments, not 3
- Heredocs: `cat <<EOF\n|||\nEOF` → 1 segment
- Escaped pipes: `printf 'a\\|b\\|c'` → 1 segment
- Subshells: `(cd /tmp && ls)` → emits inner cmds
- Background: `sleep 1 & echo done` → 2 segments
On parse failure the function falls back to a permissive single-segment result (the whole line as one segment). Callers must treat the segments as a best-effort decomposition, not a security boundary on their own — the dangerous-pattern matcher still runs against the full line as a belt-and-suspenders measure.
func (ShellSegment) InlineSource ¶ added in v1.118.0
func (s ShellSegment) InlineSource(flagPos int) string
InlineSource returns the inline source code passed via -c / -e / -r. Assumes IsInlineCodeInvocation returned true with the given flag pos.
func (ShellSegment) IsInlineCodeInvocation ¶ added in v1.118.0
func (s ShellSegment) IsInlineCodeInvocation() (lang string, flagPos int, ok bool)
IsInlineCodeInvocation returns true when the segment looks like `interpreter -flag inline-code` for one of the languages we analyze dynamically. The expected exec flag (-c, -e, -r) is returned so callers can find the next arg = the inline source.
func (ShellSegment) IsPureStdinConsumer ¶ added in v1.118.0
func (s ShellSegment) IsPureStdinConsumer() bool
IsPureStdinConsumer returns true when the segment is a known no-side-effect transformer that is safe regardless of who is feeding it data. Used by the classifier to decide that `<anything> | jq .` should not be elevated to dangerous just because the left-hand side touches files.
type SourceType ¶
type SourceType int
SourceType define o tipo de origem do comando
const ( SourceTypeUserInput SourceType = iota SourceTypeFile SourceTypeCommandOutput )
type StalenessResult ¶ added in v1.99.0
type StalenessResult struct {
IsStale bool
Reason string // human-readable reason
OriginalHash string
CurrentHash string
OriginalMod time.Time
CurrentMod time.Time
OriginalSize int64
CurrentSize int64
}
StalenessResult describes whether a file has changed since it was last read.
func (*StalenessResult) FormatWarning ¶ added in v1.99.0
func (r *StalenessResult) FormatWarning(path string) string
FormatWarning generates a warning message for stale files, suitable for injecting into tool results so the LLM can decide how to proceed.
type TaskSpec ¶ added in v1.118.0
type TaskSpec struct {
Description string
Status TaskStatus
}
TaskSpec is the LLM-friendly view of a planned task. It is what the @todo plugin uses to talk to the tracker — a flat structure decoupled from the internal *Task type's bookkeeping fields (attempts, timestamps).
type TaskStatus ¶ added in v1.47.0
type TaskStatus string
const ( TaskPending TaskStatus = "pending" TaskInProgress TaskStatus = "in_progress" TaskCompleted TaskStatus = "completed" TaskFailed TaskStatus = "failed" )
type TaskTracker ¶ added in v1.47.0
type TaskTracker struct {
// contains filtered or unexported fields
}
func NewTaskTracker ¶ added in v1.47.0
func NewTaskTracker(logger *zap.Logger) *TaskTracker
func (*TaskTracker) FormatProgress ¶ added in v1.47.0
func (t *TaskTracker) FormatProgress() string
func (*TaskTracker) GetCurrentTask ¶ added in v1.47.0
func (t *TaskTracker) GetCurrentTask() *Task
func (*TaskTracker) GetPlan ¶ added in v1.47.0
func (t *TaskTracker) GetPlan() *TaskPlan
func (*TaskTracker) MarkByID ¶ added in v1.118.0
func (t *TaskTracker) MarkByID(id int, status TaskStatus, errorMsg string) bool
MarkByID updates the status of the task with the given 1-indexed ID. Returns false when no such task exists. Used by the @todo plugin's partial-update path when the LLM wants to flip a single status without resending the whole list.
The CurrentTask cursor advances if the marked task was the current one and the new status is Completed, mirroring MarkCurrentAs.
func (*TaskTracker) MarkCurrentAs ¶ added in v1.47.0
func (t *TaskTracker) MarkCurrentAs(status TaskStatus, errorMsg string)
func (*TaskTracker) NeedsReplanning ¶ added in v1.47.0
func (t *TaskTracker) NeedsReplanning() bool
func (*TaskTracker) ParseReasoning ¶ added in v1.47.0
func (t *TaskTracker) ParseReasoning(reasoningText string) error
func (*TaskTracker) ResetPlan ¶ added in v1.47.0
func (t *TaskTracker) ResetPlan()
func (*TaskTracker) ResetPlanFromReasoning ¶ added in v1.47.0
func (t *TaskTracker) ResetPlanFromReasoning(reasoningText string, preserveCompleted bool) error
ResetPlanFromReasoning recria o plano a partir de um novo reasoning. Se preserveCompleted=true, tenta preservar como concluídas as tarefas ja concluídas do plano anterior.
func (*TaskTracker) SetTasks ¶ added in v1.118.0
func (t *TaskTracker) SetTasks(specs []TaskSpec)
SetTasks replaces the entire plan with the supplied list. Used by the @todo plugin to mirror Claude Code's TodoWrite semantics: the LLM emits the full updated list every call; the tracker reconciles without preserving prior state.
This is the only public path that lets a caller set per-task status explicitly. MarkCurrentAs is for the per-turn ReAct flow (one task at a time); SetTasks is for the LLM-driven plan overwrite.
type ToolBatch ¶ added in v1.118.0
type ToolBatch struct {
// Concurrent is true when every call in the batch is concurrency-safe
// and read-only-or-network-only (no shared-state mutations). The batch
// runs through errgroup with a semaphore-bounded fan-out.
Concurrent bool
// Calls are the per-tool invocations in their original order. The
// orchestrator returns results in the same order so the caller can
// keep its existing index-keyed accounting (batchOutputBuilder etc).
Calls []ToolCall
}
ToolBatch represents a contiguous run of tool calls that share the same execution policy (all concurrency-safe or all serial). The orchestrator partitions a turn's calls into a sequence of batches and runs each one to completion before moving on, preserving the relative order of serial steps relative to surrounding parallel batches.
func PartitionToolCalls ¶ added in v1.118.0
func PartitionToolCalls(calls []ToolCall, policy PartitionPolicy) []ToolBatch
PartitionToolCalls splits a turn's tool calls into a sequence of batches where each batch is either fully concurrent-safe or fully serial. Consecutive concurrent-safe calls are coalesced into one batch (up to MaxToolConcurrency() — larger groups are split into multiple back-to-back concurrent batches to keep memory bounded). A serial call always opens a fresh batch and any following calls stay in their own batches until the next concurrency-safe run.
The algorithm is deterministic and order-preserving: a serial call between two safe calls is NEVER folded into either neighbor's parallel batch — that would change the observable execution order.
type ToolCall ¶ added in v1.43.0
ToolCall represents a parsed tool invocation from AI output text.
func ParseToolCalls ¶ added in v1.43.0
ParseToolCalls extracts tool calls from AI response text.
Supported formats:
- XML self-closing: <tool_call name="@x" args="..." />
- XML paired: <tool_call name="@x" args="..."></tool_call>
- <tool> alias: <tool name="@x" args="..." /> — models backed by other agent CLIs (Devin CLI, Codex CLI, Claude Code) shorten the tag
- Attributes in any order, single or double quotes
- Args containing '>' characters (JSON, HTML entities, etc.)
- JSON tool calls: {"tool_call":"@coder","args":{...}}
- Multiple tool calls in a single response
type ToolContext ¶ added in v1.118.0
type ToolContext struct {
// FilesRead tracks the absolute paths the tool reported reading.
// Used by the file-staleness detector to know which paths to watch.
FilesRead []string
// FilesWritten tracks paths the tool reported writing. Used by the
// same staleness detector and by the engine's session workspace
// allowlist to surface new writes to read-on-demand.
FilesWritten []string
// HostsContacted tracks the network hosts the tool contacted.
// Telemetry / audit use only.
HostsContacted []string
// Extra is a free-form bag for forward compatibility. Plugins that
// need to track something the orchestrator doesn't know about yet
// can stash it here; consumers must defensively type-assert.
Extra map[string]any
}
ToolContext is the orchestrator-owned, per-turn state that ContextMutation callbacks can edit. It's a small, intentionally minimal surface — kept here in the agent package rather than the cli package so plugins under cli/plugins/ can manipulate it without an import cycle.
Fields populated lazily; nil maps are valid and Mutate helpers handle the lazy-init.
func (*ToolContext) ApplyMutations ¶ added in v1.118.0
func (c *ToolContext) ApplyMutations(results []ToolResult)
ApplyMutations runs each callback serially in the order given. The orchestrator calls this after a batch of concurrent tools completes — the serial application is what makes it safe for tools to mutate the shared context from within a parallel batch.
func (*ToolContext) GetExtra ¶ added in v1.118.0
func (c *ToolContext) GetExtra(key string) (any, bool)
GetExtra returns the value at key, plus an ok flag distinguishing "stored as nil" from "missing entirely".
func (*ToolContext) Merge ¶ added in v1.118.0
func (c *ToolContext) Merge(other *ToolContext)
Merge copies every recorded entry from other into c, deduping. Used by the orchestrator when reconciling per-turn context into the session-scoped state.
func (*ToolContext) PutExtra ¶ added in v1.118.0
func (c *ToolContext) PutExtra(key string, value any)
PutExtra stores a value in the Extra bag, allocating the map on first use.
func (*ToolContext) RecordFileRead ¶ added in v1.118.0
func (c *ToolContext) RecordFileRead(path string)
RecordFileRead appends a path to FilesRead, deduping. Called by tools that want to surface a read to the orchestrator's tracking layer (file-staleness, undo, audit) without owning a reference to it.
func (*ToolContext) RecordFileWrite ¶ added in v1.118.0
func (c *ToolContext) RecordFileWrite(path string)
RecordFileWrite appends a path to FilesWritten, deduping.
func (*ToolContext) RecordHostContacted ¶ added in v1.118.0
func (c *ToolContext) RecordHostContacted(host string)
RecordHostContacted appends a host to HostsContacted, deduping.
type ToolResult ¶ added in v1.118.0
type ToolResult struct {
// Output is the human/model-readable content. For Anthropic this maps
// to the tool_result `content` field; for OpenAI it goes into the
// `tool` message body.
Output string
// IsError signals that the tool encountered a business-level failure
// (the command exited non-zero, the URL returned 4xx, the file was
// not found). When true, provider adapters set the relevant
// is_error / [ERROR:<code>] marker so the model knows it's a failure
// without parsing the body.
IsError bool
// ErrorCode is the stable, locale-independent classification. Empty
// when IsError is false. Examples: "ENOENT", "EACCES", "Timeout",
// "Canceled", "ExitCode:2", "NetworkError", "UnknownError". Filled
// by ClassifyError (Fase 5.1) when the plugin returns a Go error;
// plugins that hand-craft a business error must set this themselves
// so the dashboard / log fields stay stable.
ErrorCode string
// NewMessages lets a tool emit additional conversation entries
// alongside its result — typically system-role hints
// ("output truncated to first 5000 chars") or assistant-role
// auto-suggestions. The orchestrator appends them to history in
// the order returned; ordering across concurrent tools is undefined
// (so concurrent tools should not rely on NewMessages for ordering).
NewMessages []models.Message
// ContextMutation is an optional callback applied serially by the
// orchestrator after the entire batch completes. Used to register
// side effects that need to survive across turns: "I just wrote
// /tmp/foo, add it to the allowlist", "I just read main.go, mark
// it as recently touched". Returning nil means "no mutation".
ContextMutation func(ctx *ToolContext)
// MCPMeta carries provider-agnostic structured metadata that some
// adapters can attach to the wire result. Anthropic's tool_result
// supports a `_meta` field; other providers ignore it. Use sparingly
// — most use cases are better served by NewMessages or telemetry.
MCPMeta map[string]any
// Duration is the wall-clock time the tool took to run, recorded by
// the orchestrator and exposed here so plugins can include it in
// telemetry/logs without re-measuring.
Duration time.Duration
}
ToolResult is the structured outcome of a tool invocation, decoupled from the wire format used to ship results back to the LLM. Each provider adapter (claudeai, openai, googleai, …) translates this into its own representation in cli/llm/<provider>/tool_result_adapter.go; the orchestrator never branches on provider type.
Compared to the legacy `(string, error)` return shape, ToolResult lets a plugin:
- Distinguish business errors from infrastructure errors (IsError vs the returned `error`). Business errors stay inside the conversation as tool_result with is_error=true; infrastructure errors abort the batch.
- Tag the error class with a stable code (ENOENT, Timeout, …) so the model can reason about retryability without parsing English.
- Emit additional conversational messages alongside the result (e.g. a warning that the output was truncated, a hint that a different tool would be more efficient).
- Mutate the orchestrator's per-turn context — record that a file was read, register a new allowed path, push an undo handle. The mutator runs serially after the batch completes so concurrent tools don't race on the same context object.
- Pass MCP-style structured metadata through to providers that understand it (Anthropic mcp_meta) without leaking provider specifics into plugin code.
func RunBatch ¶ added in v1.118.0
func RunBatch(ctx context.Context, batch ToolBatch, exec ExecuteFunc, opts BatchOptions) ([]ToolResult, error)
RunBatch executes the calls in batch with the given policy. The returned slice is index-aligned with batch.Calls — entry i corresponds to call i, preserving order even when the batch ran in parallel. An error is returned only for infrastructure failures that aborted the batch; per- tool business errors live inside ToolResult.IsError.
Concurrency semantics:
- Concurrent batch: errgroup with semaphore-bounded fan-out. Each goroutine populates its slot; the main goroutine waits for Wait(). CancelSiblings=true means the first infra error cancels the batch ctx so the others observe context.Canceled and bail.
- Serial batch: linear for-loop with the same ctx (no errgroup overhead). Errors cause an early return preserving prior results.
The function never panics — a panic in the callback is recovered and surfaced as an error result.
func WrapLegacyOutput ¶ added in v1.118.0
func WrapLegacyOutput(output string, err error) ToolResult
WrapLegacyOutput builds a ToolResult from the legacy (string, error) shape used by every plugin that hasn't migrated to ExecuteStructured. The error is mapped through ClassifyErrorCode to fill ErrorCode.
This is the bridge that lets the orchestrator drive every plugin — legacy or new — through the same ToolResult-shaped pipeline.
func WrapStructuredResult ¶ added in v1.118.0
func WrapStructuredResult(sr structuredCarrier, infraErr error) ToolResult
WrapStructuredResult elevates a plugins.StructuredResult into the agent-level ToolResult shape, filling ErrorCode from the infrastructure error when the plugin didn't set it itself. The Duration field is set by the orchestrator after this call; callers do not need to fill it.
type UIRenderer ¶
type UIRenderer struct {
// contains filtered or unexported fields
}
UIRenderer gerencia a renderização da interface do modo agente
func NewUIRenderer ¶
func NewUIRenderer(logger *zap.Logger) *UIRenderer
NewUIRenderer cria uma nova instância do renderizador de UI; o estilo é detectado a partir do ambiente. Para testes ou para forçar um estilo, use NewUIRendererWithStyle.
func NewUIRendererWithStyle ¶ added in v1.119.0
func NewUIRendererWithStyle(logger *zap.Logger, style UIStyle) *UIRenderer
NewUIRendererWithStyle constrói um renderer com estilo explícito. Usado por testes e por callers que precisam forçar um estilo independentemente do ambiente (ex: relatórios offline).
func (*UIRenderer) ClearScreen ¶
func (r *UIRenderer) ClearScreen()
ClearScreen limpa a tela (se permitido)
func (*UIRenderer) Colorize ¶
func (r *UIRenderer) Colorize(text string, color string) string
Colorize aplica cores ANSI (exportada com C maiúsculo). O resultado passa por theme.Recolor para que os códigos básicos legados (ColorCyan, ColorYellow, …) adotem a hue do tema ativo sob o profile de cor atual — toda a UI re-tematiza sem alterar os call sites, e a saída fica limpa (sem ANSI) quando não há terminal colorido (pipe/CI).
func (*UIRenderer) CompactAssistantText ¶ added in v1.118.1
func (r *UIRenderer) CompactAssistantText(text string)
CompactAssistantText renders the assistant's free-form text in the compact timeline. Distinct from CompactLine: the text uses the terminal's default foreground (ColorReset) so the assistant's actual answer stands out from the surrounding gray tool prose. Without this, in coder-compact mode the answer was visually indistinguishable from "Read(main.go)" lines and tool result excerpts — all the same ColorGray weight. Multi-line answers are wrapped, preserving the timeline indentation and dropping empty lines at the edges.
func (*UIRenderer) CompactBatchSummary ¶ added in v1.97.0
func (r *UIRenderer) CompactBatchSummary(successCount, total int, hasError bool)
CompactBatchSummary renders a one-line batch summary.
✓ 4/4 ações concluídas ✗ 2/4 ações concluídas (com erros)
func (*UIRenderer) CompactError ¶ added in v1.97.0
func (r *UIRenderer) CompactError(msg string)
CompactError renders an error inline.
✗ BLOCKED Ação negada pelo usuário
func (*UIRenderer) CompactLine ¶ added in v1.97.0
func (r *UIRenderer) CompactLine(icon, label, text string, color string)
CompactLine renders a single inline status line (no card/box). Used for reasoning, explanations, errors, summaries in compact mode.
● PLANO Ler main.go, modificar handler, atualizar testes ✗ ERRO Arquivo não encontrado ✓ OK 2 arquivos modificados
func (*UIRenderer) CompactMultiLine ¶ added in v1.97.0
func (r *UIRenderer) CompactMultiLine(icon, label, text string, color string, maxLines int)
CompactMultiLine renders a compact block: icon + label on first line, then indented content lines (max N lines). For reasoning/plan display.
● PLANO 1. Ler main.go 2. Modificar handleRequest 3. Atualizar testes
func (*UIRenderer) CompactToolDone ¶ added in v1.97.0
func (r *UIRenderer) CompactToolDone(toolLabel string, duration string, isError bool)
CompactToolDone renders a completed tool call in compact format:
✓ Read(main.go) 1.2s
Tool label in cyan to match CompactToolStart; the green check + cyan duration already mark this as a result so the label color reinforces the tool identity rather than competing with the success signal.
func (*UIRenderer) CompactToolStart ¶ added in v1.97.0
func (r *UIRenderer) CompactToolStart(toolLabel string)
CompactToolStart renders a tool call start in compact format:
↻ Read(main.go)
Tool label in cyan (was gray) so the tool identity stands out from the surrounding plain-gray prose. Without this, a "↻ Read(main.go)" looked identical to a free-text note in the timeline.
func (*UIRenderer) EchoUserInput ¶ added in v1.118.1
func (r *UIRenderer) EchoUserInput(text string)
EchoUserInput re-prints a line the user just submitted at the coder- mode interactive prompt, in green with a ❯ marker. The kernel echo during line-editing is uncolored, and once the line is committed it scrolls into history alongside gray tool lines and gray reasoning summaries — making it hard to tell at a glance where the user's instruction was. This persistent echo gives the user's directives a distinct visual lane.
func (*UIRenderer) IsCompact ¶ added in v1.119.0
func (r *UIRenderer) IsCompact() bool
IsCompact reports whether tool calls render as one-line ↻/✓ entries.
func (*UIRenderer) IsFull ¶ added in v1.119.0
func (r *UIRenderer) IsFull() bool
IsFull reports whether tool calls render as full bordered cards.
func (*UIRenderer) IsMinimal ¶ added in v1.119.0
func (r *UIRenderer) IsMinimal() bool
IsMinimal reports whether tool calls render as boxed-but-truncated cards.
func (*UIRenderer) PrintHeader ¶
func (r *UIRenderer) PrintHeader()
PrintHeader imprime o cabeçalho do modo agente: uma régua titulada responsiva (hierarquia título-bold + traços dim) no lugar das duas linhas de ━ com 58 colunas fixas.
func (*UIRenderer) PrintLastResult ¶
func (r *UIRenderer) PrintLastResult(outputs []*CommandOutput, lastIdx int)
PrintLastResult imprime o último resultado
func (*UIRenderer) PrintMenu ¶
func (r *UIRenderer) PrintMenu()
PrintMenu renders the agent-mode action menu in three vertical columns grouped by intent (Execution / Edit & Context / View). The previous single-column layout produced a 12-row wall of `[1..N]: …` entries that scrolled past the user's tool output every turn — the columnar layout fits the same information in 6 rows while still keeping the key + description pairing readable.
Columns are built with lipgloss.JoinHorizontal so a long description in any column does not push the other columns out of alignment.
func (*UIRenderer) PrintPlanCompact ¶
func (r *UIRenderer) PrintPlanCompact(blocks []CommandBlock, outputs []*CommandOutput)
PrintPlanCompact imprime plano em formato compacto
func (*UIRenderer) PrintPlanFull ¶
func (r *UIRenderer) PrintPlanFull(blocks []CommandBlock, outputs []*CommandOutput, validator *CommandValidator)
PrintPlanFull imprime plano em formato completo
func (*UIRenderer) PrintPrompt ¶
func (r *UIRenderer) PrintPrompt() string
PrintPrompt imprime o prompt de entrada
func (*UIRenderer) RenderAssistantResponseTimelineEvent ¶ added in v1.119.1
func (r *UIRenderer) RenderAssistantResponseTimelineEvent(icon, title, content, color string)
RenderAssistantResponseTimelineEvent draws the same card as RenderTimelineEvent but types the body progressively so the final assistant message feels alive instead of a single paste. Reserved for the model's "RESPOSTA/RESUMO" card — tool calls and reasoning still use the instant path because typing those would slow the agent loop down.
The body is glamour-rendered markdown, so it wraps with wrapStructured (not wrapText): replies that embed YAML/JSON/code keep their indentation instead of collapsing flush-left.
func (*UIRenderer) RenderBatchHeader ¶ added in v1.45.0
func (r *UIRenderer) RenderBatchHeader(totalActions int)
RenderBatchHeader exibe um cabeçalho indicando o início de um lote — uma régua titulada responsiva no lugar do sanduíche de ═ em tela cheia.
func (*UIRenderer) RenderBatchSummary ¶ added in v1.45.0
func (r *UIRenderer) RenderBatchSummary(successCount, total int, hasError bool)
RenderBatchSummary exibe o resultado final do lote
func (*UIRenderer) RenderMarkdownTimelineEvent ¶ added in v1.43.7
func (r *UIRenderer) RenderMarkdownTimelineEvent(icon, title, renderedMarkdownANSI, color string)
RenderMarkdownTimelineEvent renderiza markdown (já convertido para ANSI fora) dentro do card. Usa wrapStructured (não o wrapText de RenderTimelineEvent) para que YAML/JSON/código embutidos no markdown preservem a indentação dentro do card — o mesmo fix aplicado ao envelope de chat.
func (*UIRenderer) RenderModeBanner ¶ added in v1.119.0
func (r *UIRenderer) RenderModeBanner(icon, title string, color string, fields [][2]string)
RenderModeBanner draws the entry-banner used by /coder and /agent. Layout:
╭── 🛠 CODER MODE ─────────────────────────────╮ │ Objective · <query> │ │ Workspace · <wd> │ │ Policy · read-only por padrão · … │ ╰───────────────────────────────────────────────╯
Fields is a slice of (label, value) pairs so callers can pass any mode-specific metadata without growing the function signature. The label is dimmed (gray) and the value rendered in the default foreground so the eye lands on the value first.
func (*UIRenderer) RenderResponseEnvelope ¶ added in v1.119.2
func (r *UIRenderer) RenderResponseEnvelope(opts ResponseEnvelopeOptions)
RenderResponseEnvelope paints the assistant's reply: titled rule with the bilateral labels, indented body (wrapStructured preserves the indentation of glamour-rendered YAML/JSON/code), dim telemetry footer.
func (*UIRenderer) RenderStreamBoxEnd ¶ added in v1.47.8
func (r *UIRenderer) RenderStreamBoxEnd(color string)
RenderStreamBoxEnd closes the streamed section with a blank separator (the sóbrio treatment has no frame to mirror).
func (*UIRenderer) RenderStreamBoxStart ¶ added in v1.47.8
func (r *UIRenderer) RenderStreamBoxStart(icon, title, color string)
streamBoxHeaderWidth captures the visible width of the header drawn by RenderStreamBoxStart so RenderStreamBoxEnd can produce a footer of the same length instead of stretching to the terminal edge. It is package- level because the start/end pair runs on the same goroutine inside a tool-execution loop; concurrent streaming boxes are not supported in this renderer, so a sync.Mutex would be ceremonial overhead. RenderStreamBoxStart opens a streamed section in the sóbrio treatment: a colored bold title line; the streamed output indents beneath it via StreamOutput. No frame to close, so RenderStreamBoxEnd only restores breathing room.
func (*UIRenderer) RenderThinking ¶ added in v1.40.0
func (r *UIRenderer) RenderThinking(thought string)
RenderThinking exibe o pensamento da IA
func (*UIRenderer) RenderTimelineEvent ¶ added in v1.40.0
func (r *UIRenderer) RenderTimelineEvent(icon, title, content, color string)
RenderTimelineEvent desenha um "card" estilizado com:
- cabeçalho `╭── icon title ─────╮` que se estende até a largura do conteúdo,
- bordas laterais `│ … │` em cada linha,
- rodapé `╰──────────────────╯` do TAMANHO DO CONTEÚDO (não da tela).
Antes, o rodapé ia até a borda direita do terminal, dando uma sensação de "vazamento" quando o conteúdo era pequeno. A nova versão calcula a largura alvo como o maior entre (a) maior linha de conteúdo + padding e (b) largura do header — limitada pelo terminal. Lipgloss faz o cálculo de largura visível corretamente (ANSI-aware) via lipgloss.Width.
func (*UIRenderer) RenderToolCall ¶ added in v1.40.0
func (r *UIRenderer) RenderToolCall(toolName, rawArgs string)
RenderToolCall exibe a chamada da ferramenta de forma limpa (escondendo Base64 e sujeira HTML)
func (*UIRenderer) RenderToolCallMinimal ¶ added in v1.52.0
func (r *UIRenderer) RenderToolCallMinimal(toolName, rawArgs string, current, total int)
RenderToolCallMinimal exibe a chamada de ferramenta em modo compacto
func (*UIRenderer) RenderToolCallWithProgress ¶ added in v1.45.0
func (r *UIRenderer) RenderToolCallWithProgress(toolName, rawArgs string, current, total int)
RenderToolCallWithProgress exibe a chamada da ferramenta em formato de CARD (Box), limpando barras invertidas visuais e mostrando o progresso.
func (*UIRenderer) RenderToolResult ¶ added in v1.40.0
func (r *UIRenderer) RenderToolResult(output string, isError bool)
RenderToolResult exibe o resultado da execução. O glifo do título vem do vocabulário canônico (✓/✗, 1 célula, sem propriedade Emoji) e herda a cor da borda do card — verde/vermelho continuam carregando o estado.
func (*UIRenderer) RenderToolResultMinimal ¶ added in v1.52.0
func (r *UIRenderer) RenderToolResultMinimal(output string, isError bool)
RenderToolResultMinimal exibe o resultado em modo compacto
func (*UIRenderer) SetSkipClearOnNextDraw ¶
func (r *UIRenderer) SetSkipClearOnNextDraw(skip bool)
SetSkipClearOnNextDraw define se o próximo clear deve ser pulado
func (*UIRenderer) ShowInPager ¶
func (r *UIRenderer) ShowInPager(text string) error
ShowInPager abre texto em pager (less/more)
func (*UIRenderer) StreamOutput ¶ added in v1.47.8
func (r *UIRenderer) StreamOutput(line string)
StreamOutput emite uma linha de output streamado sob o título da seção (sóbrio: indentação em vez de barra lateral).
func (*UIRenderer) Style ¶ added in v1.119.0
func (r *UIRenderer) Style() UIStyle
Style returns the resolved UI style for this renderer.
type UIStyle ¶ added in v1.119.0
type UIStyle int
UIStyle selects how the renderer dispatches tool-call output across the timeline. The same enum drives both /coder and /agent paths; the env var CHATCLI_CODER_UI feeds it (legacy name kept on purpose — it now controls both modes, see DefaultUIStyleFromEnv).
const ( // UIStyleFull renders every tool call inside a bordered card, // every reasoning block in its own panel. Best for supervised // /agent runs where the user reviews each action. UIStyleFull UIStyle = iota // UIStyleCompact renders one-line tool calls (↻/✓) so a long // /coder session with dozens of tool invocations stays scannable. UIStyleCompact // UIStyleMinimal renders boxed tool calls with truncated reasoning. // Sits between Full and Compact: lighter than Full, more context // than Compact. UIStyleMinimal )
func DefaultUIStyleFromEnv ¶ added in v1.119.0
func DefaultUIStyleFromEnv() UIStyle
DefaultUIStyleFromEnv reads CHATCLI_CODER_UI and maps it to a UIStyle. Unset / "full" / "false" / "0" → Full. "compact" → Compact. "minimal" / "min" / "true" / "1" → Minimal. Legacy: "compact" used to imply Minimal in some call sites — kept as Compact here because that matches the explicit user intent of typing "compact".
Source Files
¶
- command_allowlist.go
- command_executor.go
- command_validator.go
- context_manager.go
- context_recovery.go
- error_classifier.go
- file_staleness.go
- inline_code_classifier.go
- json_recovery.go
- microcompact.go
- path_validator.go
- process_manager.go
- process_manager_unix.go
- quote_normalizer.go
- read_path_validator.go
- response_envelope.go
- safety_config.go
- session_workspace.go
- shell_parser.go
- task_integration.go
- task_tracker.go
- tool_orchestration.go
- tool_result.go
- tool_result_budget.go
- tool_result_pairing.go
- toolcall_parser.go
- types.go
- typewriter_pacing.go
- ui_renderer.go
Directories
¶
| Path | Synopsis |
|---|---|
|
* ChatCLI - AskUser request/answer types and parsing.
|
* ChatCLI - AskUser request/answer types and parsing. |
|
* ChatCLI - Command Line Interface for LLM interaction * Copyright (c) 2024 Edilson Freitas * License: Apache-2.0
|
* ChatCLI - Command Line Interface for LLM interaction * Copyright (c) 2024 Edilson Freitas * License: Apache-2.0 |
|
* ChatCLI - Command Line Interface for LLM interaction * Copyright (c) 2024 Edilson Freitas * License: Apache-2.0
|
* ChatCLI - Command Line Interface for LLM interaction * Copyright (c) 2024 Edilson Freitas * License: Apache-2.0 |
|
* Package park: durable snapshots for the agent ReAct loop.
|
* Package park: durable snapshots for the agent ReAct loop. |
|
* ChatCLI - Command Line Interface for LLM interaction * Copyright (c) 2024 Edilson Freitas * License: Apache-2.0
|
* ChatCLI - Command Line Interface for LLM interaction * Copyright (c) 2024 Edilson Freitas * License: Apache-2.0 |
|
convergence
* ChatCLI - Convergence: per-scorer circuit breaker.
|
* ChatCLI - Convergence: per-scorer circuit breaker. |
|
lessonq
* ChatCLI - Lesson Queue: idempotency key derivation.
|
* ChatCLI - Lesson Queue: idempotency key derivation. |
|
* ChatCLI - Command Line Interface for LLM interaction * Copyright (c) 2024 Edilson Freitas * License: Apache-2.0
|
* ChatCLI - Command Line Interface for LLM interaction * Copyright (c) 2024 Edilson Freitas * License: Apache-2.0 |
|
* ChatCLI - Command Line Interface for LLM interaction * Copyright (c) 2024 Edilson Freitas * License: Apache-2.0
|
* ChatCLI - Command Line Interface for LLM interaction * Copyright (c) 2024 Edilson Freitas * License: Apache-2.0 |
|
* ChatCLI - Builtin agent model/effort metadata * Copyright (c) 2024 Edilson Freitas * License: Apache-2.0 * * Shared base struct providing Model() and Effort() for built-in workers.
|
* ChatCLI - Builtin agent model/effort metadata * Copyright (c) 2024 Edilson Freitas * License: Apache-2.0 * * Shared base struct providing Model() and Effort() for built-in workers. |