Commit Graph

16 Commits

Author SHA1 Message Date
kingwl
d46d37f372 Merge remote-tracking branch 'origin/master' into cross-family-fs-sandbox
# Conflicts:
#	docs/config-catalog.md
#	docs/module-graph.md
#	docs/rfc/INDEX.md
#	examples/acp-agent/composition.md
#	examples/acp-agent/cordis.yml
#	examples/acp-agent/tests/snapshots/advanced-toolchain/session.1.jsonl
#	examples/acp-agent/tests/snapshots/advanced-toolchain/session.2.jsonl
#	examples/acp-agent/tests/snapshots/advanced-toolchain/session.jsonl
#	examples/acp-agent/tests/snapshots/both-mode-turn/session.jsonl
#	examples/acp-agent/tests/snapshots/permission-switching/session.jsonl
#	examples/acp-agent/tests/snapshots/skill-load/session.jsonl
#	examples/acp-agent/tests/snapshots/text-turn/session.jsonl
#	packages/bash/bash-sandbox/package.json
#	packages/bash/bash/package.json
#	packages/bash/tool-bash/src/index.ts
#	packages/fs/fs/package.json
#	packages/fs/tool-fs/package.json
#	packages/fs/tool-fs/src/index.ts
#	packages/fs/tool-fs/tests/tools.spec.ts
#	pnpm-lock.yaml
2026-07-15 21:39:02 +08:00
kingwl
52aedf226d Merge remote-tracking branch 'origin/master' into cross-family-fs-sandbox
# Conflicts:
#	docs/config-catalog.md
#	docs/cordis-catalog/events.md
#	docs/cordis-catalog/services.md
#	docs/event-producer-consumer.md
#	docs/persistence-catalog.md
#	examples/acp-agent/cordis.yml
#	examples/acp-agent/tests/snapshots/advanced-toolchain/session.1.jsonl
#	examples/acp-agent/tests/snapshots/advanced-toolchain/session.2.jsonl
#	examples/acp-agent/tests/snapshots/advanced-toolchain/session.jsonl
#	examples/acp-agent/tests/snapshots/both-mode-turn/session.jsonl
#	examples/acp-agent/tests/snapshots/permission-switching/session.jsonl
#	examples/acp-agent/tests/snapshots/skill-load/session.jsonl
#	examples/acp-agent/tests/snapshots/text-turn/session.jsonl
#	packages/bash/bash-sandbox/src/index.ts
#	packages/bash/bash/src/types.ts
#	packages/bash/tool-bash/src/index.ts
#	packages/fs/fs/src/index.ts
#	packages/sandbox/sandbox-policy/src/session-mode.ts
#	packages/ui/permission/src/index.ts
#	packages/ui/permission/tests/permission.spec.ts
#	scripts/doc-budgets.manifest.json
2026-07-14 21:25:36 +08:00
kingwl
2dc62497ce feat(sandbox): cross-family file sandbox — one policy home, sandboxed fs provider, fs escalation parity
Extend SandboxMode enforcement from bash to the filesystem tools, the sandbox
RFC's deferred cross-family phase.

- dsh-sandbox-policy (new, ctx.sandboxPolicy): the single home for the
  deployment default mode + workspaceRoot and the per-session override event,
  renamed bash/sandbox-mode -> sandbox/mode and moved here with its fold/setter.
  Decouples the bash seam from dsh-session.
- dsh-fs-sandbox (new): SandboxedFileSystem extends LocalFileSystem and fences
  write/edit by the per-call mode (read-only denies, workspace-write contains to
  the workspace + temp roots via the shared writableRoots, danger passes
  through); reads pass through. Structured FS_SANDBOX_DENIED; in-lock parent
  re-canonicalization. A policy fence in trusted code, not a kernel boundary.
- dsh-sandbox: the shared escalation kit (writableRoots, the strictly-wider
  ladder, denial/hint markers, approveEscalation) both tool families use;
  approveEscalation takes a structural approver so dsh-sandbox gains no
  approval/agent dependency, and both tools stay duplication-free.
- tool-fs: write/edit advertise sandbox_permissions/justification under a
  confining ctx.fs, map FS_SANDBOX_DENIED to the shared [sandbox: ...] marker,
  and resolve the same one-approved-wider retry.
- examples/acp-agent: composes sandbox-policy + fs-sandbox, drops the gating
  that disabled the fs stack under confined modes.

RFC docs/rfc/implemented/feature/2026-07-14-cross-family-fs-sandbox.md; the old
sandbox RFC's In-process/deferred/FAQ sections updated to shipped fact.
2026-07-14 20:05:57 +08:00
Tianyi Cui
89aedb5ea6 Merge remote-tracking branch 'origin/master' into codex/simp-hide-fs-internals
# Conflicts:
#	docs/config-catalog.md
2026-07-14 17:33:52 +08:00
Tianyi Cui
f9db1a6a08 refactor: hide filesystem implementation helpers 2026-07-14 03:30:23 +08:00
Tianyi Cui
148046b9c8 docs: rebalance prose cleanup and add trimming skill 2026-07-13 23:27:00 +08:00
Tianyi Cui
75838e10b5 docs: trim generated prose 2026-07-12 03:36:43 +08:00
Tianyi Cui
48d25cdd44 Fix review findings: validate the hooks cap, integer read caps, doc drift, config plumb-through test
A Codex review pass on the draft caught four real gaps and two solid
suggestions; all addressed except one pushed back on the merits:

- hooks-claude/hooks-codex: stderrSummaryMaxChars was the one new knob
  with NO range validation — a negative/NaN cap would silently
  misbehave inside slice(). Both bridges now assert a positive integer
  at the TOP of apply() (before the config-file parse's early return,
  so a bad value fails the load loudly), with rejection tests.
- tool-fs: the read caps count lines/chars/bytes, so positive-FINITE
  was too loose (a fractional readLimit would flow into windowing
  arithmetic and the schema description). All four now require a
  positive integer, matching tool-web's cap.
- Doc drift the gates cannot catch: tool-web's README tools table
  still named WEB_SEARCH_MAX_RESULTS as the mechanism; compact-basic's
  README/module doc and the compaction-capability-seam RFC still
  described estimation as fixed char/4 rather than the charsPerToken
  default.
- subagent-acp: the dispose graces were tested only at the
  startAcpRun level, so a regression that stopped threading plugin
  config into AcpRunSpec would have survived. A provider-path test now
  drives the trap-escalation scenario through ctx.subagents.start with
  small config graces and bounds dispose at 4s.

Pushed back on: converting compact-basic's charsPerToken to a
schemastery field. The package's whole config is deliberately
hand-rolled (resolveConfig, every threshold REQUIRED with no default —
a documented design posture); one schemastery field beside it would be
incoherent. The knob is cordis.yml-reachable, defaulted, and validated,
which is what the convention requires; migrating the package to
schemastery wholesale is pre-existing config-surface hygiene out of
this change's scope.
2026-07-04 18:06:35 +08:00
Tianyi Cui
774d460889 Expose audited hardcoded tunables as plugin config
The audit swept every packages/*/* plugin for the new AGENTS.md
convention (no hardcoded tunables in plugins) and exposes each finding
as a defaulted, validated Config field. Defaults are the previously
hardcoded values throughout, so no deployment or golden changes.

- tool-fs (had NO Config): readLimit, readMaxLineLength, readMaxBytes,
  readStreamMinSize. The caps thread through ReadToolCaps/ReadWindow —
  read-render already documented that the consumer applies the caps, so
  they become explicit per-request fields.
- tool-web: searchMaxResults (WEB_SEARCH_MAX_RESULTS stays as the
  schemastery default). Also fixes the stale GREP_LIMIT references in
  search.ts and the web-capability-seam RFC (no such constant exists).
- bash-local: graceMs (SIGTERM->SIGKILL escalation grace). The
  RunInternals.graceMs test seam is gone: graceMs is now a required
  SpawnSpec field filled from config, so tests exercise the real
  config path and the defaults live in exactly one place.
- subagent-acp: disposeEofGraceMs / disposeGraceMs. The AcpRunSpec
  fields become required for the same one-defaulting-layer reason.
- session-persistence-sqlite: journalMode ('wal' default; the
  rollback-journal modes serve filesystems where WAL's shared-memory
  files do not work, e.g. network mounts).
- hooks-claude + hooks-codex: stderrSummaryMaxChars for the persisted
  hook/result stderr summary. The duplicated summarize() helpers merge
  into hook-protocol's summarizeStderr(stderr, maxChars), beside the
  HookResultRecord field it feeds, with the bound parameterized the
  same way runHook's defaultTimeoutMs already is.
- compact-basic: charsPerToken for the token estimator (default 4, the
  English-text heuristic; CJK-heavy deployments need ~1-2 or compaction
  fires far too late). Also corrects the BasicCompactService class doc,
  which claimed defaults the required-field config never had.
- fs-local: deletes the dead STREAM_MIN_SIZE constant and the dead
  FsIoInternals.streamMinSize seam — the read-routing bound lives in
  the consumer (tool-fs), where it is now config. This is item 1 of
  the proposed prune-write-only-fs-surface RFC, annotated accordingly.

Every new field gets range validation (following the existing
assertPositiveFinite pattern), a README row, and tests covering the
configured behavior, the schema default, and load-time rejection.
2026-07-04 17:37:23 +08:00
Tianyi Cui
d8fd3225af feat(tool-fs): result-time applied-hunk diffs for write/edit
fs write/edit now emit a result-time contextual-diff tool_call_update
(the applied hunk with ±3 context lines, one hunk per replace_all site),
matching what claude-agent-acp sends and what makes an editor render the
change in place. The call-time snippet diff stays; the result hunk
supersedes it (ACP content-replace).

Mechanism:
- A persisted tool-private `meta` channel: execute may return
  `{ content, meta }`; `meta` (JsonValue) rides on the tool/result event
  and is handed back to presentResult, so the diff reproduces on replay
  (event-sourced). JsonValue is now exported from dsh-session.
- The backend returns raw before/after text (storage facts) on
  FsWriteOutcome/FsEditOutcome; the tool computes the hunk via the npm
  `diff` package's structuredPatch. A create has no before → no result
  diff; a failed/aborted mutation carries no meta.
- ToolResultView gains a DiffResultView; the bridge's result-side switch
  renders it as {type:'diff'} content blocks.

RFC: docs/rfc/implemented/architecture/2026-07-02-result-time-applied-hunk-diffs.md
(justifies the npm `diff` runtime dep over vendoring and the meta channel);
the render-intent-union RFC's Non-goal is updated to record this shipped.
All fs snapshot goldens re-recorded; edit/overwrite gain the contextual
result diff, create/read/policy-reject unchanged in structure.
2026-07-03 17:12:00 +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
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
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
5e01564afb Add filesystem capability seam and tools 2026-06-22 10:48:41 +08:00