clementine-agent 1.1.28 → 1.1.29
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/index.js +163 -8
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -3862,20 +3862,175 @@ const siCmd = program
|
|
|
3862
3862
|
.description('Manage Clementine self-improvement');
|
|
3863
3863
|
siCmd
|
|
3864
3864
|
.command('status')
|
|
3865
|
-
.description('Show self-improvement
|
|
3866
|
-
.
|
|
3865
|
+
.description('Show self-improvement health — last cycle, infra errors, per-agent runs, recent activity')
|
|
3866
|
+
.option('--json', 'Emit machine-readable JSON')
|
|
3867
|
+
.action(async (opts) => {
|
|
3868
|
+
const BOLD = '\x1b[1m';
|
|
3869
|
+
const DIM = '\x1b[0;90m';
|
|
3870
|
+
const GREEN = '\x1b[0;32m';
|
|
3871
|
+
const YELLOW = '\x1b[1;33m';
|
|
3872
|
+
const RED = '\x1b[0;31m';
|
|
3873
|
+
const CYAN = '\x1b[0;36m';
|
|
3874
|
+
const RESET = '\x1b[0m';
|
|
3867
3875
|
try {
|
|
3876
|
+
process.env.CLEMENTINE_HOME = BASE_DIR;
|
|
3868
3877
|
const { SelfImproveLoop } = await import('../agent/self-improve.js');
|
|
3869
3878
|
const { PersonalAssistant } = await import('../agent/assistant.js');
|
|
3870
3879
|
const assistant = new PersonalAssistant();
|
|
3871
3880
|
const loop = new SelfImproveLoop(assistant);
|
|
3872
3881
|
const state = loop.loadState();
|
|
3873
|
-
const
|
|
3874
|
-
|
|
3875
|
-
|
|
3876
|
-
|
|
3877
|
-
|
|
3878
|
-
|
|
3882
|
+
const log = loop.loadExperimentLog();
|
|
3883
|
+
const pending = loop.getPendingChanges();
|
|
3884
|
+
// Compute "last successful cycle" — most recent log entry that wasn't
|
|
3885
|
+
// a plateau record or pure infra failure. Different from lastRunAt
|
|
3886
|
+
// (which moves on every attempt, even crashed ones).
|
|
3887
|
+
const lastSuccessful = [...log].reverse().find(e => e.area !== 'soul' || e.hypothesis !== 'No new hypothesis — diversity constraint exhausted');
|
|
3888
|
+
const nowMs = Date.now();
|
|
3889
|
+
const formatAge = (iso) => {
|
|
3890
|
+
if (!iso)
|
|
3891
|
+
return 'never';
|
|
3892
|
+
const ms = nowMs - Date.parse(iso);
|
|
3893
|
+
const h = Math.floor(ms / 3_600_000);
|
|
3894
|
+
if (h < 1)
|
|
3895
|
+
return `${Math.floor(ms / 60_000)}m ago`;
|
|
3896
|
+
if (h < 48)
|
|
3897
|
+
return `${h}h ago`;
|
|
3898
|
+
return `${Math.floor(h / 24)}d ago`;
|
|
3899
|
+
};
|
|
3900
|
+
// Auto-applied count over the last 7 days = experiments with status 'approved'
|
|
3901
|
+
const since7d = nowMs - 7 * 86_400_000;
|
|
3902
|
+
const autoAppliedRecent = log.filter(e => e.approvalStatus === 'approved' && Date.parse(e.startedAt) >= since7d).length;
|
|
3903
|
+
// Per-agent SI runs from heartbeat state file.
|
|
3904
|
+
const hbStateFile = path.join(BASE_DIR, '.heartbeat_state.json');
|
|
3905
|
+
let perAgentRuns = {};
|
|
3906
|
+
try {
|
|
3907
|
+
if (existsSync(hbStateFile)) {
|
|
3908
|
+
const hb = JSON.parse(readFileSync(hbStateFile, 'utf-8'));
|
|
3909
|
+
perAgentRuns = (hb.lastAgentSiRuns ?? {});
|
|
3910
|
+
}
|
|
3911
|
+
}
|
|
3912
|
+
catch { /* non-fatal */ }
|
|
3913
|
+
if (opts.json) {
|
|
3914
|
+
console.log(JSON.stringify({
|
|
3915
|
+
state,
|
|
3916
|
+
lastSuccessfulAt: lastSuccessful?.startedAt ?? null,
|
|
3917
|
+
autoAppliedLast7d: autoAppliedRecent,
|
|
3918
|
+
pendingCount: pending.length,
|
|
3919
|
+
perAgentRuns,
|
|
3920
|
+
recent: log.slice(-5).reverse(),
|
|
3921
|
+
}, null, 2));
|
|
3922
|
+
return;
|
|
3923
|
+
}
|
|
3924
|
+
// ── Header ─────────────────────────────────────────────────────
|
|
3925
|
+
console.log();
|
|
3926
|
+
console.log(` ${BOLD}Self-improve loop${RESET}`);
|
|
3927
|
+
console.log(` ${DIM}Status: ${RESET}${state.status}`);
|
|
3928
|
+
console.log(` ${DIM}Last attempted: ${RESET}${state.lastRunAt || 'never'} ${DIM}(${formatAge(state.lastRunAt)})${RESET}`);
|
|
3929
|
+
// Stalled-loop warning: if we have a lastRunAt but no successful cycle
|
|
3930
|
+
// in 36+ hours, surface red. That's the visibility gap from pillar #4.
|
|
3931
|
+
const lastSuccAt = lastSuccessful?.startedAt;
|
|
3932
|
+
const hoursSinceSuccess = lastSuccAt ? (nowMs - Date.parse(lastSuccAt)) / 3_600_000 : null;
|
|
3933
|
+
if (lastSuccAt) {
|
|
3934
|
+
const stallTag = hoursSinceSuccess !== null && hoursSinceSuccess > 36 ? ` ${YELLOW}⚠ stalled${RESET}` : ` ${GREEN}✓${RESET}`;
|
|
3935
|
+
console.log(` ${DIM}Last successful: ${RESET}${lastSuccAt} ${DIM}(${formatAge(lastSuccAt)})${RESET}${stallTag}`);
|
|
3936
|
+
}
|
|
3937
|
+
else {
|
|
3938
|
+
console.log(` ${DIM}Last successful: ${RESET}never ${YELLOW}⚠ no cycles yet${RESET}`);
|
|
3939
|
+
}
|
|
3940
|
+
console.log(` ${DIM}Total experiments:${RESET} ${state.totalExperiments}`);
|
|
3941
|
+
console.log(` ${DIM}Auto-applied (7d):${RESET} ${autoAppliedRecent}`);
|
|
3942
|
+
console.log(` ${DIM}Pending review: ${RESET}${pending.length > 0 ? `${YELLOW}${pending.length}${RESET}` : '0'}`);
|
|
3943
|
+
if (state.infraError) {
|
|
3944
|
+
console.log();
|
|
3945
|
+
console.log(` ${RED}⚠ Infra error blocking the loop:${RESET}`);
|
|
3946
|
+
console.log(` Category: ${state.infraError.category}`);
|
|
3947
|
+
console.log(` Diagnostic: ${state.infraError.diagnostic.slice(0, 200)}`);
|
|
3948
|
+
}
|
|
3949
|
+
else {
|
|
3950
|
+
console.log(` ${DIM}Infra errors: ${RESET}${GREEN}none${RESET}`);
|
|
3951
|
+
}
|
|
3952
|
+
// ── Per-agent cycles ───────────────────────────────────────────
|
|
3953
|
+
const agentEntries = Object.entries(perAgentRuns);
|
|
3954
|
+
if (agentEntries.length > 0) {
|
|
3955
|
+
console.log();
|
|
3956
|
+
console.log(` ${BOLD}Per-agent cycles${RESET} ${DIM}(weekly cadence, 2 AM)${RESET}`);
|
|
3957
|
+
for (const [slug, iso] of agentEntries) {
|
|
3958
|
+
console.log(` ${CYAN}${slug.padEnd(28)}${RESET}${DIM}last run ${formatAge(iso)}${RESET}`);
|
|
3959
|
+
}
|
|
3960
|
+
}
|
|
3961
|
+
// ── Recent activity ────────────────────────────────────────────
|
|
3962
|
+
const recent = log.slice(-5).reverse();
|
|
3963
|
+
if (recent.length > 0) {
|
|
3964
|
+
console.log();
|
|
3965
|
+
console.log(` ${BOLD}Recent activity${RESET}`);
|
|
3966
|
+
for (const e of recent) {
|
|
3967
|
+
const score = (e.score * 10).toFixed(1);
|
|
3968
|
+
let icon = '❌';
|
|
3969
|
+
if (e.approvalStatus === 'approved')
|
|
3970
|
+
icon = '✅';
|
|
3971
|
+
else if (e.approvalStatus === 'pending')
|
|
3972
|
+
icon = '⏳';
|
|
3973
|
+
else if (e.approvalStatus === 'unsurfaced')
|
|
3974
|
+
icon = '⛔';
|
|
3975
|
+
const what = e.hypothesis.slice(0, 60);
|
|
3976
|
+
console.log(` ${icon} ${DIM}#${String(e.iteration).padEnd(3)}${RESET} ${e.area.padEnd(16)} ${score.padStart(4)}/10 "${what}"`);
|
|
3977
|
+
}
|
|
3978
|
+
}
|
|
3979
|
+
if (pending.length > 0) {
|
|
3980
|
+
console.log();
|
|
3981
|
+
console.log(` ${YELLOW}${pending.length} change(s) pending your review${RESET}`);
|
|
3982
|
+
console.log(` ${BOLD}clementine self-improve pending${RESET} ${DIM}— see what they propose${RESET}`);
|
|
3983
|
+
console.log(` ${BOLD}clementine self-improve apply <id>${RESET} ${DIM}— approve and apply one${RESET}`);
|
|
3984
|
+
}
|
|
3985
|
+
console.log();
|
|
3986
|
+
}
|
|
3987
|
+
catch (err) {
|
|
3988
|
+
console.error('Error:', err);
|
|
3989
|
+
process.exit(1);
|
|
3990
|
+
}
|
|
3991
|
+
});
|
|
3992
|
+
siCmd
|
|
3993
|
+
.command('pending')
|
|
3994
|
+
.description('List pending self-improve changes — what needs your review')
|
|
3995
|
+
.option('--json', 'Emit machine-readable JSON')
|
|
3996
|
+
.action(async (opts) => {
|
|
3997
|
+
const BOLD = '\x1b[1m';
|
|
3998
|
+
const DIM = '\x1b[0;90m';
|
|
3999
|
+
const YELLOW = '\x1b[1;33m';
|
|
4000
|
+
const CYAN = '\x1b[0;36m';
|
|
4001
|
+
const RESET = '\x1b[0m';
|
|
4002
|
+
try {
|
|
4003
|
+
process.env.CLEMENTINE_HOME = BASE_DIR;
|
|
4004
|
+
const { SelfImproveLoop } = await import('../agent/self-improve.js');
|
|
4005
|
+
const { PersonalAssistant } = await import('../agent/assistant.js');
|
|
4006
|
+
const assistant = new PersonalAssistant();
|
|
4007
|
+
const loop = new SelfImproveLoop(assistant);
|
|
4008
|
+
const pending = loop.getPendingChanges();
|
|
4009
|
+
if (opts.json) {
|
|
4010
|
+
console.log(JSON.stringify(pending.map(p => ({
|
|
4011
|
+
id: p.id, area: p.area, target: p.target,
|
|
4012
|
+
score: p.score, hypothesis: p.hypothesis, reason: p.reason,
|
|
4013
|
+
})), null, 2));
|
|
4014
|
+
return;
|
|
4015
|
+
}
|
|
4016
|
+
if (pending.length === 0) {
|
|
4017
|
+
console.log();
|
|
4018
|
+
console.log(` ${DIM}No changes pending review.${RESET}`);
|
|
4019
|
+
console.log();
|
|
4020
|
+
return;
|
|
4021
|
+
}
|
|
4022
|
+
console.log();
|
|
4023
|
+
console.log(` ${YELLOW}${pending.length} change${pending.length === 1 ? '' : 's'} pending${RESET}`);
|
|
4024
|
+
console.log();
|
|
4025
|
+
for (const p of pending) {
|
|
4026
|
+
const score = (p.score * 10).toFixed(1);
|
|
4027
|
+
console.log(` ${BOLD}#${p.id}${RESET} ${CYAN}${p.area}${RESET} ${DIM}→${RESET} ${p.target} ${DIM}(score ${score}/10)${RESET}`);
|
|
4028
|
+
console.log(` ${p.hypothesis}`);
|
|
4029
|
+
console.log(` ${DIM}${p.reason}${RESET}`);
|
|
4030
|
+
console.log();
|
|
4031
|
+
}
|
|
4032
|
+
console.log(` Apply: ${BOLD}clementine self-improve apply <id>${RESET}`);
|
|
4033
|
+
console.log();
|
|
3879
4034
|
}
|
|
3880
4035
|
catch (err) {
|
|
3881
4036
|
console.error('Error:', err);
|