@goodandready/dsh-cron 0.1.9 → 0.1.11

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/lib/client.js CHANGED
@@ -25,6 +25,7 @@ const React = window.React || require('react');
25
25
  const ICON_BELL = '<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"></path><path d="M13.73 21a2 2 0 0 1-3.46 0"></path></svg>';
26
26
  const ICON_CLIPBOARD = '<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"></path><rect x="8" y="2" width="8" height="4" rx="1" ry="1"></rect></svg>';
27
27
  const ICON_ACTIVITY = '<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="22 12 18 12 15 21 9 3 6 12 2 12"></polyline></svg>';
28
+ const ICON_EDIT = '<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path></svg>';
28
29
  const ICON_SEND = '<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="22" y1="2" x2="11" y2="13"></line><polygon points="22 2 15 22 11 13 2 9 22 2"></polygon></svg>';
29
30
 
30
31
  const STYLES = `
@@ -73,7 +74,10 @@ const React = window.React || require('react');
73
74
  .dsh-cron-tab { appearance: none; background: transparent; border: none; padding: 6px 14px; border-radius: 9999px; color: #9ca3af; font-size: 13.5px; cursor: pointer; }
74
75
  .dsh-cron-tab[data-active="true"] { background: var(--dsw-alias-bg-layer-4, #2a2a2a); color: #fff; font-weight: 500; }
75
76
  .dsh-cron-task-list { display: flex; flex-direction: column; gap: 12px; margin-bottom: 40px; }
76
- .dsh-cron-task-item { display: flex; align-items: center; gap: 16px; padding: 14px 18px; background: var(--dsw-alias-bg-layer-3, #1e1e1e); border: 1px solid var(--dsw-alias-border-l2, #2e2e2e); border-radius: 12px; }
77
+ .dsh-cron-task-item { display: flex; align-items: center; gap: 16px; padding: 14px 18px; background: var(--dsw-alias-bg-layer-3, #1e1e1e); border: 1px solid var(--dsw-alias-border-l2, #2e2e2e); border-radius: 12px; cursor: pointer; transition: background 0.15s, border-color 0.15s; }
78
+ .dsh-cron-task-item:hover { background: var(--dsw-alias-bg-layer-4, #242424); border-color: #444; }
79
+ .dsh-cron-task-prompt-preview { font-size: 12.5px; color: #888; margin-top: 5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 520px; line-height: 1.3; }
80
+ .dsh-cron-type-tag { display: inline-block; font-size: 10.5px; padding: 1px 6px; border-radius: 4px; text-transform: uppercase; font-weight: 600; margin-left: 8px; vertical-align: middle; }
77
81
  .dsh-cron-task-item:hover { border-color: #444; }
78
82
  .dsh-cron-task-status-btn { background: none; border: 1px solid #444; border-radius: 50%; width: 28px; height: 28px; display: flex; align-items: center; justify-content: center; color: #888; cursor: pointer; flex-shrink: 0; }
79
83
  .dsh-cron-task-status-btn[data-active="true"] { color: #10b981; border-color: #10b981; }
@@ -205,13 +209,17 @@ const React = window.React || require('react');
205
209
  const [recs, setRecs] = React.useState([]);
206
210
  const [dropdownOpen, setDropdownOpen] = React.useState(false);
207
211
 
208
- // Manual modal state
212
+ // Manual / Edit modal state
209
213
  const [manualModalOpen, setManualModalOpen] = React.useState(false);
214
+ const [formId, setFormId] = React.useState(null);
210
215
  const [formTitle, setFormTitle] = React.useState('');
211
216
  const [formSchedule, setFormSchedule] = React.useState('');
217
+ const [formType, setFormType] = React.useState('llm');
212
218
  const [formPrompt, setFormPrompt] = React.useState('');
213
219
  const [formProvider, setFormProvider] = React.useState('');
214
220
  const [formModel, setFormModel] = React.useState('');
221
+ const [formDelivery, setFormDelivery] = React.useState('isolated');
222
+ const [selectedTaskMeta, setSelectedTaskMeta] = React.useState(null);
215
223
 
216
224
  // DSH dialog state
217
225
  const [dshModalOpen, setDshModalOpen] = React.useState(false);
@@ -287,6 +295,10 @@ const React = window.React || require('react');
287
295
 
288
296
  const openManualModal = (preset) => {
289
297
  setDropdownOpen(false);
298
+ setFormId(null);
299
+ setSelectedTaskMeta(null);
300
+ setFormType('llm');
301
+ setFormDelivery('isolated');
290
302
  if (preset) {
291
303
  setFormTitle(preset.title || '');
292
304
  setFormSchedule(preset.schedule || '');
@@ -305,6 +317,22 @@ const React = window.React || require('react');
305
317
  setManualModalOpen(true);
306
318
  };
307
319
 
320
+ const openEditModal = (task) => {
321
+ setDropdownOpen(false);
322
+ setFormId(task.id);
323
+ setSelectedTaskMeta(task);
324
+ setFormTitle(task.title || '');
325
+ setFormSchedule(task.schedule || '');
326
+ setFormType(task.type || 'llm');
327
+ setFormPrompt(task.prompt || '');
328
+ setFormProvider(task.provider || '');
329
+ setFormModel(task.model || '');
330
+ setFormDelivery(task.delivery || 'isolated');
331
+ if (task.provider) loadModels(task.provider);
332
+ else if (defaultModelInfo?.provider) loadModels(defaultModelInfo.provider);
333
+ setManualModalOpen(true);
334
+ };
335
+
308
336
  const handleProviderChange = (e) => {
309
337
  const prov = e.target.value;
310
338
  setFormProvider(prov);
@@ -315,19 +343,23 @@ const React = window.React || require('react');
315
343
  const handleSaveManual = async (e) => {
316
344
  e.preventDefault();
317
345
  try {
346
+ const payload = {
347
+ id: formId || undefined,
348
+ title: formTitle,
349
+ schedule: formSchedule,
350
+ prompt: formPrompt,
351
+ type: formType,
352
+ delivery: formDelivery || 'isolated',
353
+ provider: formType === 'script' ? undefined : (formProvider || undefined),
354
+ model: formType === 'script' ? undefined : (formModel || undefined),
355
+ };
318
356
  const res = await fetch('/dsh-cron/tasks', {
319
357
  method: 'POST',
320
358
  headers: { 'Content-Type': 'application/json' },
321
- body: JSON.stringify({
322
- title: formTitle,
323
- schedule: formSchedule,
324
- prompt: formPrompt,
325
- provider: formProvider || undefined,
326
- model: formModel || undefined,
327
- })
359
+ body: JSON.stringify(payload)
328
360
  });
329
361
  const data = await res.json();
330
- if (!data.ok) { alert(data.error || 'Ошибка'); return; }
362
+ if (!data.ok) { alert(data.error || 'Ошибка сохранения'); return; }
331
363
  setManualModalOpen(false);
332
364
  fetchTasks();
333
365
  } catch (err) { alert(err.message); }
@@ -424,30 +456,57 @@ const React = window.React || require('react');
424
456
  tasks.length === 0
425
457
  ? React.createElement('div', { style: { color: '#777', padding: '16px 0', fontSize: '14px' } }, 'Нет запланированных задач')
426
458
  : tasks.map((task) =>
427
- React.createElement('div', { key: task.id, className: 'dsh-cron-task-item' },
459
+ React.createElement('div', {
460
+ key: task.id,
461
+ className: 'dsh-cron-task-item',
462
+ onClick: () => openEditModal(task)
463
+ },
428
464
  React.createElement('button', {
465
+ type: 'button',
429
466
  className: 'dsh-cron-task-status-btn',
430
467
  'data-active': task.status === 'active' ? 'true' : undefined,
431
468
  title: task.status === 'active' ? 'Приостановить' : 'Возобновить',
432
- onClick: () => handleToggleTask(task.id)
469
+ onClick: (e) => { e.stopPropagation(); handleToggleTask(task.id); }
433
470
  }, React.createElement('span', { dangerouslySetInnerHTML: { __html: task.status === 'active' ? ICON_PAUSE : ICON_PLAY } })),
434
471
  React.createElement('div', { className: 'dsh-cron-task-info' },
435
472
  React.createElement('div', { className: 'dsh-cron-task-title' },
436
473
  task.title,
474
+ React.createElement('span', {
475
+ className: 'dsh-cron-type-tag',
476
+ style: {
477
+ background: task.type === 'script' ? 'rgba(59, 130, 246, 0.15)' : 'rgba(16, 185, 129, 0.15)',
478
+ color: task.type === 'script' ? '#60a5fa' : '#34d399'
479
+ }
480
+ }, task.type === 'script' ? 'Shell' : 'LLM'),
437
481
  task.model ? React.createElement('span', { className: 'dsh-cron-task-model-tag' }, task.model.split('/').pop()) : null
438
482
  ),
439
483
  React.createElement('div', { className: 'dsh-cron-task-sched' },
440
484
  task.scheduleText || task.schedule,
441
485
  task.nextRunAt ? ` · след: ${new Date(task.nextRunAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}` : ''
442
- )
486
+ ),
487
+ task.prompt ? React.createElement('div', {
488
+ className: 'dsh-cron-task-prompt-preview',
489
+ title: task.prompt
490
+ }, task.prompt.slice(0, 140) + (task.prompt.length > 140 ? '…' : '')) : null
443
491
  ),
444
- React.createElement('div', { className: 'dsh-cron-task-actions' },
492
+ React.createElement('div', {
493
+ className: 'dsh-cron-task-actions',
494
+ onClick: (e) => e.stopPropagation()
495
+ },
445
496
  React.createElement('button', {
497
+ type: 'button',
446
498
  className: 'dsh-cron-icon-btn',
447
499
  title: 'Запустить немедленно',
448
500
  onClick: () => handleRunNow(task.id)
449
501
  }, 'Запустить'),
450
502
  React.createElement('button', {
503
+ type: 'button',
504
+ className: 'dsh-cron-icon-btn',
505
+ title: 'Редактировать / Подробнее',
506
+ onClick: () => openEditModal(task)
507
+ }, React.createElement('span', { dangerouslySetInnerHTML: { __html: ICON_EDIT } })),
508
+ React.createElement('button', {
509
+ type: 'button',
451
510
  className: 'dsh-cron-icon-btn',
452
511
  title: 'Удалить задачу',
453
512
  onClick: () => handleDelete(task.id)
@@ -481,30 +540,57 @@ const React = window.React || require('react');
481
540
  )
482
541
  ),
483
542
 
484
- // 1. Manual creation modal
543
+ // 1. Manual creation / Edit modal
485
544
  manualModalOpen && React.createElement('div', { className: 'dsh-cron-modal-overlay', onClick: () => setManualModalOpen(false) },
486
- React.createElement('div', { className: 'dsh-cron-modal', onClick: (e) => e.stopPropagation() },
487
- React.createElement('div', { className: 'dsh-cron-modal-title' }, 'Настройка запланированной задачи'),
545
+ React.createElement('div', {
546
+ className: 'dsh-cron-modal',
547
+ style: { maxWidth: '640px' },
548
+ onClick: (e) => e.stopPropagation()
549
+ },
550
+ React.createElement('div', { className: 'dsh-cron-modal-head', style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '18px' } },
551
+ React.createElement('div', { className: 'dsh-cron-modal-title', style: { margin: 0 } }, formId ? 'Редактирование задачи' : 'Новая запланированная задача'),
552
+ selectedTaskMeta && React.createElement('span', {
553
+ style: {
554
+ fontSize: '12px',
555
+ padding: '3px 8px',
556
+ borderRadius: '6px',
557
+ background: selectedTaskMeta.status === 'active' ? 'rgba(16, 185, 129, 0.15)' : 'rgba(239, 68, 68, 0.15)',
558
+ color: selectedTaskMeta.status === 'active' ? '#10b981' : '#ef4444'
559
+ }
560
+ }, selectedTaskMeta.status === 'active' ? '● Активна' : '○ На паузе')
561
+ ),
488
562
  React.createElement('form', { onSubmit: handleSaveManual },
489
563
  React.createElement('div', { className: 'dsh-cron-form-group' },
490
564
  React.createElement('label', null, 'Название задачи'),
491
565
  React.createElement('input', {
492
566
  required: true,
493
567
  value: formTitle,
494
- placeholder: 'Например: Ежедневный утренний отчет',
568
+ placeholder: 'Например: dsh-tags-watch',
495
569
  onChange: (e) => setFormTitle(e.target.value)
496
570
  })
497
571
  ),
498
- React.createElement('div', { className: 'dsh-cron-form-group' },
499
- React.createElement('label', null, 'Расписание (cron или "every 30m")'),
500
- React.createElement('input', {
501
- required: true,
502
- value: formSchedule,
503
- placeholder: '0 9 * * 1-5 или every 2h',
504
- onChange: (e) => setFormSchedule(e.target.value)
505
- })
572
+ React.createElement('div', { className: 'dsh-cron-form-row', style: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '12px' } },
573
+ React.createElement('div', { className: 'dsh-cron-form-group' },
574
+ React.createElement('label', null, 'Тип выполнения'),
575
+ React.createElement('select', {
576
+ value: formType,
577
+ onChange: (e) => setFormType(e.target.value)
578
+ },
579
+ React.createElement('option', { value: 'llm' }, 'LLM (Агент с нейросетью)'),
580
+ React.createElement('option', { value: 'script' }, 'NO-LLM (Shell-команда/Скрипт)')
581
+ )
582
+ ),
583
+ React.createElement('div', { className: 'dsh-cron-form-group' },
584
+ React.createElement('label', null, 'Расписание (cron или интервал)'),
585
+ React.createElement('input', {
586
+ required: true,
587
+ value: formSchedule,
588
+ placeholder: '0 */6 * * * или every 2h',
589
+ onChange: (e) => setFormSchedule(e.target.value)
590
+ })
591
+ )
506
592
  ),
507
- React.createElement('div', { className: 'dsh-cron-form-row' },
593
+ formType !== 'script' && React.createElement('div', { className: 'dsh-cron-form-row', style: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '12px' } },
508
594
  React.createElement('div', { className: 'dsh-cron-form-group' },
509
595
  React.createElement('label', null, 'Провайдер LLM'),
510
596
  React.createElement('select', {
@@ -527,14 +613,41 @@ const React = window.React || require('react');
527
613
  )
528
614
  ),
529
615
  React.createElement('div', { className: 'dsh-cron-form-group' },
530
- React.createElement('label', null, 'Промпт / Инструкция для агента'),
616
+ React.createElement('label', null, formType === 'script' ? 'Shell-команда или скрипт' : 'Инструкция / Промпт для агента'),
531
617
  React.createElement('textarea', {
532
618
  required: true,
619
+ rows: 9,
620
+ style: { fontFamily: 'monospace', fontSize: '12.5px', lineHeight: '1.45' },
533
621
  value: formPrompt,
534
- placeholder: 'Опишите, что именно агент должен сделать...',
622
+ placeholder: formType === 'script' ? 'curl -fsSL https://... || exit 1' : 'Опишите, что именно агент должен сделать...',
535
623
  onChange: (e) => setFormPrompt(e.target.value)
536
624
  })
537
625
  ),
626
+ selectedTaskMeta && React.createElement('div', {
627
+ style: {
628
+ background: 'rgba(255,255,255,0.03)',
629
+ border: '1px solid #2e2e2e',
630
+ borderRadius: '8px',
631
+ padding: '10px 14px',
632
+ fontSize: '12.5px',
633
+ color: '#999',
634
+ marginBottom: '16px',
635
+ display: 'flex',
636
+ justifyContent: 'space-between',
637
+ alignItems: 'center'
638
+ }
639
+ },
640
+ React.createElement('div', null,
641
+ React.createElement('span', null, 'Следующий запуск: '),
642
+ React.createElement('strong', { style: { color: '#eee' } }, selectedTaskMeta.nextRunAt ? new Date(selectedTaskMeta.nextRunAt).toLocaleString() : 'не запланирован')
643
+ ),
644
+ React.createElement('button', {
645
+ type: 'button',
646
+ className: 'dsh-cron-btn-secondary',
647
+ style: { padding: '4px 10px', fontSize: '12px' },
648
+ onClick: () => { handleRunNow(selectedTaskMeta.id); }
649
+ }, 'Запустить сейчас')
650
+ ),
538
651
  React.createElement('div', { className: 'dsh-cron-modal-foot' },
539
652
  React.createElement('button', {
540
653
  type: 'button',
@@ -544,7 +657,7 @@ const React = window.React || require('react');
544
657
  React.createElement('button', {
545
658
  type: 'submit',
546
659
  className: 'dsh-cron-btn-primary'
547
- }, 'Сохранить задачу')
660
+ }, formId ? 'Сохранить изменения' : 'Создать задачу')
548
661
  )
549
662
  )
550
663
  )
package/lib/index.js CHANGED
@@ -194,6 +194,7 @@ export function apply(ctx, config) {
194
194
  schedule: { type: 'string', required: true, description: 'Выражение cron или интервал (every 30m, 0 9 * * 1-5, daily)' },
195
195
  prompt: { type: 'string', required: true, description: 'Инструкция для агента при запуске' },
196
196
  delivery: { type: 'string', description: 'Режим запуска: current или isolated' },
197
+ type: { type: 'string', description: 'Тип задачи: "llm" (агент с LLM) или "script" (прямой запуск shell без LLM)' },
197
198
  provider: { type: 'string', description: 'ID провайдера LLM для выполнения задачи' },
198
199
  model: { type: 'string', description: 'ID модели LLM для выполнения задачи' }
199
200
  },
@@ -210,7 +211,7 @@ export function apply(ctx, config) {
210
211
  return [{ type: 'text', text: value.message }];
211
212
  }
212
213
  },
213
- execute: async ({ title, schedule, prompt, delivery, provider, model }) => {
214
+ execute: async ({ title, schedule, prompt, delivery, provider, model, type }) => {
214
215
  try {
215
216
  const parsed = parseScheduleExpression(schedule);
216
217
  const task = store.set({
@@ -218,6 +219,7 @@ export function apply(ctx, config) {
218
219
  schedule: parsed.cronPattern,
219
220
  scheduleText: parsed.humanText,
220
221
  prompt,
222
+ type: type || (provider || model ? 'llm' : 'llm'),
221
223
  delivery: delivery || 'isolated',
222
224
  provider: provider || undefined,
223
225
  model: model || undefined,
package/lib/prompt.js CHANGED
@@ -5,13 +5,46 @@
5
5
  */
6
6
  export function buildAgentCronPrompt(userText) {
7
7
  const text = (userText || '').trim();
8
- return `Пользователь хочет запланировать cron-задачу через DSH Cron:
8
+ return `Пользователь хочет настроить запланированную задачу (cron) через DSH Cron.
9
+ Исходный запрос пользователя:
9
10
  "${text}"
10
11
 
11
- ИНСТРУКЦИЯ ДЛЯ АГЕНТА:
12
- 1. Твоя цель — помочь пользователю настроить и зарегистрировать запланированную задачу с помощью инструмента \`cron_schedule_task\`.
13
- 2. Предложи подходящую и экономичную LLM-модель для выполнения этого крона (например, \`Qwen/Qwen3.8-Flash\` через Command Code или \`deepseek/deepseek-v4-flash\`), уточни у пользователя, устраивает ли она его или он предпочитает другую подключенную модель.
14
- 3. Уточни и сформулируй точное расписание cron (например, "0 * * * *" или интервал вроде "every 1h").
15
- 4. Примени Silent Rule (правило тишины): если задача мониторинга/проверки не обнаружила проблем, критических изменений или ошибок, агент должен возвращать ПУСТОЙ ответ или короткое уведомление без лишнего шума.
16
- 5. После согласования параметров ОБЯЗАТЕЛЬНО вызови инструмент \`cron_schedule_task\`, передав title, schedule, prompt и delivery ("isolated" или "current").`;
17
- }
12
+ ВАЖНЕЙШИЕ ПРАВИЛА ДЛЯ АГЕНТА:
13
+ 1. СТРОГИЙ ЗАПРЕТ НА СПЕШКУ:
14
+ КАТЕГОРИЧЕСКИ ЗАПРЕЩЕНО вызывать инструмент \`cron_schedule_task\` в первом же ответе!
15
+ Ты НЕ имеешь права создавать задачу, пока не задашь пользователю уточняющие вопросы и не получишь его подтверждение.
16
+
17
+ 2. ЧТО ТЫ ОБЯЗАН УТОЧНИТЬ В ПЕРВОМ ОТВЕТЕ:
18
+ - Тип задачи (LLM или NO-LLM?):
19
+ * LLM-крон: агент запускается по расписанию с языковой моделью, анализирует вывод, делает выводы, парсит неструктурированный текст (расходует токены).
20
+ * NO-LLM крон (скрипт/bash): периодический запуск shell-команды или скрипта (curl, git check, ping, проверка системных служб) БЕЗ участия LLM. Не расходует токены, сообщает только при ненулевом коде возврата или ошибке.
21
+ Спроси пользователя, какой вариант предпочтительнее для его задачи (или аргументированно предложи подходящий).
22
+ - Выбор модели (если выбран LLM-крон):
23
+ * Предложи экономичную и быструю модель (например, \`deepseek/deepseek-v4-flash\` или \`Qwen/Qwen3.8-Flash\`).
24
+ * Спроси, подходит ли эта модель или пользователь хочет использовать другую подключенную модель.
25
+ - Точное расписание (schedule):
26
+ * Предложи конкретное выражение cron (например, "0 */6 * * *" для 4 раз в день или "0 9 * * 1-5" по будням) и понятное текстовое пояснение.
27
+ * Уточни, устраивает ли такое расписание.
28
+ - Silent Rule (Правило тишины / Уведомления):
29
+ * По умолчанию для регулярных проверок действует Silent Rule: если всё в порядке / нет обновлений / сервис работает штатно — агент молчит или возвращает пустой ответ, чтобы не спамить. Оповещение отправляется ТОЛЬКО при обнаружении изменений, обновлений или проблем.
30
+ * Уточни, нужно ли применять Silent Rule или присылать отчёт при каждом запуске.
31
+
32
+ 3. СТРУКТУРА ПЕРВОГО ОТВЕТА:
33
+ - Кратко подтверди суть задачи.
34
+ - Сформулируй проект карточки задачи:
35
+ • Тип: [LLM / NO-LLM]
36
+ • Расписание: [cron-выражение и понятное описание]
37
+ • Модель: [если LLM — предлагаемая модель]
38
+ • Логика проверки и Silent Rule: [кратко]
39
+ - Задай недостающие вопросы (LLM/NO-LLM, модель, расписание, порог алерта).
40
+ - Попроси пользователя подтвердить или скорректировать параметры перед созданием.
41
+
42
+ 4. СОЗДАНИЕ ЗАДАЧИ:
43
+ Только ПОСЛЕ ТОГО, как пользователь ответит и подтвердит параметры, вызови инструмент \`cron_schedule_task\`, передав:
44
+ - title: понятное название задачи
45
+ - schedule: согласованное cron-выражение
46
+ - prompt: финальная подробная инструкция для агента (с обязательным Silent Rule) ИЛИ shell-команда для NO-LLM
47
+ - type: "llm" или "script"
48
+ - provider и model: согласованный провайдер и модель (если type="llm")
49
+ - delivery: "isolated"`;
50
+ }
package/lib/runner.js CHANGED
@@ -6,10 +6,28 @@ export class SessionRunner {
6
6
  this.ctx = ctx;
7
7
  }
8
8
 
9
- async execute(task) {
9
+ async execute(task) {
10
10
  console.log(`[dsh-cron] executing task "${task.title}" (${task.id})`);
11
11
 
12
- // 1. If ctx.agents is available, run through agent session
12
+ // 1. Direct shell / script task fallback (NO-LLM)
13
+ if (task.type === 'script' || task.mode === 'no-llm') {
14
+ const { exec } = await import('node:child_process');
15
+ const { promisify } = await import('node:util');
16
+ const execAsync = promisify(exec);
17
+ try {
18
+ const { stdout, stderr } = await execAsync(task.prompt, {
19
+ timeout: 60000,
20
+ cwd: process.cwd()
21
+ });
22
+ const out = (stdout || stderr || '').trim();
23
+ return out || 'Script completed with code 0';
24
+ } catch (err) {
25
+ console.error(`[dsh-cron] script task failed:`, err.message);
26
+ throw err;
27
+ }
28
+ }
29
+
30
+ // 2. If ctx.agents is available, run through agent session (LLM)
13
31
  if (this.ctx && this.ctx.agents && typeof this.ctx.agents.create === 'function') {
14
32
  try {
15
33
  const { randomUUID } = await import('node:crypto');
@@ -34,9 +52,10 @@ export class SessionRunner {
34
52
  const provider = task.provider || defaultSel?.provider;
35
53
  const model = task.model || defaultSel?.model;
36
54
 
55
+ const cwd = process.cwd();
37
56
  const handle = await this.ctx.agents.create({
38
57
  sessionId: mintSessionId(),
39
- meta: {},
58
+ meta: { cwd },
40
59
  agentOptions: provider && model ? { provider, model } : undefined,
41
60
  });
42
61
 
@@ -53,7 +72,7 @@ export class SessionRunner {
53
72
  }
54
73
  }
55
74
 
56
- // 2. Check if legacy session service is present
75
+ // 3. Check if legacy session service is present
57
76
  if (this.ctx && this.ctx.session && typeof this.ctx.session.create === 'function') {
58
77
  try {
59
78
  const session = await this.ctx.session.create({
@@ -71,15 +90,6 @@ export class SessionRunner {
71
90
  }
72
91
  }
73
92
 
74
- // 3. Direct shell / script task fallback
75
- if (task.type === 'script' && task.prompt) {
76
- const { exec } = await import('node:child_process');
77
- const { promisify } = await import('node:util');
78
- const execAsync = promisify(exec);
79
- const { stdout, stderr } = await execAsync(task.prompt, { timeout: 60000 });
80
- return stdout || stderr || 'Script completed with code 0';
81
- }
82
-
83
93
  return `[dsh-cron] Prompt triggered: "${task.prompt.slice(0, 100)}..."`;
84
94
  }
85
95
  }
package/lib/store.js CHANGED
@@ -82,21 +82,26 @@ export class TaskStore {
82
82
  set(task) {
83
83
  const now = Date.now();
84
84
  const id = task.id || ('cron_' + Math.random().toString(36).slice(2, 9));
85
+ const prev = this.tasks.get(id) || {};
85
86
  const record = {
87
+ ...prev,
88
+ ...task,
86
89
  id,
87
- title: task.title || 'Новая задача',
88
- schedule: task.schedule || '0 9 * * *',
89
- scheduleText: task.scheduleText || task.schedule,
90
- prompt: task.prompt || '',
91
- type: task.type || 'prompt',
92
- status: task.status || 'active',
93
- delivery: task.delivery || 'current',
94
- createdAt: task.createdAt || now,
90
+ title: task.title !== undefined ? task.title : (prev.title || 'Новая задача'),
91
+ schedule: task.schedule !== undefined ? task.schedule : (prev.schedule || '0 9 * * *'),
92
+ scheduleText: task.scheduleText !== undefined ? task.scheduleText : (prev.scheduleText || task.schedule),
93
+ prompt: task.prompt !== undefined ? task.prompt : (prev.prompt || ''),
94
+ type: task.type !== undefined ? task.type : (prev.type || 'llm'),
95
+ status: task.status !== undefined ? task.status : (prev.status || 'active'),
96
+ delivery: task.delivery !== undefined ? task.delivery : (prev.delivery || 'isolated'),
97
+ provider: task.provider !== undefined ? task.provider : prev.provider,
98
+ model: task.model !== undefined ? task.model : prev.model,
99
+ createdAt: prev.createdAt || task.createdAt || now,
95
100
  updatedAt: now,
96
- lastRunAt: task.lastRunAt || null,
97
- lastStatus: task.lastStatus || null,
98
- lastDurationMs: task.lastDurationMs || 0,
99
- nextRunAt: task.nextRunAt || null,
101
+ lastRunAt: task.lastRunAt !== undefined ? task.lastRunAt : (prev.lastRunAt || null),
102
+ lastStatus: task.lastStatus !== undefined ? task.lastStatus : (prev.lastStatus || null),
103
+ lastDurationMs: task.lastDurationMs !== undefined ? task.lastDurationMs : (prev.lastDurationMs || 0),
104
+ nextRunAt: task.nextRunAt !== undefined ? task.nextRunAt : (prev.nextRunAt || null),
100
105
  };
101
106
  this.tasks.set(id, record);
102
107
  this.save();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goodandready/dsh-cron",
3
- "version": "0.1.9",
3
+ "version": "0.1.11",
4
4
  "description": "Scheduled cron tasks, background automation and agent execution for DeepSeek Harness.",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",