clementine-agent 1.18.146 → 1.18.148
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.
|
@@ -351,7 +351,15 @@ export async function classifyRoute(userMessage, agents, gateway) {
|
|
|
351
351
|
undefined, // maxHours
|
|
352
352
|
undefined, // timeoutMs
|
|
353
353
|
undefined, // successCriteria
|
|
354
|
-
undefined
|
|
354
|
+
undefined, // agentSlug
|
|
355
|
+
// 1.18.148 — F1/F2 pattern: meta-jobs run predictable to keep the
|
|
356
|
+
// prompt under Claude's input limit. Without these the classifier
|
|
357
|
+
// inherited MEMORY.md + team comms + auto-matched skills, blew up
|
|
358
|
+
// 12+ times in 8 hours (silent fallback to default route).
|
|
359
|
+
undefined, // pinnedSkills
|
|
360
|
+
[], // allowedTools
|
|
361
|
+
[], // allowedMcpServers
|
|
362
|
+
true);
|
|
355
363
|
}
|
|
356
364
|
catch (err) {
|
|
357
365
|
logger.warn({ err }, 'Route classifier call failed');
|
|
@@ -45,8 +45,17 @@ const MAX_INJECTED_SKILLS = 4;
|
|
|
45
45
|
* delegation is the one thing every cron must always be able to do).
|
|
46
46
|
*/
|
|
47
47
|
export function computeEffectiveAllowedTools(jobAllow, profileAllow) {
|
|
48
|
-
|
|
48
|
+
// 1.18.148 — distinguish "no allowlist" (undefined → unrestricted) from
|
|
49
|
+
// "explicitly empty allowlist" (`[]` → deny all). Before this, both
|
|
50
|
+
// collapsed to `undefined` because of the `?.length` check, which meant
|
|
51
|
+
// meta-jobs passing `allowedTools: []` actually got the FULL tool set
|
|
52
|
+
// injected (and blew past the prompt limit when Composio toolkits piled
|
|
53
|
+
// on tool schemas). The fix: an explicit `[]` returns `['Agent']` only
|
|
54
|
+
// (just the SDK's required spawn-subagent tool).
|
|
55
|
+
if (jobAllow === undefined)
|
|
49
56
|
return undefined;
|
|
57
|
+
if (jobAllow.length === 0)
|
|
58
|
+
return ['Agent']; // explicitly empty → minimal
|
|
50
59
|
let result;
|
|
51
60
|
if (profileAllow?.length) {
|
|
52
61
|
const jobSet = new Set(jobAllow);
|
|
@@ -67,8 +76,16 @@ export function computeEffectiveAllowedTools(jobAllow, profileAllow) {
|
|
|
67
76
|
* MCP allowlist set.
|
|
68
77
|
*/
|
|
69
78
|
export function applyMcpAllowlist(servers, jobAllowedMcpServers) {
|
|
70
|
-
|
|
79
|
+
// 1.18.148 — empty array means "deny all MCP servers", not "no
|
|
80
|
+
// restriction". Before this, passing `[]` collapsed to `?.length === 0`
|
|
81
|
+
// and returned the unfiltered server map — so meta-jobs (insight-check,
|
|
82
|
+
// grade:*, route-classify, diagnose:*) got every Composio toolkit's
|
|
83
|
+
// tool schemas wired into their prompt and blew past Claude's input
|
|
84
|
+
// limit. 110+ "Prompt is too long" errors per 8 hours.
|
|
85
|
+
if (jobAllowedMcpServers === undefined)
|
|
71
86
|
return servers;
|
|
87
|
+
if (jobAllowedMcpServers.length === 0)
|
|
88
|
+
return {};
|
|
72
89
|
const allow = new Set(jobAllowedMcpServers);
|
|
73
90
|
return Object.fromEntries(Object.entries(servers).filter(([name]) => allow.has(name)));
|
|
74
91
|
}
|
|
@@ -94,8 +111,13 @@ export function applyMcpAllowlist(servers, jobAllowedMcpServers) {
|
|
|
94
111
|
* unchanged.
|
|
95
112
|
*/
|
|
96
113
|
export function widenAllowlistWithSkillTools(jobAllow, pinnedSkillTools) {
|
|
97
|
-
|
|
114
|
+
// 1.18.148 — preserve "explicitly empty" semantics. An empty array is a
|
|
115
|
+
// contract: "I want no tools." Skill-pin widening doesn't apply when no
|
|
116
|
+
// skills are pinned (which is the case for meta-jobs).
|
|
117
|
+
if (jobAllow === undefined)
|
|
98
118
|
return undefined;
|
|
119
|
+
if (jobAllow.length === 0 && !pinnedSkillTools?.length)
|
|
120
|
+
return [];
|
|
99
121
|
if (!pinnedSkillTools?.length)
|
|
100
122
|
return [...jobAllow];
|
|
101
123
|
return [...new Set([...jobAllow, ...pinnedSkillTools])];
|
|
@@ -130,8 +152,12 @@ export function extractMcpServersFromSkillBodies(bodies) {
|
|
|
130
152
|
* allowlist; doesn't synthesize one when the cron is unrestricted.
|
|
131
153
|
*/
|
|
132
154
|
export function widenMcpAllowlistWithSkillRefs(jobMcpAllow, skillReferencedServers) {
|
|
133
|
-
|
|
155
|
+
// 1.18.148 — preserve "explicitly empty" semantics. See note on
|
|
156
|
+
// applyMcpAllowlist + widenAllowlistWithSkillTools above.
|
|
157
|
+
if (jobMcpAllow === undefined)
|
|
134
158
|
return undefined;
|
|
159
|
+
if (jobMcpAllow.length === 0 && !skillReferencedServers.length)
|
|
160
|
+
return [];
|
|
135
161
|
if (!skillReferencedServers.length)
|
|
136
162
|
return [...jobMcpAllow];
|
|
137
163
|
return [...new Set([...jobMcpAllow, ...skillReferencedServers])];
|
|
@@ -572,8 +598,10 @@ export async function buildCronExecutionPlan(opts) {
|
|
|
572
598
|
const widenedJobMcpAllowlist = widenMcpAllowlistWithSkillRefs(opts.allowedMcpServers, skillReferencedMcpServers);
|
|
573
599
|
// Per-trick MCP allowlist: post-filter on the profile-narrowed map.
|
|
574
600
|
// Effective set = profile ∩ trick (widened).
|
|
601
|
+
// 1.18.148 — empty array means "deny all" not "no restriction" (was a
|
|
602
|
+
// silent prompt-bloat bug — see applyMcpAllowlist note above).
|
|
575
603
|
const mcpServerMap = applyMcpAllowlist(mcp.servers, widenedJobMcpAllowlist);
|
|
576
|
-
const allowSet = widenedJobMcpAllowlist
|
|
604
|
+
const allowSet = widenedJobMcpAllowlist === undefined ? null : new Set(widenedJobMcpAllowlist);
|
|
577
605
|
const composioConnected = allowSet ? mcp.composioConnected.filter(n => allowSet.has(n)) : mcp.composioConnected;
|
|
578
606
|
const externalConnected = allowSet ? mcp.externalConnected.filter(n => allowSet.has(n)) : mcp.externalConnected;
|
|
579
607
|
const mcpServersApplied = Object.keys(mcpServerMap);
|
package/dist/cli/dashboard.js
CHANGED
|
@@ -1754,6 +1754,28 @@ export async function cmdDashboard(opts) {
|
|
|
1754
1754
|
}
|
|
1755
1755
|
catch { /* ignore */ }
|
|
1756
1756
|
};
|
|
1757
|
+
// 1.18.147 — Auto-open the dashboard URL in the user's default
|
|
1758
|
+
// browser once the child has had a chance to bind + write the
|
|
1759
|
+
// token file. Direct `clementine dashboard` invocations now match
|
|
1760
|
+
// the restart/update flow so the user never has to copy-paste a
|
|
1761
|
+
// token by hand. Honors NO_BROWSER=1 for headless / CI runs.
|
|
1762
|
+
if (process.env.NO_BROWSER !== '1') {
|
|
1763
|
+
setTimeout(() => {
|
|
1764
|
+
try {
|
|
1765
|
+
const tokenPath = path.join(BASE_DIR, '.dashboard-token');
|
|
1766
|
+
const token = existsSync(tokenPath) ? readFileSync(tokenPath, 'utf-8').trim() : '';
|
|
1767
|
+
if (!token)
|
|
1768
|
+
return;
|
|
1769
|
+
const url = `http://localhost:${childPort}/?token=${token}`;
|
|
1770
|
+
const platform = process.platform;
|
|
1771
|
+
const cmd = platform === 'darwin' ? 'open'
|
|
1772
|
+
: platform === 'win32' ? 'start'
|
|
1773
|
+
: 'xdg-open';
|
|
1774
|
+
spawn(cmd, [url], { detached: true, stdio: 'ignore' }).unref();
|
|
1775
|
+
}
|
|
1776
|
+
catch { /* best effort */ }
|
|
1777
|
+
}, 1500);
|
|
1778
|
+
}
|
|
1757
1779
|
// Forward signals to child
|
|
1758
1780
|
process.on('SIGINT', () => { child.kill('SIGINT'); cleanup(); process.exit(0); });
|
|
1759
1781
|
process.on('SIGTERM', () => { child.kill('SIGTERM'); cleanup(); process.exit(0); });
|
package/dist/cli/index.js
CHANGED
|
@@ -472,7 +472,32 @@ function cmdStop() {
|
|
|
472
472
|
* the user's old browser tab (which would silently 401 on the stale
|
|
473
473
|
* token) doesn't waste their time.
|
|
474
474
|
*/
|
|
475
|
-
|
|
475
|
+
/**
|
|
476
|
+
* 1.18.147 — Open the dashboard URL in the user's default browser.
|
|
477
|
+
*
|
|
478
|
+
* Invoked after the dashboard child binds. Best-effort cross-platform:
|
|
479
|
+
* macOS uses `open`, Windows uses `start`, Linux falls back to
|
|
480
|
+
* `xdg-open`. Failures are swallowed (no native browser, headless
|
|
481
|
+
* SSH session, etc.) — the printed URL is still the source of truth
|
|
482
|
+
* the user can copy by hand.
|
|
483
|
+
*
|
|
484
|
+
* Honors NO_BROWSER=1 env var so CI / scripted runs don't get a
|
|
485
|
+
* spurious browser tab.
|
|
486
|
+
*/
|
|
487
|
+
function openInBrowser(url) {
|
|
488
|
+
if (process.env.NO_BROWSER === '1')
|
|
489
|
+
return;
|
|
490
|
+
const platform = process.platform;
|
|
491
|
+
const cmd = platform === 'darwin' ? 'open'
|
|
492
|
+
: platform === 'win32' ? 'start'
|
|
493
|
+
: 'xdg-open';
|
|
494
|
+
try {
|
|
495
|
+
const { spawn: spawnProc } = require('node:child_process');
|
|
496
|
+
spawnProc(cmd, [url], { detached: true, stdio: 'ignore' }).unref();
|
|
497
|
+
}
|
|
498
|
+
catch { /* no browser available; URL was already printed */ }
|
|
499
|
+
}
|
|
500
|
+
async function relaunchDashboardDetached(opts = {}) {
|
|
476
501
|
try {
|
|
477
502
|
const { spawn: spawnProc } = await import('node:child_process');
|
|
478
503
|
const child = spawnProc('node', [path.join(PACKAGE_ROOT, 'dist/cli/index.js'), 'dashboard'], { detached: true, stdio: 'ignore' });
|
|
@@ -489,7 +514,13 @@ async function relaunchDashboardDetached() {
|
|
|
489
514
|
}
|
|
490
515
|
catch { /* token may not be ready yet */ }
|
|
491
516
|
if (token) {
|
|
492
|
-
|
|
517
|
+
const url = `http://localhost:3030/?token=${token}`;
|
|
518
|
+
console.log(` Dashboard relaunched: ${url}`);
|
|
519
|
+
// 1.18.147 — auto-open the browser by default. Restart/update
|
|
520
|
+
// already imply user wants the dashboard back; making them copy a
|
|
521
|
+
// URL was a UX papercut.
|
|
522
|
+
if (opts.open !== false)
|
|
523
|
+
openInBrowser(url);
|
|
493
524
|
}
|
|
494
525
|
else {
|
|
495
526
|
console.log(' Dashboard relaunched (token not ready — check `clementine status`).');
|
|
@@ -546,7 +546,15 @@ export async function diagnoseBrokenJob(broken, gateway) {
|
|
|
546
546
|
undefined, // maxHours
|
|
547
547
|
undefined, // timeoutMs
|
|
548
548
|
undefined, // successCriteria
|
|
549
|
-
undefined
|
|
549
|
+
undefined, // agentSlug
|
|
550
|
+
// 1.18.148 — F1/F2 pattern: diagnostics are pure analysis of an
|
|
551
|
+
// existing run, no need for MEMORY.md / team / auto-skills. Without
|
|
552
|
+
// these flags the diagnostic prompt blew past Claude's input limit
|
|
553
|
+
// and broken jobs went undiagnosed silently.
|
|
554
|
+
undefined, // pinnedSkills
|
|
555
|
+
[], // allowedTools
|
|
556
|
+
[], // allowedMcpServers
|
|
557
|
+
true);
|
|
550
558
|
}
|
|
551
559
|
catch (err) {
|
|
552
560
|
logger.warn({ err, job: broken.jobName }, 'Diagnostic LLM call failed');
|
|
@@ -140,7 +140,16 @@ export async function gradeRun(entry, gateway, jobPrompt) {
|
|
|
140
140
|
undefined, // maxHours
|
|
141
141
|
undefined, // timeoutMs
|
|
142
142
|
undefined, // successCriteria
|
|
143
|
-
undefined
|
|
143
|
+
undefined, // agentSlug
|
|
144
|
+
// 1.18.148 — F1/F2 pattern: meta-jobs don't get user MEMORY.md /
|
|
145
|
+
// team comms / auto-matched skills, otherwise the prompt blows
|
|
146
|
+
// past Claude's input limit (110+ "Prompt is too long" errors/8h
|
|
147
|
+
// before this fix). Same shape applied to insight-check (1.18.132)
|
|
148
|
+
// and route-classify / failure-diagnostics in this same ship.
|
|
149
|
+
undefined, // pinnedSkills
|
|
150
|
+
[], // allowedTools — empty = no MCP injection
|
|
151
|
+
[], // allowedMcpServers — empty = no MCP servers wired
|
|
152
|
+
true);
|
|
144
153
|
}
|
|
145
154
|
catch (err) {
|
|
146
155
|
logger.warn({ err, jobName: entry.jobName }, 'Outcome grader LLM call failed');
|