PUBLIC MODULE · lib/env.js
Agent.Env
lib/env.js — Env: the sole global environment object.
Env is the REGISTRY of what is available to this process: settings, the model catalog (endpoint/model pairs with their caps — env.models()), tools, skills and prompts, and membership events. It builds and keeps that state in the background and persists it; consumers READ state and SUBSCRIBE to changes (Env.EVENT.MODELS_CHANGED), never drive a refresh, a load, or a save. IO opens a pair through env.connection(); endpoints come and go through env.login()/env.logout() — the only per-endpoint API. Usage accounting is IO's and Agent's, never Env's.
Env declares its settings sources (lib/env/source-graph.js) on a SettingsStore: package, extension, user and project files plus memory, last wins. Objects merge by key; arrays and scalars replace. Required settings-file parse failures fail startup; other malformed files keep their last good data and are retried by polling. Explicit constructor settings take precedence.
Endpoints: everything about an endpoint is settings.providers[endpoint]. A login's auth file auth-<endpoint>.json holds { [endpoint]: data } mounted at providers; only a login (or the user) creates one. Endpoint data with no file, and environment-detected endpoints, stay in memory.
Protocols: providers/*.js and configured trusted extension roots (package/user scope only — never the project folder) are scanned as default-exported classes importing only lib/context.js; Env completes their static catalog side (lib/env/provider.js) and IO their wire side (lib/io/provider.js).
Folders (env.folders): the folders that matter to a session, each a typed {kind, title, path} — the project folder (cwd), the harness (package) folder, the user settings folder, the tool roots. The TUI lists them when a fresh session starts.
class Env class
The sole global environment object: settings, auth, providers, tools, and the titled folder surface.
Defined in lib/env.js
static EVENT field
The Env event vocabulary: Symbol constants for onEvent/offEvent. Env events report what EXISTS in this environment: the native MODELS_CHANGED (models() answers differently — re-read it) and the membership events plugins install (Agent's AGENT_ADDED/AGENT_REMOVED); what an object does is that object's own events. SETTINGS_CHANGED announces changed settings values (once per storage or poll round). SETTINGS_LOADED announces a finished poll round that read files, including equal versions. Reads never emit either.
static SettingsStore field
The settings aggregator Env builds its settings from (lib/env/settings-store.js): an ordered list of file, folder/glob and memory sources, last wins, with an optional parent store; immediate reads and writes, grouped asynchronous loaded/saved/updated notices. Usable on its own for any JSON settings. Read the SettingsStore contract and standalone examples for constructor/source specs, precedence, node handles, methods and errors.
async agentCreate(options = {…}) method
Create an Agent over this env (env is always this env).
[options]object- Agent constructor options
Returns Promise<Agent> — the new, registered Agent
Throws — {Error} when this env is closed (env.closed)
agents() method
Snapshot registered Agents in registration order. Awaiting Agent.close() ensures its owned teardown and removal; close-marked Agents remain registered until releases settle. Inactivity and garbage collection never change this lifecycle registry.
Returns object[]
static close() method
Release only the static global settings facade, awaiting pending I/O. Does not close registered project Envs. Repeated/concurrent calls are idempotent; the next static settings/ready access after completion acquires fresh state.
Returns Promise<void> — Resolves after release or rejects on persistence failure.
close() method
Release what this Env runs in the background: pending model-list retries stop, every MCP server it started is killed, and its settings subscription and source graph close. Idempotent; rejects after cleanup if an owned release fails. A closed Env leaves Env.envs (env.closed) but stays readable; it does not start new MCP connections or create new agents.
Returns Promise<void> — Resolves after owned teardown and pending settings persistence; concurrent/repeated calls await the same outcome.
Throws — {Error|AggregateError} One or more release failures, after all releases have been attempted.
closed property
Read: get closed()
whether close() has run (the Env left Env.envs).
Returns boolean — whether close() has run (the Env left Env.envs).
connection(selector, { remember = true } = {…}) method
IO's handle on one pair: {endpoint, model, Protocol, url, settings, authSet(data), authReload(current)} (lib/env/models.js). An unknown endpoint, or a model its non-empty catalog lacks, throws.
selectorstring- `<endpoint>/<model>`
Returns object
constructor(options = {…}, acquisition) constructor
Internal construction; public consumers acquire a ready Env with Env.open(path, options). Build the environment: assemble layered source nodes (the package folder, user settings folder, and namespaced project files — see lib/env/source-graph.js), seed the titled folder surface, register the built-in tools. Providers and tools load afterwards (loadProviders/loadTools).
[options]Object[options.dir]string- package folder to scan (default: this package's root)
[options.settingsDir]string|null- the USER SETTINGS folder (default: the namespace setting or namespace home folder, created when missing) — scanned after the package folder and the target of every dynamic settings write; null disables the layer (embedded/test hosts)
[options.cwd]string- the PROJECT folder (default: process.cwd()); constructor-internal, not the Env.folder property — separate from `dir` so a test (or an embedding host) can isolate both independently; consumed by the folder surface below and by the system prompt's project-local AGENTS.md layer (env.systemPrompt())
[options.settings]Object- explicit settings, merged LAST (override)
[options.sessionsDir]string- ephemeral session log folder for this Env only; relative to cwd, overrides configured sessions without writing settings. Must be a non-empty path.
[options.providers]Object<string, Function>- extra provider classes by name (embedders, tests); they take precedence over a scanned class of the same name
[options.toolDirs]string[]- the tool roots initialization scans instead of the default ones (embedders, tests)
[options.skillDirs]string[]- the skill roots read instead of the accumulated default layers (embedders, tests)
[options.promptDirs]string[]- the prompt roots, likewise
[options.themes=true]boolean- load theme files under settings.themes from package, extension, user, and project roots; false opts out
[options.auth=true]boolean- retain credentials from settings layers; false strips all nested auth fields for reduced tool-worker environments (not filesystem read confinement)
Throws — {TypeError} Invalid options container or unknown own top-level keys (including symbols) before registration or settings work; settings/provider payload keys remain open.
static envs property
Read: static get envs()
The registered project Envs by project folder — at most one per canonical folder. Non-default embedding Envs from Env.open(path, options) never enter this registry. close() deregisters project Envs. A frozen snapshot per read.
Returns Readonly<Object<string, Env>> — absolute folder -> Env
static extend(plugin) method
Install a higher layer's Env members after the fact (a plugin): methods/getters land on Env.prototype and run on the env they were reached through (the safe view included); events add Symbol keys to Env.EVENT; settings add the layer's settings keys to the defaults schema. An existing name throws — plugins extend Env, never override it (lib/env/extend.js).
Returns {EVENT: Object<string, symbol>, emit: (env: object, event: symbol, payload: object) => void} — the plugin's own event symbols and its (sole) way to emit them
folder property
Read: get folder()
The absolute project folder used by this Env (fixed for its lifetime).
Returns string — The absolute project folder used by this Env (fixed for its lifetime).
folders property
Read: get folders()
The folders that matter to a session, computed from current state (the project folder follows folder): exactly one project and one harness (the package folder in use), the user settings folder unless disabled, then each tool root.
Returns ReadonlyArray<{kind: "project"|"harness"|"settings"|"tools", title: string, path: string}>
login(name, config, options) method
Log an endpoint in — one transaction (lib/env/login.js): credentials (auth, else the provider shapes token), then with a scope the endpoint persists, is verified (the provider's connection test, then verify(models) over its new catalog entries) and its models are fetched — any failure rolls memory and files back. Without a scope the registration is in-memory only (an invocation-only endpoint); its models arrive in the background.
namestring- the endpoint name
Returns Promise<{name: string, endpoint: object, auth: object|undefined, scope: string|undefined, verified: *}>
loginPresets() method
The login presets every non-secret provider publishes ({name, label, url, provider, oauth?, note?, ...}).
Returns object[]
logout(name) method
Remove an endpoint (the /logout contract): its configuration and auth files go with every in-memory trace; an environment-detected endpoint clears in memory only.
namestring
Returns {name: string, dynamic: boolean}
mcpLogin(name, options) method
Start explicit browser OAuth for a configured HTTP MCP server; never invoked by a tool call.
namestring- Configured MCP server name.
Returns Promise<{name: string}> — Signed-in server identity; rejects on discovery, registration, callback or token error.
mcpPaste(input) method
Supply a complete loopback redirect URL to a pending MCP OAuth flow.
inputstring- Complete callback URL with code, state and optional issuer.
Returns boolean — Whether a sign-in flow accepted the callback input.
mcpStatus() method
List configured MCP server connection and sign-in states; exposes no credentials.
Returns Array<{name: string, state: string}> — Live per-server state.
models(secret = false) method
The model catalog: every known endpoint/model pair with its state already computed (lib/env/models.js ModelInfo — caps, secret, loginRequired, lastUsed, plugin capacity fields). The Map and outer records are snapshots, but nested capability values may reference shared environment/provider state. Mutate deliberately; this is not a defensive deep copy. Re-read on Env.EVENT.MODELS_CHANGED.
[secret=false]boolean- include hidden pairs (flagged `secret`)
Returns Map<string, object> — `endpoint/model` -> ModelInfo
modelsReady property
Read: get modelsReady()
Settles when the background collection's first pass has answered (one-shot hosts: list models, resolve an endpoint-only selector) — including the lists of in-memory logins, fetched now on demand. Resolved at once when no collection runs.
Returns Promise<void>
name property
Read: get name()
The project name. A pinned project answers its pin record's derived settings.projects[folder].name (the shortest suffix no other pin shares); an unpinned one the shortest suffix no pinned or other open Env's folder shares (conflicts resolve on the first differing parent folders: fiz/bar/foo, faz/bar/foo); the root folder is /.
Returns string
offEvent(handle) method
Remove the listener registered with {@link onEvent}.
handlenumber- opaque handle returned by onEvent
Returns boolean — true if a listener was removed; otherwise false
onEvent(event, callback) method
Subscribe to an Env Symbol event. Env reports settings/model changes and plugin membership changes; Agent reports its own request/tool events. Membership plugins add Env.EVENT constants such as AGENT_ADDED and AGENT_REMOVED, whose payload is {agent}.
eventsymbol- one Env.EVENT constant
callback(payload: object) => void
Returns number — opaque handle (offEvent removes it)
static open(path, options) method
Acquire a fully initialized Env for a project folder. With no options, concurrent callers share the canonical folder's registered Env; each caller owns a close() obligation. Options that retain constructor/startup defaults (including {}) also reuse the canonical Env. Meaningful overrides create an isolated Env, never registered or returned by a path-only lookup. path is authoritative; options.cwd is not accepted. options.init controls startup (providers/tools/detect/models default true); models collection begins in the background and modelsReady awaits its first pass. Explicit options.settings overlay persisted configuration in a per-Env memory node: writes to owned override keys remain ephemeral; writes to other keys follow normal settings ownership and may persist. Inputs are not mutated; constructor settings are copied into the store, while provider/root references follow constructor rules.
pathstring- Project folder, relative or absolute; aliases share filesystem identity.
[options]object- Constructor options other than cwd, plus optional init settings.
Returns Promise<Env> — Ready Env; caller must await close() when finished.
Throws — {TypeError|Error} Invalid path/options or startup failure. Failed new Envs close before rejection; after fixing input or disk state, retry is safe.
promptCatalog(options = {…}) method
Format this environment's current prompt catalog as Markdown.
Returns string — Detached catalog text; reads entries without retaining or mutating them.
Effects — Reads prompt files; repeated calls reflect current catalog contents.
prompts() method
The merged prompts (same layers; a later same-named prompt replaces an earlier one), read fresh from disk — same entry shape.
Returns Map<string, {name: string, description: string, file: string, source: string, body: string}>
static ready() method
Await complete initial global settings discovery, without starting providers, models, tools or a project Env. Safe before or after loading; use this barrier rather than waiting for a possibly already-fired SETTINGS_LOADED event.
Returns Promise<Object> — The same live view as static Env.settings.
Throws — {Error} Startup/discovery failure; repair sources, await static close(), and retry.
ready() method
Complete asynchronous folder discovery and graph startup once. The constructor exposes cached, possibly incomplete settings immediately; await this for complete startup settings. SETTINGS_CHANGED announces changed values; SETTINGS_LOADED announces completed discovery/reload batches after all detected files and their effective settings views finish loading.
Returns Promise<Env> — This environment after its source graph is ready.
static settings property
Read: static get settings()
Live process-global settings from package, extensions and user sources only. settings.projects contains pinned records keyed by absolute folder. Each record reads with derived name (shortest unique folder suffix) and folder; neither derived field nor any name alias is persisted. Pins can be edited here or through an instance's reachable-project settings view. Unlike instance writes, a global write of a new folder pin does not add groups. No project Env is opened or registered. Reads may be partial before static ready(); writes follow the same authorization/defaults/persistence rules as instance settings. Nested views are stable; arrays are detached snapshots. The process owns this facade until static close(); existing project Envs retain their shared sources independently. Configure the settings directory through the namespace environment variable before first access.
Returns Object — Global live settings, including defaults and unknown keys.
settings property
Read: get settings()
The LIVE settings view (lib/env/settings-view.js): reads apply the defaults schema and derived values (settings.sessions is the resolved sessions folder); assigning or deleting a key persists only that change to the writable owner in memory immediately; file-backed updates coalesce and persist asynchronously. Await settingsFlush() for durability or write errors. Nested objects are path-stable live views; arrays are detached snapshots: replace the whole array to persist and emit events; in-place edits of a returned array do not change the Env. settings.projects reads as a live view of reachable project records, keyed by absolute folder and by name aliases pointing to the SAME records. It includes this Env's own folder even when unpinned, plus pinned projects sharing a group. projects.this aliases this Env's own folder record. Writes through folder keys or name aliases edit global pins, including new folder pins; the reachable filter restricts reads, not writes. Assigning settings.projects[folder] for a new pin inherits every true group from this Env's own current global pin, making the new entry reachable. Global Env.settings.projects writes do not auto-add groups. Each returned record has derived name and folder; aliases and derived fields are never persisted. Env.settings.projects also permits global pin edits.
Returns Object
static settingsFlush() method
Persist pending global settings edits; reads/writes are already immediate.
Returns Promise<void> — Resolves after persistence; rejects on write failure (retry safe).
settingsFlush() method
Wait for queued settings writes in this Env's shared and project file nodes. Assignments update the live view immediately; this explicit barrier makes their latest values durable or rejects on a file write error.
Returns Promise<void> — Settles after outstanding writes complete.
settingsSchema() method
The DEFAULTS SCHEMA: every top-level settings key Env (or a loaded tool — see the tools.js module contract's settingsSchema()) understands, its default and a one-line description. Discovery only (ai init, API.md) — an unknown settings key is never rejected either way.
Returns Object} key -> {default, description
skillCatalog(options = {…}) method
Format this environment's current skill catalog as Markdown.
Returns string — Detached catalog text; reads entries without retaining or mutating them.
Effects — Reads skill files; repeated calls reflect current catalog contents.
skillLoad(names, { active = new Set() } = {…}) method
Validate named skills and return their instruction payload without injecting it.
[names]string|string[]- Names to load; omitted lists the catalog.
Returns string|{result: string, system: string[]} — Detached catalog text or a complete activation payload.
Throws — {TypeError|Error} Invalid names or unknown skills; correct the complete request before retrying. No partial payload is returned.
Effects — Reads the current skill catalog; performs no context or file mutation.
skillSection(skill) method
Format a skill entry for injection into a Context.
Returns string — Detached skill section; input is neither mutated nor retained.
Throws — {TypeError} If the entry lacks a string name; pass a catalog entry before retrying.
Effects — None; repeated calls with the same entry return the same text.
skills() method
Effective skills, read fresh from package/settings/configured/environment/ project layers. Later bodies override; {{name}} and {{name[L1-L2]}} explicitly compose bodies (self references use the previous definition). Line ranges are 1-based/inclusive over expanded bodies, excluding metadata. Unknown placeholders remain literal; cycles/invalid known ranges throw. directories lists the skill's resource folders in layer order (later layers override earlier ones); tools must not show them to models.
Returns Map<string, {name: string, description: string, file: string, source: string, body: string, directories: string[]}>
systemPrompt() method
The system-prompt text(s) seeded into a FRESH context, read from disk on every call (package, user settings, then project AGENTS.md — or settings.system), with {{skill}} prefill.
Returns string[] — zero to three texts, in layering order
toolAdd(name, fn, schema, { builtin = false, file } = {…}) method
Register a tool into the flattened callable lookup (lib/env/tool-registry.js — the safe/interactive schema metadata contract lives there).
namestring- flattened tool name
fnFunction- the callable
schemaobject- MCP-like {description, inputSchema, safe?, interactive?, sandbox?, onTimeout?, readOnly?}; readOnly(args) classifies optional mutating invocations as sequential barriers.
Returns Function — fn
toolCall(name, args, context) method
Invoke one tool (lib/env/tool-registry.js). The context is handed to the tool as its second argument; Env reads: safe (refuse a tool that is not read-only), selector + io (the pair's provider tool serves first; undefined falls through to the global tool), signal and deadline, and adds statusSet(info) — the tool's own live status. When context.agent is supplied, the optional Agent plugin's private _toolContextCreate hook supplies missing tool capabilities. Missing names are ordinary errors, never a crash.
namestringargsobject[context]object
Returns Promise<*> — the tool's return value
tools(safe = false, selector) method
The tool catalog: name -> ToolInfo ({name, schema, safe, trusted, sandbox, secret, interactive, builtin, file?, storage?, status?, onTimeout?, detect?, readOnly?} — never the callable). Nested schemas/status values are shared live registry data, not defensive copies; mutating them can affect all consumers without a refresh/event. Dynamic availability is rechecked first (concurrent callers share one check). With a selector, that pair's provider tools (provider: true) shadow a global tool of the same name.
[safe=false]boolean- read-only (`safe: true`) tools only
[selector]string- `<endpoint>/<model>`
Returns Promise<Map<string, object>>