drscrewdriver/dsh-date-wrapper
Minimal date line for the DeepSeek Harness runtime-context snapshot: 'Current date: 2026-09-08 Asia/Shanghai Tuesday' (46 chars) - a cordis host plugin, no dsh source changes
Project Overview项目介绍
This is a date plugin for DeepSeek Harness. It injects a compressed 46-character current date line into DSH's runtime context, greatly reducing token consumption. Use it when you need the LLM to get an accurate current date. Do not use it with the native dsh-time-context, and it does not work in presets disabling runtime context.
这是DeepSeek Harness的日期信息插件,核心能力是将当前日期精简为一行46字符的内容注入运行时上下文,大幅减少token消耗。需要大模型获取准确当前日期时可使用,注意不要和原生dsh-time-context同时启用,在关闭运行上下文的预设中不生效。
请帮我了解并安装插件:【dsh-date-wrapper】【https://github.com/drscrewdriver/dsh-date-wrapper】
Send this message to DSH in your current session. CLI install commands may not be accurate across systems — DSH will figure it out for you.把上面这条消息直接发给当前会话里的 DSH,让它帮你了解并安装。安装命令不一定准确,发给 DSH 更稳。
Or use CLI install (for developers)或使用命令行安装(适合开发者)
CLI Install命令行安装
dsh plugin --profile web add github:drscrewdriver/dsh-date-wrapper
把 drscrewdriver/dsh-date-wrapper 加入你的 DSH 配置(web profile)即可启用。
READMEREADME
dsh-date-wrapper
- English README
- 中文 README
- 日本語 README
- 한국어 README
- Installation guide
- 中文安装指南
- 日本語インストールガイド
- 한국어 설치 안내
- Changelog
- 日本語 changelog
- 한국어 changelog
▼ DSH version compatibility
DSH version Load Host contract Client half 0.1.0-rc.7 ~ 0.1.1-rc.x ✅ systemPrompt.context({ name, order, text })— (host-only plugin) 0.1.2-alpha.2+ / 0.1.2-rc.1 ✅ same signature, byte-identical — (host-only plugin) One artifact covers both: the plugin only calls
systemPrompt.context, whose signature and semantics are unchanged betweendsh-v0.1.1-rc.2anddsh-v0.1.2-rc.1. It registers no settings namespace, reads no session data and makes no RPC call, so the 0.1.1 → 0.1.2 client/session/persistence rewrites do not touch it.
A minimal date line: it hangs
Current date: 2026-09-08 Asia/Shanghai Tuesday(46 characters, ~12 tokens) onto the runtime-context snapshot DSH already sends. It does not load@deepseek-ai/dsh-time-context, does not add extra session messages, does not patch DSH source, and needs no PR.
- How it works: DSH sessions, JSONL and request assembly (Chinese)
- HANDOVER.md (Chinese)
What this plugin solves
DSH's own @deepseek-ai/dsh-time-context injects about 280 characters of metadata on every request:
Time sampled while preparing turn 3, step 2: 2026-09-08T16:05:36+08:00[Asia/Shanghai]
Browser time zone for this request: Asia/Shanghai. Interpret otherwise-unqualified dates and times in this zone.
Elapsed since the preceding model-visible message: 2m 34s.
This plugin compresses the same information into a single 46-character line and moves where it lands — it no longer goes into the message stream:
Current date: 2026-09-08 Asia/Shanghai Tuesday
| Dimension | dsh-time-context |
dsh-date-wrapper |
|---|---|---|
| Injected text | ~280 characters | 46 characters (↓84%), ~12 tokens |
| Landing point | One message per pre-step (user/message) |
The platform runtime-context snapshot (systemPrompt.context) |
| Frequency | One event per eligible step | Re-sent with the snapshot only when the text changes (0 events within a day) |
| Dependency | agents service |
systemPrompt service |
| Runtime dependencies | — | none |
Version compatibility
| Item | Verdict |
|---|---|
| Target DSH versions | 0.1.0-rc.7 → 0.1.3-alpha.2 (contract stable, see the table below) |
| settings API | Not applicable: the plugin registers no settings and exports no schemastery Config |
| Contract points used | Exactly one — systemPrompt.context() |
| Conflict with a native feature | Overlaps @deepseek-ai/dsh-time-context; do not use both. Not installed by default = off by default |
| Browser half | None: no slot, no DOM, no CSS semantic tokens |
| DSH package imports | Zero: nothing from @deepseek-ai/*, which is stricter than the "runtime detection + dual API fallback" pattern |
| Contract point | 0.1.0-rc.7 | 0.1.1-rc.2 | 0.1.2-rc.1 | 0.1.3-alpha.2 |
|---|---|---|---|---|
systemPrompt.context(ctx): () => void |
yes | yes (verified on this host) | yes | yes |
PromptContext = { name, order, text }, no complete field |
yes | yes | yes | yes |
includeRuntimeContext / suppressRuntimeContext |
yes | yes | yes | yes |
agent-loop project() text dedupe and surfaceOp: "append" |
yes | yes | yes | not compared |
Method:
npm pack @deepseek-ai/dsh-system-prompt@<version>, unpack, and comparelib/types/index.d.tsandlib/index.js; the same for@deepseek-ai/dsh-agent-loop. Only 0.1.1-rc.2 has been verified at runtime on this host; runtime verification on 0.1.2-rc.1 / 0.1.3-alpha.2 is still pending (seeHANDOVER.md§7).
Why a runtime-context snapshot instead of a message
The first attempt copied dsh-time-context and appended a user/message in agent/pre-step. Measured cost was too high: each JSONL event is 339 bytes (the text is only 46 of them, because content and sections each store a copy) and it wrote one every turn.
Registering a runtime context instead folds the date into the snapshot message the platform already sends:
- The platform deduplicates snapshots by text (
RuntimeContextProjection.project()indsh-agent-loop:if (this.retained?.text === snapshot) return), so while the date is unchanged not a single extra event is written; - Snapshots append a new message (
surfaceOp: 'append') rather than rewriting in place, so the request sequence only grows → the prefix cache is preserved; - Our marginal cost is those 46 bytes, and only when the snapshot is re-sent because its text changed.
Measured on this host (one real session, 10 turns / 231 steps):
| Item | Measured |
|---|---|
| Platform runtime-context snapshots | 2 events, 1133 B each, 2.3 KB total |
| Real user messages | 10 events, 396 B each |
| Old approach (one message per turn) | 10 × 339 B ≈ 3.4 KB |
| This approach | 0 extra events; ~46 B folded into an existing snapshot |
Configuration
Shipped with cordis.patch.yml; restart after changing it:
- insert:
- id: date-wrapper
name: dsh-date-wrapper
config:
timeZone: Asia/Shanghai # IANA zone; omit to use the process zone
- An invalid
timeZonethrows at startup (no silent fallback to UTC). - The zone name in the text is the resolved IANA name (the process zone name when
timeZoneis omitted). - The runtime-context entry is named
date-wrapper:datewith order116(already taken: 110 sandbox, 115 approval, 120 subagent). - The plugin exports no schemastery
Config, so its config skips the host schema validation; everything is validated by hand invalidateConfig(). That is also why the Settings → Plugins page has no config form for it.
On/off: the plugin's activation is the switch, there is no panel toggle
The plugin ships no settings-panel toggle and no enabled config field, because:
- The feature switch is whether the plugin row is active. Inactive →
apply()never runs → the runtime-context entry does not exist → not a single character is injected. - There is no browser half (
dsh.client), so the UI owns no widget of ours. - DSH's built-in Settings → Plugins page already shows each entry as
enabled / disabled(read-only).
How to turn it off
Override it by id in your own profile patch layer — C:\Users\<you>\.dsh\profiles\web\cordis.patch.yml:
- id: date-wrapper
disabled: true # disabled; set back to false to restore
- Hot, no restart: that file is watched by Cordis HMR, and
disabled: truedisposes the row's fiber directly. - If the
date-wrapperrow does not exist yet (not installed), this patch only logs anentry "date-wrapper" not foundwarning; startup still succeeds. - ⚠️ The file must be a top-level YAML array; if it is malformed, startup fails (DSH is fail-loud for user patch layers).
How to remove it completely
dsh plugin --profile web remove dsh-date-wrapper
Removal goes through the bundle layer and requires a restart of dsh web (bundle patches are not hot-reloaded).
Install
dsh plugin --profile web add github:drscrewdriver/dsh-date-wrapper
Restart dsh web and refresh the page. Local paths, link mode and troubleshooting: INSTALL.md.
Verification
| # | How | Expected |
|---|---|---|
| A1 | Open a new session and send one message | The runtime-context snapshot contains Current date: YYYY-MM-DD <zone> <weekday> (shown as an injected context row sourced from system-prompt) |
| A2 | Check that line | ≤50 characters (46 measured; the PRD threshold of 30 was relaxed for the requested format) |
| A3 | Disable the plugin (profile patch disabled: true) |
The line no longer appears in later sessions' snapshots |
| A4 | Search the session log | No Time sampled / Elapsed since / Browser time zone |
| A5 | Set timeZone to UTC and restart |
The date follows UTC (may differ by one day across zone boundaries) |
Implementation notes
dsh-date-wrapper/
├── package.json # name / type: module / main / exports["."] / dsh.bundle.patch / files
├── cordis.patch.yml # one insert row (no patch-level id → lands at the profile root = host plane)
├── src/
│ ├── format.js # pure functions: resolveZone / renderDate / createDateContextText / validateConfig / TEXT_LABEL
│ └── index.js # apply(ctx, config) → ctx.inject(['systemPrompt'], …) → systemPrompt.context(...)
└── tests/
├── format.test.mjs # 11 cases (zone projection, weekday, format and length, degradation, config validation)
└── context.test.mjs # 7 cases (registration contract against a fake ctx)
- Host-plane row:
ctx.inject(['systemPrompt'], …)opens a child fiber; if the service is missing, the plugin silently registers nothing instead of failing the whole boot. - Fail-soft text provider: throwing during prompt assembly would fail every request, so a render failure returns an empty string (the platform filters empty text out).
- No
complete: setting it would shadow the entire system prompt. - Deduplication is the platform's job: no per-agent state is kept; across midnight the snapshot simply carries the new date.
- Lifecycle: the registration belongs to the
ctx.injectchild fiber and is reclaimed when the plugin is deactivated.
Development: TDD + lint
npm install # devDependencies only (eslint / @eslint/js); zero runtime dependencies
npm run tdd # watch mode: rerun on src/ or tests/ changes (node --test --watch)
npm test # one full run: node --test "tests/*.test.mjs"
node tests/format.test.mjs # run a single file (most reliable under a sandbox: no child process)
npm run lint # eslint . (src + tests + eslint.config.mjs)
npm run lint:fix # auto-fix what can be fixed
npm run verify # lint + test; run this before committing
Red-green-refactor
Test cases map directly to acceptance criteria: write a failing assertion first, then make it pass.
| Step | Action | Command |
|---|---|---|
| 1 red | Add an assertion in tests/*.test.mjs named after the acceptance criterion, asserting the behaviour you do not have yet |
npm run tdd |
| 2 green | Write the minimal implementation in src/ to pass it without touching other assertions |
npm run tdd |
| 3 refactor | Rename and extract pure functions while staying green; src/format.js holds all pure logic, src/index.js only registers |
npm run tdd |
| 4 gate | Run lint + the full suite before committing | npm run verify |
18 assertions today: format.test.mjs (11) covers the pure functions, context.test.mjs (7) asserts the registration contract against a fake ctx.
Lint configuration highlights
- ESLint 10 flat config (
eslint.config.mjs) with@eslint/jsrecommended as the baseline. - Tightened rules:
eqeqeq,prefer-const,object-shorthand,no-unused-vars(_prefix exempt). - Node globals
crypto/console/processare declared explicitly, otherwiseno-undeffalse-positives.
Known limitations
- Inactive under fixed-prompt presets: if a preset's persona sets
includeRuntimeContext: false(the officialminimaland the localsimple-replyboth do),assemble()returnscontexts: []and this plugin's entry is dropped wholesale. Those presets are designed to forbid later listeners from adding anything to the prompt. - Old snapshots stay in history: when the date changes the platform appends a new snapshot (the old one is kept) and the new one takes effect through its own "This snapshot supersedes earlier runtime-context snapshots" declaration — the same way the platform handles cwd / sandbox / approval policy changes.
- Bundle patches are not hot-reloaded: changing
cordis.patch.ymlor upgrading the plugin requires a dsh web restart (changingdisabledin the profile patch is hot). dsh-time-contextis neither loaded nor filtered: if you mount it explicitly in a preset, its verbose text appears as usual. Do not use both.- No runtime probe for the contract point:
systemPrompt.contextis called unguarded, so a future DSH rename would surface as a plugin load failure instead of a silent degradation (seeHANDOVER.md§7).
License
MIT
titanwings/dsh-automation
linhut/gongwen-skill
Jungod1121/dsh-anchored-standard
omdsh-dev/dsh-advisor
wlj521/dsh-ui-tweaks
Moeblack/deepseek-manners