@swarmclawai/swarmclaw 1.5.36 → 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.
- package/README.md +10 -1
- package/package.json +6 -1
- package/public/provider-logos/droid-cli.svg +7 -0
- package/src/app/api/setup/check-provider/route.ts +4 -2
- package/src/app/api/setup/doctor/route.ts +1 -0
- package/src/components/agents/agent-sheet.tsx +3 -1
- package/src/components/agents/inspector-panel.tsx +1 -0
- package/src/components/chat/activity-moment.tsx +1 -0
- package/src/components/chat/chat-header.tsx +2 -1
- package/src/components/chat/tool-call-bubble.tsx +5 -0
- package/src/lib/orchestrator-config.ts +1 -0
- package/src/lib/provider-sets.ts +3 -3
- package/src/lib/providers/cli-utils.test.ts +2 -0
- package/src/lib/providers/cli-utils.ts +28 -1
- package/src/lib/providers/droid-cli.ts +220 -0
- package/src/lib/providers/index.ts +11 -1
- package/src/lib/server/agents/agent-availability.test.ts +1 -1
- package/src/lib/server/agents/agent-thread-session.ts +1 -0
- package/src/lib/server/agents/task-session.ts +2 -0
- package/src/lib/server/capability-router.ts +3 -1
- package/src/lib/server/chat-execution/chat-execution-utils.ts +11 -0
- package/src/lib/server/chat-execution/chat-turn-finalization.ts +2 -0
- package/src/lib/server/chat-execution/chat-turn-tool-routing.ts +1 -0
- package/src/lib/server/chat-execution/prompt-sections.ts +2 -0
- package/src/lib/server/chatrooms/chatroom-helpers.ts +3 -0
- package/src/lib/server/chats/chat-session-service.ts +4 -0
- package/src/lib/server/connectors/session.ts +2 -0
- package/src/lib/server/context-manager.ts +1 -0
- package/src/lib/server/provider-health.ts +4 -2
- package/src/lib/server/provider-model-discovery.test.ts +1 -1
- package/src/lib/server/provider-model-discovery.ts +1 -1
- package/src/lib/server/runtime/daemon-state/core.ts +2 -2
- package/src/lib/server/session-reset-policy.ts +2 -0
- package/src/lib/server/session-tools/context.ts +2 -2
- package/src/lib/server/session-tools/delegate-droid.test.ts +24 -0
- package/src/lib/server/session-tools/delegate.ts +105 -12
- package/src/lib/server/session-tools/index.ts +3 -2
- package/src/lib/server/session-tools/session-info.ts +1 -0
- package/src/lib/server/storage-normalization.ts +3 -0
- package/src/lib/server/tool-aliases.ts +1 -1
- package/src/lib/server/tool-capability-policy.ts +2 -1
- package/src/lib/setup-defaults.ts +21 -0
- package/src/types/misc.ts +1 -1
- package/src/types/provider.ts +1 -1
- 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,7 +66,7 @@ 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
|
|
|
@@ -388,6 +389,14 @@ Operational docs: https://swarmclaw.ai/docs/observability
|
|
|
388
389
|
|
|
389
390
|
## Releases
|
|
390
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
|
+
|
|
391
400
|
### v1.5.36 Highlights
|
|
392
401
|
|
|
393
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).
|
package/package.json
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@swarmclawai/swarmclaw",
|
|
3
|
-
"version": "1.5.
|
|
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
5
|
"main": "electron-dist/main.js",
|
|
6
6
|
"license": "MIT",
|
|
7
|
+
"author": {
|
|
8
|
+
"name": "SwarmClaw",
|
|
9
|
+
"email": "noreply@swarmclaw.ai",
|
|
10
|
+
"url": "https://swarmclaw.ai"
|
|
11
|
+
},
|
|
7
12
|
"publishConfig": {
|
|
8
13
|
"access": "public",
|
|
9
14
|
"registry": "https://registry.npmjs.org/"
|
|
@@ -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 === '
|
|
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',
|
|
@@ -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',
|
package/src/lib/provider-sets.ts
CHANGED
|
@@ -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',
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import fs from 'fs'
|
|
2
|
+
import os from 'os'
|
|
3
|
+
import path from 'path'
|
|
4
|
+
import { spawn } from 'child_process'
|
|
5
|
+
import type { StreamChatOptions } from './index'
|
|
6
|
+
import { log } from '../server/logger'
|
|
7
|
+
import { loadRuntimeSettings } from '@/lib/server/runtime/runtime-settings'
|
|
8
|
+
import { resolveCliBinary, buildCliEnv, probeCliAuth, attachAbortHandler, symlinkConfigFiles, isStderrNoise } from './cli-utils'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Factory Droid CLI provider — spawns `droid exec <message> --output-format stream-json`.
|
|
12
|
+
* Tracks `session.droidSessionId` from streamed events to support multi-turn continuity.
|
|
13
|
+
*/
|
|
14
|
+
export function streamDroidCliChat({ session, message, imagePath, systemPrompt, write, active, signal }: StreamChatOptions): Promise<string> {
|
|
15
|
+
const processTimeoutMs = loadRuntimeSettings().cliProcessTimeoutMs
|
|
16
|
+
const binary = resolveCliBinary('droid')
|
|
17
|
+
if (!binary) {
|
|
18
|
+
const msg = 'Factory Droid CLI not found. Install it (brew install --cask droid, npm i -g droid, or https://docs.factory.ai/cli/getting-started/quickstart) and ensure it is on your PATH.'
|
|
19
|
+
write(`data: ${JSON.stringify({ t: 'err', text: msg })}\n\n`)
|
|
20
|
+
return Promise.resolve('')
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const env = buildCliEnv()
|
|
24
|
+
|
|
25
|
+
if (session.apiKey) {
|
|
26
|
+
env.FACTORY_API_KEY = session.apiKey
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (!session.apiKey) {
|
|
30
|
+
const auth = probeCliAuth(binary, 'droid', env, session.cwd)
|
|
31
|
+
if (!auth.authenticated) {
|
|
32
|
+
log.error('droid-cli', auth.errorMessage || 'Auth failed')
|
|
33
|
+
write(`data: ${JSON.stringify({ t: 'err', text: auth.errorMessage || 'Factory Droid CLI is not authenticated.' })}\n\n`)
|
|
34
|
+
return Promise.resolve('')
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const promptParts: string[] = []
|
|
39
|
+
if (imagePath) {
|
|
40
|
+
promptParts.push(`[The user has shared an image at: ${imagePath}]`)
|
|
41
|
+
}
|
|
42
|
+
promptParts.push(message)
|
|
43
|
+
const prompt = promptParts.join('\n\n')
|
|
44
|
+
|
|
45
|
+
const args = ['exec', prompt, '--output-format', 'stream-json']
|
|
46
|
+
if (session.droidSessionId) args.push('-s', session.droidSessionId)
|
|
47
|
+
if (session.model) args.push('-m', session.model)
|
|
48
|
+
|
|
49
|
+
let tempFactoryHome: string | null = null
|
|
50
|
+
if (systemPrompt && !session.droidSessionId) {
|
|
51
|
+
const realFactoryHome = process.env.FACTORY_HOME || path.join(os.homedir(), '.factory')
|
|
52
|
+
tempFactoryHome = path.join(os.tmpdir(), `swarmclaw-droid-${session.id}`)
|
|
53
|
+
fs.mkdirSync(tempFactoryHome, { recursive: true })
|
|
54
|
+
symlinkConfigFiles(realFactoryHome, tempFactoryHome)
|
|
55
|
+
fs.writeFileSync(path.join(tempFactoryHome, 'AGENTS.override.md'), systemPrompt)
|
|
56
|
+
env.FACTORY_HOME = tempFactoryHome
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
log.info('droid-cli', `Spawning: ${binary}`, {
|
|
60
|
+
args: args.map((a) => a.length > 100 ? a.slice(0, 100) + '...' : a),
|
|
61
|
+
cwd: session.cwd,
|
|
62
|
+
promptLen: prompt.length,
|
|
63
|
+
hasSystemPrompt: !!systemPrompt,
|
|
64
|
+
resumeSessionId: session.droidSessionId || null,
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
const proc = spawn(binary, args, {
|
|
68
|
+
cwd: session.cwd,
|
|
69
|
+
env,
|
|
70
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
71
|
+
timeout: processTimeoutMs,
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
log.info('droid-cli', `Process spawned: pid=${proc.pid}`)
|
|
75
|
+
active.set(session.id, proc)
|
|
76
|
+
attachAbortHandler(proc, signal)
|
|
77
|
+
|
|
78
|
+
let fullResponse = ''
|
|
79
|
+
let buf = ''
|
|
80
|
+
let eventCount = 0
|
|
81
|
+
let stderrText = ''
|
|
82
|
+
|
|
83
|
+
proc.stdout!.on('data', (chunk: Buffer) => {
|
|
84
|
+
const raw = chunk.toString()
|
|
85
|
+
buf += raw
|
|
86
|
+
|
|
87
|
+
if (eventCount === 0) {
|
|
88
|
+
log.debug('droid-cli', `First stdout chunk (${raw.length} bytes)`, raw.slice(0, 500))
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const lines = buf.split('\n')
|
|
92
|
+
buf = lines.pop()!
|
|
93
|
+
|
|
94
|
+
for (const line of lines) {
|
|
95
|
+
if (!line.trim()) continue
|
|
96
|
+
try {
|
|
97
|
+
const ev = JSON.parse(line) as Record<string, unknown>
|
|
98
|
+
eventCount++
|
|
99
|
+
|
|
100
|
+
const data = ev.data as Record<string, unknown> | undefined
|
|
101
|
+
|
|
102
|
+
if (typeof ev.session_id === 'string') {
|
|
103
|
+
session.droidSessionId = ev.session_id
|
|
104
|
+
} else if (typeof ev.sessionId === 'string') {
|
|
105
|
+
session.droidSessionId = ev.sessionId
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (ev.type === 'assistant.message_delta' && typeof data?.deltaContent === 'string') {
|
|
109
|
+
fullResponse += data.deltaContent
|
|
110
|
+
write(`data: ${JSON.stringify({ t: 'd', text: data.deltaContent })}\n\n`)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
else if (ev.type === 'assistant.message' && typeof data?.content === 'string') {
|
|
114
|
+
if (!fullResponse) {
|
|
115
|
+
fullResponse = data.content
|
|
116
|
+
write(`data: ${JSON.stringify({ t: 'r', text: data.content })}\n\n`)
|
|
117
|
+
}
|
|
118
|
+
log.debug('droid-cli', `Assistant message (${data.content.length} chars)`)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
else if (ev.type === 'content_block_delta') {
|
|
122
|
+
const delta = ev.delta as Record<string, unknown> | undefined
|
|
123
|
+
if (typeof delta?.text === 'string') {
|
|
124
|
+
fullResponse += delta.text
|
|
125
|
+
write(`data: ${JSON.stringify({ t: 'd', text: delta.text })}\n\n`)
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
else if (ev.type === 'agent_message_chunk' && typeof ev.text === 'string') {
|
|
130
|
+
fullResponse += ev.text
|
|
131
|
+
write(`data: ${JSON.stringify({ t: 'd', text: ev.text })}\n\n`)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
else if (ev.type === 'message' && ev.role === 'assistant' && typeof ev.content === 'string') {
|
|
135
|
+
fullResponse += ev.content
|
|
136
|
+
write(`data: ${JSON.stringify({ t: 'd', text: ev.content })}\n\n`)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
else if (ev.type === 'item.completed' && (ev.item as Record<string, unknown>)?.type === 'agent_message') {
|
|
140
|
+
const item = ev.item as Record<string, unknown>
|
|
141
|
+
if (typeof item.text === 'string') {
|
|
142
|
+
fullResponse = item.text
|
|
143
|
+
write(`data: ${JSON.stringify({ t: 'r', text: item.text })}\n\n`)
|
|
144
|
+
log.debug('droid-cli', `Agent message (${item.text.length} chars)`)
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
else if (ev.type === 'result' && typeof ev.result === 'string') {
|
|
149
|
+
fullResponse = ev.result
|
|
150
|
+
write(`data: ${JSON.stringify({ t: 'r', text: ev.result })}\n\n`)
|
|
151
|
+
log.debug('droid-cli', `Result event (${ev.result.length} chars)`)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
else if (ev.type === 'result' && ev.status === 'error') {
|
|
155
|
+
const errMsg = typeof ev.error === 'string' ? ev.error : 'Droid error'
|
|
156
|
+
write(`data: ${JSON.stringify({ t: 'err', text: errMsg })}\n\n`)
|
|
157
|
+
log.warn('droid-cli', `Error result: ${errMsg}`)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
else if (ev.type === 'error') {
|
|
161
|
+
const errMsg = typeof ev.message === 'string'
|
|
162
|
+
? ev.message
|
|
163
|
+
: typeof ev.error === 'string'
|
|
164
|
+
? ev.error
|
|
165
|
+
: 'Unknown Droid error'
|
|
166
|
+
write(`data: ${JSON.stringify({ t: 'err', text: errMsg })}\n\n`)
|
|
167
|
+
log.warn('droid-cli', `Event error: ${errMsg}`)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
else if (eventCount <= 10) {
|
|
171
|
+
log.debug('droid-cli', `Event: ${String(ev.type)}`)
|
|
172
|
+
}
|
|
173
|
+
} catch {
|
|
174
|
+
if (line.trim()) {
|
|
175
|
+
log.debug('droid-cli', `Non-JSON stdout line`, line.slice(0, 300))
|
|
176
|
+
fullResponse += line + '\n'
|
|
177
|
+
write(`data: ${JSON.stringify({ t: 'd', text: line + '\n' })}\n\n`)
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
proc.stderr!.on('data', (chunk: Buffer) => {
|
|
184
|
+
const text = chunk.toString()
|
|
185
|
+
stderrText += text
|
|
186
|
+
if (stderrText.length > 16_000) stderrText = stderrText.slice(-16_000)
|
|
187
|
+
if (isStderrNoise(text)) {
|
|
188
|
+
log.debug('droid-cli', `stderr noise [${session.id}]`, text.slice(0, 500))
|
|
189
|
+
} else {
|
|
190
|
+
log.warn('droid-cli', `stderr [${session.id}]`, text.slice(0, 500))
|
|
191
|
+
}
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
return new Promise((resolve) => {
|
|
195
|
+
proc.on('close', (code, sig) => {
|
|
196
|
+
log.info('droid-cli', `Process closed: code=${code} signal=${sig} events=${eventCount} response=${fullResponse.length}chars`)
|
|
197
|
+
active.delete(session.id)
|
|
198
|
+
if (tempFactoryHome) {
|
|
199
|
+
try { fs.rmSync(tempFactoryHome, { recursive: true }) } catch { /* ignore */ }
|
|
200
|
+
}
|
|
201
|
+
if ((code ?? 0) !== 0 && !fullResponse.trim()) {
|
|
202
|
+
const msg = stderrText.trim()
|
|
203
|
+
? `Factory Droid CLI exited with code ${code ?? 'unknown'}${sig ? ` (${sig})` : ''}: ${stderrText.trim().slice(0, 1200)}`
|
|
204
|
+
: `Factory Droid CLI exited with code ${code ?? 'unknown'}${sig ? ` (${sig})` : ''} and returned no output.`
|
|
205
|
+
write(`data: ${JSON.stringify({ t: 'err', text: msg })}\n\n`)
|
|
206
|
+
}
|
|
207
|
+
resolve(fullResponse)
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
proc.on('error', (e) => {
|
|
211
|
+
log.error('droid-cli', `Process error: ${e.message}`)
|
|
212
|
+
active.delete(session.id)
|
|
213
|
+
if (tempFactoryHome) {
|
|
214
|
+
try { fs.rmSync(tempFactoryHome, { recursive: true }) } catch { /* ignore */ }
|
|
215
|
+
}
|
|
216
|
+
write(`data: ${JSON.stringify({ t: 'err', text: e.message })}\n\n`)
|
|
217
|
+
resolve(fullResponse)
|
|
218
|
+
})
|
|
219
|
+
})
|
|
220
|
+
}
|
|
@@ -3,6 +3,7 @@ import { streamCodexCliChat } from './codex-cli'
|
|
|
3
3
|
import { streamOpenCodeCliChat } from './opencode-cli'
|
|
4
4
|
import { streamGeminiCliChat } from './gemini-cli'
|
|
5
5
|
import { streamCopilotCliChat } from './copilot-cli'
|
|
6
|
+
import { streamDroidCliChat } from './droid-cli'
|
|
6
7
|
import { streamCursorCliChat } from './cursor-cli'
|
|
7
8
|
import { streamQwenCodeCliChat } from './qwen-code-cli'
|
|
8
9
|
import { streamGooseChat } from './goose'
|
|
@@ -151,6 +152,15 @@ export const PROVIDERS: Record<string, BuiltinProviderConfig> = {
|
|
|
151
152
|
requiresEndpoint: false,
|
|
152
153
|
handler: { streamChat: streamCopilotCliChat },
|
|
153
154
|
},
|
|
155
|
+
'droid-cli': {
|
|
156
|
+
id: 'droid-cli',
|
|
157
|
+
name: 'Factory Droid CLI',
|
|
158
|
+
models: ['default'],
|
|
159
|
+
requiresApiKey: false,
|
|
160
|
+
optionalApiKey: true,
|
|
161
|
+
requiresEndpoint: false,
|
|
162
|
+
handler: { streamChat: streamDroidCliChat },
|
|
163
|
+
},
|
|
154
164
|
'cursor-cli': {
|
|
155
165
|
id: 'cursor-cli',
|
|
156
166
|
name: 'Cursor Agent CLI',
|
|
@@ -383,7 +393,7 @@ export function getProviderList(): ProviderInfo[] {
|
|
|
383
393
|
...info,
|
|
384
394
|
models: overrides[info.id] || info.models,
|
|
385
395
|
defaultModels: info.models,
|
|
386
|
-
supportsModelDiscovery: !['claude-cli', 'codex-cli', 'opencode-cli', 'gemini-cli', 'copilot-cli', 'cursor-cli', 'qwen-code-cli', 'goose', 'fireworks'].includes(info.id),
|
|
396
|
+
supportsModelDiscovery: !['claude-cli', 'codex-cli', 'opencode-cli', 'gemini-cli', 'copilot-cli', 'droid-cli', 'cursor-cli', 'qwen-code-cli', 'goose', 'fireworks'].includes(info.id),
|
|
387
397
|
}
|
|
388
398
|
})
|
|
389
399
|
|
|
@@ -4,7 +4,7 @@ import type { Agent, ProviderType } from '@/types'
|
|
|
4
4
|
import { isWorkerOnlyAgent, buildWorkerOnlyAgentMessage } from './agent-availability'
|
|
5
5
|
|
|
6
6
|
describe('isWorkerOnlyAgent', () => {
|
|
7
|
-
const CLI_PROVIDERS = ['claude-cli', 'codex-cli', 'opencode-cli', 'gemini-cli', 'copilot-cli', 'openclaw'] satisfies ProviderType[]
|
|
7
|
+
const CLI_PROVIDERS = ['claude-cli', 'codex-cli', 'opencode-cli', 'gemini-cli', 'copilot-cli', 'droid-cli', 'openclaw'] satisfies ProviderType[]
|
|
8
8
|
const NON_CLI_PROVIDERS = ['openai', 'anthropic', 'google', 'deepseek', 'groq', 'together'] satisfies ProviderType[]
|
|
9
9
|
|
|
10
10
|
function withProvider(provider: unknown): Pick<Agent, 'provider'> {
|
|
@@ -45,6 +45,7 @@ function buildThreadSession(agent: Agent, sessionId: string, user: string, creat
|
|
|
45
45
|
opencodeSessionId: existing?.opencodeSessionId || null,
|
|
46
46
|
geminiSessionId: existing?.geminiSessionId || null,
|
|
47
47
|
copilotSessionId: existing?.copilotSessionId || null,
|
|
48
|
+
droidSessionId: existing?.droidSessionId || null,
|
|
48
49
|
cursorSessionId: existing?.cursorSessionId || null,
|
|
49
50
|
qwenSessionId: existing?.qwenSessionId || null,
|
|
50
51
|
acpSessionId: existing?.acpSessionId || null,
|
|
@@ -42,6 +42,7 @@ export function createAgentTaskSession(
|
|
|
42
42
|
opencodeSessionId: null,
|
|
43
43
|
geminiSessionId: null,
|
|
44
44
|
copilotSessionId: null,
|
|
45
|
+
droidSessionId: null,
|
|
45
46
|
cursorSessionId: null,
|
|
46
47
|
qwenSessionId: null,
|
|
47
48
|
acpSessionId: null,
|
|
@@ -51,6 +52,7 @@ export function createAgentTaskSession(
|
|
|
51
52
|
opencode: null,
|
|
52
53
|
gemini: null,
|
|
53
54
|
copilot: null,
|
|
55
|
+
droid: null,
|
|
54
56
|
cursor: null,
|
|
55
57
|
qwen: null,
|
|
56
58
|
},
|
|
@@ -19,7 +19,7 @@ export interface CapabilityRoutingDecision {
|
|
|
19
19
|
primaryUrl?: string
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
type DelegateTool = 'delegate_to_claude_code' | 'delegate_to_codex_cli' | 'delegate_to_opencode_cli' | 'delegate_to_gemini_cli' | 'delegate_to_copilot_cli' | 'delegate_to_cursor_cli' | 'delegate_to_qwen_code_cli'
|
|
22
|
+
type DelegateTool = 'delegate_to_claude_code' | 'delegate_to_codex_cli' | 'delegate_to_opencode_cli' | 'delegate_to_gemini_cli' | 'delegate_to_copilot_cli' | 'delegate_to_droid_cli' | 'delegate_to_cursor_cli' | 'delegate_to_qwen_code_cli'
|
|
23
23
|
|
|
24
24
|
function findFirstUrl(text: string): string | undefined {
|
|
25
25
|
const m = text.match(/https?:\/\/[^\s<>"')]+/i)
|
|
@@ -42,6 +42,7 @@ function normalizeDelegateOrder(value: unknown): DelegateTool[] {
|
|
|
42
42
|
'delegate_to_opencode_cli',
|
|
43
43
|
'delegate_to_gemini_cli',
|
|
44
44
|
'delegate_to_copilot_cli',
|
|
45
|
+
'delegate_to_droid_cli',
|
|
45
46
|
'delegate_to_cursor_cli',
|
|
46
47
|
'delegate_to_qwen_code_cli',
|
|
47
48
|
]
|
|
@@ -54,6 +55,7 @@ function normalizeDelegateOrder(value: unknown): DelegateTool[] {
|
|
|
54
55
|
else if (raw === 'opencode') mapped.push('delegate_to_opencode_cli')
|
|
55
56
|
else if (raw === 'gemini') mapped.push('delegate_to_gemini_cli')
|
|
56
57
|
else if (raw === 'copilot') mapped.push('delegate_to_copilot_cli')
|
|
58
|
+
else if (raw === 'droid') mapped.push('delegate_to_droid_cli')
|
|
57
59
|
else if (raw === 'cursor') mapped.push('delegate_to_cursor_cli')
|
|
58
60
|
else if (raw === 'qwen') mapped.push('delegate_to_qwen_code_cli')
|
|
59
61
|
}
|
|
@@ -31,6 +31,7 @@ export type DelegateTool =
|
|
|
31
31
|
| 'delegate_to_opencode_cli'
|
|
32
32
|
| 'delegate_to_gemini_cli'
|
|
33
33
|
| 'delegate_to_copilot_cli'
|
|
34
|
+
| 'delegate_to_droid_cli'
|
|
34
35
|
| 'delegate_to_cursor_cli'
|
|
35
36
|
| 'delegate_to_qwen_code_cli'
|
|
36
37
|
|
|
@@ -138,6 +139,12 @@ export function translateRequestedToolInvocation(
|
|
|
138
139
|
if (requestedName === 'delegate_to_gemini_cli') {
|
|
139
140
|
return { toolName: 'delegate', args: { ...rawArgs, backend: 'gemini' } }
|
|
140
141
|
}
|
|
142
|
+
if (requestedName === 'delegate_to_copilot_cli') {
|
|
143
|
+
return { toolName: 'delegate', args: { ...rawArgs, backend: 'copilot' } }
|
|
144
|
+
}
|
|
145
|
+
if (requestedName === 'delegate_to_droid_cli') {
|
|
146
|
+
return { toolName: 'delegate', args: { ...rawArgs, backend: 'droid' } }
|
|
147
|
+
}
|
|
141
148
|
if (requestedName === 'delegate_to_cursor_cli') {
|
|
142
149
|
return { toolName: 'delegate', args: { ...rawArgs, backend: 'cursor' } }
|
|
143
150
|
}
|
|
@@ -306,6 +313,8 @@ export function requestedToolNamesFromMessage(message: string): string[] {
|
|
|
306
313
|
'delegate_to_codex_cli',
|
|
307
314
|
'delegate_to_opencode_cli',
|
|
308
315
|
'delegate_to_gemini_cli',
|
|
316
|
+
'delegate_to_copilot_cli',
|
|
317
|
+
'delegate_to_droid_cli',
|
|
309
318
|
'delegate_to_cursor_cli',
|
|
310
319
|
'delegate_to_qwen_code_cli',
|
|
311
320
|
'connector_message_tool',
|
|
@@ -372,6 +381,8 @@ export function enabledDelegationTools(session: SessionWithTools): DelegateTool[
|
|
|
372
381
|
if (hasToolEnabled(session, 'codex_cli')) tools.push('delegate_to_codex_cli')
|
|
373
382
|
if (hasToolEnabled(session, 'opencode_cli')) tools.push('delegate_to_opencode_cli')
|
|
374
383
|
if (hasToolEnabled(session, 'gemini_cli')) tools.push('delegate_to_gemini_cli')
|
|
384
|
+
if (hasToolEnabled(session, 'copilot_cli')) tools.push('delegate_to_copilot_cli')
|
|
385
|
+
if (hasToolEnabled(session, 'droid_cli')) tools.push('delegate_to_droid_cli')
|
|
375
386
|
if (hasToolEnabled(session, 'cursor_cli')) tools.push('delegate_to_cursor_cli')
|
|
376
387
|
if (hasToolEnabled(session, 'qwen_code_cli')) tools.push('delegate_to_qwen_code_cli')
|
|
377
388
|
return tools
|