clementine-agent 1.18.149 → 1.18.151

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.
@@ -17,24 +17,39 @@
17
17
  * hand-written project settings.
18
18
  *
19
19
  * Auth: the hook commands include the dashboard token in the X-Dashboard-Token
20
- * header. The token is baked into the curl command at install time. If the
21
- * dashboard restarts and rotates its token, the user has to re-enable hooks
22
- * (a small UX cost we accept; the alternative is having hooks read the
23
- * token from disk at fire-time which adds a syscall to every tool call).
20
+ * header. As of 1.18.151 the token is read from disk at fire-time via
21
+ * `$(cat ~/.clementine/.dashboard-token)` instead of being baked into the
22
+ * curl command at install time. The token-file path is interpolated at
23
+ * install time, but its CONTENT is read on every hook fire so dashboard
24
+ * token rotation (which `clementine update restart` does) no longer breaks
25
+ * installed hooks. The dashboard already maintains this file at startup
26
+ * (dashboard.ts:2097-2098); we just point the curl at it.
27
+ *
28
+ * The cost is one tiny `cat` syscall per tool call — negligible compared to
29
+ * the curl + dashboard ingestion already happening. The win is no more
30
+ * silent 401s when the token rotates.
24
31
  */
32
+ /** Bumped on every meaningful template change. Heartbeat reads installed
33
+ * files' `_clementine.installerVersion` and surfaces a re-install nudge
34
+ * when it lags this constant — see heartbeat.ts (1.18.151 stale-template
35
+ * detector). We never overwrite user-facing files silently; the user runs
36
+ * `clementine hooks install` to opt into the new template. */
37
+ export declare const CURRENT_INSTALLER_VERSION = "1.18.151";
25
38
  export interface SettingsTemplateOptions {
26
- /** Dashboard token to include in the X-Dashboard-Token header. Required. */
27
- token: string;
28
39
  /** Localhost port the dashboard is listening on. Defaults to 3030. */
29
40
  port?: number;
30
41
  /** Mark the file with a comment so users know who wrote it. */
31
42
  installerVersion?: string;
43
+ /** Absolute path to the token file the curl will `cat` at fire time.
44
+ * Override is for tests; production always uses the canonical
45
+ * ~/.clementine/.dashboard-token. */
46
+ tokenFilePath?: string;
32
47
  }
33
48
  /** Build the JSON content of .claude/settings.local.json. The shape matches
34
49
  * the SDK's hook config schema: a top-level `hooks` map keyed by event name,
35
50
  * each value an array of `{ hooks: [{ type, command }] }` matchers. The
36
51
  * empty matcher means "always fire". */
37
- export declare function buildSettingsTemplate(opts: SettingsTemplateOptions): Record<string, unknown>;
52
+ export declare function buildSettingsTemplate(opts?: SettingsTemplateOptions): Record<string, unknown>;
38
53
  export interface InstallResult {
39
54
  ok: boolean;
40
55
  filePath: string;
@@ -47,8 +62,12 @@ export interface InstallResult {
47
62
  }
48
63
  /** Write/update .claude/settings.local.json in `workDir`. If the file
49
64
  * already exists and is NOT installer-managed (no _clementine key), we
50
- * bail out and refuse to overwrite to avoid clobbering user content. */
51
- export declare function installPathBHooks(workDir: string, opts: SettingsTemplateOptions): InstallResult;
65
+ * bail out and refuse to overwrite to avoid clobbering user content.
66
+ *
67
+ * As of 1.18.151 no token is required at install time — the curl reads
68
+ * the live token from disk at fire time. Callers can pass tokenFilePath
69
+ * to override the default `~/.clementine/.dashboard-token` (tests). */
70
+ export declare function installPathBHooks(workDir: string, opts?: SettingsTemplateOptions): InstallResult;
52
71
  export interface HooksStatus {
53
72
  /** Whether a settings.local.json exists in the workDir. */
54
73
  installed: boolean;
@@ -17,18 +17,28 @@
17
17
  * hand-written project settings.
18
18
  *
19
19
  * Auth: the hook commands include the dashboard token in the X-Dashboard-Token
20
- * header. The token is baked into the curl command at install time. If the
21
- * dashboard restarts and rotates its token, the user has to re-enable hooks
22
- * (a small UX cost we accept; the alternative is having hooks read the
23
- * token from disk at fire-time which adds a syscall to every tool call).
20
+ * header. As of 1.18.151 the token is read from disk at fire-time via
21
+ * `$(cat ~/.clementine/.dashboard-token)` instead of being baked into the
22
+ * curl command at install time. The token-file path is interpolated at
23
+ * install time, but its CONTENT is read on every hook fire so dashboard
24
+ * token rotation (which `clementine update restart` does) no longer breaks
25
+ * installed hooks. The dashboard already maintains this file at startup
26
+ * (dashboard.ts:2097-2098); we just point the curl at it.
27
+ *
28
+ * The cost is one tiny `cat` syscall per tool call — negligible compared to
29
+ * the curl + dashboard ingestion already happening. The win is no more
30
+ * silent 401s when the token rotates.
24
31
  */
25
32
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
33
+ import os from 'node:os';
26
34
  import path from 'node:path';
27
35
  /** Hooks we install. Picked for the latency dashboard's needs:
28
36
  * PreToolUse + PostToolUse give us tool durations (PostToolUse carries
29
37
  * duration_ms). SubagentStart/Stop close the gap path C handles via
30
38
  * transcript backfill. Stop / Notification add nice-to-have signal but
31
- * are minimal cost. */
39
+ * are minimal cost. PreCompact + PostCompact (added 1.18.151) carry
40
+ * compaction telemetry — pre/post tokens, summary text, trigger source —
41
+ * which the run-detail viewer surfaces as "what got summarized away". */
32
42
  const HOOK_EVENTS = [
33
43
  'PreToolUse',
34
44
  'PostToolUse',
@@ -39,18 +49,33 @@ const HOOK_EVENTS = [
39
49
  'UserPromptSubmit',
40
50
  'SessionStart',
41
51
  'PreCompact',
52
+ 'PostCompact',
42
53
  ];
54
+ /** Bumped on every meaningful template change. Heartbeat reads installed
55
+ * files' `_clementine.installerVersion` and surfaces a re-install nudge
56
+ * when it lags this constant — see heartbeat.ts (1.18.151 stale-template
57
+ * detector). We never overwrite user-facing files silently; the user runs
58
+ * `clementine hooks install` to opt into the new template. */
59
+ export const CURRENT_INSTALLER_VERSION = '1.18.151';
60
+ /** Default location of the dashboard token file. The dashboard writes this
61
+ * on every startup (see dashboard.ts:2097-2098). Hooks read it at fire
62
+ * time via `$(cat …)` so token rotations propagate without re-installing. */
63
+ const DEFAULT_TOKEN_FILE = path.join(os.homedir(), '.clementine', '.dashboard-token');
43
64
  /** Build the JSON content of .claude/settings.local.json. The shape matches
44
65
  * the SDK's hook config schema: a top-level `hooks` map keyed by event name,
45
66
  * each value an array of `{ hooks: [{ type, command }] }` matchers. The
46
67
  * empty matcher means "always fire". */
47
- export function buildSettingsTemplate(opts) {
68
+ export function buildSettingsTemplate(opts = {}) {
48
69
  const port = opts.port ?? 3030;
70
+ const tokenFile = opts.tokenFilePath ?? DEFAULT_TOKEN_FILE;
49
71
  // Use POSIX `curl` — preinstalled on macOS and most Linuxes; Windows users
50
72
  // running WSL or Git Bash also have it. We add `--max-time 2` so a
51
- // wedged dashboard can't stall the SDK's tool execution.
73
+ // wedged dashboard can't stall the SDK's tool execution. The
74
+ // `$(cat … 2>/dev/null)` substitution reads the live dashboard token at
75
+ // fire time; if the file is briefly missing during startup the curl
76
+ // sends an empty token and the dashboard 401s harmlessly (no SDK break).
52
77
  const curlCmd = `curl -s --max-time 2 -X POST `
53
- + `-H "X-Dashboard-Token: ${opts.token}" `
78
+ + `-H "X-Dashboard-Token: $(cat ${tokenFile} 2>/dev/null)" `
54
79
  + `-H "Content-Type: application/json" `
55
80
  + `--data-binary @- `
56
81
  + `http://127.0.0.1:${port}/api/hooks/event`;
@@ -70,7 +95,7 @@ export function buildSettingsTemplate(opts) {
70
95
  _clementine: {
71
96
  managedBy: 'clementine-agent path-b-installer',
72
97
  installedAt: new Date().toISOString(),
73
- installerVersion: opts.installerVersion ?? '1.18.102',
98
+ installerVersion: opts.installerVersion ?? CURRENT_INSTALLER_VERSION,
74
99
  port,
75
100
  },
76
101
  hooks,
@@ -78,12 +103,14 @@ export function buildSettingsTemplate(opts) {
78
103
  }
79
104
  /** Write/update .claude/settings.local.json in `workDir`. If the file
80
105
  * already exists and is NOT installer-managed (no _clementine key), we
81
- * bail out and refuse to overwrite to avoid clobbering user content. */
82
- export function installPathBHooks(workDir, opts) {
106
+ * bail out and refuse to overwrite to avoid clobbering user content.
107
+ *
108
+ * As of 1.18.151 no token is required at install time — the curl reads
109
+ * the live token from disk at fire time. Callers can pass tokenFilePath
110
+ * to override the default `~/.clementine/.dashboard-token` (tests). */
111
+ export function installPathBHooks(workDir, opts = {}) {
83
112
  if (!workDir)
84
113
  return { ok: false, filePath: '', wasExisting: false, wasUpdate: false, error: 'workDir required' };
85
- if (!opts.token)
86
- return { ok: false, filePath: '', wasExisting: false, wasUpdate: false, error: 'token required' };
87
114
  const dir = path.join(workDir, '.claude');
88
115
  const file = path.join(dir, 'settings.local.json');
89
116
  let wasExisting = false;
@@ -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
  }
@@ -5451,7 +5451,10 @@ export async function cmdDashboard(opts) {
5451
5451
  }
5452
5452
  const port = Number(process.env.PORT) || 3030;
5453
5453
  const { installPathBHooks } = await import('../agent/path-b-installer.js');
5454
- const result = installPathBHooks(job.workDir, { token: dashboardToken, port });
5454
+ // 1.18.151 token no longer baked into hook command (read from
5455
+ // ~/.clementine/.dashboard-token at fire time). Path-b installer
5456
+ // takes only port + version sentinel now.
5457
+ const result = installPathBHooks(job.workDir, { port });
5455
5458
  if (!result.ok) {
5456
5459
  res.status(409).json({ ...result, ok: false });
5457
5460
  return;
@@ -6693,6 +6696,29 @@ If the tool returns nothing or errors, return an empty array \`[]\`.`,
6693
6696
  ? body.tool_response.slice(0, 500)
6694
6697
  : 'tool returned is_error=true';
6695
6698
  }
6699
+ // 1.18.151 — compaction telemetry. PreCompact carries trigger +
6700
+ // optional custom_instructions; PostCompact carries the summary text
6701
+ // and pre/post token counts. We promote these to top-level fields so
6702
+ // the run-detail viewer can render a "summarized N→M tokens" marker
6703
+ // without needing to re-parse the original SDK payload shape.
6704
+ if (hookEventName === 'PreCompact') {
6705
+ ev.kind = 'pre_compact';
6706
+ if (typeof body.trigger === 'string')
6707
+ ev.compactTrigger = body.trigger;
6708
+ if (typeof body.custom_instructions === 'string')
6709
+ ev.compactInstructions = body.custom_instructions;
6710
+ }
6711
+ else if (hookEventName === 'PostCompact') {
6712
+ ev.kind = 'post_compact';
6713
+ if (typeof body.trigger === 'string')
6714
+ ev.compactTrigger = body.trigger;
6715
+ if (typeof body.compact_summary === 'string')
6716
+ ev.compactSummary = body.compact_summary;
6717
+ if (typeof body.pre_tokens === 'number')
6718
+ ev.preTokens = body.pre_tokens;
6719
+ if (typeof body.post_tokens === 'number')
6720
+ ev.postTokens = body.post_tokens;
6721
+ }
6696
6722
  entry.eventLog.append(ev);
6697
6723
  res.json({ ok: true, runId: entry.runId, seq });
6698
6724
  }
@@ -27293,10 +27319,13 @@ function renderRunDetailWaterfall(events, runId, jobName) {
27293
27319
  if (k === 'subagent_start' || k === 'subagent_stop') return '#a855f7';
27294
27320
  if (k === 'rate_limit') return 'var(--yellow)';
27295
27321
  if (k === 'hook') return 'var(--blue)';
27322
+ if (k === 'pre_compact' || k === 'post_compact') return 'var(--orange, #f59e0b)';
27296
27323
  if (k === 'error') return 'var(--red)';
27297
27324
  return 'var(--text-muted)';
27298
27325
  }
27299
27326
  function kindLabel(k) {
27327
+ if (k === 'pre_compact') return 'COMPACT START';
27328
+ if (k === 'post_compact') return 'COMPACT DONE';
27300
27329
  return (k || 'event').toUpperCase().replace(/_/g, ' ');
27301
27330
  }
27302
27331
 
@@ -27369,6 +27398,29 @@ function renderRunDetailWaterfall(events, runId, jobName) {
27369
27398
  preview = ev.sessionId ? 'session ' + String(ev.sessionId).slice(0, 8) + '…' : '';
27370
27399
  } else if (ev.kind === 'session_end') {
27371
27400
  preview = '$' + (ev.costUsd != null ? ev.costUsd.toFixed(4) : '?') + ' · ' + (ev.stopReason || '?');
27401
+ } else if (ev.kind === 'pre_compact') {
27402
+ // 1.18.151 — PreCompact telemetry. Trigger tells us whether the SDK
27403
+ // auto-compacted to stay under context limits or the user/agent
27404
+ // requested it. custom_instructions, when present, biases what gets
27405
+ // preserved — surface it inline so debugging "why did this run forget
27406
+ // X?" is one click away.
27407
+ preview = '⤓ compaction starting (' + (ev.compactTrigger || '?') + ')';
27408
+ fullContent = ev.compactInstructions
27409
+ ? 'Custom instructions for compaction:\\n' + String(ev.compactInstructions)
27410
+ : 'No custom instructions — SDK uses its default summarization.';
27411
+ } else if (ev.kind === 'post_compact') {
27412
+ // 1.18.151 — PostCompact carries the summary text + token deltas.
27413
+ // Showing pre→post tokens makes compaction efficiency visible at a
27414
+ // glance; the summary itself is the canonical "what got remembered"
27415
+ // record for the rest of the run.
27416
+ var tokenDelta = '';
27417
+ if (typeof ev.preTokens === 'number' && typeof ev.postTokens === 'number') {
27418
+ tokenDelta = ' · ' + ev.preTokens.toLocaleString() + '→' + ev.postTokens.toLocaleString() + ' tokens';
27419
+ }
27420
+ preview = '⤴ compaction done' + tokenDelta;
27421
+ fullContent = ev.compactSummary
27422
+ ? 'Summary the SDK kept:\\n\\n' + String(ev.compactSummary)
27423
+ : '(no summary text returned)';
27372
27424
  }
27373
27425
 
27374
27426
  var rowId = 'run-evt-' + j;
@@ -159,6 +159,39 @@ export async function runStartupMaintenance(store) {
159
159
  catch (err) {
160
160
  logger.warn({ err }, 'Startup .md.bak sweep failed');
161
161
  }
162
+ // Path-B installer-version sweep (1.18.151). For every cron with a workDir
163
+ // that has a clementine-managed .claude/settings.local.json, check whether
164
+ // the installerVersion sentinel matches CURRENT_INSTALLER_VERSION. Any
165
+ // older install is missing the token-at-fire-time fix and will silently
166
+ // 401 against /api/hooks/event after the next dashboard token rotation.
167
+ // We log once per startup with the list of paths so the user knows to
168
+ // re-run `clementine hooks install` (we never silently overwrite — user
169
+ // owns their .claude/).
170
+ try {
171
+ const { parseCronJobs } = await import('../gateway/cron-scheduler.js');
172
+ const { getHooksStatus, CURRENT_INSTALLER_VERSION } = await import('../agent/path-b-installer.js');
173
+ const stale = [];
174
+ const seen = new Set();
175
+ for (const job of parseCronJobs()) {
176
+ if (!job.workDir || seen.has(job.workDir))
177
+ continue;
178
+ seen.add(job.workDir);
179
+ const s = getHooksStatus(job.workDir);
180
+ if (s.managedByUs && s.installerVersion !== CURRENT_INSTALLER_VERSION) {
181
+ stale.push({ workDir: job.workDir, installerVersion: s.installerVersion });
182
+ }
183
+ }
184
+ if (stale.length > 0) {
185
+ logger.warn({
186
+ stale,
187
+ currentVersion: CURRENT_INSTALLER_VERSION,
188
+ action: 'Re-run `clementine hooks install` per project to pick up the token-at-fire-time fix (1.18.151) — until then those projects 401 silently after each dashboard token rotation.',
189
+ }, 'Path-B hook installs are stale');
190
+ }
191
+ }
192
+ catch (err) {
193
+ logger.warn({ err }, 'Path-B stale-version sweep failed');
194
+ }
162
195
  // Embedding warm-up — pre-embed the most-cited chunks in the background so
163
196
  // the first retrievals after startup don't pay cold-start latency. Fire
164
197
  // and forget; never blocks startup.
@@ -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.151",
4
4
  "description": "Clementine — Personal AI Assistant (TypeScript)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",