clementine-agent 1.18.159 → 1.18.160

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.
@@ -21,7 +21,11 @@ import { discoverMcpServers, getClaudeIntegrations, KNOWN_MCP_DESCRIPTIONS } fro
21
21
  import { buildBuilderEnrichedMessage, builderSessionKey } from '../dashboard/builder/prompt.js';
22
22
  import { AGENTS_DIR, MEMORY_FILE, SESSIONS_FILE, applyOneMillionContextRecovery, looksLikeClaudeOneMillionContextError, normalizeClaudeSdkOptionsForOneMillionContext, } from '../config.js';
23
23
  import { parseTasks } from '../tools/shared.js';
24
- import { todayISO, CronRunLog } from '../gateway/cron-scheduler.js';
24
+ // 1.18.160 also pull parseCronJobs + parseAgentCronJobs so getCronJobs()
25
+ // returns the same merged set the runtime fires (CRON.md + agent CRON +
26
+ // schedule registry). Was reading only CRON.md before, hiding migrated
27
+ // scheduled-skills from the Tasks tab while they kept firing on schedule.
28
+ import { todayISO, CronRunLog, parseCronJobs, parseAgentCronJobs } from '../gateway/cron-scheduler.js';
25
29
  import { goalsRouter } from './routes/goals.js';
26
30
  import { delegationsRouter } from './routes/delegations.js';
27
31
  import { workflowsRouter } from './routes/workflows.js';
@@ -1270,43 +1274,29 @@ function getSessions() {
1270
1274
  }
1271
1275
  }
1272
1276
  function getCronJobs() {
1273
- let jobs = [];
1274
- // Load main CRON.md
1275
- if (existsSync(CRON_FILE)) {
1276
- try {
1277
- const raw = readFileSync(CRON_FILE, 'utf-8');
1278
- const parsed = matter(raw);
1279
- const mainJobs = (parsed.data.jobs ?? []);
1280
- jobs.push(...mainJobs);
1281
- }
1282
- catch { /* ignore */ }
1283
- }
1284
- // Load agent-scoped CRON.md files
1285
- const agentsDir = path.join(VAULT_DIR, '00-System', 'agents');
1286
- if (existsSync(agentsDir)) {
1287
- try {
1288
- for (const slug of readdirSync(agentsDir)) {
1289
- const agentCronFile = path.join(agentsDir, slug, 'CRON.md');
1290
- if (!existsSync(agentCronFile))
1291
- continue;
1292
- try {
1293
- const raw = readFileSync(agentCronFile, 'utf-8');
1294
- const parsed = matter(raw);
1295
- const agentJobs = (parsed.data.jobs ?? []);
1296
- for (const job of agentJobs) {
1297
- jobs.push({ ...job, agent: slug, name: `${slug}:${job.name}` });
1298
- }
1299
- }
1300
- catch { /* ignore individual agent parse errors */ }
1301
- }
1302
- }
1303
- catch { /* ignore */ }
1304
- }
1277
+ // 1.18.160 delegate to the canonical merger so scheduled-skill rows
1278
+ // (from ~/.clementine/schedules.json) reach the dashboard alongside
1279
+ // legacy CRON.md entries. Before this, getCronJobs() only read CRON.md
1280
+ // — so when a user migrated 14 of their 15 crons to scheduled-skills,
1281
+ // the Tasks page silently dropped to 1 card while the runtime kept
1282
+ // firing all 22 jobs. The result LOOKED like a regression because the
1283
+ // user couldn't see, edit, pause, or trace any of the migrated work
1284
+ // from the Tasks tab. parseCronJobs() reads BOTH CRON.md + agent CRON
1285
+ // files + the schedule registry, dedups by name (scheduled-skill wins
1286
+ // collisions), and stamps `source` so the existing card renderer can
1287
+ // branch on SKILL vs LEGACY CRON badge.
1288
+ //
1289
+ // The dashboard now sees exactly what the runtime fires — single
1290
+ // source of truth, no drift. Both helpers are imported at the top.
1291
+ const allJobs = [
1292
+ ...parseCronJobs(),
1293
+ ...parseAgentCronJobs(path.join(VAULT_DIR, '00-System', 'agents')),
1294
+ ];
1305
1295
  // Attach recent run history. Single source of truth via CronRunLog.readRecent
1306
1296
  // — same path the new /api/cron/runs cross-job endpoint uses, so per-card
1307
1297
  // last-run and the Recent History zone never disagree.
1308
1298
  const log = new CronRunLog();
1309
- const enriched = jobs.map((job) => {
1299
+ const enriched = allJobs.map((job) => {
1310
1300
  const name = String(job.name ?? '');
1311
1301
  const recentRuns = log.readRecent(name, 10);
1312
1302
  return { ...job, recentRuns };
@@ -27344,15 +27334,29 @@ async function refreshCron() {
27344
27334
  // (definition.source !== 'scheduled-skill') and surfaces a one-click
27345
27335
  // bulk migrator. Dismissable; persists in localStorage so it doesn't
27346
27336
  // nag on every refresh.
27337
+ // 1.18.160 — only nag for UNHEALTHY legacy crons. After the user
27338
+ // bulk-migrates 14 healthy ones, having a banner shout about the
27339
+ // 1 healthy survivor felt naggy. The user can still migrate healthy
27340
+ // ones at their leisure via per-row "→ Skill" buttons; the banner
27341
+ // earns the screen real estate only when there's a real problem.
27347
27342
  var legacyCount = 0;
27343
+ var legacyUnhealthyCount = 0;
27348
27344
  try {
27349
- legacyCount = (visibleTasks || []).filter(function(t) {
27345
+ var legacyRows = (visibleTasks || []).filter(function(t) {
27350
27346
  return !(t.definition && t.definition.source === 'scheduled-skill');
27347
+ });
27348
+ legacyCount = legacyRows.length;
27349
+ legacyUnhealthyCount = legacyRows.filter(function(t) {
27350
+ var h = t.health;
27351
+ return h === 'failed' || h === 'broken' || h === 'never_run';
27351
27352
  }).length;
27352
27353
  } catch (_) { /* defensive */ }
27353
27354
  var bannerHtml = '';
27354
27355
  var dismissed = localStorage.getItem('clem-skill-migrate-banner-dismissed') === '1';
27355
- if (legacyCount > 0 && !dismissed) {
27356
+ // Show the banner only when there's an unhealthy legacy cron to nudge
27357
+ // the user about. Healthy legacy crons live quietly until the user
27358
+ // chooses to migrate them per-row.
27359
+ if (legacyUnhealthyCount > 0 && !dismissed) {
27356
27360
  // 1.18.155 — data-banner-kind tags this as the legacy-cron soft-
27357
27361
  // deprecation banner so refreshCronMigrateBanner can suppress its
27358
27362
  // secondary "clean up preambles" banner when this one is showing
@@ -27360,8 +27364,8 @@ async function refreshCron() {
27360
27364
  bannerHtml = '<div data-banner-kind="legacy-cron-soft-deprecation" style="background:rgba(124,58,237,0.08);border:1px solid var(--purple);border-radius:8px;padding:12px 14px;margin-bottom:14px;display:flex;align-items:center;gap:12px;flex-wrap:wrap">'
27361
27365
  + '<span style="font-size:18px">⚡</span>'
27362
27366
  + '<div style="flex:1;min-width:200px">'
27363
- + '<div style="font-size:13px;font-weight:500;color:var(--text-primary)">' + legacyCount + ' legacy cron task' + (legacyCount === 1 ? '' : 's') + ' can become scheduled skills</div>'
27364
- + '<div style="font-size:11px;color:var(--text-muted);margin-top:2px">Skills are the Anthropic-pure unit of work. Scheduled-skill format is thinner, easier to maintain, and tasks can be reused on demand.</div>'
27367
+ + '<div style="font-size:13px;font-weight:500;color:var(--text-primary)">' + legacyUnhealthyCount + ' legacy cron task' + (legacyUnhealthyCount === 1 ? '' : 's') + ' failing migrating to a scheduled skill often clears the issue</div>'
27368
+ + '<div style="font-size:11px;color:var(--text-muted);margin-top:2px">Scheduled skills use a tighter context envelope (lean mode for meta-jobs) and the Anthropic-canonical SKILL.md format. Healthy legacy crons stay quietly out of the way.</div>'
27365
27369
  + '</div>'
27366
27370
  + '<button class="btn-sm btn-primary" onclick="migrateAllCronsToSkills()" style="font-size:12px;padding:6px 12px">Migrate all eligible →</button>'
27367
27371
  + '<button class="btn-sm" onclick="localStorage.setItem(\\x27clem-skill-migrate-banner-dismissed\\x27,\\x271\\x27);refreshCron()" title="Hide this banner" style="font-size:12px;padding:6px 10px">Dismiss</button>'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clementine-agent",
3
- "version": "1.18.159",
3
+ "version": "1.18.160",
4
4
  "description": "Clementine — Personal AI Assistant (TypeScript)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",