Unifies built-in CLI capability, runtime, configuration and launch contracts through a packaged neutral manifest and implements registered native ACP (adds @agentclientprotocol/sdk 1.4.0, integrity verified against the lockfile). Tip merged cleanly into the branch. Independent validation: 129/129 tests across the 17 touched test files, typecheck:core, open-sse typecheck, eslint (with suppressions) on all touched files, check:cycles, check:docs-all, deps/lockfile policy, file-size, changelog-integrity and complexity green. ACP spawn uses shell:false with a sanitized child env. Not validated: live VPS run of native ACP/launchers.
5.7 KiB
| title | version | lastUpdated |
|---|---|---|
| ACP registry and registered CLI launchers | 3.8.51 | 2026-09-21 |
ACP registry and registered CLI launchers
OmniRoute separates CLI discovery, native Agent Client Protocol, and legacy stdio adapters. Finding an installed binary does not prove its authentication, model compatibility, or readiness to handle a prompt.
The dashboard uses GET /api/acp/agents and POST /api/acp/agents for inventory
and custom-agent registration. These are local-only management routes, not a
public API for spawning processes or submitting prompts. The internal
AcpManager does not automatically become an HTTP provider fallback.
Registered contracts
config/cli-tools-manifest.json is the source of truth for built-in launch
binaries, arguments, and backend modes. The registry derives its definitions
from that manifest. Detection is cached for 60 seconds.
acp: the Gemini contract launchesgemini --experimental-acpand speaks newline-delimited ACP JSON-RPC through the official TypeScript SDK.stdio-adapter: other registered contracts retain the legacy newline-input, stdout-output adapter. A two-second output idle period ends its response. This adapter does not certify native ACP support for those CLIs.
Gemini documents the launch flag in its CLI reference. The client uses the official ACP SDK for initialization, session creation, prompt requests, notifications, and cancellation.
Custom-agent definitions remain administrator-controlled launch contracts. Registering a binary and arguments grants that process the server user's local execution privileges; registration is not a sandbox. Version probes accept only the registered executable and a recognized version flag.
Internal launch API
import { acpManager } from "@/lib/acp";
const session = acpManager.spawn("gemini", {
cwd: process.cwd(),
// Pass only the provider variables deliberately assigned to this agent.
env: {
GEMINI_API_KEY: process.env.GEMINI_API_KEY,
},
});
try {
const response = await acpManager.sendPrompt(session.id, "Explain this project", 120_000);
// Consume response in the calling application.
} finally {
acpManager.kill(session.id);
}
spawn(agentId, options) resolves the executable and arguments from the
registered definition. The only caller options are cwd and env; the old
spawn(agentId, binary, args, env) signature and executable overrides are
rejected. HTTP launch contracts are not supported by this manager.
The child inherits the same operating-system, terminal, locale, and certificate allowlist as the CLI launchers. Server/provider secrets are not copied from the parent environment. Credentials needed by the chosen CLI must be passed explicitly or supplied through that CLI's own local authentication. The child still has the local user's filesystem permissions and may read its own config.
Native lifecycle and limits
- Spawn the registered binary, initialize ACP, and create a session rooted at the selected working directory. Initialization has a ten-second limit.
- Submit a prompt and collect text notifications for that session only. Completion is the prompt RPC response, not a period of stdout silence.
- Use one prompt deadline, including any unfinished initialization; the default is 120 seconds. Concurrent prompts in the same process are rejected.
- On native timeout, attempt
session/canceland terminate the process. A bounded 100 ms window lets the notification flush before termination. - Close transport state and remove the session when initialization fails, the connection closes, the process exits, or the caller kills it.
Tool permission requests are denied. No filesystem or terminal client capabilities are advertised. These restrictions do not sandbox the child binary itself or replace a CLI's own authorization settings.
Both native text and legacy stdout/stderr retain at most 1 MiB of characters, keeping the newest output with a truncation notice. An individual native wire frame is limited to 2 MiB of bytes before SDK parsing. Buffers reset per prompt.
kill(sessionId) sends SIGTERM, then SIGKILL after five seconds if the process
has not exited. Legacy prompt timeouts release listeners and timers but leave
the session available for another prompt; callers remain responsible for
kill() or killAll() when finished.
Events and inspection
The manager emits stdout, stderr, and exit, each with sessionId.
sessionError reports a sanitized transport error. The compatibility error
event is emitted only when it has a subscriber, so a missing binary cannot
cause an unhandled EventEmitter error.
getSession(sessionId)returns a managed session orundefined.getActiveSessions()excludes stopped or stopping sessions.sendInput(sessionId, input)is available only for a live legacy adapter; native ACP rejects raw input to protect its JSON-RPC stream.killAll()terminates every session managed by that instance.
Validation boundaries
Deterministic fixtures cover the native handshake, text output, denied permissions, cancellation, concurrent prompts, failed initialization, process exit, output limits, and secret isolation. Existing legacy buffer/listener regressions remain covered. These tests do not demonstrate a live Gemini login or successful provider inference; those require a separately authorized smoke test in the target environment.