@onthewayli/dsh-plugin-path-completion
English | 中文
Codex-style @path file completion for the DeepSeek Harness Web composer, shipped as an
out-of-tree plugin: type @ (and keep typing) in the composer and the trigger menu lists matching
files and directories from your session's project directory — arrow keys, Enter, and Escape drive
the pick through the Harness's own input-trigger menu, and a picked path lands in the draft as
@./relative/path (or the quoted @"path with spaces" form), which the Harness's prompt-side
@path expansion turns into file content on send.
The plugin carries both halves in one package:
- Host half (
lib/index.js) registers one HTTP route,/path-completion/search, on the Harness webserver and answers with a bounded, ranked candidate list. It never sends file content over the wire — only names, kinds, and query-shaped paths. - Browser half (
lib/client.js) registers one@source on the shared input-trigger pipeline (ctx.inputTriggers), so no UI code, React, or CSS ships here: the pipeline's existing menu renders the candidates.
Install
# from a registry (published package)
dsh plugin --profile web add -w @onthewayli/dsh-plugin-path-completion
# from a checkout of this repo
dsh plugin --profile web add -w file:/path/to/dsh-plugin-path-completion
# from a tarball
npm pack && dsh plugin --profile web add -w ./onthewayli-dsh-plugin-path-completion-0.1.0.tgz
# from a git host (needs the profile's allowBuilds approval; pin a commit)
dsh plugin --profile web add -w github:OnTheWay111/dsh-plugin-path-completion#<sha>
dsh plugin forwards to pnpm inside ~/.dsh/profiles/web; because this package declares
dsh.bundle, it is appended to the profile's dsh.profile.bundles automatically, and its
cordis.patch.yml inserts its own loader row. Then restart the Web server: the client module
system caches package metadata per name for the process lifetime, so plugin-set changes take
effect on restart.
Uninstall with dsh plugin --profile web remove -w @onthewayli/dsh-plugin-path-completion.
Configuration
Optional row config in ~/.dsh/profiles/web/cordis.patch.yml:
- id: path-completion
config:
# Candidate-search bounds (defaults shown).
maxResults: 50 # candidates one response carries
maxVisited: 20000 # directory entries one query may visit
budgetMs: 400 # wall-clock budget for one query's walk
ignoreDirs: [node_modules, .git, dist, build, ...] # never descended into
# Extra bare `host[:port]` authorities allowed to call the route (LAN use).
trustedHosts: []
Trust model
The route answers only when the request reaches it as the GUI's own origin:
Hostmust be a loopback authority (127.0.0.1,localhost,[::1]) or an entry oftrustedHosts;- a request carrying
Originmust carry the same authority asHost; Sec-Fetch-Site: cross-siteis refused.
A browser reached over a LAN address therefore gets no completion until that authority is listed in
trustedHosts. The route is read-only, returns names only, and never exposes file contents.
Compatibility
| Plugin | Harness | Node | What it relies on |
|---|---|---|---|
| 0.1.0 | 0.1.0-rc.5 or newer in the 0.1.x line (source checkout or published dsh) |
>= 22.19 | ctx.webServer.register on the host, the inputTriggers client service and its PickOutcome.text arm on the browser, and the Harness's own prompt-side @path expansion to read a picked file on send |
Verified installs: file: directory, prebuilt tarball, and a source-checkout Harness profile, each
booting with the plugin's client entry in the boot manifest and its route answering.
Coupling facts an upgrade can invalidate:
- The Harness ships client and host together and has no wire protocol version, so a Harness upgrade
may require rebuilding this plugin. The halves type the interfaces they use structurally
(
src/index.ts,src/client/index.ts) so a mismatch fails at runtime with a named error instead of at build time against packages that may not be installed. @deepseek-ai/cordisis a peer (>=4.0.0 <5.0.0-0): the plugin is applied by the installation's own Cordis instance. No other official package is imported — collaboration is through Cordis services.- The browser half throws a named error when
inputTriggersis missing rather than silently doing nothing. - The menu group title comes from the Harness's
slash.menudictionary, which an out-of-tree plugin cannot extend; the group therefore renders under this source's raw name (files).
Known limitations
- Picking a directory inserts
@./dir/and closes the menu; the next keystroke reopens it with that directory's children. The Harness re-detects a trigger on typing, not on a programmatic insert, so an immediately-reopening drill-down needs an upstream hook. @also offers the Harness's session references (child sessions) in their own group; a bare fragment therefore matches files and session names side by side.- Candidates come from the session's project directory for relative queries, the OS home for
~/…, and the absolute path for/…. The ignore list keeps large trees responsive, so a name living only inside e.g.node_modulesis not offered unless you drill into it.
Publishing
Three distribution forms work, in increasing order of convenience for users:
| Form | Publish | User install | Build permission needed |
|---|---|---|---|
| npm registry | npm publish (or your private registry) |
dsh plugin --profile web add -w @onthewayli/dsh-plugin-path-completion |
no (prebuilt lib/ ships in the tarball) |
| tarball | npm pack |
dsh plugin --profile web add -w ./onthewayli-dsh-plugin-path-completion-0.1.0.tgz |
no |
| git host | push the repo | dsh plugin --profile web add -w github:you/repo |
yes — pnpm refuses a git dependency's prepare until the profile's pnpm-workspace.yaml allowlists it (allowBuilds); pin a commit |
files lists lib/, cordis.patch.yml, and this README, and prepublishOnly/prepare rebuild the
bundles — without the explicit files list, lib/ would be dropped from the tarball (it is gitignored)
and an installed copy would fail to load. Verify with npm pack --dry-run before publishing: the listing
must show lib/index.js and lib/client.js.
Publish under your own name: the @deepseek-ai/ scope belongs to the Harness project. Check the name is
free first (npm view <name> version), since plugin packages are already appearing on the registry.
npm now blocks publishing unless the request carries 2FA: either pass the authenticator code
(npm publish --otp=<code> --access public) or, for scripted/CI publishing, use a granular access
token with "Bypass two-factor authentication" enabled (npmjs.com → Access Tokens → Generate New
Token → Granular). A plain classic npm_ token gets a 403 on publish.
Development
# Requires tsdown + typescript; point node_modules at a Harness checkout (or install them):
# ln -s ../deepseek-harness/node_modules node_modules
#
# The symlinked node_modules carries the rolldown native binding for the arch it was installed
# under (e.g. darwin-x64). Build/pack with a Node of the SAME arch and of >= 22.19 (tsdown needs
# ^22.18 || >=24); on this machine that is the x64 build of nvm's 22.23.2:
# nvm use 22.23.2 && node_modules/.bin/tsdown
node_modules/.bin/tsdown # build lib/index.js + lib/client.js
node --experimental-strip-types --test tests/plugin.test.ts # self-contained tests, no dependencies
dsh plugin add installs a copy of this package into the profile (pnpm's file: handling), so a
rebuild is not picked up by an already-installed profile: re-run the add command after building, and
restart the server (client package metadata is cached per name for the process lifetime).
The browser bundle must keep the Harness client-plugin shape: a CommonJS body wrapped in
window.__ModuleLoader__.load({ id, factory }), with platform modules (React, cordis, the shared UI
packages) left external and everything else inlined — an out-of-tree bundle cannot import another
plugin's client module as a value.
No comments yet. Be the first to write one.