@qihongmu/dsh-plugins-scheduled-task 0.1.1-alpha.2 → 0.1.1-alpha.3
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/cordis.patch.yml +0 -2
- package/lib/typert.host.d.ts +5 -0
- package/lib/typert.host.js +37 -0
- package/lib/typert.remote-client.js +304 -0
- package/lib/types/domain.js +4 -4
- package/lib/types/index.d.ts +40 -0
- package/lib/types/index.js +105 -6
- package/lib/types/spec.js +2 -1
- package/package.json +13 -2
package/cordis.patch.yml
CHANGED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/* Vendored by hand for this external plugin (the DSH generator cannot see
|
|
2
|
+
* external repos — see FEASIBILITY.md). Host-face TYPERT manifest: the
|
|
3
|
+
* invocation descriptors are the SAME objects the vendored client
|
|
4
|
+
* contribution carries (single source of truth; the loader/registry
|
|
5
|
+
* validation treats both faces identically for direct, non-lookup methods),
|
|
6
|
+
* and `model` carries the minimal reflection face the loader accepts.
|
|
7
|
+
* Kept in sync with host/src/types.ts and typert.remote-client.js.
|
|
8
|
+
*/
|
|
9
|
+
import TYPERT_REMOTE from './typert.remote-client.js'
|
|
10
|
+
|
|
11
|
+
export const TYPERT = {
|
|
12
|
+
package: '@qihongmu/dsh-plugins-scheduled-task',
|
|
13
|
+
face: 'host',
|
|
14
|
+
schemas: [],
|
|
15
|
+
invocations: TYPERT_REMOTE.descriptors,
|
|
16
|
+
model: {
|
|
17
|
+
services: [
|
|
18
|
+
{
|
|
19
|
+
key: 'scheduledTasks',
|
|
20
|
+
exportName: 'ScheduledTaskService',
|
|
21
|
+
summary: 'Global scheduled tasks: durable registry, scheduler, delivery, Remote surface.',
|
|
22
|
+
tags: [],
|
|
23
|
+
members: [
|
|
24
|
+
{ name: 'list', signature: '() => Promise<RemoteResult<ScheduledTaskView[]>>', kind: 'method' },
|
|
25
|
+
{ name: 'create', signature: '(input: ScheduledTaskCreateInput) => Promise<RemoteResult<ScheduledTaskMutationResult>>', kind: 'method' },
|
|
26
|
+
{ name: 'update', signature: '(id: ScheduledTaskId, input: ScheduledTaskUpdateInput) => Promise<RemoteResult<ScheduledTaskMutationResult>>', kind: 'method' },
|
|
27
|
+
{ name: 'setStatus', signature: '(id: ScheduledTaskId, status: ScheduledTaskSettableStatus) => Promise<RemoteResult<ScheduledTaskMutationResult>>', kind: 'method' },
|
|
28
|
+
{ name: 'delete', signature: '(id: ScheduledTaskId) => Promise<RemoteResult<ScheduledTaskDeleteResult>>', kind: 'method' },
|
|
29
|
+
{ name: 'markRead', signature: '(id: ScheduledTaskId) => Promise<RemoteResult<null>>', kind: 'method' },
|
|
30
|
+
],
|
|
31
|
+
types: [],
|
|
32
|
+
},
|
|
33
|
+
],
|
|
34
|
+
events: [],
|
|
35
|
+
objects: [],
|
|
36
|
+
},
|
|
37
|
+
}
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
/* Generated by @deepseek-ai/dsh-typert-generator from the Host FaceModel — do not edit.
|
|
2
|
+
* Vendored by hand for this external plugin (the DSH generator cannot see external
|
|
3
|
+
* repos); kept in sync with host/src/types.ts. Shared sub-schemas are factored into
|
|
4
|
+
* constants so the rule/view union only lives in one place.
|
|
5
|
+
*/
|
|
6
|
+
import { z } from 'zod'
|
|
7
|
+
|
|
8
|
+
const $id = z.intersection(z.string(), z.unknown())
|
|
9
|
+
const $status = z.union([z.literal("active"), z.literal("paused"), z.literal("completed")])
|
|
10
|
+
const $state = z.union([z.literal("scheduled"), z.literal("overdue"), z.literal("completed")])
|
|
11
|
+
const $model = z.object({
|
|
12
|
+
'provider': z.string(),
|
|
13
|
+
'model': z.string(),
|
|
14
|
+
})
|
|
15
|
+
const $errorCode = z.union([z.literal("invalid_title"), z.literal("invalid_prompt"), z.literal("invalid_selector"), z.literal("invalid_rule"), z.literal("invalid_time_zone"), z.literal("not_future"), z.literal("time_out_of_range"), z.literal("frequency_too_high"), z.literal("task_not_found"), z.literal("internal_error")])
|
|
16
|
+
const $failure = z.object({
|
|
17
|
+
'ok': z.literal(false),
|
|
18
|
+
'code': $errorCode,
|
|
19
|
+
'message': z.string(),
|
|
20
|
+
})
|
|
21
|
+
const $rule = z.union([z.object({
|
|
22
|
+
'kind': z.literal("after"),
|
|
23
|
+
'afterSeconds': z.number(),
|
|
24
|
+
'scheduledAt': z.string(),
|
|
25
|
+
}), z.object({
|
|
26
|
+
'kind': z.literal("at"),
|
|
27
|
+
'scheduledAt': z.string(),
|
|
28
|
+
}), z.object({
|
|
29
|
+
'kind': z.literal("every"),
|
|
30
|
+
'everySeconds': z.number(),
|
|
31
|
+
'scheduledAt': z.string(),
|
|
32
|
+
}), z.object({
|
|
33
|
+
'kind': z.literal("hourly"),
|
|
34
|
+
'minute': z.number(),
|
|
35
|
+
'scheduledAt': z.string(),
|
|
36
|
+
}), z.object({
|
|
37
|
+
'kind': z.literal("daily"),
|
|
38
|
+
'time': z.string(),
|
|
39
|
+
'time_zone': z.string(),
|
|
40
|
+
'scheduledAt': z.string(),
|
|
41
|
+
}), z.object({
|
|
42
|
+
'kind': z.literal("weekly"),
|
|
43
|
+
'weekdays': z.array(z.number()),
|
|
44
|
+
'time': z.string(),
|
|
45
|
+
'time_zone': z.string(),
|
|
46
|
+
'scheduledAt': z.string(),
|
|
47
|
+
}), z.object({
|
|
48
|
+
'kind': z.literal("monthly"),
|
|
49
|
+
'dayOfMonth': z.number(),
|
|
50
|
+
'time': z.string(),
|
|
51
|
+
'time_zone': z.string(),
|
|
52
|
+
'scheduledAt': z.string(),
|
|
53
|
+
})])
|
|
54
|
+
const $taskView = z.object({
|
|
55
|
+
'id': $id,
|
|
56
|
+
'title': z.string(),
|
|
57
|
+
'prompt': z.string(),
|
|
58
|
+
'rule': $rule,
|
|
59
|
+
'status': $status,
|
|
60
|
+
'sessionId': $id,
|
|
61
|
+
'createdAt': z.string(),
|
|
62
|
+
'lastRunAt': z.string().optional(),
|
|
63
|
+
'nextRunAt': z.string().optional(),
|
|
64
|
+
'lastError': z.object({
|
|
65
|
+
at: z.string(),
|
|
66
|
+
message: z.string().min(1),
|
|
67
|
+
}).optional(),
|
|
68
|
+
'state': $state,
|
|
69
|
+
'unread': z.boolean(),
|
|
70
|
+
'workspaceId': z.string().optional(),
|
|
71
|
+
'cwd': z.string().optional(),
|
|
72
|
+
'model': $model.optional(),
|
|
73
|
+
'confirmBeforeChange': z.boolean(),
|
|
74
|
+
})
|
|
75
|
+
const $mutationResult = z.union([z.object({
|
|
76
|
+
'ok': z.literal(true),
|
|
77
|
+
'task': $taskView,
|
|
78
|
+
}), $failure])
|
|
79
|
+
const $carryFields = {
|
|
80
|
+
'prompt': z.string().optional(),
|
|
81
|
+
'workspaceId': z.string().optional(),
|
|
82
|
+
'cwd': z.string().optional(),
|
|
83
|
+
'model': $model.optional(),
|
|
84
|
+
'confirmBeforeChange': z.boolean().optional(),
|
|
85
|
+
}
|
|
86
|
+
const $selectorFields = {
|
|
87
|
+
'after_seconds': z.number().optional(),
|
|
88
|
+
'at': z.object({
|
|
89
|
+
'date': z.string(),
|
|
90
|
+
'time': z.string(),
|
|
91
|
+
'time_zone': z.string(),
|
|
92
|
+
}).optional(),
|
|
93
|
+
'every_seconds': z.number().optional(),
|
|
94
|
+
'hourly': z.object({
|
|
95
|
+
'minute': z.number(),
|
|
96
|
+
}).optional(),
|
|
97
|
+
'daily': z.object({
|
|
98
|
+
'time': z.string(),
|
|
99
|
+
'time_zone': z.string(),
|
|
100
|
+
}).optional(),
|
|
101
|
+
'weekly': z.object({
|
|
102
|
+
'weekdays': z.array(z.number()),
|
|
103
|
+
'time': z.string(),
|
|
104
|
+
'time_zone': z.string(),
|
|
105
|
+
}).optional(),
|
|
106
|
+
'monthly': z.object({
|
|
107
|
+
'dayOfMonth': z.number(),
|
|
108
|
+
'time': z.string(),
|
|
109
|
+
'time_zone': z.string(),
|
|
110
|
+
}).optional(),
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const _deepseek_ai_dsh_scheduled_task_scheduledTasks_create_parameter_0$schema = z.object({
|
|
114
|
+
'title': z.string(),
|
|
115
|
+
...$carryFields,
|
|
116
|
+
...$selectorFields,
|
|
117
|
+
})
|
|
118
|
+
const _deepseek_ai_dsh_scheduled_task_scheduledTasks_create_result$schema = $mutationResult
|
|
119
|
+
const _deepseek_ai_dsh_scheduled_task_scheduledTasks_delete_parameter_0$schema = $id
|
|
120
|
+
const _deepseek_ai_dsh_scheduled_task_scheduledTasks_delete_result$schema = z.union([z.object({
|
|
121
|
+
'ok': z.literal(true),
|
|
122
|
+
'deleted': z.boolean(),
|
|
123
|
+
}), $failure])
|
|
124
|
+
const _deepseek_ai_dsh_scheduled_task_scheduledTasks_list_result$schema = z.array($taskView)
|
|
125
|
+
const _deepseek_ai_dsh_scheduled_task_scheduledTasks_markRead_parameter_0$schema = $id
|
|
126
|
+
const _deepseek_ai_dsh_scheduled_task_scheduledTasks_markRead_result$schema = z.literal(null)
|
|
127
|
+
const _deepseek_ai_dsh_scheduled_task_scheduledTasks_setStatus_parameter_0$schema = $id
|
|
128
|
+
const _deepseek_ai_dsh_scheduled_task_scheduledTasks_setStatus_parameter_1$schema = z.union([z.literal("active"), z.literal("paused")])
|
|
129
|
+
const _deepseek_ai_dsh_scheduled_task_scheduledTasks_setStatus_result$schema = $mutationResult
|
|
130
|
+
const _deepseek_ai_dsh_scheduled_task_scheduledTasks_update_parameter_0$schema = $id
|
|
131
|
+
const _deepseek_ai_dsh_scheduled_task_scheduledTasks_update_parameter_1$schema = z.object({
|
|
132
|
+
'title': z.string().optional(),
|
|
133
|
+
...$carryFields,
|
|
134
|
+
...$selectorFields,
|
|
135
|
+
})
|
|
136
|
+
const _deepseek_ai_dsh_scheduled_task_scheduledTasks_update_result$schema = $mutationResult
|
|
137
|
+
|
|
138
|
+
export const TYPERT_REMOTE = {
|
|
139
|
+
package: '@qihongmu/dsh-plugins-scheduled-task',
|
|
140
|
+
descriptors: [
|
|
141
|
+
{
|
|
142
|
+
id: '@qihongmu/dsh-plugins-scheduled-task#scheduledTasks/create',
|
|
143
|
+
service: 'scheduledTasks',
|
|
144
|
+
namespace: 'scheduledTasks',
|
|
145
|
+
method: 'create',
|
|
146
|
+
invocation: { kind: 'direct' },
|
|
147
|
+
parameters: [
|
|
148
|
+
{
|
|
149
|
+
name: 'input',
|
|
150
|
+
wire: 'input',
|
|
151
|
+
source: 'json',
|
|
152
|
+
codec: {
|
|
153
|
+
mode: 'strict',
|
|
154
|
+
typeSymbol: '@qihongmu/dsh-plugins-scheduled-task/types#ScheduledTaskCreateInput',
|
|
155
|
+
create: () => _deepseek_ai_dsh_scheduled_task_scheduledTasks_create_parameter_0$schema,
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
],
|
|
159
|
+
result: {
|
|
160
|
+
mode: 'strict',
|
|
161
|
+
typeSymbol: '@qihongmu/dsh-plugins-scheduled-task/types#ScheduledTaskMutationResult',
|
|
162
|
+
create: () => _deepseek_ai_dsh_scheduled_task_scheduledTasks_create_result$schema,
|
|
163
|
+
},
|
|
164
|
+
sourceLocation: {"file":"packages/scheduled-task/host/src/index.ts","line":166,"column":3},
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
id: '@qihongmu/dsh-plugins-scheduled-task#scheduledTasks/delete',
|
|
168
|
+
service: 'scheduledTasks',
|
|
169
|
+
namespace: 'scheduledTasks',
|
|
170
|
+
method: 'delete',
|
|
171
|
+
invocation: { kind: 'direct' },
|
|
172
|
+
parameters: [
|
|
173
|
+
{
|
|
174
|
+
name: 'id',
|
|
175
|
+
wire: 'id',
|
|
176
|
+
source: 'json',
|
|
177
|
+
codec: {
|
|
178
|
+
mode: 'strict',
|
|
179
|
+
typeSymbol: '@qihongmu/dsh-plugins-scheduled-task/types#ScheduledTaskId',
|
|
180
|
+
create: () => _deepseek_ai_dsh_scheduled_task_scheduledTasks_delete_parameter_0$schema,
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
],
|
|
184
|
+
result: {
|
|
185
|
+
mode: 'strict',
|
|
186
|
+
typeSymbol: '@qihongmu/dsh-plugins-scheduled-task/types#ScheduledTaskDeleteResult',
|
|
187
|
+
create: () => _deepseek_ai_dsh_scheduled_task_scheduledTasks_delete_result$schema,
|
|
188
|
+
},
|
|
189
|
+
sourceLocation: {"file":"packages/scheduled-task/host/src/index.ts","line":244,"column":3},
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
id: '@qihongmu/dsh-plugins-scheduled-task#scheduledTasks/list',
|
|
193
|
+
service: 'scheduledTasks',
|
|
194
|
+
namespace: 'scheduledTasks',
|
|
195
|
+
method: 'list',
|
|
196
|
+
invocation: { kind: 'direct' },
|
|
197
|
+
parameters: [
|
|
198
|
+
],
|
|
199
|
+
result: {
|
|
200
|
+
mode: 'strict',
|
|
201
|
+
typeSymbol: '@qihongmu/dsh-plugins-scheduled-task#scheduledTasks/list:result',
|
|
202
|
+
create: () => _deepseek_ai_dsh_scheduled_task_scheduledTasks_list_result$schema,
|
|
203
|
+
},
|
|
204
|
+
sourceLocation: {"file":"packages/scheduled-task/host/src/index.ts","line":151,"column":3},
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
id: '@qihongmu/dsh-plugins-scheduled-task#scheduledTasks/markRead',
|
|
208
|
+
service: 'scheduledTasks',
|
|
209
|
+
namespace: 'scheduledTasks',
|
|
210
|
+
method: 'markRead',
|
|
211
|
+
invocation: { kind: 'direct' },
|
|
212
|
+
parameters: [
|
|
213
|
+
{
|
|
214
|
+
name: 'id',
|
|
215
|
+
wire: 'id',
|
|
216
|
+
source: 'json',
|
|
217
|
+
codec: {
|
|
218
|
+
mode: 'strict',
|
|
219
|
+
typeSymbol: '@qihongmu/dsh-plugins-scheduled-task/types#ScheduledTaskId',
|
|
220
|
+
create: () => _deepseek_ai_dsh_scheduled_task_scheduledTasks_markRead_parameter_0$schema,
|
|
221
|
+
},
|
|
222
|
+
},
|
|
223
|
+
],
|
|
224
|
+
result: {
|
|
225
|
+
mode: 'strict',
|
|
226
|
+
typeSymbol: '@qihongmu/dsh-plugins-scheduled-task#scheduledTasks/markRead:result',
|
|
227
|
+
create: () => _deepseek_ai_dsh_scheduled_task_scheduledTasks_markRead_result$schema,
|
|
228
|
+
},
|
|
229
|
+
sourceLocation: {"file":"packages/scheduled-task/host/src/index.ts","line":268,"column":3},
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
id: '@qihongmu/dsh-plugins-scheduled-task#scheduledTasks/setStatus',
|
|
233
|
+
service: 'scheduledTasks',
|
|
234
|
+
namespace: 'scheduledTasks',
|
|
235
|
+
method: 'setStatus',
|
|
236
|
+
invocation: { kind: 'direct' },
|
|
237
|
+
parameters: [
|
|
238
|
+
{
|
|
239
|
+
name: 'id',
|
|
240
|
+
wire: 'id',
|
|
241
|
+
source: 'json',
|
|
242
|
+
codec: {
|
|
243
|
+
mode: 'strict',
|
|
244
|
+
typeSymbol: '@qihongmu/dsh-plugins-scheduled-task/types#ScheduledTaskId',
|
|
245
|
+
create: () => _deepseek_ai_dsh_scheduled_task_scheduledTasks_setStatus_parameter_0$schema,
|
|
246
|
+
},
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
name: 'status',
|
|
250
|
+
wire: 'status',
|
|
251
|
+
source: 'json',
|
|
252
|
+
codec: {
|
|
253
|
+
mode: 'strict',
|
|
254
|
+
typeSymbol: '@qihongmu/dsh-plugins-scheduled-task/types#ScheduledTaskSettableStatus',
|
|
255
|
+
create: () => _deepseek_ai_dsh_scheduled_task_scheduledTasks_setStatus_parameter_1$schema,
|
|
256
|
+
},
|
|
257
|
+
},
|
|
258
|
+
],
|
|
259
|
+
result: {
|
|
260
|
+
mode: 'strict',
|
|
261
|
+
typeSymbol: '@qihongmu/dsh-plugins-scheduled-task/types#ScheduledTaskMutationResult',
|
|
262
|
+
create: () => _deepseek_ai_dsh_scheduled_task_scheduledTasks_setStatus_result$schema,
|
|
263
|
+
},
|
|
264
|
+
sourceLocation: {"file":"packages/scheduled-task/host/src/index.ts","line":222,"column":3},
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
id: '@qihongmu/dsh-plugins-scheduled-task#scheduledTasks/update',
|
|
268
|
+
service: 'scheduledTasks',
|
|
269
|
+
namespace: 'scheduledTasks',
|
|
270
|
+
method: 'update',
|
|
271
|
+
invocation: { kind: 'direct' },
|
|
272
|
+
parameters: [
|
|
273
|
+
{
|
|
274
|
+
name: 'id',
|
|
275
|
+
wire: 'id',
|
|
276
|
+
source: 'json',
|
|
277
|
+
codec: {
|
|
278
|
+
mode: 'strict',
|
|
279
|
+
typeSymbol: '@qihongmu/dsh-plugins-scheduled-task/types#ScheduledTaskId',
|
|
280
|
+
create: () => _deepseek_ai_dsh_scheduled_task_scheduledTasks_update_parameter_0$schema,
|
|
281
|
+
},
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
name: 'input',
|
|
285
|
+
wire: 'input',
|
|
286
|
+
source: 'json',
|
|
287
|
+
codec: {
|
|
288
|
+
mode: 'strict',
|
|
289
|
+
typeSymbol: '@qihongmu/dsh-plugins-scheduled-task/types#ScheduledTaskUpdateInput',
|
|
290
|
+
create: () => _deepseek_ai_dsh_scheduled_task_scheduledTasks_update_parameter_1$schema,
|
|
291
|
+
},
|
|
292
|
+
},
|
|
293
|
+
],
|
|
294
|
+
result: {
|
|
295
|
+
mode: 'strict',
|
|
296
|
+
typeSymbol: '@qihongmu/dsh-plugins-scheduled-task/types#ScheduledTaskMutationResult',
|
|
297
|
+
create: () => _deepseek_ai_dsh_scheduled_task_scheduledTasks_update_result$schema,
|
|
298
|
+
},
|
|
299
|
+
sourceLocation: {"file":"packages/scheduled-task/host/src/index.ts","line":200,"column":3},
|
|
300
|
+
},
|
|
301
|
+
],
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
export default TYPERT_REMOTE
|
package/lib/types/domain.js
CHANGED
|
@@ -89,7 +89,7 @@ function translate(selector) {
|
|
|
89
89
|
const TIME_RE = /^([01]\d|2[0-3]):([0-5]\d)$/;
|
|
90
90
|
/** Convert `date` (YYYY-MM-DD) + `time` (HH:mm or HH:mm:ss) in `timeZone` to canonical epoch ms. */
|
|
91
91
|
function atInstant(date, time, timeZone, now) {
|
|
92
|
-
const record = createAtScheduleRecord(ScheduleId('task'), 'task', { date, time: normalizeTime(time), time_zone: timeZone }, now);
|
|
92
|
+
const record = createAtScheduleRecord(ScheduleId('task'), 'task', { date, time: normalizeTime(time), time_zone: timeZone }, now, 'task');
|
|
93
93
|
return Date.parse(record.scheduledAt);
|
|
94
94
|
}
|
|
95
95
|
/**
|
|
@@ -218,18 +218,18 @@ export function buildRule(input, now) {
|
|
|
218
218
|
return {
|
|
219
219
|
kind: 'after',
|
|
220
220
|
afterSeconds: input.after_seconds,
|
|
221
|
-
scheduledAt: translate(() => createAfterScheduleRecord(ScheduleId('task'), 'task', input.after_seconds, now)),
|
|
221
|
+
scheduledAt: translate(() => createAfterScheduleRecord(ScheduleId('task'), 'task', input.after_seconds, now, 'task')),
|
|
222
222
|
};
|
|
223
223
|
}
|
|
224
224
|
if (input.at !== undefined) {
|
|
225
225
|
const at = validateAt(input.at);
|
|
226
|
-
return { kind: 'at', scheduledAt: translate(() => createAtScheduleRecord(ScheduleId('task'), 'task', at, now)) };
|
|
226
|
+
return { kind: 'at', scheduledAt: translate(() => createAtScheduleRecord(ScheduleId('task'), 'task', at, now, 'task')) };
|
|
227
227
|
}
|
|
228
228
|
if (input.every_seconds !== undefined) {
|
|
229
229
|
return {
|
|
230
230
|
kind: 'every',
|
|
231
231
|
everySeconds: input.every_seconds,
|
|
232
|
-
scheduledAt: translate(() => createEveryScheduleRecord(ScheduleId('task'), 'task', input.every_seconds, now)),
|
|
232
|
+
scheduledAt: translate(() => createEveryScheduleRecord(ScheduleId('task'), 'task', input.every_seconds, now, 'task')),
|
|
233
233
|
};
|
|
234
234
|
}
|
|
235
235
|
if (input.hourly !== undefined) {
|
package/lib/types/index.d.ts
CHANGED
|
@@ -6,6 +6,14 @@
|
|
|
6
6
|
import { Service } from '@deepseek-ai/cordis';
|
|
7
7
|
import type { Context } from '@deepseek-ai/cordis';
|
|
8
8
|
import { TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol';
|
|
9
|
+
import type { ContextFormed } from '@deepseek-ai/dsh-llm';
|
|
10
|
+
declare module '@deepseek-ai/dsh-llm' {
|
|
11
|
+
interface MessageSourceMap {
|
|
12
|
+
'scheduled-task': {
|
|
13
|
+
kind: 'scheduled-task';
|
|
14
|
+
} & ContextFormed;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
9
17
|
import type { ScheduledTaskRecord } from './spec.ts';
|
|
10
18
|
import type { ScheduledTaskCreateInput, ScheduledTaskDeleteResult, ScheduledTaskId as ScheduledTaskIdType, ScheduledTaskMutationResult, ScheduledTaskSettableStatus, ScheduledTaskUpdateInput, ScheduledTaskView } from './types.ts';
|
|
11
19
|
export { ScheduledTaskError, ScheduledTaskId, advanceRule, buildRule, renderTaskFraming, scheduledTaskView, validatePrompt, validateTitle, } from './domain.ts';
|
|
@@ -29,6 +37,7 @@ export declare function nextDelayMs(records: Iterable<readonly [ScheduledTaskIdT
|
|
|
29
37
|
* the single timer is a disposable projection of the earliest due target. Each
|
|
30
38
|
* task's run Session is created lazily on first fire and kept live while the
|
|
31
39
|
* process runs.
|
|
40
|
+
* @typert service scheduledTasks
|
|
32
41
|
*/
|
|
33
42
|
export declare class ScheduledTaskService extends TypertRemoteService {
|
|
34
43
|
/** Services required before tasks can be listed, mutated, or fired. */
|
|
@@ -36,6 +45,16 @@ export declare class ScheduledTaskService extends TypertRemoteService {
|
|
|
36
45
|
private table?;
|
|
37
46
|
private timer;
|
|
38
47
|
private readonly handles;
|
|
48
|
+
/** The composition each retained handle carries: its mutable model
|
|
49
|
+
* selection (edits reach the live agent through this ref) and the preset
|
|
50
|
+
* id mounted at compose time (a changed default forces recomposition). */
|
|
51
|
+
private readonly compositions;
|
|
52
|
+
/** Tasks with a fire currently in flight. Composing a run agent takes
|
|
53
|
+
* seconds (a first preset standing mount), and the record only stops
|
|
54
|
+
* being due at the END of fireOne — a rearm in between (an edit, another
|
|
55
|
+
* task's mutation) would otherwise admit the SAME due task twice and
|
|
56
|
+
* compose two live agents for one session id. */
|
|
57
|
+
private readonly firing;
|
|
39
58
|
/** Last task title pinned onto each run session (avoids re-rename spam). */
|
|
40
59
|
private readonly appliedTitles;
|
|
41
60
|
/** Run sessions already attached to their bound workspace (avoids re-attach churn). */
|
|
@@ -106,12 +125,33 @@ export declare class ScheduledTaskService extends TypertRemoteService {
|
|
|
106
125
|
* re-pins only when the stored title changed since the last applied pin.
|
|
107
126
|
*/
|
|
108
127
|
private pinSessionTitle;
|
|
128
|
+
/**
|
|
129
|
+
* Resolve the model selection for one run: the task's stored model when it
|
|
130
|
+
* names one, else the deployment's current default. Returns `undefined` when
|
|
131
|
+
* neither exists (legacy shape: the request waterfall picks a model).
|
|
132
|
+
*/
|
|
133
|
+
private runSelection;
|
|
134
|
+
/**
|
|
135
|
+
* Resolve the default agent preset for one run. Its standing mount is what
|
|
136
|
+
* makes the standard toolset visible — an agent composed without it gets a
|
|
137
|
+
* bare scope with ZERO tools and answers in one text-only step (the "task
|
|
138
|
+
* finishes in seconds" failure). Returns `undefined` when the deployment
|
|
139
|
+
* provides no preset roster at all; a present-but-broken roster throws so
|
|
140
|
+
* the fire records the reason instead of silently running tool-less.
|
|
141
|
+
*/
|
|
142
|
+
private composeRun;
|
|
109
143
|
/**
|
|
110
144
|
* Resolve the live run Agent for a task's CURRENT project. The durable
|
|
111
145
|
* session identity is derived from the task id + cwd (`runSessionIdOf`), so
|
|
112
146
|
* an edited project transparently starts a fresh conversation inside the new
|
|
113
147
|
* workspace while returning to an earlier project resumes that project's
|
|
114
148
|
* existing history. Returns the possibly-new sessionId for persistence.
|
|
149
|
+
*
|
|
150
|
+
* The agent is composed like a host-owned conversation: the default preset's
|
|
151
|
+
* toolset plus the bound model selection (the preset's prompt assembly reads
|
|
152
|
+
* `{{model}}` from it). Edits are picked up on the next fire: a model change
|
|
153
|
+
* reaches the live agent IN PLACE through the installed selection ref, a
|
|
154
|
+
* preset or project change recomposes via dispose + resume.
|
|
115
155
|
*/
|
|
116
156
|
private ensureAgent;
|
|
117
157
|
/** Cancel the timer and dispose every retained run Agent. */
|
package/lib/types/index.js
CHANGED
|
@@ -39,6 +39,7 @@ var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn,
|
|
|
39
39
|
};
|
|
40
40
|
import { createHash, randomUUID } from 'node:crypto';
|
|
41
41
|
import { Service } from '@deepseek-ai/cordis';
|
|
42
|
+
import { installModelSelection } from '@deepseek-ai/dsh-agent';
|
|
42
43
|
import { createUserMessage } from '@deepseek-ai/dsh-llm';
|
|
43
44
|
import { SessionId } from '@deepseek-ai/dsh-session';
|
|
44
45
|
import { WorkspaceId } from '@deepseek-ai/dsh-workspace';
|
|
@@ -97,6 +98,17 @@ function isMissingSessionError(error) {
|
|
|
97
98
|
return /\bnot found\b|\bdoes not exist\b|\bno such session\b|\bunknown session\b/
|
|
98
99
|
.test(renderThrown(error).toLowerCase());
|
|
99
100
|
}
|
|
101
|
+
/**
|
|
102
|
+
* Whether two model selections name the same route; `undefined` on both sides
|
|
103
|
+
* counts as equal (no selection bound).
|
|
104
|
+
*/
|
|
105
|
+
function selectionEquals(left, right) {
|
|
106
|
+
if (left === undefined || right === undefined)
|
|
107
|
+
return left === right;
|
|
108
|
+
return left.provider === right.provider
|
|
109
|
+
&& left.model === right.model
|
|
110
|
+
&& left.reasoningEffort === right.reasoningEffort;
|
|
111
|
+
}
|
|
100
112
|
/**
|
|
101
113
|
* Deterministic run-session identity for one task+project pair. Deriving the id
|
|
102
114
|
* from the cwd makes a project edit migrate the task to a FRESH conversation in
|
|
@@ -132,6 +144,7 @@ function applyCarry(target, carry) {
|
|
|
132
144
|
* the single timer is a disposable projection of the earliest due target. Each
|
|
133
145
|
* task's run Session is created lazily on first fire and kept live while the
|
|
134
146
|
* process runs.
|
|
147
|
+
* @typert service scheduledTasks
|
|
135
148
|
*/
|
|
136
149
|
let ScheduledTaskService = (() => {
|
|
137
150
|
let _classSuper = TypertRemoteService;
|
|
@@ -164,6 +177,16 @@ let ScheduledTaskService = (() => {
|
|
|
164
177
|
table = __runInitializers(this, _instanceExtraInitializers);
|
|
165
178
|
timer;
|
|
166
179
|
handles = new Map();
|
|
180
|
+
/** The composition each retained handle carries: its mutable model
|
|
181
|
+
* selection (edits reach the live agent through this ref) and the preset
|
|
182
|
+
* id mounted at compose time (a changed default forces recomposition). */
|
|
183
|
+
compositions = new Map();
|
|
184
|
+
/** Tasks with a fire currently in flight. Composing a run agent takes
|
|
185
|
+
* seconds (a first preset standing mount), and the record only stops
|
|
186
|
+
* being due at the END of fireOne — a rearm in between (an edit, another
|
|
187
|
+
* task's mutation) would otherwise admit the SAME due task twice and
|
|
188
|
+
* compose two live agents for one session id. */
|
|
189
|
+
firing = new Set();
|
|
167
190
|
/** Last task title pinned onto each run session (avoids re-rename spam). */
|
|
168
191
|
appliedTitles = new Map();
|
|
169
192
|
/** Run sessions already attached to their bound workspace (avoids re-attach churn). */
|
|
@@ -284,6 +307,7 @@ let ScheduledTaskService = (() => {
|
|
|
284
307
|
this.handles.delete(id);
|
|
285
308
|
await handle.dispose();
|
|
286
309
|
}
|
|
310
|
+
this.compositions.delete(id);
|
|
287
311
|
this.failures.delete(id);
|
|
288
312
|
this.rearm();
|
|
289
313
|
}
|
|
@@ -354,6 +378,9 @@ let ScheduledTaskService = (() => {
|
|
|
354
378
|
}
|
|
355
379
|
/** Queue one task run, advance its durable record, and flush its Session. */
|
|
356
380
|
async fireOne(id, record, now) {
|
|
381
|
+
if (this.firing.has(id))
|
|
382
|
+
return;
|
|
383
|
+
this.firing.add(id);
|
|
357
384
|
try {
|
|
358
385
|
const { agent, sessionId } = await this.ensureAgent(record);
|
|
359
386
|
// Pin the conversation title to the task title (re-pins only on change,
|
|
@@ -366,7 +393,7 @@ let ScheduledTaskService = (() => {
|
|
|
366
393
|
setApprovalPolicy(agent.session, record.confirmBeforeChange ? 'ask' : 'never');
|
|
367
394
|
agent.followup(createUserMessage({
|
|
368
395
|
content: [{ type: 'text', text: renderTaskFraming(record) }],
|
|
369
|
-
source: { kind: '
|
|
396
|
+
source: { kind: 'scheduled-task' },
|
|
370
397
|
}));
|
|
371
398
|
const runAt = new Date(now).toISOString();
|
|
372
399
|
const advanced = advanceRule(record.rule, now);
|
|
@@ -403,6 +430,9 @@ let ScheduledTaskService = (() => {
|
|
|
403
430
|
this.ctx.logger.warn(`scheduled-task: could not persist failure of task "${id}": ${renderThrown(persistError)}`);
|
|
404
431
|
}
|
|
405
432
|
}
|
|
433
|
+
finally {
|
|
434
|
+
this.firing.delete(id);
|
|
435
|
+
}
|
|
406
436
|
}
|
|
407
437
|
/**
|
|
408
438
|
* Attach the run session to the task's bound workspace so the conversation
|
|
@@ -447,26 +477,88 @@ let ScheduledTaskService = (() => {
|
|
|
447
477
|
this.ctx.logger.warn(`scheduled-task: could not title session "${sessionId}": ${renderThrown(error)}`);
|
|
448
478
|
}
|
|
449
479
|
}
|
|
480
|
+
/**
|
|
481
|
+
* Resolve the model selection for one run: the task's stored model when it
|
|
482
|
+
* names one, else the deployment's current default. Returns `undefined` when
|
|
483
|
+
* neither exists (legacy shape: the request waterfall picks a model).
|
|
484
|
+
*/
|
|
485
|
+
runSelection(record) {
|
|
486
|
+
if (record.model !== undefined)
|
|
487
|
+
return { provider: record.model.provider, model: record.model.model };
|
|
488
|
+
const defaults = this.ctx.get('agentDefaultModel');
|
|
489
|
+
return defaults?.currentSelection();
|
|
490
|
+
}
|
|
491
|
+
/**
|
|
492
|
+
* Resolve the default agent preset for one run. Its standing mount is what
|
|
493
|
+
* makes the standard toolset visible — an agent composed without it gets a
|
|
494
|
+
* bare scope with ZERO tools and answers in one text-only step (the "task
|
|
495
|
+
* finishes in seconds" failure). Returns `undefined` when the deployment
|
|
496
|
+
* provides no preset roster at all; a present-but-broken roster throws so
|
|
497
|
+
* the fire records the reason instead of silently running tool-less.
|
|
498
|
+
*/
|
|
499
|
+
async composeRun() {
|
|
500
|
+
const presets = this.ctx.get('agentPresets');
|
|
501
|
+
if (presets === undefined)
|
|
502
|
+
return undefined;
|
|
503
|
+
const preset = await presets.resolve(undefined);
|
|
504
|
+
if (preset.broken !== undefined) {
|
|
505
|
+
throw new Error(`default agent preset "${preset.id}" failed to activate: ${preset.broken}`);
|
|
506
|
+
}
|
|
507
|
+
return {
|
|
508
|
+
presetId: preset.id,
|
|
509
|
+
mountPreset: async (agentCtx) => {
|
|
510
|
+
await presets.mount(agentCtx, preset.id);
|
|
511
|
+
},
|
|
512
|
+
};
|
|
513
|
+
}
|
|
450
514
|
/**
|
|
451
515
|
* Resolve the live run Agent for a task's CURRENT project. The durable
|
|
452
516
|
* session identity is derived from the task id + cwd (`runSessionIdOf`), so
|
|
453
517
|
* an edited project transparently starts a fresh conversation inside the new
|
|
454
518
|
* workspace while returning to an earlier project resumes that project's
|
|
455
519
|
* existing history. Returns the possibly-new sessionId for persistence.
|
|
520
|
+
*
|
|
521
|
+
* The agent is composed like a host-owned conversation: the default preset's
|
|
522
|
+
* toolset plus the bound model selection (the preset's prompt assembly reads
|
|
523
|
+
* `{{model}}` from it). Edits are picked up on the next fire: a model change
|
|
524
|
+
* reaches the live agent IN PLACE through the installed selection ref, a
|
|
525
|
+
* preset or project change recomposes via dispose + resume.
|
|
456
526
|
*/
|
|
457
527
|
async ensureAgent(record) {
|
|
458
528
|
const desiredCwd = record.cwd ?? process.cwd();
|
|
459
529
|
const targetId = runSessionIdOf(record.id, desiredCwd);
|
|
460
|
-
const
|
|
530
|
+
const selection = this.runSelection(record);
|
|
531
|
+
const agentOptions = selection === undefined
|
|
461
532
|
? undefined
|
|
462
|
-
: { provider:
|
|
463
|
-
|
|
533
|
+
: { provider: selection.provider, model: selection.model };
|
|
534
|
+
const selectionRef = { current: selection, assembled: undefined };
|
|
535
|
+
const composition = await this.composeRun();
|
|
536
|
+
const presetMeta = composition === undefined ? {} : { agentPreset: composition.presetId };
|
|
537
|
+
const setup = selection === undefined && composition === undefined
|
|
538
|
+
? undefined
|
|
539
|
+
: async (agentCtx) => {
|
|
540
|
+
if (selection !== undefined)
|
|
541
|
+
installModelSelection(agentCtx, selectionRef);
|
|
542
|
+
if (composition !== undefined)
|
|
543
|
+
await composition.mountPreset(agentCtx);
|
|
544
|
+
};
|
|
545
|
+
const setupOption = setup === undefined ? {} : { setup };
|
|
546
|
+
// A retained run handle is reused only when it still matches the target
|
|
547
|
+
// AND carries the composition it was composed with.
|
|
464
548
|
const retained = this.handles.get(record.id);
|
|
465
549
|
if (retained !== undefined) {
|
|
466
|
-
|
|
550
|
+
const carried = this.compositions.get(record.id);
|
|
551
|
+
if (retained.agent.session.id === targetId
|
|
552
|
+
&& retained.agent.session.header.cwd === desiredCwd
|
|
553
|
+
&& carried !== undefined
|
|
554
|
+
&& carried.presetId === composition?.presetId) {
|
|
555
|
+
if (!selectionEquals(carried.selectionRef.current, selection)) {
|
|
556
|
+
carried.selectionRef.current = selection;
|
|
557
|
+
}
|
|
467
558
|
return { agent: retained.agent, sessionId: targetId };
|
|
468
559
|
}
|
|
469
560
|
this.handles.delete(record.id);
|
|
561
|
+
this.compositions.delete(record.id);
|
|
470
562
|
await retained.dispose();
|
|
471
563
|
}
|
|
472
564
|
let handle;
|
|
@@ -474,6 +566,7 @@ let ScheduledTaskService = (() => {
|
|
|
474
566
|
handle = await this.ctx.agents.resume({
|
|
475
567
|
resumeSessionId: targetId,
|
|
476
568
|
...agentOptions === undefined ? {} : { agentOptions },
|
|
569
|
+
...setupOption,
|
|
477
570
|
});
|
|
478
571
|
}
|
|
479
572
|
catch (resumeError) {
|
|
@@ -482,11 +575,16 @@ let ScheduledTaskService = (() => {
|
|
|
482
575
|
// Not persisted yet (first run for this task+project pair): create it in-project.
|
|
483
576
|
handle = await this.ctx.agents.create({
|
|
484
577
|
sessionId: targetId,
|
|
485
|
-
meta: { cwd: desiredCwd },
|
|
578
|
+
meta: { cwd: desiredCwd, ...presetMeta },
|
|
486
579
|
...agentOptions === undefined ? {} : { agentOptions },
|
|
580
|
+
...setupOption,
|
|
487
581
|
});
|
|
488
582
|
}
|
|
489
583
|
this.handles.set(record.id, handle);
|
|
584
|
+
this.compositions.set(record.id, {
|
|
585
|
+
selectionRef,
|
|
586
|
+
...composition === undefined ? {} : { presetId: composition.presetId },
|
|
587
|
+
});
|
|
490
588
|
return { agent: handle.agent, sessionId: targetId };
|
|
491
589
|
}
|
|
492
590
|
/** Cancel the timer and dispose every retained run Agent. */
|
|
@@ -498,6 +596,7 @@ let ScheduledTaskService = (() => {
|
|
|
498
596
|
}
|
|
499
597
|
const handles = [...this.handles.values()];
|
|
500
598
|
this.handles.clear();
|
|
599
|
+
this.compositions.clear();
|
|
501
600
|
await Promise.allSettled(handles.map(handle => handle.dispose()));
|
|
502
601
|
}
|
|
503
602
|
};
|
package/lib/types/spec.js
CHANGED
|
@@ -21,7 +21,8 @@ const atRule = z.object({
|
|
|
21
21
|
});
|
|
22
22
|
const everyRule = z.object({
|
|
23
23
|
kind: z.literal('every'),
|
|
24
|
-
|
|
24
|
+
// Mirrors dsh MIN_EVERY_INTERVAL_SECONDS (lowered 300 → 60 in 0.1.7).
|
|
25
|
+
everySeconds: z.number().int().min(60),
|
|
25
26
|
scheduledAt: z.string(),
|
|
26
27
|
});
|
|
27
28
|
const hourlyRule = z.object({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qihongmu/dsh-plugins-scheduled-task",
|
|
3
|
-
"version": "0.1.1-alpha.
|
|
3
|
+
"version": "0.1.1-alpha.3",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "Qihong <qihong.m.u+dev@gmail.com>",
|
|
6
6
|
"repository": {
|
|
@@ -26,7 +26,12 @@
|
|
|
26
26
|
"default": "./lib/types/types.js"
|
|
27
27
|
},
|
|
28
28
|
"./remote": {
|
|
29
|
-
"types": "./lib/typert.remote-client.d.ts"
|
|
29
|
+
"types": "./lib/typert.remote-client.d.ts",
|
|
30
|
+
"default": "./lib/typert.remote-client.js"
|
|
31
|
+
},
|
|
32
|
+
"./typert": {
|
|
33
|
+
"types": "./lib/typert.host.d.ts",
|
|
34
|
+
"default": "./lib/typert.host.js"
|
|
30
35
|
},
|
|
31
36
|
"./src/*": "./src/*",
|
|
32
37
|
"./package.json": "./package.json"
|
|
@@ -41,6 +46,8 @@
|
|
|
41
46
|
"peerDependencies": {
|
|
42
47
|
"@deepseek-ai/cordis": "*",
|
|
43
48
|
"@deepseek-ai/dsh-agent": "*",
|
|
49
|
+
"@deepseek-ai/dsh-agent-default-model": "*",
|
|
50
|
+
"@deepseek-ai/dsh-agent-preset-registry": "*",
|
|
44
51
|
"@deepseek-ai/dsh-brand": "*",
|
|
45
52
|
"@deepseek-ai/dsh-invariants": "*",
|
|
46
53
|
"@deepseek-ai/dsh-llm": "*",
|
|
@@ -56,6 +63,10 @@
|
|
|
56
63
|
},
|
|
57
64
|
"files": [
|
|
58
65
|
"lib",
|
|
66
|
+
"lib/typert.host.js",
|
|
67
|
+
"lib/typert.host.d.ts",
|
|
68
|
+
"lib/typert.remote-client.js",
|
|
69
|
+
"lib/typert.remote-client.d.ts",
|
|
59
70
|
"cordis.patch.yml",
|
|
60
71
|
"!lib/**/*.map",
|
|
61
72
|
"!lib/**/*.tsbuildinfo"
|