sandbase-harness
sandbaseai
Local-first, self-hosted AI agent runtime and MCP bridge with sandboxed sessions, memory, credentials, audit/replay, and a local Console.
PROJECT TOPICS
INSTALL REFERENCE
dsh plugin --profile web add github:GooDAnDReaDY/dsh-cron
该命令指向仓库当前默认分支;尚无绑定当前 commit 的完整验证结果。
PROJECT README
🇬🇧 English • 🇷🇺 Русский • 🇨🇳 中文说明
|
⭐ If you like this plugin, please star it on GitHub — it shows me that the plugin is useful to you and motivates me to keep developing it.
🐛 If you find a bug or would like to request a feature, open a GitHub issue in any language — I will review your proposal and implement useful suggestions in a future plugin version. |
Autonomous AI agents often need to perform recurring duties: generating daily morning digests, triaging bug trackers, checking API health, syncing databases, or running periodic Git hygiene. Without a dedicated scheduler inside the harness, users must rely on external crontab wrappers, complex webhook setups, or manual intervention.
@goodandready/dsh-cron is a native full-stack scheduling and background automation plugin for DeepSeek Harness. It bridges standard cron expressions and natural interval syntax with autonomous agent execution, providing:
cron_* tools let agents schedule their own follow-up executions during conversations.croner with interval aliases, one-shot delays, atomic file persistence, run histories, and cost tracking.dsh-tts) and Gitea, with {variable} message templates and secrets referenced by DSH credential name.graph TD
subgraph Client ["Web Client Surface (DSH UI)"]
SidebarBtn["Sidebar Clock Action<br/>(DSH Client UI Slot)"]
Overlay["Visual Task Manager Panel<br/>(Tabs: All, Active, Paused, Completed)"]
CreateWithDSH["'Create with DSH' Dialog<br/>(natural language task)"]
ManualForm["Manual Task Form<br/>(Cron Expression, Timeout, Overlap, Model)"]
SettingsCard["Settings Card<br/>(Channels, Templates, Credentials)"]
end
subgraph Server ["Server Runtime (Cordis & DSH Services)"]
HttpRoutes["HTTP REST API<br/>(/dsh-cron/*)"]
AgentTools["AI Tool Calling Gateway<br/>(cron_create_task, cron_list_tasks, ...)"]
Scheduler["TaskScheduler Engine<br/>(Croner instances + one-shot timers)"]
Store["Atomic TaskStore<br/>(tasks.json with atomic write)"]
AgentRunner["Agent Session Dispatcher<br/>(Executes prompt with chosen model)"]
Runtimes["Execution Runtimes<br/>(shell, node, python, http, ssh, docker)"]
Notify["Delivery Router<br/>(templates + 9 channels)"]
Secrets["Credential References<br/>(DSH credentials / ENV)"]
end
SidebarBtn --> Overlay
Overlay --> CreateWithDSH
Overlay --> ManualForm
SettingsCard --> HttpRoutes
CreateWithDSH -->|POST /chat/start| HttpRoutes
ManualForm -->|POST /tasks| HttpRoutes
HttpRoutes --> Scheduler
AgentTools --> Scheduler
Scheduler --> Store
Scheduler -->|Trigger on interval/one-shot| AgentRunner
Scheduler --> Notify
Click the clock icon in the DSH sidebar (positioned next to the new-session button) to open the management panel:
15m, 1h, Daily 09:00, Weekdays, Weekly Mon) directly within the modal task schedule editor with instant natural-language preview.Transform natural language into a scheduled job without guessing cron syntax:
cron_create_task tool only after your confirmation.Autonomous agents can manage schedules directly:
| Tool | Description |
|---|---|
cron_create_task |
Creates a scheduled task: title, schedule, prompt, fallbackModel (one retry on a stronger model when a run fails), optional type (llm/script/node/python/http/ssh/docker/skill/workflow), delivery, provider, model, channels, template, notifyTelegram, onlyOnFailure, timeoutSeconds, overlapPolicy, kanbanMode |
cron_schedule_task |
Alias of cron_create_task kept for compatibility with existing agent prompts |
cron_list_tasks |
Lists tasks with statuses, next run timestamps, token totals, and cost estimates |
cron_pause_task |
Pauses a schedule without deleting its configuration |
cron_resume_task |
Resumes a paused schedule |
cron_delete_task |
Permanently removes a task and its history |
cron_run_task |
Triggers an immediate out-of-band run |
cron_get_task |
Reads the full configuration of one task, including fields the list does not show |
cron_update_task |
Changes an existing task in place (whitelisted fields, same validation as the HTTP route); the model is told to confirm code-executing changes with the user first |
Example invocation the model can make during a conversation:
cron_create_task({
"title": "Morning digest",
"schedule": "0 8 * * 1-5",
"prompt": "Prepare a brief morning digest of active tasks and open tickets.",
"type": "llm",
"delivery": "isolated"
})
Powered by croner, supporting standard 5-field cron expressions plus user-friendly aliases:
0 9 * * 1-5 — weekdays at 09:00*/15 * * * * — every 15 minutes0 0 * * 0 — every Sunday at midnightevery 10m / every 2h / every 30s — natural duration intervalsdaily / hourly / weekdays shortcuts, plus standard @hourly / @daily / @weekly / @monthly / @yearly and @every 30mEurope/Berlin) on a task; without it the schedule follows the server's local timeat: 2026-09-05T15:00:00Z (exact ISO timestamp) or relative delays in 20m / in 2h (Russian aliases such as через 15 минут are accepted too). One-shot tasks flip to completed automatically after their single run and are listed under the Completed tab.maxRetries and a base retryBackoffMs per task; failed runs (error/timeout) are retried with exponential backoff, and the attempt counter resets on success.skip (default — record the gap), runOnce (execute once, late), or catchUpAll (run late and record the gap). A missed one-shot under skip is retired as completed instead of firing stale.maxConcurrent (plugin setting) caps parallel runs; extra runs are recorded as skipped with a reason.Every task picks its own runtime; non-LLM runtimes need no model and consume no tokens:
script) — command or script through the harness shell, with env and cwd.node) and Python (python) — run a snippet with an explicit interpreter path (nodePath, pythonPath); Python detects a project virtualenv.http) — GET/POST/… to a URL with custom headers and body, and the response status/output recorded in the run history.ssh) — execute a command on a remote host through a dsh-remote-workspace profile (sshProfileId) or standalone host/key fields.docker) — run the command in a container image (dockerImage).env map (KEY VALUE per line in the UI) applied to external runtimes; secrets do not belong here.workspaceId) and, for code-modifying agent tasks, run it in an isolated git worktree (worktree, keepWorktree).fallbackModel (and optionally fallbackProvider) and a failed run — error or timeout — is retried once on that model before the ordinary retry backoff applies. History records which model produced the result and whether the fallback was used, usage and cost of both attempts are summed, and the {model} template variable renders the model that finished the run. Only agent-mediated tasks (llm, skill, workflow) can use a fallback.costLimitUsd (lifetime spend limit in USD), dailyCostLimitUsd (rolling 24-hour spend limit in USD), and tokenLimit (lifetime token limit). If a task exceeds any threshold, execution is halted, the task is automatically paused with pausedReason (cost_limit_exceeded, daily_cost_limit_exceeded, or token_limit_exceeded), and an alert notification is dispatched across all active channels.default, read-only, workspace-write, or full are applied to the task's agent session before the prompt runs.A task with output can carry a silent rule written in plain words ("stay silent when no filesystem is above 80%"). On a successful run a cheap model judges the output against that rule and the report is skipped when the verdict is to stay silent, with the reason recorded in the run history. It fails open: no rule, no model, a failed call or an unreadable answer all mean the report is delivered. silentRuleModel (plugin setting) picks the model used for the judgement.
Agent tasks can ask for a diagnosis: with inspectOnFailure set, a failed run (error or timeout) is read by a model together with the task prompt and truncated output, and the run history stores a short diagnosis plus a concrete prompt change. The history entry offers to load that suggestion into the edit form — nothing is applied automatically. The model is configurable with inspectorModel, and {diagnosis} is available in message templates. A broken or unavailable model call leaves the failed run exactly as it was.
A finished run is delivered to every channel configured for the task — Telegram, dsh-kanban, Discord, Slack, ntfy, Bark, PushPlus, voice via dsh-tts, and Gitea issues:
notifyTelegram / kanbanMode switches, and an empty selection falls back to them.{title} {id} {status} {output} {error} {duration} {schedule} {time} {tokens} {cost}. Unknown placeholders are left intact, failed runs default to a failure template.onlyOnFailure — globally or per task, clean runs stay silent and only error/timeout runs are dispatched.botTokenRef, ntfyTokenRef, pushplusTokenRef, giteaTokenRef); the value is resolved at send time through the DSH credentials service with an environment-variable fallback, and never travels through plugin settings. Webhook URLs and the Bark device key do embed a secret, so they are stored in the plugin settings file but are always returned masked to the browser and a masked value echoed back by the UI never overwrites the stored one.deliveryTimeoutMs, default 15000 ms, editable in the settings panel or settings.yaml) and channels are dispatched concurrently, so one unresponsive endpoint is recorded as a failure and cannot delay the other channels or the next scheduled tick. The bound is enforced around the whole channel handler, which also covers credential resolution, which does not support abort signals.dsh-messenger-gateway section of your DSH settings.yaml (best-effort fallback).dsh-tts speaks the report through its HTTP route (ttsBaseUrl, default http://127.0.0.1:3080).giteaBaseUrl, giteaRepo, token credential); failures are labelled cron, bug, alert./dsh-cron/api/telegram-webhook):/status — general scheduler health, uptime, active/paused task counts./tasks — list configured tasks with schedule and state./run <id> — trigger immediate out-of-order execution of a task./pause <id> and /resume <id> — pause or resume a task schedule./log <id> — view the most recent run output and execution details./help — display available bot commands.
Configured via incoming webhook and restricted to chat/user IDs specified in telegramAllowedChatIds.kanbanMode set to on_failure or always, the plugin creates cards in dsh-kanban (on_failure → Backlog on error/timeout; always → Done/Backlog on completion).Prevent rogue processes from stacking concurrent duplicate executions:
timeoutSeconds) — when the limit is reached, shell subprocesses are killed immediately via the abort signal and agent sessions are disposed so they stop consuming tokens. Default: 1800 (30 minutes).overlapPolicy) — controls what happens when a tick fires while the previous run is still active:skip (default): drops the overlapping run and records a skipped entry in the run history.queue: queues the next execution and starts it as soon as the active job completes.replace: aborts the active run via AbortController and launches a fresh execution.If the daemon was offline at a scheduled time, the run is recorded as missed on startup, so gaps in the history stay visible.
heartbeatUrl and heartbeatIntervalSec in the plugin settings and the scheduler pings that URL on schedule — an external monitor alerts when the pings stop.GET /dsh-cron/heartbeat endpoint reports liveness, active task count and the last run time for your own watchdogs.Long-lived operational jobs can be declared in the profile configuration instead of being recreated by hand in the UI. The config file owns the jobs it declares: at every plugin start they are created or updated, and a job that disappears from the file is removed.
Add a jobs list to the plugin section of your profile config (cordis.patch.yml):
dsh-cron:
jobs:
- id: nightly-backup
title: Nightly backup
schedule: "0 3 * * *"
type: script
prompt: "bash /path/to/backup.sh"
channels: ["telegram"]
timeoutSeconds: 3600
- id: morning-digest
title: Morning digest
schedule: "0 8 * * 1-5"
type: llm
prompt: "Prepare a brief morning digest of active tasks."
provider: my-provider
model: provider-id/model-id
id, title, schedule; the types that carry their payload in the prompt (script, node, python, ssh, docker, llm, skill, workflow) also need a non-empty prompt. http is exempt: its target is given by httpUrl (or prompt).channels, model, provider, fallbackModel, silentRule, inspectOnFailure, timezone, timeoutSeconds, template, env, cwd, and the runtime fields (nodePath, pythonPath, httpUrl, httpMethod, httpHeaders, httpBody, sshProfileId, sshTarget, dockerImage, workspaceId, worktree, keepWorktree, skillName, workflowName).409 on the panel and on the API, and a create-or-update POST /dsh-cron/tasks that carries the existing id of a config-owned task is refused the same way — the config file is the source of truth. Run Now stays available.id created through the UI, the API or an agent tool is never overwritten: the entry is skipped and the conflict is written to the log.config.jobs[i]: …); a broken entry is skipped and cannot stop the remaining jobs or the profile./dsh-cron/api/*, #54)External systems (CI, host cron, curl) can drive the scheduler without opening the browser panel. This is the only surface behind a bearer token; the panel routes stay local and cross-origin-protected.
Set the token as the plugin setting apiToken (masked like every secret). Auth and errors:
503;Authorization: Bearer <token> → 401, compared in constant time.| Method | Path | Description |
|---|---|---|
GET |
/dsh-cron/api/tasks |
List tasks (status / query filters as the panel) |
GET |
/dsh-cron/api/tasks/:id |
Read one task |
POST |
/dsh-cron/api/tasks |
Create a task, or update the existing one when id is present |
DELETE |
/dsh-cron/api/tasks/:id |
Delete a task |
POST |
/dsh-cron/api/tasks/:id/run |
Force an immediate run |
The operations reuse the panel handlers, so the x-dsh-cron-confirm: script gate for code-executing types and the 409 refusals for config-owned tasks behave exactly as in the UI.
BASE="http://127.0.0.1:3080"
TOKEN="<API_TOKEN>"
# list
curl -s -H "Authorization: Bearer $TOKEN" "$BASE/dsh-cron/api/tasks"
# create, or update when the body carries the id
curl -s -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"id":"cleanup","title":"Cleanup","schedule":"0 4 * * *","prompt":"Remove stale temporary files."}' \
"$BASE/dsh-cron/api/tasks"
# force a run
curl -s -X POST -H "Authorization: Bearer $TOKEN" "$BASE/dsh-cron/api/tasks/cleanup/run"
# delete
curl -s -X DELETE -H "Authorization: Bearer $TOKEN" "$BASE/dsh-cron/api/tasks/cleanup"
# a code-executing task also needs the confirmation header
curl -s -X POST -H "Authorization: Bearer $TOKEN" -H "x-dsh-cron-confirm: script" \
-H "Content-Type: application/json" \
-d '{"title":"Disk check","schedule":"0 * * * *","type":"script","prompt":"df -h"}' \
"$BASE/dsh-cron/api/tasks"
GET /dsh-cron/metrics returns Prometheus text exposition, so the scheduler can be scraped without any extra dependency:
dsh_cron_tasks_total{status} — tasks by status (gauge).dsh_cron_task_last_duration_seconds{task} — duration of a task's last finished run, in seconds (gauge).dsh_cron_runs_total{status} — finished runs since the plugin process started (counter); the statuses are success, error, timeout, skipped and missed.dsh_cron_run_records — run records currently kept in memory (gauge).Only counts, statuses and durations are exported; prompts, run output and task configuration never appear in the exposition.
scrape_configs:
- job_name: dsh-cron
static_configs:
- targets: ["127.0.0.1:3080"]
metrics_path: /dsh-cron/metrics
Creating or updating a task with an unknown delivery-channel id is now rejected with 400, and the offending ids are listed:
{ "ok": false, "error": "Unknown channel ids: email_ping", "unknownChannels": ["email_ping"] }
Changed in v0.2.7: previously an unknown id was silently dropped, so a client with a typo received ok: true and ended up with a task that delivered nowhere.
Import deliberately stays tolerant (a file may come from an older build): unknown ids are dropped from the imported task, but they are named in the response (unknownChannels) and written to the scheduler log instead of disappearing silently.
deploy.sh has a verify-only mode that inspects an already installed profile without installing anything:
bash deploy.sh verify [exact-version]
It checks that the profile reports the requested version (default: the package.json version), authenticates to the web UI, then downloads the client bundle and confirms the package name is present.
Why it is needed: the web profile can sit behind an authentication plugin and answer 401 to an anonymous request, and a plugin client bundle is served only through the exact combined ?? URL printed in the authenticated index — a bare /plugins/<name>/client.js answers 404. The check therefore builds an authenticated session first.
Environment used by the check: DSH_WEB_BASE (default http://127.0.0.1:3080), DSH_WEB_TOKEN (the token; when unset, the script reads the last one printed to the unit journal), DSH_WEB_UNIT (default dsh-web.service). No secret is stored in the script.
Developer-facing, no behaviour change. parseScheduleExpression was split into small functions that keep the same branch order — parseAtExpression, parseRelativeOneShot, parseIntervalExpression, parseAliasExpression, parseCronExpression — and scheduleTask into clearScheduled, scheduleOneShot and scheduleCron. The existing test suite passed unchanged and targeted tests were added for branch precedence and error messages.
detached: true on POSIX); aborts and timeouts send SIGTERM followed by SIGKILL to the entire group (-child.pid) to eliminate orphan and zombie processes.maxConcurrent = 2 prevents CPU and memory spikes during overlapping scheduled runs.429, 502, 503, 504, ECONNRESET).GET /dsh-cron/tasks provides ETag and responds with 304 Not Modified; client UI adapts polling frequency (visibilitychange: 30s in background tabs, 8s in active tabs).tasks-history-archive.json.prReviewerEnabled in settings.🚀 Run Now, ⏸️ Pause / ▶️ Resume, 📋 Last Output). Actions are securely routed via POST /dsh-cron/telegram/webhook with Chat ID authorization matching plugin settings or harness defaults.onSuccess and onFailure downstream task triggers. Upstream output and execution metadata are forwarded to child tasks via $DSH_PREV_OUTPUT, $DSH_PREV_TASK_ID, and $DSH_PREV_STATUS environment variables for shell tasks, and {{prev.output}} (or {{prevOutput}}), {{prev.taskId}}, and {{prev.status}} variable interpolation in LLM prompts. Prompts also support dynamic runtime interpolation for {{date}}, {{time}}, {{datetime}}, {{timestamp}}, {{year}}, {{month}}, {{day}}, {{taskId}}, {{taskName}}, and {{runCount}}. Recursion depth is strictly bounded to prevent loops.llmActionsEnabled: false settings toggle (strictly disabled by default).tasks_archive.json. New REST endpoints GET /dsh-cron/tasks/:id/archive and GET /dsh-cron/tasks/:id/stats expose historical records and aggregated latency statistics. Task UI displays execution duration latency badges with color thresholds (<5s green, <30s yellow, >=30s red)./dsh-cron/metrics endpoint exports the active concurrency gauge dsh_cron_concurrent_running, per-task prompt/completion token consumption counters dsh_cron_task_tokens_total{task,model,type}, and per-task cost estimation counters dsh_cron_task_cost_usd_total{task,model}./dsh-cron/heartbeat/:id (or /dsh-cron/api/heartbeat/:id). If a ping is missed within heartbeatIntervalSeconds + gracePeriodSeconds, the task is flagged as missed, dispatches an overdue failure alert, and triggers an onFailure recovery pipeline.preflightType: http status 2xx, command exit code 0, or disk free MB space). Failing the gate cleanly marks the task as skipped without invoking LLMs or dispatching channel errors.POST /dsh-cron/tasks/:id/dry-run or UI 🧪 Dry Run button without persisting run history or delivering messages. Preview next calculated execution dates via POST /dsh-cron/schedule/preview.priority (1 = highest, 10 = lowest) to ensure critical system alerts execute ahead of bulk background jobs.selfHealingCommand (e.g. system service restart or temp cleanup). Model failures can trigger autoDiagnose: true to append an instant root-cause diagnosis.➜ onSuccess and ↳ onFailure task connections.llm tasks and on-demand agent runs now automatically resolve and mount the system agent preset (defaulting to user's standard preset via presets.mount(agentCtx, preset.id) in setup). Scheduled agent turns now possess complete tool access (file editing, workspace exploration, shell commands, etc.) instead of running with bare chat sessions.agentPreset identifier in the task form UI, REST API, or profile jobs (e.g. coding, system, minimal). When left empty, tasks automatically resolve to the harness's default preset.agentPresets service is absent or an unknown preset ID is provided, the scheduler logs an informative warning and safely proceeds with basic model execution without aborting the scheduled task.targetSessionId, Added in v0.2.13, #143)targetSessionId. When set, the scheduler resumes the existing session via agents.resume() instead of generating an isolated ephemeral session (cron-exec-${id}-${uuid}) on every tick. The agent retains conversational memory across runs, allowing periodic auditors or assistants to reference prior findings and outputs directly in context.targetSessionReset): To prevent unbounded context growth and token cost explosion over long schedules, tasks can set an automatic rotation policy:never: Continues a single session thread indefinitely.daily: Automatically rotates the session daily (<id>-YYYY-MM-DD).weekly: Automatically rotates the session weekly (<id>-YYYY-Www).targetSessionId containing {{date}} automatically interpolates today's date (YYYY-MM-DD).sessions.archive() and unflagged from ephemeral/internal, making them visible and interactable directly in the DeepSeek Harness chat interface.agentPresets, ensuring full access to workspace, terminal, and file tools on every turn.maxRetries), its attempts counter is automatically cleared, ensuring that subsequent scheduled ticks retain their full retry budget. Any regular or manual execution also guarantees a clean retry budget on launch.this.queue). Additionally, when dequeuing tasks upon concurrency slot release, inactive or deleted tasks are safely skipped.tasks-history-archive.json to the latest 1,000 runs per task, eliminating unbounded disk growth and synchronous JSON serialization lag.TaskStore maintains an automatic atomic .bak copy of tasks.json on every successful save. In the event of process crash or file corruption, the store snapshots the corrupted file to tasks.json.corrupted.<timestamp> for forensic analysis and self-heals seamlessly from the backup.targetSessionId), if an agent turn fails due to context window saturation (context_length_exceeded), the runner detects the overflow, archives the exhausted session, automatically rotates to a fresh session thread, and transparently retries execution without task failure.taskkill /pid <pid> /T /F on task cancellation or timeout, preventing zombie background processes and orphan shells from lingering in the operating system.max-height: min(90vh, calc(100vh - 36px)) and smooth internal scrolling. The modal action footer (Cancel, Save, Create) is pinned via sticky positioning (position: sticky), guaranteeing that critical buttons remain immediately accessible and never clipped regardless of form complexity or display scale.overflow-y: auto), preventing flexbox centering clipping on small screens.locale and slots dependencies in package.json client injection manifest (dsh.client.inject).lib/best-effort.js matching standard architecture with synchronous/asynchronous error suppression, fallback value handling, and optional context logging. Eliminated all 63 empty catch blocks across runner, scheduler, store, and UI client scripts..gitea/workflows/ci.yml and .github/workflows/ci.yml) running test suites and enforcing a strict preflight gate (scripts/ci-preflight.mjs) that halts on any syntax error, empty catch, theme hardcoding, or leak attempt.rgba(...) declarations in UI styles and modals with native color-mix(in srgb, var(--token) N%, transparent).dsh.client.inject in package.json to declare full package dependencies (@deepseek-ai/dsh-client-locale, @deepseek-ai/dsh-client-ui-slots).findDestructiveRecipe in listRecipes, shouldNotifyTask in shouldSendToChannel, and TEMPLATE_VARIABLES in buildTemplateVars) into active production pipelines.updater.title, updater.btnCheck, updater.checking, updater.btnUpdate, updater.updating, updater.desc, updater.current, updater.available, updater.upToDate, updater.success) into English (en) and Chinese (zh) dictionaries in lib/client-src/10-locales.js. In accordance with DSH core plugin architecture, Russian localization is maintained externally via dsh-russian-lang.lib/client.js (~3950 lines) into 14 focused, single-responsibility fragments under lib/client-src/ (none exceeding 580 lines). Integrated zero-dependency build script scripts/build-client.mjs wired into package.json (build:client, pretest). Development fragments are excluded from npm distribution via "files": ["lib/*.js", ...].onSuccess, onFailure, heartbeat, targetSession, preflight) with dedicated .dsh-cron-tag-* CSS classes powered by semantic --dsh-cron-* theme variables. Modal overlay now adapts dynamically using var(--dsw-alias-bg-mask, rgba(0, 0, 0, 0.75)), and keyframe pulse animations use theme variables without hardcoded RGBA.lib/updater.js) with /api/dsh-cron/update endpoint and dedicated Settings UI card. Automatically queries npm registry, compares semver versions including pre-releases, and upgrades @goodandready/dsh-cron in-place through the DSH CLI without manual SSH sessions. POST updates are protected via origin validation (rejectCrossOrigin).CHANNEL_LABELS, makeInspectAsk), stripped unnecessary exports from 16 internal modules, and wired supportsSilentRule directly into execution pipeline.Install into your DeepSeek Harness web profile:
dsh plugin --profile web add @goodandready/dsh-cron
Restart your DeepSeek Harness instance and refresh the browser.
settings.yaml)Configuration can be applied in settings.yaml or managed interactively via the plugin settings card in DSH:
# settings.yaml
dsh-cron:
botToken: "" # Telegram Bot API token (kept secret; see notes)
chatId: "" # Telegram chat ID that receives reports
notifyTelegram: false # deliver reports for every task globally
onlyOnFailure: false # deliver reports only for failed runs
kanbanBaseUrl: "http://127.0.0.1:3000" # dsh-kanban HTTP API base URL
defaultTimezone: "" # default IANA time zone for schedules (empty = server local)
maxConcurrent: 0 # max parallel task runs (0 = unlimited)
heartbeatUrl: "" # dead man's snitch URL pinged on the heartbeat interval
heartbeatIntervalSec: 0 # heartbeat ping interval in seconds (0 = off)
# --- delivery channels ---
botTokenRef: "" # credential NAME for the Telegram bot token
template: "" # global message template, e.g. "⏰ {title} — {status}"
channelTemplates: {} # per-channel template overrides keyed by channel id
deliveryTimeoutMs: 15000 # per-channel delivery timeout; slow channel = failure, others unaffected
discordWebhookUrl: "" # Discord webhook
slackWebhookUrl: "" # Slack incoming webhook
ntfyUrl: "https://ntfy.sh" # ntfy server; ntfyTopic / ntfyTokenRef
ntfyTopic: ""
ntfyTokenRef: ""
barkServerUrl: "https://api.day.app" # Bark server; barkKey = device key
barkKey: ""
pushplusUrl: "https://www.pushplus.plus/send" # pushplusTokenRef
pushplusTokenRef: ""
ttsBaseUrl: "http://127.0.0.1:3080" # dsh-tts base URL
giteaBaseUrl: "" # giteaRepo = owner/repo, giteaTokenRef = credential NAME
giteaRepo: ""
giteaTokenRef: ""
# --- external REST API (#54) ---
apiToken: "" # bearer token for the external /dsh-cron/api/* surface (masked; empty = 503)
| Parameter | Type | Default | Description |
|---|---|---|---|
botToken |
string |
"" |
Telegram Bot API token. If left empty, the plugin tries to inherit the bot configured for dsh-messenger-gateway in the DSH settings as a best-effort fallback. Stored as a secret field; the UI only ever displays a masked value |
chatId |
string |
"" |
Telegram chat ID that receives the reports. Empty value falls back to the first allowed chat of dsh-messenger-gateway |
notifyTelegram |
boolean |
false |
Global switch: deliver run reports to Telegram |
onlyOnFailure |
boolean |
false |
Global switch: deliver reports only for error/timeout runs |
kanbanBaseUrl |
string |
"http://127.0.0.1:3000" |
Base URL of the dsh-kanban HTTP API used for automatic card creation |
defaultTimezone |
string |
"" |
Default IANA time zone for task schedules; empty = server local time |
maxConcurrent |
number |
0 |
Cap on parallel task runs; extra runs are recorded as skipped (0 = unlimited) |
heartbeatUrl |
string |
"" |
Dead man's snitch URL pinged every heartbeatIntervalSec while the scheduler is alive |
heartbeatIntervalSec |
number |
0 |
Heartbeat ping interval in seconds (0 = disabled) |
botTokenRef |
string |
"" |
Name of the DSH credential holding the Telegram bot token; resolved at send time (falls back to botToken, then the messenger-gateway settings, then the CRON_TELEGRAM_BOT_TOKEN environment variable) |
template |
string |
"" |
Global message template with {title}/{status}/{duration}/… placeholders; empty = built-in text |
channelTemplates |
object |
{} |
Per-channel template overrides keyed by channel id (telegram, discord, …) |
deliveryTimeoutMs |
number |
15000 |
Per-channel delivery timeout; a slower endpoint is recorded as a delivery failure and does not delay the other channels or the next tick |
discordWebhookUrl / slackWebhookUrl |
string |
"" |
Webhook URLs for the Discord and Slack channels |
ntfyUrl / ntfyTopic / ntfyTokenRef |
string |
"https://ntfy.sh" / "" / "" |
ntfy server, topic and an optional token credential name (sent as Authorization: Bearer …) |
barkServerUrl / barkKey |
string |
"https://api.day.app" / "" |
Bark server and device key (key, title and text travel in the request path) |
pushplusUrl / pushplusTokenRef |
string |
"https://www.pushplus.plus/send" / "" |
PushPlus endpoint (override for a self-hosted proxy) and token credential name |
ttsBaseUrl |
string |
"http://127.0.0.1:3080" |
Base URL of the dsh-tts plugin used for voice announcements |
giteaBaseUrl / giteaRepo / giteaTokenRef |
string |
"" |
Gitea channel: base URL, owner/repo, and the credential name of the API token |
telegramAllowedChatIds |
string |
"" |
Comma-separated list of Telegram chat or user IDs authorized to execute interactive bot commands |
apiToken |
string |
"" |
Bearer token for the external /dsh-cron/api/* surface. Stored as a secret field and returned masked; empty disables the surface (503), a wrong value answers 401 |
Notes:
croner on the host clock.cron/tasks.json) and survive restarts; missed one-shots are detected on startup.All endpoints are served by the DSH web server under /dsh-cron/. Read endpoints are open to the local UI; mutating endpoints reject cross-origin requests and accept bodies up to 1 MB. Creating script-type tasks over HTTP additionally requires the x-dsh-cron-confirm: script header, which forged cross-site posts cannot attach.
| Method | Path | Description |
|---|---|---|
GET |
/dsh-cron/tasks |
List tasks; query params status (all/active/paused/completed), query (substring search). Returns tasks, recommendation templates and aggregated stats |
POST |
/dsh-cron/tasks |
Create or update a task (id present → update). Requires title, schedule, prompt |
GET |
/dsh-cron/tasks/:id/history |
Run history, ?limit=20 |
POST |
/dsh-cron/tasks/:id/run |
Trigger an immediate manual run |
POST |
/dsh-cron/tasks/:id/pause |
Pause the schedule |
POST |
/dsh-cron/tasks/:id/resume |
Resume the schedule |
POST |
/dsh-cron/tasks/:id/toggle |
Toggle active/paused |
POST |
/dsh-cron/tasks/:id/duplicate |
Creates a paused copy of a task: configuration copied, run state (history, counters, last run) reset |
GET |
/dsh-cron/recipes |
Built-in recipe catalog: ready-to-use monitoring presets grouped by category, all read-only |
GET |
/dsh-cron/tasks/export |
Versioned JSON document with task configuration only — no history or counters. Channels reference credentials by name, but a task-level env map or HTTP headers you typed in yourself are part of the configuration and therefore appear in the file |
POST |
/dsh-cron/tasks/import |
Validates a document and applies it with add, replace or skip; supports a dryRun summary. Imported tasks always start paused, so a restore never fires until reviewed |
PATCH |
/dsh-cron/tasks/:id |
Partial update (whitelisted fields only: title, schedule, prompt, type, delivery, provider, model, runtime settings, channels, template, notification/timeout/overlap/kanban settings, status, oneShot) |
DELETE |
/dsh-cron/tasks/:id |
Delete the task |
GET |
/dsh-cron/models |
List LLM providers; ?provider=<id> lists models |
POST |
/dsh-cron/chat/start |
Start a "Create with DSH" agent session with the task-setup instructions |
GET |
/dsh-cron/settings |
Client-safe settings (token masked) |
POST |
/dsh-cron/settings |
Update integration settings (applied through the settings service) |
GET |
/dsh-cron/heartbeat |
Liveness probe: active task count, last run time, server time |
POST |
/dsh-cron/telegram/test |
Send a Telegram test message |
POST |
/dsh-cron/kanban/test |
Create a Kanban connectivity-test card |
* |
/dsh-cron/action/:id/:action |
Legacy alias for the task action routes (run, toggle, delete, history) |
GET |
/dsh-cron/metrics |
Prometheus text exposition of task and run counters — never prompts or output (#53) |
GET / POST |
/api/dsh-cron/update |
One-click plugin self-updater: query registry version and in-place upgrade (#147) |
GET / POST |
/dsh-cron/api/tasks |
External token-guarded surface: list / create-or-update (#54) |
GET / DELETE |
/dsh-cron/api/tasks/:id |
External token-guarded surface: read / delete (#54) |
POST |
/dsh-cron/api/tasks/:id/run |
External token-guarded surface: force a run (#54) |
Run the automated test suite covering schedule parsing, the scheduler engine, atomic storage, HTTP helpers, notifications and tool contracts:
npm test
Run the local preflight gate to verify syntax, zero empty catch blocks, theme token compliance, package boundaries and leak protection:
node scripts/ci-preflight.mjs
MIT © GooDAnDReaDY
CLASSIFICATION EVIDENCE
系统优先读取 GitHub Topics,再与站内分类词典和词根规则比对。当前命中: automation、cron、scheduler。