Commit Graph
168 Commits
Author SHA1 Message Date
Yichen Jiang 803ed4bd95 feat(fs): add directory listing seam 2026-07-03 14:36:20 +08:00
Tianyi Cui 1a57d67058 refactor(tools): tagged render-intent union for tool-call presentation
Replace the "bag of optional fields" tool-presentation types
(ToolCallPresentation / ToolResultPresentation / ToolTerminal) with a
card-tagged discriminated union — the standing FIXME(tool-presentation).
A tool declares one render intent per call/result and the ACP bridge
switches on `card`:

  ToolCallView   = generic | terminal | diff
  ToolResultView = generic | terminal

The `diff` card is new: fs write/edit now emit an ACP {type:'diff'}
content block (an editor's inline diff), which the old shapes could not
express. The bridge also relativizes a file card's title against the
session cwd (mirroring claude-agent-acp's toDisplayPath) while keeping
locations/diff paths raw, and derives the no-capability fenced console
fallback from a terminal result's output. read gains the window-in-title
(`Read foo.txt (5 - 8)`) and an always-set location line, matching the
reference adapter field-for-field.

Migrates all three producer families (tool-fs, tool-bash, tool-todo) and
the sole consumer (the ACP bridge) together — the source does not compile
piecewise. Adds snapshot coverage for the terminal _meta path (a new
capability-advertising scenario) and re-records the fs goldens to show the
diff cards. Applied-hunk (result-time, context-line) diffs need a new
result/event shape and are a follow-up.

RFC: docs/rfc/implemented/architecture/2026-07-02-tool-render-intent-union.md
2026-07-03 02:04:03 +08:00
Tianyi Cui b3f8b4c9c6 test(fs): close abort/concurrency/observed coverage gaps + with-key e2e
Behavioral gaps from the coverage audit (line coverage was already 100%; these
close BEHAVIOR gaps):

- fs-local: service-level writeText/editText pre-abort → FS_ABORTED (file
  unchanged); concurrent guarded-write race and mixed write-vs-edit race (one
  wins, one FS_STALE_VERSION, locks released); edit→edit version refresh at the
  provider; the replaceIfVersion post-write version matches a fresh stat. fsio:
  a mid-stream abort → FS_ABORTED (previously only pre-abort was covered).
- fs-policy: the agent-without-session owner rung ({agent:{}} → no owner →
  createIfAbsent / FS_NOT_OBSERVED); fs/write-intent first-wins (symmetric to
  the existing edit-intent test).
- tool-fs: abort-through-the-tool for read/write/edit (isError FS_ABORTED, file
  unchanged); a deterministic tool-tier concurrent-edit race via a shared read;
  the throwing-fs/observed contract (a throwing listener surfaces as isError but
  the mutation already hit disk); the replace_all edit message; parseReadArgs
  rejects fractional/NaN offset and zero/negative limit.
- dsh-fs: FsError chains a cause through ErrorOptions.

New with-key e2e (packages/fs/tool-fs/tests/fs-tools.e2e.ts, self-skips without
DEEPSEEK_API_KEY): a real model drives the real read/write/edit tools to create
→ read → edit a file, verified on disk; a second test proves a relative path
resolves against the per-session cwd (factory meta.cwd) not config.cwd. Booted
via a plain tests/harness.ts. Added dsh-agent-loop + dsh-llm-deepseek devDeps.
2026-07-02 20:38:06 +08:00
Tianyi Cui bd7fb31ae3 feat(tool-fs): editor-facing presentation for read/write/edit
The fs tools rendered as generic cards (title = tool name, raw file content) in
an ACP editor. Give them tool-owned presentation like bash/subagent have:

- read → title "Read <path>", kind read, offset/limit as rawInput
- write → title "Write <path>", kind edit
- edit → title "Edit <path>", kind edit, a clipped old→new rawInput summary

Add a provider-neutral `locations: { path, line? }[]` to ToolCallPresentation —
the files a call reads/modifies — so a capable editor can follow along / jump to
the file (read carries its offset as the line). The ACP bridge forwards it onto
the wire `tool_call` (ResolvedCallPresentation + call() + the tool_call build in
streamSessionEventUpdate). This flips the `locations` cell in the ACP feature
matrix to supported. The SDK already carries `tool_call.locations`
(ToolCallLocation `{ path, line? }`), so no ACP types leak into dsh-tools.

presentResult is intentionally omitted: it only receives `{ content, isError }`,
not the write/edit outcome, so titling by create-vs-overwrite or replacement
count would mean parsing the model-facing text — the static title stays.

Tests: pure presentCall assertions for all three tools incl. locations and the
edit rawInput clip; a bridge test drives the REAL fs tools through ToolPresenter
and asserts locations reaches the wire tool_call (proven to fail without the
forwarding line). New withFs harness option + dsh-fs devDeps on dsh-acp.
2026-07-02 19:36:17 +08:00
Tianyi Cui 743eb9ea09 fix(fs): resolve paths against the caller's session cwd
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.
2026-07-02 18:29:29 +08:00
Tianyi Cui 30c1863755 fix(fs): address review — rename to dsh-fs-policy, fs/*-intent events, RFC currency, ENOTDIR
Rename per review naming decisions:
- package dsh-file-context → dsh-fs-policy (dir, package name, plugin name,
  tsconfig refs, importers, type-equiv manifest, generated catalog + module-graph)
- events fs/write-expectation → fs/write-intent, fs/edit-expectation → fs/edit-intent
  (fs/observed unchanged); type FsWriteExpectation → FsWriteIntent, "expectation"
  wording → "intent" throughout
- exported FileContextExec → FsPolicyExec

Make the implemented RFCs describe what shipped, not the superseded designs:
the 2026-06-17 capability-seam + tool-schemas RFCs no longer place policy on
ctx.fs or use full/partial-view authorization, and the fsspec RFC's ctx.fileContext
service prose is rewritten to the fs/* event-gate reality (freshness-based auth).
Sharpen docs/rfc/implemented/AGENTS.md: a rename is a fact to fix IN PLACE — the
"new RFC" escape hatch is for macro decision reversals only, not renames.

Code fixes from review:
- fsio.ts resolveLocalTarget/probe translate ENOTDIR (a parent path segment is a
  file) into the structured FsError taxonomy instead of leaking a raw Node error;
  resolve reports FS_NOT_FOUND, probe reports absent. Regression tests proven to
  fail on the unfixed code.
- tool-fs HMR test now asserts prompt sections (not just tool schemas) are
  withdrawn on disposal.
- fs/observed is a plain (unguarded) ctx.emit: correct the fs-policy comment,
  filesystem.md, and tool-fs module doc that wrongly claimed the tool "contains"
  a throwing listener; a throw surfaces as the tool's isError result.
- drop the false "loaded by the default product config" claim (no config wires
  the fs tools yet), the duplicate ctx.bash service-map row, the stale
  FileReadRequest catalog link-map entry, and the fs/fs README EOF blank line;
  correct the dsh-fs package.json description.
2026-07-02 03:12:38 +08:00
Dudu-0223 f8e99b8740 refactor(tool-fs): consolidate read rendering; drop the fs/observed try-catch
Two cohesion cleanups on the filesystem tool package:

- Fold window.ts + types.ts + formatReadOutput into one cordis-free
  read-render.ts. Line windowing, the FileReadOutcome shape, and output
  formatting are one concern (the read tool's rendering); splitting them across
  three files added no value. read.ts is now just the tool (schema + I/O).

- Drop observe.ts and emit fs/observed with a plain ctx.emit in read/write/edit.
  The event is contractually a synchronous, side-effect-only recorder
  (file-context's listener is a WeakMap.set), so the per-call try/catch guarded
  against a contract violation that cannot happen under the shipped listener —
  defensive code for an impossible case. The event contract (dsh-fs JSDoc,
  README, RFC) is updated to state the fire-and-forget semantics plainly.
2026-06-29 10:34:08 +08:00
Dudu-0223 4a1177093a refactor(tool-fs): drop per-tool subpath plugins; adopt single-tsconfig build
Adapt the four fs packages to master's single-tsconfig build convention
(lib/types outDir + types path + files allowlist), brought in by the merge.

While doing so, drop dsh-tool-fs's /read//write//edit subpath plugins. They
were the only subpath-export package in the tree and forced bespoke tsdown,
tsconfig path, package.json files, and workspace-constraint handling that no
sibling tool package (e.g. dsh-tool-bash) carries, for a focused-deployment
use case no consumer needed. dsh-tool-fs is now a single root plugin that
registers read/write/edit, mirroring dsh-tool-bash; the per-tool registration
helpers stay internal modules the root composes. The file-context event-gate
RFC is amended to record the narrowed scope.
2026-06-28 17:43:29 +08:00
Dudu-0223 89b27ad442 Merge remote-tracking branch 'origin/master' into fs-tool-clean
# Conflicts:
#	docs/architecture.md
#	docs/cordis-catalog/events-and-services.md
#	docs/core-data-structures/core.md
#	docs/module-graph.md
#	packages/README.md
#	scripts/type-equiv.manifest.json
#	tsconfig.base.json
#	tsconfig.typecheck.json
2026-06-28 17:24:45 +08:00
Dudu-0223 1059166cb1 fix: address codex review round 2
- edit tool: add the read-before-edit requirement to the model-facing prompt
  (with the just-created/edited-this-session exception), matching write's
  guidance so the model doesn't only learn it via a failed FS_NOT_OBSERVED call.
- fsspec-style-fs-seam RFC: correct the acceptance criteria that still claimed
  a ctx.fileContext service and a fileContext inject — the landed design is the
  fs/* event gate with the tool injecting fs.
- filesystem-tool-schemas RFC: replace the stale "prior full file state"
  edit requirement with version-freshness wording (any windowed read authorizes
  a fresh edit; no partial-view flag).
2026-06-28 14:15:04 +08:00
Dudu-0223 f9f475cbea fix: address codex review round 1
Correct the filesystem-wiring claims flagged by codex: no default/example
config wires the fs tools yet (the demo agents do file ops through bash), so
the docs and RFC no longer assert that "the default product config loads
dsh-file-context". They now state the intended stance — a deployment that
loads the fs tools is expected to also load dsh-file-context for
read-before-write/edit.
2026-06-28 14:02:51 +08:00
Dudu-0223 90dceea0e4 refactor(fs): make dsh-file-context an event-gate plugin, not a method service
Invert the tool↔policy control flow per the file-context event-gate RFC.
dsh-tool-fs becomes the executor — it reads/writes/edits through ctx.fs
directly, owns read windowing, and dispatches fs/write-expectation /
fs/edit-expectation (single-slot waterfalls) plus a contained fs/observed
emit. dsh-file-context drops its ctx.fileContext service and becomes a pure
event-gate plugin (observed-state + read-before-edit + version-guarded
write/edit, decided on those events). The provider's version guard becomes
optional so ctx.fs alone is a complete unconstrained text-storage seam:
removing the policy plugin gracefully loses the policy instead of breaking
the tool at a service-injection boundary.
2026-06-28 13:49:02 +08:00
Dudu-0223 d612ebaef1 fix: address codex review round 3
Record the freshness token observed AFTER the read (re-stat post-read, falling
back to the routing stat if the file vanished) so the version returned/recorded
matches the bytes returned — a writer racing between the routing stat and the
read can no longer make a follow-up edit spuriously stale. Stream reads when the
backend reports no size, so a size-less backend never buffers a large file
whole. Update the cordis-catalog link map to the current filesystem API symbols
(FileContextExec/FileReadRequest/FileReadOutcome/FsInfo/FsWriteExpectation).
2026-06-26 18:14:30 +08:00
Dudu-0223 b802912067 fix: address codex review round 2
Resolve targetKey by realpathing the nearest EXISTING ancestor and re-appending
the missing suffix, so a not-yet-created file under a symlinked ancestor with
missing intermediate dirs gets the same key before and after creation — keeping
observed-state intact across a write→edit cycle. Make the socket-type probe test
skip (not fail) when a sandbox forbids unix-domain sockets.
2026-06-26 17:59:40 +08:00
Dudu-0223 1409e2ed15 fix: address codex review round 1
Translate a mid-read AbortError from readFile into the seam's structured
FsError('FS_ABORTED') in readWholeText and readForEdit (the streaming/write
paths already did), and make the socket-type probe test reject on a listen
error instead of hanging where unix-domain sockets are unavailable.
2026-06-26 17:45:54 +08:00
Dudu-0223 ef37ce3b9d refactor(fs): split filesystem seam into provider ctx.fs + policy ctx.fileContext
Implements the split-the-filesystem-seam RFC. ctx.fs shrinks to a text-storage
provider seam (resolve/stat/readText/streamText/writeText/editText with branded
FsTargetKey/FsVersion and an explicit FsWriteExpectation); the new
dsh-file-context package owns the model-facing policy (read windowing,
observed-state, write/edit freshness) as the concrete ctx.fileContext service.

Authorization is now freshness-based rather than full/partial view: a windowed
read records the file version and authorizes a later edit when the file is
unchanged, removing the dead-end where reading lines 100-150 of a large file
could not edit line 120. editText stays a provider primitive so version guard +
literal match + atomic rewrite remain one critical section, and the stale check
runs before matching so a stale edit reports FS_STALE_VERSION. tool-fs injects
fileContext, never reaching around to ctx.fs (the no-bypass contract).
2026-06-26 17:23:18 +08:00
Dudu-0223 c7a197fb5f fix(fs-local): reject unsafe text observations 2026-06-22 18:51:30 +08:00
Dudu-0223 5e01564afb Add filesystem capability seam and tools 2026-06-22 10:48:41 +08:00