Round-four nits: the acp-snapshot README said 'the suite's hasPwsh probe
follows the executor's own resolution' — hasPwsh is caller-supplied, and the
resolvePwshPath-following behavior belongs to the shipped acp-agent caller;
reword to 'the caller-supplied hasPwsh probe (the shipped acp-agent suite
follows the executor's own resolution...)' on both language sides. And the
tool-bash README loses its only explicit pairing sentence with the dialect
guard reverted, so state the contract plainly: 'The tool contract is
bash-dialect — mount a bash-parsing executor' (both languages, pairing
re-recorded).
The ShellDialect marker on BashExecutor and the load-time rejection in
tool-bash/tool-pwsh force every test and example fake executor to declare
a dialect for a guard with no in-repo or plausible deployment to catch
(shipped compositions always pair the tool with its matching executor),
and they break the example composition suites that stub ctx.bash without
a dialect (agent-spine-demo/cli-demo/acp-demo lost the bash tool).
Keep the non-dialect doc fixes that landed with the attempt: the tool-bash
README bashEnv contract trim with the stale example import removed, the
acp-snapshot suite.ts 'usable pwsh' JSDoc, and the pwsh.cordis.yml comment
indent. The parity note records the attempt and revert under Alternatives.
The seam gains ShellDialect ('bash' | 'powershell' - concrete shells, not
families: zsh or fish would be their own values, never 'bash'); bash-local
declares bash (bash-sandbox inherits), pwsh-local declares powershell, and
both tools throw at load when the mounted executor speaks another dialect -
previously tool-pwsh over bash-local handed PowerShell text to bash -c and
the deployment error surfaced as ordinary nonzero exits. Pinned by mismatch
tests on both tools; the parity note records the contract (both languages).
Also from the review round: the tool-bash README's managed-environment
section becomes a summary linking the owning dsh-bash-env contract (the
duplicated prose carried a stale owner in its example import), the
pwshOnly JSDoc drops the stale 'on PATH' phrasing, and the task-tools
contract comment in the two pwsh compositions is indented into its block.
- tool-bash README (both languages) now shows the real four-service inject
and attributes the session-persistence contributor to dsh-bash-env.
- pwsh-local README names the also-exported ENV_OVERRIDES/ENCODING_PREAMBLE.
- agent-spine-demo drops the duplicate bash-env devDependency key.
The review caught the pwshCoverageExclusions comment claiming the inverse of
the code's behavior: a bare-PATH probe FAILING grants the exemption, so a
win32 host where only install-location pwsh (or 5.1) resolves kept the
exemption while its suites ran - over-exempting, never tightening.
resolvePwshPath/candidatePwshPaths move to the dependency-free
pwsh-local/src/resolve.ts (index.ts re-exports; public API unchanged) and
vitest.config.ts probes with that shared definition, so the exemption is
active exactly when the suites skip. The two spec headers stop saying
'on PATH' for a probe that is deliberately not PATH-only, and the parity
note records the abort backport as the one both-ways parity change (both
languages, pairing re-recorded); catalogs regenerated for the line shift.
- scrubbedParentEnv folds case before the DSH_ prefix check (Windows env
names are case-insensitive; a parent dsh_* entry read back as \*
in the child) and the service spec pins the lowercase probe.
- The acp.snapshot.ts pwsh probe follows resolvePwshPath() like the package
suites, so a Windows host with only an install-location pwsh still runs
the scenario.
- pwsh-tool-turn is re-recorded around [Console]::Out.Write('PWSH_OK'):
the fixture carries no platform newline, so one recording replays on
Windows and POSIX alike (record + refresh; replay-verified keyless).
- The pwsh-local Known Limitations bullet drops the self-defeating no-op
advice: & { } is scoped to param(...), using/#requires scripts run from a
file (both languages, pairing re-recorded).
- The capability-seams graph moves ctx.bashEnv ownership to bash-env and
lists pwsh-local/tool-pwsh on the ctx.bash seam (source updated,
docs regenerated).
- The tool-bash presenter fixture retires the stale 'command aborted'
literal for the shipped 'tool call aborted' message.
- jscpd: the executor/tool mirror dsh-bash-local/dsh-tool-bash by design
(Agent Note), so the mirrored regions carry explicit ignore markers with
reasons instead of being flagged as duplication.
- pwsh-local: a self-terminated process reports SIGTERM or SIGKILL on
POSIX (PowerShell's Stop-Process choice), not only SIGTERM.
- gen-tool-catalog.spec: the shipped-tool completeness list gains 'pwsh'.
Windows-native execution foundation: PwshLocalExecutor implements the bash
executor seam over ctx.subprocess (pwsh -NoLogo -NoProfile -NonInteractive
-Command, one argv element, no quoting layer; resolvePwshPath probes
PowerShell 7 / PATH / Windows PowerShell 5.1 as a pure function), and
tool-pwsh is the minimal PowerShell-dialect model-facing tool over ctx.bash
(foreground only, managed DSH_* env, timeout/signal/exit markers, terminal
and generic presenters). Both packages carry full suites (real pwsh,
self-skipping without it) at per-file 100% coverage; vitest's Windows
exclusion narrows from packages/bash/* to the bash-requiring packages so the
pwsh suites run natively on Windows too. The CLI gains the workspace deps
and tsconfig projects without mounting either plugin; the Windows-default
roadmap is recorded as a proposed Agent Note.
Drop SubprocessSpawnSpec.dshEnv and splitEnvChannels(); childEnv() is now
scrubbed-base + explicit entries with no namespace validation. The invariant
dropped is the reserved-namespace check on explicit entries (DSH_* rejected
from env, non-DSH_* rejected from dshEnv). Explicit-entry trust already
covers it: an explicit credential-shaped entry has always merged after the
scrub as a deliberate caller opt-in, and an explicit DSH_* entry is the same
deliberate act — the staleness invariant lives entirely in scrubbedParentEnv
dropping AMBIENT credential-shaped and DSH_* names, which stays. The
validation's only observed effect was rejecting legitimate explicit entries:
both recent CI breakages (DSH_GATE_CONCURRENCY exported into every job
crashing lsp specs, DSH_PERMISSION_MODE in acp config.env crashing the
child spawn) were this check firing on values a caller meant to pass, each
fixed by routing around the bureaucracy the seam itself imposed.
The bash seam keeps its own request/spec dshEnv field: that is bash-owned
trusted-plugin vocabulary (the ctx.bashEnv collected overlay) whose merge-last
position guarantees a caller env entry cannot displace a managed fact;
bash-local now flattens ENV_OVERRIDES -> spec.env -> spec.dshEnv into the
seam's one env map. subagent-acp and lsp-local pass their single config env
map straight through. DshEnvironment/DshEnvironmentKey/DSH_ENV_PREFIX stay on
the subprocess seam as the namespace vocabulary (bash re-exports them;
scrubbedParentEnv filters on the prefix).
Tests: the two channel-rejection specs and the splitEnvChannels partition
spec are deleted; one spawn spec now proves an explicit DSH_* env entry
reaches the child while an ambient one is scrubbed; the acp/lsp forwarding
specs keep their MOCK_ECHO_ENV / LSP_FAKE_ECHO_ENV assertions with the split
comments rewritten to merge-after-scrub. Docs (en+zh, re-recorded) and the
owning Agent Notes updated; cordis api/services catalogs regenerated.