clementine-agent 1.0.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/.env.example +44 -0
- package/LICENSE +21 -0
- package/README.md +795 -0
- package/dist/agent/agent-manager.d.ts +69 -0
- package/dist/agent/agent-manager.js +441 -0
- package/dist/agent/assistant.d.ts +225 -0
- package/dist/agent/assistant.js +3888 -0
- package/dist/agent/auto-update.d.ts +32 -0
- package/dist/agent/auto-update.js +186 -0
- package/dist/agent/daily-planner.d.ts +24 -0
- package/dist/agent/daily-planner.js +379 -0
- package/dist/agent/execution-advisor.d.ts +10 -0
- package/dist/agent/execution-advisor.js +272 -0
- package/dist/agent/hooks.d.ts +45 -0
- package/dist/agent/hooks.js +564 -0
- package/dist/agent/insight-engine.d.ts +66 -0
- package/dist/agent/insight-engine.js +225 -0
- package/dist/agent/intent-classifier.d.ts +48 -0
- package/dist/agent/intent-classifier.js +214 -0
- package/dist/agent/link-extractor.d.ts +19 -0
- package/dist/agent/link-extractor.js +90 -0
- package/dist/agent/mcp-bridge.d.ts +62 -0
- package/dist/agent/mcp-bridge.js +435 -0
- package/dist/agent/metacognition.d.ts +66 -0
- package/dist/agent/metacognition.js +221 -0
- package/dist/agent/orchestrator.d.ts +81 -0
- package/dist/agent/orchestrator.js +790 -0
- package/dist/agent/profiles.d.ts +22 -0
- package/dist/agent/profiles.js +91 -0
- package/dist/agent/prompt-cache.d.ts +24 -0
- package/dist/agent/prompt-cache.js +68 -0
- package/dist/agent/prompt-evolver.d.ts +28 -0
- package/dist/agent/prompt-evolver.js +279 -0
- package/dist/agent/role-scaffolds.d.ts +28 -0
- package/dist/agent/role-scaffolds.js +433 -0
- package/dist/agent/safe-restart.d.ts +41 -0
- package/dist/agent/safe-restart.js +150 -0
- package/dist/agent/self-improve.d.ts +66 -0
- package/dist/agent/self-improve.js +1706 -0
- package/dist/agent/session-event-log.d.ts +114 -0
- package/dist/agent/session-event-log.js +233 -0
- package/dist/agent/skill-extractor.d.ts +72 -0
- package/dist/agent/skill-extractor.js +435 -0
- package/dist/agent/source-mods.d.ts +61 -0
- package/dist/agent/source-mods.js +230 -0
- package/dist/agent/source-preflight.d.ts +25 -0
- package/dist/agent/source-preflight.js +100 -0
- package/dist/agent/stall-guard.d.ts +62 -0
- package/dist/agent/stall-guard.js +109 -0
- package/dist/agent/strategic-planner.d.ts +60 -0
- package/dist/agent/strategic-planner.js +352 -0
- package/dist/agent/team-bus.d.ts +89 -0
- package/dist/agent/team-bus.js +556 -0
- package/dist/agent/team-router.d.ts +26 -0
- package/dist/agent/team-router.js +37 -0
- package/dist/agent/tool-loop-detector.d.ts +59 -0
- package/dist/agent/tool-loop-detector.js +242 -0
- package/dist/agent/workflow-runner.d.ts +36 -0
- package/dist/agent/workflow-runner.js +317 -0
- package/dist/agent/workflow-variables.d.ts +16 -0
- package/dist/agent/workflow-variables.js +62 -0
- package/dist/channels/discord-agent-bot.d.ts +101 -0
- package/dist/channels/discord-agent-bot.js +881 -0
- package/dist/channels/discord-bot-manager.d.ts +80 -0
- package/dist/channels/discord-bot-manager.js +262 -0
- package/dist/channels/discord-utils.d.ts +51 -0
- package/dist/channels/discord-utils.js +293 -0
- package/dist/channels/discord.d.ts +12 -0
- package/dist/channels/discord.js +1832 -0
- package/dist/channels/slack-agent-bot.d.ts +73 -0
- package/dist/channels/slack-agent-bot.js +320 -0
- package/dist/channels/slack-bot-manager.d.ts +66 -0
- package/dist/channels/slack-bot-manager.js +236 -0
- package/dist/channels/slack-utils.d.ts +39 -0
- package/dist/channels/slack-utils.js +189 -0
- package/dist/channels/slack.d.ts +11 -0
- package/dist/channels/slack.js +196 -0
- package/dist/channels/telegram.d.ts +10 -0
- package/dist/channels/telegram.js +235 -0
- package/dist/channels/webhook.d.ts +9 -0
- package/dist/channels/webhook.js +78 -0
- package/dist/channels/whatsapp.d.ts +11 -0
- package/dist/channels/whatsapp.js +181 -0
- package/dist/cli/chat.d.ts +14 -0
- package/dist/cli/chat.js +220 -0
- package/dist/cli/cron.d.ts +17 -0
- package/dist/cli/cron.js +552 -0
- package/dist/cli/dashboard.d.ts +15 -0
- package/dist/cli/dashboard.js +17677 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.js +2474 -0
- package/dist/cli/routes/delegations.d.ts +19 -0
- package/dist/cli/routes/delegations.js +154 -0
- package/dist/cli/routes/digest.d.ts +17 -0
- package/dist/cli/routes/digest.js +375 -0
- package/dist/cli/routes/goals.d.ts +14 -0
- package/dist/cli/routes/goals.js +258 -0
- package/dist/cli/routes/workflows.d.ts +18 -0
- package/dist/cli/routes/workflows.js +97 -0
- package/dist/cli/setup.d.ts +8 -0
- package/dist/cli/setup.js +619 -0
- package/dist/cli/tunnel.d.ts +35 -0
- package/dist/cli/tunnel.js +141 -0
- package/dist/config.d.ts +145 -0
- package/dist/config.js +278 -0
- package/dist/events/bus.d.ts +43 -0
- package/dist/events/bus.js +136 -0
- package/dist/gateway/cron-scheduler.d.ts +166 -0
- package/dist/gateway/cron-scheduler.js +1767 -0
- package/dist/gateway/delivery-queue.d.ts +30 -0
- package/dist/gateway/delivery-queue.js +110 -0
- package/dist/gateway/heartbeat-scheduler.d.ts +99 -0
- package/dist/gateway/heartbeat-scheduler.js +1298 -0
- package/dist/gateway/heartbeat.d.ts +3 -0
- package/dist/gateway/heartbeat.js +3 -0
- package/dist/gateway/lanes.d.ts +24 -0
- package/dist/gateway/lanes.js +76 -0
- package/dist/gateway/notifications.d.ts +29 -0
- package/dist/gateway/notifications.js +75 -0
- package/dist/gateway/router.d.ts +210 -0
- package/dist/gateway/router.js +1330 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +1015 -0
- package/dist/memory/chunker.d.ts +28 -0
- package/dist/memory/chunker.js +226 -0
- package/dist/memory/consolidation.d.ts +44 -0
- package/dist/memory/consolidation.js +171 -0
- package/dist/memory/context-assembler.d.ts +50 -0
- package/dist/memory/context-assembler.js +149 -0
- package/dist/memory/embeddings.d.ts +38 -0
- package/dist/memory/embeddings.js +180 -0
- package/dist/memory/graph-store.d.ts +66 -0
- package/dist/memory/graph-store.js +613 -0
- package/dist/memory/mmr.d.ts +21 -0
- package/dist/memory/mmr.js +75 -0
- package/dist/memory/search.d.ts +26 -0
- package/dist/memory/search.js +67 -0
- package/dist/memory/store.d.ts +530 -0
- package/dist/memory/store.js +2022 -0
- package/dist/security/integrity.d.ts +24 -0
- package/dist/security/integrity.js +58 -0
- package/dist/security/patterns.d.ts +34 -0
- package/dist/security/patterns.js +110 -0
- package/dist/security/scanner.d.ts +32 -0
- package/dist/security/scanner.js +263 -0
- package/dist/tools/admin-tools.d.ts +12 -0
- package/dist/tools/admin-tools.js +1278 -0
- package/dist/tools/external-tools.d.ts +11 -0
- package/dist/tools/external-tools.js +1327 -0
- package/dist/tools/goal-tools.d.ts +9 -0
- package/dist/tools/goal-tools.js +159 -0
- package/dist/tools/mcp-server.d.ts +13 -0
- package/dist/tools/mcp-server.js +141 -0
- package/dist/tools/memory-tools.d.ts +10 -0
- package/dist/tools/memory-tools.js +568 -0
- package/dist/tools/session-tools.d.ts +6 -0
- package/dist/tools/session-tools.js +146 -0
- package/dist/tools/shared.d.ts +216 -0
- package/dist/tools/shared.js +340 -0
- package/dist/tools/team-tools.d.ts +6 -0
- package/dist/tools/team-tools.js +447 -0
- package/dist/tools/tool-meta.d.ts +34 -0
- package/dist/tools/tool-meta.js +133 -0
- package/dist/tools/vault-tools.d.ts +8 -0
- package/dist/tools/vault-tools.js +457 -0
- package/dist/types.d.ts +716 -0
- package/dist/types.js +16 -0
- package/dist/vault-migrations/0001-add-execution-framework.d.ts +10 -0
- package/dist/vault-migrations/0001-add-execution-framework.js +47 -0
- package/dist/vault-migrations/0002-add-agentic-communication.d.ts +12 -0
- package/dist/vault-migrations/0002-add-agentic-communication.js +79 -0
- package/dist/vault-migrations/0003-update-execution-pipeline-narration.d.ts +11 -0
- package/dist/vault-migrations/0003-update-execution-pipeline-narration.js +73 -0
- package/dist/vault-migrations/helpers.d.ts +14 -0
- package/dist/vault-migrations/helpers.js +44 -0
- package/dist/vault-migrations/runner.d.ts +14 -0
- package/dist/vault-migrations/runner.js +139 -0
- package/dist/vault-migrations/types.d.ts +42 -0
- package/dist/vault-migrations/types.js +9 -0
- package/install.sh +320 -0
- package/package.json +84 -0
- package/scripts/postinstall.js +125 -0
- package/vault/00-System/AGENTS.md +66 -0
- package/vault/00-System/CRON.md +71 -0
- package/vault/00-System/HEARTBEAT.md +58 -0
- package/vault/00-System/MEMORY.md +16 -0
- package/vault/00-System/SOUL.md +96 -0
- package/vault/05-Tasks/TASKS.md +19 -0
- package/vault/06-Templates/_Daily-Template.md +28 -0
- package/vault/06-Templates/_People-Template.md +22 -0
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Clementine TypeScript — Execution Advisor.
|
|
3
|
+
*
|
|
4
|
+
* Analyzes recent run history and reflection data to adaptively tune
|
|
5
|
+
* cron job execution parameters: turn limits, models, timeouts, prompt
|
|
6
|
+
* enrichment, escalation, and circuit-breaking.
|
|
7
|
+
*/
|
|
8
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
import pino from 'pino';
|
|
11
|
+
import { CRON_REFLECTIONS_DIR, ADVISOR_LOG_PATH } from '../config.js';
|
|
12
|
+
import { CronRunLog } from '../gateway/heartbeat.js';
|
|
13
|
+
import { evolvePrompt } from './prompt-evolver.js';
|
|
14
|
+
const logger = pino({ name: 'clementine.execution-advisor' });
|
|
15
|
+
// ── Tier caps for maxTurns ──────────────────────────────────────────
|
|
16
|
+
const TIER_MAX_TURNS = {
|
|
17
|
+
1: 15,
|
|
18
|
+
2: 50,
|
|
19
|
+
};
|
|
20
|
+
const DEFAULT_TIMEOUT_MS = 600_000; // 10 minutes
|
|
21
|
+
const MAX_TIMEOUT_MS = 20 * 60 * 1000; // 20 minutes
|
|
22
|
+
const CIRCUIT_BREAKER_COOLDOWN_MS = 60 * 60 * 1000; // 1 hour between retry probes
|
|
23
|
+
// ── Core function ───────────────────────────────────────────────────
|
|
24
|
+
export function getExecutionAdvice(jobName, job) {
|
|
25
|
+
const advice = {
|
|
26
|
+
adjustedMaxTurns: null,
|
|
27
|
+
adjustedModel: null,
|
|
28
|
+
adjustedTimeoutMs: null,
|
|
29
|
+
promptEnrichment: '',
|
|
30
|
+
shouldEscalate: false,
|
|
31
|
+
shouldSkip: false,
|
|
32
|
+
};
|
|
33
|
+
try {
|
|
34
|
+
const runLog = new CronRunLog();
|
|
35
|
+
const recentRuns = runLog.readRecent(jobName, 10);
|
|
36
|
+
const reflections = readReflections(jobName);
|
|
37
|
+
const consecutiveErrors = runLog.consecutiveErrors(jobName);
|
|
38
|
+
// ── Rule 1: Circuit breaker — 5+ consecutive errors ──────────
|
|
39
|
+
// Allow a recovery probe once per hour so the breaker can self-heal.
|
|
40
|
+
if (consecutiveErrors >= 5) {
|
|
41
|
+
const lastRun = recentRuns[0];
|
|
42
|
+
// If no runs exist (shouldn't happen with 5+ errors, but be safe), skip immediately
|
|
43
|
+
if (!lastRun) {
|
|
44
|
+
advice.shouldSkip = true;
|
|
45
|
+
advice.skipReason = `${consecutiveErrors} consecutive errors — circuit breaker engaged`;
|
|
46
|
+
return advice;
|
|
47
|
+
}
|
|
48
|
+
const lastRunTime = new Date(lastRun.finishedAt).getTime();
|
|
49
|
+
const elapsed = Date.now() - lastRunTime;
|
|
50
|
+
if (elapsed < CIRCUIT_BREAKER_COOLDOWN_MS) {
|
|
51
|
+
advice.shouldSkip = true;
|
|
52
|
+
advice.skipReason = `${consecutiveErrors} consecutive errors — circuit breaker engaged (next probe in ${Math.ceil((CIRCUIT_BREAKER_COOLDOWN_MS - elapsed) / 60_000)}m)`;
|
|
53
|
+
logger.debug({ job: jobName, consecutiveErrors, nextProbeMin: Math.ceil((CIRCUIT_BREAKER_COOLDOWN_MS - elapsed) / 60_000) }, 'Circuit breaker — cooling down');
|
|
54
|
+
return advice;
|
|
55
|
+
}
|
|
56
|
+
// Cooldown elapsed — allow a recovery probe
|
|
57
|
+
logger.info({ job: jobName, consecutiveErrors }, 'Circuit breaker — allowing recovery probe after cooldown');
|
|
58
|
+
}
|
|
59
|
+
// ── Learn from past outcomes ────────────────────────────────
|
|
60
|
+
const outcomeStats = getInterventionStats(jobName);
|
|
61
|
+
// ── Rule 2: Turn-limit hits → increase maxTurns ──────────────
|
|
62
|
+
checkTurnLimitHits(recentRuns, job, advice);
|
|
63
|
+
// Suppress if turn adjustments have proven ineffective
|
|
64
|
+
if (advice.adjustedMaxTurns && outcomeStats.turnAdjustSuccessRate !== null && outcomeStats.turnAdjustSuccessRate < 0.2) {
|
|
65
|
+
logger.info({ job: jobName, rate: outcomeStats.turnAdjustSuccessRate }, 'Suppressing turn adjustment — historically ineffective');
|
|
66
|
+
advice.adjustedMaxTurns = null;
|
|
67
|
+
}
|
|
68
|
+
// ── Rule 3: Low reflection quality → prompt enrichment ───────
|
|
69
|
+
checkReflectionQuality(reflections, job, advice);
|
|
70
|
+
// Suppress if enrichment has proven ineffective
|
|
71
|
+
if (advice.promptEnrichment && outcomeStats.enrichmentSuccessRate !== null && outcomeStats.enrichmentSuccessRate < 0.2) {
|
|
72
|
+
logger.info({ job: jobName, rate: outcomeStats.enrichmentSuccessRate }, 'Suppressing prompt enrichment — historically ineffective');
|
|
73
|
+
advice.promptEnrichment = '';
|
|
74
|
+
}
|
|
75
|
+
// ── Rule 4: Repeated failures on haiku → upgrade to sonnet ───
|
|
76
|
+
checkModelUpgrade(recentRuns, job, advice);
|
|
77
|
+
// Suppress if model upgrades have proven ineffective
|
|
78
|
+
if (advice.adjustedModel && outcomeStats.modelUpgradeSuccessRate !== null && outcomeStats.modelUpgradeSuccessRate < 0.2) {
|
|
79
|
+
logger.info({ job: jobName, rate: outcomeStats.modelUpgradeSuccessRate }, 'Suppressing model upgrade — historically ineffective');
|
|
80
|
+
advice.adjustedModel = null;
|
|
81
|
+
}
|
|
82
|
+
// ── Rule 5: Timeout hits → increase timeout ─────────────────
|
|
83
|
+
checkTimeoutHits(recentRuns, job, advice);
|
|
84
|
+
// ── Rule 6: Sonnet still failing → escalate to unleashed ─────
|
|
85
|
+
checkEscalation(recentRuns, reflections, job, advice);
|
|
86
|
+
// ── Rule 7: Low-confidence completions → flag for escalation ──
|
|
87
|
+
// If recent reflections show low quality even when the job "succeeds",
|
|
88
|
+
// mark for escalation so the user is notified
|
|
89
|
+
if (!advice.shouldEscalate) {
|
|
90
|
+
const recentLowQuality = reflections.slice(0, 3).filter(r => r.quality <= 3);
|
|
91
|
+
const recentSuccessRuns = recentRuns.slice(0, 3).filter(r => r.status === 'ok');
|
|
92
|
+
if (recentLowQuality.length >= 2 && recentSuccessRuns.length >= 2) {
|
|
93
|
+
advice.shouldEscalate = true;
|
|
94
|
+
advice.escalationReason = `Job completes but quality is consistently low (${recentLowQuality.length}/3 reflections scored ≤3) — may need human review`;
|
|
95
|
+
logger.debug({ job: job.name, reason: advice.escalationReason }, 'Recommending escalation due to low-confidence completions');
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
logger.warn({ err, job: jobName }, 'Execution advisor error — proceeding with defaults');
|
|
101
|
+
}
|
|
102
|
+
return advice;
|
|
103
|
+
}
|
|
104
|
+
// ── Rule helpers ────────────────────────────────────────────────────
|
|
105
|
+
function checkTurnLimitHits(runs, job, advice) {
|
|
106
|
+
// Use precise TerminalReason when available, fall back to regex on error text
|
|
107
|
+
const turnLimitHits = runs.slice(0, 5).filter(r => {
|
|
108
|
+
if (r.status !== 'error' && r.status !== 'retried')
|
|
109
|
+
return false;
|
|
110
|
+
// Precise check via SDK TerminalReason
|
|
111
|
+
if (r.terminalReason === 'max_turns')
|
|
112
|
+
return true;
|
|
113
|
+
// Fallback: regex on error text
|
|
114
|
+
const errorLower = (r.error ?? '').toLowerCase();
|
|
115
|
+
return errorLower.includes('turn') || errorLower.includes('max_turns') || errorLower.includes('maxturns');
|
|
116
|
+
});
|
|
117
|
+
// Check for prompt_too_long — this needs a DIFFERENT fix (not more turns)
|
|
118
|
+
const promptTooLong = runs.slice(0, 5).filter(r => r.terminalReason === 'prompt_too_long');
|
|
119
|
+
if (promptTooLong.length >= 1) {
|
|
120
|
+
// Don't increase turns — the prompt itself is the problem
|
|
121
|
+
advice.promptEnrichment = (advice.promptEnrichment || '') +
|
|
122
|
+
'\n\n⚠ Previous runs hit prompt length limits. Be concise. Minimize system prompt injection.';
|
|
123
|
+
logger.debug({ job: job.name, hits: promptTooLong.length }, 'Prompt too long detected — adding conciseness guidance');
|
|
124
|
+
return; // skip turn adjustment
|
|
125
|
+
}
|
|
126
|
+
if (turnLimitHits.length >= 2) {
|
|
127
|
+
const currentMax = job.maxTurns ?? 5;
|
|
128
|
+
const tierCap = TIER_MAX_TURNS[job.tier] ?? TIER_MAX_TURNS[1];
|
|
129
|
+
const proposed = Math.ceil(currentMax * 1.5);
|
|
130
|
+
advice.adjustedMaxTurns = Math.min(proposed, tierCap);
|
|
131
|
+
logger.debug({ job: job.name, from: currentMax, to: advice.adjustedMaxTurns }, 'Adjusting maxTurns due to turn-limit hits');
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
function checkReflectionQuality(reflections, job, advice) {
|
|
135
|
+
const recent = reflections.slice(0, 5); // already newest-first
|
|
136
|
+
if (recent.length < 3)
|
|
137
|
+
return;
|
|
138
|
+
const avgQuality = recent.reduce((sum, r) => sum + r.quality, 0) / recent.length;
|
|
139
|
+
if (avgQuality >= 3.0)
|
|
140
|
+
return;
|
|
141
|
+
// Delegate to prompt evolver for comprehensive enrichment
|
|
142
|
+
const enrichment = evolvePrompt({
|
|
143
|
+
jobName: job.name,
|
|
144
|
+
originalPrompt: job.prompt,
|
|
145
|
+
agentSlug: job.agentSlug,
|
|
146
|
+
});
|
|
147
|
+
if (enrichment) {
|
|
148
|
+
advice.promptEnrichment = enrichment;
|
|
149
|
+
logger.debug({ job: job.name, avgQuality: avgQuality.toFixed(1) }, 'Built prompt enrichment via prompt evolver');
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
function checkModelUpgrade(runs, job, advice) {
|
|
153
|
+
if (!job.model || !job.model.toLowerCase().includes('haiku'))
|
|
154
|
+
return;
|
|
155
|
+
// Precise: model_error from SDK means the model itself is the problem
|
|
156
|
+
const modelErrors = runs.slice(0, 5).filter(r => r.terminalReason === 'model_error');
|
|
157
|
+
if (modelErrors.length >= 1) {
|
|
158
|
+
advice.adjustedModel = 'sonnet';
|
|
159
|
+
logger.debug({ job: job.name, modelErrors: modelErrors.length }, 'Upgrading model — SDK reported model_error');
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
// Fallback: generic failure count
|
|
163
|
+
const recentFailures = runs.slice(0, 5).filter(r => r.status === 'error');
|
|
164
|
+
if (recentFailures.length >= 3) {
|
|
165
|
+
advice.adjustedModel = 'sonnet';
|
|
166
|
+
logger.debug({ job: job.name, failures: recentFailures.length }, 'Upgrading model from haiku to sonnet due to repeated failures');
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
function checkTimeoutHits(runs, job, advice) {
|
|
170
|
+
const timeoutMs = DEFAULT_TIMEOUT_MS; // standard cron timeout
|
|
171
|
+
const threshold = timeoutMs * 0.95;
|
|
172
|
+
const timeoutHits = runs.slice(0, 5).filter(r => {
|
|
173
|
+
if (r.status !== 'error')
|
|
174
|
+
return false;
|
|
175
|
+
return r.durationMs >= threshold;
|
|
176
|
+
});
|
|
177
|
+
if (timeoutHits.length >= 2) {
|
|
178
|
+
const proposed = Math.ceil(timeoutMs * 1.5);
|
|
179
|
+
advice.adjustedTimeoutMs = Math.min(proposed, MAX_TIMEOUT_MS);
|
|
180
|
+
logger.debug({ job: job.name, from: timeoutMs, to: advice.adjustedTimeoutMs }, 'Adjusting timeout due to timeout hits');
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
function checkEscalation(runs, reflections, job, advice) {
|
|
184
|
+
if (job.mode === 'unleashed')
|
|
185
|
+
return;
|
|
186
|
+
// Check if we already upgraded to sonnet and still failing
|
|
187
|
+
const isSonnet = job.model?.toLowerCase().includes('sonnet') || advice.adjustedModel === 'sonnet';
|
|
188
|
+
if (!isSonnet)
|
|
189
|
+
return;
|
|
190
|
+
const recentFailures = runs.slice(0, 5).filter(r => r.status === 'error');
|
|
191
|
+
const lowQualityReflections = reflections.slice(0, 5).filter(r => r.quality <= 2);
|
|
192
|
+
if (recentFailures.length >= 3 || lowQualityReflections.length >= 3) {
|
|
193
|
+
advice.shouldEscalate = true;
|
|
194
|
+
advice.escalationReason = recentFailures.length >= 3
|
|
195
|
+
? `${recentFailures.length} recent failures on sonnet-tier model`
|
|
196
|
+
: `${lowQualityReflections.length} low-quality reflections despite sonnet-tier model`;
|
|
197
|
+
logger.debug({ job: job.name, reason: advice.escalationReason }, 'Recommending escalation to unleashed');
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Read past advisor outcomes to learn which interventions actually work
|
|
202
|
+
* for a given job. Returns null rates when insufficient data exists.
|
|
203
|
+
*/
|
|
204
|
+
function getInterventionStats(jobName) {
|
|
205
|
+
const stats = {
|
|
206
|
+
modelUpgradeSuccessRate: null,
|
|
207
|
+
turnAdjustSuccessRate: null,
|
|
208
|
+
enrichmentSuccessRate: null,
|
|
209
|
+
sampleSize: 0,
|
|
210
|
+
};
|
|
211
|
+
if (!existsSync(ADVISOR_LOG_PATH))
|
|
212
|
+
return stats;
|
|
213
|
+
try {
|
|
214
|
+
const lines = readFileSync(ADVISOR_LOG_PATH, 'utf-8').trim().split('\n').filter(Boolean);
|
|
215
|
+
// Only scan recent entries to avoid expensive parsing on large logs
|
|
216
|
+
const outcomes = lines.slice(-200)
|
|
217
|
+
.map(l => { try {
|
|
218
|
+
return JSON.parse(l);
|
|
219
|
+
}
|
|
220
|
+
catch {
|
|
221
|
+
return null;
|
|
222
|
+
} })
|
|
223
|
+
.filter((d) => d !== null && d.type === 'outcome' && d.jobName === jobName);
|
|
224
|
+
if (outcomes.length < 3)
|
|
225
|
+
return stats;
|
|
226
|
+
stats.sampleSize = outcomes.length;
|
|
227
|
+
// Model upgrade effectiveness
|
|
228
|
+
const modelOuts = outcomes.filter(o => o.interventions?.adjustedModel);
|
|
229
|
+
if (modelOuts.length >= 2) {
|
|
230
|
+
const successes = modelOuts.filter(o => o.outcome === 'ok').length;
|
|
231
|
+
stats.modelUpgradeSuccessRate = successes / modelOuts.length;
|
|
232
|
+
}
|
|
233
|
+
// Turn adjustment effectiveness
|
|
234
|
+
const turnOuts = outcomes.filter(o => o.interventions?.adjustedMaxTurns);
|
|
235
|
+
if (turnOuts.length >= 2) {
|
|
236
|
+
const successes = turnOuts.filter(o => o.outcome === 'ok').length;
|
|
237
|
+
stats.turnAdjustSuccessRate = successes / turnOuts.length;
|
|
238
|
+
}
|
|
239
|
+
// Enrichment effectiveness
|
|
240
|
+
const enrichOuts = outcomes.filter(o => o.interventions?.enriched);
|
|
241
|
+
if (enrichOuts.length >= 2) {
|
|
242
|
+
const successes = enrichOuts.filter(o => o.outcome === 'ok').length;
|
|
243
|
+
stats.enrichmentSuccessRate = successes / enrichOuts.length;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
catch { /* ignore */ }
|
|
247
|
+
return stats;
|
|
248
|
+
}
|
|
249
|
+
// ── Reflection file reader ──────────────────────────────────────────
|
|
250
|
+
function readReflections(jobName) {
|
|
251
|
+
try {
|
|
252
|
+
const safeJob = jobName.replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
253
|
+
const reflPath = path.join(CRON_REFLECTIONS_DIR, `${safeJob}.jsonl`);
|
|
254
|
+
if (!existsSync(reflPath))
|
|
255
|
+
return [];
|
|
256
|
+
const lines = readFileSync(reflPath, 'utf-8').trim().split('\n').filter(Boolean);
|
|
257
|
+
return lines
|
|
258
|
+
.slice(-10)
|
|
259
|
+
.map(l => { try {
|
|
260
|
+
return JSON.parse(l);
|
|
261
|
+
}
|
|
262
|
+
catch {
|
|
263
|
+
return null;
|
|
264
|
+
} })
|
|
265
|
+
.filter((r) => r !== null)
|
|
266
|
+
.reverse(); // newest first
|
|
267
|
+
}
|
|
268
|
+
catch {
|
|
269
|
+
return [];
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
//# sourceMappingURL=execution-advisor.js.map
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Clementine TypeScript — Security enforcement and audit logging.
|
|
3
|
+
*
|
|
4
|
+
* Real enforcement via SDK canUseTool callback + disallowed_tools for heartbeats.
|
|
5
|
+
* Layers:
|
|
6
|
+
* - canUseTool: enforceToolPermissions() blocks destructive/credential/SSRF calls
|
|
7
|
+
* - disallowed_tools: heartbeat tool restrictions
|
|
8
|
+
* - System prompt: security rules (defense in depth)
|
|
9
|
+
* - Audit logging: persistent file + in-memory buffer
|
|
10
|
+
*/
|
|
11
|
+
import type { SendPolicy } from '../types.js';
|
|
12
|
+
export declare function setHeartbeatMode(active: boolean, tier2Allowed?: boolean): void;
|
|
13
|
+
export declare function setApprovalCallback(cb: ((desc: string) => Promise<boolean>) | null): void;
|
|
14
|
+
export declare function setProfileTier(tier: number | null): void;
|
|
15
|
+
export declare function setProfileAllowedTools(tools: string[] | null): void;
|
|
16
|
+
export declare function setSendPolicy(policy: SendPolicy | null, agentSlug: string | null): void;
|
|
17
|
+
export declare function setAgentDir(dir: string | null): void;
|
|
18
|
+
export declare function setSendPolicyChecker(checker: ((agentSlug: string, recipientEmail: string) => {
|
|
19
|
+
dailyCount: number;
|
|
20
|
+
suppressed: boolean;
|
|
21
|
+
}) | null): void;
|
|
22
|
+
export declare function setInteractionSource(source: 'owner-dm' | 'owner-channel' | 'member-channel' | 'autonomous'): void;
|
|
23
|
+
export declare function getInteractionSource(): 'owner-dm' | 'owner-channel' | 'member-channel' | 'autonomous';
|
|
24
|
+
export declare function getProfileTier(): number | null;
|
|
25
|
+
export declare function getAuditLog(): string[];
|
|
26
|
+
export declare function clearAuditLog(): void;
|
|
27
|
+
export declare function logToolUse(toolName: string, toolInput: Record<string, unknown>): void;
|
|
28
|
+
export declare function getHeartbeatDisallowedTools(): string[];
|
|
29
|
+
export declare const PRIVATE_URL_PATTERNS: RegExp[];
|
|
30
|
+
export declare function enforceToolPermissions(toolName: string, toolInput: Record<string, unknown>, sourceOverride?: 'owner-dm' | 'owner-channel' | 'member-channel' | 'autonomous'): Promise<{
|
|
31
|
+
behavior: 'allow' | 'deny';
|
|
32
|
+
message?: string;
|
|
33
|
+
}>;
|
|
34
|
+
export declare function getSecurityPrompt(): string;
|
|
35
|
+
export declare function getHeartbeatSecurityPrompt(): string;
|
|
36
|
+
export declare function getCronSecurityPrompt(tier?: number): string;
|
|
37
|
+
/**
|
|
38
|
+
* Validate MCP tool output for credential leaks and injection payloads.
|
|
39
|
+
* Available for use when tool output interception is added to the SDK streaming loop.
|
|
40
|
+
*/
|
|
41
|
+
export declare function validateToolOutput(_toolName: string, output: string): {
|
|
42
|
+
safe: boolean;
|
|
43
|
+
reason?: string;
|
|
44
|
+
};
|
|
45
|
+
//# sourceMappingURL=hooks.d.ts.map
|