@swarmclawai/swarmclaw 1.5.35 → 1.5.37

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 (47) hide show
  1. package/README.md +29 -1
  2. package/package.json +18 -1
  3. package/public/provider-logos/droid-cli.svg +7 -0
  4. package/src/app/api/setup/check-provider/route.ts +4 -2
  5. package/src/app/api/setup/doctor/route.ts +1 -0
  6. package/src/components/agents/agent-sheet.tsx +3 -1
  7. package/src/components/agents/inspector-panel.tsx +1 -0
  8. package/src/components/auth/access-key-gate.tsx +0 -24
  9. package/src/components/chat/activity-moment.tsx +1 -0
  10. package/src/components/chat/chat-header.tsx +2 -1
  11. package/src/components/chat/tool-call-bubble.tsx +5 -0
  12. package/src/components/layout/sidebar-rail.tsx +0 -47
  13. package/src/lib/orchestrator-config.ts +1 -0
  14. package/src/lib/provider-sets.ts +3 -3
  15. package/src/lib/providers/cli-utils.test.ts +2 -0
  16. package/src/lib/providers/cli-utils.ts +28 -1
  17. package/src/lib/providers/droid-cli.ts +220 -0
  18. package/src/lib/providers/index.ts +11 -1
  19. package/src/lib/server/agents/agent-availability.test.ts +1 -1
  20. package/src/lib/server/agents/agent-thread-session.ts +1 -0
  21. package/src/lib/server/agents/task-session.ts +2 -0
  22. package/src/lib/server/capability-router.ts +3 -1
  23. package/src/lib/server/chat-execution/chat-execution-utils.ts +11 -0
  24. package/src/lib/server/chat-execution/chat-turn-finalization.ts +2 -0
  25. package/src/lib/server/chat-execution/chat-turn-tool-routing.ts +1 -0
  26. package/src/lib/server/chat-execution/prompt-sections.ts +2 -0
  27. package/src/lib/server/chatrooms/chatroom-helpers.ts +3 -0
  28. package/src/lib/server/chats/chat-session-service.ts +4 -0
  29. package/src/lib/server/connectors/session.ts +2 -0
  30. package/src/lib/server/context-manager.ts +1 -0
  31. package/src/lib/server/provider-health.ts +4 -2
  32. package/src/lib/server/provider-model-discovery.test.ts +1 -1
  33. package/src/lib/server/provider-model-discovery.ts +1 -1
  34. package/src/lib/server/runtime/daemon-state/core.ts +2 -2
  35. package/src/lib/server/session-reset-policy.ts +2 -0
  36. package/src/lib/server/session-tools/context.ts +2 -2
  37. package/src/lib/server/session-tools/delegate-droid.test.ts +24 -0
  38. package/src/lib/server/session-tools/delegate.ts +105 -12
  39. package/src/lib/server/session-tools/index.ts +3 -2
  40. package/src/lib/server/session-tools/session-info.ts +1 -0
  41. package/src/lib/server/storage-normalization.ts +3 -0
  42. package/src/lib/server/tool-aliases.ts +1 -1
  43. package/src/lib/server/tool-capability-policy.ts +2 -1
  44. package/src/lib/setup-defaults.ts +21 -0
  45. package/src/types/misc.ts +1 -1
  46. package/src/types/provider.ts +1 -1
  47. package/src/types/session.ts +3 -0
package/README.md CHANGED
@@ -40,6 +40,7 @@ Extension tutorial: https://swarmclaw.ai/docs/extension-tutorial
40
40
  <td align="center"><img src="doc/assets/logos/gemini-cli.svg" width="32" alt="Gemini CLI"><br><sub>Gemini CLI</sub></td>
41
41
  <td align="center"><img src="doc/assets/logos/opencode.svg" width="32" alt="OpenCode"><br><sub>OpenCode</sub></td>
42
42
  <td align="center"><img src="doc/assets/logos/copilot-cli.svg" width="32" alt="Copilot CLI"><br><sub>Copilot</sub></td>
43
+ <td align="center"><img src="public/provider-logos/droid-cli.svg" width="32" alt="Factory Droid CLI"><br><sub>Droid</sub></td>
43
44
  <td align="center"><img src="doc/assets/logos/cursor-cli.svg" width="32" alt="Cursor Agent CLI"><br><sub>Cursor</sub></td>
44
45
  <td align="center"><img src="doc/assets/logos/qwen-code-cli.svg" width="32" alt="Qwen Code CLI"><br><sub>Qwen Code</sub></td>
45
46
  <td align="center"><img src="doc/assets/logos/goose.svg" width="32" alt="Goose"><br><sub>Goose</sub></td>
@@ -65,10 +66,23 @@ Extension tutorial: https://swarmclaw.ai/docs/extension-tutorial
65
66
  - Node.js 22.6+ (`nvm use` will pick up the repo's `.nvmrc`, which matches CI)
66
67
  - npm 10+ or another supported package manager
67
68
  - Docker Desktop is recommended for sandbox browser execution
68
- - Optional provider CLIs if you want delegated CLI backends such as Claude Code, Codex, OpenCode, Gemini, Copilot, Cursor Agent, Qwen Code, or Goose
69
+ - Optional provider CLIs if you want delegated CLI backends such as Claude Code, Codex, OpenCode, Gemini, Copilot, Factory Droid, Cursor Agent, Qwen Code, or Goose
69
70
 
70
71
  ## Quick Start
71
72
 
73
+ ### Desktop app (recommended for non-technical users)
74
+
75
+ Download the one-click installer from [swarmclaw.ai/downloads](https://swarmclaw.ai/downloads).
76
+ Available for macOS (Apple Silicon & Intel), Windows, and Linux (AppImage + .deb).
77
+
78
+ Current builds are unsigned, so on first launch:
79
+ - **macOS:** right-click the app in Finder → **Open** → **Open** to bypass Gatekeeper.
80
+ - **Windows:** if SmartScreen appears, click **More info** → **Run anyway**.
81
+ - **Linux (AppImage):** `chmod +x` the downloaded file, then run it.
82
+
83
+ Data lives in your OS app-data directory (`~/Library/Application Support/SwarmClaw`,
84
+ `%APPDATA%\SwarmClaw`, or `~/.config/SwarmClaw`), separate from any CLI or Docker install.
85
+
72
86
  ### Global install
73
87
 
74
88
  ```bash
@@ -375,6 +389,20 @@ Operational docs: https://swarmclaw.ai/docs/observability
375
389
 
376
390
  ## Releases
377
391
 
392
+ ### v1.5.37 Highlights
393
+
394
+ - **Factory Droid CLI as a provider and delegation backend**: adds [`droid`](https://docs.factory.ai/cli/droid-exec/overview) as a first-class chat provider and `delegate` backend with streaming JSON output, session resume, and a conservative `--auto low` autonomy pin on the delegate path. Install `droid` and sign in via browser (or set `FACTORY_API_KEY`), then pick **Factory Droid CLI** in the setup wizard. Resolves #38.
395
+ - **Desktop Release CI hardening**: v1.5.36's Electron build workflow failed on all three platforms. This release:
396
+ - Adds a proper `author` with email to `package.json` and a `linux.maintainer` entry in `electron-builder.yml` so the Linux `.deb` target stops rejecting the build.
397
+ - Pins `outputFileTracingRoot` in `next.config.ts` to the project root so the Next.js build no longer walks `C:\Users\<user>\Application Data` (a legacy NTFS junction that throws EPERM on Windows runners).
398
+ - Pins Python 3.11 in the desktop-release workflow so `node-gyp` rebuilds of native modules (`node-liblzma`, etc.) succeed on Python 3.12+ runners where `distutils` was removed from the stdlib.
399
+
400
+ ### v1.5.36 Highlights
401
+
402
+ - **Desktop app (Electron)**: SwarmClaw now ships as a native desktop app for macOS (Apple Silicon + Intel), Windows, and Linux (AppImage + .deb). Download from [swarmclaw.ai/downloads](https://swarmclaw.ai/downloads). The app wraps the existing standalone server inside an Electron shell, stores data in the OS app-data directory, and auto-updates via GitHub Releases (notify-only on unsigned macOS builds).
403
+ - **Desktop release CI**: new `desktop-release.yml` workflow builds and publishes installers for all three platforms to GitHub Releases on every version tag.
404
+ - **UI cleanup**: removed sibling-product navigation links from the in-app sidebar rail and login gate so the open-source app focuses on SwarmClaw itself. Those links remain in the project README and on swarmclaw.ai.
405
+
378
406
  ### v1.5.35 Highlights
379
407
 
380
408
  - **Update safety: prevent DB corruption on Linux**: `npm run update:easy`, `swarmclaw update`, and the in-app update endpoint now stop the running server (or checkpoint the SQLite WAL) before rebuilding native modules, preventing the WAL journal corruption that forced some Linux users back to the setup wizard.
package/package.json CHANGED
@@ -1,8 +1,14 @@
1
1
  {
2
2
  "name": "@swarmclawai/swarmclaw",
3
- "version": "1.5.35",
3
+ "version": "1.5.37",
4
4
  "description": "Build and run autonomous AI agents with OpenClaw, Hermes, multiple model providers, orchestration, delegation, memory, skills, schedules, and chat connectors.",
5
+ "main": "electron-dist/main.js",
5
6
  "license": "MIT",
7
+ "author": {
8
+ "name": "SwarmClaw",
9
+ "email": "noreply@swarmclaw.ai",
10
+ "url": "https://swarmclaw.ai"
11
+ },
6
12
  "publishConfig": {
7
13
  "access": "public",
8
14
  "registry": "https://registry.npmjs.org/"
@@ -78,6 +84,13 @@
78
84
  "test:builder": "tsx --test src/features/protocols/builder/utils/nodes-to-template.test.ts src/features/protocols/builder/utils/template-to-nodes.test.ts src/features/protocols/builder/validators/dag-validator.test.ts",
79
85
  "test:e2e": "tsx .workbench/browser-e2e/run.ts",
80
86
  "test:mcp:conformance": "node --import tsx ./scripts/mcp-conformance-check.ts",
87
+ "electron:compile": "tsc -p electron/tsconfig.json",
88
+ "electron:dev": "npm run electron:compile && electron electron-dist/main.js",
89
+ "electron:build": "node ./scripts/build-electron.mjs",
90
+ "electron:build:mac": "node ./scripts/build-electron.mjs --mac",
91
+ "electron:build:win": "node ./scripts/build-electron.mjs --win",
92
+ "electron:build:linux": "node ./scripts/build-electron.mjs --linux",
93
+ "electron:build:publish": "node ./scripts/build-electron.mjs --publish",
81
94
  "prepack": "npm run build:ci",
82
95
  "postinstall": "node ./scripts/postinstall.mjs"
83
96
  },
@@ -114,6 +127,7 @@
114
127
  "class-variance-authority": "^0.7.1",
115
128
  "clsx": "^2.1.1",
116
129
  "commander": "^13.1.0",
130
+ "electron-updater": "^6.3.9",
117
131
  "cron-parser": "^5.5.0",
118
132
  "cronstrue": "^3.12.0",
119
133
  "dagre": "^0.8.5",
@@ -156,7 +170,10 @@
156
170
  "zustand": "^5.0.11"
157
171
  },
158
172
  "devDependencies": {
173
+ "@electron/rebuild": "^3.7.2",
159
174
  "@types/dagre": "^0.7.54",
175
+ "electron": "^33.3.0",
176
+ "electron-builder": "^25.1.8",
160
177
  "eslint": "^9",
161
178
  "eslint-config-next": "16.1.7"
162
179
  },
@@ -0,0 +1,7 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64" role="img" aria-label="Factory Droid CLI">
2
+ <rect x="2" y="2" width="60" height="60" rx="14" fill="#0f172a"/>
3
+ <rect x="2" y="2" width="60" height="60" rx="14" fill="none" stroke="#38bdf8" stroke-width="2"/>
4
+ <path d="M20 22 h24 v6 h-9 v16 h-6 v-16 h-9 z" fill="#e2e8f0"/>
5
+ <circle cx="16" cy="48" r="2.5" fill="#38bdf8"/>
6
+ <circle cx="48" cy="48" r="2.5" fill="#38bdf8"/>
7
+ </svg>
@@ -12,6 +12,7 @@ type SetupProvider =
12
12
  | 'opencode-cli'
13
13
  | 'gemini-cli'
14
14
  | 'copilot-cli'
15
+ | 'droid-cli'
15
16
  | 'cursor-cli'
16
17
  | 'qwen-code-cli'
17
18
  | 'goose'
@@ -31,7 +32,7 @@ type SetupProvider =
31
32
  | 'openclaw'
32
33
  | 'hermes'
33
34
 
34
- type CliSetupProvider = 'claude-cli' | 'codex-cli' | 'opencode-cli' | 'gemini-cli' | 'copilot-cli' | 'cursor-cli' | 'qwen-code-cli' | 'goose'
35
+ type CliSetupProvider = 'claude-cli' | 'codex-cli' | 'opencode-cli' | 'gemini-cli' | 'copilot-cli' | 'droid-cli' | 'cursor-cli' | 'qwen-code-cli' | 'goose'
35
36
 
36
37
  interface SetupCheckBody {
37
38
  provider?: string
@@ -285,6 +286,7 @@ function checkCliProvider(provider: CliSetupProvider): { ok: boolean; message: s
285
286
  'opencode-cli': { binary: 'opencode', backend: 'opencode' as const, label: 'OpenCode CLI' },
286
287
  'gemini-cli': { binary: 'gemini', backend: 'gemini' as const, label: 'Gemini CLI' },
287
288
  'copilot-cli': { binary: 'copilot', backend: 'copilot' as const, label: 'GitHub Copilot CLI' },
289
+ 'droid-cli': { binary: 'droid', backend: 'droid' as const, label: 'Factory Droid CLI' },
288
290
  'cursor-cli': { binary: 'cursor-agent', backend: 'cursor' as const, label: 'Cursor Agent CLI' },
289
291
  'qwen-code-cli': { binary: 'qwen', backend: 'qwen' as const, label: 'Qwen Code CLI' },
290
292
  goose: { binary: 'goose', backend: 'goose' as const, label: 'Goose CLI' },
@@ -312,7 +314,7 @@ export async function POST(req: Request) {
312
314
  const credentialId = clean(body.credentialId)
313
315
  const endpoint = clean(body.endpoint)
314
316
  const model = clean(body.model)
315
- const CLI_PROVIDERS = new Set<CliSetupProvider>(['claude-cli', 'codex-cli', 'opencode-cli', 'gemini-cli', 'copilot-cli', 'cursor-cli', 'qwen-code-cli', 'goose'])
317
+ const CLI_PROVIDERS = new Set<CliSetupProvider>(['claude-cli', 'codex-cli', 'opencode-cli', 'gemini-cli', 'copilot-cli', 'droid-cli', 'cursor-cli', 'qwen-code-cli', 'goose'])
316
318
 
317
319
  // Resolve credentialId to an API key if no raw key was provided
318
320
  if (!apiKey && credentialId) {
@@ -203,6 +203,7 @@ export async function GET(req: Request) {
203
203
  { id: 'opencode-cli', label: 'OpenCode CLI', command: 'opencode' },
204
204
  { id: 'gemini-cli', label: 'Gemini CLI', command: 'gemini' },
205
205
  { id: 'copilot-cli', label: 'GitHub Copilot CLI', command: 'copilot' },
206
+ { id: 'droid-cli', label: 'Factory Droid CLI', command: 'droid' },
206
207
  { id: 'cursor-cli', label: 'Cursor Agent CLI', command: 'cursor-agent' },
207
208
  { id: 'qwen-code-cli', label: 'Qwen Code CLI', command: 'qwen' },
208
209
  { id: 'goose', label: 'Goose CLI', command: 'goose' },
@@ -2513,7 +2513,9 @@ export function AgentSheet() {
2513
2513
  ? 'Gemini CLI uses its own built-in tools and runtime — SwarmClaw does not inject local platform tools for it.'
2514
2514
  : provider === 'copilot-cli'
2515
2515
  ? 'GitHub Copilot CLI uses its own built-in tools and runtime — SwarmClaw does not inject local platform tools for it.'
2516
- : provider === 'cursor-cli'
2516
+ : provider === 'droid-cli'
2517
+ ? 'Factory Droid CLI uses its own built-in tools and autonomy controls — SwarmClaw does not inject local platform tools for it.'
2518
+ : provider === 'cursor-cli'
2517
2519
  ? 'Cursor Agent CLI runs with its own native tool/runtime layer — SwarmClaw sends prompts directly without injecting local platform tools.'
2518
2520
  : provider === 'qwen-code-cli'
2519
2521
  ? 'Qwen Code CLI uses its own native tools and runtime — SwarmClaw does not inject local platform tools for it.'
@@ -53,6 +53,7 @@ const PROVIDER_LABELS: Record<string, string> = {
53
53
  'opencode-cli': 'OpenCode CLI',
54
54
  'gemini-cli': 'Gemini CLI',
55
55
  'copilot-cli': 'Copilot CLI',
56
+ 'droid-cli': 'Droid CLI',
56
57
  'cursor-cli': 'Cursor CLI',
57
58
  'qwen-code-cli': 'Qwen Code CLI',
58
59
  goose: 'Goose',
@@ -9,11 +9,6 @@ interface AccessKeyGateProps {
9
9
  }
10
10
 
11
11
  const AUTH_CHECK_TIMEOUT_MS = 8_000
12
- const NETWORK_LINKS = [
13
- { href: 'https://www.swarmdock.ai', label: 'SwarmDock' },
14
- { href: 'https://swarmrecall.ai', label: 'SwarmRecall' },
15
- { href: 'https://swarmrelay.ai', label: 'SwarmRelay' },
16
- ]
17
12
 
18
13
  function isExpectedAuthCheckError(err: unknown): boolean {
19
14
  return isAbortError(err) || isTimeoutError(err)
@@ -427,25 +422,6 @@ export function AccessKeyGate({ onAuthenticated }: AccessKeyGateProps) {
427
422
  </>
428
423
  )}
429
424
 
430
- <div className="mt-10 border-t border-white/[0.06] pt-5" style={{ animation: 'fade-up 0.6s var(--ease-spring) 0.35s both' }}>
431
- <p className="text-[10px] font-700 uppercase tracking-[0.18em] text-text-3/55">
432
- Network
433
- </p>
434
- <div className="mt-3 flex flex-wrap items-center justify-center gap-2.5">
435
- {NETWORK_LINKS.map((link) => (
436
- <a
437
- key={link.href}
438
- href={link.href}
439
- target="_blank"
440
- rel="noopener noreferrer"
441
- className="rounded-full border border-white/[0.08] bg-white/[0.03] px-3 py-1.5 text-[12px] text-text-3
442
- no-underline transition-all duration-200 hover:border-white/[0.14] hover:bg-white/[0.06] hover:text-text"
443
- >
444
- {link.label}
445
- </a>
446
- ))}
447
- </div>
448
- </div>
449
425
  </div>
450
426
  </div>
451
427
  )
@@ -14,6 +14,7 @@ const NOTABLE_TOOLS: Record<string, { label: string; color: string; icon: 'brain
14
14
  delegate_to_opencode_cli: { label: 'Delegated to OpenCode', color: '#38BDF8', icon: 'delegate' },
15
15
  delegate_to_gemini_cli: { label: 'Delegated to Gemini CLI', color: '#38BDF8', icon: 'delegate' },
16
16
  delegate_to_cursor_cli: { label: 'Delegated to Cursor CLI', color: '#38BDF8', icon: 'delegate' },
17
+ delegate_to_droid_cli: { label: 'Delegated to Factory Droid', color: '#38BDF8', icon: 'delegate' },
17
18
  delegate_to_qwen_code_cli: { label: 'Delegated to Qwen Code', color: '#38BDF8', icon: 'delegate' },
18
19
  delegate_to_agent: { label: 'Delegating task', color: '#6366F1', icon: 'delegate' },
19
20
  check_delegation_status: { label: 'Checking delegation', color: '#6366F1', icon: 'delegate' },
@@ -220,10 +220,11 @@ export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, m
220
220
  opencodeSessionId: null,
221
221
  geminiSessionId: null,
222
222
  copilotSessionId: null,
223
+ droidSessionId: null,
223
224
  cursorSessionId: null,
224
225
  qwenSessionId: null,
225
226
  acpSessionId: null,
226
- delegateResumeIds: { claudeCode: null, codex: null, opencode: null, gemini: null, copilot: null, cursor: null, qwen: null },
227
+ delegateResumeIds: { claudeCode: null, codex: null, opencode: null, gemini: null, copilot: null, droid: null, cursor: null, qwen: null },
227
228
  })
228
229
  await refreshSession(session.id)
229
230
  } catch { /* best-effort */ }
@@ -28,6 +28,7 @@ const TOOL_COLORS: Record<string, string> = {
28
28
  delegate_to_opencode_cli: '#14B8A6',
29
29
  delegate_to_gemini_cli: '#2563EB',
30
30
  delegate_to_copilot_cli: '#6366F1',
31
+ delegate_to_droid_cli: '#A855F7',
31
32
  delegate_to_cursor_cli: '#F97316',
32
33
  delegate_to_qwen_code_cli: '#22C55E',
33
34
  whoami_tool: '#8B5CF6',
@@ -81,6 +82,7 @@ export const TOOL_LABELS: Record<string, string> = {
81
82
  opencode_cli: 'OpenCode CLI',
82
83
  gemini_cli: 'Gemini CLI',
83
84
  copilot_cli: 'Copilot CLI',
85
+ droid_cli: 'Factory Droid',
84
86
  cursor_cli: 'Cursor CLI',
85
87
  qwen_code_cli: 'Qwen Code CLI',
86
88
  spawn_subagent: 'Subagent',
@@ -91,6 +93,7 @@ export const TOOL_LABELS: Record<string, string> = {
91
93
  delegate_to_opencode_cli: 'OpenCode CLI',
92
94
  delegate_to_gemini_cli: 'Gemini CLI',
93
95
  delegate_to_copilot_cli: 'Copilot CLI',
96
+ delegate_to_droid_cli: 'Factory Droid',
94
97
  delegate_to_cursor_cli: 'Cursor CLI',
95
98
  delegate_to_qwen_code_cli: 'Qwen Code CLI',
96
99
  whoami_tool: 'Who Am I',
@@ -129,6 +132,7 @@ export const TOOL_DESCRIPTIONS: Record<string, string> = {
129
132
  opencode_cli: 'Enable delegation to OpenCode CLI',
130
133
  gemini_cli: 'Enable delegation to Gemini CLI',
131
134
  copilot_cli: 'Enable delegation to GitHub Copilot CLI',
135
+ droid_cli: 'Enable delegation to Factory Droid CLI',
132
136
  cursor_cli: 'Enable delegation to Cursor Agent CLI',
133
137
  qwen_code_cli: 'Enable delegation to Qwen Code CLI',
134
138
  spawn_subagent: 'Spawn native subagents with lineage tracking and batch support',
@@ -139,6 +143,7 @@ export const TOOL_DESCRIPTIONS: Record<string, string> = {
139
143
  delegate_to_opencode_cli: 'Delegate complex coding tasks to OpenCode CLI',
140
144
  delegate_to_gemini_cli: 'Delegate complex coding tasks to Gemini CLI',
141
145
  delegate_to_copilot_cli: 'Delegate complex coding tasks to GitHub Copilot CLI',
146
+ delegate_to_droid_cli: 'Delegate complex coding tasks to Factory Droid CLI',
142
147
  delegate_to_cursor_cli: 'Delegate complex coding tasks to Cursor Agent CLI',
143
148
  delegate_to_qwen_code_cli: 'Delegate complex coding tasks to Qwen Code CLI',
144
149
  whoami_tool: 'Reveal the current agent and chat context',
@@ -17,11 +17,6 @@ import type { AppView } from '@/types'
17
17
  const RAIL_EXPANDED_KEY = 'sc_rail_expanded'
18
18
  const GITHUB_REPO_URL = 'https://github.com/swarmclawai/swarmclaw'
19
19
  const DISCORD_URL = 'https://discord.gg/sbEavS8cPV'
20
- const NETWORK_LINKS = [
21
- { href: 'https://www.swarmdock.ai', label: 'SwarmDock', abbr: 'DO' },
22
- { href: 'https://swarmrecall.ai', label: 'SwarmRecall', abbr: 'RE' },
23
- { href: 'https://swarmrelay.ai', label: 'SwarmRelay', abbr: 'RL' },
24
- ]
25
20
 
26
21
  export function SidebarRail({
27
22
  onSwitchUser,
@@ -394,48 +389,6 @@ export function SidebarRail({
394
389
 
395
390
  {/* Bottom: Docs + Daemon + Settings + User */}
396
391
  <div className={`flex flex-col gap-1 ${railExpanded ? 'px-3' : 'items-center'}`}>
397
- {railExpanded ? (
398
- <div className="mb-1">
399
- <div className="px-3 pb-1 text-[10px] font-700 uppercase tracking-[0.12em] text-text-3/45">Network</div>
400
- <div className="flex flex-col gap-1">
401
- {NETWORK_LINKS.map((link) => (
402
- <a
403
- key={link.href}
404
- href={link.href}
405
- target="_blank"
406
- rel="noopener noreferrer"
407
- className="w-full flex items-center gap-2.5 px-3 py-2 rounded-[10px] text-[13px] font-500 cursor-pointer transition-all
408
- bg-transparent text-text-3 hover:text-text hover:bg-white/[0.04] no-underline"
409
- style={{ fontFamily: 'inherit' }}
410
- >
411
- <span className="flex h-5 w-5 shrink-0 items-center justify-center rounded-[6px] border border-white/[0.08] bg-white/[0.03] text-[10px] font-700 uppercase tracking-[0.08em] text-text-3/80">
412
- {link.abbr}
413
- </span>
414
- {link.label}
415
- <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" className="ml-auto opacity-40">
416
- <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" /><polyline points="15 3 21 3 21 9" /><line x1="10" y1="14" x2="21" y2="3" />
417
- </svg>
418
- </a>
419
- ))}
420
- </div>
421
- </div>
422
- ) : (
423
- <>
424
- <div className="my-1 h-px w-6 bg-white/[0.06]" />
425
- {NETWORK_LINKS.map((link) => (
426
- <RailTooltip key={link.href} label={link.label} description="Open product site in a new tab">
427
- <a
428
- href={link.href}
429
- target="_blank"
430
- rel="noopener noreferrer"
431
- className="rail-btn text-[10px] font-700 uppercase tracking-[0.08em] no-underline"
432
- >
433
- {link.abbr}
434
- </a>
435
- </RailTooltip>
436
- ))}
437
- </>
438
- )}
439
392
  {railExpanded ? (
440
393
  <a
441
394
  href="https://swarmclaw.ai/docs"
@@ -6,6 +6,7 @@ export const NON_ORCHESTRATOR_PROVIDERS = new Set([
6
6
  'opencode-cli',
7
7
  'gemini-cli',
8
8
  'copilot-cli',
9
+ 'droid-cli',
9
10
  'cursor-cli',
10
11
  'qwen-code-cli',
11
12
  'goose',
@@ -1,11 +1,11 @@
1
1
  /** CLI providers that use their own tool execution outside the shared tool-runtime path. */
2
- export const NON_LANGGRAPH_PROVIDER_IDS = new Set(['claude-cli', 'codex-cli', 'opencode-cli', 'gemini-cli', 'copilot-cli', 'cursor-cli', 'qwen-code-cli'])
2
+ export const NON_LANGGRAPH_PROVIDER_IDS = new Set(['claude-cli', 'codex-cli', 'opencode-cli', 'gemini-cli', 'copilot-cli', 'droid-cli', 'cursor-cli', 'qwen-code-cli'])
3
3
 
4
4
  /** Providers that manage their own runtime/tool loop even when reached over an API endpoint. */
5
5
  export const RUNTIME_MANAGED_PROVIDER_IDS = new Set(['hermes', 'goose'])
6
6
 
7
7
  /** Providers with native tool/capability support (CLI providers + OpenClaw + Hermes). */
8
- export const NATIVE_CAPABILITY_PROVIDER_IDS = new Set(['claude-cli', 'codex-cli', 'opencode-cli', 'gemini-cli', 'copilot-cli', 'cursor-cli', 'qwen-code-cli', 'goose', 'openclaw', 'hermes'])
8
+ export const NATIVE_CAPABILITY_PROVIDER_IDS = new Set(['claude-cli', 'codex-cli', 'opencode-cli', 'gemini-cli', 'copilot-cli', 'droid-cli', 'cursor-cli', 'qwen-code-cli', 'goose', 'openclaw', 'hermes'])
9
9
 
10
10
  /** Providers that can only act as workers — no coordinator role, no heartbeat, no advanced settings. */
11
- export const WORKER_ONLY_PROVIDER_IDS = new Set(['claude-cli', 'codex-cli', 'opencode-cli', 'gemini-cli', 'copilot-cli', 'cursor-cli', 'qwen-code-cli', 'goose', 'openclaw', 'hermes'])
11
+ export const WORKER_ONLY_PROVIDER_IDS = new Set(['claude-cli', 'codex-cli', 'opencode-cli', 'gemini-cli', 'copilot-cli', 'droid-cli', 'cursor-cli', 'qwen-code-cli', 'goose', 'openclaw', 'hermes'])
@@ -94,6 +94,7 @@ describe('isCliProvider', () => {
94
94
  assert.equal(isCliProvider('opencode-cli'), true)
95
95
  assert.equal(isCliProvider('gemini-cli'), true)
96
96
  assert.equal(isCliProvider('copilot-cli'), true)
97
+ assert.equal(isCliProvider('droid-cli'), true)
97
98
  assert.equal(isCliProvider('cursor-cli'), true)
98
99
  assert.equal(isCliProvider('qwen-code-cli'), true)
99
100
  assert.equal(isCliProvider('goose'), true)
@@ -118,6 +119,7 @@ describe('CLI_PROVIDER_CAPABILITIES', () => {
118
119
  assert.ok('opencode-cli' in CLI_PROVIDER_CAPABILITIES)
119
120
  assert.ok('gemini-cli' in CLI_PROVIDER_CAPABILITIES)
120
121
  assert.ok('copilot-cli' in CLI_PROVIDER_CAPABILITIES)
122
+ assert.ok('droid-cli' in CLI_PROVIDER_CAPABILITIES)
121
123
  assert.ok('cursor-cli' in CLI_PROVIDER_CAPABILITIES)
122
124
  assert.ok('qwen-code-cli' in CLI_PROVIDER_CAPABILITIES)
123
125
  assert.ok('goose' in CLI_PROVIDER_CAPABILITIES)
@@ -45,6 +45,13 @@ const KNOWN_BINARY_PATHS: Record<string, string[]> = {
45
45
  '/opt/homebrew/bin/copilot',
46
46
  path.join(os.homedir(), '.npm-global/bin/copilot'),
47
47
  ],
48
+ droid: [
49
+ path.join(os.homedir(), '.local/bin/droid'),
50
+ '/usr/local/bin/droid',
51
+ '/opt/homebrew/bin/droid',
52
+ path.join(os.homedir(), '.npm-global/bin/droid'),
53
+ path.join(os.homedir(), '.factory/bin/droid'),
54
+ ],
48
55
  'cursor-agent': [
49
56
  path.join(os.homedir(), '.local/bin/cursor-agent'),
50
57
  '/usr/local/bin/cursor-agent',
@@ -166,7 +173,7 @@ export interface AuthProbeResult {
166
173
  */
167
174
  export function probeCliAuth(
168
175
  binary: string,
169
- backend: 'claude' | 'codex' | 'opencode' | 'gemini' | 'copilot' | 'cursor' | 'qwen' | 'goose',
176
+ backend: 'claude' | 'codex' | 'opencode' | 'gemini' | 'copilot' | 'droid' | 'cursor' | 'qwen' | 'goose',
170
177
  env: NodeJS.ProcessEnv,
171
178
  cwd?: string,
172
179
  ): AuthProbeResult {
@@ -277,6 +284,25 @@ export function probeCliAuth(
277
284
  return { authenticated: true }
278
285
  }
279
286
 
287
+ if (backend === 'droid') {
288
+ if (process.env.FACTORY_API_KEY || env.FACTORY_API_KEY) {
289
+ return { authenticated: true }
290
+ }
291
+ const configPaths = [
292
+ path.join(os.homedir(), '.factory', 'config.json'),
293
+ path.join(os.homedir(), '.config', 'factory', 'config.json'),
294
+ path.join(os.homedir(), '.factory', 'auth.json'),
295
+ ]
296
+ const hasConfig = configPaths.some((p) => fs.existsSync(p))
297
+ if (!hasConfig) {
298
+ return {
299
+ authenticated: false,
300
+ errorMessage: 'Factory Droid CLI is not authenticated. Run `droid` once to sign in via browser, or set FACTORY_API_KEY and try again.',
301
+ }
302
+ }
303
+ return { authenticated: true }
304
+ }
305
+
280
306
  if (backend === 'cursor') {
281
307
  try {
282
308
  const probe = spawnSync(binary, ['status'], {
@@ -427,6 +453,7 @@ export const CLI_PROVIDER_CAPABILITIES: Record<string, string> = {
427
453
  'opencode-cli': 'code analysis, generation across multiple LLM backends',
428
454
  'gemini-cli': 'code generation, analysis with Gemini models',
429
455
  'copilot-cli': 'code generation, analysis, multi-model support via GitHub Copilot',
456
+ 'droid-cli': 'code generation, refactoring, and automation via Factory Droid with configurable autonomy',
430
457
  'cursor-cli': 'full-agent coding workflows, multi-file edits, project-aware code changes',
431
458
  'qwen-code-cli': 'terminal-native coding workflows, code generation, review, and automation',
432
459
  goose: 'agentic coding workflows with extensions, tools, and runtime-managed execution',