@goodandready/dsh-cron 0.2.16 → 0.2.17
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 +7 -0
- package/README.ru.md +7 -0
- package/README.zh.md +7 -0
- package/lib/client.js +222 -216
- package/package.json +5 -3
package/README.md
CHANGED
|
@@ -392,6 +392,13 @@ Developer-facing, no behaviour change. `parseScheduleExpression` was split into
|
|
|
392
392
|
|
|
393
393
|
|
|
394
394
|
|
|
395
|
+
### 29. Client Modular Decomposition & Theme Standardization (v0.2.17, #149, #150)
|
|
396
|
+
- **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
|
+
- **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.
|
|
398
|
+
- **Label Governance & Triage Audit (#95)**: Standardized 100% of repository issues and triage on the canonical repo-level label set.
|
|
399
|
+
|
|
400
|
+
---
|
|
401
|
+
|
|
395
402
|
### 28. In-App One-Click Auto-Updater & Error Resilience (v0.2.16, #147, #155, #156)
|
|
396
403
|
- **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
404
|
- **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,13 @@ cron_create_task({
|
|
|
268
268
|
|
|
269
269
|
|
|
270
270
|
|
|
271
|
+
### 29. Модульная декомпозиция клиента и стандартизация тем оформления (v0.2.17, #149, #150)
|
|
272
|
+
- **Модульная архитектура клиентской части (#150)**: Монолитный файл `lib/client.js` (~3950 строк) декомпозирован на 14 независимых специализированных модулей в каталоге `lib/client-src/` (каждый строго <= 580 строк). Сборка `lib/client.js` выполняется легковесным скриптом `scripts/build-client.mjs` перед запуском тестов и сборки. Исходные фрагменты исключены из npm-дистрибутива через `"files": ["lib/*.js", ...]`.
|
|
273
|
+
- **Семантические токены тем 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.
|
|
274
|
+
- **Аудит меток репозитория (#95)**: Подтверждена 100% консистентность использования канонического репозиторного набора меток и фильтрации задач.
|
|
275
|
+
|
|
276
|
+
---
|
|
277
|
+
|
|
271
278
|
### 28. Модуль самообновления в 1 клик и устойчивость к ошибкам (v0.2.16, #147, #155, #156)
|
|
272
279
|
- **Самообновление плагина (#147)**: Добавлен встроенный модуль обновления (`lib/updater.js`) с маршрутом `/api/dsh-cron/update` и карточкой в настройках Web UI. Автоматически опрашивает npm registry, корректно сравнивает semver-версии (включая пререлизы) и обновляет `@goodandready/dsh-cron` на лету через CLI DSH без необходимости входа по SSH. Запросы POST защищены проверкой источника (`rejectCrossOrigin`).
|
|
273
280
|
- **Устранение скрытых сбоев (#156)**: Ликвидированы пустые `catch`: незавершённые откаты транзакций импорта логируются с уровнем `warn`, причины fallback для динамических модулей ядра фиксируются в отладочном журнале, а при сбое открытия сессии пользователь получает понятное уведомление.
|
package/README.zh.md
CHANGED
|
@@ -391,6 +391,13 @@ bash deploy.sh verify [exact-version]
|
|
|
391
391
|
|
|
392
392
|
|
|
393
393
|
|
|
394
|
+
### 29. 客户端模块化解耦与原生主题标准化 (v0.2.17, #149, #150)
|
|
395
|
+
- **客户端模块化架构 (#150)**: 将庞大的单文件 `lib/client.js` (约 3950 行) 拆分为 `lib/client-src/` 下的 14 个高内聚模块文件 (各模块严格 <= 580 行)。集成零依赖构建脚本 `scripts/build-client.mjs` 并接入 `package.json` (`build:client`, `pretest`),通过 `"files": ["lib/*.js", ...]` 避免开发源码冗余打包进 npm 发布包。
|
|
396
|
+
- **DSH 语义化主题变量对齐 (#149)**: 将任务类型标签 (`onSuccess`, `onFailure`, `heartbeat`, `targetSession`, `preflight`) 的内联样式全部替换为基于主题变量的 `.dsh-cron-tag-*` 类;模态框遮罩层接入自适应主题遮罩变量 `var(--dsw-alias-bg-mask, rgba(0, 0, 0, 0.75))`,移除脉冲动画关键帧中的硬编码 RGBA。
|
|
397
|
+
- **标签治理与工单审计 (#95)**: 审计并确认全仓库 100% 统一规范使用仓库级标签集。
|
|
398
|
+
|
|
399
|
+
---
|
|
400
|
+
|
|
394
401
|
### 28. 应用内一键自更新与错误容错增强 (v0.2.16, #147, #155, #156)
|
|
395
402
|
- **插件自更新模块 (#147)**:新增应用内一键自更新机制 (`lib/updater.js`)、`/api/dsh-cron/update` 接口与设置面板专属卡片。自动轮询 npmjs 仓库最新版本,精准比对 Semver 版本号(含预发布版本),通过 DSH CLI 就地升级 `@goodandready/dsh-cron`,无需 SSH 终端操作。POST 请求受跨域与来源保护 (`rejectCrossOrigin`)。
|
|
396
403
|
- **消除静默异常与错误追踪 (#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',
|
|
@@ -588,214 +599,6 @@ window.__ModuleLoader__.load({
|
|
|
588
599
|
};
|
|
589
600
|
const T = (key, vars) => translate(key, vars);
|
|
590
601
|
|
|
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
602
|
const STYLES = `
|
|
800
603
|
:root {
|
|
801
604
|
--dsh-cron-success: var(--dsw-alias-state-success-primary, #10b981);
|
|
@@ -865,6 +668,12 @@ window.__ModuleLoader__.load({
|
|
|
865
668
|
.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
669
|
.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
670
|
.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; }
|
|
671
|
+
.dsh-cron-tag-success { background: var(--dsh-cron-info-bg); color: var(--dsh-cron-info); border-color: var(--dsh-cron-info); }
|
|
672
|
+
.dsh-cron-tag-failure { background: var(--dsh-cron-danger-bg); color: var(--dsh-cron-danger); border-color: var(--dsh-cron-danger); }
|
|
673
|
+
.dsh-cron-tag-heartbeat { background: var(--dsh-cron-accent-bg); color: var(--dsh-cron-accent); border-color: var(--dsh-cron-accent); }
|
|
674
|
+
.dsh-cron-tag-session { background: var(--dsh-cron-info-bg); color: var(--dsh-cron-info); border-color: var(--dsh-cron-info); }
|
|
675
|
+
.dsh-cron-tag-preflight { background: var(--dsh-cron-warning-bg); color: var(--dsh-cron-warning); border-color: var(--dsh-cron-warning); }
|
|
676
|
+
|
|
868
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, rgba(255,255,255,0.06)); color: var(--dsw-alias-label-secondary, #aaa); margin-left: 8px; vertical-align: middle; }
|
|
869
678
|
.dsh-cron-task-actions { display: flex; gap: 8px; }
|
|
870
679
|
.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 +692,7 @@ window.__ModuleLoader__.load({
|
|
|
883
692
|
.dsh-cron-rec-time { font-size: 13px; color: var(--dsw-alias-label-secondary, #9ca3af); font-weight: normal; margin-left: 8px; }
|
|
884
693
|
.dsh-cron-rec-desc { font-size: 13px; color: var(--dsw-alias-label-tertiary, #71717a); }
|
|
885
694
|
|
|
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; }
|
|
695
|
+
.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
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 rgba(0,0,0,0.6); box-sizing: border-box; }
|
|
888
697
|
.dsh-cron-modal-scroll { max-width: 640px; max-height: min(90vh, calc(100vh - 36px)); overflow-y: auto; }
|
|
889
698
|
.dsh-cron-modal::-webkit-scrollbar { width: 6px; }
|
|
@@ -963,7 +772,7 @@ window.__ModuleLoader__.load({
|
|
|
963
772
|
.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
773
|
.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
774
|
.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
|
|
775
|
+
@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
776
|
.dsh-cron-pulse { animation: dsh-cron-pulse-ring 1.6s infinite; border-color: var(--dsh-cron-success); color: var(--dsh-cron-success); }
|
|
968
777
|
|
|
969
778
|
/* #34: sidebar job list */
|
|
@@ -1026,6 +835,107 @@ window.__ModuleLoader__.load({
|
|
|
1026
835
|
el.textContent = STYLES;
|
|
1027
836
|
}
|
|
1028
837
|
|
|
838
|
+
function createErrorBoundary() {
|
|
839
|
+
if (!React || typeof React.Component !== 'function') {
|
|
840
|
+
return function NoopBoundary(props) { return (props && props.children) || null; };
|
|
841
|
+
}
|
|
842
|
+
return class ErrorBoundary extends React.Component {
|
|
843
|
+
constructor(props) {
|
|
844
|
+
super(props);
|
|
845
|
+
this.state = { hasError: false, error: null };
|
|
846
|
+
}
|
|
847
|
+
static getDerivedStateFromError(error) {
|
|
848
|
+
return { hasError: true, error };
|
|
849
|
+
}
|
|
850
|
+
componentDidCatch(error, errorInfo) {
|
|
851
|
+
console.error('[dsh-cron] React error:', error, errorInfo);
|
|
852
|
+
}
|
|
853
|
+
render() {
|
|
854
|
+
if (this.state.hasError) {
|
|
855
|
+
return React.createElement('div', { className: 'dsh-cron-status-banner', 'data-type': 'error', style: { margin: '12px 0' } },
|
|
856
|
+
React.createElement('div', { style: { fontWeight: 600, marginBottom: '6px' } }, '⚠️ Cron UI error:'),
|
|
857
|
+
React.createElement('div', { style: { fontSize: '12px', wordBreak: 'break-all' } }, String((this.state.error && this.state.error.message) || this.state.error)),
|
|
858
|
+
React.createElement('button', {
|
|
859
|
+
type: 'button',
|
|
860
|
+
className: 'dsh-cron-btn-secondary',
|
|
861
|
+
style: { marginTop: '10px', fontSize: '12px', padding: '4px 10px' },
|
|
862
|
+
onClick: () => this.setState({ hasError: false, error: null })
|
|
863
|
+
}, 'Retry')
|
|
864
|
+
);
|
|
865
|
+
}
|
|
866
|
+
return (this.props && this.props.children) || null;
|
|
867
|
+
}
|
|
868
|
+
};
|
|
869
|
+
}
|
|
870
|
+
const ErrorBoundary = createErrorBoundary();
|
|
871
|
+
|
|
872
|
+
const AGENT_FORM_TYPES = ['llm', 'skill', 'workflow'];
|
|
873
|
+
const CODE_FORM_TYPES = ['script', 'node', 'python', 'ssh', 'docker'];
|
|
874
|
+
|
|
875
|
+
// Delivery channels (#26) — must stay in sync with lib/channels.js.
|
|
876
|
+
const DELIVERY_CHANNELS = ['telegram', 'kanban', 'discord', 'slack', 'ntfy', 'bark', 'pushplus', 'tts', 'gitea'];
|
|
877
|
+
const CHANNEL_LABEL_KEYS = {
|
|
878
|
+
telegram: 'form.channelTelegram',
|
|
879
|
+
kanban: 'form.channelKanban',
|
|
880
|
+
discord: 'form.channelDiscord',
|
|
881
|
+
slack: 'form.channelSlack',
|
|
882
|
+
ntfy: 'form.channelNtfy',
|
|
883
|
+
bark: 'form.channelBark',
|
|
884
|
+
pushplus: 'form.channelPushplus',
|
|
885
|
+
tts: 'form.channelTts',
|
|
886
|
+
gitea: 'form.channelGitea',
|
|
887
|
+
};
|
|
888
|
+
|
|
889
|
+
// Non-secret delivery settings shown in the settings panel. Secrets are
|
|
890
|
+
// referenced by credential name only (#51) — never typed as values.
|
|
891
|
+
const DELIVERY_SETTING_FIELDS = [
|
|
892
|
+
{ key: 'botTokenRef', labelKey: 'settings.botTokenRefLabel', placeholderKey: 'settings.botTokenRefPlaceholder', group: 'secrets' },
|
|
893
|
+
{ key: 'ntfyTokenRef', labelKey: 'settings.ntfyTokenRefLabel', group: 'secrets' },
|
|
894
|
+
{ key: 'pushplusTokenRef', labelKey: 'settings.pushplusTokenRefLabel', group: 'secrets' },
|
|
895
|
+
{ key: 'giteaTokenRef', labelKey: 'settings.giteaTokenRefLabel', group: 'secrets' },
|
|
896
|
+
// The external API is the one surface where a typed secret is the point:
|
|
897
|
+
// a CI job cannot present a credential reference (#54).
|
|
898
|
+
{ key: 'apiToken', labelKey: 'settings.apiTokenLabel', placeholderKey: 'settings.apiTokenPlaceholder', group: 'secrets' },
|
|
899
|
+
{ key: 'discordWebhookUrl', labelKey: 'settings.discordLabel', group: 'channels' },
|
|
900
|
+
{ key: 'slackWebhookUrl', labelKey: 'settings.slackLabel', group: 'channels' },
|
|
901
|
+
{ key: 'ntfyUrl', labelKey: 'settings.ntfyUrlLabel', group: 'channels' },
|
|
902
|
+
{ key: 'ntfyTopic', labelKey: 'settings.ntfyTopicLabel', group: 'channels' },
|
|
903
|
+
{ key: 'barkServerUrl', labelKey: 'settings.barkServerLabel', group: 'channels' },
|
|
904
|
+
{ key: 'barkKey', labelKey: 'settings.barkKeyLabel', group: 'channels' },
|
|
905
|
+
{ key: 'pushplusUrl', labelKey: 'settings.pushplusUrlLabel', group: 'channels' },
|
|
906
|
+
{ key: 'ttsBaseUrl', labelKey: 'settings.ttsBaseUrlLabel', group: 'channels' },
|
|
907
|
+
{ key: 'giteaBaseUrl', labelKey: 'settings.giteaBaseUrlLabel', group: 'channels' },
|
|
908
|
+
{ key: 'giteaRepo', labelKey: 'settings.giteaRepoLabel', group: 'channels' },
|
|
909
|
+
{ key: 'deliveryTimeoutMs', labelKey: 'settings.deliveryTimeoutMsLabel', group: 'channels', numeric: true, defaultValue: 15000, min: 1000 },
|
|
910
|
+
{ key: 'silentRuleModel', labelKey: 'settings.silentRuleModelLabel', group: 'automation' },
|
|
911
|
+
{ key: 'inspectorModel', labelKey: 'settings.inspectorModelLabel', group: 'automation' },
|
|
912
|
+
];
|
|
913
|
+
const DELIVERY_SETTING_KEYS = DELIVERY_SETTING_FIELDS.map((f) => f.key);
|
|
914
|
+
// The global template plus the per-channel override map (server model).
|
|
915
|
+
const TEMPLATE_SETTING_KEYS = ['template', 'channelTemplates'];
|
|
916
|
+
|
|
917
|
+
/** Collect every delivery key the settings endpoints may return. */
|
|
918
|
+
function parseEnvText(text) {
|
|
919
|
+
const env = {};
|
|
920
|
+
for (const rawLine of String(text || '').split('\n')) {
|
|
921
|
+
const line = rawLine.trim();
|
|
922
|
+
if (!line || line.startsWith('#')) continue;
|
|
923
|
+
const idx = line.indexOf('=');
|
|
924
|
+
if (idx <= 0) continue;
|
|
925
|
+
const key = line.slice(0, idx).trim();
|
|
926
|
+
const value = line.slice(idx + 1).trim();
|
|
927
|
+
if (key) env[key] = value;
|
|
928
|
+
}
|
|
929
|
+
return Object.keys(env).length ? env : undefined;
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
function formatEnvText(env) {
|
|
933
|
+
if (!env || typeof env !== 'object') return '';
|
|
934
|
+
return Object.entries(env).map(([k, v]) => `${k}=${v}`).join('\n');
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
// Status colors: core semantic state tokens first (same set dsh-clinebot
|
|
938
|
+
// uses), plugin fallbacks as a single point of change (#105).
|
|
1029
939
|
function createToggle() {
|
|
1030
940
|
let open = false;
|
|
1031
941
|
const listeners = new Set();
|
|
@@ -1113,6 +1023,102 @@ window.__ModuleLoader__.load({
|
|
|
1113
1023
|
return (typeof t === 'function' ? t : translate)(key, vars);
|
|
1114
1024
|
}
|
|
1115
1025
|
|
|
1026
|
+
function pickDeliverySettings(source) {
|
|
1027
|
+
const out = {};
|
|
1028
|
+
for (const key of DELIVERY_SETTING_KEYS.concat(TEMPLATE_SETTING_KEYS)) {
|
|
1029
|
+
if (source && source[key] !== undefined && source[key] !== null) out[key] = source[key];
|
|
1030
|
+
}
|
|
1031
|
+
if (out.channelTemplates && typeof out.channelTemplates === 'object') {
|
|
1032
|
+
out.channelTemplates = Object.assign({}, out.channelTemplates);
|
|
1033
|
+
}
|
|
1034
|
+
return out;
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
/**
|
|
1038
|
+
* Shared delivery form used by both the task panel modal and the plugin
|
|
1039
|
+
* settings card, so a new channel is added in exactly one place.
|
|
1040
|
+
* Sections collapse via the canonical aria-expanded head contract.
|
|
1041
|
+
*/
|
|
1042
|
+
function renderDeliverySettings({ T, values, onChange, onChannelTemplate, sections, onToggleSection }) {
|
|
1043
|
+
const val = (key) => (values[key] === undefined || values[key] === null ? '' : String(values[key]));
|
|
1044
|
+
const field = (f) => React.createElement('div', { className: 'dsh-cron-form-group', key: f.key, style: { marginBottom: '10px' } },
|
|
1045
|
+
React.createElement('label', null, T(f.labelKey)),
|
|
1046
|
+
React.createElement('input', f.numeric ? {
|
|
1047
|
+
type: 'number',
|
|
1048
|
+
min: f.min !== undefined ? f.min : 1000,
|
|
1049
|
+
step: 1000,
|
|
1050
|
+
value: val(f.key),
|
|
1051
|
+
placeholder: String(f.defaultValue !== undefined ? f.defaultValue : 15000),
|
|
1052
|
+
// #115: clearing the field must send the explicit default. Deleting the
|
|
1053
|
+
// key would leave the previously stored value in place (the settings
|
|
1054
|
+
// scope skips absent keys), so the field would silently keep 1 ms.
|
|
1055
|
+
onChange: (e) => onChange(f.key, e.target.value === ''
|
|
1056
|
+
? (f.defaultValue !== undefined ? f.defaultValue : 15000)
|
|
1057
|
+
: Number(e.target.value)),
|
|
1058
|
+
} : {
|
|
1059
|
+
type: 'text',
|
|
1060
|
+
value: val(f.key),
|
|
1061
|
+
placeholder: f.placeholderKey ? T(f.placeholderKey) : '',
|
|
1062
|
+
onChange: (e) => onChange(f.key, e.target.value)
|
|
1063
|
+
})
|
|
1064
|
+
);
|
|
1065
|
+
const section = (key, titleKey, children) => {
|
|
1066
|
+
const open = Boolean(sections[key]);
|
|
1067
|
+
return React.createElement('div', { className: 'dsh-cron-section', key },
|
|
1068
|
+
React.createElement('button', {
|
|
1069
|
+
type: 'button',
|
|
1070
|
+
className: 'dsh-cron-section-head',
|
|
1071
|
+
'aria-expanded': open ? 'true' : 'false',
|
|
1072
|
+
onClick: () => onToggleSection(key)
|
|
1073
|
+
},
|
|
1074
|
+
React.createElement('span', null, T(titleKey)),
|
|
1075
|
+
React.createElement('span', { className: 'dsh-cron-section-chevron', 'data-open': open ? 'true' : 'false' }, '▾')
|
|
1076
|
+
),
|
|
1077
|
+
open && React.createElement('div', { className: 'dsh-cron-section-body' }, children)
|
|
1078
|
+
);
|
|
1079
|
+
};
|
|
1080
|
+
|
|
1081
|
+
return [
|
|
1082
|
+
section('secrets', 'settings.secretsSection', [
|
|
1083
|
+
React.createElement('div', { className: 'dsh-cron-hint', key: 'hint', style: { marginBottom: '10px' } }, T('settings.secretsHint')),
|
|
1084
|
+
...DELIVERY_SETTING_FIELDS.filter((f) => f.group === 'secrets').map(field),
|
|
1085
|
+
]),
|
|
1086
|
+
section('channels', 'settings.deliverySection', [
|
|
1087
|
+
React.createElement('div', { className: 'dsh-cron-form-row', key: 'row' },
|
|
1088
|
+
...DELIVERY_SETTING_FIELDS.filter((f) => f.group === 'channels').map(field)
|
|
1089
|
+
),
|
|
1090
|
+
]),
|
|
1091
|
+
section('automation', 'settings.automationSection', [
|
|
1092
|
+
React.createElement('div', { className: 'dsh-cron-hint', key: 'hint', style: { marginBottom: '10px' } }, T('settings.automationHint')),
|
|
1093
|
+
React.createElement('div', { className: 'dsh-cron-form-row', key: 'row' },
|
|
1094
|
+
...DELIVERY_SETTING_FIELDS.filter((f) => f.group === 'automation').map(field)
|
|
1095
|
+
),
|
|
1096
|
+
]),
|
|
1097
|
+
section('templates', 'settings.templatesSection', [
|
|
1098
|
+
React.createElement('div', { className: 'dsh-cron-form-group', key: 'global', style: { marginBottom: '10px' } },
|
|
1099
|
+
React.createElement('label', null, T('settings.templateLabel')),
|
|
1100
|
+
React.createElement('input', {
|
|
1101
|
+
type: 'text',
|
|
1102
|
+
value: val('template'),
|
|
1103
|
+
onChange: (e) => onChange('template', e.target.value)
|
|
1104
|
+
}),
|
|
1105
|
+
React.createElement('div', { className: 'dsh-cron-hint' }, T('settings.templateHint'))
|
|
1106
|
+
),
|
|
1107
|
+
React.createElement('div', { className: 'dsh-cron-form-row', key: 'perchannel' },
|
|
1108
|
+
...DELIVERY_CHANNELS.map((id) => React.createElement('div', { className: 'dsh-cron-form-group', key: id, style: { marginBottom: '10px' } },
|
|
1109
|
+
React.createElement('label', null, T('settings.channelTemplateLabel', { channel: T(CHANNEL_LABEL_KEYS[id]) })),
|
|
1110
|
+
React.createElement('input', {
|
|
1111
|
+
type: 'text',
|
|
1112
|
+
value: (values.channelTemplates && values.channelTemplates[id]) || '',
|
|
1113
|
+
onChange: (e) => onChannelTemplate(id, e.target.value)
|
|
1114
|
+
})
|
|
1115
|
+
))
|
|
1116
|
+
),
|
|
1117
|
+
]),
|
|
1118
|
+
];
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
/** Parse a KEY=VALUE textarea into the task env object (#38). */
|
|
1116
1122
|
function CronScreen(props) {
|
|
1117
1123
|
const { ctx, toggle, onClose } = props;
|
|
1118
1124
|
const t = props.t || translate;
|
|
@@ -2217,11 +2223,11 @@ window.__ModuleLoader__.load({
|
|
|
2217
2223
|
title: T_KEY(t, 'list.configManagedHint')
|
|
2218
2224
|
}, T_KEY(t, 'list.configManaged')) : null,
|
|
2219
2225
|
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
|
|
2226
|
+
task.onSuccess ? React.createElement('span', { className: 'dsh-cron-type-tag dsh-cron-tag-success', title: 'On success: triggers ' + task.onSuccess }, '➜ ' + task.onSuccess) : null,
|
|
2227
|
+
task.onFailure ? React.createElement('span', { className: 'dsh-cron-type-tag dsh-cron-tag-failure', title: 'On failure: triggers ' + task.onFailure }, '↳ ' + task.onFailure) : null,
|
|
2228
|
+
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,
|
|
2229
|
+
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,
|
|
2230
|
+
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
2231
|
task.totalCostUsd > 0 ? React.createElement('span', { className: 'dsh-cron-cost-tag' }, '$' + task.totalCostUsd.toFixed(4)) : null,
|
|
2226
2232
|
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
2233
|
),
|
|
@@ -3320,7 +3326,7 @@ window.__ModuleLoader__.load({
|
|
|
3320
3326
|
const [updateState, setUpdateState] = React.useState({
|
|
3321
3327
|
checking: false,
|
|
3322
3328
|
updating: false,
|
|
3323
|
-
currentVersion: '0.2.
|
|
3329
|
+
currentVersion: '0.2.17',
|
|
3324
3330
|
latestVersion: undefined,
|
|
3325
3331
|
updateAvailable: false,
|
|
3326
3332
|
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.17",
|
|
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",
|