conditional-custom-prompt
Verifiedconditional-custom-prompt · v0.1.0 · MIT · Web UI
A conditional custom System Prompt switch for DeepSeek Harness: when ON, the contents of a plain UTF-8 text file are injected into the system prompt as a `custom-prompt` section; when OFF, nothing at all is injected.
Install
dsh plugin add conditional-custom-prompt Confirm the layer applied with dsh --profile default --dump-config — see the install guide.
Source
Published to npm without a public repository. Inspect the package contents before installing.
Tags
Creators
Readme
conditional-custom-prompt
A conditional custom System Prompt switch for DeepSeek Harness.
When the switch is ON, the exact UTF-8 contents of one plain text file are
contributed to DSH's own system prompt as a single section named
custom-prompt. When the switch is OFF, nothing at all is injected — no
section, no heading, no placeholder, no separator.
OFF: user request → DSH's original system prompt → model
ON: user request → DSH's original system prompt
+ custom prompt → model
The plugin never touches DSH core code, the model transport, or the assembled
request frame. It registers one prompt section through the official
ctx.systemPrompt API and lets DSH assemble and send the request.
How it works
The host half registers exactly one section:
ctx.systemPrompt.section({
name: 'custom-prompt',
order: 5000,
interpolate: false,
text: () => (store.enabled ? store.text : ''),
})
Three details make the OFF guarantee exact:
- The text is a function, evaluated on every assembly. Toggling the switch therefore affects the very next model step — no restart, no re-registration, and a request already in flight is left alone.
- OFF returns the empty string. DSH's
renderPrompt()drops empty sections before building the frame, so an OFF assembly is byte-identical to one produced with the plugin absent. interpolate: falsekeeps the prompt byte-exact.{{...}}in your text is prose, not a prompt-variable reference, so a prompt can never fail assembly or be silently rewritten.
Where things live
Everything is created on first start:
| Path | Purpose |
|---|---|
<DSH_HOME>/conditional-custom-prompt/prompt.txt |
the prompt text |
<DSH_HOME>/conditional-custom-prompt/state.json |
{ "enabled": boolean } |
On Windows with DSH Desktop, <DSH_HOME> is C:\Users\<you>\.dsh, so the
prompt file is C:\Users\<you>\.dsh\conditional-custom-prompt\prompt.txt.
Both files are written atomically (a random-suffix sibling plus a rename, with a bounded retry for Windows sharing violations), so a reader never sees a partial file and a crash cannot truncate the prompt.
Using the UI
Open Settings → 自定义 Prompt.
- A large switch reads
自定义 Prompt:ONorOFF. - The prompt file path is shown in full.
- Character count, a rough token estimate, line count, and byte count are shown.
- 编辑 Prompt loads the current text into an editor.
- 导入文件 reads a local
.txt/.mdfile in the browser (UTF-8) into the editor. Nothing is written until you save. - 重新加载 re-reads the file from disk, picking up external edits.
- 保存 writes the editor contents to the prompt file atomically.
- 查看当前状态 runs a live
systemPrompt.assemble()and reports what thecustom-promptsection actually contributes.
A compact status chip in the composer tool row shows the current state at a glance and toggles it on click.
Both surfaces read and write one shared status store, so a toggle on either one is visible on the other immediately — no reload, no waiting for a remount. The chip is clickable from its first paint: if the status has not loaded yet, the click loads it and then flips the switch, and a failed call reports itself on the control instead of leaving a dead button.
The full prompt is never rendered into the main UI and never written to a log. It is returned by the host only when the editor explicitly asks for it.
Host API
One same-origin JSON route, /conditional-custom-prompt/api. A present Origin
must match Host; mutating verbs are POST.
| Action | Method | Body | Result |
|---|---|---|---|
status |
GET | — | switch, paths, counts |
prompt |
GET | — | the full prompt text |
diagnostics |
GET | — | live assembly report |
enable |
POST | { "enabled": boolean } |
new state |
save |
POST | { "prompt": string } |
writes the file |
reload |
POST | — | re-reads the file |
Verifying the ON/OFF difference
The diagnostics action is the authoritative check because it calls DSH's own
assembly:
# OFF: the section is registered but contributes nothing
Invoke-WebRequest "http://127.0.0.1:19387/conditional-custom-prompt/api?action=diagnostics" -UseBasicParsing
# → "customPromptSectionPresent":true, "customPromptSectionInNonEmpty":false,
# "nonEmptySectionCount":6, "renderedCharacters":2818
# ON: the same assembly now carries the prompt
# → "customPromptSectionInNonEmpty":true, "customPromptSectionLength":132,
# "nonEmptySectionCount":7, "renderedCharacters":2952
renderedCharacters grows by exactly the prompt's length, and every other
section is untouched — that is the whole contract.
To see it in a real request frame, the durable session log records the system
prompt as a system/message event at surface node 0. With the switch ON the
injected text appears verbatim inside it; with the switch OFF the event is
byte-identical to one recorded before the plugin was installed.
Configuration
The plugin needs no configuration. These optional fields exist for unusual setups and are read from the plugin's Loader entry:
| Field | Default | Meaning |
|---|---|---|
home |
<DSH_HOME>/conditional-custom-prompt |
storage directory |
promptFile |
prompt.txt |
prompt file, relative to home or absolute |
stateFile |
state.json |
state file, relative to home or absolute |
order |
5000 |
section position in the assembled prompt |
routePath |
/conditional-custom-prompt/api |
API route |
Implementation notes
- No harness imports. The Cordis Loader resolves a plugin's bare specifiers
through ordinary Node resolution from the profile directory, which does not
contain the
@deepseek-ai/*packages. The plugin therefore reaches every harness capability throughctx(ctx.systemPrompt,ctx.webServer) and uses Node built-ins for everything else. - No build step. Both halves are plain ESM/CJS JavaScript with JSDoc types, so the package is loaded exactly as written.
- Section names are global.
custom-promptis claimed by this plugin; installing it twice in one composition would fail assembly loudly rather than silently duplicate the section.
Installing
The package is a self-contained DSH bundle: it declares dsh.bundle.patch and
dsh.client, ships both halves as plain JavaScript, and needs no build step.
Three ways to get it onto a machine, in increasing order of convenience for the
recipient.
From a local checkout (development)
plugin_manager install_bundle D:\path\to\conditional-custom-prompt
From a tarball (hand this file to someone)
Build it once with pnpm pack (or npm pack); the result is
dist/conditional-custom-prompt-<version>.tgz. The recipient installs it with
the same plugin manager, or directly:
plugin_manager install_bundle D:\downloads\conditional-custom-prompt-0.1.0.tgz
# equivalent, from the profile directory
cd $env:DSH_PROFILE_DIR
pnpm add D:\downloads\conditional-custom-prompt-0.1.0.tgz
The tarball contains only package.json, lib/, cordis.patch.yml,
README.md, and LICENSE — no tests, no node_modules, and no user data. The
prompt file and switch state are created on the recipient's machine under their
own DSH_HOME, so nobody inherits anyone else's prompt.
From npm (easiest for the recipient)
Publishing is what makes the install a one-liner. The manifest is already
publishable — it declares the @deepseek-ai/cordis peer and is not marked
private:
npm adduser # once per machine
npm publish # from the package root
For a scoped name (@you/conditional-custom-prompt), rename the package and
publish with npm publish --access public. Recipients then install by name:
plugin_manager install_bundle conditional-custom-prompt
The plugin manager resolves the registry, runs pnpm add, reads the bundle
patch, and activates the plugin live.
Compatibility
peerDependencies pins @deepseek-ai/cordis: ^4.0.1, which is the only harness
surface this plugin touches (through ctx, not by importing it). A DSH runtime
whose Cordis is outside that range is refused before pnpm runs, so an
incompatible install fails loudly instead of half-loading. Widen the range only
after testing against the newer runtime.
Development
node test/host-harness.mjs # 42 checks: storage, section, HTTP API
node test/client-harness.mjs # 30 checks: slot components, clicks, sync, failures
node test/dist-harness.mjs # 29 checks: the publish artifact and a clean install
The distribution harness packs the package, inspects the tarball's contents,
installs it into a throwaway project with pnpm add (the same path the plugin
manager uses), then imports the installed host half, mounts it on a fake
Cordis context, and executes the installed client half. It needs CCP_PNPM
pointed at a pnpm.cjs:
$env:CCP_PNPM = "$env:DSH_HOME\dsh-runtimes\dsh-primary-runtime\dependencies\pnpm\bin\pnpm.cjs"
node test/dist-harness.mjs
The host harness points DSH_HOME at a scratch directory, fakes the two Cordis
services the plugin injects, and drives the real HTTP API. It covers the OFF
guarantee, the ON path, Unicode/XML/JSON/Markdown/{{braces}} round-tripping, a
~259k-character prompt, corrupt-state recovery, concurrent saves, and argument
validation.
The client harness loads lib/client.js through a window.__ModuleLoader__
stub exactly as the browser kernel does, renders the captured slot components
with a minimal React stand-in, and dispatches real clicks against a stubbed host
API. It asserts that the composer chip is clickable from first paint, that a
click posts the right enable call, that a failed status read surfaces an error
and stays recoverable, and — mounting both surfaces at once — that toggling the
Settings page updates the composer chip and toggling the chip updates the
Settings page.
Neither harness touches anything outside its own scratch directory.