@swarmclawai/swarmclaw 1.0.0 → 1.0.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 (34) hide show
  1. package/README.md +15 -2
  2. package/bin/doctor-cmd.js +155 -0
  3. package/bin/doctor-cmd.test.js +50 -0
  4. package/bin/install-root.js +39 -0
  5. package/bin/install-root.test.js +60 -0
  6. package/bin/server-cmd.js +160 -38
  7. package/bin/swarmclaw.js +83 -3
  8. package/bin/update-cmd.js +1 -6
  9. package/bin/update-cmd.test.js +1 -36
  10. package/bin/worker-cmd.js +13 -6
  11. package/package.json +16 -15
  12. package/scripts/postinstall.mjs +17 -13
  13. package/src/app/api/gateways/[id]/health/route.ts +2 -32
  14. package/src/app/api/gateways/health-route.test.ts +1 -1
  15. package/src/app/api/setup/check-provider/helpers.ts +28 -0
  16. package/src/app/api/setup/check-provider/route.test.ts +1 -1
  17. package/src/app/api/setup/check-provider/route.ts +5 -32
  18. package/src/app/api/tasks/import/github/helpers.ts +100 -0
  19. package/src/app/api/tasks/import/github/route.test.ts +1 -1
  20. package/src/app/api/tasks/import/github/route.ts +2 -92
  21. package/src/app/api/webhooks/[id]/helpers.ts +253 -0
  22. package/src/app/api/webhooks/[id]/route.ts +2 -243
  23. package/src/app/api/webhooks/route.test.ts +4 -2
  24. package/src/app/usage/page.tsx +22 -12
  25. package/src/cli/binary.test.js +57 -0
  26. package/src/cli/index.js +13 -1
  27. package/src/cli/server-cmd.test.js +77 -0
  28. package/src/lib/server/data-dir.test.ts +38 -3
  29. package/src/lib/server/data-dir.ts +11 -0
  30. package/src/lib/server/openclaw/health.ts +30 -1
  31. package/src/lib/server/session-tools/file-send.test.ts +18 -2
  32. package/src/lib/server/session-tools/file.ts +11 -7
  33. package/src/lib/server/skills/skill-discovery.test.ts +34 -1
  34. package/src/lib/server/skills/skill-discovery.ts +9 -4
package/bin/server-cmd.js CHANGED
@@ -5,7 +5,6 @@
5
5
  const fs = require('node:fs')
6
6
  const path = require('node:path')
7
7
  const { spawn, execFileSync } = require('node:child_process')
8
- const os = require('node:os')
9
8
  const {
10
9
  detectPackageManager,
11
10
  getInstallCommand,
@@ -13,21 +12,31 @@ const {
13
12
  const {
14
13
  readPackageVersion,
15
14
  resolvePackageRoot,
15
+ resolveStateHome,
16
16
  } = require('./install-root.js')
17
17
 
18
18
  // ---------------------------------------------------------------------------
19
19
  // Paths
20
20
  // ---------------------------------------------------------------------------
21
21
 
22
- const SWARMCLAW_HOME = process.env.SWARMCLAW_HOME || path.join(os.homedir(), '.swarmclaw')
23
22
  const PKG_ROOT = resolvePackageRoot({
24
23
  moduleDir: __dirname,
25
24
  argv1: process.argv[1],
26
25
  cwd: process.cwd(),
27
26
  })
27
+ const SWARMCLAW_HOME = resolveStateHome({
28
+ pkgRoot: PKG_ROOT,
29
+ moduleDir: __dirname,
30
+ argv1: process.argv[1],
31
+ cwd: process.cwd(),
32
+ env: process.env,
33
+ })
28
34
  const PID_FILE = path.join(SWARMCLAW_HOME, 'server.pid')
29
35
  const LOG_FILE = path.join(SWARMCLAW_HOME, 'server.log')
30
36
  const DATA_DIR = path.join(SWARMCLAW_HOME, 'data')
37
+ const WORKSPACE_DIR = path.join(SWARMCLAW_HOME, 'workspace')
38
+ const BROWSER_PROFILES_DIR = path.join(SWARMCLAW_HOME, 'browser-profiles')
39
+ const BUILD_WORKSPACES_DIR = path.join(SWARMCLAW_HOME, 'builds')
31
40
 
32
41
  // ---------------------------------------------------------------------------
33
42
  // Helpers
@@ -67,19 +76,120 @@ function resolveStandaloneBase(pkgRoot = PKG_ROOT) {
67
76
  return path.join(pkgRoot, '.next', 'standalone')
68
77
  }
69
78
 
79
+ function isGitCheckout(pkgRoot = PKG_ROOT) {
80
+ return fs.existsSync(path.join(pkgRoot, '.git'))
81
+ }
82
+
70
83
  function getVersion() {
71
84
  return readPackageVersion(PKG_ROOT) || 'unknown'
72
85
  }
73
86
 
87
+ function resolveInstalledNext(pkgRoot = PKG_ROOT) {
88
+ try {
89
+ const nextPackageJson = require.resolve('next/package.json', { paths: [pkgRoot] })
90
+ const nextPackageDir = path.dirname(nextPackageJson)
91
+ return {
92
+ nextCli: path.join(nextPackageDir, 'dist', 'bin', 'next'),
93
+ nodeModulesDir: path.dirname(nextPackageDir),
94
+ }
95
+ } catch {
96
+ return null
97
+ }
98
+ }
99
+
74
100
  function ensurePackageDependencies(pkgRoot = PKG_ROOT) {
75
- const nextCli = path.join(pkgRoot, 'node_modules', 'next', 'dist', 'bin', 'next')
76
- if (fs.existsSync(nextCli)) return nextCli
101
+ const resolved = resolveInstalledNext(pkgRoot)
102
+ if (resolved && fs.existsSync(resolved.nextCli)) return resolved
77
103
 
78
104
  const packageManager = detectPackageManager(pkgRoot, process.env)
79
105
  const install = getInstallCommand(packageManager)
80
106
  log(`Installing dependencies with ${packageManager}...`)
81
107
  execFileSync(install.command, install.args, { cwd: pkgRoot, stdio: 'inherit' })
82
- return nextCli
108
+
109
+ const installed = resolveInstalledNext(pkgRoot)
110
+ if (installed && fs.existsSync(installed.nextCli)) return installed
111
+
112
+ throw new Error('Next.js CLI was not found after installing dependencies.')
113
+ }
114
+
115
+ function resolvePackageBuildRoot(pkgRoot = PKG_ROOT) {
116
+ if (isGitCheckout(pkgRoot)) return pkgRoot
117
+ const version = readPackageVersion(pkgRoot) || 'unknown'
118
+ return path.join(BUILD_WORKSPACES_DIR, `package-${version}`)
119
+ }
120
+
121
+ function copyBuildWorkspaceContents(sourceRoot, targetRoot) {
122
+ const excluded = new Set([
123
+ '.git',
124
+ '.next',
125
+ 'data',
126
+ 'node_modules',
127
+ ])
128
+
129
+ ensureDir(targetRoot)
130
+
131
+ for (const entry of fs.readdirSync(sourceRoot, { withFileTypes: true })) {
132
+ if (excluded.has(entry.name)) continue
133
+
134
+ const sourcePath = path.join(sourceRoot, entry.name)
135
+ const targetPath = path.join(targetRoot, entry.name)
136
+ fs.rmSync(targetPath, { recursive: true, force: true })
137
+ fs.cpSync(sourcePath, targetPath, {
138
+ recursive: true,
139
+ force: true,
140
+ dereference: true,
141
+ })
142
+ }
143
+ }
144
+
145
+ function symlinkDir(targetPath, linkPath) {
146
+ fs.rmSync(linkPath, { recursive: true, force: true })
147
+ fs.symlinkSync(targetPath, linkPath, process.platform === 'win32' ? 'junction' : 'dir')
148
+ }
149
+
150
+ function prepareBuildWorkspace({ pkgRoot = PKG_ROOT, buildRoot = resolvePackageBuildRoot(pkgRoot), nodeModulesDir } = {}) {
151
+ copyBuildWorkspaceContents(pkgRoot, buildRoot)
152
+ symlinkDir(nodeModulesDir, path.join(buildRoot, 'node_modules'))
153
+ return buildRoot
154
+ }
155
+
156
+ function resolveStandaloneCandidateRoots(pkgRoot = PKG_ROOT) {
157
+ const roots = [pkgRoot]
158
+ const buildRoot = resolvePackageBuildRoot(pkgRoot)
159
+ if (buildRoot !== pkgRoot) roots.push(buildRoot)
160
+ return roots
161
+ }
162
+
163
+ function locateStandaloneServer({ pkgRoot = PKG_ROOT } = {}) {
164
+ for (const root of resolveStandaloneCandidateRoots(pkgRoot)) {
165
+ const standaloneBase = resolveStandaloneBase(root)
166
+ if (!fs.existsSync(standaloneBase)) continue
167
+
168
+ const direct = path.join(standaloneBase, 'server.js')
169
+ if (fs.existsSync(direct)) {
170
+ return { root, serverJs: direct }
171
+ }
172
+
173
+ function search(dir) {
174
+ const entries = fs.readdirSync(dir, { withFileTypes: true })
175
+ for (const entry of entries) {
176
+ const full = path.join(dir, entry.name)
177
+ if (entry.isFile() && entry.name === 'server.js') return full
178
+ if (entry.isDirectory() && entry.name !== 'node_modules') {
179
+ const found = search(full)
180
+ if (found) return found
181
+ }
182
+ }
183
+ return null
184
+ }
185
+
186
+ const nested = search(standaloneBase)
187
+ if (nested) {
188
+ return { root, serverJs: nested }
189
+ }
190
+ }
191
+
192
+ return null
83
193
  }
84
194
 
85
195
  // ---------------------------------------------------------------------------
@@ -96,14 +206,21 @@ function runBuild({ pkgRoot = PKG_ROOT } = {}) {
96
206
  ensureDir(SWARMCLAW_HOME)
97
207
  ensureDir(DATA_DIR)
98
208
 
99
- const nextCli = ensurePackageDependencies(pkgRoot)
209
+ const { nextCli, nodeModulesDir } = ensurePackageDependencies(pkgRoot)
210
+ const buildRoot = resolvePackageBuildRoot(pkgRoot)
211
+
212
+ if (buildRoot !== pkgRoot) {
213
+ prepareBuildWorkspace({ pkgRoot, buildRoot, nodeModulesDir })
214
+ log(`Using build workspace: ${buildRoot}`)
215
+ }
100
216
 
101
217
  log('Building Next.js application (this may take a minute)...')
102
- execFileSync(process.execPath, [nextCli, 'build'], {
103
- cwd: pkgRoot,
218
+ execFileSync(process.execPath, [nextCli, 'build', '--webpack'], {
219
+ cwd: buildRoot,
104
220
  stdio: 'inherit',
105
221
  env: {
106
222
  ...process.env,
223
+ SWARMCLAW_HOME,
107
224
  DATA_DIR,
108
225
  SWARMCLAW_BUILD_MODE: '1',
109
226
  },
@@ -117,29 +234,7 @@ function runBuild({ pkgRoot = PKG_ROOT } = {}) {
117
234
  // ---------------------------------------------------------------------------
118
235
 
119
236
  function findStandaloneServer({ pkgRoot = PKG_ROOT } = {}) {
120
- const standaloneBase = resolveStandaloneBase(pkgRoot)
121
-
122
- if (!fs.existsSync(standaloneBase)) {
123
- return null
124
- }
125
-
126
- const direct = path.join(standaloneBase, 'server.js')
127
- if (fs.existsSync(direct)) return direct
128
-
129
- function search(dir) {
130
- const entries = fs.readdirSync(dir, { withFileTypes: true })
131
- for (const entry of entries) {
132
- const full = path.join(dir, entry.name)
133
- if (entry.isFile() && entry.name === 'server.js') return full
134
- if (entry.isDirectory() && entry.name !== 'node_modules') {
135
- const found = search(full)
136
- if (found) return found
137
- }
138
- }
139
- return null
140
- }
141
-
142
- return search(standaloneBase)
237
+ return locateStandaloneServer({ pkgRoot })?.serverJs || null
143
238
  }
144
239
 
145
240
  // ---------------------------------------------------------------------------
@@ -147,11 +242,12 @@ function findStandaloneServer({ pkgRoot = PKG_ROOT } = {}) {
147
242
  // ---------------------------------------------------------------------------
148
243
 
149
244
  function startServer(opts, { pkgRoot = PKG_ROOT } = {}) {
150
- const serverJs = findStandaloneServer({ pkgRoot })
151
- if (!serverJs) {
245
+ const standalone = locateStandaloneServer({ pkgRoot })
246
+ if (!standalone) {
152
247
  logError('Standalone server.js not found in the installed package. Try running: swarmclaw server --build')
153
248
  process.exit(1)
154
249
  }
250
+ const { root: runtimeRoot, serverJs } = standalone
155
251
 
156
252
  ensureDir(SWARMCLAW_HOME)
157
253
  ensureDir(DATA_DIR)
@@ -162,7 +258,10 @@ function startServer(opts, { pkgRoot = PKG_ROOT } = {}) {
162
258
 
163
259
  const env = {
164
260
  ...process.env,
261
+ SWARMCLAW_HOME,
165
262
  DATA_DIR,
263
+ WORKSPACE_DIR,
264
+ BROWSER_PROFILES_DIR,
166
265
  HOSTNAME: host,
167
266
  PORT: port,
168
267
  WS_PORT: wsPort,
@@ -170,12 +269,14 @@ function startServer(opts, { pkgRoot = PKG_ROOT } = {}) {
170
269
 
171
270
  log(`Starting server on ${host}:${port} (WebSocket: ${wsPort})...`)
172
271
  log(`Package root: ${pkgRoot}`)
272
+ log(`Runtime root: ${runtimeRoot}`)
273
+ log(`Home: ${SWARMCLAW_HOME}`)
173
274
  log(`Data directory: ${DATA_DIR}`)
174
275
 
175
276
  if (opts.detach) {
176
277
  const logStream = fs.openSync(LOG_FILE, 'a')
177
278
  const child = spawn(process.execPath, [serverJs], {
178
- cwd: pkgRoot,
279
+ cwd: runtimeRoot,
179
280
  detached: true,
180
281
  env,
181
282
  stdio: ['ignore', logStream, logStream],
@@ -188,7 +289,7 @@ function startServer(opts, { pkgRoot = PKG_ROOT } = {}) {
188
289
  process.exit(0)
189
290
  } else {
190
291
  const child = spawn(process.execPath, [serverJs], {
191
- cwd: pkgRoot,
292
+ cwd: runtimeRoot,
192
293
  env,
193
294
  stdio: 'inherit',
194
295
  })
@@ -246,8 +347,11 @@ function showStatus() {
246
347
  }
247
348
 
248
349
  log(`Package: ${PKG_ROOT}`)
350
+ log(`Build workspace: ${resolvePackageBuildRoot()}`)
249
351
  log(`Home: ${SWARMCLAW_HOME}`)
250
352
  log(`Data: ${DATA_DIR}`)
353
+ log(`Workspace: ${WORKSPACE_DIR}`)
354
+ log(`Browser profiles: ${BROWSER_PROFILES_DIR}`)
251
355
  log(`WebSocket port: ${process.env.WS_PORT || '(PORT + 1)'}`)
252
356
 
253
357
  const serverJs = findStandaloneServer()
@@ -282,8 +386,7 @@ Options:
282
386
  console.log(help)
283
387
  }
284
388
 
285
- function main() {
286
- const args = process.argv.slice(3)
389
+ function main(args = process.argv.slice(3)) {
287
390
  let command = 'start'
288
391
  let forceBuild = false
289
392
  let detach = false
@@ -330,7 +433,17 @@ function main() {
330
433
  }
331
434
 
332
435
  if (needsBuild(forceBuild)) {
333
- runBuild()
436
+ if (!forceBuild) {
437
+ const installKind = isGitCheckout() ? 'checkout' : 'installed package'
438
+ log(`Standalone server bundle not found in this ${installKind}. Building locally...`)
439
+ }
440
+ try {
441
+ runBuild()
442
+ } catch (err) {
443
+ logError(`Build failed: ${err.message}`)
444
+ logError('Retry manually with: swarmclaw server --build')
445
+ process.exit(1)
446
+ }
334
447
  }
335
448
 
336
449
  startServer({ port, wsPort, host, detach })
@@ -342,12 +455,21 @@ if (require.main === module) {
342
455
 
343
456
  module.exports = {
344
457
  DATA_DIR,
458
+ BUILD_WORKSPACES_DIR,
459
+ BROWSER_PROFILES_DIR,
345
460
  PKG_ROOT,
346
461
  SWARMCLAW_HOME,
462
+ WORKSPACE_DIR,
347
463
  findStandaloneServer,
348
464
  getVersion,
465
+ isGitCheckout,
466
+ locateStandaloneServer,
349
467
  main,
350
468
  needsBuild,
469
+ prepareBuildWorkspace,
470
+ resolveInstalledNext,
471
+ resolvePackageBuildRoot,
472
+ resolveStandaloneCandidateRoots,
351
473
  resolveStandaloneBase,
352
474
  runBuild,
353
475
  }
package/bin/swarmclaw.js CHANGED
@@ -96,6 +96,19 @@ function normalizeLegacyCliEnv(env) {
96
96
  return nextEnv
97
97
  }
98
98
 
99
+ function printPackageVersion() {
100
+ const pkg = require('../package.json')
101
+ process.stdout.write(`${pkg.name || 'swarmclaw'} ${pkg.version || '0.0.0'}\n`)
102
+ }
103
+
104
+ function printVersionHelp() {
105
+ process.stdout.write(`
106
+ Usage: swarmclaw version
107
+
108
+ Show the installed SwarmClaw package version.
109
+ `.trim() + '\n')
110
+ }
111
+
99
112
  async function runMappedCli(argv) {
100
113
  const cliPath = path.join(__dirname, '..', 'src', 'cli', 'index.js')
101
114
  const cliModule = await import(cliPath)
@@ -106,25 +119,91 @@ async function runMappedCli(argv) {
106
119
  return runCli(argv)
107
120
  }
108
121
 
122
+ async function runHelp(argv) {
123
+ const [target, ...rest] = argv
124
+ if (!target) {
125
+ const code = await runMappedCli(['--help'])
126
+ process.exitCode = typeof code === 'number' ? code : 1
127
+ return
128
+ }
129
+
130
+ if (target === 'run' || target === 'start' || target === 'stop' || target === 'status' || target === 'server') {
131
+ require('./server-cmd.js').main(['--help'])
132
+ return
133
+ }
134
+ if (target === 'worker') {
135
+ require('./worker-cmd.js').main(['--help'])
136
+ return
137
+ }
138
+ if (target === 'doctor') {
139
+ require('./doctor-cmd.js').main(['--help'])
140
+ return
141
+ }
142
+ if (target === 'update') {
143
+ require('./update-cmd.js').main(['--help'])
144
+ return
145
+ }
146
+ if (target === 'version') {
147
+ printVersionHelp()
148
+ return
149
+ }
150
+
151
+ const forwarded = rest.includes('--help') || rest.includes('-h')
152
+ ? [target, ...rest]
153
+ : [target, ...rest, '--help']
154
+ const code = shouldUseLegacyTsCli(forwarded)
155
+ ? runLegacyTsCli(forwarded)
156
+ : await runMappedCli(forwarded)
157
+
158
+ process.exitCode = typeof code === 'number' ? code : 1
159
+ }
160
+
109
161
  async function main() {
110
162
  const argv = process.argv.slice(2)
111
163
  const top = argv[0]
112
164
 
113
165
  // Default to 'server' when invoked with no arguments.
114
166
  if (!top) {
115
- require('./server-cmd.js').main()
167
+ require('./server-cmd.js').main([])
168
+ return
169
+ }
170
+
171
+ if (top === '-v') {
172
+ printPackageVersion()
173
+ return
174
+ }
175
+
176
+ if (top === 'version' && argv.length === 1) {
177
+ printPackageVersion()
116
178
  return
117
179
  }
118
180
 
119
- // Route 'server', 'worker', and 'update' subcommands to CJS scripts (no TS dependency).
181
+ if (top === 'help') {
182
+ await runHelp(argv.slice(1))
183
+ return
184
+ }
185
+
186
+ // Route local lifecycle/maintenance commands to CJS scripts (no TS dependency).
120
187
  if (top === 'server') {
121
- require('./server-cmd.js').main()
188
+ require('./server-cmd.js').main(argv.slice(1))
189
+ return
190
+ }
191
+ if (top === 'run' || top === 'start') {
192
+ require('./server-cmd.js').main(argv.slice(1))
193
+ return
194
+ }
195
+ if (top === 'status' || top === 'stop') {
196
+ require('./server-cmd.js').main([top, ...argv.slice(1)])
122
197
  return
123
198
  }
124
199
  if (top === 'worker') {
125
200
  require('./worker-cmd.js').main()
126
201
  return
127
202
  }
203
+ if (top === 'doctor') {
204
+ require('./doctor-cmd.js').main(argv.slice(1))
205
+ return
206
+ }
128
207
  if (top === 'update') {
129
208
  require('./update-cmd.js').main()
130
209
  return
@@ -146,6 +225,7 @@ module.exports = {
146
225
  hasTsxRuntime,
147
226
  TS_CLI_ACTIONS,
148
227
  normalizeLegacyCliEnv,
228
+ printPackageVersion,
149
229
  supportsStripTypes,
150
230
  shouldUseLegacyTsCli,
151
231
  }
package/bin/update-cmd.js CHANGED
@@ -75,7 +75,6 @@ function runRegistrySelfUpdate(
75
75
  packageManager = resolveRegistryPackageManager(),
76
76
  execImpl = execFileSync,
77
77
  logger = { log, logError },
78
- rebuildImpl = execFileSync,
79
78
  ) {
80
79
  const update = getGlobalUpdateSpec(packageManager, PACKAGE_NAME)
81
80
  logger.log(`No git checkout detected. Updating the global ${PACKAGE_NAME} install via ${packageManager}...`)
@@ -92,15 +91,11 @@ function runRegistrySelfUpdate(
92
91
  return 1
93
92
  }
94
93
 
95
- const rebuildExitCode = rebuildStandaloneServer(rebuildImpl, logger)
96
- if (rebuildExitCode !== 0) return rebuildExitCode
97
-
98
94
  logger.log('Restart the server to apply changes: swarmclaw server stop && swarmclaw server start')
99
95
  return 0
100
96
  }
101
97
 
102
- function main() {
103
- const args = process.argv.slice(3)
98
+ function main(args = process.argv.slice(3)) {
104
99
  if (args.includes('-h') || args.includes('--help')) {
105
100
  console.log(`
106
101
  Usage: swarmclaw update
@@ -3,11 +3,10 @@
3
3
 
4
4
  const test = require('node:test')
5
5
  const assert = require('node:assert/strict')
6
- const path = require('node:path')
7
6
 
8
7
  const { runRegistrySelfUpdate } = require('./update-cmd.js')
9
8
 
10
- test('runRegistrySelfUpdate executes the manager-specific global update command and rebuilds the standalone server', () => {
9
+ test('runRegistrySelfUpdate executes the manager-specific global update command', () => {
11
10
  const messages = []
12
11
  const captured = []
13
12
 
@@ -20,9 +19,6 @@ test('runRegistrySelfUpdate executes the manager-specific global update command
20
19
  log: (message) => messages.push(`log:${message}`),
21
20
  logError: (message) => messages.push(`err:${message}`),
22
21
  },
23
- (command, args, options) => {
24
- captured.push({ command, args, options })
25
- },
26
22
  )
27
23
 
28
24
  assert.equal(exitCode, 0)
@@ -36,20 +32,9 @@ test('runRegistrySelfUpdate executes the manager-specific global update command
36
32
  timeout: 120_000,
37
33
  },
38
34
  },
39
- {
40
- command: process.execPath,
41
- args: [path.join(process.cwd(), 'bin', 'server-cmd.js'), '--build'],
42
- options: {
43
- cwd: process.cwd(),
44
- stdio: 'inherit',
45
- timeout: 600_000,
46
- },
47
- },
48
35
  ])
49
36
  assert.match(messages.join('\n'), /updating the global @swarmclawai\/swarmclaw install via pnpm/i)
50
37
  assert.match(messages.join('\n'), /global update complete via pnpm/i)
51
- assert.match(messages.join('\n'), /rebuilding the standalone server bundle/i)
52
- assert.match(messages.join('\n'), /standalone server bundle rebuilt/i)
53
38
  })
54
39
 
55
40
  test('runRegistrySelfUpdate reports a manual retry command when the registry update fails', () => {
@@ -70,23 +55,3 @@ test('runRegistrySelfUpdate reports a manual retry command when the registry upd
70
55
  assert.match(messages.join('\n'), /registry update failed: spawn bun ENOENT/i)
71
56
  assert.match(messages.join('\n'), /retry manually with: bun add -g @swarmclawai\/swarmclaw@latest/i)
72
57
  })
73
-
74
- test('runRegistrySelfUpdate reports a manual rebuild command when the rebuild step fails', () => {
75
- const messages = []
76
-
77
- const exitCode = runRegistrySelfUpdate(
78
- 'npm',
79
- () => {},
80
- {
81
- log: (message) => messages.push(`log:${message}`),
82
- logError: (message) => messages.push(`err:${message}`),
83
- },
84
- () => {
85
- throw new Error('build failed')
86
- },
87
- )
88
-
89
- assert.equal(exitCode, 1)
90
- assert.match(messages.join('\n'), /standalone rebuild failed: build failed/i)
91
- assert.match(messages.join('\n'), /retry manually with: swarmclaw server --build/i)
92
- })
package/bin/worker-cmd.js CHANGED
@@ -4,10 +4,12 @@
4
4
  const { spawn } = require('node:child_process')
5
5
 
6
6
  const {
7
+ BROWSER_PROFILES_DIR,
7
8
  DATA_DIR,
8
9
  PKG_ROOT,
9
10
  SWARMCLAW_HOME,
10
- findStandaloneServer,
11
+ WORKSPACE_DIR,
12
+ locateStandaloneServer,
11
13
  } = require('./server-cmd.js')
12
14
 
13
15
  function printHelp() {
@@ -23,8 +25,7 @@ Options:
23
25
  console.log(help)
24
26
  }
25
27
 
26
- function main() {
27
- const args = process.argv.slice(3)
28
+ function main(args = process.argv.slice(3)) {
28
29
  for (const arg of args) {
29
30
  if (arg === '-h' || arg === '--help') {
30
31
  printHelp()
@@ -36,7 +37,10 @@ function main() {
36
37
  }
37
38
  }
38
39
 
40
+ process.env.SWARMCLAW_HOME = SWARMCLAW_HOME
39
41
  process.env.DATA_DIR = DATA_DIR
42
+ process.env.WORKSPACE_DIR = WORKSPACE_DIR
43
+ process.env.BROWSER_PROFILES_DIR = BROWSER_PROFILES_DIR
40
44
  process.env.SWARMCLAW_DAEMON_BACKGROUND_SERVICES = '1'
41
45
  process.env.SWARMCLAW_WORKER_ONLY = '1'
42
46
 
@@ -44,15 +48,18 @@ function main() {
44
48
  console.log(`[swarmclaw] Package root: ${PKG_ROOT}`)
45
49
  console.log(`[swarmclaw] Home: ${SWARMCLAW_HOME}`)
46
50
  console.log(`[swarmclaw] Data directory: ${DATA_DIR}`)
51
+ console.log(`[swarmclaw] Workspace directory: ${WORKSPACE_DIR}`)
52
+ console.log(`[swarmclaw] Browser profiles: ${BROWSER_PROFILES_DIR}`)
47
53
 
48
- const serverJs = findStandaloneServer()
49
- if (!serverJs) {
54
+ const standalone = locateStandaloneServer()
55
+ if (!standalone) {
50
56
  console.error('[swarmclaw] Standalone server.js not found in the installed package. Try running: swarmclaw server --build')
51
57
  process.exit(1)
52
58
  }
59
+ const { root: runtimeRoot, serverJs } = standalone
53
60
 
54
61
  const child = spawn(process.execPath, [serverJs], {
55
- cwd: PKG_ROOT,
62
+ cwd: runtimeRoot,
56
63
  env: process.env,
57
64
  stdio: 'inherit',
58
65
  })
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@swarmclawai/swarmclaw",
3
- "version": "1.0.0",
4
- "description": "Self-hosted AI agent orchestration dashboard manage LLM providers, orchestrate agent swarms, schedule tasks, and bridge agents to chat platforms.",
3
+ "version": "1.0.3",
4
+ "description": "Self-hosted AI agent orchestration dashboard with OpenClaw integration, multi-provider support, LangGraph workflows, and chat platform connectors.",
5
5
  "license": "MIT",
6
6
  "publishConfig": {
7
7
  "access": "public",
@@ -51,8 +51,8 @@
51
51
  "dev": "next dev --turbopack --hostname 0.0.0.0 -p 3456",
52
52
  "dev:webpack": "next dev --webpack --hostname 0.0.0.0 -p 3456",
53
53
  "dev:clean": "rm -rf .next && next dev --turbopack --hostname 0.0.0.0 -p 3456",
54
- "build": "next build",
55
- "build:ci": "NEXT_DISABLE_ESLINT=1 next build",
54
+ "build": "next build --webpack",
55
+ "build:ci": "NEXT_DISABLE_ESLINT=1 next build --webpack",
56
56
  "start": "node .next/standalone/server.js",
57
57
  "start:standalone": "node .next/standalone/server.js",
58
58
  "smoke:browser": "node ./scripts/browser-route-smoke.mjs",
@@ -65,10 +65,11 @@
65
65
  "lint:baseline": "node ./scripts/lint-baseline.mjs check",
66
66
  "lint:baseline:update": "node ./scripts/lint-baseline.mjs update",
67
67
  "cli": "node ./bin/swarmclaw.js",
68
- "test:cli": "node --test src/cli/*.test.js bin/*.test.js",
68
+ "test:cli": "node --test src/cli/*.test.js bin/*.test.js scripts/postinstall.test.mjs",
69
69
  "test:setup": "tsx --test src/app/api/setup/check-provider/route.test.ts src/lib/server/provider-model-discovery.test.ts src/components/auth/setup-wizard/utils.test.ts src/components/auth/setup-wizard/types.test.ts src/hooks/setup-done-detection.test.ts src/lib/setup-defaults.test.ts",
70
70
  "test:openclaw": "tsx --test src/lib/openclaw/openclaw-agent-id.test.ts src/lib/openclaw/openclaw-endpoint.test.ts src/lib/server/agents/agent-runtime-config.test.ts src/lib/server/build-llm.test.ts src/lib/server/connectors/connector-routing.test.ts src/lib/server/connectors/openclaw.test.ts src/lib/server/gateway/protocol.test.ts src/lib/server/llm-response-cache.test.ts src/lib/server/mcp-conformance.test.ts src/lib/server/openclaw/agent-resolver.test.ts src/lib/server/openclaw/deploy.test.ts src/lib/server/openclaw/skills-normalize.test.ts src/lib/server/session-tools/openclaw-nodes.test.ts src/lib/server/tasks/task-quality-gate.test.ts src/lib/server/tasks/task-validation.test.ts src/lib/server/tool-capability-policy.test.ts src/lib/providers/openclaw-exports.test.ts src/app/api/openclaw/dashboard-url/route.test.ts",
71
71
  "test:mcp:conformance": "node --import tsx ./scripts/mcp-conformance-check.ts",
72
+ "prepack": "npm run build:ci",
72
73
  "postinstall": "node ./scripts/postinstall.mjs"
73
74
  },
74
75
  "dependencies": {
@@ -76,6 +77,7 @@
76
77
  "@langchain/anthropic": "^1.3.18",
77
78
  "@langchain/core": "^1.1.31",
78
79
  "@modelcontextprotocol/sdk": "^1.27.1",
80
+ "@tailwindcss/postcss": "^4",
79
81
  "@langchain/langgraph": "^1.2.2",
80
82
  "@langchain/openai": "^1.2.8",
81
83
  "@multiavatar/multiavatar": "^1.0.7",
@@ -116,14 +118,15 @@
116
118
  "rehype-highlight": "^7.0.2",
117
119
  "remark-gfm": "^4.0.1",
118
120
  "remove-markdown": "^0.6.3",
121
+ "shadcn": "^3.8.5",
119
122
  "sonner": "^2.0.7",
123
+ "tailwindcss": "^4",
120
124
  "tailwind-merge": "^3.4.1",
125
+ "typescript": "^5",
126
+ "tw-animate-css": "^1.4.0",
121
127
  "ws": "^8.19.0",
122
128
  "zod": "^4.3.6",
123
- "zustand": "^5.0.11"
124
- },
125
- "devDependencies": {
126
- "@tailwindcss/postcss": "^4",
129
+ "zustand": "^5.0.11",
127
130
  "@types/better-sqlite3": "^7.6.13",
128
131
  "@types/mailparser": "^3.4.6",
129
132
  "@types/node": "^20",
@@ -131,14 +134,12 @@
131
134
  "@types/qrcode": "^1.5.6",
132
135
  "@types/react": "^19",
133
136
  "@types/react-dom": "^19",
134
- "@types/ws": "^8.18.1",
137
+ "@types/ws": "^8.18.1"
138
+ },
139
+ "devDependencies": {
135
140
  "eslint": "^9",
136
141
  "eslint-config-next": "16.1.6",
137
- "shadcn": "^3.8.5",
138
- "tailwindcss": "^4",
139
- "tsx": "^4.20.6",
140
- "tw-animate-css": "^1.4.0",
141
- "typescript": "^5"
142
+ "tsx": "^4.20.6"
142
143
  },
143
144
  "optionalDependencies": {
144
145
  "botbuilder": "^4.23.3",