Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions apps/docs/content/docs/install-agent-runtime.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,17 @@ Qwen Code uses its native JSONL protocol in daemon mode: `qwen -p <prompt> --out
| Authentication | Run `qwen` once interactively and complete its configured authentication flow before starting the daemon. |
| Notes | Minimum CLI version: `0.20.0`. Override the binary with `MULTICA_QWEN_PATH` and set a daemon-wide default model with `MULTICA_QWEN_MODEL`. Add daemon-wide non-managed flags with `MULTICA_QWEN_ARGS` (POSIX shellword parsing). Managed agent `mcp_config` is written to a 0600 temporary JSON file and passed through `--mcp-config`; the file is removed after the run. Leave the field null to inherit native Qwen Code MCP settings. |

### Devin CLI (Cognition)

Devin CLI runs through its ACP server: `devin acp`. Session resumption uses ACP `session/load`; the runtime brief is written to `AGENTS.md`; assigned skills are copied to `.devin/skills/`; and personal skills are discovered from `~/.config/devin/skills/`. Devin manages its model in its own CLI configuration, so Multica disables the per-agent model picker.

| | |
|---|---|
| Daemon looks for | `devin` |
| Install | Install Devin CLI using Cognition's current [Devin documentation](https://docs.devin.ai/), then ensure `devin` is on `PATH`. |
| Authentication | Run `devin` once interactively and complete the configured authentication flow before starting the daemon. |
| Notes | Override the binary with `MULTICA_DEVIN_PATH`. Add non-managed arguments with `custom_args`; Multica reserves the `acp` subcommand. |

### Trae CLI (ByteDance)

ByteDance's official TRAE CLI (`traecli`, paired with the Trae IDE — **not** the open-source `bytedance/trae-agent`). It is ACP-native, so Multica drives it over stdio via `traecli acp serve --yolo`, sharing the transport with Hermes, Kimi, Kiro CLI, and Qoder. Session resumption works through ACP `session/load`, MCP config is passed through ACP `mcpServers`, model selection is discovered dynamically (switched per task via `session/set_model`), and skills are copied into `.traecli/skills/`.
Expand Down
1 change: 1 addition & 0 deletions packages/core/types/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ export const RUNTIME_PROFILE_PROTOCOL_FAMILIES = [
"traecli",
"grok",
"qwen",
"devin",
] as const;

export type RuntimeProtocolFamily =
Expand Down
3 changes: 2 additions & 1 deletion packages/views/onboarding/steps/step-welcome.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,8 @@ type ProviderName =
| "qoder"
| "pi"
| "copilot"
| "cursor";
| "cursor"
| "devin";

type ActivityActor =
| { kind: "user"; name: string; initial: string }
Expand Down
2 changes: 2 additions & 0 deletions packages/views/runtimes/components/provider-logo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,8 @@ export function ProviderLogo({
return <GrokLogo className={className} />;
case "qwen":
return <QwenLogo className={className} />;
case "devin":
return <Monitor className={className} />;
default:
return <Monitor className={className} />;
}
Expand Down
9 changes: 6 additions & 3 deletions server/internal/daemon/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ type Config struct {
CLIVersion string // multica CLI version (e.g. "0.1.13")
LaunchedBy string // "desktop" when spawned by the Electron app, empty for standalone
Profile string // profile name (empty = default)
Agents map[string]AgentEntry // keyed by provider: claude, codebuddy, codex, copilot, opencode, openclaw, hermes, pi, cursor, kimi, kiro, antigravity, qoder, traecli, grok, qwen
Agents map[string]AgentEntry // keyed by provider: claude, codebuddy, codex, copilot, opencode, openclaw, hermes, pi, cursor, kimi, kiro, antigravity, qoder, traecli, grok, qwen, devin
WorkspacesRoot string // base path for execution envs (default: ~/multica_workspaces)
KeepEnvAfterTask bool // preserve env after task for debugging
HealthPort int // local HTTP port for health checks (default: 19514)
Expand Down Expand Up @@ -348,8 +348,11 @@ func LoadConfig(overrides Overrides) (Config, error) {
if e, ok := probe("MULTICA_QWEN_PATH", "qwen", "MULTICA_QWEN_MODEL"); ok {
agents["qwen"] = e
}
if e, ok := probe("MULTICA_DEVIN_PATH", "devin", ""); ok {
agents["devin"] = e
}
if len(agents) == 0 && !overrides.AllowNoAgents {
return Config{}, fmt.Errorf("no agent CLI found: install claude, codebuddy, codex, copilot, opencode, deveco, openclaw, hermes, pi, cursor-agent, kimi, kiro-cli, agy, qodercli, traecli, grok, or qwen and ensure it is on PATH")
return Config{}, fmt.Errorf("no agent CLI found: install claude, codebuddy, codex, copilot, opencode, deveco, openclaw, hermes, pi, cursor-agent, kimi, kiro-cli, agy, qodercli, traecli, grok, qwen, or devin and ensure it is on PATH")
}

claudeArgs, err := shellArgsFromEnv("MULTICA_CLAUDE_ARGS")
Expand Down Expand Up @@ -863,7 +866,7 @@ func isExecutableFile(path string) bool {
// invocation, instead of paying the cost-per-miss.
var defaultAgentCommandNames = []string{
"claude", "codex", "opencode", "deveco", "openclaw", "hermes",
"pi", "cursor-agent", "copilot", "kimi", "kiro-cli", "codebuddy", "agy", "traecli", "grok", "qwen",
"pi", "cursor-agent", "copilot", "kimi", "kiro-cli", "codebuddy", "agy", "traecli", "grok", "qwen", "devin",
}

// codexDesktopAppBundlePaths returns candidate macOS app-bundle locations for
Expand Down
1 change: 1 addition & 0 deletions server/internal/daemon/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -3678,6 +3678,7 @@ var runtimeDisplayNameOverrides = map[string]string{
"traecli": "Trae",
"grok": "Grok",
"qwen": "Qwen Code",
"devin": "Devin",
}

// providerDisplayName returns the human-facing runtime name for a provider key.
Expand Down
2 changes: 2 additions & 0 deletions server/internal/daemon/execenv/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,8 @@ func skillsDirPath(workDir, provider string) string {
// (and also scans .agents/skills/). Prefer the native .grok tree.
// See Grok user-guide skills.md.
return filepath.Join(workDir, ".grok", "skills")
case "devin":
return filepath.Join(workDir, ".devin", "skills")
default:
// Fallback: write to .agent_context/skills/ (referenced by meta config).
return filepath.Join(workDir, ".agent_context", "skills")
Expand Down
2 changes: 1 addition & 1 deletion server/internal/daemon/execenv/runtime_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ func runtimeConfigPath(workDir, provider string) string {
return filepath.Join(workDir, "CODEBUDDY.md")
case "qwen":
return filepath.Join(workDir, "QWEN.md")
case "codex", "copilot", "opencode", "deveco", "openclaw", "hermes", "pi", "cursor", "kimi", "kiro", "antigravity", "qoder", "traecli", "grok":
case "codex", "copilot", "opencode", "deveco", "openclaw", "hermes", "pi", "cursor", "kimi", "kiro", "antigravity", "qoder", "traecli", "grok", "devin":
return filepath.Join(workDir, "AGENTS.md")
default:
return ""
Expand Down
2 changes: 2 additions & 0 deletions server/internal/daemon/local_skills.go
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,8 @@ func localSkillRootsForProvider(provider string) ([]localSkillRoot, bool, error)
qwenHome = filepath.Join(home, ".qwen")
}
providerRoot = filepath.Join(qwenHome, "skills")
case "devin":
providerRoot = filepath.Join(home, ".config", "devin", "skills")
default:
return nil, false, nil
}
Expand Down
8 changes: 8 additions & 0 deletions server/migrations/212_runtime_profile_add_devin.down.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
ALTER TABLE runtime_profile DROP CONSTRAINT IF EXISTS runtime_profile_protocol_family_check;

ALTER TABLE runtime_profile ADD CONSTRAINT runtime_profile_protocol_family_check
CHECK (protocol_family IN (
'claude', 'codebuddy', 'codex', 'copilot', 'opencode', 'openclaw',
'hermes', 'pi', 'cursor', 'kimi', 'kiro', 'antigravity', 'qoder',
'traecli', 'deveco', 'grok', 'qwen'
)) NOT VALID;
8 changes: 8 additions & 0 deletions server/migrations/212_runtime_profile_add_devin.up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
ALTER TABLE runtime_profile DROP CONSTRAINT IF EXISTS runtime_profile_protocol_family_check;

ALTER TABLE runtime_profile ADD CONSTRAINT runtime_profile_protocol_family_check
CHECK (protocol_family IN (
'claude', 'codebuddy', 'codex', 'copilot', 'opencode', 'openclaw',
'hermes', 'pi', 'cursor', 'kimi', 'kiro', 'antigravity', 'qoder',
'traecli', 'deveco', 'grok', 'qwen', 'devin'
)) NOT VALID;
6 changes: 5 additions & 1 deletion server/pkg/agent/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@ var SupportedTypes = []string{
"traecli",
"grok",
"qwen",
"devin",
}

// IsSupportedType reports whether agentType is in the SupportedTypes whitelist.
Expand Down Expand Up @@ -302,8 +303,10 @@ func New(agentType string, cfg Config) (Backend, error) {
return &grokBackend{cfg: cfg}, nil
case "qwen":
return &qwenBackend{cfg: cfg}, nil
case "devin":
return &devinBackend{cfg: cfg}, nil
default:
return nil, fmt.Errorf("unknown agent type: %q (supported: claude, codebuddy, codex, copilot, opencode, deveco, openclaw, hermes, pi, cursor, kimi, kiro, antigravity, qoder, traecli, grok, qwen)", agentType)
return nil, fmt.Errorf("unknown agent type: %q (supported: claude, codebuddy, codex, copilot, opencode, deveco, openclaw, hermes, pi, cursor, kimi, kiro, antigravity, qoder, traecli, grok, qwen, devin)", agentType)
}
}

Expand Down Expand Up @@ -336,6 +339,7 @@ var launchHeaders = map[string]string{
"traecli": "traecli acp serve",
"grok": "grok agent stdio",
"qwen": "qwen -p (stream-json)",
"devin": "devin acp",
}

// LaunchHeader returns the user-visible launch skeleton for agentType, or an
Expand Down
2 changes: 1 addition & 1 deletion server/pkg/agent/agent_supported_types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ func TestSupportedTypesMatchesMigrationWhitelist(t *testing.T) {
"claude": true, "codebuddy": true, "codex": true, "copilot": true,
"opencode": true, "deveco": true, "openclaw": true, "hermes": true,
"pi": true, "cursor": true, "kimi": true, "kiro": true, "antigravity": true,
"qoder": true, "traecli": true, "grok": true, "qwen": true,
"qoder": true, "traecli": true, "grok": true, "qwen": true, "devin": true,
}
if len(SupportedTypes) != len(want) {
t.Fatalf("SupportedTypes has %d entries, migration whitelist has %d; keep them in lockstep", len(SupportedTypes), len(want))
Expand Down
Loading
Loading