environment

package
v0.0.20 Latest Latest
Warning

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

Go to latest
Published: Jun 11, 2026 License: Apache-2.0 Imports: 31 Imported by: 0

Documentation

Overview

Package workspace implements local workspace operations.

HTML→Markdown / HTML→Text conversion helpers used by webfetch.

Inlined from go-pkg/x/htmlx — keep in sync. flashduty-runner is open-source and may not import internal flashcatcloud modules.

SSRF guard for outbound HTTP. Runner pods must NEVER reach IMDS or the cluster apiserver from inside; without dial-time IP validation a prompt-injected URL can exfiltrate cloud credentials from 169.254.169.254 or hit internal VPC services. The DNS-to-dial re-resolution closes the DNS-rebinding gap a host-string allowlist would leave open.

Inlined from go-pkg/x/netsafe — keep in sync. flashduty-runner is open-source and may not import internal flashcatcloud modules.

Index

Constants

View Source
const (
	// DefaultMaxOutputSize is the default character limit before truncation.
	// 50000 chars (~12.5k tokens at 4 chars/token) matches Claude Code's
	// DEFAULT_MAX_RESULT_SIZE_CHARS spill threshold — the point at which a tool
	// result is persisted to a file and the agent gets a preview + path.
	DefaultMaxOutputSize = 50000
	// DefaultPreviewSize is the maximum characters for preview
	DefaultPreviewSize = 8000 // ~2k tokens
	// DefaultPreviewLines is the number of lines shown in the preview
	DefaultPreviewLines = 20
	// OutputsDir is the directory name for storing large outputs
	OutputsDir = ".outputs"
)
View Source
const BrokerSupported = true

BrokerSupported reports whether this build can run the egress broker.

Variables

View Source
var ErrOutputCapped = errors.New("output capped")

ErrOutputCapped is returned by LimitedWriter once its byte cap has been reached. Surfaces the contract violation that would otherwise be hidden by the writer pretending it accepted everything (`return len(p), nil`).

Functions

func BashToolEnv added in v0.0.17

func BashToolEnv() []string

BashToolEnv returns the exact base environment every bash tool subprocess runs with: ambient Flashduty secrets scrubbed, and the bundled-tools dir prepended to PATH so OUR fduty resolves. Per-call extraEnv (e.g. the bash guard re-supplying credentials) is layered on top by the caller. Startup's fduty self-check uses this so it validates the real call-time PATH, not an approximation.

func BrokerEgressEnabled added in v0.0.16

func BrokerEgressEnabled() bool

BrokerEgressEnabled reports whether this runner should advertise egress-broker support to safari. Two conditions must both hold:

  1. this build can run the broker (BrokerSupported — Linux only), and
  2. the bundled fduty can act as a broker-mode client.

Condition 2 is load-bearing: once safari sees broker=1 it delivers the per-person app_key out-of-band over the inherited control fd and NEVER in the bash env. An older bundled fduty that doesn't understand FLASHDUTY_CRED_FD would then have no usable key and every authenticated call would fail. The runner does not refresh fduty on self-update, so a fleet runner can carry a stale fduty across a runner upgrade. Gating the advertisement here makes the rollout self-healing: a stale fduty keeps the legacy env-key path, and once it is updated the next reconnect advertises broker mode. It also works in zero-egress BYOC, where the runner cannot fetch a newer fduty.

The fduty probe runs at most once and is cached; its cost is a single `fduty version --json`.

func BundledToolsDir added in v0.0.11

func BundledToolsDir() string

BundledToolsDir returns the directory holding the CLIs the runner ships with it — the flashduty CLI in particular. Bash subprocesses must resolve OUR bundled flashduty rather than whatever a BYOC host happens to have earlier on PATH (a different version, or an unrelated binary of the same name).

The same value serves two roles that MUST NOT diverge: the dir we prepend to every bash subprocess's PATH (so fduty resolves), and the dir we install fduty into (FLASHDUTY_INSTALL_DIR in provision.go). If the PATH dir is read-only, install.sh relocates fduty to ~/.local/bin — which is not on the bash PATH — and every `fduty` call 127s. To make divergence impossible by construction, the default is a runner-owned, guaranteed-writable directory under the runner's state home: <state home>/bin, where the state home is the same root the runner uses for its workspace (FLASHDUTY_RUNNER_HOME, with the deprecated FLASHDUTY_RUNNER_WORKSPACE alias, falling back to ~/.flashduty). Under the systemd unit that root sits inside ReadWritePaths, so it is writable even with ProtectSystem=strict and NoNewPrivileges.

FLASHDUTY_RUNNER_BIN_DIR is an explicit override and wins outright — the AGS cloud image bakes fduty at /usr/local/bin and points this env there.

Returns "" only when neither an override nor a home dir can be resolved, in which case PATH is left untouched.

func RunnerStateHome added in v0.0.19

func RunnerStateHome() string

RunnerStateHome resolves the runner's state/home root the same way cmd does (FLASHDUTY_RUNNER_HOME > deprecated FLASHDUTY_RUNNER_WORKSPACE > ~/.flashduty), so the bundled-tools dir always lands inside the writable state tree. Also the root under which self-update keeps the canonical runner binary (<home>/bin) when the running executable's own directory is read-only.

func ShouldSkipForOutputsDir

func ShouldSkipForOutputsDir(command string) bool

ShouldSkipForOutputsDir checks if large output processing should be skipped for commands operating on .outputs/ directory to avoid circular processing. Uses a word-boundary regex so commands like "thread " don't match "head ".

Types

type Environment

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

Environment handles local filesystem operations.

func New

func New(root string, checker *permission.Checker) (*Environment, error)

New creates a new workspace with the given root directory and permission checker.

func (*Environment) Bash

Bash executes a bash command in the workspace.

func (*Environment) DeleteKnowledgeFiles

DeleteKnowledgeFiles removes the supplied files from the workspace root and scrubs their entries from the sentinel.

func (*Environment) Glob

Glob searches for files matching a pattern.

func (*Environment) Grep

Grep searches for a pattern in files.

func (*Environment) List

List lists entries in a directory.

func (*Environment) MCPCall

func (e *Environment) MCPCall(ctx context.Context, args *protocol.MCPCallArgs, logger *slog.Logger) (*protocol.MCPCallResult, error)

MCPCall executes an MCP tool call.

func (*Environment) MCPListTools

MCPListTools lists available tools from an MCP server.

func (*Environment) PackFolder added in v0.0.8

PackFolder walks args.Path, applies the skill exclusion rules, and returns a base64-encoded zip whose entries are rooted at the basename of args.Path.

func (*Environment) Read

Read reads a file from the workspace.

func (*Environment) ReconcileKnowledgeManifest added in v0.0.7

ReconcileKnowledgeManifest reconciles the on-disk knowledge tree against the supplied manifest. Files present on disk but absent from the manifest are orphans (pruned). Files present in the manifest with a checksum that disagrees with the sentinel are stale (also pruned, so the next read triggers a fresh lazy install from Safari). Files whose checksum already matches are left in place.

The orphan prune is scope-bounded when the caller sends scope lists (see ReconcileKnowledgeManifestArgs): inside a staged scope the manifest is authoritative and orphans go; a scope that is valid account-wide but not staged by this session is another session's territory and is left alone; a scope outside ValidScopes no longer exists upstream and is pruned whole. Without scope lists (an older Safari) every orphan is pruned, as before.

The runner does NOT pre-stage anything in this call: the manifest declares what *should* exist if it were read, not what *must* be cached. Cold packs stay cold; only drift is corrected. The whole pass runs under the sentinel lock so it is safe with concurrent stage calls from the same Safari.

func (*Environment) Root

func (e *Environment) Root() string

Root returns the workspace root directory.

func (*Environment) StageKnowledgeFiles

StageKnowledgeFiles writes the supplied files into the workspace root and updates the sentinel checksum map.

func (*Environment) SyncSkill

SyncSkill is probe-or-install. With empty ZipData it checks local cache against args.Checksum: hit returns {Cached:true,Path}; miss returns {Cached:false,Path:""} so the cloud retries with zip. With ZipData present it overwrites <home>/skills/<name>/ and writes .checksum.

args.SkillDir is accepted for backward compatibility but ignored — the runner derives the path from skill_name to keep layout under its control.

func (*Environment) WebFetch

WebFetch fetches content from a URL and converts it to readable format.

func (*Environment) Write

func (e *Environment) Write(ctx context.Context, args *protocol.WriteArgs) error

Write writes content to a file in the workspace.

type LargeOutputConfig

type LargeOutputConfig struct {
	MaxOutputSize int
	PreviewSize   int
	PreviewLines  int
}

LargeOutputConfig holds configuration for large output handling.

func DefaultLargeOutputConfig

func DefaultLargeOutputConfig() LargeOutputConfig

DefaultLargeOutputConfig returns the default configuration.

type LargeOutputProcessor

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

LargeOutputProcessor handles large output truncation and file storage.

func NewLargeOutputProcessor

func NewLargeOutputProcessor(ws *Environment, config LargeOutputConfig) *LargeOutputProcessor

NewLargeOutputProcessor creates a new processor with the given workspace.

func (*LargeOutputProcessor) Process

func (p *LargeOutputProcessor) Process(ctx context.Context, content string, prefix string) (*ProcessResult, error)

Process checks if content exceeds the limit and handles accordingly.

type LimitedWriter

type LimitedWriter struct {
	W     io.Writer
	Limit int64
	// contains filtered or unexported fields
}

LimitedWriter is an io.Writer that caps the total number of bytes forwarded to the wrapped writer. After the cap is reached it appends an inline marker once and then returns ErrOutputCapped on every subsequent write so callers can detect truncation via Hit().

NOTE: exec.Cmd treats any writer error as fatal — callers wrapping LimitedWriter around a stdio pipe must filter ErrOutputCapped out of cmd.Run()'s error chain.

func (*LimitedWriter) Hit added in v0.0.7

func (l *LimitedWriter) Hit() bool

Hit reports whether the writer's byte cap has been reached.

func (*LimitedWriter) Write

func (l *LimitedWriter) Write(p []byte) (n int, err error)

type ProcessResult

type ProcessResult struct {
	Content   string
	Truncated bool
	FilePath  string
	TotalSize int64
}

ProcessResult holds the result of processing large output.

Jump to

Keyboard shortcuts

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