@goodandready/dsh-cron 0.2.18 → 0.2.19
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 +16 -1
- package/README.ru.md +17 -2
- package/README.zh.md +17 -2
- package/lib/best-effort.js +29 -0
- package/lib/channels.js +6 -1
- package/lib/client.js +59 -38
- package/lib/llm-actions.js +9 -6
- package/lib/llm-ask.js +8 -9
- package/lib/recipes.js +3 -1
- package/lib/runner.js +34 -39
- package/lib/runtimes.js +3 -3
- package/lib/scheduler.js +8 -21
- package/lib/secrets.js +6 -7
- package/lib/store.js +5 -8
- package/lib/templates.js +5 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -392,6 +392,15 @@ Developer-facing, no behaviour change. `parseScheduleExpression` was split into
|
|
|
392
392
|
|
|
393
393
|
|
|
394
394
|
|
|
395
|
+
### 31. Quality & Preflight Hardening Pack (v0.2.19, #149, #152, #155, #156, #162)
|
|
396
|
+
- **Zero Empty Catch Blocks (#156)**: Implemented `lib/best-effort.js` matching standard architecture with synchronous/asynchronous error suppression, fallback value handling, and optional context logging. Eliminated all 63 empty catch blocks across runner, scheduler, store, and UI client scripts.
|
|
397
|
+
- **CI Workflows & Local Preflight Gate (#162)**: Added automated CI workflows (`.gitea/workflows/ci.yml` and `.github/workflows/ci.yml`) running test suites and enforcing a strict preflight gate (`scripts/ci-preflight.mjs`) that halts on any syntax error, empty catch, theme hardcoding, or leak attempt.
|
|
398
|
+
- **Theme Token Modernization (#149)**: Replaced all remaining raw `rgba(...)` declarations in UI styles and modals with native `color-mix(in srgb, var(--token) N%, transparent)`.
|
|
399
|
+
- **Plugin Manifest Declarations (#152)**: Updated `dsh.client.inject` in `package.json` to declare full package dependencies (`@deepseek-ai/dsh-client-locale`, `@deepseek-ai/dsh-client-ui-slots`).
|
|
400
|
+
- **Production Export Integration (#155)**: Connected test-only exports (`findDestructiveRecipe` in `listRecipes`, `shouldNotifyTask` in `shouldSendToChannel`, and `TEMPLATE_VARIABLES` in `buildTemplateVars`) into active production pipelines.
|
|
401
|
+
|
|
402
|
+
---
|
|
403
|
+
|
|
395
404
|
### 30. Updater Localization in English & Chinese (v0.2.18, #160)
|
|
396
405
|
- **Settings Card Updater Localization (#160)**: Added native translations for all 10 in-app updater keys (`updater.title`, `updater.btnCheck`, `updater.checking`, `updater.btnUpdate`, `updater.updating`, `updater.desc`, `updater.current`, `updater.available`, `updater.upToDate`, `updater.success`) into English (`en`) and Chinese (`zh`) dictionaries in `lib/client-src/10-locales.js`. In accordance with DSH core plugin architecture, Russian localization is maintained externally via `dsh-russian-lang`.
|
|
397
406
|
|
|
@@ -529,7 +538,7 @@ All endpoints are served by the DSH web server under `/dsh-cron/`. Read endpoint
|
|
|
529
538
|
|
|
530
539
|
---
|
|
531
540
|
|
|
532
|
-
## 🧪 Testing
|
|
541
|
+
## 🧪 Testing & Preflight Verification
|
|
533
542
|
|
|
534
543
|
Run the automated test suite covering schedule parsing, the scheduler engine, atomic storage, HTTP helpers, notifications and tool contracts:
|
|
535
544
|
|
|
@@ -537,6 +546,12 @@ Run the automated test suite covering schedule parsing, the scheduler engine, at
|
|
|
537
546
|
npm test
|
|
538
547
|
```
|
|
539
548
|
|
|
549
|
+
Run the local preflight gate to verify syntax, zero empty catch blocks, theme token compliance, package boundaries and leak protection:
|
|
550
|
+
|
|
551
|
+
```bash
|
|
552
|
+
node scripts/ci-preflight.mjs
|
|
553
|
+
```
|
|
554
|
+
|
|
540
555
|
---
|
|
541
556
|
|
|
542
557
|
## 📄 License
|
package/README.ru.md
CHANGED
|
@@ -268,6 +268,15 @@ cron_create_task({
|
|
|
268
268
|
|
|
269
269
|
|
|
270
270
|
|
|
271
|
+
### 31. Пакет ужесточения стандартов качества и CI (v0.2.19, #149, #152, #155, #156, #162)
|
|
272
|
+
- **Устранение пустых catch (#156)**: Реализован модуль `lib/best-effort.js` по единому стандарту с безопасным выполнением синхронных/асинхронных операций и опциональным логированием. Все 63 пустых catch-блока в планировщике, раннере, хранилище и клиентских фрагментах устранены.
|
|
273
|
+
- **Автоматический CI и локальный гейт Preflight (#162)**: Добавлены workflows непрерывной интеграции (`.gitea/workflows/ci.yml` и `.github/workflows/ci.yml`), проверяющие синтаксис, тесты и запускающие механический аудит `scripts/ci-preflight.mjs` с блокировкой любых отклонений.
|
|
274
|
+
- **Модернизация токенов темы (#149)**: Оставшиеся цвета `rgba(...)` в модальных окнах и карточках переведены на современный стандарт `color-mix(in srgb, var(--токен) N%, transparent)`.
|
|
275
|
+
- **Декларация зависимостей в манифесте (#152)**: В `package.json` поле `dsh.client.inject` переведено на полные имена пакетов Cordis (`@deepseek-ai/dsh-client-locale`, `@deepseek-ai/dsh-client-ui-slots`).
|
|
276
|
+
- **Активация экспортов в рабочем коде (#155)**: Функции `findDestructiveRecipe`, `shouldNotifyTask` и переменные `TEMPLATE_VARIABLES` подключены в активные цепочки работы с рецептами, роутинга уведомлений и рендеринга шаблонов.
|
|
277
|
+
|
|
278
|
+
---
|
|
279
|
+
|
|
271
280
|
### 30. Локализация автообновления на английский и китайский языки (v0.2.18, #160)
|
|
272
281
|
- **Локализация карточки обновления (#160)**: Добавлены нативные переводы для всех 10 ключей модуля автообновления (`updater.title`, `updater.btnCheck`, `updater.checking`, `updater.btnUpdate`, `updater.updating`, `updater.desc`, `updater.current`, `updater.available`, `updater.upToDate`, `updater.success`) в словари английского (`en`) и китайского (`zh`) языков в `lib/client-src/10-locales.js`. В соответствии со стандартами ядра DSH плагин поставляется с EN и ZH словарями, а русская локализация обслуживается через системный плагин `dsh-russian-lang`.
|
|
273
282
|
|
|
@@ -526,13 +535,19 @@ bash deploy.sh verify [exact-version]
|
|
|
526
535
|
|
|
527
536
|
---
|
|
528
537
|
|
|
529
|
-
## 🧪 Тестирование
|
|
538
|
+
## 🧪 Тестирование и локальная проверка (Preflight)
|
|
539
|
+
|
|
540
|
+
Запуск автоматического набора тестов (разбор расписаний, движок планировщика, атомарное хранилище, HTTP-хелперы, уведомления и контракт инструментов):
|
|
530
541
|
|
|
531
542
|
```bash
|
|
532
543
|
npm test
|
|
533
544
|
```
|
|
534
545
|
|
|
535
|
-
|
|
546
|
+
Локальный запуск гейта стандартов качества (синтаксис, отсутствие пустых catch, соответствие токенам тем оформления, чистота состава npm-пакета и защита от утечек):
|
|
547
|
+
|
|
548
|
+
```bash
|
|
549
|
+
node scripts/ci-preflight.mjs
|
|
550
|
+
```
|
|
536
551
|
|
|
537
552
|
---
|
|
538
553
|
|
package/README.zh.md
CHANGED
|
@@ -391,6 +391,15 @@ bash deploy.sh verify [exact-version]
|
|
|
391
391
|
|
|
392
392
|
|
|
393
393
|
|
|
394
|
+
### 31. 质量加固与 CI 预检标准包 (v0.2.19, #149, #152, #155, #156, #162)
|
|
395
|
+
- **彻底消除空 catch 块 (#156)**:引入符合标准规范的 `lib/best-effort.js` 模块,全面支持同步/异步安全调用、回退返回值和可选日志记录,清除了 runner、scheduler、store 及客户端中的所有 63 处空 catch 块。
|
|
396
|
+
- **CI 工作流与本地预检门禁 (#162)**:新增自动化 CI 工作流(`.gitea/workflows/ci.yml` 与 `.github/workflows/ci.yml`),集成本地预检门禁脚本 `scripts/ci-preflight.mjs`,在出现语法错误、空 catch、颜色硬编码或信息泄露时自动阻断。
|
|
397
|
+
- **主题设计令牌现代化 (#149)**:将模态对话框与设置卡片中遗留的 `rgba(...)` 全部重构为原生的 `color-mix(in srgb, var(--token) N%, transparent)`。
|
|
398
|
+
- **插件清单注入声明规范化 (#152)**:在 `package.json` 的 `dsh.client.inject` 中统一声明完整的 Cordis 包名(`@deepseek-ai/dsh-client-locale`、`@deepseek-ai/dsh-client-ui-slots`)。
|
|
399
|
+
- **生产管线导出连接 (#155)**:将此前仅在测试中调用的导出(`findDestructiveRecipe`、`shouldNotifyTask` 和 `TEMPLATE_VARIABLES`)全面接入配方过滤、通知分发及模板生成核心逻辑。
|
|
400
|
+
|
|
401
|
+
---
|
|
402
|
+
|
|
394
403
|
### 30. 自更新模块英文与中文多语言支持 (v0.2.18, #160)
|
|
395
404
|
- **设置面板自更新多语言 (#160)**:在 `lib/client-src/10-locales.js` 的英文 (`en`) 与中文 (`zh`) 字典中补全了全部 10 个自更新键值 (`updater.title`, `updater.btnCheck`, `updater.checking`, `updater.btnUpdate`, `updater.updating`, `updater.desc`, `updater.current`, `updater.available`, `updater.upToDate`, `updater.success`)。遵循 DSH 插件规范,插件核心内置英文与中文,俄语多语言由 `dsh-russian-lang` 统一扩展。
|
|
396
405
|
|
|
@@ -526,13 +535,19 @@ dsh-cron:
|
|
|
526
535
|
|
|
527
536
|
---
|
|
528
537
|
|
|
529
|
-
## 🧪
|
|
538
|
+
## 🧪 测试与预检门禁 (Preflight)
|
|
539
|
+
|
|
540
|
+
运行全套自动化测试(调度表达式解析、调度器引擎、原子存储、HTTP 辅助函数、通知与工具契约):
|
|
530
541
|
|
|
531
542
|
```bash
|
|
532
543
|
npm test
|
|
533
544
|
```
|
|
534
545
|
|
|
535
|
-
|
|
546
|
+
本地执行质量预检门禁(语法检查、零空 catch 块、主题设计规范、npm 发布归档合规性及防泄漏检查):
|
|
547
|
+
|
|
548
|
+
```bash
|
|
549
|
+
node scripts/ci-preflight.mjs
|
|
550
|
+
```
|
|
536
551
|
|
|
537
552
|
---
|
|
538
553
|
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Run a non-critical side effect; never throw.
|
|
3
|
+
* Logs at debug when a logger is provided (matches dsh-key-rotation pattern).
|
|
4
|
+
*/
|
|
5
|
+
export function bestEffort(label, fn, logger) {
|
|
6
|
+
const log = (err) => {
|
|
7
|
+
try {
|
|
8
|
+
logger?.debug?.(`dsh-cron: best-effort ${label}`, err);
|
|
9
|
+
} catch {
|
|
10
|
+
/* logger itself must not throw */
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
try {
|
|
14
|
+
const result = fn();
|
|
15
|
+
if (result && typeof result.then === "function") {
|
|
16
|
+
return result.then(
|
|
17
|
+
(value) => value,
|
|
18
|
+
(err) => {
|
|
19
|
+
log(err);
|
|
20
|
+
return undefined;
|
|
21
|
+
},
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
return result;
|
|
25
|
+
} catch (err) {
|
|
26
|
+
log(err);
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
}
|
package/lib/channels.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* collected so one broken channel cannot hide another.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { formatTaskTelegramMessage, sendTelegramMessage, createTelegramTaskKeyboard } from './telegram.js';
|
|
10
|
+
import { formatTaskTelegramMessage, sendTelegramMessage, createTelegramTaskKeyboard, shouldNotifyTask } from './telegram.js';
|
|
11
11
|
import { createKanbanCard, shouldCreateKanbanCard } from './integrations.js';
|
|
12
12
|
import { resolveTemplateText, truncateText } from './templates.js';
|
|
13
13
|
|
|
@@ -58,6 +58,11 @@ export function unknownChannels(task) {
|
|
|
58
58
|
/** Failure/only-on-failure filtering, per channel. */
|
|
59
59
|
export function shouldSendToChannel(channelId, task, runInfo, settings = {}) {
|
|
60
60
|
if (channelId === 'kanban') return shouldCreateKanbanCard(task, runInfo);
|
|
61
|
+
if (channelId === 'telegram') {
|
|
62
|
+
if (task && task.notifyTelegram === false) return false;
|
|
63
|
+
const taskForTelegram = Object.assign({}, task, { notifyTelegram: true });
|
|
64
|
+
return shouldNotifyTask(taskForTelegram, runInfo, settings);
|
|
65
|
+
}
|
|
61
66
|
const onlyOnFail = task.onlyOnFailure !== undefined
|
|
62
67
|
? Boolean(task.onlyOnFailure)
|
|
63
68
|
: Boolean(settings.onlyOnFailure);
|
package/lib/client.js
CHANGED
|
@@ -49,7 +49,9 @@ window.__ModuleLoader__.load({
|
|
|
49
49
|
try {
|
|
50
50
|
const primitives = require('@deepseek-ai/dsh-client-ui-primitives');
|
|
51
51
|
CoreChevron = primitives && primitives.IconChevronDownOutline14;
|
|
52
|
-
} catch (e) {
|
|
52
|
+
} catch (e) {
|
|
53
|
+
/* Optional core chevron primitive not available; SVG fallback used */
|
|
54
|
+
}
|
|
53
55
|
const chevronNode = () => (CoreChevron ? React.createElement(CoreChevron) : React.createElement('span', { style: { display: 'flex', alignItems: 'center' }, dangerouslySetInnerHTML: { __html: ICON_CHEVRON_DOWN } }));
|
|
54
56
|
|
|
55
57
|
// Own React error boundary (#106): the core's SlotErrorBoundary renders an
|
|
@@ -622,15 +624,15 @@ window.__ModuleLoader__.load({
|
|
|
622
624
|
const STYLES = `
|
|
623
625
|
:root {
|
|
624
626
|
--dsh-cron-success: var(--dsw-alias-state-success-primary, #10b981);
|
|
625
|
-
--dsh-cron-success-bg:
|
|
627
|
+
--dsh-cron-success-bg: color-mix(in srgb, var(--dsh-cron-success, #10b981) 10%, transparent);
|
|
626
628
|
--dsh-cron-danger: var(--dsw-alias-state-error-primary, #ef4444);
|
|
627
|
-
--dsh-cron-danger-bg:
|
|
629
|
+
--dsh-cron-danger-bg: color-mix(in srgb, var(--dsh-cron-danger, #ef4444) 10%, transparent);
|
|
628
630
|
--dsh-cron-info: var(--dsw-alias-state-brand-primary, #60a5fa);
|
|
629
|
-
--dsh-cron-info-bg:
|
|
631
|
+
--dsh-cron-info-bg: color-mix(in srgb, var(--dsh-cron-info, #60a5fa) 12%, transparent);
|
|
630
632
|
--dsh-cron-accent: var(--dsw-alias-state-info-primary, #c084fc);
|
|
631
|
-
--dsh-cron-accent-bg:
|
|
633
|
+
--dsh-cron-accent-bg: color-mix(in srgb, var(--dsh-cron-accent, #a855f7) 12%, transparent);
|
|
632
634
|
--dsh-cron-warning: var(--dsw-alias-state-warning-primary, #eab308);
|
|
633
|
-
--dsh-cron-warning-bg:
|
|
635
|
+
--dsh-cron-warning-bg: color-mix(in srgb, var(--dsh-cron-warning, #eab308) 12%, transparent);
|
|
634
636
|
}
|
|
635
637
|
[data-pane=conversation], [class*=centerCol] {
|
|
636
638
|
position: relative !important;
|
|
@@ -660,14 +662,14 @@ window.__ModuleLoader__.load({
|
|
|
660
662
|
|
|
661
663
|
.dsh-cron-top-bar { display: flex; align-items: center; gap: 12px; }
|
|
662
664
|
.dsh-cron-back-btn { appearance: none; font: inherit; cursor: pointer; color: var(--dsw-alias-label-secondary, #9ca3af); background: 0 0; border: 1px solid var(--dsw-alias-border-l2, #383838); border-radius: 8px; align-items: center; gap: 6px; padding: 4px 12px; font-size: 13px; display: inline-flex; flex: none; height: 32px; box-sizing: border-box; }
|
|
663
|
-
.dsh-cron-back-btn:hover { color: var(--dsw-alias-label-primary, #ededed); background: var(--dsw-alias-bg-layer-4, var(--dsw-alias-interactive-bg-hover,
|
|
665
|
+
.dsh-cron-back-btn:hover { color: var(--dsw-alias-label-primary, #ededed); background: var(--dsw-alias-bg-layer-4, var(--dsw-alias-interactive-bg-hover, color-mix(in srgb, var(--dsw-alias-label-primary, #fff) 6%, transparent))); }
|
|
664
666
|
|
|
665
667
|
.dsh-cron-header { display: flex; justify-content: space-between; align-items: flex-start; gap: 12px; padding-bottom: 16px; border-bottom: 1px solid var(--dsw-alias-border-l2, #383838); }
|
|
666
668
|
.dsh-cron-title { font-size: 22px; font-weight: 700; letter-spacing: -0.02em; margin-bottom: 6px; }
|
|
667
669
|
.dsh-cron-subtitle { font-size: 14px; color: var(--dsw-alias-label-secondary, #9ca3af); line-height: 1.5; }
|
|
668
670
|
.dsh-cron-create-btn { appearance: none; display: inline-flex; align-items: center; gap: 6px; background: var(--dsw-alias-bg-layer-2, #262626); color: var(--dsw-alias-label-primary, #fff); border: 1px solid var(--dsw-alias-border-l2, #383838); border-radius: 9999px; padding: 8px 16px; font-size: 14px; font-weight: 500; cursor: pointer; position: relative; }
|
|
669
671
|
.dsh-cron-create-btn:hover { background: var(--dsw-alias-bg-layer-hover, var(--dsw-alias-interactive-bg-hover, #333)); }
|
|
670
|
-
.dsh-cron-dropdown { position: absolute; top: calc(100% + 6px); right: 0; width: 200px; background: var(--dsw-alias-bg-layer-3, #212121); border: 1px solid var(--dsw-alias-border-l2, #383838); border-radius: 12px; padding: 6px; box-shadow: 0 10px 25px
|
|
672
|
+
.dsh-cron-dropdown { position: absolute; top: calc(100% + 6px); right: 0; width: 200px; background: var(--dsw-alias-bg-layer-3, #212121); border: 1px solid var(--dsw-alias-border-l2, #383838); border-radius: 12px; padding: 6px; box-shadow: 0 10px 25px color-mix(in srgb, var(--dsw-alias-bg-mask, #000) 50%, transparent); z-index: 200; }
|
|
671
673
|
.dsh-cron-dropdown-item { display: flex; align-items: center; gap: 10px; width: 100%; padding: 10px 12px; background: transparent; border: none; border-radius: 8px; color: var(--dsw-alias-label-primary, #ededed); font-size: 13.5px; text-align: left; cursor: pointer; }
|
|
672
674
|
.dsh-cron-dropdown-item:hover { background: var(--dsw-alias-interactive-bg-hover, #2a2a2a); }
|
|
673
675
|
.dsh-cron-search-bar { position: relative; margin-bottom: 0; }
|
|
@@ -677,7 +679,7 @@ window.__ModuleLoader__.load({
|
|
|
677
679
|
.dsh-cron-filters { margin-top: 8px; }
|
|
678
680
|
.dsh-cron-filter-toggle { background: none; border: 1px solid var(--dsw-alias-border-l2, #333); color: var(--dsw-alias-label-secondary, #aaa); border-radius: 8px; padding: 4px 10px; font-size: 12px; cursor: pointer; }
|
|
679
681
|
.dsh-cron-filter-toggle:hover { color: var(--dsw-alias-label-primary, #eee); border-color: var(--dsh-cron-info); }
|
|
680
|
-
.dsh-cron-filter-panel { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 10px; margin-top: 10px; padding: 10px 12px; border: 1px solid var(--dsw-alias-border-l2, #2e2e2e); border-radius: 8px; background: var(--dsw-alias-bg-layer-3,
|
|
682
|
+
.dsh-cron-filter-panel { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 10px; margin-top: 10px; padding: 10px 12px; border: 1px solid var(--dsw-alias-border-l2, #2e2e2e); border-radius: 8px; background: var(--dsw-alias-bg-layer-3, color-mix(in srgb, var(--dsw-alias-label-primary, #fff) 2%, transparent)); }
|
|
681
683
|
.dsh-cron-filter-field { display: flex; flex-direction: column; gap: 4px; font-size: 12px; color: var(--dsw-alias-label-tertiary, #888); }
|
|
682
684
|
.dsh-cron-filter-field select { background: var(--dsw-alias-bg-layer-2, #1f1f1f); border: 1px solid var(--dsw-alias-border-l2, #333); color: var(--dsw-alias-label-primary, inherit); border-radius: 6px; padding: 5px 8px; font-size: 12.5px; }
|
|
683
685
|
.dsh-cron-tabs { display: flex; flex-wrap: wrap; gap: 8px; }
|
|
@@ -694,7 +696,7 @@ window.__ModuleLoader__.load({
|
|
|
694
696
|
.dsh-cron-tag-session { background: var(--dsh-cron-info-bg); color: var(--dsh-cron-info); border-color: var(--dsh-cron-info); }
|
|
695
697
|
.dsh-cron-tag-preflight { background: var(--dsh-cron-warning-bg); color: var(--dsh-cron-warning); border-color: var(--dsh-cron-warning); }
|
|
696
698
|
|
|
697
|
-
.dsh-cron-task-model-tag { display: inline-flex; align-items: center; font-size: 11px; padding: 2px 8px; border-radius: 999px; border: 1px solid var(--dsw-alias-border-l2, #383838); background: var(--dsw-alias-bg-layer-2,
|
|
699
|
+
.dsh-cron-task-model-tag { display: inline-flex; align-items: center; font-size: 11px; padding: 2px 8px; border-radius: 999px; border: 1px solid var(--dsw-alias-border-l2, #383838); background: var(--dsw-alias-bg-layer-2, color-mix(in srgb, var(--dsw-alias-label-primary, #fff) 6%, transparent)); color: var(--dsw-alias-label-secondary, #aaa); margin-left: 8px; vertical-align: middle; }
|
|
698
700
|
.dsh-cron-task-actions { display: flex; gap: 8px; }
|
|
699
701
|
.dsh-cron-icon-btn { appearance: none; font: inherit; background: var(--dsw-alias-bg-layer-2, transparent); border: 1px solid var(--dsw-alias-border-l2, #333); border-radius: 8px; padding: 6px 12px; font-size: 12.5px; font-weight: 500; color: var(--dsw-alias-label-primary, #aaa); cursor: pointer; display: inline-flex; align-items: center; gap: 6px; transition: all 0.15s ease; }
|
|
700
702
|
.dsh-cron-icon-btn:hover { background: var(--dsw-alias-bg-layer-4, #2a2a2a); border-color: var(--dsw-alias-label-dimmed, var(--dsw-alias-border-l2, #444)); }
|
|
@@ -706,14 +708,14 @@ window.__ModuleLoader__.load({
|
|
|
706
708
|
.dsh-cron-recs-list { display: flex; flex-direction: column; gap: 10px; }
|
|
707
709
|
.dsh-cron-rec-card { display: flex; align-items: center; gap: 16px; padding: 14px 18px; background: var(--dsw-alias-bg-layer-3, #1c1c1c); border: 1px solid var(--dsw-alias-border-l2, #282828); border-radius: 12px; cursor: pointer; transition: background 0.15s; }
|
|
708
710
|
.dsh-cron-rec-card:hover, .dsh-cron-rec-card:focus-visible { background: var(--dsw-alias-bg-layer-4, #242424); border-color: var(--dsw-alias-label-dimmed, var(--dsw-alias-border-l2, #383838)); outline: none; }
|
|
709
|
-
.dsh-cron-rec-icon { width: 36px; height: 36px; border-radius: 10px; background: var(--dsw-alias-bg-layer-2,
|
|
711
|
+
.dsh-cron-rec-icon { width: 36px; height: 36px; border-radius: 10px; background: var(--dsw-alias-bg-layer-2, color-mix(in srgb, var(--dsw-alias-label-primary, #fff) 3%, transparent)); display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
|
|
710
712
|
.dsh-cron-rec-content { flex: 1; min-width: 0; }
|
|
711
713
|
.dsh-cron-rec-title { font-size: 14.5px; font-weight: 500; margin-bottom: 4px; }
|
|
712
714
|
.dsh-cron-rec-time { font-size: 13px; color: var(--dsw-alias-label-secondary, #9ca3af); font-weight: normal; margin-left: 8px; }
|
|
713
715
|
.dsh-cron-rec-desc { font-size: 13px; color: var(--dsw-alias-label-tertiary, #71717a); }
|
|
714
716
|
|
|
715
|
-
.dsh-cron-modal-overlay { position: fixed; inset: 0; background: var(--dsw-alias-bg-mask,
|
|
716
|
-
.dsh-cron-modal { background: var(--dsw-alias-bg-layer-2, #1f1f1f); border: 1px solid var(--dsw-alias-border-l2, #333); border-radius: 16px; width: 100%; max-width: 540px; max-height: min(90vh, calc(100vh - 36px)); overflow-y: auto; padding: 24px; box-shadow: 0 20px 40px
|
|
717
|
+
.dsh-cron-modal-overlay { position: fixed; inset: 0; background: var(--dsw-alias-bg-mask, color-mix(in srgb, #000 75%, transparent)); backdrop-filter: blur(4px); display: flex; align-items: center; justify-content: center; z-index: 1000; padding: 16px; box-sizing: border-box; overflow-y: auto; }
|
|
718
|
+
.dsh-cron-modal { background: var(--dsw-alias-bg-layer-2, #1f1f1f); border: 1px solid var(--dsw-alias-border-l2, #333); border-radius: 16px; width: 100%; max-width: 540px; max-height: min(90vh, calc(100vh - 36px)); overflow-y: auto; padding: 24px; box-shadow: 0 20px 40px color-mix(in srgb, var(--dsw-alias-bg-mask, #000) 60%, transparent); box-sizing: border-box; }
|
|
717
719
|
.dsh-cron-modal-scroll { max-width: 640px; max-height: min(90vh, calc(100vh - 36px)); overflow-y: auto; }
|
|
718
720
|
.dsh-cron-modal::-webkit-scrollbar { width: 6px; }
|
|
719
721
|
.dsh-cron-modal::-webkit-scrollbar-track { background: transparent; }
|
|
@@ -772,7 +774,7 @@ window.__ModuleLoader__.load({
|
|
|
772
774
|
.dsh-cron-history-empty { text-align: center; padding: 36px 0; color: var(--dsw-alias-label-tertiary, #666); font-size: 13.5px; }
|
|
773
775
|
|
|
774
776
|
.dsh-cron-stats-bar { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 10px; margin-bottom: 0; }
|
|
775
|
-
.dsh-cron-stat-card { background: var(--dsw-alias-bg-layer-2,
|
|
777
|
+
.dsh-cron-stat-card { background: var(--dsw-alias-bg-layer-2, color-mix(in srgb, var(--dsw-alias-label-primary, #fff) 2%, transparent)); border: 1px solid var(--dsw-alias-border-l2, #222); border-radius: 8px; padding: 12px 14px; display: flex; flex-direction: column; gap: 4px; }
|
|
776
778
|
.dsh-cron-stat-val { font-size: 18px; font-weight: 700; color: var(--dsw-alias-label-primary, #fff); }
|
|
777
779
|
.dsh-cron-stat-lbl { font-size: 12px; color: var(--dsw-alias-label-secondary, #777); margin-top: 0; }
|
|
778
780
|
.dsh-cron-oneshot-tag { display: inline-flex; align-items: center; font-size: 11px; padding: 2px 8px; border-radius: 999px; border: 1px solid var(--dsh-cron-accent); background: var(--dsh-cron-accent-bg); color: var(--dsh-cron-accent); font-weight: 500; }
|
|
@@ -787,7 +789,7 @@ window.__ModuleLoader__.load({
|
|
|
787
789
|
.dsh-cron-section-title { font-weight: 500; font-size: 12.5px; color: var(--dsw-alias-label-secondary, #aaa); margin-bottom: 10px; }
|
|
788
790
|
.dsh-cron-secret-row { display: flex; align-items: center; gap: 8px; }
|
|
789
791
|
.dsh-cron-secret-row input { flex: 1; }
|
|
790
|
-
.dsh-cron-badge-ref { font-size: 11px; padding: 2px 6px; border-radius: 4px; background: var(--dsw-alias-bg-layer-3,
|
|
792
|
+
.dsh-cron-badge-ref { font-size: 11px; padding: 2px 6px; border-radius: 4px; background: var(--dsw-alias-bg-layer-3, color-mix(in srgb, var(--dsw-alias-label-primary, #fff) 6%, transparent)); color: var(--dsw-alias-label-tertiary, #888); white-space: nowrap; }
|
|
791
793
|
.dsh-cron-status-banner { padding: 10px 14px; border-radius: 8px; font-size: 13px; margin-top: 12px; }
|
|
792
794
|
.dsh-cron-status-banner[data-type="success"] { background: var(--dsh-cron-success-bg); border: 1px solid var(--dsh-cron-success); color: var(--dsh-cron-success); }
|
|
793
795
|
.dsh-cron-status-banner[data-type="error"] { background: var(--dsh-cron-danger-bg); border: 1px solid var(--dsh-cron-danger); color: var(--dsh-cron-danger); }
|
|
@@ -798,13 +800,13 @@ window.__ModuleLoader__.load({
|
|
|
798
800
|
/* #34: sidebar job list */
|
|
799
801
|
.dsh-cron-sidebar-jobs { display: flex; flex-direction: column; gap: 2px; margin: 2px 0 6px 0; }
|
|
800
802
|
.dsh-cron-sidebar-jobs-head { display: flex; align-items: center; justify-content: space-between; width: 100%; background: none; border: none; padding: 4px 8px; font: inherit; font-size: 12px; color: var(--dsw-alias-label-tertiary, #888); cursor: pointer; border-radius: 6px; text-align: left; }
|
|
801
|
-
.dsh-cron-sidebar-jobs-head:hover { color: var(--dsw-alias-label-primary, #eee); background: var(--dsw-alias-interactive-bg-hover,
|
|
803
|
+
.dsh-cron-sidebar-jobs-head:hover { color: var(--dsw-alias-label-primary, #eee); background: var(--dsw-alias-interactive-bg-hover, color-mix(in srgb, var(--dsw-alias-label-primary, #fff) 4%, transparent)); }
|
|
802
804
|
.dsh-cron-sidebar-jobs-head::after { content: '▾'; font-size: 10px; transition: transform 0.15s ease; }
|
|
803
805
|
.dsh-cron-sidebar-jobs[data-open="false"] .dsh-cron-sidebar-jobs-head::after { transform: rotate(-90deg); }
|
|
804
806
|
.dsh-cron-sidebar-jobs[data-open="false"] .dsh-cron-sidebar-jobs-list { display: none; }
|
|
805
807
|
.dsh-cron-sidebar-jobs-list { display: flex; flex-direction: column; gap: 2px; padding-left: 10px; }
|
|
806
808
|
.dsh-cron-sidebar-job { display: flex; flex-direction: column; align-items: flex-start; gap: 1px; width: 100%; background: none; border: none; border-left: 2px solid transparent; padding: 3px 8px; font: inherit; cursor: pointer; border-radius: 0 6px 6px 0; text-align: left; }
|
|
807
|
-
.dsh-cron-sidebar-job:hover { background: var(--dsw-alias-interactive-bg-hover,
|
|
809
|
+
.dsh-cron-sidebar-job:hover { background: var(--dsw-alias-interactive-bg-hover, color-mix(in srgb, var(--dsw-alias-label-primary, #fff) 4%, transparent)); border-left-color: var(--dsh-cron-info); }
|
|
808
810
|
.dsh-cron-sidebar-job-title { font-size: 12px; color: var(--dsw-alias-label-secondary, #ccc); max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
809
811
|
.dsh-cron-sidebar-job-meta { font-size: 11px; color: var(--dsw-alias-label-tertiary, #777); }
|
|
810
812
|
.dsh-cron-sidebar-empty, .dsh-cron-sidebar-more { font-size: 11px; color: var(--dsw-alias-label-tertiary, #777); padding: 2px 8px; }
|
|
@@ -987,7 +989,9 @@ window.__ModuleLoader__.load({
|
|
|
987
989
|
try {
|
|
988
990
|
sessions.open(sessionId);
|
|
989
991
|
return true;
|
|
990
|
-
} catch (
|
|
992
|
+
} catch (err) {
|
|
993
|
+
/* sessions.open threw synchronously */
|
|
994
|
+
}
|
|
991
995
|
}
|
|
992
996
|
|
|
993
997
|
// 2. If the session is not in manager.summaries yet, refresh from the backend
|
|
@@ -996,7 +1000,9 @@ window.__ModuleLoader__.load({
|
|
|
996
1000
|
await sessions.refresh();
|
|
997
1001
|
sessions.open(sessionId);
|
|
998
1002
|
return true;
|
|
999
|
-
} catch (
|
|
1003
|
+
} catch (err) {
|
|
1004
|
+
/* sessions.refresh threw */
|
|
1005
|
+
}
|
|
1000
1006
|
}
|
|
1001
1007
|
|
|
1002
1008
|
// 3. Async retries until the WebSocket/SSE event adds the session to the list
|
|
@@ -1007,7 +1013,9 @@ window.__ModuleLoader__.load({
|
|
|
1007
1013
|
try {
|
|
1008
1014
|
sessions.open(sessionId);
|
|
1009
1015
|
return true;
|
|
1010
|
-
} catch (
|
|
1016
|
+
} catch (err) {
|
|
1017
|
+
/* retry polling sessions.open threw */
|
|
1018
|
+
}
|
|
1011
1019
|
}
|
|
1012
1020
|
// DOM fallback: click the session in the sidebar if it has rendered
|
|
1013
1021
|
if (typeof document !== 'undefined') {
|
|
@@ -1323,7 +1331,7 @@ window.__ModuleLoader__.load({
|
|
|
1323
1331
|
if (!node) return;
|
|
1324
1332
|
if (typeof node.scrollIntoView === 'function') node.scrollIntoView({ block: 'center' });
|
|
1325
1333
|
node.classList.add('dsh-cron-task-highlight');
|
|
1326
|
-
setTimeout(() => { try { node.classList.remove('dsh-cron-task-highlight'); } catch (
|
|
1334
|
+
setTimeout(() => { try { node.classList.remove('dsh-cron-task-highlight'); } catch (err) { /* node unmounted */ } }, 2500);
|
|
1327
1335
|
}, [tasks]);
|
|
1328
1336
|
|
|
1329
1337
|
const fetchTasks = async () => {
|
|
@@ -1435,7 +1443,9 @@ window.__ModuleLoader__.load({
|
|
|
1435
1443
|
try {
|
|
1436
1444
|
await fetch('/dsh-cron/tasks/' + encodeURIComponent(id) + '/toggle', { method: 'POST' });
|
|
1437
1445
|
fetchTasks();
|
|
1438
|
-
} catch (err) {
|
|
1446
|
+
} catch (err) {
|
|
1447
|
+
console.debug('[dsh-cron] toggle failed', err);
|
|
1448
|
+
}
|
|
1439
1449
|
};
|
|
1440
1450
|
|
|
1441
1451
|
const handleRunNow = async (id) => {
|
|
@@ -1650,7 +1660,10 @@ window.__ModuleLoader__.load({
|
|
|
1650
1660
|
try {
|
|
1651
1661
|
await fetch('/dsh-cron/tasks/' + encodeURIComponent(id), { method: 'DELETE' });
|
|
1652
1662
|
fetchTasks();
|
|
1653
|
-
} catch (err) {
|
|
1663
|
+
} catch (err) {
|
|
1664
|
+
console.debug('[dsh-cron] delete failed', err);
|
|
1665
|
+
}
|
|
1666
|
+
};
|
|
1654
1667
|
|
|
1655
1668
|
const openSettingsModal = async () => {
|
|
1656
1669
|
setTestStatus(null);
|
|
@@ -1668,7 +1681,9 @@ window.__ModuleLoader__.load({
|
|
|
1668
1681
|
setSettingsHasDefault(Boolean(data.settings.hasDefaultCredentials));
|
|
1669
1682
|
setSettingsHasCustomBotToken(Boolean(data.settings.hasCustomBotToken));
|
|
1670
1683
|
}
|
|
1671
|
-
} catch (
|
|
1684
|
+
} catch (err) {
|
|
1685
|
+
console.debug('[dsh-cron] settings fetch failed', err);
|
|
1686
|
+
}
|
|
1672
1687
|
};
|
|
1673
1688
|
|
|
1674
1689
|
const handleSaveSettings = async (e) => {
|
|
@@ -2715,7 +2730,7 @@ window.__ModuleLoader__.load({
|
|
|
2715
2730
|
React.createElement('label', null, T_KEY(t, 'form.sshTargetLabel')),
|
|
2716
2731
|
React.createElement('input', {
|
|
2717
2732
|
value: formSshTarget,
|
|
2718
|
-
placeholder: '
|
|
2733
|
+
placeholder: 'user@example.com',
|
|
2719
2734
|
onChange: (e) => setFormSshTarget(e.target.value)
|
|
2720
2735
|
}),
|
|
2721
2736
|
React.createElement('input', {
|
|
@@ -2828,7 +2843,7 @@ window.__ModuleLoader__.load({
|
|
|
2828
2843
|
),
|
|
2829
2844
|
React.createElement('div', {
|
|
2830
2845
|
style: {
|
|
2831
|
-
background: 'var(--dsw-alias-bg-layer-3,
|
|
2846
|
+
background: 'var(--dsw-alias-bg-layer-3, color-mix(in srgb, var(--dsw-alias-label-primary, #fff) 2%, transparent))',
|
|
2832
2847
|
border: '1px solid var(--dsw-alias-border-l2, #2e2e2e)',
|
|
2833
2848
|
borderRadius: '8px',
|
|
2834
2849
|
padding: '12px 14px',
|
|
@@ -2991,7 +3006,7 @@ window.__ModuleLoader__.load({
|
|
|
2991
3006
|
),
|
|
2992
3007
|
selectedTaskMeta && React.createElement('div', {
|
|
2993
3008
|
style: {
|
|
2994
|
-
background: 'var(--dsw-alias-bg-layer-3,
|
|
3009
|
+
background: 'var(--dsw-alias-bg-layer-3, color-mix(in srgb, var(--dsw-alias-label-primary, #fff) 3%, transparent))',
|
|
2995
3010
|
border: '1px solid var(--dsw-alias-border-l2, #2e2e2e)',
|
|
2996
3011
|
borderRadius: '8px',
|
|
2997
3012
|
padding: '10px 14px',
|
|
@@ -3181,7 +3196,7 @@ window.__ModuleLoader__.load({
|
|
|
3181
3196
|
onChange: (e) => setSettingsChatId(e.target.value)
|
|
3182
3197
|
})
|
|
3183
3198
|
),
|
|
3184
|
-
React.createElement('div', { style: { background: 'var(--dsw-alias-bg-layer-3,
|
|
3199
|
+
React.createElement('div', { style: { background: 'var(--dsw-alias-bg-layer-3, color-mix(in srgb, var(--dsw-alias-label-primary, #fff) 2%, transparent))', border: '1px solid var(--dsw-alias-border-l2, #2e2e2e)', borderRadius: '8px', padding: '12px 14px', marginBottom: '16px' } },
|
|
3185
3200
|
React.createElement('label', { className: 'dsh-cron-checkbox-row', style: { marginBottom: '8px' } },
|
|
3186
3201
|
React.createElement('input', {
|
|
3187
3202
|
type: 'checkbox',
|
|
@@ -3346,7 +3361,7 @@ window.__ModuleLoader__.load({
|
|
|
3346
3361
|
const [updateState, setUpdateState] = React.useState({
|
|
3347
3362
|
checking: false,
|
|
3348
3363
|
updating: false,
|
|
3349
|
-
currentVersion: '0.2.
|
|
3364
|
+
currentVersion: '0.2.19',
|
|
3350
3365
|
latestVersion: undefined,
|
|
3351
3366
|
updateAvailable: false,
|
|
3352
3367
|
notice: null,
|
|
@@ -3453,7 +3468,9 @@ window.__ModuleLoader__.load({
|
|
|
3453
3468
|
setSaved(true);
|
|
3454
3469
|
setTimeout(() => setSaved(false), 2500);
|
|
3455
3470
|
}
|
|
3456
|
-
} catch (err) {
|
|
3471
|
+
} catch (err) {
|
|
3472
|
+
console.debug('[dsh-cron] save settings failed', err);
|
|
3473
|
+
}
|
|
3457
3474
|
};
|
|
3458
3475
|
|
|
3459
3476
|
const testTelegram = async () => {
|
|
@@ -3558,7 +3575,7 @@ window.__ModuleLoader__.load({
|
|
|
3558
3575
|
onChange: (e) => setKanbanUrl(e.target.value)
|
|
3559
3576
|
})
|
|
3560
3577
|
),
|
|
3561
|
-
React.createElement('div', { style: { background: 'var(--dsw-alias-bg-layer-3,
|
|
3578
|
+
React.createElement('div', { style: { background: 'var(--dsw-alias-bg-layer-3, color-mix(in srgb, var(--dsw-alias-label-primary, #fff) 2%, transparent))', border: '1px solid var(--dsw-alias-border-l2, #2e2e2e)', borderRadius: '6px', padding: '10px 12px', display: 'flex', flexDirection: 'column', gap: '8px' } },
|
|
3562
3579
|
React.createElement('label', { className: 'dsh-cron-checkbox-row' },
|
|
3563
3580
|
React.createElement('input', {
|
|
3564
3581
|
type: 'checkbox',
|
|
@@ -3610,8 +3627,8 @@ window.__ModuleLoader__.load({
|
|
|
3610
3627
|
React.createElement('div', {
|
|
3611
3628
|
className: 'dsh-cron-form-group',
|
|
3612
3629
|
style: {
|
|
3613
|
-
background: 'var(--dsw-alias-bg-layer-2,
|
|
3614
|
-
border: '1px solid var(--dsw-alias-border-l2,
|
|
3630
|
+
background: 'var(--dsw-alias-bg-layer-2, color-mix(in srgb, var(--dsw-alias-label-primary, #fff) 3%, transparent))',
|
|
3631
|
+
border: '1px solid var(--dsw-alias-border-l2, color-mix(in srgb, var(--dsw-alias-label-primary, #fff) 8%, transparent))',
|
|
3615
3632
|
borderRadius: '8px',
|
|
3616
3633
|
padding: '14px',
|
|
3617
3634
|
marginBottom: '16px'
|
|
@@ -3786,7 +3803,9 @@ window.__ModuleLoader__.load({
|
|
|
3786
3803
|
const res = await fetch('/dsh-cron/tasks?status=active');
|
|
3787
3804
|
const data = await res.json();
|
|
3788
3805
|
if (data && data.ok) renderSidebarJobs(state, data.tasks || []);
|
|
3789
|
-
} catch (
|
|
3806
|
+
} catch (err) {
|
|
3807
|
+
console.debug('[dsh-cron] load sidebar jobs failed', err);
|
|
3808
|
+
}
|
|
3790
3809
|
}
|
|
3791
3810
|
|
|
3792
3811
|
function applySidebarJobsOpen(state) {
|
|
@@ -3811,7 +3830,7 @@ window.__ModuleLoader__.load({
|
|
|
3811
3830
|
event.preventDefault();
|
|
3812
3831
|
event.stopPropagation();
|
|
3813
3832
|
state.open = !state.open;
|
|
3814
|
-
try { window.localStorage.setItem(SIDEBAR_JOBS_KEY, state.open ? '1' : '0'); } catch (
|
|
3833
|
+
try { window.localStorage.setItem(SIDEBAR_JOBS_KEY, state.open ? '1' : '0'); } catch (err) { /* localStorage write failed */ }
|
|
3815
3834
|
applySidebarJobsOpen(state);
|
|
3816
3835
|
});
|
|
3817
3836
|
root.appendChild(headEl);
|
|
@@ -3854,7 +3873,7 @@ window.__ModuleLoader__.load({
|
|
|
3854
3873
|
function mountSidebarJobs(toggle, label, openTaskSignal) {
|
|
3855
3874
|
if (typeof document === 'undefined') return () => {};
|
|
3856
3875
|
const state = { toggle, openTaskSignal, root: undefined, listEl: undefined, headEl: undefined, open: false };
|
|
3857
|
-
try { state.open = window.localStorage.getItem(SIDEBAR_JOBS_KEY) === '1'; } catch (
|
|
3876
|
+
try { state.open = window.localStorage.getItem(SIDEBAR_JOBS_KEY) === '1'; } catch (err) { /* localStorage read failed */ }
|
|
3858
3877
|
|
|
3859
3878
|
const observer = new MutationObserver(() => placeSidebarJobs(state, label));
|
|
3860
3879
|
observer.observe(document.body, { childList: true, subtree: true });
|
|
@@ -3862,7 +3881,7 @@ window.__ModuleLoader__.load({
|
|
|
3862
3881
|
loadSidebarJobs(state);
|
|
3863
3882
|
const fetchTimer = setInterval(() => {
|
|
3864
3883
|
let isOpen = false;
|
|
3865
|
-
try { isOpen = window.localStorage.getItem(SIDEBAR_JOBS_KEY) === '1'; } catch (
|
|
3884
|
+
try { isOpen = window.localStorage.getItem(SIDEBAR_JOBS_KEY) === '1'; } catch (err) { /* localStorage poll failed */ }
|
|
3866
3885
|
if (isOpen) loadSidebarJobs(state);
|
|
3867
3886
|
}, 30000);
|
|
3868
3887
|
|
|
@@ -4011,7 +4030,9 @@ window.__ModuleLoader__.load({
|
|
|
4011
4030
|
if (ctx.locale && typeof ctx.locale.bind === 'function') {
|
|
4012
4031
|
translate = ctx.locale.bind(NS);
|
|
4013
4032
|
}
|
|
4014
|
-
} catch (
|
|
4033
|
+
} catch (err) {
|
|
4034
|
+
/* locale bind unavailable, fallback to default translate */
|
|
4035
|
+
}
|
|
4015
4036
|
|
|
4016
4037
|
const toggle = createToggle();
|
|
4017
4038
|
|
package/lib/llm-actions.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { bestEffort } from './best-effort.js';
|
|
1
2
|
import fs from 'node:fs';
|
|
2
3
|
|
|
3
4
|
/**
|
|
@@ -14,10 +15,10 @@ export function parseLlmActionDirectives(text) {
|
|
|
14
15
|
while ((match = blockRegex.exec(text)) !== null) {
|
|
15
16
|
const raw = match[1].trim();
|
|
16
17
|
if ((raw.startsWith('{') && raw.endsWith('}')) || (raw.startsWith('[') && raw.endsWith(']'))) {
|
|
17
|
-
|
|
18
|
+
bestEffort('parse-directive-json', () => {
|
|
18
19
|
const parsed = JSON.parse(raw);
|
|
19
20
|
collectDirectives(parsed, directives);
|
|
20
|
-
}
|
|
21
|
+
});
|
|
21
22
|
}
|
|
22
23
|
}
|
|
23
24
|
|
|
@@ -26,10 +27,10 @@ export function parseLlmActionDirectives(text) {
|
|
|
26
27
|
const inlineMatch = /\{(?:[^{}]|(\{(?:[^{}]|\{[^{}]*\})*\}))*"dsh_action[s]?"(?:[^{}]|(\{(?:[^{}]|\{[^{}]*\})*\}))*\}/gi;
|
|
27
28
|
let im;
|
|
28
29
|
while ((im = inlineMatch.exec(text)) !== null) {
|
|
29
|
-
|
|
30
|
+
bestEffort('parse-inline-directive', () => {
|
|
30
31
|
const parsed = JSON.parse(im[0]);
|
|
31
32
|
collectDirectives(parsed, directives);
|
|
32
|
-
}
|
|
33
|
+
});
|
|
33
34
|
}
|
|
34
35
|
}
|
|
35
36
|
|
|
@@ -116,7 +117,7 @@ export async function executeLlmActionDirectives({ directives, task, runInfo, sc
|
|
|
116
117
|
async function tryCreateGiteaIssue({ title, body, repo = 'goodandready/dsh-cron', fetchFn }) {
|
|
117
118
|
try {
|
|
118
119
|
let token = process.env.GITEA_TOKEN;
|
|
119
|
-
const credPath =
|
|
120
|
+
const credPath = process.env.GITEA_CREDENTIALS_PATH || '';
|
|
120
121
|
if (!token && fs.existsSync(credPath)) {
|
|
121
122
|
const data = JSON.parse(fs.readFileSync(credPath, 'utf-8'));
|
|
122
123
|
token = data?.agents?.antigravity?.token || data?.token;
|
|
@@ -136,6 +137,8 @@ async function tryCreateGiteaIssue({ title, body, repo = 'goodandready/dsh-cron'
|
|
|
136
137
|
const json = await res.json().catch(() => ({}));
|
|
137
138
|
return { number: json.number, url: json.html_url };
|
|
138
139
|
}
|
|
139
|
-
} catch {
|
|
140
|
+
} catch (err) {
|
|
141
|
+
/* ignore Gitea creation errors, best-effort */
|
|
142
|
+
}
|
|
140
143
|
return null;
|
|
141
144
|
}
|
package/lib/llm-ask.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { bestEffort } from './best-effort.js';
|
|
1
2
|
/**
|
|
2
3
|
* Small helper for asking the configured model a short question (#44, #43).
|
|
3
4
|
*
|
|
@@ -26,11 +27,11 @@ function getLlm(ctx) {
|
|
|
26
27
|
/** Provider and model for an auxiliary question: task first, then the default. */
|
|
27
28
|
export function resolveAskTarget(ctx, task = {}, preferredModel = '') {
|
|
28
29
|
let defaultSelection = null;
|
|
29
|
-
|
|
30
|
-
|
|
30
|
+
defaultSelection = bestEffort('resolve-default-model', () => {
|
|
31
|
+
return ctx && typeof ctx.get === 'function'
|
|
31
32
|
? ctx.get('agentDefaultModel')?.currentSelection?.() || null
|
|
32
33
|
: null;
|
|
33
|
-
}
|
|
34
|
+
}) || null;
|
|
34
35
|
return {
|
|
35
36
|
provider: task.provider || defaultSelection?.provider || '',
|
|
36
37
|
model: preferredModel || task.model || defaultSelection?.model || '',
|
|
@@ -60,12 +61,10 @@ function isCleanFinish(kind) {
|
|
|
60
61
|
async function buildUserMessage(text) {
|
|
61
62
|
const content = [{ type: 'text', text }];
|
|
62
63
|
const source = { kind: 'plugin', plugin: 'dsh-cron', form: 'helper-call' };
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}
|
|
68
|
-
} catch {}
|
|
64
|
+
const mod = await bestEffort('import-dsh-llm', () => import('@deepseek-ai/dsh-llm'));
|
|
65
|
+
if (mod && typeof mod.createUserMessage === 'function') {
|
|
66
|
+
return mod.createUserMessage({ content, source });
|
|
67
|
+
}
|
|
69
68
|
return { role: 'user', content, source };
|
|
70
69
|
}
|
|
71
70
|
|
package/lib/recipes.js
CHANGED
|
@@ -207,7 +207,9 @@ export const RECIPES = [
|
|
|
207
207
|
|
|
208
208
|
/** Detached copy of the catalog, safe to hand to the UI. */
|
|
209
209
|
export function listRecipes() {
|
|
210
|
-
|
|
210
|
+
const destructive = findDestructiveRecipe();
|
|
211
|
+
const safeRecipes = destructive ? RECIPES.filter((r) => r.id !== destructive.id) : RECIPES;
|
|
212
|
+
return safeRecipes.map((recipe) => ({ ...recipe, channels: [...(recipe.channels || [])] }));
|
|
211
213
|
}
|
|
212
214
|
|
|
213
215
|
export function recipesByCategory() {
|
package/lib/runner.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { bestEffort } from './best-effort.js';
|
|
1
2
|
/**
|
|
2
3
|
* Helper to detect context window overflow errors in persistent sessions (#145).
|
|
3
4
|
*/
|
|
@@ -19,21 +20,21 @@ export function isContextOverflowError(err) {
|
|
|
19
20
|
*/
|
|
20
21
|
export function terminateProcessTree(child, sig = 'SIGTERM', { isPosix = process.platform !== 'win32', execFn } = {}) {
|
|
21
22
|
if (!child || child.killed) return;
|
|
22
|
-
|
|
23
|
+
bestEffort('terminate-process-tree', () => {
|
|
23
24
|
if (isPosix && child.pid) {
|
|
24
|
-
|
|
25
|
+
bestEffort('kill-posix-pid', () => process.kill(-child.pid, sig));
|
|
25
26
|
} else if (child.pid) {
|
|
26
27
|
const runner = execFn || ((cmd) => {
|
|
27
|
-
|
|
28
|
+
bestEffort('exec-taskkill-fn', () => {
|
|
28
29
|
import('node:child_process').then(({ exec }) => {
|
|
29
30
|
exec(cmd, { windowsHide: true }, () => {});
|
|
30
31
|
}).catch(() => {});
|
|
31
|
-
}
|
|
32
|
+
});
|
|
32
33
|
});
|
|
33
|
-
|
|
34
|
-
|
|
34
|
+
bestEffort('exec-taskkill', () => runner(`taskkill /pid ${child.pid} /T /F`));
|
|
35
|
+
bestEffort('child-kill', () => child.kill(sig));
|
|
35
36
|
}
|
|
36
|
-
}
|
|
37
|
+
});
|
|
37
38
|
}
|
|
38
39
|
|
|
39
40
|
/**
|
|
@@ -145,14 +146,14 @@ export class SessionRunner {
|
|
|
145
146
|
resolveCwd(task) {
|
|
146
147
|
if (task.cwd) return String(task.cwd);
|
|
147
148
|
if (task.workspaceId && this.ctx && typeof this.ctx.get === 'function') {
|
|
148
|
-
|
|
149
|
+
const resolved = bestEffort('resolve-workspace-path', () => {
|
|
149
150
|
const workspaces = this.ctx.get('workspaces');
|
|
150
151
|
const ws = workspaces && (typeof workspaces.get === 'function'
|
|
151
152
|
? workspaces.get(task.workspaceId)
|
|
152
153
|
: (workspaces[task.workspaceId] || null));
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
154
|
+
return ws && (ws.path || ws.cwd || ws.root || ws.dir);
|
|
155
|
+
});
|
|
156
|
+
if (resolved) return String(resolved);
|
|
156
157
|
return String(task.workspaceId);
|
|
157
158
|
}
|
|
158
159
|
return process.cwd();
|
|
@@ -239,20 +240,19 @@ export class SessionRunner {
|
|
|
239
240
|
async _runViaRemoteWorkspace(task, signal) {
|
|
240
241
|
if (!task.sshProfileId || !this.ctx || typeof this.ctx.get !== 'function') return null;
|
|
241
242
|
let svc = null;
|
|
242
|
-
|
|
243
|
-
svc = this.ctx.get('remoteSsh');
|
|
244
|
-
} catch {}
|
|
243
|
+
svc = bestEffort('get-remoteSsh', () => this.ctx.get('remoteSsh'));
|
|
245
244
|
if (!svc || typeof svc.exec !== 'function') return null;
|
|
246
245
|
|
|
247
246
|
let profile = null;
|
|
248
|
-
|
|
247
|
+
profile = bestEffort('get-remoteSsh-profile', () => {
|
|
249
248
|
const settings = this.ctx.get('settings');
|
|
250
249
|
if (settings && typeof settings.get === 'function') {
|
|
251
250
|
const snap = settings.get('dsh-remote-workspace');
|
|
252
251
|
const profiles = snap && Array.isArray(snap.profiles) ? snap.profiles : [];
|
|
253
|
-
|
|
252
|
+
return profiles.find((p) => p && p.id === task.sshProfileId) || null;
|
|
254
253
|
}
|
|
255
|
-
|
|
254
|
+
return null;
|
|
255
|
+
});
|
|
256
256
|
if (!profile) {
|
|
257
257
|
throw new Error(`remote-workspace profile "${task.sshProfileId}" was not found — check dsh-remote-workspace settings, or use sshTarget instead`);
|
|
258
258
|
}
|
|
@@ -295,7 +295,7 @@ export class SessionRunner {
|
|
|
295
295
|
terminateProcessTree(child, sig, {
|
|
296
296
|
isPosix,
|
|
297
297
|
execFn: (cmd) => {
|
|
298
|
-
|
|
298
|
+
bestEffort('exec-cmd', () => exec(cmd, { windowsHide: true }, () => {}));
|
|
299
299
|
}
|
|
300
300
|
});
|
|
301
301
|
};
|
|
@@ -438,22 +438,22 @@ export class SessionRunner {
|
|
|
438
438
|
// Context window overflow auto-recovery in persistent sessions (#145)
|
|
439
439
|
if (prep.targetSessionId && isContextOverflowError(err) && !signal?.aborted) {
|
|
440
440
|
console.warn(`[dsh-cron] Persistent session "${prep.targetSessionId}" exceeded context window (${err.message}). Auto-recovering with fresh session...`);
|
|
441
|
-
|
|
441
|
+
bestEffort('archive-rotated-session', () => {
|
|
442
442
|
const sessions = this.ctx?.get?.('sessions');
|
|
443
443
|
if (sessions && typeof sessions.archive === 'function') {
|
|
444
444
|
sessions.archive(prep.targetSessionId);
|
|
445
445
|
}
|
|
446
|
-
}
|
|
446
|
+
});
|
|
447
447
|
const rotatedSessionId = `${prep.targetSessionId}-${Date.now().toString(36)}`;
|
|
448
448
|
console.log(`[dsh-cron] Rotated persistent session "${prep.targetSessionId}" -> "${rotatedSessionId}"`);
|
|
449
449
|
if (this.store) {
|
|
450
|
-
|
|
450
|
+
bestEffort('update-stored-rotated-session', () => {
|
|
451
451
|
const stored = this.store.get(task.id);
|
|
452
452
|
if (stored) {
|
|
453
453
|
stored.targetSessionId = rotatedSessionId;
|
|
454
454
|
this.store.set(stored);
|
|
455
455
|
}
|
|
456
|
-
}
|
|
456
|
+
});
|
|
457
457
|
}
|
|
458
458
|
task.targetSessionId = rotatedSessionId;
|
|
459
459
|
effectiveTask.targetSessionId = rotatedSessionId;
|
|
@@ -468,16 +468,11 @@ export class SessionRunner {
|
|
|
468
468
|
async _prepareAgentRun(task, runtime, signal) {
|
|
469
469
|
const { randomUUID } = await import('node:crypto');
|
|
470
470
|
let createUserMessage = (m) => m;
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
} catch {}
|
|
477
|
-
let sessionModule = null;
|
|
478
|
-
try {
|
|
479
|
-
sessionModule = await import('@deepseek-ai/dsh-session');
|
|
480
|
-
} catch {}
|
|
471
|
+
const llmModule = await bestEffort('import-dsh-llm', () => import('@deepseek-ai/dsh-llm'));
|
|
472
|
+
if (llmModule && typeof llmModule.createUserMessage === 'function') {
|
|
473
|
+
createUserMessage = llmModule.createUserMessage;
|
|
474
|
+
}
|
|
475
|
+
const sessionModule = await bestEffort('import-dsh-session', () => import('@deepseek-ai/dsh-session'));
|
|
481
476
|
|
|
482
477
|
const targetSessionId = resolveTargetSessionId(task.targetSessionId, task.targetSessionReset);
|
|
483
478
|
|
|
@@ -665,23 +660,23 @@ ${prompt}`;
|
|
|
665
660
|
/** Token usage reported by the session, when the core exposes it. */
|
|
666
661
|
_extractUsage(handle) {
|
|
667
662
|
const usage = { inputTokens: 0, outputTokens: 0, cacheReadTokens: 0 };
|
|
668
|
-
|
|
663
|
+
bestEffort('extract-session-usage', () => {
|
|
669
664
|
const sessionUsage = handle.agent.session?.usage || handle.agent.usage;
|
|
670
665
|
if (sessionUsage) {
|
|
671
666
|
usage.inputTokens = sessionUsage.inputTokens || sessionUsage.promptTokens || 0;
|
|
672
667
|
usage.outputTokens = sessionUsage.outputTokens || sessionUsage.completionTokens || 0;
|
|
673
668
|
usage.cacheReadTokens = sessionUsage.cacheReadTokens || sessionUsage.cachedTokens || 0;
|
|
674
669
|
}
|
|
675
|
-
}
|
|
670
|
+
});
|
|
676
671
|
return usage;
|
|
677
672
|
}
|
|
678
673
|
|
|
679
674
|
/** Dispose the session, archive it best-effort, drop the worktree (#30, #29). */
|
|
680
675
|
async _disposeAgentRun(task, prep, handle) {
|
|
681
676
|
let archivedSessionId = null;
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
}
|
|
677
|
+
archivedSessionId = bestEffort('get-archived-session-id', () => {
|
|
678
|
+
return (handle && handle.agent && handle.agent.session && handle.agent.session.id) || null;
|
|
679
|
+
}) || null;
|
|
685
680
|
try {
|
|
686
681
|
if (handle && typeof handle.dispose === 'function') {
|
|
687
682
|
await handle.dispose();
|
|
@@ -694,12 +689,12 @@ ${prompt}`;
|
|
|
694
689
|
// Best-effort: keep ephemeral cron sessions out of the main chat list (#30).
|
|
695
690
|
// Persistent sessions (targetSessionId configured) stay unarchived so users can interact with them (#143).
|
|
696
691
|
if (archivedSessionId && this.ctx && this.ctx.get && !prep.targetSessionId) {
|
|
697
|
-
|
|
692
|
+
bestEffort('archive-session-cleanup', () => {
|
|
698
693
|
const sessions = this.ctx.get('sessions');
|
|
699
694
|
if (sessions && typeof sessions.archive === 'function') {
|
|
700
695
|
sessions.archive(archivedSessionId);
|
|
701
696
|
}
|
|
702
|
-
}
|
|
697
|
+
});
|
|
703
698
|
}
|
|
704
699
|
// #29: dispose the isolated worktree (kept when keepWorktree is set).
|
|
705
700
|
if (prep.taskWorktree && !task.keepWorktree) {
|
package/lib/runtimes.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { bestEffort } from './best-effort.js';
|
|
1
2
|
/**
|
|
2
3
|
* Runtime invocation builders for non-LLM task types (#4, #5, #7, #8, #9, #38).
|
|
3
4
|
*
|
|
@@ -68,9 +69,8 @@ function detectPython(task, cwd) {
|
|
|
68
69
|
candidates.push(path.join(base, '.venv', 'Scripts', 'python.exe'));
|
|
69
70
|
candidates.push(path.join(base, 'venv', 'bin', 'python'));
|
|
70
71
|
for (const candidate of candidates) {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
} catch {}
|
|
72
|
+
const exists = bestEffort('check-python-candidate', () => fs.existsSync(candidate));
|
|
73
|
+
if (exists) return candidate;
|
|
74
74
|
}
|
|
75
75
|
return process.platform === 'win32' ? 'python' : 'python3';
|
|
76
76
|
}
|
package/lib/scheduler.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { bestEffort } from './best-effort.js';
|
|
1
2
|
import { Cron } from 'croner';
|
|
2
3
|
import { exec } from 'node:child_process';
|
|
3
4
|
import fs from 'node:fs';
|
|
@@ -341,9 +342,7 @@ export class TaskScheduler {
|
|
|
341
342
|
costUsd: 0,
|
|
342
343
|
};
|
|
343
344
|
|
|
344
|
-
|
|
345
|
-
this.store.recordRun(task.id, runInfo);
|
|
346
|
-
} catch {}
|
|
345
|
+
bestEffort('record-run', () => this.store.recordRun(task.id, runInfo), this.logger);
|
|
347
346
|
|
|
348
347
|
await this.deliverNotifications(task, runInfo);
|
|
349
348
|
|
|
@@ -404,30 +403,22 @@ export class TaskScheduler {
|
|
|
404
403
|
stopAll() {
|
|
405
404
|
this.stopHeartbeatWatcher();
|
|
406
405
|
for (const [id, job] of this.jobs.entries()) {
|
|
407
|
-
|
|
408
|
-
job.stop();
|
|
409
|
-
} catch {}
|
|
406
|
+
bestEffort('stop-job', () => job.stop(), this.logger);
|
|
410
407
|
}
|
|
411
408
|
this.jobs.clear();
|
|
412
409
|
|
|
413
410
|
for (const [id, timer] of this.timers.entries()) {
|
|
414
|
-
|
|
415
|
-
clearTimeout(timer);
|
|
416
|
-
} catch {}
|
|
411
|
+
bestEffort('clear-timer', () => clearTimeout(timer), this.logger);
|
|
417
412
|
}
|
|
418
413
|
this.timers.clear();
|
|
419
414
|
|
|
420
415
|
for (const [id, timer] of this.retryTimers.entries()) {
|
|
421
|
-
|
|
422
|
-
clearTimeout(timer);
|
|
423
|
-
} catch {}
|
|
416
|
+
bestEffort('clear-retry-timer', () => clearTimeout(timer), this.logger);
|
|
424
417
|
}
|
|
425
418
|
this.retryTimers.clear();
|
|
426
419
|
|
|
427
420
|
for (const [, run] of this.running.entries()) {
|
|
428
|
-
|
|
429
|
-
run.controller.abort(new Error('Scheduler stopped'));
|
|
430
|
-
} catch {}
|
|
421
|
+
bestEffort('abort-running', () => run.controller.abort(new Error('Scheduler stopped')), this.logger);
|
|
431
422
|
}
|
|
432
423
|
this.running.clear();
|
|
433
424
|
}
|
|
@@ -994,9 +985,7 @@ export class TaskScheduler {
|
|
|
994
985
|
}
|
|
995
986
|
const running = this.running.get(taskId);
|
|
996
987
|
if (running) {
|
|
997
|
-
|
|
998
|
-
running.controller.abort(new Error('Task paused'));
|
|
999
|
-
} catch {}
|
|
988
|
+
bestEffort('abort-paused-task', () => running.controller.abort(new Error('Task paused')), this.logger);
|
|
1000
989
|
this.running.delete(taskId);
|
|
1001
990
|
}
|
|
1002
991
|
this.queue = this.queue.filter((item) => item.taskId !== taskId);
|
|
@@ -1016,9 +1005,7 @@ export class TaskScheduler {
|
|
|
1016
1005
|
}
|
|
1017
1006
|
const running = this.running.get(taskId);
|
|
1018
1007
|
if (running) {
|
|
1019
|
-
|
|
1020
|
-
running.controller.abort(new Error('Task deleted'));
|
|
1021
|
-
} catch {}
|
|
1008
|
+
bestEffort('abort-deleted-task', () => running.controller.abort(new Error('Task deleted')), this.logger);
|
|
1022
1009
|
this.running.delete(taskId);
|
|
1023
1010
|
}
|
|
1024
1011
|
this.queue = this.queue.filter((item) => item.taskId !== taskId);
|
package/lib/secrets.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { bestEffort } from './best-effort.js';
|
|
1
2
|
/**
|
|
2
3
|
* Credential handling for dsh-cron (#51).
|
|
3
4
|
*
|
|
@@ -54,10 +55,8 @@ export async function resolveCredentialValue(ctx, refName) {
|
|
|
54
55
|
// Prefer the harness ref factory; fall back to the plain name when the
|
|
55
56
|
// package is not installed (keeps tests and trimmed builds working).
|
|
56
57
|
let ref = name;
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
if (mod && typeof mod.credentialRef === 'function') ref = mod.credentialRef(name);
|
|
60
|
-
} catch {}
|
|
58
|
+
const mod = await bestEffort('import-credentials', () => import('@deepseek-ai/dsh-credentials'));
|
|
59
|
+
if (mod && typeof mod.credentialRef === 'function') ref = mod.credentialRef(name);
|
|
61
60
|
const hit = await creds.resolve(ref);
|
|
62
61
|
if (hit && hit.value) return String(hit.value);
|
|
63
62
|
}
|
|
@@ -70,7 +69,7 @@ export async function resolveCredentialValue(ctx, refName) {
|
|
|
70
69
|
|
|
71
70
|
/** Read the best-effort Telegram defaults from the messenger-gateway file. */
|
|
72
71
|
function readGatewayToken() {
|
|
73
|
-
|
|
72
|
+
return bestEffort('read-gateway-token', () => {
|
|
74
73
|
const p = getDshSettingsPath();
|
|
75
74
|
if (!fs.existsSync(p)) return null;
|
|
76
75
|
const text = fs.readFileSync(p, 'utf-8');
|
|
@@ -82,8 +81,8 @@ function readGatewayToken() {
|
|
|
82
81
|
const hit = /^[ \t]+botToken:[ \t]*(.+?)[ \t]*$/.exec(line);
|
|
83
82
|
if (hit) return hit[1].replace(/^['"]|['"]$/g, '').trim() || null;
|
|
84
83
|
}
|
|
85
|
-
|
|
86
|
-
|
|
84
|
+
return null;
|
|
85
|
+
}) || null;
|
|
87
86
|
}
|
|
88
87
|
|
|
89
88
|
/**
|
package/lib/store.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { bestEffort } from './best-effort.js';
|
|
1
2
|
import fs from 'node:fs';
|
|
2
3
|
import path from 'node:path';
|
|
3
4
|
import { getDshDefaultTelegramCredentials } from './telegram.js';
|
|
@@ -68,7 +69,7 @@ export class TaskStore {
|
|
|
68
69
|
const raw = fs.readFileSync(this.filePath, 'utf-8');
|
|
69
70
|
loadData(raw);
|
|
70
71
|
if (!fs.existsSync(bakPath)) {
|
|
71
|
-
|
|
72
|
+
bestEffort('backup-initial', () => fs.copyFileSync(this.filePath, bakPath));
|
|
72
73
|
}
|
|
73
74
|
} catch (err) {
|
|
74
75
|
console.error('[dsh-cron] store read error (corrupted file):', err.message);
|
|
@@ -93,7 +94,7 @@ export class TaskStore {
|
|
|
93
94
|
try {
|
|
94
95
|
const rawBak = fs.readFileSync(bakPath, 'utf-8');
|
|
95
96
|
loadData(rawBak);
|
|
96
|
-
|
|
97
|
+
bestEffort('restore-from-bak', () => fs.copyFileSync(bakPath, this.filePath));
|
|
97
98
|
console.warn(`[dsh-cron] restored missing tasks store from ${bakPath}`);
|
|
98
99
|
} catch (bakErr) {
|
|
99
100
|
console.error('[dsh-cron] failed to restore tasks store from .bak:', bakErr.message);
|
|
@@ -119,9 +120,7 @@ export class TaskStore {
|
|
|
119
120
|
const tmp = `${this.filePath}.tmp.${process.pid}_${Date.now()}_${rand}`;
|
|
120
121
|
fs.writeFileSync(tmp, JSON.stringify(data, null, 2), 'utf-8');
|
|
121
122
|
fs.renameSync(tmp, this.filePath);
|
|
122
|
-
|
|
123
|
-
fs.copyFileSync(this.filePath, `${this.filePath}.bak`);
|
|
124
|
-
} catch {}
|
|
123
|
+
bestEffort('backup-on-save', () => fs.copyFileSync(this.filePath, `${this.filePath}.bak`));
|
|
125
124
|
} catch (err) {
|
|
126
125
|
console.error('[dsh-cron] store save error:', err.message);
|
|
127
126
|
}
|
|
@@ -393,9 +392,7 @@ export class TaskStore {
|
|
|
393
392
|
const archivePath = path.join(dir, 'tasks-history-archive.json');
|
|
394
393
|
let archive = {};
|
|
395
394
|
if (fs.existsSync(archivePath)) {
|
|
396
|
-
|
|
397
|
-
archive = JSON.parse(fs.readFileSync(archivePath, 'utf-8')) || {};
|
|
398
|
-
} catch {}
|
|
395
|
+
archive = bestEffort('parse-history-archive', () => JSON.parse(fs.readFileSync(archivePath, 'utf-8'))) || {};
|
|
399
396
|
}
|
|
400
397
|
if (!Array.isArray(archive[id])) archive[id] = [];
|
|
401
398
|
archive[id].push(...overflowRuns);
|
package/lib/templates.js
CHANGED
|
@@ -51,7 +51,7 @@ export function buildTemplateVars(task, runInfo = {}) {
|
|
|
51
51
|
const error = runInfo.error || (status === 'error' || status === 'timeout' ? runInfo.output || '' : '');
|
|
52
52
|
const usage = runInfo.usage || {};
|
|
53
53
|
const tokens = (usage.inputTokens || 0) + (usage.outputTokens || 0) + (usage.cacheReadTokens || 0);
|
|
54
|
-
|
|
54
|
+
const vars = {
|
|
55
55
|
title: task.title || 'Task',
|
|
56
56
|
id: task.id || '',
|
|
57
57
|
status,
|
|
@@ -65,6 +65,10 @@ export function buildTemplateVars(task, runInfo = {}) {
|
|
|
65
65
|
model: runInfo.model || '',
|
|
66
66
|
diagnosis: truncateText(runInfo.diagnosis || ''),
|
|
67
67
|
};
|
|
68
|
+
for (const key of TEMPLATE_VARIABLES) {
|
|
69
|
+
if (vars[key] === undefined) vars[key] = '';
|
|
70
|
+
}
|
|
71
|
+
return vars;
|
|
68
72
|
}
|
|
69
73
|
|
|
70
74
|
/** Replace {var} placeholders; unknown placeholders are left untouched. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodandready/dsh-cron",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.19",
|
|
4
4
|
"description": "Scheduled cron tasks, background automation and agent execution for DeepSeek Harness.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -45,8 +45,8 @@
|
|
|
45
45
|
"client": {
|
|
46
46
|
"platform": "web",
|
|
47
47
|
"inject": [
|
|
48
|
-
"locale",
|
|
49
|
-
"slots"
|
|
48
|
+
"@deepseek-ai/dsh-client-locale",
|
|
49
|
+
"@deepseek-ai/dsh-client-ui-slots"
|
|
50
50
|
]
|
|
51
51
|
}
|
|
52
52
|
},
|