@goodandready/dsh-cron 0.2.17 → 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 +21 -1
- package/README.ru.md +22 -2
- package/README.zh.md +22 -2
- package/lib/best-effort.js +29 -0
- package/lib/channels.js +6 -1
- package/lib/client.js +79 -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,20 @@ 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
|
+
|
|
404
|
+
### 30. Updater Localization in English & Chinese (v0.2.18, #160)
|
|
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`.
|
|
406
|
+
|
|
407
|
+
---
|
|
408
|
+
|
|
395
409
|
### 29. Client Modular Decomposition & Theme Standardization (v0.2.17, #149, #150)
|
|
396
410
|
- **Modular Client Architecture (#150)**: Decomposed monolithic `lib/client.js` (~3950 lines) into 14 focused, single-responsibility fragments under `lib/client-src/` (none exceeding 580 lines). Integrated zero-dependency build script `scripts/build-client.mjs` wired into `package.json` (`build:client`, `pretest`). Development fragments are excluded from npm distribution via `"files": ["lib/*.js", ...]`.
|
|
397
411
|
- **DSH Theme Tokens & Visual Standardization (#149)**: Replaced inline styles on task type badges (`onSuccess`, `onFailure`, `heartbeat`, `targetSession`, `preflight`) with dedicated `.dsh-cron-tag-*` CSS classes powered by semantic `--dsh-cron-*` theme variables. Modal overlay now adapts dynamically using `var(--dsw-alias-bg-mask, rgba(0, 0, 0, 0.75))`, and keyframe pulse animations use theme variables without hardcoded RGBA.
|
|
@@ -524,7 +538,7 @@ All endpoints are served by the DSH web server under `/dsh-cron/`. Read endpoint
|
|
|
524
538
|
|
|
525
539
|
---
|
|
526
540
|
|
|
527
|
-
## 🧪 Testing
|
|
541
|
+
## 🧪 Testing & Preflight Verification
|
|
528
542
|
|
|
529
543
|
Run the automated test suite covering schedule parsing, the scheduler engine, atomic storage, HTTP helpers, notifications and tool contracts:
|
|
530
544
|
|
|
@@ -532,6 +546,12 @@ Run the automated test suite covering schedule parsing, the scheduler engine, at
|
|
|
532
546
|
npm test
|
|
533
547
|
```
|
|
534
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
|
+
|
|
535
555
|
---
|
|
536
556
|
|
|
537
557
|
## 📄 License
|
package/README.ru.md
CHANGED
|
@@ -268,6 +268,20 @@ 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
|
+
|
|
280
|
+
### 30. Локализация автообновления на английский и китайский языки (v0.2.18, #160)
|
|
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`.
|
|
282
|
+
|
|
283
|
+
---
|
|
284
|
+
|
|
271
285
|
### 29. Модульная декомпозиция клиента и стандартизация тем оформления (v0.2.17, #149, #150)
|
|
272
286
|
- **Модульная архитектура клиентской части (#150)**: Монолитный файл `lib/client.js` (~3950 строк) декомпозирован на 14 независимых специализированных модулей в каталоге `lib/client-src/` (каждый строго <= 580 строк). Сборка `lib/client.js` выполняется легковесным скриптом `scripts/build-client.mjs` перед запуском тестов и сборки. Исходные фрагменты исключены из npm-дистрибутива через `"files": ["lib/*.js", ...]`.
|
|
273
287
|
- **Семантические токены тем DSH (#149)**: Инлайн-стили тегов типа задач (`onSuccess`, `onFailure`, `heartbeat`, `targetSession`, `preflight`) переведены на выделенные CSS-классы `.dsh-cron-tag-*` на базе переменных темы `--dsh-cron-*`. Оверлей модальных окон переведен на адаптивную маску `var(--dsw-alias-bg-mask, rgba(0, 0, 0, 0.75))`, а анимация пульсации избавлена от захардкоженных значений RGBA.
|
|
@@ -521,13 +535,19 @@ bash deploy.sh verify [exact-version]
|
|
|
521
535
|
|
|
522
536
|
---
|
|
523
537
|
|
|
524
|
-
## 🧪 Тестирование
|
|
538
|
+
## 🧪 Тестирование и локальная проверка (Preflight)
|
|
539
|
+
|
|
540
|
+
Запуск автоматического набора тестов (разбор расписаний, движок планировщика, атомарное хранилище, HTTP-хелперы, уведомления и контракт инструментов):
|
|
525
541
|
|
|
526
542
|
```bash
|
|
527
543
|
npm test
|
|
528
544
|
```
|
|
529
545
|
|
|
530
|
-
|
|
546
|
+
Локальный запуск гейта стандартов качества (синтаксис, отсутствие пустых catch, соответствие токенам тем оформления, чистота состава npm-пакета и защита от утечек):
|
|
547
|
+
|
|
548
|
+
```bash
|
|
549
|
+
node scripts/ci-preflight.mjs
|
|
550
|
+
```
|
|
531
551
|
|
|
532
552
|
---
|
|
533
553
|
|
package/README.zh.md
CHANGED
|
@@ -391,6 +391,20 @@ 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
|
+
|
|
403
|
+
### 30. 自更新模块英文与中文多语言支持 (v0.2.18, #160)
|
|
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` 统一扩展。
|
|
405
|
+
|
|
406
|
+
---
|
|
407
|
+
|
|
394
408
|
### 29. 客户端模块化解耦与原生主题标准化 (v0.2.17, #149, #150)
|
|
395
409
|
- **客户端模块化架构 (#150)**: 将庞大的单文件 `lib/client.js` (约 3950 行) 拆分为 `lib/client-src/` 下的 14 个高内聚模块文件 (各模块严格 <= 580 行)。集成零依赖构建脚本 `scripts/build-client.mjs` 并接入 `package.json` (`build:client`, `pretest`),通过 `"files": ["lib/*.js", ...]` 避免开发源码冗余打包进 npm 发布包。
|
|
396
410
|
- **DSH 语义化主题变量对齐 (#149)**: 将任务类型标签 (`onSuccess`, `onFailure`, `heartbeat`, `targetSession`, `preflight`) 的内联样式全部替换为基于主题变量的 `.dsh-cron-tag-*` 类;模态框遮罩层接入自适应主题遮罩变量 `var(--dsw-alias-bg-mask, rgba(0, 0, 0, 0.75))`,移除脉冲动画关键帧中的硬编码 RGBA。
|
|
@@ -521,13 +535,19 @@ dsh-cron:
|
|
|
521
535
|
|
|
522
536
|
---
|
|
523
537
|
|
|
524
|
-
## 🧪
|
|
538
|
+
## 🧪 测试与预检门禁 (Preflight)
|
|
539
|
+
|
|
540
|
+
运行全套自动化测试(调度表达式解析、调度器引擎、原子存储、HTTP 辅助函数、通知与工具契约):
|
|
525
541
|
|
|
526
542
|
```bash
|
|
527
543
|
npm test
|
|
528
544
|
```
|
|
529
545
|
|
|
530
|
-
|
|
546
|
+
本地执行质量预检门禁(语法检查、零空 catch 块、主题设计规范、npm 发布归档合规性及防泄漏检查):
|
|
547
|
+
|
|
548
|
+
```bash
|
|
549
|
+
node scripts/ci-preflight.mjs
|
|
550
|
+
```
|
|
531
551
|
|
|
532
552
|
---
|
|
533
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
|
|
@@ -294,6 +296,16 @@ window.__ModuleLoader__.load({
|
|
|
294
296
|
'settings.saved': '✓ Saved',
|
|
295
297
|
'settings.saveError': 'Save failed: {error}',
|
|
296
298
|
'settings.saveErrorUnknown': 'unknown error',
|
|
299
|
+
'updater.title': 'Plugin Auto-Updater',
|
|
300
|
+
'updater.btnCheck': 'Check for updates',
|
|
301
|
+
'updater.checking': 'Checking...',
|
|
302
|
+
'updater.btnUpdate': 'Update to v{version}',
|
|
303
|
+
'updater.updating': 'Updating...',
|
|
304
|
+
'updater.desc': 'Check npm registry and install plugin updates in-place without SSH access.',
|
|
305
|
+
'updater.current': 'Current version: v{version}',
|
|
306
|
+
'updater.available': 'Update available: v{version}',
|
|
307
|
+
'updater.upToDate': 'Up to date',
|
|
308
|
+
'updater.success': 'Successfully updated to v{version}. Please restart DSH or reload profile to apply changes.',
|
|
297
309
|
'dsh.modalTitle': 'Schedule a task with the DSH agent',
|
|
298
310
|
'dsh.descLabel': 'Describe the task and the desired frequency:',
|
|
299
311
|
'dsh.placeholder': 'For example: check free disk space and failed systemd services every 2 hours, and alert only if something is wrong...',
|
|
@@ -574,6 +586,16 @@ window.__ModuleLoader__.load({
|
|
|
574
586
|
'settings.saved': '✓ 已保存',
|
|
575
587
|
'settings.saveError': '保存失败: {error}',
|
|
576
588
|
'settings.saveErrorUnknown': '未知错误',
|
|
589
|
+
'updater.title': '插件自更新',
|
|
590
|
+
'updater.btnCheck': '检查更新',
|
|
591
|
+
'updater.checking': '正在检查...',
|
|
592
|
+
'updater.btnUpdate': '升级至 v{version}',
|
|
593
|
+
'updater.updating': '正在更新...',
|
|
594
|
+
'updater.desc': '通过 npm 仓库在线检查并就地升级插件,无需 SSH 终端操作。',
|
|
595
|
+
'updater.current': '当前版本: v{version}',
|
|
596
|
+
'updater.available': '发现新版本: v{version}',
|
|
597
|
+
'updater.upToDate': '已是最新版本',
|
|
598
|
+
'updater.success': '已成功升级至 v{version}。请重启 DSH 或重载配置以应用更新。',
|
|
577
599
|
'dsh.modalTitle': '让 DSH 智能体创建任务',
|
|
578
600
|
'dsh.descLabel': '请描述你的任务目标和执行频率:',
|
|
579
601
|
'dsh.placeholder': '例如: 每两小时检查一次磁盘空间和失败的 systemd 守护进程,并在异常时报警...',
|
|
@@ -602,15 +624,15 @@ window.__ModuleLoader__.load({
|
|
|
602
624
|
const STYLES = `
|
|
603
625
|
:root {
|
|
604
626
|
--dsh-cron-success: var(--dsw-alias-state-success-primary, #10b981);
|
|
605
|
-
--dsh-cron-success-bg:
|
|
627
|
+
--dsh-cron-success-bg: color-mix(in srgb, var(--dsh-cron-success, #10b981) 10%, transparent);
|
|
606
628
|
--dsh-cron-danger: var(--dsw-alias-state-error-primary, #ef4444);
|
|
607
|
-
--dsh-cron-danger-bg:
|
|
629
|
+
--dsh-cron-danger-bg: color-mix(in srgb, var(--dsh-cron-danger, #ef4444) 10%, transparent);
|
|
608
630
|
--dsh-cron-info: var(--dsw-alias-state-brand-primary, #60a5fa);
|
|
609
|
-
--dsh-cron-info-bg:
|
|
631
|
+
--dsh-cron-info-bg: color-mix(in srgb, var(--dsh-cron-info, #60a5fa) 12%, transparent);
|
|
610
632
|
--dsh-cron-accent: var(--dsw-alias-state-info-primary, #c084fc);
|
|
611
|
-
--dsh-cron-accent-bg:
|
|
633
|
+
--dsh-cron-accent-bg: color-mix(in srgb, var(--dsh-cron-accent, #a855f7) 12%, transparent);
|
|
612
634
|
--dsh-cron-warning: var(--dsw-alias-state-warning-primary, #eab308);
|
|
613
|
-
--dsh-cron-warning-bg:
|
|
635
|
+
--dsh-cron-warning-bg: color-mix(in srgb, var(--dsh-cron-warning, #eab308) 12%, transparent);
|
|
614
636
|
}
|
|
615
637
|
[data-pane=conversation], [class*=centerCol] {
|
|
616
638
|
position: relative !important;
|
|
@@ -640,14 +662,14 @@ window.__ModuleLoader__.load({
|
|
|
640
662
|
|
|
641
663
|
.dsh-cron-top-bar { display: flex; align-items: center; gap: 12px; }
|
|
642
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; }
|
|
643
|
-
.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))); }
|
|
644
666
|
|
|
645
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); }
|
|
646
668
|
.dsh-cron-title { font-size: 22px; font-weight: 700; letter-spacing: -0.02em; margin-bottom: 6px; }
|
|
647
669
|
.dsh-cron-subtitle { font-size: 14px; color: var(--dsw-alias-label-secondary, #9ca3af); line-height: 1.5; }
|
|
648
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; }
|
|
649
671
|
.dsh-cron-create-btn:hover { background: var(--dsw-alias-bg-layer-hover, var(--dsw-alias-interactive-bg-hover, #333)); }
|
|
650
|
-
.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; }
|
|
651
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; }
|
|
652
674
|
.dsh-cron-dropdown-item:hover { background: var(--dsw-alias-interactive-bg-hover, #2a2a2a); }
|
|
653
675
|
.dsh-cron-search-bar { position: relative; margin-bottom: 0; }
|
|
@@ -657,7 +679,7 @@ window.__ModuleLoader__.load({
|
|
|
657
679
|
.dsh-cron-filters { margin-top: 8px; }
|
|
658
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; }
|
|
659
681
|
.dsh-cron-filter-toggle:hover { color: var(--dsw-alias-label-primary, #eee); border-color: var(--dsh-cron-info); }
|
|
660
|
-
.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)); }
|
|
661
683
|
.dsh-cron-filter-field { display: flex; flex-direction: column; gap: 4px; font-size: 12px; color: var(--dsw-alias-label-tertiary, #888); }
|
|
662
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; }
|
|
663
685
|
.dsh-cron-tabs { display: flex; flex-wrap: wrap; gap: 8px; }
|
|
@@ -674,7 +696,7 @@ window.__ModuleLoader__.load({
|
|
|
674
696
|
.dsh-cron-tag-session { background: var(--dsh-cron-info-bg); color: var(--dsh-cron-info); border-color: var(--dsh-cron-info); }
|
|
675
697
|
.dsh-cron-tag-preflight { background: var(--dsh-cron-warning-bg); color: var(--dsh-cron-warning); border-color: var(--dsh-cron-warning); }
|
|
676
698
|
|
|
677
|
-
.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; }
|
|
678
700
|
.dsh-cron-task-actions { display: flex; gap: 8px; }
|
|
679
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; }
|
|
680
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)); }
|
|
@@ -686,14 +708,14 @@ window.__ModuleLoader__.load({
|
|
|
686
708
|
.dsh-cron-recs-list { display: flex; flex-direction: column; gap: 10px; }
|
|
687
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; }
|
|
688
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; }
|
|
689
|
-
.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; }
|
|
690
712
|
.dsh-cron-rec-content { flex: 1; min-width: 0; }
|
|
691
713
|
.dsh-cron-rec-title { font-size: 14.5px; font-weight: 500; margin-bottom: 4px; }
|
|
692
714
|
.dsh-cron-rec-time { font-size: 13px; color: var(--dsw-alias-label-secondary, #9ca3af); font-weight: normal; margin-left: 8px; }
|
|
693
715
|
.dsh-cron-rec-desc { font-size: 13px; color: var(--dsw-alias-label-tertiary, #71717a); }
|
|
694
716
|
|
|
695
|
-
.dsh-cron-modal-overlay { position: fixed; inset: 0; background: var(--dsw-alias-bg-mask,
|
|
696
|
-
.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; }
|
|
697
719
|
.dsh-cron-modal-scroll { max-width: 640px; max-height: min(90vh, calc(100vh - 36px)); overflow-y: auto; }
|
|
698
720
|
.dsh-cron-modal::-webkit-scrollbar { width: 6px; }
|
|
699
721
|
.dsh-cron-modal::-webkit-scrollbar-track { background: transparent; }
|
|
@@ -752,7 +774,7 @@ window.__ModuleLoader__.load({
|
|
|
752
774
|
.dsh-cron-history-empty { text-align: center; padding: 36px 0; color: var(--dsw-alias-label-tertiary, #666); font-size: 13.5px; }
|
|
753
775
|
|
|
754
776
|
.dsh-cron-stats-bar { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 10px; margin-bottom: 0; }
|
|
755
|
-
.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; }
|
|
756
778
|
.dsh-cron-stat-val { font-size: 18px; font-weight: 700; color: var(--dsw-alias-label-primary, #fff); }
|
|
757
779
|
.dsh-cron-stat-lbl { font-size: 12px; color: var(--dsw-alias-label-secondary, #777); margin-top: 0; }
|
|
758
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; }
|
|
@@ -767,7 +789,7 @@ window.__ModuleLoader__.load({
|
|
|
767
789
|
.dsh-cron-section-title { font-weight: 500; font-size: 12.5px; color: var(--dsw-alias-label-secondary, #aaa); margin-bottom: 10px; }
|
|
768
790
|
.dsh-cron-secret-row { display: flex; align-items: center; gap: 8px; }
|
|
769
791
|
.dsh-cron-secret-row input { flex: 1; }
|
|
770
|
-
.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; }
|
|
771
793
|
.dsh-cron-status-banner { padding: 10px 14px; border-radius: 8px; font-size: 13px; margin-top: 12px; }
|
|
772
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); }
|
|
773
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); }
|
|
@@ -778,13 +800,13 @@ window.__ModuleLoader__.load({
|
|
|
778
800
|
/* #34: sidebar job list */
|
|
779
801
|
.dsh-cron-sidebar-jobs { display: flex; flex-direction: column; gap: 2px; margin: 2px 0 6px 0; }
|
|
780
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; }
|
|
781
|
-
.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)); }
|
|
782
804
|
.dsh-cron-sidebar-jobs-head::after { content: '▾'; font-size: 10px; transition: transform 0.15s ease; }
|
|
783
805
|
.dsh-cron-sidebar-jobs[data-open="false"] .dsh-cron-sidebar-jobs-head::after { transform: rotate(-90deg); }
|
|
784
806
|
.dsh-cron-sidebar-jobs[data-open="false"] .dsh-cron-sidebar-jobs-list { display: none; }
|
|
785
807
|
.dsh-cron-sidebar-jobs-list { display: flex; flex-direction: column; gap: 2px; padding-left: 10px; }
|
|
786
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; }
|
|
787
|
-
.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); }
|
|
788
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; }
|
|
789
811
|
.dsh-cron-sidebar-job-meta { font-size: 11px; color: var(--dsw-alias-label-tertiary, #777); }
|
|
790
812
|
.dsh-cron-sidebar-empty, .dsh-cron-sidebar-more { font-size: 11px; color: var(--dsw-alias-label-tertiary, #777); padding: 2px 8px; }
|
|
@@ -967,7 +989,9 @@ window.__ModuleLoader__.load({
|
|
|
967
989
|
try {
|
|
968
990
|
sessions.open(sessionId);
|
|
969
991
|
return true;
|
|
970
|
-
} catch (
|
|
992
|
+
} catch (err) {
|
|
993
|
+
/* sessions.open threw synchronously */
|
|
994
|
+
}
|
|
971
995
|
}
|
|
972
996
|
|
|
973
997
|
// 2. If the session is not in manager.summaries yet, refresh from the backend
|
|
@@ -976,7 +1000,9 @@ window.__ModuleLoader__.load({
|
|
|
976
1000
|
await sessions.refresh();
|
|
977
1001
|
sessions.open(sessionId);
|
|
978
1002
|
return true;
|
|
979
|
-
} catch (
|
|
1003
|
+
} catch (err) {
|
|
1004
|
+
/* sessions.refresh threw */
|
|
1005
|
+
}
|
|
980
1006
|
}
|
|
981
1007
|
|
|
982
1008
|
// 3. Async retries until the WebSocket/SSE event adds the session to the list
|
|
@@ -987,7 +1013,9 @@ window.__ModuleLoader__.load({
|
|
|
987
1013
|
try {
|
|
988
1014
|
sessions.open(sessionId);
|
|
989
1015
|
return true;
|
|
990
|
-
} catch (
|
|
1016
|
+
} catch (err) {
|
|
1017
|
+
/* retry polling sessions.open threw */
|
|
1018
|
+
}
|
|
991
1019
|
}
|
|
992
1020
|
// DOM fallback: click the session in the sidebar if it has rendered
|
|
993
1021
|
if (typeof document !== 'undefined') {
|
|
@@ -1303,7 +1331,7 @@ window.__ModuleLoader__.load({
|
|
|
1303
1331
|
if (!node) return;
|
|
1304
1332
|
if (typeof node.scrollIntoView === 'function') node.scrollIntoView({ block: 'center' });
|
|
1305
1333
|
node.classList.add('dsh-cron-task-highlight');
|
|
1306
|
-
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);
|
|
1307
1335
|
}, [tasks]);
|
|
1308
1336
|
|
|
1309
1337
|
const fetchTasks = async () => {
|
|
@@ -1415,7 +1443,9 @@ window.__ModuleLoader__.load({
|
|
|
1415
1443
|
try {
|
|
1416
1444
|
await fetch('/dsh-cron/tasks/' + encodeURIComponent(id) + '/toggle', { method: 'POST' });
|
|
1417
1445
|
fetchTasks();
|
|
1418
|
-
} catch (err) {
|
|
1446
|
+
} catch (err) {
|
|
1447
|
+
console.debug('[dsh-cron] toggle failed', err);
|
|
1448
|
+
}
|
|
1419
1449
|
};
|
|
1420
1450
|
|
|
1421
1451
|
const handleRunNow = async (id) => {
|
|
@@ -1630,7 +1660,10 @@ window.__ModuleLoader__.load({
|
|
|
1630
1660
|
try {
|
|
1631
1661
|
await fetch('/dsh-cron/tasks/' + encodeURIComponent(id), { method: 'DELETE' });
|
|
1632
1662
|
fetchTasks();
|
|
1633
|
-
} catch (err) {
|
|
1663
|
+
} catch (err) {
|
|
1664
|
+
console.debug('[dsh-cron] delete failed', err);
|
|
1665
|
+
}
|
|
1666
|
+
};
|
|
1634
1667
|
|
|
1635
1668
|
const openSettingsModal = async () => {
|
|
1636
1669
|
setTestStatus(null);
|
|
@@ -1648,7 +1681,9 @@ window.__ModuleLoader__.load({
|
|
|
1648
1681
|
setSettingsHasDefault(Boolean(data.settings.hasDefaultCredentials));
|
|
1649
1682
|
setSettingsHasCustomBotToken(Boolean(data.settings.hasCustomBotToken));
|
|
1650
1683
|
}
|
|
1651
|
-
} catch (
|
|
1684
|
+
} catch (err) {
|
|
1685
|
+
console.debug('[dsh-cron] settings fetch failed', err);
|
|
1686
|
+
}
|
|
1652
1687
|
};
|
|
1653
1688
|
|
|
1654
1689
|
const handleSaveSettings = async (e) => {
|
|
@@ -2695,7 +2730,7 @@ window.__ModuleLoader__.load({
|
|
|
2695
2730
|
React.createElement('label', null, T_KEY(t, 'form.sshTargetLabel')),
|
|
2696
2731
|
React.createElement('input', {
|
|
2697
2732
|
value: formSshTarget,
|
|
2698
|
-
placeholder: '
|
|
2733
|
+
placeholder: 'user@example.com',
|
|
2699
2734
|
onChange: (e) => setFormSshTarget(e.target.value)
|
|
2700
2735
|
}),
|
|
2701
2736
|
React.createElement('input', {
|
|
@@ -2808,7 +2843,7 @@ window.__ModuleLoader__.load({
|
|
|
2808
2843
|
),
|
|
2809
2844
|
React.createElement('div', {
|
|
2810
2845
|
style: {
|
|
2811
|
-
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))',
|
|
2812
2847
|
border: '1px solid var(--dsw-alias-border-l2, #2e2e2e)',
|
|
2813
2848
|
borderRadius: '8px',
|
|
2814
2849
|
padding: '12px 14px',
|
|
@@ -2971,7 +3006,7 @@ window.__ModuleLoader__.load({
|
|
|
2971
3006
|
),
|
|
2972
3007
|
selectedTaskMeta && React.createElement('div', {
|
|
2973
3008
|
style: {
|
|
2974
|
-
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))',
|
|
2975
3010
|
border: '1px solid var(--dsw-alias-border-l2, #2e2e2e)',
|
|
2976
3011
|
borderRadius: '8px',
|
|
2977
3012
|
padding: '10px 14px',
|
|
@@ -3161,7 +3196,7 @@ window.__ModuleLoader__.load({
|
|
|
3161
3196
|
onChange: (e) => setSettingsChatId(e.target.value)
|
|
3162
3197
|
})
|
|
3163
3198
|
),
|
|
3164
|
-
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' } },
|
|
3165
3200
|
React.createElement('label', { className: 'dsh-cron-checkbox-row', style: { marginBottom: '8px' } },
|
|
3166
3201
|
React.createElement('input', {
|
|
3167
3202
|
type: 'checkbox',
|
|
@@ -3326,7 +3361,7 @@ window.__ModuleLoader__.load({
|
|
|
3326
3361
|
const [updateState, setUpdateState] = React.useState({
|
|
3327
3362
|
checking: false,
|
|
3328
3363
|
updating: false,
|
|
3329
|
-
currentVersion: '0.2.
|
|
3364
|
+
currentVersion: '0.2.19',
|
|
3330
3365
|
latestVersion: undefined,
|
|
3331
3366
|
updateAvailable: false,
|
|
3332
3367
|
notice: null,
|
|
@@ -3433,7 +3468,9 @@ window.__ModuleLoader__.load({
|
|
|
3433
3468
|
setSaved(true);
|
|
3434
3469
|
setTimeout(() => setSaved(false), 2500);
|
|
3435
3470
|
}
|
|
3436
|
-
} catch (err) {
|
|
3471
|
+
} catch (err) {
|
|
3472
|
+
console.debug('[dsh-cron] save settings failed', err);
|
|
3473
|
+
}
|
|
3437
3474
|
};
|
|
3438
3475
|
|
|
3439
3476
|
const testTelegram = async () => {
|
|
@@ -3538,7 +3575,7 @@ window.__ModuleLoader__.load({
|
|
|
3538
3575
|
onChange: (e) => setKanbanUrl(e.target.value)
|
|
3539
3576
|
})
|
|
3540
3577
|
),
|
|
3541
|
-
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' } },
|
|
3542
3579
|
React.createElement('label', { className: 'dsh-cron-checkbox-row' },
|
|
3543
3580
|
React.createElement('input', {
|
|
3544
3581
|
type: 'checkbox',
|
|
@@ -3590,8 +3627,8 @@ window.__ModuleLoader__.load({
|
|
|
3590
3627
|
React.createElement('div', {
|
|
3591
3628
|
className: 'dsh-cron-form-group',
|
|
3592
3629
|
style: {
|
|
3593
|
-
background: 'var(--dsw-alias-bg-layer-2,
|
|
3594
|
-
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))',
|
|
3595
3632
|
borderRadius: '8px',
|
|
3596
3633
|
padding: '14px',
|
|
3597
3634
|
marginBottom: '16px'
|
|
@@ -3766,7 +3803,9 @@ window.__ModuleLoader__.load({
|
|
|
3766
3803
|
const res = await fetch('/dsh-cron/tasks?status=active');
|
|
3767
3804
|
const data = await res.json();
|
|
3768
3805
|
if (data && data.ok) renderSidebarJobs(state, data.tasks || []);
|
|
3769
|
-
} catch (
|
|
3806
|
+
} catch (err) {
|
|
3807
|
+
console.debug('[dsh-cron] load sidebar jobs failed', err);
|
|
3808
|
+
}
|
|
3770
3809
|
}
|
|
3771
3810
|
|
|
3772
3811
|
function applySidebarJobsOpen(state) {
|
|
@@ -3791,7 +3830,7 @@ window.__ModuleLoader__.load({
|
|
|
3791
3830
|
event.preventDefault();
|
|
3792
3831
|
event.stopPropagation();
|
|
3793
3832
|
state.open = !state.open;
|
|
3794
|
-
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 */ }
|
|
3795
3834
|
applySidebarJobsOpen(state);
|
|
3796
3835
|
});
|
|
3797
3836
|
root.appendChild(headEl);
|
|
@@ -3834,7 +3873,7 @@ window.__ModuleLoader__.load({
|
|
|
3834
3873
|
function mountSidebarJobs(toggle, label, openTaskSignal) {
|
|
3835
3874
|
if (typeof document === 'undefined') return () => {};
|
|
3836
3875
|
const state = { toggle, openTaskSignal, root: undefined, listEl: undefined, headEl: undefined, open: false };
|
|
3837
|
-
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 */ }
|
|
3838
3877
|
|
|
3839
3878
|
const observer = new MutationObserver(() => placeSidebarJobs(state, label));
|
|
3840
3879
|
observer.observe(document.body, { childList: true, subtree: true });
|
|
@@ -3842,7 +3881,7 @@ window.__ModuleLoader__.load({
|
|
|
3842
3881
|
loadSidebarJobs(state);
|
|
3843
3882
|
const fetchTimer = setInterval(() => {
|
|
3844
3883
|
let isOpen = false;
|
|
3845
|
-
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 */ }
|
|
3846
3885
|
if (isOpen) loadSidebarJobs(state);
|
|
3847
3886
|
}, 30000);
|
|
3848
3887
|
|
|
@@ -3991,7 +4030,9 @@ window.__ModuleLoader__.load({
|
|
|
3991
4030
|
if (ctx.locale && typeof ctx.locale.bind === 'function') {
|
|
3992
4031
|
translate = ctx.locale.bind(NS);
|
|
3993
4032
|
}
|
|
3994
|
-
} catch (
|
|
4033
|
+
} catch (err) {
|
|
4034
|
+
/* locale bind unavailable, fallback to default translate */
|
|
4035
|
+
}
|
|
3995
4036
|
|
|
3996
4037
|
const toggle = createToggle();
|
|
3997
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
|
},
|