Skip to content

Latest commit

 

History

History
745 lines (617 loc) · 55.4 KB

PROVENANCE.md

File metadata and controls

745 lines (617 loc) · 55.4 KB

Provenance

What this repository is, where its parts came from, what was verified, and what was not.

Unreleased: OPS PTC preset fragment

presets/ops-ptc.patch.yml is a generated copy of @deepseek-ai/dsh-web-app/presets/ptc.patch.yml (MIT, Copyright (c) 2026 DeepSeek), changed only in identity: declaration id preset-ops-ptc, preset id ops-ptc, display name/description and order. scripts/sync-preset-ops-ptc.mjs regenerates it from the installed host, --check refuses drift, and the file header records the source path and its SHA-256. The fragment is not a bundle row: dsh-ops parses it at runtime with the host's loadOverlayPatches and registers the preset through agentPresets.register, so no preset declaration is installed into the profile. The upstream default PTC preset remains the host's own and is not modified.

presets/ops-ptc.patch.yml 是 @deepseek-ai/dsh-web-app/presets/ptc.patch.yml(MIT,2026 DeepSeek)的生成副本, 只改了身份:声明 id、preset id、名称/描述与排序;scripts/sync-preset-ops-ptc.mjs 从已安装宿主重新生成, --check 拒绝漂移,文件头记录来源路径与 SHA-256。该片段不是 bundle 行:dsh-ops 在运行时用宿主的 loadOverlayPatches 解析它,再通过 agentPresets.register 注册预设,因此不会把预设声明装进 profile。 上游官方 PTC 预设本身未被修改。

Unreleased: batch-call guidance prompt

prompts/batching-guidance.md is original text written for this project, not copied from a third party. It ships in the package's files and is read at runtime by loadBatchingGuidance() in src/ptc/batching-guidance.ts; operators may edit it in an installed copy, and an unreadable or empty file skips delivery instead of failing a tool call. Revision marker 2 identifies the current wording contract in plugin-context messages.

prompts/batching-guidance.md 是本项目自研文案,非第三方拷贝;它随包发布,由 src/ptc/batching-guidance.ts 的 loadBatchingGuidance() 在运行时读取。操作者可直接编辑安装副本;读不到或为空时跳过投递,而不影响工具 调用。插件上下文消息里的 revision 2 标识当前文案契约。

Unreleased: optional Windows Bash payload and host PowerShell

The npm package and release assembly declare this distribution's own Windows Bash platform package as an OPTIONAL dependency: @dsh-ops/bash-win32-x64@0.2.1, published from the pinned upstream PortableGit release (v2.56.0.windows.2, GNU bash 5.3.15), so npm fetches it automatically on Windows x64. POSIX keeps using the host Bash and adds no payload; optional-dependency resolution is silent on failure, so dsh-ops bash-runtime install (with the 0.4.0 runtime settings pages) stays the selection and repair path. PowerShell is selected entirely by the host; OPS no longer changes its pwshPath or activates legacy provisioned copies. The exported strict TypeScript manager previews a standard Windows installation and executes WinGet only on request. It follows Microsoft's installation guidance and preserves installer prompts. WinGet's 0x8A15002B is reported only as no applicable update; cancellation after launch has unknown effects. No system installer was run during validation: fixture runners cover argv, exits, cancellation and preservation, while a real child tests spawn reporting.

npm 包与发布组装重新声明本发行自己的 Windows bash 载荷包为可选依赖:@dsh-ops/bash-win32-x64@0.2.1,由固定的上游 PortableGit 版本(v2.56.0.windows.2,GNU bash 5.3.15)发布,Windows x64 上由 npm 自动拉取。POSIX 继续使用宿主 bash,不新增载荷;optional 依赖解析失败是静默的,因此 dsh-ops bash-runtime install(以及 0.4.0 的运行时设置页)仍是选择与修复路径。 PowerShell 完全由宿主选择,OPS 不再修改 pwshPath 或启用旧托管副本。 严格 TypeScript 管理器默认预览,显式请求才通过 WinGet 安装标准 PowerShell,保留安装提示。 验证未运行系统安装器:fixture 覆盖参数、退出码、取消及保留行为,真实子进程验证启动反馈。 下文旧版本的载荷与覆盖机制仅是历史记录,不描述当前行为。

Unreleased: host Bash contract

The shell contract, approval and job adapters in src/shells/ adapt @deepseek-ai/dsh-tool-bash 0.2.0-rc.2 (MIT, Copyright (c) 2026 DeepSeek). The executor was checked against @deepseek-ai/dsh-bash-local at the same version. OPS selects an absolute executable, preserves its argument and Windows IPC validation, and borrows host services without replacing them. The upstream MIT license is retained in licenses/deepseek-harness/LICENSE; the package adds no host value imports.

Shell 契约、审批与 jobs 适配改写自同版本宿主工具(MIT,2026 DeepSeek), 执行器参照同版本 bash-local 核验。OPS 保留绝对执行路径、参数及 Windows IPC 校验, 只借用宿主服务,不替换服务,也不增加宿主值导入。上游许可证保留于上述路径。

Unreleased: FastCtx removed

The FastCtx-backed dsh-ops/background component was removed together with the vendored FastCtx source (vendor/fastctx/), this fork's FastCtx payload package (@dsh-ops/fastctx-<platform>-<arch>, no longer a dependency or a release artifact), the optional @fastctx/darwin-arm64 dependency, and the CLI commands that resolved, built or provisioned FastCtx. No FastCtx code or binary is part of this distribution any more. FastCtx's Apache License 2.0 text and upstream notice are kept in licenses/fastctx/, and its required attribution is kept verbatim in NOTICE and the READMEs for the earlier versions that carried it. The current distribution is MIT; the composite MIT AND Apache-2.0 statement applies only to those earlier FastCtx-containing releases.

The sections below are records of the releases they name. Where they describe FastCtx, vendor/fastctx/, the background component, or the FastCtx payload package, they describe those releases, not this one; those files remain in the repository history and in the published tarballs of the versions that carried them.

Current target matrix (0.4.x)

The current package declares win32-x64, linux-x64, darwin-arm64 and darwin-x64. The 0.3.1 record below is historical: it shipped Apple Silicon only and did not include Intel. macOS 12's CLONE_ACL fallback is a separate follow-up; see macOS.

0.3.1 release scope and verification

0.3.1 targets Windows x64, Linux x64 and macOS Apple Silicon (darwin-arm64). Intel Mac and other ARM64 targets are not supported. Exact target pairs live in PLATFORMS and component targets; npm os/cpu filters are only their coarse projection. Windows Bash/pwsh remain pinned Windows-only optional dependencies; POSIX uses host Bash. No postinstall downloader is included.

The fixed file worker implements inspect/glob/grep/replace, receives session-cwd paths plus authoritative DSH write policy, and rechecks the writable-root allowlist in Rust before replacement. It uses the trusted-operation threat model of DSH fs-sandbox, with no-follow paths, revision checks and host subprocess governance. Arbitrary Bash/PTC code still uses the original DSH OS confinement; Windows partial enforcement is reported unchanged.

Windows workspace roots whose DACL already grants WRITE_OWNER are used as-is whatever their owner; otherwise an owned root gets a backed-up directory-only WRITE_OWNER before outer run_code/pwsh launch, with no recursive grants/owner changes/denial removal (#651). Restricted Bash uses an installation-relative private MSYS copy: only import DLL names are redirected to original MIT IPC proxies, preserving the token and disk API paths. Original payload/proxy/source hashes are recorded and checked. Only the original GPL payload is an existing dependency; no modified MSYS DLL or complete private copy is included in the 0.3.1 tarball. Runtime modifications are identified in the local receipt; original licenses remain in the copy. This does not attest source-delivery closure for the historical PortableGit payload dependency (see the 0.2.1 historical limitation below).

All three workers are staged with version/target/hash receipts. Formal release closure requires every exact target and the Windows IPC binary/source closure. Local check --staged is a development check, not release approval. Final verification is linked in docs/release-0.3.1.md and the GitHub Release; early contributor measurements are not substituted for the integrated revision. GitHub publication does not automatically publish npm.

The 0.3.1 Release notes headed that asset table Target | SHA-256 and listed the worker digest — the value runtime/<target>/provenance.json records — not the digest of the attached native-<target>-runtime-v0.3.1.zip; the 0.3.2 notes kept the same heading and listed the ZIP digest. Both values are meaningful and they cover different bytes, so comparing one against the other reports a false mismatch. The published bodies are left as they were; this paragraph is the correction, recorded here the same way as the notes-only Releases below.

Historical development evidence

The file component is no longer a FastCtx client. native/src/file.rs implements bounded inspect, glob, grep and replace; native/src/file_path.rs implements absolute host-resolved path validation and component-by-component no-follow checks. The host adapter in lib/file/ obtains the authoritative DSH session cwd and sandbox policy, forwards cancellation, and maps worker JSON into the DSH structured output contract. The initial exact-argv file confinement below was superseded by the fixed trusted-operation fence described above.

At the time of the Windows-only 0.3.0 development, the worker was built with the managed Rust 1.97.1 toolchain and staged under runtime/win32-x64 with a version/target/executable/SHA-256 receipt. cargo check, cargo build --release --locked, cargo clippy --all-targets -D warnings, manifest validation and the local JavaScript suites passed. The six-target release matrix and Linux/macOS runtime acceptance were not claimed at that point; Linux x64 file and PTC acceptance was added afterwards and is claimed above. FastCtx source/vendor remained unchanged and was retained only for the explicitly disabled background component, until both were removed (see "Unreleased: FastCtx removed").

The independent MIT Rust core in native/ is original dsh-ops code. It uses cap-std/cap-fs-ext handle-relative filesystem operations, serde, SHA-256, UUIDs, regex and fs2 workspace locking. No FastCtx source was modified or linked by the file component; legacy FastCtx remained only behind the disabled background component until its removal, and all applicable notices remain intact.

Host contract source (DSH 0.2.0-rc.2):

  • packages/core/tools/src/index.ts: register, presentAs, scoped SDK/wire presentation and tools/change; the component never invokes private modeFor.
  • packages/core/system-prompt/src/index.ts: public assemble({scope}), sections and wire tools distinguish pure PTC from native/hybrid.
  • packages/sandbox/{sandbox,sandbox-policy}/src/index.ts: per-session policy, exact argv confinement, full/partial enforcement and runner diagnostics.
  • packages/subprocess/subprocess/src/types.ts: bounded stdin/collected stdout, credential scrub and managed-range cancellation.
  • packages/jobs/jobs/src/{index,types}.ts: optional host-owned foreground job admission, progress, wait, cancellation and collected-record removal.

Windows x64 release worker build and three isolated PTC scenarios run with real Rust/Cordis/tool/prompt services; confinement and optional jobs are simulated. Linux x64 (x86_64-unknown-linux-gnu) and macOS arm64 (aarch64-apple-darwin) cargo check --locked pass from the Windows toolchain. These are compile checks, not linking/runtime/real sandbox acceptance. Other platform/architecture pairs, real process sandbox denial and live Desktop/PTC execution remain release gates. Real Windows x64 DSH sandbox-local/subprocess-local acceptance was subsequently added and passed: workspace-write copy, read-only result page without storage writes, denied outside-root mutation, and unconfined full-access outside-root mutation. This corrects the initial full-only enforcement gate: original Windows ACL reports partial and is now accepted with that exact fact returned.

Runtime discovery now resolves packaged runtime/<platform>-<arch> bytes with version/checksum receipts before source fallback. Source builds stage their runtime automatically. Release assembly requires six native target builds and rejects incomplete packages; the release matrix has not been run or published here.

No npm publish, GitHub push, or claimed model-token/turn benchmark took place. Details, budgets and security limitations: Native PTC.

0.2.9 Bug fixes

This release contains only bug fixes. It skips the Windows WSL launcher during PATH fallback (#12), rejects undeclared ops_bash arguments such as run_in_background before spawn (#10), and writes the one-time legacy shell fallback migration directly through the owning loader tree instead of invoking Entry.update(). The last change prevents a migration from remounting dsh-ops while Desktop is already reloading a profile row, addressing the reported configuration-edit slowdown with dsh-ops 0.2.6 and DSH 0.2.0-rc.2.

No feature, component, configuration key, or runtime capability was added. The three Windows payload dependencies remain pinned to 0.2.1.

Issue #9 showed that a host package-manager prune can leave the bash payload declared in the profile lockfile but absent on disk. ops_bash now converts a spawn-time ENOENT into a concise actionable result instead of exposing a raw process exception. It does not silently fall back to an unapproved system shell.

The Electron workspace-write 0xC0000142 ACL-runner failure is a DSH host sandbox issue outside this plugin's subprocess API. This release documents that boundary and does not claim to repair the host's Windows ACL runner.

0.2.7 Long-script tool-call stability

Issue #7 showed that putting long heredoc-heavy scripts into one JSON command string can increase malformed tool-call risk and waste an entire model turn. This release adds ops_bash.script_path, keeps short commands on command, and updates the runtime guidance and documentation to steer long scripts through the file path.

The main package version was 0.2.7; the three Windows payload dependencies remained pinned to 0.2.1 and were not republished.

0.2.6 Owned client lifecycle

Issue #1's latest controlled reproduction corrects the early orphan hypothesis: runtime-host is a deliberately detached per-user singleton reused by clients; its normal idle timeout is ten minutes. A dead-parent PID alone does not prove it leaked. Leaked serve clients keep it active, so this release targets only clients the plugin owns, not shared runtime-host or durable job supervisors.

lib/handshake.js tracks exact clients for Node's synchronous exit hook, forces FASTCTX_NO_PARENT_WATCH=0 on spawned proxies, rejects pending requests on close, shares one closing promise, closes lost reply transports, and bounds shutdown escalation. The existing Rust watcher checks PID plus process creation identity (src/process_identity.rs:251, src/runtime/session.rs:477); no Rust files or 0.2.1 payloads were changed. Shared client leases still close only at zero owners. Existing clients launched by older versions are not automatically swept or adopted.

Windows x64 / Node 24.18.0 acceptance against the published FastCtx payload: 50 manifest checks, 9 suites, 132 checks, no skips. The lifecycle suite covers pending-call cancellation, concurrent close, reply EOF, real stdin EOF, final lease release, repeated explicit owner exit and forced owner termination with an inherited no-parent-watch escape hatch. This is isolated process evidence, not a claim of completed desktop/web upgrade or a ten-minute idle timing test.

0.2.5 Stable safety and verification

The stable main package is dsh-ops@0.2.9; it deliberately excludes the context-compaction beta branch. Main-package patches and runtime payloads have independent versions. The exact dependency mapping is:

Main package Payload dependency (unchanged) Upstream runtime
dsh-ops@0.2.9 @dsh-ops/fastctx-win32-x64@0.2.1 FastCtx 0.2.6, upstream revision ccaa157790d02328a60786eb94ee5ad698995a5f; built from dd348254281bf5eea6c4d9d07c49d82c565666f4
dsh-ops@0.2.9 @dsh-ops/bash-win32-x64@0.2.1 PortableGit v2.56.0.windows.2 (2.56.0.2), bash 5.3.15
dsh-ops@0.2.9 @dsh-ops/pwsh-win32-x64@0.2.1 PowerShell 7.6.6

The same payload mapping applies to main-package versions 0.2.2–0.2.5. Each payload includes provenance.json with its executable SHA-256 and upstream identity; published tarball checksums are in docs/release-0.2.1.md. No payload was rebuilt for 0.2.5. GitHub v0.2.1/0.2.3/0.2.4 Releases are notes-only; they do not claim attached tarballs or SHA256SUMS. The assets table below is historical v0.2.0 distribution evidence, not a current-release asset promise.

System bash fallback is now opt-in (allowSystemShellFallback: false in both code and bundle defaults). FastCtx receives the plugin-resolved executor, or a disabled sentinel when none exists; background commands are not published in the latter case. Shared runtime identity includes the executor choice. Explicit bashPath, legacy provisioned copies and packaged payloads remain operator-controlled inputs; this does not make host shell tools or file tools a sandbox. On first activation, lib/config-updates.js disables legacy explicit true before resolution and submits the owning raw loader row for persistence with configUpdateVersion: 1. Subsequent manual opt-ins are retained. Disabled rows migrate when activated; no-loader mounts warn and use the safe value in memory. Include.write schedules asynchronous IO, so later disk failures are reported by the loader rather than claimed as confirmed persistence by the plugin. Loader APIs were read at vendor/loader/src/config/entry.ts:116, vendor/include/src/index.ts:365 in DSH 0.2.0-rc.2; tests cover raw-node preservation, per-row markers, retries, cancellation and real Cordis remount/publication.

The 0.2.5 automated acceptance on Windows x64 / Node 24.18.0 used DSH_OPS_REQUIRE_RUNTIME=1 npm run verify: 50 manifest checks, 8 suites, 125 checks, no skips. Integration covers real registry scopes, file calls/error results, component prompt/unload, dynamic session command authority, inherited restrictions, shared leases and reconnect. The packaged 0.2.1 FastCtx executable is used for final release acceptance. No live desktop upgrade or model request is claimed by these automated tests.

0.2.4 Component lifecycle

Three exported subpath plugins replace the single bundle row: shell/file on by default, background off. Each owns its registrations and system-prompt section; file and background lease one plugin-owned MCP connection scoped to the Cordis root. No global host registry or service methods are replaced. Foreground ops_run is no longer published. Background shutdown drains tracked calls, attempts to kill only remembered job IDs, then releases its connection lease; disconnect can still lose ownership, so cleanup is not guaranteed for unknown durable jobs.

The unconditional pwsh-sandbox patch is removed. Shell mounts an internal/config waterfall hook targeted at the official pwsh-sandbox row, then uses public Fiber.update(rawConfig, true) to apply/revoke the temporary path via the official config lifecycle (noSave). Removing the hook recomputes from the latest owning raw config, not a stale saved copy. It does not mutate a service/config reference, write profile files, change OS defaults or cancel existing host shell processes. An isolated real Cordis/ToolRuntime inspection verified independent tool/prompt withdrawal, permission-gated commands, shared-client leases, and config restoration with a stand-in executor. Real desktop marketplace toggles and the real sandbox executor require user validation; no model requests/old suites were run.

Default schema bytes drop from 10,700 (0.2.3 ten tools) to 7,685 (five tools). This is a byte metric, not a tokenizer or proof of lower whole-task usage.

0.2.3 Prompt and display update

Add one model-facing sentence in lib/policy.js: ops_* arguments are strict: pass only declared fields. FastCtx strips additionalProperties from published schemas for provider portability but retains its runtime unknown-field guards; this prompt reminder does not change schemas or Rust behaviour. No files under vendor/fastctx were modified. The display title and bilingual descriptions use the maintainer's wording. The new icon is an operator-supplied image resized, without cropping, to a 256×256 PNG under the official host's 256 KiB limit; no independent authorship or licence verification of that image is claimed.

0.2.2 Bash schema hotfix

The maintainer reported a model API rejection after official marketplace installation: ops_bash had no object-root JSON Schema. The plugin supplied a field map directly to tools.register, whereas only the host's defineTool helper compiles such maps; raw registration and model projection preserve parameters unchanged. The plugin now supplies explicit type: 'object', properties, and root required. Inspection through the installed official 0.2.0-rc.2 registry confirms the exported schema is object-rooted and valid. No model API request or full marketplace round trip is claimed. The three existing 0.2.1 runtime payloads are reused unchanged; only the main package is patched.

0.2.1 Windows payload packaging

The assembler now builds all three Windows x64 payload packages. Bash carries complete, unmodified PortableGit 2.56.0.2 (GNU bash 5.3.15); pwsh carries complete PowerShell 7.6.6 under bin/. Original licenses/notices are preserved. Archive and executable hashes are pinned in lib/shells.js. All three runtime packages are injected into the main publish artifact; no install lifecycle downloads.

The maintainer directed publication after the source-delivery risk was reported. The first attempt used an unrelated local npm credential and received E403; selecting the maintainer's bypass credential succeeded, so that failure was credential selection, not the maintainer's token. Publishing hit a second real blocker: npm rejects tar hard links (E415), so the assembler materializes PortableGit's 84 linked paths and the published bash package is 161.2 MB packed rather than the 136.1 MB first measured. All four packages are published at 0.2.1; the registry-reported digests of exactly what users receive are recorded in docs/release-0.2.1.md, because the local dist provenance is regenerated with a new timestamp on every build and does not describe the earlier-published FastCtx tarball byte for byte.

PortableGit's etc/package-versions.txt includes many GPL/LGPL components; complete matching source closure/delivery has not been collected or reviewed. Retaining upstream licenses does not attest that those obligations have been fulfilled. No source offer is invented. Historical 0.2.0 pin-package descriptions below do not describe this new payload layout.

The profile installer delegates to the official DSH CLI, retaining its locks, compatibility checks and rollback rather than cloning them. Live installation on each application version has not been attested. Default README is Chinese; README.en.md is the English counterpart.

Current slimming-pass evidence (supersedes historical ladder claims below)

This pass changes only plugin presentation, registration, and result projection; no vendored functionality changed. Historical tables below describe the earlier implementation and its earlier tests, not acceptance of the current revision.

  • Restored three-layer intent: tools → ops_bash → PowerShell 7. lib/session-shells.js publishes bash through plugin-owned agent child fibers only with full-access authority and the subprocess service; it is independent of FastCtx availability and enableShellTools. Prompt routing prefers bash for general commands, not PowerShell fallback.
  • Permission authority: host packages/sandbox/sandbox-policy/src/index.ts:157-186, sandboxPolicy.resolve({session}); durable mode event at src/session-mode.ts:25-56.
  • Session mode feed: ctx.on('session/event', (session, event)), not client-wire projection change notifications. The plugin reconciles plugin-owned agent child fibers on sandbox/mode.
  • Own registration scopes: packages/core/tools/src/index.ts:1058-1088; inherited restrictions and own-layer exemption at :1090-1124,1163-1206; prompt callbacks use assembly agent plus ctx.get('tools').schemas(agent).
  • Concurrency API: packages/core/tools/src/index.ts:267-280,1303-1309 requires a pure function, not a boolean. FastCtx's read/search handlers use shared bounded permits and blocking executors (vendor/fastctx/src/server.rs:198-280); MCP request IDs isolate pending replies. Only read-only tools opt into sibling overlap.
  • Host timeout metadata requires work quiescence; the current MCP transport removes pending waits but cannot prove server cancellation. No host timeoutMs promise is added.
  • Job launch ID is parsed only from FastCtx's successful terminal marker (vendor/fastctx/src/shell/jobs/mod.rs:232-278); global durable lists at :1053-1250 are projected to session-owned IDs. Footer grammar/placement comes from vendor/fastctx/src/background_status.rs.
  • Baseline at pre-change plugin HEAD dee3c57, runtime FastCtx 0.2.6, is recorded in docs/schema-baseline.json. scripts/measure-schemas.mjs measures compact name/description/parameters JSON, not use frequency or actual billing.
  • Historical slimming pass only: the operator requested no regression/CI additions or suite runs then. This limitation is superseded by the 0.2.5 acceptance record above; npm run verify is again the current gate. See docs/manual-validation.md for separate manual checks.
  • Residual boundaries: file tools remain outside the host filesystem sandbox; shell-mode gating does not confine ops_replace. Approval and sandbox state are separate; no new approval escalation is implemented. Reconnect loses job ownership and does not prove durable jobs terminated.

Two bodies of work

Author License Where
The dsh-ops plugin this repository MIT lib/, bin/, scripts/, test/, cordis.patch.yml, dsh-plugin.json, docs
FastCtx yc-duan Apache-2.0 license text and upstream notice only, in licenses/fastctx/; the vendored source was removed

While FastCtx was vendored, vendor/fastctx/UPSTREAM.md recorded the exact upstream revision and vendor/fastctx/FORK.md every difference from it — the distribution machinery removed, the standalone-only code removed, and every source file this fork touched (each carried its own Apache-2.0 §4(b) notice at the top). Both remain in the repository history. Current releases are MIT-licensed; the composite MIT AND Apache-2.0 statement and FastCtx's attribution apply to the earlier releases that carried FastCtx and are preserved in NOTICE.

Distribution and binary sources

The plugin reaches a profile as a dependency of that profile — dsh plugin --profile <p> add dsh-ops, or plugin_manager { action: "install_bundle" }. What that install brings is this distribution's own work: one payload we build, and two pins. The channels and the commands are described in the README under "What an install brings".

What this distribution ships

Artifact Contents
dsh-ops@0.2.0 the plugin itself: JavaScript, the manifest, the bundle patch, and the vendored FastCtx source. 497,013 B packed, 2,339,108 B unpacked, 130 files
@dsh-ops/fastctx-win32-x64@0.2.0 a payload we build: this fork's FastCtx, bin/fastctx.exe
@dsh-ops/bash-win32-x64@0.2.0 a pin only: upstream URL, release, version, byte count, and SHA-256. No binaries
@dsh-ops/pwsh-win32-x64@0.2.0 a pin only: the same for PowerShell 7. No binaries
SHA256SUMS (historical v0.2.0 release asset) the four v0.2.0 tarball digests; later notes-only Releases do not attach this asset. Published 0.2.1 payload checksums are in docs/release-0.2.1.md.

What this distribution does not ship

The Git for Windows and PowerShell archives are not redistributed here: no tarball, no release asset, and no cache in this repository holds them. dsh-ops provision-shells reads the pin, downloads the archive from the upstream URL on the user's own machine, verifies it against the pinned SHA-256, and unpacks it under <DSH_HOME>/dsh-ops/shells/<name>/<version>/. Those bytes travel from upstream to that machine directly, and the licences below are the ones the user obtains them under.

The pins

Package Upstream source Release Version Artifact Bytes SHA-256 Licence
@dsh-ops/fastctx-win32-x64 this fork's build of vendor/fastctx/, upstream yc-duan/fastctx at ccaa157790d02328a60786eb94ee5ad698995a5f v0.2.6 0.2.6 bin/fastctx.exe, built here 49,492,480 7f4b494627cf3c0b298dd0b6d36624fd09d35bd1add655c5d55e0372e9e20987 Apache-2.0
@dsh-ops/bash-win32-x64 Git for Windows v2.56.0.windows.2 2.56.0.2 PortableGit-2.56.0.2-64-bit.7z.exe — fetched by the user, not redistributed here 60,027,568 075e158ef8e1f0ab80b347e245405d3eca735c2dc88fd8e032e137d0ca61f61b GPL-2.0-only
@dsh-ops/pwsh-win32-x64 PowerShell v7.6.6 7.6.6 PowerShell-7.6.6-win-x64.zip — fetched by the user, not redistributed here 106,328,873 02fe458be20493fbdf43f61ea20610b811ee6c738ab1676c61b9cfcd1a33c860 MIT

The @dsh-ops/fastctx-win32-x64 row records the 0.2.6 payload from the releases that carried FastCtx; that package is no longer published. The two shell pins below are current.

The pin values live in one table — SHELL_UPSTREAM_PINS in lib/shells.js, which dsh-ops provision-shells downloads from and the release build imports when it publishes the pinned facts beside the packages it assembles — and the release's provenance.json records what was built from them. The URLs are upstream's own release URLs verbatim, so a pin can be checked against the upstream release page rather than trusted from here. Each pin also records the digest of the executable inside its archive (bin/bash.exe, bin/pwsh.exe), which the provisioner re-checks after unpacking, so an archive that yields something else is refused. Unpacked, the bash pin is about 390 MiB and the PowerShell pin about 245 MiB on disk — which is why neither is part of an install.

Every @dsh-ops/* platform package is an optional dependency of the published main package, and each one carries its own LICENSE, NOTICE, and metadata beside that payload or pin.

The licence each pin points at

The plugin is MIT-licensed, and the current distribution contains no FastCtx code. The shell pins point at someone else's distribution:

  • fastctx — a historical Apache-2.0 dependency, removed in 0.3.2. Earlier releases vendored FastCtx in vendor/fastctx/ and shipped this fork's build; those releases carried FastCtx's Apache-2.0 text, upstream's notice and this fork's §4(b) change notices, and every source file they touched had its own §4(b) notice (vendor/fastctx/FORK.md is the enumeration). The FastCtx source and binary are no longer part of this distribution; licenses/fastctx/ keeps the license text and upstream notice, and NOTICE keeps the required attribution for those earlier releases.
  • bash — Git for Windows' GPL-2.0. The pin names the upstream archive and its digest; the download, the extraction, and the resulting copy on disk are the user's, and the GPL-2.0 terms (and the corresponding source, published by the Git for Windows and Git projects) attach to those bytes. No GPL-2.0-covered component is part of this distribution, which is why nothing here carries a GPL text or makes a source offer for code it does not ship.
  • pwsh — Microsoft's MIT. The same shape: the pin points at Microsoft's own release asset, the download happens on the user's machine, and the licence text and copyright are Microsoft's. Nothing in the plugin's MIT grant covers it.

Host targeting

The plugin targets DSH 0.2.0-rc.2, the version of the desktop installation this was developed against.

The source of that exact version is readable on this machine: dsh-core/v0.2.0-rc.2/ is a checkout of tag dsh-v0.2.0-rc.2 (639ed015, commit date 2026-09-29), 14,104 files. The installed host itself ships inside app.asar and stays unreadable from a shell, so the readable copy is what "the host" means below.

Each depended-on API is therefore established twice: read from that source, with the file and line recorded here, and run by the suite named in the same row. The line numbers are of dsh-core/v0.2.0-rc.2/ and will move with upstream; the file is the durable part.

Depended on Read from Run by
new Context(), ctx.plugin, ctx.inject, ctx.effect, ctx.on, ctx.waterfall, ctx.get Cordis source, vendored: vendor/cordis/src/ the mount suite boots a real context and disposes it
tools.register(definition) — one object argument, returns the unregister disposer packages/core/tools/src/index.ts:1057-1088 the mount suite reads back the nine registered schemas, and the shells suite asserts the ops_bash definition and that its disposer removes it
tools.restrict(filter) — {allow?, deny?} over the global tools of one agent scope; requires a scoped context, rejects unknown names, intersects, lifts on dispose packages/core/tools/src/index.ts:1090-1124, type at :700-705 used by this plugin: on agent/created, and only while a rung of its own is live, it masks the configured host shell tools out of that agent's own view (lib/index.js hideHostShellTools), and the disposer it returns is handed to the plugin's effect too. The mount suite asserts the masked names leave schemas(agent), stop resolving through get(name, agent), are still refused by the fence, stay registered globally, and come back when the plugin unloads; a name the agent registered itself makes restrict() throw, which the plugin reports once and leaves visible
tools.guard(guard) — monotonic denial registered after the pre-execute waterfall; the guard returns a denial reason or undefined packages/core/tools/src/index.ts:1126-1142, type at :723-731 not used by this plugin yet: the fence at tools/pre-execute — with its model-readable reason and its next() delegation — plus the per-agent mask already implement deny-host-shell
tools.get(name, scope?) — the definition one scope resolves, or undefined; the lookup half of "visibility, lookup, and execution agree" packages/core/tools/src/index.ts:1230 the mount suite only: a masked host shell name must not resolve for that agent while it stays registered globally
ctx.on('agent/created') and the agent's own scoped context (agent.ctx) — the event fires once per agent, after its scope is published and before its queued work is released and its prompt first assembled; the payload is {agent, source, signal} packages/core/agent/src/runtime-types.ts:261 (the typed event), sequenced at packages/core/agent/src/index.ts:172-181 and emitted serially at :550 the mount suite dispatches the real event (ctx.serial('agent/created', …)) into real @deepseek-ai/dsh-scope scopes and asserts all three outcomes: hidden while a rung of the plugin's own is live, reported-and-visible when no such rung is live, and reported-and-visible when restrict() cannot name the tool
tools/pre-execute waterfall, (exec, next) => PreToolDecision, @mode waterfall packages/core/tools/src/index.ts:142-153 the fence suite dispatches the real waterfall and asserts {kind:'deny'}
systemPrompt.section({name, order, text, interpolate}), duplicate names throw, returns an effect disposer packages/core/system-prompt/src/index.ts:446-463, PromptSection at :52-76 the mount suite renders both sections from a real assembly
systemPrompt.assemble({}) resolves sections in ascending order, equal orders by name packages/core/system-prompt/src/index.ts:548-558, sort at :237-238 the mount suite asserts both sections appear in one assembly
ctx.get('tools').schemas() — one deep-cloned schema per visible tool, the global view when no scope is passed; the plugin's only answer to "are the ops_ tools live right now" (asked at every prompt assembly) and to "which host shell names does this agent actually see" (asked when it hides them) packages/core/tools/src/index.ts:1252-1262 the mount suite renders the tooling section from a real assembly and asserts it names exactly the live tools; it also reads schemas(agent) before and after a mask
ctx.get('subprocess') — the abstract SubprocessRuntime: spawn(spec) with a fully explicit argv, cwd, per-stream stdio, graceMs, cancellation, and env overrides merged after the service's own credential scrub; argv is never shell-interpreted packages/subprocess/subprocess/README.md:28 (mount and consume), :44-46 (the call shape), docs/subsystems/subprocess.md:91-104 (the fully-explicit spec), :267-269 (ctx.subprocess registration) the shells suite, through a recording subprocess service: ops_bash spawns argv: [<resolved bash>, '-c', <command>], forwards exactly the terminal overrides, adds no credential-shaped name of its own, and reports a missing service instead of throwing something opaque. The mount suite is where the service is real, and where the registration is shown to be gated on it: with no ctx.subprocess, a resolved bash publishes no rung and installs no mask
the credential rule both spawn paths keep: a child never inherits credential-shaped names or ambient DSH_* facts, and a transport that owns its own spawn (the SDK client, MCP) stays outside the service so the policy has one source packages/subprocess/subprocess/README.md:80 (the scrub and its merges), :84 (route around the service when the transport owns the spawn), :104 (scrubbedParentEnv), packages/subprocess/subprocess-local/README.md:156 (the scrub is a name heuristic, not a guarantee) the mount suite's childEnv check, for the MCP child. That child's rule is restated in lib/tools.js childEnv() rather than imported, because importing scrubbedParentEnv would be the @deepseek-ai/* value import this host half forbids; the ops_bash child gets the service's own scrub instead (ctx.get('subprocess') above)
this plugin's own ops_ namespace is its conflict domain: a name already registered in the same scope throws, so the whole generation is rolled back rather than half-published packages/core/tools/src/index.ts:746-748 (both duplicate branches) not covered against the real host — no suite publishes a colliding name; the fixture's registry raises on a duplicate (test/mount.test.mjs) and #publish in lib/tools.js is where the rollback lives
a bundle is a package declaring dsh.bundle.patch, and a layer replaces a row's whole config: docs/user/develop/basic/publish.md:11-16, :56-64, :129-132 scripts/validate-manifest.mjs parses the shipped patch and runs the row's config through the plugin's own validator
Cordis answers every service property read with a fresh traceable wrapper vendor/cordis/src/utils.ts:116-125 (getTraceable) and :165-218 (createTraceable, a new Proxy per read) the incident's regression assertions in the mount suite: the registry carries no own register after mount, and the same foreign name registered once in each of two scopes stays out of this plugin's global view
ctx.pluginManager (installBundle, removeBundle, setBundleEnabled, setPluginEnabled, inspect, registries, waitForInstall, cancelInstall, version exemptions) docs/subsystems/boot.md:85-180 not run here; the install path is exercised by hand, not by a suite

One row left this table when the plugin began speaking MCP itself: @deepseek-ai/dsh-mcp-client and its mcp__<serverName>__<rawName> naming contract are not part of this plugin's dependency surface — nothing in lib/ imports it, mounts it, or names anything after the names it would have produced. The 0.2.0 changelog entry records its removal from package.json, together with the declaration and the manifest check that went with it.

The package versions under test are pinned in devDependencies, so the gate is reproducible. Because those are npm releases of the same packages rather than the copies inside app.asar, a host-side API change between 0.2.0-rc.2 and what this repository installs surfaces as a red mount suite.

Two things the read established that the earlier execution-only record could not:

  • tools.register takes one object, not (name, definition). The earlier record said "the ToolRuntime register", which left the argument form to the suite. The signature is register(definition: ToolDefinition): () => void (packages/core/tools/src/index.ts:1063), and it validates output.schema, output.render, and timeoutMs before registering.
  • restrict and guard exist, with exactly the monotonic semantics this plugin's design assumes. restrict narrows what one agent inherits (:1097), guard denies after the waterfall and cannot be turned back into permission by a later listener (:1136, :723-731). The plugin uses restrict for the visibility half of deny-host-shell and is runtime-verified for it; it registers no guard yet, so that half is read-only here.

The boundary the 2026-10-08 incident established

What happened. While the plugin still mounted the harness MCP bridge, it renamed that bridge's tools by installing an interceptor on the shared tool registry — interceptBridgeRegistrations wrote an own register property on the service object. A Cordis service property read returns a wrapper bound to the reading context (vendor/cordis/src/utils.ts:165-218), so the interceptor's original.call(…) attributed every later registration — including foreign ones — to this plugin's host plane. The host's second registration of a name that another plugin installs per agent (subagent) then collided and every new session failed: tool "subagent" is already registered (for a per-agent variant, register through that agent's agent.ctx instead) (packages/core/tools/src/index.ts:746-747, the scope === undefined branch, i.e. the global layer). A fresh DSH_HOME without this plugin did not fail, which is what located the fault here rather than in the composition.

What stands now. The plugin publishes its own tools through its own ctx.tools.register (lib/tools.js for the hosted nine, lib/shells.js for ops_bash) and rewrites nothing: no own property on the shared registry, no replaced service method, no name derived from another component's naming. The one place it acts on another context's tools is agent.ctx.tools.restrict({ deny }) — the documented per-agent visibility call, made on that agent's own scoped registry to hide the host shell tools this deployment asked to deny. The symptoms, the mechanism, and the regression contract are stated in the plugin's AGENTS.md, "上游兼容 (upstream compatibility)"; that section is the authority for the boundary, and test/mount.test.mjs is the executable form of it.

Rules that follow, and that this repository must not regress:

  • Do not replace a shared service method to observe or reshape it. Reading a service method is not identity-stable, and the wrapper carries the caller's context, so re-entering through a saved original changes whose registration it is.
  • The public namespace is this plugin's own registrations. ops_<name> comes from publicToolName() in lib/policy.js and is registered into this plugin's scope, so no other component's naming can change or claim it.
  • Release only through the disposers the surface collected. ctx.get('tools').register is not identity-stable across two reads (ctx.get('tools').register !== ctx.get('tools').register), so a registration cannot be recognized by function identity; one generation is published and released as one unit.
  • Foreign registrations are none of this plugin's business. A name in another server's namespace, a lookalike of this plugin's own, or the same name in two scopes must survive a mount, an unload, and each other — test/mount.test.mjs is what keeps that true.
  • Act on another context's tools only through its documented API, on that context. The per-agent mask is agent.ctx.tools.restrict({ deny }), called on the agent's own scoped registry and released through the plugin's effect as well; the plugin never reaches into a registry instance and never names a tool that agent registered itself.

Not verified here

  • A real dsh plugin add into a live profile. The profile that booted the session which produced this record is the one that installed the plugin by link:, so the install did happen — but through a manual profile edit, not through dsh plugin. The installation path (dsh plugin --profile … add, the profile's dsh.profile.bundles ordering, the host compatibility gate reading peerDependencies) is documented from the harness sources above, not exercised by a suite in this repository.
  • Hot reload of a replaced package. ctx.hmr is read (docs/subsystems/boot.md:56-83) and the upstream rule that replacing an installed package needs a restart is quoted in the README (packages/preset/agent-preset/skills/cordis-plugin-development/references/host-plugin.md:60), but no suite here replaces a mounted package and observes the new module generation.
  • A conflicting registration against the real host. lib/tools.js publishes one whole generation and rolls a partial one back when a name is already taken, but no suite publishes a name that collides with this plugin's ops_ namespace against the real registry: only the fixture's own registry in test/mount.test.mjs raises on a duplicate.
  • The platform packages and the pins, against a real tag. The release step declares @dsh-ops/fastctx-<platform>-<arch> and the two pins, and the shell layouts they can resolve to are exercised against a temporary install in test/shells-resolution.test.mjs — including the L3 expression agreeing with the plugin's own probe on every layout. What has never run is the release pipeline itself: no tag has been pushed and nothing has been published, so no tarball and no Release asset has been fetched from a release, and no deployment has yet resolved a runtime from one. The first real tag is what has to prove that path end to end.
  • A provision-shells download against upstream's live assets. The pin values are read from SHELL_UPSTREAM_PINS in lib/shells.js, and the digests there were checked against the archives this machine downloaded while the release build was assembled — but that was a build-time fetch here, not dsh-ops provision-shells fetching into <DSH_HOME>/dsh-ops/shells/ on a user's machine, and not a re-download of the pinned bytes after upstream had served them again. Whether upstream still serves the pinned URL, and whether the extracted tree is the one the plugin's probe accepts, is what the first run of that command has to establish.
  • A real DSH boot running the L3 override. The patch's !!js expression is parsed from cordis.patch.yml and evaluated the way the vendored loader does it (with (ctx) { return eval(expr) }, vendor/loader/src/config/utils.ts:5-9) — inert without a bundled pwsh, resolving one with it — but no DSH boot has mounted the patched pwsh-sandbox row.

Design decisions worth recording

Two spawn paths, one credential policy

The plugin reaches every host service through ctx.inject([...]) / ctx.get() and imports no @deepseek-ai/* value at any point. That leaves two ways to start a process, and the credential rule is kept on both.

The FastCtx server is the plugin's own spawn (lib/handshake.js, node:child_process.spawn), because it is the plugin's transport, and the harness's own guidance for that case is to stay outside the subprocess service — "when a transport owns its own spawn (the SDK client, MCP), route around the service and import scrubbedParentEnv directly so environment policy stays single-sourced" (packages/subprocess/subprocess/README.md:84). Importing scrubbedParentEnv is exactly the value import this host half forbids, so the rule is restated in lib/tools.js childEnv(): credential-shaped names (*KEY* / *PASSWORD* / *SECRET* / *TOKEN*) and ambient DSH_* facts are dropped, tool configuration locations such as GH_CONFIG_DIR survive, and the mount suite pins that behaviour. The heuristic has the limits upstream documents (packages/subprocess/subprocess-local/README.md:156): a differently named secret passes through.

ops_bash goes through the service (lib/shells.js). It has no reason to own a spawn, so it takes the harness's real credential scrub, output spilling, and process-range termination, and supplies only the argv, the directory, the budgets, the terminal overrides, and its cancellation — read with ctx.get('subprocess') rather than imported. Nothing in the plugin writes process.env, PATH, or the working directory.

One question decides the ladder and the mask

ladderLevels({published, pwsh}) (lib/policy.js, a pure function) turns the published ops_ names plus one resolution fact into the three rungs: rung 1 is live when any published name is not one of this plugin's own in-process tools — so the shell tools cannot stand in for a server that is not there — rung 2 when ops_bash is among them, and rung 3 when this plugin has a pwsh of its own on this platform. hostShellEnforcement({shellPolicy, levels}) then asks the same object whether deny-host-shell may hide the host shell tools, and it is asked again as each agent appears. Both answers therefore follow "what the model can reach"; resolveShells() supplies the detail and the report and never the decision. The tools/pre-execute fence is installed by the mode alone, so the guarantee does not depend on either answer.

Rung 3 is a bundle patch; rung 2 is a tool

The two bundled rungs are implemented differently because the host leaves two different seams open, and both were read from the host source rather than assumed:

  • Rung 2 must be a tool. bash-local has no executable field — its config is cwd/timeoutMs/maxTimeoutMs/maxOutputBytes/maxSpillBytes/graceMs (packages/shell/bash-local/src/index.ts) — so the only zero-internal-dependency way to run the plugin's own bash is to publish ops_bash and execute through ctx.subprocess. The rung is therefore the published tool, not the resolved executable: a bash that resolved without that service is a rung the model cannot reach, so lib/shells.js publishes nothing, names the resolved bash in its report, and the ladder has no rung 2.
  • Rung 3 must be a patch. pwsh-sandbox inherits pwshPath verbatim from pwsh-local (packages/shell/pwsh-sandbox/src/index.ts:40, type Config = LocalConfig), so pointing that row at the plugin's executable puts the bundled pwsh 7 under the host's sandbox, credential scrub, and output governance with no custom executor and no patched internals. A bundle patch is evaluated before any dsh-ops row is mounted, which is also why there is no config key for it: a key could never reach the expression, and a key that pretended to would make the ladder claim a rung the host row never runs. A patch replaces the target row's entire config, so every non-default field must be restated — the target row declares none (packages/bundle/base/cordis.patch.yml:241-243), and the shells suite asserts that the shipped patch writes pwshPath and nothing else. The expression and the probe are single-sourced by an order, not by an import (a bundle patch is plain YAML and cannot import lib/shells.js): both accept PWSH_LAYOUT_ORDER — the provisioned copy under <DSH_HOME>/dsh-ops/shells/pwsh/<version>/ first, then an installed @dsh-ops/pwsh-<platform>-<arch> package's bin/pwsh.exe, then vendor/pwsh/<platform>-<arch>/pwsh.exe inside this package — apply the same lstat-based usability test, and the shells suite asserts that agreement on every layout, so the ladder's rung 3 and the host row are one fact rather than two guesses.

No Schemastery Config

The plugin exports no Config schema, so the row's config is validated by lib/config.js instead. A Config export means importing @deepseek-ai/schemastery — a value import whose resolution the profile loader owns — and this host half deliberately imports no @deepseek-ai/* value at load time. There is no exception now: every host service this plugin needs arrives as a service, never as an import.

Resolution and probing are synchronous

apply resolves the executable and probes it with --version before returning. Both are synchronous, so they belong in apply rather than behind a service injection: required: true must reject activation, which is only observable if the failure happens while the loader is still loading the plugin.

Prompt section orders 1500 and 3250

The harness allocates named section orders centrally, and an external contribution supplies its own numbers. Reading the table (packages/core/system-prompt/src/index.ts:125-159):

  • 1500 is already claimed — it is TOOL_GREP. The host-shell rule therefore shares an order with a first-party section instead of sitting in a gap. Equal orders are broken by name, code-unit order (:231-238), so dsh-ops:host-shell-policy sorts before tool-grep; the rule still lands after TOOL_BASH (1000) and before MCP_SERVERS (3100), which is the position the policy text is written for. lib/policy.js states the same allocation in place, and scripts/validate-manifest.mjs compares both numbers against the manifest.
  • 3250 sits in a real gap — between MCP_SERVERS (3100) and TOOLS_SDK (5000), which is where the tooling section is meant to sit.

ctx.get('tools'), never ctx.tools, from the prompt scope

The tooling section's text function asks the registry whether the FastCtx tools exist, and lib/shells.js asks it where to register ops_bash. Both run from injection scopes that do not declare tools; the ctx.tools property proxy is topology-sensitive and answered undefined there. The mount suite caught this — the section rendered empty next to nine registered tools.

Historical verification record (0.2.0, 441b095)

This record was introduced by commit 441b095; it describes that implementation, not main after the later component and PNG changes. For current acceptance use npm run verify and the 0.2.5 record above. Historical Windows x64 / Node 24.18.0 output:

node scripts/validate-manifest.mjs   # all 47 checks passed
node test/run.mjs                    # 7 suites, 122 checks, passed in 9s
node bin/dsh-ops.mjs status          # fastctx 0.2.6 from the managed runtime, 9 tools

The vendored source build:

cargo 1.97.1 (c980f4866 2026-06-30)
cargo build --release --locked       # Finished `release` profile in 7m 05s
vendor/fastctx/target/release/fastctx.exe --version   # fastctx 0.2.6