@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
@@ -0,0 +1,136 @@
1
+ import type { OpenClawSkillEntry, SkillInstallOption, SkillRequirements } from '@/types'
2
+
3
+ interface GatewayConfigCheck {
4
+ path?: string
5
+ satisfied?: boolean
6
+ }
7
+
8
+ interface GatewayInstallOption {
9
+ kind?: string
10
+ label?: string
11
+ bins?: string[]
12
+ }
13
+
14
+ interface GatewaySkillRequirements {
15
+ bins?: string[]
16
+ anyBins?: string[][]
17
+ env?: string[]
18
+ config?: string[]
19
+ os?: string[]
20
+ }
21
+
22
+ interface GatewaySkillEntry {
23
+ name?: string
24
+ description?: string
25
+ source?: string
26
+ eligible?: boolean
27
+ requirements?: GatewaySkillRequirements
28
+ missing?: GatewaySkillRequirements
29
+ disabled?: boolean
30
+ install?: GatewayInstallOption[]
31
+ configChecks?: GatewayConfigCheck[]
32
+ skillKey?: string
33
+ baseDir?: string
34
+ }
35
+
36
+ interface GatewaySkillsStatusPayload {
37
+ skills?: GatewaySkillEntry[]
38
+ }
39
+
40
+ function uniq(values: Array<string | undefined | null>): string[] {
41
+ return [...new Set(values.map((value) => (value ?? '').trim()).filter(Boolean))]
42
+ }
43
+
44
+ function normalizeSource(source: string | undefined): OpenClawSkillEntry['source'] {
45
+ switch ((source ?? '').trim()) {
46
+ case 'openclaw-bundled':
47
+ case 'bundled':
48
+ return 'bundled'
49
+ case 'managed':
50
+ return 'managed'
51
+ case 'personal':
52
+ return 'personal'
53
+ case 'workspace':
54
+ return 'workspace'
55
+ default:
56
+ return 'workspace'
57
+ }
58
+ }
59
+
60
+ function normalizeInstallOptions(install: GatewayInstallOption[] | undefined): SkillInstallOption[] | undefined {
61
+ if (!Array.isArray(install) || !install.length) return undefined
62
+ const normalized = install
63
+ .map((entry) => {
64
+ const kind = (entry.kind ?? '').trim()
65
+ if (!kind || !entry.label?.trim()) return null
66
+ if (!['brew', 'node', 'go', 'uv', 'download'].includes(kind)) return null
67
+ return {
68
+ kind: kind as SkillInstallOption['kind'],
69
+ label: entry.label.trim(),
70
+ bins: Array.isArray(entry.bins) ? uniq(entry.bins) : undefined,
71
+ } satisfies SkillInstallOption
72
+ })
73
+ .filter((value): value is NonNullable<typeof value> => value !== null)
74
+ return normalized.length ? normalized : undefined
75
+ }
76
+
77
+ function normalizeRequirements(input: GatewaySkillRequirements | undefined): SkillRequirements | undefined {
78
+ if (!input || typeof input !== 'object') return undefined
79
+ const bins = Array.isArray(input.bins) ? uniq(input.bins) : undefined
80
+ const anyBins = Array.isArray(input.anyBins)
81
+ ? input.anyBins
82
+ .map((group) => Array.isArray(group) ? uniq(group) : [])
83
+ .filter((group) => group.length > 0)
84
+ : undefined
85
+ const env = Array.isArray(input.env) ? uniq(input.env) : undefined
86
+ const config = Array.isArray(input.config) ? uniq(input.config) : undefined
87
+ const os = Array.isArray(input.os) ? uniq(input.os) : undefined
88
+ if (!bins && !anyBins && !env && !config && !os) return undefined
89
+ return { bins, anyBins, env, config, os }
90
+ }
91
+
92
+ function flattenMissing(input: GatewaySkillRequirements | undefined): string[] | undefined {
93
+ if (!input || typeof input !== 'object') return undefined
94
+ const out: string[] = []
95
+ for (const value of Array.isArray(input.bins) ? uniq(input.bins) : []) out.push(value)
96
+ for (const group of Array.isArray(input.anyBins) ? input.anyBins : []) {
97
+ const normalized = Array.isArray(group) ? uniq(group) : []
98
+ if (normalized.length) out.push(`one of: ${normalized.join(' | ')}`)
99
+ }
100
+ for (const value of Array.isArray(input.env) ? uniq(input.env) : []) out.push(`env ${value}`)
101
+ for (const value of Array.isArray(input.config) ? uniq(input.config) : []) out.push(`config ${value}`)
102
+ for (const value of Array.isArray(input.os) ? uniq(input.os) : []) out.push(`os ${value}`)
103
+ return out.length ? out : undefined
104
+ }
105
+
106
+ export function normalizeOpenClawSkillsPayload(payload: unknown): OpenClawSkillEntry[] {
107
+ const rawSkills = Array.isArray(payload)
108
+ ? payload as GatewaySkillEntry[]
109
+ : Array.isArray((payload as GatewaySkillsStatusPayload | null | undefined)?.skills)
110
+ ? (payload as GatewaySkillsStatusPayload).skills!
111
+ : []
112
+
113
+ return rawSkills
114
+ .map((skill) => {
115
+ const name = skill.name?.trim()
116
+ if (!name) return null
117
+ return {
118
+ name,
119
+ description: skill.description?.trim() || undefined,
120
+ source: normalizeSource(skill.source),
121
+ eligible: skill.eligible === true,
122
+ missing: flattenMissing(skill.missing),
123
+ disabled: skill.disabled === true,
124
+ installOptions: normalizeInstallOptions(skill.install),
125
+ skillRequirements: normalizeRequirements(skill.requirements),
126
+ configChecks: Array.isArray(skill.configChecks)
127
+ ? skill.configChecks
128
+ .filter((check) => check.path?.trim())
129
+ .map((check) => ({ key: check.path!.trim(), ok: check.satisfied === true }))
130
+ : undefined,
131
+ skillKey: skill.skillKey?.trim() || undefined,
132
+ baseDir: skill.baseDir?.trim() || undefined,
133
+ } satisfies OpenClawSkillEntry
134
+ })
135
+ .filter((skill): skill is NonNullable<typeof skill> => skill !== null)
136
+ }
@@ -2,6 +2,7 @@ import fs from 'node:fs'
2
2
  import path from 'node:path'
3
3
  import crypto from 'node:crypto'
4
4
  import { DATA_DIR } from './data-dir'
5
+ import { normalizeOpenClawAgentId } from '@/lib/openclaw-agent-id'
5
6
  import { loadSettings, loadAgents, saveAgents, loadSchedules, saveSchedules, loadCredentials, decryptKey, encryptKey } from './storage'
6
7
  import { getMemoryDb } from './memory-db'
7
8
  import type { AppSettings, MemoryEntry, Schedule } from '@/types'
@@ -182,7 +183,7 @@ export function pushAgentToOpenClaw(agentId: string): { written: string[] } {
182
183
  const agent = agents[agentId]
183
184
  if (!agent) throw new Error(`Agent not found: ${agentId}`)
184
185
 
185
- const agentDir = path.join(config.workspacePath, 'agents', agent.name.toLowerCase().replace(/\s+/g, '-'))
186
+ const agentDir = path.join(config.workspacePath, 'agents', normalizeOpenClawAgentId(agent.name))
186
187
  ensureDir(agentDir)
187
188
 
188
189
  const written: string[] = []
@@ -214,7 +215,7 @@ export function pullAgentFromOpenClaw(agentId: string): { updated: string[] } {
214
215
  const agent = agents[agentId]
215
216
  if (!agent) throw new Error(`Agent not found: ${agentId}`)
216
217
 
217
- const agentDir = path.join(config.workspacePath, 'agents', agent.name.toLowerCase().replace(/\s+/g, '-'))
218
+ const agentDir = path.join(config.workspacePath, 'agents', normalizeOpenClawAgentId(agent.name))
218
219
  const updated: string[] = []
219
220
 
220
221
  const soulPath = path.join(agentDir, 'SOUL.md')
@@ -13,6 +13,7 @@ import { notify } from './ws-hub'
13
13
  import { pushMainLoopEventToMainSessions } from './main-agent-loop'
14
14
  import { buildCurrentDateTimePromptContext } from './prompt-runtime-context'
15
15
  import { getPluginManager } from './plugins'
16
+ import './builtin-plugins'
16
17
  import { genId } from '@/lib/id'
17
18
  import { NON_LANGGRAPH_PROVIDER_IDS } from '@/lib/provider-sets'
18
19
  import type { Agent, TaskComment, MessageToolEvent } from '@/types'
@@ -118,7 +119,7 @@ async function executeSubTaskViaCli(agent: Agent, task: string, parentSessionId:
118
119
  messages: [],
119
120
  createdAt: Date.now(),
120
121
  lastActiveAt: Date.now(),
121
- sessionType: 'orchestrated' as const,
122
+ sessionType: 'human' as const,
122
123
  agentId: agent.id,
123
124
  parentSessionId,
124
125
  plugins: agent.plugins || agent.tools || [],
@@ -177,7 +178,11 @@ export async function executeLangGraphOrchestrator(
177
178
  return `Agent "${agentName}" not found. Available: ${agents.map((a) => a.name).join(', ')}`
178
179
  }
179
180
  console.log(`[orchestrator-lg] Delegating to ${agent.name}: ${agentTask.slice(0, 80)}`)
180
- getPluginManager().runHook('onAgentDelegation', { sourceAgentId: orchestrator.id, targetAgentId: agent.id, task: agentTask })
181
+ getPluginManager().runHook(
182
+ 'onAgentDelegation',
183
+ { sourceAgentId: orchestrator.id, targetAgentId: agent.id, task: agentTask },
184
+ { enabledIds: orchestrator.plugins || [] },
185
+ )
181
186
  const result = await executeSubTaskViaCli(agent, agentTask, sessionId)
182
187
  saveMessage(sessionId, 'assistant', `Delegated to ${agent.name}: ${agentTask.slice(0, 100)}`, [{
183
188
  name: 'delegate_to_agent',
@@ -393,6 +398,7 @@ export async function executeLangGraphOrchestrator(
393
398
 
394
399
  const checkpointSaver = getCheckpointSaver()
395
400
  const isStrictMode = settings.capabilityPolicyMode === 'strict'
401
+ const approvalInterruptsEnabled = isStrictMode && settings.approvalsEnabled !== false
396
402
  const allTools = [delegateTool, storeMemoryTool, searchMemoryTool, getSecretTool, commentOnTaskTool, createTaskTool, markCompleteTool]
397
403
  const llmWithTools = llm.bindTools(allTools)
398
404
  const toolNode = new ToolNode(allTools)
@@ -472,7 +478,7 @@ export async function executeLangGraphOrchestrator(
472
478
 
473
479
  const compiledGraph = graph.compile({
474
480
  checkpointer: checkpointSaver,
475
- ...(isStrictMode ? { interruptBefore: ['tools'] } : {}),
481
+ ...(approvalInterruptsEnabled ? { interruptBefore: ['tools'] } : {}),
476
482
  })
477
483
 
478
484
  // Export graph structure for introspection
@@ -534,7 +540,7 @@ export async function executeLangGraphOrchestrator(
534
540
  }
535
541
 
536
542
  // Check for interrupt (paused before tool execution in strict mode)
537
- if (isStrictMode && taskId) {
543
+ if (approvalInterruptsEnabled && taskId) {
538
544
  const state = await compiledGraph.getState({ configurable: { thread_id: threadId } })
539
545
  const nextNodes = state?.next || []
540
546
  if (nextNodes.includes('tools')) {
@@ -606,8 +612,7 @@ export async function executeLangGraphOrchestrator(
606
612
  const completeMatch = finalResult.match(/ORCHESTRATION_COMPLETE:\s*([\s\S]+)/)
607
613
  const summary = completeMatch ? completeMatch[1].trim() : finalResult
608
614
 
609
- // Append meta so the main-loop knows we are done
610
- return `${summary}\n\n[MAIN_LOOP_META] {"status":"ok", "follow_up":false, "summary":${JSON.stringify(summary.slice(0, 300))}, "mission_task_id":${JSON.stringify(taskId || null)}}`
615
+ return summary
611
616
  }
612
617
 
613
618
  /**
@@ -628,7 +633,11 @@ export async function resumeLangGraphOrchestrator(
628
633
  async ({ agentName, task: agentTask }) => {
629
634
  const agent = agents.find((a) => a.name.toLowerCase() === agentName.toLowerCase())
630
635
  if (!agent) return `Agent "${agentName}" not found. Available: ${agents.map((a) => a.name).join(', ')}`
631
- getPluginManager().runHook('onAgentDelegation', { sourceAgentId: orchestrator.id, targetAgentId: agent.id, task: agentTask })
636
+ getPluginManager().runHook(
637
+ 'onAgentDelegation',
638
+ { sourceAgentId: orchestrator.id, targetAgentId: agent.id, task: agentTask },
639
+ { enabledIds: orchestrator.plugins || [] },
640
+ )
632
641
  const result = await executeSubTaskViaCli(agent, agentTask, sessionId)
633
642
  saveMessage(sessionId, 'assistant', `Delegated to ${agent.name}: ${agentTask.slice(0, 100)}`, [{
634
643
  name: 'delegate_to_agent',
@@ -753,6 +762,7 @@ export async function resumeLangGraphOrchestrator(
753
762
  const checkpointSaver = getCheckpointSaver()
754
763
  const settings = loadSettings()
755
764
  const isStrictMode = settings.capabilityPolicyMode === 'strict'
765
+ const approvalInterruptsEnabled = isStrictMode && settings.approvalsEnabled !== false
756
766
 
757
767
  const allTools = [delegateTool, storeMemoryTool, searchMemoryTool, getSecretTool, commentOnTaskTool, createTaskTool, markCompleteTool]
758
768
  const llmWithTools = llm.bindTools(allTools)
@@ -782,7 +792,7 @@ export async function resumeLangGraphOrchestrator(
782
792
  .addEdge('router', 'agent')
783
793
  .compile({
784
794
  checkpointer: checkpointSaver,
785
- ...(isStrictMode ? { interruptBefore: ['tools'] } : {}),
795
+ ...(approvalInterruptsEnabled ? { interruptBefore: ['tools'] } : {}),
786
796
  })
787
797
 
788
798
  let finalResult = ''
@@ -42,7 +42,7 @@ export function createOrchestratorSession(
42
42
  messages: [] as any[],
43
43
  createdAt: Date.now(),
44
44
  lastActiveAt: Date.now(),
45
- sessionType: 'orchestrated' as const,
45
+ sessionType: 'human' as const,
46
46
  agentId: orchestrator.id,
47
47
  parentSessionId: parentSessionId || null,
48
48
  plugins: Array.isArray(orchestrator.plugins) ? [...orchestrator.plugins] : (Array.isArray(orchestrator.tools) ? [...orchestrator.tools] : []),
@@ -86,6 +86,7 @@ async function executeOrchestratorLegacy(
86
86
  sessionId: string,
87
87
  taskId?: string,
88
88
  ): Promise<string> {
89
+ void taskId
89
90
  const allAgents = loadAgents()
90
91
  const sessions = loadSessions()
91
92
  const session = sessions[sessionId]
@@ -241,7 +242,7 @@ async function executeOrchestratorLegacy(
241
242
 
242
243
  if (cmd.done) {
243
244
  result = cmd.summary || fullText
244
- return `${result}\n\n[MAIN_LOOP_META] {"status":"ok", "follow_up":false, "summary":${JSON.stringify(result.slice(0, 300))}, "mission_task_id":${JSON.stringify(taskId || null)}}`
245
+ return result
245
246
  }
246
247
  }
247
248
 
@@ -256,7 +257,7 @@ async function executeOrchestratorLegacy(
256
257
  result = `Loop stopped after reaching max turns (${maxTurns}).`
257
258
  }
258
259
 
259
- return `${result}\n\n[MAIN_LOOP_META] {"status":"ok", "follow_up":false, "summary":${JSON.stringify(result.slice(0, 300))}, "mission_task_id":${JSON.stringify(taskId || null)}}`
260
+ return result
260
261
  }
261
262
 
262
263
  async function executeSubTask(
@@ -288,7 +289,7 @@ async function executeSubTask(
288
289
  messages: [] as any[],
289
290
  createdAt: Date.now(),
290
291
  lastActiveAt: Date.now(),
291
- sessionType: 'orchestrated' as const,
292
+ sessionType: 'human' as const,
292
293
  agentId: agent.id,
293
294
  parentSessionId,
294
295
  plugins: agent.plugins || agent.tools || [],
@@ -11,9 +11,33 @@ import path from 'path'
11
11
  const UPLOAD_DIR = process.env.SWARMCLAW_UPLOAD_DIR || path.join(process.env.DATA_DIR || path.join(process.cwd(), 'data'), 'uploads')
12
12
  if (!fs.existsSync(UPLOAD_DIR)) fs.mkdirSync(UPLOAD_DIR, { recursive: true })
13
13
 
14
- const child = spawn('npx', ['@playwright/mcp@latest'], {
15
- stdio: ['pipe', 'pipe', 'pipe'],
16
- })
14
+ function resolvePlaywrightCli() {
15
+ const candidates = [
16
+ path.join(process.cwd(), 'node_modules', '@playwright', 'mcp', 'cli.js'),
17
+ path.join(process.cwd(), '[project]', 'node_modules', '@playwright', 'mcp', 'cli.js'),
18
+ ]
19
+ return candidates.find((candidate) => fs.existsSync(candidate)) || null
20
+ }
21
+
22
+ function sanitizePlaywrightEnv(baseEnv) {
23
+ const env = { ...baseEnv }
24
+ for (const key of Object.keys(env)) {
25
+ if (!key.toUpperCase().startsWith('PLAYWRIGHT_MCP_')) continue
26
+ delete env[key]
27
+ }
28
+ return env
29
+ }
30
+
31
+ const cliPath = resolvePlaywrightCli()
32
+ const child = cliPath
33
+ ? spawn(process.execPath, [cliPath], {
34
+ stdio: ['pipe', 'pipe', 'pipe'],
35
+ env: sanitizePlaywrightEnv(process.env),
36
+ })
37
+ : spawn('npx', ['@playwright/mcp@latest'], {
38
+ stdio: ['pipe', 'pipe', 'pipe'],
39
+ env: sanitizePlaywrightEnv(process.env),
40
+ })
17
41
 
18
42
  // Forward stdin → child
19
43
  process.stdin.on('data', (chunk) => child.stdin.write(chunk))
@@ -0,0 +1,215 @@
1
+ import assert from 'node:assert/strict'
2
+ import { describe, it } from 'node:test'
3
+ import fs from 'node:fs'
4
+ import path from 'node:path'
5
+ import { getPluginManager, normalizeMarketplacePluginUrl, sanitizePluginFilename } from './plugins'
6
+ import { canonicalizePluginId, expandPluginIds, pluginIdMatches } from './tool-aliases'
7
+ import { DATA_DIR } from './data-dir'
8
+
9
+ let testPluginSeq = 0
10
+
11
+ function uniquePluginId(prefix: string): string {
12
+ testPluginSeq += 1
13
+ return `${prefix}_${Date.now()}_${testPluginSeq}`
14
+ }
15
+
16
+ describe('plugin id canonicalization', () => {
17
+ it('normalizes built-in aliases to canonical plugin families', () => {
18
+ assert.equal(canonicalizePluginId('session_info'), 'manage_sessions')
19
+ assert.equal(canonicalizePluginId('connectors'), 'manage_connectors')
20
+ assert.equal(canonicalizePluginId('subagent'), 'spawn_subagent')
21
+ assert.equal(canonicalizePluginId('http'), 'http_request')
22
+ assert.equal(canonicalizePluginId('human_loop'), 'ask_human')
23
+ assert.equal(canonicalizePluginId('dataframe'), 'table')
24
+ assert.equal(canonicalizePluginId('extract_structured'), 'extract')
25
+ })
26
+
27
+ it('expands aliases to include the canonical family id', () => {
28
+ const expanded = expandPluginIds(['session_info', 'http', 'human_loop'])
29
+ assert.equal(expanded.includes('manage_sessions'), true)
30
+ assert.equal(expanded.includes('session_info'), true)
31
+ assert.equal(expanded.includes('http_request'), true)
32
+ assert.equal(expanded.includes('http'), true)
33
+ assert.equal(expanded.includes('ask_human'), true)
34
+ assert.equal(expanded.includes('human_loop'), true)
35
+ })
36
+
37
+ it('does not expand a specific platform tool back into manage_platform', () => {
38
+ const expanded = expandPluginIds(['manage_schedules'])
39
+ assert.equal(expanded.includes('manage_schedules'), true)
40
+ assert.equal(expanded.includes('manage_platform'), false)
41
+ assert.equal(pluginIdMatches(['manage_platform'], 'manage_schedules'), true)
42
+ assert.equal(pluginIdMatches(['manage_schedules'], 'manage_platform'), false)
43
+ })
44
+ })
45
+
46
+ describe('plugin install helpers', () => {
47
+ it('rewrites legacy marketplace URLs to the canonical raw source', () => {
48
+ const normalized = normalizeMarketplacePluginUrl('https://github.com/swarmclawai/plugins/blob/master/foo/bar.js')
49
+ assert.equal(normalized, 'https://raw.githubusercontent.com/swarmclawai/swarmforge/main/foo/bar.js')
50
+ })
51
+
52
+ it('allows .js and .mjs plugin filenames and blocks traversal', () => {
53
+ assert.equal(sanitizePluginFilename('plugin.js'), 'plugin.js')
54
+ assert.equal(sanitizePluginFilename('plugin.mjs'), 'plugin.mjs')
55
+ assert.throws(() => sanitizePluginFilename('../plugin.js'), /Invalid filename/)
56
+ assert.throws(() => sanitizePluginFilename('plugin.ts'), /Filename must end/)
57
+ })
58
+ })
59
+
60
+ describe('plugin manager hook execution', () => {
61
+ it('applies beforeToolExec mutations only for explicitly enabled plugins', async () => {
62
+ const pluginId = uniquePluginId('before_tool_exec')
63
+ getPluginManager().registerBuiltin(pluginId, {
64
+ name: 'Before Tool Exec Test',
65
+ hooks: {
66
+ beforeToolExec: ({ input }) => ({ ...(input || {}), patched: true }),
67
+ },
68
+ })
69
+
70
+ const withoutEnable = await getPluginManager().runBeforeToolExec(
71
+ { toolName: 'shell', input: { original: true } },
72
+ {},
73
+ )
74
+ assert.deepEqual(withoutEnable, { original: true })
75
+
76
+ const withEnable = await getPluginManager().runBeforeToolExec(
77
+ { toolName: 'shell', input: { original: true } },
78
+ { enabledIds: [pluginId] },
79
+ )
80
+ assert.deepEqual(withEnable, { original: true, patched: true })
81
+ })
82
+
83
+ it('chains text transforms in plugin order', async () => {
84
+ const pluginA = uniquePluginId('transform_a')
85
+ const pluginB = uniquePluginId('transform_b')
86
+ getPluginManager().registerBuiltin(pluginA, {
87
+ name: 'Transform A',
88
+ hooks: {
89
+ transformOutboundMessage: ({ text }) => `${text} A`,
90
+ },
91
+ })
92
+ getPluginManager().registerBuiltin(pluginB, {
93
+ name: 'Transform B',
94
+ hooks: {
95
+ transformOutboundMessage: ({ text }) => `${text} B`,
96
+ },
97
+ })
98
+
99
+ const transformed = await getPluginManager().transformText(
100
+ 'transformOutboundMessage',
101
+ {
102
+ session: {
103
+ id: 's1',
104
+ name: 'Test Session',
105
+ cwd: process.cwd(),
106
+ user: 'tester',
107
+ provider: 'openai',
108
+ model: 'gpt-test',
109
+ claudeSessionId: null,
110
+ messages: [],
111
+ createdAt: Date.now(),
112
+ lastActiveAt: Date.now(),
113
+ plugins: [pluginA, pluginB],
114
+ },
115
+ text: 'base',
116
+ },
117
+ { enabledIds: [pluginA, pluginB] },
118
+ )
119
+
120
+ assert.equal(transformed, 'base A B')
121
+ })
122
+
123
+ it('does not run generic hooks unless scope is provided explicitly', async () => {
124
+ const pluginId = uniquePluginId('scoped_hook')
125
+ let callCount = 0
126
+ getPluginManager().registerBuiltin(pluginId, {
127
+ name: 'Scoped Hook Test',
128
+ hooks: {
129
+ afterChatTurn: () => {
130
+ callCount += 1
131
+ },
132
+ },
133
+ })
134
+
135
+ await getPluginManager().runHook(
136
+ 'afterChatTurn',
137
+ {
138
+ session: {
139
+ id: 's2',
140
+ name: 'Scoped Hook Session',
141
+ cwd: process.cwd(),
142
+ user: 'tester',
143
+ provider: 'openai',
144
+ model: 'gpt-test',
145
+ claudeSessionId: null,
146
+ messages: [],
147
+ createdAt: Date.now(),
148
+ lastActiveAt: Date.now(),
149
+ },
150
+ message: 'hi',
151
+ response: 'hello',
152
+ source: 'chat',
153
+ internal: false,
154
+ },
155
+ {},
156
+ )
157
+ assert.equal(callCount, 0)
158
+
159
+ await getPluginManager().runHook(
160
+ 'afterChatTurn',
161
+ {
162
+ session: {
163
+ id: 's3',
164
+ name: 'Scoped Hook Session Enabled',
165
+ cwd: process.cwd(),
166
+ user: 'tester',
167
+ provider: 'openai',
168
+ model: 'gpt-test',
169
+ claudeSessionId: null,
170
+ messages: [],
171
+ createdAt: Date.now(),
172
+ lastActiveAt: Date.now(),
173
+ plugins: [pluginId],
174
+ },
175
+ message: 'hi',
176
+ response: 'hello',
177
+ source: 'chat',
178
+ internal: false,
179
+ },
180
+ { enabledIds: [pluginId] },
181
+ )
182
+ assert.equal(callCount, 1)
183
+ })
184
+
185
+ it('stores dependency-aware plugins in managed workspaces', async () => {
186
+ const filename = `${uniquePluginId('workspace_plugin')}.js`
187
+ const manager = getPluginManager()
188
+
189
+ await manager.savePluginSource(
190
+ filename,
191
+ 'module.exports = { name: "Workspace Plugin", tools: [] }',
192
+ {
193
+ packageJson: {
194
+ name: 'workspace-plugin',
195
+ dependencies: {
196
+ lodash: '^4.17.21',
197
+ },
198
+ },
199
+ packageManager: 'npm',
200
+ },
201
+ )
202
+
203
+ const meta = manager.listPlugins().find((plugin) => plugin.filename === filename)
204
+ assert.equal(meta?.isBuiltin, false)
205
+ assert.equal(meta?.hasDependencyManifest, true)
206
+ assert.equal(meta?.dependencyCount, 1)
207
+ assert.equal(meta?.packageManager, 'npm')
208
+ assert.equal(manager.readPluginSource(filename).includes('Workspace Plugin'), true)
209
+
210
+ const shimPath = path.join(DATA_DIR, 'plugins', filename)
211
+ assert.equal(fs.readFileSync(shimPath, 'utf8').includes('Auto-generated plugin workspace shim'), true)
212
+
213
+ assert.equal(manager.deletePlugin(filename), true)
214
+ })
215
+ })