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.
|
|
21
|
-
* dashboard
|
|
22
|
-
*
|
|
23
|
-
*
|
|
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
|
|
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
|
-
|
|
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.
|
|
21
|
-
* dashboard
|
|
22
|
-
*
|
|
23
|
-
*
|
|
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: ${
|
|
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 ??
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|
package/dist/cli/dashboard.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
|
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.
|
|
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.
|
|
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` true→false 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
|
|
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([
|