clementine-agent 1.18.149 → 1.18.150

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.
@@ -2054,13 +2054,29 @@ export class SelfImproveLoop {
2054
2054
  }
2055
2055
  }
2056
2056
  catch { /* non-fatal */ }
2057
- // Identify consistently failed approaches
2057
+ // Identify consistently failed approaches.
2058
+ // 1.18.150 — exact-string Set dedup let near-duplicate hypotheses ("improve
2059
+ // cron prompt clarity" vs "improve cron prompt for clarity") both make it
2060
+ // through and waste two of the five slots. Use the existing tokenize +
2061
+ // jaccard helpers (Set ≥0.85 collapse) to merge near-duplicates while
2062
+ // preserving genuinely distinct framings.
2058
2063
  const failedHypotheses = history
2059
2064
  .filter(e => !e.accepted && e.score < 0.3 && !e.type)
2060
2065
  .map(e => e.hypothesis.slice(0, 80));
2061
2066
  if (failedHypotheses.length > 0) {
2062
2067
  lines.push('\n### Approaches That Scored Poorly (avoid these)');
2063
- for (const h of [...new Set(failedHypotheses)].slice(0, 5)) {
2068
+ const merged = [];
2069
+ const mergedTokens = [];
2070
+ for (const h of failedHypotheses) {
2071
+ const ht = tokenizeForDrift(h);
2072
+ if (mergedTokens.some(t => jaccardSimilarity(t, ht) >= 0.85))
2073
+ continue;
2074
+ merged.push(h);
2075
+ mergedTokens.push(ht);
2076
+ if (merged.length >= 5)
2077
+ break;
2078
+ }
2079
+ for (const h of merged) {
2064
2080
  lines.push(`- "${h}"`);
2065
2081
  }
2066
2082
  }
@@ -325,7 +325,7 @@ export function registerAdminTools(server) {
325
325
  const unique = [...new Set(tools)].sort();
326
326
  writeFileSync(ALLOWED_TOOLS_EXTRA, JSON.stringify(unique, null, 2));
327
327
  }
328
- server.tool('allow_tool', 'Add a tool name to your self-managed allowedTools list. Use when you see a tool in the SDK inventory but get "not in function schema" when you try to call it. Writes to ~/.clementine/allowed-tools-extra.json; takes effect on your NEXT query. If the tool name isn\'t yet in the cached inventory, this auto-refreshes the probe first — covers the case where the owner just added a new claude.ai connector or local MCP server. Owner-DM only.', {
328
+ server.tool('allow_tool', 'Add a tool name to your self-managed allowedTools list (~/.clementine/allowed-tools-extra.json). Use when an SDK-inventory tool returns "not in function schema". Effective on your next query. Auto-refreshes the inventory probe if the tool isn\'t cached. Owner-DM only.', {
329
329
  name: z.string().describe('Exact tool name (e.g. "mcp__claude_ai_Google_Drive__search_files")'),
330
330
  reason: z.string().optional().describe('Brief note: why you need this tool. For audit trail.'),
331
331
  }, async ({ name, reason }) => {
@@ -368,7 +368,7 @@ export function registerAdminTools(server) {
368
368
  logger.info({ name: trimmed, reason, totalExtras: current.length }, 'allow_tool');
369
369
  return textResult(`Added ${trimmed} to ~/.clementine/allowed-tools-extra.json (${current.length} total extras)${refreshNote}. Active on your next query — no daemon restart needed.${reason ? ` Reason: ${reason}` : ''}`);
370
370
  });
371
- server.tool('refresh_tool_inventory', 'Force a fresh probe of the SDK\'s tool inventory, picking up any claude.ai connectors, Composio toolkits, or local MCP servers the owner has added since the last cache refresh. Owner-DM only. Use this when the owner says "I just added X at claude.ai" or when an expected integration isn\'t showing up. Updates ~/.clementine/.tool-inventory.json and syncs claude-integrations.json. Returns a diff of what changed.', {}, async () => {
371
+ server.tool('refresh_tool_inventory', 'Force a fresh probe of the SDK tool inventory to pick up newly added claude.ai connectors, Composio toolkits, or local MCP servers. Updates ~/.clementine/.tool-inventory.json + syncs claude-integrations.json and returns a diff. Owner-DM only.', {}, async () => {
372
372
  const gate = requireOwnerDm();
373
373
  if (!gate.ok)
374
374
  return textResult(gate.message);
@@ -1025,7 +1025,7 @@ export function registerAdminTools(server) {
1025
1025
  return textResult(lines.join('\n\n'));
1026
1026
  });
1027
1027
  // ── Add Cron Job ────────────────────────────────────────────────────────
1028
- server.tool('add_cron_job', 'Add a new scheduled task. BEFORE CALLING THIS TOOL: propose the concrete plan to the user in chat and get explicit approval. The `prompt` you save should be SELF-CONTAINED list the actual recipients, the actual template/content, the actual criteria. AVOID vague references like "recent leads" or "this week\'s items" that the trick will re-derive at fire-time, because re-derivation reads from MEMORY.md which drifts between chat-time agreement and fire-time execution. Good prompt: "Send template `monday-followup` to alice@x.com, bob@y.com, carol@z.com." Bad prompt: "Send follow-up to recent leads." The default `predictable: true` mode runs the trick with ONLY the prompt + explicitly-attached skills/tools no MEMORY.md, no team-comms injection, no runtime skill auto-match. Set `predictable: false` ONLY if the user explicitly wants a dynamic trick that re-resolves data each fire (e.g., "summarize yesterday\'s daily note" where the data legitimately changes).', {
1028
+ server.tool('add_cron_job', 'Add a new scheduled task. Propose the plan in chat and get user approval first. The `prompt` MUST be self-contained: name the actual recipients, template, and criteria vague references ("recent leads", "this week\'s items") drift between chat-time and fire-time. Default `predictable: true` runs with only prompt + pinned skills/tools (no MEMORY.md, no auto-match). Set `predictable: false` only when the user explicitly wants dynamic re-resolution each fire.', {
1029
1029
  name: z.string().describe('Job name (unique identifier)'),
1030
1030
  schedule: z.string().describe('Cron expression (e.g., "0 9 * * 1" for Monday 9 AM)'),
1031
1031
  prompt: z.string().describe('The prompt/instruction for the assistant to execute. SHOULD BE CONCRETE — list actual recipients, criteria, content. Vague prompts re-derive at fire-time and cause "agent agreed in chat but emailed wrong people" failures.'),
@@ -1143,7 +1143,7 @@ export function registerAdminTools(server) {
1143
1143
  return textResult(`Added cron job "${jobName}":\n${details.join('\n')}\n\n${verifyMsg}${goalHint}`);
1144
1144
  });
1145
1145
  // ── Update Cron Job ─────────────────────────────────────────────────────
1146
- server.tool('update_cron_job', 'Update an existing cron job in CRON.md. Partial — only fields you supply change. To CLEAR a capability allowlist (skills/allowed_tools/allowed_mcp_servers/tags), pass an empty array. To clear category, pass an empty string. The daemon auto-reloads on file change. Use preview_cron_job to confirm what will run before the next fire. Flipping `predictable` from true to false changes whether the trick reads MEMORY.md at fire-time — make sure the user understands the tradeoff before you toggle it.', {
1146
+ server.tool('update_cron_job', 'Update an existing cron job in CRON.md. Partial — only fields you supply change. Pass an empty array to clear a capability allowlist (skills/allowed_tools/allowed_mcp_servers/tags); empty string clears category. Daemon auto-reloads. Run preview_cron_job before relying on the change. Flipping `predictable` truefalse makes the trick read MEMORY.md at fire-time — confirm the tradeoff with the user.', {
1147
1147
  name: z.string().describe('Existing job name to update.'),
1148
1148
  schedule: z.string().optional().describe('New cron expression.'),
1149
1149
  prompt: z.string().optional().describe('New prompt.'),
@@ -1279,7 +1279,7 @@ export function registerAdminTools(server) {
1279
1279
  return textResult(`Updated cron job "${jobName}":\n ${changed.join('\n ')}\n\nDaemon will pick up the new definition within ~2s. Use \`preview_cron_job\` to confirm what will actually run.`);
1280
1280
  });
1281
1281
  // ── Preview Cron Job ────────────────────────────────────────────────────
1282
- server.tool('preview_cron_job', 'Show EXACTLY what a cron job ("trick") will send the agent on its next fire without dispatching to the agent. Composes the same context blocks (memory, progress, goals, skills, team, criteria, prompt, how-to-respond) the runner would compose at fire time. Returns the built prompt + resolved skills (full content) + effective tool/MCP allowlists + warnings (missing pins). Use this to sanity-check tricks after configuring them via chat or the dashboard, especially when you want predictable morning behavior.', {
1282
+ server.tool('preview_cron_job', 'Show what a cron job will send the agent on its next fire without dispatching. Composes the same context blocks the runner uses at fire time and returns the built prompt + resolved skills + effective tool/MCP allowlists + warnings. Use to sanity-check tricks after configuration.', {
1283
1283
  name: z.string().describe('Exact name of the cron job to preview (use list_cron_jobs to see available).'),
1284
1284
  }, async ({ name: jobName }) => {
1285
1285
  const [{ parseCronJobs, parseAgentCronJobs }, { buildCronExecutionPlan }, { loadSkillByName }, { discoverMcpServers }] = await Promise.all([
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clementine-agent",
3
- "version": "1.18.149",
3
+ "version": "1.18.150",
4
4
  "description": "Clementine — Personal AI Assistant (TypeScript)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",