@argszero/cordis-plugin-schedule-cron 0.1.0
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/LICENSE +21 -0
- package/README.md +99 -0
- package/lib/engine.d.ts +95 -0
- package/lib/engine.d.ts.map +1 -0
- package/lib/engine.js +116 -0
- package/lib/engine.js.map +1 -0
- package/lib/index.d.ts +27 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +87 -0
- package/lib/index.js.map +1 -0
- package/lib/types.d.ts +81 -0
- package/lib/types.d.ts.map +1 -0
- package/lib/types.js +13 -0
- package/lib/types.js.map +1 -0
- package/package.json +67 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 argszero
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# @argszero/cordis-plugin-schedule-cron
|
|
2
|
+
|
|
3
|
+
Cron-driven **autonomous task runs** for the deepseek-harness (`dsh`) agent.
|
|
4
|
+
|
|
5
|
+
Unlike the official [`@deepseek-ai/dsh-schedule`](https://www.npmjs.com/package/@deepseek-ai/dsh-schedule) — which only **messages the live session** (a follow-up delivered into whatever agent is already running and idle) — this plugin **spawns a fresh agent run** when a calendar fire occurs, so scheduled work actually happens even with no browser session open. It is the "EMRG-style" scheduled-task runner: a cron calendar rule awakens a brand-new agent turn.
|
|
6
|
+
|
|
7
|
+
## Why this exists
|
|
8
|
+
|
|
9
|
+
`dsh-schedule` explicitly documents the gap:
|
|
10
|
+
|
|
11
|
+
> "当交付必须到达会话之外时请避开它...或者当你需要**「每个工作日 9 点」这类日历规则**时:重复提醒**只按固定间隔运行**。"
|
|
12
|
+
|
|
13
|
+
That is, it has:
|
|
14
|
+
- **No calendar rules** — only `after_seconds` / `at` / `every_seconds` fixed intervals.
|
|
15
|
+
- **No autonomous drive** — it delivers into a live session; closed/cold sessions just leave the reminder overdue.
|
|
16
|
+
|
|
17
|
+
`dsh-cron` fills both: standard 5-field cron calendar rules and a fresh agent run per fire.
|
|
18
|
+
|
|
19
|
+
## Install
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
npm install @argszero/cordis-plugin-schedule-cron
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Then mount it into your `dsh` profile (a Cordis overlay). For example:
|
|
26
|
+
|
|
27
|
+
```yaml
|
|
28
|
+
# cordis.yml fragment
|
|
29
|
+
plugins:
|
|
30
|
+
schedule-cron:
|
|
31
|
+
cron:
|
|
32
|
+
- id: daily-triage
|
|
33
|
+
expression: "0 9 * * 1-5" # 09:00 Mon–Fri
|
|
34
|
+
task: "Review the open issues in this repo and summarize anything urgent."
|
|
35
|
+
- id: weekly-report
|
|
36
|
+
expression: "0 8 * * 1" # 08:00 Monday
|
|
37
|
+
task: "Write this week's status report."
|
|
38
|
+
provider: deepseek
|
|
39
|
+
model: deepseek-chat
|
|
40
|
+
cwd: /path/to/workspace
|
|
41
|
+
tickMs: 60000
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## How it works
|
|
45
|
+
|
|
46
|
+
On a tick (`tickMs`, default `60_000`), the plugin checks each rule. When a rule is due:
|
|
47
|
+
|
|
48
|
+
1. `ctx.agents.create({ sessionId, meta, agentOptions })` composes a **fresh agent** (a brand new session) — the same primitive the ACP bridge uses for non-interactive sessions.
|
|
49
|
+
2. `agent.followup(createUserMessage({ content: [{ type: 'text', text: task }], source: { kind: 'plugin', plugin: 'schedule-cron' } }))` drives its first turn **autonomously**.
|
|
50
|
+
|
|
51
|
+
The fire uses a **monotonic per-rule cursor** (`croner`), so:
|
|
52
|
+
- Each occurrence fires exactly once.
|
|
53
|
+
- On first mount, every cursor anchors to its **next future** occurrence (no backfill burst for past times).
|
|
54
|
+
- A long-downed schedule is capped at `MAX_CATCH_UP_PER_TICK = 4` fires per tick (no burst).
|
|
55
|
+
- Disabled rules (`enabled: false`) are parsed but never fired.
|
|
56
|
+
|
|
57
|
+
A rule's state is recoverable across a restart via `initialState` (`lastFiredAt` per rule id), so a process restart mid-window resumes forward instead of re-firing.
|
|
58
|
+
|
|
59
|
+
## API
|
|
60
|
+
|
|
61
|
+
### Config
|
|
62
|
+
|
|
63
|
+
| Field | Type | Default | Description |
|
|
64
|
+
|-------|------|---------|-------------|
|
|
65
|
+
| `cron` | `CronRule[]` | `[]` | The rules to fire. |
|
|
66
|
+
| `tickMs` | number | `60000` | How often (ms) to check for due rules. |
|
|
67
|
+
| `initialState` | `CronRuleState[]` | `[]` | Restore `lastFiredAt` baselines across a restart. |
|
|
68
|
+
|
|
69
|
+
### `CronRule`
|
|
70
|
+
|
|
71
|
+
| Field | Type | Description |
|
|
72
|
+
|-------|------|-------------|
|
|
73
|
+
| `id` | string | Stable persistence key (defaults to `expr:<expr>`). |
|
|
74
|
+
| `expression` | `string \| string[]` | 5-field cron; an array acts as alternatives. |
|
|
75
|
+
| `task` | string | Prompt delivered as the spawned agent's first turn. |
|
|
76
|
+
| `provider` | string | Provider route (defaults to harness default). |
|
|
77
|
+
| `model` | string | Model id (defaults to harness default). |
|
|
78
|
+
| `cwd` | string | Working dir for the spawned agent (defaults to harness default). |
|
|
79
|
+
| `enabled` | boolean | `true` if the rule should fire (defaults true). |
|
|
80
|
+
|
|
81
|
+
### Engine (pure, no harness needed)
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
import { collectDue, initialCursor, resumeCursor, ruleIdOf, normalizeExpression } from '@argszero/cordis-plugin-schedule-cron'
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
The `collectDue(cursors, now)` function is dependency-free and unit-tested, so it can be driven by any timer (not just Cordis).
|
|
88
|
+
|
|
89
|
+
## Development
|
|
90
|
+
|
|
91
|
+
```sh
|
|
92
|
+
npm install
|
|
93
|
+
npm run build # tsc emits lib/
|
|
94
|
+
npm test # node test/logic.test.mjs (needs build first)
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## License
|
|
98
|
+
|
|
99
|
+
MIT
|
package/lib/engine.d.ts
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure fire-engine logic for the cron plugin.
|
|
3
|
+
*
|
|
4
|
+
* No Cordis dependency: the computation (which rules are due, how to advance a
|
|
5
|
+
* per-rule cursor) is kept pure so it can be unit-tested without a harness.
|
|
6
|
+
* The Cordis plugin (`index.ts`) owns the tick timer and the
|
|
7
|
+
* `ctx.agents.create()` call; this module only answers "what should fire now".
|
|
8
|
+
*
|
|
9
|
+
* @module @argszero/cordis-plugin-schedule-cron/engine
|
|
10
|
+
*/
|
|
11
|
+
import { Cron } from 'croner';
|
|
12
|
+
import type { CronRule, CronRuleState } from './types.js';
|
|
13
|
+
/**
|
|
14
|
+
* The deadline offset (ms) a fire must sit before the clock for a rule already
|
|
15
|
+
* behind on multiple occurrences. We fire at most one occurrence per tick so a
|
|
16
|
+
* long-downed schedule doesn't backfill a burst of runs.
|
|
17
|
+
*/
|
|
18
|
+
export declare const MAX_CATCH_UP_PER_TICK = 4;
|
|
19
|
+
/**
|
|
20
|
+
* A rule that is due to fire right now, along with the occurrence timestamp.
|
|
21
|
+
*/
|
|
22
|
+
export interface DueFire {
|
|
23
|
+
readonly rule: CronRule;
|
|
24
|
+
/** The occurrence this fire represents (the schedule time it satisfies). */
|
|
25
|
+
readonly occurrenceAt: Date;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* A normalized, mutable view of one rule's schedule state. The plugin holds a
|
|
29
|
+
* `Map<id, CronCursor>`; each cursor is advanced one occurrence per fire.
|
|
30
|
+
*/
|
|
31
|
+
export interface CronCursor {
|
|
32
|
+
/** The rule this cursor serves. */
|
|
33
|
+
readonly rule: CronRule;
|
|
34
|
+
/** The parsed Cron engine (one per rule, reused across ticks). */
|
|
35
|
+
readonly cron: Cron;
|
|
36
|
+
/** The next un-fired occurrence. `null` when the rule is exhausted/absent. */
|
|
37
|
+
next: Date | null;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Resolve a stable id for a rule. Falls back to the joined expression so a
|
|
41
|
+
* rule configured without an explicit `id` still has a durable persistence key.
|
|
42
|
+
*/
|
|
43
|
+
export declare function ruleIdOf(rule: CronRule): string;
|
|
44
|
+
/**
|
|
45
|
+
* Normalize a cron expression (or array of them) to a single croner-parseable
|
|
46
|
+
* string. `croner` accepts a single string; an array is joined with `,` so
|
|
47
|
+
* multiple expressions act as alternatives on the same rule.
|
|
48
|
+
*/
|
|
49
|
+
export declare function normalizeExpression(expression: CronRule['expression']): string;
|
|
50
|
+
/**
|
|
51
|
+
* Compute the next strict occurrence of a cron expression at or after `from`.
|
|
52
|
+
* Returns `null` when the expression yields no future occurrence.
|
|
53
|
+
*/
|
|
54
|
+
export declare function nextOccurrence(cron: Cron, from: Date): Date | null;
|
|
55
|
+
/**
|
|
56
|
+
* Build the initial cursor for a rule. On first boot we anchor the cursor to
|
|
57
|
+
* the **next** future occurrence (never a past one), so a freshly-mounted
|
|
58
|
+
* schedule does not immediately backfill a burst of runs for every past
|
|
59
|
+
* occurrence.
|
|
60
|
+
*
|
|
61
|
+
* @param rule - the cron rule.
|
|
62
|
+
* @param now - the current time (the boot anchor).
|
|
63
|
+
*/
|
|
64
|
+
export declare function initialCursor(rule: CronRule, now: Date): CronCursor;
|
|
65
|
+
/**
|
|
66
|
+
* Restore a cursor from persisted `lastFiredAt`. The cursor is anchored one
|
|
67
|
+
* occurrence **after** the last fire, so a restart in the middle of an
|
|
68
|
+
* overdue window resumes forward without re-firing a completed occurrence.
|
|
69
|
+
*
|
|
70
|
+
* @param rule - the cron rule.
|
|
71
|
+
* @param lastFiredAt - the ISO timestamp of the last fire recorded for this rule.
|
|
72
|
+
* @param fallbackNow - the boot anchor when the recorded timestamp is invalid.
|
|
73
|
+
*/
|
|
74
|
+
export declare function resumeCursor(rule: CronRule, lastFiredAt: string, fallbackNow: Date): CronCursor;
|
|
75
|
+
/**
|
|
76
|
+
* Advance a cursor to the strictly-next occurrence after it fired.
|
|
77
|
+
* @param cursor - the cursor that just fired.
|
|
78
|
+
* @returns the advanced cursor (same object).
|
|
79
|
+
*/
|
|
80
|
+
export declare function advance(cursor: CronCursor): CronCursor;
|
|
81
|
+
/**
|
|
82
|
+
* Decide which rules are due at `now`, producing the fires + the per-rule
|
|
83
|
+
* state to persist afterwards. At most {@link MAX_CATCH_UP_PER_TICK} fires
|
|
84
|
+
* total are produced per call so a long-downed schedule doesn't burst.
|
|
85
|
+
*
|
|
86
|
+
* @param cursors - the current cursor map (keyed by rule id).
|
|
87
|
+
* @param now - the current time this tick observed.
|
|
88
|
+
* @returns the due fires and a snapshot of the post-fire cursors (for the
|
|
89
|
+
* optional state projection).
|
|
90
|
+
*/
|
|
91
|
+
export declare function collectDue(cursors: ReadonlyMap<string, CronCursor>, now: Date): {
|
|
92
|
+
fires: DueFire[];
|
|
93
|
+
state: CronRuleState[];
|
|
94
|
+
};
|
|
95
|
+
//# sourceMappingURL=engine.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAA;AAC7B,OAAO,KAAK,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAEzD;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,IAAI,CAAA;AAEtC;;GAEG;AACH,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAA;IACvB,4EAA4E;IAC5E,QAAQ,CAAC,YAAY,EAAE,IAAI,CAAA;CAC5B;AAED;;;GAGG;AACH,MAAM,WAAW,UAAU;IACzB,mCAAmC;IACnC,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAA;IACvB,kEAAkE;IAClE,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAA;IACnB,8EAA8E;IAC9E,IAAI,EAAE,IAAI,GAAG,IAAI,CAAA;CAClB;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,CAI/C;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,QAAQ,CAAC,YAAY,CAAC,GAAG,MAAM,CAE9E;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAGlE;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,IAAI,GAAG,UAAU,CAInE;AAED;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,EAAE,WAAW,EAAE,IAAI,GAAG,UAAU,CAM/F;AAED;;;;GAIG;AACH,wBAAgB,OAAO,CAAC,MAAM,EAAE,UAAU,GAAG,UAAU,CAGtD;AAED;;;;;;;;;GASG;AACH,wBAAgB,UAAU,CACxB,OAAO,EAAE,WAAW,CAAC,MAAM,EAAE,UAAU,CAAC,EACxC,GAAG,EAAE,IAAI,GACR;IAAE,KAAK,EAAE,OAAO,EAAE,CAAC;IAAC,KAAK,EAAE,aAAa,EAAE,CAAA;CAAE,CAqB9C"}
|
package/lib/engine.js
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure fire-engine logic for the cron plugin.
|
|
3
|
+
*
|
|
4
|
+
* No Cordis dependency: the computation (which rules are due, how to advance a
|
|
5
|
+
* per-rule cursor) is kept pure so it can be unit-tested without a harness.
|
|
6
|
+
* The Cordis plugin (`index.ts`) owns the tick timer and the
|
|
7
|
+
* `ctx.agents.create()` call; this module only answers "what should fire now".
|
|
8
|
+
*
|
|
9
|
+
* @module @argszero/cordis-plugin-schedule-cron/engine
|
|
10
|
+
*/
|
|
11
|
+
import { Cron } from 'croner';
|
|
12
|
+
/**
|
|
13
|
+
* The deadline offset (ms) a fire must sit before the clock for a rule already
|
|
14
|
+
* behind on multiple occurrences. We fire at most one occurrence per tick so a
|
|
15
|
+
* long-downed schedule doesn't backfill a burst of runs.
|
|
16
|
+
*/
|
|
17
|
+
export const MAX_CATCH_UP_PER_TICK = 4;
|
|
18
|
+
/**
|
|
19
|
+
* Resolve a stable id for a rule. Falls back to the joined expression so a
|
|
20
|
+
* rule configured without an explicit `id` still has a durable persistence key.
|
|
21
|
+
*/
|
|
22
|
+
export function ruleIdOf(rule) {
|
|
23
|
+
if (rule.id !== undefined && rule.id !== '')
|
|
24
|
+
return rule.id;
|
|
25
|
+
const expr = Array.isArray(rule.expression) ? rule.expression.join(',') : rule.expression;
|
|
26
|
+
return `expr:${expr}`;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Normalize a cron expression (or array of them) to a single croner-parseable
|
|
30
|
+
* string. `croner` accepts a single string; an array is joined with `,` so
|
|
31
|
+
* multiple expressions act as alternatives on the same rule.
|
|
32
|
+
*/
|
|
33
|
+
export function normalizeExpression(expression) {
|
|
34
|
+
return Array.isArray(expression) ? expression.join(',') : expression;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Compute the next strict occurrence of a cron expression at or after `from`.
|
|
38
|
+
* Returns `null` when the expression yields no future occurrence.
|
|
39
|
+
*/
|
|
40
|
+
export function nextOccurrence(cron, from) {
|
|
41
|
+
const next = cron.nextRun(from);
|
|
42
|
+
return next ?? null;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Build the initial cursor for a rule. On first boot we anchor the cursor to
|
|
46
|
+
* the **next** future occurrence (never a past one), so a freshly-mounted
|
|
47
|
+
* schedule does not immediately backfill a burst of runs for every past
|
|
48
|
+
* occurrence.
|
|
49
|
+
*
|
|
50
|
+
* @param rule - the cron rule.
|
|
51
|
+
* @param now - the current time (the boot anchor).
|
|
52
|
+
*/
|
|
53
|
+
export function initialCursor(rule, now) {
|
|
54
|
+
const cron = new Cron(normalizeExpression(rule.expression));
|
|
55
|
+
const next = nextOccurrence(cron, now);
|
|
56
|
+
return { rule, cron, next };
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Restore a cursor from persisted `lastFiredAt`. The cursor is anchored one
|
|
60
|
+
* occurrence **after** the last fire, so a restart in the middle of an
|
|
61
|
+
* overdue window resumes forward without re-firing a completed occurrence.
|
|
62
|
+
*
|
|
63
|
+
* @param rule - the cron rule.
|
|
64
|
+
* @param lastFiredAt - the ISO timestamp of the last fire recorded for this rule.
|
|
65
|
+
* @param fallbackNow - the boot anchor when the recorded timestamp is invalid.
|
|
66
|
+
*/
|
|
67
|
+
export function resumeCursor(rule, lastFiredAt, fallbackNow) {
|
|
68
|
+
const cron = new Cron(normalizeExpression(rule.expression));
|
|
69
|
+
const from = new Date(lastFiredAt);
|
|
70
|
+
const parsed = Number.isNaN(from.getTime()) ? fallbackNow : from;
|
|
71
|
+
const next = nextOccurrence(cron, parsed);
|
|
72
|
+
return { rule, cron, next };
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Advance a cursor to the strictly-next occurrence after it fired.
|
|
76
|
+
* @param cursor - the cursor that just fired.
|
|
77
|
+
* @returns the advanced cursor (same object).
|
|
78
|
+
*/
|
|
79
|
+
export function advance(cursor) {
|
|
80
|
+
cursor.next = cursor.next === null ? null : nextOccurrence(cursor.cron, cursor.next);
|
|
81
|
+
return cursor;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Decide which rules are due at `now`, producing the fires + the per-rule
|
|
85
|
+
* state to persist afterwards. At most {@link MAX_CATCH_UP_PER_TICK} fires
|
|
86
|
+
* total are produced per call so a long-downed schedule doesn't burst.
|
|
87
|
+
*
|
|
88
|
+
* @param cursors - the current cursor map (keyed by rule id).
|
|
89
|
+
* @param now - the current time this tick observed.
|
|
90
|
+
* @returns the due fires and a snapshot of the post-fire cursors (for the
|
|
91
|
+
* optional state projection).
|
|
92
|
+
*/
|
|
93
|
+
export function collectDue(cursors, now) {
|
|
94
|
+
const fires = [];
|
|
95
|
+
const state = [];
|
|
96
|
+
let budget = MAX_CATCH_UP_PER_TICK;
|
|
97
|
+
for (const cursor of cursors.values()) {
|
|
98
|
+
if (cursor.rule.enabled === false)
|
|
99
|
+
continue;
|
|
100
|
+
if (cursor.next === null)
|
|
101
|
+
continue;
|
|
102
|
+
if (cursor.next > now)
|
|
103
|
+
continue;
|
|
104
|
+
// One fire per tick per rule, up to the catch-up budget.
|
|
105
|
+
if (budget <= 0)
|
|
106
|
+
break;
|
|
107
|
+
const occurrenceAt = cursor.next;
|
|
108
|
+
fires.push({ rule: cursor.rule, occurrenceAt });
|
|
109
|
+
advance(cursor);
|
|
110
|
+
// Persist the occurrence the cursor just consumed (before advance mutated `next`).
|
|
111
|
+
state.push({ id: ruleIdOf(cursor.rule), lastFiredAt: occurrenceAt.toISOString() });
|
|
112
|
+
budget -= 1;
|
|
113
|
+
}
|
|
114
|
+
return { fires, state };
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=engine.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"engine.js","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"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"}
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @argszero/cordis-plugin-schedule-cron
|
|
3
|
+
*
|
|
4
|
+
* Cron-driven autonomous task runs for the dsh harness. On a calendar cron
|
|
5
|
+
* fire this plugin **spawns a fresh agent run** via `ctx.agents.create()` and
|
|
6
|
+
* `agent.followup()`, so scheduled work happens even with no browser session
|
|
7
|
+
* open. This is the EMRG-style "autonomous scheduled task" model, distinct
|
|
8
|
+
* from the official @deepseek-ai/dsh-schedule which only messages the live
|
|
9
|
+
* session.
|
|
10
|
+
*
|
|
11
|
+
* @module @argszero/cordis-plugin-schedule-cron
|
|
12
|
+
*/
|
|
13
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
14
|
+
import type { ScheduleCronConfig } from './types.js';
|
|
15
|
+
export type * from './types.js';
|
|
16
|
+
export { collectDue, initialCursor, resumeCursor, ruleIdOf, normalizeExpression } from './engine.js';
|
|
17
|
+
export type { CronCursor, DueFire } from './engine.js';
|
|
18
|
+
/** Cordis function-plugin name. */
|
|
19
|
+
export declare const name = "schedule-cron";
|
|
20
|
+
/** Services required before any scheduled run can be spawned. */
|
|
21
|
+
export declare const inject: string[];
|
|
22
|
+
/**
|
|
23
|
+
* Mount the cron scheduler. Config holds the rules; each fire spawns a fresh
|
|
24
|
+
* agent run.
|
|
25
|
+
*/
|
|
26
|
+
export declare function apply(ctx: Context, config: ScheduleCronConfig): void;
|
|
27
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAYlD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAEpD,mBAAmB,YAAY,CAAA;AAC/B,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,YAAY,EAAE,QAAQ,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AACpG,YAAY,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,aAAa,CAAA;AAEtD,mCAAmC;AACnC,eAAO,MAAM,IAAI,kBAAkB,CAAA;AACnC,iEAAiE;AACjE,eAAO,MAAM,MAAM,UAAgC,CAAA;AAInD;;;GAGG;AACH,wBAAgB,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,kBAAkB,GAAG,IAAI,CA6DpE"}
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @argszero/cordis-plugin-schedule-cron
|
|
3
|
+
*
|
|
4
|
+
* Cron-driven autonomous task runs for the dsh harness. On a calendar cron
|
|
5
|
+
* fire this plugin **spawns a fresh agent run** via `ctx.agents.create()` and
|
|
6
|
+
* `agent.followup()`, so scheduled work happens even with no browser session
|
|
7
|
+
* open. This is the EMRG-style "autonomous scheduled task" model, distinct
|
|
8
|
+
* from the official @deepseek-ai/dsh-schedule which only messages the live
|
|
9
|
+
* session.
|
|
10
|
+
*
|
|
11
|
+
* @module @argszero/cordis-plugin-schedule-cron
|
|
12
|
+
*/
|
|
13
|
+
import { SessionId as toSessionId } from '@deepseek-ai/dsh-session';
|
|
14
|
+
import { createUserMessage } from '@deepseek-ai/dsh-llm';
|
|
15
|
+
import { randomUUID } from 'node:crypto';
|
|
16
|
+
import { collectDue, initialCursor, resumeCursor, ruleIdOf, } from './engine.js';
|
|
17
|
+
export { collectDue, initialCursor, resumeCursor, ruleIdOf, normalizeExpression } from './engine.js';
|
|
18
|
+
/** Cordis function-plugin name. */
|
|
19
|
+
export const name = 'schedule-cron';
|
|
20
|
+
/** Services required before any scheduled run can be spawned. */
|
|
21
|
+
export const inject = ['agents', 'sessions', 'llm'];
|
|
22
|
+
const DEFAULT_TICK_MS = 60_000;
|
|
23
|
+
/**
|
|
24
|
+
* Mount the cron scheduler. Config holds the rules; each fire spawns a fresh
|
|
25
|
+
* agent run.
|
|
26
|
+
*/
|
|
27
|
+
export function apply(ctx, config) {
|
|
28
|
+
const tickMs = config.tickMs ?? DEFAULT_TICK_MS;
|
|
29
|
+
const rules = config.cron ?? [];
|
|
30
|
+
// Build the cursor map. Restore from initialState when present, else anchor
|
|
31
|
+
// each cursor to its next future occurrence (no backfill burst on mount).
|
|
32
|
+
const cursors = new Map();
|
|
33
|
+
const bootNow = new Date();
|
|
34
|
+
const restored = new Map();
|
|
35
|
+
for (const state of config.initialState ?? [])
|
|
36
|
+
restored.set(state.id, state.lastFiredAt);
|
|
37
|
+
for (const rule of rules) {
|
|
38
|
+
const id = ruleIdOf(rule);
|
|
39
|
+
if (cursors.has(id))
|
|
40
|
+
continue;
|
|
41
|
+
const lastFiredAt = restored.get(id);
|
|
42
|
+
cursors.set(id, lastFiredAt === undefined
|
|
43
|
+
? initialCursor(rule, bootNow)
|
|
44
|
+
: resumeCursor(rule, lastFiredAt, bootNow));
|
|
45
|
+
}
|
|
46
|
+
let stopped = false;
|
|
47
|
+
/** Spawn one fresh agent run for a fired rule. */
|
|
48
|
+
async function fire(rule) {
|
|
49
|
+
const sessionId = toSessionId(`session-${randomUUID()}`);
|
|
50
|
+
const handle = await ctx.agents.create({
|
|
51
|
+
sessionId,
|
|
52
|
+
meta: rule.cwd === undefined ? {} : { cwd: rule.cwd },
|
|
53
|
+
agentOptions: rule.provider === undefined
|
|
54
|
+
? (rule.model === undefined ? {} : { model: rule.model })
|
|
55
|
+
: { provider: rule.provider, ...(rule.model === undefined ? {} : { model: rule.model }) },
|
|
56
|
+
});
|
|
57
|
+
const message = createUserMessage({
|
|
58
|
+
content: [{ type: 'text', text: rule.task }],
|
|
59
|
+
source: { kind: 'plugin', plugin: 'schedule-cron' },
|
|
60
|
+
});
|
|
61
|
+
// Commit the fire to the durable log even if the turn errors.
|
|
62
|
+
handle.agent.followup(message);
|
|
63
|
+
// The agent lives on; the handle's disposer stays with the plugin.
|
|
64
|
+
}
|
|
65
|
+
ctx.effect(() => {
|
|
66
|
+
const onTick = async () => {
|
|
67
|
+
if (stopped)
|
|
68
|
+
return;
|
|
69
|
+
const now = new Date();
|
|
70
|
+
const { fires } = collectDue(cursors, now);
|
|
71
|
+
for (const due of fires) {
|
|
72
|
+
try {
|
|
73
|
+
await fire(due.rule);
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
ctx.logger.warn('[schedule-cron] fire failed', { rule: due.rule.id, error });
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
const timerId = setInterval(() => { void onTick(); }, tickMs);
|
|
81
|
+
return () => {
|
|
82
|
+
stopped = true;
|
|
83
|
+
clearInterval(timerId);
|
|
84
|
+
};
|
|
85
|
+
}, 'schedule-cron.tick()');
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=index.js.map
|
package/lib/index.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/lib/types.d.ts
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Types for @argszero/cordis-plugin-schedule-cron.
|
|
3
|
+
*
|
|
4
|
+
* A cron rule describes a calendar-based recurrence and the task the harness
|
|
5
|
+
* should run when it fires. Unlike the official @deepseek-ai/dsh-schedule
|
|
6
|
+
* (which messages the *live* session), each fire here spawns a **fresh
|
|
7
|
+
* autonomous agent run** via `ctx.agents.create()` + `agent.followup()`,
|
|
8
|
+
* so work happens even when no browser session is open.
|
|
9
|
+
*
|
|
10
|
+
* @module @argszero/cordis-plugin-schedule-cron
|
|
11
|
+
*/
|
|
12
|
+
/** A calendar cron expression in the standard 5-field form. */
|
|
13
|
+
export interface CronRule {
|
|
14
|
+
/**
|
|
15
|
+
* The rule id. Persistence keys on this to dedupe fires. Auto-assigned when omitted.
|
|
16
|
+
*/
|
|
17
|
+
readonly id?: string;
|
|
18
|
+
/**
|
|
19
|
+
* Standard 5-field cron expression: `minute hour day-of-month month day-of-week`.
|
|
20
|
+
* Example: `"0 9 * * 1-5"` for 09:00 on weekdays. May be an array of expressions.
|
|
21
|
+
*/
|
|
22
|
+
readonly expression: string | readonly string[];
|
|
23
|
+
/**
|
|
24
|
+
* The prompt/task to run when the rule fires. This is delivered as a
|
|
25
|
+
* `UserMessage` into the spawned agent's first turn.
|
|
26
|
+
*/
|
|
27
|
+
readonly task: string;
|
|
28
|
+
/**
|
|
29
|
+
* Optional provider route for the spawned agent. Defaults to the harness's
|
|
30
|
+
* current default selection when omitted.
|
|
31
|
+
*/
|
|
32
|
+
readonly provider?: string;
|
|
33
|
+
/**
|
|
34
|
+
* Optional model id interpreted by the selected provider. Defaults to the
|
|
35
|
+
* harness's current default selection when omitted.
|
|
36
|
+
*/
|
|
37
|
+
readonly model?: string;
|
|
38
|
+
/**
|
|
39
|
+
* Optional working directory for the spawned agent. Defaults to the
|
|
40
|
+
* harness default `cwd` when omitted.
|
|
41
|
+
*/
|
|
42
|
+
readonly cwd?: string;
|
|
43
|
+
/**
|
|
44
|
+
* Whether the rule is enabled. Disabled rules are parsed but never fired.
|
|
45
|
+
* Useful for a persisted registry that the host toggles without deleting.
|
|
46
|
+
*/
|
|
47
|
+
readonly enabled?: boolean;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Per-rule fire state. Duration-preserving key so the plugin can survive a
|
|
51
|
+
* process restart without re-firing already-completed shedules.
|
|
52
|
+
*/
|
|
53
|
+
export interface CronRuleState {
|
|
54
|
+
/** The rule id. */
|
|
55
|
+
readonly id: string;
|
|
56
|
+
/** The last scheduled occurrence the plugin actually fired. */
|
|
57
|
+
readonly lastFiredAt: string;
|
|
58
|
+
}
|
|
59
|
+
/** Plugin configuration shape. */
|
|
60
|
+
export interface ScheduleCronConfig {
|
|
61
|
+
/**
|
|
62
|
+
* The cron rules this plugin owns. When a rule fires, the plugin spawns a
|
|
63
|
+
* fresh agent run with the rule's `task` as its first turn.
|
|
64
|
+
*/
|
|
65
|
+
readonly cron: readonly CronRule[];
|
|
66
|
+
/**
|
|
67
|
+
* How often (milliseconds) the plugin wakes to check for due rules.
|
|
68
|
+
* Defaults to 60_000 (one minute). For fine-grained schedules, lower this
|
|
69
|
+
* (e.g. 15_000) — but sub-minute cron expressions are not honored by the
|
|
70
|
+
* standard 5-field form.
|
|
71
|
+
*/
|
|
72
|
+
readonly tickMs?: number;
|
|
73
|
+
/**
|
|
74
|
+
* Optional initial state to restore dedupe baselines across a restart.
|
|
75
|
+
* Without it the plugin refuses to fire a rule it has no baseline for on
|
|
76
|
+
* the first boot (guarding against a "backfill burst" of fires for every
|
|
77
|
+
* past occurrence).
|
|
78
|
+
*/
|
|
79
|
+
readonly initialState?: readonly CronRuleState[];
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,+DAA+D;AAC/D,MAAM,WAAW,QAAQ;IACvB;;OAEG;IACH,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,CAAA;IACpB;;;OAGG;IACH,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAAA;IAC/C;;;OAGG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB;;;OAGG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;IAC1B;;;OAGG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;IACvB;;;OAGG;IACH,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAA;IACrB;;;OAGG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAC3B;AAED;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,mBAAmB;IACnB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,+DAA+D;IAC/D,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;CAC7B;AAED,kCAAkC;AAClC,MAAM,WAAW,kBAAkB;IACjC;;;OAGG;IACH,QAAQ,CAAC,IAAI,EAAE,SAAS,QAAQ,EAAE,CAAA;IAClC;;;;;OAKG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;IACxB;;;;;OAKG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,SAAS,aAAa,EAAE,CAAA;CACjD"}
|
package/lib/types.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Types for @argszero/cordis-plugin-schedule-cron.
|
|
3
|
+
*
|
|
4
|
+
* A cron rule describes a calendar-based recurrence and the task the harness
|
|
5
|
+
* should run when it fires. Unlike the official @deepseek-ai/dsh-schedule
|
|
6
|
+
* (which messages the *live* session), each fire here spawns a **fresh
|
|
7
|
+
* autonomous agent run** via `ctx.agents.create()` + `agent.followup()`,
|
|
8
|
+
* so work happens even when no browser session is open.
|
|
9
|
+
*
|
|
10
|
+
* @module @argszero/cordis-plugin-schedule-cron
|
|
11
|
+
*/
|
|
12
|
+
export {};
|
|
13
|
+
//# sourceMappingURL=types.js.map
|
package/lib/types.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG"}
|
package/package.json
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@argszero/cordis-plugin-schedule-cron",
|
|
3
|
+
"description": "Cron-driven autonomous task runs for the dsh harness: fire a calendar cron rule and spawn a fresh agent run via ctx.agents.create(), unlike the official @deepseek-ai/dsh-schedule which only messages the live session.",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "lib/index.js",
|
|
7
|
+
"types": "lib/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./lib/index.d.ts",
|
|
11
|
+
"default": "./lib/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"lib",
|
|
16
|
+
"README.md",
|
|
17
|
+
"LICENSE"
|
|
18
|
+
],
|
|
19
|
+
"peerDependencies": {
|
|
20
|
+
"@deepseek-ai/cordis": ">=4.0.0 <5",
|
|
21
|
+
"@deepseek-ai/dsh-agent": ">=0.1.0",
|
|
22
|
+
"@deepseek-ai/dsh-brand": ">=0.1.0",
|
|
23
|
+
"@deepseek-ai/dsh-llm": ">=0.1.0",
|
|
24
|
+
"@deepseek-ai/dsh-session": ">=0.1.0",
|
|
25
|
+
"@deepseek-ai/dsh-tools": ">=0.1.0"
|
|
26
|
+
},
|
|
27
|
+
"scripts": {
|
|
28
|
+
"build": "tsc -p .",
|
|
29
|
+
"prepack": "tsc -p .",
|
|
30
|
+
"test": "node test/logic.test.mjs"
|
|
31
|
+
},
|
|
32
|
+
"engines": {
|
|
33
|
+
"node": "^22.19 || >=24"
|
|
34
|
+
},
|
|
35
|
+
"keywords": [
|
|
36
|
+
"deepseek",
|
|
37
|
+
"dsh",
|
|
38
|
+
"cordis",
|
|
39
|
+
"plugin",
|
|
40
|
+
"schedule",
|
|
41
|
+
"cron",
|
|
42
|
+
"task-runner",
|
|
43
|
+
"autonomous",
|
|
44
|
+
"scheduler"
|
|
45
|
+
],
|
|
46
|
+
"license": "MIT",
|
|
47
|
+
"publishConfig": {
|
|
48
|
+
"access": "public"
|
|
49
|
+
},
|
|
50
|
+
"repository": {
|
|
51
|
+
"type": "git",
|
|
52
|
+
"url": "git+https://github.com/argszero/cordis-plugin-schedule-cron.git"
|
|
53
|
+
},
|
|
54
|
+
"dependencies": {
|
|
55
|
+
"croner": "^10.0.1"
|
|
56
|
+
},
|
|
57
|
+
"devDependencies": {
|
|
58
|
+
"@deepseek-ai/cordis": ">=4.0.1-rc.4",
|
|
59
|
+
"@deepseek-ai/dsh-agent": "0.1.2-rc.1",
|
|
60
|
+
"@deepseek-ai/dsh-brand": "0.1.2-rc.1",
|
|
61
|
+
"@deepseek-ai/dsh-llm": "0.1.2-rc.1",
|
|
62
|
+
"@deepseek-ai/dsh-session": "0.1.2-rc.1",
|
|
63
|
+
"@deepseek-ai/dsh-tools": "0.1.2-rc.1",
|
|
64
|
+
"@types/node": "^26.4.1",
|
|
65
|
+
"typescript": "^7.0.2"
|
|
66
|
+
}
|
|
67
|
+
}
|