@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
package/README.md CHANGED
@@ -37,6 +37,8 @@ SwarmClaw was built for OpenClaw users who outgrew a single agent. Connect each
37
37
 
38
38
  SwarmClaw includes the `openclaw` CLI as a bundled dependency, so there is no separate OpenClaw CLI install step.
39
39
 
40
+ The Providers screen now supports named OpenClaw gateway profiles with discovery, health checks, default-gateway selection, and an External Agent Runtimes view for remote workers that register/heartbeat into SwarmClaw.
41
+
40
42
  The OpenClaw Control Plane in SwarmClaw adds:
41
43
  - Reload mode switching (`hot`, `hybrid`, `full`)
42
44
  - Config issue detection and guided repair
@@ -47,13 +49,13 @@ The Agent Inspector Panel lets you edit OpenClaw files (`SOUL.md`, `IDENTITY.md`
47
49
 
48
50
  To connect an agent to an OpenClaw gateway:
49
51
 
50
- 1. Create or edit an agent
51
- 2. Toggle **OpenClaw Gateway** ON
52
- 3. Enter the gateway URL (e.g. `http://192.168.1.50:18789` or `https://my-vps:18789`)
53
- 4. Add a gateway token if authentication is enabled on the remote gateway
52
+ 1. Optional: create a named gateway profile in **Providers** and mark a default
53
+ 2. Create or edit an agent
54
+ 3. Toggle **OpenClaw Gateway** ON
55
+ 4. Select a saved gateway profile or enter a direct gateway URL/token override
54
56
  5. Click **Connect** — approve the device in your gateway's dashboard if prompted, then **Retry Connection**
55
57
 
56
- Each agent can point to a **different** OpenClaw gateway — one local, several remote. This is how you manage a **swarm of OpenClaws** from a single dashboard.
58
+ Each agent can point to a **different** OpenClaw gateway profile or direct endpoint — one local, several remote. This is how you manage a **swarm of OpenClaws** from a single dashboard.
57
59
 
58
60
  URLs without a protocol are auto-prefixed with `http://`. For remote gateways with TLS, use `https://` explicitly.
59
61
 
@@ -77,21 +79,37 @@ Skill source and runbook: [`swarmclaw/SKILL.md`](swarmclaw/SKILL.md).
77
79
  ## Requirements
78
80
 
79
81
  - **Node.js** 22.6+
80
- - **npm** 10+
82
+ - **Node.js** 22.6+
83
+ - One of: **npm** 10+, **pnpm**, **Yarn**, or **Bun**
81
84
  - **Claude Code CLI** (optional, for `claude-cli` provider) — [Install](https://docs.anthropic.com/en/docs/claude-code/overview)
82
85
  - **OpenAI Codex CLI** (optional, for `codex-cli` provider) — [Install](https://github.com/openai/codex)
83
86
  - **OpenCode CLI** (optional, for `opencode-cli` provider) — [Install](https://github.com/opencode-ai/opencode)
84
87
  - **Gemini CLI** (optional, for `delegate` backend `gemini`) — install and authenticate `gemini` on your host
88
+ - **Deno** (required for `sandbox_exec`) — auto-installed by `npm run quickstart` / `npm run setup:easy` when missing
85
89
 
86
90
  ## Quick Start
87
91
 
88
- ### npm (recommended)
92
+ SwarmClaw is published to the npm registry once and can be installed with `npm`, `pnpm`, `yarn`, or `bun`. There is no separate package-manager signup for end users.
93
+
94
+ ### Global install
89
95
 
90
96
  ```bash
91
97
  npm i -g @swarmclawai/swarmclaw
98
+ pnpm add -g @swarmclawai/swarmclaw
99
+ yarn global add @swarmclawai/swarmclaw
100
+ bun add -g @swarmclawai/swarmclaw
92
101
  swarmclaw
93
102
  ```
94
103
 
104
+ ### One-off run
105
+
106
+ ```bash
107
+ npx @swarmclawai/swarmclaw
108
+ pnpm dlx @swarmclawai/swarmclaw
109
+ yarn dlx @swarmclawai/swarmclaw
110
+ bunx @swarmclawai/swarmclaw
111
+ ```
112
+
95
113
  ### Install script
96
114
 
97
115
  ```bash
@@ -99,7 +117,7 @@ curl -fsSL https://raw.githubusercontent.com/swarmclawai/swarmclaw/main/install.
99
117
  ```
100
118
 
101
119
  The installer resolves the latest stable release tag and installs that version by default.
102
- To pin a version: `SWARMCLAW_VERSION=v0.7.2 curl ... | bash`
120
+ To pin a version: `SWARMCLAW_VERSION=v0.7.4 curl ... | bash`
103
121
 
104
122
  Or run locally from the repo (friendly for non-technical users):
105
123
 
@@ -111,10 +129,19 @@ npm run quickstart
111
129
 
112
130
  `npm run quickstart` will:
113
131
  - Check Node/npm versions
132
+ - Install Deno if the sandbox runtime is missing
114
133
  - Install dependencies
115
134
  - Prepare `.env.local` and `data/`
116
135
  - Start the app at `http://localhost:3456`
117
136
 
137
+ If you prefer another package manager for local development:
138
+
139
+ ```bash
140
+ pnpm install && pnpm dev
141
+ yarn install && yarn dev
142
+ bun install && bun run dev
143
+ ```
144
+
118
145
  `postinstall` rebuilds `better-sqlite3` natively. If you install with `--ignore-scripts`, run `npm rebuild better-sqlite3` manually.
119
146
 
120
147
  On first launch, SwarmClaw will:
@@ -143,7 +170,7 @@ Notes:
143
170
  - When run with no flags in a TTY, `setup init` enters interactive mode — pick providers, enter keys, name agents, and add multiple providers in one session.
144
171
  - Use `--no-interactive` to force flag-only mode.
145
172
  - On a fresh instance, `setup init` can auto-discover and claim the first-run access key from `/api/auth`.
146
- - For existing installs, pass `--key <ACCESS_KEY>` (or set `SWARMCLAW_ACCESS_KEY`).
173
+ - For existing installs, pass `--key <ACCESS_KEY>` or set `SWARMCLAW_ACCESS_KEY` / `SWARMCLAW_API_KEY`.
147
174
  - `setup init` performs provider validation, stores credentials, creates a starter agent, and marks setup complete.
148
175
  - Use `--skip-check` to bypass connection validation.
149
176
 
@@ -164,7 +191,7 @@ Notes:
164
191
  ## Features
165
192
 
166
193
  - **15 providers out of the box** - CLI providers + major hosted APIs + OpenAI-compatible custom endpoints
167
- - **OpenClaw-native control plane** - per-agent gateway mapping, reload modes, sync, and approval flows
194
+ - **OpenClaw-native control plane** - named gateway profiles, external runtimes, reload modes, sync, and approval flows
168
195
  - **Agent builder + inspector** - personality/system tuning, skill management, and OpenClaw file editing
169
196
  - **Rich toolset** - shell, files, browser, git, sandbox execution, memory, MCP, and delegation
170
197
  - **Platform automation** - agents can manage tasks, schedules, chats, connectors, secrets, and more
@@ -249,6 +276,8 @@ src/
249
276
  | xAI (Grok) | api.x.ai | Grok 3, Grok 3 Fast, Grok 3 Mini |
250
277
  | Fireworks AI | api.fireworks.ai | DeepSeek R1, Llama 3.3 70B, Qwen 3 |
251
278
 
279
+ If a provider is configured, SwarmClaw can populate the model dropdown from that provider’s advertised model list. For OpenAI this means the selector can auto-fill current OpenAI models, while still allowing users to type a newer or custom model manually if it is not in the fetched list yet.
280
+
252
281
  ### Local & Remote
253
282
 
254
283
  | Provider | Type | Notes |
@@ -287,6 +316,7 @@ Connector ingress now also supports optional pairing/allowlist policy:
287
316
  - `dmPolicy: allowlist` blocks unknown senders until approved
288
317
  - `/pair` flow lets approved admins generate and approve pairing codes
289
318
  - `/think` command can set connector thread thinking level (`low`, `medium`, `high`)
319
+ - Session overrides also support per-thread `/reply`, `/scope`, `/thread`, `/provider`, `/model`, `/idle`, `/maxage`, and `/reset` controls
290
320
 
291
321
  ## Agent Tools
292
322
 
@@ -301,18 +331,19 @@ Agents can use the following tools when enabled:
301
331
  | Edit File | Search-and-replace editing (exact match required) |
302
332
  | Web Search | Search the web via DuckDuckGo HTML scraping |
303
333
  | Web Fetch | Fetch and extract text content from URLs (uses cheerio) |
304
- | CLI Delegation | Delegate complex tasks to Claude Code, Codex CLI, OpenCode CLI, or Gemini CLI |
305
- | Spawn Subagent | Delegate a sub-task to another agent and capture its response in the current run |
306
- | Browser | Playwright-powered web browsing via MCP (navigate, click, type, screenshot, PDF) |
334
+ | CLI Delegation | Delegate complex tasks to Claude Code, Codex CLI, OpenCode CLI, or Gemini CLI, either inline or as a background job handle |
335
+ | Spawn Subagent | Delegate a sub-task to another agent with `status` / `list` / `wait` / `cancel` handles and inherited browser state when needed |
336
+ | Browser | Playwright-powered web browsing via MCP with persistent profiles, structured page reads, form helpers, verification actions, and resumable state |
307
337
  | Canvas | Present/hide/snapshot live HTML content in a chat canvas panel |
308
338
  | HTTP Request | Make direct API calls with method, headers, body, redirect control, and timeout |
309
339
  | Git | Run structured git subcommands (`status`, `diff`, `log`, `add`, `commit`, `push`, etc.) with repo safety checks |
310
340
  | Memory | Store and retrieve long-term memories with FTS5 + vector search, file references, image attachments, and linked memory graph traversal |
341
+ | Monitor | Inspect system state and create durable watches over files, endpoints, tasks, webhooks, and page/content changes (`monitor_tool`) |
311
342
  | Wallet | Manage an agent-linked Solana wallet (`wallet_tool`) to check balance/address, send SOL (limits + approval), and review transaction history |
312
343
  | Image Generation | Generate images from prompts (`generate_image`) via OpenAI, Stability, Replicate, fal.ai, Together, Fireworks, BFL, or custom endpoints; saved to uploads |
313
344
  | Email | Send outbound email via SMTP (`email`) with `send`/`status` actions |
314
345
  | Calendar | Manage Google/Outlook events (`calendar`) with list/create/update/delete/status actions |
315
- | Sandbox | Run JS/TS (Deno) or Python code in an isolated sandbox. Created files are returned as downloadable artifacts |
346
+ | Sandbox | Run JS/TS in a Deno sandbox when custom code is necessary. If Deno is unavailable it fails closed with guidance; for simple API calls, prefer HTTP Request. |
316
347
  | MCP Servers | Connect to external Model Context Protocol servers. Tools from MCP servers are injected as first-class agent tools |
317
348
 
318
349
  ### Platform Tools
@@ -324,7 +355,7 @@ Agents with platform tools enabled can manage the SwarmClaw instance:
324
355
  | Manage Agents | List, create, update, delete agents |
325
356
  | Manage Tasks | Create and manage task board items with agent assignment |
326
357
  | Manage Schedules | Create cron, interval, or one-time scheduled jobs |
327
- | Reminders | Schedule a conversational wake event in the current chat (`schedule_wake`) |
358
+ | Reminders | Schedule a durable conversational wake event in the current chat (`schedule_wake`) |
328
359
  | Manage Skills | List, create, update reusable skill definitions |
329
360
  | Manage Documents | Upload/search/get/delete indexed docs for lightweight RAG workflows |
330
361
  | Manage Webhooks | Register external webhook endpoints that trigger agent chats |
@@ -367,21 +398,6 @@ Daemon runtime also triggers memory consolidation (daily summary generation plus
367
398
  - **API:** `GET /api/daemon` (status), `POST /api/daemon` with `{"action": "start"}` or `{"action": "stop"}`
368
399
  - Auto-starts on first authenticated runtime traffic (`/api/auth` or `/api/daemon`) unless `SWARMCLAW_DAEMON_AUTOSTART=0`
369
400
 
370
- ## Main Agent Loop
371
-
372
- For autonomous long-running missions, enable the **Main Loop** on an agent-thread or orchestrated chat. This lets an agent pursue a goal continuously with heartbeat-driven progress checks and automatic followups.
373
-
374
- - **Heartbeat prompts:** `SWARM_MAIN_MISSION_TICK` triggers on each heartbeat, giving the agent its goal, status, and pending events
375
- - **Auto-followup:** When an agent returns `[MAIN_LOOP_META] {"follow_up":true}`, the loop schedules another tick after `delay_sec`
376
- - **Mission state:** Tracks `goal`, `status` (idle/progress/blocked/ok), `summary`, `nextAction`, `autonomyMode` (assist/autonomous), and pending events
377
- - **Autonomy modes:**
378
- - `autonomous`: Agent executes safe actions without confirmation, only asks when blocked by permissions/credentials
379
- - `assist`: Agent asks before irreversible external actions (sending messages, purchases, account mutations)
380
- - **API:** `POST /api/chats/[id]/main-loop` with `{"tick":true}` to trigger a mission tick
381
- - **CLI:** `swarmclaw chats main-loop <id>` to inspect loop state, or `swarmclaw chats main-loop-action <id> --data '{"action":"nudge"}'` to control it
382
-
383
- Use this for background agents that should "keep working" on a goal until blocked or complete.
384
-
385
401
  ## Loop Modes
386
402
 
387
403
  Configure loop behavior in **Settings → Runtime & Loop Controls**:
@@ -440,9 +456,9 @@ Agents and chats can have **fallback credentials**. If the primary API key gets
440
456
 
441
457
  ## Plugin System
442
458
 
443
- SwarmClaw features a powerful, modular plugin system designed for both agent enhancement and application extensibility. It is fully compatible with the **OpenClaw** plugin format.
459
+ SwarmClaw features a modular plugin system for agent capabilities, UI extensions, provider/connectors, and post-turn automation. It supports the native SwarmClaw hook/tool format and the **OpenClaw** register/activate formats.
444
460
 
445
- Plugins can be managed in **Settings → Plugins** and installed via the Marketplace, URL, or by dropping `.js` files into `data/plugins/`.
461
+ Plugins can be managed in **Settings → Plugins** and installed via the Marketplace, HTTPS URL, or by dropping `.js` / `.mjs` files into `data/plugins/`.
446
462
 
447
463
  Docs:
448
464
  - Full docs: https://swarmclaw.ai/docs
@@ -453,17 +469,30 @@ Docs:
453
469
  Unlike standard tool systems, SwarmClaw plugins can modify the application itself:
454
470
 
455
471
  - **Agent Tools**: Define custom tools that agents can autonomously discover and use.
456
- - **Lifecycle Hooks**: Intercept events like `beforeAgentStart`, `afterToolExec`, and `onMessage`.
472
+ - **Lifecycle Hooks**: Intercept events like `beforeAgentStart`, `beforeToolExec`, `afterToolExec`, `afterChatTurn`, and `onMessage`.
457
473
  - **UI Extensions**:
458
474
  - `sidebarItems`: Inject new navigation links into the main sidebar.
459
475
  - `headerWidgets`: Add status badges or indicators to the chat header (e.g., Wallet Balance).
460
476
  - `chatInputActions`: Add custom action buttons next to the chat input (e.g., "Quick Scan").
461
477
  - `plugin-ui` Messages: Render rich, interactive React cards in the chat stream.
462
478
  - **Deep Chat Hooks**:
463
- - `transformInboundMessage`: Modify user messages before they reach the agent.
464
- - `transformOutboundMessage`: Modify agent responses before they are saved or displayed.
479
+ - `transformInboundMessage`: Modify user messages before they reach the agent runtime.
480
+ - `transformOutboundMessage`: Modify agent responses before they are persisted or displayed.
481
+ - `beforeToolExec`: Can rewrite tool input before the selected tool executes.
465
482
  - **Custom Providers**: Add new LLM backends (e.g., a specialized local model or a new API).
466
483
  - **Custom Connectors**: Build new chat platform bridges (e.g., a proprietary internal messenger).
484
+ - **Per-plugin Settings**: Declare `ui.settingsFields` and read/write them via `/api/plugins/settings`. Fields marked `type: 'secret'` are encrypted at rest.
485
+
486
+ ### Canonical Plugin IDs
487
+
488
+ Built-in capabilities now resolve to a single canonical plugin family ID across agent configs, policy rules, approvals, and the Plugins UI. Legacy aliases still work, but the canonical IDs are what you should document and store going forward.
489
+
490
+ - `manage_sessions` instead of `session_info`
491
+ - `manage_connectors` instead of `connectors`
492
+ - `http_request` instead of `http`
493
+ - `spawn_subagent` instead of `subagent`
494
+ - `manage_chatrooms` instead of `chatroom`
495
+ - `schedule_wake` instead of `schedule`
467
496
 
468
497
  ### Autonomous Capability Discovery
469
498
 
@@ -494,12 +523,53 @@ module.exports = {
494
523
  };
495
524
  ```
496
525
 
526
+ Hook signatures of note:
527
+
528
+ - `beforeToolExec({ toolName, input })` may return a replacement input object.
529
+ - `afterToolExec({ session, toolName, input, output })` observes completed tool executions.
530
+ - `transformInboundMessage({ session, text })` and `transformOutboundMessage({ session, text })` run sequentially across enabled plugins.
531
+
532
+ ### Building Plugins
533
+
534
+ The shortest reliable workflow for a new plugin:
535
+
536
+ 1. Create a focused `.js` or `.mjs` file under `data/plugins/`.
537
+ 2. Export `name`, `description`, any `hooks`, and optional `tools` / `ui.settingsFields`.
538
+ 3. Keep tool outputs structured when the agent needs to chain them into later steps.
539
+ 4. Use `settingsFields` for secrets or environment-specific values instead of hardcoding them.
540
+ 5. If the plugin needs third-party npm packages, attach a `package.json` manifest so SwarmClaw can manage it in a per-plugin workspace.
541
+ 6. Enable the plugin in **Settings → Plugins** and test both the tool path and any hook behavior.
542
+ 7. If you host it remotely, install from a stable HTTPS URL so SwarmClaw can record source metadata and update it later.
543
+
544
+ A fuller step-by-step walkthrough lives at https://swarmclaw.ai/docs/plugin-tutorial.
545
+
497
546
  ### Lifecycle Management
498
547
 
499
548
  - **Versioning**: All plugins support semantic versioning (e.g., `v1.2.3`).
500
- - **Updates**: Plugins can be updated individually or in bulk via the Plugins manager.
501
- - **Hot-Reload**: The system automatically reloads plugin logic when a file is updated or a new plugin is installed.
502
- - **Stability Guardrails**: Consecutive plugin failures are tracked in `data/plugin-failures.json`; failing plugins are auto-disabled, a warning notification is emitted in-app, and users can re-enable manually from the Plugins manager.
549
+ - **Updates**: External plugins installed from a recorded source URL can be updated individually or in bulk via the Plugins manager. Built-ins update with the app release.
550
+ - **Hot Reload**: Changes inside `data/plugins/` invalidate the plugin registry automatically, and installs/updates trigger an immediate reload.
551
+ - **Plugin Workspaces**: Plugins with a manifest are managed under `data/plugins/.workspaces/<plugin>/`, and dependency installs can be triggered from the plugin detail sheet or `POST /api/plugins/dependencies`.
552
+ - **Stability Guardrails**: Consecutive plugin failures are tracked in `data/plugin-failures.json`; failing external plugins are auto-disabled, a warning notification is emitted in-app, and users can re-enable manually from the Plugins manager.
553
+ - **Source Metadata**: Marketplace/URL installs record the normalized source URL and source hash in `data/plugins.json`.
554
+ - **Settings Safety**: Plugin settings are validated against declared `settingsFields`; unknown keys are ignored and `secret` values are stored encrypted.
555
+
556
+ ### Browser, Watch, and Delegation Upgrades
557
+
558
+ - **Persistent Browser Profiles**: The built-in `browser` plugin now keeps a reusable profile per chat/session, and subagents inherit the parent profile by default. Profiles live under `~/.swarmclaw/browser-profiles` unless you override `BROWSER_PROFILES_DIR`, so cookies, storage, and authenticated state survive longer-running work without polluting the project tree. Browser state is exposed at `GET /api/chats/[id]/browser`.
559
+ - **Higher-Level Browser Actions**: In addition to raw Playwright-style actions, `browser` supports workflow-oriented actions such as `read_page`, `extract_links`, `extract_form_fields`, `extract_table`, `fill_form`, `submit_form`, `scroll_until`, `download_file`, `complete_web_task`, `verify_text`, `verify_element`, `verify_list`, `verify_value`, `profile`, and `reset_profile`.
560
+ - **Structured Browser State**: Browser sessions persist recent observations, tabs, artifacts (screenshots / PDFs / downloads), current URL, and last errors in `browser_sessions`, which makes autonomous browser tasks easier to resume, inspect, and hand off across turns.
561
+ - **Durable Watches**: `schedule_wake` now uses persisted watch jobs instead of an in-memory timer, and `monitor_tool` supports `create_watch`, `list_watches`, `get_watch`, and `cancel_watch` across `time`, `http`, `file`, `task`, `webhook`, and `page` conditions. The same watch system also powers the new `mailbox`, session-mailbox, and approval waits used by human-loop flows. Watches support common checks like status/status sets, regex or text matches, content changes, existence checks, inbound mailbox correlation IDs, and webhook event filters.
562
+ - **Long-Running Delegation Handles**: `delegate` and `spawn_subagent` support handle-based flows instead of only synchronous final text. Use `background=true` or `waitForCompletion=false` to launch long-running work, then inspect or stop it with `action=status|list|wait|cancel`.
563
+ - **Delegation Job Persistence**: Delegate and subagent runs are recorded in `delegation_jobs` with checkpoints, backend/session metadata, resume IDs, child session IDs, and terminal-status recovery after daemon restarts. Late completions no longer overwrite cancelled jobs.
564
+
565
+ ### New Primitive Plugins
566
+
567
+ - **Mailbox / Inbox Automation**: The built-in `mailbox` plugin adds IMAP/SMTP inbox access with `status`, `list_messages`, `list_threads`, `search_messages`, `read_message`, `download_attachment`, `reply`, and `wait_for_email`. It supports durable inbound-email waits and reuses plugin settings / connector config where possible. Configure it in **Settings → Plugins** with `imapHost`, `smtpHost`, `user`, `password`, and optional reply defaults.
568
+ - **Human-in-the-Loop Requests**: The built-in `ask_human` plugin provides `request_input`, `request_approval`, `wait_for_reply`, `wait_for_approval`, `list_mailbox`, `ack_mailbox`, and `status`. It is backed by session mailbox envelopes plus approval records so agents can pause and resume on real human responses instead of polling ad hoc state.
569
+ - **Document Parsing / OCR**: The built-in `document` plugin adds `read`, `metadata`, `ocr`, `extract_tables`, `store`, `list`, `search`, `get`, and `delete`. It uses the shared document extraction helpers for PDFs, Office docs, OCR-able images, HTML, CSV/TSV/XLSX, ZIP inspection, and plain text files.
570
+ - **Schema-Driven Extraction**: The built-in `extract` plugin adds `extract_structured` and `summarize`, using the current session model/provider to turn raw text or local files into validated JSON objects. This is the primitive to combine with browser / document / crawl output when an agent needs structured records instead of prose.
571
+ - **Tabular Data Operations**: The built-in `table` plugin adds `read`, `load_csv`, `load_xlsx`, `summarize`, `filter`, `sort`, `group`, `pivot`, `dedupe`, `join`, and `write`. It operates on CSV, TSV, JSON array-of-objects, or XLSX inputs without forcing agents to drop into shell or Python for basic spreadsheet work, and transformed tables can be persisted with `outputPath` / `saveTo`.
572
+ - **Multi-Page Crawling**: The built-in `crawl` plugin adds `crawl_site`, `follow_pagination`, `extract_sitemap`, `dedupe_pages`, and `batch_extract`. It handles BFS-style same-origin site traversal, accepts either a fresh start URL or an explicit page list, and can hand the aggregate page set directly into structured extraction for research-heavy autonomous tasks.
503
573
 
504
574
  ## Deploy to a VPS
505
575
 
@@ -600,7 +670,7 @@ npm run update:easy # safe update helper for local installs
600
670
  SwarmClaw uses tag-based releases (`vX.Y.Z`) as the stable channel.
601
671
 
602
672
  ```bash
603
- # example patch release (v0.7.2 style)
673
+ # example patch release (v0.7.4 style)
604
674
  npm version patch
605
675
  git push origin main --follow-tags
606
676
  ```
@@ -610,16 +680,15 @@ On `v*` tags, GitHub Actions will:
610
680
  2. Create a GitHub Release
611
681
  3. Build and publish Docker images to `ghcr.io/swarmclawai/swarmclaw` (`:vX.Y.Z`, `:latest`, `:sha-*`)
612
682
 
613
- #### v0.7.2 Release Readiness Notes
683
+ #### v0.7.4 Release Readiness Notes
614
684
 
615
- Before shipping `v0.7.2`, confirm the following user-facing changes are reflected in docs:
685
+ Before shipping `v0.7.4`, confirm the following user-facing changes are reflected in docs:
616
686
 
617
- 1. `sessions` `chats` naming is consistent in README, site docs, and CLI/API examples.
618
- 2. New built-in plugins/tools are documented (`image_gen`, `email`, `calendar`) including settings expectations.
619
- 3. Plugin settings support is documented (`settingsFields`, `/api/plugins/settings` read/write endpoints).
620
- 4. Usage telemetry docs include plugin rollups (`byPlugin`) and plugin token usage dashboard context.
621
- 5. Gemini CLI delegation support and `geminiResumeId` task metadata are documented where CLI sessions are described.
622
- 6. Site release notes include a `v0.7.2` section and docs index points to the current release notes version.
687
+ 1. Sandbox docs are updated everywhere to reflect the current Deno-only `sandbox_exec` behavior and the guidance to prefer `http_request` for simple API calls.
688
+ 2. OpenClaw docs cover the current gateway/runtime behavior, including per-agent gateway routing, control-plane actions, and inspector-side advanced controls.
689
+ 3. Site and README install/version strings are updated to `v0.7.4`, including install snippets, release notes index text, and sidebar/footer labels.
690
+ 4. Release notes summarize the user-visible setup/auth/runtime changes from the current worktree, especially gateway/external-agent/setup flow improvements.
691
+ 5. CLI and tool docs do not reference removed or non-functional surfaces such as the old `openclaw_sandbox` bridge.
623
692
 
624
693
  ## CLI
625
694
 
@@ -654,9 +723,6 @@ swarmclaw agents list
654
723
  swarmclaw chats create --name "Main Ops" --agent-id <agentId>
655
724
  swarmclaw chats list
656
725
 
657
- # run a main loop action
658
- swarmclaw chats main-loop-action <chatId> --data '{"action":"nudge"}'
659
-
660
726
  # run setup diagnostics
661
727
  swarmclaw setup doctor
662
728
  ```
@@ -0,0 +1,157 @@
1
+ 'use strict'
2
+ /* eslint-disable @typescript-eslint/no-require-imports */
3
+
4
+ const fs = require('node:fs')
5
+ const path = require('node:path')
6
+
7
+ const LOCKFILE_NAMES = [
8
+ 'package-lock.json',
9
+ 'pnpm-lock.yaml',
10
+ 'yarn.lock',
11
+ 'bun.lock',
12
+ 'bun.lockb',
13
+ ]
14
+ const INSTALL_METADATA_FILE = '.swarmclaw-install.json'
15
+
16
+ function normalizePackageManager(raw) {
17
+ switch (String(raw || '').trim().toLowerCase()) {
18
+ case 'pnpm':
19
+ case 'yarn':
20
+ case 'bun':
21
+ case 'npm':
22
+ return String(raw).trim().toLowerCase()
23
+ default:
24
+ return null
25
+ }
26
+ }
27
+
28
+ function detectPackageManagerFromUserAgent(userAgent) {
29
+ const normalized = String(userAgent || '').toLowerCase()
30
+ if (normalized.startsWith('pnpm/')) return 'pnpm'
31
+ if (normalized.startsWith('yarn/')) return 'yarn'
32
+ if (normalized.startsWith('bun/')) return 'bun'
33
+ if (normalized.startsWith('npm/')) return 'npm'
34
+ return null
35
+ }
36
+
37
+ function readInstallMetadata(rootDir) {
38
+ const metadataPath = path.join(rootDir, INSTALL_METADATA_FILE)
39
+ if (!fs.existsSync(metadataPath)) return null
40
+ try {
41
+ const raw = JSON.parse(fs.readFileSync(metadataPath, 'utf8'))
42
+ return raw && typeof raw === 'object' ? raw : null
43
+ } catch {
44
+ return null
45
+ }
46
+ }
47
+
48
+ function detectPackageManager(rootDir, env = process.env) {
49
+ const envOverride = normalizePackageManager(env.SWARMCLAW_PACKAGE_MANAGER)
50
+ if (envOverride) return envOverride
51
+
52
+ const installMetadata = readInstallMetadata(rootDir)
53
+ const installManager = normalizePackageManager(installMetadata?.packageManager)
54
+ if (installManager) return installManager
55
+
56
+ if (fs.existsSync(path.join(rootDir, 'bun.lock')) || fs.existsSync(path.join(rootDir, 'bun.lockb'))) return 'bun'
57
+ if (fs.existsSync(path.join(rootDir, 'pnpm-lock.yaml'))) return 'pnpm'
58
+ if (fs.existsSync(path.join(rootDir, 'yarn.lock'))) return 'yarn'
59
+ if (fs.existsSync(path.join(rootDir, 'package-lock.json'))) return 'npm'
60
+
61
+ const userAgentManager = detectPackageManagerFromUserAgent(env.npm_config_user_agent)
62
+ if (userAgentManager) return userAgentManager
63
+ return 'npm'
64
+ }
65
+
66
+ function getInstallCommand(packageManager, omitDev = false) {
67
+ switch (packageManager) {
68
+ case 'pnpm':
69
+ return omitDev
70
+ ? { command: 'pnpm', args: ['install', '--prod'] }
71
+ : { command: 'pnpm', args: ['install'] }
72
+ case 'yarn':
73
+ return omitDev
74
+ ? { command: 'yarn', args: ['install', '--production=true'] }
75
+ : { command: 'yarn', args: ['install'] }
76
+ case 'bun':
77
+ return omitDev
78
+ ? { command: 'bun', args: ['install', '--production'] }
79
+ : { command: 'bun', args: ['install'] }
80
+ case 'npm':
81
+ default:
82
+ return omitDev
83
+ ? { command: 'npm', args: ['install', '--omit=dev'] }
84
+ : { command: 'npm', args: ['install'] }
85
+ }
86
+ }
87
+
88
+ function getGlobalUpdateCommand(packageManager, packageName) {
89
+ return getGlobalUpdateSpec(packageManager, packageName).display
90
+ }
91
+
92
+ function getGlobalUpdateSpec(packageManager, packageName) {
93
+ switch (packageManager) {
94
+ case 'pnpm':
95
+ return {
96
+ command: 'pnpm',
97
+ args: ['add', '-g', `${packageName}@latest`],
98
+ display: `pnpm add -g ${packageName}@latest`,
99
+ }
100
+ case 'yarn':
101
+ return {
102
+ command: 'yarn',
103
+ args: ['global', 'add', `${packageName}@latest`],
104
+ display: `yarn global add ${packageName}@latest`,
105
+ }
106
+ case 'bun':
107
+ return {
108
+ command: 'bun',
109
+ args: ['add', '-g', `${packageName}@latest`],
110
+ display: `bun add -g ${packageName}@latest`,
111
+ }
112
+ case 'npm':
113
+ default:
114
+ return {
115
+ command: 'npm',
116
+ args: ['update', '-g', packageName],
117
+ display: `npm update -g ${packageName}`,
118
+ }
119
+ }
120
+ }
121
+
122
+ function getRunScriptCommand(packageManager, scriptName) {
123
+ switch (packageManager) {
124
+ case 'pnpm':
125
+ return { command: 'pnpm', args: [scriptName] }
126
+ case 'yarn':
127
+ return { command: 'yarn', args: [scriptName] }
128
+ case 'bun':
129
+ return { command: 'bun', args: ['run', scriptName] }
130
+ case 'npm':
131
+ default:
132
+ return { command: 'npm', args: ['run', scriptName] }
133
+ }
134
+ }
135
+
136
+ function dependenciesChanged(diffText) {
137
+ if (!diffText) return false
138
+ return String(diffText)
139
+ .split('\n')
140
+ .map((line) => line.trim())
141
+ .filter(Boolean)
142
+ .some((file) => file === 'package.json' || LOCKFILE_NAMES.includes(file))
143
+ }
144
+
145
+ module.exports = {
146
+ dependenciesChanged,
147
+ detectPackageManager,
148
+ detectPackageManagerFromUserAgent,
149
+ getGlobalUpdateCommand,
150
+ getGlobalUpdateSpec,
151
+ getInstallCommand,
152
+ getRunScriptCommand,
153
+ INSTALL_METADATA_FILE,
154
+ LOCKFILE_NAMES,
155
+ normalizePackageManager,
156
+ readInstallMetadata,
157
+ }
@@ -0,0 +1,90 @@
1
+ 'use strict'
2
+ /* eslint-disable @typescript-eslint/no-require-imports */
3
+
4
+ const test = require('node:test')
5
+ const assert = require('node:assert/strict')
6
+ const fs = require('node:fs')
7
+ const os = require('node:os')
8
+ const path = require('node:path')
9
+
10
+ const {
11
+ INSTALL_METADATA_FILE,
12
+ LOCKFILE_NAMES,
13
+ dependenciesChanged,
14
+ detectPackageManager,
15
+ detectPackageManagerFromUserAgent,
16
+ getGlobalUpdateSpec,
17
+ getInstallCommand,
18
+ getRunScriptCommand,
19
+ } = require('./package-manager.js')
20
+
21
+ test('detectPackageManagerFromUserAgent parses supported package managers', () => {
22
+ assert.equal(detectPackageManagerFromUserAgent('pnpm/10.6.1 npm/? node/v22.6.0 darwin arm64'), 'pnpm')
23
+ assert.equal(detectPackageManagerFromUserAgent('yarn/4.7.0 npm/? node/v22.6.0 darwin arm64'), 'yarn')
24
+ assert.equal(detectPackageManagerFromUserAgent('bun/1.2.10 npm/? node/v22.6.0 darwin arm64'), 'bun')
25
+ assert.equal(detectPackageManagerFromUserAgent('npm/10.9.2 node/v22.6.0 darwin arm64'), 'npm')
26
+ })
27
+
28
+ test('detectPackageManager prefers the lockfile present in the workspace', () => {
29
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'swarmclaw-pm-'))
30
+
31
+ fs.writeFileSync(path.join(tmpDir, 'pnpm-lock.yaml'), 'lock', 'utf8')
32
+ assert.equal(detectPackageManager(tmpDir), 'pnpm')
33
+
34
+ fs.writeFileSync(path.join(tmpDir, 'bun.lockb'), 'lock', 'utf8')
35
+ assert.equal(detectPackageManager(tmpDir), 'bun')
36
+
37
+ fs.rmSync(tmpDir, { recursive: true, force: true })
38
+ })
39
+
40
+ test('detectPackageManager falls back to npm when no lockfile exists', () => {
41
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'swarmclaw-pm-empty-'))
42
+ assert.equal(detectPackageManager(tmpDir), 'npm')
43
+ fs.rmSync(tmpDir, { recursive: true, force: true })
44
+ })
45
+
46
+ test('detectPackageManager uses install metadata when present', () => {
47
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'swarmclaw-pm-meta-'))
48
+ fs.writeFileSync(
49
+ path.join(tmpDir, INSTALL_METADATA_FILE),
50
+ JSON.stringify({ packageManager: 'yarn' }),
51
+ 'utf8',
52
+ )
53
+ assert.equal(detectPackageManager(tmpDir), 'yarn')
54
+ fs.rmSync(tmpDir, { recursive: true, force: true })
55
+ })
56
+
57
+ test('dependenciesChanged recognizes package.json and all supported lockfiles', () => {
58
+ assert.equal(dependenciesChanged('package.json\nsrc/app.ts'), true)
59
+ for (const lockfile of LOCKFILE_NAMES) {
60
+ assert.equal(dependenciesChanged(`${lockfile}\nREADME.md`), true)
61
+ }
62
+ assert.equal(dependenciesChanged('README.md\nsrc/index.ts'), false)
63
+ })
64
+
65
+ test('getInstallCommand returns manager-specific install arguments', () => {
66
+ assert.deepEqual(getInstallCommand('npm', true), { command: 'npm', args: ['install', '--omit=dev'] })
67
+ assert.deepEqual(getInstallCommand('pnpm', false), { command: 'pnpm', args: ['install'] })
68
+ assert.deepEqual(getInstallCommand('yarn', true), { command: 'yarn', args: ['install', '--production=true'] })
69
+ assert.deepEqual(getInstallCommand('bun', true), { command: 'bun', args: ['install', '--production'] })
70
+ })
71
+
72
+ test('getRunScriptCommand returns manager-specific script launchers', () => {
73
+ assert.deepEqual(getRunScriptCommand('npm', 'build'), { command: 'npm', args: ['run', 'build'] })
74
+ assert.deepEqual(getRunScriptCommand('pnpm', 'start'), { command: 'pnpm', args: ['start'] })
75
+ assert.deepEqual(getRunScriptCommand('yarn', 'dev'), { command: 'yarn', args: ['dev'] })
76
+ assert.deepEqual(getRunScriptCommand('bun', 'start'), { command: 'bun', args: ['run', 'start'] })
77
+ })
78
+
79
+ test('getGlobalUpdateSpec returns manager-specific update commands', () => {
80
+ assert.deepEqual(getGlobalUpdateSpec('npm', '@swarmclawai/swarmclaw'), {
81
+ command: 'npm',
82
+ args: ['update', '-g', '@swarmclawai/swarmclaw'],
83
+ display: 'npm update -g @swarmclawai/swarmclaw',
84
+ })
85
+ assert.deepEqual(getGlobalUpdateSpec('pnpm', '@swarmclawai/swarmclaw'), {
86
+ command: 'pnpm',
87
+ args: ['add', '-g', '@swarmclawai/swarmclaw@latest'],
88
+ display: 'pnpm add -g @swarmclawai/swarmclaw@latest',
89
+ })
90
+ })