Skip to content

conditional-custom-prompt

Verified

conditional-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: false keeps 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:ON or OFF.
  • 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 / .md file 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 the custom-prompt section 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 through ctx (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-prompt is 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.