npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@tutti-os/agent-acp-kit

v0.7.6

Published

ACP-oriented toolkit for detecting, launching, and streaming local coding agents.

Readme

@tutti-os/agent-acp-kit

@tutti-os/agent-acp-kit lets a host application detect, launch, stream, cancel, and resume local coding agents through a stable TypeScript facade.

It is built for apps that want to support Codex, Claude Code, and ACP-compatible agents such as Gemini CLI, Cursor Agent, GitHub Copilot CLI, Factory Droid, fast-agent, OpenCode, Qwen Code, Kimi CLI, Kilo, Mistral Vibe, and Trae CLI without scattering provider-specific process, transport, MCP, skill, model, and event parsing logic throughout the app.

This is an embeddable host SDK. It is not a replacement for ACP clients such as acpx, and it is not a single-provider ACP adapter binary such as codex-acp.

Why This Exists

Local coding agents do not all expose the same interface:

  • Codex is CLI-first and can stream JSONL from codex exec --json.
  • Claude Code is CLI-first and streams stream-json output.
  • ACP-compatible agents speak JSON-RPC session protocols and can be discovered through the ACP Registry.
  • Host apps still need their own messages, sessions, tool permissions, replay, canvas state, billing, and product semantics.

This package sits in the middle. It owns local agent execution. Your application owns product behavior.

ACP-compatible Agents

ACP-compatible agents in the wider ecosystem include:

| Agent | Typical ACP command | | ------------------ | ---------------------------------- | | Gemini CLI | gemini --acp | | Cursor Agent | cursor-agent acp | | GitHub Copilot CLI | copilot --acp --stdio | | Factory Droid | droid exec --output-format acp | | fast-agent | uvx fast-agent-mcp acp | | OpenCode | opencode acp | | Qwen Code | qwen --acp --experimental-skills | | Kimi CLI | kimi acp | | Kilo | kilo acp | | Mistral Vibe | vibe-acp | | Trae CLI | traecli acp serve |

This table is for ecosystem orientation. Commands can vary by agent version, so use the registry as the source of truth. Built-in provider support in this package is listed below, and hosts can use createGenericAcpProvider() or a custom provider plugin when they already have an ACP command to launch.

For the latest installable agent list, see:

Install

npm install @tutti-os/agent-acp-kit

Other package managers work too:

pnpm add @tutti-os/agent-acp-kit
yarn add @tutti-os/agent-acp-kit
bun add @tutti-os/agent-acp-kit

Requirements:

  • Node.js 22 or newer.
  • ESM runtime.
  • Installed provider CLIs for the providers you want to use.

Local Quick Start

For a regular local host, the application owns the workspace directory and passes that path directly as cwd.

import {
  createClaudeProvider,
  createCodexProvider,
  createLocalAgentRuntime,
} from "@tutti-os/agent-acp-kit";

const runtime = createLocalAgentRuntime({
  providers: [createCodexProvider(), createClaudeProvider()],
});

const detections = await runtime.detect();
console.log(
  detections.map((item) => ({
    provider: item.provider,
    supported: item.supported,
    authState: item.authState,
    models: item.models,
    reason: item.reason,
  })),
);

for await (const event of runtime.run({
  runId: crypto.randomUUID(),
  provider: "codex",
  cwd: "/path/to/workspace",
  prompt: "Inspect this project and summarize the architecture.",
  model: "codex:gpt-5.4",
})) {
  if (event.type === "text_delta") {
    process.stdout.write(event.text);
  }

  if (event.type === "tool_call") {
    console.log("tool started", event.name, event.input);
  }

  if (event.type === "done") {
    console.log("run finished", event.status);
  }
}

What You Get

| Area | Included | | ----------------- | ----------------------------------------------------------------------------------------------------------------------- | | Runtime facade | detect(), run(), cancel(), listProviders() | | Providers | Codex, Claude Code, ACP presets, generic ACP, fake test provider | | Process runtime | command resolution, stdin prompt delivery, timeout, cancel, stderr tail, redaction | | Transports | JSONL, plain stdout, ACP JSON-RPC | | MCP delivery | normalized stdio/http MCP server config passed into provider launch plans | | Skills | materialized files, prompt injection, project-instruction style delivery, cleanup | | Events | normalized AgentEvent discriminated union | | Testing | fake provider, fake ACP peer, fixtures, conformance helpers | | Tutti integration | auto CLI-backed/standalone agent catalog, target-scoped composer options, dynamic skill context, browser-safe contracts |

Tutti Workspace Apps

Tutti apps keep platform integration behind @tutti-os/agent-acp-kit/tutti and keep actual Agent execution in an app-owned local runtime:

import {
  createDefaultLocalAgentRuntime,
} from "@tutti-os/agent-acp-kit";
import {
  loadTuttiAgentSkillContext,
} from "@tutti-os/agent-acp-kit/tutti";

const runtime = createDefaultLocalAgentRuntime();
const projectCwd = await resolveAppLocalProjectCwd(projectId);
const detectContext = { cwd: projectCwd };
const agents = await runtime.detect(detectContext);
const agent =
  agents.find(
    (item) =>
      item.isDefault && item.supported,
  ) ??
  agents.find((item) => item.supported);
if (!agent) {
  throw new Error("No local Agent is currently available.");
}
if (!agent.agentTargetId) throw new Error("Selected Agent has no target identity.");
const skills = await loadTuttiAgentSkillContext({
  agentTargetId: agent.agentTargetId,
  agentSessionId: runId,
  cwd: projectCwd,
  detectContext,
});
for await (const event of runtime.run({
  agentTargetId: agent.agentTargetId,
  runId,
  provider: agent.provider,
  cwd: projectCwd,
  prompt,
  skillManifest: skills.skillManifest,
})) {
  // Persist or stream normalized Agent events in the App.
}

The host owns both cwd and environment policy. Pass the app-local run or project directory explicitly; the kit does not derive a business workspace root from TUTTI_WORKSPACE_ROOT or another host path alias. When env is omitted, runtime detection and execution inherit the host process environment. When env is present, its values overlay that ambient environment. The kit does not apply an application-specific allowlist or remove arbitrary host variables; it only performs provider/runtime operational normalization such as removing a nested Claude session marker and extending PATH. A host that must exclude product secrets is responsible for constructing an appropriate process environment before invoking the runtime.

There is no app-facing mode switch. runtime.detect() is the single discovery API. When TUTTI_CLI is configured it uses agent list plus target-scoped composer JSON and preserves every exact Agent Target, including multiple targets backed by one runtime provider. Without TUTTI_CLI it performs direct Provider plugin detection. runtime.run() accepts the selected agentTargetId and applies its latest target-scoped model and reasoning defaults internally before launching the app-owned local Provider process. Composer permission defaults are UI presentation state and do not override an autonomous Workspace App run. Permission comes only from an explicit AgentRunInput.permission selection or the SDK autonomous default described below.

loadTuttiAgentCatalog() and loadTuttiAgentComposerOptions() remain available as low-level compatibility APIs, but Workspace Apps should not compose them themselves.

The integration negotiates both daemon generations during rollout. It prefers the agent-ID contract and falls back to the old provider contract only when the CLI reports that agent list is unsupported. The kit resolves provider runtime metadata internally from the selected target. Legacy fallback requires an exact target ID from the old catalog and fails closed when multiple targets share a provider; ordinary CLI failures never trigger fallback.

Apps that wrap a Provider plugin can preserve that customization:

const runtime = createDefaultLocalAgentRuntime({
  providers: customProviderPlugins,
});

Hosts can pass the same detectContext object to runtime detection and skill helpers. App-owned Tutti children use projectTuttiCliChildProcess at their child boundary and pass returned output through redactTuttiCliChildProcessText before logging or returning errors.

Apps do not construct daemon URLs or CLI argv, read catalog tokens, pass app IDs, or derive agent IDs from provider names. They persist and round-trip the exact agentTargetId returned by the live catalog. Provider IDs are open runtime metadata rather than the public selection identity. Claude Code runtime metadata is canonicalized to claude-code; legacy claude remains input-only.

Frontend code can import DTO types and guards without Node dependencies:

import {
  isTuttiAgentCatalog,
  type TuttiAgentCatalog,
} from "@tutti-os/agent-acp-kit/tutti/contracts";

Provider Support

| Provider | Status | Transport | Notes | | --------------------------- | ------------ | --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Codex | Supported | codex exec --json JSONL | Dynamic model discovery via codex debug models; per-run CODEX_HOME with copied auth and sanitized config; same-provider resume via codex exec resume --json <session> - | | Claude Code (claude-code) | Supported | claude -p --output-format stream-json | Canonical provider ID is claude-code; legacy claude input is accepted internally; supports fallback model hints, custom model pass-through, and same-provider resume via --resume <session> | | Tutti Agent (tutti-agent) | Supported | tutti-agent exec --json JSONL | First-party canonical provider; runs use a temporary TUTTI_AGENT_HOME derived from the VM-local source Home; authentication is probed with tutti-agent login status; no Nexight runtime alias | | Devin for Terminal | Experimental | ACP JSON-RPC | Shared generic ACP transport; command override DEVIN_ACP_BIN | | Hermes | Experimental | ACP JSON-RPC | Shared generic ACP transport; command override HERMES_ACP_BIN | | Kimi | Experimental | ACP JSON-RPC | Shared generic ACP transport; command override KIMI_ACP_BIN | | Kiro | Experimental | ACP JSON-RPC | Shared generic ACP transport; command override KIRO_ACP_BIN | | Kilo | Experimental | ACP JSON-RPC | Shared generic ACP transport; command override KILO_ACP_BIN | | Mistral Vibe | Experimental | ACP JSON-RPC | Shared generic ACP transport; command override VIBE_ACP_BIN | | Cursor Agent | Experimental | ACP JSON-RPC | Shared generic ACP transport with run-scoped selected skills; command override CURSOR_ACP_BIN | | Gemini CLI | Experimental | ACP JSON-RPC | Shared generic ACP transport; command override GEMINI_ACP_BIN | | OpenCode | Experimental | ACP JSON-RPC | Shared generic ACP transport with run-scoped selected skills; command override OPENCODE_ACP_BIN | | Qoder CLI | Experimental | ACP JSON-RPC | Shared generic ACP transport; command override QODER_ACP_BIN | | Qwen Code | Experimental | ACP JSON-RPC | Shared generic ACP transport; command override QWEN_ACP_BIN | | Generic ACP | Experimental | ACP JSON-RPC | Bring your own ACP agent command | | Fake | Test helper | In-memory async events | For host tests and conformance checks |

Built-in real local providers do not impose a provider-level concurrency cap. Hosts can still enforce stricter queueing, cancellation, or watchdog policies around runtime.run() when a product surface needs serialized execution.

Local Codex runs always use a run-scoped temporary CODEX_HOME. The temporary root comes from run env TMPDIR, TEMP, or TMP, then process env, then the OS default. The provider shares auth.json, sessions/, and plugins/cache/ with the requested CODEX_HOME or the user's default ~/.codex so token refreshes, native resume, and plugin assets stay durable across runs; plugin cache exposure is best-effort and does not block a run. It copies isolated config files such as config.json, config.toml, and instructions.md, preserves compatible config.toml settings such as custom model providers and base_url, removes Codex config values known to break current CLI parsing, disables Codex native multi-agent for single-process run lifecycle safety, and overlays any run-scoped MCP server config.

Codex and Tutti Agent keep one .agent-acp-kit-codex-root marker in the application working directory after the first successful preparation. The marker is project metadata used by Codex root discovery. It is deliberately not created and removed per run: concurrent runs share it, avoiding remote filesystem churn and preventing one finishing run from removing another run's root marker.

Host Integration Pattern

Treat this package as the local-agent execution layer, not as your application orchestrator.

Your host should own:

  • User, session, run, and message persistence.
  • Assistant message anchor creation.
  • Runtime policy, such as trusted local mode, default provider, default model, and tool allowlists.
  • Domain tools and MCP server creation.
  • Mapping AgentEvent into your app stream, websocket, or replay protocol.
  • Billing, job queues, media storage, canvas writes, and product state.
  • Cross-provider resume or handoff semantics.

This package should own:

  • Provider detection and capability reporting.
  • Provider-specific command args, env, MCP config delivery, and model normalization.
  • Process supervision and transport handling.
  • Provider output parsing into AgentEvent.
  • Cleanup of per-run temporary files it creates.

Keep the host adapter thin:

const mcpServers = [
  {
    name: "app-tools",
    type: "stdio" as const,
    command: process.execPath,
    args: ["/absolute/path/to/app-tools-mcp.js"],
    env: {
      APP_TOOL_TOKEN: runScopedToken,
      APP_DAEMON_URL: "http://127.0.0.1:3001",
    },
  },
];

for await (const event of runtime.run({
  runId,
  provider: selectedProvider,
  cwd: workspaceDir,
  prompt: userPrompt,
  systemPrompt,
  history,
  model,
  mcpServers,
  skillManifest,
  extraAllowedDirs: [workspaceDir],
  env: providerEnv,
  resume: resumeContext,
})) {
  await projectAgentEventToHostStream(event);
}

Events

AgentEvent is a TypeScript discriminated union. Narrow on event.type and TypeScript will expose the fields for that event variant.

if (event.type === "tool_result" && event.status === "failed") {
  console.error(event.error);
}

Common event types:

| Event | Meaning | | ---------------- | ----------------------------------------------------------------- | | status | Lifecycle progress such as detecting, spawning, running, warning | | thinking_delta | Incremental reasoning or thinking text when a provider exposes it | | text_delta | Assistant text | | tool_call | Normalized tool start | | tool_result | Normalized tool completion or failure | | stderr | Redacted stderr text | | error | Runtime or provider error | | done | Terminal event with completed, failed, or canceled |

For MCP tool events, name is the normalized short tool name. Providers that surface a server namespace may also include rawName and mcpServerName, so hosts can distinguish same-named tools exposed by different MCP servers while keeping backward-compatible short-name routing.

Hosts should persist enough event data for replay and should treat done as the terminal source of truth for a run. Individual error events are diagnostics, not terminal status by themselves.

Opt-in timing diagnostics

Apps can ask the runtime for secret-free preparation and execution timings by setting metadata.timingDiagnostics on a run:

for await (const event of runtime.run({
  runId,
  provider: "codex",
  cwd,
  prompt,
  metadata: { timingDiagnostics: true },
})) {
  if (event.type === "status" && event.diagnostic?.kind === "timing") {
    appLogger.info("agent_kit_timing", event.diagnostic);
    continue;
  }
  // Project normal provider events into the UI.
}

Timing diagnostics separate runtime preparation (process_env, tutti_run_context, and provider_plan) from provider execution (transport_started, provider_first_event, provider_first_text, provider_first_tool, and provider_done). Failed preparation and provider stream stages carry an outcome without including the error message. Each diagnostic contains stage and total elapsed milliseconds only; it never contains prompts, paths, environment values, MCP credentials, or provider output. Hosts should persist these events with their own run id and should not render them as user-visible agent status messages.

Codex reconnect progress such as Reconnecting... 2/5 (request timed out) is a transient provider retry state. The Codex provider maps those JSONL messages to status events with status: "warning" so hosts can show progress without ending the run.

Models

Use runtime.detect() to get Agent Targets, provider installation/support status, and authoritative model options.

const modelOptions = await runtime.detect();

Tutti agent catalogs are cached per CLI/workspace and composer options are cached per CLI/workspace/exact Agent Target for the lifetime of the runtime. Changing from the workspace root to an app-local project cwd therefore does not repeat provider/model discovery or temporarily invalidate the selected Agent Target; standalone no-argument detection is cached per Provider. After the catalog, credentials, models, or local CLI installation changes, call runtime.detect({ refresh: true }) to refresh it.

Provider behavior differs:

  • Codex: attempts dynamic discovery with codex debug models, then falls back to bundled or package model hints.
  • Claude Code: returns fallback hints such as sonnet, opus, haiku, and known full ids, then adds configured custom ids from the Claude settings file when present. Custom model ids can be passed through.
  • ACP providers: attempt model discovery through ACP session lifecycle when the peer supports it.

Provider plugin diagnostics remain internal to standalone projection. The public result intentionally contains only provider identity, supported, authState, models/default model, and an optional display-only reason.

Hosts should not hardcode Codex or Claude model lists above this package. If a UI needs additional custom models, keep that UI behavior in the host and pass the chosen id into AgentRunInput.model.

Permissions

Runs accept an optional provider-neutral permission selection. Pass both the semantic returned by the composer and its provider mode id when available:

permission: {
  modeId: selectedMode.id,
  semantic: selectedMode.semantic,
}

The SDK maps this policy to each provider. Workspace App runs default to full-access when the host omits permission: Codex uses its unrestricted sandbox mode, Claude uses --dangerously-skip-permissions, and ACP requests select a recognized permissive option when the peer offers one (otherwise the request is cancelled). An App can pass an explicit narrower semantic for a run. For ACP, every non-full-access semantic cancels permission requests because the protocol adapter cannot safely infer a tool's risk from a provider-specific option id. The permissionConfig.defaultValue returned by a Tutti composer is the UI's initial selection, not an explicit App run selection. Hosts that intentionally offer a permission picker must pass the user's explicit choice in AgentRunInput.permission; the automatic Tutti runtime integration never promotes the composer default into execution policy.

VM-local Codex Home

For Codex runs, pass the VM session user's existing Codex home explicitly:

for await (const event of runtime.run({
  runId,
  provider: "codex",
  cwd: workspaceDir,
  prompt,
  env: {
    ...process.env,
    CODEX_HOME: "/home/tsh-runtime/.codex",
  },
  mcpServers,
})) {
  // Project AgentEvent into the host protocol.
}

The value in CODEX_HOME is the source home owned by the VM session user. The kit creates a temporary per-run Codex home under TMPDIR, links auth.json and shared session/plugin state from the source home, copies and sanitizes config.toml, and overlays the run's MCP servers. It never treats an App runtime directory as the source login home.

Installing Local Providers

Hosts can expose an install action for supported local providers through one public function:

import { installAgentProvider } from "@tutti-os/agent-acp-kit";

const result = await installAgentProvider("codex");

Supported install targets are codex and claude. The function installs and checks the official provider CLIs only:

  • Codex install: npm install -g @openai/codex
  • Claude Code install: npm install -g @anthropic-ai/claude-code

ACP adapter packages such as codex-acp and claude-agent-acp are not required for these built-in providers. The install status still reports whether legacy adapter binaries are present as compatibility metadata, but adapter presence no longer affects availability, command selection, or post-install success.

The result is structured instead of throwing raw shell output. Failed installs include a failureReason such as install_command_failed, install_timed_out, or post_install_probe_failed. A successful install can still return auth_required in after.availability; hosts should then prompt the user to log in with the provider CLI.

Install, detection, and runtime launch envs include common local binary directories such as /opt/homebrew/bin, /usr/local/bin, ~/.local/bin, npm configured prefix bins, and npm's global prefix bin when it can be resolved.

MCP Tools

This package does not define product tools. It accepts mcpServers and converts them into the provider's expected format.

const mcpServers = [
  {
    name: "app-tools",
    type: "stdio" as const,
    command: "node",
    args: ["/absolute/path/to/app-tools-mcp.js"],
    env: { APP_TOOL_TOKEN: runScopedToken },
    toolTimeoutMs: 30 * 60_000,
    startupTimeoutMs: 2 * 60_000,
    // Optional override: "auto" | "prompt" | "writes" | "approve".
    // Run-scoped application MCP servers default to "approve".
    defaultToolsApprovalMode: "approve" as const,
  },
];

Timeouts are normalized by provider. Codex writes startup_timeout_sec and tool_timeout_sec into its per-run config. Claude Code writes per-server timeout for tool calls. Generic ACP providers receive only standard ACP MCP server fields because the ACP MCP server schema does not define timeout fields. Codex also writes default_tools_approval_mode; it defaults to approve so trusted run-scoped application MCP tools can execute in non-interactive runs. Hosts can explicitly choose auto, prompt, or writes for less-trusted MCP servers. Claude full-access runs use --dangerously-skip-permissions. Codex and Claude MCP delivery uses those provider-native, run-scoped config paths. Generic ACP providers receive normalized MCP servers through ACP.

Keep tool tokens run-scoped and short-lived. Do not pass broad application secrets or database credentials directly to agent processes.

Skills

skillManifest supports three delivery modes:

  • materialized-files: writes selected skills into the run-scoped native provider home when the provider supports it (CODEX_HOME/skills for Codex and TUTTI_AGENT_HOME/skills for Tutti Agent), or into a provider-owned temporary run root for Claude Code and Generic ACP providers, then references them in the prompt. Cursor, OpenCode, and the other ACP presets use this Generic ACP layout without replacing their authentication HOME.
  • prompt-injection: injects skill content into the provider prompt.
  • project-instructions: injects instruction-style skill content.

The package handles delivery and cleanup for runtime and adapter execution. A host that calls a provider's low-level buildLaunchPlan() directly owns the resulting launch-plan artifact lifetime. The host remains the source of truth for skill selection, permission, and storage.

Codex-compatible provider homes, Claude Code invocation artifacts, and Generic ACP selected-skill roots are run-scoped. Their parent directory comes from run env TMPDIR, TEMP, or TMP, falling back to the process env and then the operating-system temp directory. Consequently VM callers can set TMPDIR to an App runtime directory while non-VM callers need no special configuration. Selected skills never mutate the source or global provider home, or the project working directory. Generic ACP child-process TMPDIR, TEMP, and TMP point at an isolated tmp/ below the same run root so provider-generated logs and scratch files are reclaimed with the run. Claude Code's generated MCP config is kept in the same temporary run root. Generic ACP MCP definitions are delivered through ACP session/new and are not copied into generated files. For provider-owned roots, materializedPath is treated as an input hint only; the provider rewrites the effective path to <run-home>/skills/<safe-slug>.

Hosts can also pass skill manifests produced by external commands. Tutti workspace apps can use the Tutti subpath helper to load dynamic CLI skills, then decide how to merge Tutti's recommended system prompt with the app-owned prompt:

import { loadTuttiAgentSkillContext } from "@tutti-os/agent-acp-kit/tutti";

const tuttiContext = await loadTuttiAgentSkillContext({
  provider,
  agentSessionId: runId,
  cwd,
  commandEnvNames: ["MY_APP_TUTTI_CLI"],
});
const systemPrompt = [
  appSystemPrompt,
  tuttiContext.recommendedSystemPrompt?.content,
]
  .filter(Boolean)
  .join("\n\n");

for await (const event of runtime.run({
  runId,
  provider,
  cwd,
  prompt,
  systemPrompt,
  skillManifest: tuttiContext.skillManifest,
})) {
  await projectAgentEventToHostStream(event);
}

Cancellation And Resume

Use runtime.cancel(runId) or abort the signal passed into runtime.run().

const controller = new AbortController();
const stream = runtime.run({ ...input, signal: controller.signal });

controller.abort();
await runtime.cancel(input.runId);

Resume is conservative by design:

  • Same-provider resume may pass providerSessionId or resumeToken when the provider supports it.
  • If no provider resume metadata exists, pass resume: { mode: "fresh" }.
  • Cross-provider resume should be host-level handoff: rebuild prompt, history, and context, then start a fresh provider run.

Hosts should still pass durable history on every run. Native resume is an optimization for the same provider, not the only source of continuity:

const sameProvider = previousRun?.provider === selectedProvider;
const providerResumeId =
  previousRun?.providerSessionId ?? previousRun?.resumeToken;
const resume =
  sameProvider && providerResumeId
    ? {
        mode: "provider" as const,
        providerSessionId: previousRun?.providerSessionId,
        resumeToken: previousRun?.resumeToken,
      }
    : { mode: "fresh" as const };

for await (const event of runtime.run({
  runId,
  provider: selectedProvider,
  cwd,
  prompt,
  history: durableMessages,
  resume,
})) {
  if (event.type === "done") {
    await saveRunResumeMetadata(runId, {
      providerSessionId: event.sessionId,
      resumeToken: event.resumeToken,
    });
  }
}

Public API

Main export:

import {
  createLocalAgentRuntime,
  createCodexProvider,
  createClaudeProvider,
  createDefaultLocalAgentProviderPlugins,
  createGenericAcpProvider,
  installAgentProvider,
  type AgentEvent,
  type AgentRunInput,
} from "@tutti-os/agent-acp-kit";

Runtime control plane export:

import {
  createRuntimeControlPlane,
  inferRuntimeKind,
} from "@tutti-os/agent-acp-kit/runtime-control-plane";

Testing export:

import {
  assertProviderConformance,
  createFakeAcpPeer,
  createFakeProvider,
} from "@tutti-os/agent-acp-kit/testing";

Development

pnpm install
pnpm typecheck
pnpm test
pnpm build
pnpm pack:check

Release Workflow

Use .github/workflows/npm-package-release.yml for package releases.

Stable latest releases:

  • Open and merge a PR that bumps package.json to the target stable version.
  • Run the workflow from main with version_bump=current, dist_tag=latest, and dry_run=true first.
  • Re-run with dry_run=false to publish the current package.json version and push tag v<version>.

Beta and other prerelease packages:

  • Run the workflow from a feature or release branch.
  • Use dist_tag=beta and either a prerelease bump such as prepatch, preminor, premajor, or prerelease, or version_bump=custom with a prerelease version such as 0.3.0-beta.0.
  • The workflow commits the prerelease version bump back to the triggering branch, publishes the package, and pushes tag v<version>.

The workflow never commits directly to main. Non-latest releases must use prerelease semver versions, and latest releases must use stable semver versions.

Security

Local agents execute user-trusted CLIs on the local machine. Only enable this package in trusted local mode.

Recommended host policy:

  • Use run-scoped tool tokens with TTL and explicit revoke.
  • Do not pass Supabase, database, or cloud provider tokens directly to agents.
  • Redact stdout and stderr before persistence.
  • Clean per-run temporary directories.
  • Limit MCP tool allowlists per run.
  • Gate dangerous provider flags behind trusted local mode.
  • Persist terminal events durably so cancellation or failure cannot be overwritten by late process output.

License

@tutti-os/agent-acp-kit is licensed under the Apache License 2.0.

Roadmap

  • Stabilize the public AgentRunInput and AgentEvent contracts.
  • Expand provider conformance tests for ACP lifecycle edge cases.
  • Add more provider-specific adapters where shared ACP behavior is not enough.
  • Add first-class examples for desktop apps and local web apps.
  • Add repository-level CONTRIBUTING.md and SECURITY.md before broader external contribution.