@goodandready/dsh-cron 0.2.16 → 0.2.18
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 +12 -0
- package/README.ru.md +12 -0
- package/README.zh.md +12 -0
- package/lib/client.js +242 -216
- package/package.json +5 -3
package/README.md
CHANGED
|
@@ -392,6 +392,18 @@ Developer-facing, no behaviour change. `parseScheduleExpression` was split into
|
|
|
392
392
|
|
|
393
393
|
|
|
394
394
|
|
|
395
|
+
### 30. Updater Localization in English & Chinese (v0.2.18, #160)
|
|
396
|
+
- **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
|
+
|
|
398
|
+
---
|
|
399
|
+
|
|
400
|
+
### 29. Client Modular Decomposition & Theme Standardization (v0.2.17, #149, #150)
|
|
401
|
+
- **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", ...]`.
|
|
402
|
+
- **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.
|
|
403
|
+
- **Label Governance & Triage Audit (#95)**: Standardized 100% of repository issues and triage on the canonical repo-level label set.
|
|
404
|
+
|
|
405
|
+
---
|
|
406
|
+
|
|
395
407
|
### 28. In-App One-Click Auto-Updater & Error Resilience (v0.2.16, #147, #155, #156)
|
|
396
408
|
- **Plugin Self-Updater (#147)**: Added in-app one-click self-updating module (`lib/updater.js`) with `/api/dsh-cron/update` endpoint and dedicated Settings UI card. Automatically queries npm registry, compares semver versions including pre-releases, and upgrades `@goodandready/dsh-cron` in-place through the DSH CLI without manual SSH sessions. POST updates are protected via origin validation (`rejectCrossOrigin`).
|
|
397
409
|
- **Silent Failure Elimination (#156)**: Replaced silent empty catches with diagnostic logging: uncompleted import transaction rollbacks are surfaced to system logs with warning severity, dynamic core module fallback reasons are logged, and session opening failures inform the user with actionable notifications.
|
package/README.ru.md
CHANGED
|
@@ -268,6 +268,18 @@ cron_create_task({
|
|
|
268
268
|
|
|
269
269
|
|
|
270
270
|
|
|
271
|
+
### 30. Локализация автообновления на английский и китайский языки (v0.2.18, #160)
|
|
272
|
+
- **Локализация карточки обновления (#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
|
+
|
|
274
|
+
---
|
|
275
|
+
|
|
276
|
+
### 29. Модульная декомпозиция клиента и стандартизация тем оформления (v0.2.17, #149, #150)
|
|
277
|
+
- **Модульная архитектура клиентской части (#150)**: Монолитный файл `lib/client.js` (~3950 строк) декомпозирован на 14 независимых специализированных модулей в каталоге `lib/client-src/` (каждый строго <= 580 строк). Сборка `lib/client.js` выполняется легковесным скриптом `scripts/build-client.mjs` перед запуском тестов и сборки. Исходные фрагменты исключены из npm-дистрибутива через `"files": ["lib/*.js", ...]`.
|
|
278
|
+
- **Семантические токены тем 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.
|
|
279
|
+
- **Аудит меток репозитория (#95)**: Подтверждена 100% консистентность использования канонического репозиторного набора меток и фильтрации задач.
|
|
280
|
+
|
|
281
|
+
---
|
|
282
|
+
|
|
271
283
|
### 28. Модуль самообновления в 1 клик и устойчивость к ошибкам (v0.2.16, #147, #155, #156)
|
|
272
284
|
- **Самообновление плагина (#147)**: Добавлен встроенный модуль обновления (`lib/updater.js`) с маршрутом `/api/dsh-cron/update` и карточкой в настройках Web UI. Автоматически опрашивает npm registry, корректно сравнивает semver-версии (включая пререлизы) и обновляет `@goodandready/dsh-cron` на лету через CLI DSH без необходимости входа по SSH. Запросы POST защищены проверкой источника (`rejectCrossOrigin`).
|
|
273
285
|
- **Устранение скрытых сбоев (#156)**: Ликвидированы пустые `catch`: незавершённые откаты транзакций импорта логируются с уровнем `warn`, причины fallback для динамических модулей ядра фиксируются в отладочном журнале, а при сбое открытия сессии пользователь получает понятное уведомление.
|
package/README.zh.md
CHANGED
|
@@ -391,6 +391,18 @@ bash deploy.sh verify [exact-version]
|
|
|
391
391
|
|
|
392
392
|
|
|
393
393
|
|
|
394
|
+
### 30. 自更新模块英文与中文多语言支持 (v0.2.18, #160)
|
|
395
|
+
- **设置面板自更新多语言 (#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
|
+
|
|
397
|
+
---
|
|
398
|
+
|
|
399
|
+
### 29. 客户端模块化解耦与原生主题标准化 (v0.2.17, #149, #150)
|
|
400
|
+
- **客户端模块化架构 (#150)**: 将庞大的单文件 `lib/client.js` (约 3950 行) 拆分为 `lib/client-src/` 下的 14 个高内聚模块文件 (各模块严格 <= 580 行)。集成零依赖构建脚本 `scripts/build-client.mjs` 并接入 `package.json` (`build:client`, `pretest`),通过 `"files": ["lib/*.js", ...]` 避免开发源码冗余打包进 npm 发布包。
|
|
401
|
+
- **DSH 语义化主题变量对齐 (#149)**: 将任务类型标签 (`onSuccess`, `onFailure`, `heartbeat`, `targetSession`, `preflight`) 的内联样式全部替换为基于主题变量的 `.dsh-cron-tag-*` 类;模态框遮罩层接入自适应主题遮罩变量 `var(--dsw-alias-bg-mask, rgba(0, 0, 0, 0.75))`,移除脉冲动画关键帧中的硬编码 RGBA。
|
|
402
|
+
- **标签治理与工单审计 (#95)**: 审计并确认全仓库 100% 统一规范使用仓库级标签集。
|
|
403
|
+
|
|
404
|
+
---
|
|
405
|
+
|
|
394
406
|
### 28. 应用内一键自更新与错误容错增强 (v0.2.16, #147, #155, #156)
|
|
395
407
|
- **插件自更新模块 (#147)**:新增应用内一键自更新机制 (`lib/updater.js`)、`/api/dsh-cron/update` 接口与设置面板专属卡片。自动轮询 npmjs 仓库最新版本,精准比对 Semver 版本号(含预发布版本),通过 DSH CLI 就地升级 `@goodandready/dsh-cron`,无需 SSH 终端操作。POST 请求受跨域与来源保护 (`rejectCrossOrigin`)。
|
|
396
408
|
- **消除静默异常与错误追踪 (#156)**:全面消除空 `catch` 异常压制:任务导入事务回滚失败记录 `warn` 级别日志、动态核心模块降级原因记录诊断日志、会话自动唤出失败向用户呈现友好提示。
|
package/lib/client.js
CHANGED
|
@@ -44,6 +44,17 @@ window.__ModuleLoader__.load({
|
|
|
44
44
|
// Locale dictionary: English is the canonical source language (Issue #87).
|
|
45
45
|
// Russian and other languages are provided at runtime by the translation
|
|
46
46
|
// plugin; no per-language duplicates are stored here.
|
|
47
|
+
// Core chevron primitive when the build ships it; own SVG fallback otherwise.
|
|
48
|
+
let CoreChevron = null;
|
|
49
|
+
try {
|
|
50
|
+
const primitives = require('@deepseek-ai/dsh-client-ui-primitives');
|
|
51
|
+
CoreChevron = primitives && primitives.IconChevronDownOutline14;
|
|
52
|
+
} catch (e) {}
|
|
53
|
+
const chevronNode = () => (CoreChevron ? React.createElement(CoreChevron) : React.createElement('span', { style: { display: 'flex', alignItems: 'center' }, dangerouslySetInnerHTML: { __html: ICON_CHEVRON_DOWN } }));
|
|
54
|
+
|
|
55
|
+
// Own React error boundary (#106): the core's SlotErrorBoundary renders an
|
|
56
|
+
// empty div on crash, so a slot failure would silently vanish. Ours keeps
|
|
57
|
+
// the failure visible with a retry.
|
|
47
58
|
const STRINGS = {
|
|
48
59
|
en: {
|
|
49
60
|
'sidebar.label': 'Scheduled tasks',
|
|
@@ -283,6 +294,16 @@ window.__ModuleLoader__.load({
|
|
|
283
294
|
'settings.saved': '✓ Saved',
|
|
284
295
|
'settings.saveError': 'Save failed: {error}',
|
|
285
296
|
'settings.saveErrorUnknown': 'unknown error',
|
|
297
|
+
'updater.title': 'Plugin Auto-Updater',
|
|
298
|
+
'updater.btnCheck': 'Check for updates',
|
|
299
|
+
'updater.checking': 'Checking...',
|
|
300
|
+
'updater.btnUpdate': 'Update to v{version}',
|
|
301
|
+
'updater.updating': 'Updating...',
|
|
302
|
+
'updater.desc': 'Check npm registry and install plugin updates in-place without SSH access.',
|
|
303
|
+
'updater.current': 'Current version: v{version}',
|
|
304
|
+
'updater.available': 'Update available: v{version}',
|
|
305
|
+
'updater.upToDate': 'Up to date',
|
|
306
|
+
'updater.success': 'Successfully updated to v{version}. Please restart DSH or reload profile to apply changes.',
|
|
286
307
|
'dsh.modalTitle': 'Schedule a task with the DSH agent',
|
|
287
308
|
'dsh.descLabel': 'Describe the task and the desired frequency:',
|
|
288
309
|
'dsh.placeholder': 'For example: check free disk space and failed systemd services every 2 hours, and alert only if something is wrong...',
|
|
@@ -563,6 +584,16 @@ window.__ModuleLoader__.load({
|
|
|
563
584
|
'settings.saved': '✓ 已保存',
|
|
564
585
|
'settings.saveError': '保存失败: {error}',
|
|
565
586
|
'settings.saveErrorUnknown': '未知错误',
|
|
587
|
+
'updater.title': '插件自更新',
|
|
588
|
+
'updater.btnCheck': '检查更新',
|
|
589
|
+
'updater.checking': '正在检查...',
|
|
590
|
+
'updater.btnUpdate': '升级至 v{version}',
|
|
591
|
+
'updater.updating': '正在更新...',
|
|
592
|
+
'updater.desc': '通过 npm 仓库在线检查并就地升级插件,无需 SSH 终端操作。',
|
|
593
|
+
'updater.current': '当前版本: v{version}',
|
|
594
|
+
'updater.available': '发现新版本: v{version}',
|
|
595
|
+
'updater.upToDate': '已是最新版本',
|
|
596
|
+
'updater.success': '已成功升级至 v{version}。请重启 DSH 或重载配置以应用更新。',
|
|
566
597
|
'dsh.modalTitle': '让 DSH 智能体创建任务',
|
|
567
598
|
'dsh.descLabel': '请描述你的任务目标和执行频率:',
|
|
568
599
|
'dsh.placeholder': '例如: 每两小时检查一次磁盘空间和失败的 systemd 守护进程,并在异常时报警...',
|
|
@@ -588,214 +619,6 @@ window.__ModuleLoader__.load({
|
|
|
588
619
|
};
|
|
589
620
|
const T = (key, vars) => translate(key, vars);
|
|
590
621
|
|
|
591
|
-
// Core chevron primitive when the build ships it; own SVG fallback otherwise.
|
|
592
|
-
let CoreChevron = null;
|
|
593
|
-
try {
|
|
594
|
-
const primitives = require('@deepseek-ai/dsh-client-ui-primitives');
|
|
595
|
-
CoreChevron = primitives && primitives.IconChevronDownOutline14;
|
|
596
|
-
} catch (e) {}
|
|
597
|
-
const chevronNode = () => (CoreChevron ? React.createElement(CoreChevron) : React.createElement('span', { style: { display: 'flex', alignItems: 'center' }, dangerouslySetInnerHTML: { __html: ICON_CHEVRON_DOWN } }));
|
|
598
|
-
|
|
599
|
-
// Own React error boundary (#106): the core's SlotErrorBoundary renders an
|
|
600
|
-
// empty div on crash, so a slot failure would silently vanish. Ours keeps
|
|
601
|
-
// the failure visible with a retry.
|
|
602
|
-
function createErrorBoundary() {
|
|
603
|
-
if (!React || typeof React.Component !== 'function') {
|
|
604
|
-
return function NoopBoundary(props) { return (props && props.children) || null; };
|
|
605
|
-
}
|
|
606
|
-
return class ErrorBoundary extends React.Component {
|
|
607
|
-
constructor(props) {
|
|
608
|
-
super(props);
|
|
609
|
-
this.state = { hasError: false, error: null };
|
|
610
|
-
}
|
|
611
|
-
static getDerivedStateFromError(error) {
|
|
612
|
-
return { hasError: true, error };
|
|
613
|
-
}
|
|
614
|
-
componentDidCatch(error, errorInfo) {
|
|
615
|
-
console.error('[dsh-cron] React error:', error, errorInfo);
|
|
616
|
-
}
|
|
617
|
-
render() {
|
|
618
|
-
if (this.state.hasError) {
|
|
619
|
-
return React.createElement('div', { className: 'dsh-cron-status-banner', 'data-type': 'error', style: { margin: '12px 0' } },
|
|
620
|
-
React.createElement('div', { style: { fontWeight: 600, marginBottom: '6px' } }, '⚠️ Cron UI error:'),
|
|
621
|
-
React.createElement('div', { style: { fontSize: '12px', wordBreak: 'break-all' } }, String((this.state.error && this.state.error.message) || this.state.error)),
|
|
622
|
-
React.createElement('button', {
|
|
623
|
-
type: 'button',
|
|
624
|
-
className: 'dsh-cron-btn-secondary',
|
|
625
|
-
style: { marginTop: '10px', fontSize: '12px', padding: '4px 10px' },
|
|
626
|
-
onClick: () => this.setState({ hasError: false, error: null })
|
|
627
|
-
}, 'Retry')
|
|
628
|
-
);
|
|
629
|
-
}
|
|
630
|
-
return (this.props && this.props.children) || null;
|
|
631
|
-
}
|
|
632
|
-
};
|
|
633
|
-
}
|
|
634
|
-
const ErrorBoundary = createErrorBoundary();
|
|
635
|
-
|
|
636
|
-
const AGENT_FORM_TYPES = ['llm', 'skill', 'workflow'];
|
|
637
|
-
const CODE_FORM_TYPES = ['script', 'node', 'python', 'ssh', 'docker'];
|
|
638
|
-
|
|
639
|
-
// Delivery channels (#26) — must stay in sync with lib/channels.js.
|
|
640
|
-
const DELIVERY_CHANNELS = ['telegram', 'kanban', 'discord', 'slack', 'ntfy', 'bark', 'pushplus', 'tts', 'gitea'];
|
|
641
|
-
const CHANNEL_LABEL_KEYS = {
|
|
642
|
-
telegram: 'form.channelTelegram',
|
|
643
|
-
kanban: 'form.channelKanban',
|
|
644
|
-
discord: 'form.channelDiscord',
|
|
645
|
-
slack: 'form.channelSlack',
|
|
646
|
-
ntfy: 'form.channelNtfy',
|
|
647
|
-
bark: 'form.channelBark',
|
|
648
|
-
pushplus: 'form.channelPushplus',
|
|
649
|
-
tts: 'form.channelTts',
|
|
650
|
-
gitea: 'form.channelGitea',
|
|
651
|
-
};
|
|
652
|
-
|
|
653
|
-
// Non-secret delivery settings shown in the settings panel. Secrets are
|
|
654
|
-
// referenced by credential name only (#51) — never typed as values.
|
|
655
|
-
const DELIVERY_SETTING_FIELDS = [
|
|
656
|
-
{ key: 'botTokenRef', labelKey: 'settings.botTokenRefLabel', placeholderKey: 'settings.botTokenRefPlaceholder', group: 'secrets' },
|
|
657
|
-
{ key: 'ntfyTokenRef', labelKey: 'settings.ntfyTokenRefLabel', group: 'secrets' },
|
|
658
|
-
{ key: 'pushplusTokenRef', labelKey: 'settings.pushplusTokenRefLabel', group: 'secrets' },
|
|
659
|
-
{ key: 'giteaTokenRef', labelKey: 'settings.giteaTokenRefLabel', group: 'secrets' },
|
|
660
|
-
// The external API is the one surface where a typed secret is the point:
|
|
661
|
-
// a CI job cannot present a credential reference (#54).
|
|
662
|
-
{ key: 'apiToken', labelKey: 'settings.apiTokenLabel', placeholderKey: 'settings.apiTokenPlaceholder', group: 'secrets' },
|
|
663
|
-
{ key: 'discordWebhookUrl', labelKey: 'settings.discordLabel', group: 'channels' },
|
|
664
|
-
{ key: 'slackWebhookUrl', labelKey: 'settings.slackLabel', group: 'channels' },
|
|
665
|
-
{ key: 'ntfyUrl', labelKey: 'settings.ntfyUrlLabel', group: 'channels' },
|
|
666
|
-
{ key: 'ntfyTopic', labelKey: 'settings.ntfyTopicLabel', group: 'channels' },
|
|
667
|
-
{ key: 'barkServerUrl', labelKey: 'settings.barkServerLabel', group: 'channels' },
|
|
668
|
-
{ key: 'barkKey', labelKey: 'settings.barkKeyLabel', group: 'channels' },
|
|
669
|
-
{ key: 'pushplusUrl', labelKey: 'settings.pushplusUrlLabel', group: 'channels' },
|
|
670
|
-
{ key: 'ttsBaseUrl', labelKey: 'settings.ttsBaseUrlLabel', group: 'channels' },
|
|
671
|
-
{ key: 'giteaBaseUrl', labelKey: 'settings.giteaBaseUrlLabel', group: 'channels' },
|
|
672
|
-
{ key: 'giteaRepo', labelKey: 'settings.giteaRepoLabel', group: 'channels' },
|
|
673
|
-
{ key: 'deliveryTimeoutMs', labelKey: 'settings.deliveryTimeoutMsLabel', group: 'channels', numeric: true, defaultValue: 15000, min: 1000 },
|
|
674
|
-
{ key: 'silentRuleModel', labelKey: 'settings.silentRuleModelLabel', group: 'automation' },
|
|
675
|
-
{ key: 'inspectorModel', labelKey: 'settings.inspectorModelLabel', group: 'automation' },
|
|
676
|
-
];
|
|
677
|
-
const DELIVERY_SETTING_KEYS = DELIVERY_SETTING_FIELDS.map((f) => f.key);
|
|
678
|
-
// The global template plus the per-channel override map (server model).
|
|
679
|
-
const TEMPLATE_SETTING_KEYS = ['template', 'channelTemplates'];
|
|
680
|
-
|
|
681
|
-
/** Collect every delivery key the settings endpoints may return. */
|
|
682
|
-
function pickDeliverySettings(source) {
|
|
683
|
-
const out = {};
|
|
684
|
-
for (const key of DELIVERY_SETTING_KEYS.concat(TEMPLATE_SETTING_KEYS)) {
|
|
685
|
-
if (source && source[key] !== undefined && source[key] !== null) out[key] = source[key];
|
|
686
|
-
}
|
|
687
|
-
if (out.channelTemplates && typeof out.channelTemplates === 'object') {
|
|
688
|
-
out.channelTemplates = Object.assign({}, out.channelTemplates);
|
|
689
|
-
}
|
|
690
|
-
return out;
|
|
691
|
-
}
|
|
692
|
-
|
|
693
|
-
/**
|
|
694
|
-
* Shared delivery form used by both the task panel modal and the plugin
|
|
695
|
-
* settings card, so a new channel is added in exactly one place.
|
|
696
|
-
* Sections collapse via the canonical aria-expanded head contract.
|
|
697
|
-
*/
|
|
698
|
-
function renderDeliverySettings({ T, values, onChange, onChannelTemplate, sections, onToggleSection }) {
|
|
699
|
-
const val = (key) => (values[key] === undefined || values[key] === null ? '' : String(values[key]));
|
|
700
|
-
const field = (f) => React.createElement('div', { className: 'dsh-cron-form-group', key: f.key, style: { marginBottom: '10px' } },
|
|
701
|
-
React.createElement('label', null, T(f.labelKey)),
|
|
702
|
-
React.createElement('input', f.numeric ? {
|
|
703
|
-
type: 'number',
|
|
704
|
-
min: f.min !== undefined ? f.min : 1000,
|
|
705
|
-
step: 1000,
|
|
706
|
-
value: val(f.key),
|
|
707
|
-
placeholder: String(f.defaultValue !== undefined ? f.defaultValue : 15000),
|
|
708
|
-
// #115: clearing the field must send the explicit default. Deleting the
|
|
709
|
-
// key would leave the previously stored value in place (the settings
|
|
710
|
-
// scope skips absent keys), so the field would silently keep 1 ms.
|
|
711
|
-
onChange: (e) => onChange(f.key, e.target.value === ''
|
|
712
|
-
? (f.defaultValue !== undefined ? f.defaultValue : 15000)
|
|
713
|
-
: Number(e.target.value)),
|
|
714
|
-
} : {
|
|
715
|
-
type: 'text',
|
|
716
|
-
value: val(f.key),
|
|
717
|
-
placeholder: f.placeholderKey ? T(f.placeholderKey) : '',
|
|
718
|
-
onChange: (e) => onChange(f.key, e.target.value)
|
|
719
|
-
})
|
|
720
|
-
);
|
|
721
|
-
const section = (key, titleKey, children) => {
|
|
722
|
-
const open = Boolean(sections[key]);
|
|
723
|
-
return React.createElement('div', { className: 'dsh-cron-section', key },
|
|
724
|
-
React.createElement('button', {
|
|
725
|
-
type: 'button',
|
|
726
|
-
className: 'dsh-cron-section-head',
|
|
727
|
-
'aria-expanded': open ? 'true' : 'false',
|
|
728
|
-
onClick: () => onToggleSection(key)
|
|
729
|
-
},
|
|
730
|
-
React.createElement('span', null, T(titleKey)),
|
|
731
|
-
React.createElement('span', { className: 'dsh-cron-section-chevron', 'data-open': open ? 'true' : 'false' }, '▾')
|
|
732
|
-
),
|
|
733
|
-
open && React.createElement('div', { className: 'dsh-cron-section-body' }, children)
|
|
734
|
-
);
|
|
735
|
-
};
|
|
736
|
-
|
|
737
|
-
return [
|
|
738
|
-
section('secrets', 'settings.secretsSection', [
|
|
739
|
-
React.createElement('div', { className: 'dsh-cron-hint', key: 'hint', style: { marginBottom: '10px' } }, T('settings.secretsHint')),
|
|
740
|
-
...DELIVERY_SETTING_FIELDS.filter((f) => f.group === 'secrets').map(field),
|
|
741
|
-
]),
|
|
742
|
-
section('channels', 'settings.deliverySection', [
|
|
743
|
-
React.createElement('div', { className: 'dsh-cron-form-row', key: 'row' },
|
|
744
|
-
...DELIVERY_SETTING_FIELDS.filter((f) => f.group === 'channels').map(field)
|
|
745
|
-
),
|
|
746
|
-
]),
|
|
747
|
-
section('automation', 'settings.automationSection', [
|
|
748
|
-
React.createElement('div', { className: 'dsh-cron-hint', key: 'hint', style: { marginBottom: '10px' } }, T('settings.automationHint')),
|
|
749
|
-
React.createElement('div', { className: 'dsh-cron-form-row', key: 'row' },
|
|
750
|
-
...DELIVERY_SETTING_FIELDS.filter((f) => f.group === 'automation').map(field)
|
|
751
|
-
),
|
|
752
|
-
]),
|
|
753
|
-
section('templates', 'settings.templatesSection', [
|
|
754
|
-
React.createElement('div', { className: 'dsh-cron-form-group', key: 'global', style: { marginBottom: '10px' } },
|
|
755
|
-
React.createElement('label', null, T('settings.templateLabel')),
|
|
756
|
-
React.createElement('input', {
|
|
757
|
-
type: 'text',
|
|
758
|
-
value: val('template'),
|
|
759
|
-
onChange: (e) => onChange('template', e.target.value)
|
|
760
|
-
}),
|
|
761
|
-
React.createElement('div', { className: 'dsh-cron-hint' }, T('settings.templateHint'))
|
|
762
|
-
),
|
|
763
|
-
React.createElement('div', { className: 'dsh-cron-form-row', key: 'perchannel' },
|
|
764
|
-
...DELIVERY_CHANNELS.map((id) => React.createElement('div', { className: 'dsh-cron-form-group', key: id, style: { marginBottom: '10px' } },
|
|
765
|
-
React.createElement('label', null, T('settings.channelTemplateLabel', { channel: T(CHANNEL_LABEL_KEYS[id]) })),
|
|
766
|
-
React.createElement('input', {
|
|
767
|
-
type: 'text',
|
|
768
|
-
value: (values.channelTemplates && values.channelTemplates[id]) || '',
|
|
769
|
-
onChange: (e) => onChannelTemplate(id, e.target.value)
|
|
770
|
-
})
|
|
771
|
-
))
|
|
772
|
-
),
|
|
773
|
-
]),
|
|
774
|
-
];
|
|
775
|
-
}
|
|
776
|
-
|
|
777
|
-
/** Parse a KEY=VALUE textarea into the task env object (#38). */
|
|
778
|
-
function parseEnvText(text) {
|
|
779
|
-
const env = {};
|
|
780
|
-
for (const rawLine of String(text || '').split('\n')) {
|
|
781
|
-
const line = rawLine.trim();
|
|
782
|
-
if (!line || line.startsWith('#')) continue;
|
|
783
|
-
const idx = line.indexOf('=');
|
|
784
|
-
if (idx <= 0) continue;
|
|
785
|
-
const key = line.slice(0, idx).trim();
|
|
786
|
-
const value = line.slice(idx + 1).trim();
|
|
787
|
-
if (key) env[key] = value;
|
|
788
|
-
}
|
|
789
|
-
return Object.keys(env).length ? env : undefined;
|
|
790
|
-
}
|
|
791
|
-
|
|
792
|
-
function formatEnvText(env) {
|
|
793
|
-
if (!env || typeof env !== 'object') return '';
|
|
794
|
-
return Object.entries(env).map(([k, v]) => `${k}=${v}`).join('\n');
|
|
795
|
-
}
|
|
796
|
-
|
|
797
|
-
// Status colors: core semantic state tokens first (same set dsh-clinebot
|
|
798
|
-
// uses), plugin fallbacks as a single point of change (#105).
|
|
799
622
|
const STYLES = `
|
|
800
623
|
:root {
|
|
801
624
|
--dsh-cron-success: var(--dsw-alias-state-success-primary, #10b981);
|
|
@@ -865,6 +688,12 @@ window.__ModuleLoader__.load({
|
|
|
865
688
|
.dsh-cron-task-item:hover, .dsh-cron-task-item:focus-visible { background: var(--dsw-alias-bg-layer-4, #242424); border-color: var(--dsw-alias-label-dimmed, var(--dsw-alias-border-l1, #444)); outline: none; }
|
|
866
689
|
.dsh-cron-task-prompt-preview { font-size: 12.5px; color: var(--dsw-alias-label-secondary, #888); margin-top: 5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 520px; line-height: 1.3; }
|
|
867
690
|
.dsh-cron-type-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); text-transform: uppercase; font-weight: 500; margin-left: 8px; vertical-align: middle; }
|
|
691
|
+
.dsh-cron-tag-success { background: var(--dsh-cron-info-bg); color: var(--dsh-cron-info); border-color: var(--dsh-cron-info); }
|
|
692
|
+
.dsh-cron-tag-failure { background: var(--dsh-cron-danger-bg); color: var(--dsh-cron-danger); border-color: var(--dsh-cron-danger); }
|
|
693
|
+
.dsh-cron-tag-heartbeat { background: var(--dsh-cron-accent-bg); color: var(--dsh-cron-accent); border-color: var(--dsh-cron-accent); }
|
|
694
|
+
.dsh-cron-tag-session { background: var(--dsh-cron-info-bg); color: var(--dsh-cron-info); border-color: var(--dsh-cron-info); }
|
|
695
|
+
.dsh-cron-tag-preflight { background: var(--dsh-cron-warning-bg); color: var(--dsh-cron-warning); border-color: var(--dsh-cron-warning); }
|
|
696
|
+
|
|
868
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, rgba(255,255,255,0.06)); color: var(--dsw-alias-label-secondary, #aaa); margin-left: 8px; vertical-align: middle; }
|
|
869
698
|
.dsh-cron-task-actions { display: flex; gap: 8px; }
|
|
870
699
|
.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; }
|
|
@@ -883,7 +712,7 @@ window.__ModuleLoader__.load({
|
|
|
883
712
|
.dsh-cron-rec-time { font-size: 13px; color: var(--dsw-alias-label-secondary, #9ca3af); font-weight: normal; margin-left: 8px; }
|
|
884
713
|
.dsh-cron-rec-desc { font-size: 13px; color: var(--dsw-alias-label-tertiary, #71717a); }
|
|
885
714
|
|
|
886
|
-
.dsh-cron-modal-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.75); backdrop-filter: blur(4px); display: flex; align-items: center; justify-content: center; z-index: 1000; padding: 16px; box-sizing: border-box; overflow-y: auto; }
|
|
715
|
+
.dsh-cron-modal-overlay { position: fixed; inset: 0; background: var(--dsw-alias-bg-mask, rgba(0, 0, 0, 0.75)); backdrop-filter: blur(4px); display: flex; align-items: center; justify-content: center; z-index: 1000; padding: 16px; box-sizing: border-box; overflow-y: auto; }
|
|
887
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 rgba(0,0,0,0.6); box-sizing: border-box; }
|
|
888
717
|
.dsh-cron-modal-scroll { max-width: 640px; max-height: min(90vh, calc(100vh - 36px)); overflow-y: auto; }
|
|
889
718
|
.dsh-cron-modal::-webkit-scrollbar { width: 6px; }
|
|
@@ -963,7 +792,7 @@ window.__ModuleLoader__.load({
|
|
|
963
792
|
.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); }
|
|
964
793
|
.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); }
|
|
965
794
|
.dsh-cron-status-banner[data-type="info"] { background: var(--dsh-cron-info-bg); border: 1px solid var(--dsh-cron-info); color: var(--dsh-cron-info); }
|
|
966
|
-
@keyframes dsh-cron-pulse-ring { 0% { box-shadow: 0 0 0 0
|
|
795
|
+
@keyframes dsh-cron-pulse-ring { 0% { box-shadow: 0 0 0 0 var(--dsh-cron-success-bg); } 70% { box-shadow: 0 0 0 9px transparent; } 100% { box-shadow: 0 0 0 0 transparent; } }
|
|
967
796
|
.dsh-cron-pulse { animation: dsh-cron-pulse-ring 1.6s infinite; border-color: var(--dsh-cron-success); color: var(--dsh-cron-success); }
|
|
968
797
|
|
|
969
798
|
/* #34: sidebar job list */
|
|
@@ -1026,6 +855,107 @@ window.__ModuleLoader__.load({
|
|
|
1026
855
|
el.textContent = STYLES;
|
|
1027
856
|
}
|
|
1028
857
|
|
|
858
|
+
function createErrorBoundary() {
|
|
859
|
+
if (!React || typeof React.Component !== 'function') {
|
|
860
|
+
return function NoopBoundary(props) { return (props && props.children) || null; };
|
|
861
|
+
}
|
|
862
|
+
return class ErrorBoundary extends React.Component {
|
|
863
|
+
constructor(props) {
|
|
864
|
+
super(props);
|
|
865
|
+
this.state = { hasError: false, error: null };
|
|
866
|
+
}
|
|
867
|
+
static getDerivedStateFromError(error) {
|
|
868
|
+
return { hasError: true, error };
|
|
869
|
+
}
|
|
870
|
+
componentDidCatch(error, errorInfo) {
|
|
871
|
+
console.error('[dsh-cron] React error:', error, errorInfo);
|
|
872
|
+
}
|
|
873
|
+
render() {
|
|
874
|
+
if (this.state.hasError) {
|
|
875
|
+
return React.createElement('div', { className: 'dsh-cron-status-banner', 'data-type': 'error', style: { margin: '12px 0' } },
|
|
876
|
+
React.createElement('div', { style: { fontWeight: 600, marginBottom: '6px' } }, '⚠️ Cron UI error:'),
|
|
877
|
+
React.createElement('div', { style: { fontSize: '12px', wordBreak: 'break-all' } }, String((this.state.error && this.state.error.message) || this.state.error)),
|
|
878
|
+
React.createElement('button', {
|
|
879
|
+
type: 'button',
|
|
880
|
+
className: 'dsh-cron-btn-secondary',
|
|
881
|
+
style: { marginTop: '10px', fontSize: '12px', padding: '4px 10px' },
|
|
882
|
+
onClick: () => this.setState({ hasError: false, error: null })
|
|
883
|
+
}, 'Retry')
|
|
884
|
+
);
|
|
885
|
+
}
|
|
886
|
+
return (this.props && this.props.children) || null;
|
|
887
|
+
}
|
|
888
|
+
};
|
|
889
|
+
}
|
|
890
|
+
const ErrorBoundary = createErrorBoundary();
|
|
891
|
+
|
|
892
|
+
const AGENT_FORM_TYPES = ['llm', 'skill', 'workflow'];
|
|
893
|
+
const CODE_FORM_TYPES = ['script', 'node', 'python', 'ssh', 'docker'];
|
|
894
|
+
|
|
895
|
+
// Delivery channels (#26) — must stay in sync with lib/channels.js.
|
|
896
|
+
const DELIVERY_CHANNELS = ['telegram', 'kanban', 'discord', 'slack', 'ntfy', 'bark', 'pushplus', 'tts', 'gitea'];
|
|
897
|
+
const CHANNEL_LABEL_KEYS = {
|
|
898
|
+
telegram: 'form.channelTelegram',
|
|
899
|
+
kanban: 'form.channelKanban',
|
|
900
|
+
discord: 'form.channelDiscord',
|
|
901
|
+
slack: 'form.channelSlack',
|
|
902
|
+
ntfy: 'form.channelNtfy',
|
|
903
|
+
bark: 'form.channelBark',
|
|
904
|
+
pushplus: 'form.channelPushplus',
|
|
905
|
+
tts: 'form.channelTts',
|
|
906
|
+
gitea: 'form.channelGitea',
|
|
907
|
+
};
|
|
908
|
+
|
|
909
|
+
// Non-secret delivery settings shown in the settings panel. Secrets are
|
|
910
|
+
// referenced by credential name only (#51) — never typed as values.
|
|
911
|
+
const DELIVERY_SETTING_FIELDS = [
|
|
912
|
+
{ key: 'botTokenRef', labelKey: 'settings.botTokenRefLabel', placeholderKey: 'settings.botTokenRefPlaceholder', group: 'secrets' },
|
|
913
|
+
{ key: 'ntfyTokenRef', labelKey: 'settings.ntfyTokenRefLabel', group: 'secrets' },
|
|
914
|
+
{ key: 'pushplusTokenRef', labelKey: 'settings.pushplusTokenRefLabel', group: 'secrets' },
|
|
915
|
+
{ key: 'giteaTokenRef', labelKey: 'settings.giteaTokenRefLabel', group: 'secrets' },
|
|
916
|
+
// The external API is the one surface where a typed secret is the point:
|
|
917
|
+
// a CI job cannot present a credential reference (#54).
|
|
918
|
+
{ key: 'apiToken', labelKey: 'settings.apiTokenLabel', placeholderKey: 'settings.apiTokenPlaceholder', group: 'secrets' },
|
|
919
|
+
{ key: 'discordWebhookUrl', labelKey: 'settings.discordLabel', group: 'channels' },
|
|
920
|
+
{ key: 'slackWebhookUrl', labelKey: 'settings.slackLabel', group: 'channels' },
|
|
921
|
+
{ key: 'ntfyUrl', labelKey: 'settings.ntfyUrlLabel', group: 'channels' },
|
|
922
|
+
{ key: 'ntfyTopic', labelKey: 'settings.ntfyTopicLabel', group: 'channels' },
|
|
923
|
+
{ key: 'barkServerUrl', labelKey: 'settings.barkServerLabel', group: 'channels' },
|
|
924
|
+
{ key: 'barkKey', labelKey: 'settings.barkKeyLabel', group: 'channels' },
|
|
925
|
+
{ key: 'pushplusUrl', labelKey: 'settings.pushplusUrlLabel', group: 'channels' },
|
|
926
|
+
{ key: 'ttsBaseUrl', labelKey: 'settings.ttsBaseUrlLabel', group: 'channels' },
|
|
927
|
+
{ key: 'giteaBaseUrl', labelKey: 'settings.giteaBaseUrlLabel', group: 'channels' },
|
|
928
|
+
{ key: 'giteaRepo', labelKey: 'settings.giteaRepoLabel', group: 'channels' },
|
|
929
|
+
{ key: 'deliveryTimeoutMs', labelKey: 'settings.deliveryTimeoutMsLabel', group: 'channels', numeric: true, defaultValue: 15000, min: 1000 },
|
|
930
|
+
{ key: 'silentRuleModel', labelKey: 'settings.silentRuleModelLabel', group: 'automation' },
|
|
931
|
+
{ key: 'inspectorModel', labelKey: 'settings.inspectorModelLabel', group: 'automation' },
|
|
932
|
+
];
|
|
933
|
+
const DELIVERY_SETTING_KEYS = DELIVERY_SETTING_FIELDS.map((f) => f.key);
|
|
934
|
+
// The global template plus the per-channel override map (server model).
|
|
935
|
+
const TEMPLATE_SETTING_KEYS = ['template', 'channelTemplates'];
|
|
936
|
+
|
|
937
|
+
/** Collect every delivery key the settings endpoints may return. */
|
|
938
|
+
function parseEnvText(text) {
|
|
939
|
+
const env = {};
|
|
940
|
+
for (const rawLine of String(text || '').split('\n')) {
|
|
941
|
+
const line = rawLine.trim();
|
|
942
|
+
if (!line || line.startsWith('#')) continue;
|
|
943
|
+
const idx = line.indexOf('=');
|
|
944
|
+
if (idx <= 0) continue;
|
|
945
|
+
const key = line.slice(0, idx).trim();
|
|
946
|
+
const value = line.slice(idx + 1).trim();
|
|
947
|
+
if (key) env[key] = value;
|
|
948
|
+
}
|
|
949
|
+
return Object.keys(env).length ? env : undefined;
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
function formatEnvText(env) {
|
|
953
|
+
if (!env || typeof env !== 'object') return '';
|
|
954
|
+
return Object.entries(env).map(([k, v]) => `${k}=${v}`).join('\n');
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
// Status colors: core semantic state tokens first (same set dsh-clinebot
|
|
958
|
+
// uses), plugin fallbacks as a single point of change (#105).
|
|
1029
959
|
function createToggle() {
|
|
1030
960
|
let open = false;
|
|
1031
961
|
const listeners = new Set();
|
|
@@ -1113,6 +1043,102 @@ window.__ModuleLoader__.load({
|
|
|
1113
1043
|
return (typeof t === 'function' ? t : translate)(key, vars);
|
|
1114
1044
|
}
|
|
1115
1045
|
|
|
1046
|
+
function pickDeliverySettings(source) {
|
|
1047
|
+
const out = {};
|
|
1048
|
+
for (const key of DELIVERY_SETTING_KEYS.concat(TEMPLATE_SETTING_KEYS)) {
|
|
1049
|
+
if (source && source[key] !== undefined && source[key] !== null) out[key] = source[key];
|
|
1050
|
+
}
|
|
1051
|
+
if (out.channelTemplates && typeof out.channelTemplates === 'object') {
|
|
1052
|
+
out.channelTemplates = Object.assign({}, out.channelTemplates);
|
|
1053
|
+
}
|
|
1054
|
+
return out;
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
/**
|
|
1058
|
+
* Shared delivery form used by both the task panel modal and the plugin
|
|
1059
|
+
* settings card, so a new channel is added in exactly one place.
|
|
1060
|
+
* Sections collapse via the canonical aria-expanded head contract.
|
|
1061
|
+
*/
|
|
1062
|
+
function renderDeliverySettings({ T, values, onChange, onChannelTemplate, sections, onToggleSection }) {
|
|
1063
|
+
const val = (key) => (values[key] === undefined || values[key] === null ? '' : String(values[key]));
|
|
1064
|
+
const field = (f) => React.createElement('div', { className: 'dsh-cron-form-group', key: f.key, style: { marginBottom: '10px' } },
|
|
1065
|
+
React.createElement('label', null, T(f.labelKey)),
|
|
1066
|
+
React.createElement('input', f.numeric ? {
|
|
1067
|
+
type: 'number',
|
|
1068
|
+
min: f.min !== undefined ? f.min : 1000,
|
|
1069
|
+
step: 1000,
|
|
1070
|
+
value: val(f.key),
|
|
1071
|
+
placeholder: String(f.defaultValue !== undefined ? f.defaultValue : 15000),
|
|
1072
|
+
// #115: clearing the field must send the explicit default. Deleting the
|
|
1073
|
+
// key would leave the previously stored value in place (the settings
|
|
1074
|
+
// scope skips absent keys), so the field would silently keep 1 ms.
|
|
1075
|
+
onChange: (e) => onChange(f.key, e.target.value === ''
|
|
1076
|
+
? (f.defaultValue !== undefined ? f.defaultValue : 15000)
|
|
1077
|
+
: Number(e.target.value)),
|
|
1078
|
+
} : {
|
|
1079
|
+
type: 'text',
|
|
1080
|
+
value: val(f.key),
|
|
1081
|
+
placeholder: f.placeholderKey ? T(f.placeholderKey) : '',
|
|
1082
|
+
onChange: (e) => onChange(f.key, e.target.value)
|
|
1083
|
+
})
|
|
1084
|
+
);
|
|
1085
|
+
const section = (key, titleKey, children) => {
|
|
1086
|
+
const open = Boolean(sections[key]);
|
|
1087
|
+
return React.createElement('div', { className: 'dsh-cron-section', key },
|
|
1088
|
+
React.createElement('button', {
|
|
1089
|
+
type: 'button',
|
|
1090
|
+
className: 'dsh-cron-section-head',
|
|
1091
|
+
'aria-expanded': open ? 'true' : 'false',
|
|
1092
|
+
onClick: () => onToggleSection(key)
|
|
1093
|
+
},
|
|
1094
|
+
React.createElement('span', null, T(titleKey)),
|
|
1095
|
+
React.createElement('span', { className: 'dsh-cron-section-chevron', 'data-open': open ? 'true' : 'false' }, '▾')
|
|
1096
|
+
),
|
|
1097
|
+
open && React.createElement('div', { className: 'dsh-cron-section-body' }, children)
|
|
1098
|
+
);
|
|
1099
|
+
};
|
|
1100
|
+
|
|
1101
|
+
return [
|
|
1102
|
+
section('secrets', 'settings.secretsSection', [
|
|
1103
|
+
React.createElement('div', { className: 'dsh-cron-hint', key: 'hint', style: { marginBottom: '10px' } }, T('settings.secretsHint')),
|
|
1104
|
+
...DELIVERY_SETTING_FIELDS.filter((f) => f.group === 'secrets').map(field),
|
|
1105
|
+
]),
|
|
1106
|
+
section('channels', 'settings.deliverySection', [
|
|
1107
|
+
React.createElement('div', { className: 'dsh-cron-form-row', key: 'row' },
|
|
1108
|
+
...DELIVERY_SETTING_FIELDS.filter((f) => f.group === 'channels').map(field)
|
|
1109
|
+
),
|
|
1110
|
+
]),
|
|
1111
|
+
section('automation', 'settings.automationSection', [
|
|
1112
|
+
React.createElement('div', { className: 'dsh-cron-hint', key: 'hint', style: { marginBottom: '10px' } }, T('settings.automationHint')),
|
|
1113
|
+
React.createElement('div', { className: 'dsh-cron-form-row', key: 'row' },
|
|
1114
|
+
...DELIVERY_SETTING_FIELDS.filter((f) => f.group === 'automation').map(field)
|
|
1115
|
+
),
|
|
1116
|
+
]),
|
|
1117
|
+
section('templates', 'settings.templatesSection', [
|
|
1118
|
+
React.createElement('div', { className: 'dsh-cron-form-group', key: 'global', style: { marginBottom: '10px' } },
|
|
1119
|
+
React.createElement('label', null, T('settings.templateLabel')),
|
|
1120
|
+
React.createElement('input', {
|
|
1121
|
+
type: 'text',
|
|
1122
|
+
value: val('template'),
|
|
1123
|
+
onChange: (e) => onChange('template', e.target.value)
|
|
1124
|
+
}),
|
|
1125
|
+
React.createElement('div', { className: 'dsh-cron-hint' }, T('settings.templateHint'))
|
|
1126
|
+
),
|
|
1127
|
+
React.createElement('div', { className: 'dsh-cron-form-row', key: 'perchannel' },
|
|
1128
|
+
...DELIVERY_CHANNELS.map((id) => React.createElement('div', { className: 'dsh-cron-form-group', key: id, style: { marginBottom: '10px' } },
|
|
1129
|
+
React.createElement('label', null, T('settings.channelTemplateLabel', { channel: T(CHANNEL_LABEL_KEYS[id]) })),
|
|
1130
|
+
React.createElement('input', {
|
|
1131
|
+
type: 'text',
|
|
1132
|
+
value: (values.channelTemplates && values.channelTemplates[id]) || '',
|
|
1133
|
+
onChange: (e) => onChannelTemplate(id, e.target.value)
|
|
1134
|
+
})
|
|
1135
|
+
))
|
|
1136
|
+
),
|
|
1137
|
+
]),
|
|
1138
|
+
];
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
/** Parse a KEY=VALUE textarea into the task env object (#38). */
|
|
1116
1142
|
function CronScreen(props) {
|
|
1117
1143
|
const { ctx, toggle, onClose } = props;
|
|
1118
1144
|
const t = props.t || translate;
|
|
@@ -2217,11 +2243,11 @@ window.__ModuleLoader__.load({
|
|
|
2217
2243
|
title: T_KEY(t, 'list.configManagedHint')
|
|
2218
2244
|
}, T_KEY(t, 'list.configManaged')) : null,
|
|
2219
2245
|
task.lastDurationMs > 0 ? React.createElement('span', { className: 'dsh-cron-cost-tag', title: 'Last execution duration' }, task.lastDurationMs + 'ms') : null,
|
|
2220
|
-
task.onSuccess ? React.createElement('span', { className: 'dsh-cron-type-tag', title: 'On success: triggers ' + task.onSuccess
|
|
2221
|
-
task.onFailure ? React.createElement('span', { className: 'dsh-cron-type-tag', title: 'On failure: triggers ' + task.onFailure
|
|
2222
|
-
task.heartbeatIntervalSeconds > 0 ? React.createElement('span', { className: 'dsh-cron-type-tag', title: 'Heartbeat: every ' + task.heartbeatIntervalSeconds + 's'
|
|
2223
|
-
task.targetSessionId ? React.createElement('span', { className: 'dsh-cron-type-tag', title: 'Target session: ' + task.targetSessionId + (task.targetSessionReset && task.targetSessionReset !== 'never' ? ' (' + task.targetSessionReset + ')' : '')
|
|
2224
|
-
task.preflightType && task.preflightType !== 'none' ? React.createElement('span', { className: 'dsh-cron-type-tag', title: 'Preflight: ' + task.preflightType
|
|
2246
|
+
task.onSuccess ? React.createElement('span', { className: 'dsh-cron-type-tag dsh-cron-tag-success', title: 'On success: triggers ' + task.onSuccess }, '➜ ' + task.onSuccess) : null,
|
|
2247
|
+
task.onFailure ? React.createElement('span', { className: 'dsh-cron-type-tag dsh-cron-tag-failure', title: 'On failure: triggers ' + task.onFailure }, '↳ ' + task.onFailure) : null,
|
|
2248
|
+
task.heartbeatIntervalSeconds > 0 ? React.createElement('span', { className: 'dsh-cron-type-tag dsh-cron-tag-heartbeat', title: 'Heartbeat: every ' + task.heartbeatIntervalSeconds + 's' }, '💓 ' + task.heartbeatIntervalSeconds + 's') : null,
|
|
2249
|
+
task.targetSessionId ? React.createElement('span', { className: 'dsh-cron-type-tag dsh-cron-tag-session', title: 'Target session: ' + task.targetSessionId + (task.targetSessionReset && task.targetSessionReset !== 'never' ? ' (' + task.targetSessionReset + ')' : '') }, '🧵 ' + task.targetSessionId) : null,
|
|
2250
|
+
task.preflightType && task.preflightType !== 'none' ? React.createElement('span', { className: 'dsh-cron-type-tag dsh-cron-tag-preflight', title: 'Preflight: ' + task.preflightType }, '🛡️ ' + task.preflightType) : null,
|
|
2225
2251
|
task.totalCostUsd > 0 ? React.createElement('span', { className: 'dsh-cron-cost-tag' }, '$' + task.totalCostUsd.toFixed(4)) : null,
|
|
2226
2252
|
task.totalTokens > 0 ? React.createElement('span', { className: 'dsh-cron-tokens-tag' }, (task.totalTokens > 1000 ? Math.round(task.totalTokens / 1000) + 'k' : task.totalTokens) + ' tok') : null
|
|
2227
2253
|
),
|
|
@@ -3320,7 +3346,7 @@ window.__ModuleLoader__.load({
|
|
|
3320
3346
|
const [updateState, setUpdateState] = React.useState({
|
|
3321
3347
|
checking: false,
|
|
3322
3348
|
updating: false,
|
|
3323
|
-
currentVersion: '0.2.
|
|
3349
|
+
currentVersion: '0.2.18',
|
|
3324
3350
|
latestVersion: undefined,
|
|
3325
3351
|
updateAvailable: false,
|
|
3326
3352
|
notice: null,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodandready/dsh-cron",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.18",
|
|
4
4
|
"description": "Scheduled cron tasks, background automation and agent execution for DeepSeek Harness.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -11,13 +11,15 @@
|
|
|
11
11
|
"./package.json": "./package.json"
|
|
12
12
|
},
|
|
13
13
|
"files": [
|
|
14
|
-
"lib
|
|
14
|
+
"lib/*.js",
|
|
15
15
|
"cordis.patch.yml",
|
|
16
16
|
"README.md",
|
|
17
17
|
"LICENSE"
|
|
18
18
|
],
|
|
19
19
|
"scripts": {
|
|
20
|
-
"test": "node --test test/*.test.mjs"
|
|
20
|
+
"test": "node --test test/*.test.mjs",
|
|
21
|
+
"build:client": "node scripts/build-client.mjs",
|
|
22
|
+
"pretest": "node scripts/build-client.mjs"
|
|
21
23
|
},
|
|
22
24
|
"keywords": [
|
|
23
25
|
"dsh",
|