muse-acp
v0.1.0
Published
ACP adapter for Muse Code — bridges Agent Client Protocol to Muse Session Protocol via @muse-code/sdk
Downloads
168
Maintainers
Readme
muse-acp — ACP Adapter for Muse Code
Bridges Agent Client Protocol (ACP) to Muse Code via its Muse Session Protocol (MSP).
Use Muse from any ACP-compatible client (e.g. Zed) without leaving your editor.
ACP client (Zed) ──stdio JSON-RPC──▶ muse-acp ──MSP / muse serve──▶ Muse engineHow it works
| ACP (client ↔ adapter) | MSP (adapter ↔ muse) | Owner |
|---|---|---|
| initialize | initialize handshake + muse serve spawn | msp/manager.ts |
| session/new {cwd} | session/start {workspaceRoot} | msp/manager.ts |
| session/prompt {prompt: ContentBlock[]} | turn/start {input: [{type:"text"}]} → TurnHandle iterators | bridge/contentMap.ts + msp/manager.ts |
| session/update notifications | item/delta, item/completed, turn/completed | bridge/streaming.ts |
| session/request_permission | ApprovalRouter (approval/requested → approval/decide) | msp/manager.ts |
| session/cancel | TurnHandle abort | msp/manager.ts |
Content blocks (text, image, resource, resource_link, audio) are mapped in src/bridge/contentMap.ts. Streaming deltas and tool calls are fanned out to ACP session/update via src/bridge/streaming.ts.
Install
git clone https://github.com/sanjay3290/muse-acp
cd muse-acp
npm install
npm run buildRequires Node 20+ and a muse binary on PATH (or set MUSE_BIN).
# check
muse serve --help
which muse # e.g. ~/.local/bin/museUsage
As ACP agent (stdio)
Add to your ACP client's agent config (Zed example):
{
"agents": {
"muse": {
"command": "node",
"args": ["/path/to/muse-acp/dist/src/cli.js"],
"env": { "MUSE_BIN": "/path/to/muse" }
}
}
}Or with the installed binary:
{
"agents": {
"muse": {
"command": "muse-acp"
}
}
}CLI options:
muse-acp --help
muse-acp --muse-bin /custom/path/muse --log-level debug
MUSE_BIN=/custom/muse muse-acp
MUSE_ACP_LOG_LEVEL=debug muse-acpLogs go to stderr only — stdout is reserved for ACP JSON-RPC.
Direct test (no editor)
# ACP initialize
echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":1,"clientInfo":{"name":"test","version":"0.1.0"}}}' \
| node dist/src/cli.js
# Full flow: initialize → session/new → prompt (needs muse binary)
# Use any ACP client that speaks session/new + session/promptProgrammatic
import { MspManager } from "muse-acp";
import { contentBlocksToInput } from "muse-acp/dist/src/bridge/contentMap.js";
const mgr = new MspManager();
const session = await mgr.createSession("/path/to/workspace");
const stopReason = await mgr.sendTurn(session.sessionId,
contentBlocksToInput([{ type: "text", text: "fix the failing tests" }]),
{ onSessionUpdate: (u) => console.log(u) },
);Project layout
src/
cli.ts # shebang entry, arg parsing, ACP stdio loop
acp/
agent.ts # ACP Agent handlers registered on @agentclientprotocol/sdk
msp/
manager.ts # MuseClient lifecycle (spawn, handshake, session store)
bridge/
contentMap.ts # ACP ContentBlock[] ↔ MSP input
streaming.ts # MSP TurnHandle → ACP session/update
util/
logger.ts # stderr-only logger
env.ts # muse binary resolution
vendor/
muse-sdk/src/ # vendored @muse-code/sdk (tdd SS7.1 facade)
msp-ts/ # vendored @muse-code/msp wire types (msp.d.ts)
tests/
contentMap.test.ts
streaming.test.ts
protocol.test.ts
acpServer.test.tsvendor/ is a build-time copy of muse-code-sdk (clients/sdk-ts/src + clients/msp-ts + schema/msp/msp.d.ts). It is not a fork — edits belong upstream in meta-models/muse-code-sdk.
Two local patches are applied on top of the vendor tree and will be lost on re-vendor. Re-apply each with patch -p1 < patches/<name>.patch after copying a new vendor tree:
patches/muse-sdk-multistage-approval.patch— drives the approval handler fromapproval/updated, notapproval/requestedalone (facade/approval.ts,facade/session.ts). A compound shell command (wc a.txt; echo x) is one approval with N stages; muse asks once and then advances every later stage withapproval/updatedonly. Without the patch the SDK decides stage 1 and the approval — and the turn — pends forever. Upstream fix belongs inApprovalRouter. Covered bytests/approvalStages.test.ts.patches/muse-sdk-connection-getter.patch— exposesMuseClient.connection(getter forturn/cancelinmsp/manager.ts). Upstream fix would be to expose aturnCancel/turnInterruptmethod on the facade or to makeMuseClient's connection accessible.
Protocol notes
- ACP version: 1, over the official
@agentclientprotocol/sdk. The SDK owns framing, routing and schema validation; this repo owns only the MSP translation. SDK 1.x carries the stable ACP v1 wire schema — itsexperimental/v2entry point is a separate, unstable surface and is not used. - Capabilities:
loadSession, andpromptCapabilitiesimage+embeddedContext. - Stop reasons: ACP's vocabulary is
end_turn,max_tokens,max_turn_requests,refusal,cancelled— there is nofailed. A turn that times out, errors, or loses its host rejectssession/promptwith a JSON-RPC error instead of resolving with a stop reason. The 120s deadline also sendsturn/cancelto muse so the host stops working a turn the client has been told about. session/cancelis a notification. A client that sends it as a request gets-32601.- MSP fingerprint: pinned from
muse-code-sdkschema manifest (sha256:…). Verified oninitializehandshake (advisory, viafingerprint.ts). - One
muse serveper adapter:MuseClientmultiplexes many ACP sessions over onemuse servechild (oneConnection). Eachsession/newmaps to one MSPsession/start. - Approvals:
Session.onApproval→ ACPsession/request_permission→approval/decide. Default-deny if no handler, if the client cancels, or if the request errors. MSP'sdecision+scopemap onto ACP's fourPermissionOptionKindvalues (allow_once,allow_always,reject_once,reject_always) — a scope that outlives the call is "always". - Multi-stage approvals: a compound shell command (
wc -c f.txt; cat f.txt) is ONE muse approval with one stage per command. Muse asks for each stage in turn, so the ACP client sees N separatesession/request_permissioncalls for one tool call. Expected, not a bug — in Zed that is several prompts for one command. Stage 1 arrives asapproval/requested; stages 2..N arrive asapproval/updated(see the vendor patch above). - Streaming:
TurnHandle.deltas()+TurnHandle.items()are consumed concurrently;TurnHandle.completed(Promise<TurnOutcome>) drivesstopReason.
Limitations
When muse-acp is spawned inside Muse Code's own sandbox (e.g. as a
muse.bash tool), muse serve cannot read ~/.config/muse/auth.json
(Operation not permitted). The adapter will return connection reached EOF
for session/new. This is expected — run muse-acp from your ACP client
(Zed, etc.) outside the Muse sandbox, or launch Muse with sandbox disabled.
Development
npm run build # tsc
npm run dev # tsx src/cli.ts (no build)
npm test # vitest (26 tests, no muse binary needed)
npm run lint # tsc --noEmitLicense
Apache License 2.0 — see LICENSE.
This repository vendors third-party code that is not Apache-2.0:
vendor/muse-sdk/ and vendor/msp-ts/ are from
meta-models/muse-code-sdk,
Copyright (c) Meta Platforms, Inc. and affiliates, licensed under the MIT
License. The full MIT text is preserved alongside each. Local modifications to
the vendored SDK are kept as patches in patches/ and documented above. See
NOTICE for the full third-party attribution.
