clementine-agent 1.0.11 → 1.0.13
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/agent/agent-manager.js +32 -0
- package/dist/agent/assistant.js +8 -22
- package/dist/agent/daily-planner.js +5 -15
- package/dist/agent/insight-engine.js +4 -14
- package/dist/agent/prompt-evolver.js +7 -15
- package/dist/agent/self-improve.js +20 -26
- package/dist/agent/strategic-planner.js +5 -26
- package/dist/agent/team-bus.js +5 -14
- package/dist/cli/dashboard.js +2 -3
- package/dist/cli/routes/digest.d.ts +0 -1
- package/dist/cli/routes/digest.js +6 -11
- package/dist/cli/routes/goals.d.ts +5 -2
- package/dist/cli/routes/goals.js +85 -74
- package/dist/gateway/cron-scheduler.js +31 -20
- package/dist/gateway/heartbeat-scheduler.d.ts +3 -5
- package/dist/gateway/heartbeat-scheduler.js +8 -16
- package/dist/tools/goal-tools.d.ts +4 -2
- package/dist/tools/goal-tools.js +46 -58
- package/dist/tools/session-tools.js +14 -21
- package/dist/tools/shared.d.ts +40 -0
- package/dist/tools/shared.js +84 -0
- package/package.json +1 -1
|
@@ -14,8 +14,10 @@ import { execSync } from 'node:child_process';
|
|
|
14
14
|
import fs from 'node:fs';
|
|
15
15
|
import path from 'node:path';
|
|
16
16
|
import matter from 'gray-matter';
|
|
17
|
+
import { randomBytes } from 'node:crypto';
|
|
17
18
|
import { ProfileManager } from './profiles.js';
|
|
18
19
|
import { getScaffoldForRole } from './role-scaffolds.js';
|
|
20
|
+
import { writeGoalForOwner } from '../tools/shared.js';
|
|
19
21
|
// ── Keychain helpers for agent secrets ────────────────────────────────
|
|
20
22
|
function storeAgentSecret(slug, key, value) {
|
|
21
23
|
execSync(`security add-generic-password -U -s "clementine" -a "AGENT_${slug.toUpperCase()}_${key}" -w "${value}"`, { stdio: 'pipe', timeout: 3000 });
|
|
@@ -324,6 +326,36 @@ export class AgentManager {
|
|
|
324
326
|
}
|
|
325
327
|
}
|
|
326
328
|
}
|
|
329
|
+
// Seed a starter goal so the agent shows up in goal reviews from day one.
|
|
330
|
+
// Status is "pending" — forces the owner to give it a real success metric
|
|
331
|
+
// before it starts driving goal_work sessions.
|
|
332
|
+
try {
|
|
333
|
+
const now = new Date().toISOString();
|
|
334
|
+
const starterGoal = {
|
|
335
|
+
id: randomBytes(4).toString('hex'),
|
|
336
|
+
title: `${config.name}: Define Success Metric`,
|
|
337
|
+
description: `Starter goal auto-created when ${config.name} was hired. Replace this with a ` +
|
|
338
|
+
`concrete outcome and measurable success metric (e.g., "book 3 demos/week", ` +
|
|
339
|
+
`"publish 2 posts/week", "reduce queue backlog under 50 items"). Set status to ` +
|
|
340
|
+
`"active" once defined so goal_work sessions can drive progress.`,
|
|
341
|
+
status: 'pending',
|
|
342
|
+
owner: slug,
|
|
343
|
+
priority: 'high',
|
|
344
|
+
createdAt: now,
|
|
345
|
+
updatedAt: now,
|
|
346
|
+
progressNotes: [],
|
|
347
|
+
nextActions: [
|
|
348
|
+
`Define the measurable success metric for ${config.name}`,
|
|
349
|
+
'Link the relevant cron jobs once the metric is set',
|
|
350
|
+
'Set status to "active" to enable goal_work sessions',
|
|
351
|
+
],
|
|
352
|
+
blockers: ['Success metric not yet defined'],
|
|
353
|
+
reviewFrequency: 'weekly',
|
|
354
|
+
linkedCronJobs: [],
|
|
355
|
+
};
|
|
356
|
+
writeGoalForOwner(starterGoal);
|
|
357
|
+
}
|
|
358
|
+
catch { /* non-fatal — agent is still created */ }
|
|
327
359
|
// Invalidate cache
|
|
328
360
|
this.cacheTime = 0;
|
|
329
361
|
return this.get(slug);
|
package/dist/agent/assistant.js
CHANGED
|
@@ -13,11 +13,11 @@ import fs from 'node:fs';
|
|
|
13
13
|
import path from 'node:path';
|
|
14
14
|
import { query as rawQuery, listSubagents, getSubagentMessages, } from '@anthropic-ai/claude-agent-sdk';
|
|
15
15
|
import pino from 'pino';
|
|
16
|
-
import { BASE_DIR, PKG_DIR, VAULT_DIR, DAILY_NOTES_DIR, SOUL_FILE, AGENTS_FILE, MEMORY_FILE, PROFILES_DIR, AGENTS_DIR, ASSISTANT_NAME, OWNER_NAME, MODEL, MODELS, HEARTBEAT_MAX_TURNS, SEARCH_CONTEXT_LIMIT, SEARCH_RECENCY_LIMIT, SYSTEM_PROMPT_MAX_CONTEXT_CHARS, SESSION_EXCHANGE_HISTORY_SIZE, SESSION_EXCHANGE_MAX_CHARS, INJECTED_CONTEXT_MAX_CHARS, UNLEASHED_PHASE_TURNS, UNLEASHED_DEFAULT_MAX_HOURS, UNLEASHED_MAX_PHASES, PROJECTS_META_FILE,
|
|
16
|
+
import { BASE_DIR, PKG_DIR, VAULT_DIR, DAILY_NOTES_DIR, SOUL_FILE, AGENTS_FILE, MEMORY_FILE, PROFILES_DIR, AGENTS_DIR, ASSISTANT_NAME, OWNER_NAME, MODEL, MODELS, HEARTBEAT_MAX_TURNS, SEARCH_CONTEXT_LIMIT, SEARCH_RECENCY_LIMIT, SYSTEM_PROMPT_MAX_CONTEXT_CHARS, SESSION_EXCHANGE_HISTORY_SIZE, SESSION_EXCHANGE_MAX_CHARS, INJECTED_CONTEXT_MAX_CHARS, UNLEASHED_PHASE_TURNS, UNLEASHED_DEFAULT_MAX_HOURS, UNLEASHED_MAX_PHASES, PROJECTS_META_FILE, CRON_PROGRESS_DIR, CRON_REFLECTIONS_DIR, HANDOFFS_DIR, BUDGET, ENABLE_1M_CONTEXT, IDENTITY_FILE, CLAUDE_CODE_OAUTH_TOKEN, ANTHROPIC_API_KEY as CONFIG_ANTHROPIC_API_KEY, } from '../config.js';
|
|
17
17
|
import { DEFAULT_CHANNEL_CAPABILITIES } from '../types.js';
|
|
18
18
|
import { enforceToolPermissions, getSecurityPrompt, getHeartbeatSecurityPrompt, getCronSecurityPrompt, getHeartbeatDisallowedTools, logToolUse, setProfileTier, setProfileAllowedTools, setAgentDir, setSendPolicy, setInteractionSource, } from './hooks.js';
|
|
19
19
|
import { scanner } from '../security/scanner.js';
|
|
20
|
-
import { agentWorkingMemoryFile } from '../tools/shared.js';
|
|
20
|
+
import { agentWorkingMemoryFile, listAllGoals } from '../tools/shared.js';
|
|
21
21
|
import { AgentManager } from './agent-manager.js';
|
|
22
22
|
import { extractLinks } from './link-extractor.js';
|
|
23
23
|
import { StallGuard } from './stall-guard.js';
|
|
@@ -1491,17 +1491,9 @@ You have a cost budget per message — not a hard turn limit. Work until the tas
|
|
|
1491
1491
|
}
|
|
1492
1492
|
const goals = [];
|
|
1493
1493
|
try {
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
try {
|
|
1498
|
-
const goal = JSON.parse(fs.readFileSync(path.join(GOALS_DIR, f), 'utf-8'));
|
|
1499
|
-
if (goal.status === 'active')
|
|
1500
|
-
goals.push({ goal, file: f });
|
|
1501
|
-
}
|
|
1502
|
-
catch {
|
|
1503
|
-
continue;
|
|
1504
|
-
}
|
|
1494
|
+
for (const { goal, filePath } of listAllGoals()) {
|
|
1495
|
+
if (goal.status === 'active')
|
|
1496
|
+
goals.push({ goal, file: path.basename(filePath) });
|
|
1505
1497
|
}
|
|
1506
1498
|
}
|
|
1507
1499
|
catch { /* non-fatal */ }
|
|
@@ -3039,15 +3031,9 @@ You have a cost budget per message — not a hard turn limit. Work until the tas
|
|
|
3039
3031
|
// ── Goal context: inject linked goal info ───────────────────────
|
|
3040
3032
|
let goalContext = '';
|
|
3041
3033
|
try {
|
|
3042
|
-
|
|
3043
|
-
const
|
|
3044
|
-
|
|
3045
|
-
.map(f => { try {
|
|
3046
|
-
return JSON.parse(fs.readFileSync(path.join(GOALS_DIR, f), 'utf-8'));
|
|
3047
|
-
}
|
|
3048
|
-
catch {
|
|
3049
|
-
return null;
|
|
3050
|
-
} })
|
|
3034
|
+
{
|
|
3035
|
+
const linkedGoals = listAllGoals()
|
|
3036
|
+
.map(({ goal }) => goal)
|
|
3051
3037
|
.filter(g => g && g.status === 'active' && g.linkedCronJobs?.includes(jobName));
|
|
3052
3038
|
if (linkedGoals.length > 0) {
|
|
3053
3039
|
const goalLines = linkedGoals.map((g) => {
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs';
|
|
9
9
|
import path from 'node:path';
|
|
10
10
|
import pino from 'pino';
|
|
11
|
-
import { BASE_DIR,
|
|
11
|
+
import { BASE_DIR, CRON_REFLECTIONS_DIR, TASKS_FILE, INBOX_DIR, MODELS, } from '../config.js';
|
|
12
|
+
import { listAllGoals } from '../tools/shared.js';
|
|
12
13
|
const logger = pino({ name: 'clementine.daily-planner' });
|
|
13
14
|
const PLANS_DIR = path.join(BASE_DIR, 'plans', 'daily');
|
|
14
15
|
// ── Helpers ──────────────────────────────────────────────────────────
|
|
@@ -95,21 +96,10 @@ export class DailyPlanner {
|
|
|
95
96
|
return sections.join('\n\n') || 'No context available — all clear.';
|
|
96
97
|
}
|
|
97
98
|
loadActiveGoals() {
|
|
98
|
-
if (!existsSync(GOALS_DIR))
|
|
99
|
-
return [];
|
|
100
99
|
try {
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
try {
|
|
105
|
-
const goal = JSON.parse(readFileSync(path.join(GOALS_DIR, f), 'utf-8'));
|
|
106
|
-
if (goal.status === 'active')
|
|
107
|
-
goals.push(goal);
|
|
108
|
-
}
|
|
109
|
-
catch {
|
|
110
|
-
continue;
|
|
111
|
-
}
|
|
112
|
-
}
|
|
100
|
+
const goals = listAllGoals()
|
|
101
|
+
.map(({ goal }) => goal)
|
|
102
|
+
.filter(g => g && g.status === 'active');
|
|
113
103
|
return goals.sort((a, b) => {
|
|
114
104
|
const p = { high: 0, medium: 1, low: 2 };
|
|
115
105
|
return (p[a.priority] ?? 2) - (p[b.priority] ?? 2);
|
|
@@ -12,6 +12,7 @@ import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
|
|
12
12
|
import path from 'node:path';
|
|
13
13
|
import pino from 'pino';
|
|
14
14
|
import { GOALS_DIR, BASE_DIR } from '../config.js';
|
|
15
|
+
import { listAllGoals } from '../tools/shared.js';
|
|
15
16
|
const logger = pino({ name: 'clementine.insight-engine' });
|
|
16
17
|
const BASE_COOLDOWN_MS = 2 * 60 * 60 * 1000; // 2 hours
|
|
17
18
|
const MAX_DAILY_INSIGHTS = 3;
|
|
@@ -133,20 +134,9 @@ export function gatherInsightSignals(gateway) {
|
|
|
133
134
|
if (sessionCount === 0) {
|
|
134
135
|
// No recent activity — could note quiet period if there are pending goals
|
|
135
136
|
try {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
try {
|
|
140
|
-
const g = JSON.parse(readFileSync(path.join(GOALS_DIR, f), 'utf-8'));
|
|
141
|
-
return g.status === 'active' && g.priority === 'high';
|
|
142
|
-
}
|
|
143
|
-
catch {
|
|
144
|
-
return false;
|
|
145
|
-
}
|
|
146
|
-
});
|
|
147
|
-
if (activeHighPriority.length > 0) {
|
|
148
|
-
signals.push(`Quiet period: ${activeHighPriority.length} high-priority goal(s) active but no recent user interaction`);
|
|
149
|
-
}
|
|
137
|
+
const activeHighPriority = listAllGoals().filter(({ goal }) => goal.status === 'active' && goal.priority === 'high');
|
|
138
|
+
if (activeHighPriority.length > 0) {
|
|
139
|
+
signals.push(`Quiet period: ${activeHighPriority.length} high-priority goal(s) active but no recent user interaction`);
|
|
150
140
|
}
|
|
151
141
|
}
|
|
152
142
|
catch { /* non-fatal */ }
|
|
@@ -5,10 +5,11 @@
|
|
|
5
5
|
* reflections, progress state, and goal context. Returns the enrichment
|
|
6
6
|
* string to be appended to the original prompt.
|
|
7
7
|
*/
|
|
8
|
-
import { appendFileSync, existsSync, mkdirSync, readFileSync
|
|
8
|
+
import { appendFileSync, existsSync, mkdirSync, readFileSync } from 'node:fs';
|
|
9
9
|
import path from 'node:path';
|
|
10
10
|
import pino from 'pino';
|
|
11
|
-
import { BASE_DIR, CRON_REFLECTIONS_DIR, CRON_PROGRESS_DIR
|
|
11
|
+
import { BASE_DIR, CRON_REFLECTIONS_DIR, CRON_PROGRESS_DIR } from '../config.js';
|
|
12
|
+
import { listAllGoals } from '../tools/shared.js';
|
|
12
13
|
const logger = pino({ name: 'clementine.prompt-evolver' });
|
|
13
14
|
/**
|
|
14
15
|
* Evolve a static cron prompt by enriching it with lessons from reflections,
|
|
@@ -225,22 +226,13 @@ function extractProgressInsights(jobName) {
|
|
|
225
226
|
* Find goals that reference this cron job and inject alignment guidance.
|
|
226
227
|
*/
|
|
227
228
|
function extractGoalGuidance(jobName) {
|
|
228
|
-
if (!existsSync(GOALS_DIR))
|
|
229
|
-
return null;
|
|
230
229
|
try {
|
|
231
|
-
const files = readdirSync(GOALS_DIR).filter(f => f.endsWith('.json'));
|
|
232
230
|
const linkedGoals = [];
|
|
233
|
-
for (const
|
|
234
|
-
|
|
235
|
-
const goal = JSON.parse(readFileSync(path.join(GOALS_DIR, f), 'utf-8'));
|
|
236
|
-
if (goal.status !== 'active')
|
|
237
|
-
continue;
|
|
238
|
-
if (goal.linkedCronJobs?.includes(jobName)) {
|
|
239
|
-
linkedGoals.push(goal);
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
catch {
|
|
231
|
+
for (const { goal } of listAllGoals()) {
|
|
232
|
+
if (goal.status !== 'active')
|
|
243
233
|
continue;
|
|
234
|
+
if (goal.linkedCronJobs?.includes(jobName)) {
|
|
235
|
+
linkedGoals.push(goal);
|
|
244
236
|
}
|
|
245
237
|
}
|
|
246
238
|
if (linkedGoals.length === 0)
|
|
@@ -14,6 +14,7 @@ import matter from 'gray-matter';
|
|
|
14
14
|
import path from 'node:path';
|
|
15
15
|
import pino from 'pino';
|
|
16
16
|
import { BASE_DIR, SELF_IMPROVE_DIR, SOUL_FILE, AGENTS_FILE, CRON_FILE, WORKFLOWS_DIR, VAULT_DIR, MEMORY_DB_PATH, AGENTS_DIR, PKG_DIR, CRON_REFLECTIONS_DIR, GOALS_DIR, } from '../config.js';
|
|
17
|
+
import { listAllGoals } from '../tools/shared.js';
|
|
17
18
|
const logger = pino({ name: 'clementine.self-improve' });
|
|
18
19
|
// ── Defaults ─────────────────────────────────────────────────────────
|
|
19
20
|
const DEFAULT_CONFIG = {
|
|
@@ -481,34 +482,27 @@ export class SelfImproveLoop {
|
|
|
481
482
|
}
|
|
482
483
|
}
|
|
483
484
|
catch { /* non-fatal */ }
|
|
484
|
-
// Gather goal health data
|
|
485
|
+
// Gather goal health data (walks global + per-agent goals dirs)
|
|
485
486
|
const goalHealth = [];
|
|
486
487
|
try {
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
const
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
isStale: goal.status === 'active' && daysSinceUpdate > staleThreshold,
|
|
506
|
-
linkedCronJobs: goal.linkedCronJobs || [],
|
|
507
|
-
progressCount: goal.progressNotes?.length ?? 0,
|
|
508
|
-
});
|
|
509
|
-
}
|
|
510
|
-
catch { /* skip malformed */ }
|
|
511
|
-
}
|
|
488
|
+
const now = Date.now();
|
|
489
|
+
const DAY_MS = 86_400_000;
|
|
490
|
+
for (const { goal, owner } of listAllGoals()) {
|
|
491
|
+
const lastUpdate = goal.updatedAt ? new Date(goal.updatedAt).getTime() : 0;
|
|
492
|
+
const daysSinceUpdate = Math.floor((now - lastUpdate) / DAY_MS);
|
|
493
|
+
const staleThreshold = goal.reviewFrequency === 'daily' ? 1 : goal.reviewFrequency === 'weekly' ? 7 : 30;
|
|
494
|
+
goalHealth.push({
|
|
495
|
+
id: goal.id,
|
|
496
|
+
title: goal.title,
|
|
497
|
+
status: goal.status,
|
|
498
|
+
owner: owner,
|
|
499
|
+
priority: goal.priority,
|
|
500
|
+
daysSinceUpdate,
|
|
501
|
+
reviewFrequency: goal.reviewFrequency,
|
|
502
|
+
isStale: goal.status === 'active' && daysSinceUpdate > staleThreshold,
|
|
503
|
+
linkedCronJobs: goal.linkedCronJobs || [],
|
|
504
|
+
progressCount: goal.progressNotes?.length ?? 0,
|
|
505
|
+
});
|
|
512
506
|
}
|
|
513
507
|
}
|
|
514
508
|
catch { /* non-fatal */ }
|
|
@@ -13,6 +13,7 @@ import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from
|
|
|
13
13
|
import path from 'node:path';
|
|
14
14
|
import pino from 'pino';
|
|
15
15
|
import { BASE_DIR, GOALS_DIR, MODELS } from '../config.js';
|
|
16
|
+
import { listAllGoals } from '../tools/shared.js';
|
|
16
17
|
const logger = pino({ name: 'clementine.strategic-planner' });
|
|
17
18
|
const DAILY_PLANS_DIR = path.join(BASE_DIR, 'plans', 'daily');
|
|
18
19
|
const WEEKLY_PLANS_DIR = path.join(BASE_DIR, 'plans', 'weekly');
|
|
@@ -265,34 +266,12 @@ export class StrategicPlanner {
|
|
|
265
266
|
}).filter(Boolean);
|
|
266
267
|
}
|
|
267
268
|
loadActiveGoals() {
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
.filter(f => f.endsWith('.json'))
|
|
272
|
-
.map(f => {
|
|
273
|
-
try {
|
|
274
|
-
return JSON.parse(readFileSync(path.join(GOALS_DIR, f), 'utf-8'));
|
|
275
|
-
}
|
|
276
|
-
catch {
|
|
277
|
-
return null;
|
|
278
|
-
}
|
|
279
|
-
})
|
|
280
|
-
.filter((g) => g && g.status === 'active');
|
|
269
|
+
return listAllGoals()
|
|
270
|
+
.map(({ goal }) => goal)
|
|
271
|
+
.filter(g => g && g.status === 'active');
|
|
281
272
|
}
|
|
282
273
|
loadAllGoals() {
|
|
283
|
-
|
|
284
|
-
return [];
|
|
285
|
-
return readdirSync(GOALS_DIR)
|
|
286
|
-
.filter(f => f.endsWith('.json'))
|
|
287
|
-
.map(f => {
|
|
288
|
-
try {
|
|
289
|
-
return JSON.parse(readFileSync(path.join(GOALS_DIR, f), 'utf-8'));
|
|
290
|
-
}
|
|
291
|
-
catch {
|
|
292
|
-
return null;
|
|
293
|
-
}
|
|
294
|
-
})
|
|
295
|
-
.filter(Boolean);
|
|
274
|
+
return listAllGoals().map(({ goal }) => goal);
|
|
296
275
|
}
|
|
297
276
|
}
|
|
298
277
|
// ── Date helpers ───────────────────────────────────────────────────────
|
package/dist/agent/team-bus.js
CHANGED
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
* Logs to JSONL and optionally mirrors to a Discord channel.
|
|
6
6
|
*/
|
|
7
7
|
import { createHash, randomBytes } from 'node:crypto';
|
|
8
|
-
import { appendFileSync, existsSync, mkdirSync, readFileSync,
|
|
9
|
-
import os from 'node:os';
|
|
8
|
+
import { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
10
9
|
import path from 'node:path';
|
|
11
10
|
import pino from 'pino';
|
|
11
|
+
import { listAllGoals } from '../tools/shared.js';
|
|
12
12
|
const logger = pino({ name: 'clementine.team-bus' });
|
|
13
13
|
/** Max inter-agent message depth before rejection (anti-loop). */
|
|
14
14
|
const MAX_DEPTH = 3;
|
|
@@ -247,19 +247,10 @@ export class TeamBus {
|
|
|
247
247
|
}
|
|
248
248
|
/** Broadcast a message to all team agents (optionally scoped to a goal). */
|
|
249
249
|
async broadcast(fromSlug, content, goalId, _sessionKey) {
|
|
250
|
-
const goalsDir = path.join(process.env.CLEMENTINE_HOME || path.join(os.homedir(), '.clementine'), 'goals');
|
|
251
250
|
let targetSlugs = [];
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
const goal = JSON.parse(readFileSync(path.join(goalsDir, f), 'utf-8'));
|
|
256
|
-
if (goal.id === goalId && goal.owner && goal.owner !== fromSlug) {
|
|
257
|
-
targetSlugs.push(goal.owner);
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
catch {
|
|
261
|
-
continue;
|
|
262
|
-
}
|
|
251
|
+
for (const { goal, owner } of listAllGoals()) {
|
|
252
|
+
if (goal.id === goalId && owner && owner !== fromSlug) {
|
|
253
|
+
targetSlugs.push(owner);
|
|
263
254
|
}
|
|
264
255
|
}
|
|
265
256
|
const allAgents = this.teamRouter.listTeamAgents();
|
package/dist/cli/dashboard.js
CHANGED
|
@@ -5684,15 +5684,14 @@ export async function cmdDashboard(opts) {
|
|
|
5684
5684
|
res.json(analytics);
|
|
5685
5685
|
});
|
|
5686
5686
|
// ── Goals, Delegations, Workflows, Digest — extracted routers ──
|
|
5687
|
-
const GOALS_DIR = path.join(BASE_DIR, 'goals');
|
|
5688
5687
|
const CRON_RUNS_DIR = path.join(BASE_DIR, 'cron', 'runs');
|
|
5689
5688
|
const AGENTS_BASE = path.join(VAULT_DIR, '00-System', 'agents');
|
|
5690
5689
|
const WORKFLOWS_DIR = path.join(VAULT_DIR, '00-System', 'workflows');
|
|
5691
5690
|
const WORKFLOW_RUNS_DIR = path.join(BASE_DIR, 'workflows', 'runs');
|
|
5692
|
-
app.use('/api/goals', goalsRouter({
|
|
5691
|
+
app.use('/api/goals', goalsRouter({ cronRunsDir: CRON_RUNS_DIR, vaultDir: VAULT_DIR, cronFile: CRON_FILE, getGateway }));
|
|
5693
5692
|
app.use('/api/delegations', delegationsRouter({ agentsBase: AGENTS_BASE, getGateway, broadcastEvent }));
|
|
5694
5693
|
app.use('/api/workflows', workflowsRouter({ workflowsDir: WORKFLOWS_DIR, workflowRunsDir: WORKFLOW_RUNS_DIR, agentsBase: AGENTS_BASE, getGateway, broadcastEvent, cachedAsync }));
|
|
5695
|
-
app.use('/api/digest', digestRouter({ baseDir: BASE_DIR, vaultDir: VAULT_DIR,
|
|
5694
|
+
app.use('/api/digest', digestRouter({ baseDir: BASE_DIR, vaultDir: VAULT_DIR, memoryDbPath: MEMORY_DB_PATH, parseEnvFile, getGateway, cached }));
|
|
5696
5695
|
// Voice audio route (served from digest router but needs top-level mount for /api/voice/ path)
|
|
5697
5696
|
app.get('/api/voice/audio/:hash', (req, res) => {
|
|
5698
5697
|
const hash = req.params.hash.replace(/[^a-f0-9]/g, '');
|
|
@@ -7,6 +7,7 @@ import { existsSync, readFileSync, writeFileSync, readdirSync, mkdirSync, } from
|
|
|
7
7
|
import { randomBytes } from 'node:crypto';
|
|
8
8
|
import path from 'node:path';
|
|
9
9
|
import matter from 'gray-matter';
|
|
10
|
+
import { listAllGoals } from '../../tools/shared.js';
|
|
10
11
|
export function getDigestPrefs(prefsFile) {
|
|
11
12
|
const defaults = {
|
|
12
13
|
enabled: false,
|
|
@@ -27,7 +28,7 @@ export function getDigestPrefs(prefsFile) {
|
|
|
27
28
|
}
|
|
28
29
|
export function digestRouter(deps) {
|
|
29
30
|
const router = Router();
|
|
30
|
-
const { baseDir, vaultDir,
|
|
31
|
+
const { baseDir, vaultDir, memoryDbPath, parseEnvFile, getGateway, cached } = deps;
|
|
31
32
|
const prefsFile = path.join(baseDir, 'digest-preferences.json');
|
|
32
33
|
const voiceCacheDir = path.join(baseDir, 'cache', 'voice');
|
|
33
34
|
// Graph API helper for email
|
|
@@ -101,16 +102,10 @@ export function digestRouter(deps) {
|
|
|
101
102
|
}
|
|
102
103
|
if (secs.goals !== false) {
|
|
103
104
|
try {
|
|
104
|
-
|
|
105
|
-
const
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
}
|
|
109
|
-
catch {
|
|
110
|
-
return null;
|
|
111
|
-
} }).filter(Boolean);
|
|
112
|
-
const active = goals.filter((g) => g.status === 'active');
|
|
113
|
-
const blocked = goals.filter((g) => g.status === 'blocked');
|
|
105
|
+
{
|
|
106
|
+
const goals = listAllGoals().map(e => e.goal);
|
|
107
|
+
const active = goals.filter(g => g.status === 'active');
|
|
108
|
+
const blocked = goals.filter(g => g.status === 'blocked');
|
|
114
109
|
let goalText = `${active.length} active, ${blocked.length} blocked\n`;
|
|
115
110
|
active.slice(0, 5).forEach((g) => {
|
|
116
111
|
goalText += ` - ${g.title} [${g.priority}]`;
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Goals API routes — extracted from dashboard.ts
|
|
2
|
+
* Goals API routes — extracted from dashboard.ts.
|
|
3
|
+
*
|
|
4
|
+
* Goals live per-owner: Clementine's at ~/.clementine/goals/, each agent's at
|
|
5
|
+
* ~/.clementine/vault/00-System/agents/{slug}/goals/. Uses the goal-store
|
|
6
|
+
* helpers in tools/shared.ts so the dashboard doesn't need to know the layout.
|
|
3
7
|
*/
|
|
4
8
|
import { Router } from 'express';
|
|
5
9
|
import type { Gateway } from '../../gateway/router.js';
|
|
6
10
|
export interface GoalsRouterDeps {
|
|
7
|
-
goalsDir: string;
|
|
8
11
|
cronRunsDir: string;
|
|
9
12
|
vaultDir: string;
|
|
10
13
|
cronFile: string;
|