@swarmclawai/swarmclaw 0.2.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/README.md +577 -0
- package/bin/server-cmd.js +359 -0
- package/bin/swarmclaw.js +29 -0
- package/bin/swarmclaw.mjs +1504 -0
- package/next.config.ts +33 -0
- package/package.json +112 -0
- package/postcss.config.mjs +7 -0
- package/public/branding/swarmclaw-org-avatar.png +0 -0
- package/public/branding/swarmclaw-org-avatar.svg +58 -0
- package/public/file.svg +1 -0
- package/public/globe.svg +1 -0
- package/public/next.svg +1 -0
- package/public/screenshots/agents.png +0 -0
- package/public/screenshots/connectors.png +0 -0
- package/public/screenshots/dashboard.png +0 -0
- package/public/screenshots/new-session-openclaw.png +0 -0
- package/public/screenshots/providers.png +0 -0
- package/public/screenshots/schedules.png +0 -0
- package/public/screenshots/tasks.png +0 -0
- package/public/vercel.svg +1 -0
- package/public/window.svg +1 -0
- package/src/app/api/agents/[id]/route.ts +30 -0
- package/src/app/api/agents/[id]/thread/route.ts +66 -0
- package/src/app/api/agents/generate/route.ts +42 -0
- package/src/app/api/agents/route.ts +33 -0
- package/src/app/api/auth/route.ts +25 -0
- package/src/app/api/claude-skills/route.ts +42 -0
- package/src/app/api/clawhub/install/route.ts +39 -0
- package/src/app/api/clawhub/search/route.ts +11 -0
- package/src/app/api/connectors/[id]/route.ts +79 -0
- package/src/app/api/connectors/route.ts +60 -0
- package/src/app/api/credentials/[id]/route.ts +14 -0
- package/src/app/api/credentials/route.ts +31 -0
- package/src/app/api/daemon/health-check/route.ts +11 -0
- package/src/app/api/daemon/route.ts +22 -0
- package/src/app/api/dirs/pick/route.ts +60 -0
- package/src/app/api/dirs/route.ts +29 -0
- package/src/app/api/documents/[id]/route.ts +47 -0
- package/src/app/api/documents/route.ts +93 -0
- package/src/app/api/files/serve/route.ts +69 -0
- package/src/app/api/generate/info/route.ts +12 -0
- package/src/app/api/generate/route.ts +106 -0
- package/src/app/api/ip/route.ts +6 -0
- package/src/app/api/knowledge/[id]/route.ts +61 -0
- package/src/app/api/knowledge/route.ts +48 -0
- package/src/app/api/knowledge/upload/route.ts +86 -0
- package/src/app/api/logs/route.ts +65 -0
- package/src/app/api/mcp-servers/[id]/route.ts +32 -0
- package/src/app/api/mcp-servers/[id]/test/route.ts +23 -0
- package/src/app/api/mcp-servers/[id]/tools/route.ts +32 -0
- package/src/app/api/mcp-servers/route.ts +27 -0
- package/src/app/api/memory/[id]/route.ts +126 -0
- package/src/app/api/memory/maintenance/route.ts +63 -0
- package/src/app/api/memory/route.ts +111 -0
- package/src/app/api/memory-images/[filename]/route.ts +36 -0
- package/src/app/api/orchestrator/run/route.ts +43 -0
- package/src/app/api/plugins/install/route.ts +58 -0
- package/src/app/api/plugins/marketplace/route.ts +33 -0
- package/src/app/api/plugins/route.ts +21 -0
- package/src/app/api/preview-server/route.ts +339 -0
- package/src/app/api/providers/[id]/models/route.ts +29 -0
- package/src/app/api/providers/[id]/route.ts +34 -0
- package/src/app/api/providers/configs/route.ts +7 -0
- package/src/app/api/providers/ollama/route.ts +30 -0
- package/src/app/api/providers/openclaw/health/route.ts +23 -0
- package/src/app/api/providers/route.ts +28 -0
- package/src/app/api/runs/[id]/route.ts +9 -0
- package/src/app/api/runs/route.ts +13 -0
- package/src/app/api/schedules/[id]/route.ts +28 -0
- package/src/app/api/schedules/[id]/run/route.ts +104 -0
- package/src/app/api/schedules/route.ts +78 -0
- package/src/app/api/secrets/[id]/route.ts +29 -0
- package/src/app/api/secrets/route.ts +42 -0
- package/src/app/api/sessions/[id]/browser/route.ts +13 -0
- package/src/app/api/sessions/[id]/chat/route.ts +96 -0
- package/src/app/api/sessions/[id]/clear/route.ts +19 -0
- package/src/app/api/sessions/[id]/deploy/route.ts +34 -0
- package/src/app/api/sessions/[id]/devserver/route.ts +69 -0
- package/src/app/api/sessions/[id]/mailbox/route.ts +70 -0
- package/src/app/api/sessions/[id]/main-loop/route.ts +94 -0
- package/src/app/api/sessions/[id]/messages/route.ts +9 -0
- package/src/app/api/sessions/[id]/retry/route.ts +28 -0
- package/src/app/api/sessions/[id]/route.ts +103 -0
- package/src/app/api/sessions/[id]/stop/route.ts +13 -0
- package/src/app/api/sessions/heartbeat/route.ts +26 -0
- package/src/app/api/sessions/route.ts +85 -0
- package/src/app/api/settings/route.ts +58 -0
- package/src/app/api/setup/check-provider/route.ts +326 -0
- package/src/app/api/setup/doctor/route.ts +250 -0
- package/src/app/api/skills/[id]/route.ts +40 -0
- package/src/app/api/skills/import/route.ts +69 -0
- package/src/app/api/skills/route.ts +28 -0
- package/src/app/api/tasks/[id]/route.ts +102 -0
- package/src/app/api/tasks/route.ts +115 -0
- package/src/app/api/tts/route.ts +40 -0
- package/src/app/api/upload/route.ts +18 -0
- package/src/app/api/uploads/[filename]/route.ts +59 -0
- package/src/app/api/usage/route.ts +35 -0
- package/src/app/api/version/route.ts +81 -0
- package/src/app/api/version/update/route.ts +95 -0
- package/src/app/api/webhooks/[id]/history/route.ts +13 -0
- package/src/app/api/webhooks/[id]/route.ts +204 -0
- package/src/app/api/webhooks/route.ts +37 -0
- package/src/app/favicon.ico +0 -0
- package/src/app/globals.css +370 -0
- package/src/app/layout.tsx +52 -0
- package/src/app/page.tsx +172 -0
- package/src/cli/index.js +1232 -0
- package/src/cli/index.test.js +281 -0
- package/src/cli/index.ts +1158 -0
- package/src/cli/spec.js +284 -0
- package/src/components/agents/agent-card.tsx +219 -0
- package/src/components/agents/agent-chat-list.tsx +165 -0
- package/src/components/agents/agent-list.tsx +110 -0
- package/src/components/agents/agent-sheet.tsx +1220 -0
- package/src/components/auth/access-key-gate.tsx +248 -0
- package/src/components/auth/setup-wizard.tsx +940 -0
- package/src/components/auth/user-picker.tsx +88 -0
- package/src/components/chat/chat-area.tsx +406 -0
- package/src/components/chat/chat-header.tsx +491 -0
- package/src/components/chat/chat-tool-toggles.tsx +161 -0
- package/src/components/chat/code-block.tsx +146 -0
- package/src/components/chat/dev-server-bar.tsx +39 -0
- package/src/components/chat/message-bubble.tsx +486 -0
- package/src/components/chat/message-list.tsx +299 -0
- package/src/components/chat/session-debug-panel.tsx +196 -0
- package/src/components/chat/streaming-bubble.tsx +85 -0
- package/src/components/chat/thinking-indicator.tsx +26 -0
- package/src/components/chat/tool-call-bubble.tsx +438 -0
- package/src/components/chat/tool-request-banner.tsx +103 -0
- package/src/components/connectors/connector-list.tsx +196 -0
- package/src/components/connectors/connector-sheet.tsx +804 -0
- package/src/components/input/chat-input.tsx +235 -0
- package/src/components/knowledge/knowledge-list.tsx +206 -0
- package/src/components/knowledge/knowledge-sheet.tsx +316 -0
- package/src/components/layout/app-layout.tsx +1016 -0
- package/src/components/layout/daemon-indicator.tsx +56 -0
- package/src/components/layout/mobile-header.tsx +31 -0
- package/src/components/layout/network-banner.tsx +17 -0
- package/src/components/layout/update-banner.tsx +130 -0
- package/src/components/logs/log-list.tsx +358 -0
- package/src/components/mcp-servers/mcp-server-list.tsx +122 -0
- package/src/components/mcp-servers/mcp-server-sheet.tsx +243 -0
- package/src/components/memory/memory-card.tsx +63 -0
- package/src/components/memory/memory-detail.tsx +339 -0
- package/src/components/memory/memory-list.tsx +198 -0
- package/src/components/memory/memory-sheet.tsx +70 -0
- package/src/components/plugins/plugin-list.tsx +60 -0
- package/src/components/plugins/plugin-sheet.tsx +311 -0
- package/src/components/providers/provider-list.tsx +96 -0
- package/src/components/providers/provider-sheet.tsx +542 -0
- package/src/components/runs/run-list.tsx +231 -0
- package/src/components/schedules/schedule-card.tsx +63 -0
- package/src/components/schedules/schedule-list.tsx +76 -0
- package/src/components/schedules/schedule-sheet.tsx +336 -0
- package/src/components/secrets/secret-sheet.tsx +180 -0
- package/src/components/secrets/secrets-list.tsx +91 -0
- package/src/components/sessions/new-session-sheet.tsx +478 -0
- package/src/components/sessions/session-card.tsx +144 -0
- package/src/components/sessions/session-list.tsx +202 -0
- package/src/components/shared/ai-gen-block.tsx +77 -0
- package/src/components/shared/avatar.tsx +48 -0
- package/src/components/shared/bottom-sheet.tsx +30 -0
- package/src/components/shared/confirm-dialog.tsx +47 -0
- package/src/components/shared/connector-platform-icon.tsx +113 -0
- package/src/components/shared/dir-browser.tsx +285 -0
- package/src/components/shared/dropdown.tsx +55 -0
- package/src/components/shared/icon-button.tsx +25 -0
- package/src/components/shared/settings/plugin-manager.tsx +207 -0
- package/src/components/shared/settings/section-capability-policy.tsx +93 -0
- package/src/components/shared/settings/section-embedding.tsx +99 -0
- package/src/components/shared/settings/section-heartbeat.tsx +168 -0
- package/src/components/shared/settings/section-memory.tsx +77 -0
- package/src/components/shared/settings/section-orchestrator.tsx +108 -0
- package/src/components/shared/settings/section-providers.tsx +181 -0
- package/src/components/shared/settings/section-runtime-loop.tsx +183 -0
- package/src/components/shared/settings/section-secrets.tsx +132 -0
- package/src/components/shared/settings/section-user-preferences.tsx +24 -0
- package/src/components/shared/settings/section-voice.tsx +53 -0
- package/src/components/shared/settings/settings-sheet.tsx +88 -0
- package/src/components/shared/settings/types.ts +7 -0
- package/src/components/shared/settings/utils.ts +13 -0
- package/src/components/shared/settings-sheet.tsx +1 -0
- package/src/components/shared/skeleton.tsx +19 -0
- package/src/components/shared/usage-badge.tsx +28 -0
- package/src/components/skills/clawhub-browser.tsx +225 -0
- package/src/components/skills/skill-list.tsx +70 -0
- package/src/components/skills/skill-sheet.tsx +254 -0
- package/src/components/tasks/task-board.tsx +96 -0
- package/src/components/tasks/task-card.tsx +179 -0
- package/src/components/tasks/task-column.tsx +73 -0
- package/src/components/tasks/task-list.tsx +118 -0
- package/src/components/tasks/task-sheet.tsx +415 -0
- package/src/components/ui/avatar.tsx +109 -0
- package/src/components/ui/badge.tsx +48 -0
- package/src/components/ui/button.tsx +64 -0
- package/src/components/ui/card.tsx +92 -0
- package/src/components/ui/dialog.tsx +158 -0
- package/src/components/ui/dropdown-menu.tsx +257 -0
- package/src/components/ui/input.tsx +21 -0
- package/src/components/ui/scroll-area.tsx +58 -0
- package/src/components/ui/select.tsx +190 -0
- package/src/components/ui/separator.tsx +28 -0
- package/src/components/ui/sheet.tsx +143 -0
- package/src/components/ui/sonner.tsx +22 -0
- package/src/components/ui/textarea.tsx +18 -0
- package/src/components/ui/tooltip.tsx +56 -0
- package/src/components/usage/usage-list.tsx +105 -0
- package/src/components/webhooks/webhook-list.tsx +166 -0
- package/src/components/webhooks/webhook-sheet.tsx +402 -0
- package/src/hooks/use-auto-resize.ts +20 -0
- package/src/hooks/use-media-query.ts +21 -0
- package/src/hooks/use-speech-recognition.ts +83 -0
- package/src/instrumentation.ts +8 -0
- package/src/lib/agents.ts +13 -0
- package/src/lib/api-client.ts +100 -0
- package/src/lib/chat.ts +60 -0
- package/src/lib/memory.ts +42 -0
- package/src/lib/openclaw-endpoint.test.ts +48 -0
- package/src/lib/openclaw-endpoint.ts +67 -0
- package/src/lib/provider-config.ts +13 -0
- package/src/lib/providers/anthropic.ts +135 -0
- package/src/lib/providers/claude-cli.ts +202 -0
- package/src/lib/providers/codex-cli.ts +260 -0
- package/src/lib/providers/index.ts +351 -0
- package/src/lib/providers/ollama.ts +131 -0
- package/src/lib/providers/openai.ts +164 -0
- package/src/lib/providers/openclaw.ts +330 -0
- package/src/lib/providers/opencode-cli.ts +164 -0
- package/src/lib/runtime-loop.ts +15 -0
- package/src/lib/schedule-dedupe.test.ts +84 -0
- package/src/lib/schedule-dedupe.ts +174 -0
- package/src/lib/schedule-name.ts +62 -0
- package/src/lib/schedules.ts +16 -0
- package/src/lib/server/agent-registry.ts +70 -0
- package/src/lib/server/api-routes.test.ts +362 -0
- package/src/lib/server/autonomy-contract.ts +200 -0
- package/src/lib/server/build-llm.ts +155 -0
- package/src/lib/server/capability-router.test.ts +21 -0
- package/src/lib/server/capability-router.ts +172 -0
- package/src/lib/server/chat-execution.ts +894 -0
- package/src/lib/server/clawhub-client.test.ts +161 -0
- package/src/lib/server/clawhub-client.ts +26 -0
- package/src/lib/server/connectors/connector-routing.test.ts +243 -0
- package/src/lib/server/connectors/discord.ts +116 -0
- package/src/lib/server/connectors/googlechat.ts +66 -0
- package/src/lib/server/connectors/manager.ts +559 -0
- package/src/lib/server/connectors/matrix.ts +78 -0
- package/src/lib/server/connectors/media.ts +149 -0
- package/src/lib/server/connectors/openclaw.test.ts +375 -0
- package/src/lib/server/connectors/openclaw.ts +1132 -0
- package/src/lib/server/connectors/signal.ts +183 -0
- package/src/lib/server/connectors/slack.ts +258 -0
- package/src/lib/server/connectors/teams.ts +94 -0
- package/src/lib/server/connectors/telegram.ts +221 -0
- package/src/lib/server/connectors/types.ts +62 -0
- package/src/lib/server/connectors/whatsapp.ts +349 -0
- package/src/lib/server/context-manager.ts +232 -0
- package/src/lib/server/cost.ts +31 -0
- package/src/lib/server/daemon-state.ts +354 -0
- package/src/lib/server/data-dir.ts +3 -0
- package/src/lib/server/embeddings.ts +111 -0
- package/src/lib/server/execution-log.ts +257 -0
- package/src/lib/server/gateway/protocol.test.ts +54 -0
- package/src/lib/server/gateway/protocol.ts +114 -0
- package/src/lib/server/heartbeat-service.ts +366 -0
- package/src/lib/server/knowledge-db.test.ts +441 -0
- package/src/lib/server/logger.ts +47 -0
- package/src/lib/server/main-agent-loop.ts +1017 -0
- package/src/lib/server/mcp-client.test.ts +342 -0
- package/src/lib/server/mcp-client.ts +130 -0
- package/src/lib/server/memory-db.ts +1078 -0
- package/src/lib/server/memory-graph.test.ts +153 -0
- package/src/lib/server/memory-graph.ts +138 -0
- package/src/lib/server/openclaw-health.ts +245 -0
- package/src/lib/server/orchestrator-lg.ts +431 -0
- package/src/lib/server/orchestrator.ts +364 -0
- package/src/lib/server/playwright-proxy.mjs +70 -0
- package/src/lib/server/plugins.ts +229 -0
- package/src/lib/server/process-manager.ts +327 -0
- package/src/lib/server/provider-health.ts +113 -0
- package/src/lib/server/queue.ts +859 -0
- package/src/lib/server/runtime-settings.ts +119 -0
- package/src/lib/server/scheduler.ts +196 -0
- package/src/lib/server/session-mailbox.ts +129 -0
- package/src/lib/server/session-run-manager.ts +512 -0
- package/src/lib/server/session-tools/connector.ts +124 -0
- package/src/lib/server/session-tools/context-mgmt.ts +103 -0
- package/src/lib/server/session-tools/context.ts +114 -0
- package/src/lib/server/session-tools/crud.ts +673 -0
- package/src/lib/server/session-tools/delegate.ts +708 -0
- package/src/lib/server/session-tools/file.ts +264 -0
- package/src/lib/server/session-tools/index.ts +164 -0
- package/src/lib/server/session-tools/memory.ts +230 -0
- package/src/lib/server/session-tools/session-info.ts +422 -0
- package/src/lib/server/session-tools/session-tools-wiring.test.ts +166 -0
- package/src/lib/server/session-tools/shell.ts +171 -0
- package/src/lib/server/session-tools/web.ts +408 -0
- package/src/lib/server/session-tools.ts +9 -0
- package/src/lib/server/skills-normalize.ts +130 -0
- package/src/lib/server/storage-mcp.test.ts +161 -0
- package/src/lib/server/storage.ts +670 -0
- package/src/lib/server/stream-agent-chat.ts +571 -0
- package/src/lib/server/task-reports.ts +122 -0
- package/src/lib/server/task-result.ts +161 -0
- package/src/lib/server/task-validation.test.ts +27 -0
- package/src/lib/server/task-validation.ts +90 -0
- package/src/lib/server/tool-capability-policy.test.ts +58 -0
- package/src/lib/server/tool-capability-policy.ts +262 -0
- package/src/lib/sessions.ts +68 -0
- package/src/lib/tasks.ts +20 -0
- package/src/lib/tts.ts +42 -0
- package/src/lib/upload.ts +10 -0
- package/src/lib/utils.ts +6 -0
- package/src/proxy.ts +43 -0
- package/src/stores/use-app-store.ts +468 -0
- package/src/stores/use-chat-store.ts +323 -0
- package/src/types/index.ts +621 -0
- package/tsconfig.json +34 -0
|
@@ -0,0 +1,571 @@
|
|
|
1
|
+
import fs from 'fs'
|
|
2
|
+
import { createReactAgent } from '@langchain/langgraph/prebuilt'
|
|
3
|
+
import { HumanMessage, AIMessage } from '@langchain/core/messages'
|
|
4
|
+
import { buildSessionTools } from './session-tools'
|
|
5
|
+
import { buildChatModel } from './build-llm'
|
|
6
|
+
import { loadSettings, loadAgents, loadSkills, appendUsage } from './storage'
|
|
7
|
+
import { estimateCost } from './cost'
|
|
8
|
+
import { getPluginManager } from './plugins'
|
|
9
|
+
import { loadRuntimeSettings, getAgentLoopRecursionLimit } from './runtime-settings'
|
|
10
|
+
import { getMemoryDb } from './memory-db'
|
|
11
|
+
import { logExecution } from './execution-log'
|
|
12
|
+
import type { Session, Message, UsageRecord } from '@/types'
|
|
13
|
+
|
|
14
|
+
interface StreamAgentChatOpts {
|
|
15
|
+
session: Session
|
|
16
|
+
message: string
|
|
17
|
+
imagePath?: string
|
|
18
|
+
apiKey: string | null
|
|
19
|
+
systemPrompt?: string
|
|
20
|
+
write: (data: string) => void
|
|
21
|
+
history: Message[]
|
|
22
|
+
fallbackCredentialIds?: string[]
|
|
23
|
+
signal?: AbortSignal
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function buildToolCapabilityLines(enabledTools: string[]): string[] {
|
|
27
|
+
const lines: string[] = []
|
|
28
|
+
if (enabledTools.includes('shell')) lines.push('- Shell execution is available (`execute_command`). Use it for real checks/build/test steps.')
|
|
29
|
+
if (enabledTools.includes('process')) lines.push('- Process control is available (`process_tool`) for long-running commands (poll/log/write/kill).')
|
|
30
|
+
if (enabledTools.includes('files') || enabledTools.includes('copy_file') || enabledTools.includes('move_file') || enabledTools.includes('delete_file')) {
|
|
31
|
+
lines.push('- File operations are available (`read_file`, `write_file`, `list_files`, `copy_file`, `move_file`, `send_file`). `delete_file` is destructive and may be disabled unless explicitly enabled.')
|
|
32
|
+
}
|
|
33
|
+
if (enabledTools.includes('edit_file')) lines.push('- Precise single-match replacement is available (`edit_file`).')
|
|
34
|
+
if (enabledTools.includes('web_search')) lines.push('- Web search is available (`web_search`). Use it for external research, options discovery, and validation.')
|
|
35
|
+
if (enabledTools.includes('web_fetch')) lines.push('- URL content extraction is available (`web_fetch`) for source-backed analysis.')
|
|
36
|
+
if (enabledTools.includes('browser')) lines.push('- Browser automation is available (`browser`). Use it for interactive websites and screenshots.')
|
|
37
|
+
if (enabledTools.includes('claude_code')) lines.push('- Claude delegation is available (`delegate_to_claude_code`) for deep coding/refactor tasks. Resume IDs may be returned via `[delegate_meta]`.')
|
|
38
|
+
if (enabledTools.includes('codex_cli')) lines.push('- Codex delegation is available (`delegate_to_codex_cli`) for deep coding/refactor tasks. Resume IDs may be returned via `[delegate_meta]`.')
|
|
39
|
+
if (enabledTools.includes('opencode_cli')) lines.push('- OpenCode delegation is available (`delegate_to_opencode_cli`) for deep coding/refactor tasks. Resume IDs may be returned via `[delegate_meta]`.')
|
|
40
|
+
if (enabledTools.includes('memory')) lines.push('- Long-term memory is available (`memory_tool`) to store and recall durable context.')
|
|
41
|
+
if (enabledTools.includes('manage_agents')) lines.push('- Agent management is available (`manage_agents`) to create or adjust specialist agents.')
|
|
42
|
+
if (enabledTools.includes('manage_tasks')) lines.push('- Task management is available (`manage_tasks`) to create and track execution plans.')
|
|
43
|
+
if (enabledTools.includes('manage_schedules')) lines.push('- Schedule management is available (`manage_schedules`) for recurring/ongoing runs.')
|
|
44
|
+
if (enabledTools.includes('manage_documents')) lines.push('- Document indexing/search is available (`manage_documents`) for long-term knowledge and retrieval.')
|
|
45
|
+
if (enabledTools.includes('manage_webhooks')) lines.push('- Webhook registration is available (`manage_webhooks`) so external events can trigger agent work.')
|
|
46
|
+
if (enabledTools.includes('manage_skills')) lines.push('- Skill management is available (`manage_skills`) to add reusable capabilities.')
|
|
47
|
+
if (enabledTools.includes('manage_connectors')) lines.push('- Connector management is available (`manage_connectors`) for channels like WhatsApp/Telegram/Slack, plus proactive outbound notifications via `connector_message_tool`.')
|
|
48
|
+
if (enabledTools.includes('manage_sessions')) lines.push('- Session management is available (`manage_sessions`, `sessions_tool`, `whoami_tool`, `search_history_tool`) for session identity, history lookup, delegation, and inter-session messaging.')
|
|
49
|
+
// Context tools are available to any session with tools (not just manage_sessions)
|
|
50
|
+
if (enabledTools.length > 0) {
|
|
51
|
+
lines.push('- Context management is available (`context_status`, `context_summarize`). Use `context_status` to check token usage and `context_summarize` to compact conversation history when approaching limits.')
|
|
52
|
+
lines.push('- Agent delegation is available (`delegate_to_agent`). Use it to assign tasks to other agents based on their capabilities.')
|
|
53
|
+
}
|
|
54
|
+
if (enabledTools.includes('manage_secrets')) lines.push('- Secret management is available (`manage_secrets`) for durable encrypted credentials and API tokens.')
|
|
55
|
+
return lines
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function buildAgenticExecutionPolicy(opts: {
|
|
59
|
+
enabledTools: string[]
|
|
60
|
+
loopMode: 'bounded' | 'ongoing'
|
|
61
|
+
heartbeatPrompt: string
|
|
62
|
+
heartbeatIntervalSec: number
|
|
63
|
+
}) {
|
|
64
|
+
const hasTooling = opts.enabledTools.length > 0
|
|
65
|
+
const toolLines = buildToolCapabilityLines(opts.enabledTools)
|
|
66
|
+
const delegationOrder = [
|
|
67
|
+
opts.enabledTools.includes('claude_code') ? '`delegate_to_claude_code`' : null,
|
|
68
|
+
opts.enabledTools.includes('codex_cli') ? '`delegate_to_codex_cli`' : null,
|
|
69
|
+
opts.enabledTools.includes('opencode_cli') ? '`delegate_to_opencode_cli`' : null,
|
|
70
|
+
].filter(Boolean) as string[]
|
|
71
|
+
const hasDelegationTool = delegationOrder.length > 0
|
|
72
|
+
return [
|
|
73
|
+
'## Agentic Execution Policy',
|
|
74
|
+
'You are not a passive chatbot. Execute work proactively and use available tools to gather evidence, create artifacts, and make progress.',
|
|
75
|
+
hasTooling
|
|
76
|
+
? 'For open-ended requests, run an action loop: plan briefly, execute tools, evaluate results, then continue until meaningful progress is achieved.'
|
|
77
|
+
: 'This session has no tools enabled, so be explicit about what tool access is needed for deeper execution.',
|
|
78
|
+
'Do not stop at generic advice when the request implies action (research, coding, setup, business ideas, optimization, automation, or platform operations).',
|
|
79
|
+
'For multi-step work, keep the user informed with short progress updates tied to real actions (what you are doing now, what finished, and what is next).',
|
|
80
|
+
'If you state an intention to do research/build/execute, immediately follow through with tool calls in the same run.',
|
|
81
|
+
'Never claim completed research/build results without tool evidence. If a tool fails or returns empty results, say that clearly and retry with another approach.',
|
|
82
|
+
'If the user names a tool explicitly (for example "call connector_message_tool"), you must actually invoke that tool instead of simulating or paraphrasing its result.',
|
|
83
|
+
'Before finalizing: verify key claims with concrete outputs from tools whenever tools are available.',
|
|
84
|
+
opts.loopMode === 'ongoing'
|
|
85
|
+
? 'Loop mode is ONGOING: prefer continued execution and progress tracking over one-shot replies; keep iterating until done, blocked, or safety/runtime limits are reached.'
|
|
86
|
+
: 'Loop mode is BOUNDED: still execute multiple steps when needed, but finish within the recursion budget.',
|
|
87
|
+
opts.enabledTools.includes('manage_tasks')
|
|
88
|
+
? 'When goals are long-lived, create/update tasks in the task board so progress is trackable over time.'
|
|
89
|
+
: '',
|
|
90
|
+
opts.enabledTools.includes('manage_schedules')
|
|
91
|
+
? 'When goals require follow-up, create schedules for recurring checks or future actions instead of waiting for manual prompts.'
|
|
92
|
+
: '',
|
|
93
|
+
opts.enabledTools.includes('manage_schedules')
|
|
94
|
+
? 'Before creating a schedule, first inspect existing schedules (list/get) and reuse or update a matching one instead of creating duplicates.'
|
|
95
|
+
: '',
|
|
96
|
+
opts.enabledTools.includes('manage_agents')
|
|
97
|
+
? 'If a specialist would improve output, create or configure a focused agent and assign work accordingly.'
|
|
98
|
+
: '',
|
|
99
|
+
opts.enabledTools.includes('manage_documents')
|
|
100
|
+
? 'For substantial context, store source documents and retrieve them with manage_documents search/get instead of relying on short memory snippets alone.'
|
|
101
|
+
: '',
|
|
102
|
+
opts.enabledTools.includes('manage_webhooks')
|
|
103
|
+
? 'For event-driven workflows, register webhooks and let external triggers enqueue follow-up work automatically.'
|
|
104
|
+
: '',
|
|
105
|
+
opts.enabledTools.includes('manage_connectors')
|
|
106
|
+
? 'If the user wants proactive outreach (e.g., WhatsApp updates), configure connectors and pair with schedules/tasks to deliver status updates.'
|
|
107
|
+
: '',
|
|
108
|
+
opts.enabledTools.includes('manage_sessions')
|
|
109
|
+
? 'When coordinating platform work, inspect existing sessions and avoid duplicating active efforts.'
|
|
110
|
+
: '',
|
|
111
|
+
hasDelegationTool
|
|
112
|
+
? `For substantial coding/build/refactor/test requests, prefer CLI delegation first using this order: ${delegationOrder.join(' -> ')}.`
|
|
113
|
+
: '',
|
|
114
|
+
hasDelegationTool
|
|
115
|
+
? 'Use direct shell/file tool loops yourself mainly for small edits, quick verification, or when delegation tools are unavailable/failing.'
|
|
116
|
+
: '',
|
|
117
|
+
opts.enabledTools.includes('memory')
|
|
118
|
+
? 'Memory is active and required for long-horizon work: before major tasks, run memory_tool search/list for relevant prior work; after each meaningful step, store concise reusable notes (what changed, where it lives, constraints, next step). Treat memory as shared context plus your own agent notes, not as user-owned personal profile data.'
|
|
119
|
+
: '',
|
|
120
|
+
opts.enabledTools.includes('memory')
|
|
121
|
+
? 'The platform preloads relevant memory context each turn. Use memory_tool for deeper lookup, explicit recall requests, and durable storage.'
|
|
122
|
+
: '',
|
|
123
|
+
opts.enabledTools.includes('memory')
|
|
124
|
+
? 'If the user gives an open goal (e.g. "go make money"), do not keep re-asking broad clarifying questions. Form a hypothesis, execute a concrete step, then adapt using memory + evidence.'
|
|
125
|
+
: '',
|
|
126
|
+
'## Knowing When Not to Reply',
|
|
127
|
+
'Real conversations have natural pauses. Not every message needs a response — sometimes the most human thing is comfortable silence.',
|
|
128
|
+
'Reply with exactly "NO_MESSAGE" (nothing else) to suppress outbound delivery when replying would feel unnatural.',
|
|
129
|
+
'Think about what a thoughtful friend would do:',
|
|
130
|
+
'- "okay" / "alright" / "cool" / "got it" / "sounds good" → they\'re just acknowledging, not expecting a reply back',
|
|
131
|
+
'- "thanks" / "thx" / "ty" after you\'ve helped → the conversation is wrapping up naturally',
|
|
132
|
+
'- thumbs up, emoji reactions, read receipts → these are closers, not openers',
|
|
133
|
+
'- "night" / "ttyl" / "bye" / "gotta go" → they\'re leaving, let them go',
|
|
134
|
+
'- "haha" / "lol" / "lmao" → they appreciated something, no follow-up needed',
|
|
135
|
+
'- forwarded content or status updates with no question → they\'re sharing, not asking',
|
|
136
|
+
'Always reply when:',
|
|
137
|
+
'- There is a question, even an implied one ("I wonder if...")',
|
|
138
|
+
'- They give you a task or instruction',
|
|
139
|
+
'- They share something emotional or personal — silence here feels cold',
|
|
140
|
+
'- They say "thanks" with a follow-up context ("thanks, what about X?") or in a tone that expects "you\'re welcome"',
|
|
141
|
+
'- You have something genuinely useful to add',
|
|
142
|
+
'The test: if you saw this message from a friend, would you feel compelled to type something back? If not, NO_MESSAGE.',
|
|
143
|
+
'Ask for confirmation only for high-risk or irreversible actions. For normal low-risk research/build steps, proceed autonomously.',
|
|
144
|
+
'Default behavior is execution, not interrogation: do not ask exploratory clarification questions when a safe next action exists.',
|
|
145
|
+
'Do not pause for a "continue" confirmation after the user has already asked you to execute a goal. Keep moving until blocked by permissions, missing credentials, or hard tool failures.',
|
|
146
|
+
'Never repeat one-time side effects that are already complete (for example creating the same schedule/task again). Verify state first, then either continue execution or reply HEARTBEAT_OK.',
|
|
147
|
+
'For main-loop tick messages that begin with "SWARM_MAIN_MISSION_TICK" or "SWARM_MAIN_AUTO_FOLLOWUP", follow that response contract exactly and include one valid [MAIN_LOOP_META] JSON line when you are not returning HEARTBEAT_OK.',
|
|
148
|
+
`Heartbeat protocol: if the user message is exactly "${opts.heartbeatPrompt}", reply exactly "HEARTBEAT_OK" when there is nothing important to report; otherwise reply with a concise progress update and immediate next step.`,
|
|
149
|
+
opts.heartbeatIntervalSec > 0
|
|
150
|
+
? `Expected heartbeat cadence is roughly every ${opts.heartbeatIntervalSec} seconds while ongoing work is active.`
|
|
151
|
+
: '',
|
|
152
|
+
toolLines.length ? 'Available capabilities:\n' + toolLines.join('\n') : '',
|
|
153
|
+
].filter(Boolean).join('\n')
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export interface StreamAgentChatResult {
|
|
157
|
+
/** All text accumulated across every LLM turn (for SSE / web UI history). */
|
|
158
|
+
fullText: string
|
|
159
|
+
/** Text from only the final LLM turn — after the last tool call completed.
|
|
160
|
+
* Use this for connector delivery so intermediate planning text isn't sent. */
|
|
161
|
+
finalResponse: string
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export async function streamAgentChat(opts: StreamAgentChatOpts): Promise<StreamAgentChatResult> {
|
|
165
|
+
const { session, message, imagePath, apiKey, systemPrompt, write, history, fallbackCredentialIds, signal } = opts
|
|
166
|
+
|
|
167
|
+
// fallbackCredentialIds is intentionally accepted for compatibility with caller signatures.
|
|
168
|
+
void fallbackCredentialIds
|
|
169
|
+
const llm = buildChatModel({
|
|
170
|
+
provider: session.provider,
|
|
171
|
+
model: session.model,
|
|
172
|
+
apiKey,
|
|
173
|
+
apiEndpoint: session.apiEndpoint,
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
// Build stateModifier
|
|
177
|
+
const settings = loadSettings()
|
|
178
|
+
const runtime = loadRuntimeSettings()
|
|
179
|
+
const heartbeatPrompt = (typeof settings.heartbeatPrompt === 'string' && settings.heartbeatPrompt.trim())
|
|
180
|
+
? settings.heartbeatPrompt.trim()
|
|
181
|
+
: 'SWARM_HEARTBEAT_CHECK'
|
|
182
|
+
const heartbeatIntervalSec = (() => {
|
|
183
|
+
const raw = settings.heartbeatIntervalSec
|
|
184
|
+
const parsed = typeof raw === 'number'
|
|
185
|
+
? raw
|
|
186
|
+
: typeof raw === 'string'
|
|
187
|
+
? Number.parseInt(raw, 10)
|
|
188
|
+
: Number.NaN
|
|
189
|
+
if (!Number.isFinite(parsed)) return 120
|
|
190
|
+
return Math.max(0, Math.min(3600, Math.trunc(parsed)))
|
|
191
|
+
})()
|
|
192
|
+
|
|
193
|
+
const stateModifierParts: string[] = []
|
|
194
|
+
const hasProvidedSystemPrompt = typeof systemPrompt === 'string' && systemPrompt.trim().length > 0
|
|
195
|
+
|
|
196
|
+
if (hasProvidedSystemPrompt) {
|
|
197
|
+
stateModifierParts.push(systemPrompt!.trim())
|
|
198
|
+
} else {
|
|
199
|
+
if (settings.userPrompt) stateModifierParts.push(settings.userPrompt)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Load agent context when a full prompt was not already composed by the route layer.
|
|
203
|
+
let agentPlatformAssignScope: 'self' | 'all' = 'self'
|
|
204
|
+
let agentMcpServerIds: string[] | undefined
|
|
205
|
+
let agentMcpDisabledTools: string[] | undefined
|
|
206
|
+
if (session.agentId) {
|
|
207
|
+
const agents = loadAgents()
|
|
208
|
+
const agent = agents[session.agentId]
|
|
209
|
+
agentPlatformAssignScope = agent?.platformAssignScope || 'self'
|
|
210
|
+
agentMcpServerIds = agent?.mcpServerIds
|
|
211
|
+
agentMcpDisabledTools = agent?.mcpDisabledTools
|
|
212
|
+
if (!hasProvidedSystemPrompt) {
|
|
213
|
+
if (agent?.soul) stateModifierParts.push(agent.soul)
|
|
214
|
+
if (agent?.systemPrompt) stateModifierParts.push(agent.systemPrompt)
|
|
215
|
+
if (agent?.skillIds?.length) {
|
|
216
|
+
const allSkills = loadSkills()
|
|
217
|
+
for (const skillId of agent.skillIds) {
|
|
218
|
+
const skill = allSkills[skillId]
|
|
219
|
+
if (skill?.content) stateModifierParts.push(`## Skill: ${skill.name}\n${skill.content}`)
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (!hasProvidedSystemPrompt) {
|
|
226
|
+
stateModifierParts.push('You are a capable AI assistant with tool access. Be execution-oriented and outcome-focused.')
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if ((session.tools || []).includes('memory') && session.agentId) {
|
|
230
|
+
try {
|
|
231
|
+
const memDb = getMemoryDb()
|
|
232
|
+
const memoryQuerySeed = [
|
|
233
|
+
message,
|
|
234
|
+
...history
|
|
235
|
+
.slice(-4)
|
|
236
|
+
.filter((h) => h.role === 'user')
|
|
237
|
+
.map((h) => h.text),
|
|
238
|
+
].join('\n')
|
|
239
|
+
|
|
240
|
+
const relevantLookup = memDb.searchWithLinked(memoryQuerySeed, session.agentId, 1, 10, 14)
|
|
241
|
+
const relevant = relevantLookup.entries.slice(0, 6)
|
|
242
|
+
const recent = memDb.list(session.agentId, 12).slice(0, 6)
|
|
243
|
+
|
|
244
|
+
const seen = new Set<string>()
|
|
245
|
+
const formatMemoryLine = (m: any) => {
|
|
246
|
+
const category = String(m.category || 'note')
|
|
247
|
+
const title = String(m.title || 'Untitled').replace(/\s+/g, ' ').trim()
|
|
248
|
+
const snippet = String(m.content || '').replace(/\s+/g, ' ').trim().slice(0, 220)
|
|
249
|
+
return `- [${category}] ${title}: ${snippet}`
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const relevantLines = relevant
|
|
253
|
+
.filter((m) => {
|
|
254
|
+
if (!m?.id || seen.has(m.id)) return false
|
|
255
|
+
seen.add(m.id)
|
|
256
|
+
return true
|
|
257
|
+
})
|
|
258
|
+
.map(formatMemoryLine)
|
|
259
|
+
|
|
260
|
+
const recentLines = recent
|
|
261
|
+
.filter((m) => {
|
|
262
|
+
if (!m?.id || seen.has(m.id)) return false
|
|
263
|
+
seen.add(m.id)
|
|
264
|
+
return true
|
|
265
|
+
})
|
|
266
|
+
.map(formatMemoryLine)
|
|
267
|
+
|
|
268
|
+
const memorySections: string[] = []
|
|
269
|
+
if (relevantLines.length) {
|
|
270
|
+
memorySections.push(
|
|
271
|
+
[
|
|
272
|
+
'## Relevant Memory Hits',
|
|
273
|
+
'These memories were retrieved by relevance for the current objective.',
|
|
274
|
+
...relevantLines,
|
|
275
|
+
].join('\n'),
|
|
276
|
+
)
|
|
277
|
+
}
|
|
278
|
+
if (recentLines.length) {
|
|
279
|
+
memorySections.push(
|
|
280
|
+
[
|
|
281
|
+
'## Recent Memory Notes',
|
|
282
|
+
'Recent durable notes that may still apply.',
|
|
283
|
+
...recentLines,
|
|
284
|
+
].join('\n'),
|
|
285
|
+
)
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (memorySections.length) {
|
|
289
|
+
stateModifierParts.push(memorySections.join('\n\n'))
|
|
290
|
+
}
|
|
291
|
+
} catch {
|
|
292
|
+
// If memory context fails to load, continue without blocking the run.
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Inject agent awareness (Phase 2: agents know about each other)
|
|
297
|
+
if ((session.tools || []).length > 0 && session.agentId) {
|
|
298
|
+
try {
|
|
299
|
+
const { buildAgentAwarenessBlock } = await import('./agent-registry')
|
|
300
|
+
const awarenessBlock = buildAgentAwarenessBlock(session.agentId)
|
|
301
|
+
if (awarenessBlock) stateModifierParts.push(awarenessBlock)
|
|
302
|
+
} catch {
|
|
303
|
+
// If agent registry fails, continue without blocking the run.
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Tell the LLM about tools it could use but doesn't have enabled
|
|
308
|
+
{
|
|
309
|
+
const enabledSet = new Set(session.tools || [])
|
|
310
|
+
const allToolIds = [
|
|
311
|
+
'shell', 'files', 'edit_file', 'process', 'web_search', 'web_fetch', 'browser', 'memory',
|
|
312
|
+
'claude_code', 'codex_cli', 'opencode_cli',
|
|
313
|
+
'orchestrator',
|
|
314
|
+
'manage_agents', 'manage_tasks', 'manage_schedules', 'manage_skills',
|
|
315
|
+
'manage_documents', 'manage_webhooks', 'manage_connectors', 'manage_sessions', 'manage_secrets',
|
|
316
|
+
]
|
|
317
|
+
const disabled = allToolIds.filter((t) => !enabledSet.has(t))
|
|
318
|
+
const mcpDisabled = agentMcpDisabledTools ?? []
|
|
319
|
+
const allDisabled = [...disabled, ...mcpDisabled]
|
|
320
|
+
if (allDisabled.length > 0) {
|
|
321
|
+
const delegateNote = disabled.includes('orchestrator')
|
|
322
|
+
? '\n\nIMPORTANT: The `delegate_to_agent` tool requires the `orchestrator` capability to be enabled. You must request access to `orchestrator` before you can delegate work to other agents.'
|
|
323
|
+
: ''
|
|
324
|
+
stateModifierParts.push(
|
|
325
|
+
`## Disabled Tools\nThe following tools exist but are not enabled for you: ${allDisabled.join(', ')}.\n` +
|
|
326
|
+
'If you need one of these to complete a task, use the `request_tool_access` tool to ask the user for permission.' +
|
|
327
|
+
delegateNote,
|
|
328
|
+
)
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
stateModifierParts.push(
|
|
333
|
+
buildAgenticExecutionPolicy({
|
|
334
|
+
enabledTools: session.tools || [],
|
|
335
|
+
loopMode: runtime.loopMode,
|
|
336
|
+
heartbeatPrompt,
|
|
337
|
+
heartbeatIntervalSec,
|
|
338
|
+
}),
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
const stateModifier = stateModifierParts.join('\n\n')
|
|
342
|
+
|
|
343
|
+
const { tools, cleanup } = await buildSessionTools(session.cwd, session.tools || [], {
|
|
344
|
+
agentId: session.agentId,
|
|
345
|
+
sessionId: session.id,
|
|
346
|
+
platformAssignScope: agentPlatformAssignScope,
|
|
347
|
+
mcpServerIds: agentMcpServerIds,
|
|
348
|
+
mcpDisabledTools: agentMcpDisabledTools,
|
|
349
|
+
})
|
|
350
|
+
const agent = createReactAgent({ llm, tools, stateModifier })
|
|
351
|
+
const recursionLimit = getAgentLoopRecursionLimit(runtime)
|
|
352
|
+
|
|
353
|
+
// Build message history for context
|
|
354
|
+
const IMAGE_EXTS = /\.(png|jpg|jpeg|gif|webp|bmp)$/i
|
|
355
|
+
const TEXT_EXTS = /\.(txt|md|csv|json|xml|html|js|ts|tsx|jsx|py|go|rs|java|c|cpp|h|yml|yaml|toml|env|log|sh|sql|css|scss)$/i
|
|
356
|
+
|
|
357
|
+
function buildLangChainContent(text: string, filePath?: string): any {
|
|
358
|
+
if (!filePath || !fs.existsSync(filePath)) return text
|
|
359
|
+
if (IMAGE_EXTS.test(filePath)) {
|
|
360
|
+
const data = fs.readFileSync(filePath).toString('base64')
|
|
361
|
+
const ext = filePath.split('.').pop()?.toLowerCase() || 'png'
|
|
362
|
+
const mimeType = ext === 'jpg' ? 'image/jpeg' : `image/${ext}`
|
|
363
|
+
return [
|
|
364
|
+
{ type: 'image_url', image_url: { url: `data:${mimeType};base64,${data}` } },
|
|
365
|
+
{ type: 'text', text },
|
|
366
|
+
]
|
|
367
|
+
}
|
|
368
|
+
if (TEXT_EXTS.test(filePath) || filePath.endsWith('.pdf')) {
|
|
369
|
+
try {
|
|
370
|
+
const fileContent = fs.readFileSync(filePath, 'utf-8')
|
|
371
|
+
const name = filePath.split('/').pop() || 'file'
|
|
372
|
+
return `[Attached file: ${name}]\n\n${fileContent}\n\n${text}`
|
|
373
|
+
} catch { return text }
|
|
374
|
+
}
|
|
375
|
+
return `[Attached file: ${filePath.split('/').pop()}]\n\n${text}`
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// Auto-compaction: prune old history if approaching context window limit
|
|
379
|
+
let effectiveHistory = history
|
|
380
|
+
try {
|
|
381
|
+
const { shouldAutoCompact, consolidateToMemory, slidingWindowCompact, estimateTokens } = await import('./context-manager')
|
|
382
|
+
const systemPromptTokens = estimateTokens(stateModifier)
|
|
383
|
+
if (shouldAutoCompact(history, systemPromptTokens, session.provider, session.model)) {
|
|
384
|
+
// Consolidate important old messages to memory before pruning
|
|
385
|
+
const oldMessages = history.slice(0, -10)
|
|
386
|
+
if (oldMessages.length > 0 && session.agentId) {
|
|
387
|
+
consolidateToMemory(oldMessages, session.agentId, session.id)
|
|
388
|
+
}
|
|
389
|
+
// Keep last 10 messages via sliding window
|
|
390
|
+
effectiveHistory = slidingWindowCompact(history, 10)
|
|
391
|
+
console.log(`[stream-agent-chat] Auto-compacted session ${session.id}: ${history.length} → ${effectiveHistory.length} messages`)
|
|
392
|
+
}
|
|
393
|
+
} catch {
|
|
394
|
+
// If context manager fails, continue with full history
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const langchainMessages: Array<HumanMessage | AIMessage> = []
|
|
398
|
+
for (const m of effectiveHistory.slice(-20)) {
|
|
399
|
+
if (m.role === 'user') {
|
|
400
|
+
langchainMessages.push(new HumanMessage({ content: buildLangChainContent(m.text, m.imagePath) }))
|
|
401
|
+
} else {
|
|
402
|
+
langchainMessages.push(new AIMessage({ content: m.text }))
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// Add current message
|
|
407
|
+
langchainMessages.push(new HumanMessage({ content: buildLangChainContent(message, imagePath) }))
|
|
408
|
+
|
|
409
|
+
let fullText = ''
|
|
410
|
+
let lastSegment = ''
|
|
411
|
+
let hasToolCalls = false
|
|
412
|
+
let totalInputTokens = 0
|
|
413
|
+
let totalOutputTokens = 0
|
|
414
|
+
|
|
415
|
+
// Plugin hooks: beforeAgentStart
|
|
416
|
+
const pluginMgr = getPluginManager()
|
|
417
|
+
await pluginMgr.runHook('beforeAgentStart', { session, message })
|
|
418
|
+
|
|
419
|
+
const abortController = new AbortController()
|
|
420
|
+
const abortFromSignal = () => abortController.abort()
|
|
421
|
+
if (signal) {
|
|
422
|
+
if (signal.aborted) abortController.abort()
|
|
423
|
+
else signal.addEventListener('abort', abortFromSignal)
|
|
424
|
+
}
|
|
425
|
+
let timedOut = false
|
|
426
|
+
const loopTimer = runtime.loopMode === 'ongoing' && runtime.ongoingLoopMaxRuntimeMs
|
|
427
|
+
? setTimeout(() => {
|
|
428
|
+
timedOut = true
|
|
429
|
+
abortController.abort()
|
|
430
|
+
}, runtime.ongoingLoopMaxRuntimeMs)
|
|
431
|
+
: null
|
|
432
|
+
|
|
433
|
+
try {
|
|
434
|
+
const eventStream = agent.streamEvents(
|
|
435
|
+
{ messages: langchainMessages },
|
|
436
|
+
{ version: 'v2', recursionLimit, signal: abortController.signal },
|
|
437
|
+
)
|
|
438
|
+
|
|
439
|
+
for await (const event of eventStream) {
|
|
440
|
+
const kind = event.event
|
|
441
|
+
|
|
442
|
+
if (kind === 'on_chat_model_stream') {
|
|
443
|
+
const chunk = event.data?.chunk
|
|
444
|
+
if (chunk?.content) {
|
|
445
|
+
// content can be string or array of content blocks
|
|
446
|
+
const text = typeof chunk.content === 'string'
|
|
447
|
+
? chunk.content
|
|
448
|
+
: Array.isArray(chunk.content)
|
|
449
|
+
? chunk.content.map((c: any) => c.text || '').join('')
|
|
450
|
+
: ''
|
|
451
|
+
if (text) {
|
|
452
|
+
fullText += text
|
|
453
|
+
lastSegment += text
|
|
454
|
+
write(`data: ${JSON.stringify({ t: 'd', text })}\n\n`)
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
} else if (kind === 'on_llm_end') {
|
|
458
|
+
// Track token usage from LLM responses
|
|
459
|
+
const usage = event.data?.output?.llmOutput?.tokenUsage
|
|
460
|
+
|| event.data?.output?.llmOutput?.usage
|
|
461
|
+
|| event.data?.output?.usage_metadata
|
|
462
|
+
if (usage) {
|
|
463
|
+
totalInputTokens += usage.promptTokens || usage.input_tokens || 0
|
|
464
|
+
totalOutputTokens += usage.completionTokens || usage.output_tokens || 0
|
|
465
|
+
}
|
|
466
|
+
} else if (kind === 'on_tool_start') {
|
|
467
|
+
hasToolCalls = true
|
|
468
|
+
lastSegment = ''
|
|
469
|
+
const toolName = event.name || 'unknown'
|
|
470
|
+
const input = event.data?.input
|
|
471
|
+
// Plugin hooks: beforeToolExec
|
|
472
|
+
await pluginMgr.runHook('beforeToolExec', { toolName, input })
|
|
473
|
+
const inputStr = typeof input === 'string' ? input : JSON.stringify(input)
|
|
474
|
+
logExecution(session.id, 'tool_call', `${toolName} invoked`, {
|
|
475
|
+
agentId: session.agentId,
|
|
476
|
+
detail: { toolName, input: inputStr?.slice(0, 4000) },
|
|
477
|
+
})
|
|
478
|
+
write(`data: ${JSON.stringify({
|
|
479
|
+
t: 'tool_call',
|
|
480
|
+
toolName,
|
|
481
|
+
toolInput: inputStr,
|
|
482
|
+
})}\n\n`)
|
|
483
|
+
} else if (kind === 'on_tool_end') {
|
|
484
|
+
const toolName = event.name || 'unknown'
|
|
485
|
+
const output = event.data?.output
|
|
486
|
+
const outputStr = typeof output === 'string'
|
|
487
|
+
? output
|
|
488
|
+
: output?.content
|
|
489
|
+
? String(output.content)
|
|
490
|
+
: JSON.stringify(output)
|
|
491
|
+
// Plugin hooks: afterToolExec
|
|
492
|
+
await pluginMgr.runHook('afterToolExec', { toolName, input: null, output: outputStr })
|
|
493
|
+
logExecution(session.id, 'tool_result', `${toolName} returned`, {
|
|
494
|
+
agentId: session.agentId,
|
|
495
|
+
detail: { toolName, output: outputStr?.slice(0, 4000), error: /^(Error:|error:)/i.test((outputStr || '').trim()) || undefined },
|
|
496
|
+
})
|
|
497
|
+
// Enriched file_op logging for file-mutating tools
|
|
498
|
+
if (['write_file', 'edit_file', 'copy_file', 'move_file', 'delete_file'].includes(toolName)) {
|
|
499
|
+
const inputData = event.data?.input
|
|
500
|
+
const inputObj = typeof inputData === 'object' ? inputData : {}
|
|
501
|
+
logExecution(session.id, 'file_op', `${toolName}: ${inputObj?.filePath || inputObj?.sourcePath || 'unknown'}`, {
|
|
502
|
+
agentId: session.agentId,
|
|
503
|
+
detail: { toolName, filePath: inputObj?.filePath, sourcePath: inputObj?.sourcePath, destinationPath: inputObj?.destinationPath, success: !/^Error/i.test((outputStr || '').trim()) },
|
|
504
|
+
})
|
|
505
|
+
}
|
|
506
|
+
// Enriched commit logging for git operations
|
|
507
|
+
if (toolName === 'execute_command' && outputStr) {
|
|
508
|
+
const commitMatch = outputStr.match(/\[[\w/-]+\s+([a-f0-9]{7,40})\]/)
|
|
509
|
+
if (commitMatch) {
|
|
510
|
+
logExecution(session.id, 'commit', `git commit ${commitMatch[1]}`, {
|
|
511
|
+
agentId: session.agentId,
|
|
512
|
+
detail: { commitId: commitMatch[1], outputPreview: outputStr.slice(0, 500) },
|
|
513
|
+
})
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
write(`data: ${JSON.stringify({
|
|
517
|
+
t: 'tool_result',
|
|
518
|
+
toolName,
|
|
519
|
+
toolOutput: outputStr?.slice(0, 2000),
|
|
520
|
+
})}\n\n`)
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
} catch (err: any) {
|
|
524
|
+
const errMsg = timedOut
|
|
525
|
+
? 'Ongoing loop stopped after reaching the configured runtime limit.'
|
|
526
|
+
: err.message || String(err)
|
|
527
|
+
logExecution(session.id, 'error', errMsg, { agentId: session.agentId, detail: { timedOut } })
|
|
528
|
+
write(`data: ${JSON.stringify({ t: 'err', text: errMsg })}\n\n`)
|
|
529
|
+
} finally {
|
|
530
|
+
if (loopTimer) clearTimeout(loopTimer)
|
|
531
|
+
if (signal) signal.removeEventListener('abort', abortFromSignal)
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// Track cost
|
|
535
|
+
const totalTokens = totalInputTokens + totalOutputTokens
|
|
536
|
+
if (totalTokens > 0) {
|
|
537
|
+
const cost = estimateCost(session.model, totalInputTokens, totalOutputTokens)
|
|
538
|
+
const usageRecord: UsageRecord = {
|
|
539
|
+
sessionId: session.id,
|
|
540
|
+
messageIndex: history.length,
|
|
541
|
+
model: session.model,
|
|
542
|
+
provider: session.provider,
|
|
543
|
+
inputTokens: totalInputTokens,
|
|
544
|
+
outputTokens: totalOutputTokens,
|
|
545
|
+
totalTokens,
|
|
546
|
+
estimatedCost: cost,
|
|
547
|
+
timestamp: Date.now(),
|
|
548
|
+
}
|
|
549
|
+
appendUsage(session.id, usageRecord)
|
|
550
|
+
// Send usage metadata to client
|
|
551
|
+
write(`data: ${JSON.stringify({
|
|
552
|
+
t: 'md',
|
|
553
|
+
text: JSON.stringify({ usage: { inputTokens: totalInputTokens, outputTokens: totalOutputTokens, totalTokens, estimatedCost: cost } }),
|
|
554
|
+
})}\n\n`)
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
// Plugin hooks: afterAgentComplete
|
|
558
|
+
await pluginMgr.runHook('afterAgentComplete', { session, response: fullText })
|
|
559
|
+
|
|
560
|
+
// Clean up browser and other session resources
|
|
561
|
+
await cleanup()
|
|
562
|
+
|
|
563
|
+
// If tools were called, finalResponse is the text from the last LLM turn only.
|
|
564
|
+
// Fall back to fullText if the last segment is empty (e.g. agent ended on a tool call
|
|
565
|
+
// with no summary text).
|
|
566
|
+
const finalResponse = hasToolCalls
|
|
567
|
+
? (lastSegment.trim() || fullText)
|
|
568
|
+
: fullText
|
|
569
|
+
|
|
570
|
+
return { fullText, finalResponse }
|
|
571
|
+
}
|