The ACP bridge gives each session its own workspace (SessionHeader.cwd), and
dsh-tool-bash already resolves a bash workdir against it. But ctx.fs.resolve(path)
took no caller context and dsh-fs-local resolved every relative path against a
fixed config.cwd (process.cwd() at plugin load) — so in the ACP demo `write
foo.txt` and `bash cat foo.txt` hit different directories the moment an editor
opens any project other than the server's launch dir.
Thread the session cwd into resolution, mirroring dsh-tool-bash: widen
FileSystem.resolve to resolve(path, opts?: { cwd?: string }); dsh-fs-local bases
a relative path on opts.cwd ?? config.cwd (absolute paths ignore it); the
read/write/edit tools derive it via a shared sessionCwd(exec) helper
(exec.agent?.session.header.cwd). The provider stays free of dsh-agent/dsh-session
— the tool projects exec → cwd and hands over a plain string, per the
explicit-at-seams convention. Backward compatible (the arg is optional).
Tests: fs-local resolve(path,{cwd}) bases relative on the passed cwd / ignores it
for absolute; tool integration writes/reads/edits in a session cwd != config.cwd
and verifies the file on disk (proven to fail on the pre-fix no-cwd path). Fakes
that stood in a bare {session:{}} now carry a header so sessionCwd doesn't throw.
RFC in docs/rfc/implemented/architecture/2026-07-02-fs-per-session-cwd.md.
25 lines
1.2 KiB
TypeScript
25 lines
1.2 KiB
TypeScript
/**
|
|
* Derive the working directory a filesystem tool resolves relative paths
|
|
* against: the calling agent's per-session workspace
|
|
* (`exec.agent.session.header.cwd`), so each ACP session's `read`/`write`/`edit`
|
|
* act on ITS workspace, not the server's launch dir — mirroring how
|
|
* `dsh-tool-bash` defaults a bash `workdir` to the session cwd.
|
|
*
|
|
* The `agent` is optional-chained — a non-agent caller yields `undefined`, and
|
|
* the tool then calls `ctx.fs.resolve(path)` with no base so the backend applies
|
|
* its own configured default (preserving the non-ACP / no-session behavior).
|
|
* `session`/`header` are non-optional on a real `Agent`, so only `agent` needs
|
|
* the guard (mirroring `dsh-tool-bash`'s `resolveWorkdir`). Returning `undefined`
|
|
* rather than reading `process.cwd()` here keeps the default in ONE place (the
|
|
* provider), per the "explicit > implicit at seams" convention.
|
|
*
|
|
* @module @deepseek-ai/dsh-tool-fs/session-cwd
|
|
*/
|
|
|
|
import type { ToolExecution } from '@deepseek-ai/dsh-tools'
|
|
|
|
/** The session workspace cwd for this call, or `undefined` when none applies. */
|
|
export function sessionCwd(exec: ToolExecution): string | undefined {
|
|
return exec.agent?.session.header.cwd
|
|
}
|