@goodandready/dsh-cron 0.2.27 → 0.2.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +43 -21
- package/README.ru.md +42 -19
- package/README.zh.md +42 -19
- package/lib/api-helpers.js +13 -3
- package/lib/api.js +65 -42
- package/lib/client.js +1 -1
- package/lib/cron-tool.js +402 -0
- package/lib/http-utils.js +186 -16
- package/lib/index.js +24 -293
- package/lib/prompt.js +7 -4
- package/lib/task-patch.js +4 -3
- package/package.json +2 -2
package/lib/index.js
CHANGED
|
@@ -92,64 +92,26 @@ export const name = '@goodandready/dsh-cron';
|
|
|
92
92
|
export const inject = ['tools', 'webServer', 'llm', 'agents', 'agentDefaultModel', 'credentials'];
|
|
93
93
|
|
|
94
94
|
import { executeCreateTask } from './task-create.js';
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
provider: { type: 'string', description: 'Model provider (optional)' },
|
|
104
|
-
inspectOnFailure: { type: 'boolean', description: 'Diagnose failed runs with a model and store the diagnosis with the run (agent tasks)' },
|
|
105
|
-
silentRule: { type: 'string', description: 'Plain-language condition for staying silent on a successful run (script/node/python/http tasks)' },
|
|
106
|
-
fallbackModel: { type: 'string', description: 'Model tried once more when the primary model fails (llm/skill/workflow tasks)' },
|
|
107
|
-
fallbackProvider: { type: 'string', description: 'Provider for the fallback model (defaults to the task provider)' },
|
|
108
|
-
model: { type: 'string', description: 'Model (optional)' },
|
|
109
|
-
timezone: { type: 'string', description: 'IANA time zone for the schedule, e.g. "Europe/Berlin" (optional, default = server local)' },
|
|
110
|
-
notifyTelegram: { type: 'boolean', description: 'Send the run report to Telegram' },
|
|
111
|
-
onlyOnFailure: { type: 'boolean', description: 'Send reports only for failures' },
|
|
112
|
-
timeoutSeconds: { type: 'number', description: 'Execution time limit in seconds' },
|
|
113
|
-
overlapPolicy: { type: 'string', enum: ['skip', 'queue', 'replace'], description: 'Overlap policy' },
|
|
114
|
-
misfirePolicy: { type: 'string', enum: ['skip', 'runOnce', 'catchUpAll'], description: 'What to do with a run missed during downtime' },
|
|
115
|
-
maxRetries: { type: 'number', description: 'Automatic retry attempts on failure (0 = off)' },
|
|
116
|
-
retryBackoffMs: { type: 'number', description: 'Base backoff between retries in ms, doubled per attempt' },
|
|
117
|
-
permissionPreset: { type: 'string', enum: ['default', 'read-only', 'workspace-write', 'full'], description: 'Permission preset applied to the task session' },
|
|
118
|
-
kanbanMode: { type: 'string', enum: ['none', 'on_failure', 'always'], description: 'Create a dsh-kanban card for runs' },
|
|
119
|
-
channels: { type: 'array', items: { type: 'string', enum: CHANNEL_IDS }, description: 'Notification channels for this task; empty = legacy telegram/kanban flags' },
|
|
120
|
-
template: { type: 'string', description: 'Notification template with {title} {status} {output} {error} {duration} placeholders; empty = built-in text' },
|
|
121
|
-
env: { type: 'object', additionalProperties: true, description: 'Environment variables for external runtimes (KEY: value); do not store secrets here' },
|
|
122
|
-
cwd: { type: 'string', description: 'Working directory for the run (external runtimes and agent sessions)' },
|
|
123
|
-
workspaceId: { type: 'string', description: 'Workspace bound to the task; resolved through the harness' },
|
|
124
|
-
worktree: { type: 'boolean', description: 'Run code-modifying agent tasks in an isolated git worktree' },
|
|
125
|
-
keepWorktree: { type: 'boolean', description: 'Keep the created worktree after the run' },
|
|
126
|
-
nodePath: { type: 'string', description: 'Node.js binary for node tasks (default: the harness runtime)' },
|
|
127
|
-
pythonPath: { type: 'string', description: 'Python interpreter for python tasks (default: auto-detected venv)' },
|
|
128
|
-
httpMethod: { type: 'string', description: 'HTTP method for http tasks' },
|
|
129
|
-
httpUrl: { type: 'string', description: 'URL for http tasks' },
|
|
130
|
-
httpHeaders: { type: 'object', additionalProperties: true, description: 'Headers for http tasks' },
|
|
131
|
-
httpBody: { type: 'string', description: 'Request body for http tasks' },
|
|
132
|
-
sshProfileId: { type: 'string', description: 'dsh-remote-workspace profile id that provides host and credentials' },
|
|
133
|
-
sshTarget: { type: 'string', description: 'Fallback user@host when no remote-workspace profile is used' },
|
|
134
|
-
sshPort: { type: 'number', description: 'SSH port (with sshTarget fallback)' },
|
|
135
|
-
sshKeyPath: { type: 'string', description: 'Private key path (with sshTarget fallback)' },
|
|
136
|
-
dockerImage: { type: 'string', description: 'Container image for docker tasks' },
|
|
137
|
-
skillName: { type: 'string', description: 'DSH skill name for skill tasks' },
|
|
138
|
-
workflowName: { type: 'string', description: 'DSH workflow name for workflow tasks' },
|
|
139
|
-
};
|
|
95
|
+
import {
|
|
96
|
+
cronToolParameters,
|
|
97
|
+
cronToolOutput,
|
|
98
|
+
executeCronTool,
|
|
99
|
+
rejectLegacyTool,
|
|
100
|
+
LEGACY_TOOL_NAMES,
|
|
101
|
+
LEGACY_ACTION_MAP,
|
|
102
|
+
} from './cron-tool.js';
|
|
140
103
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
}
|
|
150
|
-
},
|
|
151
|
-
render: (_args, val) => [{ type: 'text', text: val.message || JSON.stringify(val) }]
|
|
104
|
+
export {
|
|
105
|
+
executeCreateTask,
|
|
106
|
+
cronToolParameters,
|
|
107
|
+
cronToolOutput,
|
|
108
|
+
executeCronTool,
|
|
109
|
+
rejectLegacyTool,
|
|
110
|
+
LEGACY_TOOL_NAMES,
|
|
111
|
+
LEGACY_ACTION_MAP,
|
|
152
112
|
};
|
|
113
|
+
export const createTaskParameters = cronToolParameters;
|
|
114
|
+
export const createTaskOutput = cronToolOutput;
|
|
153
115
|
|
|
154
116
|
export function apply(ctx, rawConfig) {
|
|
155
117
|
const config = unwrapConfig(rawConfig);
|
|
@@ -286,243 +248,12 @@ export function apply(ctx, rawConfig) {
|
|
|
286
248
|
}
|
|
287
249
|
}), 'dsh-cron: /settings');
|
|
288
250
|
|
|
289
|
-
// 2. Tools
|
|
290
|
-
ctx.tools.register(defineTool({
|
|
291
|
-
name: 'cron_create_task',
|
|
292
|
-
description: 'Create a new scheduled cron task (cron schedule, or one-shot via "at: <ISO timestamp>" or "in 20m")',
|
|
293
|
-
parameters: createTaskParameters,
|
|
294
|
-
output: createTaskOutput,
|
|
295
|
-
execute: async (args) => executeCreateTask(store, scheduler, args)
|
|
296
|
-
}));
|
|
297
|
-
|
|
298
|
-
// Alias tool cron_schedule_task for compatibility with agent prompts (#70)
|
|
299
|
-
ctx.tools.register(defineTool({
|
|
300
|
-
name: 'cron_schedule_task',
|
|
301
|
-
description: 'Alias for cron_create_task: create a new scheduled cron task',
|
|
302
|
-
parameters: createTaskParameters,
|
|
303
|
-
output: createTaskOutput,
|
|
304
|
-
execute: async (args) => executeCreateTask(store, scheduler, args)
|
|
305
|
-
}));
|
|
306
|
-
|
|
307
|
-
ctx.tools.register(defineTool({
|
|
308
|
-
name: 'cron_list_tasks',
|
|
309
|
-
description: 'List all scheduled cron tasks',
|
|
310
|
-
parameters: {
|
|
311
|
-
status: { type: 'string', enum: ['all', 'active', 'paused', 'completed'], description: 'Filter by status' }
|
|
312
|
-
},
|
|
313
|
-
output: {
|
|
314
|
-
schema: {
|
|
315
|
-
type: 'object',
|
|
316
|
-
additionalProperties: true,
|
|
317
|
-
properties: {
|
|
318
|
-
count: { type: 'number' },
|
|
319
|
-
tasks: { type: 'array', items: { type: 'object', additionalProperties: true } }
|
|
320
|
-
}
|
|
321
|
-
},
|
|
322
|
-
render: (_args, val) => [{ type: 'text', text: `Total tasks: ${val.count}` }]
|
|
323
|
-
},
|
|
324
|
-
execute: async (args) => {
|
|
325
|
-
const tasks = store.list({ status: args.status || 'all' });
|
|
326
|
-
return {
|
|
327
|
-
count: tasks.length,
|
|
328
|
-
tasks: tasks.map(t => ({
|
|
329
|
-
id: t.id,
|
|
330
|
-
title: t.title,
|
|
331
|
-
schedule: t.scheduleText || t.schedule,
|
|
332
|
-
status: t.status,
|
|
333
|
-
nextRunAt: t.nextRunAt ? new Date(t.nextRunAt).toISOString() : null,
|
|
334
|
-
lastStatus: t.lastStatus,
|
|
335
|
-
totalTokens: t.totalTokens || 0,
|
|
336
|
-
totalCostUsd: t.totalCostUsd || 0,
|
|
337
|
-
oneShot: Boolean(t.oneShot),
|
|
338
|
-
}))
|
|
339
|
-
};
|
|
340
|
-
}
|
|
341
|
-
}));
|
|
342
|
-
|
|
343
|
-
ctx.tools.register(defineTool({
|
|
344
|
-
name: 'cron_pause_task',
|
|
345
|
-
description: 'Pause a task by its ID',
|
|
346
|
-
parameters: {
|
|
347
|
-
id: { type: 'string', description: 'Task identifier', required: true }
|
|
348
|
-
},
|
|
349
|
-
output: {
|
|
350
|
-
schema: {
|
|
351
|
-
type: 'object',
|
|
352
|
-
additionalProperties: true,
|
|
353
|
-
properties: {
|
|
354
|
-
success: { type: 'boolean' },
|
|
355
|
-
message: { type: 'string' }
|
|
356
|
-
}
|
|
357
|
-
},
|
|
358
|
-
render: (_args, val) => [{ type: 'text', text: val.message }]
|
|
359
|
-
},
|
|
360
|
-
execute: async (args) => {
|
|
361
|
-
// A config-owned task cannot be paused from here either: the next start
|
|
362
|
-
// would resume it, and the agent would have reported a change that never
|
|
363
|
-
// happened (#50 review finding).
|
|
364
|
-
if (isConfigOwned(store.get(args.id))) return { success: false, message: configOwnedMessage(args.id) };
|
|
365
|
-
const task = scheduler.pauseTask(args.id);
|
|
366
|
-
if (!task) return { success: false, message: 'Task not found' };
|
|
367
|
-
return { success: true, message: `Task "${task.title}" paused` };
|
|
368
|
-
}
|
|
369
|
-
}));
|
|
370
|
-
|
|
371
|
-
ctx.tools.register(defineTool({
|
|
372
|
-
name: 'cron_resume_task',
|
|
373
|
-
description: 'Resume a paused task',
|
|
374
|
-
parameters: {
|
|
375
|
-
id: { type: 'string', description: 'Task identifier', required: true }
|
|
376
|
-
},
|
|
377
|
-
output: {
|
|
378
|
-
schema: {
|
|
379
|
-
type: 'object',
|
|
380
|
-
additionalProperties: true,
|
|
381
|
-
properties: {
|
|
382
|
-
success: { type: 'boolean' },
|
|
383
|
-
message: { type: 'string' }
|
|
384
|
-
}
|
|
385
|
-
},
|
|
386
|
-
render: (_args, val) => [{ type: 'text', text: val.message }]
|
|
387
|
-
},
|
|
388
|
-
execute: async (args) => {
|
|
389
|
-
if (isConfigOwned(store.get(args.id))) return { success: false, message: configOwnedMessage(args.id) };
|
|
390
|
-
const task = scheduler.resumeTask(args.id);
|
|
391
|
-
if (!task) return { success: false, message: 'Task not found' };
|
|
392
|
-
return { success: true, message: `Task "${task.title}" resumed (${task.scheduleText})` };
|
|
393
|
-
}
|
|
394
|
-
}));
|
|
395
|
-
|
|
251
|
+
// 2. Tools: unified background automation runner (Issue #196: 9 tools -> 1)
|
|
396
252
|
ctx.tools.register(defineTool({
|
|
397
|
-
name: '
|
|
398
|
-
description: '
|
|
399
|
-
parameters:
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
output: {
|
|
403
|
-
schema: {
|
|
404
|
-
type: 'object',
|
|
405
|
-
additionalProperties: true,
|
|
406
|
-
properties: {
|
|
407
|
-
success: { type: 'boolean' },
|
|
408
|
-
message: { type: 'string' }
|
|
409
|
-
}
|
|
410
|
-
},
|
|
411
|
-
render: (_args, val) => [{ type: 'text', text: val.message }]
|
|
412
|
-
},
|
|
413
|
-
execute: async (args) => {
|
|
414
|
-
// Deleting a config-owned task would also drop its run history, and the
|
|
415
|
-
// task would come back at the next start without it.
|
|
416
|
-
if (isConfigOwned(store.get(args.id))) return { success: false, message: configOwnedMessage(args.id) };
|
|
417
|
-
scheduler.pauseTask(args.id);
|
|
418
|
-
const ok = store.delete(args.id);
|
|
419
|
-
return { success: ok, message: ok ? 'Task deleted' : 'Task not found' };
|
|
420
|
-
}
|
|
421
|
-
}));
|
|
422
|
-
|
|
423
|
-
ctx.tools.register(defineTool({
|
|
424
|
-
name: 'cron_get_task',
|
|
425
|
-
description: 'Read the full configuration of one scheduled task, including fields that are not visible in the task list',
|
|
426
|
-
parameters: {
|
|
427
|
-
id: { type: 'string', description: 'Task identifier', required: true }
|
|
428
|
-
},
|
|
429
|
-
output: {
|
|
430
|
-
schema: {
|
|
431
|
-
type: 'object',
|
|
432
|
-
additionalProperties: true,
|
|
433
|
-
properties: {
|
|
434
|
-
success: { type: 'boolean' },
|
|
435
|
-
message: { type: 'string' },
|
|
436
|
-
task: { type: 'object', additionalProperties: true }
|
|
437
|
-
}
|
|
438
|
-
},
|
|
439
|
-
render: (_args, val) => [{ type: 'text', text: val.message }]
|
|
440
|
-
},
|
|
441
|
-
execute: async (args) => {
|
|
442
|
-
const task = store.get(args.id);
|
|
443
|
-
if (!task) return { success: false, message: 'Task not found' };
|
|
444
|
-
const summary = [
|
|
445
|
-
`Task "${task.title}" (${task.id})`,
|
|
446
|
-
`type: ${task.type || 'llm'}`,
|
|
447
|
-
`schedule: ${task.scheduleText || task.schedule}`,
|
|
448
|
-
`status: ${task.status}`,
|
|
449
|
-
task.type === 'llm' || !task.type ? `model: ${task.model || '(default)'}${task.fallbackModel ? ` (fallback: ${task.fallbackModel})` : ''}` : null,
|
|
450
|
-
`channels: ${Array.isArray(task.channels) && task.channels.length ? task.channels.join(', ') : '(legacy flags)'}`,
|
|
451
|
-
`prompt: ${String(task.prompt || '').slice(0, 500)}`,
|
|
452
|
-
].filter(Boolean).join(String.fromCharCode(10));
|
|
453
|
-
return { success: true, message: summary, task };
|
|
454
|
-
}
|
|
455
|
-
}));
|
|
456
|
-
|
|
457
|
-
ctx.tools.register(defineTool({
|
|
458
|
-
name: 'cron_update_task',
|
|
459
|
-
description: 'Change an existing scheduled task. Ask the user what to change first. Switching a task to a code-executing type (script/node/python/ssh/docker) is refused unless confirmCodeSwitch is set, which requires the user to have agreed explicitly. Only the documented task fields can be changed.',
|
|
460
|
-
parameters: {
|
|
461
|
-
id: { type: 'string', description: 'Task identifier', required: true },
|
|
462
|
-
confirmCodeSwitch: { type: 'boolean', description: 'Set to true only after the user explicitly agreed to switch this task into a type that executes code' },
|
|
463
|
-
title: { type: 'string', description: 'New title' },
|
|
464
|
-
schedule: { type: 'string', description: 'New schedule (cron expression or interval)' },
|
|
465
|
-
prompt: { type: 'string', description: 'New prompt or command' },
|
|
466
|
-
type: { type: 'string', enum: TASK_TYPES, description: 'New execution type' },
|
|
467
|
-
model: { type: 'string', description: 'New model for agent tasks' },
|
|
468
|
-
fallbackModel: { type: 'string', description: 'New fallback model' },
|
|
469
|
-
provider: { type: 'string', description: 'New provider' },
|
|
470
|
-
channels: { type: 'array', items: { type: 'string', enum: CHANNEL_IDS }, description: 'New delivery channels' },
|
|
471
|
-
template: { type: 'string', description: 'New message template' },
|
|
472
|
-
onlyOnFailure: { type: 'boolean', description: 'Deliver only on failures' },
|
|
473
|
-
timeoutSeconds: { type: 'number', description: 'New execution timeout in seconds' },
|
|
474
|
-
overlapPolicy: { type: 'string', enum: ['skip', 'queue', 'replace'], description: 'New overlap policy' },
|
|
475
|
-
maxRetries: { type: 'number', description: 'New retry count' },
|
|
476
|
-
kanbanMode: { type: 'string', enum: ['none', 'on_failure', 'always'], description: 'New Kanban policy' },
|
|
477
|
-
timezone: { type: 'string', description: 'New IANA time zone' },
|
|
478
|
-
cwd: { type: 'string', description: 'New working directory' },
|
|
479
|
-
env: { type: 'object', additionalProperties: true, description: 'New environment variables (KEY: value)' }
|
|
480
|
-
},
|
|
481
|
-
output: {
|
|
482
|
-
schema: {
|
|
483
|
-
type: 'object',
|
|
484
|
-
additionalProperties: true,
|
|
485
|
-
properties: {
|
|
486
|
-
success: { type: 'boolean' },
|
|
487
|
-
message: { type: 'string' }
|
|
488
|
-
}
|
|
489
|
-
},
|
|
490
|
-
render: (_args, val) => [{ type: 'text', text: val.message }]
|
|
491
|
-
},
|
|
492
|
-
execute: async (args) => {
|
|
493
|
-
const { id, confirmCodeSwitch, ...patch } = args || {};
|
|
494
|
-
if (!id) return { success: false, message: 'A task id is required' };
|
|
495
|
-
const result = applyTaskPatch({ store, scheduler, id, body: patch, allowCodeSwitch: confirmCodeSwitch === true });
|
|
496
|
-
if (!result.ok) return { success: false, message: result.error };
|
|
497
|
-
return {
|
|
498
|
-
success: true,
|
|
499
|
-
message: `Task "${result.task.title}" updated (${result.task.scheduleText || result.task.schedule}). Changed: ${describeTaskPatch(result.task, result.patch)}`,
|
|
500
|
-
};
|
|
501
|
-
}
|
|
502
|
-
}));
|
|
503
|
-
|
|
504
|
-
ctx.tools.register(defineTool({
|
|
505
|
-
name: 'cron_run_task',
|
|
506
|
-
description: 'Trigger a task immediately, out of band',
|
|
507
|
-
parameters: {
|
|
508
|
-
id: { type: 'string', description: 'Task identifier', required: true }
|
|
509
|
-
},
|
|
510
|
-
output: {
|
|
511
|
-
schema: {
|
|
512
|
-
type: 'object',
|
|
513
|
-
additionalProperties: true,
|
|
514
|
-
properties: {
|
|
515
|
-
success: { type: 'boolean' },
|
|
516
|
-
message: { type: 'string' }
|
|
517
|
-
}
|
|
518
|
-
},
|
|
519
|
-
render: (_args, val) => [{ type: 'text', text: val.message }]
|
|
520
|
-
},
|
|
521
|
-
execute: async (args) => {
|
|
522
|
-
const task = store.get(args.id);
|
|
523
|
-
if (!task) return { success: false, message: 'Task not found' };
|
|
524
|
-
scheduler.triggerManualRun(args.id);
|
|
525
|
-
return { success: true, message: `Manual run started for task "${task.title}"` };
|
|
526
|
-
}
|
|
253
|
+
name: 'cron',
|
|
254
|
+
description: 'Background automation runner for DSH: isolated agent runs, script/node/python/http/ssh/docker runtimes, cost guard, notifications, heartbeats. For simple reminders in the current chat, use the built-in schedule_create tool instead. Actions: create, list, get, update, pause, resume, run, delete.',
|
|
255
|
+
parameters: cronToolParameters,
|
|
256
|
+
output: cronToolOutput,
|
|
257
|
+
execute: async (args) => executeCronTool(store, scheduler, args),
|
|
527
258
|
}));
|
|
528
259
|
}
|
package/lib/prompt.js
CHANGED
|
@@ -12,9 +12,11 @@ The user's original request:
|
|
|
12
12
|
"${text}"
|
|
13
13
|
|
|
14
14
|
CRITICAL RULES FOR THE AGENT:
|
|
15
|
-
1.
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
1. EVALUATE SIMPLE IN-CHAT REMINDER VS BACKGROUND AUTOMATION:
|
|
16
|
+
- If the user simply wants a reminder or alert delivered into the current conversation (e.g. "remind me in 2 hours", "say good morning at 9am here"), suggest using the core built-in \`schedule_create\` tool instead.
|
|
17
|
+
- Use the \`cron\` tool for background automation running in separate isolated sessions, shell/python/node/http/ssh/docker runtimes, multi-channel alerts (Telegram, Discord, Slack, etc.), or monitoring.
|
|
18
|
+
- NEVER RUSH: It is STRICTLY FORBIDDEN to call the \`cron\` tool in your first reply!
|
|
19
|
+
You MUST NOT create the task until you have asked clarifying questions and received the user's confirmation.
|
|
18
20
|
|
|
19
21
|
2. FIRST QUESTION: LLM vs NO-LLM Shell (critical):
|
|
20
22
|
- Evaluate the nature of the task:
|
|
@@ -46,7 +48,8 @@ CRITICAL RULES FOR THE AGENT:
|
|
|
46
48
|
- Ask any remaining questions and ask the user to confirm task creation.
|
|
47
49
|
|
|
48
50
|
5. CREATING THE TASK:
|
|
49
|
-
Only AFTER the user replies and confirms the parameters, call the \`
|
|
51
|
+
Only AFTER the user replies and confirms the parameters, call the \`cron\` tool with:
|
|
52
|
+
- action: "create"
|
|
50
53
|
- title: a clear task name
|
|
51
54
|
- schedule: the agreed cron expression
|
|
52
55
|
- prompt: the final shell command/script path (for type="script") OR detailed instructions including the Silent Rule (for type="llm")
|
package/lib/task-patch.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Partial task updates, shared by the HTTP PATCH route and the
|
|
2
|
+
* Partial task updates, shared by the HTTP PATCH route and the cron tool (action: 'update')
|
|
3
3
|
* tool (#49).
|
|
4
4
|
*
|
|
5
5
|
* One implementation keeps the whitelist, the type validation, the schedule
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
import { parseScheduleExpression } from './scheduler.js';
|
|
12
12
|
import { normalizeTaskType, CODE_EXECUTING_TYPES } from './runtimes.js';
|
|
13
13
|
import { PATCHABLE_TASK_FIELDS, validateTaskType } from './task-transfer.js';
|
|
14
|
-
import { pickPatchableFields } from './http-utils.js';
|
|
14
|
+
import { pickPatchableFields, restoreTaskSecrets } from './http-utils.js';
|
|
15
15
|
import { unknownChannelIds } from './channels.js';
|
|
16
16
|
import { isConfigOwned, configOwnedMessage } from './config-jobs.js';
|
|
17
17
|
|
|
@@ -28,7 +28,8 @@ export function isCodeTypeSwitch(current, nextType) {
|
|
|
28
28
|
* Returns `{ ok: false, error }` when the merged task would be invalid.
|
|
29
29
|
*/
|
|
30
30
|
export function buildTaskPatch(current, body) {
|
|
31
|
-
const
|
|
31
|
+
const mergedBody = current ? restoreTaskSecrets(body || {}, current) : (body || {});
|
|
32
|
+
const patch = pickPatchableFields(mergedBody, PATCHABLE_TASK_FIELDS);
|
|
32
33
|
if (patch.type !== undefined) {
|
|
33
34
|
patch.type = normalizeTaskType(patch.type);
|
|
34
35
|
const typeError = validateTaskType(patch.type, { ...current, ...patch });
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodandready/dsh-cron",
|
|
3
|
-
"version": "0.2.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.2.29",
|
|
4
|
+
"description": "Background automation runner for DSH: isolated agent runs, script/HTTP/SSH/Docker runtimes, cost guard, notifications, heartbeats.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"exports": {
|