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,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Clementine TypeScript — Daemon-Driven Auto-Update.
|
|
3
|
+
*
|
|
4
|
+
* Checks for and applies upstream changes without requiring `clementine update`.
|
|
5
|
+
* Source modifications from self-improve are tracked in ~/.clementine/ (not git),
|
|
6
|
+
* so git pull is always clean. After pulling, source mods are reconciled.
|
|
7
|
+
*/
|
|
8
|
+
export interface UpdateCheckResult {
|
|
9
|
+
available: boolean;
|
|
10
|
+
commitsBehind: number;
|
|
11
|
+
summary?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface UpdateApplyResult {
|
|
14
|
+
success: boolean;
|
|
15
|
+
error?: string;
|
|
16
|
+
reconcileResult?: import('./source-mods.js').ReconcileResult;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Check if upstream has new commits. Safe to call from cron — no side effects.
|
|
20
|
+
*/
|
|
21
|
+
export declare function checkForUpdates(pkgDir: string): Promise<UpdateCheckResult>;
|
|
22
|
+
/**
|
|
23
|
+
* Apply upstream updates:
|
|
24
|
+
* 1. Reset any uncommitted src/ changes (mods are tracked in ~/.clementine/)
|
|
25
|
+
* 2. Pull latest from origin/main
|
|
26
|
+
* 3. Install deps + build
|
|
27
|
+
* 4. Reconcile source modifications
|
|
28
|
+
* 5. Rebuild if mods were re-applied
|
|
29
|
+
* 6. Write sentinel + restart
|
|
30
|
+
*/
|
|
31
|
+
export declare function applyUpdate(pkgDir: string): Promise<UpdateApplyResult>;
|
|
32
|
+
//# sourceMappingURL=auto-update.d.ts.map
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Clementine TypeScript — Daemon-Driven Auto-Update.
|
|
3
|
+
*
|
|
4
|
+
* Checks for and applies upstream changes without requiring `clementine update`.
|
|
5
|
+
* Source modifications from self-improve are tracked in ~/.clementine/ (not git),
|
|
6
|
+
* so git pull is always clean. After pulling, source mods are reconciled.
|
|
7
|
+
*/
|
|
8
|
+
import { execSync } from 'node:child_process';
|
|
9
|
+
import { writeFileSync } from 'node:fs';
|
|
10
|
+
import path from 'node:path';
|
|
11
|
+
import pino from 'pino';
|
|
12
|
+
import { BASE_DIR } from '../config.js';
|
|
13
|
+
import { reconcileSourceMods } from './source-mods.js';
|
|
14
|
+
const logger = pino({ name: 'clementine.auto-update' });
|
|
15
|
+
const SENTINEL_PATH = path.join(BASE_DIR, '.restart-sentinel.json');
|
|
16
|
+
/**
|
|
17
|
+
* Check if upstream has new commits. Safe to call from cron — no side effects.
|
|
18
|
+
*/
|
|
19
|
+
export async function checkForUpdates(pkgDir) {
|
|
20
|
+
try {
|
|
21
|
+
execSync('git fetch origin main --quiet', {
|
|
22
|
+
cwd: pkgDir,
|
|
23
|
+
stdio: 'pipe',
|
|
24
|
+
timeout: 30_000,
|
|
25
|
+
});
|
|
26
|
+
const countStr = execSync('git rev-list HEAD..origin/main --count', {
|
|
27
|
+
cwd: pkgDir,
|
|
28
|
+
encoding: 'utf-8',
|
|
29
|
+
}).trim();
|
|
30
|
+
const commitsBehind = parseInt(countStr, 10) || 0;
|
|
31
|
+
if (commitsBehind === 0) {
|
|
32
|
+
return { available: false, commitsBehind: 0 };
|
|
33
|
+
}
|
|
34
|
+
const summary = execSync('git log HEAD..origin/main --oneline', {
|
|
35
|
+
cwd: pkgDir,
|
|
36
|
+
encoding: 'utf-8',
|
|
37
|
+
}).trim();
|
|
38
|
+
return { available: true, commitsBehind, summary };
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
logger.warn({ err }, 'Update check failed (network issue?)');
|
|
42
|
+
return { available: false, commitsBehind: 0 };
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Apply upstream updates:
|
|
47
|
+
* 1. Reset any uncommitted src/ changes (mods are tracked in ~/.clementine/)
|
|
48
|
+
* 2. Pull latest from origin/main
|
|
49
|
+
* 3. Install deps + build
|
|
50
|
+
* 4. Reconcile source modifications
|
|
51
|
+
* 5. Rebuild if mods were re-applied
|
|
52
|
+
* 6. Write sentinel + restart
|
|
53
|
+
*/
|
|
54
|
+
export async function applyUpdate(pkgDir) {
|
|
55
|
+
try {
|
|
56
|
+
// 1. Ensure we're on main and clean
|
|
57
|
+
try {
|
|
58
|
+
const currentBranch = execSync('git rev-parse --abbrev-ref HEAD', {
|
|
59
|
+
cwd: pkgDir,
|
|
60
|
+
encoding: 'utf-8',
|
|
61
|
+
}).trim();
|
|
62
|
+
if (currentBranch !== 'main') {
|
|
63
|
+
execSync('git checkout main', { cwd: pkgDir, stdio: 'pipe' });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
catch { /* best effort */ }
|
|
67
|
+
// Reset any local src/ changes (source mods are tracked in registry, not git)
|
|
68
|
+
try {
|
|
69
|
+
execSync('git checkout -- src/', { cwd: pkgDir, stdio: 'pipe' });
|
|
70
|
+
}
|
|
71
|
+
catch { /* no changes to reset */ }
|
|
72
|
+
// 2. Pull from origin
|
|
73
|
+
try {
|
|
74
|
+
execSync('git pull --ff-only origin main', {
|
|
75
|
+
cwd: pkgDir,
|
|
76
|
+
stdio: 'pipe',
|
|
77
|
+
timeout: 60_000,
|
|
78
|
+
});
|
|
79
|
+
logger.info('Pulled latest from origin/main');
|
|
80
|
+
}
|
|
81
|
+
catch (err) {
|
|
82
|
+
logger.error({ err }, 'git pull --ff-only failed');
|
|
83
|
+
return { success: false, error: `git pull failed: ${String(err)}` };
|
|
84
|
+
}
|
|
85
|
+
// 3. Install dependencies (in case they changed)
|
|
86
|
+
// Include devDependencies — typescript is needed for the build step
|
|
87
|
+
try {
|
|
88
|
+
execSync('npm install', {
|
|
89
|
+
cwd: pkgDir,
|
|
90
|
+
stdio: 'pipe',
|
|
91
|
+
timeout: 120_000,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
catch (err) {
|
|
95
|
+
logger.warn({ err }, 'npm install failed during update');
|
|
96
|
+
// Non-fatal — build may still work
|
|
97
|
+
}
|
|
98
|
+
// 3b. Rebuild native modules (better-sqlite3) for current Node version
|
|
99
|
+
// The postinstall hook silently swallows errors, so do this explicitly.
|
|
100
|
+
try {
|
|
101
|
+
execSync('npm rebuild better-sqlite3', {
|
|
102
|
+
cwd: pkgDir,
|
|
103
|
+
stdio: 'pipe',
|
|
104
|
+
timeout: 60_000,
|
|
105
|
+
});
|
|
106
|
+
logger.info('Native module rebuild succeeded');
|
|
107
|
+
}
|
|
108
|
+
catch (err) {
|
|
109
|
+
logger.warn({ err }, 'npm rebuild better-sqlite3 failed — memory may not work after restart');
|
|
110
|
+
}
|
|
111
|
+
// 4. Build — use local tsc binary directly (npx can fail if typescript isn't cached)
|
|
112
|
+
try {
|
|
113
|
+
execSync('./node_modules/.bin/tsc', {
|
|
114
|
+
cwd: pkgDir,
|
|
115
|
+
stdio: 'pipe',
|
|
116
|
+
timeout: 120_000,
|
|
117
|
+
});
|
|
118
|
+
logger.info('Build succeeded after update');
|
|
119
|
+
}
|
|
120
|
+
catch (err) {
|
|
121
|
+
logger.error({ err }, 'Build failed after update');
|
|
122
|
+
return { success: false, error: `Build failed after update: ${String(err)}` };
|
|
123
|
+
}
|
|
124
|
+
// 5. Reconcile source modifications
|
|
125
|
+
const reconcileResult = reconcileSourceMods(pkgDir);
|
|
126
|
+
logger.info({
|
|
127
|
+
reapplied: reconcileResult.reapplied.length,
|
|
128
|
+
superseded: reconcileResult.superseded.length,
|
|
129
|
+
needsReconciliation: reconcileResult.needsReconciliation.length,
|
|
130
|
+
failed: reconcileResult.failed.length,
|
|
131
|
+
}, 'Source mod reconciliation complete');
|
|
132
|
+
// 6. Rebuild if any mods were re-applied
|
|
133
|
+
if (reconcileResult.reapplied.length > 0) {
|
|
134
|
+
try {
|
|
135
|
+
execSync('./node_modules/.bin/tsc', { cwd: pkgDir, stdio: 'pipe', timeout: 120_000 });
|
|
136
|
+
logger.info('Rebuild succeeded after re-applying source mods');
|
|
137
|
+
}
|
|
138
|
+
catch (err) {
|
|
139
|
+
logger.warn({ err }, 'Rebuild failed after re-applying source mods');
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
// 6b. Run vault migrations
|
|
143
|
+
try {
|
|
144
|
+
const { runVaultMigrations } = await import('../vault-migrations/runner.js');
|
|
145
|
+
const migResult = await runVaultMigrations(path.join(BASE_DIR, 'vault'));
|
|
146
|
+
logger.info({
|
|
147
|
+
applied: migResult.applied.length,
|
|
148
|
+
skipped: migResult.skipped.length,
|
|
149
|
+
failed: migResult.failed.length,
|
|
150
|
+
}, 'Vault migrations complete');
|
|
151
|
+
}
|
|
152
|
+
catch (err) {
|
|
153
|
+
logger.warn({ err }, 'Vault migration failed (non-fatal)');
|
|
154
|
+
}
|
|
155
|
+
// 7. Get version info and write sentinel + restart
|
|
156
|
+
let commitHash = '';
|
|
157
|
+
let commitDate = '';
|
|
158
|
+
try {
|
|
159
|
+
commitHash = execSync('git rev-parse --short HEAD', { cwd: pkgDir, encoding: 'utf-8' }).trim();
|
|
160
|
+
commitDate = execSync('git log -1 --format=%ci HEAD', { cwd: pkgDir, encoding: 'utf-8' }).trim().slice(0, 10);
|
|
161
|
+
// Count would have been calculated pre-pull; approximate from reconcile context
|
|
162
|
+
}
|
|
163
|
+
catch { /* best effort */ }
|
|
164
|
+
const sentinel = {
|
|
165
|
+
previousPid: process.pid,
|
|
166
|
+
restartedAt: new Date().toISOString(),
|
|
167
|
+
reason: 'update',
|
|
168
|
+
updateDetails: {
|
|
169
|
+
commitHash,
|
|
170
|
+
commitDate,
|
|
171
|
+
modsReapplied: reconcileResult.reapplied.length,
|
|
172
|
+
modsSuperseded: reconcileResult.superseded.length,
|
|
173
|
+
modsNeedReconciliation: reconcileResult.needsReconciliation.length,
|
|
174
|
+
modsFailed: reconcileResult.failed.length,
|
|
175
|
+
},
|
|
176
|
+
};
|
|
177
|
+
writeFileSync(SENTINEL_PATH, JSON.stringify(sentinel, null, 2));
|
|
178
|
+
process.kill(process.pid, 'SIGUSR1');
|
|
179
|
+
return { success: true, reconcileResult };
|
|
180
|
+
}
|
|
181
|
+
catch (err) {
|
|
182
|
+
logger.error({ err }, 'Update apply failed');
|
|
183
|
+
return { success: false, error: String(err) };
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
//# sourceMappingURL=auto-update.js.map
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Clementine TypeScript — Daily Planning Session.
|
|
3
|
+
*
|
|
4
|
+
* Gathers context from goals, cron runs, reflections, tasks, and inbox,
|
|
5
|
+
* then uses a lightweight Haiku call to produce a prioritized daily plan.
|
|
6
|
+
* Plans are persisted to ~/.clementine/plans/daily/{date}.json.
|
|
7
|
+
*/
|
|
8
|
+
import type { DailyPlan } from '../types.js';
|
|
9
|
+
export declare class DailyPlanner {
|
|
10
|
+
constructor();
|
|
11
|
+
hasPlanForToday(): boolean;
|
|
12
|
+
getPlan(date?: string): DailyPlan | null;
|
|
13
|
+
plan(): Promise<DailyPlan>;
|
|
14
|
+
private gatherContext;
|
|
15
|
+
private loadActiveGoals;
|
|
16
|
+
private loadRecentCronFailures;
|
|
17
|
+
private loadRecentReflections;
|
|
18
|
+
private loadPendingTasks;
|
|
19
|
+
private countInboxItems;
|
|
20
|
+
private loadDelegatedTasks;
|
|
21
|
+
private generatePlan;
|
|
22
|
+
private fallbackPlan;
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=daily-planner.d.ts.map
|
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Clementine TypeScript — Daily Planning Session.
|
|
3
|
+
*
|
|
4
|
+
* Gathers context from goals, cron runs, reflections, tasks, and inbox,
|
|
5
|
+
* then uses a lightweight Haiku call to produce a prioritized daily plan.
|
|
6
|
+
* Plans are persisted to ~/.clementine/plans/daily/{date}.json.
|
|
7
|
+
*/
|
|
8
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
import pino from 'pino';
|
|
11
|
+
import Anthropic from '@anthropic-ai/sdk';
|
|
12
|
+
import { BASE_DIR, GOALS_DIR, CRON_REFLECTIONS_DIR, TASKS_FILE, INBOX_DIR, MODELS, } from '../config.js';
|
|
13
|
+
const logger = pino({ name: 'clementine.daily-planner' });
|
|
14
|
+
const PLANS_DIR = path.join(BASE_DIR, 'plans', 'daily');
|
|
15
|
+
// ── .env reader (self-contained — no config.ts secret imports) ───────
|
|
16
|
+
function getEnvValue(key) {
|
|
17
|
+
// Check process env first (already loaded by the daemon)
|
|
18
|
+
if (process.env[key])
|
|
19
|
+
return process.env[key];
|
|
20
|
+
// Fall back to .env file
|
|
21
|
+
const envPath = path.join(BASE_DIR, '.env');
|
|
22
|
+
if (!existsSync(envPath))
|
|
23
|
+
return '';
|
|
24
|
+
for (const line of readFileSync(envPath, 'utf-8').split('\n')) {
|
|
25
|
+
const trimmed = line.trim();
|
|
26
|
+
if (!trimmed || trimmed.startsWith('#'))
|
|
27
|
+
continue;
|
|
28
|
+
const eqIndex = trimmed.indexOf('=');
|
|
29
|
+
if (eqIndex === -1)
|
|
30
|
+
continue;
|
|
31
|
+
if (trimmed.slice(0, eqIndex) !== key)
|
|
32
|
+
continue;
|
|
33
|
+
let value = trimmed.slice(eqIndex + 1);
|
|
34
|
+
if ((value.startsWith('"') && value.endsWith('"')) ||
|
|
35
|
+
(value.startsWith("'") && value.endsWith("'"))) {
|
|
36
|
+
value = value.slice(1, -1);
|
|
37
|
+
}
|
|
38
|
+
return value;
|
|
39
|
+
}
|
|
40
|
+
return '';
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Build Anthropic client credentials.
|
|
44
|
+
* Priority: ANTHROPIC_AUTH_TOKEN (OAuth) > ANTHROPIC_API_KEY (legacy raw key).
|
|
45
|
+
* Returns null if neither is configured.
|
|
46
|
+
*/
|
|
47
|
+
function getAnthropicCredentials() {
|
|
48
|
+
const oauthToken = getEnvValue('CLAUDE_CODE_OAUTH_TOKEN');
|
|
49
|
+
if (oauthToken)
|
|
50
|
+
return { authToken: oauthToken };
|
|
51
|
+
const authToken = getEnvValue('ANTHROPIC_AUTH_TOKEN');
|
|
52
|
+
if (authToken)
|
|
53
|
+
return { authToken };
|
|
54
|
+
const apiKey = getEnvValue('ANTHROPIC_API_KEY');
|
|
55
|
+
if (apiKey)
|
|
56
|
+
return { apiKey };
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
// ── Helpers ──────────────────────────────────────────────────────────
|
|
60
|
+
function todayStr() {
|
|
61
|
+
const d = new Date();
|
|
62
|
+
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
|
|
63
|
+
}
|
|
64
|
+
// ── DailyPlanner ────────────────────────────────────────────────────
|
|
65
|
+
export class DailyPlanner {
|
|
66
|
+
constructor() {
|
|
67
|
+
if (!existsSync(PLANS_DIR)) {
|
|
68
|
+
mkdirSync(PLANS_DIR, { recursive: true });
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
hasPlanForToday() {
|
|
72
|
+
const planPath = path.join(PLANS_DIR, `${todayStr()}.json`);
|
|
73
|
+
return existsSync(planPath);
|
|
74
|
+
}
|
|
75
|
+
getPlan(date) {
|
|
76
|
+
const d = date ?? todayStr();
|
|
77
|
+
const planPath = path.join(PLANS_DIR, `${d}.json`);
|
|
78
|
+
if (!existsSync(planPath))
|
|
79
|
+
return null;
|
|
80
|
+
try {
|
|
81
|
+
return JSON.parse(readFileSync(planPath, 'utf-8'));
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
async plan() {
|
|
88
|
+
const context = this.gatherContext();
|
|
89
|
+
const plan = await this.generatePlan(context);
|
|
90
|
+
const planPath = path.join(PLANS_DIR, `${plan.date}.json`);
|
|
91
|
+
writeFileSync(planPath, JSON.stringify(plan, null, 2));
|
|
92
|
+
logger.info({ date: plan.date, priorities: plan.priorities.length }, 'Daily plan generated');
|
|
93
|
+
return plan;
|
|
94
|
+
}
|
|
95
|
+
// ── Context Gathering ───────────────────────────────────────────────
|
|
96
|
+
gatherContext() {
|
|
97
|
+
const sections = [];
|
|
98
|
+
// Active goals
|
|
99
|
+
const goals = this.loadActiveGoals();
|
|
100
|
+
if (goals.length > 0) {
|
|
101
|
+
const goalLines = goals.map(g => {
|
|
102
|
+
const parts = [`- "${g.title}" (${g.priority} priority, owner: ${g.owner})`];
|
|
103
|
+
if (g.nextActions?.length)
|
|
104
|
+
parts.push(` Next: ${g.nextActions.slice(0, 3).join('; ')}`);
|
|
105
|
+
if (g.blockers?.length)
|
|
106
|
+
parts.push(` Blockers: ${g.blockers.join('; ')}`);
|
|
107
|
+
if (g.targetDate)
|
|
108
|
+
parts.push(` Target: ${g.targetDate}`);
|
|
109
|
+
return parts.join('\n');
|
|
110
|
+
});
|
|
111
|
+
sections.push(`## Active Goals (${goals.length})\n${goalLines.join('\n')}`);
|
|
112
|
+
}
|
|
113
|
+
// Recent cron failures (last 24h)
|
|
114
|
+
const failures = this.loadRecentCronFailures();
|
|
115
|
+
if (failures.length > 0) {
|
|
116
|
+
const failLines = failures.map(f => `- ${f.jobName}: ${f.error ?? 'unknown error'} (${f.finishedAt})`);
|
|
117
|
+
sections.push(`## Recent Cron Failures (last 24h)\n${failLines.join('\n')}`);
|
|
118
|
+
}
|
|
119
|
+
// Recent reflections
|
|
120
|
+
const reflections = this.loadRecentReflections();
|
|
121
|
+
if (reflections.length > 0) {
|
|
122
|
+
const refLines = reflections.map(r => `- ${r.jobName} (quality: ${r.quality}/5): ${r.gap || 'no gap noted'}`);
|
|
123
|
+
sections.push(`## Recent Cron Reflections\n${refLines.join('\n')}`);
|
|
124
|
+
}
|
|
125
|
+
// Pending tasks
|
|
126
|
+
const pendingTasks = this.loadPendingTasks();
|
|
127
|
+
if (pendingTasks.length > 0) {
|
|
128
|
+
sections.push(`## Pending Tasks (${pendingTasks.length})\n${pendingTasks.slice(0, 15).map(t => `- ${t}`).join('\n')}`);
|
|
129
|
+
}
|
|
130
|
+
// Inbox
|
|
131
|
+
const inboxCount = this.countInboxItems();
|
|
132
|
+
if (inboxCount > 0) {
|
|
133
|
+
sections.push(`## Inbox\n${inboxCount} unprocessed item(s)`);
|
|
134
|
+
}
|
|
135
|
+
// Delegated tasks
|
|
136
|
+
const delegated = this.loadDelegatedTasks();
|
|
137
|
+
if (delegated.length > 0) {
|
|
138
|
+
sections.push(`## Delegated Tasks (${delegated.length})\n${delegated.map(d => `- [${d.status}] ${d.task} (to: ${d.toAgent})`).join('\n')}`);
|
|
139
|
+
}
|
|
140
|
+
return sections.join('\n\n') || 'No context available — all clear.';
|
|
141
|
+
}
|
|
142
|
+
loadActiveGoals() {
|
|
143
|
+
if (!existsSync(GOALS_DIR))
|
|
144
|
+
return [];
|
|
145
|
+
try {
|
|
146
|
+
const files = readdirSync(GOALS_DIR).filter(f => f.endsWith('.json'));
|
|
147
|
+
const goals = [];
|
|
148
|
+
for (const f of files) {
|
|
149
|
+
try {
|
|
150
|
+
const goal = JSON.parse(readFileSync(path.join(GOALS_DIR, f), 'utf-8'));
|
|
151
|
+
if (goal.status === 'active')
|
|
152
|
+
goals.push(goal);
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return goals.sort((a, b) => {
|
|
159
|
+
const p = { high: 0, medium: 1, low: 2 };
|
|
160
|
+
return (p[a.priority] ?? 2) - (p[b.priority] ?? 2);
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
return [];
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
loadRecentCronFailures() {
|
|
168
|
+
const runDir = path.join(BASE_DIR, 'cron', 'runs');
|
|
169
|
+
if (!existsSync(runDir))
|
|
170
|
+
return [];
|
|
171
|
+
const cutoff = Date.now() - 86_400_000;
|
|
172
|
+
const failures = [];
|
|
173
|
+
try {
|
|
174
|
+
for (const f of readdirSync(runDir).filter(f => f.endsWith('.jsonl'))) {
|
|
175
|
+
try {
|
|
176
|
+
const lines = readFileSync(path.join(runDir, f), 'utf-8').trim().split('\n').filter(Boolean);
|
|
177
|
+
for (const line of lines.slice(-10)) {
|
|
178
|
+
try {
|
|
179
|
+
const entry = JSON.parse(line);
|
|
180
|
+
if (entry.status !== 'ok' && entry.finishedAt && new Date(entry.finishedAt).getTime() > cutoff) {
|
|
181
|
+
failures.push({ jobName: entry.jobName ?? f.replace('.jsonl', ''), error: entry.error, finishedAt: entry.finishedAt });
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
catch {
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
catch {
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
catch { /* non-fatal */ }
|
|
195
|
+
return failures;
|
|
196
|
+
}
|
|
197
|
+
loadRecentReflections() {
|
|
198
|
+
if (!existsSync(CRON_REFLECTIONS_DIR))
|
|
199
|
+
return [];
|
|
200
|
+
const results = [];
|
|
201
|
+
try {
|
|
202
|
+
for (const f of readdirSync(CRON_REFLECTIONS_DIR).filter(f => f.endsWith('.jsonl'))) {
|
|
203
|
+
try {
|
|
204
|
+
const lines = readFileSync(path.join(CRON_REFLECTIONS_DIR, f), 'utf-8').trim().split('\n').filter(Boolean);
|
|
205
|
+
const recent = lines.slice(-5);
|
|
206
|
+
for (const line of recent) {
|
|
207
|
+
try {
|
|
208
|
+
const entry = JSON.parse(line);
|
|
209
|
+
results.push({ jobName: entry.jobName ?? f.replace('.jsonl', ''), quality: entry.quality ?? 0, gap: entry.gap ?? '' });
|
|
210
|
+
}
|
|
211
|
+
catch {
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
catch {
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
catch { /* non-fatal */ }
|
|
222
|
+
return results;
|
|
223
|
+
}
|
|
224
|
+
loadPendingTasks() {
|
|
225
|
+
if (!existsSync(TASKS_FILE))
|
|
226
|
+
return [];
|
|
227
|
+
try {
|
|
228
|
+
const content = readFileSync(TASKS_FILE, 'utf-8');
|
|
229
|
+
return content.split('\n')
|
|
230
|
+
.filter(line => /^\s*- \[ \]/.test(line))
|
|
231
|
+
.map(line => line.trim().replace(/^- \[ \]\s*/, ''));
|
|
232
|
+
}
|
|
233
|
+
catch {
|
|
234
|
+
return [];
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
countInboxItems() {
|
|
238
|
+
if (!existsSync(INBOX_DIR))
|
|
239
|
+
return 0;
|
|
240
|
+
try {
|
|
241
|
+
return readdirSync(INBOX_DIR).filter(f => f.endsWith('.md') && !f.startsWith('_')).length;
|
|
242
|
+
}
|
|
243
|
+
catch {
|
|
244
|
+
return 0;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
loadDelegatedTasks() {
|
|
248
|
+
const delegationsDir = path.join(BASE_DIR, 'vault', '00-System', 'agents');
|
|
249
|
+
if (!existsSync(delegationsDir))
|
|
250
|
+
return [];
|
|
251
|
+
const results = [];
|
|
252
|
+
try {
|
|
253
|
+
for (const agentDir of readdirSync(delegationsDir)) {
|
|
254
|
+
const tasksDir = path.join(delegationsDir, agentDir, 'delegations');
|
|
255
|
+
if (!existsSync(tasksDir))
|
|
256
|
+
continue;
|
|
257
|
+
try {
|
|
258
|
+
for (const f of readdirSync(tasksDir).filter(f => f.endsWith('.json'))) {
|
|
259
|
+
try {
|
|
260
|
+
const task = JSON.parse(readFileSync(path.join(tasksDir, f), 'utf-8'));
|
|
261
|
+
if (task.status !== 'completed') {
|
|
262
|
+
results.push({ task: task.task ?? f, toAgent: task.toAgent ?? agentDir, status: task.status ?? 'pending' });
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
catch {
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
catch {
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
catch { /* non-fatal */ }
|
|
276
|
+
return results;
|
|
277
|
+
}
|
|
278
|
+
// ── Plan Generation ─────────────────────────────────────────────────
|
|
279
|
+
async generatePlan(context) {
|
|
280
|
+
const today = todayStr();
|
|
281
|
+
const prompt = `Today is ${today}. Based on the following context about active goals, tasks, cron job health, and inbox items, produce a prioritized daily plan.
|
|
282
|
+
|
|
283
|
+
${context}
|
|
284
|
+
|
|
285
|
+
Respond with a JSON object (no markdown fencing) matching this schema:
|
|
286
|
+
{
|
|
287
|
+
"date": "${today}",
|
|
288
|
+
"createdAt": "<ISO timestamp>",
|
|
289
|
+
"priorities": [
|
|
290
|
+
{ "type": "goal|task|cron-fix|inbox", "id": "<goal-id or description>", "action": "<what to do>", "urgency": <1-5> }
|
|
291
|
+
],
|
|
292
|
+
"suggestedCronChanges": [
|
|
293
|
+
{ "job": "<job name>", "change": "adjust-schedule|adjust-prompt|disable", "reason": "<why>" }
|
|
294
|
+
],
|
|
295
|
+
"newWork": [
|
|
296
|
+
{ "description": "<what to do>", "goalId": "<optional goal id>", "suggestedSchedule": "<optional cron expression>" }
|
|
297
|
+
],
|
|
298
|
+
"summary": "<2-3 sentence summary of the day's focus>"
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
Rules:
|
|
302
|
+
- Urgency 5 = critical/overdue, 1 = nice-to-have
|
|
303
|
+
- Limit to at most 10 priorities, 5 cron changes, 5 new work items
|
|
304
|
+
- Focus on actionable items, not status reports
|
|
305
|
+
- If everything is on track, return minimal priorities`;
|
|
306
|
+
const creds = getAnthropicCredentials();
|
|
307
|
+
if (!creds) {
|
|
308
|
+
logger.warn('No Anthropic credentials found — generating fallback plan. Run `clementine login` to authenticate.');
|
|
309
|
+
return this.fallbackPlan(today);
|
|
310
|
+
}
|
|
311
|
+
try {
|
|
312
|
+
const client = new Anthropic(creds.authToken ? { authToken: creds.authToken } : { apiKey: creds.apiKey });
|
|
313
|
+
const response = await client.messages.create({
|
|
314
|
+
model: MODELS.haiku,
|
|
315
|
+
max_tokens: 2000,
|
|
316
|
+
messages: [{ role: 'user', content: prompt }],
|
|
317
|
+
system: 'You are a planning assistant. Analyze the context and produce a prioritized daily plan as JSON. Return only valid JSON, no markdown fencing.',
|
|
318
|
+
});
|
|
319
|
+
const text = response.content
|
|
320
|
+
.filter(block => block.type === 'text')
|
|
321
|
+
.map(block => block.text)
|
|
322
|
+
.join('');
|
|
323
|
+
const plan = JSON.parse(text);
|
|
324
|
+
plan.date = today;
|
|
325
|
+
plan.createdAt = new Date().toISOString();
|
|
326
|
+
plan.priorities = plan.priorities ?? [];
|
|
327
|
+
plan.suggestedCronChanges = plan.suggestedCronChanges ?? [];
|
|
328
|
+
plan.newWork = plan.newWork ?? [];
|
|
329
|
+
plan.summary = plan.summary ?? 'No summary generated.';
|
|
330
|
+
return plan;
|
|
331
|
+
}
|
|
332
|
+
catch (err) {
|
|
333
|
+
logger.warn({ err }, 'LLM plan generation failed — using fallback');
|
|
334
|
+
return this.fallbackPlan(today);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
fallbackPlan(date) {
|
|
338
|
+
const priorities = [];
|
|
339
|
+
// Add high-priority goals
|
|
340
|
+
const goals = this.loadActiveGoals();
|
|
341
|
+
for (const g of goals.filter(g => g.priority === 'high').slice(0, 3)) {
|
|
342
|
+
priorities.push({
|
|
343
|
+
type: 'goal',
|
|
344
|
+
id: g.id,
|
|
345
|
+
action: g.nextActions?.[0] ?? `Review "${g.title}"`,
|
|
346
|
+
urgency: 4,
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
// Add cron failures
|
|
350
|
+
const failures = this.loadRecentCronFailures();
|
|
351
|
+
for (const f of failures.slice(0, 3)) {
|
|
352
|
+
priorities.push({
|
|
353
|
+
type: 'cron-fix',
|
|
354
|
+
id: f.jobName,
|
|
355
|
+
action: `Fix failing job: ${f.jobName}`,
|
|
356
|
+
urgency: 4,
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
// Add inbox
|
|
360
|
+
const inbox = this.countInboxItems();
|
|
361
|
+
if (inbox > 0) {
|
|
362
|
+
priorities.push({
|
|
363
|
+
type: 'inbox',
|
|
364
|
+
id: 'inbox',
|
|
365
|
+
action: `Process ${inbox} inbox item(s)`,
|
|
366
|
+
urgency: 2,
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
return {
|
|
370
|
+
date,
|
|
371
|
+
createdAt: new Date().toISOString(),
|
|
372
|
+
priorities,
|
|
373
|
+
suggestedCronChanges: [],
|
|
374
|
+
newWork: [],
|
|
375
|
+
summary: `Fallback plan: ${priorities.length} items to address. LLM planning unavailable.`,
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
//# sourceMappingURL=daily-planner.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
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 type { CronJobDefinition, ExecutionAdvice } from '../types.js';
|
|
9
|
+
export declare function getExecutionAdvice(jobName: string, job: CronJobDefinition): ExecutionAdvice;
|
|
10
|
+
//# sourceMappingURL=execution-advisor.d.ts.map
|