acpsvc

package
v0.36.0 Latest Latest
Warning

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

Go to latest
Published: Jul 19, 2026 License: Apache-2.0 Imports: 38 Imported by: 0

Documentation

Index

Constants

View Source
const (
	// TerminalOutputMetaKey is the `_meta` key under which contenox streams live
	// shell-session output over the ACP WebSocket. It rides a session/update
	// notification whose sessionUpdate discriminator is TerminalOutputUpdateKind;
	// the whole thing lives in the spec's reserved `_meta` namespace, exactly like
	// WorkspaceConfigOptionsMetaKey — a conformant foreign client that does not
	// recognize the key (or the extension update kind) ignores it and keeps
	// working. The payload is a terminalOutputPayload: {sessionId, offset, chunk,
	// reset}. See docs/development/blueprints/beam/shell-sessions.md.
	TerminalOutputMetaKey = "contenox.terminalOutput"

	// TerminalOutputUpdateKind is the extension session/update discriminator that
	// carries a TerminalOutputMetaKey payload. Underscore-prefixed to mark it an
	// extension (mirroring libacp.ExtensionMethodPrefix); unknown to conformant
	// clients, which skip it.
	TerminalOutputUpdateKind libacp.SessionUpdateKind = "_contenox.terminalOutput"
)
View Source
const AgentMetaKey = "contenox.agent"

AgentMetaKey is the session/new (and session/list) `_meta` key a client uses to bind a session to a REGISTERED external ACP agent instead of the native task-chain engine: `{"contenox.agent": "<registered agent name>"}`. Absent = native chain path, byte-for-byte the historical behavior. It is a contenox extension living in the spec's reserved `_meta` namespace (the same precedent as WorkspaceConfigOptionsMetaKey); conformant clients that don't recognize it ignore `_meta` entirely.

View Source
const AgentModeConfigOptionID = "contenox.agent-mode"

AgentModeConfigOptionID is the reserved SessionConfigOption id under which an external session surfaces the DOWNSTREAM agent's session Modes (its SessionModeState) as a single synthetic "select" picker in the upstream client's toolbar. The ACP spec models session modes (session/set_mode + SessionModeState) and config options (session/set_config_option + SessionConfigOption) as two distinct surfaces; contenox does not expose a first-class mode toggle to its clients, so a downstream agent that advertises modes only (claude-code-acp: default/acceptEdits/plan/dontAsk/bypassPermissions, zero configOptions) would otherwise render an empty toolbar. Mapping the modes onto one synthetic config option — id AgentModeConfigOptionID, label "Mode", type "select", each availableMode as a value(id)→label(name), currentValue the currentModeId — lets the existing config-option picker render it; a set on this id is translated back to session/set_mode (see externalDriver.SetConfigOption), and a downstream current_mode_update is relayed as a config_option_update over this same id. It lives in contenox's reserved dotted namespace so it never collides with a downstream agent's own option ids.

View Source
const AgentModelConfigOptionID = "contenox.agent-model"

AgentModelConfigOptionID is the reserved SessionConfigOption id under which an external session surfaces the DOWNSTREAM agent's UNSTABLE model-picker state (its SessionModelState) as a single synthetic "select" picker in the upstream client's toolbar — the exact parallel of AgentModeConfigOptionID for session modes. Zed's claude-code-acp advertises a `models` state (availableModels + currentModelId) in its session/new response and switches models via the unstable `session/set_model` method (`unstable_setSessionModel`), a surface distinct from both session modes and config options; contenox does not expose a first-class model toggle to its clients, so mapping that state onto one synthetic config option — id AgentModelConfigOptionID, label "Model", type "select", each availableModel as a value(modelId)→label(name), currentValue the currentModelId — lets the existing config-option picker render it. A set on this id is translated back to session/set_model (see externalDriver.SetConfigOption). Unlike modes, the ACP session/update stream carries NO model-update kind, so there is nothing to relay after a switch: the (stateless) set_model response is the truth and the confirmed model is adopted locally. The model entries carry no effort/fast-mode facet (claude-code-acp's availableModels are modelId + name + description only), so this select has no sub-option for reasoning effort. It lives in contenox's reserved dotted namespace so it never collides with a downstream agent's own option ids, and it is placed after the synthetic mode option and before the downstream's own config options.

View Source
const (

	// WorkspaceConfigOptionsMetaKey is the initialize-response `_meta` key under
	// which contenox advertises the workspace-level (session-less) config
	// options. Sessions are minted lazily (on first prompt — see AcpChatPage's
	// handleSubmit), so a fresh chat has no session and therefore none of the
	// per-session config options that normally arrive with session/new. This
	// extension lets a client render the model/think/HITL/token-limit controls
	// on the empty chat, stage the user's choices, and re-apply them via
	// set_config_option right after session/new — crucial when the configured
	// default model is broken and the user must pick a working one BEFORE the
	// first (failing) turn. It is a contenox extension living in the spec's
	// reserved `_meta` namespace: conformant clients that don't recognize the
	// key ignore it entirely and simply wait for the per-session options.
	WorkspaceConfigOptionsMetaKey = "contenox.workspaceConfigOptions"
)

Variables

View Source
var ErrNoBoundSession = errors.New("acpsvc: no ACP transport bound to contenox session")

ErrNoBoundSession reports that no live ACP transport owns the contenox session named in the request context. serve's shared AskApproval keys its fallback on this: when the router cannot route (a headless/API caller, or a session with no live WS connection), the engine's HITL request goes to the approval-API path instead of hanging on a permission prompt nobody answers.

Functions

func CleanupStaleACPManagedMCPServers added in v0.29.0

func CleanupStaleACPManagedMCPServers(ctx context.Context, db libdb.DBManager) error

CleanupStaleACPManagedMCPServers removes client-scoped ACP MCP registrations left behind by a previous process. Durable MCP configuration must be created through the normal `contenox mcp` commands or HTTP API; session/new and session/load MCP servers are temporary by ACP contract.

func New

func New(deps Deps) libacp.AgentFactory

func NewACPCommandRunner

func NewACPCommandRunner(transport func() *Transport) localtools.CommandRunner

func NewACPCommandRunnerWithShell added in v0.28.4

func NewACPCommandRunnerWithShell(transport func() *Transport, shell localtools.PlatformShell) localtools.CommandRunner

func NewACPCwdResolver

func NewACPCwdResolver(transport func() *Transport) func(context.Context) string

func NewACPFileIO

func NewACPFileIO(transport func() *Transport) localtools.FileIO

func NewServeCwdResolver added in v0.36.0

func NewServeCwdResolver(db libdb.DBManager, defaultRoot string) func(context.Context) string

NewServeCwdResolver returns the cwd resolver for the serve path, where a single shared local_fs tool is consulted by many per-connection transports — so it cannot close over one transport the way the stdio path does. Instead it resolves the session's persisted workspace cwd from the database (keyed by the internal session id in ctx), falling back to defaultRoot when the session has none, its stored cwd is the legacy "/" sentinel, or there is no session in scope. The stored cwd is already validated against the allowlist at session/new time, so this read is trusted; defaultRoot is the Factory default.

func ReadConfigValue

func ReadConfigValue(ctx context.Context, db libdb.DBManager, key string) string

Types

type ChainRegistry

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

func LoadChainRegistry

func LoadChainRegistry() (*ChainRegistry, error)

func LoadChainRegistryFrom

func LoadChainRegistryFrom(filename, envVar string) (*ChainRegistry, error)

LoadChainRegistryFrom loads the ACP chain for a specific profile: filename is the ~/.contenox/ file the chain is read from, envVar overrides that path. A missing file is a hard error (fail closed) — callers must not fall back to a different chain.

func (*ChainRegistry) Default

func (*ChainRegistry) Source

func (r *ChainRegistry) Source() string

type Deps

type Deps struct {
	Engine             *enginesvc.Engine
	DB                 libdb.DBManager
	ChainRegistry      *ChainRegistry
	DefaultModel       string
	DefaultProvider    string
	DefaultAltModel    string
	DefaultAltProvider string
	DefaultMaxTokens   string
	DefaultThink       string
	WorkspaceID        string
	// ContenoxDir is the active .contenox directory, used to locate auxiliary
	// chains (e.g. chain-compact.json for the /compact command).
	ContenoxDir string

	// WorkspaceRoots is the allowlist of directories a client may choose as a
	// session's workspace (its cwd). When nil, no allowlist is enforced and any
	// absolute cwd is accepted — the historical behavior for the stdio ACP path,
	// where the editor owns the filesystem. serve sets it so a browser client
	// can only root a session inside an operator-approved directory. The sentinel
	// cwd "/" (what beam sends today) and an empty cwd both resolve to the
	// default root, so existing clients keep working.
	WorkspaceRoots *vfs.Factory

	// ShellSessions manages the per-chat-session persistent PTY shells behind the
	// shell-session surface (the terminal panel + shell_session_run/read tools).
	// Nil when shell tooling is disabled: the terminal extension methods report
	// method-not-found and no live output is streamed — the feature is absent,
	// not broken.
	ShellSessions shellsession.Manager

	// KnownPolicies are the HITL policy preset names shown by /policy when
	// listing. Display only — empty just omits the list.
	KnownPolicies []string
	// HITLDefaultPolicyName is the policy the engine falls back to when no
	// override is set, shown by /policy so the status is accurate. Display only.
	HITLDefaultPolicyName string

	// UpdateBanner is an optional one-shot message sent to the client as an
	// agent_message_chunk on the first session created or loaded. Empty = no banner.
	UpdateBanner string

	// EnvSetup enables the env_var auth method: in setup-only mode initialize
	// advertises Vars as the environment the client should collect/set, and
	// authenticate with the env method calls Complete to finish setup
	// non-interactively from the current environment. Nil disables the method.
	EnvSetup *EnvSetupSpec

	// PermissionRouter, when set, is a process-shared registry each transport
	// records its live (contenox session -> this transport) bindings into, so a
	// single shared engine can route a HITL approval back to the WS connection
	// whose client raised it. serve sets it (it hosts many ACP WS connections
	// behind one engine); the stdio ACP path leaves it nil — it has one
	// transport, late-bound directly into the engine's AskApproval closure.
	PermissionRouter *PermissionRouter
}

type EnvSetupSpec added in v0.36.0

type EnvSetupSpec struct {
	Vars     []libacp.AuthEnvVar
	Complete func(ctx context.Context) error
}

EnvSetupSpec describes environment-variable-based setup (the non-interactive sibling of the terminal setup wizard).

type PermissionRouter added in v0.36.0

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

PermissionRouter maps a contenox session id to the ACP Transport that owns it. serve runs many ACP WebSocket connections (each its own Transport) behind a SINGLE shared engine, so the engine's one AskApproval callback cannot close over a single transport the way the stdio ACP path does (acp_cmd.go late-binds its lone transport directly). Instead each Transport registers its live (contenox session -> this transport) bindings here, and serve's AskApproval consults the router to dispatch session/request_permission to the exact WS connection whose client raised the gated tool call — the one beam's PermissionGate is waiting on.

The stdio ACP path leaves Deps.PermissionRouter nil: it has exactly one transport and needs no routing.

func NewPermissionRouter added in v0.36.0

func NewPermissionRouter() *PermissionRouter

NewPermissionRouter returns an empty router ready to be shared across a serve process's ACP WebSocket transports.

func (*PermissionRouter) AskApproval added in v0.36.0

AskApproval bridges an engine HITL request to the ACP transport that owns the contenox session named in ctx (runtimetypes.SessionIDContextKey), driving that connection's session/request_permission flow. It returns ErrNoBoundSession when no live transport owns the session so the caller can fall back to a non-ACP approval path; a genuine deny resolves as (false, nil) and a client cancellation as (false, context.Canceled) — neither is ErrNoBoundSession, so neither triggers a fallback.

type Transport

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

func (*Transport) AskApproval

func (t *Transport) AskApproval(ctx context.Context, req hitlservice.ApprovalRequest) (bool, error)

func (*Transport) Authenticate

func (*Transport) Cancel

Cancel handles session/cancel: it aborts the session's in-flight prompt turn with context.Canceled semantics. Prompt's error path keys the silent-cancel on errors.Is(err, context.Canceled) and resolves the prompt with stopReason "cancelled" (never a JSON-RPC error), per the ACP contract. A cancel for a session with no running turn is a clean no-op.

func (*Transport) Close

func (t *Transport) Close(ctx context.Context) error

func (*Transport) CloseSession added in v0.36.0

CloseSession releases the connection-local resources of a session without touching its stored history. Closing an unknown session succeeds: the desired state (not open here) already holds.

func (*Transport) DeleteSession added in v0.36.0

DeleteSession removes the session's stored history (and any connection-local state). Per spec, deleting a nonexistent session succeeds silently, and the session disappears from session/list.

func (*Transport) Initialize

func (*Transport) ListSessions

func (*Transport) LoadSession

func (*Transport) Logout added in v0.36.0

Logout is not supported: contenox's auth model (the terminal/browser/env setup wizards above) has no persisted "logged in" session to tear down, and Initialize never advertises AgentCapabilities.Auth.Logout, so a conformant client will never call this.

func (*Transport) NewSession

func (*Transport) Prompt

Prompt resolves the session and dispatches the turn to its driver. The driver (native chain engine vs. registered downstream ACP agent) owns everything the turn does — there is no native-vs-external branch here.

func (*Transport) ResumeSession added in v0.36.0

ResumeSession is session/load without the history replay: the client kept its transcript and only needs the server-side session re-bound.

func (*Transport) SetSessionConfigOption added in v0.29.0

func (*Transport) SetSessionMode added in v0.36.0

SetSessionMode is not supported: contenox does not model a Zed-style Ask/Code session mode toggle as a first-class session/set_mode capability — the equivalent controls (model, HITL policy, think level) are exposed as session config options instead. Initialize never returns a Modes state in session/new or session/load, so a conformant client will never call this.

func (*Transport) SetSessionModel added in v0.36.0

SetSessionModel is not supported on contenox's OWN upstream surface: the runtime never advertises a `models` state (SessionModelState) to its clients — for an external session the DOWNSTREAM agent's model picker is surfaced as the synthetic AgentModelConfigOptionID config option and switched via set_config_option (which the driver translates to the downstream's session/set_model), and a native session exposes no model picker of this UNSTABLE shape at all. So a conformant client never calls this; it reports MethodNotFound, mirroring SetSessionMode.

Jump to

Keyboard shortcuts

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