@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.
Files changed (319) hide show
  1. package/README.md +577 -0
  2. package/bin/server-cmd.js +359 -0
  3. package/bin/swarmclaw.js +29 -0
  4. package/bin/swarmclaw.mjs +1504 -0
  5. package/next.config.ts +33 -0
  6. package/package.json +112 -0
  7. package/postcss.config.mjs +7 -0
  8. package/public/branding/swarmclaw-org-avatar.png +0 -0
  9. package/public/branding/swarmclaw-org-avatar.svg +58 -0
  10. package/public/file.svg +1 -0
  11. package/public/globe.svg +1 -0
  12. package/public/next.svg +1 -0
  13. package/public/screenshots/agents.png +0 -0
  14. package/public/screenshots/connectors.png +0 -0
  15. package/public/screenshots/dashboard.png +0 -0
  16. package/public/screenshots/new-session-openclaw.png +0 -0
  17. package/public/screenshots/providers.png +0 -0
  18. package/public/screenshots/schedules.png +0 -0
  19. package/public/screenshots/tasks.png +0 -0
  20. package/public/vercel.svg +1 -0
  21. package/public/window.svg +1 -0
  22. package/src/app/api/agents/[id]/route.ts +30 -0
  23. package/src/app/api/agents/[id]/thread/route.ts +66 -0
  24. package/src/app/api/agents/generate/route.ts +42 -0
  25. package/src/app/api/agents/route.ts +33 -0
  26. package/src/app/api/auth/route.ts +25 -0
  27. package/src/app/api/claude-skills/route.ts +42 -0
  28. package/src/app/api/clawhub/install/route.ts +39 -0
  29. package/src/app/api/clawhub/search/route.ts +11 -0
  30. package/src/app/api/connectors/[id]/route.ts +79 -0
  31. package/src/app/api/connectors/route.ts +60 -0
  32. package/src/app/api/credentials/[id]/route.ts +14 -0
  33. package/src/app/api/credentials/route.ts +31 -0
  34. package/src/app/api/daemon/health-check/route.ts +11 -0
  35. package/src/app/api/daemon/route.ts +22 -0
  36. package/src/app/api/dirs/pick/route.ts +60 -0
  37. package/src/app/api/dirs/route.ts +29 -0
  38. package/src/app/api/documents/[id]/route.ts +47 -0
  39. package/src/app/api/documents/route.ts +93 -0
  40. package/src/app/api/files/serve/route.ts +69 -0
  41. package/src/app/api/generate/info/route.ts +12 -0
  42. package/src/app/api/generate/route.ts +106 -0
  43. package/src/app/api/ip/route.ts +6 -0
  44. package/src/app/api/knowledge/[id]/route.ts +61 -0
  45. package/src/app/api/knowledge/route.ts +48 -0
  46. package/src/app/api/knowledge/upload/route.ts +86 -0
  47. package/src/app/api/logs/route.ts +65 -0
  48. package/src/app/api/mcp-servers/[id]/route.ts +32 -0
  49. package/src/app/api/mcp-servers/[id]/test/route.ts +23 -0
  50. package/src/app/api/mcp-servers/[id]/tools/route.ts +32 -0
  51. package/src/app/api/mcp-servers/route.ts +27 -0
  52. package/src/app/api/memory/[id]/route.ts +126 -0
  53. package/src/app/api/memory/maintenance/route.ts +63 -0
  54. package/src/app/api/memory/route.ts +111 -0
  55. package/src/app/api/memory-images/[filename]/route.ts +36 -0
  56. package/src/app/api/orchestrator/run/route.ts +43 -0
  57. package/src/app/api/plugins/install/route.ts +58 -0
  58. package/src/app/api/plugins/marketplace/route.ts +33 -0
  59. package/src/app/api/plugins/route.ts +21 -0
  60. package/src/app/api/preview-server/route.ts +339 -0
  61. package/src/app/api/providers/[id]/models/route.ts +29 -0
  62. package/src/app/api/providers/[id]/route.ts +34 -0
  63. package/src/app/api/providers/configs/route.ts +7 -0
  64. package/src/app/api/providers/ollama/route.ts +30 -0
  65. package/src/app/api/providers/openclaw/health/route.ts +23 -0
  66. package/src/app/api/providers/route.ts +28 -0
  67. package/src/app/api/runs/[id]/route.ts +9 -0
  68. package/src/app/api/runs/route.ts +13 -0
  69. package/src/app/api/schedules/[id]/route.ts +28 -0
  70. package/src/app/api/schedules/[id]/run/route.ts +104 -0
  71. package/src/app/api/schedules/route.ts +78 -0
  72. package/src/app/api/secrets/[id]/route.ts +29 -0
  73. package/src/app/api/secrets/route.ts +42 -0
  74. package/src/app/api/sessions/[id]/browser/route.ts +13 -0
  75. package/src/app/api/sessions/[id]/chat/route.ts +96 -0
  76. package/src/app/api/sessions/[id]/clear/route.ts +19 -0
  77. package/src/app/api/sessions/[id]/deploy/route.ts +34 -0
  78. package/src/app/api/sessions/[id]/devserver/route.ts +69 -0
  79. package/src/app/api/sessions/[id]/mailbox/route.ts +70 -0
  80. package/src/app/api/sessions/[id]/main-loop/route.ts +94 -0
  81. package/src/app/api/sessions/[id]/messages/route.ts +9 -0
  82. package/src/app/api/sessions/[id]/retry/route.ts +28 -0
  83. package/src/app/api/sessions/[id]/route.ts +103 -0
  84. package/src/app/api/sessions/[id]/stop/route.ts +13 -0
  85. package/src/app/api/sessions/heartbeat/route.ts +26 -0
  86. package/src/app/api/sessions/route.ts +85 -0
  87. package/src/app/api/settings/route.ts +58 -0
  88. package/src/app/api/setup/check-provider/route.ts +326 -0
  89. package/src/app/api/setup/doctor/route.ts +250 -0
  90. package/src/app/api/skills/[id]/route.ts +40 -0
  91. package/src/app/api/skills/import/route.ts +69 -0
  92. package/src/app/api/skills/route.ts +28 -0
  93. package/src/app/api/tasks/[id]/route.ts +102 -0
  94. package/src/app/api/tasks/route.ts +115 -0
  95. package/src/app/api/tts/route.ts +40 -0
  96. package/src/app/api/upload/route.ts +18 -0
  97. package/src/app/api/uploads/[filename]/route.ts +59 -0
  98. package/src/app/api/usage/route.ts +35 -0
  99. package/src/app/api/version/route.ts +81 -0
  100. package/src/app/api/version/update/route.ts +95 -0
  101. package/src/app/api/webhooks/[id]/history/route.ts +13 -0
  102. package/src/app/api/webhooks/[id]/route.ts +204 -0
  103. package/src/app/api/webhooks/route.ts +37 -0
  104. package/src/app/favicon.ico +0 -0
  105. package/src/app/globals.css +370 -0
  106. package/src/app/layout.tsx +52 -0
  107. package/src/app/page.tsx +172 -0
  108. package/src/cli/index.js +1232 -0
  109. package/src/cli/index.test.js +281 -0
  110. package/src/cli/index.ts +1158 -0
  111. package/src/cli/spec.js +284 -0
  112. package/src/components/agents/agent-card.tsx +219 -0
  113. package/src/components/agents/agent-chat-list.tsx +165 -0
  114. package/src/components/agents/agent-list.tsx +110 -0
  115. package/src/components/agents/agent-sheet.tsx +1220 -0
  116. package/src/components/auth/access-key-gate.tsx +248 -0
  117. package/src/components/auth/setup-wizard.tsx +940 -0
  118. package/src/components/auth/user-picker.tsx +88 -0
  119. package/src/components/chat/chat-area.tsx +406 -0
  120. package/src/components/chat/chat-header.tsx +491 -0
  121. package/src/components/chat/chat-tool-toggles.tsx +161 -0
  122. package/src/components/chat/code-block.tsx +146 -0
  123. package/src/components/chat/dev-server-bar.tsx +39 -0
  124. package/src/components/chat/message-bubble.tsx +486 -0
  125. package/src/components/chat/message-list.tsx +299 -0
  126. package/src/components/chat/session-debug-panel.tsx +196 -0
  127. package/src/components/chat/streaming-bubble.tsx +85 -0
  128. package/src/components/chat/thinking-indicator.tsx +26 -0
  129. package/src/components/chat/tool-call-bubble.tsx +438 -0
  130. package/src/components/chat/tool-request-banner.tsx +103 -0
  131. package/src/components/connectors/connector-list.tsx +196 -0
  132. package/src/components/connectors/connector-sheet.tsx +804 -0
  133. package/src/components/input/chat-input.tsx +235 -0
  134. package/src/components/knowledge/knowledge-list.tsx +206 -0
  135. package/src/components/knowledge/knowledge-sheet.tsx +316 -0
  136. package/src/components/layout/app-layout.tsx +1016 -0
  137. package/src/components/layout/daemon-indicator.tsx +56 -0
  138. package/src/components/layout/mobile-header.tsx +31 -0
  139. package/src/components/layout/network-banner.tsx +17 -0
  140. package/src/components/layout/update-banner.tsx +130 -0
  141. package/src/components/logs/log-list.tsx +358 -0
  142. package/src/components/mcp-servers/mcp-server-list.tsx +122 -0
  143. package/src/components/mcp-servers/mcp-server-sheet.tsx +243 -0
  144. package/src/components/memory/memory-card.tsx +63 -0
  145. package/src/components/memory/memory-detail.tsx +339 -0
  146. package/src/components/memory/memory-list.tsx +198 -0
  147. package/src/components/memory/memory-sheet.tsx +70 -0
  148. package/src/components/plugins/plugin-list.tsx +60 -0
  149. package/src/components/plugins/plugin-sheet.tsx +311 -0
  150. package/src/components/providers/provider-list.tsx +96 -0
  151. package/src/components/providers/provider-sheet.tsx +542 -0
  152. package/src/components/runs/run-list.tsx +231 -0
  153. package/src/components/schedules/schedule-card.tsx +63 -0
  154. package/src/components/schedules/schedule-list.tsx +76 -0
  155. package/src/components/schedules/schedule-sheet.tsx +336 -0
  156. package/src/components/secrets/secret-sheet.tsx +180 -0
  157. package/src/components/secrets/secrets-list.tsx +91 -0
  158. package/src/components/sessions/new-session-sheet.tsx +478 -0
  159. package/src/components/sessions/session-card.tsx +144 -0
  160. package/src/components/sessions/session-list.tsx +202 -0
  161. package/src/components/shared/ai-gen-block.tsx +77 -0
  162. package/src/components/shared/avatar.tsx +48 -0
  163. package/src/components/shared/bottom-sheet.tsx +30 -0
  164. package/src/components/shared/confirm-dialog.tsx +47 -0
  165. package/src/components/shared/connector-platform-icon.tsx +113 -0
  166. package/src/components/shared/dir-browser.tsx +285 -0
  167. package/src/components/shared/dropdown.tsx +55 -0
  168. package/src/components/shared/icon-button.tsx +25 -0
  169. package/src/components/shared/settings/plugin-manager.tsx +207 -0
  170. package/src/components/shared/settings/section-capability-policy.tsx +93 -0
  171. package/src/components/shared/settings/section-embedding.tsx +99 -0
  172. package/src/components/shared/settings/section-heartbeat.tsx +168 -0
  173. package/src/components/shared/settings/section-memory.tsx +77 -0
  174. package/src/components/shared/settings/section-orchestrator.tsx +108 -0
  175. package/src/components/shared/settings/section-providers.tsx +181 -0
  176. package/src/components/shared/settings/section-runtime-loop.tsx +183 -0
  177. package/src/components/shared/settings/section-secrets.tsx +132 -0
  178. package/src/components/shared/settings/section-user-preferences.tsx +24 -0
  179. package/src/components/shared/settings/section-voice.tsx +53 -0
  180. package/src/components/shared/settings/settings-sheet.tsx +88 -0
  181. package/src/components/shared/settings/types.ts +7 -0
  182. package/src/components/shared/settings/utils.ts +13 -0
  183. package/src/components/shared/settings-sheet.tsx +1 -0
  184. package/src/components/shared/skeleton.tsx +19 -0
  185. package/src/components/shared/usage-badge.tsx +28 -0
  186. package/src/components/skills/clawhub-browser.tsx +225 -0
  187. package/src/components/skills/skill-list.tsx +70 -0
  188. package/src/components/skills/skill-sheet.tsx +254 -0
  189. package/src/components/tasks/task-board.tsx +96 -0
  190. package/src/components/tasks/task-card.tsx +179 -0
  191. package/src/components/tasks/task-column.tsx +73 -0
  192. package/src/components/tasks/task-list.tsx +118 -0
  193. package/src/components/tasks/task-sheet.tsx +415 -0
  194. package/src/components/ui/avatar.tsx +109 -0
  195. package/src/components/ui/badge.tsx +48 -0
  196. package/src/components/ui/button.tsx +64 -0
  197. package/src/components/ui/card.tsx +92 -0
  198. package/src/components/ui/dialog.tsx +158 -0
  199. package/src/components/ui/dropdown-menu.tsx +257 -0
  200. package/src/components/ui/input.tsx +21 -0
  201. package/src/components/ui/scroll-area.tsx +58 -0
  202. package/src/components/ui/select.tsx +190 -0
  203. package/src/components/ui/separator.tsx +28 -0
  204. package/src/components/ui/sheet.tsx +143 -0
  205. package/src/components/ui/sonner.tsx +22 -0
  206. package/src/components/ui/textarea.tsx +18 -0
  207. package/src/components/ui/tooltip.tsx +56 -0
  208. package/src/components/usage/usage-list.tsx +105 -0
  209. package/src/components/webhooks/webhook-list.tsx +166 -0
  210. package/src/components/webhooks/webhook-sheet.tsx +402 -0
  211. package/src/hooks/use-auto-resize.ts +20 -0
  212. package/src/hooks/use-media-query.ts +21 -0
  213. package/src/hooks/use-speech-recognition.ts +83 -0
  214. package/src/instrumentation.ts +8 -0
  215. package/src/lib/agents.ts +13 -0
  216. package/src/lib/api-client.ts +100 -0
  217. package/src/lib/chat.ts +60 -0
  218. package/src/lib/memory.ts +42 -0
  219. package/src/lib/openclaw-endpoint.test.ts +48 -0
  220. package/src/lib/openclaw-endpoint.ts +67 -0
  221. package/src/lib/provider-config.ts +13 -0
  222. package/src/lib/providers/anthropic.ts +135 -0
  223. package/src/lib/providers/claude-cli.ts +202 -0
  224. package/src/lib/providers/codex-cli.ts +260 -0
  225. package/src/lib/providers/index.ts +351 -0
  226. package/src/lib/providers/ollama.ts +131 -0
  227. package/src/lib/providers/openai.ts +164 -0
  228. package/src/lib/providers/openclaw.ts +330 -0
  229. package/src/lib/providers/opencode-cli.ts +164 -0
  230. package/src/lib/runtime-loop.ts +15 -0
  231. package/src/lib/schedule-dedupe.test.ts +84 -0
  232. package/src/lib/schedule-dedupe.ts +174 -0
  233. package/src/lib/schedule-name.ts +62 -0
  234. package/src/lib/schedules.ts +16 -0
  235. package/src/lib/server/agent-registry.ts +70 -0
  236. package/src/lib/server/api-routes.test.ts +362 -0
  237. package/src/lib/server/autonomy-contract.ts +200 -0
  238. package/src/lib/server/build-llm.ts +155 -0
  239. package/src/lib/server/capability-router.test.ts +21 -0
  240. package/src/lib/server/capability-router.ts +172 -0
  241. package/src/lib/server/chat-execution.ts +894 -0
  242. package/src/lib/server/clawhub-client.test.ts +161 -0
  243. package/src/lib/server/clawhub-client.ts +26 -0
  244. package/src/lib/server/connectors/connector-routing.test.ts +243 -0
  245. package/src/lib/server/connectors/discord.ts +116 -0
  246. package/src/lib/server/connectors/googlechat.ts +66 -0
  247. package/src/lib/server/connectors/manager.ts +559 -0
  248. package/src/lib/server/connectors/matrix.ts +78 -0
  249. package/src/lib/server/connectors/media.ts +149 -0
  250. package/src/lib/server/connectors/openclaw.test.ts +375 -0
  251. package/src/lib/server/connectors/openclaw.ts +1132 -0
  252. package/src/lib/server/connectors/signal.ts +183 -0
  253. package/src/lib/server/connectors/slack.ts +258 -0
  254. package/src/lib/server/connectors/teams.ts +94 -0
  255. package/src/lib/server/connectors/telegram.ts +221 -0
  256. package/src/lib/server/connectors/types.ts +62 -0
  257. package/src/lib/server/connectors/whatsapp.ts +349 -0
  258. package/src/lib/server/context-manager.ts +232 -0
  259. package/src/lib/server/cost.ts +31 -0
  260. package/src/lib/server/daemon-state.ts +354 -0
  261. package/src/lib/server/data-dir.ts +3 -0
  262. package/src/lib/server/embeddings.ts +111 -0
  263. package/src/lib/server/execution-log.ts +257 -0
  264. package/src/lib/server/gateway/protocol.test.ts +54 -0
  265. package/src/lib/server/gateway/protocol.ts +114 -0
  266. package/src/lib/server/heartbeat-service.ts +366 -0
  267. package/src/lib/server/knowledge-db.test.ts +441 -0
  268. package/src/lib/server/logger.ts +47 -0
  269. package/src/lib/server/main-agent-loop.ts +1017 -0
  270. package/src/lib/server/mcp-client.test.ts +342 -0
  271. package/src/lib/server/mcp-client.ts +130 -0
  272. package/src/lib/server/memory-db.ts +1078 -0
  273. package/src/lib/server/memory-graph.test.ts +153 -0
  274. package/src/lib/server/memory-graph.ts +138 -0
  275. package/src/lib/server/openclaw-health.ts +245 -0
  276. package/src/lib/server/orchestrator-lg.ts +431 -0
  277. package/src/lib/server/orchestrator.ts +364 -0
  278. package/src/lib/server/playwright-proxy.mjs +70 -0
  279. package/src/lib/server/plugins.ts +229 -0
  280. package/src/lib/server/process-manager.ts +327 -0
  281. package/src/lib/server/provider-health.ts +113 -0
  282. package/src/lib/server/queue.ts +859 -0
  283. package/src/lib/server/runtime-settings.ts +119 -0
  284. package/src/lib/server/scheduler.ts +196 -0
  285. package/src/lib/server/session-mailbox.ts +129 -0
  286. package/src/lib/server/session-run-manager.ts +512 -0
  287. package/src/lib/server/session-tools/connector.ts +124 -0
  288. package/src/lib/server/session-tools/context-mgmt.ts +103 -0
  289. package/src/lib/server/session-tools/context.ts +114 -0
  290. package/src/lib/server/session-tools/crud.ts +673 -0
  291. package/src/lib/server/session-tools/delegate.ts +708 -0
  292. package/src/lib/server/session-tools/file.ts +264 -0
  293. package/src/lib/server/session-tools/index.ts +164 -0
  294. package/src/lib/server/session-tools/memory.ts +230 -0
  295. package/src/lib/server/session-tools/session-info.ts +422 -0
  296. package/src/lib/server/session-tools/session-tools-wiring.test.ts +166 -0
  297. package/src/lib/server/session-tools/shell.ts +171 -0
  298. package/src/lib/server/session-tools/web.ts +408 -0
  299. package/src/lib/server/session-tools.ts +9 -0
  300. package/src/lib/server/skills-normalize.ts +130 -0
  301. package/src/lib/server/storage-mcp.test.ts +161 -0
  302. package/src/lib/server/storage.ts +670 -0
  303. package/src/lib/server/stream-agent-chat.ts +571 -0
  304. package/src/lib/server/task-reports.ts +122 -0
  305. package/src/lib/server/task-result.ts +161 -0
  306. package/src/lib/server/task-validation.test.ts +27 -0
  307. package/src/lib/server/task-validation.ts +90 -0
  308. package/src/lib/server/tool-capability-policy.test.ts +58 -0
  309. package/src/lib/server/tool-capability-policy.ts +262 -0
  310. package/src/lib/sessions.ts +68 -0
  311. package/src/lib/tasks.ts +20 -0
  312. package/src/lib/tts.ts +42 -0
  313. package/src/lib/upload.ts +10 -0
  314. package/src/lib/utils.ts +6 -0
  315. package/src/proxy.ts +43 -0
  316. package/src/stores/use-app-store.ts +468 -0
  317. package/src/stores/use-chat-store.ts +323 -0
  318. package/src/types/index.ts +621 -0
  319. 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
+ }