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,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Clementine TypeScript — Agent manager (scoped multi-agent system).
|
|
3
|
+
*
|
|
4
|
+
* Loads agent profiles from two sources:
|
|
5
|
+
* 1. vault/00-System/agents/{slug}/agent.md — new agent directory format
|
|
6
|
+
* 2. vault/00-System/profiles/*.md — legacy profile files
|
|
7
|
+
*
|
|
8
|
+
* Same slug in agents/ wins over profiles/ (agents/ is the primary source).
|
|
9
|
+
* Uses the same 60s TTL cache as ProfileManager.
|
|
10
|
+
*
|
|
11
|
+
* Provides CRUD operations for creating/updating/deleting agents.
|
|
12
|
+
*/
|
|
13
|
+
import type { AgentProfile, AgentStatus, SendPolicy } from '../types.js';
|
|
14
|
+
export interface AgentCreateConfig {
|
|
15
|
+
name: string;
|
|
16
|
+
description: string;
|
|
17
|
+
personality?: string;
|
|
18
|
+
tier?: number;
|
|
19
|
+
model?: string;
|
|
20
|
+
avatar?: string;
|
|
21
|
+
channelName?: string | string[];
|
|
22
|
+
teamChat?: boolean;
|
|
23
|
+
respondToAll?: boolean;
|
|
24
|
+
canMessage?: string[];
|
|
25
|
+
allowedTools?: string[];
|
|
26
|
+
allowedUsers?: string[];
|
|
27
|
+
project?: string;
|
|
28
|
+
projects?: string[];
|
|
29
|
+
discordToken?: string;
|
|
30
|
+
discordChannelId?: string;
|
|
31
|
+
slackBotToken?: string;
|
|
32
|
+
slackAppToken?: string;
|
|
33
|
+
slackChannelId?: string;
|
|
34
|
+
sendPolicy?: SendPolicy;
|
|
35
|
+
role?: string;
|
|
36
|
+
status?: AgentStatus;
|
|
37
|
+
budgetMonthlyCents?: number;
|
|
38
|
+
}
|
|
39
|
+
export declare class AgentManager {
|
|
40
|
+
private agentsDir;
|
|
41
|
+
private legacyManager;
|
|
42
|
+
private cache;
|
|
43
|
+
private cacheTime;
|
|
44
|
+
constructor(agentsDir: string, legacyProfilesDir: string);
|
|
45
|
+
private refreshIfStale;
|
|
46
|
+
private loadAgentFile;
|
|
47
|
+
get(slug: string): AgentProfile | null;
|
|
48
|
+
listAll(): AgentProfile[];
|
|
49
|
+
/** Get the agent's directory path (only for agents/ dir agents, not legacy). */
|
|
50
|
+
getAgentDir(slug: string): string | null;
|
|
51
|
+
/** Check if an agent has its own CRON.md. */
|
|
52
|
+
hasOwnCron(slug: string): boolean;
|
|
53
|
+
/** Check if an agent has its own workflows directory. */
|
|
54
|
+
hasOwnWorkflows(slug: string): boolean;
|
|
55
|
+
/** Get the path to an agent's CRON.md (or null). */
|
|
56
|
+
getCronPath(slug: string): string | null;
|
|
57
|
+
/** Get the path to an agent's workflows directory (or null). */
|
|
58
|
+
getWorkflowsDir(slug: string): string | null;
|
|
59
|
+
createAgent(config: AgentCreateConfig): AgentProfile;
|
|
60
|
+
updateAgent(slug: string, changes: Partial<AgentCreateConfig>): AgentProfile;
|
|
61
|
+
/** Quick status update without touching other config. */
|
|
62
|
+
setStatus(slug: string, status: AgentStatus): void;
|
|
63
|
+
/** Check if an agent is runnable (active status). */
|
|
64
|
+
isRunnable(slug: string): boolean;
|
|
65
|
+
deleteAgent(slug: string): void;
|
|
66
|
+
/** Force cache refresh (used after external modifications). */
|
|
67
|
+
invalidateCache(): void;
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=agent-manager.d.ts.map
|
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Clementine TypeScript — Agent manager (scoped multi-agent system).
|
|
3
|
+
*
|
|
4
|
+
* Loads agent profiles from two sources:
|
|
5
|
+
* 1. vault/00-System/agents/{slug}/agent.md — new agent directory format
|
|
6
|
+
* 2. vault/00-System/profiles/*.md — legacy profile files
|
|
7
|
+
*
|
|
8
|
+
* Same slug in agents/ wins over profiles/ (agents/ is the primary source).
|
|
9
|
+
* Uses the same 60s TTL cache as ProfileManager.
|
|
10
|
+
*
|
|
11
|
+
* Provides CRUD operations for creating/updating/deleting agents.
|
|
12
|
+
*/
|
|
13
|
+
import { execSync } from 'node:child_process';
|
|
14
|
+
import fs from 'node:fs';
|
|
15
|
+
import path from 'node:path';
|
|
16
|
+
import matter from 'gray-matter';
|
|
17
|
+
import { ProfileManager } from './profiles.js';
|
|
18
|
+
import { getScaffoldForRole } from './role-scaffolds.js';
|
|
19
|
+
// ── Keychain helpers for agent secrets ────────────────────────────────
|
|
20
|
+
function storeAgentSecret(slug, key, value) {
|
|
21
|
+
execSync(`security add-generic-password -U -s "clementine" -a "AGENT_${slug.toUpperCase()}_${key}" -w "${value}"`, { stdio: 'pipe', timeout: 3000 });
|
|
22
|
+
}
|
|
23
|
+
function getAgentSecret(slug, key) {
|
|
24
|
+
try {
|
|
25
|
+
return execSync(`security find-generic-password -s "clementine" -a "AGENT_${slug.toUpperCase()}_${key}" -w`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'], timeout: 3000 }).trim();
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return '';
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function deleteAgentSecret(slug, key) {
|
|
32
|
+
try {
|
|
33
|
+
execSync(`security delete-generic-password -s "clementine" -a "AGENT_${slug.toUpperCase()}_${key}"`, { stdio: 'pipe', timeout: 3000 });
|
|
34
|
+
}
|
|
35
|
+
catch { /* not found — ok */ }
|
|
36
|
+
}
|
|
37
|
+
const CACHE_TTL_MS = 60_000;
|
|
38
|
+
export class AgentManager {
|
|
39
|
+
agentsDir;
|
|
40
|
+
legacyManager;
|
|
41
|
+
cache = new Map();
|
|
42
|
+
cacheTime = 0;
|
|
43
|
+
constructor(agentsDir, legacyProfilesDir) {
|
|
44
|
+
this.agentsDir = agentsDir;
|
|
45
|
+
this.legacyManager = new ProfileManager(legacyProfilesDir);
|
|
46
|
+
}
|
|
47
|
+
refreshIfStale() {
|
|
48
|
+
const now = Date.now();
|
|
49
|
+
if (now - this.cacheTime < CACHE_TTL_MS && this.cache.size > 0) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
const profiles = new Map();
|
|
53
|
+
// 1. Load from agents/{slug}/agent.md (primary)
|
|
54
|
+
if (fs.existsSync(this.agentsDir)) {
|
|
55
|
+
try {
|
|
56
|
+
const dirs = fs.readdirSync(this.agentsDir, { withFileTypes: true })
|
|
57
|
+
.filter(d => d.isDirectory() && !d.name.startsWith('_'))
|
|
58
|
+
.map(d => d.name)
|
|
59
|
+
.sort();
|
|
60
|
+
for (const slug of dirs) {
|
|
61
|
+
const agentFile = path.join(this.agentsDir, slug, 'agent.md');
|
|
62
|
+
if (!fs.existsSync(agentFile))
|
|
63
|
+
continue;
|
|
64
|
+
try {
|
|
65
|
+
const profile = this.loadAgentFile(agentFile, slug);
|
|
66
|
+
profiles.set(slug, profile);
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
// Skip malformed agent files
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
// agents dir not readable
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
// 2. Load legacy profiles (only for slugs not already loaded)
|
|
78
|
+
for (const legacy of this.legacyManager.listAll()) {
|
|
79
|
+
if (!profiles.has(legacy.slug)) {
|
|
80
|
+
profiles.set(legacy.slug, legacy);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
this.cache = profiles;
|
|
84
|
+
this.cacheTime = now;
|
|
85
|
+
}
|
|
86
|
+
loadAgentFile(filePath, slug) {
|
|
87
|
+
const raw = fs.readFileSync(filePath, 'utf-8');
|
|
88
|
+
const { data: meta, content } = matter(raw);
|
|
89
|
+
// Cap tier at 2 — agents can never grant Tier 3
|
|
90
|
+
const tier = Math.min(Number(meta.tier ?? 1), 2);
|
|
91
|
+
// Parse team-specific frontmatter
|
|
92
|
+
let team;
|
|
93
|
+
const channelName = Array.isArray(meta.channelName)
|
|
94
|
+
? meta.channelName.map(String).filter(Boolean)
|
|
95
|
+
: meta.channelName ? String(meta.channelName) : undefined;
|
|
96
|
+
const canMessage = Array.isArray(meta.canMessage)
|
|
97
|
+
? meta.canMessage.map(String).filter(Boolean)
|
|
98
|
+
: [];
|
|
99
|
+
const allowedTools = Array.isArray(meta.allowedTools)
|
|
100
|
+
? meta.allowedTools.map(String).filter(Boolean)
|
|
101
|
+
: undefined;
|
|
102
|
+
const allowedUsers = Array.isArray(meta.allowedUsers)
|
|
103
|
+
? meta.allowedUsers.map(String).filter(Boolean)
|
|
104
|
+
: typeof meta.allowedUsers === 'string'
|
|
105
|
+
? meta.allowedUsers.split(',').map((s) => s.trim()).filter(Boolean)
|
|
106
|
+
: undefined;
|
|
107
|
+
if (channelName && (typeof channelName === 'string' || channelName.length > 0)) {
|
|
108
|
+
const teamChat = meta.teamChat === true || meta.teamChat === 'true';
|
|
109
|
+
const respondToAll = meta.respondToAll === true || meta.respondToAll === 'true';
|
|
110
|
+
team = { channelName, channels: [], canMessage, allowedTools, allowedUsers, teamChat, respondToAll: respondToAll || undefined };
|
|
111
|
+
}
|
|
112
|
+
// Resolve Discord token — migrate plaintext to Keychain if needed
|
|
113
|
+
let discordToken;
|
|
114
|
+
if (meta.discordToken) {
|
|
115
|
+
const raw = String(meta.discordToken);
|
|
116
|
+
if (raw === 'keychain') {
|
|
117
|
+
discordToken = getAgentSecret(slug, 'DISCORD_TOKEN') || undefined;
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
// Plaintext token in frontmatter — migrate to Keychain
|
|
121
|
+
discordToken = raw;
|
|
122
|
+
try {
|
|
123
|
+
storeAgentSecret(slug, 'DISCORD_TOKEN', raw);
|
|
124
|
+
meta.discordToken = 'keychain';
|
|
125
|
+
const updated = matter.stringify(content, meta);
|
|
126
|
+
fs.writeFileSync(filePath, updated);
|
|
127
|
+
}
|
|
128
|
+
catch { /* migration failed — continue with plaintext */ }
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
// Resolve Slack tokens — same keychain migration pattern as Discord
|
|
132
|
+
let slackBotToken;
|
|
133
|
+
if (meta.slackBotToken) {
|
|
134
|
+
const rawSlack = String(meta.slackBotToken);
|
|
135
|
+
if (rawSlack === 'keychain') {
|
|
136
|
+
slackBotToken = getAgentSecret(slug, 'SLACK_BOT_TOKEN') || undefined;
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
slackBotToken = rawSlack;
|
|
140
|
+
try {
|
|
141
|
+
storeAgentSecret(slug, 'SLACK_BOT_TOKEN', rawSlack);
|
|
142
|
+
meta.slackBotToken = 'keychain';
|
|
143
|
+
const updated = matter.stringify(content, meta);
|
|
144
|
+
fs.writeFileSync(filePath, updated);
|
|
145
|
+
}
|
|
146
|
+
catch { /* migration failed — continue with plaintext */ }
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
let slackAppToken;
|
|
150
|
+
if (meta.slackAppToken) {
|
|
151
|
+
const rawApp = String(meta.slackAppToken);
|
|
152
|
+
if (rawApp === 'keychain') {
|
|
153
|
+
slackAppToken = getAgentSecret(slug, 'SLACK_APP_TOKEN') || undefined;
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
slackAppToken = rawApp;
|
|
157
|
+
try {
|
|
158
|
+
storeAgentSecret(slug, 'SLACK_APP_TOKEN', rawApp);
|
|
159
|
+
meta.slackAppToken = 'keychain';
|
|
160
|
+
const updated = matter.stringify(content, meta);
|
|
161
|
+
fs.writeFileSync(filePath, updated);
|
|
162
|
+
}
|
|
163
|
+
catch { /* migration failed — continue with plaintext */ }
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
// Parse sendPolicy from frontmatter
|
|
167
|
+
let sendPolicy;
|
|
168
|
+
if (meta.sendPolicy && typeof meta.sendPolicy === 'object') {
|
|
169
|
+
const sp = meta.sendPolicy;
|
|
170
|
+
const requiresApproval = ['none', 'first-in-sequence', 'all'].includes(sp.requiresApproval)
|
|
171
|
+
? sp.requiresApproval
|
|
172
|
+
: 'all';
|
|
173
|
+
sendPolicy = {
|
|
174
|
+
maxDailyEmails: Number(sp.maxDailyEmails ?? 50),
|
|
175
|
+
requiresApproval,
|
|
176
|
+
businessHoursOnly: sp.businessHoursOnly === true || sp.businessHoursOnly === 'true',
|
|
177
|
+
};
|
|
178
|
+
if (Array.isArray(sp.allowedTemplates) && sp.allowedTemplates.length > 0) {
|
|
179
|
+
sendPolicy.allowedTemplates = sp.allowedTemplates.map(String);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return {
|
|
183
|
+
slug,
|
|
184
|
+
name: String(meta.name ?? slug.replace(/-/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase())),
|
|
185
|
+
tier,
|
|
186
|
+
description: String(meta.description ?? ''),
|
|
187
|
+
systemPromptBody: content.trim(),
|
|
188
|
+
model: meta.model ? String(meta.model) : undefined,
|
|
189
|
+
avatar: meta.avatar ? String(meta.avatar) : undefined,
|
|
190
|
+
team,
|
|
191
|
+
project: meta.project ? String(meta.project) : undefined,
|
|
192
|
+
projects: Array.isArray(meta.projects) ? meta.projects.map(String) : undefined,
|
|
193
|
+
agentDir: path.dirname(filePath),
|
|
194
|
+
discordToken,
|
|
195
|
+
discordChannelId: meta.discordChannelId ? String(meta.discordChannelId) : undefined,
|
|
196
|
+
slackBotToken,
|
|
197
|
+
slackAppToken,
|
|
198
|
+
slackChannelId: meta.slackChannelId ? String(meta.slackChannelId) : undefined,
|
|
199
|
+
sendPolicy,
|
|
200
|
+
allowedMcpServers: Array.isArray(meta.allowedMcpServers)
|
|
201
|
+
? meta.allowedMcpServers.map(String).filter(Boolean)
|
|
202
|
+
: undefined,
|
|
203
|
+
status: (['active', 'paused', 'error', 'terminated'].includes(meta.status) ? meta.status : 'active'),
|
|
204
|
+
budgetMonthlyCents: meta.budgetMonthlyCents ? Number(meta.budgetMonthlyCents) : undefined,
|
|
205
|
+
strictMemoryIsolation: meta.strictMemoryIsolation === false ? false : true, // default true for all agents
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
// ── ProfileManager-compatible interface ───────────────────────────
|
|
209
|
+
get(slug) {
|
|
210
|
+
this.refreshIfStale();
|
|
211
|
+
return this.cache.get(slug) ?? null;
|
|
212
|
+
}
|
|
213
|
+
listAll() {
|
|
214
|
+
this.refreshIfStale();
|
|
215
|
+
return [...this.cache.values()];
|
|
216
|
+
}
|
|
217
|
+
// ── Agent directory helpers ───────────────────────────────────────
|
|
218
|
+
/** Get the agent's directory path (only for agents/ dir agents, not legacy). */
|
|
219
|
+
getAgentDir(slug) {
|
|
220
|
+
const dir = path.join(this.agentsDir, slug);
|
|
221
|
+
return fs.existsSync(path.join(dir, 'agent.md')) ? dir : null;
|
|
222
|
+
}
|
|
223
|
+
/** Check if an agent has its own CRON.md. */
|
|
224
|
+
hasOwnCron(slug) {
|
|
225
|
+
const dir = this.getAgentDir(slug);
|
|
226
|
+
return dir !== null && fs.existsSync(path.join(dir, 'CRON.md'));
|
|
227
|
+
}
|
|
228
|
+
/** Check if an agent has its own workflows directory. */
|
|
229
|
+
hasOwnWorkflows(slug) {
|
|
230
|
+
const dir = this.getAgentDir(slug);
|
|
231
|
+
return dir !== null && fs.existsSync(path.join(dir, 'workflows'));
|
|
232
|
+
}
|
|
233
|
+
/** Get the path to an agent's CRON.md (or null). */
|
|
234
|
+
getCronPath(slug) {
|
|
235
|
+
const dir = this.getAgentDir(slug);
|
|
236
|
+
if (!dir)
|
|
237
|
+
return null;
|
|
238
|
+
const cronPath = path.join(dir, 'CRON.md');
|
|
239
|
+
return fs.existsSync(cronPath) ? cronPath : null;
|
|
240
|
+
}
|
|
241
|
+
/** Get the path to an agent's workflows directory (or null). */
|
|
242
|
+
getWorkflowsDir(slug) {
|
|
243
|
+
const dir = this.getAgentDir(slug);
|
|
244
|
+
if (!dir)
|
|
245
|
+
return null;
|
|
246
|
+
const wfDir = path.join(dir, 'workflows');
|
|
247
|
+
return fs.existsSync(wfDir) ? wfDir : null;
|
|
248
|
+
}
|
|
249
|
+
// ── CRUD ──────────────────────────────────────────────────────────
|
|
250
|
+
createAgent(config) {
|
|
251
|
+
const slug = config.name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
|
|
252
|
+
const agentDir = path.join(this.agentsDir, slug);
|
|
253
|
+
if (fs.existsSync(path.join(agentDir, 'agent.md'))) {
|
|
254
|
+
throw new Error(`Agent '${slug}' already exists.`);
|
|
255
|
+
}
|
|
256
|
+
// Ensure directories exist
|
|
257
|
+
fs.mkdirSync(agentDir, { recursive: true });
|
|
258
|
+
// Build frontmatter
|
|
259
|
+
const frontmatter = {
|
|
260
|
+
name: config.name,
|
|
261
|
+
description: config.description,
|
|
262
|
+
tier: Math.min(config.tier ?? 2, 2),
|
|
263
|
+
strictMemoryIsolation: true, // new agents get hard isolation by default
|
|
264
|
+
};
|
|
265
|
+
if (config.model)
|
|
266
|
+
frontmatter.model = config.model;
|
|
267
|
+
if (config.avatar)
|
|
268
|
+
frontmatter.avatar = config.avatar;
|
|
269
|
+
if (config.channelName)
|
|
270
|
+
frontmatter.channelName = config.channelName;
|
|
271
|
+
if (config.teamChat)
|
|
272
|
+
frontmatter.teamChat = config.teamChat;
|
|
273
|
+
if (config.respondToAll)
|
|
274
|
+
frontmatter.respondToAll = config.respondToAll;
|
|
275
|
+
if (config.canMessage?.length)
|
|
276
|
+
frontmatter.canMessage = config.canMessage;
|
|
277
|
+
if (config.allowedTools?.length)
|
|
278
|
+
frontmatter.allowedTools = config.allowedTools;
|
|
279
|
+
if (config.allowedUsers?.length)
|
|
280
|
+
frontmatter.allowedUsers = config.allowedUsers;
|
|
281
|
+
if (config.project)
|
|
282
|
+
frontmatter.project = config.project;
|
|
283
|
+
if (config.discordToken) {
|
|
284
|
+
storeAgentSecret(slug, 'DISCORD_TOKEN', config.discordToken);
|
|
285
|
+
frontmatter.discordToken = 'keychain';
|
|
286
|
+
}
|
|
287
|
+
if (config.discordChannelId)
|
|
288
|
+
frontmatter.discordChannelId = config.discordChannelId;
|
|
289
|
+
if (config.slackBotToken) {
|
|
290
|
+
storeAgentSecret(slug, 'SLACK_BOT_TOKEN', config.slackBotToken);
|
|
291
|
+
frontmatter.slackBotToken = 'keychain';
|
|
292
|
+
}
|
|
293
|
+
if (config.slackAppToken) {
|
|
294
|
+
storeAgentSecret(slug, 'SLACK_APP_TOKEN', config.slackAppToken);
|
|
295
|
+
frontmatter.slackAppToken = 'keychain';
|
|
296
|
+
}
|
|
297
|
+
if (config.slackChannelId)
|
|
298
|
+
frontmatter.slackChannelId = config.slackChannelId;
|
|
299
|
+
if (config.sendPolicy)
|
|
300
|
+
frontmatter.sendPolicy = config.sendPolicy;
|
|
301
|
+
if (config.status)
|
|
302
|
+
frontmatter.status = config.status;
|
|
303
|
+
if (config.budgetMonthlyCents)
|
|
304
|
+
frontmatter.budgetMonthlyCents = config.budgetMonthlyCents;
|
|
305
|
+
const body = config.personality || `You are ${config.name}. ${config.description}`;
|
|
306
|
+
const content = matter.stringify(body, frontmatter);
|
|
307
|
+
fs.writeFileSync(path.join(agentDir, 'agent.md'), content);
|
|
308
|
+
// Scaffold role-specific working directory (CRON.md, playbook, sequences)
|
|
309
|
+
if (config.role) {
|
|
310
|
+
const scaffolder = getScaffoldForRole(config.role);
|
|
311
|
+
if (scaffolder) {
|
|
312
|
+
const scaffold = scaffolder(config.name, slug);
|
|
313
|
+
// Write CRON.md — the autonomous job definitions
|
|
314
|
+
if (scaffold.cronMd) {
|
|
315
|
+
fs.writeFileSync(path.join(agentDir, 'CRON.md'), scaffold.cronMd);
|
|
316
|
+
}
|
|
317
|
+
// Write playbook — ICP, email rules, escalation criteria
|
|
318
|
+
if (scaffold.playbook) {
|
|
319
|
+
fs.writeFileSync(path.join(agentDir, 'PLAYBOOK.md'), scaffold.playbook);
|
|
320
|
+
}
|
|
321
|
+
// Write sequence definitions
|
|
322
|
+
if (scaffold.sequences) {
|
|
323
|
+
fs.writeFileSync(path.join(agentDir, 'SEQUENCES.md'), scaffold.sequences);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
// Invalidate cache
|
|
328
|
+
this.cacheTime = 0;
|
|
329
|
+
return this.get(slug);
|
|
330
|
+
}
|
|
331
|
+
updateAgent(slug, changes) {
|
|
332
|
+
const agentDir = path.join(this.agentsDir, slug);
|
|
333
|
+
const agentFile = path.join(agentDir, 'agent.md');
|
|
334
|
+
if (!fs.existsSync(agentFile)) {
|
|
335
|
+
throw new Error(`Agent '${slug}' not found in agents directory.`);
|
|
336
|
+
}
|
|
337
|
+
const raw = fs.readFileSync(agentFile, 'utf-8');
|
|
338
|
+
const { data: meta, content: body } = matter(raw);
|
|
339
|
+
// Merge changes into frontmatter
|
|
340
|
+
if (changes.name !== undefined)
|
|
341
|
+
meta.name = changes.name;
|
|
342
|
+
if (changes.description !== undefined)
|
|
343
|
+
meta.description = changes.description;
|
|
344
|
+
if (changes.tier !== undefined)
|
|
345
|
+
meta.tier = Math.min(changes.tier, 2);
|
|
346
|
+
if (changes.model !== undefined)
|
|
347
|
+
meta.model = changes.model;
|
|
348
|
+
if (changes.avatar !== undefined)
|
|
349
|
+
meta.avatar = changes.avatar;
|
|
350
|
+
if (changes.channelName !== undefined)
|
|
351
|
+
meta.channelName = changes.channelName;
|
|
352
|
+
if (changes.teamChat !== undefined)
|
|
353
|
+
meta.teamChat = changes.teamChat;
|
|
354
|
+
if (changes.respondToAll !== undefined)
|
|
355
|
+
meta.respondToAll = changes.respondToAll;
|
|
356
|
+
if (changes.canMessage !== undefined)
|
|
357
|
+
meta.canMessage = changes.canMessage;
|
|
358
|
+
if (changes.allowedTools !== undefined)
|
|
359
|
+
meta.allowedTools = changes.allowedTools;
|
|
360
|
+
if (changes.allowedUsers !== undefined)
|
|
361
|
+
meta.allowedUsers = changes.allowedUsers;
|
|
362
|
+
if (changes.project !== undefined)
|
|
363
|
+
meta.project = changes.project;
|
|
364
|
+
if (changes.discordToken !== undefined) {
|
|
365
|
+
if (changes.discordToken) {
|
|
366
|
+
storeAgentSecret(slug, 'DISCORD_TOKEN', changes.discordToken);
|
|
367
|
+
meta.discordToken = 'keychain';
|
|
368
|
+
}
|
|
369
|
+
else {
|
|
370
|
+
deleteAgentSecret(slug, 'DISCORD_TOKEN');
|
|
371
|
+
meta.discordToken = undefined;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
if (changes.discordChannelId !== undefined)
|
|
375
|
+
meta.discordChannelId = changes.discordChannelId || undefined;
|
|
376
|
+
if (changes.slackBotToken !== undefined) {
|
|
377
|
+
if (changes.slackBotToken) {
|
|
378
|
+
storeAgentSecret(slug, 'SLACK_BOT_TOKEN', changes.slackBotToken);
|
|
379
|
+
meta.slackBotToken = 'keychain';
|
|
380
|
+
}
|
|
381
|
+
else {
|
|
382
|
+
deleteAgentSecret(slug, 'SLACK_BOT_TOKEN');
|
|
383
|
+
meta.slackBotToken = undefined;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
if (changes.slackAppToken !== undefined) {
|
|
387
|
+
if (changes.slackAppToken) {
|
|
388
|
+
storeAgentSecret(slug, 'SLACK_APP_TOKEN', changes.slackAppToken);
|
|
389
|
+
meta.slackAppToken = 'keychain';
|
|
390
|
+
}
|
|
391
|
+
else {
|
|
392
|
+
deleteAgentSecret(slug, 'SLACK_APP_TOKEN');
|
|
393
|
+
meta.slackAppToken = undefined;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
if (changes.slackChannelId !== undefined)
|
|
397
|
+
meta.slackChannelId = changes.slackChannelId || undefined;
|
|
398
|
+
if (changes.sendPolicy !== undefined)
|
|
399
|
+
meta.sendPolicy = changes.sendPolicy || undefined;
|
|
400
|
+
if (changes.status !== undefined)
|
|
401
|
+
meta.status = changes.status;
|
|
402
|
+
if (changes.budgetMonthlyCents !== undefined)
|
|
403
|
+
meta.budgetMonthlyCents = changes.budgetMonthlyCents || undefined;
|
|
404
|
+
const newBody = changes.personality ?? body;
|
|
405
|
+
const updated = matter.stringify(newBody, meta);
|
|
406
|
+
fs.writeFileSync(agentFile, updated);
|
|
407
|
+
// Invalidate cache
|
|
408
|
+
this.cacheTime = 0;
|
|
409
|
+
return this.get(slug);
|
|
410
|
+
}
|
|
411
|
+
/** Quick status update without touching other config. */
|
|
412
|
+
setStatus(slug, status) {
|
|
413
|
+
this.updateAgent(slug, { status });
|
|
414
|
+
}
|
|
415
|
+
/** Check if an agent is runnable (active status). */
|
|
416
|
+
isRunnable(slug) {
|
|
417
|
+
const agent = this.get(slug);
|
|
418
|
+
if (!agent)
|
|
419
|
+
return false;
|
|
420
|
+
return !agent.status || agent.status === 'active';
|
|
421
|
+
}
|
|
422
|
+
deleteAgent(slug) {
|
|
423
|
+
const agentDir = path.join(this.agentsDir, slug);
|
|
424
|
+
if (!fs.existsSync(agentDir)) {
|
|
425
|
+
throw new Error(`Agent '${slug}' not found.`);
|
|
426
|
+
}
|
|
427
|
+
// Clean up Keychain secrets
|
|
428
|
+
deleteAgentSecret(slug, 'DISCORD_TOKEN');
|
|
429
|
+
deleteAgentSecret(slug, 'SLACK_BOT_TOKEN');
|
|
430
|
+
deleteAgentSecret(slug, 'SLACK_APP_TOKEN');
|
|
431
|
+
// Remove directory recursively
|
|
432
|
+
fs.rmSync(agentDir, { recursive: true, force: true });
|
|
433
|
+
// Invalidate cache
|
|
434
|
+
this.cacheTime = 0;
|
|
435
|
+
}
|
|
436
|
+
/** Force cache refresh (used after external modifications). */
|
|
437
|
+
invalidateCache() {
|
|
438
|
+
this.cacheTime = 0;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
//# sourceMappingURL=agent-manager.js.map
|