{"_id":"@aiwayds/dsh-cron","_rev":"4-a024b0721ad979690fa4f50e6ffa7401","name":"@aiwayds/dsh-cron","dist-tags":{"latest":"0.3.0"},"versions":{"0.1.0":{"name":"@aiwayds/dsh-cron","version":"0.1.0","keywords":["dsh","dsh-plugin","plugin","cron","scheduler","timer"],"license":"MIT","_id":"@aiwayds/dsh-cron@0.1.0","maintainers":[{"name":"aiwayds","email":"aiway.ds@qq.com"}],"homepage":"https://github.com/fan56/dsh-cron#readme","bugs":{"url":"https://github.com/fan56/dsh-cron/issues"},"dsh":{"bundle":{"patch":"./cordis.patch.yml"}},"dist":{"shasum":"c15391a05c0db1e0a2256afade2f7e3b4627931a","tarball":"https://registry.npmjs.org/@aiwayds/dsh-cron/-/dsh-cron-0.1.0.tgz","fileCount":32,"integrity":"sha512-34/ltqnEOf7Nu0BxgeXtOjtd2NB8mZfcpsdtZPVS96mnWEpgSE1vAfwut7LbcLP5iY4jAKAM5ztOxiiUafutDQ==","signatures":[{"sig":"MEYCIQCMRNyyfGPfXvNje8KIn3ghJfT7Khdysl1QtDncSybYlAIhAOdAyM5eIrhKcNuBzm6lZguFaej81rwoZnblagHdTik0","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@aiwayds%2fdsh-cron@0.1.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":132645},"main":"lib/index.js","type":"module","types":"./lib/index.d.ts","engines":{"node":">=22.19.0"},"gitHead":"9c4b5687f58dff188bd2f5f2b7d4196c9363051b","scripts":{"test":"node --test test/cron-expr.test.mjs test/window.test.mjs test/store.test.mjs test/scheduler.test.mjs test/tools.test.mjs","build":"tsc -p tsconfig.json","check":"tsc --noEmit -p tsconfig.json","smoke":"node scripts/smoke-boot.mjs","pretest":"tsc -p tsconfig.json"},"_npmUser":{"name":"aiwayds","email":"aiway.ds@qq.com"},"repository":{"url":"git+https://github.com/fan56/dsh-cron.git","type":"git"},"_npmVersion":"10.9.8","description":"dsh plugin: cron scheduling — bounded tasks with calendar & interval rules, delivered to live agents","directories":{},"_nodeVersion":"22.23.2","_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.8.0","@types/node":"^22","@deepseek-ai/cordis":"4.0.2","@deepseek-ai/dsh-llm":"0.1.2-alpha.4","@deepseek-ai/dsh-agent":"0.1.2-alpha.4","@deepseek-ai/dsh-scope":"0.1.2-alpha.4","@deepseek-ai/dsh-tools":"0.1.2-alpha.4","@deepseek-ai/dsh-session":"0.1.2-alpha.4","@deepseek-ai/dsh-timeout":"0.1.2-alpha.4","@deepseek-ai/schemastery":"3.18.2","@deepseek-ai/dsh-commands":"0.1.2-alpha.4","@deepseek-ai/dsh-settings":"0.1.2-alpha.4","@deepseek-ai/dsh-util-values":"0.1.2-alpha.4"},"peerDependencies":{"@deepseek-ai/cordis":"^4.0.2","@deepseek-ai/dsh-llm":">=0.1.2-alpha.4","@deepseek-ai/dsh-agent":">=0.1.2-alpha.4","@deepseek-ai/dsh-tools":">=0.1.2-alpha.4","@deepseek-ai/schemastery":"^3.18.2","@deepseek-ai/dsh-commands":">=0.1.2-alpha.4","@deepseek-ai/dsh-settings":">=0.1.2-alpha.4"},"_npmOperationalInternal":{"tmp":"tmp/dsh-cron_0.1.0_1788367141594_0.6519990743599009","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@aiwayds/dsh-cron","version":"0.2.0","keywords":["dsh","dsh-plugin","plugin","cron","scheduler","timer"],"license":"MIT","_id":"@aiwayds/dsh-cron@0.2.0","maintainers":[{"name":"aiwayds","email":"aiway.ds@qq.com"}],"homepage":"https://github.com/fan56/dsh-cron#readme","bugs":{"url":"https://github.com/fan56/dsh-cron/issues"},"dsh":{"bundle":{"patch":"./cordis.patch.yml"}},"dist":{"shasum":"5e3cce46cf9dcb023fccf9176c463eeb5e3d2b9f","tarball":"https://registry.npmjs.org/@aiwayds/dsh-cron/-/dsh-cron-0.2.0.tgz","fileCount":32,"integrity":"sha512-LcXXnxiX8/jj0ss4fIXyKrv3pH9xuy+7M2r6mwQAKhMFhW0zl49YE35rFFjPPStwf8G/TV1ZT8e4o3uqNEQQhQ==","signatures":[{"sig":"MEUCIQDFlABjfGh6S0IZuExwBlNFvdGbRKIGOzymJpKx/qkVYQIgQyQU4JeEZ6ZfLylDx1l3OofeDWiYKlrWLkzYB4f96E0=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@aiwayds%2fdsh-cron@0.2.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":132812},"main":"lib/index.js","type":"module","types":"./lib/index.d.ts","engines":{"node":">=22.19.0"},"gitHead":"75c9a131b97b871667e0d3ab793c18018093751a","scripts":{"test":"node --test test/cron-expr.test.mjs test/window.test.mjs test/store.test.mjs test/scheduler.test.mjs test/tools.test.mjs","build":"tsc -p tsconfig.json","check":"tsc --noEmit -p tsconfig.json","smoke":"node scripts/smoke-boot.mjs","pretest":"tsc -p tsconfig.json"},"_npmUser":{"name":"aiwayds","email":"aiway.ds@qq.com"},"repository":{"url":"git+https://github.com/fan56/dsh-cron.git","type":"git"},"_npmVersion":"10.9.8","description":"dsh plugin: cron scheduling — bounded tasks with calendar & interval rules, delivered to live agents","directories":{},"_nodeVersion":"22.23.2","_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.8.0","@types/node":"^22","@deepseek-ai/cordis":"4.0.2","@deepseek-ai/dsh-llm":"0.1.2-rc.1","@deepseek-ai/dsh-agent":"0.1.2-rc.1","@deepseek-ai/dsh-scope":"0.1.2-rc.1","@deepseek-ai/dsh-tools":"0.1.2-rc.1","@deepseek-ai/dsh-session":"0.1.2-rc.1","@deepseek-ai/dsh-timeout":"0.1.2-rc.1","@deepseek-ai/schemastery":"3.18.2","@deepseek-ai/dsh-commands":"0.1.2-rc.1","@deepseek-ai/dsh-settings":"0.1.2-rc.1","@deepseek-ai/dsh-util-values":"0.1.2-rc.1"},"peerDependencies":{"@deepseek-ai/cordis":"^4.0.2","@deepseek-ai/dsh-llm":">=0.1.2-rc.1","@deepseek-ai/dsh-agent":">=0.1.2-rc.1","@deepseek-ai/dsh-tools":">=0.1.2-rc.1","@deepseek-ai/schemastery":"^3.18.2","@deepseek-ai/dsh-commands":">=0.1.2-rc.1","@deepseek-ai/dsh-settings":">=0.1.2-rc.1"},"_npmOperationalInternal":{"tmp":"tmp/dsh-cron_0.2.0_1788442318529_0.7886005152692226","host":"s3://npm-registry-packages-npm-production"}},"0.2.1":{"name":"@aiwayds/dsh-cron","version":"0.2.1","keywords":["dsh","dsh-plugin","plugin","cron","scheduler","timer"],"license":"MIT","_id":"@aiwayds/dsh-cron@0.2.1","maintainers":[{"name":"aiwayds","email":"aiway.ds@qq.com"}],"homepage":"https://github.com/fan56/dsh-cron#readme","bugs":{"url":"https://github.com/fan56/dsh-cron/issues"},"dsh":{"bundle":{"patch":"./cordis.patch.yml"}},"dist":{"shasum":"0a7c785d45778a74f794956d338225f05fe54e9a","tarball":"https://registry.npmjs.org/@aiwayds/dsh-cron/-/dsh-cron-0.2.1.tgz","fileCount":32,"integrity":"sha512-0+hTFUTSL16ULpKPpPm8avOR2kxmut9nZhI4UdTDByjUGY+eLGvYhY3XKKkSKfE3Tko/htIV3AVAPGLrGBPRYA==","signatures":[{"sig":"MEUCICVxBB3s47Aeg+As09iT90Q66qcv692sxROU6jfMrmv8AiEAg9VrqklxkB/VcuwIdudTMs+KjrCctieTuWstJ1jPQhU=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@aiwayds%2fdsh-cron@0.2.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":133922},"main":"lib/index.js","type":"module","types":"./lib/index.d.ts","engines":{"node":">=22.19.0"},"gitHead":"a6a5a422d61599b2cd7f183a1cce55f15ace3f82","scripts":{"test":"node --test test/cron-expr.test.mjs test/window.test.mjs test/store.test.mjs test/scheduler.test.mjs test/tools.test.mjs","build":"tsc -p tsconfig.json","check":"tsc --noEmit -p tsconfig.json","smoke":"node scripts/smoke-boot.mjs","pretest":"tsc -p tsconfig.json"},"_npmUser":{"name":"aiwayds","email":"aiway.ds@qq.com"},"repository":{"url":"git+https://github.com/fan56/dsh-cron.git","type":"git"},"_npmVersion":"10.9.8","description":"dsh plugin: cron scheduling — bounded tasks with calendar & interval rules, delivered to live agents","directories":{},"_nodeVersion":"22.23.2","_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.8.0","@types/node":"^22","@deepseek-ai/cordis":"4.0.2","@deepseek-ai/dsh-llm":"0.1.2-rc.1","@deepseek-ai/dsh-agent":"0.1.2-rc.1","@deepseek-ai/dsh-scope":"0.1.2-rc.1","@deepseek-ai/dsh-tools":"0.1.2-rc.1","@deepseek-ai/dsh-session":"0.1.2-rc.1","@deepseek-ai/dsh-timeout":"0.1.2-rc.1","@deepseek-ai/schemastery":"3.18.2","@deepseek-ai/dsh-commands":"0.1.2-rc.1","@deepseek-ai/dsh-settings":"0.1.2-rc.1","@deepseek-ai/dsh-util-values":"0.1.2-rc.1"},"peerDependencies":{"@deepseek-ai/cordis":"^4.0.2","@deepseek-ai/dsh-llm":">=0.1.2-rc.1","@deepseek-ai/dsh-agent":">=0.1.2-rc.1","@deepseek-ai/dsh-tools":">=0.1.2-rc.1","@deepseek-ai/schemastery":"^3.18.2","@deepseek-ai/dsh-commands":">=0.1.2-rc.1","@deepseek-ai/dsh-settings":">=0.1.2-rc.1"},"_npmOperationalInternal":{"tmp":"tmp/dsh-cron_0.2.1_1788603339276_0.4884118995031832","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"_id":"@aiwayds/dsh-cron@0.3.0","dsh":{"bundle":{"patch":"./cordis.patch.yml"}},"bugs":{"url":"https://github.com/fan56/dsh-cron/issues"},"dist":{"shasum":"22e1177bd73aa186d53e4f32c8fa58a018b112f2","tarball":"https://registry.npmjs.org/@aiwayds/dsh-cron/-/dsh-cron-0.3.0.tgz","fileCount":32,"integrity":"sha512-QlZ0ni9JBlnE4x1unjx/D/pa9+jV6kkxUvy0vGlZu9e8Kn/7du7fNX0n3xsDxxhywpDYUkTvVo1i3sZn6Pp6hw==","signatures":[{"sig":"MEQCIArEkA58eij70mEqj4X5Dt1E3bAB1L/O6yBavBfRLJjPAiB4LSCX6vzXjH03M+AKJ69Xv5CqWFTui6siHNIQJ0oy0A==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIHTZOv/J3bni0M4/d+JJX7D2lCZ7QR955MDbXY1o6MmpAiEAwa2Cn+gnygMtQefKbSx/XnmvD/URh6yQaNdj1F9tcA4="}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@aiwayds%2fdsh-cron@0.3.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":133922},"main":"lib/index.js","name":"@aiwayds/dsh-cron","type":"module","types":"./lib/index.d.ts","engines":{"node":">=22.19.0"},"gitHead":"e6b8f5ba75be36655534a49bbb44df059e44fbc4","license":"MIT","scripts":{"test":"node --test test/cron-expr.test.mjs test/window.test.mjs test/store.test.mjs test/scheduler.test.mjs test/tools.test.mjs","build":"tsc -p tsconfig.json","check":"tsc --noEmit -p tsconfig.json","smoke":"node scripts/smoke-boot.mjs","pretest":"tsc -p tsconfig.json"},"version":"0.3.0","_npmUser":{"name":"aiwayds","email":"aiway.ds@qq.com"},"homepage":"https://github.com/fan56/dsh-cron#readme","keywords":["dsh","dsh-plugin","plugin","cron","scheduler","timer"],"repository":{"url":"git+https://github.com/fan56/dsh-cron.git","type":"git"},"_npmVersion":"10.9.8","description":"dsh plugin: cron scheduling — bounded tasks with calendar & interval rules, delivered to live agents","directories":{},"maintainers":[{"name":"aiwayds","email":"aiway.ds@qq.com"}],"_nodeVersion":"22.23.2","_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.8.0","@types/node":"^22","@deepseek-ai/cordis":"4.0.2","@deepseek-ai/dsh-llm":"0.1.5-rc.2","@deepseek-ai/dsh-agent":"0.1.5-rc.2","@deepseek-ai/dsh-scope":"0.1.5-rc.2","@deepseek-ai/dsh-tools":"0.1.5-rc.2","@deepseek-ai/dsh-session":"0.1.5-rc.2","@deepseek-ai/dsh-timeout":"0.1.5-rc.2","@deepseek-ai/schemastery":"3.18.2","@deepseek-ai/dsh-commands":"0.1.5-rc.2","@deepseek-ai/dsh-settings":"0.1.5-rc.2","@deepseek-ai/dsh-util-values":"0.1.5-rc.2"},"peerDependencies":{"@deepseek-ai/cordis":"^4.0.2","@deepseek-ai/dsh-llm":">=0.1.5-rc.2","@deepseek-ai/dsh-agent":">=0.1.5-rc.2","@deepseek-ai/dsh-tools":">=0.1.5-rc.2","@deepseek-ai/schemastery":"^3.18.2","@deepseek-ai/dsh-commands":">=0.1.5-rc.2","@deepseek-ai/dsh-settings":">=0.1.5-rc.2"},"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/dsh-cron_0.3.0_1789133947524_0.3789494010961636"}}},"time":{"created":"2026-09-02T16:39:01.250Z","modified":"2026-09-11T13:39:07.972Z","0.1.0":"2026-09-02T16:39:01.725Z","0.2.0":"2026-09-03T13:31:58.658Z","0.2.1":"2026-09-05T10:15:39.420Z","0.3.0":"2026-09-11T13:39:07.600Z"},"bugs":{"url":"https://github.com/fan56/dsh-cron/issues"},"license":"MIT","homepage":"https://github.com/fan56/dsh-cron#readme","keywords":["dsh","dsh-plugin","plugin","cron","scheduler","timer"],"repository":{"url":"git+https://github.com/fan56/dsh-cron.git","type":"git"},"description":"dsh plugin: cron scheduling — bounded tasks with calendar & interval rules, delivered to live agents","maintainers":[{"name":"aiwayds","email":"aiway.ds@qq.com"}],"readme":"# @aiwayds/dsh-cron\n\nCron scheduling for the [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) as an independent plugin: schedule prompts on standard cron expressions or fixed intervals and have them delivered to live agents — across TUI, web, and feishu surfaces.\n\n**Requires dsh >= 0.1.5-rc.2** — this plugin targets the dsh RC/stable line only (CI and releases resolve the newest of the `latest`/`next` dist-tags at runtime). **The alpha line is no longer supported.**\n\nDesign docs: [CONTEXT.md](./CONTEXT.md) (glossary) and [docs/adr/](./docs/adr) (decisions).\n\n## Why not dsh-schedule?\n\n`@deepseek-ai/dsh-schedule` provides session-scoped reminders (`after` / `at` / `every_seconds ≥ 5min`) that only fire while the original session stays live, and its protocol explicitly excludes cron expressions. dsh-cron is the complement: **profile-anchored recurring tasks** — a fire lands in whichever live root agent exists, regardless of which surface the session belongs to.\n\n## Core properties\n\n- **No infinite cron** (ADR 0006): every recurring task carries a mandatory validity window (`max_duration_seconds` or `end_at`), capped at one year. On expiry the task delivers one terminal `expired` fire and archives itself. One-shot tasks self-archive after their single fire.\n- **Two rule shapes** (ADR 0007): `cron` (5-field calendar expression, host-local time) or `every_seconds` (interval ≥ 60s, anchored at `start_at`, first fire one full interval in).\n- **The plugin owns the clock** (ADR 0007): the model only expresses intent (`every_seconds: 600`, `end_at: \"2026-12-31T00:00:00Z\"`); all time math happens in the plugin and tool results echo absolute ISO times (`now`, `next_fire`). The model never needs to know the current time.\n- **Per-task delivery policy** (ADR 0003): `followup` (default) queues a new turn via the agent's idle phase — a busy target waits and coalesces overdue occurrences into one fire annotated with `coalesced_count`. `steer` submits through `agent.steer()` and lands at the nearest step boundary, mid-turn — the watchdog option.\n- **Two execution modes** (ADR 0005): `sub-agent` (default) — the framing instructs the model to spawn an isolated background worker, then call `cron_report` to backfill status/summary into the fire record; `self` — handled in the target conversation itself, best-effort reporting.\n- **Missed occurrences are skipped, never delivered late** (ADR 0002): if the profile was down or no agent was live when an occurrence came due, it is logged and settled. A one-shot missed in downtime archives as `missed`. Cron means \"execute at time X\"; dsh-cron does not resurrect stale work.\n- **Full traceability** (Q3): every delivered fire produces a durable `FireRecord` (due time, policy, mode, coalesced count, status, summary). Records ride with the task, capped at 7 per task (configurable); ended tasks move to `_history.json` (cap 50).\n- **Tools for every agent** (ADR 0006): `cron_create` / `cron_list` / `cron_delete` / `cron_report` are registered on all runtime agents — root or sub-agent — and every task records `createdBy`.\n\n## Agent usage (primary)\n\nThe LLM calls the tools directly. Humans just talk:\n\n- \"每 10 分钟检查一次 CI run 1234，最多查两小时，成功或失败就停\" →\n  `cron_create({ every_seconds: 600, prompt: \"检查 CI run 1234，终态则 cron_delete 本任务并汇报\", max_duration_seconds: 7200 })`\n- \"工作日每天早上 9 点生成周报，到年底为止\" →\n  `cron_create({ cron: \"0 9 * * 1-5\", prompt: \"生成本周周报\", end_at: \"2026-12-31T23:59:59+08:00\" })`\n- \"每小时看一眼磁盘，如果超过 90% 立刻插进来提醒我\" →\n  `cron_create({ cron: \"0 * * * *\", prompt: \"检查磁盘使用率\", delivery_policy: \"steer\", execution_mode: \"self\", max_duration_seconds: 86400 })`\n\n### Tool reference\n\n| Tool | Purpose |\n| ---- | ------- |\n| `cron_create` | Create a task. Exactly one rule (`cron` \\| `every_seconds`); recurring tasks require exactly one window bound (`max_duration_seconds` \\| `end_at`). |\n| `cron_list` | Active tasks with next-fire times and retained fire records. |\n| `cron_delete` | Delete and archive (status `cancelled`). |\n| `cron_report` | Backfill a fire's outcome (`completed`/`failed` + summary); one-shots archive on report. |\n\n### Cron syntax\n\nStandard 5 fields: `minute hour day-of-month month day-of-week`, with lists (`1,3,5`), ranges (`1-5`), steps (`*/2`), and month/day names (`jan`, `mon`). Evaluated in the **host's local time zone** (a per-task IANA `time_zone` is a planned fast-follow — see ADR 0009).\n\n## Human usage\n\n```\n/cron list\n/cron create \"*/10 * * * *\" \"检查 CI run 1234，终态则删除本任务\" --for=7200\n/cron create \"0 9 * * 1-5\" \"生成周报\" --until=2026-12-31T23:59:59+08:00\n/cron delete a1b2c3d4\n/cron fires a1b2c3d4\n/cron history 20\n```\n\n## Persistence\n\nProfile-level, not per-session (ADR 0001):\n\n```\n$DSH_HOME/storages/cron/<8-hex-id>.json   # active tasks with their fire records\n$DSH_HOME/storages/cron/_history.json     # capped archive of ended tasks\n```\n\nAtomic writes (tmp + fsync + rename); corrupt files are skipped, never fatal.\n\n## Installation\n\n```bash\ndsh plugin --profile tui add @aiwayds/dsh-cron\n```\n\nThen add the package to the profile's `dsh.profile.bundles` (after `@deepseek-ai/dsh-base`) and restart the profile.\n\n## Uninstall\n\n```bash\ndsh plugin --profile tui remove @aiwayds/dsh-cron\n```\n\nThe host reconciles the profile automatically: the `dsh.profile.bundles` entry is spliced and the plugin's patch layer drops — schedules silently stop firing.\n\nWhat stays on disk, kept on purpose (deleting data is destructive, and a reinstall rehydrates it):\n\n- `<dsh home>/storages/cron/<id>.json` — active tasks with their fire records, including the schedules' validity windows.\n- `<dsh home>/storages/cron/_history.json` — the capped archive of ended tasks.\n\nReinstall semantics follow [ADR 0002](./docs/adr/0002-skip-missed-occurrences.md): occurrences that came due while the plugin was away are **skipped and logged, never delivered late** — a one-shot missed in downtime is archived as `missed`. Cron means \"execute at time X\"; the plugin does not resurrect stale work after a gap.\n\nThe `cron` skill copied to `$DSH_HOME/skills/cron` (see [Skill](#skill-model-guidance)) is a plain file copy and survives removal — delete it by hand if unwanted. To purge the task data too:\n\n```bash\nrm -r ~/.dsh/storages/cron\n```\n\n### Skill (model guidance)\n\nThe npm tarball ships a `cron` skill (`skill/cron/SKILL.md`) that teaches the model when and how to use the tools (rule selection, window thinking, the deploy-monitor and watchdog patterns). Install it by copying to the dsh skill root:\n\n```bash\nmkdir -p $DSH_HOME/skills && cp -r <package>/skill/cron $DSH_HOME/skills/\n```\n\n### Settings (`cron` namespace in settings.yaml)\n\n| Key | Default | Meaning |\n| --- | ------- | ------- |\n| `fireHistoryLimit` | 7 | Fire records kept per task (oldest evicted). |\n| `historyLimit` | 50 | Archived tasks kept in `_history.json`. |\n| `tickIntervalMs` | 15000 | Scheduler tick period. |\n| `storageDir` | — | Storage override; empty = `<dsh home>/storages/cron`. |\n\n## Limitations\n\n- **Fires need a live profile**: dsh-cron is an in-process plugin by design (ADR 0001; an MCP server cannot push). Nothing fires while dsh is not running, and downtime-accrued occurrences are skipped by policy.\n- **Host-local calendar time**: calendar rules read the host clock's zone; containers in UTC will shift wall-clock schedules (ADR 0009).\n- **`self`-mode outcomes are best-effort**: nothing can force the model to call `cron_report`; sub-agent mode is the traceable default.\n- **One steer per tick**: while a steer sits unconsumed in an inbox, later occurrences still submit once per tick rather than collapsing (dsh's inbox cannot be introspected from a plugin).\n\n## Architecture\n\n```\nsrc/\n  index.ts        plugin entry: settings, agent/created mount, /cron, tick loop\n  scheduler.ts    dsh-free tick engine (fires, coalescing, skip, expiry)\n  rule.ts         selector + validity-window validation, occurrence math\n  cron-expr.ts    5-field cron parser + next-fire computation\n  framing.ts      [CRON FIRE] model framing (injection-resistant)\n  tools.ts        cron_create / cron_list / cron_delete / cron_report\n  store.ts        per-id JSON task store + capped history (atomic writes)\n  types.ts        data model and clock seam\n  paths.ts        dsh home resolution\ntest/             unit suites over the compiled lib (fake clocks/agents)\nscripts/\n  smoke-boot.mjs  real-host smoke: pack → scratch profile → boot\ne2e/              podman suite: real dsh TUI in tmux driven against a scripted mock LLM\n```\n\n## E2E\n\n`e2e/run-e2e.sh` builds a container image (Ubuntu + Node + a global dsh install + the packed plugin tarball) and drives the real TUI in tmux against an in-container **mock OpenAI-compatible LLM** — no credentials, fully deterministic, the same suite that gates CI. The scenarios exercise the whole chain end to end:\n\n1. profile assembly — the plugin composes into the real dsh bundle tree\n2. TUI boot against the scripted provider\n3. self-mode chain — the model creates a task, the scheduler fires after 60s, the `[CRON FIRE]` framing lands in a live turn, `cron_report` backfills the record, the store asserts `status: completed`\n4. sub-agent chain — the fire spawns a real dsh subagent whose `cron_report` is backfilled by the child\n5. commands & expiry — `/cron list` / `/cron help`, the window closes, the task archives itself as `expired`, `/cron delete` archives as `cancelled`\n\n```bash\n./e2e/run-e2e.sh                   # dev machine (uses registry mirrors)\nCLEAN_NETWORK=1 ./e2e/run-e2e.sh   # CI-like network (official endpoints)\n```\n\nPorted from [@aiwayds/pi-kimi-cron](https://github.com/fan56/pi-kimi-cron) (itself ported from Kimi Code's cron module), redesigned for dsh's runtime — the deltas are all recorded in [docs/adr/](./docs/adr).\n\n## License\n\n[MIT](./LICENSE)\n","readmeFilename":"README.md"}