@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
@@ -25,7 +25,7 @@ import { markProviderFailure, markProviderSuccess } from '../provider-health'
25
25
  import { loadRuntimeSettings } from '../runtime/runtime-settings'
26
26
  import { getSessionDepth } from '../agents/subagent-runtime'
27
27
 
28
- const DELEGATE_BACKEND_ORDER: DelegateBackend[] = ['claude', 'codex', 'opencode', 'gemini', 'copilot', 'cursor', 'qwen']
28
+ const DELEGATE_BACKEND_ORDER: DelegateBackend[] = ['claude', 'codex', 'opencode', 'gemini', 'copilot', 'droid', 'cursor', 'qwen']
29
29
 
30
30
  interface DelegateContext {
31
31
  id?: string
@@ -34,8 +34,8 @@ interface DelegateContext {
34
34
  jobId?: string | null
35
35
  cwd?: string
36
36
  claudeTimeoutMs?: number
37
- readStoredDelegateResumeId?: (key: 'claudeCode' | 'codex' | 'opencode' | 'gemini' | 'copilot' | 'cursor' | 'qwen') => string | null
38
- persistDelegateResumeId?: (key: 'claudeCode' | 'codex' | 'opencode' | 'gemini' | 'copilot' | 'cursor' | 'qwen', id: string | null | undefined) => void
37
+ readStoredDelegateResumeId?: (key: 'claudeCode' | 'codex' | 'opencode' | 'gemini' | 'copilot' | 'droid' | 'cursor' | 'qwen') => string | null
38
+ persistDelegateResumeId?: (key: 'claudeCode' | 'codex' | 'opencode' | 'gemini' | 'copilot' | 'droid' | 'cursor' | 'qwen', id: string | null | undefined) => void
39
39
  ctx?: {
40
40
  delegationEnabled?: boolean
41
41
  delegationTargetMode?: 'all' | 'selected'
@@ -48,7 +48,7 @@ interface DelegateContext {
48
48
  hasTool?: (name: string) => boolean
49
49
  }
50
50
 
51
- type DelegateBackend = 'claude' | 'codex' | 'opencode' | 'gemini' | 'copilot' | 'cursor' | 'qwen'
51
+ type DelegateBackend = 'claude' | 'codex' | 'opencode' | 'gemini' | 'copilot' | 'droid' | 'cursor' | 'qwen'
52
52
 
53
53
  interface DelegateRuntimeState {
54
54
  child?: ChildProcess | null
@@ -129,10 +129,11 @@ function buildDelegateResumePatch(bctx: DelegateContext) {
129
129
  opencode: bctx.readStoredDelegateResumeId?.('opencode') || null,
130
130
  gemini: bctx.readStoredDelegateResumeId?.('gemini') || null,
131
131
  copilot: bctx.readStoredDelegateResumeId?.('copilot') || null,
132
+ droid: bctx.readStoredDelegateResumeId?.('droid') || null,
132
133
  cursor: bctx.readStoredDelegateResumeId?.('cursor') || null,
133
134
  qwen: bctx.readStoredDelegateResumeId?.('qwen') || null,
134
135
  }
135
- const resumeId = resumeIds.claudeCode || resumeIds.codex || resumeIds.opencode || resumeIds.gemini || resumeIds.copilot || resumeIds.cursor || resumeIds.qwen || null
136
+ const resumeId = resumeIds.claudeCode || resumeIds.codex || resumeIds.opencode || resumeIds.gemini || resumeIds.copilot || resumeIds.droid || resumeIds.cursor || resumeIds.qwen || null
136
137
  return { resumeIds, resumeId }
137
138
  }
138
139
 
@@ -144,6 +145,7 @@ function coerceDelegateBackend(value: unknown): DelegateBackend | null {
144
145
  if (['opencode', 'open code', 'open-code', 'open_code'].includes(normalized)) return 'opencode'
145
146
  if (['gemini', 'gemini cli', 'gemini-cli', 'gemini_cli'].includes(normalized)) return 'gemini'
146
147
  if (['copilot', 'copilot cli', 'copilot-cli', 'copilot_cli', 'github copilot'].includes(normalized)) return 'copilot'
148
+ if (['droid', 'droid cli', 'droid-cli', 'droid_cli', 'factory', 'factory droid', 'factory-droid', 'factory_droid'].includes(normalized)) return 'droid'
147
149
  if (['cursor', 'cursor cli', 'cursor-cli', 'cursor_cli', 'cursor-agent'].includes(normalized)) return 'cursor'
148
150
  if (['qwen', 'qwen code', 'qwen-code', 'qwen_code', 'qwen-code-cli', 'qwen_code_cli'].includes(normalized)) return 'qwen'
149
151
  return null
@@ -340,21 +342,22 @@ function coerceOptionalBool(value: unknown): boolean | null {
340
342
  }
341
343
 
342
344
  function resumeStorageKeyForBackend(
343
- backend: 'claude' | 'codex' | 'opencode' | 'gemini' | 'copilot' | 'cursor' | 'qwen',
344
- ): 'claudeCode' | 'codex' | 'opencode' | 'gemini' | 'copilot' | 'cursor' | 'qwen' {
345
+ backend: 'claude' | 'codex' | 'opencode' | 'gemini' | 'copilot' | 'droid' | 'cursor' | 'qwen',
346
+ ): 'claudeCode' | 'codex' | 'opencode' | 'gemini' | 'copilot' | 'droid' | 'cursor' | 'qwen' {
345
347
  if (backend === 'claude') return 'claudeCode'
346
348
  if (backend === 'codex') return 'codex'
347
349
  if (backend === 'opencode') return 'opencode'
348
350
  if (backend === 'gemini') return 'gemini'
349
351
  if (backend === 'copilot') return 'copilot'
352
+ if (backend === 'droid') return 'droid'
350
353
  if (backend === 'cursor') return 'cursor'
351
354
  return 'qwen'
352
355
  }
353
356
 
354
357
  export function resolveDelegateResumeConfig(
355
358
  normalized: Record<string, unknown>,
356
- backend: 'claude' | 'codex' | 'opencode' | 'gemini' | 'copilot' | 'cursor' | 'qwen',
357
- bctx: { readStoredDelegateResumeId?: (key: 'claudeCode' | 'codex' | 'opencode' | 'gemini' | 'copilot' | 'cursor' | 'qwen') => string | null },
359
+ backend: 'claude' | 'codex' | 'opencode' | 'gemini' | 'copilot' | 'droid' | 'cursor' | 'qwen',
360
+ bctx: { readStoredDelegateResumeId?: (key: 'claudeCode' | 'codex' | 'opencode' | 'gemini' | 'copilot' | 'droid' | 'cursor' | 'qwen') => string | null },
358
361
  ): { resume: boolean; resumeId: string } {
359
362
  const explicitResumeId = typeof normalized.resumeId === 'string' ? normalized.resumeId.trim() : ''
360
363
  if (explicitResumeId) return { resume: true, resumeId: explicitResumeId }
@@ -429,6 +432,11 @@ const DELEGATE_BACKEND_ADAPTERS: Record<DelegateBackend, DelegateBackendAdapter>
429
432
  binaryName: 'copilot',
430
433
  run: runCopilotDelegate,
431
434
  },
435
+ droid: {
436
+ backend: 'droid',
437
+ binaryName: 'droid',
438
+ run: runDroidDelegate,
439
+ },
432
440
  cursor: {
433
441
  backend: 'cursor',
434
442
  binaryName: 'cursor-agent',
@@ -459,6 +467,7 @@ function providerIdForBackend(backend: DelegateBackend): string {
459
467
  if (backend === 'opencode') return 'opencode-cli'
460
468
  if (backend === 'gemini') return 'gemini-cli'
461
469
  if (backend === 'copilot') return 'copilot-cli'
470
+ if (backend === 'droid') return 'droid-cli'
462
471
  if (backend === 'cursor') return 'cursor-cli'
463
472
  return 'qwen-code-cli'
464
473
  }
@@ -983,6 +992,90 @@ async function runGeminiDelegate(binary: string, task: string, resume: boolean,
983
992
  }
984
993
  }
985
994
 
995
+ async function runDroidDelegate(binary: string, task: string, resume: boolean, resumeId: string, bctx: DelegateContext, runtime?: DelegateRuntimeState): Promise<DelegateBackendResult> {
996
+ try {
997
+ const env = buildCliEnv()
998
+ const auth = probeCliAuth(binary, 'droid', env, bctx.cwd)
999
+ if (!auth.authenticated) {
1000
+ return buildDelegateFailure('droid', auth.errorMessage || 'Factory Droid CLI is not authenticated.', 'auth')
1001
+ }
1002
+
1003
+ const storedResumeId = bctx.readStoredDelegateResumeId?.('droid')
1004
+ const resumeIdToUse = resumeId?.trim() || (resume ? storedResumeId : null)
1005
+
1006
+ return await new Promise<DelegateBackendResult>((resolve) => {
1007
+ const args = ['exec', task, '--output-format', 'stream-json', '--auto', 'low']
1008
+ if (resumeIdToUse) args.push('-s', resumeIdToUse)
1009
+
1010
+ const child = spawn(binary, args, { cwd: bctx.cwd, env, stdio: ['ignore', 'pipe', 'pipe'] })
1011
+ bindDelegateRuntime(runtime, child)
1012
+ let stdoutBuf = ''
1013
+ let stderrBuf = ''
1014
+ let responseText = ''
1015
+ let discoveredId: string | null = null
1016
+ let settled = false
1017
+
1018
+ const finish = (result: DelegateBackendResult) => {
1019
+ if (settled) return
1020
+ settled = true
1021
+ resolve(result)
1022
+ }
1023
+
1024
+ const timeoutHandle = setTimeout(() => {
1025
+ try { child.kill('SIGTERM') } catch { /* ignore */ }
1026
+ }, bctx.claudeTimeoutMs || 300000)
1027
+
1028
+ child.stdout?.on('data', (chunk) => {
1029
+ stdoutBuf += chunk.toString()
1030
+ const lines = stdoutBuf.split('\n')
1031
+ stdoutBuf = lines.pop() || ''
1032
+ for (const line of lines) {
1033
+ const trimmed = line.trim()
1034
+ if (!trimmed) continue
1035
+ try {
1036
+ const ev = JSON.parse(trimmed) as Record<string, unknown>
1037
+ const sid = typeof ev.session_id === 'string'
1038
+ ? ev.session_id
1039
+ : typeof ev.sessionId === 'string'
1040
+ ? ev.sessionId
1041
+ : null
1042
+ if (sid) discoveredId = sid
1043
+ const text = parseCursorOutputText(ev)
1044
+ if (text) {
1045
+ if (String(ev.type || '').includes('result') || String(ev.type || '').includes('completed')) responseText = text
1046
+ else responseText += text
1047
+ }
1048
+ } catch {
1049
+ responseText += `${line}\n`
1050
+ }
1051
+ }
1052
+ })
1053
+
1054
+ child.stderr?.on('data', (chunk) => {
1055
+ stderrBuf += chunk.toString()
1056
+ if (stderrBuf.length > 16_000) stderrBuf = stderrBuf.slice(-16_000)
1057
+ })
1058
+
1059
+ child.on('close', (code, signal) => {
1060
+ clearTimeout(timeoutHandle)
1061
+ if (discoveredId) bctx.persistDelegateResumeId?.('droid', discoveredId)
1062
+ const output = responseText.trim()
1063
+ if (output) return finish(buildDelegateSuccess('droid', output))
1064
+ const stderr = stderrBuf.trim()
1065
+ if (stderr) return finish(buildDelegateFailure('droid', stderr))
1066
+ return finish(buildDelegateFailure('droid', `Droid exited with code ${code ?? 'unknown'}${signal ? ` (${signal})` : ''}.`, 'runtime'))
1067
+ })
1068
+
1069
+ child.on('error', (err) => {
1070
+ clearTimeout(timeoutHandle)
1071
+ finish(buildDelegateFailure('droid', err.message, 'spawn'))
1072
+ })
1073
+ })
1074
+ } catch (err: unknown) {
1075
+ return buildDelegateFailure('droid', errorMessage(err), 'runtime')
1076
+ }
1077
+ }
1078
+
986
1079
  async function runCopilotDelegate(binary: string, task: string, resume: boolean, resumeId: string, bctx: DelegateContext, runtime?: DelegateRuntimeState): Promise<DelegateBackendResult> {
987
1080
  try {
988
1081
  const env = buildCliEnv()
@@ -1304,19 +1397,19 @@ const DelegateExtension: Extension = {
1304
1397
  name: 'Core Delegate',
1305
1398
  description: 'Delegate complex multi-file tasks to specialized CLI backends or other agents.',
1306
1399
  hooks: {
1307
- getCapabilityDescription: () => 'I can hand off coding work to Claude Code, Codex, OpenCode, Gemini CLI, Cursor CLI, or Qwen Code CLI (`delegate`) for file creation, refactoring, debugging, code generation, and multi-file edits. Resume IDs may come back via `[delegate_meta]`.',
1400
+ getCapabilityDescription: () => 'I can hand off coding work to Claude Code, Codex, OpenCode, Gemini CLI, Copilot CLI, Factory Droid CLI, Cursor CLI, or Qwen Code CLI (`delegate`) for file creation, refactoring, debugging, code generation, and multi-file edits. Resume IDs may come back via `[delegate_meta]`.',
1308
1401
  getOperatingGuidance: () => ['CRITICAL: `execute_command` (not delegation) for running servers, installs, scripts. Delegation sessions end and kill processes.', 'Delegate for code tasks: writing/creating files, refactors, debugging, generation, test suites, data exports to files.'],
1309
1402
  } as ExtensionHooks,
1310
1403
  tools: [
1311
1404
  {
1312
1405
  name: 'delegate',
1313
- description: 'Delegate to a specialized backend (Claude, Codex, OpenCode, Gemini, Cursor, Qwen) for code tasks: writing files, refactoring, debugging, code generation, and multi-file edits. Supports background jobs with action=status|list|wait|cancel.',
1406
+ description: 'Delegate to a specialized backend (Claude, Codex, OpenCode, Gemini, Copilot, Droid, Cursor, Qwen) for code tasks: writing files, refactoring, debugging, code generation, and multi-file edits. Supports background jobs with action=status|list|wait|cancel.',
1314
1407
  parameters: {
1315
1408
  type: 'object',
1316
1409
  properties: {
1317
1410
  action: { type: 'string', enum: ['start', 'status', 'list', 'wait', 'cancel'] },
1318
1411
  task: { type: 'string' },
1319
- backend: { type: 'string', enum: ['claude', 'codex', 'opencode', 'gemini', 'copilot', 'cursor', 'qwen'] },
1412
+ backend: { type: 'string', enum: ['claude', 'codex', 'opencode', 'gemini', 'copilot', 'droid', 'cursor', 'qwen'] },
1320
1413
  resume: { type: 'boolean' },
1321
1414
  resumeId: { type: 'string', description: 'Optional explicit session/thread ID to resume' },
1322
1415
  jobId: { type: 'string' },
@@ -76,6 +76,7 @@ const DELEGATION_TOOL_NAMES = new Set([
76
76
  'delegate_to_opencode_cli',
77
77
  'delegate_to_gemini_cli',
78
78
  'delegate_to_copilot_cli',
79
+ 'delegate_to_droid_cli',
79
80
  'delegate_to_cursor_cli',
80
81
  'delegate_to_qwen_code_cli',
81
82
  ])
@@ -122,14 +123,14 @@ export async function buildSessionTools(cwd: string, enabledExtensions: string[]
122
123
  return loadSession(ctx.sessionId)
123
124
  }
124
125
 
125
- const readStoredDelegateResumeId = (key: 'claudeCode' | 'codex' | 'opencode' | 'gemini' | 'copilot' | 'cursor' | 'qwen'): string | null => {
126
+ const readStoredDelegateResumeId = (key: 'claudeCode' | 'codex' | 'opencode' | 'gemini' | 'copilot' | 'droid' | 'cursor' | 'qwen'): string | null => {
126
127
  const session = resolveCurrentSession()
127
128
  if (!session?.delegateResumeIds || typeof session.delegateResumeIds !== 'object') return null
128
129
  const raw = session.delegateResumeIds[key]
129
130
  return typeof raw === 'string' && raw.trim() ? raw.trim() : null
130
131
  }
131
132
 
132
- const persistDelegateResumeId = (key: 'claudeCode' | 'codex' | 'opencode' | 'gemini' | 'copilot' | 'cursor' | 'qwen', resumeId: string | null | undefined): void => {
133
+ const persistDelegateResumeId = (key: 'claudeCode' | 'codex' | 'opencode' | 'gemini' | 'copilot' | 'droid' | 'cursor' | 'qwen', resumeId: string | null | undefined): void => {
133
134
  const normalized = typeof resumeId === 'string' ? resumeId.trim() : ''
134
135
  if (!normalized || !ctx?.sessionId) return
135
136
  patchSession(ctx.sessionId, (target) => {
@@ -50,6 +50,7 @@ function normalizeRuntimeExtensionId(extensionId: string): string {
50
50
  if (normalized === 'delegate_to_opencode_cli' || normalized === 'opencode_cli') return 'opencode_cli'
51
51
  if (normalized === 'delegate_to_gemini_cli' || normalized === 'gemini_cli') return 'gemini_cli'
52
52
  if (normalized === 'delegate_to_copilot_cli' || normalized === 'copilot_cli') return 'copilot_cli'
53
+ if (normalized === 'delegate_to_droid_cli' || normalized === 'droid_cli') return 'droid_cli'
53
54
  if (normalized === 'delegate_to_cursor_cli' || normalized === 'cursor_cli') return 'cursor_cli'
54
55
  if (normalized === 'delegate_to_qwen_code_cli' || normalized === 'qwen_code_cli') return 'qwen_code_cli'
55
56
  if (['session_info', 'sessions_tool', 'whoami_tool', 'search_history_tool'].includes(normalized)) return 'manage_sessions'
@@ -649,6 +649,7 @@ function normalizeStoredRecordInner(
649
649
  if (session.geminiSessionId === undefined) session.geminiSessionId = null
650
650
  // Default copilotSessionId for new field
651
651
  if (session.copilotSessionId === undefined) session.copilotSessionId = null
652
+ if (session.droidSessionId === undefined) session.droidSessionId = null
652
653
  if (session.cursorSessionId === undefined) session.cursorSessionId = null
653
654
  if (session.qwenSessionId === undefined) session.qwenSessionId = null
654
655
  if (session.acpSessionId === undefined) session.acpSessionId = null
@@ -659,12 +660,14 @@ function normalizeStoredRecordInner(
659
660
  opencode: null,
660
661
  gemini: null,
661
662
  copilot: null,
663
+ droid: null,
662
664
  cursor: null,
663
665
  qwen: null,
664
666
  }
665
667
  } else {
666
668
  const resumeIds = session.delegateResumeIds as Record<string, unknown>
667
669
  if (resumeIds.copilot === undefined) resumeIds.copilot = null
670
+ if (resumeIds.droid === undefined) resumeIds.droid = null
668
671
  if (resumeIds.cursor === undefined) resumeIds.cursor = null
669
672
  if (resumeIds.qwen === undefined) resumeIds.qwen = null
670
673
  }
@@ -5,7 +5,7 @@ const EXTENSION_ALIAS_GROUPS: string[][] = [
5
5
  ['edit_file'],
6
6
  ['web', 'web_search', 'web_fetch', 'http_request', 'http'],
7
7
  ['browser', 'openclaw_browser'],
8
- ['delegate', 'claude_code', 'codex_cli', 'opencode_cli', 'gemini_cli', 'copilot_cli', 'cursor_cli', 'qwen_code_cli', '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'],
8
+ ['delegate', 'claude_code', 'codex_cli', 'opencode_cli', 'gemini_cli', 'copilot_cli', 'droid_cli', 'cursor_cli', 'qwen_code_cli', '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'],
9
9
  ['manage_platform'],
10
10
  ['manage_agents'],
11
11
  ['manage_projects'],
@@ -53,12 +53,13 @@ const TOOL_DESCRIPTORS: Record<string, ToolDescriptor> = {
53
53
  web_search: { categories: ['network'], concreteTools: ['web_search'] },
54
54
  web_fetch: { categories: ['network'], concreteTools: ['web_fetch'] },
55
55
  browser: { categories: ['browser', 'network'], concreteTools: ['browser', 'openclaw_browser'] },
56
- delegate: { categories: ['delegation', 'execution'], concreteTools: ['delegate', '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'] },
56
+ delegate: { categories: ['delegation', 'execution'], concreteTools: ['delegate', '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'] },
57
57
  claude_code: { categories: ['delegation', 'execution'], concreteTools: ['delegate_to_claude_code'] },
58
58
  codex_cli: { categories: ['delegation', 'execution'], concreteTools: ['delegate_to_codex_cli'] },
59
59
  opencode_cli: { categories: ['delegation', 'execution'], concreteTools: ['delegate_to_opencode_cli'] },
60
60
  gemini_cli: { categories: ['delegation', 'execution'], concreteTools: ['delegate_to_gemini_cli'] },
61
61
  copilot_cli: { categories: ['delegation', 'execution'], concreteTools: ['delegate_to_copilot_cli'] },
62
+ droid_cli: { categories: ['delegation', 'execution'], concreteTools: ['delegate_to_droid_cli'] },
62
63
  cursor_cli: { categories: ['delegation', 'execution'], concreteTools: ['delegate_to_cursor_cli'] },
63
64
  qwen_code_cli: { categories: ['delegation', 'execution'], concreteTools: ['delegate_to_qwen_code_cli'] },
64
65
  memory: { categories: ['memory'], concreteTools: ['memory', 'memory_tool', 'memory_search', 'memory_get', 'memory_store', 'memory_update', 'context_status', 'context_summarize'] },
@@ -9,6 +9,7 @@ export type SetupProvider =
9
9
  | 'opencode-cli'
10
10
  | 'gemini-cli'
11
11
  | 'copilot-cli'
12
+ | 'droid-cli'
12
13
  | 'cursor-cli'
13
14
  | 'qwen-code-cli'
14
15
  | 'goose'
@@ -97,6 +98,19 @@ export const SETUP_PROVIDERS: SetupProviderOption[] = [
97
98
  badge: 'CLI',
98
99
  icon: 'P',
99
100
  },
101
+ {
102
+ id: 'droid-cli',
103
+ name: 'Factory Droid CLI',
104
+ description: 'Factory.ai’s terminal coding agent with headless exec mode, session resume, and autonomy controls.',
105
+ requiresKey: false,
106
+ supportsEndpoint: false,
107
+ optionalKey: true,
108
+ keyUrl: 'https://app.factory.ai/settings/api-keys',
109
+ keyLabel: 'app.factory.ai',
110
+ keyPlaceholder: 'FACTORY_API_KEY (optional if signed in via `droid`)',
111
+ badge: 'CLI',
112
+ icon: 'F',
113
+ },
100
114
  {
101
115
  id: 'cursor-cli',
102
116
  name: 'Cursor Agent CLI',
@@ -743,6 +757,13 @@ export const DEFAULT_AGENTS: Record<SetupProvider, DefaultAgentConfig> = {
743
757
  model: 'claude-sonnet-4-5',
744
758
  tools: STARTER_AGENT_TOOLS,
745
759
  },
760
+ 'droid-cli': {
761
+ name: 'Factory Droid',
762
+ description: 'A helpful assistant powered by Factory Droid CLI.',
763
+ systemPrompt: SWARMCLAW_ASSISTANT_PROMPT,
764
+ model: 'default',
765
+ tools: STARTER_AGENT_TOOLS,
766
+ },
746
767
  'cursor-cli': {
747
768
  name: 'Cursor CLI',
748
769
  description: 'A helpful assistant powered by Cursor Agent CLI.',
package/src/types/misc.ts CHANGED
@@ -387,7 +387,7 @@ export interface DelegationJobRecord {
387
387
  id: string
388
388
  kind: DelegationJobKind
389
389
  status: DelegationJobStatus
390
- backend?: 'claude' | 'codex' | 'opencode' | 'gemini' | 'copilot' | 'cursor' | 'qwen' | null
390
+ backend?: 'claude' | 'codex' | 'opencode' | 'gemini' | 'copilot' | 'droid' | 'cursor' | 'qwen' | null
391
391
  parentSessionId?: string | null
392
392
  childSessionId?: string | null
393
393
  agentId?: string | null
@@ -1,4 +1,4 @@
1
- export type ProviderType = 'claude-cli' | 'codex-cli' | 'opencode-cli' | 'gemini-cli' | 'copilot-cli' | 'cursor-cli' | 'qwen-code-cli' | 'goose' | 'openai' | 'openrouter' | 'ollama' | 'anthropic' | 'openclaw' | 'hermes' | 'google' | 'deepseek' | 'groq' | 'together' | 'mistral' | 'xai' | 'fireworks' | 'nebius' | 'deepinfra'
1
+ export type ProviderType = 'claude-cli' | 'codex-cli' | 'opencode-cli' | 'gemini-cli' | 'copilot-cli' | 'droid-cli' | 'cursor-cli' | 'qwen-code-cli' | 'goose' | 'openai' | 'openrouter' | 'ollama' | 'anthropic' | 'openclaw' | 'hermes' | 'google' | 'deepseek' | 'groq' | 'together' | 'mistral' | 'xai' | 'fireworks' | 'nebius' | 'deepinfra'
2
2
  export type ProviderId = ProviderType | (string & {})
3
3
 
4
4
  export interface ProviderInfo {
@@ -71,6 +71,7 @@ export interface Session {
71
71
  opencodeSessionId?: string | null
72
72
  geminiSessionId?: string | null
73
73
  copilotSessionId?: string | null
74
+ droidSessionId?: string | null
74
75
  cursorSessionId?: string | null
75
76
  qwenSessionId?: string | null
76
77
  acpSessionId?: string | null
@@ -80,6 +81,7 @@ export interface Session {
80
81
  opencode?: string | null
81
82
  gemini?: string | null
82
83
  copilot?: string | null
84
+ droid?: string | null
83
85
  cursor?: string | null
84
86
  qwen?: string | null
85
87
  }
@@ -206,6 +208,7 @@ export type SessionTool =
206
208
  | 'opencode_cli'
207
209
  | 'gemini_cli'
208
210
  | 'copilot_cli'
211
+ | 'droid_cli'
209
212
  | 'cursor_cli'
210
213
  | 'qwen_code_cli'
211
214
  | 'web_search'