diff --git a/AGENTS.md b/AGENTS.md index 622649f..544c972 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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 diff --git a/docs/plans/state.json b/docs/plans/state.json new file mode 100644 index 0000000..e832b04 --- /dev/null +++ b/docs/plans/state.json @@ -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" + } +} diff --git a/package.json b/package.json index e6dec7e..32c8af6 100644 --- a/package.json +++ b/package.json @@ -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" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 967021d..4275f6e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,6 +14,12 @@ importers: '@modelcontextprotocol/sdk': specifier: ^1.26.0 version: 1.26.0(zod@3.25.76) + '@mozilla/readability': + specifier: ^0.5.0 + version: 0.5.0 + '@slack/bolt': + specifier: ^4.6.0 + version: 4.6.0(@types/express@5.0.6) better-sqlite3: specifier: ^11.0.0 version: 11.10.0 @@ -26,6 +32,9 @@ importers: croner: specifier: ^10.0.1 version: 10.0.1 + discord.js: + specifier: ^14.25.1 + version: 14.25.1 grammy: specifier: ^1.35.0 version: 1.39.3 @@ -35,6 +44,9 @@ importers: ink-text-input: specifier: ^6.0.0 version: 6.0.0(ink@6.6.0(@types/react@19.2.11)(react@19.2.4))(react@19.2.4) + linkedom: + specifier: ^0.18.0 + version: 0.18.12 marked: specifier: ^17.0.1 version: 17.0.1 @@ -50,6 +62,9 @@ importers: react: specifier: ^19.0.0 version: 19.2.4 + turndown: + specifier: ^7.2.0 + version: 7.2.2 ws: specifier: ^8.19.0 version: 8.19.0 @@ -72,6 +87,9 @@ importers: '@types/react': specifier: ^19.0.0 version: 19.2.11 + '@types/turndown': + specifier: ^5.0.0 + version: 5.0.6 '@types/ws': specifier: ^8.18.1 version: 8.18.1 @@ -101,6 +119,34 @@ packages: resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} engines: {node: '>=0.1.90'} + '@discordjs/builders@1.13.1': + resolution: {integrity: sha512-cOU0UDHc3lp/5nKByDxkmRiNZBpdp0kx55aarbiAfakfKJHlxv/yFW1zmIqCAmwH5CRlrH9iMFKJMpvW4DPB+w==} + engines: {node: '>=16.11.0'} + + '@discordjs/collection@1.5.3': + resolution: {integrity: sha512-SVb428OMd3WO1paV3rm6tSjM4wC+Kecaa1EUGX7vc6/fddvw/6lg90z4QtCqm21zvVe92vMMDt9+DkIvjXImQQ==} + engines: {node: '>=16.11.0'} + + '@discordjs/collection@2.1.1': + resolution: {integrity: sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg==} + engines: {node: '>=18'} + + '@discordjs/formatters@0.6.2': + resolution: {integrity: sha512-y4UPwWhH6vChKRkGdMB4odasUbHOUwy7KL+OVwF86PvT6QVOwElx+TiI1/6kcmcEe+g5YRXJFiXSXUdabqZOvQ==} + engines: {node: '>=16.11.0'} + + '@discordjs/rest@2.6.0': + resolution: {integrity: sha512-RDYrhmpB7mTvmCKcpj+pc5k7POKszS4E2O9TYc+U+Y4iaCP+r910QdO43qmpOja8LRr1RJ0b3U+CqVsnPqzf4w==} + engines: {node: '>=18'} + + '@discordjs/util@1.2.0': + resolution: {integrity: sha512-3LKP7F2+atl9vJFhaBjn4nOaSWahZ/yWjOvA4e5pnXkt2qyXRCHLxoBQy81GFtLGCq7K9lPm9R517M1U+/90Qg==} + engines: {node: '>=18'} + + '@discordjs/ws@1.2.3': + resolution: {integrity: sha512-wPlQDxEmlDg5IxhJPuxXr3Vy9AjYq5xCvFWGJyD7w7Np8ZGu+Mc+97LCoEc/+AYCo2IDpKioiH0/c/mj5ZR9Uw==} + engines: {node: '>=16.11.0'} + '@esbuild/aix-ppc64@0.27.2': resolution: {integrity: sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==} engines: {node: '>=18'} @@ -323,6 +369,9 @@ packages: '@jridgewell/sourcemap-codec@1.5.5': resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + '@mixmark-io/domino@2.2.0': + resolution: {integrity: sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw==} + '@modelcontextprotocol/sdk@1.26.0': resolution: {integrity: sha512-Y5RmPncpiDtTXDbLKswIJzTqu2hyBKxTNsgKqKclDbhIgg1wgtf1fRuvxgTnRfcnxtvvgbIEcqUOzZrJ6iSReg==} engines: {node: '>=18'} @@ -333,6 +382,10 @@ packages: '@cfworker/json-schema': optional: true + '@mozilla/readability@0.5.0': + resolution: {integrity: sha512-Z+CZ3QaosfFaTqvhQsIktyGrjFjSC0Fa4EMph4mqKnWhmyoGICsV/8QK+8HpXut6zV7zwfWwqDmEjtk1Qf6EgQ==} + engines: {node: '>=14.0.0'} + '@rollup/rollup-android-arm-eabi@4.57.1': resolution: {integrity: sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==} cpu: [arm] @@ -471,31 +524,90 @@ packages: cpu: [x64] os: [win32] + '@sapphire/async-queue@1.5.5': + resolution: {integrity: sha512-cvGzxbba6sav2zZkH8GPf2oGk9yYoD5qrNWdu9fRehifgnFZJMV+nuy2nON2roRO4yQQ+v7MK/Pktl/HgfsUXg==} + engines: {node: '>=v14.0.0', npm: '>=7.0.0'} + + '@sapphire/shapeshift@4.0.0': + resolution: {integrity: sha512-d9dUmWVA7MMiKobL3VpLF8P2aeanRTu6ypG2OIaEv/ZHH/SUQ2iHOVyi5wAPjQ+HmnMuL0whK9ez8I/raWbtIg==} + engines: {node: '>=v16'} + + '@sapphire/snowflake@3.5.3': + resolution: {integrity: sha512-jjmJywLAFoWeBi1W7994zZyiNWPIiqRRNAmSERxyg93xRGzNYvGjlZ0gR6x0F4gPRi2+0O6S71kOZYyr3cxaIQ==} + engines: {node: '>=v14.0.0', npm: '>=7.0.0'} + '@sindresorhus/is@4.6.0': resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==} engines: {node: '>=10'} + '@slack/bolt@4.6.0': + resolution: {integrity: sha512-xPgfUs2+OXSugz54Ky07pA890+Qydk22SYToi8uGpXeHSt1JWwFJkRyd/9Vlg5I1AdfdpGXExDpwnbuN9Q/2dQ==} + engines: {node: '>=18', npm: '>=8.6.0'} + peerDependencies: + '@types/express': ^5.0.0 + + '@slack/logger@4.0.0': + resolution: {integrity: sha512-Wz7QYfPAlG/DR+DfABddUZeNgoeY7d1J39OCR2jR+v7VBsB8ezulDK5szTnDDPDwLH5IWhLvXIHlCFZV7MSKgA==} + engines: {node: '>= 18', npm: '>= 8.6.0'} + + '@slack/oauth@3.0.4': + resolution: {integrity: sha512-+8H0g7mbrHndEUbYCP7uYyBCbwqmm3E6Mo3nfsDvZZW74zKk1ochfH/fWSvGInYNCVvaBUbg3RZBbTp0j8yJCg==} + engines: {node: '>=18', npm: '>=8.6.0'} + + '@slack/socket-mode@2.0.5': + resolution: {integrity: sha512-VaapvmrAifeFLAFaDPfGhEwwunTKsI6bQhYzxRXw7BSujZUae5sANO76WqlVsLXuhVtCVrBWPiS2snAQR2RHJQ==} + engines: {node: '>= 18', npm: '>= 8.6.0'} + + '@slack/types@2.19.0': + resolution: {integrity: sha512-7+QZ38HGcNh/b/7MpvPG6jnw7mliV6UmrquJLqgdxkzJgQEYUcEztvFWRU49z0x4vthF0ixL5lTK601AXrS8IA==} + engines: {node: '>= 12.13.0', npm: '>= 6.12.0'} + + '@slack/web-api@7.13.0': + resolution: {integrity: sha512-ERcExbWrnkDN8ovoWWe6Wgt/usanj1dWUd18dJLpctUI4mlPS0nKt81Joh8VI+OPbNnY1lIilVt9gdMBD9U2ig==} + engines: {node: '>= 18', npm: '>= 8.6.0'} + '@types/better-sqlite3@7.6.13': resolution: {integrity: sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA==} + '@types/body-parser@1.19.6': + resolution: {integrity: sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==} + '@types/cardinal@2.1.1': resolution: {integrity: sha512-/xCVwg8lWvahHsV2wXZt4i64H1sdL+sN1Uoq7fAc8/FA6uYHjuIveDwPwvGUYp4VZiv85dVl6J/Bum3NDAOm8g==} '@types/chai@5.2.3': resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + '@types/connect@3.4.38': + resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} + '@types/deep-eql@4.0.2': resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + '@types/express-serve-static-core@5.1.1': + resolution: {integrity: sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A==} + + '@types/express@5.0.6': + resolution: {integrity: sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==} + + '@types/http-errors@2.0.5': + resolution: {integrity: sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==} + '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + '@types/jsonwebtoken@9.0.10': + resolution: {integrity: sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==} + '@types/marked-terminal@6.1.1': resolution: {integrity: sha512-DfoUqkmFDCED7eBY9vFUhJ9fW8oZcMAK5EwRDQ9drjTbpQa+DnBTQQCwWhTFVf4WsZ6yYcJTI8D91wxTWXRZZQ==} + '@types/ms@2.1.0': + resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} + '@types/node-fetch@2.6.13': resolution: {integrity: sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==} @@ -505,9 +617,27 @@ packages: '@types/node@22.19.7': resolution: {integrity: sha512-MciR4AKGHWl7xwxkBa6xUGxQJ4VBOmPTF7sL+iGzuahOFaO0jHCsuEfS80pan1ef4gWId1oWOweIhrDEYLuaOw==} + '@types/qs@6.14.0': + resolution: {integrity: sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==} + + '@types/range-parser@1.2.7': + resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} + '@types/react@19.2.11': resolution: {integrity: sha512-tORuanb01iEzWvMGVGv2ZDhYZVeRMrw453DCSAIn/5yvcSVnMoUMTyf33nQJLahYEnv9xqrTNbgz4qY5EfSh0g==} + '@types/retry@0.12.0': + resolution: {integrity: sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==} + + '@types/send@1.2.1': + resolution: {integrity: sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==} + + '@types/serve-static@2.2.0': + resolution: {integrity: sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==} + + '@types/turndown@5.0.6': + resolution: {integrity: sha512-ru00MoyeeouE5BX4gRL+6m/BsDfbRayOskWqUvh7CLGW+UXxHQItqALa38kKnOiZPqJrtzJUgAC2+F0rL1S4Pg==} + '@types/ws@8.18.1': resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} @@ -540,6 +670,10 @@ packages: '@vitest/utils@3.2.4': resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==} + '@vladfrangu/async_event_emitter@2.4.7': + resolution: {integrity: sha512-Xfe6rpCTxSxfbswi/W/Pz7zp1WWSNn4A0eW4mLkQUewCrXXtMj31lCg+iQyTkh/CkusZSq9eDflu7tjEDXUY6g==} + engines: {node: '>=v14.0.0', npm: '>=7.0.0'} + abort-controller@3.0.0: resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} engines: {node: '>=6.5'} @@ -613,6 +747,9 @@ packages: resolution: {integrity: sha512-ooviqdwwgfIfNmDwo94wlshcdzfO64XV0Cg6oDsDYBJfITDz1EngD2z7DkbvCWn+XIMsIqW27sEVF6qcpJrRcg==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + axios@1.13.4: + resolution: {integrity: sha512-1wVkUaAO6WyaYtCkcYCOx12ZgpGf9Zif+qXa4n+oYzK558YryKqiL6UWwd5DqiH3VRW0GYhTZQ/vlgJrCoNQlg==} + balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} @@ -632,9 +769,15 @@ packages: resolution: {integrity: sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==} engines: {node: '>=18'} + boolbase@1.0.0: + resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} + brace-expansion@1.1.12: resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} + buffer-equal-constant-time@1.0.1: + resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} + buffer@5.7.1: resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} @@ -759,6 +902,16 @@ packages: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} + css-select@5.2.2: + resolution: {integrity: sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==} + + css-what@6.2.2: + resolution: {integrity: sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==} + engines: {node: '>= 6'} + + cssom@0.5.0: + resolution: {integrity: sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==} + csstype@3.2.3: resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} @@ -798,10 +951,33 @@ packages: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} + discord-api-types@0.38.38: + resolution: {integrity: sha512-7qcM5IeZrfb+LXW07HvoI5L+j4PQeMZXEkSm1htHAHh4Y9JSMXBWjy/r7zmUCOj4F7zNjMcm7IMWr131MT2h0Q==} + + discord.js@14.25.1: + resolution: {integrity: sha512-2l0gsPOLPs5t6GFZfQZKnL1OJNYFcuC/ETWsW4VtKVD/tg4ICa9x+jb9bkPffkMdRpRpuUaO/fKkHCBeiCKh8g==} + engines: {node: '>=18'} + + dom-serializer@2.0.0: + resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} + + domelementtype@2.3.0: + resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + + domhandler@5.0.3: + resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} + engines: {node: '>= 4'} + + domutils@3.2.2: + resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} + dunder-proto@1.0.1: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} + ecdsa-sig-formatter@1.0.11: + resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} + ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} @@ -821,6 +997,14 @@ packages: end-of-stream@1.4.5: resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + + entities@7.0.1: + resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==} + engines: {node: '>=0.12'} + environment@1.1.0: resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==} engines: {node: '>=18'} @@ -920,6 +1104,12 @@ packages: resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} engines: {node: '>=6'} + eventemitter3@4.0.7: + resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} + + eventemitter3@5.0.4: + resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==} + eventsource-parser@3.0.6: resolution: {integrity: sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==} engines: {node: '>=18.0.0'} @@ -989,6 +1179,15 @@ packages: flatted@3.3.3: resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + follow-redirects@1.15.11: + resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + form-data-encoder@1.7.2: resolution: {integrity: sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==} @@ -1080,6 +1279,12 @@ packages: resolution: {integrity: sha512-l7qMiNee7t82bH3SeyUCt9UF15EVmaBvsppY2zQtrbIhl/yzBTny+YUxsVjSjQ6gaqaeVtZmGocom8TzBlA4Yw==} engines: {node: '>=16.9.0'} + html-escaper@3.0.3: + resolution: {integrity: sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==} + + htmlparser2@10.1.0: + resolution: {integrity: sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==} + http-errors@2.0.1: resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} engines: {node: '>= 0.8'} @@ -1144,6 +1349,9 @@ packages: resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} engines: {node: '>= 0.10'} + is-electron@2.2.2: + resolution: {integrity: sha512-FO/Rhvz5tuw4MCWkpMzHFKWD2LsfHzIb7i6MdPYZ/KW7AlxawyLkqdy+jPZP1WubqEADE3O4FUENlJHDfQASRg==} + is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} @@ -1168,6 +1376,10 @@ packages: is-promise@4.0.0: resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} + is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} @@ -1196,6 +1408,16 @@ packages: json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + jsonwebtoken@9.0.3: + resolution: {integrity: sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==} + engines: {node: '>=12', npm: '>=6'} + + jwa@2.0.1: + resolution: {integrity: sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==} + + jws@4.0.1: + resolution: {integrity: sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==} + keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} @@ -1203,16 +1425,55 @@ packages: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} + linkedom@0.18.12: + resolution: {integrity: sha512-jalJsOwIKuQJSeTvsgzPe9iJzyfVaEJiEXl+25EkKevsULHvMJzpNqwvj1jOESWdmgKDiXObyjOYwlUqG7wo1Q==} + engines: {node: '>=16'} + peerDependencies: + canvas: '>= 2' + peerDependenciesMeta: + canvas: + optional: true + locate-path@6.0.0: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} + lodash.includes@4.3.0: + resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==} + + lodash.isboolean@3.0.3: + resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==} + + lodash.isinteger@4.0.4: + resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==} + + lodash.isnumber@3.0.3: + resolution: {integrity: sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==} + + lodash.isplainobject@4.0.6: + resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} + + lodash.isstring@4.0.1: + resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==} + lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + lodash.once@4.1.1: + resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} + + lodash.snakecase@4.1.1: + resolution: {integrity: sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==} + + lodash@4.17.23: + resolution: {integrity: sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==} + loupe@3.2.1: resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} + magic-bytes.js@1.13.0: + resolution: {integrity: sha512-afO2mnxW7GDTXMm5/AoN1WuOcdoKhtgXjIvHmobqTD1grNplhGdv3PFOyjCVmrnOZBIT/gD/koDKpYG+0mvHcg==} + magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} @@ -1320,6 +1581,9 @@ packages: encoding: optional: true + nth-check@2.1.1: + resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -1358,6 +1622,10 @@ packages: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} + p-finally@1.0.0: + resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==} + engines: {node: '>=4'} + p-limit@3.1.0: resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} engines: {node: '>=10'} @@ -1366,6 +1634,18 @@ packages: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} + p-queue@6.6.2: + resolution: {integrity: sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==} + engines: {node: '>=8'} + + p-retry@4.6.2: + resolution: {integrity: sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==} + engines: {node: '>=8'} + + p-timeout@3.2.0: + resolution: {integrity: sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==} + engines: {node: '>=8'} + parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} @@ -1433,6 +1713,9 @@ packages: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} + proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + pump@3.0.3: resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==} @@ -1489,6 +1772,10 @@ packages: resolution: {integrity: sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + retry@0.13.1: + resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==} + engines: {node: '>= 4'} + rollup@4.57.1: resolution: {integrity: sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} @@ -1670,6 +1957,16 @@ packages: tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + ts-mixer@6.0.4: + resolution: {integrity: sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA==} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + tsscmp@1.0.6: + resolution: {integrity: sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==} + engines: {node: '>=0.6.x'} + tsx@4.21.0: resolution: {integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==} engines: {node: '>=18.0.0'} @@ -1678,6 +1975,9 @@ packages: tunnel-agent@0.6.0: resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} + turndown@7.2.2: + resolution: {integrity: sha512-1F7db8BiExOKxjSMU2b7if62D/XOyQyZbPKq/nUwopfgnHlqXHqQ0lvfUTeUIr1lZJzOPFn43dODyMSIfvWRKQ==} + type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} @@ -1695,12 +1995,19 @@ packages: engines: {node: '>=14.17'} hasBin: true + uhyphen@0.2.0: + resolution: {integrity: sha512-qz3o9CHXmJJPGBdqzab7qAYuW8kQGKNEuoHFYrBwV6hWIMcpAmxDLXojcHfFr9US1Pe6zUswEIJIbLI610fuqA==} + undici-types@5.26.5: resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + undici@6.21.3: + resolution: {integrity: sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw==} + engines: {node: '>=18.17'} + unicode-emoji-modifier-base@1.0.0: resolution: {integrity: sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g==} engines: {node: '>=4'} @@ -1900,6 +2207,55 @@ snapshots: '@colors/colors@1.5.0': optional: true + '@discordjs/builders@1.13.1': + dependencies: + '@discordjs/formatters': 0.6.2 + '@discordjs/util': 1.2.0 + '@sapphire/shapeshift': 4.0.0 + discord-api-types: 0.38.38 + fast-deep-equal: 3.1.3 + ts-mixer: 6.0.4 + tslib: 2.8.1 + + '@discordjs/collection@1.5.3': {} + + '@discordjs/collection@2.1.1': {} + + '@discordjs/formatters@0.6.2': + dependencies: + discord-api-types: 0.38.38 + + '@discordjs/rest@2.6.0': + dependencies: + '@discordjs/collection': 2.1.1 + '@discordjs/util': 1.2.0 + '@sapphire/async-queue': 1.5.5 + '@sapphire/snowflake': 3.5.3 + '@vladfrangu/async_event_emitter': 2.4.7 + discord-api-types: 0.38.38 + magic-bytes.js: 1.13.0 + tslib: 2.8.1 + undici: 6.21.3 + + '@discordjs/util@1.2.0': + dependencies: + discord-api-types: 0.38.38 + + '@discordjs/ws@1.2.3': + dependencies: + '@discordjs/collection': 2.1.1 + '@discordjs/rest': 2.6.0 + '@discordjs/util': 1.2.0 + '@sapphire/async-queue': 1.5.5 + '@types/ws': 8.18.1 + '@vladfrangu/async_event_emitter': 2.4.7 + discord-api-types: 0.38.38 + tslib: 2.8.1 + ws: 8.19.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + '@esbuild/aix-ppc64@0.27.2': optional: true @@ -2043,6 +2399,8 @@ snapshots: '@jridgewell/sourcemap-codec@1.5.5': {} + '@mixmark-io/domino@2.2.0': {} + '@modelcontextprotocol/sdk@1.26.0(zod@3.25.76)': dependencies: '@hono/node-server': 1.19.9(hono@4.11.7) @@ -2065,6 +2423,8 @@ snapshots: transitivePeerDependencies: - supports-color + '@mozilla/readability@0.5.0': {} + '@rollup/rollup-android-arm-eabi@4.57.1': optional: true @@ -2140,12 +2500,91 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.57.1': optional: true + '@sapphire/async-queue@1.5.5': {} + + '@sapphire/shapeshift@4.0.0': + dependencies: + fast-deep-equal: 3.1.3 + lodash: 4.17.23 + + '@sapphire/snowflake@3.5.3': {} + '@sindresorhus/is@4.6.0': {} + '@slack/bolt@4.6.0(@types/express@5.0.6)': + dependencies: + '@slack/logger': 4.0.0 + '@slack/oauth': 3.0.4 + '@slack/socket-mode': 2.0.5 + '@slack/types': 2.19.0 + '@slack/web-api': 7.13.0 + '@types/express': 5.0.6 + axios: 1.13.4 + express: 5.2.1 + path-to-regexp: 8.3.0 + raw-body: 3.0.2 + tsscmp: 1.0.6 + transitivePeerDependencies: + - bufferutil + - debug + - supports-color + - utf-8-validate + + '@slack/logger@4.0.0': + dependencies: + '@types/node': 22.19.7 + + '@slack/oauth@3.0.4': + dependencies: + '@slack/logger': 4.0.0 + '@slack/web-api': 7.13.0 + '@types/jsonwebtoken': 9.0.10 + '@types/node': 22.19.7 + jsonwebtoken: 9.0.3 + transitivePeerDependencies: + - debug + + '@slack/socket-mode@2.0.5': + dependencies: + '@slack/logger': 4.0.0 + '@slack/web-api': 7.13.0 + '@types/node': 22.19.7 + '@types/ws': 8.18.1 + eventemitter3: 5.0.4 + ws: 8.19.0 + transitivePeerDependencies: + - bufferutil + - debug + - utf-8-validate + + '@slack/types@2.19.0': {} + + '@slack/web-api@7.13.0': + dependencies: + '@slack/logger': 4.0.0 + '@slack/types': 2.19.0 + '@types/node': 22.19.7 + '@types/retry': 0.12.0 + axios: 1.13.4 + eventemitter3: 5.0.4 + form-data: 4.0.5 + is-electron: 2.2.2 + is-stream: 2.0.1 + p-queue: 6.6.2 + p-retry: 4.6.2 + retry: 0.13.1 + transitivePeerDependencies: + - debug + '@types/better-sqlite3@7.6.13': dependencies: '@types/node': 22.19.7 + '@types/body-parser@1.19.6': + dependencies: + '@types/connect': 3.4.38 + '@types/node': 22.19.7 + '@types/cardinal@2.1.1': {} '@types/chai@5.2.3': @@ -2153,12 +2592,36 @@ snapshots: '@types/deep-eql': 4.0.2 assertion-error: 2.0.1 + '@types/connect@3.4.38': + dependencies: + '@types/node': 22.19.7 + '@types/deep-eql@4.0.2': {} '@types/estree@1.0.8': {} + '@types/express-serve-static-core@5.1.1': + dependencies: + '@types/node': 22.19.7 + '@types/qs': 6.14.0 + '@types/range-parser': 1.2.7 + '@types/send': 1.2.1 + + '@types/express@5.0.6': + dependencies: + '@types/body-parser': 1.19.6 + '@types/express-serve-static-core': 5.1.1 + '@types/serve-static': 2.2.0 + + '@types/http-errors@2.0.5': {} + '@types/json-schema@7.0.15': {} + '@types/jsonwebtoken@9.0.10': + dependencies: + '@types/ms': 2.1.0 + '@types/node': 22.19.7 + '@types/marked-terminal@6.1.1': dependencies: '@types/cardinal': 2.1.1 @@ -2166,6 +2629,8 @@ snapshots: chalk: 5.6.2 marked: 11.2.0 + '@types/ms@2.1.0': {} + '@types/node-fetch@2.6.13': dependencies: '@types/node': 22.19.7 @@ -2179,10 +2644,27 @@ snapshots: dependencies: undici-types: 6.21.0 + '@types/qs@6.14.0': {} + + '@types/range-parser@1.2.7': {} + '@types/react@19.2.11': dependencies: csstype: 3.2.3 + '@types/retry@0.12.0': {} + + '@types/send@1.2.1': + dependencies: + '@types/node': 22.19.7 + + '@types/serve-static@2.2.0': + dependencies: + '@types/http-errors': 2.0.5 + '@types/node': 22.19.7 + + '@types/turndown@5.0.6': {} + '@types/ws@8.18.1': dependencies: '@types/node': 22.19.7 @@ -2229,6 +2711,8 @@ snapshots: loupe: 3.2.1 tinyrainbow: 2.0.0 + '@vladfrangu/async_event_emitter@2.4.7': {} + abort-controller@3.0.0: dependencies: event-target-shim: 5.0.1 @@ -2290,6 +2774,14 @@ snapshots: auto-bind@5.0.1: {} + axios@1.13.4: + dependencies: + follow-redirects: 1.15.11 + form-data: 4.0.5 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + balanced-match@1.0.2: {} base64-js@1.5.1: {} @@ -2323,11 +2815,15 @@ snapshots: transitivePeerDependencies: - supports-color + boolbase@1.0.0: {} + brace-expansion@1.1.12: dependencies: balanced-match: 1.0.2 concat-map: 0.0.1 + buffer-equal-constant-time@1.0.1: {} + buffer@5.7.1: dependencies: base64-js: 1.5.1 @@ -2443,6 +2939,18 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 + css-select@5.2.2: + dependencies: + boolbase: 1.0.0 + css-what: 6.2.2 + domhandler: 5.0.3 + domutils: 3.2.2 + nth-check: 2.1.1 + + css-what@6.2.2: {} + + cssom@0.5.0: {} + csstype@3.2.3: {} debug@4.4.3: @@ -2465,12 +2973,55 @@ snapshots: detect-libc@2.1.2: {} + discord-api-types@0.38.38: {} + + discord.js@14.25.1: + dependencies: + '@discordjs/builders': 1.13.1 + '@discordjs/collection': 1.5.3 + '@discordjs/formatters': 0.6.2 + '@discordjs/rest': 2.6.0 + '@discordjs/util': 1.2.0 + '@discordjs/ws': 1.2.3 + '@sapphire/snowflake': 3.5.3 + discord-api-types: 0.38.38 + fast-deep-equal: 3.1.3 + lodash.snakecase: 4.1.1 + magic-bytes.js: 1.13.0 + tslib: 2.8.1 + undici: 6.21.3 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + dom-serializer@2.0.0: + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + entities: 4.5.0 + + domelementtype@2.3.0: {} + + domhandler@5.0.3: + dependencies: + domelementtype: 2.3.0 + + domutils@3.2.2: + dependencies: + dom-serializer: 2.0.0 + domelementtype: 2.3.0 + domhandler: 5.0.3 + dunder-proto@1.0.1: dependencies: call-bind-apply-helpers: 1.0.2 es-errors: 1.3.0 gopd: 1.2.0 + ecdsa-sig-formatter@1.0.11: + dependencies: + safe-buffer: 5.2.1 + ee-first@1.1.1: {} emoji-regex@10.6.0: {} @@ -2485,6 +3036,10 @@ snapshots: dependencies: once: 1.4.0 + entities@4.5.0: {} + + entities@7.0.1: {} + environment@1.1.0: {} es-define-property@1.0.1: {} @@ -2617,6 +3172,10 @@ snapshots: event-target-shim@5.0.1: {} + eventemitter3@4.0.7: {} + + eventemitter3@5.0.4: {} + eventsource-parser@3.0.6: {} eventsource@3.0.7: @@ -2706,6 +3265,8 @@ snapshots: flatted@3.3.3: {} + follow-redirects@1.15.11: {} + form-data-encoder@1.7.2: {} form-data@4.0.5: @@ -2794,6 +3355,15 @@ snapshots: hono@4.11.7: {} + html-escaper@3.0.3: {} + + htmlparser2@10.1.0: + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + domutils: 3.2.2 + entities: 7.0.1 + http-errors@2.0.1: dependencies: depd: 2.0.0 @@ -2870,6 +3440,8 @@ snapshots: ipaddr.js@1.9.1: {} + is-electron@2.2.2: {} + is-extglob@2.1.1: {} is-fullwidth-code-point@3.0.0: {} @@ -2886,6 +3458,8 @@ snapshots: is-promise@4.0.0: {} + is-stream@2.0.1: {} + isexe@2.0.0: {} jose@6.1.3: {} @@ -2906,6 +3480,30 @@ snapshots: json-stable-stringify-without-jsonify@1.0.1: {} + jsonwebtoken@9.0.3: + dependencies: + jws: 4.0.1 + lodash.includes: 4.3.0 + lodash.isboolean: 3.0.3 + lodash.isinteger: 4.0.4 + lodash.isnumber: 3.0.3 + lodash.isplainobject: 4.0.6 + lodash.isstring: 4.0.1 + lodash.once: 4.1.1 + ms: 2.1.3 + semver: 7.7.3 + + jwa@2.0.1: + dependencies: + buffer-equal-constant-time: 1.0.1 + ecdsa-sig-formatter: 1.0.11 + safe-buffer: 5.2.1 + + jws@4.0.1: + dependencies: + jwa: 2.0.1 + safe-buffer: 5.2.1 + keyv@4.5.4: dependencies: json-buffer: 3.0.1 @@ -2915,14 +3513,42 @@ snapshots: prelude-ls: 1.2.1 type-check: 0.4.0 + linkedom@0.18.12: + dependencies: + css-select: 5.2.2 + cssom: 0.5.0 + html-escaper: 3.0.3 + htmlparser2: 10.1.0 + uhyphen: 0.2.0 + locate-path@6.0.0: dependencies: p-locate: 5.0.0 + lodash.includes@4.3.0: {} + + lodash.isboolean@3.0.3: {} + + lodash.isinteger@4.0.4: {} + + lodash.isnumber@3.0.3: {} + + lodash.isplainobject@4.0.6: {} + + lodash.isstring@4.0.1: {} + lodash.merge@4.6.2: {} + lodash.once@4.1.1: {} + + lodash.snakecase@4.1.1: {} + + lodash@4.17.23: {} + loupe@3.2.1: {} + magic-bytes.js@1.13.0: {} + magic-string@0.30.21: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -3005,6 +3631,10 @@ snapshots: dependencies: whatwg-url: 5.0.0 + nth-check@2.1.1: + dependencies: + boolbase: 1.0.0 + object-assign@4.1.1: {} object-inspect@1.13.4: {} @@ -3049,6 +3679,8 @@ snapshots: type-check: 0.4.0 word-wrap: 1.2.5 + p-finally@1.0.0: {} + p-limit@3.1.0: dependencies: yocto-queue: 0.1.0 @@ -3057,6 +3689,20 @@ snapshots: dependencies: p-limit: 3.1.0 + p-queue@6.6.2: + dependencies: + eventemitter3: 4.0.7 + p-timeout: 3.2.0 + + p-retry@4.6.2: + dependencies: + '@types/retry': 0.12.0 + retry: 0.13.1 + + p-timeout@3.2.0: + dependencies: + p-finally: 1.0.0 + parent-module@1.0.1: dependencies: callsites: 3.1.0 @@ -3117,6 +3763,8 @@ snapshots: forwarded: 0.2.0 ipaddr.js: 1.9.1 + proxy-from-env@1.1.0: {} + pump@3.0.3: dependencies: end-of-stream: 1.4.5 @@ -3170,6 +3818,8 @@ snapshots: onetime: 5.1.2 signal-exit: 3.0.7 + retry@0.13.1: {} + rollup@4.57.1: dependencies: '@types/estree': 1.0.8 @@ -3401,6 +4051,12 @@ snapshots: tr46@0.0.3: {} + ts-mixer@6.0.4: {} + + tslib@2.8.1: {} + + tsscmp@1.0.6: {} + tsx@4.21.0: dependencies: esbuild: 0.27.2 @@ -3412,6 +4068,10 @@ snapshots: dependencies: safe-buffer: 5.2.1 + turndown@7.2.2: + dependencies: + '@mixmark-io/domino': 2.2.0 + type-check@0.4.0: dependencies: prelude-ls: 1.2.1 @@ -3426,10 +4086,14 @@ snapshots: typescript@5.9.3: {} + uhyphen@0.2.0: {} + undici-types@5.26.5: {} undici-types@6.21.0: {} + undici@6.21.3: {} + unicode-emoji-modifier-base@1.0.0: {} unpipe@1.0.0: {} diff --git a/src/backends/native/agent.ts b/src/backends/native/agent.ts index 6a89e4a..70c30dc 100644 --- a/src/backends/native/agent.ts +++ b/src/backends/native/agent.ts @@ -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; } diff --git a/src/backends/native/orchestrator.ts b/src/backends/native/orchestrator.ts index 2049674..cffb797 100644 --- a/src/backends/native/orchestrator.ts +++ b/src/backends/native/orchestrator.ts @@ -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 = 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 { + 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. diff --git a/src/channels/index.ts b/src/channels/index.ts index 5baa453..456cadc 100644 --- a/src/channels/index.ts +++ b/src/channels/index.ts @@ -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'; diff --git a/src/config/schema.ts b/src/config/schema.ts index 77dfc53..6f18c66 100644 --- a/src/config/schema.ts +++ b/src/config/schema.ts @@ -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; @@ -133,3 +172,8 @@ export type ModelConfig = z.infer; export type CronJobConfig = z.infer; export type AgentsConfig = z.infer; export type CompactionConfig = z.infer; +export type MemoryConfig = z.infer; +export type WebSearchConfig = z.infer; +export type ProcessConfig = z.infer; +export type DiscordConfig = z.infer; +export type SlackConfig = z.infer; diff --git a/src/context/compaction.ts b/src/context/compaction.ts index 1f2f968..8fcbd03 100644 --- a/src/context/compaction.ts +++ b/src/context/compaction.ts @@ -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 { 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, diff --git a/src/daemon/index.ts b/src/daemon/index.ts index ecc0c22..0fd0a19 100644 --- a/src/daemon/index.ts +++ b/src/daemon/index.ts @@ -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(); @@ -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 { 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 { 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 { toolRegistry, toolExecutor, config, + memoryStore, })); // Register Telegram adapter @@ -331,6 +379,28 @@ export async function startDaemon(config: Config): Promise { }); 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); diff --git a/src/tools/builtin/index.ts b/src/tools/builtin/index.ts index 00895f5..8175a78 100644 --- a/src/tools/builtin/index.ts +++ b/src/tools/builtin/index.ts @@ -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)]; +} diff --git a/src/tools/index.ts b/src/tools/index.ts index e7667fd..c22d8da 100644 --- a/src/tools/index.ts +++ b/src/tools/index.ts @@ -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';