@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,119 @@
1
+ import type { LoopMode } from '@/types'
2
+ import {
3
+ DEFAULT_AGENT_LOOP_RECURSION_LIMIT,
4
+ DEFAULT_CLAUDE_CODE_TIMEOUT_SEC,
5
+ DEFAULT_CLI_PROCESS_TIMEOUT_SEC,
6
+ DEFAULT_LEGACY_ORCHESTRATOR_MAX_TURNS,
7
+ DEFAULT_LOOP_MODE,
8
+ DEFAULT_ONGOING_LOOP_MAX_ITERATIONS,
9
+ DEFAULT_ONGOING_LOOP_MAX_RUNTIME_MINUTES,
10
+ DEFAULT_ORCHESTRATOR_LOOP_RECURSION_LIMIT,
11
+ DEFAULT_SHELL_COMMAND_TIMEOUT_SEC,
12
+ } from '@/lib/runtime-loop'
13
+ import { loadSettings } from './storage'
14
+
15
+ export interface RuntimeSettings {
16
+ loopMode: LoopMode
17
+ agentLoopRecursionLimit: number
18
+ orchestratorLoopRecursionLimit: number
19
+ legacyOrchestratorMaxTurns: number
20
+ ongoingLoopMaxIterations: number
21
+ ongoingLoopMaxRuntimeMs: number | null
22
+ shellCommandTimeoutMs: number
23
+ claudeCodeTimeoutMs: number
24
+ cliProcessTimeoutMs: number
25
+ }
26
+
27
+ function parseIntSetting(value: unknown, fallback: number, min: number, max: number): number {
28
+ const parsed = typeof value === 'number'
29
+ ? value
30
+ : typeof value === 'string'
31
+ ? Number.parseInt(value, 10)
32
+ : Number.NaN
33
+ if (!Number.isFinite(parsed)) return fallback
34
+ const int = Math.trunc(parsed)
35
+ return Math.max(min, Math.min(max, int))
36
+ }
37
+
38
+ function parseLoopMode(value: unknown): LoopMode {
39
+ return value === 'ongoing' ? 'ongoing' : DEFAULT_LOOP_MODE
40
+ }
41
+
42
+ export function loadRuntimeSettings(): RuntimeSettings {
43
+ const settings = loadSettings()
44
+ const loopMode = parseLoopMode(settings.loopMode)
45
+
46
+ const agentLoopRecursionLimit = parseIntSetting(
47
+ settings.agentLoopRecursionLimit,
48
+ DEFAULT_AGENT_LOOP_RECURSION_LIMIT,
49
+ 1,
50
+ 200,
51
+ )
52
+ const orchestratorLoopRecursionLimit = parseIntSetting(
53
+ settings.orchestratorLoopRecursionLimit,
54
+ DEFAULT_ORCHESTRATOR_LOOP_RECURSION_LIMIT,
55
+ 1,
56
+ 300,
57
+ )
58
+ const legacyOrchestratorMaxTurns = parseIntSetting(
59
+ settings.legacyOrchestratorMaxTurns,
60
+ DEFAULT_LEGACY_ORCHESTRATOR_MAX_TURNS,
61
+ 1,
62
+ 300,
63
+ )
64
+ const ongoingLoopMaxIterations = parseIntSetting(
65
+ settings.ongoingLoopMaxIterations,
66
+ DEFAULT_ONGOING_LOOP_MAX_ITERATIONS,
67
+ 10,
68
+ 5000,
69
+ )
70
+ const ongoingLoopMaxRuntimeMinutes = parseIntSetting(
71
+ settings.ongoingLoopMaxRuntimeMinutes,
72
+ DEFAULT_ONGOING_LOOP_MAX_RUNTIME_MINUTES,
73
+ 0,
74
+ 1440,
75
+ )
76
+
77
+ const shellCommandTimeoutSec = parseIntSetting(
78
+ settings.shellCommandTimeoutSec,
79
+ DEFAULT_SHELL_COMMAND_TIMEOUT_SEC,
80
+ 1,
81
+ 600,
82
+ )
83
+ const claudeCodeTimeoutSec = parseIntSetting(
84
+ settings.claudeCodeTimeoutSec,
85
+ DEFAULT_CLAUDE_CODE_TIMEOUT_SEC,
86
+ 5,
87
+ 7200,
88
+ )
89
+ const cliProcessTimeoutSec = parseIntSetting(
90
+ settings.cliProcessTimeoutSec,
91
+ DEFAULT_CLI_PROCESS_TIMEOUT_SEC,
92
+ 10,
93
+ 7200,
94
+ )
95
+
96
+ return {
97
+ loopMode,
98
+ agentLoopRecursionLimit,
99
+ orchestratorLoopRecursionLimit,
100
+ legacyOrchestratorMaxTurns,
101
+ ongoingLoopMaxIterations,
102
+ ongoingLoopMaxRuntimeMs: ongoingLoopMaxRuntimeMinutes > 0 ? ongoingLoopMaxRuntimeMinutes * 60_000 : null,
103
+ shellCommandTimeoutMs: shellCommandTimeoutSec * 1000,
104
+ claudeCodeTimeoutMs: claudeCodeTimeoutSec * 1000,
105
+ cliProcessTimeoutMs: cliProcessTimeoutSec * 1000,
106
+ }
107
+ }
108
+
109
+ export function getAgentLoopRecursionLimit(runtime: RuntimeSettings): number {
110
+ return runtime.loopMode === 'ongoing' ? runtime.ongoingLoopMaxIterations : runtime.agentLoopRecursionLimit
111
+ }
112
+
113
+ export function getOrchestratorLoopRecursionLimit(runtime: RuntimeSettings): number {
114
+ return runtime.loopMode === 'ongoing' ? runtime.ongoingLoopMaxIterations : runtime.orchestratorLoopRecursionLimit
115
+ }
116
+
117
+ export function getLegacyOrchestratorMaxTurns(runtime: RuntimeSettings): number {
118
+ return runtime.loopMode === 'ongoing' ? runtime.ongoingLoopMaxIterations : runtime.legacyOrchestratorMaxTurns
119
+ }
@@ -0,0 +1,196 @@
1
+ import crypto from 'crypto'
2
+ import { loadSchedules, saveSchedules, loadAgents, loadTasks, saveTasks } from './storage'
3
+ import { enqueueTask } from './queue'
4
+ import { CronExpressionParser } from 'cron-parser'
5
+ import { pushMainLoopEventToMainSessions } from './main-agent-loop'
6
+ import { getScheduleSignatureKey } from '@/lib/schedule-dedupe'
7
+
8
+ const TICK_INTERVAL = 60_000 // 60 seconds
9
+ let intervalId: ReturnType<typeof setInterval> | null = null
10
+
11
+ interface ScheduleTaskLike {
12
+ status?: string
13
+ sourceScheduleKey?: string | null
14
+ }
15
+
16
+ interface SchedulerScheduleLike {
17
+ id: string
18
+ name: string
19
+ agentId: string
20
+ taskPrompt: string
21
+ scheduleType: 'cron' | 'interval' | 'once'
22
+ cron?: string
23
+ intervalMs?: number
24
+ runAt?: number
25
+ lastRunAt?: number
26
+ nextRunAt?: number
27
+ status: 'active' | 'paused' | 'completed' | 'failed'
28
+ linkedTaskId?: string | null
29
+ runNumber?: number
30
+ createdInSessionId?: string | null
31
+ createdByAgentId?: string | null
32
+ }
33
+
34
+ export function startScheduler() {
35
+ if (intervalId) return
36
+ console.log('[scheduler] Starting scheduler engine (60s tick)')
37
+
38
+ // Compute initial nextRunAt for cron schedules missing it
39
+ computeNextRuns()
40
+
41
+ intervalId = setInterval(tick, TICK_INTERVAL)
42
+ }
43
+
44
+ export function stopScheduler() {
45
+ if (intervalId) {
46
+ clearInterval(intervalId)
47
+ intervalId = null
48
+ console.log('[scheduler] Stopped scheduler engine')
49
+ }
50
+ }
51
+
52
+ function computeNextRuns() {
53
+ const schedules = loadSchedules()
54
+ let changed = false
55
+ for (const schedule of Object.values(schedules) as SchedulerScheduleLike[]) {
56
+ if (schedule.status !== 'active') continue
57
+ if (schedule.scheduleType === 'cron' && schedule.cron && !schedule.nextRunAt) {
58
+ try {
59
+ const interval = CronExpressionParser.parse(schedule.cron)
60
+ schedule.nextRunAt = interval.next().getTime()
61
+ changed = true
62
+ } catch (err) {
63
+ console.error(`[scheduler] Invalid cron for ${schedule.id}:`, err)
64
+ schedule.status = 'failed'
65
+ changed = true
66
+ }
67
+ }
68
+ }
69
+ if (changed) saveSchedules(schedules)
70
+ }
71
+
72
+ async function tick() {
73
+ const now = Date.now()
74
+ const schedules = loadSchedules()
75
+ const agents = loadAgents()
76
+ const tasks = loadTasks()
77
+ const inFlightScheduleKeys = new Set<string>(
78
+ Object.values(tasks as Record<string, ScheduleTaskLike>)
79
+ .filter((task) => task && (task.status === 'queued' || task.status === 'running'))
80
+ .map((task) => (typeof task.sourceScheduleKey === 'string' ? task.sourceScheduleKey : ''))
81
+ .filter((value: string) => value.length > 0),
82
+ )
83
+
84
+ const advanceSchedule = (schedule: SchedulerScheduleLike): void => {
85
+ if (schedule.scheduleType === 'cron' && schedule.cron) {
86
+ try {
87
+ const interval = CronExpressionParser.parse(schedule.cron)
88
+ schedule.nextRunAt = interval.next().getTime()
89
+ } catch {
90
+ schedule.status = 'failed'
91
+ }
92
+ } else if (schedule.scheduleType === 'interval' && schedule.intervalMs) {
93
+ schedule.nextRunAt = now + schedule.intervalMs
94
+ } else if (schedule.scheduleType === 'once') {
95
+ schedule.status = 'completed'
96
+ schedule.nextRunAt = undefined
97
+ }
98
+ }
99
+
100
+ for (const schedule of Object.values(schedules) as SchedulerScheduleLike[]) {
101
+ if (schedule.status !== 'active') continue
102
+ if (!schedule.nextRunAt || schedule.nextRunAt > now) continue
103
+
104
+ const scheduleSignature = getScheduleSignatureKey(schedule)
105
+ if (scheduleSignature && inFlightScheduleKeys.has(scheduleSignature)) {
106
+ advanceSchedule(schedule)
107
+ saveSchedules(schedules)
108
+ continue
109
+ }
110
+
111
+ const agent = agents[schedule.agentId]
112
+ if (!agent) {
113
+ console.error(`[scheduler] Agent ${schedule.agentId} not found for schedule ${schedule.id}`)
114
+ schedule.status = 'failed'
115
+ saveSchedules(schedules)
116
+ pushMainLoopEventToMainSessions({
117
+ type: 'schedule_failed',
118
+ text: `Schedule failed: "${schedule.name}" (${schedule.id}) — agent ${schedule.agentId} not found.`,
119
+ })
120
+ continue
121
+ }
122
+
123
+ console.log(`[scheduler] Firing schedule "${schedule.name}" (${schedule.id})`)
124
+ schedule.lastRunAt = now
125
+ schedule.runNumber = (schedule.runNumber || 0) + 1
126
+
127
+ // Compute next run
128
+ advanceSchedule(schedule)
129
+
130
+ // Reuse linked task if it exists and is not currently in-flight
131
+ let taskId = ''
132
+ const existingTaskId = typeof schedule.linkedTaskId === 'string' ? schedule.linkedTaskId : ''
133
+ const existingTask = existingTaskId ? tasks[existingTaskId] : null
134
+ if (existingTask && existingTask.status !== 'queued' && existingTask.status !== 'running') {
135
+ // Accumulate stats from the previous run before resetting
136
+ taskId = existingTaskId
137
+ const prev = existingTask as any
138
+ prev.totalRuns = (prev.totalRuns || 0) + 1
139
+ if (existingTask.status === 'completed') prev.totalCompleted = (prev.totalCompleted || 0) + 1
140
+ if (existingTask.status === 'failed') prev.totalFailed = (prev.totalFailed || 0) + 1
141
+
142
+ // Reset for the new run
143
+ existingTask.status = 'backlog'
144
+ existingTask.title = `[Sched] ${schedule.name} (run #${schedule.runNumber})`
145
+ existingTask.result = null
146
+ existingTask.error = null
147
+ existingTask.sessionId = null
148
+ existingTask.updatedAt = now
149
+ existingTask.queuedAt = null
150
+ existingTask.startedAt = null
151
+ existingTask.completedAt = null
152
+ existingTask.archivedAt = null
153
+ existingTask.attempts = 0
154
+ existingTask.retryScheduledAt = null
155
+ existingTask.deadLetteredAt = null
156
+ existingTask.validation = null
157
+ prev.runNumber = schedule.runNumber
158
+ } else {
159
+ // Create a new linked task (first run or previous task still in-flight)
160
+ taskId = crypto.randomBytes(4).toString('hex')
161
+ tasks[taskId] = {
162
+ id: taskId,
163
+ title: `[Sched] ${schedule.name} (run #${schedule.runNumber})`,
164
+ description: schedule.taskPrompt,
165
+ status: 'backlog',
166
+ agentId: schedule.agentId,
167
+ sessionId: null,
168
+ result: null,
169
+ error: null,
170
+ createdAt: now,
171
+ updatedAt: now,
172
+ queuedAt: null,
173
+ startedAt: null,
174
+ completedAt: null,
175
+ sourceType: 'schedule',
176
+ sourceScheduleId: schedule.id,
177
+ sourceScheduleName: schedule.name,
178
+ sourceScheduleKey: scheduleSignature || null,
179
+ createdInSessionId: schedule.createdInSessionId || null,
180
+ createdByAgentId: schedule.createdByAgentId || null,
181
+ runNumber: schedule.runNumber,
182
+ }
183
+ schedule.linkedTaskId = taskId
184
+ }
185
+
186
+ saveTasks(tasks)
187
+ saveSchedules(schedules)
188
+
189
+ enqueueTask(taskId)
190
+ if (scheduleSignature) inFlightScheduleKeys.add(scheduleSignature)
191
+ pushMainLoopEventToMainSessions({
192
+ type: 'schedule_fired',
193
+ text: `Schedule fired: "${schedule.name}" (${schedule.id}) run #${schedule.runNumber} — task ${taskId}`,
194
+ })
195
+ }
196
+ }
@@ -0,0 +1,129 @@
1
+ import crypto from 'crypto'
2
+ import { loadSessions, saveSessions } from './storage'
3
+
4
+ export type MailboxStatus = 'new' | 'ack'
5
+
6
+ export interface MailboxEnvelope {
7
+ id: string
8
+ type: string
9
+ payload: string
10
+ fromSessionId?: string | null
11
+ fromAgentId?: string | null
12
+ toSessionId: string
13
+ toAgentId?: string | null
14
+ correlationId?: string | null
15
+ status: MailboxStatus
16
+ createdAt: number
17
+ expiresAt?: number | null
18
+ ackAt?: number | null
19
+ }
20
+
21
+ interface MailboxOptions {
22
+ limit?: number
23
+ includeAcked?: boolean
24
+ }
25
+
26
+ function normalizeMailboxList(raw: unknown): MailboxEnvelope[] {
27
+ if (!Array.isArray(raw)) return []
28
+ const out: MailboxEnvelope[] = []
29
+ for (const item of raw) {
30
+ if (!item || typeof item !== 'object') continue
31
+ const v = item as MailboxEnvelope
32
+ if (!v.id || !v.toSessionId) continue
33
+ out.push(v)
34
+ }
35
+ return out
36
+ }
37
+
38
+ function pruneExpired(envelopes: MailboxEnvelope[], now = Date.now()): MailboxEnvelope[] {
39
+ return envelopes.filter((env) => !env.expiresAt || env.expiresAt > now)
40
+ }
41
+
42
+ export function sendMailboxEnvelope(input: {
43
+ toSessionId: string
44
+ type: string
45
+ payload: string
46
+ fromSessionId?: string | null
47
+ fromAgentId?: string | null
48
+ toAgentId?: string | null
49
+ correlationId?: string | null
50
+ ttlSec?: number | null
51
+ }): MailboxEnvelope {
52
+ const sessions = loadSessions()
53
+ const target = sessions[input.toSessionId]
54
+ if (!target) throw new Error(`Target session not found: ${input.toSessionId}`)
55
+
56
+ const now = Date.now()
57
+ const ttl = typeof input.ttlSec === 'number' && Number.isFinite(input.ttlSec)
58
+ ? Math.max(0, Math.min(7 * 24 * 3600, Math.trunc(input.ttlSec)))
59
+ : null
60
+ const envelope: MailboxEnvelope = {
61
+ id: crypto.randomBytes(6).toString('hex'),
62
+ type: (input.type || 'message').trim() || 'message',
63
+ payload: String(input.payload || ''),
64
+ fromSessionId: input.fromSessionId || null,
65
+ fromAgentId: input.fromAgentId || null,
66
+ toSessionId: input.toSessionId,
67
+ toAgentId: input.toAgentId || null,
68
+ correlationId: input.correlationId || null,
69
+ status: 'new',
70
+ createdAt: now,
71
+ expiresAt: ttl ? now + ttl * 1000 : null,
72
+ ackAt: null,
73
+ }
74
+
75
+ const existing = pruneExpired(normalizeMailboxList(target.mailbox || []), now)
76
+ existing.push(envelope)
77
+ target.mailbox = existing
78
+ target.lastActiveAt = now
79
+ sessions[input.toSessionId] = target
80
+ saveSessions(sessions)
81
+ return envelope
82
+ }
83
+
84
+ export function listMailbox(sessionId: string, opts: MailboxOptions = {}): MailboxEnvelope[] {
85
+ const sessions = loadSessions()
86
+ const target = sessions[sessionId]
87
+ if (!target) throw new Error(`Session not found: ${sessionId}`)
88
+ const list = pruneExpired(normalizeMailboxList(target.mailbox || []))
89
+ const includeAcked = opts.includeAcked === true
90
+ const filtered = includeAcked ? list : list.filter((env) => env.status !== 'ack')
91
+ const limit = Math.max(1, Math.min(500, Math.trunc(opts.limit || 50)))
92
+ return filtered
93
+ .sort((a, b) => b.createdAt - a.createdAt)
94
+ .slice(0, limit)
95
+ }
96
+
97
+ export function ackMailboxEnvelope(sessionId: string, envelopeId: string): MailboxEnvelope | null {
98
+ const sessions = loadSessions()
99
+ const target = sessions[sessionId]
100
+ if (!target) throw new Error(`Session not found: ${sessionId}`)
101
+ const list = pruneExpired(normalizeMailboxList(target.mailbox || []))
102
+ const idx = list.findIndex((env) => env.id === envelopeId)
103
+ if (idx === -1) return null
104
+ list[idx] = {
105
+ ...list[idx],
106
+ status: 'ack',
107
+ ackAt: Date.now(),
108
+ }
109
+ target.mailbox = list
110
+ target.lastActiveAt = Date.now()
111
+ sessions[sessionId] = target
112
+ saveSessions(sessions)
113
+ return list[idx]
114
+ }
115
+
116
+ export function clearMailbox(sessionId: string, includeAcked = true): { before: number; after: number } {
117
+ const sessions = loadSessions()
118
+ const target = sessions[sessionId]
119
+ if (!target) throw new Error(`Session not found: ${sessionId}`)
120
+ const list = pruneExpired(normalizeMailboxList(target.mailbox || []))
121
+ const before = list.length
122
+ const afterList = includeAcked ? [] : list.filter((env) => env.status !== 'ack')
123
+ target.mailbox = afterList
124
+ target.lastActiveAt = Date.now()
125
+ sessions[sessionId] = target
126
+ saveSessions(sessions)
127
+ return { before, after: afterList.length }
128
+ }
129
+