@qilin-agent/schedule 3.1.6 → 3.1.8
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.i18n.yaml +72 -39
- package/README.md +180 -56
- package/README.zh.md +185 -61
- package/lib/index.js +5565 -1191
- package/lib/typert.host.js +1886 -267
- package/lib/typert.remote-client.d.ts +22 -10
- package/lib/typert.remote-client.js +1727 -190
- package/lib/types/delivery-history.d.ts +4 -6
- package/lib/types/delivery-history.js +7 -20
- package/lib/types/domain.d.ts +117 -80
- package/lib/types/domain.js +311 -797
- package/lib/types/index.d.ts +168 -80
- package/lib/types/index.js +531 -222
- package/lib/types/runtime.d.ts +92 -4
- package/lib/types/runtime.js +392 -72
- package/lib/types/storage.d.ts +126 -24
- package/lib/types/storage.js +85 -16
- package/lib/types/types.d.ts +287 -148
- package/lib/types/types.js +1 -1
- package/lib/types/update.d.ts +16 -21
- package/lib/types/update.js +72 -144
- package/package.json +38 -35
package/lib/types/update.js
CHANGED
|
@@ -1,164 +1,92 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/** Typed timing construction and edits shared by all Schedule management callers. */
|
|
2
2
|
import { isDeepStrictEqual } from 'node:util';
|
|
3
|
-
import {
|
|
4
|
-
/**
|
|
5
|
-
function
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
case 'weekly': return 'weekly';
|
|
11
|
-
case 'cron': return 'cron';
|
|
12
|
-
default: return undefined;
|
|
3
|
+
import { createAfterScheduleRecord, createAtScheduleRecord, createCronScheduleRecord, createDailyScheduleRecord, createEveryScheduleRecord, createWeeklyScheduleRecord, createRRuleScheduleRecord, parseAtInput, parseCronInput, parseDailyInput, parseWeeklyInput, normalizeRRule, updateRRuleScheduleRecord, ScheduleInputError, scheduleTitle, isRecord, hasExactKeys, } from "./domain.js";
|
|
4
|
+
/** Read exactly one matching timing payload at the untrusted management boundary. */
|
|
5
|
+
function validateTiming(value) {
|
|
6
|
+
const selectors = { after: 'after_seconds', at: 'at', every: 'every_seconds', daily: 'daily', weekly: 'weekly', cron: 'cron', rrule: 'rrule' };
|
|
7
|
+
if (!isRecord(value) || !(value.kind in selectors)
|
|
8
|
+
|| !hasExactKeys(value, ['kind', selectors[value.kind]])) {
|
|
9
|
+
throw new ScheduleInputError('invalid_rule', 'timing must contain exactly kind and its matching selector.');
|
|
13
10
|
}
|
|
11
|
+
return value;
|
|
14
12
|
}
|
|
15
13
|
/**
|
|
16
|
-
*
|
|
17
|
-
* @param
|
|
18
|
-
* @
|
|
14
|
+
* Construct a current record with one authoritative timing kind and a future committed target.
|
|
15
|
+
* @param id - Host-generated task identity.
|
|
16
|
+
* @param title - Required user-facing task title.
|
|
17
|
+
* @param prompt - Non-empty instruction delivered at each occurrence.
|
|
18
|
+
* @param value - Exactly one typed timing selector.
|
|
19
|
+
* @param now - Explicit acceptance time in epoch milliseconds.
|
|
20
|
+
* @returns A validated record with its first future occurrence.
|
|
19
21
|
*/
|
|
20
|
-
function
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
export function createScheduleRecord(id, title, prompt, value, now) {
|
|
23
|
+
const timing = validateTiming(value);
|
|
24
|
+
switch (timing.kind) {
|
|
25
|
+
case 'after': return createAfterScheduleRecord(id, prompt, timing.after_seconds, now, title);
|
|
26
|
+
case 'at': return createAtScheduleRecord(id, prompt, timing.at, now, title);
|
|
27
|
+
case 'every': return createEveryScheduleRecord(id, prompt, timing.every_seconds, now, title);
|
|
28
|
+
case 'daily': return createDailyScheduleRecord(id, prompt, timing.daily, now, title);
|
|
29
|
+
case 'weekly': return createWeeklyScheduleRecord(id, prompt, timing.weekly, now, title);
|
|
30
|
+
case 'cron': return createCronScheduleRecord(id, prompt, timing.cron, now, title);
|
|
31
|
+
case 'rrule': return createRRuleScheduleRecord(id, title, prompt, timing.rrule, now);
|
|
23
32
|
}
|
|
24
|
-
const change = value;
|
|
25
|
-
const selector = timingSelector(change.kind);
|
|
26
|
-
const keys = Object.keys(change);
|
|
27
|
-
if (selector === undefined || keys.length !== 2 || !keys.includes('kind') || !keys.includes(selector)) {
|
|
28
|
-
throw new ScheduleInputError('invalid_rule', 'Timing change must contain exactly kind and its matching timing selector.');
|
|
29
|
-
}
|
|
30
|
-
return change;
|
|
31
33
|
}
|
|
32
|
-
/**
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
34
|
+
/** Express a record's rule as editable timing; a one-shot retains its committed absolute target. */
|
|
35
|
+
function timingOf(record) {
|
|
36
|
+
switch (record.kind) {
|
|
37
|
+
case 'after':
|
|
38
|
+
case 'at': return { kind: 'at', at: record.scheduledAt };
|
|
39
|
+
case 'every': return { kind: 'every', every_seconds: record.everySeconds };
|
|
40
|
+
case 'daily': return { kind: 'daily', daily: { time: record.time, time_zone: record.timeZone } };
|
|
41
|
+
case 'weekly': return { kind: 'weekly', weekly: { time: record.time, time_zone: record.timeZone, weekdays: record.weekdays } };
|
|
42
|
+
case 'cron': return { kind: 'cron', cron: { expression: record.expression, time_zone: record.timeZone } };
|
|
43
|
+
case 'rrule': return { kind: 'rrule', rrule: record.rrule };
|
|
40
44
|
}
|
|
41
|
-
return prompt.trim();
|
|
42
45
|
}
|
|
43
|
-
/**
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}
|
|
54
|
-
/**
|
|
55
|
-
* Apply a replacement name and instruction to a stored record without touching its rule.
|
|
56
|
-
* @param current - Current active record holding the committed target.
|
|
57
|
-
* @param title - Validated replacement or retained name.
|
|
58
|
-
* @param prompt - Validated replacement or retained instruction.
|
|
59
|
-
* @returns The same record when both are unchanged, otherwise a frozen copy with the new content.
|
|
60
|
-
*/
|
|
61
|
-
function withContent(current, title, prompt) {
|
|
62
|
-
return title === current.title && prompt === current.prompt
|
|
63
|
-
? current
|
|
64
|
-
: Object.freeze({ ...current, title, prompt });
|
|
65
|
-
}
|
|
66
|
-
/**
|
|
67
|
-
* Resolve the requested rule against the current record after the complete expected record has matched.
|
|
68
|
-
*
|
|
69
|
-
* The change kind may differ from the current record's kind; a change whose normalized
|
|
70
|
-
* timing equals the current rule keeps the committed target and only applies the supplied
|
|
71
|
-
* name and instruction, while any other change recomputes the target exactly as creation
|
|
72
|
-
* computes it from the same now.
|
|
73
|
-
*/
|
|
74
|
-
function changedRecord(current, title, prompt, value, now) {
|
|
75
|
-
if (value === undefined)
|
|
76
|
-
return withContent(current, title, prompt);
|
|
77
|
-
const change = validateChange(value);
|
|
78
|
-
switch (change.kind) {
|
|
79
|
-
case 'at':
|
|
80
|
-
if ((current.kind === 'after' || current.kind === 'at')
|
|
81
|
-
&& parseAtInput(change.at) === Date.parse(current.scheduledAt))
|
|
82
|
-
return withContent(current, title, prompt);
|
|
83
|
-
return createAtScheduleRecord(current.id, prompt, change.at, now, title);
|
|
84
|
-
case 'every':
|
|
85
|
-
if (current.kind === 'every' && change.every_seconds === current.everySeconds) {
|
|
86
|
-
return withContent(current, title, prompt);
|
|
87
|
-
}
|
|
88
|
-
return createEveryScheduleRecord(current.id, prompt, change.every_seconds, now, title);
|
|
89
|
-
case 'daily': {
|
|
90
|
-
if (current.kind === 'daily') {
|
|
91
|
-
const normalized = parseDailyInput(change.daily);
|
|
92
|
-
if (normalized.time === current.time
|
|
93
|
-
&& normalized.timeZone === canonicalizeTimeZone(current.timeZone))
|
|
94
|
-
return withContent(current, title, prompt);
|
|
95
|
-
}
|
|
96
|
-
return createDailyScheduleRecord(current.id, prompt, change.daily, now, title);
|
|
97
|
-
}
|
|
98
|
-
case 'weekly': {
|
|
99
|
-
if (current.kind === 'weekly') {
|
|
100
|
-
const normalized = parseWeeklyInput(change.weekly);
|
|
101
|
-
if (normalized.time === current.time
|
|
102
|
-
&& normalized.timeZone === canonicalizeTimeZone(current.timeZone)
|
|
103
|
-
&& isDeepStrictEqual(normalized.weekdays, current.weekdays))
|
|
104
|
-
return withContent(current, title, prompt);
|
|
105
|
-
}
|
|
106
|
-
return createWeeklyScheduleRecord(current.id, prompt, change.weekly, now, title);
|
|
107
|
-
}
|
|
108
|
-
case 'cron': {
|
|
109
|
-
if (current.kind === 'cron') {
|
|
110
|
-
const normalized = parseCronInput(change.cron);
|
|
111
|
-
if (normalized.expression === canonicalizeCronExpression(current.expression)
|
|
112
|
-
&& normalized.timeZone === canonicalizeTimeZone(current.timeZone))
|
|
113
|
-
return withContent(current, title, prompt);
|
|
114
|
-
}
|
|
115
|
-
return createCronScheduleRecord(current.id, prompt, change.cron, now, title);
|
|
116
|
-
}
|
|
117
|
-
/* v8 ignore next 4 -- validateChange rejects unknown discriminants before this closed union switch. */
|
|
118
|
-
default: {
|
|
119
|
-
const unreachable = change;
|
|
120
|
-
throw new Error(`Unknown validated timing change: ${String(unreachable)}`);
|
|
121
|
-
}
|
|
46
|
+
/** Normalize timing for equivalence before calculating any new target. */
|
|
47
|
+
function normalizedTiming(value) {
|
|
48
|
+
const timing = validateTiming(value);
|
|
49
|
+
switch (timing.kind) {
|
|
50
|
+
case 'after': return timing;
|
|
51
|
+
case 'at': return { kind: 'at', at: parseAtInput(timing.at) };
|
|
52
|
+
case 'every': return timing;
|
|
53
|
+
case 'daily': return { kind: 'daily', ...parseDailyInput(timing.daily) };
|
|
54
|
+
case 'weekly': return { kind: 'weekly', ...parseWeeklyInput(timing.weekly) };
|
|
55
|
+
case 'cron': return { kind: 'cron', ...parseCronInput(timing.cron) };
|
|
56
|
+
case 'rrule': return { kind: 'rrule', rrule: normalizeRRule(timing.rrule) };
|
|
122
57
|
}
|
|
123
58
|
}
|
|
124
59
|
/**
|
|
125
|
-
*
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
* @param current - Current active Host record.
|
|
132
|
-
* @param expected - Untrusted complete record observed by the caller.
|
|
133
|
-
* @param change - Strict timing selector, whose kind may differ from the current record's kind, or undefined to keep timing.
|
|
134
|
-
* @param now - Single wall-clock sample from the accepted FIFO slot.
|
|
135
|
-
* @param content - Untrusted replacement name and instruction; each omitted field keeps its stored value.
|
|
136
|
-
* @returns Current/new record or a bounded input/conflict result; unrelated failures throw.
|
|
60
|
+
* Keep unchanged timing and committed targets, or re-arm through the same constructor as creation.
|
|
61
|
+
* @param current - Stored record before the edit.
|
|
62
|
+
* @param patch - Descriptive fields and optional typed timing replacement.
|
|
63
|
+
* @param now - Explicit edit acceptance time in epoch milliseconds.
|
|
64
|
+
* @param resume - Whether to find a future occurrence for unchanged timing.
|
|
65
|
+
* @returns The retained or revised authoritative timing record.
|
|
137
66
|
*/
|
|
138
|
-
export function
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
catch (error) {
|
|
144
|
-
if (!(error instanceof ScheduleLogError))
|
|
145
|
-
throw error;
|
|
146
|
-
return { code: 'invalid_rule', message: 'expected must be a complete valid Schedule record.' };
|
|
147
|
-
}
|
|
148
|
-
if (!isDeepStrictEqual(current, decoded)) {
|
|
149
|
-
return { id: current.id, updated: false, code: 'schedule_conflict' };
|
|
67
|
+
export function updateScheduleRecord(current, patch, now, resume) {
|
|
68
|
+
const title = scheduleTitle(patch.title === undefined ? current.title : patch.title);
|
|
69
|
+
const prompt = patch.prompt === undefined ? current.prompt : patch.prompt;
|
|
70
|
+
if (typeof prompt !== 'string' || prompt.trim().length === 0) {
|
|
71
|
+
throw new ScheduleInputError('invalid_prompt', 'prompt must be non-empty after trimming.');
|
|
150
72
|
}
|
|
73
|
+
const timing = patch.timing === undefined ? timingOf(current) : patch.timing;
|
|
74
|
+
const changed = patch.timing !== undefined && !isDeepStrictEqual(normalizedTiming(timing), normalizedTiming(timingOf(current)));
|
|
151
75
|
try {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
76
|
+
if (current.kind === 'rrule' && timing.kind === 'rrule') {
|
|
77
|
+
return updateRRuleScheduleRecord(current, { title, prompt,
|
|
78
|
+
...(changed ? { rrule: timing.rrule } : {}),
|
|
79
|
+
}, now, changed || resume);
|
|
80
|
+
}
|
|
81
|
+
if (changed || resume)
|
|
82
|
+
return createScheduleRecord(current.id, title, prompt, timing, now);
|
|
157
83
|
}
|
|
158
84
|
catch (error) {
|
|
159
|
-
if (!
|
|
160
|
-
throw
|
|
161
|
-
|
|
85
|
+
if (resume && !changed && error instanceof ScheduleInputError && error.code === 'not_future') {
|
|
86
|
+
throw new ScheduleInputError('not_future', 'This task has no future occurrence. Set a future schedule before resuming it.');
|
|
87
|
+
}
|
|
88
|
+
throw error;
|
|
162
89
|
}
|
|
90
|
+
return title === current.title && prompt.trim() === current.prompt ? current : Object.freeze({ ...current, title, prompt: prompt.trim() });
|
|
163
91
|
}
|
|
164
92
|
//# sourceMappingURL=update.js.map
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qilin-agent/schedule",
|
|
3
|
-
"description": "Host-wide durable reminders with shared management and
|
|
4
|
-
"version": "3.1.
|
|
3
|
+
"description": "Host-wide durable reminders with shared management and delivery to existing or new Sessions",
|
|
4
|
+
"version": "3.1.8",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -49,43 +49,46 @@
|
|
|
49
49
|
],
|
|
50
50
|
"license": "MIT",
|
|
51
51
|
"peerDependencies": {
|
|
52
|
-
"@qilin-agent/agent": "3.1.
|
|
53
|
-
"@qilin-agent/
|
|
54
|
-
"@qilin-agent/
|
|
55
|
-
"@qilin-agent/
|
|
56
|
-
"@qilin-agent/
|
|
57
|
-
"@qilin-agent/
|
|
58
|
-
"@qilin-agent/
|
|
59
|
-
"@qilin-agent/
|
|
60
|
-
"@qilin-agent/
|
|
61
|
-
"@qilin-agent/
|
|
62
|
-
"@qilin-agent/
|
|
63
|
-
"@qilin-agent/
|
|
52
|
+
"@qilin-agent/agent": "3.1.8",
|
|
53
|
+
"@qilin-agent/api-session-controller": "3.1.8",
|
|
54
|
+
"@qilin-agent/brand": "3.1.8",
|
|
55
|
+
"@qilin-agent/llm": "3.1.8",
|
|
56
|
+
"@qilin-agent/session": "3.1.8",
|
|
57
|
+
"@qilin-agent/session-persistence": "3.1.8",
|
|
58
|
+
"@qilin-agent/session-projection": "3.1.8",
|
|
59
|
+
"@qilin-agent/session-title": "3.1.8",
|
|
60
|
+
"@qilin-agent/storage-domain": "3.1.8",
|
|
61
|
+
"@qilin-agent/subagent": "3.1.8",
|
|
62
|
+
"@qilin-agent/tools": "3.1.8",
|
|
63
|
+
"@qilin-agent/typert-protocol": "3.1.8",
|
|
64
|
+
"@qilin-agent/kylin": "~4.0.6-alpha.2"
|
|
64
65
|
},
|
|
65
66
|
"dependencies": {
|
|
67
|
+
"@qilin-agent/schemastery": "~3.18.6-alpha.2",
|
|
66
68
|
"@js-temporal/polyfill": "0.5.1",
|
|
67
|
-
"zod": "^4.4.3"
|
|
68
|
-
"@qilin-agent/schemastery": "~3.18.5"
|
|
69
|
+
"zod": "^4.4.3"
|
|
69
70
|
},
|
|
70
71
|
"devDependencies": {
|
|
71
|
-
"@qilin-agent/kylin-plugin-loader": "~1.0.
|
|
72
|
-
"@qilin-agent/agent": "3.1.
|
|
73
|
-
"@qilin-agent/agent-loop
|
|
74
|
-
"@qilin-agent/
|
|
75
|
-
"@qilin-agent/
|
|
76
|
-
"@qilin-agent/brand": "3.1.
|
|
77
|
-
"@qilin-agent/
|
|
78
|
-
"@qilin-agent/session
|
|
79
|
-
"@qilin-agent/session-
|
|
80
|
-
"@qilin-agent/
|
|
81
|
-
"@qilin-agent/session": "3.1.
|
|
82
|
-
"@qilin-agent/storage
|
|
83
|
-
"@qilin-agent/
|
|
84
|
-
"@qilin-agent/storage": "3.1.
|
|
85
|
-
"@qilin-agent/
|
|
86
|
-
"@qilin-agent/system-prompt": "3.1.
|
|
87
|
-
"@qilin-agent/
|
|
88
|
-
"@qilin-agent/typert-protocol": "3.1.
|
|
89
|
-
"@qilin-agent/kylin": "~4.0.
|
|
72
|
+
"@qilin-agent/kylin-plugin-loader": "~1.0.7-alpha.2",
|
|
73
|
+
"@qilin-agent/agent": "3.1.8",
|
|
74
|
+
"@qilin-agent/agent-loop": "3.1.8",
|
|
75
|
+
"@qilin-agent/agent-loop-testkit": "3.1.8",
|
|
76
|
+
"@qilin-agent/api-session-controller": "3.1.8",
|
|
77
|
+
"@qilin-agent/brand": "3.1.8",
|
|
78
|
+
"@qilin-agent/llm": "3.1.8",
|
|
79
|
+
"@qilin-agent/session": "3.1.8",
|
|
80
|
+
"@qilin-agent/session-persistence": "3.1.8",
|
|
81
|
+
"@qilin-agent/session-persistence-jsonl": "3.1.8",
|
|
82
|
+
"@qilin-agent/session-projection": "3.1.8",
|
|
83
|
+
"@qilin-agent/storage": "3.1.8",
|
|
84
|
+
"@qilin-agent/storage-domain": "3.1.8",
|
|
85
|
+
"@qilin-agent/storage-json": "3.1.8",
|
|
86
|
+
"@qilin-agent/subagent": "3.1.8",
|
|
87
|
+
"@qilin-agent/system-prompt": "3.1.8",
|
|
88
|
+
"@qilin-agent/tools": "3.1.8",
|
|
89
|
+
"@qilin-agent/typert-protocol": "3.1.8",
|
|
90
|
+
"@qilin-agent/kylin": "~4.0.6-alpha.2",
|
|
91
|
+
"@qilin-agent/goal": "3.1.8",
|
|
92
|
+
"@qilin-agent/goal-round-driver": "3.1.8"
|
|
90
93
|
}
|
|
91
94
|
}
|