Skip to content
dsh.fish
Bundle

@onthewayli/dsh-plugin-path-completion

Codex-style @path file completion for the DeepSeek Harness Web composer: a bounded host-side path search plus a browser trigger source.

Source
OnTheWay111
License
MIT
Updated
Updated 11 hours ago

Readme

# @onthewayli/dsh-plugin-path-completion

English | [中文](README.zh.md)

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

```sh
# 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`:

```yaml
- 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:

- `Host` must be a loopback authority (`127.0.0.1`, `localhost`, `[::1]`) or an entry of
  `trustedHosts`;
- a request carrying `Origin` must carry the same authority as `Host`;
- `Sec-Fetch-Site: cross-site` is 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/cordis` is 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 `inputTriggers` is missing rather than silently doing nothing.
- The menu group title comes from the Harness's `slash.menu` dictionary, 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_modules` is 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

```sh
# 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.

Install

dsh plugin --profile web add github:OnTheWay111/dsh-plugin-path-completion

Profile: web

  • This package builds from source on install. pnpm will ask you to allow its build script — that is permission to run the package’s code on your machine, outside the agent sandbox. Only allow sources you trust.
  • This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.
Source