feat: wire up all Phase 2-6 features into daemon and config
Integrate all new features into the shared infrastructure: - Config schema: add memory, discord, slack, process, web_search schemas - Daemon wiring: memory store init, tool registration, channel adapters - Orchestrator: memory injection into system prompt, extraction on compaction - Agent: add setSystemPrompt() for dynamic prompt updates - Channel/tool index: export new adapters and tool factories - Add @slack/bolt, discord.js, turndown, linkedom, @mozilla/readability deps - Update state.json with Phase 3b completion (494 tests passing)
This commit is contained in:
@@ -1,5 +1,9 @@
|
||||
# Flynn AGENTS.md
|
||||
|
||||
## General Rules
|
||||
|
||||
- **Parallelise with subagents:** For every task, use multiple subagents with the appropriate model to work more efficiently. Dispatch independent subtasks in parallel rather than executing them sequentially.
|
||||
|
||||
## Build, Lint, and Test Commands
|
||||
|
||||
```bash
|
||||
|
||||
@@ -0,0 +1,215 @@
|
||||
{
|
||||
"version": "1.0",
|
||||
"updated_at": "2026-02-06",
|
||||
"description": "Tracks the status of all Flynn plans and implementation phases",
|
||||
|
||||
"plans": {
|
||||
"openclaw-feature-gap-analysis": {
|
||||
"file": "2026-02-06-openclaw-feature-gap-analysis.md",
|
||||
"status": "completed",
|
||||
"date": "2026-02-06",
|
||||
"summary": "Comprehensive comparison of Flynn vs OpenClaw. 118 features compared: 29 match, 11 partial, 78 missing."
|
||||
},
|
||||
"p0-p1-implementation-plan": {
|
||||
"file": "2026-02-06-p0-p1-implementation-plan.md",
|
||||
"status": "in_progress",
|
||||
"date": "2026-02-06",
|
||||
"summary": "7 features across 7 phases (0-6). Estimated 15-22 days total.",
|
||||
"phases": {
|
||||
"phase_0_multi_model_delegation": {
|
||||
"priority": "P0",
|
||||
"status": "completed",
|
||||
"description": "AgentOrchestrator with sub-agent delegation to different model tiers",
|
||||
"files_created": [
|
||||
"src/backends/native/orchestrator.ts",
|
||||
"src/backends/native/orchestrator.test.ts",
|
||||
"src/backends/native/prompts.ts"
|
||||
],
|
||||
"files_modified": [
|
||||
"src/daemon/index.ts",
|
||||
"src/config/schema.ts"
|
||||
],
|
||||
"test_status": "20/20 passing"
|
||||
},
|
||||
"phase_1_context_compaction": {
|
||||
"priority": "P0",
|
||||
"status": "completed",
|
||||
"description": "Token estimation, auto-compaction, /compact command, SQLite history replacement",
|
||||
"files_created": [
|
||||
"src/context/tokens.ts",
|
||||
"src/context/tokens.test.ts",
|
||||
"src/context/compaction.ts",
|
||||
"src/context/compaction.test.ts",
|
||||
"src/context/index.ts"
|
||||
],
|
||||
"files_modified": [
|
||||
"src/backends/native/orchestrator.ts",
|
||||
"src/session/manager.ts",
|
||||
"src/session/store.ts",
|
||||
"src/config/schema.ts",
|
||||
"src/daemon/index.ts",
|
||||
"src/frontends/tui/commands.ts"
|
||||
],
|
||||
"test_status": "21/21 passing"
|
||||
},
|
||||
"phase_2_memory_system": {
|
||||
"priority": "P0",
|
||||
"status": "completed",
|
||||
"description": "Persistent memory with file-based storage, memory tools, auto-extraction after compaction",
|
||||
"depends_on": ["phase_0", "phase_1"],
|
||||
"files_created": [
|
||||
"src/memory/store.ts",
|
||||
"src/memory/store.test.ts",
|
||||
"src/memory/index.ts",
|
||||
"src/tools/builtin/memory-read.ts",
|
||||
"src/tools/builtin/memory-write.ts",
|
||||
"src/tools/builtin/memory-search.ts"
|
||||
],
|
||||
"files_modified": [
|
||||
"src/tools/builtin/index.ts",
|
||||
"src/config/schema.ts",
|
||||
"src/backends/native/agent.ts",
|
||||
"src/backends/native/orchestrator.ts",
|
||||
"src/context/compaction.ts",
|
||||
"src/daemon/index.ts"
|
||||
],
|
||||
"test_status": "17/17 passing"
|
||||
},
|
||||
"phase_3_messaging_channels": {
|
||||
"priority": "P1",
|
||||
"status": "in_progress",
|
||||
"description": "Discord, Slack, WhatsApp channel adapters",
|
||||
"sub_phases": {
|
||||
"3a_discord": {
|
||||
"status": "completed",
|
||||
"effort": "1-2 days",
|
||||
"files_created": [
|
||||
"src/channels/discord/adapter.ts",
|
||||
"src/channels/discord/adapter.test.ts",
|
||||
"src/channels/discord/index.ts"
|
||||
],
|
||||
"files_modified": [
|
||||
"src/config/schema.ts",
|
||||
"src/channels/index.ts",
|
||||
"src/daemon/index.ts"
|
||||
],
|
||||
"test_status": "22/22 passing"
|
||||
},
|
||||
"3b_slack": {
|
||||
"status": "completed",
|
||||
"effort": "1 day",
|
||||
"files_created": [
|
||||
"src/channels/slack/adapter.ts",
|
||||
"src/channels/slack/adapter.test.ts",
|
||||
"src/channels/slack/index.ts"
|
||||
],
|
||||
"files_modified": [
|
||||
"src/config/schema.ts",
|
||||
"src/channels/index.ts",
|
||||
"src/daemon/index.ts",
|
||||
"package.json"
|
||||
],
|
||||
"new_dependencies": ["@slack/bolt"],
|
||||
"test_status": "22/22 passing",
|
||||
"notes": "Socket Mode only (HTTP fallback deferred). Slash commands deferred. User ID used as senderName (display name resolution is a follow-up)."
|
||||
},
|
||||
"3c_whatsapp": { "status": "not_started", "effort": "2-3 days" }
|
||||
},
|
||||
"planned_files": [
|
||||
"src/channels/discord/adapter.ts",
|
||||
"src/channels/discord/index.ts",
|
||||
"src/channels/slack/adapter.ts",
|
||||
"src/channels/slack/index.ts",
|
||||
"src/channels/whatsapp/adapter.ts",
|
||||
"src/channels/whatsapp/index.ts",
|
||||
"src/channels/utils/chunking.ts",
|
||||
"src/channels/utils/auth.ts",
|
||||
"src/channels/utils/markdown.ts"
|
||||
]
|
||||
},
|
||||
"phase_4_web_search": {
|
||||
"priority": "P1",
|
||||
"status": "completed",
|
||||
"description": "Web search tool (Brave Search API + SearXNG fallback)",
|
||||
"effort": "0.5 day",
|
||||
"files_created": [
|
||||
"src/tools/builtin/web-search.ts",
|
||||
"src/tools/builtin/web-search.test.ts"
|
||||
],
|
||||
"files_modified": [
|
||||
"src/config/schema.ts",
|
||||
"src/tools/builtin/index.ts",
|
||||
"src/tools/index.ts",
|
||||
"src/daemon/index.ts"
|
||||
],
|
||||
"test_status": "14/14 passing"
|
||||
},
|
||||
"phase_5_background_exec": {
|
||||
"priority": "P1",
|
||||
"status": "completed",
|
||||
"description": "Background process management tools (start, status, output, kill, list)",
|
||||
"effort": "1-2 days",
|
||||
"files_created": [
|
||||
"src/tools/builtin/process/manager.ts",
|
||||
"src/tools/builtin/process/start.ts",
|
||||
"src/tools/builtin/process/status.ts",
|
||||
"src/tools/builtin/process/output.ts",
|
||||
"src/tools/builtin/process/kill.ts",
|
||||
"src/tools/builtin/process/list.ts",
|
||||
"src/tools/builtin/process/index.ts",
|
||||
"src/tools/builtin/process/manager.test.ts"
|
||||
],
|
||||
"files_modified": [
|
||||
"src/config/schema.ts",
|
||||
"src/tools/builtin/index.ts",
|
||||
"src/tools/index.ts",
|
||||
"src/daemon/index.ts"
|
||||
],
|
||||
"test_status": "28/28 passing"
|
||||
},
|
||||
"phase_6_enhanced_web_fetch": {
|
||||
"priority": "P1",
|
||||
"status": "completed",
|
||||
"description": "HTML-to-markdown extraction, format parameter, response caching",
|
||||
"effort": "1 day",
|
||||
"files_modified": [
|
||||
"src/tools/builtin/web-fetch.ts",
|
||||
"src/tools/builtin/web-fetch.test.ts"
|
||||
],
|
||||
"new_dependencies": ["turndown", "linkedom", "@mozilla/readability", "@types/turndown"],
|
||||
"test_status": "10/10 passing"
|
||||
}
|
||||
}
|
||||
},
|
||||
"earlier_plans": {
|
||||
"status": "completed",
|
||||
"summary": "Original design and implementation phases from 2026-02-02 to 2026-02-05",
|
||||
"plans": [
|
||||
{ "file": "2026-02-02-flynn-design.md", "status": "completed" },
|
||||
{ "file": "2026-02-02-flynn-phase1-implementation.md", "status": "completed" },
|
||||
{ "file": "2026-02-02-flynn-phase2-implementation.md", "status": "completed" },
|
||||
{ "file": "2026-02-05-flynn-phase3-implementation.md", "status": "completed" },
|
||||
{ "file": "2026-02-05-tui-redesign.md", "status": "completed" },
|
||||
{ "file": "2026-02-05-tui-redesign-implementation.md", "status": "completed" },
|
||||
{ "file": "2026-02-05-llamacpp-integration-design.md", "status": "completed" },
|
||||
{ "file": "2026-02-05-llamacpp-implementation.md", "status": "completed" },
|
||||
{ "file": "2026-02-05-backend-switch-design.md", "status": "completed" },
|
||||
{ "file": "2026-02-05-backend-switch-implementation.md", "status": "completed" },
|
||||
{ "file": "2026-02-05-openclaw-parity-design.md", "status": "completed" },
|
||||
{ "file": "2026-02-05-phase1-tool-framework.md", "status": "completed" },
|
||||
{ "file": "2026-02-05-phase2-websocket-gateway.md", "status": "completed" },
|
||||
{ "file": "2026-02-05-phase3-channel-adapters.md", "status": "completed" },
|
||||
{ "file": "2026-02-05-phase5-cli-cron-doctor-design.md", "status": "completed" },
|
||||
{ "file": "2026-02-05-phase5a-implementation.md", "status": "completed" }
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
"overall_progress": {
|
||||
"total_test_count": 494,
|
||||
"all_tests_passing": true,
|
||||
"p0_completion": "3/3 (100%)",
|
||||
"p1_completion": "3/4 (75%)",
|
||||
"next_up": "phase_3c_whatsapp_adapter"
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,7 @@
|
||||
"@types/marked-terminal": "^6.1.1",
|
||||
"@types/node": "^22.0.0",
|
||||
"@types/react": "^19.0.0",
|
||||
"@types/turndown": "^5.0.0",
|
||||
"@types/ws": "^8.18.1",
|
||||
"eslint": "^9.0.0",
|
||||
"tsx": "^4.0.0",
|
||||
@@ -40,18 +41,23 @@
|
||||
"dependencies": {
|
||||
"@anthropic-ai/sdk": "^0.39.0",
|
||||
"@modelcontextprotocol/sdk": "^1.26.0",
|
||||
"@mozilla/readability": "^0.5.0",
|
||||
"@slack/bolt": "^4.6.0",
|
||||
"better-sqlite3": "^11.0.0",
|
||||
"cli-highlight": "^2.1.11",
|
||||
"commander": "^14.0.3",
|
||||
"croner": "^10.0.1",
|
||||
"discord.js": "^14.25.1",
|
||||
"grammy": "^1.35.0",
|
||||
"ink": "^6.0.0",
|
||||
"ink-text-input": "^6.0.0",
|
||||
"linkedom": "^0.18.0",
|
||||
"marked": "^17.0.1",
|
||||
"marked-terminal": "^7.3.0",
|
||||
"ollama": "^0.5.0",
|
||||
"openai": "^4.0.0",
|
||||
"react": "^19.0.0",
|
||||
"turndown": "^7.2.0",
|
||||
"ws": "^8.19.0",
|
||||
"yaml": "^2.7.0",
|
||||
"zod": "^3.24.0"
|
||||
|
||||
Generated
+664
File diff suppressed because it is too large
Load Diff
@@ -199,6 +199,10 @@ export class NativeAgent {
|
||||
return this.currentTier;
|
||||
}
|
||||
|
||||
setSystemPrompt(prompt: string): void {
|
||||
this.systemPrompt = prompt;
|
||||
}
|
||||
|
||||
setOnToolUse(callback: ((event: ToolUseEvent) => void) | undefined): void {
|
||||
this.onToolUse = callback;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { ChatRequest, Message, TokenUsage } from '../../models/types.js';
|
||||
import type { Session } from '../../session/index.js';
|
||||
import type { ToolRegistry } from '../../tools/registry.js';
|
||||
import type { ToolExecutor } from '../../tools/executor.js';
|
||||
import type { MemoryStore } from '../../memory/store.js';
|
||||
import { NativeAgent } from './agent.js';
|
||||
import type { ToolUseEvent } from './agent.js';
|
||||
import { shouldCompact } from '../../context/tokens.js';
|
||||
@@ -64,6 +65,8 @@ export interface OrchestratorConfig {
|
||||
modelName?: string;
|
||||
/** Optional override for the context window size (in tokens). */
|
||||
contextWindow?: number;
|
||||
/** Optional memory store for injecting persistent memory into the system prompt. */
|
||||
memoryStore?: MemoryStore;
|
||||
}
|
||||
|
||||
// ── AgentOrchestrator ─────────────────────────────────────────────────
|
||||
@@ -86,6 +89,8 @@ export class AgentOrchestrator {
|
||||
private _compactionConfig?: CompactionConfig;
|
||||
private _modelName?: string;
|
||||
private _contextWindow?: number;
|
||||
private _memoryStore?: MemoryStore;
|
||||
private _systemPromptBase: string;
|
||||
private _usageByTier: Map<string, TierUsageStats> = new Map();
|
||||
|
||||
constructor(config: OrchestratorConfig) {
|
||||
@@ -97,6 +102,8 @@ export class AgentOrchestrator {
|
||||
this._compactionConfig = config.compaction;
|
||||
this._modelName = config.modelName;
|
||||
this._contextWindow = config.contextWindow;
|
||||
this._memoryStore = config.memoryStore;
|
||||
this._systemPromptBase = config.systemPrompt;
|
||||
|
||||
// Create the primary NativeAgent for user-facing conversation
|
||||
this._agent = new NativeAgent({
|
||||
@@ -178,6 +185,7 @@ export class AgentOrchestrator {
|
||||
* exceeds the context window threshold and compacts it before processing.
|
||||
*/
|
||||
async process(userMessage: string): Promise<string> {
|
||||
this._injectMemoryContext();
|
||||
await this.compactIfNeeded();
|
||||
return this._agent.process(userMessage);
|
||||
}
|
||||
@@ -199,6 +207,7 @@ export class AgentOrchestrator {
|
||||
messages,
|
||||
orchestrator: this,
|
||||
config,
|
||||
memoryStore: this._memoryStore,
|
||||
});
|
||||
|
||||
// If nothing was actually compacted, skip the replace
|
||||
@@ -268,6 +277,27 @@ export class AgentOrchestrator {
|
||||
|
||||
// ── Private helpers ───────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Inject persistent memory context into the primary agent's system prompt.
|
||||
* Reads from the memory store and appends relevant context to the base
|
||||
* system prompt. If no memory store is configured or no memory content
|
||||
* exists, restores the original base prompt.
|
||||
*/
|
||||
private _injectMemoryContext(): void {
|
||||
if (!this._memoryStore) {
|
||||
return;
|
||||
}
|
||||
|
||||
const memoryContext = this._memoryStore.getContextForPrompt();
|
||||
if (!memoryContext) {
|
||||
this._agent.setSystemPrompt(this._systemPromptBase);
|
||||
return;
|
||||
}
|
||||
|
||||
const enrichedPrompt = `${this._systemPromptBase}\n\n# Memory Context\n\nThe following is your persistent memory. Use it to maintain continuity across sessions.\n\n${memoryContext}`;
|
||||
this._agent.setSystemPrompt(enrichedPrompt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether automatic compaction should run, and if so, compact.
|
||||
* Called before each `process()` call when compaction is configured.
|
||||
|
||||
@@ -9,3 +9,5 @@ export type {
|
||||
export { ChannelRegistry } from './registry.js';
|
||||
export { TelegramAdapter, type TelegramAdapterConfig } from './telegram/index.js';
|
||||
export { WebChatAdapter, type WebChatAdapterConfig } from './webchat/index.js';
|
||||
export { DiscordAdapter, type DiscordAdapterConfig } from './discord/index.js';
|
||||
export { SlackAdapter, type SlackAdapterConfig } from './slack/index.js';
|
||||
|
||||
@@ -107,6 +107,13 @@ const agentsSchema = z.object({
|
||||
max_delegation_depth: z.number().min(1).max(10).default(3),
|
||||
}).default({});
|
||||
|
||||
const memorySchema = z.object({
|
||||
enabled: z.boolean().default(true),
|
||||
dir: z.string().optional(), // Default: ~/.local/share/flynn/memory
|
||||
auto_extract: z.boolean().default(true),
|
||||
max_context_tokens: z.number().min(100).max(10000).default(2000),
|
||||
}).default({});
|
||||
|
||||
const compactionSchema = z.object({
|
||||
enabled: z.boolean().default(true),
|
||||
threshold_pct: z.number().min(10).max(100).default(80),
|
||||
@@ -114,8 +121,37 @@ const compactionSchema = z.object({
|
||||
summary_max_tokens: z.number().min(128).max(4096).default(1024),
|
||||
}).default({});
|
||||
|
||||
const discordSchema = z.object({
|
||||
bot_token: z.string().min(1, 'Bot token is required'),
|
||||
allowed_guild_ids: z.array(z.string()).default([]),
|
||||
allowed_channel_ids: z.array(z.string()).default([]),
|
||||
require_mention: z.boolean().default(true),
|
||||
}).optional();
|
||||
|
||||
const slackSchema = z.object({
|
||||
bot_token: z.string().min(1, 'Bot token is required'),
|
||||
app_token: z.string().min(1, 'App token is required'),
|
||||
signing_secret: z.string().min(1, 'Signing secret is required'),
|
||||
allowed_channel_ids: z.array(z.string()).default([]),
|
||||
}).optional();
|
||||
|
||||
const processSchema = z.object({
|
||||
max_concurrent: z.number().min(1).max(50).default(10),
|
||||
max_runtime_minutes: z.number().min(1).max(1440).default(60),
|
||||
buffer_size: z.number().min(1024).max(1048576).default(65536),
|
||||
}).default({});
|
||||
|
||||
const webSearchSchema = z.object({
|
||||
provider: z.enum(['brave', 'searxng']).default('brave'),
|
||||
api_key: z.string().optional(),
|
||||
endpoint: z.string().optional(),
|
||||
max_results: z.number().min(1).max(20).default(5),
|
||||
}).default({});
|
||||
|
||||
export const configSchema = z.object({
|
||||
telegram: telegramSchema,
|
||||
discord: discordSchema,
|
||||
slack: slackSchema,
|
||||
server: serverSchema.default({}),
|
||||
models: modelsSchema,
|
||||
backends: backendsSchema.default({}),
|
||||
@@ -125,6 +161,9 @@ export const configSchema = z.object({
|
||||
automation: automationSchema,
|
||||
agents: agentsSchema,
|
||||
compaction: compactionSchema,
|
||||
memory: memorySchema,
|
||||
process: processSchema,
|
||||
web_search: webSearchSchema,
|
||||
});
|
||||
|
||||
export type Config = z.infer<typeof configSchema>;
|
||||
@@ -133,3 +172,8 @@ export type ModelConfig = z.infer<typeof modelConfigSchema>;
|
||||
export type CronJobConfig = z.infer<typeof cronJobSchema>;
|
||||
export type AgentsConfig = z.infer<typeof agentsSchema>;
|
||||
export type CompactionConfig = z.infer<typeof compactionSchema>;
|
||||
export type MemoryConfig = z.infer<typeof memorySchema>;
|
||||
export type WebSearchConfig = z.infer<typeof webSearchSchema>;
|
||||
export type ProcessConfig = z.infer<typeof processSchema>;
|
||||
export type DiscordConfig = z.infer<typeof discordSchema>;
|
||||
export type SlackConfig = z.infer<typeof slackSchema>;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { Message } from '../models/types.js';
|
||||
import type { AgentOrchestrator } from '../backends/native/orchestrator.js';
|
||||
import { COMPACTION_SYSTEM_PROMPT } from '../backends/native/prompts.js';
|
||||
import type { MemoryStore } from '../memory/store.js';
|
||||
import { COMPACTION_SYSTEM_PROMPT, MEMORY_EXTRACTION_PROMPT } from '../backends/native/prompts.js';
|
||||
import { estimateMessageTokens } from './tokens.js';
|
||||
|
||||
export interface CompactionConfig {
|
||||
@@ -33,6 +34,8 @@ export async function compactHistory(opts: {
|
||||
messages: Message[];
|
||||
orchestrator: AgentOrchestrator;
|
||||
config: CompactionConfig;
|
||||
memoryStore?: MemoryStore;
|
||||
autoExtract?: boolean;
|
||||
}): Promise<CompactionResult> {
|
||||
const { messages, orchestrator, config } = opts;
|
||||
|
||||
@@ -65,6 +68,29 @@ export async function compactHistory(opts: {
|
||||
content: '[Summary of earlier conversation]\n\n' + result.content,
|
||||
};
|
||||
|
||||
// Phase 2: Extract persistent facts and append to memory (if enabled)
|
||||
if (opts.memoryStore && opts.autoExtract !== false) {
|
||||
try {
|
||||
const extractionTier = orchestrator.getDelegationTier('memory_extraction');
|
||||
const extraction = await orchestrator.delegate({
|
||||
tier: extractionTier,
|
||||
systemPrompt: MEMORY_EXTRACTION_PROMPT,
|
||||
message: `Extract persistent facts from this conversation:\n\n${formattedConversation}`,
|
||||
maxTokens: 512,
|
||||
});
|
||||
|
||||
// Only write if the extraction produced meaningful content
|
||||
const extractedContent = extraction.content.trim();
|
||||
if (extractedContent.length > 0 && !extractedContent.toLowerCase().includes('no facts')) {
|
||||
opts.memoryStore.write('global', extractedContent, 'append');
|
||||
console.log(`[Flynn:memory] Extracted ${extractedContent.length} chars of facts to global memory`);
|
||||
}
|
||||
} catch (error) {
|
||||
// Memory extraction is best-effort — don't fail compaction if it errors
|
||||
console.warn('[Flynn:memory] Failed to extract facts during compaction:', error);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
messages: [summaryMessage, ...toKeep],
|
||||
compactedCount: toCompact.length,
|
||||
|
||||
+72
-2
@@ -5,9 +5,11 @@ import type { ModelClient } from '../models/index.js';
|
||||
import { AgentOrchestrator, type DelegationConfig } from '../backends/index.js';
|
||||
import { SessionStore, SessionManager } from '../session/index.js';
|
||||
import { HookEngine } from '../hooks/index.js';
|
||||
import { ToolRegistry, ToolExecutor, allBuiltinTools } from '../tools/index.js';
|
||||
import { ToolRegistry, ToolExecutor, allBuiltinTools, createWebSearchTools, createProcessTools, ProcessManager } from '../tools/index.js';
|
||||
import { MemoryStore } from '../memory/index.js';
|
||||
import { createMemoryTools } from '../tools/builtin/index.js';
|
||||
import { GatewayServer } from '../gateway/index.js';
|
||||
import { ChannelRegistry, TelegramAdapter, WebChatAdapter } from '../channels/index.js';
|
||||
import { ChannelRegistry, TelegramAdapter, WebChatAdapter, DiscordAdapter, SlackAdapter } from '../channels/index.js';
|
||||
import { CronScheduler } from '../automation/index.js';
|
||||
import type { InboundMessage, OutboundMessage } from '../channels/index.js';
|
||||
import { McpManager } from '../mcp/index.js';
|
||||
@@ -144,6 +146,7 @@ function createMessageRouter(deps: {
|
||||
toolRegistry: ToolRegistry;
|
||||
toolExecutor: ToolExecutor;
|
||||
config: Config;
|
||||
memoryStore?: MemoryStore;
|
||||
}) {
|
||||
// Cache agents by session ID to avoid recreating on every message
|
||||
const agents = new Map<string, AgentOrchestrator>();
|
||||
@@ -176,6 +179,7 @@ function createMessageRouter(deps: {
|
||||
} : undefined,
|
||||
modelName: deps.config.models.default.model,
|
||||
contextWindow: deps.config.models.default.context_window,
|
||||
memoryStore: deps.memoryStore,
|
||||
});
|
||||
agents.set(sessionId, agent);
|
||||
}
|
||||
@@ -228,6 +232,13 @@ export async function startDaemon(config: Config): Promise<DaemonContext> {
|
||||
const dataDir = resolve(homedir(), '.local/share/flynn');
|
||||
mkdirSync(dataDir, { recursive: true });
|
||||
|
||||
// Initialize memory store
|
||||
const memoryDir = config.memory.dir ?? resolve(dataDir, 'memory');
|
||||
mkdirSync(memoryDir, { recursive: true });
|
||||
const memoryStore = config.memory.enabled
|
||||
? new MemoryStore({ dir: memoryDir, maxContextTokens: config.memory.max_context_tokens })
|
||||
: undefined;
|
||||
|
||||
// Initialize session store and manager
|
||||
const sessionStore = new SessionStore(resolve(dataDir, 'sessions.db'));
|
||||
const sessionManager = new SessionManager(sessionStore);
|
||||
@@ -245,6 +256,42 @@ export async function startDaemon(config: Config): Promise<DaemonContext> {
|
||||
for (const tool of allBuiltinTools) {
|
||||
toolRegistry.register(tool);
|
||||
}
|
||||
|
||||
// Register memory tools if memory is enabled
|
||||
if (memoryStore) {
|
||||
for (const tool of createMemoryTools(memoryStore)) {
|
||||
toolRegistry.register(tool);
|
||||
}
|
||||
}
|
||||
|
||||
// Register web search tool if configured with credentials
|
||||
if (config.web_search.api_key || config.web_search.endpoint) {
|
||||
for (const tool of createWebSearchTools({
|
||||
provider: config.web_search.provider,
|
||||
apiKey: config.web_search.api_key,
|
||||
endpoint: config.web_search.endpoint,
|
||||
maxResults: config.web_search.max_results,
|
||||
})) {
|
||||
toolRegistry.register(tool);
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize process manager and register process tools
|
||||
const processManager = new ProcessManager({
|
||||
maxConcurrent: config.process.max_concurrent,
|
||||
maxRuntimeMinutes: config.process.max_runtime_minutes,
|
||||
bufferSize: config.process.buffer_size,
|
||||
});
|
||||
|
||||
for (const tool of createProcessTools(processManager)) {
|
||||
toolRegistry.register(tool);
|
||||
}
|
||||
|
||||
lifecycle.onShutdown(async () => {
|
||||
await processManager.shutdown();
|
||||
console.log('Process manager stopped');
|
||||
});
|
||||
|
||||
const toolExecutor = new ToolExecutor(toolRegistry, hookEngine);
|
||||
|
||||
// Initialize MCP manager and start configured servers
|
||||
@@ -321,6 +368,7 @@ export async function startDaemon(config: Config): Promise<DaemonContext> {
|
||||
toolRegistry,
|
||||
toolExecutor,
|
||||
config,
|
||||
memoryStore,
|
||||
}));
|
||||
|
||||
// Register Telegram adapter
|
||||
@@ -331,6 +379,28 @@ export async function startDaemon(config: Config): Promise<DaemonContext> {
|
||||
});
|
||||
channelRegistry.register(telegramAdapter);
|
||||
|
||||
// Register Discord adapter (if configured)
|
||||
if (config.discord) {
|
||||
const discordAdapter = new DiscordAdapter({
|
||||
botToken: config.discord.bot_token,
|
||||
allowedGuildIds: config.discord.allowed_guild_ids.length > 0 ? config.discord.allowed_guild_ids : undefined,
|
||||
allowedChannelIds: config.discord.allowed_channel_ids.length > 0 ? config.discord.allowed_channel_ids : undefined,
|
||||
requireMention: config.discord.require_mention,
|
||||
});
|
||||
channelRegistry.register(discordAdapter);
|
||||
}
|
||||
|
||||
// Register Slack adapter (if configured)
|
||||
if (config.slack) {
|
||||
const slackAdapter = new SlackAdapter({
|
||||
botToken: config.slack.bot_token,
|
||||
appToken: config.slack.app_token,
|
||||
signingSecret: config.slack.signing_secret,
|
||||
allowedChannelIds: config.slack.allowed_channel_ids.length > 0 ? config.slack.allowed_channel_ids : undefined,
|
||||
});
|
||||
channelRegistry.register(slackAdapter);
|
||||
}
|
||||
|
||||
// Register WebChat adapter (wraps the gateway)
|
||||
const webChatAdapter = new WebChatAdapter({ gateway });
|
||||
channelRegistry.register(webChatAdapter);
|
||||
|
||||
@@ -4,15 +4,29 @@ export { fileWriteTool } from './file-write.js';
|
||||
export { fileEditTool } from './file-edit.js';
|
||||
export { fileListTool } from './file-list.js';
|
||||
export { webFetchTool } from './web-fetch.js';
|
||||
export { createMemoryReadTool } from './memory-read.js';
|
||||
export { createMemoryWriteTool } from './memory-write.js';
|
||||
export { createMemorySearchTool } from './memory-search.js';
|
||||
export { createWebSearchTool } from './web-search.js';
|
||||
export type { WebSearchConfig } from './web-search.js';
|
||||
export { createProcessTools, ProcessManager } from './process/index.js';
|
||||
export type { ProcessManagerConfig } from './process/index.js';
|
||||
|
||||
import type { Tool } from '../types.js';
|
||||
import type { MemoryStore } from '../../memory/store.js';
|
||||
import type { WebSearchConfig } from './web-search.js';
|
||||
import { shellExecTool } from './shell.js';
|
||||
import { fileReadTool } from './file-read.js';
|
||||
import { fileWriteTool } from './file-write.js';
|
||||
import { fileEditTool } from './file-edit.js';
|
||||
import { fileListTool } from './file-list.js';
|
||||
import { webFetchTool } from './web-fetch.js';
|
||||
import { createMemoryReadTool } from './memory-read.js';
|
||||
import { createMemoryWriteTool } from './memory-write.js';
|
||||
import { createMemorySearchTool } from './memory-search.js';
|
||||
import { createWebSearchTool } from './web-search.js';
|
||||
|
||||
/** Static builtin tools that don't require runtime dependencies. */
|
||||
export const allBuiltinTools: Tool[] = [
|
||||
shellExecTool,
|
||||
fileReadTool,
|
||||
@@ -21,3 +35,17 @@ export const allBuiltinTools: Tool[] = [
|
||||
fileListTool,
|
||||
webFetchTool,
|
||||
];
|
||||
|
||||
/** Create memory tools that require a MemoryStore instance. */
|
||||
export function createMemoryTools(store: MemoryStore): Tool[] {
|
||||
return [
|
||||
createMemoryReadTool(store),
|
||||
createMemoryWriteTool(store),
|
||||
createMemorySearchTool(store),
|
||||
];
|
||||
}
|
||||
|
||||
/** Create the web search tool with provider config. */
|
||||
export function createWebSearchTools(config: WebSearchConfig): Tool[] {
|
||||
return [createWebSearchTool(config)];
|
||||
}
|
||||
|
||||
+3
-1
@@ -3,7 +3,9 @@ export { ToolRegistry } from './registry.js';
|
||||
export type { AnthropicToolDef, OpenAIToolDef } from './registry.js';
|
||||
export { ToolExecutor } from './executor.js';
|
||||
export type { ToolExecutorConfig } from './executor.js';
|
||||
export { allBuiltinTools } from './builtin/index.js';
|
||||
export { allBuiltinTools, createWebSearchTools, createProcessTools, ProcessManager } from './builtin/index.js';
|
||||
export type { WebSearchConfig } from './builtin/web-search.js';
|
||||
export type { ProcessManagerConfig } from './builtin/process/index.js';
|
||||
export { shellExecTool } from './builtin/shell.js';
|
||||
export { fileReadTool } from './builtin/file-read.js';
|
||||
export { fileWriteTool } from './builtin/file-write.js';
|
||||
|
||||
Reference in New Issue
Block a user