@swarmclawai/swarmclaw 0.7.1 → 0.7.3

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 (237) hide show
  1. package/README.md +155 -150
  2. package/package.json +1 -1
  3. package/src/app/api/agents/[id]/route.ts +26 -0
  4. package/src/app/api/agents/[id]/thread/route.ts +37 -9
  5. package/src/app/api/agents/route.ts +13 -2
  6. package/src/app/api/auth/route.ts +76 -7
  7. package/src/app/api/chatrooms/[id]/chat/route.ts +7 -2
  8. package/src/app/api/{sessions → chats}/[id]/browser/route.ts +5 -1
  9. package/src/app/api/{sessions → chats}/[id]/chat/route.ts +7 -3
  10. package/src/app/api/{sessions → chats}/[id]/checkpoints/route.ts +1 -1
  11. package/src/app/api/chats/[id]/main-loop/route.ts +13 -0
  12. package/src/app/api/{sessions → chats}/[id]/messages/route.ts +19 -13
  13. package/src/app/api/{sessions → chats}/[id]/restore/route.ts +1 -1
  14. package/src/app/api/{sessions → chats}/[id]/route.ts +22 -52
  15. package/src/app/api/{sessions → chats}/[id]/stop/route.ts +6 -1
  16. package/src/app/api/{sessions → chats}/route.ts +21 -7
  17. package/src/app/api/connectors/[id]/doctor/route.ts +26 -0
  18. package/src/app/api/connectors/doctor/route.ts +13 -0
  19. package/src/app/api/files/open/route.ts +16 -14
  20. package/src/app/api/memory/maintenance/route.ts +11 -1
  21. package/src/app/api/openclaw/agent-files/route.ts +27 -4
  22. package/src/app/api/openclaw/skills/route.ts +11 -3
  23. package/src/app/api/plugins/dependencies/route.ts +24 -0
  24. package/src/app/api/plugins/install/route.ts +15 -92
  25. package/src/app/api/plugins/route.ts +6 -26
  26. package/src/app/api/plugins/settings/route.ts +40 -0
  27. package/src/app/api/plugins/ui/route.ts +1 -0
  28. package/src/app/api/settings/route.ts +49 -7
  29. package/src/app/api/tasks/[id]/route.ts +15 -6
  30. package/src/app/api/tasks/bulk/route.ts +2 -2
  31. package/src/app/api/tasks/route.ts +9 -4
  32. package/src/app/api/usage/route.ts +30 -0
  33. package/src/app/api/webhooks/[id]/route.ts +8 -1
  34. package/src/app/page.tsx +9 -2
  35. package/src/cli/index.js +39 -33
  36. package/src/cli/index.ts +43 -49
  37. package/src/cli/spec.js +29 -27
  38. package/src/components/agents/agent-card.tsx +16 -13
  39. package/src/components/agents/agent-chat-list.tsx +104 -4
  40. package/src/components/agents/agent-list.tsx +54 -22
  41. package/src/components/agents/agent-sheet.tsx +209 -18
  42. package/src/components/agents/cron-job-form.tsx +3 -3
  43. package/src/components/agents/inspector-panel.tsx +110 -50
  44. package/src/components/auth/access-key-gate.tsx +36 -97
  45. package/src/components/auth/setup-wizard.tsx +5 -38
  46. package/src/components/chat/chat-area.tsx +39 -27
  47. package/src/components/{sessions/session-card.tsx → chat/chat-card.tsx} +7 -23
  48. package/src/components/chat/chat-header.tsx +299 -314
  49. package/src/components/{sessions/session-list.tsx → chat/chat-list.tsx} +11 -14
  50. package/src/components/chat/chat-tool-toggles.tsx +26 -17
  51. package/src/components/chat/checkpoint-timeline.tsx +4 -4
  52. package/src/components/chat/message-bubble.tsx +4 -1
  53. package/src/components/chat/message-list.tsx +5 -3
  54. package/src/components/chat/session-debug-panel.tsx +1 -1
  55. package/src/components/chat/tool-request-banner.tsx +3 -3
  56. package/src/components/chatrooms/agent-hover-card.tsx +3 -3
  57. package/src/components/chatrooms/chatroom-tool-request-banner.tsx +2 -2
  58. package/src/components/chatrooms/chatroom-view.tsx +347 -205
  59. package/src/components/connectors/connector-list.tsx +265 -127
  60. package/src/components/connectors/connector-sheet.tsx +218 -1
  61. package/src/components/home/home-view.tsx +129 -5
  62. package/src/components/layout/app-layout.tsx +392 -182
  63. package/src/components/layout/mobile-header.tsx +26 -8
  64. package/src/components/plugins/plugin-list.tsx +487 -254
  65. package/src/components/plugins/plugin-sheet.tsx +236 -13
  66. package/src/components/projects/project-detail.tsx +183 -0
  67. package/src/components/settings/gateway-connection-panel.tsx +1 -1
  68. package/src/components/shared/agent-picker-list.tsx +2 -2
  69. package/src/components/shared/command-palette.tsx +111 -25
  70. package/src/components/shared/settings/plugin-manager.tsx +20 -4
  71. package/src/components/shared/settings/section-capability-policy.tsx +105 -0
  72. package/src/components/shared/settings/section-heartbeat.tsx +78 -1
  73. package/src/components/shared/settings/section-orchestrator.tsx +3 -3
  74. package/src/components/shared/settings/section-providers.tsx +1 -1
  75. package/src/components/shared/settings/section-runtime-loop.tsx +5 -5
  76. package/src/components/shared/settings/section-secrets.tsx +6 -6
  77. package/src/components/shared/settings/section-user-preferences.tsx +1 -1
  78. package/src/components/shared/settings/section-voice.tsx +5 -1
  79. package/src/components/shared/settings/section-web-search.tsx +10 -2
  80. package/src/components/shared/settings/settings-page.tsx +244 -56
  81. package/src/components/tasks/approvals-panel.tsx +205 -18
  82. package/src/components/tasks/task-board.tsx +242 -46
  83. package/src/components/usage/metrics-dashboard.tsx +147 -1
  84. package/src/components/wallets/wallet-panel.tsx +17 -5
  85. package/src/components/webhooks/webhook-sheet.tsx +8 -8
  86. package/src/lib/auth.ts +17 -0
  87. package/src/lib/chat-streaming-state.test.ts +108 -0
  88. package/src/lib/chat-streaming-state.ts +108 -0
  89. package/src/lib/chat.ts +1 -1
  90. package/src/lib/{sessions.ts → chats.ts} +28 -18
  91. package/src/lib/openclaw-agent-id.test.ts +14 -0
  92. package/src/lib/openclaw-agent-id.ts +31 -0
  93. package/src/lib/providers/claude-cli.ts +1 -1
  94. package/src/lib/server/agent-assignment.test.ts +112 -0
  95. package/src/lib/server/agent-assignment.ts +169 -0
  96. package/src/lib/server/approval-connector-notify.test.ts +253 -0
  97. package/src/lib/server/approvals-auto-approve.test.ts +205 -0
  98. package/src/lib/server/approvals.ts +483 -75
  99. package/src/lib/server/autonomy-runtime.test.ts +341 -0
  100. package/src/lib/server/browser-state.test.ts +118 -0
  101. package/src/lib/server/browser-state.ts +123 -0
  102. package/src/lib/server/build-llm.test.ts +36 -0
  103. package/src/lib/server/build-llm.ts +11 -4
  104. package/src/lib/server/builtin-plugins.ts +34 -0
  105. package/src/lib/server/capability-router.ts +10 -8
  106. package/src/lib/server/chat-execution-heartbeat.test.ts +40 -0
  107. package/src/lib/server/chat-execution-tool-events.test.ts +134 -0
  108. package/src/lib/server/chat-execution.ts +285 -165
  109. package/src/lib/server/chatroom-health.test.ts +26 -0
  110. package/src/lib/server/chatroom-health.ts +2 -3
  111. package/src/lib/server/chatroom-helpers.test.ts +67 -2
  112. package/src/lib/server/chatroom-helpers.ts +48 -8
  113. package/src/lib/server/connectors/discord.ts +175 -11
  114. package/src/lib/server/connectors/doctor.test.ts +80 -0
  115. package/src/lib/server/connectors/doctor.ts +116 -0
  116. package/src/lib/server/connectors/manager.ts +948 -112
  117. package/src/lib/server/connectors/policy.test.ts +222 -0
  118. package/src/lib/server/connectors/policy.ts +452 -0
  119. package/src/lib/server/connectors/slack.ts +188 -9
  120. package/src/lib/server/connectors/telegram.ts +65 -15
  121. package/src/lib/server/connectors/thread-context.test.ts +44 -0
  122. package/src/lib/server/connectors/thread-context.ts +72 -0
  123. package/src/lib/server/connectors/types.ts +41 -11
  124. package/src/lib/server/cost.ts +34 -1
  125. package/src/lib/server/daemon-state.ts +61 -3
  126. package/src/lib/server/data-dir.ts +13 -0
  127. package/src/lib/server/delegation-jobs.test.ts +140 -0
  128. package/src/lib/server/delegation-jobs.ts +248 -0
  129. package/src/lib/server/document-utils.test.ts +47 -0
  130. package/src/lib/server/document-utils.ts +397 -0
  131. package/src/lib/server/heartbeat-service.ts +14 -40
  132. package/src/lib/server/heartbeat-source.test.ts +22 -0
  133. package/src/lib/server/heartbeat-source.ts +7 -0
  134. package/src/lib/server/identity-continuity.test.ts +77 -0
  135. package/src/lib/server/identity-continuity.ts +127 -0
  136. package/src/lib/server/mailbox-utils.ts +347 -0
  137. package/src/lib/server/main-agent-loop.ts +28 -1103
  138. package/src/lib/server/memory-db.ts +4 -6
  139. package/src/lib/server/memory-tiers.ts +40 -0
  140. package/src/lib/server/openclaw-agent-resolver.test.ts +70 -0
  141. package/src/lib/server/openclaw-agent-resolver.ts +128 -0
  142. package/src/lib/server/openclaw-exec-config.ts +5 -6
  143. package/src/lib/server/openclaw-skills-normalize.test.ts +56 -0
  144. package/src/lib/server/openclaw-skills-normalize.ts +136 -0
  145. package/src/lib/server/openclaw-sync.ts +3 -2
  146. package/src/lib/server/orchestrator-lg.ts +20 -9
  147. package/src/lib/server/orchestrator.ts +7 -7
  148. package/src/lib/server/playwright-proxy.mjs +27 -3
  149. package/src/lib/server/plugins.test.ts +207 -0
  150. package/src/lib/server/plugins.ts +927 -66
  151. package/src/lib/server/provider-health.ts +38 -6
  152. package/src/lib/server/queue.ts +13 -28
  153. package/src/lib/server/scheduler.ts +2 -0
  154. package/src/lib/server/session-archive-memory.test.ts +85 -0
  155. package/src/lib/server/session-archive-memory.ts +230 -0
  156. package/src/lib/server/session-mailbox.ts +8 -18
  157. package/src/lib/server/session-reset-policy.test.ts +99 -0
  158. package/src/lib/server/session-reset-policy.ts +311 -0
  159. package/src/lib/server/session-run-manager.ts +33 -82
  160. package/src/lib/server/session-tools/autonomy-tools.test.ts +105 -0
  161. package/src/lib/server/session-tools/calendar.ts +366 -0
  162. package/src/lib/server/session-tools/canvas.ts +1 -1
  163. package/src/lib/server/session-tools/chatroom.ts +4 -2
  164. package/src/lib/server/session-tools/connector.ts +114 -10
  165. package/src/lib/server/session-tools/context.ts +21 -5
  166. package/src/lib/server/session-tools/crawl.ts +447 -0
  167. package/src/lib/server/session-tools/crud.ts +74 -28
  168. package/src/lib/server/session-tools/delegate-fallback.test.ts +219 -0
  169. package/src/lib/server/session-tools/delegate.ts +497 -24
  170. package/src/lib/server/session-tools/discovery.ts +24 -6
  171. package/src/lib/server/session-tools/document.ts +283 -0
  172. package/src/lib/server/session-tools/edit_file.ts +4 -2
  173. package/src/lib/server/session-tools/email.ts +320 -0
  174. package/src/lib/server/session-tools/extract.ts +137 -0
  175. package/src/lib/server/session-tools/file-normalize.test.ts +93 -0
  176. package/src/lib/server/session-tools/file-send.test.ts +84 -1
  177. package/src/lib/server/session-tools/file.ts +241 -25
  178. package/src/lib/server/session-tools/git.ts +1 -1
  179. package/src/lib/server/session-tools/http.ts +1 -1
  180. package/src/lib/server/session-tools/human-loop.ts +227 -0
  181. package/src/lib/server/session-tools/image-gen.ts +380 -0
  182. package/src/lib/server/session-tools/index.ts +130 -50
  183. package/src/lib/server/session-tools/mailbox.ts +276 -0
  184. package/src/lib/server/session-tools/memory.ts +172 -3
  185. package/src/lib/server/session-tools/monitor.ts +151 -8
  186. package/src/lib/server/session-tools/normalize-tool-args.ts +17 -14
  187. package/src/lib/server/session-tools/openclaw-nodes.ts +1 -1
  188. package/src/lib/server/session-tools/openclaw-workspace.ts +1 -1
  189. package/src/lib/server/session-tools/platform-normalize.test.ts +142 -0
  190. package/src/lib/server/session-tools/platform.ts +148 -7
  191. package/src/lib/server/session-tools/plugin-creator.ts +89 -26
  192. package/src/lib/server/session-tools/primitive-tools.test.ts +257 -0
  193. package/src/lib/server/session-tools/replicate.ts +301 -0
  194. package/src/lib/server/session-tools/sample-ui.ts +1 -1
  195. package/src/lib/server/session-tools/sandbox.ts +4 -2
  196. package/src/lib/server/session-tools/schedule.ts +24 -12
  197. package/src/lib/server/session-tools/session-info.ts +43 -7
  198. package/src/lib/server/session-tools/session-tools-wiring.test.ts +31 -17
  199. package/src/lib/server/session-tools/shell.ts +5 -2
  200. package/src/lib/server/session-tools/subagent.ts +194 -28
  201. package/src/lib/server/session-tools/table.ts +587 -0
  202. package/src/lib/server/session-tools/wallet.ts +42 -12
  203. package/src/lib/server/session-tools/web-browser-config.test.ts +39 -0
  204. package/src/lib/server/session-tools/web.ts +926 -91
  205. package/src/lib/server/storage.ts +255 -16
  206. package/src/lib/server/stream-agent-chat.ts +116 -268
  207. package/src/lib/server/structured-extract.test.ts +72 -0
  208. package/src/lib/server/structured-extract.ts +373 -0
  209. package/src/lib/server/task-mention.test.ts +16 -2
  210. package/src/lib/server/task-mention.ts +61 -10
  211. package/src/lib/server/tool-aliases.ts +66 -18
  212. package/src/lib/server/tool-capability-policy.test.ts +9 -9
  213. package/src/lib/server/tool-capability-policy.ts +38 -27
  214. package/src/lib/server/tool-retry.ts +2 -0
  215. package/src/lib/server/watch-jobs.test.ts +173 -0
  216. package/src/lib/server/watch-jobs.ts +532 -0
  217. package/src/lib/server/ws-hub.ts +5 -3
  218. package/src/lib/tool-definitions.ts +4 -0
  219. package/src/lib/validation/schemas.test.ts +26 -0
  220. package/src/lib/validation/schemas.ts +10 -1
  221. package/src/lib/ws-client.ts +14 -12
  222. package/src/proxy.ts +5 -5
  223. package/src/stores/use-app-store.ts +5 -11
  224. package/src/stores/use-chat-store.ts +38 -9
  225. package/src/types/index.ts +352 -47
  226. package/src/app/api/sessions/[id]/main-loop/route.ts +0 -94
  227. package/src/components/sessions/new-session-sheet.tsx +0 -253
  228. package/src/lib/server/main-session.ts +0 -24
  229. package/src/lib/server/session-run-manager.test.ts +0 -23
  230. /package/src/app/api/{sessions → chats}/[id]/clear/route.ts +0 -0
  231. /package/src/app/api/{sessions → chats}/[id]/deploy/route.ts +0 -0
  232. /package/src/app/api/{sessions → chats}/[id]/devserver/route.ts +0 -0
  233. /package/src/app/api/{sessions → chats}/[id]/edit-resend/route.ts +0 -0
  234. /package/src/app/api/{sessions → chats}/[id]/fork/route.ts +0 -0
  235. /package/src/app/api/{sessions → chats}/[id]/mailbox/route.ts +0 -0
  236. /package/src/app/api/{sessions → chats}/[id]/retry/route.ts +0 -0
  237. /package/src/app/api/{sessions → chats}/heartbeat/route.ts +0 -0
@@ -8,16 +8,98 @@ import { getPluginManager } from '../plugins'
8
8
  import type { Plugin, PluginHooks } from '@/types'
9
9
  import { safePath, truncate } from './context'
10
10
  import { normalizeToolInputArgs } from './normalize-tool-args'
11
+ import { cancelWatchJob, createWatchJob, getWatchJob, listWatchJobs } from '../watch-jobs'
12
+ import { ensureSessionBrowserProfileId, loadBrowserSessionRecord } from '../browser-state'
13
+
14
+ type WatchKind = 'time' | 'http' | 'file' | 'task' | 'webhook' | 'page'
15
+
16
+ async function createDurableWatch(
17
+ normalized: Record<string, unknown>,
18
+ bctx: { cwd: string; sessionId?: string; agentId?: string | null },
19
+ explicitType?: WatchKind,
20
+ ) {
21
+ const watchType = (explicitType || String(normalized.watchType || normalized.type || '').trim().toLowerCase()) as WatchKind
22
+ if (!watchType) return 'Error: watchType is required.'
23
+ if (!['time', 'http', 'file', 'task', 'webhook', 'page'].includes(watchType)) {
24
+ return `Error: Unsupported watchType "${watchType}".`
25
+ }
26
+
27
+ const sessionId = typeof normalized.sessionId === 'string' ? normalized.sessionId : bctx.sessionId
28
+ const agentId = typeof normalized.agentId === 'string' ? normalized.agentId : (bctx.agentId || undefined)
29
+ const resumeMessage = String(normalized.resumeMessage || normalized.message || '').trim()
30
+ if (!resumeMessage) return 'Error: resumeMessage is required.'
31
+
32
+ const target = (normalized.target ?? normalized.url ?? normalized.path) as string | undefined
33
+ const delayMinutes = typeof normalized.delayMinutes === 'number' ? normalized.delayMinutes : undefined
34
+ const runAt = typeof normalized.runAt === 'number'
35
+ ? normalized.runAt
36
+ : delayMinutes !== undefined
37
+ ? Date.now() + Math.max(0, delayMinutes) * 60_000
38
+ : undefined
39
+ const intervalMs = typeof normalized.intervalSec === 'number'
40
+ ? Math.max(15, normalized.intervalSec) * 1000
41
+ : typeof normalized.intervalMs === 'number'
42
+ ? Math.max(15_000, normalized.intervalMs)
43
+ : undefined
44
+ const timeoutAt = typeof normalized.timeoutMinutes === 'number'
45
+ ? Date.now() + Math.max(1, normalized.timeoutMinutes) * 60_000
46
+ : typeof normalized.timeoutAt === 'number'
47
+ ? normalized.timeoutAt
48
+ : undefined
49
+ const browserProfileId = sessionId ? ensureSessionBrowserProfileId(sessionId).profileId : null
50
+ const targetPath = watchType === 'file' && target ? safePath(bctx.cwd, target) : target
51
+ const pageUrl = watchType === 'page' && !target && sessionId
52
+ ? loadBrowserSessionRecord(sessionId)?.currentUrl || undefined
53
+ : undefined
54
+ const pageTarget = target || pageUrl
55
+ if ((watchType === 'http' || watchType === 'page') && !pageTarget) {
56
+ return `Error: ${watchType === 'page' ? 'url or active browser page' : 'url'} is required.`
57
+ }
58
+
59
+ const job = await createWatchJob({
60
+ type: watchType,
61
+ sessionId: sessionId || null,
62
+ agentId: agentId || null,
63
+ createdByAgentId: agentId || null,
64
+ browserProfileId,
65
+ description: typeof normalized.description === 'string' ? normalized.description : null,
66
+ resumeMessage,
67
+ runAt,
68
+ intervalMs,
69
+ timeoutAt,
70
+ target: {
71
+ url: watchType === 'http' || watchType === 'page' ? pageTarget : undefined,
72
+ path: watchType === 'file' ? targetPath : undefined,
73
+ taskId: watchType === 'task' ? String(normalized.taskId || normalized.id || '') : undefined,
74
+ webhookId: watchType === 'webhook' ? String(normalized.webhookId || normalized.id || '') : undefined,
75
+ baselineHash: undefined,
76
+ },
77
+ condition: {
78
+ containsText: typeof normalized.containsText === 'string' ? normalized.containsText : undefined,
79
+ textGone: typeof normalized.textGone === 'string' ? normalized.textGone : undefined,
80
+ regex: typeof normalized.regex === 'string' ? normalized.regex : undefined,
81
+ changed: normalized.changed === true,
82
+ exists: normalized.exists,
83
+ status: typeof normalized.status === 'number' ? normalized.status : undefined,
84
+ statusIn: Array.isArray(normalized.statusIn) ? normalized.statusIn : undefined,
85
+ event: typeof normalized.event === 'string' ? normalized.event : undefined,
86
+ threshold: typeof normalized.threshold === 'number' ? normalized.threshold : undefined,
87
+ },
88
+ })
89
+ return JSON.stringify(job, null, 2)
90
+ }
11
91
 
12
92
  /**
13
93
  * Unified Monitoring Logic
14
94
  */
15
- async function executeMonitorAction(args: any, bctx: { cwd: string }) {
95
+ async function executeMonitorAction(args: any, bctx: { cwd: string; sessionId?: string; agentId?: string | null }) {
16
96
  const normalized = normalizeToolInputArgs((args ?? {}) as Record<string, unknown>)
17
97
  const action = normalized.action as string | undefined
18
98
  const target = (normalized.target ?? normalized.url ?? normalized.path) as string | undefined
19
99
  const limit = normalized.limit as number | undefined
20
100
  const threshold = normalized.threshold as number | undefined
101
+ const sessionId = typeof normalized.sessionId === 'string' ? normalized.sessionId : bctx.sessionId
102
+ const agentId = typeof normalized.agentId === 'string' ? normalized.agentId : (bctx.agentId || undefined)
21
103
 
22
104
  try {
23
105
  switch (action) {
@@ -65,6 +147,7 @@ async function executeMonitorAction(args: any, bctx: { cwd: string }) {
65
147
  status: res.status,
66
148
  ok: res.ok,
67
149
  latency: `${latency}ms`,
150
+ thresholdExceeded: typeof threshold === 'number' ? latency >= threshold : undefined,
68
151
  url
69
152
  }, null, 2)
70
153
  } catch (err: any) {
@@ -76,6 +159,55 @@ async function executeMonitorAction(args: any, bctx: { cwd: string }) {
76
159
  }
77
160
  }
78
161
 
162
+ case 'create_watch': {
163
+ return createDurableWatch(normalized, bctx)
164
+ }
165
+
166
+ case 'wait_until': {
167
+ return createDurableWatch(normalized, bctx, 'time')
168
+ }
169
+
170
+ case 'wait_for_http': {
171
+ return createDurableWatch(normalized, bctx, 'http')
172
+ }
173
+
174
+ case 'wait_for_file': {
175
+ return createDurableWatch(normalized, bctx, 'file')
176
+ }
177
+
178
+ case 'wait_for_task': {
179
+ return createDurableWatch(normalized, bctx, 'task')
180
+ }
181
+
182
+ case 'wait_for_webhook': {
183
+ return createDurableWatch(normalized, bctx, 'webhook')
184
+ }
185
+
186
+ case 'wait_for_page_change': {
187
+ return createDurableWatch(normalized, bctx, 'page')
188
+ }
189
+
190
+ case 'list_watches': {
191
+ const filterSessionId = normalized.all === true ? undefined : sessionId
192
+ return JSON.stringify(listWatchJobs({ sessionId: filterSessionId || null }), null, 2)
193
+ }
194
+
195
+ case 'get_watch': {
196
+ const id = String(normalized.id || '').trim()
197
+ if (!id) return 'Error: id is required.'
198
+ const job = getWatchJob(id)
199
+ if (!job) return `Error: watch job "${id}" not found.`
200
+ return JSON.stringify(job, null, 2)
201
+ }
202
+
203
+ case 'cancel_watch': {
204
+ const id = String(normalized.id || '').trim()
205
+ if (!id) return 'Error: id is required.'
206
+ const job = cancelWatchJob(id)
207
+ if (!job) return `Error: watch job "${id}" not found.`
208
+ return JSON.stringify(job, null, 2)
209
+ }
210
+
79
211
  default:
80
212
  return `Error: Unknown action "${action}"`
81
213
  }
@@ -89,22 +221,29 @@ async function executeMonitorAction(args: any, bctx: { cwd: string }) {
89
221
  */
90
222
  const MonitorPlugin: Plugin = {
91
223
  name: 'Core Monitor',
92
- description: 'System observability: check resource usage, watch logs, and ping endpoints.',
224
+ description: 'System observability and durable watch jobs: inspect system state, monitor files/endpoints/tasks, and resume agents when conditions trigger.',
93
225
  hooks: {} as PluginHooks,
94
226
  tools: [
95
227
  {
96
228
  name: 'monitor_tool',
97
- description: 'Observe system health, log activity, or endpoint availability.',
229
+ description: 'Observe system health, inspect logs/endpoints, or create durable waits like wait_for_http, wait_for_file, wait_for_webhook, and wait_for_page_change.',
98
230
  parameters: {
99
231
  type: 'object',
100
232
  properties: {
101
- action: { type: 'string', enum: ['sys_info', 'watch_log', 'ping'] },
233
+ action: { type: 'string', enum: ['sys_info', 'watch_log', 'ping', 'create_watch', 'wait_until', 'wait_for_http', 'wait_for_file', 'wait_for_task', 'wait_for_webhook', 'wait_for_page_change', 'list_watches', 'get_watch', 'cancel_watch'] },
102
234
  target: { type: 'string', description: 'Log file path (for watch_log) or URL (for ping)' },
103
- limit: { type: 'number', description: 'Number of lines or bytes to retrieve' }
235
+ limit: { type: 'number', description: 'Number of lines or bytes to retrieve' },
236
+ watchType: { type: 'string', enum: ['time', 'http', 'file', 'task', 'webhook', 'page'] },
237
+ resumeMessage: { type: 'string', description: 'Message injected when the watch triggers and the agent wakes up.' },
238
+ regex: { type: 'string', description: 'Regex pattern used by file/page/http watchers.' },
104
239
  },
105
240
  required: ['action']
106
241
  },
107
- execute: async (args, context) => executeMonitorAction(args, { cwd: context.session.cwd || process.cwd() })
242
+ execute: async (args, context) => executeMonitorAction(args, {
243
+ cwd: context.session.cwd || process.cwd(),
244
+ sessionId: context.session.id,
245
+ agentId: context.session.agentId,
246
+ })
108
247
  }
109
248
  ]
110
249
  }
@@ -112,10 +251,14 @@ const MonitorPlugin: Plugin = {
112
251
  getPluginManager().registerBuiltin('monitor', MonitorPlugin)
113
252
 
114
253
  export function buildMonitorTools(bctx: ToolBuildContext): StructuredToolInterface[] {
115
- if (!bctx.hasTool('monitor')) return []
254
+ if (!bctx.hasPlugin('monitor')) return []
116
255
  return [
117
256
  tool(
118
- async (args) => executeMonitorAction(args, { cwd: bctx.cwd }),
257
+ async (args) => executeMonitorAction(args, {
258
+ cwd: bctx.cwd,
259
+ sessionId: bctx.ctx?.sessionId || undefined,
260
+ agentId: bctx.ctx?.agentId || undefined,
261
+ }),
119
262
  {
120
263
  name: 'monitor_tool',
121
264
  description: MonitorPlugin.tools![0].description,
@@ -1,4 +1,5 @@
1
1
  export type ToolArgsRecord = Record<string, unknown>
2
+ const NESTED_WRAPPER_KEYS = ['input', 'args', 'arguments', 'payload', 'parameters'] as const
2
3
 
3
4
  function parseRecordCandidate(value: unknown): ToolArgsRecord | null {
4
5
  if (!value) return null
@@ -26,22 +27,24 @@ function parseRecordCandidate(value: unknown): ToolArgsRecord | null {
26
27
  * as either objects or JSON strings.
27
28
  */
28
29
  export function normalizeToolInputArgs(rawArgs: ToolArgsRecord): ToolArgsRecord {
29
- const nestedSources: Array<ToolArgsRecord | null> = [
30
- parseRecordCandidate(rawArgs.input),
31
- parseRecordCandidate(rawArgs.args),
32
- parseRecordCandidate(rawArgs.arguments),
33
- parseRecordCandidate(rawArgs.payload),
34
- ]
35
-
36
30
  const normalized: ToolArgsRecord = {}
37
- for (const nested of nestedSources) {
38
- if (!nested) continue
39
- Object.assign(normalized, nested)
40
- }
31
+ const queue: ToolArgsRecord[] = [rawArgs]
32
+ const visited = new Set<ToolArgsRecord>()
33
+
34
+ while (queue.length > 0) {
35
+ const current = queue.shift()
36
+ if (!current || visited.has(current)) continue
37
+ visited.add(current)
41
38
 
42
- for (const [key, value] of Object.entries(rawArgs)) {
43
- if (value === undefined || value === null) continue
44
- normalized[key] = value
39
+ for (const key of NESTED_WRAPPER_KEYS) {
40
+ const nested = parseRecordCandidate(current[key])
41
+ if (nested) queue.push(nested)
42
+ }
43
+
44
+ for (const [key, value] of Object.entries(current)) {
45
+ if (value === undefined || value === null) continue
46
+ normalized[key] = value
47
+ }
45
48
  }
46
49
 
47
50
  return normalized
@@ -81,7 +81,7 @@ getPluginManager().registerBuiltin('openclaw_nodes', NodesPlugin)
81
81
  * Legacy Bridge
82
82
  */
83
83
  export function buildOpenClawNodeTools(bctx: ToolBuildContext): StructuredToolInterface[] {
84
- if (!bctx.hasTool('openclaw_nodes')) return []
84
+ if (!bctx.hasPlugin('openclaw_nodes')) return []
85
85
  return [
86
86
  tool(
87
87
  async (args) => executeNodesAction(args),
@@ -128,7 +128,7 @@ getPluginManager().registerBuiltin('openclaw_workspace', WorkspacePlugin)
128
128
  * Legacy Bridge
129
129
  */
130
130
  export function buildOpenClawWorkspaceTools(bctx: ToolBuildContext): StructuredToolInterface[] {
131
- if (!bctx.hasTool('openclaw_workspace')) return []
131
+ if (!bctx.hasPlugin('openclaw_workspace')) return []
132
132
  return [
133
133
  tool(
134
134
  async (args) => executeWorkspaceAction(args),
@@ -0,0 +1,142 @@
1
+ import { describe, it } from 'node:test'
2
+ import assert from 'node:assert/strict'
3
+ import { normalizePlatformActionArgs } from './platform'
4
+
5
+ describe('normalizePlatformActionArgs', () => {
6
+ it('packs top-level create fields into data', () => {
7
+ const out = normalizePlatformActionArgs({
8
+ resource: 'tasks',
9
+ action: 'create',
10
+ title: 'Write docs',
11
+ agentId: 'default',
12
+ })
13
+
14
+ assert.equal(out.resource, 'tasks')
15
+ assert.equal(out.action, 'create')
16
+ assert.deepEqual(JSON.parse(String(out.data)), {
17
+ title: 'Write docs',
18
+ agentId: 'default',
19
+ })
20
+ })
21
+
22
+ it('merges object data with top-level overrides', () => {
23
+ const out = normalizePlatformActionArgs({
24
+ resource: 'tasks',
25
+ action: 'create',
26
+ data: { title: 'Old title', agentId: 'coder' },
27
+ title: 'New title',
28
+ })
29
+
30
+ assert.deepEqual(JSON.parse(String(out.data)), {
31
+ title: 'New title',
32
+ agentId: 'coder',
33
+ })
34
+ })
35
+
36
+ it('normalizes legacy resources envelope with parameters payload', () => {
37
+ const out = normalizePlatformActionArgs({
38
+ input: JSON.stringify({
39
+ resources: [
40
+ {
41
+ resource: 'tasks',
42
+ action: 'create',
43
+ parameters: {
44
+ title: 'Legacy task',
45
+ assigned_agent: 'default',
46
+ },
47
+ },
48
+ ],
49
+ }),
50
+ })
51
+
52
+ assert.equal(out.resource, 'tasks')
53
+ assert.equal(out.action, 'create')
54
+ assert.deepEqual(JSON.parse(String(out.data)), {
55
+ title: 'Legacy task',
56
+ assigned_agent: 'default',
57
+ })
58
+ })
59
+
60
+ it('normalizes singular resource names and resource payload objects', () => {
61
+ const out = normalizePlatformActionArgs({
62
+ input: JSON.stringify({
63
+ resource: 'task',
64
+ action: 'create',
65
+ task: {
66
+ title: 'Legacy singular task',
67
+ assigned_to: 'default',
68
+ },
69
+ }),
70
+ })
71
+
72
+ assert.equal(out.resource, 'tasks')
73
+ assert.equal(out.action, 'create')
74
+ assert.deepEqual(JSON.parse(String(out.data)), {
75
+ title: 'Legacy singular task',
76
+ assigned_to: 'default',
77
+ })
78
+ })
79
+
80
+ it('normalizes legacy backlog task resource names to tasks', () => {
81
+ const out = normalizePlatformActionArgs({
82
+ input: JSON.stringify({
83
+ resource: 'backlog_task',
84
+ action: 'create',
85
+ backlog_task: {
86
+ title: 'Legacy backlog task',
87
+ description: 'Keep the intended task payload',
88
+ },
89
+ }),
90
+ })
91
+
92
+ assert.equal(out.resource, 'tasks')
93
+ assert.equal(out.action, 'create')
94
+ assert.deepEqual(JSON.parse(String(out.data)), {
95
+ title: 'Legacy backlog task',
96
+ description: 'Keep the intended task payload',
97
+ })
98
+ })
99
+
100
+ it('normalizes resources entries that use type instead of resource', () => {
101
+ const out = normalizePlatformActionArgs({
102
+ input: JSON.stringify({
103
+ action: 'create',
104
+ resources: [
105
+ {
106
+ type: 'task',
107
+ parameters: {
108
+ title: 'Typed task resource',
109
+ description: 'Created through a typed resources envelope',
110
+ },
111
+ },
112
+ ],
113
+ }),
114
+ })
115
+
116
+ assert.equal(out.resource, 'tasks')
117
+ assert.equal(out.action, 'create')
118
+ assert.deepEqual(JSON.parse(String(out.data)), {
119
+ title: 'Typed task resource',
120
+ description: 'Created through a typed resources envelope',
121
+ })
122
+ })
123
+
124
+ it('infers schedules resource from create_schedule style actions', () => {
125
+ const out = normalizePlatformActionArgs({
126
+ input: JSON.stringify({
127
+ action: 'create_schedule',
128
+ data: {
129
+ name: 'Surgery check-in',
130
+ scheduleType: 'once',
131
+ },
132
+ }),
133
+ })
134
+
135
+ assert.equal(out.resource, 'schedules')
136
+ assert.equal(out.action, 'create')
137
+ assert.deepEqual(JSON.parse(String(out.data)), {
138
+ name: 'Surgery check-in',
139
+ scheduleType: 'once',
140
+ })
141
+ })
142
+ })
@@ -6,17 +6,155 @@ import type { Plugin, PluginHooks } from '@/types'
6
6
  import { getPluginManager } from '../plugins'
7
7
  import { normalizeToolInputArgs } from './normalize-tool-args'
8
8
 
9
+ function parsePlatformData(value: unknown): Record<string, unknown> | null {
10
+ if (!value) return null
11
+ if (typeof value === 'object' && !Array.isArray(value)) {
12
+ return value as Record<string, unknown>
13
+ }
14
+ if (typeof value !== 'string') return null
15
+ const trimmed = value.trim()
16
+ if (!trimmed) return null
17
+ try {
18
+ const parsed = JSON.parse(trimmed)
19
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
20
+ return parsed as Record<string, unknown>
21
+ }
22
+ } catch {
23
+ // Preserve non-JSON data strings as-is in the caller.
24
+ }
25
+ return null
26
+ }
27
+
28
+ function firstPlatformResource(value: unknown): Record<string, unknown> | null {
29
+ if (!Array.isArray(value)) return null
30
+ const first = value.find((entry) => entry && typeof entry === 'object' && !Array.isArray(entry))
31
+ return first && typeof first === 'object' && !Array.isArray(first)
32
+ ? first as Record<string, unknown>
33
+ : null
34
+ }
35
+
36
+ function normalizePlatformResourceName(value: unknown): string | undefined {
37
+ if (typeof value !== 'string') return undefined
38
+ const normalized = value.trim().toLowerCase()
39
+ if (!normalized) return undefined
40
+ const singularMap: Record<string, string> = {
41
+ agent: 'agents',
42
+ task: 'tasks',
43
+ backlog_task: 'tasks',
44
+ 'backlog-task': 'tasks',
45
+ backlogtask: 'tasks',
46
+ task_backlog: 'tasks',
47
+ 'task-backlog': 'tasks',
48
+ work_item: 'tasks',
49
+ 'work-item': 'tasks',
50
+ schedule: 'schedules',
51
+ skill: 'skills',
52
+ document: 'documents',
53
+ secret: 'secrets',
54
+ connector: 'connectors',
55
+ session: 'sessions',
56
+ }
57
+ return singularMap[normalized] || normalized
58
+ }
59
+
60
+ function inferPlatformResourceFromAction(value: unknown): { resource?: string; action?: string } {
61
+ if (typeof value !== 'string') return {}
62
+ const normalized = value.trim().toLowerCase().replace(/-/g, '_')
63
+ if (!normalized) return {}
64
+ const match = normalized.match(/^(list|get|create|update|delete)_([a-z_]+)$/)
65
+ if (!match) return {}
66
+ const [, action, rawResource] = match
67
+ const resource = normalizePlatformResourceName(rawResource)
68
+ if (!resource) return {}
69
+ return { resource, action }
70
+ }
71
+
72
+ function extractPlatformFields(value: Record<string, unknown>): Record<string, unknown> {
73
+ const fields: Record<string, unknown> = {}
74
+ for (const [key, fieldValue] of Object.entries(value)) {
75
+ if (fieldValue === undefined || fieldValue === null) continue
76
+ if (['input', 'args', 'arguments', 'payload', 'resources', 'parameters', 'resource', 'type', 'action', 'id'].includes(key)) continue
77
+ fields[key] = fieldValue
78
+ }
79
+ return fields
80
+ }
81
+
82
+ export function normalizePlatformActionArgs(rawArgs: Record<string, unknown>): Record<string, unknown> {
83
+ const normalized = normalizeToolInputArgs(rawArgs)
84
+ const resourceEntry = firstPlatformResource(normalized.resources)
85
+ const { resource, action, id, data, ...rest } = normalized
86
+ const payload: Record<string, unknown> = {}
87
+ const resourceValue = resource ?? resourceEntry?.resource ?? resourceEntry?.type
88
+ const rawResourceName = typeof resourceValue === 'string'
89
+ ? String(resourceValue).trim()
90
+ : undefined
91
+
92
+ const rawAction = action ?? resourceEntry?.action
93
+ const inferredFromAction = resourceValue === undefined
94
+ ? inferPlatformResourceFromAction(rawAction)
95
+ : {}
96
+ const effectiveResource = normalizePlatformResourceName(resourceValue) ?? inferredFromAction.resource
97
+ const effectiveAction = inferredFromAction.action && resourceValue === undefined
98
+ ? inferredFromAction.action
99
+ : rawAction
100
+ const effectiveId = id ?? resourceEntry?.id
101
+
102
+ if (effectiveResource !== undefined) payload.resource = effectiveResource
103
+ if (effectiveAction !== undefined) payload.action = effectiveAction
104
+ if (effectiveId !== undefined) payload.id = effectiveId
105
+
106
+ const directFields = extractPlatformFields(rest)
107
+ const resourcePayloadCandidates = effectiveResource
108
+ ? uniqueStrings([
109
+ rawResourceName,
110
+ effectiveResource,
111
+ effectiveResource.replace(/s$/, ''),
112
+ ])
113
+ : []
114
+ const directResourcePayload = resourcePayloadCandidates
115
+ .map((candidate) => parsePlatformData(normalized[candidate]))
116
+ .find(Boolean)
117
+ || null
118
+ if (effectiveResource) {
119
+ for (const candidate of resourcePayloadCandidates) delete directFields[candidate]
120
+ }
121
+ const parameterFields = {
122
+ ...(parsePlatformData(resourceEntry?.parameters) || {}),
123
+ ...(parsePlatformData(resourceEntry?.params) || {}),
124
+ ...(parsePlatformData(normalized.parameters) || {}),
125
+ ...(directResourcePayload || {}),
126
+ }
127
+ const parsedData = parsePlatformData(data)
128
+ const mergedData = {
129
+ ...(parsedData || {}),
130
+ ...parameterFields,
131
+ ...directFields,
132
+ }
133
+
134
+ if (Object.keys(mergedData).length > 0) {
135
+ payload.data = JSON.stringify(mergedData)
136
+ } else if (typeof data === 'string' && data.trim()) {
137
+ payload.data = data
138
+ }
139
+
140
+ return payload
141
+ }
142
+
143
+ function uniqueStrings(values: Array<string | undefined>): string[] {
144
+ return [...new Set(values.filter((value): value is string => Boolean(value)))]
145
+ }
146
+
9
147
  /**
10
148
  * Unified Platform Execution Logic
11
149
  */
12
150
  async function executePlatformAction(args: any, bctx: any) {
13
- const normalized = normalizeToolInputArgs((args ?? {}) as Record<string, unknown>)
14
- const { resource, action, id, data, ...rest } = normalized
151
+ const normalized = normalizePlatformActionArgs((args ?? {}) as Record<string, unknown>)
152
+ const { resource, action, id, data } = normalized
15
153
 
16
154
  // We reuse the existing CRUD tool logic but expose it via a single tool
17
155
  const crudTools = buildCrudTools({
18
156
  ...bctx,
19
- hasTool: (id: string) => [
157
+ hasPlugin: (id: string) => [
20
158
  'manage_agents',
21
159
  'manage_tasks',
22
160
  'manage_schedules',
@@ -36,7 +174,7 @@ async function executePlatformAction(args: any, bctx: any) {
36
174
  }
37
175
 
38
176
  // Forward to the specific CRUD tool implementation
39
- return targetTool.invoke({ action, id, data, ...rest })
177
+ return targetTool.invoke({ action, id, data })
40
178
  }
41
179
 
42
180
  /**
@@ -45,11 +183,14 @@ async function executePlatformAction(args: any, bctx: any) {
45
183
  const PlatformPlugin: Plugin = {
46
184
  name: 'Core Platform',
47
185
  description: 'Unified management of agents, tasks, schedules, skills, documents, and secrets.',
48
- hooks: {} as PluginHooks,
186
+ hooks: {
187
+ getCapabilityDescription: () => 'I can create and configure other agents (`manage_agents`), manage tasks (`manage_tasks`), set up schedules (`manage_schedules`), store and search documents (`manage_documents`), register webhooks (`manage_webhooks`), manage reusable skills (`manage_skills`), and store encrypted secrets (`manage_secrets`).',
188
+ getOperatingGuidance: () => ['Create/update tasks for long-lived goals to track progress.', 'Use schedules for follow-ups. Check existing schedules before creating new ones.', 'Inspect existing chats before creating duplicates.'],
189
+ } as PluginHooks,
49
190
  tools: [
50
191
  {
51
192
  name: 'manage_platform',
52
- description: 'Unified tool for managing all SwarmClaw resources.',
193
+ description: 'Unified tool for managing all SwarmClaw resources. For create/update, pass resource + action, then either put fields inside data, pass them as top-level fields, or use a single resources[0].parameters envelope.',
53
194
  parameters: {
54
195
  type: 'object',
55
196
  properties: {
@@ -71,7 +212,7 @@ getPluginManager().registerBuiltin('manage_platform', PlatformPlugin)
71
212
  * Legacy Bridge
72
213
  */
73
214
  export function buildPlatformTools(bctx: ToolBuildContext): StructuredToolInterface[] {
74
- if (!bctx.hasTool('manage_platform')) return []
215
+ if (!bctx.hasPlugin('manage_platform')) return []
75
216
 
76
217
  return [
77
218
  tool(