@goodandready/dsh-cron 0.2.5 → 0.2.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +144 -6
- package/docs/README.ru.md +147 -5
- package/docs/README.zh.md +144 -6
- package/lib/api.js +533 -0
- package/lib/channels.js +6 -2
- package/lib/client.js +185 -12
- package/lib/config-jobs.js +206 -0
- package/lib/external-api.js +74 -0
- package/lib/failure-inspector.js +107 -0
- package/lib/http-utils.js +24 -0
- package/lib/index.js +176 -388
- package/lib/llm-ask.js +141 -0
- package/lib/metrics.js +97 -0
- package/lib/recipes.js +238 -0
- package/lib/runner.js +210 -171
- package/lib/scheduler.js +373 -192
- package/lib/silent-rule.js +100 -0
- package/lib/store.js +14 -3
- package/lib/task-patch.js +98 -0
- package/lib/task-transfer.js +4 -0
- package/lib/templates.js +4 -0
- package/package.json +3 -2
- package/docs/design/DESIGN.md +0 -81
- package/docs/plans/0.2.5-ui-block.md +0 -50
package/README.md
CHANGED
|
@@ -99,13 +99,15 @@ Autonomous agents can manage schedules directly:
|
|
|
99
99
|
|
|
100
100
|
| Tool | Description |
|
|
101
101
|
|:---|:---|
|
|
102
|
-
| `cron_create_task` | Creates a scheduled task: `title`, `schedule`, `prompt`, optional `type` (`llm`/`script`/`node`/`python`/`http`/`ssh`/`docker`/`skill`/`workflow`), `delivery`, `provider`, `model`, `channels`, `template`, `notifyTelegram`, `onlyOnFailure`, `timeoutSeconds`, `overlapPolicy`, `kanbanMode` |
|
|
102
|
+
| `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` |
|
|
103
103
|
| `cron_schedule_task` | Alias of `cron_create_task` kept for compatibility with existing agent prompts |
|
|
104
104
|
| `cron_list_tasks` | Lists tasks with statuses, next run timestamps, token totals, and cost estimates |
|
|
105
105
|
| `cron_pause_task` | Pauses a schedule without deleting its configuration |
|
|
106
106
|
| `cron_resume_task` | Resumes a paused schedule |
|
|
107
107
|
| `cron_delete_task` | Permanently removes a task and its history |
|
|
108
108
|
| `cron_run_task` | Triggers an immediate out-of-band run |
|
|
109
|
+
| `cron_get_task` | Reads the full configuration of one task, including fields the list does not show |
|
|
110
|
+
| `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 |
|
|
109
111
|
|
|
110
112
|
Example invocation the model can make during a conversation:
|
|
111
113
|
|
|
@@ -147,12 +149,21 @@ Every task picks its own runtime; non-LLM runtimes need no model and consume no
|
|
|
147
149
|
* **Environment variables** — a per-task `env` map (KEY VALUE per line in the UI) applied to external runtimes; secrets do not belong here.
|
|
148
150
|
* **Workspaces and worktrees** — bind a task to a harness workspace (`workspaceId`) and, for code-modifying agent tasks, run it in an isolated git worktree (`worktree`, `keepWorktree`).
|
|
149
151
|
|
|
150
|
-
### 7.
|
|
152
|
+
### 7. Cost Control: Fallback Model
|
|
153
|
+
A task can run on the cheap model by default and still finish on the strong one: set `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.
|
|
154
|
+
|
|
155
|
+
### 8. Session Integration & Permissions
|
|
151
156
|
* **Per-task permission presets** — `default`, `read-only`, `workspace-write`, or `full` are applied to the task's agent session before the prompt runs.
|
|
152
157
|
* **Session auto-archive** — isolated cron sessions are archived after each run (best-effort) so they do not clutter the chat list.
|
|
153
158
|
* **History → session navigation** — every LLM run records its session; open it straight from the run history entry.
|
|
154
159
|
|
|
155
|
-
###
|
|
160
|
+
### 9. Quiet by Rule
|
|
161
|
+
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.
|
|
162
|
+
|
|
163
|
+
### 10. Failure Diagnosis
|
|
164
|
+
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.
|
|
165
|
+
|
|
166
|
+
### 11. Notification Channels & Message Templates
|
|
156
167
|
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:
|
|
157
168
|
|
|
158
169
|
* **Per-task channels** — tick the channels in the task form; an explicit selection overrides the legacy `notifyTelegram` / `kanbanMode` switches, and an empty selection falls back to them.
|
|
@@ -168,11 +179,11 @@ A finished run is delivered to every channel configured for the task — Telegra
|
|
|
168
179
|
* **Gitea** — opens an issue with the run report (`giteaBaseUrl`, `giteaRepo`, token credential); failures are labelled `cron`, `bug`, `alert`.
|
|
169
180
|
* **Test dispatch button** — verify Telegram connectivity on the spot before scheduling critical jobs.
|
|
170
181
|
|
|
171
|
-
###
|
|
182
|
+
### 12. Kanban Integration & Cost Meter
|
|
172
183
|
* **Automatic Kanban cards** — with `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).
|
|
173
184
|
* **Token & execution cost meter** — token consumption (input, output, cache reads) is tracked per run and per task, with USD estimates from a built-in pricing table and an aggregated analytics bar.
|
|
174
185
|
|
|
175
|
-
###
|
|
186
|
+
### 13. Overlap Policies & Execution Timeout
|
|
176
187
|
Prevent rogue processes from stacking concurrent duplicate executions:
|
|
177
188
|
|
|
178
189
|
* **Execution timeout (`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).
|
|
@@ -183,10 +194,129 @@ Prevent rogue processes from stacking concurrent duplicate executions:
|
|
|
183
194
|
|
|
184
195
|
If the daemon was offline at a scheduled time, the run is recorded as `missed` on startup, so gaps in the history stay visible.
|
|
185
196
|
|
|
186
|
-
###
|
|
197
|
+
### 14. Heartbeat Monitoring (#16-style dead man's switch)
|
|
187
198
|
* Set `heartbeatUrl` and `heartbeatIntervalSec` in the plugin settings and the scheduler pings that URL on schedule — an external monitor alerts when the pings stop.
|
|
188
199
|
* A built-in `GET /dsh-cron/heartbeat` endpoint reports liveness, active task count and the last run time for your own watchdogs.
|
|
189
200
|
|
|
201
|
+
### 15. Declarative Jobs From the Profile Config (#50)
|
|
202
|
+
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.
|
|
203
|
+
|
|
204
|
+
Add a `jobs` list to the plugin section of your profile config (`cordis.patch.yml`):
|
|
205
|
+
|
|
206
|
+
```yaml
|
|
207
|
+
dsh-cron:
|
|
208
|
+
jobs:
|
|
209
|
+
- id: nightly-backup
|
|
210
|
+
title: Nightly backup
|
|
211
|
+
schedule: "0 3 * * *"
|
|
212
|
+
type: script
|
|
213
|
+
prompt: "bash /path/to/backup.sh"
|
|
214
|
+
channels: ["telegram"]
|
|
215
|
+
timeoutSeconds: 3600
|
|
216
|
+
- id: morning-digest
|
|
217
|
+
title: Morning digest
|
|
218
|
+
schedule: "0 8 * * 1-5"
|
|
219
|
+
type: llm
|
|
220
|
+
prompt: "Prepare a brief morning digest of active tasks."
|
|
221
|
+
provider: my-provider
|
|
222
|
+
model: provider-id/model-id
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
* Required per entry: `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`).
|
|
226
|
+
* Any other task field is passed through with the same validation as the API: `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`).
|
|
227
|
+
* Declared jobs are marked **managed by the config**; the panel shows a source label instead of edit and delete actions.
|
|
228
|
+
* Editing, pausing, resuming, toggling or deleting a config-owned task is refused with `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.
|
|
229
|
+
* A task with the same `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.
|
|
230
|
+
* Code-executing types are activated like any other declared job, but at startup the plugin writes a warning to the log, so a code path introduced through the config file is visible.
|
|
231
|
+
* Entries are validated one by one with an indexed message (`config.jobs[i]: …`); a broken entry is skipped and cannot stop the remaining jobs or the profile.
|
|
232
|
+
|
|
233
|
+
### 16. External REST API (`/dsh-cron/api/*`, #54)
|
|
234
|
+
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.
|
|
235
|
+
|
|
236
|
+
Set the token as the plugin setting `apiToken` (masked like every secret). Auth and errors:
|
|
237
|
+
* no token configured → the whole surface answers `503`;
|
|
238
|
+
* a missing or wrong `Authorization: Bearer <token>` → `401`, compared in constant time.
|
|
239
|
+
|
|
240
|
+
| Method | Path | Description |
|
|
241
|
+
|:---|:---|:---|
|
|
242
|
+
| `GET` | `/dsh-cron/api/tasks` | List tasks (`status` / `query` filters as the panel) |
|
|
243
|
+
| `GET` | `/dsh-cron/api/tasks/:id` | Read one task |
|
|
244
|
+
| `POST` | `/dsh-cron/api/tasks` | Create a task, or update the existing one when `id` is present |
|
|
245
|
+
| `DELETE` | `/dsh-cron/api/tasks/:id` | Delete a task |
|
|
246
|
+
| `POST` | `/dsh-cron/api/tasks/:id/run` | Force an immediate run |
|
|
247
|
+
|
|
248
|
+
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.
|
|
249
|
+
|
|
250
|
+
```bash
|
|
251
|
+
BASE="http://127.0.0.1:3080"
|
|
252
|
+
TOKEN="<API_TOKEN>"
|
|
253
|
+
|
|
254
|
+
# list
|
|
255
|
+
curl -s -H "Authorization: Bearer $TOKEN" "$BASE/dsh-cron/api/tasks"
|
|
256
|
+
|
|
257
|
+
# create, or update when the body carries the id
|
|
258
|
+
curl -s -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
|
|
259
|
+
-d '{"id":"cleanup","title":"Cleanup","schedule":"0 4 * * *","prompt":"Remove stale temporary files."}' \
|
|
260
|
+
"$BASE/dsh-cron/api/tasks"
|
|
261
|
+
|
|
262
|
+
# force a run
|
|
263
|
+
curl -s -X POST -H "Authorization: Bearer $TOKEN" "$BASE/dsh-cron/api/tasks/cleanup/run"
|
|
264
|
+
|
|
265
|
+
# delete
|
|
266
|
+
curl -s -X DELETE -H "Authorization: Bearer $TOKEN" "$BASE/dsh-cron/api/tasks/cleanup"
|
|
267
|
+
|
|
268
|
+
# a code-executing task also needs the confirmation header
|
|
269
|
+
curl -s -X POST -H "Authorization: Bearer $TOKEN" -H "x-dsh-cron-confirm: script" \
|
|
270
|
+
-H "Content-Type: application/json" \
|
|
271
|
+
-d '{"title":"Disk check","schedule":"0 * * * *","type":"script","prompt":"df -h"}' \
|
|
272
|
+
"$BASE/dsh-cron/api/tasks"
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
### 17. Prometheus Metrics (#53)
|
|
276
|
+
`GET /dsh-cron/metrics` returns Prometheus text exposition, so the scheduler can be scraped without any extra dependency:
|
|
277
|
+
|
|
278
|
+
* `dsh_cron_tasks_total{status}` — tasks by status (gauge).
|
|
279
|
+
* `dsh_cron_task_last_duration_seconds{task}` — duration of a task's last finished run, in seconds (gauge).
|
|
280
|
+
* `dsh_cron_runs_total{status}` — finished runs since the plugin process started (counter); the statuses are `success`, `error`, `timeout`, `skipped` and `missed`.
|
|
281
|
+
* `dsh_cron_run_records` — run records currently kept in memory (gauge).
|
|
282
|
+
|
|
283
|
+
Only counts, statuses and durations are exported; prompts, run output and task configuration never appear in the exposition.
|
|
284
|
+
|
|
285
|
+
```yaml
|
|
286
|
+
scrape_configs:
|
|
287
|
+
- job_name: dsh-cron
|
|
288
|
+
static_configs:
|
|
289
|
+
- targets: ["127.0.0.1:3080"]
|
|
290
|
+
metrics_path: /dsh-cron/metrics
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
### 18. Strict Channel Validation (#121)
|
|
294
|
+
Creating or updating a task with an unknown delivery-channel id is now rejected with `400`, and the offending ids are listed:
|
|
295
|
+
|
|
296
|
+
```json
|
|
297
|
+
{ "ok": false, "error": "Unknown channel ids: email_ping", "unknownChannels": ["email_ping"] }
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
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.
|
|
301
|
+
|
|
302
|
+
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.
|
|
303
|
+
|
|
304
|
+
### 19. Post-Install Verification (#126)
|
|
305
|
+
`deploy.sh` has a verify-only mode that inspects an already installed profile without installing anything:
|
|
306
|
+
|
|
307
|
+
```bash
|
|
308
|
+
bash deploy.sh verify [exact-version]
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
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.
|
|
312
|
+
|
|
313
|
+
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.
|
|
314
|
+
|
|
315
|
+
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.
|
|
316
|
+
|
|
317
|
+
### 20. Internal Refactor: Schedule Parsing and Arming (#97)
|
|
318
|
+
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.
|
|
319
|
+
|
|
190
320
|
---
|
|
191
321
|
|
|
192
322
|
## 📦 Installation
|
|
@@ -235,6 +365,8 @@ dsh-cron:
|
|
|
235
365
|
giteaBaseUrl: "" # giteaRepo = owner/repo, giteaTokenRef = credential NAME
|
|
236
366
|
giteaRepo: ""
|
|
237
367
|
giteaTokenRef: ""
|
|
368
|
+
# --- external REST API (#54) ---
|
|
369
|
+
apiToken: "" # bearer token for the external /dsh-cron/api/* surface (masked; empty = 503)
|
|
238
370
|
```
|
|
239
371
|
|
|
240
372
|
### Configuration Parameters
|
|
@@ -260,6 +392,7 @@ dsh-cron:
|
|
|
260
392
|
| `pushplusUrl` / `pushplusTokenRef` | `string` | `"https://www.pushplus.plus/send"` / `""` | PushPlus endpoint (override for a self-hosted proxy) and token credential name |
|
|
261
393
|
| `ttsBaseUrl` | `string` | `"http://127.0.0.1:3080"` | Base URL of the `dsh-tts` plugin used for voice announcements |
|
|
262
394
|
| `giteaBaseUrl` / `giteaRepo` / `giteaTokenRef` | `string` | `""` | Gitea channel: base URL, `owner/repo`, and the credential name of the API token |
|
|
395
|
+
| `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 |
|
|
263
396
|
|
|
264
397
|
Notes:
|
|
265
398
|
|
|
@@ -283,6 +416,7 @@ All endpoints are served by the DSH web server under `/dsh-cron/`. Read endpoint
|
|
|
283
416
|
| `POST` | `/dsh-cron/tasks/:id/resume` | Resume the schedule |
|
|
284
417
|
| `POST` | `/dsh-cron/tasks/:id/toggle` | Toggle active/paused |
|
|
285
418
|
| `POST` | `/dsh-cron/tasks/:id/duplicate` | Creates a paused copy of a task: configuration copied, run state (history, counters, last run) reset |
|
|
419
|
+
| `GET` | `/dsh-cron/recipes` | Built-in recipe catalog: ready-to-use monitoring presets grouped by category, all read-only |
|
|
286
420
|
| `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 |
|
|
287
421
|
| `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 |
|
|
288
422
|
| `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`) |
|
|
@@ -295,6 +429,10 @@ All endpoints are served by the DSH web server under `/dsh-cron/`. Read endpoint
|
|
|
295
429
|
| `POST` | `/dsh-cron/telegram/test` | Send a Telegram test message |
|
|
296
430
|
| `POST` | `/dsh-cron/kanban/test` | Create a Kanban connectivity-test card |
|
|
297
431
|
| `*` | `/dsh-cron/action/:id/:action` | Legacy alias for the task action routes (`run`, `toggle`, `delete`, `history`) |
|
|
432
|
+
| `GET` | `/dsh-cron/metrics` | Prometheus text exposition of task and run counters — never prompts or output (#53) |
|
|
433
|
+
| `GET` / `POST` | `/dsh-cron/api/tasks` | External token-guarded surface: list / create-or-update (#54) |
|
|
434
|
+
| `GET` / `DELETE` | `/dsh-cron/api/tasks/:id` | External token-guarded surface: read / delete (#54) |
|
|
435
|
+
| `POST` | `/dsh-cron/api/tasks/:id/run` | External token-guarded surface: force a run (#54) |
|
|
298
436
|
|
|
299
437
|
---
|
|
300
438
|
|
package/docs/README.ru.md
CHANGED
|
@@ -98,13 +98,15 @@ graph TD
|
|
|
98
98
|
|
|
99
99
|
| Инструмент | Описание |
|
|
100
100
|
|:---|:---|
|
|
101
|
-
| `cron_create_task` | Создаёт задачу: `title`, `schedule`, `prompt`, опционально `type` (`llm`/`script`/`node`/`python`/`http`/`ssh`/`docker`/`skill`/`workflow`), `delivery`, `provider`, `model`, `channels`, `template`, `notifyTelegram`, `onlyOnFailure`, `timeoutSeconds`, `overlapPolicy`, `kanbanMode` |
|
|
101
|
+
| `cron_create_task` | Создаёт задачу: `title`, `schedule`, `prompt`, `fallbackModel` (одна повторная попытка на сильной модели при сбое), опционально `type` (`llm`/`script`/`node`/`python`/`http`/`ssh`/`docker`/`skill`/`workflow`), `delivery`, `provider`, `model`, `channels`, `template`, `notifyTelegram`, `onlyOnFailure`, `timeoutSeconds`, `overlapPolicy`, `kanbanMode` |
|
|
102
102
|
| `cron_schedule_task` | Псевдоним `cron_create_task` для совместимости с существующими промптами |
|
|
103
103
|
| `cron_list_tasks` | Список задач со статусами, временем следующего запуска, токенами и стоимостью |
|
|
104
104
|
| `cron_pause_task` | Приостанавливает расписание без удаления конфигурации |
|
|
105
105
|
| `cron_resume_task` | Возобновляет приостановленное расписание |
|
|
106
106
|
| `cron_delete_task` | Полностью удаляет задачу и её историю |
|
|
107
107
|
| `cron_run_task` | Немедленный внеплановый запуск |
|
|
108
|
+
| `cron_get_task` | Полная конфигурация одной задачи, включая поля, которых нет в списке |
|
|
109
|
+
| `cron_update_task` | Изменяет существующую задачу на месте (whitelisted-поля, та же валидация, что у HTTP-маршрута); модели предписано сперва подтверждать с пользователем изменения, исполняющие код |
|
|
108
110
|
|
|
109
111
|
Пример вызова модели в диалоге:
|
|
110
112
|
|
|
@@ -146,12 +148,21 @@ cron_create_task({
|
|
|
146
148
|
* **Переменные окружения** — карта `env` на задачу (в UI — строки KEY VALUE) для внешних рантаймов; секретам здесь не место.
|
|
147
149
|
* **Workspace и worktree** — привязка задачи к workspace харнесса (`workspaceId`) и, для изменяющих код агентских задач, запуск в изолированном git worktree (`worktree`, `keepWorktree`).
|
|
148
150
|
|
|
149
|
-
### 7.
|
|
151
|
+
### 7. Экономия: fallback-модель
|
|
152
|
+
Задача может идти на дешёвой модели по умолчанию и всё же завершиться на сильной: задайте `fallbackModel` (и при необходимости `fallbackProvider`), и сбойный запуск (`error` или `timeout`) один раз повторится на этой модели, прежде чем включится обычный retry с задержкой. В истории видно, какая модель произвела результат и был ли использован fallback; расход и стоимость обеих попыток суммируются; переменная шаблона `{model}` подставляет модель, завершившую запуск. Fallback доступен только агентским типам (`llm`, `skill`, `workflow`).
|
|
153
|
+
|
|
154
|
+
### 8. Интеграция сессий и права
|
|
150
155
|
* **Permission-пресеты на задачу** — `default`, `read-only`, `workspace-write` или `full` применяются к сессии агента перед запуском промпта.
|
|
151
156
|
* **Автоархивация сессий** — изолированные cron-сессии архивируются после запуска (best-effort), не засоряя список чатов.
|
|
152
157
|
* **История → сессия** — каждый LLM-запуск хранит свою сессию; открыть диалог можно прямо из записи истории.
|
|
153
158
|
|
|
154
|
-
###
|
|
159
|
+
### 9. Тишина по правилу
|
|
160
|
+
У задачи с выводом может быть **правило тишины**, написанное словами («молчи, если ни один раздел не занят больше 80%»). На успешном запуске дешёвая модель сверяет вывод с правилом, и отчёт пропускается, если вердикт — молчать; причина сохраняется в истории запуска. Работает fail-open: нет правила, нет модели, сбой вызова или нечитаемый ответ — отчёт доставляется. Настройка `silentRuleModel` задаёт модель для проверки.
|
|
161
|
+
|
|
162
|
+
### 10. Диагностика сбоев
|
|
163
|
+
Агентские задачи могут заказывать диагноз: с включённым `inspectOnFailure` сбойный запуск (`error` или `timeout`) вместе с промптом задачи и обрезанным выводом читает модель, и в историю запуска попадают короткий диагноз и конкретная правка промпта. В записи истории есть кнопка, подставляющая эту правку в форму редактирования — автоматически ничего не применяется. Модель задаётся настройкой `inspectorModel`, в шаблонах доступна переменная `{diagnosis}`. Недоступная модель оставляет сбойный запуск ровно таким, каким он был.
|
|
164
|
+
|
|
165
|
+
### 11. Каналы доставки и шаблоны сообщений
|
|
155
166
|
Отчёт о завершённом запуске уходит во все каналы, выбранные для задачи — Telegram, dsh-kanban, Discord, Slack, ntfy, Bark, PushPlus, голос через `dsh-tts` и issue в Gitea:
|
|
156
167
|
|
|
157
168
|
* **Перенос задач** — экспорт всей конфигурации в версионированный JSON и импорт с предварительной сводкой; импортированные задачи приходят на паузе.
|
|
@@ -168,11 +179,11 @@ cron_create_task({
|
|
|
168
179
|
* **Gitea** — создаёт issue с отчётом (`giteaBaseUrl`, `giteaRepo`, credential токена); сбойные запуски помечаются метками `cron`, `bug`, `alert`.
|
|
169
180
|
* **Кнопка проверки** — проверьте доставку в Telegram до запуска критичных задач.
|
|
170
181
|
|
|
171
|
-
###
|
|
182
|
+
### 12. Интеграция с Kanban и учёт стоимости
|
|
172
183
|
* **Автоматические карточки Kanban** — при `kanbanMode` = `on_failure` или `always` плагин создаёт карточки в `dsh-kanban` (`on_failure` → *Backlog* при `error`/`timeout`; `always` → *Done*/*Backlog* по завершении).
|
|
173
184
|
* **Счётчик токенов и стоимости** — потребление токенов (ввод, вывод, чтения из кэша) учитывается по запускам и задачам с оценкой в USD по встроенной таблице цен и сводной панелью аналитики.
|
|
174
185
|
|
|
175
|
-
###
|
|
186
|
+
### 13. Политики наложения и таймаут выполнения
|
|
176
187
|
|
|
177
188
|
* **Таймаут (`timeoutSeconds`)** — по достижении лимита shell-процесс немедленно завершается через abort-сигнал, а агентская сессия закрывается, чтобы не расходовать токены. По умолчанию `1800` (30 минут).
|
|
178
189
|
* **Политика наложения (`overlapPolicy`)** — что делать, когда тик срабатывает при ещё активном предыдущем запуске:
|
|
@@ -228,8 +239,133 @@ dsh-cron:
|
|
|
228
239
|
giteaBaseUrl: "" # giteaRepo = owner/repo, giteaTokenRef = ИМЯ credential
|
|
229
240
|
giteaRepo: ""
|
|
230
241
|
giteaTokenRef: ""
|
|
242
|
+
# --- внешний REST API (#54) ---
|
|
243
|
+
apiToken: "" # bearer-токен внешнего префикса /dsh-cron/api/* (маскируется; пусто = 503)
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### 14. Мониторинг heartbeat (dead man's switch)
|
|
247
|
+
* Задайте `heartbeatUrl` и `heartbeatIntervalSec` в настройках плагина — планировщик будет пинговать этот адрес по расписанию, и внешний монитор сообщит, когда пинги прекратятся.
|
|
248
|
+
* Встроенный эндпоинт `GET /dsh-cron/heartbeat` сообщает живость, число активных задач и время последнего запуска для ваших собственных сторожей.
|
|
249
|
+
|
|
250
|
+
### 15. Задачи из конфига профиля (#50)
|
|
251
|
+
Долгоживущие эксплуатационные задачи можно объявлять в конфиге профиля, а не пересоздавать руками в интерфейсе. Владелец объявленных задач — файл конфига: при каждом старте плагина они создаются или обновляются, а задача, исчезнувшая из файла, удаляется.
|
|
252
|
+
|
|
253
|
+
Добавьте список `jobs` в секцию плагина конфига профиля (`cordis.patch.yml`):
|
|
254
|
+
|
|
255
|
+
```yaml
|
|
256
|
+
dsh-cron:
|
|
257
|
+
jobs:
|
|
258
|
+
- id: nightly-backup
|
|
259
|
+
title: Nightly backup
|
|
260
|
+
schedule: "0 3 * * *"
|
|
261
|
+
type: script
|
|
262
|
+
prompt: "bash /path/to/backup.sh"
|
|
263
|
+
channels: ["telegram"]
|
|
264
|
+
timeoutSeconds: 3600
|
|
265
|
+
- id: morning-digest
|
|
266
|
+
title: Morning digest
|
|
267
|
+
schedule: "0 8 * * 1-5"
|
|
268
|
+
type: llm
|
|
269
|
+
prompt: "Prepare a brief morning digest of active tasks."
|
|
270
|
+
provider: my-provider
|
|
271
|
+
model: provider-id/model-id
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
* Обязательные поля записи: `id`, `title`, `schedule`; типам, у которых полезная нагрузка — это промпт (`script`, `node`, `python`, `ssh`, `docker`, `llm`, `skill`, `workflow`), нужен ещё непустой `prompt`. `http` — исключение: цель задаётся `httpUrl` (или `prompt`).
|
|
275
|
+
* Остальные поля задачи проходят как есть с той же валидацией, что и в API: `channels`, `model`, `provider`, `fallbackModel`, `silentRule`, `inspectOnFailure`, `timezone`, `timeoutSeconds`, `template`, `env`, `cwd` и рантайм-поля (`nodePath`, `pythonPath`, `httpUrl`, `httpMethod`, `httpHeaders`, `httpBody`, `sshProfileId`, `sshTarget`, `dockerImage`, `workspaceId`, `worktree`, `keepWorktree`, `skillName`, `workflowName`).
|
|
276
|
+
* Объявленные задачи помечаются как **управляемые конфигом**; в панели вместо действий правки и удаления выводится метка источника.
|
|
277
|
+
* Правка, пауза, возобновление, переключение и удаление конфиг-задачи отклоняются с `409` в панели и по API, и создание-обновление через `POST /dsh-cron/tasks` с существующим `id` конфиг-задачи отклоняется так же — источник правды файл конфига. **Запустить сейчас** остаётся доступным.
|
|
278
|
+
* Задача с тем же `id`, созданная через UI, API или инструмент агента, никогда не перезаписывается: запись пропускается, конфликт пишется в лог.
|
|
279
|
+
* Код-исполняющие типы активируются как обычные объявленные задачи, но при старте плагин пишет предупреждение в лог — путь исполнения кода, добавленный правкой конфига, остаётся видимым.
|
|
280
|
+
* Записи валидируются по одной с указанием индекса (`config.jobs[i]: …`); одна плохая запись пропускается и не может остановить остальные задачи или профиль.
|
|
281
|
+
|
|
282
|
+
### 16. Внешний REST API (`/dsh-cron/api/*`, #54)
|
|
283
|
+
Внешние системы (CI, cron хоста, `curl`) могут управлять планировщиком без открытия панели. Это единственная поверхность за bearer-токеном; маршруты панели остаются локальными и защищёнными от cross-origin.
|
|
284
|
+
|
|
285
|
+
Токен задаётся настройкой плагина `apiToken` (маскируется, как любой секрет). Аутентификация и ошибки:
|
|
286
|
+
* токен не задан → вся поверхность отвечает `503`;
|
|
287
|
+
* нет заголовка `Authorization: Bearer <token>` или токен неверный → `401`; сравнение постоянное по времени.
|
|
288
|
+
|
|
289
|
+
| Метод | Путь | Описание |
|
|
290
|
+
|:---|:---|:---|
|
|
291
|
+
| `GET` | `/dsh-cron/api/tasks` | Список задач (фильтры `status` / `query`, как в панели) |
|
|
292
|
+
| `GET` | `/dsh-cron/api/tasks/:id` | Чтение одной задачи |
|
|
293
|
+
| `POST` | `/dsh-cron/api/tasks` | Создание задачи или обновление существующей при наличии `id` |
|
|
294
|
+
| `DELETE` | `/dsh-cron/api/tasks/:id` | Удаление задачи |
|
|
295
|
+
| `POST` | `/dsh-cron/api/tasks/:id/run` | Принудительный немедленный запуск |
|
|
296
|
+
|
|
297
|
+
Операции переиспользуют обработчики панели, поэтому гейт `x-dsh-cron-confirm: script` для код-исполняющих типов и отказ `409` для конфиг-задач действуют здесь так же, как в UI.
|
|
298
|
+
|
|
299
|
+
```bash
|
|
300
|
+
BASE="http://127.0.0.1:3080"
|
|
301
|
+
TOKEN="<API_TOKEN>"
|
|
302
|
+
|
|
303
|
+
# список
|
|
304
|
+
curl -s -H "Authorization: Bearer $TOKEN" "$BASE/dsh-cron/api/tasks"
|
|
305
|
+
|
|
306
|
+
# создание или обновление, если в теле есть id
|
|
307
|
+
curl -s -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
|
|
308
|
+
-d '{"id":"cleanup","title":"Cleanup","schedule":"0 4 * * *","prompt":"Remove stale temporary files."}' \
|
|
309
|
+
"$BASE/dsh-cron/api/tasks"
|
|
310
|
+
|
|
311
|
+
# принудительный запуск
|
|
312
|
+
curl -s -X POST -H "Authorization: Bearer $TOKEN" "$BASE/dsh-cron/api/tasks/cleanup/run"
|
|
313
|
+
|
|
314
|
+
# удаление
|
|
315
|
+
curl -s -X DELETE -H "Authorization: Bearer $TOKEN" "$BASE/dsh-cron/api/tasks/cleanup"
|
|
316
|
+
|
|
317
|
+
# код-исполняющей задаче нужен ещё заголовок подтверждения
|
|
318
|
+
curl -s -X POST -H "Authorization: Bearer $TOKEN" -H "x-dsh-cron-confirm: script" \
|
|
319
|
+
-H "Content-Type: application/json" \
|
|
320
|
+
-d '{"title":"Disk check","schedule":"0 * * * *","type":"script","prompt":"df -h"}' \
|
|
321
|
+
"$BASE/dsh-cron/api/tasks"
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
### 17. Метрики Prometheus (#53)
|
|
325
|
+
`GET /dsh-cron/metrics` отдаёт текст в формате Prometheus, поэтому планировщик можно снимать scrape'ом без новых зависимостей:
|
|
326
|
+
|
|
327
|
+
* `dsh_cron_tasks_total{status}` — число задач по статусам (gauge).
|
|
328
|
+
* `dsh_cron_task_last_duration_seconds{task}` — длительность последнего завершённого запуска задачи в секундах (gauge).
|
|
329
|
+
* `dsh_cron_runs_total{status}` — завершённые запуски с момента старта процесса плагина (counter); статусы `success`, `error`, `timeout`, `skipped`, `missed`.
|
|
330
|
+
* `dsh_cron_run_records` — число записей о запусках, хранимых в памяти (gauge).
|
|
331
|
+
|
|
332
|
+
В экспозицию попадают только счётчики, статусы и длительности; промпты, вывод запусков и конфигурация задач в неё не входят.
|
|
333
|
+
|
|
334
|
+
```yaml
|
|
335
|
+
scrape_configs:
|
|
336
|
+
- job_name: dsh-cron
|
|
337
|
+
static_configs:
|
|
338
|
+
- targets: ["127.0.0.1:3080"]
|
|
339
|
+
metrics_path: /dsh-cron/metrics
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
### 18. Строгая проверка каналов (#121)
|
|
343
|
+
Создание или обновление задачи с неизвестным идентификатором канала теперь отклоняется с `400`, а виновники перечисляются в ответе:
|
|
344
|
+
|
|
345
|
+
```json
|
|
346
|
+
{ "ok": false, "error": "Unknown channel ids: email_ping", "unknownChannels": ["email_ping"] }
|
|
231
347
|
```
|
|
232
348
|
|
|
349
|
+
Changed in v0.2.7: раньше неизвестный идентификатор молча отбрасывался, поэтому клиент с опечаткой получал `ok: true` и задачу, которая никуда не доставляет.
|
|
350
|
+
|
|
351
|
+
Импорт намеренно остаётся терпимым (файл может быть из старой версии): неизвестные идентификаторы отбрасываются у импортируемой задачи, но перечисляются в ответе (`unknownChannels`) и пишутся в лог планировщика, а не исчезают молча.
|
|
352
|
+
|
|
353
|
+
### 19. Проверка после установки (#126)
|
|
354
|
+
У `deploy.sh` есть режим только-проверки уже установленного профиля, ничего не устанавливающий:
|
|
355
|
+
|
|
356
|
+
```bash
|
|
357
|
+
bash deploy.sh verify [exact-version]
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
Он проверяет, что профиль сообщает нужную версию (по умолчанию — версия из `package.json`), аутентифицируется в web UI, затем скачивает клиентский бандл и убеждается, что имя пакета в нём присутствует.
|
|
361
|
+
|
|
362
|
+
Зачем это нужно: web-профиль может стоять за плагином аутентификации и отвечать `401` на анонимный запрос, а клиентский бандл плагина отдаётся только по точному combined-URL вида `??` из аутентифицированного индекса — голый `/plugins/<name>/client.js` отвечает `404`. Поэтому проверка сначала строит аутентифицированную сессию.
|
|
363
|
+
|
|
364
|
+
Переменные окружения проверки: `DSH_WEB_BASE` (по умолчанию `http://127.0.0.1:3080`), `DSH_WEB_TOKEN` (токен; если не задан, скрипт берёт последний из журнала юнита), `DSH_WEB_UNIT` (по умолчанию `dsh-web.service`). Секретов в скрипте нет.
|
|
365
|
+
|
|
366
|
+
### 20. Внутренняя разбивка: разбор расписания и постановка (#97)
|
|
367
|
+
Только для разработчиков, поведение не меняется. `parseScheduleExpression` разбит на маленькие функции с тем же порядком ветвей — `parseAtExpression`, `parseRelativeOneShot`, `parseIntervalExpression`, `parseAliasExpression`, `parseCronExpression`, — а `scheduleTask` — на `clearScheduled`, `scheduleOneShot` и `scheduleCron`. Прежний набор тестов прошёл без правок, добавлены точечные тесты на приоритет ветвей и ошибки.
|
|
368
|
+
|
|
233
369
|
### Параметры
|
|
234
370
|
|
|
235
371
|
| Параметр | Тип | По умолчанию | Описание |
|
|
@@ -253,6 +389,7 @@ dsh-cron:
|
|
|
253
389
|
| `pushplusUrl` / `pushplusTokenRef` | `string` | `"https://www.pushplus.plus/send"` / `""` | Endpoint PushPlus (переопределяется для self-hosted прокси) и имя credential токена |
|
|
254
390
|
| `ttsBaseUrl` | `string` | `"http://127.0.0.1:3080"` | Базовый URL плагина `dsh-tts` для голосовых объявлений |
|
|
255
391
|
| `giteaBaseUrl` / `giteaRepo` / `giteaTokenRef` | `string` | `""` | Канал Gitea: базовый URL, `owner/repo` и имя credential API-токена |
|
|
392
|
+
| `apiToken` | `string` | `""` | Bearer-токен внешней поверхности `/dsh-cron/api/*`. Секретное поле, отдаётся замаскированным; пусто отключает поверхность (503), неверное значение — 401 |
|
|
256
393
|
|
|
257
394
|
Примечания:
|
|
258
395
|
|
|
@@ -276,6 +413,7 @@ dsh-cron:
|
|
|
276
413
|
| `POST` | `/dsh-cron/tasks/:id/resume` | Возобновление расписания |
|
|
277
414
|
| `POST` | `/dsh-cron/tasks/:id/toggle` | Переключение активна/на паузе |
|
|
278
415
|
| `POST` | `/dsh-cron/tasks/:id/duplicate` | Копия задачи в статусе «на паузе»: настройки копируются, история и счётчики сбрасываются |
|
|
416
|
+
| `GET` | `/dsh-cron/recipes` | Встроенный каталог рецептов: готовые мониторинговые пресеты по категориям, все только на чтение |
|
|
279
417
|
| `GET` | `/dsh-cron/tasks/export` | Версионированный JSON только с конфигурацией задач — без истории и счётчиков. Каналы ссылаются на credential по имени, но введённые вручную `env` и HTTP-заголовки задачи являются частью конфигурации и попадают в файл |
|
|
280
418
|
| `POST` | `/dsh-cron/tasks/import` | Проверяет документ и применяет его стратегией `add`, `replace` или `skip`; поддерживает `dryRun`. Импортированные задачи всегда приходят **на паузе** — восстановление не сработает само |
|
|
281
419
|
| `PATCH` | `/dsh-cron/tasks/:id` | Частичное обновление (только whitelisted-поля: `title`, `schedule`, `prompt`, `type`, `delivery`, `provider`, `model`, настройки уведомлений/таймаута/overlap/kanban, `status`, `oneShot`) |
|
|
@@ -288,6 +426,10 @@ dsh-cron:
|
|
|
288
426
|
| `POST` | `/dsh-cron/telegram/test` | Тестовое сообщение в Telegram |
|
|
289
427
|
| `POST` | `/dsh-cron/kanban/test` | Тестовая карточка в Kanban |
|
|
290
428
|
| `*` | `/dsh-cron/action/:id/:action` | Legacy-алиас действий над задачей (`run`, `toggle`, `delete`, `history`) |
|
|
429
|
+
| `GET` | `/dsh-cron/metrics` | Текст в формате Prometheus: счётчики задач и запусков — без промптов и вывода (#53) |
|
|
430
|
+
| `GET` / `POST` | `/dsh-cron/api/tasks` | Внешняя поверхность под токеном: список / создание-обновление (#54) |
|
|
431
|
+
| `GET` / `DELETE` | `/dsh-cron/api/tasks/:id` | Внешняя поверхность под токеном: чтение / удаление (#54) |
|
|
432
|
+
| `POST` | `/dsh-cron/api/tasks/:id/run` | Внешняя поверхность под токеном: принудительный запуск (#54) |
|
|
291
433
|
|
|
292
434
|
---
|
|
293
435
|
|