clementine-agent 1.18.150 → 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;
|
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.
|