@goodandready/dsh-cron 0.1.10 → 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.
Files changed (3) hide show
  1. package/lib/client.js +143 -30
  2. package/lib/store.js +17 -12
  3. package/package.json +1 -1
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/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.10",
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",