clementine-agent 1.18.131 → 1.18.132

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.
@@ -34,6 +34,14 @@ const slashCommands = [
34
34
  .addChoices({ name: 'List jobs', value: 'list' }, { name: 'Run a job', value: 'run' }, { name: 'Enable a job', value: 'enable' }, { name: 'Disable a job', value: 'disable' }))
35
35
  .addStringOption(o => o.setName('job').setDescription('Job name (for run/enable/disable)').setAutocomplete(true)),
36
36
  new SlashCommandBuilder().setName('heartbeat').setDescription('Run heartbeat check manually'),
37
+ // 1.18.132 — Phase 3: /skill is the on-demand sibling to scheduled
38
+ // skills. List the catalog or fire any skill once from anywhere
39
+ // Discord can reach. Uses cmdCronRun's catalog fallback (1.18.129)
40
+ // so unscheduled skills work too.
41
+ new SlashCommandBuilder().setName('skill').setDescription('List or run a skill on demand')
42
+ .addStringOption(o => o.setName('action').setDescription('Action').setRequired(true)
43
+ .addChoices({ name: 'List skills', value: 'list' }, { name: 'Run a skill', value: 'run' }))
44
+ .addStringOption(o => o.setName('name').setDescription('Skill name (for run)').setAutocomplete(true)),
37
45
  new SlashCommandBuilder().setName('tools').setDescription('List available MCP tools'),
38
46
  new SlashCommandBuilder().setName('toolset').setDescription('Set this chat tool mode')
39
47
  .addStringOption(o => o.setName('mode').setDescription('Tool mode').setRequired(true)
@@ -1190,6 +1198,23 @@ export async function startDiscord(gateway, heartbeat, cronScheduler, dispatcher
1190
1198
  .slice(0, 25);
1191
1199
  await interaction.respond(filtered.map(name => ({ name, value: name })));
1192
1200
  }
1201
+ else if (interaction.commandName === 'skill') {
1202
+ // 1.18.132 — autocomplete from the live skill catalog (every
1203
+ // skill, scheduled or not). cmdCronRun handles either case.
1204
+ const focused = interaction.options.getFocused().toLowerCase();
1205
+ try {
1206
+ const { listSkills } = await import('../agent/skill-store.js');
1207
+ const skills = listSkills();
1208
+ const filtered = skills
1209
+ .map(s => s.frontmatter.name)
1210
+ .filter(name => name.toLowerCase().includes(focused))
1211
+ .slice(0, 25);
1212
+ await interaction.respond(filtered.map(name => ({ name, value: name })));
1213
+ }
1214
+ catch {
1215
+ await interaction.respond([]);
1216
+ }
1217
+ }
1193
1218
  return;
1194
1219
  }
1195
1220
  // ── Slash commands ───────────────────────────────────────────────
@@ -1373,6 +1398,76 @@ export async function startDiscord(gateway, heartbeat, cronScheduler, dispatcher
1373
1398
  gateway.injectContext(sessionKey, `!cron run ${jobName}`, response);
1374
1399
  return;
1375
1400
  }
1401
+ // 1.18.132 — Phase 3: /skill list / /skill run <name>
1402
+ // List shows the full catalog (folder + flat) with descriptions.
1403
+ // Run fires the named skill via the same cronScheduler.runManual
1404
+ // path; cmdCronRun's catalog fallback (1.18.129) means unscheduled
1405
+ // skills still fire correctly. Output streams back to Discord.
1406
+ if (name === 'skill') {
1407
+ const action = cmd.options.getString('action', true);
1408
+ const skillName = cmd.options.getString('name') ?? '';
1409
+ if (action === 'list') {
1410
+ try {
1411
+ const { listSkills } = await import('../agent/skill-store.js');
1412
+ const skills = listSkills();
1413
+ if (skills.length === 0) {
1414
+ await cmd.reply('No skills found yet. Use the dashboard Skill Builder or `mcp__clementine-tools__create_skill` to author one.');
1415
+ return;
1416
+ }
1417
+ const lines = skills.map(s => {
1418
+ const fm = s.frontmatter;
1419
+ const ext = fm.clementine ?? {};
1420
+ const useCount = typeof ext.useCount === 'number' ? ext.useCount : 0;
1421
+ const desc = (fm.description || '').slice(0, 100) + ((fm.description || '').length > 100 ? '…' : '');
1422
+ return `• \`${fm.name}\` — ${desc}${useCount > 0 ? ` (${useCount}×)` : ''}`;
1423
+ });
1424
+ const header = `**${skills.length} skill${skills.length === 1 ? '' : 's'} available** — fire any with \`/skill run <name>\`:\n\n`;
1425
+ const chunks = chunkText(header + lines.join('\n'), 1900);
1426
+ await cmd.reply(chunks[0]);
1427
+ for (let i = 1; i < chunks.length; i++)
1428
+ await cmd.followUp(chunks[i]);
1429
+ }
1430
+ catch (err) {
1431
+ await cmd.reply(`Failed to list skills: ${err}`);
1432
+ }
1433
+ return;
1434
+ }
1435
+ if (action === 'run') {
1436
+ if (!skillName) {
1437
+ await cmd.reply('Specify a skill name. Try `/skill list` to see options.');
1438
+ return;
1439
+ }
1440
+ try {
1441
+ const { getSkill } = await import('../agent/skill-store.js');
1442
+ const skill = getSkill(skillName);
1443
+ if (!skill) {
1444
+ await cmd.reply(`Skill '${skillName}' not found. Try \`/skill list\`.`);
1445
+ return;
1446
+ }
1447
+ // Re-uses the cron run pipeline. cmdCronRun (1.18.129) falls
1448
+ // back to the skill catalog when the name isn't a registered
1449
+ // cron job, so this works for both scheduled + unscheduled
1450
+ // skills.
1451
+ if (cronScheduler.isJobRunning(skillName)) {
1452
+ await cmd.reply(`Skill '${skillName}' is already running.`);
1453
+ return;
1454
+ }
1455
+ await cmd.deferReply();
1456
+ const response = await cronScheduler.runManual(skillName);
1457
+ const chunks = chunkText(response || `*(skill '${skillName}' completed — no output)*`, 1900);
1458
+ await cmd.editReply(chunks[0]);
1459
+ for (let i = 1; i < chunks.length; i++)
1460
+ await cmd.followUp(chunks[i]);
1461
+ gateway.injectContext(sessionKey, `!skill run ${skillName}`, response);
1462
+ }
1463
+ catch (err) {
1464
+ await cmd.followUp({ content: `Skill run failed: ${err}` }).catch(() => { });
1465
+ }
1466
+ return;
1467
+ }
1468
+ await cmd.reply('Unknown action. Try `/skill list` or `/skill run <name>`.');
1469
+ return;
1470
+ }
1376
1471
  // Workflow command
1377
1472
  if (name === 'workflow') {
1378
1473
  const action = cmd.options.getString('action', true);
@@ -5066,6 +5066,189 @@ export async function cmdDashboard(opts) {
5066
5066
  res.status(500).json({ ok: false, error: String(err) });
5067
5067
  }
5068
5068
  });
5069
+ // 1.18.132 — Phase 3: convert a legacy cron to a scheduled-skill
5070
+ // registry entry. Distinct from /api/cron/:job/migrate (which keeps
5071
+ // the cron in CRON.md but cleans it up). This endpoint REMOVES the
5072
+ // entry from CRON.md and writes it into ~/.clementine/schedules.json
5073
+ // — the Anthropic-pure target shape.
5074
+ //
5075
+ // Eligibility: the cron must already pin a skill OR have a matchable
5076
+ // skill name. We refuse to migrate a cron with no skill to fall back
5077
+ // on, because the schedules.json shape only references {skillName →
5078
+ // schedule}; it has no place for the cron's own prompt body. Users
5079
+ // with custom-prompt crons can either (a) create a skill from the
5080
+ // prompt first via the Builder, then re-run this, or (b) keep the
5081
+ // legacy cron format.
5082
+ app.post('/api/cron/:job/migrate-to-skill', async (req, res) => {
5083
+ try {
5084
+ const jobName = req.params.job;
5085
+ if (!jobName) {
5086
+ res.status(400).json({ ok: false, error: 'job name required' });
5087
+ return;
5088
+ }
5089
+ const { findMatchingSkill } = await import('../agent/cron-migrator.js');
5090
+ const { listSkills } = await import('../agent/skill-store.js');
5091
+ const { setSchedule } = await import('../agent/schedule-registry.js');
5092
+ const { parseCronJobs, parseAgentCronJobs } = await import('../gateway/cron-scheduler.js');
5093
+ const { cronFile, bareJobName } = resolveJobCronFile(jobName);
5094
+ const allJobs = [...parseCronJobs(), ...parseAgentCronJobs(path.join(VAULT_DIR, '00-System', 'agents'))];
5095
+ const target = allJobs.find(j => String(j.name).toLowerCase() === jobName.toLowerCase());
5096
+ if (!target)
5097
+ return res.status(404).json({ ok: false, error: `job "${jobName}" not found` });
5098
+ // Only migrate true legacy CRON.md jobs — scheduled skills aren't migration targets.
5099
+ if (target.source === 'scheduled-skill') {
5100
+ return res.status(400).json({ ok: false, error: 'already a scheduled skill' });
5101
+ }
5102
+ // Find the skill to bind. Prefer an explicit pin; fall back to
5103
+ // findMatchingSkill which uses prompt + name keyword overlap.
5104
+ let skillName = null;
5105
+ const skills = listSkills();
5106
+ if (target.skills && target.skills.length > 0) {
5107
+ // Use the first pinned skill that actually exists in the catalog.
5108
+ skillName = target.skills.find(s => skills.some(sk => sk.frontmatter.name === s)) ?? target.skills[0];
5109
+ }
5110
+ else {
5111
+ const m = findMatchingSkill(target, skills);
5112
+ if (m)
5113
+ skillName = m.frontmatter.name;
5114
+ }
5115
+ if (!skillName) {
5116
+ return res.status(400).json({
5117
+ ok: false,
5118
+ error: 'No skill match. Create a skill from this prompt in the Builder first, then retry.',
5119
+ });
5120
+ }
5121
+ // Sanity: a skill of this name must exist in the catalog (otherwise
5122
+ // the runtime will treat it as a missing pin).
5123
+ if (!skills.some(s => s.frontmatter.name === skillName)) {
5124
+ return res.status(400).json({ ok: false, error: `Pinned skill "${skillName}" not in catalog. Create it first.` });
5125
+ }
5126
+ // Write to schedules.json. Carry over agentSlug + enabled + schedule.
5127
+ const entry = setSchedule(skillName, {
5128
+ schedule: target.schedule,
5129
+ enabled: target.enabled !== false,
5130
+ agentSlug: target.agentSlug ?? null,
5131
+ });
5132
+ // Remove from CRON.md by bare name.
5133
+ const bakPath = cronFile + '.bak';
5134
+ try {
5135
+ writeFileSync(bakPath, readFileSync(cronFile, 'utf-8'));
5136
+ }
5137
+ catch { /* best-effort */ }
5138
+ const { parsed, jobs } = readCronFileAt(cronFile);
5139
+ const idx = jobs.findIndex(j => String(j.name).toLowerCase() === bareJobName.toLowerCase());
5140
+ if (idx < 0) {
5141
+ return res.status(404).json({ ok: false, error: `job not in CRON.md (looked for "${bareJobName}")` });
5142
+ }
5143
+ const removed = jobs.splice(idx, 1)[0];
5144
+ writeCronFileAt(cronFile, parsed, jobs);
5145
+ // Hot-reload + broadcast so the Tasks page updates without manual refresh.
5146
+ try {
5147
+ const gw = await getGateway();
5148
+ const sched = gw.cronScheduler;
5149
+ if (sched && typeof sched.reloadJobs === 'function')
5150
+ sched.reloadJobs();
5151
+ }
5152
+ catch { /* best-effort */ }
5153
+ try {
5154
+ broadcastEvent({ type: 'cron_deleted', data: { job: jobName, source: 'cron-md', migrated: true } });
5155
+ }
5156
+ catch { /* non-fatal */ }
5157
+ res.json({
5158
+ ok: true,
5159
+ skillName,
5160
+ scheduleEntry: entry,
5161
+ removedFrom: cronFile,
5162
+ bakPath,
5163
+ removedBareName: removed?.name,
5164
+ message: `Converted "${jobName}" → scheduled skill "${skillName}". CRON.md backup at ${bakPath}.`,
5165
+ });
5166
+ }
5167
+ catch (err) {
5168
+ res.status(500).json({ ok: false, error: String(err) });
5169
+ }
5170
+ });
5171
+ app.post('/api/cron/migrate-all-to-skills', async (_req, res) => {
5172
+ try {
5173
+ const { findMatchingSkill } = await import('../agent/cron-migrator.js');
5174
+ const { listSkills } = await import('../agent/skill-store.js');
5175
+ const { setSchedule } = await import('../agent/schedule-registry.js');
5176
+ const { parseCronJobs, parseAgentCronJobs } = await import('../gateway/cron-scheduler.js');
5177
+ const skills = listSkills();
5178
+ const allJobs = [...parseCronJobs(), ...parseAgentCronJobs(path.join(VAULT_DIR, '00-System', 'agents'))];
5179
+ const legacy = allJobs.filter(j => j.source !== 'scheduled-skill');
5180
+ const migrated = [];
5181
+ const skipped = [];
5182
+ // Group by source CRON.md so we open + parse + write each file once.
5183
+ const byFile = new Map();
5184
+ for (const job of legacy) {
5185
+ let skillName = null;
5186
+ if (job.skills && job.skills.length > 0) {
5187
+ skillName = job.skills.find(s => skills.some(sk => sk.frontmatter.name === s)) ?? null;
5188
+ }
5189
+ if (!skillName) {
5190
+ const m = findMatchingSkill(job, skills);
5191
+ if (m)
5192
+ skillName = m.frontmatter.name;
5193
+ }
5194
+ if (!skillName) {
5195
+ skipped.push({ name: job.name, reason: 'no skill match' });
5196
+ continue;
5197
+ }
5198
+ if (!skills.some(s => s.frontmatter.name === skillName)) {
5199
+ skipped.push({ name: job.name, reason: `skill "${skillName}" not in catalog` });
5200
+ continue;
5201
+ }
5202
+ try {
5203
+ setSchedule(skillName, {
5204
+ schedule: job.schedule,
5205
+ enabled: job.enabled !== false,
5206
+ agentSlug: job.agentSlug ?? null,
5207
+ });
5208
+ migrated.push({ name: job.name, skillName });
5209
+ const { cronFile, bareJobName } = resolveJobCronFile(job.name);
5210
+ if (!byFile.has(cronFile))
5211
+ byFile.set(cronFile, { jobsToRemove: new Set(), cronFile });
5212
+ byFile.get(cronFile).jobsToRemove.add(bareJobName);
5213
+ }
5214
+ catch (err) {
5215
+ skipped.push({ name: job.name, reason: String(err) });
5216
+ }
5217
+ }
5218
+ const touchedFiles = [];
5219
+ for (const { cronFile, jobsToRemove } of byFile.values()) {
5220
+ try {
5221
+ const bakPath = cronFile + '.bak';
5222
+ try {
5223
+ writeFileSync(bakPath, readFileSync(cronFile, 'utf-8'));
5224
+ }
5225
+ catch { /* best-effort */ }
5226
+ const { parsed, jobs } = readCronFileAt(cronFile);
5227
+ const filtered = jobs.filter(j => !jobsToRemove.has(String(j.name)));
5228
+ writeCronFileAt(cronFile, parsed, filtered);
5229
+ touchedFiles.push(cronFile);
5230
+ }
5231
+ catch (err) {
5232
+ skipped.push({ name: '(file write)', reason: `${cronFile}: ${err}` });
5233
+ }
5234
+ }
5235
+ try {
5236
+ const gw = await getGateway();
5237
+ const sched = gw.cronScheduler;
5238
+ if (sched && typeof sched.reloadJobs === 'function')
5239
+ sched.reloadJobs();
5240
+ }
5241
+ catch { /* best-effort */ }
5242
+ try {
5243
+ broadcastEvent({ type: 'cron_migrated_to_skills', data: { migratedCount: migrated.length } });
5244
+ }
5245
+ catch { /* non-fatal */ }
5246
+ res.json({ ok: true, migrated, skipped, touchedFiles });
5247
+ }
5248
+ catch (err) {
5249
+ res.status(500).json({ ok: false, error: String(err) });
5250
+ }
5251
+ });
5069
5252
  app.post('/api/cron/migrate-all', async (_req, res) => {
5070
5253
  try {
5071
5254
  const { migrateAllEligibleJobs } = await import('../agent/cron-migrator.js');
@@ -25276,10 +25459,53 @@ function renderScheduledTaskCard(task) {
25276
25459
  + '<button class="btn-sm secondary" data-trace-job="' + esc(task.name) + '" title="View execution trace">Trace</button>'
25277
25460
  + (defObj.source === 'scheduled-skill'
25278
25461
  ? '<button class="btn-sm secondary btn-danger" onclick="unscheduleSkillFromCard(\\x27' + safeName + '\\x27)" title="Remove the schedule (skill stays)">Unschedule</button>'
25279
- : '<button class="btn-sm secondary btn-danger" onclick="confirmDeleteCron(\\x27' + safeName + '\\x27)" title="Delete task">Del</button>')
25462
+ : '<button class="btn-sm secondary" onclick="migrateCronToSkillFromCard(\\x27' + safeName + '\\x27)" title="Convert this legacy cron to a scheduled skill — Anthropic-pure format">→ Skill</button>'
25463
+ + '<button class="btn-sm secondary btn-danger" onclick="confirmDeleteCron(\\x27' + safeName + '\\x27)" title="Delete task">Del</button>')
25280
25464
  + '</div></div>';
25281
25465
  }
25282
25466
 
25467
+ // 1.18.132 — Phase 3 migrator: convert one legacy cron → scheduled skill.
25468
+ // Calls /api/cron/:job/migrate-to-skill which writes schedules.json and
25469
+ // removes the entry from CRON.md (with a .bak backup).
25470
+ async function migrateCronToSkillFromCard(jobName) {
25471
+ if (!confirm('Convert "' + jobName + '" to a scheduled skill?\\n\\n' +
25472
+ 'This writes a thin entry in ~/.clementine/schedules.json that points to a skill, ' +
25473
+ 'and removes the legacy fat-cron entry from CRON.md. ' +
25474
+ 'A .bak backup is left so you can restore if anything looks wrong.')) return;
25475
+ try {
25476
+ var r = await apiFetch('/api/cron/' + encodeURIComponent(jobName) + '/migrate-to-skill', { method: 'POST' });
25477
+ var d = await r.json();
25478
+ if (!r.ok) { toast(d.error || 'Migration failed', 'error'); return; }
25479
+ toast('Migrated "' + jobName + '" → scheduled skill "' + d.skillName + '"', 'success');
25480
+ if (typeof refreshCron === 'function') refreshCron();
25481
+ } catch (err) {
25482
+ toast('Failed: ' + err, 'error');
25483
+ }
25484
+ }
25485
+
25486
+ // Bulk migrator from the deprecation banner. Migrates every legacy cron
25487
+ // that has a matching skill; reports skipped ones with reasons.
25488
+ async function migrateAllCronsToSkills() {
25489
+ if (!confirm('Convert ALL eligible legacy crons to scheduled skills?\\n\\n' +
25490
+ 'Each cron with a matched skill will become a thin schedules.json entry. ' +
25491
+ 'CRON.md files get .bak backups. Crons without a skill match are skipped — ' +
25492
+ 'no data loss.')) return;
25493
+ try {
25494
+ var r = await apiFetch('/api/cron/migrate-all-to-skills', { method: 'POST' });
25495
+ var d = await r.json();
25496
+ if (!r.ok) { toast(d.error || 'Bulk migration failed', 'error'); return; }
25497
+ var migrated = (d.migrated || []).length;
25498
+ var skipped = (d.skipped || []).length;
25499
+ var msg = 'Migrated ' + migrated + ' cron' + (migrated === 1 ? '' : 's') + ' to scheduled skills';
25500
+ if (skipped > 0) msg += '. ' + skipped + ' skipped — see console for reasons.';
25501
+ toast(msg, migrated > 0 ? 'success' : 'info');
25502
+ if (skipped > 0 && d.skipped) console.warn('[migrate-all-to-skills] skipped:', d.skipped);
25503
+ if (typeof refreshCron === 'function') refreshCron();
25504
+ } catch (err) {
25505
+ toast('Failed: ' + err, 'error');
25506
+ }
25507
+ }
25508
+
25283
25509
  // 1.18.129 — replace the "+ New Task" tile with a small dropdown that
25284
25510
  // nudges users toward the new "schedule a skill" path. Legacy cron
25285
25511
  // option stays for backward compat / power users with hand-rolled
@@ -26817,7 +27043,30 @@ async function refreshCron() {
26817
27043
  // placeholder; refreshCronCleanBanner() fetches /api/cron/migrate-preview
26818
27044
  // async and fills this in only when there are eligible legacy jobs.
26819
27045
  // The banner stays empty (zero visual noise) when the vault is clean.
26820
- var html = '<div id="cron-migrate-banner-host"></div>';
27046
+ // 1.18.132 Phase 3: soft-deprecate banner. Counts legacy crons
27047
+ // (definition.source !== 'scheduled-skill') and surfaces a one-click
27048
+ // bulk migrator. Dismissable; persists in localStorage so it doesn't
27049
+ // nag on every refresh.
27050
+ var legacyCount = 0;
27051
+ try {
27052
+ legacyCount = (visibleTasks || []).filter(function(t) {
27053
+ return !(t.definition && t.definition.source === 'scheduled-skill');
27054
+ }).length;
27055
+ } catch (_) { /* defensive */ }
27056
+ var bannerHtml = '';
27057
+ var dismissed = localStorage.getItem('clem-skill-migrate-banner-dismissed') === '1';
27058
+ if (legacyCount > 0 && !dismissed) {
27059
+ bannerHtml = '<div 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">'
27060
+ + '<span style="font-size:18px">⚡</span>'
27061
+ + '<div style="flex:1;min-width:200px">'
27062
+ + '<div style="font-size:13px;font-weight:500;color:var(--text-primary)">' + legacyCount + ' legacy cron task' + (legacyCount === 1 ? '' : 's') + ' can become scheduled skills</div>'
27063
+ + '<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>'
27064
+ + '</div>'
27065
+ + '<button class="btn-sm btn-primary" onclick="migrateAllCronsToSkills()" style="font-size:12px;padding:6px 12px">Migrate all eligible →</button>'
27066
+ + '<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>'
27067
+ + '</div>';
27068
+ }
27069
+ var html = bannerHtml + '<div id="cron-migrate-banner-host"></div>';
26821
27070
  html += '<div id="health-strip" class="health-strip"></div>';
26822
27071
  // 1.18.115 — collapse the cost/latency/reliability/activity mini-cards
26823
27072
  // into a <details> block. The Health Strip already covers what most
@@ -1032,12 +1032,31 @@ export class HeartbeatScheduler {
1032
1032
  let response = null;
1033
1033
  try {
1034
1034
  const cronCall = buildInsightCheckCronCall(prompt);
1035
+ // 1.18.132 — fix the "Prompt is too long" loop. Insight-check is
1036
+ // an internal Haiku classifier ("should we proactively message
1037
+ // Nathan?") with a tightly-built prompt assembled by
1038
+ // gatherInsightSignals + buildInsightPrompt. Without predictable
1039
+ // mode the runtime ALSO injected MEMORY.md (8k cap) + team
1040
+ // comms + delegation queue + auto-matched skills (up to 4 skill
1041
+ // bodies). The user's MEMORY.md alone was ~80% of the budget;
1042
+ // adding even one auto-matched skill body tipped it past the
1043
+ // model's input window.
1044
+ //
1045
+ // Pass predictable: true + an explicit empty allowedTools list so
1046
+ // the SDK call is just: built-in claude_code system prompt +
1047
+ // our compact insight prompt. This is a classifier, not a working
1048
+ // agent; nothing in MEMORY.md or skill bodies is relevant to
1049
+ // urgency rating.
1035
1050
  response = await this.gateway.handleCronJob(cronCall.jobName, cronCall.jobPrompt, cronCall.tier, cronCall.maxTurns, cronCall.model, undefined, // workDir
1036
1051
  'standard', // mode (display only)
1037
1052
  undefined, // maxHours
1038
1053
  undefined, // timeoutMs
1039
1054
  undefined, // successCriteria
1040
- undefined);
1055
+ undefined, // agentSlug
1056
+ undefined, // pinnedSkills
1057
+ [], // allowedTools — empty = no MCP injection
1058
+ [], // allowedMcpServers — empty = no MCP servers wired
1059
+ true);
1041
1060
  this.runLog.append({
1042
1061
  jobName: 'insight-check',
1043
1062
  startedAt: icStartedAt.toISOString(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clementine-agent",
3
- "version": "1.18.131",
3
+ "version": "1.18.132",
4
4
  "description": "Clementine — Personal AI Assistant (TypeScript)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",