profclaw 2.0.0 → 2.3.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 +14 -2
- package/CHANGELOG.md +37 -0
- package/CONTRIBUTING.md +20 -0
- package/Dockerfile +39 -23
- package/README.md +174 -40
- package/dist/agents/agent-summary.d.ts +44 -0
- package/dist/agents/agent-summary.js +176 -0
- package/dist/agents/auto-memory.d.ts +59 -0
- package/dist/agents/auto-memory.js +310 -0
- package/dist/agents/checkpoint-manager.d.ts +76 -0
- package/dist/agents/checkpoint-manager.js +156 -0
- package/dist/agents/circuit-breaker.d.ts +54 -0
- package/dist/agents/circuit-breaker.js +153 -0
- package/dist/agents/context-compactor.d.ts +70 -0
- package/dist/agents/context-compactor.js +361 -0
- package/dist/agents/conversation-branch.d.ts +55 -0
- package/dist/agents/conversation-branch.js +115 -0
- package/dist/agents/error-recovery.d.ts +44 -0
- package/dist/agents/error-recovery.js +197 -0
- package/dist/agents/events.d.ts +76 -0
- package/dist/agents/events.js +8 -0
- package/dist/agents/executor.d.ts +35 -1
- package/dist/agents/executor.js +573 -124
- package/dist/agents/file-snapshots.d.ts +72 -0
- package/dist/agents/file-snapshots.js +182 -0
- package/dist/agents/index.d.ts +2 -0
- package/dist/agents/orchestrator.d.ts +183 -0
- package/dist/agents/orchestrator.js +495 -0
- package/dist/agents/permissions.d.ts +63 -0
- package/dist/agents/permissions.js +138 -0
- package/dist/agents/plan-mode.d.ts +71 -0
- package/dist/agents/plan-mode.js +186 -0
- package/dist/agents/prompt-suggestions.d.ts +33 -0
- package/dist/agents/prompt-suggestions.js +208 -0
- package/dist/agents/rate-limit-monitor.d.ts +56 -0
- package/dist/agents/rate-limit-monitor.js +224 -0
- package/dist/agents/result-store.d.ts +37 -0
- package/dist/agents/result-store.js +104 -0
- package/dist/agents/session-diff.d.ts +43 -0
- package/dist/agents/session-diff.js +258 -0
- package/dist/agents/tool-loader.d.ts +65 -0
- package/dist/agents/tool-loader.js +273 -0
- package/dist/agents/transcript.d.ts +96 -0
- package/dist/agents/transcript.js +267 -0
- package/dist/agents/worktree-manager.d.ts +80 -0
- package/dist/agents/worktree-manager.js +203 -0
- package/dist/auth/middleware.js +14 -0
- package/dist/chat/agentic-executor.d.ts +7 -0
- package/dist/chat/agentic-executor.js +253 -30
- package/dist/chat/conversations.d.ts +2 -0
- package/dist/chat/conversations.js +8 -0
- package/dist/chat/execution/executor.d.ts +8 -0
- package/dist/chat/execution/executor.js +157 -103
- package/dist/chat/execution/index.js +5 -1
- package/dist/chat/execution/tool-router.js +40 -14
- package/dist/chat/execution/tools/canvas.d.ts +2 -2
- package/dist/chat/execution/tools/code-tools.d.ts +66 -0
- package/dist/chat/execution/tools/code-tools.js +353 -0
- package/dist/chat/execution/tools/complete-task.d.ts +2 -2
- package/dist/chat/execution/tools/cron-tool.d.ts +26 -10
- package/dist/chat/execution/tools/cron-tool.js +48 -3
- package/dist/chat/execution/tools/decompose-task.d.ts +58 -0
- package/dist/chat/execution/tools/decompose-task.js +166 -0
- package/dist/chat/execution/tools/exec.d.ts +5 -2
- package/dist/chat/execution/tools/exec.js +294 -155
- package/dist/chat/execution/tools/feed-tools.d.ts +77 -0
- package/dist/chat/execution/tools/feed-tools.js +142 -0
- package/dist/chat/execution/tools/file-ops.d.ts +1 -1
- package/dist/chat/execution/tools/github.d.ts +2 -2
- package/dist/chat/execution/tools/index.d.ts +14 -2
- package/dist/chat/execution/tools/index.js +86 -12
- package/dist/chat/execution/tools/integrations.d.ts +8 -8
- package/dist/chat/execution/tools/multi-patch.d.ts +53 -0
- package/dist/chat/execution/tools/multi-patch.js +373 -0
- package/dist/chat/execution/tools/openai-image-gen.d.ts +2 -2
- package/dist/chat/execution/tools/plan-tools.d.ts +80 -0
- package/dist/chat/execution/tools/plan-tools.js +134 -0
- package/dist/chat/execution/tools/profclaw-ops.d.ts +6 -6
- package/dist/chat/execution/tools/repl-tool.d.ts +46 -0
- package/dist/chat/execution/tools/repl-tool.js +207 -0
- package/dist/chat/execution/tools/sessions-send.d.ts +2 -2
- package/dist/chat/execution/tools/slack-actions.d.ts +2 -2
- package/dist/chat/execution/tools/system.d.ts +4 -4
- package/dist/chat/execution/tools/todo-tool.d.ts +72 -0
- package/dist/chat/execution/tools/todo-tool.js +152 -0
- package/dist/chat/execution/tools/tool-search.d.ts +50 -0
- package/dist/chat/execution/tools/tool-search.js +155 -0
- package/dist/chat/execution/types.d.ts +21 -14
- package/dist/chat/execution/workflows/executor.d.ts +11 -0
- package/dist/chat/execution/workflows/executor.js +99 -1
- package/dist/chat/execution/workflows/types.d.ts +35 -2
- package/dist/chat/format/index.d.ts +1 -0
- package/dist/chat/format/index.js +1 -0
- package/dist/chat/format/markdown-convert.d.ts +23 -0
- package/dist/chat/format/markdown-convert.js +149 -0
- package/dist/chat/index.d.ts +1 -1
- package/dist/chat/index.js +1 -1
- package/dist/chat/message-handler.js +58 -6
- package/dist/chat/proactive/index.d.ts +62 -0
- package/dist/chat/proactive/index.js +147 -0
- package/dist/chat/prompt-adapter.js +2 -0
- package/dist/chat/providers/irc/index.d.ts +2 -2
- package/dist/chat/providers/telegram/index.d.ts +2 -0
- package/dist/chat/providers/telegram/index.js +21 -2
- package/dist/chat/system-prompts.d.ts +3 -3
- package/dist/chat/system-prompts.js +116 -61
- package/dist/chat/tools.d.ts +6 -6
- package/dist/cli/commands/auth.js +64 -1
- package/dist/cli/commands/chat.js +1327 -138
- package/dist/cli/commands/doctor.js +222 -16
- package/dist/cli/commands/history.d.ts +3 -0
- package/dist/cli/commands/history.js +138 -0
- package/dist/cli/commands/import.d.ts +44 -0
- package/dist/cli/commands/import.js +526 -0
- package/dist/cli/commands/init.d.ts +13 -0
- package/dist/cli/commands/init.js +381 -0
- package/dist/cli/commands/onboard.js +210 -30
- package/dist/cli/commands/plan.d.ts +13 -0
- package/dist/cli/commands/plan.js +184 -0
- package/dist/cli/commands/serve.d.ts +7 -0
- package/dist/cli/commands/serve.js +77 -18
- package/dist/cli/commands/setup.js +84 -3
- package/dist/cli/commands/status.js +54 -0
- package/dist/cli/commands/tools.js +13 -13
- package/dist/cli/commands/tui.js +26 -1
- package/dist/cli/index.js +19 -0
- package/dist/cli/ink/App.d.ts +13 -0
- package/dist/cli/ink/App.js +6 -0
- package/dist/cli/ink/ChatApp.d.ts +60 -0
- package/dist/cli/ink/ChatApp.js +325 -0
- package/dist/cli/ink/DashboardApp.d.ts +3 -0
- package/dist/cli/ink/DashboardApp.js +143 -0
- package/dist/cli/ink/components/AgentStatus.d.ts +17 -0
- package/dist/cli/ink/components/AgentStatus.js +33 -0
- package/dist/cli/ink/components/ChannelList.d.ts +17 -0
- package/dist/cli/ink/components/ChannelList.js +33 -0
- package/dist/cli/ink/components/ConnectionStatus.d.ts +19 -0
- package/dist/cli/ink/components/ConnectionStatus.js +50 -0
- package/dist/cli/ink/components/CostBar.d.ts +19 -0
- package/dist/cli/ink/components/CostBar.js +35 -0
- package/dist/cli/ink/components/HookStatus.d.ts +15 -0
- package/dist/cli/ink/components/HookStatus.js +7 -0
- package/dist/cli/ink/components/PermissionPrompt.d.ts +21 -0
- package/dist/cli/ink/components/PermissionPrompt.js +51 -0
- package/dist/cli/ink/components/PlanView.d.ts +23 -0
- package/dist/cli/ink/components/PlanView.js +26 -0
- package/dist/cli/ink/components/ProviderList.d.ts +16 -0
- package/dist/cli/ink/components/ProviderList.js +16 -0
- package/dist/cli/ink/components/SessionHeader.d.ts +15 -0
- package/dist/cli/ink/components/SessionHeader.js +15 -0
- package/dist/cli/ink/components/StreamingMessage.d.ts +20 -0
- package/dist/cli/ink/components/StreamingMessage.js +27 -0
- package/dist/cli/ink/components/SuggestionBar.d.ts +15 -0
- package/dist/cli/ink/components/SuggestionBar.js +28 -0
- package/dist/cli/ink/components/SystemHealth.d.ts +20 -0
- package/dist/cli/ink/components/SystemHealth.js +39 -0
- package/dist/cli/ink/components/TaskList.d.ts +17 -0
- package/dist/cli/ink/components/TaskList.js +59 -0
- package/dist/cli/ink/components/ToolCall.d.ts +17 -0
- package/dist/cli/ink/components/ToolCall.js +43 -0
- package/dist/cli/ink/index.d.ts +23 -0
- package/dist/cli/ink/index.js +15 -0
- package/dist/cli/ink/keybindings.d.ts +35 -0
- package/dist/cli/ink/keybindings.js +105 -0
- package/dist/cli/ink/markdown-renderer.d.ts +47 -0
- package/dist/cli/ink/markdown-renderer.js +380 -0
- package/dist/cli/ink/output-styles.d.ts +39 -0
- package/dist/cli/ink/output-styles.js +119 -0
- package/dist/cli/interactive/auto-serve.d.ts +38 -0
- package/dist/cli/interactive/auto-serve.js +209 -0
- package/dist/cli/interactive/index.d.ts +25 -0
- package/dist/cli/interactive/index.js +23 -0
- package/dist/cli/interactive/picker.d.ts +28 -0
- package/dist/cli/interactive/picker.js +153 -0
- package/dist/cli/interactive/renderer.d.ts +26 -0
- package/dist/cli/interactive/renderer.js +555 -0
- package/dist/cli/interactive/repl.d.ts +14 -0
- package/dist/cli/interactive/repl.js +1516 -0
- package/dist/cli/interactive/stream-client.d.ts +59 -0
- package/dist/cli/interactive/stream-client.js +506 -0
- package/dist/cli/interactive/types.d.ts +121 -0
- package/dist/cli/interactive/types.js +10 -0
- package/dist/cli/utils/api.d.ts +9 -0
- package/dist/cli/utils/api.js +58 -2
- package/dist/cron/natural-language.d.ts +54 -0
- package/dist/cron/natural-language.js +569 -0
- package/dist/cron/scheduler.d.ts +34 -4
- package/dist/cron/scheduler.js +141 -9
- package/dist/cron/templates.d.ts +1 -1
- package/dist/cron/templates.js +219 -0
- package/dist/feeds/feed-engine.d.ts +149 -0
- package/dist/feeds/feed-engine.js +600 -0
- package/dist/feeds/index.d.ts +9 -0
- package/dist/feeds/index.js +8 -0
- package/dist/gateway/router.js +6 -1
- package/dist/hooks/built-in/audit-log.d.ts +11 -0
- package/dist/hooks/built-in/audit-log.js +84 -0
- package/dist/hooks/built-in/cost-warning.d.ts +10 -0
- package/dist/hooks/built-in/cost-warning.js +59 -0
- package/dist/hooks/built-in/dangerous-tool.d.ts +32 -0
- package/dist/hooks/built-in/dangerous-tool.js +80 -0
- package/dist/hooks/built-in/index.d.ts +14 -0
- package/dist/hooks/built-in/index.js +20 -0
- package/dist/hooks/index.d.ts +3 -0
- package/dist/hooks/index.js +6 -0
- package/dist/hooks/loader.d.ts +17 -0
- package/dist/hooks/loader.js +142 -0
- package/dist/hooks/registry.d.ts +62 -0
- package/dist/hooks/registry.js +127 -0
- package/dist/integrations/web-search.js +6 -3
- package/dist/memory/index.d.ts +1 -0
- package/dist/memory/index.js +2 -0
- package/dist/memory/memory-service.js +14 -13
- package/dist/memory/observational.d.ts +62 -0
- package/dist/memory/observational.js +396 -0
- package/dist/middleware/content-filter.d.ts +22 -0
- package/dist/middleware/content-filter.js +69 -0
- package/dist/middleware/request-validator.d.ts +159 -0
- package/dist/middleware/request-validator.js +93 -0
- package/dist/plugins/sdk.d.ts +22 -0
- package/dist/plugins/sdk.js +17 -1
- package/dist/projects/types.d.ts +10 -10
- package/dist/providers/ai-sdk.d.ts +11 -0
- package/dist/providers/ai-sdk.js +78 -19
- package/dist/providers/core/models.js +67 -1
- package/dist/providers/core/types.d.ts +3 -0
- package/dist/providers/index.d.ts +1 -0
- package/dist/providers/index.js +1 -0
- package/dist/providers/smart-router.d.ts +118 -0
- package/dist/providers/smart-router.js +685 -0
- package/dist/providers/types.d.ts +16 -16
- package/dist/routes/agents.js +50 -0
- package/dist/routes/auth.js +8 -1
- package/dist/routes/chat.js +259 -14
- package/dist/routes/costs.js +32 -0
- package/dist/routes/cron.js +80 -5
- package/dist/routes/feeds.d.ts +10 -0
- package/dist/routes/feeds.js +168 -0
- package/dist/routes/mcp.js +21 -0
- package/dist/routes/memory.js +130 -0
- package/dist/routes/skills.js +129 -0
- package/dist/routes/teams.d.ts +9 -0
- package/dist/routes/teams.js +210 -0
- package/dist/routes/tools.js +14 -0
- package/dist/routes/users.js +2 -0
- package/dist/server/route-loader.js +3 -1
- package/dist/server/stream-bridge.d.ts +19 -0
- package/dist/server/stream-bridge.js +38 -0
- package/dist/server.js +310 -36
- package/dist/skills/marketplace.d.ts +87 -0
- package/dist/skills/marketplace.js +268 -0
- package/dist/storage/index.d.ts +14 -0
- package/dist/storage/index.js +64 -3
- package/dist/storage/schema.d.ts +4 -4
- package/dist/storage/schema.js +1 -1
- package/dist/teams/index.d.ts +110 -0
- package/dist/teams/index.js +365 -0
- package/dist/tickets/types.d.ts +39 -39
- package/dist/types/summary.d.ts +5 -5
- package/dist/utils/auto-update.d.ts +23 -0
- package/dist/utils/auto-update.js +157 -0
- package/dist/utils/offline-detect.d.ts +55 -0
- package/dist/utils/offline-detect.js +121 -0
- package/dist/utils/pid-file.d.ts +36 -0
- package/dist/utils/pid-file.js +125 -0
- package/dist/utils/prevent-sleep.d.ts +27 -0
- package/dist/utils/prevent-sleep.js +95 -0
- package/dist/utils/sanitizer.d.ts +57 -0
- package/dist/utils/sanitizer.js +174 -0
- package/docker-compose.yml +13 -137
- package/package.json +31 -6
- package/skills/deploy-cloudflare/SKILL.md +62 -0
- package/skills/deploy-vercel/SKILL.md +67 -0
- package/skills/docker-deploy/SKILL.md +158 -0
- package/skills/full-stack-builder/SKILL.md +92 -0
- package/skills/humanizer/SKILL.md +488 -0
- package/skills/profclaw-assistant/SKILL.md +85 -56
- package/ui/dist/assets/ActivityView-Da6HQ8ws.js +1 -0
- package/ui/dist/assets/AgentList-H58R0kX3.js +1 -0
- package/ui/dist/assets/AnalyticsDashboard-B28AeRsc.js +1 -0
- package/ui/dist/assets/AreaChart-DwLzeAQ8.js +1 -0
- package/ui/dist/assets/CartesianChart-piKQsTAR.js +36 -0
- package/ui/dist/assets/ChatView-Dbj8rxU5.js +10 -0
- package/ui/dist/assets/CostsDashboard-C6F5df0F.js +1 -0
- package/ui/dist/assets/EstimateSelect-wga0rUKX.js +1 -0
- package/ui/dist/assets/FeedDashboard-Ef6FEzEf.js +1 -0
- package/ui/dist/assets/FloatingChatbot-0yhkUeCq.js +2 -0
- package/ui/dist/assets/InviteCodeManagement-tUXfx4Bc.js +2 -0
- package/ui/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/ui/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/ui/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/ui/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/ui/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/ui/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/ui/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/ui/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/ui/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/ui/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/ui/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/ui/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/ui/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/ui/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/ui/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/ui/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/ui/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/ui/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/ui/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/ui/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/ui/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/ui/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/ui/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/ui/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/ui/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/ui/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/ui/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/ui/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/ui/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/ui/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/ui/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/ui/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/ui/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/ui/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/ui/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/ui/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/ui/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/ui/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/ui/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/ui/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/ui/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/ui/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/ui/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/ui/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/ui/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/ui/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/ui/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/ui/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/ui/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/ui/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/ui/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/ui/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/ui/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/ui/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/ui/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/ui/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/ui/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/ui/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/ui/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/ui/dist/assets/MarketplacePage-DGrLtolR.js +1 -0
- package/ui/dist/assets/{ProjectDetail--gPfvATB.js → ProjectDetail-BgWvA6UP.js} +2 -2
- package/ui/dist/assets/ProjectIcon-TDuliqwg.js +1 -0
- package/ui/dist/assets/ProjectList-DXgM4187.js +1 -0
- package/ui/dist/assets/{Settings-1kaf7VHE.js → Settings-xA8Zhi3p.js} +3 -3
- package/ui/dist/assets/StatusIndicator-B8vfFAxg.js +1 -0
- package/ui/dist/assets/{SummaryDetail-DV3AmONk.js → SummaryDetail-eedRfTtF.js} +2 -2
- package/ui/dist/assets/SummaryList-CLNX2P84.js +1 -0
- package/ui/dist/assets/TaskDetail-BtWclNiR.js +1 -0
- package/ui/dist/assets/TaskList-JjODAgSc.js +1 -0
- package/ui/dist/assets/TeamsPage-CBK5iAyx.js +1 -0
- package/ui/dist/assets/TicketBoard-C9aJNNYp.js +1 -0
- package/ui/dist/assets/TicketDetail-CV5JmL31.js +1 -0
- package/ui/dist/assets/TicketList-68V4lzUz.js +1 -0
- package/ui/dist/assets/UserManagement-DKTb6UpE.js +2 -0
- package/ui/dist/assets/ViewSwitcher-BvdQht2Y.js +1 -0
- package/ui/dist/assets/alert-dialog-O6qBOv_f.js +1 -0
- package/ui/dist/assets/{archive-BsLa7brF.js → archive-CX7UvZpf.js} +1 -1
- package/ui/dist/assets/badge-BbfnwDJP.js +1 -0
- package/ui/dist/assets/{bug-CmZI7jqM.js → bug-3WX0394-.js} +1 -1
- package/ui/dist/assets/{calendar-Bf0t2B82.js → calendar-buq77TaS.js} +1 -1
- package/ui/dist/assets/{chevron-up-CoUsOvZj.js → chevron-up-4ymsBJRo.js} +1 -1
- package/ui/dist/assets/{circle-x-Dgw11LgT.js → circle-x-ByXtwR6Y.js} +1 -1
- package/ui/dist/assets/constants-CVdDh7p0.js +1 -0
- package/ui/dist/assets/{cpu-DiPMQICV.js → cpu-CiuoCeDY.js} +1 -1
- package/ui/dist/assets/{database-BwTfupDi.js → database-BWXSBg5a.js} +1 -1
- package/ui/dist/assets/dialog-lDg4NvMV.js +1 -0
- package/ui/dist/assets/dollar-sign-Dmijd6DP.js +1 -0
- package/ui/dist/assets/{ellipsis-UJx4Xnf6.js → ellipsis-vT-5SVIp.js} +1 -1
- package/ui/dist/assets/{ellipsis-vertical-DoULYEKn.js → ellipsis-vertical-B4tAX_bL.js} +1 -1
- package/ui/dist/assets/feature-markdown-DBo5Q7uq.js +289 -0
- package/ui/dist/assets/{file-code-B2UrJLwi.js → file-code-ChOWdKXf.js} +1 -1
- package/ui/dist/assets/{folder-open-BJi577JH.js → folder-open-CkOUbIiP.js} +1 -1
- package/ui/dist/assets/{funnel-PkaD_HSR.js → funnel-uZkoSPP-.js} +1 -1
- package/ui/dist/assets/{git-branch-C_K30am0.js → git-branch-CwvUxj7C.js} +1 -1
- package/ui/dist/assets/{git-commit-horizontal-IT6ecxWQ.js → git-commit-horizontal-lxPzCvhD.js} +1 -1
- package/ui/dist/assets/{globe-f-jjCa_g.js → globe-D_WpJdsj.js} +1 -1
- package/ui/dist/assets/{hash-DNEv8Elo.js → hash-CA0hJ7vh.js} +1 -1
- package/ui/dist/assets/{history-BQSBo_1Q.js → history-CEWMLELs.js} +1 -1
- package/ui/dist/assets/inbox-ehtOptTV.js +1 -0
- package/ui/dist/assets/{index-nS3EErDg.js → index-C1rhbml4.js} +3 -3
- package/ui/dist/assets/index-CR8gfCHv.js +1 -0
- package/ui/dist/assets/index-DVXMpWai.js +1 -0
- package/ui/dist/assets/index-DaDJMM2v.js +1 -0
- package/ui/dist/assets/index-Djdq_0xu.js +1 -0
- package/ui/dist/assets/index-M46Gcb8K.css +1 -0
- package/ui/dist/assets/index-sEBN7w8J.js +109 -0
- package/ui/dist/assets/layers-C6Mhf8Nk.js +1 -0
- package/ui/dist/assets/{layout-dashboard-CcKt1hXw.js → layout-dashboard-BRUM0kpT.js} +1 -1
- package/ui/dist/assets/{lightbulb-Jhp7FvbU.js → lightbulb-CN2q1O5l.js} +1 -1
- package/ui/dist/assets/{link-2-DdO7ostF.js → link-2-CC4E28yJ.js} +1 -1
- package/ui/dist/assets/markdown-renderer-C6ENbB0u.js +10 -0
- package/ui/dist/assets/markdown-renderer-CfVKi3_s.css +1 -0
- package/ui/dist/assets/{message-square-CBgH65X7.js → message-square-Bbb241am.js} +1 -1
- package/ui/dist/assets/package-CwT3IzFx.js +1 -0
- package/ui/dist/assets/{pencil-CYXCEPQh.js → pencil-CDmiHzoj.js} +1 -1
- package/ui/dist/assets/{play-d1ONaH0-.js → play-tjy5jMQX.js} +1 -1
- package/ui/dist/assets/popover-CFHZf3pE.js +1 -0
- package/ui/dist/assets/progress-BboUHuLf.js +1 -0
- package/ui/dist/assets/{rocket-DhvUetsk.js → rocket--8ZHgdXJ.js} +1 -1
- package/ui/dist/assets/select-n108rXny.js +1 -0
- package/ui/dist/assets/{send-P0r5D7td.js → send-Cu7MR2Qh.js} +1 -1
- package/ui/dist/assets/server-DmodxZp-.js +1 -0
- package/ui/dist/assets/{settings-2-ocj5uHGx.js → settings-2-CkRNqkxj.js} +1 -1
- package/ui/dist/assets/sheet-7aUJkyzU.js +1 -0
- package/ui/dist/assets/{shield-check-DfVdE34l.js → shield-check-ViI3nMKE.js} +1 -1
- package/ui/dist/assets/{shield-off-8l0J6R-8.js → shield-off-DN3tagkn.js} +1 -1
- package/ui/dist/assets/skeleton-C0E1Nfro.js +1 -0
- package/ui/dist/assets/{smartphone-77_g3SQn.js → smartphone-5FghiXpA.js} +1 -1
- package/ui/dist/assets/{square-Ckzsr3Rn.js → square-DiNSx8K7.js} +1 -1
- package/ui/dist/assets/star-DhdFVidS.js +1 -0
- package/ui/dist/assets/switch-CIgVbZqQ.js +1 -0
- package/ui/dist/assets/table-BC56FVMV.js +1 -0
- package/ui/dist/assets/{tag-C35mgoEv.js → tag-CH_vQliw.js} +1 -1
- package/ui/dist/assets/textarea-CxDiKFXZ.js +1 -0
- package/ui/dist/assets/{timer-DHJBS2w1.js → timer-CdGVTb60.js} +1 -1
- package/ui/dist/assets/tooltip-BuW9A5UO.js +1 -0
- package/ui/dist/assets/{trash-2-keO5tKPE.js → trash-2-CucZJL0N.js} +1 -1
- package/ui/dist/assets/trending-down-BCezC1z2.js +1 -0
- package/ui/dist/assets/{trending-up-BBv8KbrY.js → trending-up-C5OYX2k2.js} +1 -1
- package/ui/dist/assets/vendor-query-C0p7b53x.js +4 -0
- package/ui/dist/assets/vendor-react-Mc5_kfYG.js +12 -0
- package/ui/dist/assets/vendor-ui-CXPAUBFv.js +51 -0
- package/ui/dist/assets/{wand-sparkles-BusQ52ZK.js → wand-sparkles-BaH6pJnc.js} +1 -1
- package/ui/dist/assets/{wrench-02YaOSiK.js → wrench-CjxjP6ok.js} +1 -1
- package/ui/dist/index.html +5 -2
- package/ui/dist/assets/ActivityView-BeshZXmE.js +0 -1
- package/ui/dist/assets/AgentList-QVHeldPi.js +0 -1
- package/ui/dist/assets/AnalyticsDashboard-C9R97dXE.js +0 -1
- package/ui/dist/assets/AreaChart-B73gaiVQ.js +0 -1
- package/ui/dist/assets/CartesianChart-CXYL2R-A.js +0 -36
- package/ui/dist/assets/ChatView-qjZErlrp.js +0 -10
- package/ui/dist/assets/CostsDashboard-O4DCLy1i.js +0 -1
- package/ui/dist/assets/EstimateSelect-1hQf4Osk.js +0 -1
- package/ui/dist/assets/FloatingChatbot-BZBzrJxd.js +0 -2
- package/ui/dist/assets/InviteCodeManagement-LEoGqYeA.js +0 -2
- package/ui/dist/assets/ProjectIcon-Chd_ofsw.js +0 -1
- package/ui/dist/assets/ProjectList-BvUfVB6c.js +0 -4
- package/ui/dist/assets/StatusIndicator-pRfkNcdV.js +0 -1
- package/ui/dist/assets/SummaryList-B6kXiTxL.js +0 -1
- package/ui/dist/assets/TaskDetail-CV6CzbOS.js +0 -1
- package/ui/dist/assets/TaskList-DL7brQKd.js +0 -1
- package/ui/dist/assets/TicketBoard-CrkkFj7C.js +0 -1
- package/ui/dist/assets/TicketDetail-hnnYclpD.js +0 -1
- package/ui/dist/assets/TicketList-CsDAyQCL.js +0 -1
- package/ui/dist/assets/UserManagement-BGFQyTSr.js +0 -2
- package/ui/dist/assets/ViewSwitcher-CnL0OqA8.js +0 -1
- package/ui/dist/assets/alert-dialog-SKM6Gjz4.js +0 -7
- package/ui/dist/assets/badge-CVHH1a-v.js +0 -1
- package/ui/dist/assets/brain-6o_RHVpe.js +0 -1
- package/ui/dist/assets/constants-Q5Zivl-a.js +0 -1
- package/ui/dist/assets/dialog-BUqN9Psj.js +0 -1
- package/ui/dist/assets/index-BUmnwPQr.js +0 -1
- package/ui/dist/assets/index-BfF5G4Cw.js +0 -1
- package/ui/dist/assets/index-C8N375c2.js +0 -1
- package/ui/dist/assets/index-CuxnqVVz.css +0 -1
- package/ui/dist/assets/index-CzMM3QOk.js +0 -1
- package/ui/dist/assets/index-lJlNPFVJ.js +0 -67
- package/ui/dist/assets/layers-jFqVrWMD.js +0 -1
- package/ui/dist/assets/markdown-renderer-n1ToC4XQ.js +0 -36
- package/ui/dist/assets/popover-CrmZgoWu.js +0 -1
- package/ui/dist/assets/progress-uv4SPkL5.js +0 -1
- package/ui/dist/assets/select-CeQ8AHGy.js +0 -1
- package/ui/dist/assets/server-CVHTZB72.js +0 -1
- package/ui/dist/assets/sheet-DuQExSho.js +0 -1
- package/ui/dist/assets/skeleton-TFiFJ3nB.js +0 -1
- package/ui/dist/assets/switch-CAePkd6k.js +0 -1
- package/ui/dist/assets/table-ZjAZ7hkt.js +0 -1
- package/ui/dist/assets/textarea-DsVPnSgR.js +0 -1
- package/ui/dist/assets/tooltip-BYmQ331W.js +0 -1
|
@@ -110,7 +110,8 @@ async function searchSerper(query, apiKey, options = {}) {
|
|
|
110
110
|
*/
|
|
111
111
|
async function searchSearxng(query, baseUrl, options = {}) {
|
|
112
112
|
const headers = {
|
|
113
|
-
Accept: 'application/json',
|
|
113
|
+
'Accept': 'application/json',
|
|
114
|
+
'User-Agent': 'profClaw/2.0 (search integration)',
|
|
114
115
|
};
|
|
115
116
|
if (options.apiKey) {
|
|
116
117
|
headers['Authorization'] = `Bearer ${options.apiKey}`;
|
|
@@ -264,9 +265,11 @@ export function isWebSearchAvailable(config) {
|
|
|
264
265
|
* Get default config from environment variables
|
|
265
266
|
*/
|
|
266
267
|
export function getDefaultWebSearchConfig() {
|
|
267
|
-
// Auto-detect provider
|
|
268
|
+
// Auto-detect provider: prefer paid providers over public SearXNG
|
|
268
269
|
let provider = 'brave';
|
|
269
|
-
if (process.env.
|
|
270
|
+
if (process.env.BRAVE_API_KEY)
|
|
271
|
+
provider = 'brave';
|
|
272
|
+
else if (process.env.SERPER_API_KEY)
|
|
270
273
|
provider = 'serper';
|
|
271
274
|
else if (process.env.TAVILY_API_KEY)
|
|
272
275
|
provider = 'tavily';
|
package/dist/memory/index.d.ts
CHANGED
|
@@ -5,4 +5,5 @@
|
|
|
5
5
|
*/
|
|
6
6
|
export { DEFAULT_MEMORY_CONFIG, type MemoryConfig, type MemoryFile, type MemoryChunk, type SearchResult, type MemoryStats, type MemorySession, initMemoryTables, syncMemoryFiles, searchMemory, getMemoryContent, chunkText, estimateTokens, hashContent, getMemoryStats, listMemoryFiles, listFileChunks, deleteMemoryChunk, deleteMemoryFile, clearAllMemories, createMemorySession, listMemorySessions, updateSessionStats, archiveSession, } from './memory-service.js';
|
|
7
7
|
export { MemoryWatcher, getMemoryWatcher, initMemoryWatcher, stopMemoryWatcher, type MemoryWatcherOptions, type MemoryWatcherState, } from './memory-watcher.js';
|
|
8
|
+
export { observeConversation, observeWindow, compressToDigest, type ConversationSnapshot, type Observation, type ObservationResult, type ObservationType, } from './observational.js';
|
|
8
9
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/memory/index.js
CHANGED
|
@@ -22,4 +22,6 @@ listMemoryFiles, listFileChunks, deleteMemoryChunk, deleteMemoryFile, clearAllMe
|
|
|
22
22
|
createMemorySession, listMemorySessions, updateSessionStats, archiveSession, } from './memory-service.js';
|
|
23
23
|
// Memory Watcher (automatic sync)
|
|
24
24
|
export { MemoryWatcher, getMemoryWatcher, initMemoryWatcher, stopMemoryWatcher, } from './memory-watcher.js';
|
|
25
|
+
// Observational Memory
|
|
26
|
+
export { observeConversation, observeWindow, compressToDigest, } from './observational.js';
|
|
25
27
|
//# sourceMappingURL=index.js.map
|
|
@@ -354,8 +354,8 @@ async function indexFile(path, content, fileStat, source, config, embeddingServi
|
|
|
354
354
|
args: [chunk.text, chunkId, path, source, config.model, chunk.startLine, chunk.endLine],
|
|
355
355
|
});
|
|
356
356
|
}
|
|
357
|
-
catch {
|
|
358
|
-
|
|
357
|
+
catch (err) {
|
|
358
|
+
logger.warn('[Memory] FTS5 insert failed (indexFile), text search may be degraded:', { error: err instanceof Error ? err.message : String(err) });
|
|
359
359
|
}
|
|
360
360
|
}
|
|
361
361
|
}
|
|
@@ -408,8 +408,8 @@ async function reindexFile(path, content, fileStat, source, config, embeddingSer
|
|
|
408
408
|
args: [chunk.text, chunkId, path, source, config.model, chunk.startLine, chunk.endLine],
|
|
409
409
|
});
|
|
410
410
|
}
|
|
411
|
-
catch {
|
|
412
|
-
|
|
411
|
+
catch (err) {
|
|
412
|
+
logger.warn('[Memory] FTS5 insert failed (reindexFile), text search may be degraded:', { error: err instanceof Error ? err.message : String(err) });
|
|
413
413
|
}
|
|
414
414
|
}
|
|
415
415
|
}
|
|
@@ -431,8 +431,8 @@ async function removeFileChunks(path) {
|
|
|
431
431
|
args: [chunk.id],
|
|
432
432
|
});
|
|
433
433
|
}
|
|
434
|
-
catch {
|
|
435
|
-
|
|
434
|
+
catch (err) {
|
|
435
|
+
logger.warn('[Memory] FTS5 delete failed (removeFileChunks):', { error: err instanceof Error ? err.message : String(err) });
|
|
436
436
|
}
|
|
437
437
|
}
|
|
438
438
|
// Delete chunks
|
|
@@ -591,7 +591,8 @@ async function ftsSearch(query, limit) {
|
|
|
591
591
|
}
|
|
592
592
|
return chunks;
|
|
593
593
|
}
|
|
594
|
-
catch {
|
|
594
|
+
catch (err) {
|
|
595
|
+
logger.warn('[Memory] FTS5 search unavailable, falling back to LIKE search:', { error: err instanceof Error ? err.message : String(err) });
|
|
595
596
|
// FTS5 not available, fall back to LIKE search
|
|
596
597
|
const result = await client.execute({
|
|
597
598
|
sql: `SELECT id, path, source, start_line, end_line, hash, text, model
|
|
@@ -732,8 +733,8 @@ export async function deleteMemoryChunk(chunkId) {
|
|
|
732
733
|
args: [chunkId],
|
|
733
734
|
});
|
|
734
735
|
}
|
|
735
|
-
catch {
|
|
736
|
-
|
|
736
|
+
catch (err) {
|
|
737
|
+
logger.warn('[Memory] FTS5 delete failed (deleteMemoryChunk):', { error: err instanceof Error ? err.message : String(err) });
|
|
737
738
|
}
|
|
738
739
|
// Delete from chunks
|
|
739
740
|
const result = await client.execute({
|
|
@@ -762,8 +763,8 @@ export async function clearAllMemories() {
|
|
|
762
763
|
try {
|
|
763
764
|
await client.execute(`DELETE FROM memory_fts`);
|
|
764
765
|
}
|
|
765
|
-
catch {
|
|
766
|
-
|
|
766
|
+
catch (err) {
|
|
767
|
+
logger.warn('[Memory] FTS5 clear failed (clearAllMemories):', { error: err instanceof Error ? err.message : String(err) });
|
|
767
768
|
}
|
|
768
769
|
await client.execute(`DELETE FROM memory_chunks`);
|
|
769
770
|
await client.execute(`DELETE FROM memory_files`);
|
|
@@ -998,8 +999,8 @@ export async function indexConversation(input, config = DEFAULT_MEMORY_CONFIG) {
|
|
|
998
999
|
args: [chunk.text, chunkId, path, source, config.model, chunk.startLine, chunk.endLine],
|
|
999
1000
|
});
|
|
1000
1001
|
}
|
|
1001
|
-
catch {
|
|
1002
|
-
|
|
1002
|
+
catch (err) {
|
|
1003
|
+
logger.warn('[Memory] FTS5 insert failed (indexConversation), text search may be degraded:', { error: err instanceof Error ? err.message : String(err) });
|
|
1003
1004
|
}
|
|
1004
1005
|
}
|
|
1005
1006
|
logger.info(`[Memory] Indexed conversation ${input.conversationId}: ${chunks.length} chunks, ~${tokensEstimate} tokens`);
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Observational Memory Engine
|
|
3
|
+
*
|
|
4
|
+
* Background agent that compresses conversation history into structured
|
|
5
|
+
* observations. Instead of storing raw messages, it extracts:
|
|
6
|
+
* - Decisions made and their rationale
|
|
7
|
+
* - User preferences (language, framework, style, tools)
|
|
8
|
+
* - Problems solved and solution patterns
|
|
9
|
+
* - Project facts and context
|
|
10
|
+
*
|
|
11
|
+
* This is the "profClaw never forgets AND learns" story.
|
|
12
|
+
* Based on VentureBeat's observational memory pattern (10x cheaper than RAG).
|
|
13
|
+
*/
|
|
14
|
+
export type ObservationType = 'decision' | 'preference' | 'solution' | 'fact' | 'error_pattern' | 'workflow';
|
|
15
|
+
export interface Observation {
|
|
16
|
+
id: string;
|
|
17
|
+
type: ObservationType;
|
|
18
|
+
summary: string;
|
|
19
|
+
details: Record<string, unknown>;
|
|
20
|
+
confidence: number;
|
|
21
|
+
source: {
|
|
22
|
+
conversationId: string;
|
|
23
|
+
messageRange: [number, number];
|
|
24
|
+
timestamp: number;
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
export interface ConversationSnapshot {
|
|
28
|
+
conversationId: string;
|
|
29
|
+
messages: Array<{
|
|
30
|
+
role: 'user' | 'assistant' | 'system';
|
|
31
|
+
content: string;
|
|
32
|
+
model?: string;
|
|
33
|
+
toolsUsed?: string[];
|
|
34
|
+
}>;
|
|
35
|
+
metadata?: {
|
|
36
|
+
provider?: string;
|
|
37
|
+
channel?: string;
|
|
38
|
+
userId?: string;
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
export interface ObservationResult {
|
|
42
|
+
observations: Observation[];
|
|
43
|
+
preferencesTracked: number;
|
|
44
|
+
experiencesRecorded: number;
|
|
45
|
+
processingTimeMs: number;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Process a conversation snapshot and extract observations.
|
|
49
|
+
* Call this after a conversation ends or at periodic intervals.
|
|
50
|
+
*/
|
|
51
|
+
export declare function observeConversation(snapshot: ConversationSnapshot): Promise<ObservationResult>;
|
|
52
|
+
/**
|
|
53
|
+
* Process a batch of recent messages (sliding window).
|
|
54
|
+
* Use this for periodic observation without waiting for conversation end.
|
|
55
|
+
*/
|
|
56
|
+
export declare function observeWindow(conversationId: string, messages: ConversationSnapshot['messages'], windowSize?: number, userId?: string): Promise<ObservationResult>;
|
|
57
|
+
/**
|
|
58
|
+
* Build a compressed summary of a conversation for long-term storage.
|
|
59
|
+
* Replaces raw message history with a structured digest.
|
|
60
|
+
*/
|
|
61
|
+
export declare function compressToDigest(messages: ConversationSnapshot['messages'], observations: Observation[]): string;
|
|
62
|
+
//# sourceMappingURL=observational.d.ts.map
|
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Observational Memory Engine
|
|
3
|
+
*
|
|
4
|
+
* Background agent that compresses conversation history into structured
|
|
5
|
+
* observations. Instead of storing raw messages, it extracts:
|
|
6
|
+
* - Decisions made and their rationale
|
|
7
|
+
* - User preferences (language, framework, style, tools)
|
|
8
|
+
* - Problems solved and solution patterns
|
|
9
|
+
* - Project facts and context
|
|
10
|
+
*
|
|
11
|
+
* This is the "profClaw never forgets AND learns" story.
|
|
12
|
+
* Based on VentureBeat's observational memory pattern (10x cheaper than RAG).
|
|
13
|
+
*/
|
|
14
|
+
import { randomUUID } from 'node:crypto';
|
|
15
|
+
import { createContextualLogger } from '../utils/logger.js';
|
|
16
|
+
import { recordExperience, trackPreference, findSimilarExperiences, } from './experience-store.js';
|
|
17
|
+
const log = createContextualLogger('ObservationalMemory');
|
|
18
|
+
/**
|
|
19
|
+
* Extract decisions from conversation (user chose X over Y, agreed to approach).
|
|
20
|
+
*/
|
|
21
|
+
const decisionExtractor = {
|
|
22
|
+
type: 'decision',
|
|
23
|
+
extract: (messages, ctx) => {
|
|
24
|
+
const observations = [];
|
|
25
|
+
const decisionPatterns = [
|
|
26
|
+
/\b(?:let's go with|decided to|choosing|we'll use|going with|picked|selected)\b\s+(.{10,80})/i,
|
|
27
|
+
/\b(?:yes|agreed|correct|that's right|exactly|perfect)\b.*\b(?:use|go with|pick|choose)\b\s+(.{5,60})/i,
|
|
28
|
+
/\b(?:instead of|rather than|not|don't)\b\s+(.{5,60})\b(?:use|go with|pick)\b\s+(.{5,60})/i,
|
|
29
|
+
];
|
|
30
|
+
for (let i = 0; i < messages.length; i++) {
|
|
31
|
+
const msg = messages[i];
|
|
32
|
+
if (msg.role !== 'user' && msg.role !== 'assistant')
|
|
33
|
+
continue;
|
|
34
|
+
for (const pattern of decisionPatterns) {
|
|
35
|
+
const match = msg.content.match(pattern);
|
|
36
|
+
if (match) {
|
|
37
|
+
observations.push({
|
|
38
|
+
id: randomUUID(),
|
|
39
|
+
type: 'decision',
|
|
40
|
+
summary: `Decision: ${match[0].slice(0, 120)}`,
|
|
41
|
+
details: { matchedText: match[0], role: msg.role },
|
|
42
|
+
confidence: 0.7,
|
|
43
|
+
source: {
|
|
44
|
+
conversationId: ctx.conversationId,
|
|
45
|
+
messageRange: [ctx.startIdx + i, ctx.startIdx + i],
|
|
46
|
+
timestamp: Date.now(),
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
break; // one per message
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return observations;
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* Extract user preferences (language, framework, tool, style choices).
|
|
58
|
+
*/
|
|
59
|
+
const preferenceExtractor = {
|
|
60
|
+
type: 'preference',
|
|
61
|
+
extract: (messages, ctx) => {
|
|
62
|
+
const observations = [];
|
|
63
|
+
const prefPatterns = [
|
|
64
|
+
{ pattern: /\b(?:I (?:use|prefer|like|want|always use|work with))\s+(\w[\w\s.-]{2,30})/i, category: 'tool' },
|
|
65
|
+
{ pattern: /\b(?:write (?:it )?in|using|with)\s+(TypeScript|JavaScript|Python|Go|Rust|Java|C\+\+|Ruby|PHP|Swift|Kotlin)/i, category: 'language' },
|
|
66
|
+
{ pattern: /\b(?:use|using|with)\s+(React|Vue|Angular|Svelte|Next\.?js|Nuxt|Express|Hono|FastAPI|Django|Spring|Rails)/i, category: 'framework' },
|
|
67
|
+
{ pattern: /\b(?:style|format|convention).*\b(camelCase|snake_case|PascalCase|kebab-case)/i, category: 'style' },
|
|
68
|
+
{ pattern: /\b(?:don't|never|avoid|skip|no)\s+(\w[\w\s]{2,30})/i, category: 'anti-preference' },
|
|
69
|
+
];
|
|
70
|
+
for (const msg of messages) {
|
|
71
|
+
if (msg.role !== 'user')
|
|
72
|
+
continue;
|
|
73
|
+
for (const { pattern, category } of prefPatterns) {
|
|
74
|
+
const match = msg.content.match(pattern);
|
|
75
|
+
if (match && match[1]) {
|
|
76
|
+
observations.push({
|
|
77
|
+
id: randomUUID(),
|
|
78
|
+
type: 'preference',
|
|
79
|
+
summary: `Preference (${category}): ${match[1].trim()}`,
|
|
80
|
+
details: { category, value: match[1].trim(), fullMatch: match[0] },
|
|
81
|
+
confidence: 0.6,
|
|
82
|
+
source: {
|
|
83
|
+
conversationId: ctx.conversationId,
|
|
84
|
+
messageRange: [ctx.startIdx, ctx.startIdx + messages.length - 1],
|
|
85
|
+
timestamp: Date.now(),
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return observations;
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
/**
|
|
95
|
+
* Extract solution patterns (problem -> tool chain -> outcome).
|
|
96
|
+
*/
|
|
97
|
+
const solutionExtractor = {
|
|
98
|
+
type: 'solution',
|
|
99
|
+
extract: (messages, ctx) => {
|
|
100
|
+
const observations = [];
|
|
101
|
+
// Find messages with tool usage followed by success indicators
|
|
102
|
+
const toolMessages = messages.filter(m => m.toolsUsed && m.toolsUsed.length > 0);
|
|
103
|
+
if (toolMessages.length === 0)
|
|
104
|
+
return observations;
|
|
105
|
+
// Look for the user's original request
|
|
106
|
+
const firstUserMsg = messages.find(m => m.role === 'user');
|
|
107
|
+
if (!firstUserMsg)
|
|
108
|
+
return observations;
|
|
109
|
+
// Check if the conversation ended positively
|
|
110
|
+
const lastMessages = messages.slice(-3);
|
|
111
|
+
const endedPositively = lastMessages.some(m => m.role === 'user' && /\b(thanks|perfect|great|works|done|good|nice)\b/i.test(m.content)) || lastMessages.some(m => m.role === 'assistant' && /\b(completed|done|finished|success)\b/i.test(m.content));
|
|
112
|
+
if (endedPositively && toolMessages.length > 0) {
|
|
113
|
+
const allTools = [...new Set(toolMessages.flatMap(m => m.toolsUsed ?? []))];
|
|
114
|
+
observations.push({
|
|
115
|
+
id: randomUUID(),
|
|
116
|
+
type: 'solution',
|
|
117
|
+
summary: `Solved "${firstUserMsg.content.slice(0, 80)}" using ${allTools.join(' -> ')}`,
|
|
118
|
+
details: {
|
|
119
|
+
problem: firstUserMsg.content.slice(0, 200),
|
|
120
|
+
tools: allTools,
|
|
121
|
+
messageCount: messages.length,
|
|
122
|
+
},
|
|
123
|
+
confidence: endedPositively ? 0.85 : 0.5,
|
|
124
|
+
source: {
|
|
125
|
+
conversationId: ctx.conversationId,
|
|
126
|
+
messageRange: [ctx.startIdx, ctx.startIdx + messages.length - 1],
|
|
127
|
+
timestamp: Date.now(),
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
return observations;
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
/**
|
|
135
|
+
* Extract project facts (file paths, architecture, naming conventions).
|
|
136
|
+
*/
|
|
137
|
+
const factExtractor = {
|
|
138
|
+
type: 'fact',
|
|
139
|
+
extract: (messages, ctx) => {
|
|
140
|
+
const observations = [];
|
|
141
|
+
const factPatterns = [
|
|
142
|
+
{ pattern: /\b(?:the (?:project|repo|codebase) (?:uses|has|is|runs on))\s+(.{10,100})/i, category: 'architecture' },
|
|
143
|
+
{ pattern: /\b(?:deployed (?:on|to|at|via))\s+(\S+)/i, category: 'deployment' },
|
|
144
|
+
{ pattern: /\b(?:database|db) (?:is|uses)\s+(\w+)/i, category: 'database' },
|
|
145
|
+
{ pattern: /\b(?:CI|CD|pipeline) (?:is|uses|runs on)\s+(\w[\w\s.-]+)/i, category: 'ci' },
|
|
146
|
+
];
|
|
147
|
+
for (const msg of messages) {
|
|
148
|
+
if (msg.role !== 'user')
|
|
149
|
+
continue;
|
|
150
|
+
for (const { pattern, category } of factPatterns) {
|
|
151
|
+
const match = msg.content.match(pattern);
|
|
152
|
+
if (match) {
|
|
153
|
+
observations.push({
|
|
154
|
+
id: randomUUID(),
|
|
155
|
+
type: 'fact',
|
|
156
|
+
summary: `Project fact (${category}): ${match[0].slice(0, 100)}`,
|
|
157
|
+
details: { category, fact: match[0], value: match[1] },
|
|
158
|
+
confidence: 0.65,
|
|
159
|
+
source: {
|
|
160
|
+
conversationId: ctx.conversationId,
|
|
161
|
+
messageRange: [ctx.startIdx, ctx.startIdx + messages.length - 1],
|
|
162
|
+
timestamp: Date.now(),
|
|
163
|
+
},
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return observations;
|
|
169
|
+
},
|
|
170
|
+
};
|
|
171
|
+
/**
|
|
172
|
+
* Extract error recovery patterns (what went wrong, how it was fixed).
|
|
173
|
+
*/
|
|
174
|
+
const errorPatternExtractor = {
|
|
175
|
+
type: 'error_pattern',
|
|
176
|
+
extract: (messages, ctx) => {
|
|
177
|
+
const observations = [];
|
|
178
|
+
for (let i = 0; i < messages.length - 1; i++) {
|
|
179
|
+
const msg = messages[i];
|
|
180
|
+
const next = messages[i + 1];
|
|
181
|
+
// Look for error followed by fix
|
|
182
|
+
const isError = msg.role === 'assistant' &&
|
|
183
|
+
/\b(error|failed|exception|crash|bug|issue|problem)\b/i.test(msg.content);
|
|
184
|
+
const isFix = next && next.role === 'assistant' &&
|
|
185
|
+
/\b(fix|resolved|solved|workaround|updated|changed)\b/i.test(next.content);
|
|
186
|
+
if (isError && isFix) {
|
|
187
|
+
observations.push({
|
|
188
|
+
id: randomUUID(),
|
|
189
|
+
type: 'error_pattern',
|
|
190
|
+
summary: `Error recovery: ${msg.content.slice(0, 60)} -> ${next.content.slice(0, 60)}`,
|
|
191
|
+
details: {
|
|
192
|
+
error: msg.content.slice(0, 200),
|
|
193
|
+
fix: next.content.slice(0, 200),
|
|
194
|
+
toolsUsed: next.toolsUsed,
|
|
195
|
+
},
|
|
196
|
+
confidence: 0.75,
|
|
197
|
+
source: {
|
|
198
|
+
conversationId: ctx.conversationId,
|
|
199
|
+
messageRange: [ctx.startIdx + i, ctx.startIdx + i + 1],
|
|
200
|
+
timestamp: Date.now(),
|
|
201
|
+
},
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return observations;
|
|
206
|
+
},
|
|
207
|
+
};
|
|
208
|
+
const ALL_EXTRACTORS = [
|
|
209
|
+
decisionExtractor,
|
|
210
|
+
preferenceExtractor,
|
|
211
|
+
solutionExtractor,
|
|
212
|
+
factExtractor,
|
|
213
|
+
errorPatternExtractor,
|
|
214
|
+
];
|
|
215
|
+
// Core Engine
|
|
216
|
+
/**
|
|
217
|
+
* Process a conversation snapshot and extract observations.
|
|
218
|
+
* Call this after a conversation ends or at periodic intervals.
|
|
219
|
+
*/
|
|
220
|
+
export async function observeConversation(snapshot) {
|
|
221
|
+
const start = Date.now();
|
|
222
|
+
const allObservations = [];
|
|
223
|
+
let preferencesTracked = 0;
|
|
224
|
+
let experiencesRecorded = 0;
|
|
225
|
+
const ctx = {
|
|
226
|
+
conversationId: snapshot.conversationId,
|
|
227
|
+
userId: snapshot.metadata?.userId,
|
|
228
|
+
startIdx: 0,
|
|
229
|
+
};
|
|
230
|
+
// Run all extractors
|
|
231
|
+
for (const extractor of ALL_EXTRACTORS) {
|
|
232
|
+
try {
|
|
233
|
+
const observations = extractor.extract(snapshot.messages, ctx);
|
|
234
|
+
allObservations.push(...observations);
|
|
235
|
+
}
|
|
236
|
+
catch (error) {
|
|
237
|
+
log.warn(`Extractor ${extractor.type} failed`, {
|
|
238
|
+
error: error instanceof Error ? error.message : String(error),
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
// Deduplicate by summary similarity
|
|
243
|
+
const seen = new Set();
|
|
244
|
+
const unique = allObservations.filter(obs => {
|
|
245
|
+
const key = obs.summary.toLowerCase().slice(0, 50);
|
|
246
|
+
if (seen.has(key))
|
|
247
|
+
return false;
|
|
248
|
+
seen.add(key);
|
|
249
|
+
return true;
|
|
250
|
+
});
|
|
251
|
+
// Check for duplicates against existing experiences
|
|
252
|
+
const deduplicated = [];
|
|
253
|
+
for (const obs of unique) {
|
|
254
|
+
const existing = await findSimilarExperiences(obs.summary, [], 1);
|
|
255
|
+
if (existing.length > 0 && existing[0].successScore >= obs.confidence) {
|
|
256
|
+
continue; // already know this
|
|
257
|
+
}
|
|
258
|
+
deduplicated.push(obs);
|
|
259
|
+
}
|
|
260
|
+
// Persist observations
|
|
261
|
+
for (const obs of deduplicated) {
|
|
262
|
+
try {
|
|
263
|
+
// Map observation type to experience type
|
|
264
|
+
const expType = obs.type === 'preference' ? 'user_preference'
|
|
265
|
+
: obs.type === 'solution' || obs.type === 'workflow' ? 'task_solution'
|
|
266
|
+
: obs.type === 'error_pattern' ? 'error_recovery'
|
|
267
|
+
: 'task_solution';
|
|
268
|
+
await recordExperience({
|
|
269
|
+
type: expType,
|
|
270
|
+
intent: obs.summary,
|
|
271
|
+
solution: obs.details,
|
|
272
|
+
successScore: obs.confidence,
|
|
273
|
+
tags: [obs.type, ...(obs.details['category'] ? [String(obs.details['category'])] : [])],
|
|
274
|
+
sourceConversationId: obs.source.conversationId,
|
|
275
|
+
userId: ctx.userId,
|
|
276
|
+
});
|
|
277
|
+
experiencesRecorded++;
|
|
278
|
+
// Track preferences specifically
|
|
279
|
+
if (obs.type === 'preference' && ctx.userId && obs.details['category'] && obs.details['value']) {
|
|
280
|
+
await trackPreference(ctx.userId, String(obs.details['category']), String(obs.details['value']));
|
|
281
|
+
preferencesTracked++;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
catch (error) {
|
|
285
|
+
log.warn('Failed to persist observation', {
|
|
286
|
+
type: obs.type,
|
|
287
|
+
error: error instanceof Error ? error.message : String(error),
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
const result = {
|
|
292
|
+
observations: deduplicated,
|
|
293
|
+
preferencesTracked,
|
|
294
|
+
experiencesRecorded,
|
|
295
|
+
processingTimeMs: Date.now() - start,
|
|
296
|
+
};
|
|
297
|
+
if (deduplicated.length > 0) {
|
|
298
|
+
log.info('Observations extracted', {
|
|
299
|
+
conversationId: snapshot.conversationId,
|
|
300
|
+
total: deduplicated.length,
|
|
301
|
+
byType: countByType(deduplicated),
|
|
302
|
+
preferencesTracked,
|
|
303
|
+
experiencesRecorded,
|
|
304
|
+
processingTimeMs: result.processingTimeMs,
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
return result;
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Process a batch of recent messages (sliding window).
|
|
311
|
+
* Use this for periodic observation without waiting for conversation end.
|
|
312
|
+
*/
|
|
313
|
+
export async function observeWindow(conversationId, messages, windowSize = 20, userId) {
|
|
314
|
+
// Take last N messages
|
|
315
|
+
const window = messages.slice(-windowSize);
|
|
316
|
+
return observeConversation({
|
|
317
|
+
conversationId,
|
|
318
|
+
messages: window,
|
|
319
|
+
metadata: { userId },
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Build a compressed summary of a conversation for long-term storage.
|
|
324
|
+
* Replaces raw message history with a structured digest.
|
|
325
|
+
*/
|
|
326
|
+
export function compressToDigest(messages, observations) {
|
|
327
|
+
const sections = [];
|
|
328
|
+
// Conversation overview
|
|
329
|
+
const userMessages = messages.filter(m => m.role === 'user');
|
|
330
|
+
const assistantMessages = messages.filter(m => m.role === 'assistant');
|
|
331
|
+
sections.push(`Conversation: ${userMessages.length} user messages, ${assistantMessages.length} assistant responses.`);
|
|
332
|
+
// Group observations by type
|
|
333
|
+
const grouped = new Map();
|
|
334
|
+
for (const obs of observations) {
|
|
335
|
+
const list = grouped.get(obs.type) ?? [];
|
|
336
|
+
list.push(obs);
|
|
337
|
+
grouped.set(obs.type, list);
|
|
338
|
+
}
|
|
339
|
+
if (grouped.has('decision')) {
|
|
340
|
+
sections.push('Decisions: ' + grouped.get('decision').map(o => o.summary).join('; '));
|
|
341
|
+
}
|
|
342
|
+
if (grouped.has('preference')) {
|
|
343
|
+
sections.push('Preferences: ' + grouped.get('preference').map(o => o.summary).join('; '));
|
|
344
|
+
}
|
|
345
|
+
if (grouped.has('solution')) {
|
|
346
|
+
sections.push('Solutions: ' + grouped.get('solution').map(o => o.summary).join('; '));
|
|
347
|
+
}
|
|
348
|
+
if (grouped.has('fact')) {
|
|
349
|
+
sections.push('Facts: ' + grouped.get('fact').map(o => o.summary).join('; '));
|
|
350
|
+
}
|
|
351
|
+
if (grouped.has('error_pattern')) {
|
|
352
|
+
sections.push('Error patterns: ' + grouped.get('error_pattern').map(o => o.summary).join('; '));
|
|
353
|
+
}
|
|
354
|
+
// Key topics from user messages
|
|
355
|
+
const topics = extractTopics(userMessages.map(m => m.content));
|
|
356
|
+
if (topics.length > 0) {
|
|
357
|
+
sections.push('Topics: ' + topics.join(', '));
|
|
358
|
+
}
|
|
359
|
+
return sections.join('\n');
|
|
360
|
+
}
|
|
361
|
+
// Helpers
|
|
362
|
+
function countByType(observations) {
|
|
363
|
+
const counts = {};
|
|
364
|
+
for (const obs of observations) {
|
|
365
|
+
counts[obs.type] = (counts[obs.type] ?? 0) + 1;
|
|
366
|
+
}
|
|
367
|
+
return counts;
|
|
368
|
+
}
|
|
369
|
+
function extractTopics(texts) {
|
|
370
|
+
const wordFreq = new Map();
|
|
371
|
+
const stopWords = new Set([
|
|
372
|
+
'the', 'a', 'an', 'is', 'are', 'was', 'were', 'be', 'been', 'being',
|
|
373
|
+
'have', 'has', 'had', 'do', 'does', 'did', 'will', 'would', 'could',
|
|
374
|
+
'should', 'may', 'might', 'must', 'can', 'shall', 'to', 'of', 'in',
|
|
375
|
+
'on', 'at', 'for', 'with', 'by', 'from', 'up', 'about', 'into',
|
|
376
|
+
'through', 'how', 'what', 'when', 'where', 'why', 'which', 'this',
|
|
377
|
+
'that', 'these', 'those', 'and', 'but', 'or', 'not', 'no', 'so',
|
|
378
|
+
'if', 'then', 'than', 'too', 'very', 'just', 'also', 'its', 'my',
|
|
379
|
+
'your', 'it', 'i', 'me', 'we', 'you', 'he', 'she', 'they', 'them',
|
|
380
|
+
'please', 'thanks', 'thank', 'hi', 'hello', 'hey',
|
|
381
|
+
]);
|
|
382
|
+
for (const text of texts) {
|
|
383
|
+
const words = text.toLowerCase().replace(/[^\w\s]/g, ' ').split(/\s+/);
|
|
384
|
+
for (const word of words) {
|
|
385
|
+
if (word.length < 3 || stopWords.has(word))
|
|
386
|
+
continue;
|
|
387
|
+
wordFreq.set(word, (wordFreq.get(word) ?? 0) + 1);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
return [...wordFreq.entries()]
|
|
391
|
+
.filter(([, count]) => count >= 2)
|
|
392
|
+
.sort((a, b) => b[1] - a[1])
|
|
393
|
+
.slice(0, 8)
|
|
394
|
+
.map(([word]) => word);
|
|
395
|
+
}
|
|
396
|
+
//# sourceMappingURL=observational.js.map
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Content Filter Middleware
|
|
3
|
+
*
|
|
4
|
+
* Optional profanity/content moderation for chat endpoints.
|
|
5
|
+
* Activate with CONTENT_FILTER=true in .env.
|
|
6
|
+
* Skipped in pico mode to preserve memory footprint.
|
|
7
|
+
*/
|
|
8
|
+
import { type Context, type Next } from 'hono';
|
|
9
|
+
/**
|
|
10
|
+
* Hono middleware that checks POST body for profanity on chat send endpoints.
|
|
11
|
+
*
|
|
12
|
+
* Conditions for enforcement:
|
|
13
|
+
* - CONTENT_FILTER=true env var is set
|
|
14
|
+
* - Deployment mode is mini or pro (skipped for pico)
|
|
15
|
+
* - Request method is POST
|
|
16
|
+
* - Body has a `message` or `content` string field
|
|
17
|
+
*
|
|
18
|
+
* On flagged content returns 400 { error, flagged: true }.
|
|
19
|
+
* On clean content calls next().
|
|
20
|
+
*/
|
|
21
|
+
export declare function contentFilter(): (c: Context, next: Next) => Promise<Response | void>;
|
|
22
|
+
//# sourceMappingURL=content-filter.d.ts.map
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Content Filter Middleware
|
|
3
|
+
*
|
|
4
|
+
* Optional profanity/content moderation for chat endpoints.
|
|
5
|
+
* Activate with CONTENT_FILTER=true in .env.
|
|
6
|
+
* Skipped in pico mode to preserve memory footprint.
|
|
7
|
+
*/
|
|
8
|
+
import { logger } from '../utils/logger.js';
|
|
9
|
+
import { getMode } from '../core/deployment.js';
|
|
10
|
+
// Lazy-loaded to avoid paying startup cost when filter is disabled
|
|
11
|
+
let filterInstance = null;
|
|
12
|
+
async function getFilter() {
|
|
13
|
+
if (!filterInstance) {
|
|
14
|
+
const { Filter } = await import('glin-profanity');
|
|
15
|
+
filterInstance = new Filter();
|
|
16
|
+
}
|
|
17
|
+
return filterInstance;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Hono middleware that checks POST body for profanity on chat send endpoints.
|
|
21
|
+
*
|
|
22
|
+
* Conditions for enforcement:
|
|
23
|
+
* - CONTENT_FILTER=true env var is set
|
|
24
|
+
* - Deployment mode is mini or pro (skipped for pico)
|
|
25
|
+
* - Request method is POST
|
|
26
|
+
* - Body has a `message` or `content` string field
|
|
27
|
+
*
|
|
28
|
+
* On flagged content returns 400 { error, flagged: true }.
|
|
29
|
+
* On clean content calls next().
|
|
30
|
+
*/
|
|
31
|
+
export function contentFilter() {
|
|
32
|
+
return async (c, next) => {
|
|
33
|
+
const enabled = process.env.CONTENT_FILTER?.toLowerCase() === 'true';
|
|
34
|
+
if (!enabled)
|
|
35
|
+
return next();
|
|
36
|
+
const mode = getMode();
|
|
37
|
+
if (mode === 'pico')
|
|
38
|
+
return next();
|
|
39
|
+
if (c.req.method !== 'POST')
|
|
40
|
+
return next();
|
|
41
|
+
let text;
|
|
42
|
+
try {
|
|
43
|
+
const body = await c.req.json();
|
|
44
|
+
if (typeof body.message === 'string') {
|
|
45
|
+
text = body.message;
|
|
46
|
+
}
|
|
47
|
+
else if (typeof body.content === 'string') {
|
|
48
|
+
text = body.content;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
// Unparseable body — let the handler deal with it
|
|
53
|
+
return next();
|
|
54
|
+
}
|
|
55
|
+
if (!text)
|
|
56
|
+
return next();
|
|
57
|
+
const filter = await getFilter();
|
|
58
|
+
const result = filter.checkProfanity(text);
|
|
59
|
+
if (result.containsProfanity) {
|
|
60
|
+
logger.warn('Content filter flagged message', {
|
|
61
|
+
profaneWords: result.profaneWords,
|
|
62
|
+
path: c.req.path,
|
|
63
|
+
});
|
|
64
|
+
return c.json({ error: 'Message flagged by content filter', flagged: true }, 400);
|
|
65
|
+
}
|
|
66
|
+
return next();
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=content-filter.js.map
|