feat(acp): honor per-session cwd — run each ACP session in its own workspace
Lifts the RFC 010 § Deferred restriction that the server had to launch in the
workspace ("cwd must equal the launch directory"). An editor can now open any
project folder, and N concurrent sessions over one connection can each target a
different directory.
- packages/acp: drop the `cwd === process.cwd()` guard in validateWorkspaceParams
(keep "must be absolute" — the cwd becomes the session header / bash workdir),
and drop the persisted-cwd-vs-launch-dir check in session/load (a resumed
session keeps its original header.cwd, so its bash tools run in its workspace).
- packages/tool-bash: the missing link — default the bash workdir to the calling
agent's session cwd (`exec.agent.session.header.cwd`) via a new resolveWorkdir
helper. An explicit model `workdir` still wins; a relative one resolves against
the session cwd. This is the only correct spot for multi-session: N sessions
share one ctx.bash executor, so the workdir must come per-call from exec.agent,
not executor config. Falls back to the executor default when no session cwd is
available (preserves non-ACP behavior).
- Trust: the cwd originates from the ACP client (the user's editor) at
session/new — same trust level as the old launch dir; no new untrusted-input
path. `additionalDirectories` (scope widening / sandbox) stays rejected.
- Tests: bridge accepts any absolute cwd + records it on the header; session/load
honors the persisted cwd; bash defaults to / resolves relative against the
session cwd; two sessions with different cwds each run bash in their own dir;
non-absolute cwd still rejected. 100% per-file coverage maintained.
- Docs: RFC 010 status + § Deferred cwd bullet marked RESOLVED; acp README adds a
Per-session cwd section; tool-bash + example READMEs and e2e comments updated.
This commit is contained in:
@@ -28,8 +28,8 @@ Add to your Zed `settings.json` under `agent_servers`:
|
||||
}
|
||||
```
|
||||
|
||||
Run from the repo root (the MVP requires the server's launch directory to be the workspace — see the `cwd` note in `packages/acp`).
|
||||
The editor sets each session's `cwd` to the project it opens; the agent's bash tools run there (see the per-session `cwd` note in `packages/acp`), so the server does not need to be launched in the workspace.
|
||||
|
||||
## MVP limitations
|
||||
|
||||
The bridge supports N concurrent sessions per connection (RFC 011). Remaining limits: text-only prompts, `cwd` must equal the launch directory, and the tool-permission gate is deferred (`TODO(rfc010-permission-gate)` — tools run with the executor's full authority). See `packages/acp/README.md` for the full contract.
|
||||
The bridge supports N concurrent sessions per connection, each in its own workspace `cwd` (RFC 011). Remaining limits: text-only prompts, `additionalDirectories` rejected (a session operates in its single `cwd`), and the tool-permission gate is deferred (`TODO(rfc010-permission-gate)` — tools run with the executor's full authority). See `packages/acp/README.md` for the full contract.
|
||||
|
||||
@@ -28,9 +28,10 @@ import {
|
||||
|
||||
const startScript = fileURLToPath(new URL('../start.ts', import.meta.url))
|
||||
// Resolve tsx's loader to an ABSOLUTE path: the subprocess runs with cwd set to
|
||||
// a temp workdir (the MVP requires session cwd === process.cwd()), where a bare
|
||||
// `--import tsx` would not resolve from node_modules. import.meta.resolve gives
|
||||
// the worktree's tsx regardless of the child's cwd.
|
||||
// a temp workdir (this test launches there and uses it as the session cwd; the
|
||||
// bridge no longer requires cwd === the launch dir, but a temp dir keeps the
|
||||
// test hermetic), where a bare `--import tsx` would not resolve from
|
||||
// node_modules. import.meta.resolve gives the worktree's tsx regardless of cwd.
|
||||
const tsxLoader = fileURLToPath(import.meta.resolve('tsx'))
|
||||
|
||||
interface Spawned {
|
||||
@@ -123,7 +124,8 @@ describe.skipIf(!process.env.DEEPSEEK_API_KEY)('acp-agent e2e: real prompt over
|
||||
const { client, updates } = spawned
|
||||
|
||||
await client.initialize({ protocolVersion: PROTOCOL_VERSION, clientCapabilities: {} })
|
||||
// The MVP requires cwd === the server's launch dir (its cwd is `workdir`).
|
||||
// Any absolute cwd is honored now; use the temp `workdir` as this session's
|
||||
// workspace (the bash tool will run there) — it need not equal the launch dir.
|
||||
const { sessionId } = await client.newSession({ cwd: workdir, mcpServers: [] })
|
||||
|
||||
const res = await client.prompt({
|
||||
|
||||
Reference in New Issue
Block a user