@swarmclawai/swarmclaw 0.7.2 → 0.7.4

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 (274) hide show
  1. package/README.md +116 -50
  2. package/bin/package-manager.js +157 -0
  3. package/bin/package-manager.test.js +90 -0
  4. package/bin/server-cmd.js +38 -7
  5. package/bin/swarmclaw.js +54 -4
  6. package/bin/update-cmd.js +48 -10
  7. package/bin/update-cmd.test.js +55 -0
  8. package/package.json +8 -3
  9. package/scripts/postinstall.mjs +26 -0
  10. package/src/app/api/agents/[id]/route.ts +43 -0
  11. package/src/app/api/agents/[id]/thread/route.ts +39 -8
  12. package/src/app/api/agents/route.ts +35 -2
  13. package/src/app/api/auth/route.ts +77 -8
  14. package/src/app/api/chatrooms/[id]/chat/route.ts +22 -6
  15. package/src/app/api/chatrooms/[id]/pins/route.ts +2 -1
  16. package/src/app/api/chatrooms/[id]/reactions/route.ts +2 -1
  17. package/src/app/api/chatrooms/[id]/route.ts +6 -0
  18. package/src/app/api/chats/[id]/browser/route.ts +5 -1
  19. package/src/app/api/chats/[id]/chat/route.ts +7 -3
  20. package/src/app/api/chats/[id]/messages/route.ts +19 -13
  21. package/src/app/api/chats/[id]/route.ts +30 -0
  22. package/src/app/api/chats/[id]/stop/route.ts +6 -1
  23. package/src/app/api/chats/heartbeat/route.ts +2 -1
  24. package/src/app/api/chats/route.ts +23 -1
  25. package/src/app/api/connectors/[id]/doctor/route.ts +26 -0
  26. package/src/app/api/connectors/doctor/route.ts +13 -0
  27. package/src/app/api/external-agents/[id]/heartbeat/route.ts +33 -0
  28. package/src/app/api/external-agents/[id]/route.ts +31 -0
  29. package/src/app/api/external-agents/register/route.ts +3 -0
  30. package/src/app/api/external-agents/route.ts +66 -0
  31. package/src/app/api/files/open/route.ts +16 -14
  32. package/src/app/api/gateways/[id]/health/route.ts +28 -0
  33. package/src/app/api/gateways/[id]/route.ts +79 -0
  34. package/src/app/api/gateways/route.ts +57 -0
  35. package/src/app/api/memory/maintenance/route.ts +11 -1
  36. package/src/app/api/openclaw/agent-files/route.ts +27 -4
  37. package/src/app/api/openclaw/gateway/route.ts +10 -7
  38. package/src/app/api/openclaw/skills/route.ts +12 -4
  39. package/src/app/api/plugins/dependencies/route.ts +24 -0
  40. package/src/app/api/plugins/install/route.ts +15 -92
  41. package/src/app/api/plugins/route.ts +3 -26
  42. package/src/app/api/plugins/settings/route.ts +17 -12
  43. package/src/app/api/plugins/ui/route.ts +1 -0
  44. package/src/app/api/providers/[id]/discover-models/route.ts +27 -0
  45. package/src/app/api/schedules/[id]/route.ts +38 -9
  46. package/src/app/api/schedules/route.ts +51 -28
  47. package/src/app/api/settings/route.ts +55 -17
  48. package/src/app/api/setup/doctor/route.ts +6 -4
  49. package/src/app/api/tasks/[id]/route.ts +16 -6
  50. package/src/app/api/tasks/bulk/route.ts +3 -3
  51. package/src/app/api/tasks/route.ts +9 -4
  52. package/src/app/api/webhooks/[id]/route.ts +8 -1
  53. package/src/app/page.tsx +135 -17
  54. package/src/cli/binary.test.js +142 -0
  55. package/src/cli/index.js +38 -11
  56. package/src/cli/index.test.js +195 -0
  57. package/src/cli/index.ts +21 -12
  58. package/src/cli/server-cmd.test.js +59 -0
  59. package/src/cli/spec.js +20 -2
  60. package/src/components/agents/agent-card.tsx +15 -12
  61. package/src/components/agents/agent-chat-list.tsx +101 -1
  62. package/src/components/agents/agent-list.tsx +46 -9
  63. package/src/components/agents/agent-sheet.tsx +456 -23
  64. package/src/components/agents/inspector-panel.tsx +110 -49
  65. package/src/components/agents/sandbox-env-panel.tsx +4 -1
  66. package/src/components/auth/access-key-gate.tsx +36 -97
  67. package/src/components/auth/setup-wizard.tsx +970 -275
  68. package/src/components/chat/chat-area.tsx +70 -27
  69. package/src/components/chat/chat-card.tsx +6 -21
  70. package/src/components/chat/chat-header.tsx +263 -366
  71. package/src/components/chat/chat-list.tsx +62 -26
  72. package/src/components/chat/checkpoint-timeline.tsx +1 -1
  73. package/src/components/chat/message-list.tsx +145 -19
  74. package/src/components/chatrooms/chatroom-input.tsx +96 -33
  75. package/src/components/chatrooms/chatroom-list.tsx +141 -72
  76. package/src/components/chatrooms/chatroom-message.tsx +7 -6
  77. package/src/components/chatrooms/chatroom-sheet.tsx +13 -1
  78. package/src/components/chatrooms/chatroom-tool-request-banner.tsx +5 -2
  79. package/src/components/chatrooms/chatroom-view.tsx +422 -209
  80. package/src/components/chatrooms/reaction-picker.tsx +38 -33
  81. package/src/components/connectors/connector-list.tsx +265 -127
  82. package/src/components/connectors/connector-sheet.tsx +217 -0
  83. package/src/components/gateways/gateway-sheet.tsx +567 -0
  84. package/src/components/home/home-view.tsx +128 -4
  85. package/src/components/input/chat-input.tsx +135 -86
  86. package/src/components/layout/app-layout.tsx +385 -194
  87. package/src/components/layout/mobile-header.tsx +26 -8
  88. package/src/components/memory/memory-browser.tsx +71 -6
  89. package/src/components/memory/memory-card.tsx +18 -0
  90. package/src/components/memory/memory-detail.tsx +58 -31
  91. package/src/components/memory/memory-sheet.tsx +32 -4
  92. package/src/components/plugins/plugin-list.tsx +15 -3
  93. package/src/components/plugins/plugin-sheet.tsx +118 -9
  94. package/src/components/projects/project-detail.tsx +189 -1
  95. package/src/components/providers/provider-list.tsx +158 -2
  96. package/src/components/providers/provider-sheet.tsx +81 -70
  97. package/src/components/shared/agent-picker-list.tsx +2 -2
  98. package/src/components/shared/bottom-sheet.tsx +31 -15
  99. package/src/components/shared/command-palette.tsx +111 -24
  100. package/src/components/shared/confirm-dialog.tsx +45 -30
  101. package/src/components/shared/model-combobox.tsx +90 -8
  102. package/src/components/shared/settings/plugin-manager.tsx +20 -4
  103. package/src/components/shared/settings/section-capability-policy.tsx +105 -0
  104. package/src/components/shared/settings/section-heartbeat.tsx +88 -6
  105. package/src/components/shared/settings/section-orchestrator.tsx +6 -3
  106. package/src/components/shared/settings/section-runtime-loop.tsx +5 -5
  107. package/src/components/shared/settings/section-secrets.tsx +6 -6
  108. package/src/components/shared/settings/section-user-preferences.tsx +1 -1
  109. package/src/components/shared/settings/section-voice.tsx +5 -1
  110. package/src/components/shared/settings/section-web-search.tsx +10 -2
  111. package/src/components/shared/settings/settings-page.tsx +248 -47
  112. package/src/components/tasks/approvals-panel.tsx +211 -18
  113. package/src/components/tasks/task-board.tsx +242 -46
  114. package/src/components/ui/dialog.tsx +2 -2
  115. package/src/components/usage/metrics-dashboard.tsx +74 -1
  116. package/src/components/wallets/wallet-approval-dialog.tsx +59 -54
  117. package/src/components/wallets/wallet-panel.tsx +17 -5
  118. package/src/components/webhooks/webhook-sheet.tsx +7 -7
  119. package/src/lib/auth.ts +17 -0
  120. package/src/lib/chat-streaming-state.test.ts +108 -0
  121. package/src/lib/chat-streaming-state.ts +108 -0
  122. package/src/lib/heartbeat-defaults.ts +48 -0
  123. package/src/lib/memory-presentation.ts +59 -0
  124. package/src/lib/openclaw-agent-id.test.ts +14 -0
  125. package/src/lib/openclaw-agent-id.ts +31 -0
  126. package/src/lib/provider-model-discovery-client.ts +29 -0
  127. package/src/lib/providers/index.ts +12 -5
  128. package/src/lib/runtime-loop.ts +105 -3
  129. package/src/lib/safe-storage.ts +6 -1
  130. package/src/lib/server/agent-assignment.test.ts +112 -0
  131. package/src/lib/server/agent-assignment.ts +169 -0
  132. package/src/lib/server/agent-runtime-config.test.ts +141 -0
  133. package/src/lib/server/agent-runtime-config.ts +277 -0
  134. package/src/lib/server/approval-connector-notify.test.ts +253 -0
  135. package/src/lib/server/approvals-auto-approve.test.ts +264 -0
  136. package/src/lib/server/approvals.ts +483 -75
  137. package/src/lib/server/autonomy-runtime.test.ts +341 -0
  138. package/src/lib/server/browser-state.test.ts +118 -0
  139. package/src/lib/server/browser-state.ts +123 -0
  140. package/src/lib/server/build-llm.test.ts +44 -0
  141. package/src/lib/server/build-llm.ts +11 -4
  142. package/src/lib/server/builtin-plugins.ts +34 -0
  143. package/src/lib/server/chat-execution-heartbeat.test.ts +40 -0
  144. package/src/lib/server/chat-execution-tool-events.test.ts +219 -0
  145. package/src/lib/server/chat-execution.ts +402 -125
  146. package/src/lib/server/chatroom-health.test.ts +26 -0
  147. package/src/lib/server/chatroom-health.ts +2 -3
  148. package/src/lib/server/chatroom-helpers.test.ts +74 -2
  149. package/src/lib/server/chatroom-helpers.ts +144 -11
  150. package/src/lib/server/chatroom-session-persistence.test.ts +87 -0
  151. package/src/lib/server/connectors/discord.ts +175 -11
  152. package/src/lib/server/connectors/doctor.test.ts +80 -0
  153. package/src/lib/server/connectors/doctor.ts +116 -0
  154. package/src/lib/server/connectors/manager.ts +994 -130
  155. package/src/lib/server/connectors/policy.test.ts +222 -0
  156. package/src/lib/server/connectors/policy.ts +452 -0
  157. package/src/lib/server/connectors/slack.ts +189 -10
  158. package/src/lib/server/connectors/telegram.ts +65 -15
  159. package/src/lib/server/connectors/thread-context.test.ts +44 -0
  160. package/src/lib/server/connectors/thread-context.ts +72 -0
  161. package/src/lib/server/connectors/types.ts +41 -11
  162. package/src/lib/server/daemon-state.ts +62 -3
  163. package/src/lib/server/data-dir.ts +13 -0
  164. package/src/lib/server/delegation-jobs.test.ts +140 -0
  165. package/src/lib/server/delegation-jobs.ts +248 -0
  166. package/src/lib/server/document-utils.test.ts +47 -0
  167. package/src/lib/server/document-utils.ts +397 -0
  168. package/src/lib/server/eval/agent-regression.test.ts +47 -0
  169. package/src/lib/server/eval/agent-regression.ts +1742 -0
  170. package/src/lib/server/eval/runner.ts +11 -1
  171. package/src/lib/server/eval/store.ts +2 -1
  172. package/src/lib/server/heartbeat-service.ts +23 -43
  173. package/src/lib/server/heartbeat-source.test.ts +22 -0
  174. package/src/lib/server/heartbeat-source.ts +7 -0
  175. package/src/lib/server/identity-continuity.test.ts +77 -0
  176. package/src/lib/server/identity-continuity.ts +127 -0
  177. package/src/lib/server/mailbox-utils.ts +347 -0
  178. package/src/lib/server/main-agent-loop.ts +31 -964
  179. package/src/lib/server/memory-db.ts +4 -6
  180. package/src/lib/server/memory-tiers.ts +40 -0
  181. package/src/lib/server/openclaw-agent-resolver.test.ts +70 -0
  182. package/src/lib/server/openclaw-agent-resolver.ts +128 -0
  183. package/src/lib/server/openclaw-exec-config.ts +6 -5
  184. package/src/lib/server/openclaw-gateway.ts +123 -36
  185. package/src/lib/server/openclaw-skills-normalize.test.ts +56 -0
  186. package/src/lib/server/openclaw-skills-normalize.ts +136 -0
  187. package/src/lib/server/openclaw-sync.ts +3 -2
  188. package/src/lib/server/orchestrator-lg.ts +18 -8
  189. package/src/lib/server/orchestrator.ts +5 -4
  190. package/src/lib/server/playwright-proxy.mjs +27 -3
  191. package/src/lib/server/plugins.test.ts +215 -0
  192. package/src/lib/server/plugins.ts +832 -69
  193. package/src/lib/server/provider-health.ts +33 -3
  194. package/src/lib/server/provider-model-discovery.ts +481 -0
  195. package/src/lib/server/queue.ts +4 -21
  196. package/src/lib/server/runtime-settings.test.ts +119 -0
  197. package/src/lib/server/runtime-settings.ts +12 -92
  198. package/src/lib/server/schedule-normalization.ts +187 -0
  199. package/src/lib/server/scheduler.ts +2 -0
  200. package/src/lib/server/session-archive-memory.test.ts +85 -0
  201. package/src/lib/server/session-archive-memory.ts +230 -0
  202. package/src/lib/server/session-mailbox.ts +8 -18
  203. package/src/lib/server/session-reset-policy.test.ts +99 -0
  204. package/src/lib/server/session-reset-policy.ts +311 -0
  205. package/src/lib/server/session-run-manager.ts +33 -80
  206. package/src/lib/server/session-tools/autonomy-tools.test.ts +128 -0
  207. package/src/lib/server/session-tools/calendar.ts +2 -12
  208. package/src/lib/server/session-tools/connector.ts +109 -8
  209. package/src/lib/server/session-tools/context.ts +14 -2
  210. package/src/lib/server/session-tools/crawl.ts +447 -0
  211. package/src/lib/server/session-tools/crud.ts +96 -34
  212. package/src/lib/server/session-tools/delegate-fallback.test.ts +219 -0
  213. package/src/lib/server/session-tools/delegate.ts +406 -20
  214. package/src/lib/server/session-tools/discovery-approvals.test.ts +170 -0
  215. package/src/lib/server/session-tools/discovery.ts +40 -12
  216. package/src/lib/server/session-tools/document.ts +283 -0
  217. package/src/lib/server/session-tools/email.ts +1 -3
  218. package/src/lib/server/session-tools/extract.ts +137 -0
  219. package/src/lib/server/session-tools/file-normalize.test.ts +98 -0
  220. package/src/lib/server/session-tools/file-send.test.ts +84 -1
  221. package/src/lib/server/session-tools/file.ts +243 -24
  222. package/src/lib/server/session-tools/http.ts +9 -3
  223. package/src/lib/server/session-tools/human-loop.ts +227 -0
  224. package/src/lib/server/session-tools/image-gen.ts +1 -3
  225. package/src/lib/server/session-tools/index.ts +87 -2
  226. package/src/lib/server/session-tools/mailbox.ts +276 -0
  227. package/src/lib/server/session-tools/manage-schedules.test.ts +137 -0
  228. package/src/lib/server/session-tools/memory.ts +35 -3
  229. package/src/lib/server/session-tools/monitor.ts +162 -12
  230. package/src/lib/server/session-tools/normalize-tool-args.ts +17 -14
  231. package/src/lib/server/session-tools/openclaw-nodes.test.ts +111 -0
  232. package/src/lib/server/session-tools/openclaw-nodes.ts +86 -20
  233. package/src/lib/server/session-tools/platform-normalize.test.ts +142 -0
  234. package/src/lib/server/session-tools/platform.ts +142 -4
  235. package/src/lib/server/session-tools/plugin-creator.ts +95 -25
  236. package/src/lib/server/session-tools/primitive-tools.test.ts +257 -0
  237. package/src/lib/server/session-tools/replicate.ts +1 -3
  238. package/src/lib/server/session-tools/sandbox.ts +51 -92
  239. package/src/lib/server/session-tools/schedule.ts +20 -10
  240. package/src/lib/server/session-tools/session-info.ts +58 -4
  241. package/src/lib/server/session-tools/session-tools-wiring.test.ts +54 -17
  242. package/src/lib/server/session-tools/shell.ts +2 -2
  243. package/src/lib/server/session-tools/subagent.ts +195 -27
  244. package/src/lib/server/session-tools/table.ts +587 -0
  245. package/src/lib/server/session-tools/wallet.ts +13 -10
  246. package/src/lib/server/session-tools/web-browser-config.test.ts +39 -0
  247. package/src/lib/server/session-tools/web.ts +947 -108
  248. package/src/lib/server/storage.ts +255 -10
  249. package/src/lib/server/stream-agent-chat.test.ts +61 -0
  250. package/src/lib/server/stream-agent-chat.ts +185 -25
  251. package/src/lib/server/structured-extract.test.ts +72 -0
  252. package/src/lib/server/structured-extract.ts +373 -0
  253. package/src/lib/server/task-mention.test.ts +16 -2
  254. package/src/lib/server/task-mention.ts +61 -11
  255. package/src/lib/server/tool-aliases.ts +80 -12
  256. package/src/lib/server/tool-capability-policy.ts +7 -1
  257. package/src/lib/server/tool-retry.ts +2 -0
  258. package/src/lib/server/watch-jobs.test.ts +173 -0
  259. package/src/lib/server/watch-jobs.ts +532 -0
  260. package/src/lib/server/ws-hub.ts +5 -3
  261. package/src/lib/setup-defaults.ts +352 -11
  262. package/src/lib/tool-definitions.ts +3 -4
  263. package/src/lib/validation/schemas.test.ts +26 -0
  264. package/src/lib/validation/schemas.ts +62 -1
  265. package/src/lib/ws-client.ts +14 -12
  266. package/src/proxy.ts +5 -5
  267. package/src/stores/use-app-store.ts +43 -7
  268. package/src/stores/use-chat-store.ts +31 -2
  269. package/src/stores/use-chatroom-store.ts +153 -26
  270. package/src/types/index.ts +470 -44
  271. package/src/app/api/chats/[id]/main-loop/route.ts +0 -94
  272. package/src/components/chat/new-chat-sheet.tsx +0 -253
  273. package/src/lib/server/main-session.ts +0 -17
  274. package/src/lib/server/session-run-manager.test.ts +0 -26
@@ -14,22 +14,20 @@ function getDenoPath(): string | null {
14
14
  return findBinaryOnPath('deno')
15
15
  }
16
16
 
17
- function getNodePath(): string | null {
18
- return findBinaryOnPath('node')
19
- }
20
-
21
- function getTsxPath(): string | null {
22
- return findBinaryOnPath('tsx')
23
- }
24
-
25
- function getPythonPath(): string | null {
26
- return findBinaryOnPath('python3') ?? findBinaryOnPath('python')
27
- }
28
-
29
17
  const EXT_MAP: Record<string, string> = {
30
18
  javascript: 'js',
31
19
  typescript: 'ts',
32
- python: 'py',
20
+ }
21
+
22
+ function sandboxUnavailableError(reason: string): string {
23
+ return JSON.stringify({
24
+ error: reason,
25
+ guidance: [
26
+ 'Install Deno or run `npm run setup:easy` to enable sandbox_exec.',
27
+ 'Use http_request for straightforward API calls.',
28
+ 'Use plugin_creator plus manage_schedules for recurring automations.',
29
+ ],
30
+ })
33
31
  }
34
32
 
35
33
  /**
@@ -45,18 +43,13 @@ async function executeSandboxExec(args: any, context: { sessionId?: string; cwd?
45
43
  const sessionId = context.sessionId ?? 'unknown'
46
44
  const sandboxDir = path.join('/tmp', `swarmclaw-sandbox-${sessionId}-${Date.now()}`)
47
45
  const denoPath = getDenoPath()
48
- const nodePath = getNodePath()
49
- const tsxPath = getTsxPath()
50
- const pythonPath = getPythonPath()
51
46
 
52
- if (language === 'javascript' && !denoPath && !nodePath) {
53
- return JSON.stringify({ error: 'No JavaScript runtime available. Install Deno or Node.js.' })
54
- }
55
- if (language === 'typescript' && !denoPath && !tsxPath) {
56
- return JSON.stringify({ error: 'No TypeScript runtime available. Install Deno or tsx.' })
47
+ if (language !== 'javascript' && language !== 'typescript') {
48
+ return sandboxUnavailableError('sandbox_exec currently supports only JavaScript and TypeScript via Deno.')
57
49
  }
58
- if (language === 'python' && !pythonPath) {
59
- return JSON.stringify({ error: 'Python is not installed.' })
50
+
51
+ if (!denoPath) {
52
+ return sandboxUnavailableError('Deno is required for sandbox_exec. Unsafe Node/Python fallbacks are disabled.')
60
53
  }
61
54
 
62
55
  try {
@@ -65,36 +58,15 @@ async function executeSandboxExec(args: any, context: { sessionId?: string; cwd?
65
58
  const scriptPath = path.join(sandboxDir, scriptFile)
66
59
  fs.writeFileSync(scriptPath, code, 'utf-8')
67
60
 
68
- let result: ReturnType<typeof spawnSync>
69
-
70
- if (language === 'javascript') {
71
- if (denoPath) {
72
- result = spawnSync(denoPath, [
73
- 'run', '--allow-read=.', '--allow-write=.', '--allow-net', '--deny-env', '--no-prompt', scriptFile,
74
- ], { cwd: sandboxDir, encoding: 'utf-8', timeout, maxBuffer: MAX_OUTPUT })
75
- } else {
76
- result = spawnSync(nodePath!, [scriptPath], {
77
- cwd: sandboxDir, encoding: 'utf-8', timeout, maxBuffer: MAX_OUTPUT,
78
- env: { PATH: process.env.PATH || '/usr/bin:/bin' } as any,
79
- })
80
- }
81
- } else if (language === 'typescript') {
82
- if (denoPath) {
83
- result = spawnSync(denoPath, [
84
- 'run', '--allow-read=.', '--allow-write=.', '--allow-net', '--deny-env', '--no-prompt', scriptFile,
85
- ], { cwd: sandboxDir, encoding: 'utf-8', timeout, maxBuffer: MAX_OUTPUT })
86
- } else {
87
- result = spawnSync(tsxPath!, [scriptPath], {
88
- cwd: sandboxDir, encoding: 'utf-8', timeout, maxBuffer: MAX_OUTPUT,
89
- env: { PATH: process.env.PATH || '/usr/bin:/bin' } as any,
90
- })
91
- }
92
- } else {
93
- result = spawnSync(pythonPath!, [scriptPath], {
94
- cwd: sandboxDir, encoding: 'utf-8', timeout, maxBuffer: MAX_OUTPUT,
95
- env: { PATH: process.env.PATH || '/usr/bin:/bin' } as any,
96
- })
97
- }
61
+ const result = spawnSync(denoPath, [
62
+ 'run',
63
+ '--allow-read=.',
64
+ '--allow-write=.',
65
+ '--allow-net',
66
+ '--deny-env',
67
+ '--no-prompt',
68
+ scriptFile,
69
+ ], { cwd: sandboxDir, encoding: 'utf-8', timeout, maxBuffer: MAX_OUTPUT })
98
70
 
99
71
  const stdout = truncate((result.stdout || '').toString(), MAX_OUTPUT)
100
72
  const stderr = truncate((result.stderr || '').toString(), MAX_OUTPUT)
@@ -125,16 +97,18 @@ async function executeSandboxExec(args: any, context: { sessionId?: string; cwd?
125
97
  }
126
98
 
127
99
  async function executeListRuntimes() {
128
- const runtimes: Record<string, any> = {}
129
- for (const [name, bin] of [['deno', getDenoPath()], ['node', getNodePath()], ['tsx', getTsxPath()], ['python', getPythonPath()]] as const) {
130
- if (bin) {
131
- const ver = spawnSync(bin, ['--version'], { encoding: 'utf-8', timeout: 3000 })
132
- runtimes[name] = { available: true, version: (ver.stdout || '').split('\n')[0]?.trim() || null }
133
- } else {
134
- runtimes[name] = { available: false }
135
- }
100
+ const denoPath = getDenoPath()
101
+ if (!denoPath) {
102
+ return sandboxUnavailableError('Deno is not available for sandbox_exec.')
136
103
  }
137
- return JSON.stringify(runtimes)
104
+ const ver = spawnSync(denoPath, ['--version'], { encoding: 'utf-8', timeout: 3000 })
105
+ return JSON.stringify({
106
+ deno: {
107
+ available: true,
108
+ version: (ver.stdout || '').split('\n')[0]?.trim() || null,
109
+ },
110
+ sandboxReady: true,
111
+ })
138
112
  }
139
113
 
140
114
  /**
@@ -142,18 +116,23 @@ async function executeListRuntimes() {
142
116
  */
143
117
  const SandboxPlugin: Plugin = {
144
118
  name: 'Core Sandbox',
145
- description: 'Secure isolated code execution for JS, TS, and Python.',
119
+ description: 'Deno-based isolated code execution for JavaScript and TypeScript when custom code is necessary.',
146
120
  hooks: {
147
- getCapabilityDescription: () => 'I can run code in a sandbox (`sandbox_exec`) JS/TS via Deno or Python, in an isolated environment. I get stdout, stderr, and any files created.',
121
+ getCapabilityDescription: () => 'I can run JavaScript or TypeScript in a Deno sandbox (`sandbox_exec`) when custom code is necessary. For straightforward API calls, use `http_request` instead.',
122
+ getOperatingGuidance: () => [
123
+ 'Use `http_request` for straightforward REST/JSON API calls instead of writing code in `sandbox_exec`.',
124
+ 'Use `sandbox_exec` only when custom parsing or transformation code is actually needed.',
125
+ 'For recurring automations, prefer `plugin_creator` plus `manage_schedules` over repeated sandbox runs.',
126
+ ],
148
127
  } as PluginHooks,
149
128
  tools: [
150
129
  {
151
130
  name: 'sandbox_exec',
152
- description: 'Execute code in an isolated sandbox.',
131
+ description: 'Execute JavaScript or TypeScript in a Deno sandbox when custom code is necessary.',
153
132
  parameters: {
154
133
  type: 'object',
155
134
  properties: {
156
- language: { type: 'string', enum: ['javascript', 'typescript', 'python'] },
135
+ language: { type: 'string', enum: ['javascript', 'typescript'] },
157
136
  code: { type: 'string' },
158
137
  timeoutSec: { type: 'number' }
159
138
  },
@@ -163,7 +142,7 @@ const SandboxPlugin: Plugin = {
163
142
  },
164
143
  {
165
144
  name: 'sandbox_list_runtimes',
166
- description: 'List available sandbox runtimes.',
145
+ description: 'Report whether the Deno sandbox runtime is available.',
167
146
  parameters: { type: 'object', properties: {} },
168
147
  execute: async () => executeListRuntimes()
169
148
  }
@@ -185,7 +164,11 @@ export function buildSandboxTools(bctx: ToolBuildContext): StructuredToolInterfa
185
164
  {
186
165
  name: 'sandbox_exec',
187
166
  description: SandboxPlugin.tools![0].description,
188
- schema: z.object({}).passthrough()
167
+ schema: z.object({
168
+ language: z.enum(['javascript', 'typescript']),
169
+ code: z.string(),
170
+ timeoutSec: z.number().optional(),
171
+ })
189
172
  }
190
173
  ),
191
174
  tool(
@@ -198,29 +181,5 @@ export function buildSandboxTools(bctx: ToolBuildContext): StructuredToolInterfa
198
181
  )
199
182
  )
200
183
 
201
- const openclawPath = findBinaryOnPath('openclaw') || findBinaryOnPath('clawdbot')
202
- if (openclawPath) {
203
- tools.push(
204
- tool(
205
- async (rawArgs) => {
206
- const normalized = normalizeToolInputArgs((rawArgs ?? {}) as Record<string, unknown>)
207
- const code = normalized.code as string | undefined
208
- const explain = normalized.explain as boolean | undefined
209
- try {
210
- if (!code) return JSON.stringify({ error: 'code is required' })
211
- const args = explain ? ['sandbox', 'explain', code] : ['sandbox', 'run', code]
212
- const result = spawnSync(openclawPath, args, { encoding: 'utf-8', timeout: 60_000, maxBuffer: MAX_OUTPUT })
213
- return JSON.stringify({ exitCode: result.status ?? 0, stdout: truncate(result.stdout || '', MAX_OUTPUT), stderr: truncate(result.stderr || '', MAX_OUTPUT) })
214
- } catch (err: any) { return JSON.stringify({ error: err.message }) }
215
- },
216
- {
217
- name: 'openclaw_sandbox',
218
- description: 'Execute or explain code through OpenClaw CLI.',
219
- schema: z.object({ code: z.string(), explain: z.boolean().optional() }),
220
- }
221
- )
222
- )
223
- }
224
-
225
184
  return tools
226
185
  }
@@ -6,6 +6,7 @@ import type { ToolBuildContext } from './context'
6
6
  import type { Plugin, PluginHooks } from '@/types'
7
7
  import { getPluginManager } from '../plugins'
8
8
  import { normalizeToolInputArgs } from './normalize-tool-args'
9
+ import { createWatchJob } from '../watch-jobs'
9
10
 
10
11
  /**
11
12
  * Core Schedule Execution Logic
@@ -15,7 +16,7 @@ async function executeScheduleWake(args: { delayMinutes: number; message: string
15
16
  const delayMinutes = normalized.delayMinutes as number
16
17
  const message = normalized.message as string
17
18
  if (!context.sessionId) return 'Cannot schedule wake: no session context.'
18
- if (delayMinutes < 0 || delayMinutes > 1440) return 'delayMinutes must be between 0 and 1440 (24 hours).'
19
+ if (delayMinutes < 0 || delayMinutes > 43_200) return 'delayMinutes must be between 0 and 43200 (30 days).'
19
20
 
20
21
  if (delayMinutes === 0) {
21
22
  enqueueSystemEvent(context.sessionId, `[Scheduled Wake Event / Reminder] ${message}`)
@@ -23,15 +24,24 @@ async function executeScheduleWake(args: { delayMinutes: number; message: string
23
24
  return 'Successfully scheduled an immediate wake event.'
24
25
  }
25
26
 
26
- const delayMs = delayMinutes * 60 * 1000
27
- setTimeout(() => {
28
- if (context.sessionId) {
29
- enqueueSystemEvent(context.sessionId, `[Scheduled Wake Event / Reminder] ${message}`)
30
- requestHeartbeatNow({ sessionId: context.sessionId, reason: 'scheduled_wake' })
31
- }
32
- }, delayMs)
27
+ const runAt = Date.now() + delayMinutes * 60 * 1000
28
+ const watch = await createWatchJob({
29
+ type: 'time',
30
+ sessionId: context.sessionId,
31
+ resumeMessage: message,
32
+ description: `Scheduled wake in ${delayMinutes} minutes`,
33
+ target: { source: 'schedule_wake' },
34
+ condition: {},
35
+ runAt,
36
+ })
33
37
 
34
- return `Successfully scheduled a wake event in ${delayMinutes} minutes.`
38
+ return JSON.stringify({
39
+ ok: true,
40
+ jobId: watch.id,
41
+ delayMinutes,
42
+ runAt,
43
+ message,
44
+ })
35
45
  }
36
46
 
37
47
  /**
@@ -39,7 +49,7 @@ async function executeScheduleWake(args: { delayMinutes: number; message: string
39
49
  */
40
50
  const SchedulePlugin: Plugin = {
41
51
  name: 'Core Scheduler',
42
- description: 'Schedule wake events and reminders for agents.',
52
+ description: 'Schedule durable wake events and reminders for agents.',
43
53
  hooks: {
44
54
  getCapabilityDescription: () => 'I can set a conversational timer (`schedule_wake`) to remind myself to check back on something later in this chat.',
45
55
  } as PluginHooks,
@@ -25,14 +25,36 @@ async function executeWhoAmI(context: { sessionId?: string; agentId?: string })
25
25
  } catch (err: any) { return `Error: ${err.message}` }
26
26
  }
27
27
 
28
+ function inferSessionsAction(
29
+ normalized: Record<string, unknown>,
30
+ context: { sessionId?: string; agentId?: string },
31
+ ): string | undefined {
32
+ const explicit = typeof normalized.action === 'string' ? normalized.action.trim() : ''
33
+ if (explicit) return explicit
34
+
35
+ const hasUpdates = !!normalized.updates && typeof normalized.updates === 'object'
36
+ const hasSpawnTarget = typeof normalized.agentId === 'string' || typeof normalized.agent_id === 'string'
37
+ const hasHistoryTarget =
38
+ typeof normalized.sessionId === 'string'
39
+ || typeof normalized.session_id === 'string'
40
+ || typeof normalized.limit === 'number'
41
+ || !!context.sessionId
42
+
43
+ if (hasUpdates) return 'update'
44
+ if (hasSpawnTarget) return 'spawn'
45
+ if (hasHistoryTarget) return 'history'
46
+ return 'list'
47
+ }
48
+
28
49
  async function executeSessionsAction(args: any, context: { sessionId?: string; agentId?: string; cwd: string }) {
29
50
  const normalized = normalizeToolInputArgs((args ?? {}) as Record<string, unknown>)
30
- const action = normalized.action as string | undefined
51
+ const action = inferSessionsAction(normalized, context)
31
52
  const sessionId = (normalized.sessionId ?? normalized.session_id) as string | undefined
32
53
  const message = normalized.message as string | undefined
33
54
  const limit = normalized.limit as number | undefined
34
55
  const agentId = (normalized.agentId ?? normalized.agent_id) as string | undefined
35
56
  const name = normalized.name as string | undefined
57
+ const updates = normalized.updates as Record<string, unknown> | undefined
36
58
  try {
37
59
  const sessions = loadSessions()
38
60
  if (action === 'list') {
@@ -53,12 +75,43 @@ async function executeSessionsAction(args: any, context: { sessionId?: string; a
53
75
  sessions[id] = {
54
76
  id, name: (name || `${agent.name} Chat`).trim(), cwd: context.cwd, user: 'system',
55
77
  provider: agent.provider, model: agent.model, credentialId: agent.credentialId || null,
56
- messages: [], createdAt: now, lastActiveAt: now, sessionType: 'orchestrated',
78
+ messages: [], createdAt: now, lastActiveAt: now, sessionType: 'human',
57
79
  agentId: agent.id, parentSessionId: context.sessionId || undefined, plugins: agent.plugins || agent.tools || [],
58
80
  }
59
81
  saveSessions(sessions)
60
82
  return JSON.stringify({ sessionId: id, name: agent.name })
61
83
  }
84
+ if (action === 'update') {
85
+ const targetId = sessionId || context.sessionId || ''
86
+ if (!targetId) return 'sessionId required.'
87
+ const target = sessions[targetId]
88
+ if (!target) return 'Not found.'
89
+ const allowedKeys = new Set([
90
+ 'thinkingLevel',
91
+ 'connectorThinkLevel',
92
+ 'sessionResetMode',
93
+ 'sessionIdleTimeoutSec',
94
+ 'sessionMaxAgeSec',
95
+ 'sessionDailyResetAt',
96
+ 'sessionResetTimezone',
97
+ 'connectorSessionScope',
98
+ 'connectorReplyMode',
99
+ 'connectorThreadBinding',
100
+ 'connectorGroupPolicy',
101
+ 'connectorIdleTimeoutSec',
102
+ 'connectorMaxAgeSec',
103
+ 'identityState',
104
+ 'provider',
105
+ 'model',
106
+ ])
107
+ const patch = updates && typeof updates === 'object' ? updates : {}
108
+ for (const [key, value] of Object.entries(patch)) {
109
+ if (!allowedKeys.has(key)) continue
110
+ target[key] = value
111
+ }
112
+ saveSessions(sessions)
113
+ return JSON.stringify({ sessionId: targetId, updated: Object.keys(patch).filter((key) => allowedKeys.has(key)) })
114
+ }
62
115
  return `Unknown action "${action}".`
63
116
  } catch (err: any) { return `Error: ${err.message}` }
64
117
  }
@@ -86,11 +139,12 @@ const SessionInfoPlugin: Plugin = {
86
139
  parameters: {
87
140
  type: 'object',
88
141
  properties: {
89
- action: { type: 'string', enum: ['list', 'history', 'spawn', 'status', 'stop'] },
142
+ action: { type: 'string', enum: ['list', 'history', 'spawn', 'status', 'stop', 'update'] },
90
143
  sessionId: { type: 'string' },
91
144
  agentId: { type: 'string' },
92
145
  message: { type: 'string' },
93
- limit: { type: 'number' }
146
+ limit: { type: 'number' },
147
+ updates: { type: 'object' },
94
148
  },
95
149
  required: ['action']
96
150
  },
@@ -24,6 +24,23 @@ describe('module exports', () => {
24
24
  const mem = await import('./memory')
25
25
  assert.equal(typeof mem.buildMemoryTools, 'function')
26
26
  })
27
+
28
+ it('primitive tool builders are exported', async () => {
29
+ const document = await import('./document')
30
+ const extract = await import('./extract')
31
+ const table = await import('./table')
32
+ const crawl = await import('./crawl')
33
+ const mailbox = await import('./mailbox')
34
+ const humanLoop = await import('./human-loop')
35
+ const sandbox = await import('./sandbox')
36
+ assert.equal(typeof document.buildDocumentTools, 'function')
37
+ assert.equal(typeof extract.buildExtractTools, 'function')
38
+ assert.equal(typeof table.buildTableTools, 'function')
39
+ assert.equal(typeof crawl.buildCrawlTools, 'function')
40
+ assert.equal(typeof mailbox.buildMailboxTools, 'function')
41
+ assert.equal(typeof humanLoop.buildHumanLoopTools, 'function')
42
+ assert.equal(typeof sandbox.buildSandboxTools, 'function')
43
+ })
27
44
  })
28
45
 
29
46
  // ---------------------------------------------------------------------------
@@ -57,49 +74,69 @@ describe('buildSessionTools signature', () => {
57
74
  // Verify the function has arity of at least 2
58
75
  assert.ok(buildSessionTools.length >= 2, 'buildSessionTools should accept at least 2 params')
59
76
  })
77
+
78
+ it('sandbox builder exposes only the local Deno sandbox tools', async () => {
79
+ const { buildSandboxTools } = await import('./sandbox')
80
+ const bctx: import('./context').ToolBuildContext = {
81
+ cwd: process.cwd(),
82
+ ctx: { sessionId: 'sandbox-test' },
83
+ hasPlugin: (name) => name === 'sandbox',
84
+ hasTool: (name) => name === 'sandbox',
85
+ cleanupFns: [],
86
+ commandTimeoutMs: 1_000,
87
+ claudeTimeoutMs: 1_000,
88
+ cliProcessTimeoutMs: 1_000,
89
+ persistDelegateResumeId: () => {},
90
+ readStoredDelegateResumeId: () => null,
91
+ resolveCurrentSession: () => null,
92
+ activePlugins: ['sandbox'],
93
+ }
94
+
95
+ const tools = buildSandboxTools(bctx).map((tool) => tool.name).sort()
96
+ assert.deepEqual(tools, ['sandbox_exec', 'sandbox_list_runtimes'])
97
+ })
60
98
  })
61
99
 
62
100
  // ---------------------------------------------------------------------------
63
- // 4. Memory tool schema — knowledge actions
101
+ // 4. Memory tool schema
64
102
  // buildMemoryTools calls getMemoryDb() eagerly so we cannot invoke it
65
103
  // without a real SQLite DB. Instead we read the source and verify the
66
- // action enum includes the knowledge actions.
104
+ // declared action enum matches the current JSON schema definition.
67
105
  // ---------------------------------------------------------------------------
68
106
  describe('memory tool knowledge actions (source verification)', () => {
69
- it('action enum in memory.ts includes knowledge_store and knowledge_search', async () => {
107
+ it('action enum in memory.ts includes the declared base actions', async () => {
70
108
  const fs = await import('fs')
71
109
  const src = fs.readFileSync(
72
110
  new URL('./memory.ts', import.meta.url).pathname,
73
111
  'utf-8',
74
112
  )
75
113
 
76
- // Find the z.enum([...]) for the action field
77
- const enumMatch = src.match(/z\.enum\(\[([^\]]+)\]\)\.describe\([^)]*action/s)
78
- assert.ok(enumMatch, 'Should find a z.enum() for the action field')
114
+ const enumMatch = src.match(/action:\s*\{\s*type:\s*'string',\s*enum:\s*\[([^\]]+)\]/s)
115
+ assert.ok(enumMatch, 'Should find the action enum in the memory tool schema')
79
116
 
80
117
  const enumBody = enumMatch![1]
81
- assert.ok(enumBody.includes("'knowledge_store'"), 'action enum should include knowledge_store')
82
- assert.ok(enumBody.includes("'knowledge_search'"), 'action enum should include knowledge_search')
118
+ const expectedActions = ['store', 'get', 'search', 'list', 'delete']
119
+ for (const action of expectedActions) {
120
+ assert.ok(
121
+ enumBody.includes(`'${action}'`),
122
+ `action enum should include '${action}'`,
123
+ )
124
+ }
83
125
  })
84
126
 
85
- it('action enum includes all expected base actions', async () => {
127
+ it('action enum does not advertise removed knowledge actions', async () => {
86
128
  const fs = await import('fs')
87
129
  const src = fs.readFileSync(
88
130
  new URL('./memory.ts', import.meta.url).pathname,
89
131
  'utf-8',
90
132
  )
91
133
 
92
- const enumMatch = src.match(/z\.enum\(\[([^\]]+)\]\)/)
134
+ const enumMatch = src.match(/action:\s*\{\s*type:\s*'string',\s*enum:\s*\[([^\]]+)\]/s)
93
135
  assert.ok(enumMatch)
94
136
  const enumBody = enumMatch![1]
95
137
 
96
- const expectedActions = ['store', 'get', 'search', 'list', 'delete', 'link', 'unlink', 'knowledge_store', 'knowledge_search']
97
- for (const action of expectedActions) {
98
- assert.ok(
99
- enumBody.includes(`'${action}'`),
100
- `action enum should include '${action}'`,
101
- )
102
- }
138
+ assert.equal(enumBody.includes("'knowledge_store'"), false)
139
+ assert.equal(enumBody.includes("'knowledge_search'"), false)
103
140
  })
104
141
  })
105
142
 
@@ -163,8 +163,8 @@ const ShellPlugin: Plugin = {
163
163
  name: 'Core Shell',
164
164
  description: 'Execute shell commands and manage background processes.',
165
165
  hooks: {
166
- getCapabilityDescription: () => 'I can run shell commands (`execute_command`) servers, installs, scripts, git, builds, anything. I can run things in the background for long-lived processes like dev servers.',
167
- getOperatingGuidance: () => ['Shell: use `execute_command` for servers, installs, scripts, git. Use `background=true` for long-lived processes.', 'Verify servers with `process_tool` status/log and liveness probes before claiming success.', 'Resolve IPs/URLs via shell — never use placeholders. Retry path errors without workdir override.'],
166
+ getCapabilityDescription: () => 'I can run shell commands with the unified `shell` tool. Use action `execute` for commands, and `list` / `status` / `poll` / `log` for long-lived processes.',
167
+ getOperatingGuidance: () => ['Shell: use `shell` with `{"action":"execute","command":"..."}` for servers, installs, scripts, and git. Use `background=true` for long-lived processes.', 'Verify servers with `shell` status/log actions and liveness probes before claiming success.', 'Resolve IPs/URLs via shell — never use placeholders. Retry path errors without workdir override.'],
168
168
  } as PluginHooks,
169
169
  tools: [
170
170
  {