clementine-agent 1.7.0 → 1.8.0
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/cli/dashboard.js
CHANGED
|
@@ -15255,6 +15255,12 @@ function switchBuildTab(tab) {
|
|
|
15255
15255
|
if (typeof _ensureDrawflowLoaded === 'function') {
|
|
15256
15256
|
_ensureDrawflowLoaded().catch(function() { /* */ });
|
|
15257
15257
|
}
|
|
15258
|
+
// Populate the Owner picker once per session — agents rarely change
|
|
15259
|
+
// mid-session, so the cost is minimal and the dropdown is ready by
|
|
15260
|
+
// the time the user clicks New.
|
|
15261
|
+
if (typeof populateBuilderOwnerPicker === 'function') {
|
|
15262
|
+
populateBuilderOwnerPicker().catch(function() { /* */ });
|
|
15263
|
+
}
|
|
15258
15264
|
// Focus chat input
|
|
15259
15265
|
setTimeout(function() {
|
|
15260
15266
|
var bi = document.getElementById('builder-input');
|
|
@@ -15263,10 +15269,10 @@ function switchBuildTab(tab) {
|
|
|
15263
15269
|
}
|
|
15264
15270
|
}
|
|
15265
15271
|
|
|
15266
|
-
// "New" button in the Build header strip — context-aware
|
|
15267
|
-
//
|
|
15268
|
-
//
|
|
15269
|
-
//
|
|
15272
|
+
// "New" button in the Build header strip — context-aware. Crons open the
|
|
15273
|
+
// dedicated cron modal so the entry lands in CRON.md (not as a one-step
|
|
15274
|
+
// workflow file). Workflows route through /api/builder/workflows. Owner is
|
|
15275
|
+
// read from the header's Owner picker — empty string means global.
|
|
15270
15276
|
async function newFromBuildHeader() {
|
|
15271
15277
|
var activeTab = document.querySelector('#build-tabs button.active')?.getAttribute('data-build-tab') || 'workflows';
|
|
15272
15278
|
if (activeTab === 'skills') {
|
|
@@ -15279,22 +15285,72 @@ async function newFromBuildHeader() {
|
|
|
15279
15285
|
toast('Pick a template to fork from the cards.', 'info');
|
|
15280
15286
|
return;
|
|
15281
15287
|
}
|
|
15282
|
-
var
|
|
15283
|
-
|
|
15288
|
+
var owner = (document.getElementById('builder-owner') || {}).value || '';
|
|
15289
|
+
if (activeTab === 'crons') {
|
|
15290
|
+
if (typeof openCreateCronModal === 'function') {
|
|
15291
|
+
openCreateCronModal(owner);
|
|
15292
|
+
return;
|
|
15293
|
+
}
|
|
15294
|
+
}
|
|
15295
|
+
var name = prompt('Name your new workflow:');
|
|
15284
15296
|
if (!name || !name.trim()) return;
|
|
15285
15297
|
try {
|
|
15286
15298
|
var body = { name: name.trim() };
|
|
15287
|
-
if (
|
|
15299
|
+
if (owner) body.agent = owner;
|
|
15288
15300
|
var r = await apiJson('POST', '/api/builder/workflows', body);
|
|
15289
15301
|
if (r && r.error) { toast('Create failed: ' + r.error, 'error'); return; }
|
|
15290
15302
|
if (r && r.id) {
|
|
15291
|
-
await refreshBuilderCanvasPicker(
|
|
15303
|
+
await refreshBuilderCanvasPicker('workflow');
|
|
15292
15304
|
await openBuilderWorkflow(r.id);
|
|
15293
|
-
toast('Created ' +
|
|
15305
|
+
toast('Created workflow: ' + name + (owner ? ' (' + owner + ')' : ''), 'success');
|
|
15294
15306
|
}
|
|
15295
15307
|
} catch (err) { toast('Create error: ' + err, 'error'); }
|
|
15296
15308
|
}
|
|
15297
15309
|
|
|
15310
|
+
// Owner picker — populated from /api/agents on first build-tab activation
|
|
15311
|
+
// and refreshed on demand. Empty value = Clementine/global; any other value
|
|
15312
|
+
// is the agent slug for scoped reads/writes.
|
|
15313
|
+
var _builderOwnerPickerLoaded = false;
|
|
15314
|
+
async function populateBuilderOwnerPicker(force) {
|
|
15315
|
+
if (_builderOwnerPickerLoaded && !force) return;
|
|
15316
|
+
var sel = document.getElementById('builder-owner');
|
|
15317
|
+
if (!sel) return;
|
|
15318
|
+
try {
|
|
15319
|
+
var r = await apiFetch('/api/agents');
|
|
15320
|
+
var agents = r.ok ? await r.json() : [];
|
|
15321
|
+
var prev = sel.value;
|
|
15322
|
+
var opts = '<option value="">Clementine (global)</option>';
|
|
15323
|
+
if (Array.isArray(agents)) {
|
|
15324
|
+
for (var i = 0; i < agents.length; i++) {
|
|
15325
|
+
var slug = agents[i] && agents[i].slug;
|
|
15326
|
+
if (!slug) continue;
|
|
15327
|
+
var label = agents[i].name ? (agents[i].name + ' (' + slug + ')') : slug;
|
|
15328
|
+
opts += '<option value="' + esc(slug) + '">' + esc(label) + '</option>';
|
|
15329
|
+
}
|
|
15330
|
+
}
|
|
15331
|
+
sel.innerHTML = opts;
|
|
15332
|
+
if (prev) sel.value = prev;
|
|
15333
|
+
_builderOwnerPickerLoaded = true;
|
|
15334
|
+
} catch (err) {
|
|
15335
|
+
// Leave the default global option in place; not fatal.
|
|
15336
|
+
}
|
|
15337
|
+
}
|
|
15338
|
+
|
|
15339
|
+
// Mirror the visible owner selection into the legacy hidden builder-agent
|
|
15340
|
+
// input + label so chat/skill/agent flows that already read those keep
|
|
15341
|
+
// working, then refresh the canvas picker so the list re-filters.
|
|
15342
|
+
async function onBuilderOwnerChange() {
|
|
15343
|
+
var sel = document.getElementById('builder-owner');
|
|
15344
|
+
var owner = sel ? sel.value : '';
|
|
15345
|
+
var hidden = document.getElementById('builder-agent');
|
|
15346
|
+
var label = document.getElementById('builder-agent-label');
|
|
15347
|
+
if (hidden) hidden.value = owner || '';
|
|
15348
|
+
if (label) label.textContent = owner ? 'Owner: ' + owner : '';
|
|
15349
|
+
var typeSel = document.getElementById('builder-type');
|
|
15350
|
+
var type = typeSel && typeSel.value === 'cron' ? 'cron' : 'workflow';
|
|
15351
|
+
await refreshBuilderCanvasPicker(type);
|
|
15352
|
+
}
|
|
15353
|
+
|
|
15298
15354
|
// ── Build templates: fork a starter pattern into a new workflow ─────
|
|
15299
15355
|
async function forkBuildTemplate(templateId) {
|
|
15300
15356
|
var templates = {
|
|
@@ -19503,10 +19559,19 @@ async function refreshBuilderCanvasPicker(type) {
|
|
|
19503
19559
|
var picker = document.getElementById('builder-canvas-picker');
|
|
19504
19560
|
if (!picker) return;
|
|
19505
19561
|
try {
|
|
19562
|
+
var owner = (document.getElementById('builder-owner') || {}).value || '';
|
|
19506
19563
|
var r = await apiFetch('/api/builder/workflows');
|
|
19507
19564
|
var d = await r.json();
|
|
19508
|
-
var items = (d.workflows || []).filter(function(w) {
|
|
19509
|
-
|
|
19565
|
+
var items = (d.workflows || []).filter(function(w) {
|
|
19566
|
+
if (w.origin !== type) return false;
|
|
19567
|
+
// Owner filter: empty owner = Clementine/global only; named owner =
|
|
19568
|
+
// agent-scoped entries for that slug only. The serializer reports
|
|
19569
|
+
// scope='agent' for entries living under <AGENTS_DIR>/<slug>/.
|
|
19570
|
+
if (owner) return w.scope === 'agent' && w.agentSlug === owner;
|
|
19571
|
+
return w.scope !== 'agent';
|
|
19572
|
+
});
|
|
19573
|
+
var ownerLabel = owner ? '@' + owner : 'global';
|
|
19574
|
+
var opts = '<option value="">' + (items.length ? '— pick a ' + type + ' (' + ownerLabel + ') —' : '(none yet for ' + ownerLabel + ')') + '</option>';
|
|
19510
19575
|
for (var i = 0; i < items.length; i++) {
|
|
19511
19576
|
var w = items[i];
|
|
19512
19577
|
var lbl = w.name + (w.schedule ? ' · ' + w.schedule : '') + (w.enabled ? '' : ' · off');
|
|
@@ -23,6 +23,7 @@ export declare class HeartbeatScheduler {
|
|
|
23
23
|
private lastDenseBackfillAt;
|
|
24
24
|
private denseBackfillInFlight;
|
|
25
25
|
private lastSalienceDecayDate;
|
|
26
|
+
private lastMemoryPulseDate;
|
|
26
27
|
/** Wire up the cron scheduler so daily plan suggestions can be applied. */
|
|
27
28
|
setCronScheduler(cs: CronScheduler): void;
|
|
28
29
|
private getLastAgentSiRun;
|
|
@@ -53,6 +54,14 @@ export declare class HeartbeatScheduler {
|
|
|
53
54
|
* HeartbeatState. Pinned chunks exempt; soft-deleted and superseded skipped.
|
|
54
55
|
*/
|
|
55
56
|
private maybeRunSalienceDecay;
|
|
57
|
+
/**
|
|
58
|
+
* Weekly Memory Pulse — once-per-week observability report on the memory
|
|
59
|
+
* subsystem. Aggregates the same signals visible on Brain → Health
|
|
60
|
+
* (coverage, recent writes, supersedes, recall contribution) into a
|
|
61
|
+
* compact message and dispatches it. Skipped if nothing meaningful
|
|
62
|
+
* happened this week (avoids empty noise on quiet weeks).
|
|
63
|
+
*/
|
|
64
|
+
private maybeSendMemoryPulse;
|
|
56
65
|
private runInsightCheck;
|
|
57
66
|
/** Called when user replies to a proactive message — resets cooldown. */
|
|
58
67
|
recordInsightAcknowledged(): void;
|
|
@@ -33,6 +33,7 @@ export class HeartbeatScheduler {
|
|
|
33
33
|
lastDenseBackfillAt = 0;
|
|
34
34
|
denseBackfillInFlight = false;
|
|
35
35
|
lastSalienceDecayDate = '';
|
|
36
|
+
lastMemoryPulseDate = '';
|
|
36
37
|
/** Wire up the cron scheduler so daily plan suggestions can be applied. */
|
|
37
38
|
setCronScheduler(cs) { this.cronScheduler = cs; }
|
|
38
39
|
getLastAgentSiRun(slug) {
|
|
@@ -53,6 +54,8 @@ export class HeartbeatScheduler {
|
|
|
53
54
|
this.lastConsolidationDate = this.lastState.lastConsolidationDate;
|
|
54
55
|
if (this.lastState.lastSalienceDecayDate)
|
|
55
56
|
this.lastSalienceDecayDate = this.lastState.lastSalienceDecayDate;
|
|
57
|
+
if (this.lastState.lastMemoryPulseDate)
|
|
58
|
+
this.lastMemoryPulseDate = this.lastState.lastMemoryPulseDate;
|
|
56
59
|
if (this.lastState.lastAgentSiRuns) {
|
|
57
60
|
this.lastAgentSiRuns = new Map(Object.entries(this.lastState.lastAgentSiRuns));
|
|
58
61
|
}
|
|
@@ -305,6 +308,9 @@ export class HeartbeatScheduler {
|
|
|
305
308
|
}).catch(err => {
|
|
306
309
|
logger.warn({ err }, 'Weekly review failed');
|
|
307
310
|
});
|
|
311
|
+
// Memory Pulse — weekly observability report on the 5-phase memory
|
|
312
|
+
// system. Skipped if nothing happened (avoids empty noise on quiet weeks).
|
|
313
|
+
this.maybeSendMemoryPulse();
|
|
308
314
|
}
|
|
309
315
|
// First Monday of month: monthly assessment (between 8-9 PM)
|
|
310
316
|
if (now.getDay() === 1 && now.getDate() <= 7 && hour >= 20 && hour < 21) {
|
|
@@ -805,6 +811,74 @@ export class HeartbeatScheduler {
|
|
|
805
811
|
logger.debug({ err }, 'Salience decay sweep failed (non-fatal)');
|
|
806
812
|
}
|
|
807
813
|
}
|
|
814
|
+
/**
|
|
815
|
+
* Weekly Memory Pulse — once-per-week observability report on the memory
|
|
816
|
+
* subsystem. Aggregates the same signals visible on Brain → Health
|
|
817
|
+
* (coverage, recent writes, supersedes, recall contribution) into a
|
|
818
|
+
* compact message and dispatches it. Skipped if nothing meaningful
|
|
819
|
+
* happened this week (avoids empty noise on quiet weeks).
|
|
820
|
+
*/
|
|
821
|
+
maybeSendMemoryPulse() {
|
|
822
|
+
const today = todayISO();
|
|
823
|
+
if (this.lastMemoryPulseDate === today)
|
|
824
|
+
return;
|
|
825
|
+
const store = this.gateway.getMemoryStore();
|
|
826
|
+
if (!store)
|
|
827
|
+
return;
|
|
828
|
+
try {
|
|
829
|
+
const stats = store.getMemoryStats();
|
|
830
|
+
const supersedeStats = typeof store.getSupersedeStats === 'function'
|
|
831
|
+
? store.getSupersedeStats()
|
|
832
|
+
: { superseded: 0 };
|
|
833
|
+
const graphStats = typeof store.getGraphStats === 'function'
|
|
834
|
+
? store.getGraphStats({ lookbackHours: 24 * 7 })
|
|
835
|
+
: { wikilinkCount: 0, recallContributionByType: {}, tracesAnalyzed: 0 };
|
|
836
|
+
const recentWrites = typeof store.getRecentWrites === 'function'
|
|
837
|
+
? store
|
|
838
|
+
.getRecentWrites(500)
|
|
839
|
+
: [];
|
|
840
|
+
const weekAgoIso = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString();
|
|
841
|
+
const writesThisWeek = recentWrites.filter(w => w.extractedAt > weekAgoIso);
|
|
842
|
+
const supersedesThisWeek = writesThisWeek.filter(w => w.status === 'superseded').length;
|
|
843
|
+
const dedupedThisWeek = writesThisWeek.filter(w => w.status === 'dedup_skipped').length;
|
|
844
|
+
const writesWithSalience = writesThisWeek.filter(w => w.salienceHint != null).length;
|
|
845
|
+
// Skip if nothing happened (no writes, no traces) — don't spam empty reports
|
|
846
|
+
if (writesThisWeek.length === 0 && graphStats.tracesAnalyzed === 0)
|
|
847
|
+
return;
|
|
848
|
+
const coveragePct = stats.totalChunks > 0
|
|
849
|
+
? Math.round((stats.chunksWithDenseEmbeddings / stats.totalChunks) * 100)
|
|
850
|
+
: 0;
|
|
851
|
+
const cb = graphStats.recallContributionByType;
|
|
852
|
+
const totalMatches = Object.values(cb).reduce((a, b) => a + b, 0);
|
|
853
|
+
const pctOf = (n) => totalMatches > 0 ? Math.round((n / totalMatches) * 100) : 0;
|
|
854
|
+
const lines = [
|
|
855
|
+
`**Memory Pulse — last 7 days**`,
|
|
856
|
+
``,
|
|
857
|
+
`**Coverage:** ${coveragePct}% semantic (${stats.chunksWithDenseEmbeddings.toLocaleString()}/${stats.totalChunks.toLocaleString()} chunks)`,
|
|
858
|
+
];
|
|
859
|
+
if (writesThisWeek.length > 0) {
|
|
860
|
+
lines.push(`**Writes:** ${writesThisWeek.length} captured`
|
|
861
|
+
+ (writesWithSalience > 0 ? `, ${writesWithSalience} with salience hint` : '')
|
|
862
|
+
+ (dedupedThisWeek > 0 ? `, ${dedupedThisWeek} reinforced` : ''));
|
|
863
|
+
}
|
|
864
|
+
if (supersedesThisWeek > 0 || supersedeStats.superseded > 0) {
|
|
865
|
+
lines.push(`**Self-correction:** ${supersedesThisWeek} this week, ${supersedeStats.superseded} all-time`);
|
|
866
|
+
}
|
|
867
|
+
if (graphStats.tracesAnalyzed > 0 && totalMatches > 0) {
|
|
868
|
+
lines.push(`**Recall mix:** ${pctOf(cb.fts ?? 0)}% lexical · ${pctOf(cb.vector ?? 0)}% semantic · ${pctOf(cb.graph ?? 0)}% graph · ${pctOf(cb.recency ?? 0)}% recent`);
|
|
869
|
+
}
|
|
870
|
+
lines.push(``);
|
|
871
|
+
lines.push(`Full breakdown on the dashboard: Brain → Health.`);
|
|
872
|
+
this.dispatcher.send(lines.join('\n')).catch(err => logger.debug({ err }, 'Failed to send memory pulse'));
|
|
873
|
+
this.lastMemoryPulseDate = today;
|
|
874
|
+
this.lastState.lastMemoryPulseDate = today;
|
|
875
|
+
this.saveState();
|
|
876
|
+
logger.info({ coveragePct, writesThisWeek: writesThisWeek.length, supersedesThisWeek }, 'Memory Pulse sent');
|
|
877
|
+
}
|
|
878
|
+
catch (err) {
|
|
879
|
+
logger.debug({ err }, 'Memory Pulse failed (non-fatal)');
|
|
880
|
+
}
|
|
881
|
+
}
|
|
808
882
|
async runInsightCheck() {
|
|
809
883
|
// Initialize insight state if needed
|
|
810
884
|
if (!this.lastState.insightState) {
|
package/dist/types.d.ts
CHANGED
|
@@ -216,6 +216,7 @@ export interface HeartbeatState {
|
|
|
216
216
|
lastAgentSiRuns?: Record<string, string>;
|
|
217
217
|
lastSkillDecayDate?: string;
|
|
218
218
|
lastSalienceDecayDate?: string;
|
|
219
|
+
lastMemoryPulseDate?: string;
|
|
219
220
|
/** Proactive insight engine state */
|
|
220
221
|
insightState?: {
|
|
221
222
|
sentToday: string[];
|