code

package
v0.16.0 Latest Latest
Warning

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

Go to latest
Published: Aug 28, 2026 License: MIT Imports: 38 Imported by: 0

Documentation

Overview

Package code provides workspace-scoped tools for reading, writing, patching, searching, and executing commands.

These tools are powerful and intentionally not a sandbox. Downstream agents decide which tools to expose and which guardrails, workspace roots, read boundaries, and shell policies to apply.

Package code holds the file-editing tools used by the coder profile. All path-taking tools share a Workspace value that enforces a root boundary — no tool in this package may touch paths outside that root.

Index

Constants

View Source
const (
	ToolNameBash           tools.ToolName = "bash"
	ToolNameRead           tools.ToolName = "read"
	ToolNameWrite          tools.ToolName = "write"
	ToolNameWriteAppend    tools.ToolName = "write_append"
	ToolNameEdit           tools.ToolName = "edit"
	ToolNameLs             tools.ToolName = "ls"
	ToolNameGrep           tools.ToolName = "grep"
	ToolNameGlob           tools.ToolName = "glob"
	ToolNameSavePlan       tools.ToolName = "save_plan"
	ToolNameSavePlanAppend tools.ToolName = "save_plan_append"
	ToolNameApplyPatch     tools.ToolName = "apply_patch"
	ToolNameUpdatePlan     tools.ToolName = "update_plan"
	ToolNameFileMap        tools.ToolName = "file_map"
	ToolNameRetrieveCode   tools.ToolName = "retrieve_code"

	// Long-running process management — paired with bash background=true.
	ToolNameBashOutput    tools.ToolName = "bash_output"
	ToolNameStopProcess   tools.ToolName = "stop_process"
	ToolNameListProcesses tools.ToolName = "list_processes"
)

Tool name constants for the file-editing toolset.

View Source
const PlansDir = ".zarlcode/plans"

PlansDir is the workspace-relative directory where save_plan writes markdown plan documents. Hidden under .zarlcode/ alongside the other zarlcode config (skills, agents, prompts) so the workspace root stays uncluttered. Mkdirs lazily on first save.

Variables

View Source
var ErrParseStepStatus = errors.New("invalid input provided to parse to StepStatus")
View Source
var ErrProcessNotFound = errors.New("processmgr: process not found")

ErrProcessNotFound is returned by Output / Kill / Info when the process_id doesn't match any tracked process.

View Source
var ErrTooManyProcesses = errors.New("processmgr: too many live processes")

ErrTooManyProcesses is returned by StartProcess when the live count is already at the configured cap.

View Source
var StepStatuses = stepStatusesContainer{
	PENDING: StepStatus{
		// contains filtered or unexported fields
	},
	INPROGRESS: StepStatus{
		// contains filtered or unexported fields
	},
	COMPLETED: StepStatus{
		// contains filtered or unexported fields
	},
}

StepStatuses is a main entry point using the StepStatus type. It it a container for all enum values and provides a convenient way to access all enum values and perform operations, with convenience methods for common use cases.

Functions

func ExhaustiveStepStatuses

func ExhaustiveStepStatuses(f func(StepStatus))

ExhaustiveStepStatuses iterates over all enum values and calls the provided function for each value. This function is useful for performing operations on all valid enum values in a loop.

func GitHead

func GitHead(ctx context.Context, workspace string) string

GitHead returns the workspace's current HEAD commit, or "" when the workspace isn't a git repository. Capture it before a run starts so a HEAD the agent moves mid-run doesn't shift the diff baseline.

func NewMemoryPlanStore

func NewMemoryPlanStore() *memoryPlanStore

NewMemoryPlanStore returns a PlanStore backed by an in-memory value. No locking — the runner serialises tool calls so concurrent SetPlan from multiple tool invocations can't happen.

func PatchExistingPaths added in v0.10.0

func PatchExistingPaths(text string) []string

PatchExistingPaths returns only paths that a patch expects to already exist: Update and Delete sources. Add destinations and Move-to destinations are creations and therefore excluded.

func PatchPaths

func PatchPaths(text string) []string

PatchPaths scans patch text for the file headers it touches and returns workspace-relative paths in declaration order. Add / Update / Delete each contribute their target path; an Update followed by "*** Move to:" contributes both the original and the destination so observability layers can diff both ends.

Returns nil on a patch that doesn't parse — callers treat that as "no observable paths" and skip snapshotting. The full parser inside Execute will surface the syntax error to the model.

Exposed for pkg/agent/diffrecorder: apply_patch can mutate multiple files in a single call, so the single-"path"-argument extractor the rest of the recordable tools share doesn't fit. The recorder calls this to enumerate the files it should snapshot before dispatching apply_patch.

func UntrackedFiles

func UntrackedFiles(ctx context.Context, workspace string) map[string]bool

UntrackedFiles returns the set of untracked paths git reports for the workspace. Capture it before a run and pass it to WorktreeDiff as exclude so pre-existing untracked files don't count as agent changes. Empty map when the workspace isn't a git repository.

func WorktreeDiff

func WorktreeDiff(ctx context.Context, workspace, base string, exclude map[string]bool) string

WorktreeDiff returns the unified diff of the workspace against base ("" means HEAD): tracked changes from `git diff base`, plus a synthesized /dev/null diff for each untracked file not in exclude (plain diff omits untracked files). Best-effort by design — it returns "" when nothing changed or git is unavailable, and a failed tracked-file diff doesn't stop untracked capture.

Types

type ApplyPatchArgs

type ApplyPatchArgs struct {
	Patch string `json:"patch" doc:"The full patch text including the \x60*** Begin Patch\x60 / \x60*** End Patch\x60 envelope."`
}

ApplyPatchArgs is the typed argument struct ApplyPatchTool.Execute decodes into via tools.DecodeArgs.

type ApplyPatchTool

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

ApplyPatchTool applies a Codex-style "stripped-down diff" patch across multiple files in the workspace. The patch grammar is the envelope-and-hunks format documented at openai/codex/codex-rs/apply-patch/apply_patch_tool_instructions.md — we implement it verbatim so models trained against it (GPT-5 family, Claude) emit patches the parser accepts as-is.

The tool buys two things over the existing edit/write tools:

  1. Multi-file changes commit atomically — every file change is staged in memory, and the commit phase only fires after every hunk has applied cleanly. A bad hunk halts the whole patch with no half-written files.
  2. Models that know the format produce far more reliable diffs than they do dictating search/replace strings, especially for overlapping or sequence-sensitive edits.

func NewApplyPatchTool

func NewApplyPatchTool(ws Workspace) *ApplyPatchTool

NewApplyPatchTool returns the unified-diff patch tool bound to ws.

func (*ApplyPatchTool) Definition

func (t *ApplyPatchTool) Definition() tools.ToolSpec

Definition advertises apply_patch with the single patch parameter (the full *** Begin Patch / *** End Patch envelope); Mutates is true because a committed patch adds, updates, deletes, or moves workspace files.

func (*ApplyPatchTool) Execute

Execute parses the patch, plans all file mutations, then commits them in one pass. Errors at any stage abort the whole operation.

type BashArgs

type BashArgs struct {
	Command        string `json:"command" doc:"Shell command for bash -c (or sh -c)."`
	TimeoutSeconds int    `json:"timeout_seconds,omitempty" doc:"Timeout seconds; max 600. Ignored in background."`
	Background     bool   `json:"background,omitempty" doc:"Start managed background process."`
	Description    string `json:"description,omitempty" doc:"Short process label."`
}

BashArgs is the typed argument struct BashTool.Execute decodes into via tools.DecodeArgs. Field tags drive both JSON decoding and SchemaFor schema generation.

type BashOption

type BashOption func(*BashTool)

BashOption tunes BashTool construction. The variadic options pattern is consistent with the rest of pkg/ai/tools.

func WithEnv

func WithEnv(env map[string]string) BashOption

WithEnv appends child-process environment variables to every shell command. Values override the inherited process environment for the spawned shell and its children.

func WithProcessManager

func WithProcessManager(m *ProcessManager) BashOption

WithProcessManager enables managed background processes — the bash tool returns a process_id usable by bash_output / kill_bash / list_processes instead of a raw PID + log path.

func WithSandbox

func WithSandbox(sb Sandboxer) BashOption

WithSandbox confines every foreground command behind sb (background commands go through the ProcessManager, which carries its own sandboxer — wire the same instance to both or they drift). Nil is a no-op so callers can pass through an unset optional.

type BashOutputArgs

type BashOutputArgs struct {
	ProcessID    ProcessID          `json:"process_id" doc:"Process id returned by bash(background=true)."`
	StdoutCursor int                `json:"stdout_cursor,omitempty" doc:"Last-seen stdout cursor; omit on first call to read from start."`
	StderrCursor int                `json:"stderr_cursor,omitempty" doc:"Last-seen stderr cursor; omit on first call to read from start."`
	MaxLines     int                `json:"max_lines,omitempty" doc:"Cap returned lines per stream (default 1000, 0 = no cap)."`
	Output       tools.OutputFormat `` /* 132-byte string literal not displayed */
}

BashOutputArgs is the typed argument struct BashOutputTool.Execute decodes into via tools.DecodeArgs. Cursor and max_lines are integers, not uint64, because JSON numbers + the runner's parameter normalisation prefer int.

type BashOutputResult

type BashOutputResult struct {
	Snapshot OutputSnapshot
	Output   tools.OutputFormat
}

BashOutputResult is bash_output's structured Data: the polled snapshot plus the requested output mode. A consumer renders from Snapshot directly; the model sees String(): labelled sections or the JSON snapshot, per Output.

func (BashOutputResult) String

func (r BashOutputResult) String() string

String renders the model-facing form for the requested output mode. Stdout and stderr are run through tools.RedactSecrets first — the same best-effort scrub the foreground bash path applies — so a backgrounded `printenv` (or any command that prints a token) doesn't bypass redaction on its way into the conversation history.

type BashOutputTool

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

BashOutputTool reads incremental stdout/stderr from a background process started via bash(background=true). The agent passes the cursor returned by the previous call to avoid re-reading the same content — same pattern as Claude Code's BashOutput, so models already trained on that surface know what to do.

func NewBashOutputTool

func NewBashOutputTool(m *ProcessManager) *BashOutputTool

NewBashOutputTool returns the tool that reads buffered output from a background process managed by m.

func (*BashOutputTool) Definition

func (*BashOutputTool) Definition() tools.ToolSpec

Definition advertises bash_output with process_id (required), stdout/stderr cursors, max_lines, and a labeled|json output enum; polling never mutates.

func (*BashOutputTool) Execute

Execute requires process_id, defaults max_lines to 1000, and reads the incremental snapshot from the process manager at the supplied cursors (negative cursors read from the start; unknown ids are NotFound). The result's String() redacts secrets line by line before the model sees the output.

type BashTool

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

BashTool runs a shell command with cwd set to the workspace root.

procMgr (optional) routes background=true through a managed process with in-memory output capture so the agent can poll via bash_output and kill via stop_process. When nil, background mode falls back to the legacy "detach + write to log file" path. The zarlcode wires a real ProcessManager via WithProcessManager; other consumers (headless tests) can omit it and keep the simpler log-file behaviour.

func NewBashTool

func NewBashTool(ws Workspace, opts ...BashOption) *BashTool

NewBashTool returns the shell tool rooted at ws. Without a process manager (WithProcessManager), background execution degrades to the legacy detach-and-log path.

func (*BashTool) Definition

func (t *BashTool) Definition() tools.ToolSpec

Definition advertises bash with command (required), timeout_seconds, background, and description parameters; the spec text documents the 1MB output cap, 300s default / 600s max timeout, background process management, and the pkill -f footgun. Mutates stays false — a shell command is not a tracked file edit and must not count as patch-producing work — but AffectsWorkspace is true: a command can write files or mutate git/env state, so cache-invalidation and plan-first gating treat it as workspace-changing via ChangesWorkspace.

func (*BashTool) Execute

func (t *BashTool) Execute(ctx context.Context, call tools.ToolCall) (*tools.ToolResult, error)

Execute runs the command via /bin/bash -c (falling back to /bin/sh) with cwd at the workspace root. background=true — or a command matching longRunningPatterns, which is auto-backgrounded with a notice — returns immediately with a process handle. Foreground runs cap output at bashMaxOutput (1 MB), enforce the clamped timeout by SIGKILLing the whole process group, strip ANSI, redact secrets, and append the exit code; the ProcessEffect carries timeout/truncation flags.

type ContextSandboxer added in v0.16.0

type ContextSandboxer interface {
	SandboxContext(ctx context.Context, cmd *exec.Cmd) error
}

ContextSandboxer selects confinement using call-scoped policy such as a spawned task's work mode. Implementations must retain the same composition contract as Sandboxer.

type EditArgs

type EditArgs struct {
	Path       string `json:"path" doc:"Path inside the workspace."`
	OldString  string `json:"old_string" doc:"Exact text to replace."`
	NewString  string `json:"new_string" doc:"Replacement text."`
	ReplaceAll bool   `json:"replace_all,omitempty" doc:"Replace every occurrence (default false)."`
}

EditArgs is the typed argument struct EditTool.Execute decodes into via tools.DecodeArgs. Field tags drive both JSON decoding and SchemaFor schema generation.

type EditFileHLArgs

type EditFileHLArgs struct {
	Path string `json:"path" doc:"Path inside the workspace."`

	Edits []HashlineEdit `json:"edits" doc:"Atomic edits; pass an array even for one."`
}

EditFileHLArgs is the typed argument struct EditFileHLTool.Execute decodes.

type EditFileHLTool

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

EditFileHLTool edits a workspace file through the read output's line/hash anchors. Its Definition returns ToolNameEdit — it replaces EditTool in the standard toolset without changing the name the model sees.

func NewEditFileHLTool

func NewEditFileHLTool(ws Workspace) *EditFileHLTool

NewEditFileHLTool returns the hashline edit tool bound to ws.

func (*EditFileHLTool) Definition

func (t *EditFileHLTool) Definition() tools.ToolSpec

Definition advertises edit as a mutating line-anchor edit tool.

func (*EditFileHLTool) Execute

Execute verifies the requested line/hash anchors against the current file and then performs one byte-level splice at line boundaries. The old file content never has to be reproduced in the arguments; stale anchors are refused before any write occurs.

type EditTool

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

EditTool performs an exact-string replacement in a workspace file. It is retained for legacy consumers and focused tests; the standard coderunner / zarlcode tool surface now exposes EditFileHLTool under the same `edit` name.

Without replace_all, old_string must appear exactly once — otherwise the edit is rejected to prevent accidental whole-file rewrites.

Whitespace-tolerant fallback

When exact match returns zero hits (and replace_all is off), the tool re-tries on a line-normalised view: per-line trailing whitespace and the \r in CRLF are stripped from both old_string and the file before the search. If exactly one match exists in the normalised view, the corresponding byte range in the *original* file is replaced. The model's exact whitespace inside lines and around the splice point is preserved; the model is informed in the result message so it can see the fuzzy path fired.

Ambiguity is still refused: if normalisation produces multiple hits, the edit is rejected with the count so the model adds more context.

Argument size cap

Both old_string and new_string are capped at maxEditArgBytes (default 64KB, tunable via CODE_EDIT_MAX_BYTES — see limits.go). The cap is generous — modern providers handle 64KB string args cleanly. Historical context: older llama.cpp builds dropped characters inside multi-KB streaming tool-call JSON, which is why this cap exists at all. The cap is tighter than write's because edit always carries two such args (old_string + new_string) and the failure mode is per-arg.

func NewEditTool

func NewEditTool(ws Workspace) *EditTool

NewEditTool returns the legacy exact-string edit tool bound to ws.

func (*EditTool) Definition

func (t *EditTool) Definition() tools.ToolSpec

Definition advertises the legacy exact-string edit shape: path, old_string, new_string, and replace_all. Mutates is true because a successful edit rewrites the file in place.

func (*EditTool) Execute

func (t *EditTool) Execute(_ context.Context, call tools.ToolCall) (*tools.ToolResult, error)

Execute caps old_string and new_string at maxEditArgBytes (64KB default) up front, then — holding the path lock across the read-modify-write — requires a unique exact match unless replace_all is set, falling back to a single-hit whitespace-normalised match when the exact search finds nothing. Ambiguity at either stage is refused with the match count; success emits a FileModify effect.

This legacy exact-string path remains useful for narrow consumers, but the standard coding surface prefers EditFileHLTool's anchored workflow.

type FileMapArgs added in v0.2.1

type FileMapArgs struct {
	// Root scopes the walk to a subtree. Empty means the workspace root.
	Root string `json:"root,omitempty" doc:"Optional sub-tree to scan, relative to the workspace. Empty = workspace root."`
	// Pattern selects files under Root using the same doublestar glob semantics as glob. Empty defaults to *.go.
	Pattern string `` /* 127-byte string literal not displayed */
	// IncludeTests includes *_test.go files. Default false.
	IncludeTests bool `json:"include_tests,omitempty" doc:"Include Go test files (*_test.go). Default false."`
	// MaxFiles caps the number of files parsed. Default 200.
	MaxFiles int `json:"max_files,omitempty" doc:"Cap on parsed files. Default 200."`
	// Output selects labelled plaintext or JSON. Empty means labelled.
	Output tools.OutputFormat `json:"output,omitempty" enum:"labeled,json" doc:"Output format: \"labeled\" (default) or \"json\"."`
}

FileMapArgs configures the syntax-aware file map.

type FileMapResult added in v0.2.1

type FileMapResult struct {
	Payload fileMapPayload
	Output  tools.OutputFormat
}

FileMapResult is file_map's structured result and model-facing renderer.

func (FileMapResult) String added in v0.2.1

func (r FileMapResult) String() string

String renders either compact labelled text or JSON.

type FileMapTool added in v0.2.1

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

FileMapTool builds a deterministic, syntax-aware outline for source files.

func NewFileMapTool added in v0.2.1

func NewFileMapTool(ws Workspace, opts ...ReadOption) *FileMapTool

NewFileMapTool returns a deterministic source-outline tool bound to ws.

func (*FileMapTool) Definition added in v0.2.1

func (t *FileMapTool) Definition() tools.ToolSpec

Definition advertises file_map as a read-only AST outline tool.

func (*FileMapTool) Execute added in v0.2.1

func (t *FileMapTool) Execute(ctx context.Context, call tools.ToolCall) (*tools.ToolResult, error)

Execute parses matching files and returns their deterministic outlines.

type GlobArgs

type GlobArgs struct {
	// Pattern is the doublestar-flavoured glob. See type docs for
	// semantics. Required.
	Pattern string `` /* 193-byte string literal not displayed */
	// Root scopes the walk to a subtree (workspace-relative). Empty
	// means the whole workspace.
	Root string `json:"root,omitempty" doc:"Optional sub-tree to scope the walk to (workspace-relative). Empty = whole workspace."`
	// IncludeDirs adds directory entries to the result. Default
	// false — most callers want files only.
	IncludeDirs bool `json:"include_dirs,omitempty" doc:"Include directory entries in results. Default false (files only)."`
	// MaxResults caps the returned list. Default 200; a workspace
	// of 50k files would otherwise produce a multi-MB result.
	MaxResults int `` /* 130-byte string literal not displayed */
	// Output selects the model-facing rendering. Empty = labelled.
	Output tools.OutputFormat `` /* 129-byte string literal not displayed */
}

GlobArgs is the typed argument struct shared by both glob impls. Field tags drive both JSON decoding and SchemaFor schema generation.

type GlobResult

type GlobResult struct {
	Entries    []globEntry
	Pattern    string
	Root       string
	Truncated  bool
	MaxResults int
	Output     tools.OutputFormat
}

GlobResult is glob's structured Data: the matched entries plus the call inputs needed to render. A consumer (the TUI) renders from Entries directly instead of re-parsing a string, while the model sees String(): labelled plaintext or the JSON payload, per the requested Output.

func (GlobResult) String

func (r GlobResult) String() string

String renders the model-facing form for the requested output mode.

type GlobTool

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

GlobTool enumerates workspace paths matching a glob pattern. The output field selects the model-facing rendering (labelled plaintext, the default, or JSON); the structured entries are identical either way.

Labelled output shape:

matches: 3  pattern: *.go
  main.go         142B
  pkg/foo/foo.go  256B

JSON output shape:

{
  "pattern": "*.go",
  "matches": 3,
  "truncated": false,
  "entries": [
    {"path": "main.go",         "size": 142, "dir": false},
    {"path": "pkg/foo/foo.go",  "size": 256, "dir": false}
  ]
}

Pattern semantics (matched by doublestar.Match):

*.go         — match files anywhere in the tree whose basename ends in .go
**/*.go      — same, but explicit (the more common spelling)
pkg/**/*.go  — go files anywhere under pkg/
pkg/agent/*  — direct children of pkg/agent (single segment)
**/main.go   — every main.go in the tree

A bare basename pattern (no path separator) matches anywhere recursively. A pattern containing `/` is rooted against the workspace (or the `root` arg) and respects path structure literally.

func NewGlobTool

func NewGlobTool(ws Workspace, opts ...ReadOption) *GlobTool

NewGlobTool returns a JSON glob tool bound to the given workspace.

func (*GlobTool) Definition

func (t *GlobTool) Definition() tools.ToolSpec

Definition advertises glob with pattern (required), root, include_dirs, max_results (default 200), and a labeled|json output enum; enumeration never mutates.

func (*GlobTool) Execute

func (t *GlobTool) Execute(ctx context.Context, call tools.ToolCall) (*tools.ToolResult, error)

Execute walks the workspace (or the configured sub-root) and returns every path matching the pattern, in lexical order, in the tool's configured output format (labelled text by default).

type GrepArgs

type GrepArgs struct {
	Pattern         string             `json:"pattern" doc:"Regular expression."`
	Path            string             `json:"path,omitempty" doc:"Subpath inside workspace (default = workspace root)."`
	Glob            string             `json:"glob,omitempty" doc:"Glob filter (e.g. *.go)."`
	CaseInsensitive bool               `json:"case_insensitive,omitempty" doc:"Case-insensitive match."`
	MaxResults      int                `json:"max_results,omitempty" doc:"Cap on returned matches (default 100)."`
	Output          tools.OutputFormat `json:"output,omitempty" enum:"labeled,json" doc:"Output format: \"labeled\" (default, hits grouped by file) or \"json\"."`
}

GrepArgs is the typed argument struct GrepTool.Execute decodes into via tools.DecodeArgs. Field tags drive both JSON decoding and SchemaFor schema generation.

type GrepHit

type GrepHit struct {
	File string `json:"file"`
	Line int    `json:"line"`
	Text string `json:"text"`
}

GrepHit is one match in a grep result: the file, line number, and the matched line's text.

type GrepResult

type GrepResult struct {
	Hits       []GrepHit
	Truncated  bool
	Output     tools.OutputFormat
	Pattern    string
	Path       string
	Glob       string
	MaxResults int
}

GrepResult is grep's structured Data: the matches, whether the max-results cap truncated them, and the call inputs needed to render. It is the tool's re-parsing a string, while the model sees String(): labelled plaintext or a JSON object, per the requested Output.

func (GrepResult) String

func (r GrepResult) String() string

String renders the model-facing form for the requested output mode.

type GrepTool

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

GrepTool wraps ripgrep and returns structured matches. The output field selects the model-facing rendering (labelled plaintext or JSON); the structured hits are identical either way.

func NewGrepTool

func NewGrepTool(ws Workspace, opts ...ReadOption) *GrepTool

NewGrepTool returns the content-search tool bound to ws.

func (*GrepTool) Definition

func (t *GrepTool) Definition() tools.ToolSpec

Definition advertises grep with pattern (required), path, glob, case_insensitive, max_results (default 100), and a labeled|json output enum; searching never mutates.

func (*GrepTool) Execute

func (t *GrepTool) Execute(ctx context.Context, call tools.ToolCall) (*tools.ToolResult, error)

Execute requires ripgrep on PATH and a non-empty pattern, scopes the search to the resolved path (workspace root by default), and caps hits at max_results (grepDefaultMaxResults = 100 when unset). rg's exit 1 on zero matches is treated as success with an empty hit list.

type HashlineEdit added in v0.3.0

type HashlineEdit struct {
	StartLine int    `json:"start_line" doc:"1-based line from read."`
	StartHash string `json:"start_hash" doc:"3/4-char hash from read."`
	EndLine   int    `json:"end_line,omitempty" doc:"Inclusive range end; omit for one line."`
	EndHash   string `json:"end_hash,omitempty" doc:"Hash for end_line."`
	NewString string `json:"new_string,omitempty" doc:"Replacement/insertion bytes, including intended newlines."`
	Mode      string `` /* 135-byte string literal not displayed */
}

HashlineEdit describes one edit inside EditFileHLArgs.Edits.

type ListProcessesArgs

type ListProcessesArgs struct {
	Output tools.OutputFormat `json:"output,omitempty" enum:"labeled,json" doc:"Output format: \"labeled\" (default, one block per process) or \"json\"."`
}

ListProcessesArgs carries only the output-format choice — the tool otherwise takes no arguments.

type ListProcessesResult

type ListProcessesResult struct {
	Procs  []ProcessInfo
	Output tools.OutputFormat
}

ListProcessesResult is list_processes's structured Data: the process snapshot plus the requested output mode. A consumer renders from Procs directly; the model sees String(): labelled blocks or the JSON list, per Output.

func (ListProcessesResult) String

func (r ListProcessesResult) String() string

String renders the model-facing form for the requested output mode.

type ListProcessesTool

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

ListProcessesTool returns a snapshot of every tracked process — live processes plus those that exited recently enough to still be in the reap window. The agent uses this to discover process_ids it forgot, audit what's running before spawning more work, and reconcile state after a long pause.

func NewListProcessesTool

func NewListProcessesTool(m *ProcessManager) *ListProcessesTool

NewListProcessesTool returns the tool that enumerates the background processes managed by m.

func (*ListProcessesTool) Definition

func (*ListProcessesTool) Definition() tools.ToolSpec

Definition advertises list_processes whose only parameter is the labeled|json output enum; listing never mutates.

func (*ListProcessesTool) Execute

Execute snapshots every tracked process from the manager — running plus recently exited — and returns a ListProcessesResult rendered per the requested output format; nothing past argument decoding can fail.

type LsArgs

type LsArgs struct {
	Path       string             `json:"path,omitempty" doc:"Directory inside workspace (default = root)."`
	ShowHidden bool               `json:"show_hidden,omitempty" doc:"Include dotfiles."`
	Output     tools.OutputFormat `` /* 138-byte string literal not displayed */
}

LsArgs is the typed argument struct LsTool.Execute decodes into via tools.DecodeArgs. Field tags drive both JSON decoding and SchemaFor schema generation.

type LsResult

type LsResult struct {
	Entries    []lsEntry
	Path       string
	ShowHidden bool
	Output     tools.OutputFormat
}

LsResult is ls's structured Data: the directory entries plus the call inputs needed to render. A consumer (the TUI) renders from Entries directly instead of re-parsing a string, while the model sees String(): labelled plaintext or a JSON array, per the requested Output.

func (LsResult) String

func (r LsResult) String() string

String renders the model-facing form for the requested output mode.

type LsTool

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

LsTool lists workspace directory entries. Hidden entries (starting with ".") are excluded by default. The output field selects the model-facing rendering (labelled plaintext or JSON); the structured entries are identical either way.

func NewLsTool

func NewLsTool(ws Workspace, opts ...ReadOption) *LsTool

NewLsTool returns the directory-listing tool bound to ws.

func (*LsTool) Definition

func (t *LsTool) Definition() tools.ToolSpec

Definition advertises ls with optional path, show_hidden, and a labeled|json output enum; listing never mutates.

func (*LsTool) Execute

func (t *LsTool) Execute(ctx context.Context, call tools.ToolCall) (*tools.ToolResult, error)

Execute resolves the directory (workspace root when path is empty), reads a single level only, skips dotfiles unless show_hidden, classifies entries as file/dir/symlink, and returns them sorted by name in the requested output format.

type OutputSnapshot

type OutputSnapshot struct {
	ID                 ProcessID `json:"process_id"`
	Running            bool      `json:"running"`
	ExitCode           *int      `json:"exit_code,omitempty"`
	Stdout             []string  `json:"stdout"`
	Stderr             []string  `json:"stderr"`
	StdoutCursor       uint64    `json:"stdout_cursor"`
	StderrCursor       uint64    `json:"stderr_cursor"`
	StdoutDroppedSince uint64    `json:"stdout_dropped_since,omitempty"`
	StderrDroppedSince uint64    `json:"stderr_dropped_since,omitempty"`
}

OutputSnapshot is the result of one bash_output call: incremental stdout/stderr since the supplied cursor plus a fresh cursor for the next poll. Dropped counters non-zero mean lines rotated out of the ring buffer between reads — the agent uses this signal to decide whether to throttle output (or accept a partial view).

type Plan

type Plan struct {
	Steps       []PlanStep `json:"steps"`
	Explanation string     `json:"explanation,omitempty"`
}

Plan is the structured state mutated by update_plan and rendered in the plan dock tab. Explanation is the optional one-liner the model sends with each update (e.g. "split step 2 into two steps") — surfaced in the pane so the user can see why the plan changed.

func (Plan) IsEmpty

func (p Plan) IsEmpty() bool

IsEmpty reports whether the plan has any steps. Used by the pane to decide between rendering the empty placeholder and the list.

type PlanStep

type PlanStep struct {
	Text   string     `json:"step"`
	Status StepStatus `json:"status"`
}

PlanStep is one entry in the ordered plan. Text is the human-readable description; Status is the lifecycle marker. Steps have no stable identity beyond their position — update_plan replaces the whole list, so callers don't need to thread IDs.

type PlanStore

type PlanStore interface {
	// SetPlan replaces the current plan with p. The store is expected
	// to broadcast the change to any listeners (typically the TUI)
	// before returning, so the user sees the new state immediately
	// after the tool call completes.
	SetPlan(p Plan)
	// GetPlan returns the current plan (zero-value Plan when none).
	GetPlan() Plan
}

PlanStore is the dependency seam between the update_plan tool and whatever process-wide state owns the live plan. Tools are wired in at registration with a concrete *shellModel implementation; tests use an in-memory fake.

Defined consumer-side here (not in zkit/agent/runner or zarlcode/tui) because the tool is the only direct caller. Other consumers read state via the same interface from wherever they happen to live.

type ProcessID added in v0.3.1

type ProcessID string

ProcessID identifies a managed background process.

func (ProcessID) String added in v0.3.1

func (id ProcessID) String() string

String returns the process identifier as a string.

type ProcessInfo

type ProcessInfo struct {
	ID          ProcessID `json:"process_id"`
	Command     string    `json:"command"`
	PID         int       `json:"pid"`
	CWD         string    `json:"cwd"`
	StartedAt   time.Time `json:"started_at"`
	Running     bool      `json:"running"`
	ExitedAt    time.Time `json:"exited_at"`
	ExitCode    int       `json:"exit_code,omitempty"`
	StdoutLines int       `json:"stdout_lines"`
	StderrLines int       `json:"stderr_lines"`
}

ProcessInfo is the public snapshot of a managed process. Returned by List and embedded in tool-result payloads.

type ProcessManager

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

func NewProcessManager

func NewProcessManager(ws ProcessWorkspace, opts ...ProcessManagerOption) *ProcessManager

NewProcessManager constructs a manager bound to a workspace. Apply options to tune caps + reap window; defaults are tuned for the zarlcode use case (16 concurrent processes, 10k lines per stream, 60s post-exit retention).

func (*ProcessManager) Close

func (m *ProcessManager) Close(ctx context.Context)

Close terminates every live process (with the same bounded escalation as KillAll) AND tears down the background reaper goroutine. Idempotent — safe to call multiple times. Use this instead of bare KillAll when the manager itself should not outlive the call (almost always the right answer on shell exit).

The reaper goroutine receives on a close channel; after Close returns its `reapDone` channel is closed too, so callers can observe full teardown by selecting on m.ReaperDone() if needed.

func (*ProcessManager) Info

func (m *ProcessManager) Info(id ProcessID) (ProcessInfo, error)

Info returns a single process's snapshot. Useful for /processes detail views; the tool layer uses List().

func (*ProcessManager) Kill

func (m *ProcessManager) Kill(id ProcessID, signal syscall.Signal) (int, error)

Kill sends signal to the process. SIGTERM (the default) gives the process 5s to exit cleanly before escalating to SIGKILL. SIGINT applies a similar two-stage escalation. SIGKILL is immediate. Returns the eventual exit code (or -1 if the process didn't exit in time).

func (*ProcessManager) KillAll

func (m *ProcessManager) KillAll(ctx context.Context)

KillAll terminates every live process and waits for them to exit, bounded by the supplied context. Use on shell shutdown so orphan processes don't leak past the TUI quit.

func (*ProcessManager) List

func (m *ProcessManager) List() []ProcessInfo

List returns a snapshot of all tracked processes (live + recently exited still in the reap window). Ordered newest-first so the most relevant process is at the top of the agent's view.

func (*ProcessManager) Output

func (m *ProcessManager) Output(id ProcessID, stdoutCursor, stderrCursor uint64, maxLines int) (OutputSnapshot, error)

Output returns incremental stdout/stderr for the named process. stdoutCursor / stderrCursor come from a prior call's snapshot; pass 0 on the first call to read from the start. maxLines caps the return per stream (0 = no cap).

func (*ProcessManager) SetOutputSink added in v0.11.1

func (m *ProcessManager) SetOutputSink(s ProcessOutputSink)

SetOutputSink installs (or clears) the process-exit output callback after construction. The app wires this once both the manager and the sink that persists its output exist. Set before starting background processes.

func (*ProcessManager) StartProcess

func (m *ProcessManager) StartProcess(command string) (ProcessID, error)

StartProcess spawns the command in the workspace root, captures stdout/stderr into ring buffers, and returns the assigned process_id. The process is detached from ctx — once started it outlives the originating tool call. The manager retains a reference until reapAfter elapses past its exit.

command runs through /bin/bash -c (falling back to /bin/sh) to match the synchronous bash tool's semantics — same env, same shell, same setsid isolation.

func (*ProcessManager) StartProcessContext added in v0.16.0

func (m *ProcessManager) StartProcessContext(ctx context.Context, command string) (ProcessID, error)

StartProcessContext is StartProcess with call-scoped sandbox policy. The context is consulted while preparing confinement but does not own the detached process lifetime; ProcessManager.Close/Kill remain the owners.

func (*ProcessManager) Wait added in v0.11.0

func (m *ProcessManager) Wait(id ProcessID) error

Wait blocks until the managed process has exited and its output pipes have drained.

type ProcessManagerOption

type ProcessManagerOption func(*ProcessManager)

ProcessManagerOption tunes the manager at construction.

func WithMaxAliveProcesses

func WithMaxAliveProcesses(n int) ProcessManagerOption

WithMaxAliveProcesses caps concurrent live processes. Hit it and StartProcess returns ErrTooManyProcesses without forking anything.

func WithProcessEnv

func WithProcessEnv(env map[string]string) ProcessManagerOption

WithProcessEnv appends child-process environment variables to every managed background shell command. Values override the inherited process environment.

func WithProcessOutputBuffer

func WithProcessOutputBuffer(lines int) ProcessManagerOption

WithProcessOutputBuffer sets the per-stream ring buffer cap (in lines). Lower values reduce memory pressure on chatty processes; higher values let the agent inspect more history.

func WithProcessOutputSink added in v0.11.1

func WithProcessOutputSink(s ProcessOutputSink) ProcessManagerOption

WithProcessOutputSink installs a callback that receives a background process's full accumulated stdout/stderr when it exits.

func WithProcessSandbox

func WithProcessSandbox(sb Sandboxer) ProcessManagerOption

WithProcessSandbox confines every background process behind sb — the same instance the bash tool gets via WithSandbox, so foreground and background commands run under one policy. Nil is a no-op.

func WithReapAfter

func WithReapAfter(d time.Duration) ProcessManagerOption

WithReapAfter sets how long an exited process stays in the manager for post-mortem inspection. Default 60s.

type ProcessOutputSink added in v0.11.1

type ProcessOutputSink func(id ProcessID, command string, exitCode int, stdout, stderr []string)

ProcessOutputSink receives a background process's full accumulated output when it exits. Runs on the process's reaper goroutine; implementations should be fast and must not block indefinitely.

type ProcessWorkspace

type ProcessWorkspace interface {
	Root() string
}

ProcessWorkspace is the minimum surface from code.Workspace the manager needs. Defined locally so consumers can pass a fake in tests without pulling the whole workspace package.

type ReadArgs

type ReadArgs struct {
	Path   string `json:"path" doc:"Path relative to the workspace root (or absolute, must be inside root)."`
	Offset int    `json:"offset,omitempty" doc:"Zero-based line offset to start reading from."`
	Limit  int    `json:"limit,omitempty" doc:"Maximum number of lines to return (default 2000)."`
}

ReadArgs is the typed argument struct ReadTool.Execute decodes into via tools.DecodeArgs. Field tags drive both JSON decoding and SchemaFor schema generation.

type ReadFileHLArgs

type ReadFileHLArgs struct {
	Path    string `json:"path" doc:"Path relative to the workspace root (or absolute, must be inside root)."`
	Offset  int    `json:"offset,omitempty" doc:"Zero-based line offset to start reading from."`
	Limit   int    `json:"limit,omitempty" doc:"Maximum number of lines to return (default 2000)."`
	HashLen int    `json:"hash_len,omitempty" doc:"Hash prefix length: 3 or 4 base64 SHA-256 characters (default 4)."`
}

ReadFileHLArgs is the typed argument struct ReadFileHLTool.Execute decodes.

type ReadFileHLTool

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

ReadFileHLTool reads a file with line-number + hash anchors for the edit tool. Its Definition returns ToolNameRead — it replaces ReadTool in the standard toolset without changing the name the model sees.

func NewReadFileHLTool

func NewReadFileHLTool(ws Workspace, opts ...ReadOption) *ReadFileHLTool

NewReadFileHLTool returns the hashline read tool bound to ws.

func (*ReadFileHLTool) Definition

func (t *ReadFileHLTool) Definition() tools.ToolSpec

Definition advertises read with path, offset, limit, and hash_len.

func (*ReadFileHLTool) Execute

Execute returns line-oriented file content as LINE:HASH|text rows. Hashes are computed over displayed line content only; LF and CRLF terminators are not included, while other whitespace is.

type ReadOption added in v0.1.3

type ReadOption func(*readPolicy)

ReadOption tunes read-side tools (read, hashline read, ls, grep, glob).

func WithUnrestrictedReads added in v0.1.3

func WithUnrestrictedReads() ReadOption

WithUnrestrictedReads allows read-side tools to access paths outside the workspace root. Mutating tools ignore this policy and remain workspace-bound.

type ReadTool

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

ReadTool reads a file from the workspace and returns line-numbered content.

func NewReadTool

func NewReadTool(ws Workspace, opts ...ReadOption) *ReadTool

NewReadTool returns the file-reading tool bound to ws.

func (*ReadTool) Definition

func (t *ReadTool) Definition() tools.ToolSpec

Definition advertises read with path (required), offset, and limit parameters; reads never mutate, so Mutates stays false.

func (*ReadTool) Execute

func (t *ReadTool) Execute(_ context.Context, call tools.ToolCall) (*tools.ToolResult, error)

Execute resolves the path inside the workspace (or anywhere on the host when unrestricted reads are enabled), refuses files over readMaxBytes (10 MB) before reading, rejects content with a NUL byte in the first 8 KB as binary, and returns 1-based line-numbered output starting at offset (negative offsets clamp to 0; default limit readDefaultLimit = 2000 lines, with a truncation footer when more lines remain).

type RetrieveCodeArgs added in v0.2.1

type RetrieveCodeArgs struct {
	// Query is tokenized and matched against syntax chunks. Required.
	Query string `json:"query" doc:"Search query. Tokens are matched deterministically against paths, symbol metadata, and chunk text."`
	// Root scopes the scan to a subtree. Empty means the workspace root.
	Root string `json:"root,omitempty" doc:"Optional sub-tree to scan, relative to the workspace. Empty = workspace root."`
	// Pattern selects files under Root using glob semantics. Empty defaults to *.go.
	Pattern string `json:"pattern,omitempty" doc:"Glob pattern for files to retrieve from. Empty defaults to *.go."`
	// IncludeTests includes *_test.go files. Default false.
	IncludeTests bool `json:"include_tests,omitempty" doc:"Include Go test files (*_test.go). Default false."`
	// Limit caps returned chunks. Default 8.
	Limit int `json:"limit,omitempty" doc:"Maximum chunks to return. Default 8."`
	// MaxFiles caps the number of files parsed. Default 500.
	MaxFiles int `json:"max_files,omitempty" doc:"Cap on parsed files. Default 500."`
	// MaxBytesPerChunk caps rendered source text per chunk. Default 12000.
	MaxBytesPerChunk int `json:"max_bytes_per_chunk,omitempty" doc:"Cap rendered source bytes per chunk. Default 12000."`
	// Output selects labelled plaintext or JSON. Empty means labelled.
	Output tools.OutputFormat `json:"output,omitempty" enum:"labeled,json" doc:"Output format: \"labeled\" (default) or \"json\"."`
}

RetrieveCodeArgs configures deterministic code retrieval.

type RetrieveCodeResult added in v0.2.1

type RetrieveCodeResult struct {
	Payload retrieveCodePayload
	Output  tools.OutputFormat
}

RetrieveCodeResult is retrieve_code's structured result and renderer.

func (RetrieveCodeResult) Paths added in v0.9.0

func (r RetrieveCodeResult) Paths() []string

Paths returns the workspace paths represented by the retrieved chunks in result order, with duplicates removed. The returned slice is independent of the result and may be mutated by the caller.

func (RetrieveCodeResult) String added in v0.2.1

func (r RetrieveCodeResult) String() string

String renders labelled text by default or JSON when requested.

type RetrieveCodeTool added in v0.2.1

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

RetrieveCodeTool performs deterministic lexical retrieval over syntax chunks.

func NewRetrieveCodeTool added in v0.2.1

func NewRetrieveCodeTool(ws Workspace, opts ...ReadOption) *RetrieveCodeTool

NewRetrieveCodeTool returns a deterministic retrieval tool bound to ws.

func (*RetrieveCodeTool) Definition added in v0.2.1

func (t *RetrieveCodeTool) Definition() tools.ToolSpec

Definition advertises retrieve_code as a read-only deterministic retrieval tool.

func (*RetrieveCodeTool) Execute added in v0.2.1

func (t *RetrieveCodeTool) Execute(ctx context.Context, call tools.ToolCall) (*tools.ToolResult, error)

Execute scans files, syntax-chunks them, ranks chunks deterministically, and returns the top matches.

type Sandboxer

type Sandboxer interface {
	Sandbox(cmd *exec.Cmd) error
}

Sandboxer hardens a fully-prepared command just before it starts — the implementation may rewrite argv (re-exec shims), adjust SysProcAttr (namespaces), or both. Defined here, consumer-side: the bash tool and the process manager are the only spawners, and they only need this one method. The concrete implementation lives in zkit/agent/sandbox; anything satisfying the shape works (tests use in-package fakes).

A Sandboxer must compose with the spawner's own setup: it is called after Dir, Env, stdio, and SysProcAttr (Setsid) are in place, and must mutate rather than replace what's already there.

type SavePlanAppendArgs

type SavePlanAppendArgs struct {
	Name    string `` /* 255-byte string literal not displayed */
	Content string `json:"content" doc:"Bytes to append. Keep each call under the configured cap (256KB default)."`
}

SavePlanAppendArgs is the typed argument struct SavePlanAppendTool.Execute decodes into via tools.DecodeArgs.

type SavePlanAppendTool

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

SavePlanAppendTool appends content to a plan file under .zarlcode/plans/<name>.md. It is the fallback path for plans larger than the SavePlanTool one-shot cap, and mirrors the WriteToolWriteAppendTool relationship — same scaffold-then- chunk recipe, narrowed to the plans directory so the read-only invariant of plan mode is preserved.

Why a dedicated append for plans (not just write_append)

write_append's path is unconstrained inside the workspace, so plan mode can't expose it without re-opening the "models can use the carve-out to mutate arbitrary files" door save_plan was designed to close. save_plan_append's path is locked the same way save_plan's is: same slug regex, same join under PlansDir, same rejection of slashes / dots / traversal.

Usage pattern when a plan exceeds [maxWriteContentBytes]:

save_plan(name, "")                                  // empty scaffold
save_plan_append(name, "<up to ~256KB chunk>")       // chunk 1
save_plan_append(name, "<up to ~256KB chunk>")       // chunk 2
... etc

No state is tracked between calls — the tool is stateless and just opens the file in append mode each time. The running file size is reported back in the success message so the model can sanity-check progress without re-reading the file.

func NewSavePlanAppendTool

func NewSavePlanAppendTool(ws Workspace) *SavePlanAppendTool

NewSavePlanAppendTool returns the tool that appends steps to an existing plan artifact in ws.

func (*SavePlanAppendTool) Definition

func (t *SavePlanAppendTool) Definition() tools.ToolSpec

Definition advertises save_plan_append with name (required slug) and content; like save_plan it leaves Mutates unset — appends land only under the plans directory.

func (*SavePlanAppendTool) Execute

Execute requires both name and content, caps each chunk at maxAppendContentBytes (256KB default), enforces the same safePlanName slug rules as save_plan, then appends to .zarlcode/plans/<name>.md under the path lock — creating the parent directory and file when the scaffold step was skipped. Success reports the running file size.

type SavePlanArgs

type SavePlanArgs struct {
	Name    string `` /* 228-byte string literal not displayed */
	Content string `json:"content" doc:"Full markdown body of the plan."`
}

SavePlanArgs is the typed argument struct SavePlanTool.Execute decodes into via tools.DecodeArgs. Field tags drive both JSON decoding and SchemaFor schema generation.

type SavePlanTool

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

SavePlanTool persists a plan-mode markdown artifact to <workspace>/.zarlcode/plans/<name>.md.

Why a dedicated tool, not the generic write

Plan mode strips write/edit/bash from the model's tool surface so the read-only invariant is unbreakable. But "produce a plan" is pointless if the plan can only live in the transcript — once the session compacts or the user closes the shell, it's gone.

save_plan is the carve-out: a single, narrow, path-locked write the model can call from plan mode to drop the plan as a real markdown file. Writes outside PlansDir are rejected at the tool layer; the model cannot use save_plan as a back-door to modify arbitrary files.

The tool is also available in build mode (registered unconditionally at startup) so a build-mode agent can save a retrospective or post-mortem in the same convention.

func NewSavePlanTool

func NewSavePlanTool(ws Workspace) *SavePlanTool

NewSavePlanTool returns the tool that writes a structured plan artifact into ws.

func (*SavePlanTool) Definition

func (t *SavePlanTool) Definition() tools.ToolSpec

Definition advertises save_plan with an optional name slug and required content; Mutates is left unset even though it writes a file — the write is confined to the plans directory.

func (*SavePlanTool) Execute

func (t *SavePlanTool) Execute(_ context.Context, call tools.ToolCall) (*tools.ToolResult, error)

Execute requires content and caps it at maxWriteContentBytes (shared with write, 256KB default — oversized content gets a scaffold-plus-save_plan_append recipe), defaults an empty name to a plan-YYYYMMDD-HHMM timestamp slug, enforces the safePlanName regex, and writes <name>.md under PlansDir while holding the path lock.

type StepStatus

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

StepStatus is a type that represents a single enum value. It combines the core information about the enum constant and it's defined fields.

func ParseStepStatus

func ParseStepStatus(input any) (StepStatus, error)

ParseStepStatus parses the input value into an enum value. It returns the parsed enum value or an error if the input is invalid. It is a convenience function that can be used to parse enum values from various input types, such as strings, byte slices, or other enum types.

func (StepStatus) IsValid

func (s StepStatus) IsValid() bool

IsValid checks whether the StepStatuses value is valid. A valid value is one that is defined in the original enum and not marked as invalid.

func (StepStatus) MarshalBinary

func (s StepStatus) MarshalBinary() ([]byte, error)

MarshalBinary implements the encoding.BinaryMarshaler interface for StepStatus. It returns the binary representation of the enum value as a byte slice.

func (StepStatus) MarshalJSON

func (s StepStatus) MarshalJSON() ([]byte, error)

MarshalJSON implements the json.Marshaler interface for StepStatus. It returns the JSON representation of the enum value as a byte slice.

func (StepStatus) MarshalText

func (s StepStatus) MarshalText() ([]byte, error)

MarshalText implements the encoding.TextMarshaler interface for StepStatus. It returns the string representation of the enum value as a byte slice

func (StepStatus) MarshalYAML

func (s StepStatus) MarshalYAML() ([]byte, error)

MarshalYAML implements the yaml.Marshaler interface for StepStatus. It returns the string representation of the enum value.

func (*StepStatus) Scan

func (s *StepStatus) Scan(value any) error

Scan implements the database/sql.Scanner interface for StepStatus. It parses the string representation of the enum value from the database row. It returns an error if the row does not contain a valid enum value.

func (StepStatus) String

func (s StepStatus) String() string

String implements the Stringer interface. It returns the canonical absolute name of the enum value.

func (*StepStatus) UnmarshalBinary

func (s *StepStatus) UnmarshalBinary(by []byte) error

UnmarshalBinary implements the encoding.BinaryUnmarshaler interface for StepStatus. It parses the binary representation of the enum value from the byte slice. It returns an error if the byte slice does not contain a valid enum value.

func (*StepStatus) UnmarshalJSON

func (s *StepStatus) UnmarshalJSON(by []byte) error

UnmarshalJSON implements the json.Unmarshaler interface for StepStatus. It parses the JSON representation of the enum value from the byte slice. It returns an error if the input is not a valid JSON representation.

func (*StepStatus) UnmarshalText

func (s *StepStatus) UnmarshalText(by []byte) error

UnmarshalText implements the encoding.TextUnmarshaler interface for StepStatus. It parses the string representation of the enum value from the byte slice. It returns an error if the byte slice does not contain a valid enum value.

func (*StepStatus) UnmarshalYAML

func (s *StepStatus) UnmarshalYAML(by []byte) error

UnmarshalYAML implements the yaml.Unmarshaler interface for Planet. It parses the byte slice representation of the enum value and returns an error if the YAML byte slice does not contain a valid enum value.

func (StepStatus) Value

func (s StepStatus) Value() (driver.Value, error)

Value implements the database/sql/driver.Valuer interface for StepStatus. It returns the string representation of the enum value.

type StopProcessArgs

type StopProcessArgs struct {
	ProcessID ProcessID `json:"process_id" doc:"Process id returned by bash(background=true)."`
	Signal    string    `` /* 153-byte string literal not displayed */
}

StopProcessArgs is the typed argument struct StopProcessTool.Execute decodes into via tools.DecodeArgs. Signal stays a string and is matched against TERM/KILL/INT inside Execute (the JSON Schema declares the enum; the SchemaGuardrail validates membership before dispatch).

type StopProcessTool

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

StopProcessTool kills a background process. SIGTERM by default with a 5s escalate-to-SIGKILL grace; explicit signal override available for the rare case the agent needs immediate KILL or a gentle INT.

func NewStopProcessTool

func NewStopProcessTool(m *ProcessManager) *StopProcessTool

NewStopProcessTool returns the tool that terminates a background process managed by m.

func (*StopProcessTool) Definition

func (*StopProcessTool) Definition() tools.ToolSpec

Definition advertises stop_process with process_id (required) and an optional TERM/KILL/INT signal enum; the spec text documents the SIGTERM-with-5s-grace default and idempotent exit-code semantics.

func (*StopProcessTool) Execute

Execute requires process_id, maps the signal string (case-insensitively) to SIGKILL or SIGINT — anything else falls back to SIGTERM — and delegates to the manager's Kill, returning {process_id, exit_code, killed_at}; unknown ids are NotFound.

type UpdatePlanArgs

type UpdatePlanArgs struct {
	Plan        []UpdatePlanStepArg `json:"plan" doc:"Complete ordered plan; replaces the prior plan."`
	Explanation string              `json:"explanation,omitempty" doc:"Optional short reason for the update."`
}

UpdatePlanArgs is the typed argument struct UpdatePlanTool.Execute decodes into via tools.DecodeArgs. The `plan` array decodes directly into a typed slice — no .([]any) / .(map[string]any) dance, no per-field type assertions.

type UpdatePlanStepArg

type UpdatePlanStepArg struct {
	Step   string     `json:"step" doc:"Step description."`
	Status StepStatus `json:"status" enum:"pending,in_progress,completed" doc:"Step status."`
}

UpdatePlanStepArg is one step in the typed update_plan payload. Mirrors the JSON Schema's per-item object {step, status}.

type UpdatePlanTool

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

UpdatePlanTool is the structured-plan tracker. It coexists with save_plan: save_plan persists the narrative markdown archive, update_plan owns the live, mutating step list rendered in the plan dock tab.

API mirrors Codex's update_plan verbatim: every call carries the FULL plan (steps + statuses). No partial-update semantics — the model resends the whole list each time. This is what GPT-5 family models are trained on, and it's easier to reason about (no step-id thread to maintain across calls).

Sequence-of-call expectations:

  • PLAN mode end: call update_plan once with all steps at "pending" to seed the structured list alongside the markdown plan save_plan persisted.
  • BUILD mode, starting work: call update_plan with the same list, flipping one step to "in_progress".
  • BUILD mode, finishing a step: call update_plan again with that step at "completed" and (optionally) the next one "in_progress".

The prompt enforces this; the tool itself is permissive — it accepts any plan shape, since the model occasionally rearranges/extends steps mid-task and we'd rather see the rework than reject it.

func NewUpdatePlanTool

func NewUpdatePlanTool(store PlanStore) *UpdatePlanTool

NewUpdatePlanTool returns a tool bound to store. Caller is responsible for the broadcast behaviour of store.SetPlan (in production the shellModel implementation pushes a tea.Msg to the TUI).

func (*UpdatePlanTool) Definition

func (t *UpdatePlanTool) Definition() tools.ToolSpec

Definition advertises update_plan taking the full plan array ({step, status} items, status enum pending|in_progress|completed) plus an optional explanation; Mutates is left unset — the plan lives in the store, not in workspace files.

func (*UpdatePlanTool) Execute

Execute requires a non-empty plan array, trims each step, treats an omitted status as pending, and validates statuses via StepStatus.IsValid — the first empty step or unknown status fails the whole call. On success it replaces the stored plan wholesale and returns per-status counts.

type Workspace

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

Workspace is a filesystem boundary. All paths handed to a code tool are resolved against the root; anything that would resolve outside the root (including via symlinks or "..") is refused.

Workspace values are passed by value to each tool but share their path-lock map AND the openat-style os.Root handle via pointer, so concurrent write/edit calls to the same path serialise correctly even when issued through different tool instances, and every writer benefits from the root-anchored open without re-opening the directory handle per call.

TOCTOU hardening: the per-tool boundary check via Workspace.Resolve uses lexical + EvalSymlinks-of-prefix matching, which is sufficient to reject obviously-out-of-tree paths cheaply. The actual file open then goes through the *os.Root handle so that even if the parent directory is swapped for a symlink between resolve and open, the kernel-level traversal refuses to follow it. Earlier shape did `os.WriteFile(abs, ...)` against an arbitrary absolute path — `Resolve` told you the path was safe at check time but nothing stopped a concurrent local actor from escaping the root between check and use.

func NewWorkspace

func NewWorkspace(root string) (Workspace, error)

NewWorkspace returns a Workspace rooted at the given absolute path. The root is canonicalized via filepath.EvalSymlinks so symlink-to-outside checks compare apples to apples, then opened as an os.Root so subsequent file operations can use openat-style traversal that refuses to escape the directory regardless of symlinks underneath.

func (Workspace) LockPath

func (w Workspace) LockPath(absPath string) func()

LockPath acquires the per-path mutex for the given (already-resolved) absolute path and returns the unlock function. Callers must defer the returned unlock. Used by write/edit/append-tools to serialise writers targeting the same file.

func (Workspace) MkdirParentInRoot

func (w Workspace) MkdirParentInRoot(abs string) error

MkdirParentInRoot creates the parent directory of abs (if it doesn't already exist) via the openat-style os.Root handle. Use before [OpenFileInRoot] when the target path is a fresh write into a directory that may not exist yet.

func (Workspace) OSRoot

func (w Workspace) OSRoot() *os.Root

OSRoot returns the underlying os.Root handle. Writers in this package use it for openat-style file operations; external callers almost never need it. May be nil for zero-value Workspaces (e.g. constructed in tests without NewWorkspace); callers should fall back to plain os operations in that case.

func (Workspace) OpenFileInRoot

func (w Workspace) OpenFileInRoot(abs string, flag int, perm os.FileMode) (*os.File, error)

OpenFileInRoot opens the named file relative to the workspace root. Same TOCTOU-safety argument as WriteFileInRoot — uses openat-style traversal so a symlinked parent can't escape.

func (Workspace) ReadDirInRoot

func (w Workspace) ReadDirInRoot(abs string) ([]os.DirEntry, error)

ReadDirInRoot reads a directory through the workspace root handle.

func (Workspace) ReadDirPath added in v0.1.3

func (w Workspace) ReadDirPath(abs string) ([]os.DirEntry, error)

ReadDirPath reads a directory via the workspace root handle when it remains under the workspace, falling back to direct host directory reads for unrestricted read-side paths outside the workspace.

func (Workspace) ReadFileInRoot

func (w Workspace) ReadFileInRoot(abs string) ([]byte, error)

ReadFileInRoot reads abs through the workspace root handle. This is the read-side companion to WriteFileInRoot: callers still use Resolve for friendly errors and path locks, but the actual open happens relative to os.Root so a concurrent symlink swap cannot escape.

func (Workspace) ReadFilePath added in v0.1.3

func (w Workspace) ReadFilePath(abs string) ([]byte, error)

ReadFilePath reads abs through the workspace root handle when it remains under the workspace, falling back to direct host reads for unrestricted read-side paths outside the workspace.

func (Workspace) RelToRoot

func (w Workspace) RelToRoot(abs string) (string, error)

RelToRoot returns the path of abs relative to the workspace root. Used by writers to convert the absolute path Resolve returned into the root-relative form os.Root expects. Returns an error if abs is outside the root — defensive check; callers normally pass a path that Resolve already validated.

func (Workspace) RemoveInRoot

func (w Workspace) RemoveInRoot(abs string) error

RemoveInRoot removes the file at the workspace-relative path via the os.Root handle. Mirrors os.Remove semantics (file-only; directories are out of scope for code tools).

func (Workspace) Resolve

func (w Workspace) Resolve(p string) (string, error)

Resolve returns the cleaned absolute path for p, where p may be relative (joined to root) or absolute (must be inside root). Symlinks are followed and the final path is re-checked against root.

func (Workspace) ResolveForRead added in v0.1.3

func (w Workspace) ResolveForRead(p string, unrestricted bool) (string, error)

ResolveForRead resolves p for a read-side tool. When unrestricted is false it enforces the workspace root exactly like Resolve. When unrestricted is true, reads may escape the workspace: absolute paths are used as-is and relative paths are cleaned after joining to the workspace root, so ../ segments may walk out.

func (Workspace) Root

func (w Workspace) Root() string

Root returns the canonicalized root path.

func (Workspace) StatInRoot

func (w Workspace) StatInRoot(abs string) (os.FileInfo, error)

StatInRoot stats abs through the workspace root handle.

func (Workspace) StatPath added in v0.1.3

func (w Workspace) StatPath(abs string) (os.FileInfo, error)

StatPath stats abs either through the workspace root handle (for paths still under root) or directly through the host filesystem (for unrestricted read-side paths outside the workspace).

func (Workspace) WriteFileInRoot

func (w Workspace) WriteFileInRoot(abs string, data []byte, perm os.FileMode) error

WriteFileInRoot writes data to the workspace-relative path via the os.Root handle. Parent directories are created (also via the root handle) up to the workspace root — never beyond.

This is the TOCTOU-safe replacement for the os.WriteFile + os.MkdirAll pair the writers used to call directly. The kernel refuses to follow symlinks out of the root even if a directory in the path is swapped for a symlink between Resolve and Write.

Falls back to plain os operations for zero-value Workspaces (tests constructing tools without NewWorkspace); production paths always go through the os.Root handle.

type WriteAppendArgs

type WriteAppendArgs struct {
	Path    string `json:"path" doc:"Path relative to workspace root."`
	Content string `json:"content" doc:"Bytes to append. Keep each call under the configured cap (256KB default)."`
}

WriteAppendArgs is the typed argument struct WriteAppendTool.Execute decodes into via tools.DecodeArgs. Field tags drive both JSON decoding and SchemaFor schema generation.

type WriteAppendTool

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

WriteAppendTool appends bytes to a file, creating it if missing. It exists as the fallback path for files larger than the `write` cap — modern llama.cpp builds + hosted providers handle the full 256KB default cleanly, but a model that needs to emit something larger (or hits a server that struggles with long streaming args) can scaffold with an empty `write` and stream chunks via `write_append`.

Historical note: older llama.cpp builds dropped characters inside multi-KB streaming tool-call JSON; the tool was originally authored to chunk at ~500 bytes for Qwen3.6. That regime is gone — the cap is now 256KB by default (tunable via CODE_APPEND_MAX_BYTES). Don't chunk smaller than necessary; the extra round-trips cost iterations.

Pattern for an oversized file (>256KB):

write(path, "")                            // empty scaffold
write_append(path, "<up to ~256KB chunk>") // chunk 1
write_append(path, "<up to ~256KB chunk>") // chunk 2
... etc

No state is tracked between calls — the tool is stateless and just opens the file in append mode each time. Concurrent calls to the same path race; the workspace's per-call serialisation should keep that from happening, but if you parallelise tool calls in the future, add a mutex keyed by absolute path.

func NewWriteAppendTool

func NewWriteAppendTool(ws Workspace) *WriteAppendTool

NewWriteAppendTool returns the append-or-create file tool bound to ws.

func (*WriteAppendTool) Definition

func (t *WriteAppendTool) Definition() tools.ToolSpec

Definition advertises write_append with required path and content; Mutates is true because each call appends bytes, creating the file and parent directories when missing.

func (*WriteAppendTool) Execute

Execute caps each chunk at maxAppendContentBytes (256KB default), resolves the path, and — under the per-path lock — mkdirs the parent and opens O_APPEND|O_CREATE through the workspace root handle. Success reports the running file size and emits a FileAppend effect.

type WriteArgs

type WriteArgs struct {
	Path    string `json:"path" doc:"Path relative to workspace root."`
	Content string `json:"content" doc:"Full file contents to write."`
}

WriteArgs is the typed argument shape WriteTool's Execute decodes into. Field tags drive both JSON decoding and SchemaFor schema generation — doc tags supply the LLM-facing descriptions.

type WriteResult added in v0.2.1

type WriteResult struct {
	Path  string `json:"path"`
	Bytes int    `json:"bytes"`
}

WriteResult is write's structured success payload.

func (WriteResult) String added in v0.2.1

func (r WriteResult) String() string

String renders the model-facing success text for WriteResult.

type WriteTool

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

WriteTool creates a new file inside the workspace. Existing paths are rejected with a Validation error whose Reason names edit as the recovery path — this is a runtime invariant, not prompt guidance: the tool physically cannot overwrite, so models that would otherwise silently clobber an existing file are forced through the edit/read loop. Small-model benchmark runs show the refusal fires on a substantial fraction of exercises and consistently improves correctness — the model's "rewrite the whole file" instinct is rarely the right move once a file exists.

Content size cap

content is capped at maxWriteContentBytes (default 256KB, tunable via CODE_WRITE_MAX_BYTES — see limits.go). Older llama.cpp builds dropped characters inside long streaming tool-call JSON; the cap is generous now (modern servers handle 256KB cleanly) but the scaffold-with-empty-write + chunked write_append fallback path stays available for whatever genuine outliers a model might produce. The cap is enforced at the tool layer because prompt compliance breaks under context pressure.

func NewWriteTool

func NewWriteTool(ws Workspace) *WriteTool

NewWriteTool returns a workspace-scoped write tool. The returned concrete type caches a typed-tool adapter so Execute stays a thin dispatch boundary.

func (*WriteTool) Definition

func (t *WriteTool) Definition() tools.ToolSpec

Definition advertises write with required path and content; Mutates is true because a successful call creates a new file. The spec text routes existing-path retries to edit.

func (*WriteTool) Execute

func (t *WriteTool) Execute(ctx context.Context, call tools.ToolCall) (*tools.ToolResult, error)

Execute rejects content over maxWriteContentBytes (256KB default) before touching the filesystem, resolves the path, then — under the per-path lock — refuses existing paths with an edit recipe and writes through the workspace's os.Root handle, emitting a FileCreate effect carrying the byte count.

Jump to

Keyboard shortcuts

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