@goodandready/dsh-cron 0.2.29 → 0.2.30

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/lib/index.js CHANGED
@@ -1,3 +1,5 @@
1
+ import fs from 'node:fs';
2
+ import { setLogger, logger } from './logger.js';
1
3
  import { registerPluginUpdater } from './updater.js';
2
4
  import { credentialRefStatus } from './secrets.js';
3
5
  import { TaskStore } from './store.js';
@@ -110,10 +112,11 @@ export {
110
112
  LEGACY_TOOL_NAMES,
111
113
  LEGACY_ACTION_MAP,
112
114
  };
113
- export const createTaskParameters = cronToolParameters;
114
- export const createTaskOutput = cronToolOutput;
115
+ const pkgUrl = new URL('../package.json', import.meta.url);
116
+ const pkgVersion = JSON.parse(fs.readFileSync(pkgUrl, 'utf8')).version || '0.2.29';
115
117
 
116
118
  export function apply(ctx, rawConfig) {
119
+ if (ctx && ctx.logger) setLogger(ctx.logger);
117
120
  const config = unwrapConfig(rawConfig);
118
121
  const store = new TaskStore();
119
122
 
@@ -172,9 +175,9 @@ export function apply(ctx, rawConfig) {
172
175
  try {
173
176
  const configJobs = Array.isArray(config && config.jobs) ? config.jobs : [];
174
177
  const summary = applyConfigSync({ store, scheduler, entries: configJobs });
175
- console.log('[dsh-cron] config jobs synced: ' + JSON.stringify(summary));
178
+ logger.info('[dsh-cron] config jobs synced: ' + JSON.stringify(summary));
176
179
  } catch (err) {
177
- console.error('[dsh-cron] config job sync failed:', err.message);
180
+ logger.error('[dsh-cron] config job sync failed:', err.message);
178
181
  }
179
182
 
180
183
  scheduler.start();
@@ -186,7 +189,7 @@ export function apply(ctx, rawConfig) {
186
189
  const intervalMs = Number(config.heartbeatIntervalSec) * 1000;
187
190
  heartbeatTimer = setInterval(() => {
188
191
  fetch(config.heartbeatUrl, { method: 'GET' }).catch((err) => {
189
- console.warn('[dsh-cron] heartbeat ping failed:', err.message);
192
+ logger.warn('[dsh-cron] heartbeat ping failed:', err.message);
190
193
  });
191
194
  }, intervalMs);
192
195
  }
@@ -224,7 +227,7 @@ export function apply(ctx, rawConfig) {
224
227
  handler: async (req, res) => {
225
228
  try {
226
229
  if (req.method === 'GET') {
227
- sendJson(res, 200, { ok: true, settings: store.getClientSettings() });
230
+ sendJson(res, 200, { ok: true, settings: { ...store.getClientSettings(), version: pkgVersion } });
228
231
  return;
229
232
  }
230
233
  if (req.method === 'POST') {
@@ -236,7 +239,7 @@ export function apply(ctx, rawConfig) {
236
239
  const scopeErrors = await applySettingsToScope(settingsService, payload);
237
240
  sendJson(res, 200, {
238
241
  ok: true,
239
- settings: store.getClientSettings(),
242
+ settings: { ...store.getClientSettings(), version: pkgVersion },
240
243
  scopeErrors: (scopeErrors && scopeErrors.length > 0) ? scopeErrors : undefined
241
244
  });
242
245
  return;
@@ -1,3 +1,4 @@
1
+ import { logger } from './logger.js';
1
2
  /**
2
3
  * Integration module for dsh-kanban and LLM token cost estimation.
3
4
  */
@@ -77,7 +78,7 @@ export async function createKanbanCard({
77
78
  const data = await res.json().catch(() => ({}));
78
79
  return { success: true, task: data.task };
79
80
  } catch (err) {
80
- console.error('[dsh-cron] failed to create kanban card:', err.message);
81
+ logger.error('[dsh-cron] failed to create kanban card:', err.message);
81
82
  return { success: false, error: err.message };
82
83
  }
83
84
  }
@@ -1,3 +1,4 @@
1
+ import { logger } from './logger.js';
1
2
  import { bestEffort } from './best-effort.js';
2
3
  import fs from 'node:fs';
3
4
 
@@ -103,7 +104,7 @@ export async function executeLlmActionDirectives({ directives, task, runInfo, sc
103
104
  results.push({ type, status: giteaRes ? 'created' : 'skipped', details: giteaRes });
104
105
  }
105
106
  } catch (err) {
106
- console.warn(`[dsh-cron] action directive ${type} execution failed:`, err.message);
107
+ logger.warn(`[dsh-cron] action directive ${type} execution failed:`, err.message);
107
108
  results.push({ type, status: 'error', error: err.message });
108
109
  }
109
110
  }
package/lib/logger.js ADDED
@@ -0,0 +1,38 @@
1
+ // Unified logging wrapper for dsh-cron (#204)
2
+ // Routes logs through ctx.logger when running inside DSH/Cordis,
3
+ // and safely falls back to console in tests or standalone runs.
4
+
5
+ let currentLogger = console;
6
+
7
+ export function setLogger(logger) {
8
+ if (logger && typeof logger === 'object') {
9
+ currentLogger = logger;
10
+ }
11
+ }
12
+
13
+ function getLogger() {
14
+ return currentLogger;
15
+ }
16
+
17
+ export const logger = {
18
+ info(...args) {
19
+ const fn = currentLogger.info || currentLogger.log || console.log;
20
+ return fn.call(currentLogger, ...args);
21
+ },
22
+ warn(...args) {
23
+ const fn = currentLogger.warn || currentLogger.log || console.warn;
24
+ return fn.call(currentLogger, ...args);
25
+ },
26
+ error(...args) {
27
+ const fn = currentLogger.error || currentLogger.log || console.error;
28
+ return fn.call(currentLogger, ...args);
29
+ },
30
+ debug(...args) {
31
+ const fn = currentLogger.debug || currentLogger.log || console.debug;
32
+ return fn.call(currentLogger, ...args);
33
+ },
34
+ log(...args) {
35
+ const fn = currentLogger.info || currentLogger.log || console.log;
36
+ return fn.call(currentLogger, ...args);
37
+ },
38
+ };
@@ -1,4 +1,8 @@
1
1
  export async function getModelsHandler(ctx, req, res, sendJson) {
2
+ if ((req.method || 'GET') !== 'GET') {
3
+ sendJson(res, 405, { ok: false, error: 'Method not allowed' });
4
+ return;
5
+ }
2
6
  const current = ctx.get('agentDefaultModel')?.currentSelection?.() ?? null;
3
7
  let providers = [];
4
8
  try {
package/lib/routes.js CHANGED
@@ -89,7 +89,7 @@ export function registerRoutes(ctx, { store, scheduler, getCronSettingsScope, re
89
89
  path: '/dsh-cron/recipes',
90
90
  handler: async (req, res) => {
91
91
  try {
92
- if (req.method !== 'GET') {
92
+ if ((req.method || 'GET') !== 'GET') {
93
93
  sendJson(res, 405, { ok: false, error: 'Method not allowed' });
94
94
  return;
95
95
  }
@@ -108,6 +108,10 @@ export function registerRoutes(ctx, { store, scheduler, getCronSettingsScope, re
108
108
  path: '/dsh-cron/heartbeat',
109
109
  handler: async (req, res) => {
110
110
  try {
111
+ if ((req.method || 'GET') !== 'GET') {
112
+ sendJson(res, 405, { ok: false, error: 'Method not allowed' });
113
+ return;
114
+ }
111
115
  const tasks = store.list({ status: 'active' });
112
116
  const lastRuns = tasks.map((t) => t.lastRunAt || 0);
113
117
  sendJson(res, 200, {
@@ -1,3 +1,4 @@
1
+ import { logger } from './logger.js';
1
2
  import { bestEffort } from './best-effort.js';
2
3
  import {
3
4
  buildInvocation,
@@ -74,7 +75,7 @@ export async function runHttp(task, request, signal) {
74
75
  if (!res.ok) {
75
76
  const err = new Error(`${summary}\n${String(text).slice(0, 500)}`);
76
77
  if (isTransientError(res.status) && attempts < maxAttempts && !signal?.aborted) {
77
- console.warn(`[dsh-cron] HTTP request returned ${res.status}, retrying attempt ${attempts + 1}/${maxAttempts}...`);
78
+ logger.warn(`[dsh-cron] HTTP request returned ${res.status}, retrying attempt ${attempts + 1}/${maxAttempts}...`);
78
79
  await new Promise((r) => setTimeout(r, attempts * 1000));
79
80
  continue;
80
81
  }
@@ -87,7 +88,7 @@ export async function runHttp(task, request, signal) {
87
88
  };
88
89
  } catch (err) {
89
90
  if (isTransientError(err) && attempts < maxAttempts && !signal?.aborted) {
90
- console.warn(`[dsh-cron] HTTP request failed (${err.message}), retrying attempt ${attempts + 1}/${maxAttempts}...`);
91
+ logger.warn(`[dsh-cron] HTTP request failed (${err.message}), retrying attempt ${attempts + 1}/${maxAttempts}...`);
91
92
  await new Promise((r) => setTimeout(r, attempts * 1000));
92
93
  continue;
93
94
  }
@@ -1,3 +1,4 @@
1
+ import { logger } from './logger.js';
1
2
  import { bestEffort } from './best-effort.js';
2
3
 
3
4
  /**
@@ -46,7 +47,7 @@ export async function removeTaskWorktree(cwd, wtPath) {
46
47
  await new Promise((resolve) => {
47
48
  execFile('git', ['-C', cwd, 'worktree', 'remove', wtPath], { maxBuffer: 4 * 1024 * 1024 }, (err, stdout, stderr) => {
48
49
  if (err) {
49
- console.warn(`[dsh-cron] task worktree kept (not removable without force): ${wtPath} — ${String(stderr || err.message).trim()}`);
50
+ logger.warn(`[dsh-cron] task worktree kept (not removable without force): ${wtPath} — ${String(stderr || err.message).trim()}`);
50
51
  }
51
52
  execFile('git', ['-C', cwd, 'worktree', 'prune'], () => resolve());
52
53
  });
package/lib/runner.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { logger } from './logger.js';
1
2
  import { bestEffort } from './best-effort.js';
2
3
  import { estimateTokenCost } from './integrations.js';
3
4
  import {
@@ -141,7 +142,7 @@ export class SessionRunner {
141
142
  }
142
143
 
143
144
  async _run(task, signal, options = {}) {
144
- console.log(`[dsh-cron] executing task "${task.title}" (${task.id})`);
145
+ logger.info(`[dsh-cron] executing task "${task.title}" (${task.id})`);
145
146
 
146
147
  // 1. Non-LLM runtimes: shell, Node.js, Python, HTTP, SSH, Docker (#4 #5 #7 #8 #9)
147
148
  const runtime = normalizeTaskType(task.type);
@@ -177,7 +178,7 @@ export class SessionRunner {
177
178
  return { output: out, usage, costUsd: estimateTokenCost(task.model, usage) };
178
179
  }
179
180
  } catch (err) {
180
- console.error(`[dsh-cron] session execution failed:`, err.message);
181
+ logger.error(`[dsh-cron] session execution failed:`, err.message);
181
182
  throw err;
182
183
  }
183
184
  return this._runBare(task);
@@ -207,7 +208,7 @@ export class SessionRunner {
207
208
  } catch (err) {
208
209
  // Context window overflow auto-recovery in persistent sessions (#145)
209
210
  if (prep.targetSessionId && isContextOverflowError(err) && !signal?.aborted) {
210
- console.warn(`[dsh-cron] Persistent session "${prep.targetSessionId}" exceeded context window (${err.message}). Auto-recovering with fresh session...`);
211
+ logger.warn(`[dsh-cron] Persistent session "${prep.targetSessionId}" exceeded context window (${err.message}). Auto-recovering with fresh session...`);
211
212
  bestEffort('archive-rotated-session', () => {
212
213
  const sessions = this.ctx?.get?.('sessions');
213
214
  if (sessions && typeof sessions.archive === 'function') {
@@ -215,7 +216,7 @@ export class SessionRunner {
215
216
  }
216
217
  });
217
218
  const rotatedSessionId = `${prep.targetSessionId}-${Date.now().toString(36)}`;
218
- console.log(`[dsh-cron] Rotated persistent session "${prep.targetSessionId}" -> "${rotatedSessionId}"`);
219
+ logger.info(`[dsh-cron] Rotated persistent session "${prep.targetSessionId}" -> "${rotatedSessionId}"`);
219
220
  if (this.store) {
220
221
  bestEffort('update-stored-rotated-session', () => {
221
222
  const stored = this.store.get(task.id);
@@ -230,7 +231,7 @@ export class SessionRunner {
230
231
  const freshPrep = await this._prepareAgentRun(effectiveTask, runtime, signal);
231
232
  return await this._executeAgentTurn(task, freshPrep);
232
233
  }
233
- console.error(`[dsh-cron] agent execution failed:`, err.message);
234
+ logger.error(`[dsh-cron] agent execution failed:`, err.message);
234
235
  throw err;
235
236
  }
236
237
  }
@@ -306,7 +307,7 @@ export class SessionRunner {
306
307
  taskWorktree = await this.createTaskWorktree(baseCwd, task);
307
308
  cwd = taskWorktree;
308
309
  } catch (wtErr) {
309
- console.warn('[dsh-cron] task worktree not created, running in the workspace:', wtErr.message);
310
+ logger.warn('[dsh-cron] task worktree not created, running in the workspace:', wtErr.message);
310
311
  }
311
312
  }
312
313
  return { baseCwd, cwd, taskWorktree };
@@ -349,7 +350,7 @@ export class SessionRunner {
349
350
  await presets.standingKeyFor(preset.id);
350
351
  }
351
352
  } catch (presetErr) {
352
- console.warn('[dsh-cron] agent preset unavailable, running without one:', presetErr && presetErr.message);
353
+ logger.warn('[dsh-cron] agent preset unavailable, running without one:', presetErr && presetErr.message);
353
354
  preset = null;
354
355
  }
355
356
  }
@@ -375,9 +376,9 @@ export class SessionRunner {
375
376
  ...presetSetup,
376
377
  });
377
378
  isResumed = true;
378
- console.log(`[dsh-cron] resumed existing persistent session: ${prep.targetSessionId}`);
379
+ logger.info(`[dsh-cron] resumed existing persistent session: ${prep.targetSessionId}`);
379
380
  } catch (resumeErr) {
380
- console.log(`[dsh-cron] could not resume session ${prep.targetSessionId} (${resumeErr.message}), creating new persistent session`);
381
+ logger.info(`[dsh-cron] could not resume session ${prep.targetSessionId} (${resumeErr.message}), creating new persistent session`);
381
382
  handle = null;
382
383
  }
383
384
  }
@@ -431,7 +432,7 @@ export class SessionRunner {
431
432
  presets.set(handle.agent.session, task.permissionPreset);
432
433
  }
433
434
  } catch (ppErr) {
434
- console.warn('[dsh-cron] permission preset not applied:', ppErr.message);
435
+ logger.warn('[dsh-cron] permission preset not applied:', ppErr.message);
435
436
  }
436
437
  }
437
438
 
@@ -462,7 +463,7 @@ export class SessionRunner {
462
463
  await handle.agent.destroy();
463
464
  }
464
465
  } catch (disposeErr) {
465
- console.warn('[dsh-cron] session dispose notice:', disposeErr.message);
466
+ logger.warn('[dsh-cron] session dispose notice:', disposeErr.message);
466
467
  }
467
468
  // Best-effort: keep ephemeral cron sessions out of the main chat list (#30).
468
469
  // Persistent sessions (targetSessionId configured) stay unarchived so users can interact with them (#143).
@@ -1,3 +1,4 @@
1
+ import { logger } from './logger.js';
1
2
  import { checkAndApplyBurnGuard, checkTaskBudgetLimits } from './burn-guard.js';
2
3
  import { resolveTaskForExecution } from './prompt-interpolation.js';
3
4
  import { bestEffort } from './best-effort.js';
@@ -126,7 +127,7 @@ export async function executeWithFallback(scheduler, task, signal, options = {})
126
127
  provider: task.fallbackProvider || task.provider,
127
128
  model: task.fallbackModel,
128
129
  };
129
- console.log(`[dsh-cron] task "${task.title}" failed on ${task.model || 'the default model'}, retrying once on ${task.fallbackModel}`);
130
+ logger.info(`[dsh-cron] task "${task.title}" failed on ${task.model || 'the default model'}, retrying once on ${task.fallbackModel}`);
130
131
  const secondary = await scheduler.executeOnce(fallbackTask, signal, options);
131
132
  return {
132
133
  ...secondary,
@@ -143,12 +144,12 @@ export async function executeWithFallback(scheduler, task, signal, options = {})
143
144
  export function beginRun(scheduler, task, taskId) {
144
145
  if (!scheduler.running.has(taskId) && scheduler.maxConcurrent > 0 && scheduler.running.size >= scheduler.maxConcurrent) {
145
146
  if (task.overlapPolicy === 'queue') {
146
- console.log(`[dsh-cron] Task "${task.title}" (${taskId}) queued by concurrency limit (${scheduler.maxConcurrent})`);
147
+ logger.info(`[dsh-cron] Task "${task.title}" (${taskId}) queued by concurrency limit (${scheduler.maxConcurrent})`);
147
148
  scheduler.queue.push({ taskId, priority: task.priority !== undefined ? task.priority : 5, queuedAt: Date.now() });
148
149
  scheduler.queue.sort((a, b) => (a.priority - b.priority) || (a.queuedAt - b.queuedAt));
149
150
  return null;
150
151
  }
151
- console.log(`[dsh-cron] Task "${task.title}" (${taskId}) skipped: concurrency limit (${scheduler.maxConcurrent})`);
152
+ logger.info(`[dsh-cron] Task "${task.title}" (${taskId}) skipped: concurrency limit (${scheduler.maxConcurrent})`);
152
153
  scheduler.recordSkipped(taskId, `Skipped: concurrency limit reached (${scheduler.maxConcurrent} parallel runs)`);
153
154
  return null;
154
155
  }
@@ -157,16 +158,16 @@ export function beginRun(scheduler, task, taskId) {
157
158
  const active = scheduler.running.get(taskId);
158
159
  if (active) {
159
160
  if (overlapPolicy === 'skip') {
160
- console.log(`[dsh-cron] Task "${task.title}" (${taskId}) is already running, skipping run (overlapPolicy: skip)`);
161
+ logger.info(`[dsh-cron] Task "${task.title}" (${taskId}) is already running, skipping run (overlapPolicy: skip)`);
161
162
  scheduler.recordSkipped(taskId, 'Skipped: the previous run is still in progress (overlapPolicy: skip)');
162
163
  return null;
163
164
  }
164
165
  if (overlapPolicy === 'replace') {
165
- console.log(`[dsh-cron] Task "${task.title}" (${taskId}) is already running, canceling current run (overlapPolicy: replace)`);
166
+ logger.info(`[dsh-cron] Task "${task.title}" (${taskId}) is already running, canceling current run (overlapPolicy: replace)`);
166
167
  active.controller.abort(new Error('Aborted by overlap policy: replace'));
167
168
  }
168
169
  if (overlapPolicy === 'queue') {
169
- console.log(`[dsh-cron] Task "${task.title}" (${taskId}) is already running, queueing next run (overlapPolicy: queue)`);
170
+ logger.info(`[dsh-cron] Task "${task.title}" (${taskId}) is already running, queueing next run (overlapPolicy: queue)`);
170
171
  active.queueCount = (active.queueCount || 0) + 1;
171
172
  return null;
172
173
  }
@@ -189,7 +190,7 @@ export async function executeTask(scheduler, taskId, options = {}) {
189
190
  const runs = (scheduler.store.history && scheduler.store.history.get(taskId)) || [];
190
191
  const guard = checkTaskBudgetLimits(task, runs);
191
192
  if (guard.exceeded) {
192
- console.warn(`[dsh-cron] Burn Guard pre-flight blocked task "${task.title}" (${taskId}): ${guard.reason}`);
193
+ logger.warn(`[dsh-cron] Burn Guard pre-flight blocked task "${task.title}" (${taskId}): ${guard.reason}`);
193
194
  scheduler.pauseTask(taskId, guard.reason);
194
195
  scheduler.recordSkipped(taskId, `Burn Guard pre-flight pause: ${guard.reason}`);
195
196
  checkAndApplyBurnGuard(scheduler, taskId, { status: 'error', costUsd: 0, durationMs: 0 }).catch(() => {});
@@ -222,7 +223,7 @@ export async function executeTask(scheduler, taskId, options = {}) {
222
223
 
223
224
  const preflight = await executePreflight(task);
224
225
  if (!preflight.ok) {
225
- console.log(`[dsh-cron] task "${task.title}" (${taskId}) skipped: ${preflight.reason}`);
226
+ logger.info(`[dsh-cron] task "${task.title}" (${taskId}) skipped: ${preflight.reason}`);
226
227
  scheduler.running.delete(taskId);
227
228
  scheduler.recordSkipped(taskId, `Preflight check skipped: ${preflight.reason}`);
228
229
  return null;
@@ -282,18 +283,18 @@ export async function applyModelAssists(task, runInfo, options) {
282
283
  runInfo.diagnosis = inspection.diagnosis;
283
284
  runInfo.suggestion = inspection.suggestion;
284
285
  runInfo.confidence = inspection.confidence;
285
- console.log(`[dsh-cron] task "${task.title}" failure diagnosed (${inspection.confidence}): ${inspection.diagnosis}`);
286
+ logger.info(`[dsh-cron] task "${task.title}" failure diagnosed (${inspection.confidence}): ${inspection.diagnosis}`);
286
287
  } else if (inspection.error && task.inspectOnFailure) {
287
- console.warn(`[dsh-cron] failure inspection for "${task.title}" not applied: ${inspection.error}`);
288
+ logger.warn(`[dsh-cron] failure inspection for "${task.title}" not applied: ${inspection.error}`);
288
289
  }
289
290
 
290
291
  const silent = await applySilentRuleToRun(task, runInfo, options);
291
292
  if (silent.skipped) {
292
293
  runInfo.silentSkip = true;
293
294
  runInfo.silentReason = silent.reason;
294
- console.log(`[dsh-cron] task "${task.title}" stayed silent by rule: ${silent.reason || 'no reason given'}`);
295
+ logger.info(`[dsh-cron] task "${task.title}" stayed silent by rule: ${silent.reason || 'no reason given'}`);
295
296
  } else if (silent.verdictError) {
296
- console.warn(`[dsh-cron] silent rule for "${task.title}" not applied: ${silent.verdictError}`);
297
+ logger.warn(`[dsh-cron] silent rule for "${task.title}" not applied: ${silent.verdictError}`);
297
298
  }
298
299
  return silent;
299
300
  }
@@ -308,19 +309,19 @@ export async function deliverNotifications(task, runInfo, { store, resolveSecret
308
309
  try {
309
310
  settings = { ...settings, ...(await resolveSecrets(settings)) };
310
311
  } catch (secErr) {
311
- console.warn('[dsh-cron] credential resolution failed:', secErr.message);
312
+ logger.warn('[dsh-cron] credential resolution failed:', secErr.message);
312
313
  }
313
314
  }
314
315
  if (typeof deliver !== 'function') return;
315
316
  const result = await deliver(task, runInfo, settings);
316
317
  if (result && Array.isArray(result.delivered) && result.delivered.length) {
317
- console.log(`[dsh-cron] delivered "${task.title}" to: ${result.delivered.map((d) => d.channel).join(', ')}`);
318
+ logger.info(`[dsh-cron] delivered "${task.title}" to: ${result.delivered.map((d) => d.channel).join(', ')}`);
318
319
  }
319
320
  if (result && Array.isArray(result.failures) && result.failures.length) {
320
- console.warn(`[dsh-cron] delivery failures for "${task.title}": ${result.failures.map((f) => `${f.channel}: ${f.error}`).join('; ')}`);
321
+ logger.warn(`[dsh-cron] delivery failures for "${task.title}": ${result.failures.map((f) => `${f.channel}: ${f.error}`).join('; ')}`);
321
322
  }
322
323
  } catch (notifyErr) {
323
- console.error('[dsh-cron] notification error:', notifyErr.message);
324
+ logger.error('[dsh-cron] notification error:', notifyErr.message);
324
325
  }
325
326
  }
326
327
 
@@ -343,7 +344,7 @@ export function finishRun(scheduler, task, taskId, status, queuedCount) {
343
344
  }
344
345
  }
345
346
  } catch (err) {
346
- console.error('[dsh-cron] failed to persist the next run of task', taskId + ':', err.message);
347
+ logger.error('[dsh-cron] failed to persist the next run of task', taskId + ':', err.message);
347
348
  }
348
349
 
349
350
  try {
@@ -359,7 +360,7 @@ export function finishRun(scheduler, task, taskId, status, queuedCount) {
359
360
  scheduler.retryTimers.delete(taskId);
360
361
  scheduler.runTask(taskId, { isRetry: true });
361
362
  }, backoffMs));
362
- console.log(`[dsh-cron] retry ${attempt}/${maxRetries} for task "${task.title}" in ${backoffMs}ms`);
363
+ logger.info(`[dsh-cron] retry ${attempt}/${maxRetries} for task "${task.title}" in ${backoffMs}ms`);
363
364
  } else if (!failed && task.attempts) {
364
365
  task.attempts = 0;
365
366
  scheduler.store.set(task);
@@ -368,7 +369,7 @@ export function finishRun(scheduler, task, taskId, status, queuedCount) {
368
369
  scheduler.store.set(task);
369
370
  }
370
371
  } catch (err) {
371
- console.error('[dsh-cron] retry bookkeeping failed for task', taskId + ':', err.message);
372
+ logger.error('[dsh-cron] retry bookkeeping failed for task', taskId + ':', err.message);
372
373
  }
373
374
 
374
375
  if (queuedCount > 0) {
@@ -398,7 +399,7 @@ export async function handleCompleteRun(scheduler, task, taskId, currentRun, out
398
399
  };
399
400
 
400
401
  if (isFailure && task.selfHealingCommand) {
401
- console.log(`[dsh-cron] running self-healing command for task "${task.title}": ${task.selfHealingCommand}`);
402
+ logger.info(`[dsh-cron] running self-healing command for task "${task.title}": ${task.selfHealingCommand}`);
402
403
  try {
403
404
  await new Promise((resolve) => {
404
405
  exec(task.selfHealingCommand, { timeout: 30000 }, (shErr, stdout, stderr) => {
@@ -435,7 +436,7 @@ export async function handleCompleteRun(scheduler, task, taskId, currentRun, out
435
436
  try {
436
437
  scheduler.store.recordRun(taskId, runInfo);
437
438
  } catch (recordErr) {
438
- console.error(`[dsh-cron] failed to record the run of task ${taskId}:`, recordErr.message);
439
+ logger.error(`[dsh-cron] failed to record the run of task ${taskId}:`, recordErr.message);
439
440
  }
440
441
 
441
442
  const finishedRun = scheduler.running.get(taskId);
@@ -480,7 +481,7 @@ export async function handleCompleteRun(scheduler, task, taskId, currentRun, out
480
481
  }
481
482
  }
482
483
  } catch (actErr) {
483
- console.warn(`[dsh-cron] structured action error for ${taskId}:`, actErr.message);
484
+ logger.warn(`[dsh-cron] structured action error for ${taskId}:`, actErr.message);
484
485
  }
485
486
  }
486
487
 
@@ -492,7 +493,7 @@ export async function handleCompleteRun(scheduler, task, taskId, currentRun, out
492
493
  const targetId = nextTaskId.trim();
493
494
  const nextTask = scheduler.store.get(targetId);
494
495
  if (nextTask) {
495
- console.log(`[dsh-cron] triggering chained task "${nextTask.title}" (${targetId}) from "${task.title}" (depth ${currentDepth + 1})`);
496
+ logger.info(`[dsh-cron] triggering chained task "${nextTask.title}" (${targetId}) from "${task.title}" (depth ${currentDepth + 1})`);
496
497
  scheduler.runNow(targetId, {
497
498
  chainDepth: currentDepth + 1,
498
499
  prevOutput: outcome.output || outcome.error || '',
@@ -501,13 +502,13 @@ export async function handleCompleteRun(scheduler, task, taskId, currentRun, out
501
502
  prevDurationMs: Date.now() - start,
502
503
  prevCostUsd: outcome.costUsd || 0,
503
504
  }).catch((err) => {
504
- console.warn(`[dsh-cron] chained task ${targetId} failed to trigger:`, err.message);
505
+ logger.warn(`[dsh-cron] chained task ${targetId} failed to trigger:`, err.message);
505
506
  });
506
507
  } else {
507
- console.warn(`[dsh-cron] chained task target not found: ${targetId}`);
508
+ logger.warn(`[dsh-cron] chained task target not found: ${targetId}`);
508
509
  }
509
510
  } else {
510
- console.warn(`[dsh-cron] chained task depth limit (5) reached for task ${task.id} -> ${nextTaskId}. Loop prevented.`);
511
+ logger.warn(`[dsh-cron] chained task depth limit (5) reached for task ${task.id} -> ${nextTaskId}. Loop prevented.`);
511
512
  }
512
513
  }
513
514
  }
@@ -1,3 +1,4 @@
1
+ import { logger } from './logger.js';
1
2
  import { bestEffort } from './best-effort.js';
2
3
 
3
4
  /**
@@ -41,7 +42,7 @@ export async function checkHeartbeats(scheduler) {
41
42
  task.updatedAt = now;
42
43
  scheduler.store.save();
43
44
 
44
- console.warn(`[dsh-cron] Heartbeat missed for "${task.title}" (${task.id})!`);
45
+ logger.warn(`[dsh-cron] Heartbeat missed for "${task.title}" (${task.id})!`);
45
46
  const runInfo = {
46
47
  at: now,
47
48
  status: 'missed',
@@ -62,7 +63,7 @@ export async function checkHeartbeats(scheduler) {
62
63
  chainDepth: 1,
63
64
  prevOutput: runInfo.output,
64
65
  prevTaskId: task.id,
65
- }).catch((err) => console.warn('[dsh-cron] heartbeat onFailure trigger error:', err.message));
66
+ }).catch((err) => logger.warn('[dsh-cron] heartbeat onFailure trigger error:', err.message));
66
67
  }
67
68
  }
68
69
  }
package/lib/scheduler.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { logger } from './logger.js';
1
2
  import { bestEffort } from './best-effort.js';
2
3
  import { Cron } from 'croner';
3
4
  import {
@@ -297,7 +298,7 @@ export class TaskScheduler {
297
298
  if (missed) {
298
299
  const missedAt = task.nextRunAt;
299
300
  const policy = task.misfirePolicy || 'skip';
300
- console.warn(`[dsh-cron] Task "${task.title}" (${task.id}) missed scheduled run at ${new Date(missedAt).toISOString()} (misfire: ${policy})`);
301
+ logger.warn(`[dsh-cron] Task "${task.title}" (${task.id}) missed scheduled run at ${new Date(missedAt).toISOString()} (misfire: ${policy})`);
301
302
  this.countRun('missed');
302
303
  this.store.recordRun(task.id, {
303
304
  at: missedAt,
@@ -369,7 +370,7 @@ export class TaskScheduler {
369
370
  const timer = setTimeout(() => {
370
371
  this.timers.delete(task.id);
371
372
  this.runTask(task.id).catch((err) => {
372
- console.error(`[dsh-cron] one-shot run of task ${task.id} failed:`, (err && err.message) || err);
373
+ logger.error(`[dsh-cron] one-shot run of task ${task.id} failed:`, (err && err.message) || err);
373
374
  });
374
375
  }, delay);
375
376
  this.timers.set(task.id, timer);
@@ -380,7 +381,7 @@ export class TaskScheduler {
380
381
  const job = new Cron(parsed.cronPattern, {
381
382
  protect: true,
382
383
  sloppyRanges: true,
383
- catch: (err) => console.error(`[dsh-cron] scheduled run of "${task.title}" (${task.id}) failed:`, (err && err.message) || err),
384
+ catch: (err) => logger.error(`[dsh-cron] scheduled run of "${task.title}" (${task.id}) failed:`, (err && err.message) || err),
384
385
  ...(timezone ? { timezone } : {}),
385
386
  }, async () => {
386
387
  await this.runTask(task.id);
@@ -407,7 +408,7 @@ export class TaskScheduler {
407
408
  }
408
409
  this.scheduleCron(task, parsed);
409
410
  } catch (err) {
410
- console.error(`[dsh-cron] failed to schedule task ${task.id}:`, err.message);
411
+ logger.error(`[dsh-cron] failed to schedule task ${task.id}:`, err.message);
411
412
  }
412
413
  }
413
414
 
package/lib/settings.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { logger } from './logger.js';
1
2
  import z from '@deepseek-ai/schemastery';
2
3
  import { credentialRefStatus } from './secrets.js';
3
4
  import { TaskStore } from './store.js';
@@ -147,14 +148,14 @@ export function sanitizeSettingsPayload(body) {
147
148
  if (key in payload && typeof payload[key] === 'string' && payload[key].trim()) {
148
149
  const refCheck = credentialRefStatus(payload[key]);
149
150
  if (!refCheck.ok) {
150
- console.warn(`[dsh-cron] invalid credential reference in "${key}": ${refCheck.error}`);
151
+ logger.warn(`[dsh-cron] invalid credential reference in "${key}": ${refCheck.error}`);
151
152
  delete payload[key];
152
153
  }
153
154
  }
154
155
  }
155
156
  for (const key of TaskStore.FORBIDDEN_SETTING_KEYS) {
156
157
  if (key in payload) {
157
- console.warn(`[dsh-cron] refusing raw secret "${key}" from the client — use a credential reference`);
158
+ logger.warn(`[dsh-cron] refusing raw secret "${key}" from the client — use a credential reference`);
158
159
  delete payload[key];
159
160
  }
160
161
  }
package/lib/store.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { logger } from './logger.js';
1
2
  import { bestEffort } from './best-effort.js';
2
3
  import fs from 'node:fs';
3
4
  import path from 'node:path';
@@ -74,21 +75,21 @@ export class TaskStore {
74
75
  bestEffort('backup-initial', () => fs.copyFileSync(this.filePath, bakPath));
75
76
  }
76
77
  } catch (err) {
77
- console.error('[dsh-cron] store read error (corrupted file):', err.message);
78
+ logger.error('[dsh-cron] store read error (corrupted file):', err.message);
78
79
  try {
79
80
  const corruptedPath = `${this.filePath}.corrupted.${Date.now()}`;
80
81
  fs.copyFileSync(this.filePath, corruptedPath);
81
- console.warn(`[dsh-cron] corrupted store backed up to ${corruptedPath}`);
82
+ logger.warn(`[dsh-cron] corrupted store backed up to ${corruptedPath}`);
82
83
  } catch (snapErr) {
83
- console.error('[dsh-cron] failed to snapshot corrupted store:', snapErr.message);
84
+ logger.error('[dsh-cron] failed to snapshot corrupted store:', snapErr.message);
84
85
  }
85
86
  if (fs.existsSync(bakPath)) {
86
87
  try {
87
88
  const rawBak = fs.readFileSync(bakPath, 'utf-8');
88
89
  loadData(rawBak);
89
- console.warn(`[dsh-cron] successfully recovered tasks store from ${bakPath}`);
90
+ logger.warn(`[dsh-cron] successfully recovered tasks store from ${bakPath}`);
90
91
  } catch (bakErr) {
91
- console.error('[dsh-cron] failed to recover tasks store from .bak:', bakErr.message);
92
+ logger.error('[dsh-cron] failed to recover tasks store from .bak:', bakErr.message);
92
93
  }
93
94
  }
94
95
  }
@@ -97,9 +98,9 @@ export class TaskStore {
97
98
  const rawBak = fs.readFileSync(bakPath, 'utf-8');
98
99
  loadData(rawBak);
99
100
  bestEffort('restore-from-bak', () => fs.copyFileSync(bakPath, this.filePath));
100
- console.warn(`[dsh-cron] restored missing tasks store from ${bakPath}`);
101
+ logger.warn(`[dsh-cron] restored missing tasks store from ${bakPath}`);
101
102
  } catch (bakErr) {
102
- console.error('[dsh-cron] failed to restore tasks store from .bak:', bakErr.message);
103
+ logger.error('[dsh-cron] failed to restore tasks store from .bak:', bakErr.message);
103
104
  }
104
105
  }
105
106
  }
@@ -158,7 +159,7 @@ export class TaskStore {
158
159
  bestEffort('backup-on-save', () => fs.copyFileSync(this.filePath, `${this.filePath}.bak`));
159
160
  }
160
161
  } catch (err) {
161
- console.error('[dsh-cron] store save error:', err.message);
162
+ logger.error('[dsh-cron] store save error:', err.message);
162
163
  }
163
164
  }
164
165
 
@@ -222,7 +223,7 @@ export class TaskStore {
222
223
 
223
224
  for (const key of TaskStore.FORBIDDEN_SETTING_KEYS) {
224
225
  if (key in patch) {
225
- console.warn(`[dsh-cron] refusing to store raw secret "${key}" in settings — use a credential reference instead`);
226
+ logger.warn(`[dsh-cron] refusing to store raw secret "${key}" in settings — use a credential reference instead`);
226
227
  delete patch[key];
227
228
  }
228
229
  }
@@ -441,7 +442,7 @@ export class TaskStore {
441
442
  fs.writeFileSync(tmp, JSON.stringify(archive), 'utf-8');
442
443
  fs.renameSync(tmp, archivePath);
443
444
  } catch (archiveErr) {
444
- console.error('[dsh-cron] history archive error:', archiveErr.message);
445
+ logger.error('[dsh-cron] history archive error:', archiveErr.message);
445
446
  }
446
447
  }
447
448