DeepSeek Harness Plugin

Saretheya/dsh-tick

Stars ★ 0 Downloads (30d) 1,451 Category Workflow & Automation Added 2026-09-24 npm dsh-tick

Session-local scheduled tasks: inject a prompt into the current session after a delay, at a fixed time, or on a repeating interval, so the model wakes up and runs it on time. Manage them from a panel above the composer, the /schedule command, or seven model-facing tools. Timers freeze while DSH is closed and never auto-resume; pause keeps the remaining time, so resuming continues from where it stopped.

Install

# from npm (prebuilt)

dsh plugin --profile web add dsh-tick

# from GitHub (first run asks for allowBuilds approval — follow the hint, retry)

dsh plugin --profile web add github:Saretheya/dsh-tick

Any plugin you install runs third-party code with your own permissions — it can read your files, use your credentials, and reach the network, and tool approvals don’t sandbox it. GitHub-sourced plugins also run build scripts at install time — pnpm blocks those until you allow them, so an install can stop with ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED or ERR_PNPM_IGNORED_BUILDS; dsh prints the exact key to add under allowBuilds in your profile’s pnpm-workspace.yaml, and the install works on the next run. Allowing a build is a trust decision: only install sources you trust, and pin a commit (github:owner/repo#sha).

README

dsh-tick

Schedule work inside a DSH session: when the time comes, a prompt is injected into the current session to wake the model up and run it.

English · 中文

The collapsed summary row above the composer — click it to manage tasks.


What it does

You want the AI to "check the build queue in 5 minutes", "summarize today's work at 11pm", or "look at the deployment every 10 minutes" — say it once, and it happens on time.

dsh-tick brings scheduling into the current session: at the appointed time your prompt is injected into that session, waking the model to carry it out. Tasks belong to the session that created them and never leak into another one.

Expanded panel


Three timing modes

Mode How you ask Behaviour
Delay "check the build queue in 2 minutes" Runs once when the countdown ends
Fixed time "summarize my work at 23:00 today" Runs once at that moment
Periodic "look at the deployment every 30 minutes" Fires on a fixed interval until you pause or delete it

Delays are capped at 30 days (longer ones are rejected, and the error tells you to use fixed-time mode instead); periodic intervals have no duration cap. To wait longer, use Fixed time with an absolute moment.

One caveat for very long intervals: timers freeze while DSH is closed, so a periodic task only keeps a true interval while DSH stays open.


Three ways to manage tasks

One set of tasks, whichever way you prefer:

  1. The panel above the composer — expand it to see time remaining, pause, edit, delete;
  2. The /schedule command — create one right in the chat box, no panel needed;
  3. Let the assistant manage them — your model can create, list, pause and delete tasks directly.
/schedule +30m check the build queue
/schedule +2h30m summarize today's changes
/schedule every 10m look at the deployment
/schedule @2026-09-21T09:00:00+08:00 summarize what is left before standup
/schedule list

Durations use s / m / h (e.g. +30m, +2h30m); Chinese units (秒 / 分钟 / 小时) work too. The d (days) unit is not supported — to wait more than a day, use Fixed time with an absolute moment instead.


How it behaves

Pause and resume freeze the remaining time. Pause a task with 4 minutes left, resume it tomorrow, and it still waits 4 minutes — it will not fire immediately just because a night passed.

Timers freeze while DSH is closed (delay and periodic modes). When you reopen DSH those tasks come back paused, so nothing fires while you are away. A fixed-time task whose moment passed in the meantime is marked Overdue and waits for you.

Overdue and paused

The three rows above are: Paused (periodic), Overdue (fixed time, missed across a restart), and Paused (delay). The red number in the sidebar footer is the total overdue count across all sessions.

An overdue task runs the moment you press play — the resume action is "run now".


Install

dsh plugin --profile web add dsh-tick

Restart DSH afterwards.

Requires DSH 0.1.5-rc.2 or newer — both 0.1.5 and 0.2.0 are tested, see "Compatible DSH versions" below. The plugin does not touch settings.yaml; uninstalling removes it along with its own folder, leaving nothing behind.


Configuration

The plugin keeps its own config file inside its own folder (nothing is written to DSH's settings):

{
  // Language of the prompt injected into the model: 'en' (default) | 'zh' | 'custom'
  "serverPromptLanguage": "en",

  // ── These three gates ship with CONSERVATIVE defaults — raise them to taste ──
  "maxTasksPerSession": 50,      // how many tasks one session may store
  "maxActivePerSession": 10,     // how many may be enabled at once
  "maxInjectionsPerMinute": 1,   // injections per minute (guards self-loops)

  "heartbeatMs": 15000,          // heartbeat interval: timer precision across restarts
  "graceMs": 60000,              // tolerance so a task that just fired is not called "missed"
  "showDock": true,              // show the panel above the composer
  "showSidebarSummary": false,   // sidebar footer summary entry (off by default)
  "allowAiCreate": true          // let the model create tasks
}

⚠️ The three gates default to conservative values (maxTasksPerSession: 50, maxActivePerSession: 10, maxInjectionsPerMinute: 1). They exist to catch runaway batches of AI-created tasks and self-injection loops — they are not performance limits. If they feel tight in daily use, just raise them: they are a safety rope for you, not a hard product constraint.

Every option here takes effect immediately — no DSH restart needed.

Injection language (serverPromptLanguage)

Controls the language of the prompt the model sees, independently of the UI language:

Value Effect
"en" (default) English
"zh" Chinese
"custom" Your own templates

Defaulting to English matches what DSH's own goal / todo / schedule plugins do: they ship a bilingual UI, but the prompt injected into the model is always English.

Choosing custom generates a template file with full inline documentation, picked up on the next injection — no restart needed. If that file is ever corrupted, the plugin renames it to .bad-<timestamp>, regenerates the initial template, and shows a one-off notice in the conversation.


UI language

The panel, the sidebar entry and /schedule replies follow DSH's language setting (Settings → General → Language) — both Chinese and English are supported. Chinese is used when no choice has been made.

The prompt injected into the model uses serverPromptLanguage above and is independent of the UI language.


Boundaries with DSH

  • No custom events in the session log. Task state lives in the plugin's own folder. DSH's session log enforces an event-type allowlist and offers no public way for a plugin to register its own types.
  • Injected messages are marked as plugin-sourced and never impersonate the user. Session history always distinguishes "what you said" from "what the timer injected".
  • It does not take over the official dsh-schedule. That plugin is a host-wide durable reminder system (cross-session, and it can wake cold sessions); this one is a session-local task system. They occupy different niches — running both leaves you and the model facing two scheduling systems.

Appendix: why there is no in-session icon

You will not see a scheduled-task icon in the session list. That is not an oversight — the official mechanism simply cannot provide one.

DSH's sidebar exposes only six slots, and the session-list area (sidebar.workspaces) is a single slot already claimed by the official workspace UI. single means "the highest-priority renderer wins": a second registrant replaces rather than joins, so nothing can be added incrementally. Inside an individual session row there is no slot render point at all.

The only workable path is DOM injection keyed on minified class names — it breaks on every DSH release and fails silently when it does. Not worth it. The official goal and todo plugins have no in-session icon either.

The alternative: a cross-session summary entry in the sidebar footer (showSidebarSummary, off by default). Turn it on and it shows the pending and overdue totals across all sessions; clicking it expands a cross-session list you can jump from. It uses the official sidebar.footer.action slot with zero DOM dependency — just set showSidebarSummary to true in the config above.


Known limitations

  • Timers need DSH running. Delay and periodic modes freeze while DSH is closed; keep DSH open if you need them to follow real time.
  • No catch-up. Rounds missed while DSH was closed are discarded, not replayed on restart.
  • Injection rate defaults to 1 per minute (maxInjectionsPerMinute): if you set a shorter interval than that, the extra firings are throttled. Raise this value if you need faster.
  • The d (days) unit is not supported. Use h (e.g. +48h) or Fixed time.

Changelog

v1.1.0 (2026-11-04) — background wake + collapsed countdown

① New allowBackgroundWake option (on by default)

Previously, when a task came due while its session was not open on screen, the plugin could not reach an execution target for that session — so the task only fired later, all at once, the moment you opened the session.

Now the plugin wakes that session when a task comes due and delivers on time. It goes through the official sessionController.resolveAgent() (contract: Resolve or resume one ordinary Session), which is exactly what the official dsh-schedule does.

If you would rather keep things quiet and only deliver into sessions you already have open, set allowBackgroundWake to false in data/config.json. Nothing is lost — tasks stay overdue and fire as usual once you open the session.

② Fixed the countdown not updating while collapsed

The collapsed summary row above the composer already showed "next task in …", but only refreshed every second while expanded; when collapsed the number froze until the next poll. Now it ticks every second in both states whenever a pending task exists (re-render only, no requests).


v1.0.3 (2026-11-03) — fix injection failure on DSH 0.2.1

Symptom: after upgrading to DSH 0.2.1, a task failing at fire time aborts the whole turn with:

本轮运行失败  format v4 message requires a producer-owned source kind

Cause: 0.2.1 enables session format v4, which retires the kind: 'plugin' message source in favour of a "producer-owned" kind. The plugin was injecting { kind: 'plugin', plugin: 'tick' } — exactly the shape now rejected — so the injection step failed. The timer itself was fine: the task kept its schedule and was marked done; only "deliver the prompt into the session" broke.

Fix: inject { kind: 'plugin:tick' }. That shape is not arbitrary — it is the very shape DSH itself produces when migrating old data to v4 (its migration maps plugin:'tick' to plugin:tick), so it matches official semantics exactly. Session history still shows at a glance that this came from a plugin, and it can never be mistaken for something you said.

Compatibility (one package, no per-version build):

DSH version Status
0.1.5-rc.2 ✅ supported (that release has no runtime check on message source)
0.2.0 ✅ supported (already reads config via describe(), see below)
0.2.1-rc.5 ✅ supported (the target of this fix)

The right path is chosen by capability detection, not a version check:

0.1.x 0.2.x
Reading UI language settings.get('locale') settings.describe() (get removed)
Language change notification settings.watch(cb) the settings/document-updated event (watch removed)

Everything else this plugin uses (defineTool / ctx.tools.register / createUserMessage / connection.fetch.register / ctx.slots.register / ctx.locale.*) is unchanged in 0.2.x.


License

MIT

Copyright (c) 2026 Saretheya · MIT License

Content from the project README on GitHub ↗

Links

More in this category

View the whole category →

Community comments

Comments are public GitHub Discussions. Loading them connects to GitHub and Giscus; a GitHub account is required to post.