dsh-web
zhu1090093659/dsh-webDeepSeek Harness(DSH)Web 插件聚合生态包 · 一切皆插件,创意工坊分发(dsh-market.com)
DSH host plugin that guards against uncached prompts: blocks sends (read-only) and/or archives sessions after the prompt cache likely expired.
English | 日本語 | 简体中文
A plugin for DeepSeek Harness (DSH) that prevents accidental sends to old sessions whose prompt cache has expired.
If you send a follow-up message to a session where some time has passed since the last response, the entire accumulated conversation history is billed at the regular uncached input rate. cache-sentinel monitors the time elapsed since the last response and prevents accidental sends by automatically blocking sends to old sessions or automatically archiving the sessions themselves.
For sessions where the prompt cache lifetime has passed, this plugin prevents accidental sends in two ways:
/unlock command.You can switch at any time from the chat /sentinel command to both mode, which combines both (input blocking after 60 minutes, archiving after 24 hours), or off mode, which temporarily disables the feature.
dsh plugin --profile web add github:AliceSaikawa/cache-sentinelweb is an example. Specify the profile name you actually use. cache-sentinel works only in profiles that include the Web app, such as web and desktop (see Supported Versions and Environment).
This installs the latest main. To pin a version, append a tag as #v (for example, github:AliceSaikawa/cache-sentinel#v1.2.3). Tags up to and including v0.5.0 do not contain the built lib/, so they cannot be installed this way. You can also install by giving the URL of the cache-sentinel.tgz attached to a GitHub Release.
Plugin code is loaded when DSH starts, so a restart is required after adding it.
Example when running DSH as a systemd user service:
systemctl --user restart deepseek-harnessdsh plugin --profile web list cache-sentinel --depth 0dsh plugin --profile web remove cache-sentinelMode · Behavior block (default) · Blocks input once idleMinutes (default: 60 minutes) have passed since the last response (viewing is still allowed). /unlock allows the next single send archive · Automatically archives the session once idleMinutes (default: 60 minutes) have passed since the last update (hidden from the sidebar and cannot be opened) both · Blocks input idleMinutes (default: 60 minutes) after the last response, and automatically archives archiveAfterMinutes (default: 1440 minutes = 24 hours) after the last update off · Neither blocks input nor archives sessions (/sentinel and /unlock commands remain available)
/sentinel CommandYou can check the current status and switch modes from the chat input box.
/sentinel: Shows the current effective mode and its source (set via /sentinel / set in YAML), thresholds, elapsed minutes since the last response in this session, and dryRun status./sentinel block | archive | both | off: Switches the effective mode. It is saved to $DSH_HOME/cache-sentinel/state.json and preserved across DSH restarts (takes precedence over mode in YAML)./sentinel default: Clears the mode saved via /sentinel and reverts to the mode setting in YAML./unlock Command/unlock in an expired session under input blocking (block / both mode) allows just the next send once. The permission expires after unlockTtlMinutes (default: 10 minutes).step/end) returns after unlocking, the permission has served its purpose and normal checks resume (since the session is not expired right after a response, you can continue the conversation naturally).LC_ALL, LC_MESSAGES, LANG, and the system locale, in that order; the official Web UI resolves it from the browser language (navigator.languages). English, Japanese, and Simplified Chinese are supported, and unsupported languages fall back to English. Log output (lines prefixed with [cache-sentinel]) stays in Japanese.lib/client.js) displays a status banner above the input box (conversation.input.dock)./unlock, the reason appears under the warning. Send /unlock from the input box instead.GET /api/cache-sentinel.status?sessionId= (with Connection authentication) returns the session status JSON.You can change the settings in your profile's cordis.patch.yml. Omitted keys use their default values.
- id: cache-sentinel
config:
mode: block # block / archive / both / off (can be overridden with /sentinel)
idleMinutes: 60 # threshold for input blocking (min); in archive mode, the archive threshold (min)
archiveAfterMinutes: 1440 # archive threshold in both mode (min)
unlockTtlMinutes: 10 # how long /unlock stays valid (min)
checkIntervalSeconds: 60 # archive check interval (sec, 10 or more)
skipPinned: true # do not archive pinned sessions
archiveExisting: false # also archive sessions that have been idle since before archiving was first enabled
maxPerCycle: 20 # maximum sessions to archive per cycle
dryRun: false # when true, neither block nor archive, only log
# stateFile: /path/to/state.json # path to persistent state file (default: $DSH_HOME/cache-sentinel/state.json)cordis.patch.yml take effect without restarting DSH (the plugin is re-applied with the new settings, and in-memory /unlock permissions are cleared).block / both Mode)agent/pre-step hook (prepend: true). Because it runs before automatic compaction (dsh-compaction-basic), model calls for automatic compaction do not occur in expired sessions either.step/end event (including history inherited from a fork). New sessions with no model response yet, or sessions whose elapsed time is within idleMinutes minutes (inclusive), are allowed through.archive / both Mode)checkIntervalSeconds seconds thereafter.lastActivityAt) is the maximum of the session log file (session.v*.jsonl[.zstd]) mtime, the timestamp of the last event in a loaded session, and the timestamp when the session was manually unarchived (or the creation time if none are available).baselineAt) when an archiving mode was first enabled are not archived by default (set archiveExisting: true to include them).skipPinned: true), subagent sessions, and sessions with running work (turns, jobs, subagents, reminders) are excluded./unlock cannot be executed from the GUI.session-title-first-prompt-llm) calls the model independently on the first send and is outside the gate (only on the first send in new sessions or forks, and only the first message is sent).After re-adding a new version with dsh plugin --profile web add ..., restart DSH before enabling the plugin or changing its settings. Toggling the plugin off and on without restarting will reuse the old module remaining in the process.
Startup, blocks, /unlock grants, mode changes, and archiving are output with a [cache-sentinel] prefix to stdout / stderr and the DSH logger.
Example for checking logs when running DSH as a systemd user service (deepseek-harness):
journalctl --user -u deepseek-harness | grep cache-sentinelpnpm test # unit tests (node --test)
pnpm run typecheck # type check (tsc --noEmit)
pnpm run build # build (lib/index.js, lib/client.js)Installing with github: does not build anything, so the built lib/ is committed to the repository. When you change src/, run pnpm run build and commit lib/ as well.
CI (.github/workflows/ci.yml) runs the same three commands on Linux and Windows for every push to main and every pull request, and also checks that the committed lib/ matches the build output. Releases come from .github/workflows/release.yml on a v* tag push: the tag must match the version in package.json, and the built cache-sentinel.tgz is attached to the GitHub Release.
To build from source and install a local .tgz:
git clone https://github.com/AliceSaikawa/cache-sentinel.git
cd cache-sentinel
pnpm install
pnpm run build
pnpm pack
dsh plugin --profile web add file:$PWD/cache-sentinel-$(node -p "require('./package.json').version").tgzscripts/dsh-rpc.mjs is a verification script for environments running DSH as the deepseek-harness user systemd service; it retrieves the token from the startup URL in the journal and calls DSH RPCs directly.
node scripts/dsh-rpc.mjs workspace/unarchiveSession '{"sessionId":"..."}'
node scripts/dsh-rpc.mjs --args commands/execute '{"agentId":"<sessionId>","line":"/unlock","submittedAttachments":[]}'scripts/dsh-compat.ts checks whether the @deepseek-ai/* packages that cache-sentinel uses have changed between DSH versions. When a new DSH version is released, it compares each package against the verified version and reports same (no change, so it should stay compatible) / diff (files changed, so a human should read the diff) / missing (fetch failed).
node scripts/dsh-compat.ts /var/tmp/dsh-compat 0.2.1-rc.1 0.2.1-rc.2The same check runs on GitHub Actions (.github/workflows/dsh-compat.yml). Start it from Actions > DSH compat > Run workflow, with the base version and the versions to check (space-separated). Dist-tags such as next are resolved to the version of @deepseek-ai/dsh. The result appears in the job summary. The job fails only on missing; a diff still needs a human to read it. On pull requests that change the script or the workflow, it runs automatically on a pinned pair (0.2.1-rc.1 and 0.2.1-alpha.5).
See docs/ for design, specifications, and investigation details (all documents under docs/ are in Japanese).
web and desktop, that is, profiles whose dsh.profile.bundles includes @deepseek-ai/dsh-web-app (custom profiles created from the web template also qualify). cache-sentinel requires the workspaceRegistry and connection services, which only that bundle provides.headless and acp, cache-sentinel does not start. It keeps waiting for those services, so input blocking, archiving, /sentinel, and /unlock are all unavailable. No error is reported, and the [cache-sentinel] startup log line does not appear.desktop uses the same bundle as web but has not been verified on a real environment. The npm dsh CLI rejects plugin management for the desktop profile, so add the plugin with the dsh command installed with the Desktop app.pnpm on PATH when adding plugins (when installing with github: or from the Releases .tgz, no build is needed on the user side)MIT License — see LICENSE.