@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,200 @@
|
|
|
1
|
+
import type { GoalContract } from '@/types'
|
|
2
|
+
|
|
3
|
+
const PLAN_LINE_RE = /\[MAIN_LOOP_PLAN\]\s*(\{[^\n]*\})/i
|
|
4
|
+
const REVIEW_LINE_RE = /\[MAIN_LOOP_REVIEW\]\s*(\{[^\n]*\})/i
|
|
5
|
+
|
|
6
|
+
export interface MainLoopPlanMeta {
|
|
7
|
+
steps?: string[]
|
|
8
|
+
current_step?: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface MainLoopReviewMeta {
|
|
12
|
+
note?: string
|
|
13
|
+
confidence?: number
|
|
14
|
+
needs_replan?: boolean
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function cleanText(value: string, max = 400): string {
|
|
18
|
+
return (value || '').replace(/\s+/g, ' ').trim().slice(0, max)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function uniqueStrings(input: string[]): string[] {
|
|
22
|
+
const seen = new Set<string>()
|
|
23
|
+
const out: string[] = []
|
|
24
|
+
for (const value of input) {
|
|
25
|
+
const normalized = cleanText(value, 240)
|
|
26
|
+
if (!normalized || seen.has(normalized.toLowerCase())) continue
|
|
27
|
+
seen.add(normalized.toLowerCase())
|
|
28
|
+
out.push(normalized)
|
|
29
|
+
}
|
|
30
|
+
return out
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function safeJsonParse<T>(value: string): T | null {
|
|
34
|
+
try {
|
|
35
|
+
const parsed = JSON.parse(value)
|
|
36
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) return parsed as T
|
|
37
|
+
} catch {
|
|
38
|
+
// ignore malformed json
|
|
39
|
+
}
|
|
40
|
+
return null
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function parseTaggedJsonLine<T>(text: string, tagRegex: RegExp): T | null {
|
|
44
|
+
const raw = (text || '').trim()
|
|
45
|
+
if (!raw) return null
|
|
46
|
+
const tagged = raw.match(tagRegex)?.[1]
|
|
47
|
+
if (tagged) {
|
|
48
|
+
const parsed = safeJsonParse<T>(tagged)
|
|
49
|
+
if (parsed) return parsed
|
|
50
|
+
}
|
|
51
|
+
for (const line of raw.split('\n')) {
|
|
52
|
+
const trimmed = line.trim()
|
|
53
|
+
if (!trimmed.startsWith('{') || !trimmed.endsWith('}')) continue
|
|
54
|
+
const parsed = safeJsonParse<T>(trimmed)
|
|
55
|
+
if (parsed) return parsed
|
|
56
|
+
}
|
|
57
|
+
return null
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function parseBudgetUsd(text: string): number | null {
|
|
61
|
+
const patterns = [
|
|
62
|
+
/budget[^$\d]{0,20}\$?\s*(\d+(?:\.\d+)?)/i,
|
|
63
|
+
/\$\s*(\d+(?:\.\d+)?)/,
|
|
64
|
+
/(\d+(?:\.\d+)?)\s*(usd|dollars?)/i,
|
|
65
|
+
]
|
|
66
|
+
for (const re of patterns) {
|
|
67
|
+
const m = text.match(re)
|
|
68
|
+
if (!m?.[1]) continue
|
|
69
|
+
const num = Number.parseFloat(m[1])
|
|
70
|
+
if (!Number.isFinite(num)) continue
|
|
71
|
+
return Math.max(0, Math.min(1_000_000, num))
|
|
72
|
+
}
|
|
73
|
+
return null
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function parseDeadlineAt(text: string): number | null {
|
|
77
|
+
const patterns = [
|
|
78
|
+
/\bby\s+([A-Za-z]{3,10}\s+\d{1,2},?\s+\d{4})/i,
|
|
79
|
+
/\bby\s+(\d{4}-\d{2}-\d{2})/i,
|
|
80
|
+
/\bdeadline[^A-Za-z0-9]{0,8}([A-Za-z]{3,10}\s+\d{1,2},?\s+\d{4})/i,
|
|
81
|
+
/\bdeadline[^A-Za-z0-9]{0,8}(\d{4}-\d{2}-\d{2})/i,
|
|
82
|
+
]
|
|
83
|
+
for (const re of patterns) {
|
|
84
|
+
const value = text.match(re)?.[1]
|
|
85
|
+
if (!value) continue
|
|
86
|
+
const ts = Date.parse(value)
|
|
87
|
+
if (!Number.isFinite(ts)) continue
|
|
88
|
+
return ts
|
|
89
|
+
}
|
|
90
|
+
return null
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function parseSuccessMetric(text: string): string | null {
|
|
94
|
+
const patterns = [
|
|
95
|
+
/success(?:\s+is|\s+means|\s+metric)?\s*[:=-]\s*([^\n.]{4,180})/i,
|
|
96
|
+
/metric\s*[:=-]\s*([^\n.]{4,180})/i,
|
|
97
|
+
/kpi\s*[:=-]\s*([^\n.]{4,180})/i,
|
|
98
|
+
]
|
|
99
|
+
for (const re of patterns) {
|
|
100
|
+
const value = cleanText(text.match(re)?.[1] || '', 180)
|
|
101
|
+
if (value) return value
|
|
102
|
+
}
|
|
103
|
+
return null
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function parseObjective(text: string): string | null {
|
|
107
|
+
const direct = cleanText(text, 300)
|
|
108
|
+
if (!direct) return null
|
|
109
|
+
const firstSentence = direct.split(/(?<=[.!?])\s+/)[0]?.trim() || direct
|
|
110
|
+
return cleanText(firstSentence, 300) || null
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function parseConstraints(text: string): string[] {
|
|
114
|
+
const constraints: string[] = []
|
|
115
|
+
const lines = text.split('\n').map((line) => line.trim()).filter(Boolean)
|
|
116
|
+
for (const line of lines) {
|
|
117
|
+
if (/^(must|should|avoid|without|do not|don't|within|under|limit)/i.test(line)) {
|
|
118
|
+
constraints.push(line.replace(/^[-*]\s*/, ''))
|
|
119
|
+
continue
|
|
120
|
+
}
|
|
121
|
+
if (/constraint[s]?\s*[:=-]/i.test(line)) {
|
|
122
|
+
const value = line.split(/[:=-]/).slice(1).join(':').trim()
|
|
123
|
+
if (value) constraints.push(value)
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return uniqueStrings(constraints).slice(0, 8)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function parseGoalContractFromText(text: string): GoalContract | null {
|
|
130
|
+
const objective = parseObjective(text || '')
|
|
131
|
+
if (!objective) return null
|
|
132
|
+
const constraints = parseConstraints(text || '')
|
|
133
|
+
const budgetUsd = parseBudgetUsd(text || '')
|
|
134
|
+
const deadlineAt = parseDeadlineAt(text || '')
|
|
135
|
+
const successMetric = parseSuccessMetric(text || '')
|
|
136
|
+
return {
|
|
137
|
+
objective,
|
|
138
|
+
constraints: constraints.length ? constraints : undefined,
|
|
139
|
+
budgetUsd: budgetUsd ?? null,
|
|
140
|
+
deadlineAt: deadlineAt ?? null,
|
|
141
|
+
successMetric: successMetric || null,
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function mergeGoalContracts(
|
|
146
|
+
current: GoalContract | null | undefined,
|
|
147
|
+
next: GoalContract | null | undefined,
|
|
148
|
+
): GoalContract | null {
|
|
149
|
+
if (!current && !next) return null
|
|
150
|
+
if (!current) return next || null
|
|
151
|
+
if (!next) return current
|
|
152
|
+
return {
|
|
153
|
+
objective: next.objective || current.objective,
|
|
154
|
+
constraints: next.constraints?.length ? next.constraints : current.constraints,
|
|
155
|
+
budgetUsd: next.budgetUsd ?? current.budgetUsd ?? null,
|
|
156
|
+
deadlineAt: next.deadlineAt ?? current.deadlineAt ?? null,
|
|
157
|
+
successMetric: next.successMetric || current.successMetric || null,
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export function parseMainLoopPlan(text: string): MainLoopPlanMeta | null {
|
|
162
|
+
const parsed = parseTaggedJsonLine<Record<string, unknown>>(text, PLAN_LINE_RE)
|
|
163
|
+
if (!parsed) return null
|
|
164
|
+
const steps = Array.isArray(parsed.steps)
|
|
165
|
+
? uniqueStrings(parsed.steps.filter((v): v is string => typeof v === 'string')).slice(0, 8)
|
|
166
|
+
: []
|
|
167
|
+
const currentStep = typeof parsed.current_step === 'string'
|
|
168
|
+
? cleanText(parsed.current_step, 220)
|
|
169
|
+
: ''
|
|
170
|
+
if (!steps.length && !currentStep) return null
|
|
171
|
+
return {
|
|
172
|
+
steps: steps.length ? steps : undefined,
|
|
173
|
+
current_step: currentStep || undefined,
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export function parseMainLoopReview(text: string): MainLoopReviewMeta | null {
|
|
178
|
+
const parsed = parseTaggedJsonLine<Record<string, unknown>>(text, REVIEW_LINE_RE)
|
|
179
|
+
if (!parsed) return null
|
|
180
|
+
const note = typeof parsed.note === 'string' ? cleanText(parsed.note, 320) : ''
|
|
181
|
+
const confidenceRaw = typeof parsed.confidence === 'number'
|
|
182
|
+
? parsed.confidence
|
|
183
|
+
: typeof parsed.confidence === 'string'
|
|
184
|
+
? Number.parseFloat(parsed.confidence)
|
|
185
|
+
: Number.NaN
|
|
186
|
+
const confidence = Number.isFinite(confidenceRaw)
|
|
187
|
+
? Math.max(0, Math.min(1, confidenceRaw))
|
|
188
|
+
: undefined
|
|
189
|
+
const needsReplan = parsed.needs_replan === true
|
|
190
|
+
? true
|
|
191
|
+
: parsed.needs_replan === false
|
|
192
|
+
? false
|
|
193
|
+
: undefined
|
|
194
|
+
if (!note && typeof confidence !== 'number' && typeof needsReplan !== 'boolean') return null
|
|
195
|
+
return {
|
|
196
|
+
note: note || undefined,
|
|
197
|
+
confidence,
|
|
198
|
+
needs_replan: needsReplan,
|
|
199
|
+
}
|
|
200
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { ChatAnthropic } from '@langchain/anthropic'
|
|
2
|
+
import { ChatOpenAI } from '@langchain/openai'
|
|
3
|
+
import { loadCredentials, decryptKey, loadAgents, loadSettings } from './storage'
|
|
4
|
+
import { getProviderList } from '../providers'
|
|
5
|
+
import { normalizeOpenClawEndpoint } from '../openclaw-endpoint'
|
|
6
|
+
|
|
7
|
+
const OLLAMA_CLOUD_URL = 'https://ollama.com/v1'
|
|
8
|
+
const OLLAMA_LOCAL_URL = 'http://localhost:11434/v1'
|
|
9
|
+
const NON_LANGGRAPH_PROVIDER_IDS = new Set(['claude-cli', 'codex-cli', 'opencode-cli'])
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Build a LangChain chat model from provider config.
|
|
13
|
+
* Uses the provider registry for endpoint defaults — no hardcoded provider list.
|
|
14
|
+
* Anthropic is the only special case (different LangChain class); everything else is OpenAI-compatible.
|
|
15
|
+
*/
|
|
16
|
+
export function buildChatModel(opts: {
|
|
17
|
+
provider: string
|
|
18
|
+
model: string
|
|
19
|
+
apiKey: string | null
|
|
20
|
+
apiEndpoint?: string | null
|
|
21
|
+
}) {
|
|
22
|
+
const { provider, model, apiKey, apiEndpoint } = opts
|
|
23
|
+
const providers = getProviderList()
|
|
24
|
+
const providerInfo = providers.find((p) => p.id === provider)
|
|
25
|
+
const endpointRaw = apiEndpoint || providerInfo?.defaultEndpoint || null
|
|
26
|
+
const endpoint = provider === 'openclaw'
|
|
27
|
+
? normalizeOpenClawEndpoint(endpointRaw)
|
|
28
|
+
: endpointRaw
|
|
29
|
+
|
|
30
|
+
if (provider === 'anthropic') {
|
|
31
|
+
return new ChatAnthropic({
|
|
32
|
+
model: model || 'claude-sonnet-4-6',
|
|
33
|
+
anthropicApiKey: apiKey || undefined,
|
|
34
|
+
maxTokens: 8192,
|
|
35
|
+
})
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (provider === 'ollama') {
|
|
39
|
+
const baseURL = apiKey && apiKey !== 'ollama'
|
|
40
|
+
? OLLAMA_CLOUD_URL
|
|
41
|
+
: (endpoint ? `${endpoint}/v1` : OLLAMA_LOCAL_URL)
|
|
42
|
+
return new ChatOpenAI({
|
|
43
|
+
model: model || 'qwen3.5',
|
|
44
|
+
apiKey: apiKey || 'ollama',
|
|
45
|
+
configuration: { baseURL },
|
|
46
|
+
})
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// All other providers — OpenAI-compatible with their registered endpoint
|
|
50
|
+
const config: any = { model: model || 'gpt-4o', apiKey: apiKey || undefined }
|
|
51
|
+
if (endpoint) {
|
|
52
|
+
config.configuration = { baseURL: endpoint }
|
|
53
|
+
// OpenClaw endpoints behind Hostinger's proxy use express.json() middleware
|
|
54
|
+
// which consumes the request body before http-proxy-middleware can forward it.
|
|
55
|
+
// Sending as text/plain bypasses the body parser while the gateway still parses JSON.
|
|
56
|
+
if (provider === 'openclaw') {
|
|
57
|
+
config.configuration.defaultHeaders = { 'Content-Type': 'text/plain' }
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return config.configuration
|
|
61
|
+
? new ChatOpenAI(config)
|
|
62
|
+
: new ChatOpenAI({ model: config.model, apiKey: config.apiKey })
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function resolveApiKeyFromCredential(credentialId: string | null | undefined): string | null {
|
|
66
|
+
if (!credentialId) return null
|
|
67
|
+
const creds = loadCredentials()
|
|
68
|
+
const cred = creds[credentialId]
|
|
69
|
+
if (!cred?.encryptedKey) return null
|
|
70
|
+
try {
|
|
71
|
+
return decryptKey(cred.encryptedKey)
|
|
72
|
+
} catch {
|
|
73
|
+
return null
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Build a LangChain LLM for generation tasks.
|
|
79
|
+
* Priority:
|
|
80
|
+
* 1) Settings -> Orchestrator Engine (if non-CLI provider configured)
|
|
81
|
+
* 2) Default agent (must be non-CLI)
|
|
82
|
+
*/
|
|
83
|
+
export async function buildLLM() {
|
|
84
|
+
const providers = getProviderList()
|
|
85
|
+
const settings = loadSettings()
|
|
86
|
+
|
|
87
|
+
const configuredProvider = typeof settings.langGraphProvider === 'string'
|
|
88
|
+
? settings.langGraphProvider.trim()
|
|
89
|
+
: ''
|
|
90
|
+
const hasConfiguredProvider = configuredProvider.length > 0 && !NON_LANGGRAPH_PROVIDER_IDS.has(configuredProvider)
|
|
91
|
+
|
|
92
|
+
if (hasConfiguredProvider) {
|
|
93
|
+
const providerInfo = providers.find((p) => p.id === configuredProvider)
|
|
94
|
+
const model = (typeof settings.langGraphModel === 'string' && settings.langGraphModel.trim())
|
|
95
|
+
? settings.langGraphModel.trim()
|
|
96
|
+
: providerInfo?.models?.[0] || ''
|
|
97
|
+
const apiKey = resolveApiKeyFromCredential(settings.langGraphCredentialId)
|
|
98
|
+
const apiEndpoint = (typeof settings.langGraphEndpoint === 'string' && settings.langGraphEndpoint.trim())
|
|
99
|
+
? settings.langGraphEndpoint.trim()
|
|
100
|
+
: providerInfo?.defaultEndpoint || null
|
|
101
|
+
|
|
102
|
+
if (providerInfo?.requiresApiKey && !apiKey) {
|
|
103
|
+
throw new Error(`Orchestrator Engine provider "${providerInfo.name}" requires an API key. Configure one in Settings.`)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
llm: buildChatModel({
|
|
108
|
+
provider: configuredProvider,
|
|
109
|
+
model,
|
|
110
|
+
apiKey,
|
|
111
|
+
apiEndpoint,
|
|
112
|
+
}),
|
|
113
|
+
provider: configuredProvider,
|
|
114
|
+
model,
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const agents = loadAgents()
|
|
119
|
+
const agent = agents.default as {
|
|
120
|
+
provider?: string
|
|
121
|
+
model?: string
|
|
122
|
+
credentialId?: string | null
|
|
123
|
+
apiEndpoint?: string | null
|
|
124
|
+
} | undefined
|
|
125
|
+
|
|
126
|
+
if (!agent) {
|
|
127
|
+
throw new Error('Default agent not found. Configure Orchestrator Engine in Settings.')
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (!agent.provider || NON_LANGGRAPH_PROVIDER_IDS.has(agent.provider)) {
|
|
131
|
+
throw new Error('Generate with AI requires a non-CLI provider. Configure Orchestrator Engine in Settings.')
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const providerInfo = providers.find((p) => p.id === agent.provider)
|
|
135
|
+
const model = (typeof agent.model === 'string' && agent.model.trim())
|
|
136
|
+
? agent.model.trim()
|
|
137
|
+
: providerInfo?.models?.[0] || ''
|
|
138
|
+
const apiKey = resolveApiKeyFromCredential(agent.credentialId)
|
|
139
|
+
const apiEndpoint = agent.apiEndpoint || providerInfo?.defaultEndpoint || null
|
|
140
|
+
|
|
141
|
+
if (providerInfo?.requiresApiKey && !apiKey) {
|
|
142
|
+
throw new Error(`Default agent provider "${providerInfo.name}" requires an API key.`)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return {
|
|
146
|
+
llm: buildChatModel({
|
|
147
|
+
provider: agent.provider,
|
|
148
|
+
model,
|
|
149
|
+
apiKey,
|
|
150
|
+
apiEndpoint: agent.apiEndpoint,
|
|
151
|
+
}),
|
|
152
|
+
provider: agent.provider as string,
|
|
153
|
+
model,
|
|
154
|
+
}
|
|
155
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
import { test } from 'node:test'
|
|
3
|
+
import { routeTaskIntent } from './capability-router.ts'
|
|
4
|
+
|
|
5
|
+
test('routeTaskIntent keeps recall-style prompts as general intent', () => {
|
|
6
|
+
const decision = routeTaskIntent(
|
|
7
|
+
'What token did we store earlier as e2e_validation_token? Reply only with the token.',
|
|
8
|
+
['memory', 'web_search'],
|
|
9
|
+
null,
|
|
10
|
+
)
|
|
11
|
+
assert.equal(decision.intent, 'general')
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
test('routeTaskIntent keeps coding prompts prioritized over memory keywords', () => {
|
|
15
|
+
const decision = routeTaskIntent(
|
|
16
|
+
'Build and test a calculator app, then remember the final path in memory.',
|
|
17
|
+
['memory', 'shell', 'files'],
|
|
18
|
+
null,
|
|
19
|
+
)
|
|
20
|
+
assert.equal(decision.intent, 'coding')
|
|
21
|
+
})
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import type { AppSettings } from '@/types'
|
|
2
|
+
|
|
3
|
+
export type TaskIntent =
|
|
4
|
+
| 'coding'
|
|
5
|
+
| 'research'
|
|
6
|
+
| 'browsing'
|
|
7
|
+
| 'outreach'
|
|
8
|
+
| 'scheduling'
|
|
9
|
+
| 'general'
|
|
10
|
+
|
|
11
|
+
export interface CapabilityRoutingDecision {
|
|
12
|
+
intent: TaskIntent
|
|
13
|
+
confidence: number
|
|
14
|
+
preferredTools: string[]
|
|
15
|
+
preferredDelegates: Array<'delegate_to_claude_code' | 'delegate_to_codex_cli' | 'delegate_to_opencode_cli'>
|
|
16
|
+
primaryUrl?: string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function findFirstUrl(text: string): string | undefined {
|
|
20
|
+
const m = text.match(/https?:\/\/[^\s<>"')]+/i)
|
|
21
|
+
return m?.[0]
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function containsAny(text: string, terms: string[]): boolean {
|
|
25
|
+
return terms.some((term) => text.includes(term))
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function normalizeDelegateOrder(
|
|
29
|
+
value: unknown,
|
|
30
|
+
): Array<'delegate_to_claude_code' | 'delegate_to_codex_cli' | 'delegate_to_opencode_cli'> {
|
|
31
|
+
const fallback: Array<'delegate_to_claude_code' | 'delegate_to_codex_cli' | 'delegate_to_opencode_cli'> = [
|
|
32
|
+
'delegate_to_claude_code',
|
|
33
|
+
'delegate_to_codex_cli',
|
|
34
|
+
'delegate_to_opencode_cli',
|
|
35
|
+
]
|
|
36
|
+
if (!Array.isArray(value) || !value.length) return fallback
|
|
37
|
+
|
|
38
|
+
const mapped: Array<'delegate_to_claude_code' | 'delegate_to_codex_cli' | 'delegate_to_opencode_cli'> = []
|
|
39
|
+
for (const raw of value) {
|
|
40
|
+
if (raw === 'claude') mapped.push('delegate_to_claude_code')
|
|
41
|
+
else if (raw === 'codex') mapped.push('delegate_to_codex_cli')
|
|
42
|
+
else if (raw === 'opencode') mapped.push('delegate_to_opencode_cli')
|
|
43
|
+
}
|
|
44
|
+
if (!mapped.length) return fallback
|
|
45
|
+
const deduped = Array.from(new Set(mapped))
|
|
46
|
+
for (const tool of fallback) {
|
|
47
|
+
if (!deduped.includes(tool)) deduped.push(tool)
|
|
48
|
+
}
|
|
49
|
+
return deduped
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function routeTaskIntent(
|
|
53
|
+
message: string,
|
|
54
|
+
enabledTools: string[],
|
|
55
|
+
settings?: AppSettings | null,
|
|
56
|
+
): CapabilityRoutingDecision {
|
|
57
|
+
const text = (message || '').toLowerCase()
|
|
58
|
+
const url = findFirstUrl(message || '')
|
|
59
|
+
const delegateOrder = normalizeDelegateOrder(settings?.autonomyPreferredDelegates)
|
|
60
|
+
|
|
61
|
+
const coding = containsAny(text, [
|
|
62
|
+
'build',
|
|
63
|
+
'implement',
|
|
64
|
+
'create app',
|
|
65
|
+
'refactor',
|
|
66
|
+
'fix bug',
|
|
67
|
+
'write code',
|
|
68
|
+
'codebase',
|
|
69
|
+
'typescript',
|
|
70
|
+
'javascript',
|
|
71
|
+
'react',
|
|
72
|
+
'next.js',
|
|
73
|
+
'unit test',
|
|
74
|
+
'run tests',
|
|
75
|
+
'compile',
|
|
76
|
+
'npm ',
|
|
77
|
+
'pnpm ',
|
|
78
|
+
'yarn ',
|
|
79
|
+
])
|
|
80
|
+
if (coding) {
|
|
81
|
+
return {
|
|
82
|
+
intent: 'coding',
|
|
83
|
+
confidence: 0.9,
|
|
84
|
+
preferredTools: ['claude_code', 'codex_cli', 'opencode_cli', 'shell', 'files', 'edit_file'],
|
|
85
|
+
preferredDelegates: delegateOrder,
|
|
86
|
+
primaryUrl: url,
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const outreach = containsAny(text, [
|
|
91
|
+
'send update',
|
|
92
|
+
'message',
|
|
93
|
+
'whatsapp',
|
|
94
|
+
'telegram',
|
|
95
|
+
'slack',
|
|
96
|
+
'discord',
|
|
97
|
+
'notify',
|
|
98
|
+
'broadcast',
|
|
99
|
+
])
|
|
100
|
+
if (outreach) {
|
|
101
|
+
return {
|
|
102
|
+
intent: 'outreach',
|
|
103
|
+
confidence: 0.8,
|
|
104
|
+
preferredTools: ['connector_message_tool', 'manage_connectors', 'manage_sessions'],
|
|
105
|
+
preferredDelegates: delegateOrder,
|
|
106
|
+
primaryUrl: url,
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const scheduling = containsAny(text, [
|
|
111
|
+
'schedule',
|
|
112
|
+
'every day',
|
|
113
|
+
'every week',
|
|
114
|
+
'cron',
|
|
115
|
+
'recurring',
|
|
116
|
+
'remind',
|
|
117
|
+
'follow up tomorrow',
|
|
118
|
+
])
|
|
119
|
+
if (scheduling) {
|
|
120
|
+
return {
|
|
121
|
+
intent: 'scheduling',
|
|
122
|
+
confidence: 0.75,
|
|
123
|
+
preferredTools: ['manage_schedules', 'manage_tasks'],
|
|
124
|
+
preferredDelegates: delegateOrder,
|
|
125
|
+
primaryUrl: url,
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const browsing = !!url && (
|
|
130
|
+
containsAny(text, ['browser', 'click', 'fill form', 'log in', 'screenshot', 'navigate'])
|
|
131
|
+
|| enabledTools.includes('browser')
|
|
132
|
+
)
|
|
133
|
+
if (browsing) {
|
|
134
|
+
return {
|
|
135
|
+
intent: 'browsing',
|
|
136
|
+
confidence: 0.7,
|
|
137
|
+
preferredTools: ['browser', 'web_fetch'],
|
|
138
|
+
preferredDelegates: delegateOrder,
|
|
139
|
+
primaryUrl: url,
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const research = containsAny(text, [
|
|
144
|
+
'research',
|
|
145
|
+
'look up',
|
|
146
|
+
'find out',
|
|
147
|
+
'search for',
|
|
148
|
+
'compare',
|
|
149
|
+
'latest',
|
|
150
|
+
'news',
|
|
151
|
+
'wikipedia',
|
|
152
|
+
'summarize this url',
|
|
153
|
+
'analyze website',
|
|
154
|
+
]) || !!url
|
|
155
|
+
if (research) {
|
|
156
|
+
return {
|
|
157
|
+
intent: 'research',
|
|
158
|
+
confidence: 0.7,
|
|
159
|
+
preferredTools: ['web_search', 'web_fetch', 'browser'],
|
|
160
|
+
preferredDelegates: delegateOrder,
|
|
161
|
+
primaryUrl: url,
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return {
|
|
166
|
+
intent: 'general',
|
|
167
|
+
confidence: 0.5,
|
|
168
|
+
preferredTools: [],
|
|
169
|
+
preferredDelegates: delegateOrder,
|
|
170
|
+
primaryUrl: url,
|
|
171
|
+
}
|
|
172
|
+
}
|