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,352 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Clementine TypeScript — Strategic Planner (Multi-Horizon Planning).
|
|
3
|
+
*
|
|
4
|
+
* Weekly reviews: synthesize daily plans + goal progress into accomplishments,
|
|
5
|
+
* missed targets, patterns, and recommendations.
|
|
6
|
+
*
|
|
7
|
+
* Monthly assessments: cross-reference weekly reviews with goal completion
|
|
8
|
+
* and self-improvement experiments. Proposes OKR-style goals.
|
|
9
|
+
*
|
|
10
|
+
* Plans are persisted to ~/.clementine/plans/weekly/ and monthly/.
|
|
11
|
+
*/
|
|
12
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs';
|
|
13
|
+
import path from 'node:path';
|
|
14
|
+
import pino from 'pino';
|
|
15
|
+
import Anthropic from '@anthropic-ai/sdk';
|
|
16
|
+
import { BASE_DIR, GOALS_DIR, MODELS } from '../config.js';
|
|
17
|
+
const logger = pino({ name: 'clementine.strategic-planner' });
|
|
18
|
+
const DAILY_PLANS_DIR = path.join(BASE_DIR, 'plans', 'daily');
|
|
19
|
+
const WEEKLY_PLANS_DIR = path.join(BASE_DIR, 'plans', 'weekly');
|
|
20
|
+
const MONTHLY_PLANS_DIR = path.join(BASE_DIR, 'plans', 'monthly');
|
|
21
|
+
// ── .env reader ──────────────────────────────────────────────────────
|
|
22
|
+
function getEnvValue(key) {
|
|
23
|
+
if (process.env[key])
|
|
24
|
+
return process.env[key];
|
|
25
|
+
const envPath = path.join(BASE_DIR, '.env');
|
|
26
|
+
if (!existsSync(envPath))
|
|
27
|
+
return '';
|
|
28
|
+
for (const line of readFileSync(envPath, 'utf-8').split('\n')) {
|
|
29
|
+
const trimmed = line.trim();
|
|
30
|
+
if (!trimmed || trimmed.startsWith('#'))
|
|
31
|
+
continue;
|
|
32
|
+
const eqIndex = trimmed.indexOf('=');
|
|
33
|
+
if (eqIndex === -1)
|
|
34
|
+
continue;
|
|
35
|
+
if (trimmed.slice(0, eqIndex) !== key)
|
|
36
|
+
continue;
|
|
37
|
+
let value = trimmed.slice(eqIndex + 1);
|
|
38
|
+
if ((value.startsWith('"') && value.endsWith('"')) ||
|
|
39
|
+
(value.startsWith("'") && value.endsWith("'"))) {
|
|
40
|
+
value = value.slice(1, -1);
|
|
41
|
+
}
|
|
42
|
+
return value;
|
|
43
|
+
}
|
|
44
|
+
return '';
|
|
45
|
+
}
|
|
46
|
+
function getAnthropicCredentials() {
|
|
47
|
+
const oauthToken = getEnvValue('CLAUDE_CODE_OAUTH_TOKEN');
|
|
48
|
+
if (oauthToken)
|
|
49
|
+
return { authToken: oauthToken };
|
|
50
|
+
const authToken = getEnvValue('ANTHROPIC_AUTH_TOKEN');
|
|
51
|
+
if (authToken)
|
|
52
|
+
return { authToken };
|
|
53
|
+
const apiKey = getEnvValue('ANTHROPIC_API_KEY');
|
|
54
|
+
if (apiKey)
|
|
55
|
+
return { apiKey };
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
function makeAnthropicClient() {
|
|
59
|
+
const creds = getAnthropicCredentials();
|
|
60
|
+
if (!creds)
|
|
61
|
+
return null;
|
|
62
|
+
return new Anthropic(creds.authToken ? { authToken: creds.authToken } : { apiKey: creds.apiKey });
|
|
63
|
+
}
|
|
64
|
+
// ── Strategic Planner ────────────────────────────────────────────────
|
|
65
|
+
export class StrategicPlanner {
|
|
66
|
+
constructor() {
|
|
67
|
+
mkdirSync(WEEKLY_PLANS_DIR, { recursive: true });
|
|
68
|
+
mkdirSync(MONTHLY_PLANS_DIR, { recursive: true });
|
|
69
|
+
}
|
|
70
|
+
// ── Weekly Review ────────────────────────────────────────────────────
|
|
71
|
+
hasWeeklyReview(weekId) {
|
|
72
|
+
const id = weekId ?? currentWeekId();
|
|
73
|
+
return existsSync(path.join(WEEKLY_PLANS_DIR, `${id}.json`));
|
|
74
|
+
}
|
|
75
|
+
async generateWeeklyReview() {
|
|
76
|
+
const weekId = currentWeekId();
|
|
77
|
+
const context = this.gatherWeeklyContext();
|
|
78
|
+
const review = await this.callLlmForWeekly(weekId, context);
|
|
79
|
+
writeFileSync(path.join(WEEKLY_PLANS_DIR, `${weekId}.json`), JSON.stringify(review, null, 2));
|
|
80
|
+
logger.info({ weekId, accomplishments: review.accomplishments.length }, 'Weekly review generated');
|
|
81
|
+
return review;
|
|
82
|
+
}
|
|
83
|
+
gatherWeeklyContext() {
|
|
84
|
+
const parts = [];
|
|
85
|
+
// Last 7 daily plans
|
|
86
|
+
const dailyPlans = this.loadRecentDailyPlans(7);
|
|
87
|
+
if (dailyPlans.length > 0) {
|
|
88
|
+
parts.push('## Daily Plans This Week');
|
|
89
|
+
for (const plan of dailyPlans) {
|
|
90
|
+
const priorities = plan.priorities.map(p => ` - [${p.type}] ${p.action} (urgency: ${p.urgency})`).join('\n');
|
|
91
|
+
parts.push(`### ${plan.date}\n${plan.summary}\n${priorities}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// Goal progress
|
|
95
|
+
const goals = this.loadActiveGoals();
|
|
96
|
+
if (goals.length > 0) {
|
|
97
|
+
parts.push('## Goal Status');
|
|
98
|
+
for (const goal of goals) {
|
|
99
|
+
const recentNotes = (goal.progressNotes ?? []).slice(-3).join('; ');
|
|
100
|
+
parts.push(`- [${goal.priority}] ${goal.title}: ${goal.status} | Notes: ${recentNotes || 'none'}`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
// Goal progress logs
|
|
104
|
+
const progressDir = path.join(GOALS_DIR, 'progress');
|
|
105
|
+
if (existsSync(progressDir)) {
|
|
106
|
+
const files = readdirSync(progressDir).filter(f => f.endsWith('.progress.jsonl'));
|
|
107
|
+
const weekAgo = new Date(Date.now() - 7 * 86_400_000).toISOString();
|
|
108
|
+
const entries = [];
|
|
109
|
+
for (const file of files.slice(0, 10)) {
|
|
110
|
+
const lines = readFileSync(path.join(progressDir, file), 'utf-8').trim().split('\n').filter(Boolean);
|
|
111
|
+
for (const line of lines.slice(-10)) {
|
|
112
|
+
try {
|
|
113
|
+
const entry = JSON.parse(line);
|
|
114
|
+
if (entry.timestamp > weekAgo) {
|
|
115
|
+
entries.push(`[${entry.status}] ${entry.focus}: ${entry.resultSnippet?.slice(0, 80)}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
if (entries.length > 0) {
|
|
124
|
+
parts.push(`## Goal Work Sessions This Week\n${entries.join('\n')}`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return parts.join('\n\n');
|
|
128
|
+
}
|
|
129
|
+
async callLlmForWeekly(weekId, context) {
|
|
130
|
+
const goals = this.loadActiveGoals();
|
|
131
|
+
const prompt = `You are reviewing the week ${weekId}. Based on the context below, produce a weekly review.\n\n` +
|
|
132
|
+
`${context}\n\n` +
|
|
133
|
+
`Output ONLY a JSON object:\n` +
|
|
134
|
+
`{\n` +
|
|
135
|
+
` "accomplishments": ["what was achieved"],\n` +
|
|
136
|
+
` "missedTargets": ["what was planned but not done"],\n` +
|
|
137
|
+
` "patterns": ["recurring themes or issues noticed"],\n` +
|
|
138
|
+
` "recommendations": ["what to focus on next week"],\n` +
|
|
139
|
+
` "summary": "2-3 sentence overall assessment"\n` +
|
|
140
|
+
`}`;
|
|
141
|
+
const defaultReview = {
|
|
142
|
+
weekId,
|
|
143
|
+
createdAt: new Date().toISOString(),
|
|
144
|
+
accomplishments: [],
|
|
145
|
+
missedTargets: [],
|
|
146
|
+
patterns: [],
|
|
147
|
+
recommendations: [],
|
|
148
|
+
goalProgress: goals.map(g => ({
|
|
149
|
+
goalId: g.id,
|
|
150
|
+
title: g.title,
|
|
151
|
+
status: g.status,
|
|
152
|
+
noteCount: g.progressNotes?.length ?? 0,
|
|
153
|
+
})),
|
|
154
|
+
summary: 'No data available for weekly review.',
|
|
155
|
+
};
|
|
156
|
+
const client = makeAnthropicClient();
|
|
157
|
+
if (!client)
|
|
158
|
+
return defaultReview;
|
|
159
|
+
try {
|
|
160
|
+
const response = await client.messages.create({
|
|
161
|
+
model: MODELS.haiku,
|
|
162
|
+
max_tokens: 1000,
|
|
163
|
+
messages: [{ role: 'user', content: prompt }],
|
|
164
|
+
});
|
|
165
|
+
const text = response.content[0]?.type === 'text' ? response.content[0].text : '';
|
|
166
|
+
const jsonMatch = text.match(/\{[\s\S]*\}/);
|
|
167
|
+
if (jsonMatch) {
|
|
168
|
+
const parsed = JSON.parse(jsonMatch[0]);
|
|
169
|
+
return {
|
|
170
|
+
...defaultReview,
|
|
171
|
+
accomplishments: parsed.accomplishments ?? [],
|
|
172
|
+
missedTargets: parsed.missedTargets ?? [],
|
|
173
|
+
patterns: parsed.patterns ?? [],
|
|
174
|
+
recommendations: parsed.recommendations ?? [],
|
|
175
|
+
summary: parsed.summary ?? defaultReview.summary,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
catch (err) {
|
|
180
|
+
logger.warn({ err }, 'Weekly review LLM call failed');
|
|
181
|
+
}
|
|
182
|
+
return defaultReview;
|
|
183
|
+
}
|
|
184
|
+
// ── Monthly Assessment ───────────────────────────────────────────────
|
|
185
|
+
hasMonthlyAssessment(monthId) {
|
|
186
|
+
const id = monthId ?? currentMonthId();
|
|
187
|
+
return existsSync(path.join(MONTHLY_PLANS_DIR, `${id}.json`));
|
|
188
|
+
}
|
|
189
|
+
async generateMonthlyAssessment() {
|
|
190
|
+
const monthId = currentMonthId();
|
|
191
|
+
const context = this.gatherMonthlyContext();
|
|
192
|
+
const assessment = await this.callLlmForMonthly(monthId, context);
|
|
193
|
+
writeFileSync(path.join(MONTHLY_PLANS_DIR, `${monthId}.json`), JSON.stringify(assessment, null, 2));
|
|
194
|
+
logger.info({ monthId }, 'Monthly assessment generated');
|
|
195
|
+
return assessment;
|
|
196
|
+
}
|
|
197
|
+
gatherMonthlyContext() {
|
|
198
|
+
const parts = [];
|
|
199
|
+
// Last 4 weekly reviews
|
|
200
|
+
const weeklyFiles = existsSync(WEEKLY_PLANS_DIR)
|
|
201
|
+
? readdirSync(WEEKLY_PLANS_DIR).filter(f => f.endsWith('.json')).sort().slice(-4)
|
|
202
|
+
: [];
|
|
203
|
+
if (weeklyFiles.length > 0) {
|
|
204
|
+
parts.push('## Weekly Reviews');
|
|
205
|
+
for (const file of weeklyFiles) {
|
|
206
|
+
try {
|
|
207
|
+
const review = JSON.parse(readFileSync(path.join(WEEKLY_PLANS_DIR, file), 'utf-8'));
|
|
208
|
+
parts.push(`### ${review.weekId}\n${review.summary}`);
|
|
209
|
+
if (review.patterns.length > 0) {
|
|
210
|
+
parts.push(`Patterns: ${review.patterns.join('; ')}`);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
catch {
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
// Goal completion stats
|
|
219
|
+
const goals = this.loadActiveGoals();
|
|
220
|
+
const allGoals = this.loadAllGoals();
|
|
221
|
+
const completed = allGoals.filter(g => g.status === 'completed');
|
|
222
|
+
parts.push(`## Goals: ${goals.length} active, ${completed.length} completed this period`);
|
|
223
|
+
return parts.join('\n\n');
|
|
224
|
+
}
|
|
225
|
+
async callLlmForMonthly(monthId, context) {
|
|
226
|
+
const allGoals = this.loadAllGoals();
|
|
227
|
+
const completed = allGoals.filter(g => g.status === 'completed').length;
|
|
228
|
+
const total = allGoals.length || 1;
|
|
229
|
+
const defaultAssessment = {
|
|
230
|
+
monthId,
|
|
231
|
+
createdAt: new Date().toISOString(),
|
|
232
|
+
weeklyTrends: [],
|
|
233
|
+
goalCompletionRate: completed / total,
|
|
234
|
+
systemicIssues: [],
|
|
235
|
+
proposedGoals: [],
|
|
236
|
+
summary: 'No data available for monthly assessment.',
|
|
237
|
+
};
|
|
238
|
+
const client2 = makeAnthropicClient();
|
|
239
|
+
if (!client2)
|
|
240
|
+
return defaultAssessment;
|
|
241
|
+
const prompt = `You are generating a monthly strategic assessment for ${monthId}.\n\n` +
|
|
242
|
+
`${context}\n\n` +
|
|
243
|
+
`Output ONLY a JSON object:\n` +
|
|
244
|
+
`{\n` +
|
|
245
|
+
` "weeklyTrends": ["trends across the weekly reviews"],\n` +
|
|
246
|
+
` "systemicIssues": ["recurring problems that need structural fixes"],\n` +
|
|
247
|
+
` "proposedGoals": [{"title": "...", "description": "...", "priority": "high|medium|low"}],\n` +
|
|
248
|
+
` "summary": "2-3 sentence strategic assessment"\n` +
|
|
249
|
+
`}`;
|
|
250
|
+
try {
|
|
251
|
+
const response = await client2.messages.create({
|
|
252
|
+
model: MODELS.haiku,
|
|
253
|
+
max_tokens: 1000,
|
|
254
|
+
messages: [{ role: 'user', content: prompt }],
|
|
255
|
+
});
|
|
256
|
+
const text = response.content[0]?.type === 'text' ? response.content[0].text : '';
|
|
257
|
+
const jsonMatch = text.match(/\{[\s\S]*\}/);
|
|
258
|
+
if (jsonMatch) {
|
|
259
|
+
const parsed = JSON.parse(jsonMatch[0]);
|
|
260
|
+
return {
|
|
261
|
+
...defaultAssessment,
|
|
262
|
+
weeklyTrends: parsed.weeklyTrends ?? [],
|
|
263
|
+
systemicIssues: parsed.systemicIssues ?? [],
|
|
264
|
+
proposedGoals: parsed.proposedGoals ?? [],
|
|
265
|
+
summary: parsed.summary ?? defaultAssessment.summary,
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
catch (err) {
|
|
270
|
+
logger.warn({ err }, 'Monthly assessment LLM call failed');
|
|
271
|
+
}
|
|
272
|
+
return defaultAssessment;
|
|
273
|
+
}
|
|
274
|
+
// ── Goal-Plan Alignment Check ────────────────────────────────────────
|
|
275
|
+
/**
|
|
276
|
+
* Check if today's daily plan aligns with active high-priority goals.
|
|
277
|
+
* Returns a warning string if misaligned, null if fine.
|
|
278
|
+
*/
|
|
279
|
+
checkGoalPlanAlignment(dailyPlan) {
|
|
280
|
+
const goals = this.loadActiveGoals();
|
|
281
|
+
const highPriority = goals.filter(g => g.priority === 'high');
|
|
282
|
+
if (highPriority.length === 0)
|
|
283
|
+
return null;
|
|
284
|
+
// Check if any priority items reference a high-priority goal
|
|
285
|
+
const goalTitlesLower = highPriority.map(g => g.title.toLowerCase());
|
|
286
|
+
const priorityTexts = dailyPlan.priorities.map(p => p.action.toLowerCase());
|
|
287
|
+
const hasGoalWork = goalTitlesLower.some(title => priorityTexts.some(pt => pt.includes(title.slice(0, 20)) || pt.includes(title.split(' ')[0])));
|
|
288
|
+
// Also check if any priorities are of type 'goal'
|
|
289
|
+
const hasGoalType = dailyPlan.priorities.some(p => p.type === 'goal');
|
|
290
|
+
if (!hasGoalWork && !hasGoalType) {
|
|
291
|
+
const goalNames = highPriority.map(g => g.title).join(', ');
|
|
292
|
+
return `Today's plan doesn't directly advance any high-priority goals (${goalNames}). Consider scheduling focused work on at least one.`;
|
|
293
|
+
}
|
|
294
|
+
return null;
|
|
295
|
+
}
|
|
296
|
+
// ── Helpers ──────────────────────────────────────────────────────────
|
|
297
|
+
loadRecentDailyPlans(days) {
|
|
298
|
+
if (!existsSync(DAILY_PLANS_DIR))
|
|
299
|
+
return [];
|
|
300
|
+
const files = readdirSync(DAILY_PLANS_DIR).filter(f => f.endsWith('.json')).sort().slice(-days);
|
|
301
|
+
return files.map(f => {
|
|
302
|
+
try {
|
|
303
|
+
return JSON.parse(readFileSync(path.join(DAILY_PLANS_DIR, f), 'utf-8'));
|
|
304
|
+
}
|
|
305
|
+
catch {
|
|
306
|
+
return null;
|
|
307
|
+
}
|
|
308
|
+
}).filter(Boolean);
|
|
309
|
+
}
|
|
310
|
+
loadActiveGoals() {
|
|
311
|
+
if (!existsSync(GOALS_DIR))
|
|
312
|
+
return [];
|
|
313
|
+
return readdirSync(GOALS_DIR)
|
|
314
|
+
.filter(f => f.endsWith('.json'))
|
|
315
|
+
.map(f => {
|
|
316
|
+
try {
|
|
317
|
+
return JSON.parse(readFileSync(path.join(GOALS_DIR, f), 'utf-8'));
|
|
318
|
+
}
|
|
319
|
+
catch {
|
|
320
|
+
return null;
|
|
321
|
+
}
|
|
322
|
+
})
|
|
323
|
+
.filter((g) => g && g.status === 'active');
|
|
324
|
+
}
|
|
325
|
+
loadAllGoals() {
|
|
326
|
+
if (!existsSync(GOALS_DIR))
|
|
327
|
+
return [];
|
|
328
|
+
return readdirSync(GOALS_DIR)
|
|
329
|
+
.filter(f => f.endsWith('.json'))
|
|
330
|
+
.map(f => {
|
|
331
|
+
try {
|
|
332
|
+
return JSON.parse(readFileSync(path.join(GOALS_DIR, f), 'utf-8'));
|
|
333
|
+
}
|
|
334
|
+
catch {
|
|
335
|
+
return null;
|
|
336
|
+
}
|
|
337
|
+
})
|
|
338
|
+
.filter(Boolean);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
// ── Date helpers ───────────────────────────────────────────────────────
|
|
342
|
+
function currentWeekId() {
|
|
343
|
+
const now = new Date();
|
|
344
|
+
const yearStart = new Date(now.getFullYear(), 0, 1);
|
|
345
|
+
const weekNum = Math.ceil(((now.getTime() - yearStart.getTime()) / 86_400_000 + yearStart.getDay() + 1) / 7);
|
|
346
|
+
return `${now.getFullYear()}-W${String(weekNum).padStart(2, '0')}`;
|
|
347
|
+
}
|
|
348
|
+
function currentMonthId() {
|
|
349
|
+
const now = new Date();
|
|
350
|
+
return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}`;
|
|
351
|
+
}
|
|
352
|
+
//# sourceMappingURL=strategic-planner.js.map
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Clementine TypeScript — Inter-agent message bus.
|
|
3
|
+
*
|
|
4
|
+
* Enables async message passing between team agents via gateway.injectContext.
|
|
5
|
+
* Logs to JSONL and optionally mirrors to a Discord channel.
|
|
6
|
+
*/
|
|
7
|
+
import type { TeamMessage } from '../types.js';
|
|
8
|
+
import type { Gateway } from '../gateway/router.js';
|
|
9
|
+
import type { TeamRouter } from './team-router.js';
|
|
10
|
+
export declare class TeamBus {
|
|
11
|
+
private gateway;
|
|
12
|
+
private teamRouter;
|
|
13
|
+
private commsChannelId?;
|
|
14
|
+
private logFile;
|
|
15
|
+
private recentMessages;
|
|
16
|
+
/** "from:to" → last send timestamp (for cooldown). */
|
|
17
|
+
private cooldowns;
|
|
18
|
+
/** "slug" → context shares sent today (resets at midnight). */
|
|
19
|
+
private contextShareCounts;
|
|
20
|
+
/** "from:to:contentHash" → timestamp (for content dedup). */
|
|
21
|
+
private contentHashes;
|
|
22
|
+
private statusChangeListeners;
|
|
23
|
+
private pendingRequests;
|
|
24
|
+
private botManager?;
|
|
25
|
+
private slackBotManager?;
|
|
26
|
+
constructor(gateway: Gateway, teamRouter: TeamRouter, options: {
|
|
27
|
+
commsChannelId?: string;
|
|
28
|
+
logFile: string;
|
|
29
|
+
botManager?: import('../channels/discord-bot-manager.js').BotManager;
|
|
30
|
+
slackBotManager?: import('../channels/slack-bot-manager.js').SlackBotManager;
|
|
31
|
+
});
|
|
32
|
+
/** Update the BotManager reference (called if set after construction). */
|
|
33
|
+
setBotManager(botManager: import('../channels/discord-bot-manager.js').BotManager): void;
|
|
34
|
+
/** Update the SlackBotManager reference (called if set after construction). */
|
|
35
|
+
setSlackBotManager(slackBotManager: import('../channels/slack-bot-manager.js').SlackBotManager): void;
|
|
36
|
+
/**
|
|
37
|
+
* Resolve a session key for the target agent via BotManager or SlackBotManager.
|
|
38
|
+
*/
|
|
39
|
+
private resolveSessionKey;
|
|
40
|
+
/**
|
|
41
|
+
* Derive the agent slug that owns a given session key.
|
|
42
|
+
* Returns null for primary/owner sessions (no agent slug).
|
|
43
|
+
*/
|
|
44
|
+
private deriveSlugFromSession;
|
|
45
|
+
/** Agent A sends a direct message to Agent B (fire-and-forget with cooldown/dedup). */
|
|
46
|
+
send(fromSlug: string, toSlug: string, content: string, depth?: number, sessionKey?: string): Promise<TeamMessage>;
|
|
47
|
+
/**
|
|
48
|
+
* Send a structured request and wait for the target agent's response.
|
|
49
|
+
* Bypasses cooldown — structured requests are explicitly tracked.
|
|
50
|
+
*/
|
|
51
|
+
request(fromSlug: string, toSlug: string, content: string, timeoutMs?: number, sessionKey?: string): Promise<TeamMessage>;
|
|
52
|
+
/** Deliver a response to a pending structured request. */
|
|
53
|
+
respond(toRequestId: string, fromSlug: string, content: string, _sessionKey?: string): Promise<TeamMessage>;
|
|
54
|
+
/** Broadcast a message to all team agents (optionally scoped to a goal). */
|
|
55
|
+
broadcast(fromSlug: string, content: string, goalId: string, _sessionKey?: string): Promise<TeamMessage[]>;
|
|
56
|
+
/** Get pending structured requests addressed to an agent. */
|
|
57
|
+
getPendingRequests(agentSlug: string): Array<{
|
|
58
|
+
requestId: string;
|
|
59
|
+
fromAgent: string;
|
|
60
|
+
content: string;
|
|
61
|
+
timestamp: string;
|
|
62
|
+
expectedBy?: string;
|
|
63
|
+
}>;
|
|
64
|
+
/** Core delivery logic — sends a message via bot or session injection. */
|
|
65
|
+
private deliverMessage;
|
|
66
|
+
/** Register a listener that fires when team state changes. */
|
|
67
|
+
onStatusChange(cb: () => void): void;
|
|
68
|
+
/** Get recent inter-agent messages (for dashboard). */
|
|
69
|
+
getRecentMessages(limit?: number): TeamMessage[];
|
|
70
|
+
/** Get messages for a specific agent (sent or received). */
|
|
71
|
+
getMessagesForAgent(slug: string, limit?: number): TeamMessage[];
|
|
72
|
+
/** Load messages from the JSONL log file (cold start). */
|
|
73
|
+
loadFromLog(limit?: number): void;
|
|
74
|
+
/**
|
|
75
|
+
* Deliver any undelivered messages from the JSONL log.
|
|
76
|
+
* Called periodically by the daemon to pick up messages
|
|
77
|
+
* written by the MCP tool (which runs out-of-process).
|
|
78
|
+
*/
|
|
79
|
+
deliverPending(): Promise<number>;
|
|
80
|
+
/** Post an embed to the team comms Discord channel. */
|
|
81
|
+
private mirrorToDiscord;
|
|
82
|
+
/**
|
|
83
|
+
* Share context between agents — lightweight, rate-limited updates.
|
|
84
|
+
* Longer cooldown (4 hours) and daily cap (2 per agent) vs regular messages.
|
|
85
|
+
* Used for proactive context sharing, not task delegation.
|
|
86
|
+
*/
|
|
87
|
+
shareContext(fromSlug: string, toSlug: string, content: string): Promise<TeamMessage | null>;
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=team-bus.d.ts.map
|