@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,431 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
import { tool } from '@langchain/core/tools'
|
|
3
|
+
import { createReactAgent } from '@langchain/langgraph/prebuilt'
|
|
4
|
+
import { loadSessions, saveSessions, loadAgents, loadCredentials, loadSettings, loadSecrets, loadTasks, saveTasks, decryptKey, loadSkills } from './storage'
|
|
5
|
+
import { loadRuntimeSettings, getOrchestratorLoopRecursionLimit } from './runtime-settings'
|
|
6
|
+
import { getMemoryDb } from './memory-db'
|
|
7
|
+
import { buildChatModel } from './build-llm'
|
|
8
|
+
import crypto from 'crypto'
|
|
9
|
+
import type { Agent, TaskComment } from '@/types'
|
|
10
|
+
|
|
11
|
+
const NON_LANGGRAPH_PROVIDER_IDS = new Set(['claude-cli', 'codex-cli', 'opencode-cli'])
|
|
12
|
+
|
|
13
|
+
function resolveCredential(credentialId: string | null | undefined): string | null {
|
|
14
|
+
if (!credentialId) return null
|
|
15
|
+
const creds = loadCredentials()
|
|
16
|
+
const cred = creds[credentialId]
|
|
17
|
+
if (!cred?.encryptedKey) return null
|
|
18
|
+
try { return decryptKey(cred.encryptedKey) } catch { return null }
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Resolve which provider/model/key the orchestration routing layer should use */
|
|
22
|
+
function getOrchestrationEngineConfig(orchestrator: Agent): { provider: string; model: string; apiKey: string | null; apiEndpoint: string | null } {
|
|
23
|
+
const settings = loadSettings()
|
|
24
|
+
const configuredProvider = typeof settings.langGraphProvider === 'string'
|
|
25
|
+
? settings.langGraphProvider.trim()
|
|
26
|
+
: ''
|
|
27
|
+
const configuredModel = typeof settings.langGraphModel === 'string'
|
|
28
|
+
? settings.langGraphModel.trim()
|
|
29
|
+
: ''
|
|
30
|
+
const configuredApiKey = resolveCredential(settings.langGraphCredentialId)
|
|
31
|
+
const configuredEndpoint = typeof settings.langGraphEndpoint === 'string' && settings.langGraphEndpoint.trim()
|
|
32
|
+
? settings.langGraphEndpoint.trim()
|
|
33
|
+
: null
|
|
34
|
+
|
|
35
|
+
const fallbackProvider = orchestrator.provider === 'claude-cli' ? 'anthropic' : orchestrator.provider
|
|
36
|
+
const fallbackModel = orchestrator.model || ''
|
|
37
|
+
const fallbackApiKey = resolveCredential(orchestrator.credentialId)
|
|
38
|
+
const fallbackEndpoint = orchestrator.apiEndpoint || null
|
|
39
|
+
|
|
40
|
+
const useConfiguredEngine = configuredProvider.length > 0 && !NON_LANGGRAPH_PROVIDER_IDS.has(configuredProvider)
|
|
41
|
+
|
|
42
|
+
if (useConfiguredEngine) {
|
|
43
|
+
return {
|
|
44
|
+
provider: configuredProvider,
|
|
45
|
+
model: configuredModel,
|
|
46
|
+
apiKey: configuredApiKey,
|
|
47
|
+
apiEndpoint: configuredEndpoint,
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
provider: fallbackProvider,
|
|
53
|
+
model: fallbackModel,
|
|
54
|
+
apiKey: fallbackApiKey,
|
|
55
|
+
apiEndpoint: fallbackEndpoint,
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Resolve secrets available to this orchestrator */
|
|
60
|
+
function getSecretsForOrchestrator(orchestratorId: string): { name: string; service: string; value: string }[] {
|
|
61
|
+
const allSecrets = loadSecrets()
|
|
62
|
+
const result: { name: string; service: string; value: string }[] = []
|
|
63
|
+
for (const secret of Object.values(allSecrets) as any[]) {
|
|
64
|
+
const isGlobal = secret.scope === 'global'
|
|
65
|
+
const isScoped = secret.scope === 'agent' && secret.agentIds?.includes(orchestratorId)
|
|
66
|
+
if (isGlobal || isScoped) {
|
|
67
|
+
try {
|
|
68
|
+
const value = decryptKey(secret.encryptedValue)
|
|
69
|
+
result.push({ name: secret.name, service: secret.service, value })
|
|
70
|
+
} catch { /* skip if decrypt fails */ }
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return result
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function saveMessage(sessionId: string, role: 'user' | 'assistant', text: string) {
|
|
77
|
+
const sessions = loadSessions()
|
|
78
|
+
const session = sessions[sessionId]
|
|
79
|
+
if (!session) return
|
|
80
|
+
session.messages.push({ role, text, time: Date.now() })
|
|
81
|
+
session.lastActiveAt = Date.now()
|
|
82
|
+
saveSessions(sessions)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Import the existing sub-task execution from the old orchestrator */
|
|
86
|
+
async function executeSubTaskViaCli(agent: Agent, task: string, parentSessionId: string): Promise<string> {
|
|
87
|
+
// Dynamic import to avoid circular deps
|
|
88
|
+
const { callProvider } = await import('./orchestrator')
|
|
89
|
+
const crypto = await import('crypto')
|
|
90
|
+
const { loadSessions: ls, saveSessions: ss } = await import('./storage')
|
|
91
|
+
|
|
92
|
+
const sessions = ls()
|
|
93
|
+
const parentSession = sessions[parentSessionId]
|
|
94
|
+
const childId = crypto.randomBytes(4).toString('hex')
|
|
95
|
+
sessions[childId] = {
|
|
96
|
+
id: childId,
|
|
97
|
+
name: `[Agent] ${agent.name}: ${task.slice(0, 40)}`,
|
|
98
|
+
cwd: parentSession?.cwd || process.cwd(),
|
|
99
|
+
user: 'system',
|
|
100
|
+
provider: agent.provider,
|
|
101
|
+
model: agent.model,
|
|
102
|
+
credentialId: agent.credentialId || null,
|
|
103
|
+
apiEndpoint: agent.apiEndpoint || null,
|
|
104
|
+
claudeSessionId: null,
|
|
105
|
+
codexThreadId: null,
|
|
106
|
+
opencodeSessionId: null,
|
|
107
|
+
delegateResumeIds: {
|
|
108
|
+
claudeCode: null,
|
|
109
|
+
codex: null,
|
|
110
|
+
opencode: null,
|
|
111
|
+
},
|
|
112
|
+
messages: [],
|
|
113
|
+
createdAt: Date.now(),
|
|
114
|
+
lastActiveAt: Date.now(),
|
|
115
|
+
sessionType: 'orchestrated' as const,
|
|
116
|
+
agentId: agent.id,
|
|
117
|
+
parentSessionId,
|
|
118
|
+
tools: agent.tools || [],
|
|
119
|
+
}
|
|
120
|
+
ss(sessions)
|
|
121
|
+
|
|
122
|
+
const result = await callProvider(agent, agent.systemPrompt, [{ role: 'user', text: task }])
|
|
123
|
+
|
|
124
|
+
const s2 = ls()
|
|
125
|
+
if (s2[childId]) {
|
|
126
|
+
s2[childId].messages.push({ role: 'user', text: task, time: Date.now() })
|
|
127
|
+
s2[childId].messages.push({ role: 'assistant', text: result, time: Date.now() })
|
|
128
|
+
s2[childId].lastActiveAt = Date.now()
|
|
129
|
+
ss(s2)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return result
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export async function executeLangGraphOrchestrator(
|
|
136
|
+
orchestrator: Agent,
|
|
137
|
+
task: string,
|
|
138
|
+
sessionId: string,
|
|
139
|
+
): Promise<string> {
|
|
140
|
+
const allAgents = loadAgents()
|
|
141
|
+
|
|
142
|
+
// Build available agents list
|
|
143
|
+
const agentIds = orchestrator.subAgentIds || []
|
|
144
|
+
const agents = agentIds.map((id) => allAgents[id]).filter(Boolean) as Agent[]
|
|
145
|
+
const agentListContext = agents.length
|
|
146
|
+
? '\n\nAvailable agents:\n' + agents.map((a) => {
|
|
147
|
+
const tools = a.tools?.length ? ` [tools: ${a.tools.join(', ')}]` : ''
|
|
148
|
+
const skills = a.skills?.length ? ` [skills: ${a.skills.join(', ')}]` : ''
|
|
149
|
+
return `- ${a.name}: ${a.description}${tools}${skills}`
|
|
150
|
+
}).join('\n')
|
|
151
|
+
: '\n\n(No agents available for delegation.)'
|
|
152
|
+
|
|
153
|
+
// Load relevant memories
|
|
154
|
+
const db = getMemoryDb()
|
|
155
|
+
const memories = db.getByAgent(orchestrator.id)
|
|
156
|
+
const memoryContext = memories.length
|
|
157
|
+
? '\n\nRelevant memories:\n' + memories.slice(0, 10).map((m) => `[${m.category}] ${m.title}: ${m.content.slice(0, 200)}`).join('\n')
|
|
158
|
+
: ''
|
|
159
|
+
|
|
160
|
+
// Define tools
|
|
161
|
+
const delegateTool = tool(
|
|
162
|
+
async ({ agentName, task: agentTask }) => {
|
|
163
|
+
const agent = agents.find((a) => a.name.toLowerCase() === agentName.toLowerCase())
|
|
164
|
+
if (!agent) {
|
|
165
|
+
return `Agent "${agentName}" not found. Available: ${agents.map((a) => a.name).join(', ')}`
|
|
166
|
+
}
|
|
167
|
+
console.log(`[orchestrator-lg] Delegating to ${agent.name}: ${agentTask.slice(0, 80)}`)
|
|
168
|
+
saveMessage(sessionId, 'assistant', `[Delegating to ${agent.name}]: ${agentTask}`)
|
|
169
|
+
const result = await executeSubTaskViaCli(agent, agentTask, sessionId)
|
|
170
|
+
saveMessage(sessionId, 'user', `[Agent ${agent.name} result]: ${result.slice(0, 2000)}`)
|
|
171
|
+
return result
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
name: 'delegate_to_agent',
|
|
175
|
+
description: 'Delegate a task to one of the available agents. The agent will execute the task and return its result.',
|
|
176
|
+
schema: z.object({
|
|
177
|
+
agentName: z.string().describe('Name of the agent to delegate to'),
|
|
178
|
+
task: z.string().describe('The task description for the agent'),
|
|
179
|
+
}),
|
|
180
|
+
},
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
const storeMemoryTool = tool(
|
|
184
|
+
async ({ category, title, content }) => {
|
|
185
|
+
db.add({
|
|
186
|
+
agentId: orchestrator.id,
|
|
187
|
+
sessionId,
|
|
188
|
+
category,
|
|
189
|
+
title,
|
|
190
|
+
content,
|
|
191
|
+
})
|
|
192
|
+
console.log(`[orchestrator-lg] Stored memory: [${category}] ${title}`)
|
|
193
|
+
return 'Memory stored successfully.'
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
name: 'store_memory',
|
|
197
|
+
description: 'Store information in long-term memory for future reference.',
|
|
198
|
+
schema: z.object({
|
|
199
|
+
category: z.string().describe('Category keyword (e.g. "seo", "deployment", "finding")'),
|
|
200
|
+
title: z.string().describe('Short descriptive title'),
|
|
201
|
+
content: z.string().describe('The content to remember'),
|
|
202
|
+
}),
|
|
203
|
+
},
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
const searchMemoryTool = tool(
|
|
207
|
+
async ({ query }) => {
|
|
208
|
+
const results = db.search(query, orchestrator.id)
|
|
209
|
+
if (!results.length) return 'No matching memories found.'
|
|
210
|
+
return results.map((m) => `[${m.category}] ${m.title}: ${m.content.slice(0, 300)}`).join('\n')
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
name: 'search_memory',
|
|
214
|
+
description: 'Search long-term memory for relevant information.',
|
|
215
|
+
schema: z.object({
|
|
216
|
+
query: z.string().describe('Search terms'),
|
|
217
|
+
}),
|
|
218
|
+
},
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
const markCompleteTool = tool(
|
|
222
|
+
async ({ summary }) => {
|
|
223
|
+
console.log(`[orchestrator-lg] Marked complete: ${summary.slice(0, 100)}`)
|
|
224
|
+
return `ORCHESTRATION_COMPLETE: ${summary}`
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
name: 'mark_complete',
|
|
228
|
+
description: 'Signal that the orchestration task is done. Call this when all work is finished.',
|
|
229
|
+
schema: z.object({
|
|
230
|
+
summary: z.string().describe('Summary of what was accomplished'),
|
|
231
|
+
}),
|
|
232
|
+
},
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
// Secrets
|
|
236
|
+
const availableSecrets = getSecretsForOrchestrator(orchestrator.id)
|
|
237
|
+
|
|
238
|
+
const getSecretTool = tool(
|
|
239
|
+
async ({ serviceName }) => {
|
|
240
|
+
const match = availableSecrets.find(
|
|
241
|
+
(s) => s.service.toLowerCase() === serviceName.toLowerCase() || s.name.toLowerCase() === serviceName.toLowerCase(),
|
|
242
|
+
)
|
|
243
|
+
if (!match) {
|
|
244
|
+
return `No secret found for "${serviceName}". Available services: ${availableSecrets.map((s) => s.service).join(', ') || 'none'}`
|
|
245
|
+
}
|
|
246
|
+
console.log(`[orchestrator-lg] Retrieved secret for service: ${match.service}`)
|
|
247
|
+
return JSON.stringify({ name: match.name, service: match.service, value: match.value })
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
name: 'get_secret',
|
|
251
|
+
description: 'Retrieve a stored credential/secret by service name (e.g. "gmail", "ahrefs"). Returns the decrypted value. Use this when you need API keys or login credentials for external services.',
|
|
252
|
+
schema: z.object({
|
|
253
|
+
serviceName: z.string().describe('The service name or secret name to look up'),
|
|
254
|
+
}),
|
|
255
|
+
},
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
// Task board tools
|
|
259
|
+
const commentOnTaskTool = tool(
|
|
260
|
+
async ({ taskId, comment }) => {
|
|
261
|
+
const tasks = loadTasks()
|
|
262
|
+
const t = tasks[taskId]
|
|
263
|
+
if (!t) return `Task "${taskId}" not found.`
|
|
264
|
+
if (!t.comments) t.comments = []
|
|
265
|
+
const c: TaskComment = {
|
|
266
|
+
id: crypto.randomBytes(4).toString('hex'),
|
|
267
|
+
author: orchestrator.name,
|
|
268
|
+
agentId: orchestrator.id,
|
|
269
|
+
text: comment,
|
|
270
|
+
createdAt: Date.now(),
|
|
271
|
+
}
|
|
272
|
+
t.comments.push(c)
|
|
273
|
+
t.updatedAt = Date.now()
|
|
274
|
+
saveTasks(tasks)
|
|
275
|
+
console.log(`[orchestrator-lg] Commented on task "${t.title}": ${comment.slice(0, 80)}`)
|
|
276
|
+
return `Comment added to task "${t.title}".`
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
name: 'comment_on_task',
|
|
280
|
+
description: 'Add a comment to a task on the task board. Use this to provide status updates, ask questions, or leave notes for the user.',
|
|
281
|
+
schema: z.object({
|
|
282
|
+
taskId: z.string().describe('The task ID to comment on'),
|
|
283
|
+
comment: z.string().describe('The comment text'),
|
|
284
|
+
}),
|
|
285
|
+
},
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
const createTaskTool = tool(
|
|
289
|
+
async ({ title, description: desc }) => {
|
|
290
|
+
const tasks = loadTasks()
|
|
291
|
+
const id = crypto.randomBytes(4).toString('hex')
|
|
292
|
+
tasks[id] = {
|
|
293
|
+
id,
|
|
294
|
+
title,
|
|
295
|
+
description: desc,
|
|
296
|
+
status: 'backlog',
|
|
297
|
+
agentId: orchestrator.id,
|
|
298
|
+
sessionId: null,
|
|
299
|
+
result: null,
|
|
300
|
+
error: null,
|
|
301
|
+
comments: [],
|
|
302
|
+
createdAt: Date.now(),
|
|
303
|
+
updatedAt: Date.now(),
|
|
304
|
+
queuedAt: null,
|
|
305
|
+
startedAt: null,
|
|
306
|
+
completedAt: null,
|
|
307
|
+
}
|
|
308
|
+
saveTasks(tasks)
|
|
309
|
+
console.log(`[orchestrator-lg] Created backlog task: "${title}" (${id})`)
|
|
310
|
+
return `Task "${title}" created in backlog (id: ${id}). The user can review and queue it.`
|
|
311
|
+
},
|
|
312
|
+
{
|
|
313
|
+
name: 'create_task',
|
|
314
|
+
description: 'Create a new task in the backlog for the user to review. Use this when you identify follow-up work, need user input, or want to suggest next steps.',
|
|
315
|
+
schema: z.object({
|
|
316
|
+
title: z.string().describe('Short task title'),
|
|
317
|
+
description: z.string().describe('Detailed description of what needs to be done'),
|
|
318
|
+
}),
|
|
319
|
+
},
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
// Build secrets context for the system prompt
|
|
323
|
+
const secretsContext = availableSecrets.length
|
|
324
|
+
? '\n\nAvailable secrets (use get_secret tool to retrieve values):\n' + availableSecrets.map((s) => `- ${s.name} (${s.service})`).join('\n')
|
|
325
|
+
: ''
|
|
326
|
+
|
|
327
|
+
// Build task context
|
|
328
|
+
const allTasks = loadTasks()
|
|
329
|
+
const taskList = Object.values(allTasks)
|
|
330
|
+
const taskContext = taskList.length
|
|
331
|
+
? '\n\nCurrent task board:\n' + taskList.slice(0, 20).map((t: any) => `- [${t.status}] "${t.title}" (id: ${t.id})`).join('\n')
|
|
332
|
+
: ''
|
|
333
|
+
|
|
334
|
+
// Build routing LLM from Settings -> Orchestrator Engine (fallback: orchestrator's own provider)
|
|
335
|
+
const engine = getOrchestrationEngineConfig(orchestrator)
|
|
336
|
+
const llm = buildChatModel({
|
|
337
|
+
provider: engine.provider,
|
|
338
|
+
model: engine.model,
|
|
339
|
+
apiKey: engine.apiKey,
|
|
340
|
+
apiEndpoint: engine.apiEndpoint,
|
|
341
|
+
})
|
|
342
|
+
// Build system message: [userPrompt] \n\n [soul] \n\n [systemPrompt] \n\n [orchestrator context]
|
|
343
|
+
const settings = loadSettings()
|
|
344
|
+
const promptParts: string[] = []
|
|
345
|
+
if (settings.userPrompt) promptParts.push(settings.userPrompt)
|
|
346
|
+
if (orchestrator.soul) promptParts.push(orchestrator.soul)
|
|
347
|
+
if (orchestrator.systemPrompt) promptParts.push(orchestrator.systemPrompt)
|
|
348
|
+
// Inject dynamic skills
|
|
349
|
+
if (orchestrator.skillIds?.length) {
|
|
350
|
+
const allSkills = loadSkills()
|
|
351
|
+
for (const skillId of orchestrator.skillIds) {
|
|
352
|
+
const skill = allSkills[skillId]
|
|
353
|
+
if (skill?.content) promptParts.push(`## Skill: ${skill.name}\n${skill.content}`)
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
const basePrompt = promptParts.join('\n\n')
|
|
357
|
+
|
|
358
|
+
const systemMessage = [
|
|
359
|
+
basePrompt,
|
|
360
|
+
'\nYou are an orchestrator. Use the provided tools to delegate tasks to agents, store and search memories, retrieve secrets for external services, and mark tasks complete.',
|
|
361
|
+
'\nAgents with [tools: browser] have access to a Playwright browser and can navigate websites, scrape data, fill forms, take screenshots, and interact with web pages.',
|
|
362
|
+
'\nAgents with [skills: ...] have specialized Claude Code skills. When delegating to them, mention the skill in your task instructions. For example, if an agent has [skills: frontend-design], tell it "Use /frontend-design to build this UI". Available skills: frontend-design (production-grade UI), site-builder (build from spec), site-tester (QA in Chrome), seo-site-auditor (SEO audit), keyword-researcher (keyword research via Ahrefs).',
|
|
363
|
+
'\nWhen delegating to an agent that needs credentials, use get_secret first and include the credential in the task instructions.',
|
|
364
|
+
'\nYou can comment on tasks to provide status updates and create new backlog tasks for follow-up work.',
|
|
365
|
+
'\nAlways call mark_complete when you are done.',
|
|
366
|
+
agentListContext,
|
|
367
|
+
memoryContext,
|
|
368
|
+
secretsContext,
|
|
369
|
+
taskContext,
|
|
370
|
+
].join('\n')
|
|
371
|
+
|
|
372
|
+
const agent = createReactAgent({
|
|
373
|
+
llm,
|
|
374
|
+
tools: [delegateTool, storeMemoryTool, searchMemoryTool, getSecretTool, commentOnTaskTool, createTaskTool, markCompleteTool],
|
|
375
|
+
stateModifier: systemMessage,
|
|
376
|
+
})
|
|
377
|
+
|
|
378
|
+
// Save initial user message
|
|
379
|
+
saveMessage(sessionId, 'user', task)
|
|
380
|
+
|
|
381
|
+
let finalResult = ''
|
|
382
|
+
const runtime = loadRuntimeSettings()
|
|
383
|
+
const recursionLimit = getOrchestratorLoopRecursionLimit(runtime)
|
|
384
|
+
const abortController = new AbortController()
|
|
385
|
+
let timedOut = false
|
|
386
|
+
const loopTimer = runtime.loopMode === 'ongoing' && runtime.ongoingLoopMaxRuntimeMs
|
|
387
|
+
? setTimeout(() => {
|
|
388
|
+
timedOut = true
|
|
389
|
+
abortController.abort()
|
|
390
|
+
}, runtime.ongoingLoopMaxRuntimeMs)
|
|
391
|
+
: null
|
|
392
|
+
|
|
393
|
+
try {
|
|
394
|
+
const stream = await agent.stream(
|
|
395
|
+
{ messages: [{ role: 'user' as const, content: task }] },
|
|
396
|
+
{ recursionLimit, signal: abortController.signal },
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
for await (const chunk of stream) {
|
|
400
|
+
// chunk has 'agent' or 'tools' keys with messages arrays
|
|
401
|
+
const agentChunk = (chunk as any).agent
|
|
402
|
+
if (agentChunk?.messages) {
|
|
403
|
+
const msgs = Array.isArray(agentChunk.messages) ? agentChunk.messages : [agentChunk.messages]
|
|
404
|
+
for (const msg of msgs) {
|
|
405
|
+
const text = typeof msg.content === 'string'
|
|
406
|
+
? msg.content
|
|
407
|
+
: Array.isArray(msg.content)
|
|
408
|
+
? msg.content.map((c: any) => c.text || '').join('')
|
|
409
|
+
: ''
|
|
410
|
+
if (text) {
|
|
411
|
+
finalResult = text
|
|
412
|
+
saveMessage(sessionId, 'assistant', text)
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
} catch (err: any) {
|
|
418
|
+
const errMsg = timedOut
|
|
419
|
+
? 'Ongoing loop stopped after reaching the configured runtime limit.'
|
|
420
|
+
: err.message || String(err)
|
|
421
|
+
console.error(`[orchestrator-lg] Error:`, errMsg)
|
|
422
|
+
saveMessage(sessionId, 'assistant', `[Error] ${errMsg}`)
|
|
423
|
+
throw new Error(errMsg)
|
|
424
|
+
} finally {
|
|
425
|
+
if (loopTimer) clearTimeout(loopTimer)
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// Extract summary from mark_complete if present
|
|
429
|
+
const completeMatch = finalResult.match(/ORCHESTRATION_COMPLETE:\s*([\s\S]+)/)
|
|
430
|
+
return completeMatch ? completeMatch[1].trim() : finalResult
|
|
431
|
+
}
|