clementine-agent 1.18.83 → 1.18.84
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.
- package/dist/agent/assistant.d.ts +0 -2
- package/dist/agent/assistant.js +23 -9
- package/dist/agent/run-agent.d.ts +15 -0
- package/dist/agent/run-agent.js +54 -0
- package/dist/cli/cron.js +7 -0
- package/dist/cli/dashboard.js +17 -8
- package/dist/gateway/cron-scheduler.js +5 -2
- package/dist/types.d.ts +5 -0
- package/package.json +1 -1
|
@@ -70,8 +70,6 @@ export declare class PersonalAssistant {
|
|
|
70
70
|
private memoryStore;
|
|
71
71
|
private _lastUserMessage?;
|
|
72
72
|
onSkillProposed: ((skill: import('../types.js').SkillDocument) => void) | null;
|
|
73
|
-
private _lastMcpStatus;
|
|
74
|
-
private _lastMcpStatusTime;
|
|
75
73
|
/** Terminal reason from the last SDK query — consumed by cron scheduler for precise error classification. */
|
|
76
74
|
private _lastTerminalReason?;
|
|
77
75
|
/** Per-session stall nudge — set after a query shows stall signals, consumed on the next query. */
|
package/dist/agent/assistant.js
CHANGED
|
@@ -17,6 +17,7 @@ import { BASE_DIR, PKG_DIR, VAULT_DIR, DAILY_NOTES_DIR, SOUL_FILE, AGENTS_FILE,
|
|
|
17
17
|
import { summarizeIntegrationStatus } from '../config/integrations-registry.js';
|
|
18
18
|
import { loadToolPreferences, computeAvailability, buildPromptInstruction, buildComposioStatusBlock, KNOWN_SERVICES, } from '../integrations/tool-preferences.js';
|
|
19
19
|
import { loadClaudeIntegrations } from './mcp-bridge.js';
|
|
20
|
+
import { getLatestMcpStatusSnapshot, recordMcpStatusFromSystemInit, invalidateMcpStatusEntry } from './run-agent.js';
|
|
20
21
|
import { detectFrustrationSignals, detectRepeatedTopics } from './insight-engine.js';
|
|
21
22
|
import { DEFAULT_CHANNEL_CAPABILITIES } from '../types.js';
|
|
22
23
|
import { enforceToolPermissions, getSecurityPrompt, getHeartbeatSecurityPrompt, getCronSecurityPrompt, getHeartbeatDisallowedTools, logToolUse, logAuditJsonl, } from './hooks.js';
|
|
@@ -710,8 +711,10 @@ export class PersonalAssistant {
|
|
|
710
711
|
memoryStore = null; // Typed as any — MemoryStore may not be available yet
|
|
711
712
|
_lastUserMessage;
|
|
712
713
|
onSkillProposed = null;
|
|
713
|
-
|
|
714
|
-
|
|
714
|
+
// PRD Phase 2 / 1.18.84: superseded by the shared module-level cache in
|
|
715
|
+
// run-agent.ts (getLatestMcpStatusSnapshot / recordMcpStatusFromSystemInit).
|
|
716
|
+
// The fields below were declared but never written pre-1.18.84; the
|
|
717
|
+
// module cache populates from every system/init message instead.
|
|
715
718
|
/** Terminal reason from the last SDK query — consumed by cron scheduler for precise error classification. */
|
|
716
719
|
_lastTerminalReason;
|
|
717
720
|
/** Per-session stall nudge — set after a query shows stall signals, consumed on the next query. */
|
|
@@ -808,7 +811,12 @@ export class PersonalAssistant {
|
|
|
808
811
|
this.onSkillProposed = cb;
|
|
809
812
|
}
|
|
810
813
|
getMcpStatus() {
|
|
811
|
-
|
|
814
|
+
// 1.18.84 correctness fix: delegate to the shared module-level cache.
|
|
815
|
+
// Pre-1.18.84 we returned this._lastMcpStatus, which was declared but
|
|
816
|
+
// never written — getMcpStatus() always returned empty. Now run-agent
|
|
817
|
+
// and assistant query streams record into a shared snapshot via
|
|
818
|
+
// recordMcpStatusFromSystemInit when the SDK init message lands.
|
|
819
|
+
return getLatestMcpStatusSnapshot();
|
|
812
820
|
}
|
|
813
821
|
/**
|
|
814
822
|
* PRD Phase 2.1: clear the cached status for one server so the next query
|
|
@@ -818,12 +826,9 @@ export class PersonalAssistant {
|
|
|
818
826
|
* stale error/auth state. Returns the post-clear cached snapshot.
|
|
819
827
|
*/
|
|
820
828
|
invalidateMcpStatus(serverName) {
|
|
821
|
-
const
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
if (cleared)
|
|
825
|
-
this._lastMcpStatusTime = new Date().toISOString();
|
|
826
|
-
return { servers: this._lastMcpStatus, updatedAt: this._lastMcpStatusTime, cleared };
|
|
829
|
+
const result = invalidateMcpStatusEntry(serverName);
|
|
830
|
+
const snapshot = getLatestMcpStatusSnapshot();
|
|
831
|
+
return { servers: snapshot.servers, updatedAt: snapshot.updatedAt, cleared: result.cleared };
|
|
827
832
|
}
|
|
828
833
|
/** Inject a background work result into the session as silent follow-up context. */
|
|
829
834
|
injectPendingContext(sessionKey, userPrompt, result) {
|
|
@@ -2999,6 +3004,15 @@ You have a cost budget per message — not a hard turn limit. Work until the tas
|
|
|
2999
3004
|
const trace = [];
|
|
3000
3005
|
const stream = query({ prompt, options: sdkOptions });
|
|
3001
3006
|
for await (const message of stream) {
|
|
3007
|
+
// 1.18.84 correctness: capture MCP server status from SDK init.
|
|
3008
|
+
if (message.type === 'system' && message.subtype === 'init') {
|
|
3009
|
+
const mcpServersRaw = message.mcp_servers;
|
|
3010
|
+
if (mcpServersRaw)
|
|
3011
|
+
try {
|
|
3012
|
+
recordMcpStatusFromSystemInit(mcpServersRaw);
|
|
3013
|
+
}
|
|
3014
|
+
catch { /* non-fatal */ }
|
|
3015
|
+
}
|
|
3002
3016
|
if (message.type === 'assistant') {
|
|
3003
3017
|
const blocks = getContentBlocks(message);
|
|
3004
3018
|
for (const block of blocks) {
|
|
@@ -22,6 +22,21 @@
|
|
|
22
22
|
* long-task preflight, NO mode=unleashed wrapper.
|
|
23
23
|
*/
|
|
24
24
|
import { type AgentDefinition } from '@anthropic-ai/claude-agent-sdk';
|
|
25
|
+
/** Read the latest MCP status snapshot. Safe to call from any module. */
|
|
26
|
+
export declare function getLatestMcpStatusSnapshot(): {
|
|
27
|
+
servers: Array<{
|
|
28
|
+
name: string;
|
|
29
|
+
status: string;
|
|
30
|
+
}>;
|
|
31
|
+
updatedAt: string;
|
|
32
|
+
};
|
|
33
|
+
/** Write a fresh snapshot. Called from system/init handlers. */
|
|
34
|
+
export declare function recordMcpStatusFromSystemInit(rawMcpServers: unknown): void;
|
|
35
|
+
/** Drop one server from the cache so the next query repopulates it. */
|
|
36
|
+
export declare function invalidateMcpStatusEntry(name: string): {
|
|
37
|
+
cleared: boolean;
|
|
38
|
+
updatedAt: string;
|
|
39
|
+
};
|
|
25
40
|
import type { AgentProfile } from '../types.js';
|
|
26
41
|
import type { AgentManager } from './agent-manager.js';
|
|
27
42
|
import type { MemoryStore } from '../memory/store.js';
|
package/dist/agent/run-agent.js
CHANGED
|
@@ -24,6 +24,49 @@
|
|
|
24
24
|
import path from 'node:path';
|
|
25
25
|
import { query } from '@anthropic-ai/claude-agent-sdk';
|
|
26
26
|
import pino from 'pino';
|
|
27
|
+
/**
|
|
28
|
+
* Module-level cache of MCP server statuses from the most recent SDK
|
|
29
|
+
* init message. Populated by every runAgent / PersonalAssistant query
|
|
30
|
+
* stream that captures `system/init`. Read by Assistant.getMcpStatus()
|
|
31
|
+
* and the dashboard's Tools & MCP catalog page.
|
|
32
|
+
*
|
|
33
|
+
* Pre-1.18.84 the assistant declared a private _lastMcpStatus but no
|
|
34
|
+
* code wrote to it — getMcpStatus() always returned empty, making the
|
|
35
|
+
* catalog status pills misleading. The shared module cache fixes that
|
|
36
|
+
* without coupling assistant.ts to runAgent's stream loop.
|
|
37
|
+
*/
|
|
38
|
+
let _lastMcpStatusSnapshot = {
|
|
39
|
+
servers: [],
|
|
40
|
+
updatedAt: '',
|
|
41
|
+
};
|
|
42
|
+
/** Read the latest MCP status snapshot. Safe to call from any module. */
|
|
43
|
+
export function getLatestMcpStatusSnapshot() {
|
|
44
|
+
return { servers: [..._lastMcpStatusSnapshot.servers], updatedAt: _lastMcpStatusSnapshot.updatedAt };
|
|
45
|
+
}
|
|
46
|
+
/** Write a fresh snapshot. Called from system/init handlers. */
|
|
47
|
+
export function recordMcpStatusFromSystemInit(rawMcpServers) {
|
|
48
|
+
if (!Array.isArray(rawMcpServers))
|
|
49
|
+
return;
|
|
50
|
+
const servers = [];
|
|
51
|
+
for (const entry of rawMcpServers) {
|
|
52
|
+
if (!entry || typeof entry !== 'object')
|
|
53
|
+
continue;
|
|
54
|
+
const e = entry;
|
|
55
|
+
if (typeof e.name !== 'string' || !e.name)
|
|
56
|
+
continue;
|
|
57
|
+
servers.push({ name: e.name, status: typeof e.status === 'string' ? e.status : 'unknown' });
|
|
58
|
+
}
|
|
59
|
+
_lastMcpStatusSnapshot = { servers, updatedAt: new Date().toISOString() };
|
|
60
|
+
}
|
|
61
|
+
/** Drop one server from the cache so the next query repopulates it. */
|
|
62
|
+
export function invalidateMcpStatusEntry(name) {
|
|
63
|
+
const before = _lastMcpStatusSnapshot.servers.length;
|
|
64
|
+
_lastMcpStatusSnapshot = {
|
|
65
|
+
servers: _lastMcpStatusSnapshot.servers.filter((s) => s.name !== name),
|
|
66
|
+
updatedAt: new Date().toISOString(),
|
|
67
|
+
};
|
|
68
|
+
return { cleared: _lastMcpStatusSnapshot.servers.length < before, updatedAt: _lastMcpStatusSnapshot.updatedAt };
|
|
69
|
+
}
|
|
27
70
|
import { BASE_DIR, PKG_DIR, CLAUDE_CODE_OAUTH_TOKEN, ANTHROPIC_API_KEY as CONFIG_ANTHROPIC_API_KEY, normalizeClaudeSdkOptionsForOneMillionContext, } from '../config.js';
|
|
28
71
|
import { buildAgentMap } from './agent-definitions.js';
|
|
29
72
|
const MCP_SERVER_SCRIPT = path.join(PKG_DIR, 'dist', 'tools', 'mcp-server.js');
|
|
@@ -229,6 +272,17 @@ export async function runAgent(prompt, opts) {
|
|
|
229
272
|
for await (const message of stream) {
|
|
230
273
|
if (message.type === 'system' && message.subtype === 'init') {
|
|
231
274
|
sessionId = message.session_id ?? '';
|
|
275
|
+
// PRD Phase 2 / 1.18.84 correctness: capture the SDK-reported MCP
|
|
276
|
+
// server status so getMcpStatus() (and the dashboard's Tools & MCP
|
|
277
|
+
// catalog) actually has data. The init message includes
|
|
278
|
+
// mcp_servers: Array<{ name, status }> per the SDK protocol.
|
|
279
|
+
const mcpServersRaw = message.mcp_servers;
|
|
280
|
+
if (mcpServersRaw) {
|
|
281
|
+
try {
|
|
282
|
+
recordMcpStatusFromSystemInit(mcpServersRaw);
|
|
283
|
+
}
|
|
284
|
+
catch { /* non-fatal */ }
|
|
285
|
+
}
|
|
232
286
|
logger.debug({ sessionKey: opts.sessionKey, sdkSessionId: sessionId }, 'runAgent: SDK session initialized');
|
|
233
287
|
continue;
|
|
234
288
|
}
|
package/dist/cli/cron.js
CHANGED
|
@@ -140,6 +140,10 @@ export async function cmdCronRun(jobName) {
|
|
|
140
140
|
try {
|
|
141
141
|
const response = await gateway.handleCronJob(job.name, job.prompt, job.tier, job.maxTurns, job.model, job.workDir, job.mode, job.maxHours);
|
|
142
142
|
const finishedAt = new Date();
|
|
143
|
+
// 1.18.84: trigger source comes from the CRON_RUN_TRIGGER env var set
|
|
144
|
+
// by the dashboard's manual-run endpoint. Defaults to 'scheduled' for
|
|
145
|
+
// any other invocation path (CLI, daemon-internal callsites).
|
|
146
|
+
const trigger = process.env.CRON_RUN_TRIGGER || 'scheduled';
|
|
143
147
|
const entry = {
|
|
144
148
|
jobName: job.name,
|
|
145
149
|
startedAt: startedAt.toISOString(),
|
|
@@ -148,6 +152,7 @@ export async function cmdCronRun(jobName) {
|
|
|
148
152
|
durationMs: finishedAt.getTime() - startedAt.getTime(),
|
|
149
153
|
attempt: 1,
|
|
150
154
|
outputPreview: response ? response.slice(0, 200) : undefined,
|
|
155
|
+
trigger,
|
|
151
156
|
};
|
|
152
157
|
// PRD Phase 1.1: goal-orientation evaluator (mirrors the daemon path).
|
|
153
158
|
if (job.successSchema || (job.successCriteriaText && job.successCriteriaText.trim())) {
|
|
@@ -170,6 +175,7 @@ export async function cmdCronRun(jobName) {
|
|
|
170
175
|
}
|
|
171
176
|
catch (err) {
|
|
172
177
|
const finishedAt = new Date();
|
|
178
|
+
const trigger = process.env.CRON_RUN_TRIGGER || 'scheduled';
|
|
173
179
|
runLog.append({
|
|
174
180
|
jobName: job.name,
|
|
175
181
|
startedAt: startedAt.toISOString(),
|
|
@@ -179,6 +185,7 @@ export async function cmdCronRun(jobName) {
|
|
|
179
185
|
error: String(err).slice(0, 500),
|
|
180
186
|
errorType: classifyError(err),
|
|
181
187
|
attempt: 1,
|
|
188
|
+
trigger,
|
|
182
189
|
});
|
|
183
190
|
console.error(`Error: ${err}`);
|
|
184
191
|
process.exit(1);
|
package/dist/cli/dashboard.js
CHANGED
|
@@ -4613,7 +4613,8 @@ export async function cmdDashboard(opts) {
|
|
|
4613
4613
|
detached: true,
|
|
4614
4614
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
4615
4615
|
cwd: BASE_DIR,
|
|
4616
|
-
|
|
4616
|
+
// 1.18.84: pass the trigger source so cron.ts stamps it on the run entry.
|
|
4617
|
+
env: { ...process.env, CLEMENTINE_HOME: BASE_DIR, CRON_RUN_TRIGGER: 'manual' },
|
|
4617
4618
|
});
|
|
4618
4619
|
// Capture stderr for error reporting
|
|
4619
4620
|
let stderr = '';
|
|
@@ -23811,12 +23812,13 @@ function renderRunListBody(allRuns) {
|
|
|
23811
23812
|
var startedAt = entry.startedAt ? new Date(entry.startedAt) : null;
|
|
23812
23813
|
var startedLabel = startedAt ? startedAt.toLocaleString() : '—';
|
|
23813
23814
|
var durationLabel = entry.durationMs != null ? formatDurationMs(entry.durationMs) : '—';
|
|
23814
|
-
//
|
|
23815
|
-
//
|
|
23816
|
-
|
|
23817
|
-
|
|
23818
|
-
|
|
23819
|
-
|
|
23815
|
+
// 1.18.84: real persisted trigger field. Falls back to a heuristic for
|
|
23816
|
+
// pre-1.18.84 run entries that don't have the field set.
|
|
23817
|
+
var triggerLabel = entry.trigger || (entry.attempt > 1 ? 'retry' : 'scheduled');
|
|
23818
|
+
var triggerColor = entry.trigger === 'manual' ? 'var(--accent)'
|
|
23819
|
+
: entry.trigger === 'after' ? 'var(--purple)'
|
|
23820
|
+
: entry.trigger === 'discord' ? 'var(--blue)'
|
|
23821
|
+
: 'var(--text-muted)';
|
|
23820
23822
|
// Goal cell
|
|
23821
23823
|
var goalCell = '<div></div>';
|
|
23822
23824
|
if (entry.goalCheck) {
|
|
@@ -23932,7 +23934,14 @@ async function refreshToolsMcpCatalog() {
|
|
|
23932
23934
|
try {
|
|
23933
23935
|
var sR = await apiFetch('/api/mcp-status');
|
|
23934
23936
|
var statusJson = await sR.json();
|
|
23935
|
-
|
|
23937
|
+
// /api/mcp-status returns { servers: [{name, status}], updatedAt }.
|
|
23938
|
+
// Build a name → entry lookup so renderMcpCatalogCard can probe by name.
|
|
23939
|
+
if (statusJson && Array.isArray(statusJson.servers)) {
|
|
23940
|
+
for (var si = 0; si < statusJson.servers.length; si++) {
|
|
23941
|
+
var entry = statusJson.servers[si];
|
|
23942
|
+
if (entry && entry.name) statusMap[entry.name] = entry;
|
|
23943
|
+
}
|
|
23944
|
+
}
|
|
23936
23945
|
} catch (e) { /* status is optional — servers still render without it */ }
|
|
23937
23946
|
try {
|
|
23938
23947
|
var lR = await apiFetch('/api/mcp-servers');
|
|
@@ -888,7 +888,7 @@ export class CronScheduler {
|
|
|
888
888
|
ctx.agentSlug = wf.agentSlug;
|
|
889
889
|
return ctx;
|
|
890
890
|
}
|
|
891
|
-
async runJob(job) {
|
|
891
|
+
async runJob(job, trigger = 'scheduled') {
|
|
892
892
|
const creditBlock = getBackgroundCreditBlock();
|
|
893
893
|
if (creditBlock) {
|
|
894
894
|
logger.warn({ job: job.name, until: creditBlock.until }, 'Cron job skipped — Claude credit block active');
|
|
@@ -1206,6 +1206,8 @@ export class CronScheduler {
|
|
|
1206
1206
|
outputPreview: response ? response.slice(0, 200) : undefined,
|
|
1207
1207
|
advisorApplied,
|
|
1208
1208
|
terminalReason,
|
|
1209
|
+
// 1.18.84: persist the actual trigger source for the Run list filter.
|
|
1210
|
+
trigger,
|
|
1209
1211
|
// Trick capability metadata — surfaced by the dashboard's
|
|
1210
1212
|
// "ran with: …" line. Omit empty arrays to keep the JSONL light.
|
|
1211
1213
|
...(cronMetadata?.skillsApplied?.length ? { skillsApplied: cronMetadata.skillsApplied } : {}),
|
|
@@ -1268,7 +1270,8 @@ export class CronScheduler {
|
|
|
1268
1270
|
const dependents = this.jobs.filter(j => j.after === job.name && j.enabled && !this.disabledJobs.has(j.name));
|
|
1269
1271
|
for (const dep of dependents) {
|
|
1270
1272
|
logger.info(`Chain: '${job.name}' succeeded — triggering '${dep.name}'`);
|
|
1271
|
-
|
|
1273
|
+
// 1.18.84: chained-after triggers carry trigger='after' for the Run list filter.
|
|
1274
|
+
this.runJob(dep, 'after').catch((err) => {
|
|
1272
1275
|
logger.error({ err, job: dep.name }, `Chained job '${dep.name}' failed`);
|
|
1273
1276
|
});
|
|
1274
1277
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -447,6 +447,11 @@ export interface CronRunEntry {
|
|
|
447
447
|
allowedToolsApplied?: string[];
|
|
448
448
|
/** MCP servers live for this run (post profile + trick allowlist intersection). */
|
|
449
449
|
mcpServersApplied?: string[];
|
|
450
|
+
/** PRD §6 / 1.18.84: how this run was triggered. Persisted by the
|
|
451
|
+
* scheduler (cron tick / chained 'after' / manual-run endpoint /
|
|
452
|
+
* Discord) so the Run list can filter by source instead of guessing
|
|
453
|
+
* via heuristics on attempt count. */
|
|
454
|
+
trigger?: 'manual' | 'scheduled' | 'webhook' | 'api' | 'fork' | 'resume' | 'discord' | 'after';
|
|
450
455
|
/** PRD Phase 1: did the run accomplish what it was supposed to?
|
|
451
456
|
* Computed at run-end when the Task has successSchema or successCriteriaText.
|
|
452
457
|
* - status='pass' both configured checks passed (or the only one configured did)
|