Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
82 commits
Select commit Hold shift + click to select a range
e348c0e
feat(providers): add Devin as a first-class provider
arul28 Sep 17, 2026
57ab006
feat(devin): use Cognition mark for all Devin surfaces, drop sidebar …
arul28 Sep 17, 2026
aa91065
ci(typecheck-desktop): raise Node heap to 8GB for desktop tsc
arul28 Sep 17, 2026
a62ccdc
fix(devin): second-round review fixes — pagination, pull repo check, …
arul28 Sep 17, 2026
be155fa
Merge branch 'main' into devin/1789603059-devin-provider
arul28 Sep 21, 2026
4383c66
fix(devin): keep cloud-turn completion pending until provably termina…
arul28 Sep 21, 2026
e8abd68
fix(devin): wire cloud commands into the remote router; address revie…
arul28 Sep 21, 2026
b81cdc7
fix(devin): verify() gates multi-org tokens too; v1 mirrors keep full…
arul28 Sep 21, 2026
b800673
fix(devin): page through all orgs during verify and org resolution
arul28 Sep 21, 2026
2bcb43b
chore: retrigger flaky desktop test shards
arul28 Sep 21, 2026
7813414
chore: retrigger flaky desktop test shard
arul28 Sep 21, 2026
775e2da
merge: main into devin-provider
arul28 Sep 21, 2026
86298f9
merge: main into devin-provider
arul28 Sep 21, 2026
0707361
fix(devin): disarm fleet delete on dismiss, clear stale org on v1, la…
arul28 Sep 21, 2026
b259b1a
fix(devin): verify lane branch is published before naming it to Devin
arul28 Sep 21, 2026
774f6f2
fix(devin): require remote lane branch to match HEAD before prompting…
arul28 Sep 21, 2026
b336e86
Merge remote-tracking branch 'origin/main' into devin/1789603059-devi…
arul28 Sep 21, 2026
80cc855
feat(devin): VM actions via devin CLI + platform picker on cloud create
arul28 Sep 21, 2026
9e7d6a1
fix(devin): platform on remote create, eventId-only remote dedupe, su…
arul28 Sep 21, 2026
bc9a729
fix(devin): make cloud echo dedupe durable across restarts
arul28 Sep 21, 2026
388146c
fix(devin): record the matched local fingerprint when consuming a clo…
arul28 Sep 21, 2026
6442c4c
fix(devin): verify PATs on non-enterprise accounts by probing the con…
arul28 Sep 21, 2026
73614ae
docs(devin): note org id entry for non-enterprise accounts
arul28 Sep 21, 2026
e6900e1
fix(devin): add org id field to Devin Cloud settings so non-enterpris…
arul28 Sep 21, 2026
5a7309e
fix(desktop): populate fleet Mine filter via /v3/self caller identity
arul28 Sep 21, 2026
3472136
fix(desktop): retry /v3/self after transient failures, drop stale clo…
arul28 Sep 21, 2026
ead40bf
fix(desktop): broaden attachment markup sniffing (BOM, comments, bare…
arul28 Sep 21, 2026
581a0ef
fix(desktop): flag markup only at document root so inline tags in tex…
arul28 Sep 21, 2026
5fc9583
fix(desktop): peel processing instructions at first > per HTML bogus-…
arul28 Sep 21, 2026
13da848
Merge remote-tracking branch 'origin/main' into devin/1789603059-devi…
arul28 Sep 22, 2026
5f7c7fa
fix(desktop): harden devin cloud pull/mirror edge cases
arul28 Sep 22, 2026
7ef66ca
chore: retrigger ci
arul28 Sep 22, 2026
44f8b7e
Merge remote-tracking branch 'origin/main' into devin/1789603059-devi…
arul28 Sep 22, 2026
dbf9cd9
chore: retrigger ci
arul28 Sep 22, 2026
9595bd1
fix(ade-cli): allow empty apiKey in remote setDevinCloudCredentials
arul28 Sep 22, 2026
32993a9
merge main into devin-provider branch
arul28 Sep 23, 2026
5da958a
fix(desktop): devin manifest entries, xai color, page-shape validatio…
arul28 Sep 23, 2026
689f683
merge main into devin-provider branch
arul28 Sep 23, 2026
9b45c86
fix merge fallout in chat header accessory
arul28 Sep 23, 2026
607f6ff
fix(desktop): devin review round — PR repo matching, env-key status, …
arul28 Sep 23, 2026
cba0e29
chore: retrigger ci (shard-8 flake)
arul28 Sep 23, 2026
6303a79
Merge remote-tracking branch 'origin/main' into devin/1789603059-devi…
arul28 Sep 23, 2026
8764952
fix(desktop): devin review — hard-fail repo-less cloud launch, XDG cr…
arul28 Sep 23, 2026
8416953
fix(sync): gate Devin credential writes with localOnly policy
arul28 Sep 23, 2026
90feb49
fix(devin): deliver cloud-send attachments and persist mirror state
arul28 Sep 23, 2026
9062afb
fix(devin): hold send slot across attachment uploads
arul28 Sep 23, 2026
80fcb1a
fix(devin): release send slot on any pre-dispatch failure
arul28 Sep 23, 2026
fd2f0ce
chore: retrigger CI for flaky modelManifestService shard
arul28 Sep 23, 2026
6cc88aa
Merge remote-tracking branch 'origin/main' into devin/1789603059-devi…
arul28 Sep 23, 2026
35d195e
fix(devin): adopt main's usage/account dialect surface after merge
arul28 Sep 23, 2026
d766060
fix(devin): close review gaps in creds, auth detection, and echo dedupe
arul28 Sep 23, 2026
507ea46
fix(devin): dedupe echoes on the delivered fingerprint, pin XDG override
arul28 Sep 23, 2026
9168888
fix(devin): let cleared pending echoes stay cleared
arul28 Sep 23, 2026
a1af8aa
Merge branch 'main' into devin/1789603059-devin-provider
arul28 Sep 24, 2026
f57c647
fix(desktop): devin cloud attachments, relink guard, handoff title, s…
arul28 Sep 24, 2026
37d78de
chore: retrigger CI for flaky agentChatService watchdog shard
arul28 Sep 24, 2026
a530e30
Merge branch 'main' into devin/1789603059-devin-provider
arul28 Sep 24, 2026
80f1b60
fix(desktop): route Devin fleet modal through shared Dialog primitive
arul28 Sep 24, 2026
6ffe1c3
fix(devin): mine filter on v1 keys, bare-slug repo match, pull-branch…
arul28 Sep 24, 2026
335dfd3
fix(devin): handoff attachments, pending-done restart, unbounded link…
arul28 Sep 24, 2026
4ab1745
Merge remote-tracking branch 'origin/main' into devin/1789603059-devi…
arul28 Sep 24, 2026
a16f6ca
fix(ui): route Mac Desktop overlays through shared primitives
arul28 Sep 24, 2026
7c78d6d
fix(devin): close follow-up, fleet, auth, and composer gaps from review
arul28 Sep 24, 2026
2a496d1
fix(devin): terminal-only done, branch restore, prompt repo binding
arul28 Sep 24, 2026
6be1e31
fix(devin): serialize lane pulls, recheck attachment size after read
arul28 Sep 24, 2026
c2a7e4c
merge main: adopt shared overlay layers for mac desktop surfaces
arul28 Sep 24, 2026
ca74db7
fix(devin): handle pull chain cleanup on rejection
arul28 Sep 24, 2026
2ea78a1
fix(desktop): unblock linked Devin cloud sends and attachment-only la…
arul28 Sep 24, 2026
1334720
fix(desktop): align linked-cloud send gating with the submit predicate
arul28 Sep 24, 2026
520d82e
fix(desktop): split cloud send predicate by launch vs linked reply
arul28 Sep 24, 2026
0b44d89
Merge remote-tracking branch 'origin/main' into devin/1789603059-devi…
arul28 Sep 24, 2026
743d2e1
fix(desktop): keep the Devin fleet button's visibility poll while con…
arul28 Sep 24, 2026
0cbac67
fix(providers): harden the Devin integration against the live API
arul28 Sep 25, 2026
b553d28
Merge remote-tracking branch 'origin/main' into devin/1789603059-devi…
arul28 Sep 25, 2026
a306e7e
feat(providers): discover Devin models from the CLI catalog
arul28 Sep 25, 2026
b276c62
feat(settings): one-card provider pages, back arrow on the title row,…
arul28 Sep 28, 2026
ab051ee
feat(cloud): cloud lanes and one cloud agents panel for Devin and Cursor
arul28 Sep 28, 2026
f77ca09
quality: apply review fixes
arul28 Sep 28, 2026
82efce2
fix(cloud): keep Cursor's service tier off Devin chats; a failed laun…
arul28 Sep 28, 2026
6c432f4
Merge remote-tracking branch 'origin/main' into devin/1789603059-devi…
arul28 Sep 28, 2026
5933ea9
fix(cloud): restore provider controls and align cloud lane tests
arul28 Sep 28, 2026
4a86a6e
test(preload): allow continued polling after epoch reset
arul28 Sep 28, 2026
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
4 changes: 4 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,10 @@ jobs:
packages/chat-ui/node_modules
key: nm-v3-${{ hashFiles('apps/desktop/package-lock.json','apps/ade-cli/package-lock.json','apps/web/package-lock.json','apps/webhook-relay/package-lock.json','apps/push-relay/package-lock.json','packages/sdk/package-lock.json','packages/chat-ui/package-lock.json') }}
- run: cd apps/desktop && npm run typecheck
env:
# tsc on the desktop codebase sits near Node's default ~4GB heap;
# the lint script already needs --max-old-space-size=8192.
NODE_OPTIONS: --max-old-space-size=8192

typecheck-ade-cli:
needs: install
Expand Down
2 changes: 1 addition & 1 deletion apps/ade-cli/src/adeRpcServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -404,7 +404,7 @@ const TOOL_SPECS: ToolSpec[] = [
additionalProperties: false,
properties: {
laneId: { type: "string", minLength: 1 },
provider: { type: "string", enum: ["claude", "codex", "cursor", "droid", "opencode", "pi", "qwen", "kimi", "grok", "copilot", "shell"] },
provider: { type: "string", enum: ["claude", "codex", "cursor", "droid", "opencode", "pi", "qwen", "kimi", "grok", "copilot", "devin", "shell"] },
permissionMode: { type: "string", enum: [...AGENT_CHAT_PERMISSION_MODE_VALUES], default: "default" },
droidPermissionMode: { type: "string", enum: [...AGENT_CHAT_DROID_PERMISSION_MODE_VALUES] },
title: { type: "string" },
Expand Down
16 changes: 16 additions & 0 deletions apps/ade-cli/src/bootstrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,8 @@ import { createLinearAccessTokenGetter, createLinearIngressService } from "../..
import { buildLinearAutomationDispatches } from "../../desktop/src/main/services/automations/linearAutomationDispatch";
import { createCursorCloudIngressService } from "../../desktop/src/main/services/automations/cursorCloudIngressService";
import { createCursorCloudFleetService } from "../../desktop/src/main/services/chat/cursorCloudFleetService";
import { createCloudAgentsServiceFromHost, type CloudAgentsService } from "../../desktop/src/main/services/chat/cloudAgentsService";
import { resolveDevinCloudBinary } from "../../desktop/src/main/services/chat/devinCloudBinary";
import { buildCursorCloudAutomationDispatches } from "../../desktop/src/main/services/automations/cursorCloudAutomationDispatch";
import { openCursorCloudCredentialStore } from "../../desktop/src/main/services/chat/cursorCloudCreateOptions";
import { createAutomationSecretService } from "../../desktop/src/main/services/automations/automationSecretService";
Expand Down Expand Up @@ -389,6 +391,7 @@ export type AdeRuntime = {
agentChatService?: ReturnType<typeof createAgentChatService> | null;
chatLaunchService?: ChatLaunchService | null;
cursorCloudFleetService?: ReturnType<typeof createCursorCloudFleetService> | null;
cloudAgentsService?: CloudAgentsService | null;
prService?: ReturnType<typeof createPrService>;
prSummaryService?: ReturnType<typeof createPrSummaryService> | null;
fileService?: ReturnType<typeof createFileService> | null;
Expand Down Expand Up @@ -2316,6 +2319,17 @@ export async function createAdeRuntime(args: {
return { state: status.state, lastEventAt: status.lastEventAt };
},
});
const cloudAgentsService = createCloudAgentsServiceFromHost({
projectRoot,
logger,
laneService,
getAgentChatService: () => agentChatService ?? null,
cursorFleet: cursorCloudFleetService,
archiveCursorAgent: (agentId) => aiIntegrationService.archiveCursorCloudAgent(agentId),
unarchiveCursorAgent: (agentId) => aiIntegrationService.unarchiveCursorCloudAgent(agentId),
cursorCreateRun: (args) => aiIntegrationService.createCursorCloudRun({ ...args, workOnCurrentBranch: true }),
resolveDevinBinary: resolveDevinCloudBinary,
});
const configReloadService = createConfigReloadService({
paths: {
sharedPath: adeProjectService.paths.sharedConfigPath,
Expand Down Expand Up @@ -2757,6 +2771,7 @@ export async function createAdeRuntime(args: {
agentChatService,
chatLaunchService,
cursorCloudFleetService,
cloudAgentsService,
pushPublisherService,
ctoStateService,
ctoMemoryService,
Expand Down Expand Up @@ -2934,6 +2949,7 @@ export async function createAdeRuntime(args: {
agentChatService,
chatLaunchService,
cursorCloudFleetService,
cloudAgentsService,
ctoStateService,
ctoMemoryService,
ctoVoiceCallService,
Expand Down
2 changes: 1 addition & 1 deletion apps/ade-cli/src/cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3951,7 +3951,7 @@ describe("ADE CLI", () => {
"shell",
"--print-config",
]),
).toThrow(/provider must be one of claude, codex, cursor, droid, opencode, pi, qwen, kimi, grok, or copilot/);
).toThrow(/provider must be one of claude, codex, cursor, droid, opencode, pi, qwen, kimi, grok, copilot, or devin/);
});

it("accepts a mis-cased provider when starting a CLI session", () => {
Expand Down
16 changes: 16 additions & 0 deletions apps/ade-cli/src/services/agentRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,22 @@ export const AGENT_CLI_REGISTRY: AgentCliDescriptor[] = [
/\bgh[_ ]token\b.*\b(invalid|missing|not found|not set|required|unauthorized|must be set)\b/i,
],
},
{
agent: "devin",
displayName: "Devin CLI",
binaryNames: ["devin"],
installCommand: "curl -fsSL https://cli.devin.ai/install.sh | bash",
authCommand: "devin auth login",
missingErrorPatterns: [
/\bdevin\b.*\b(command not found|not recognized|not found|enoent)\b/i,
/\bspawn\s+devin\s+enoent\b/i,
],
notAuthErrorPatterns: [
/\bdevin\b.*\b(not logged in|not authenticated|unauthorized|authentication failed|login required|no credentials|sign\s*in)\b/i,
/\brun\s+[`'"]?devin\s+auth\s+login[`'"]?/i,
/\bwindsurf[_ ]api[_ ]key\b.*\b(invalid|missing|not found|not set|required|unauthorized|must be set)\b/i,
],
},
];

function descriptorMatchesPreferred(descriptor: AgentCliDescriptor, preferredAgent: string | null | undefined): boolean {
Expand Down
8 changes: 8 additions & 0 deletions apps/ade-cli/src/services/sync/syncHostService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6480,6 +6480,10 @@ describe("CTO-gated Linear sync commands", () => {
// both are host state mutations refused to read-only viewers.
"ai.cursorCloudResolveLane",
"ai.cursorCloudPullIntoLane",
"ai.openCloudAgent",
"ai.stopCloudAgent",
"ai.archiveCloudAgent",
"ai.launchCloudAgent",
// Resuming spends a provider turn, so it is a host mutation a
// read-only viewer never gets to make.
"chat.resumeUsageLimitNow",
Expand All @@ -6506,6 +6510,10 @@ describe("CTO-gated Linear sync commands", () => {
"ai.cursorCloudResolveLane",
"ai.cursorCloudPullIntoLane",
"ai.cursorCloudStopRun",
"ai.openCloudAgent",
"ai.stopCloudAgent",
"ai.archiveCloudAgent",
"ai.launchCloudAgent",
"macDesktop.start",
"macDesktop.stop",
"macDesktop.takeControl",
Expand Down
3 changes: 3 additions & 0 deletions apps/ade-cli/src/services/sync/syncHostService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ import type { AccountAttestationConfig } from "../account/sharedAccountAuthServi
import { verifyClerkAccountAttestation } from "../account/accountAttestationVerifier";
import type { createAgentChatService } from "../../../../desktop/src/main/services/chat/agentChatService";
import type { createCursorCloudFleetService } from "../../../../desktop/src/main/services/chat/cursorCloudFleetService";
import type { CloudAgentsService } from "../../../../desktop/src/main/services/chat/cloudAgentsService";
import type { createAiIntegrationService } from "../../../../desktop/src/main/services/ai/aiIntegrationService";
import type { createCtoStateService } from "../../../../desktop/src/main/services/cto/ctoStateService";
import type { CtoMemoryService } from "../../../../desktop/src/main/services/cto/ctoMemoryService";
Expand Down Expand Up @@ -1138,6 +1139,7 @@ type SyncHostServiceArgs = {
agentChatService?: ReturnType<typeof createAgentChatService>;
chatLaunchService?: ChatLaunchService | null;
cursorCloudFleetService?: ReturnType<typeof createCursorCloudFleetService> | null;
cloudAgentsService?: CloudAgentsService | null;
personalChatScope?: Pick<
PersonalChatScopeContract,
"call" | "streamEvents" | "transcriptPath" | "isTurnActive"
Expand Down Expand Up @@ -2403,6 +2405,7 @@ export function createSyncHostService(args: SyncHostServiceArgs) {
agentChatService: args.agentChatService,
chatLaunchService: args.chatLaunchService,
cursorCloudFleetService: args.cursorCloudFleetService,
cloudAgentsService: args.cloudAgentsService,
personalChatScope: args.personalChatScope,
aiIntegrationService: args.aiIntegrationService,
accountSettingsStore: args.accountSettingsStore,
Expand Down
91 changes: 91 additions & 0 deletions apps/ade-cli/src/services/sync/syncRemoteCommandService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,7 @@ import { buildAiSettingsStatus, getUnavailableAiStatus, isDatabaseClosedError }
import type { createAiIntegrationService } from "../../../../desktop/src/main/services/ai/aiIntegrationService";
import type { createAgentChatService } from "../../../../desktop/src/main/services/chat/agentChatService";
import type { createCursorCloudFleetService } from "../../../../desktop/src/main/services/chat/cursorCloudFleetService";
import type { CloudAgentsService } from "../../../../desktop/src/main/services/chat/cloudAgentsService";
import { resolveSmartLinkPreview } from "../../../../desktop/src/main/services/chat/smartLinkPreviewService";
import { getSourceFaviconService } from "../../../../desktop/src/main/services/chat/sourceFaviconService";
import {
Expand Down Expand Up @@ -551,6 +552,7 @@ type SyncRemoteCommandServiceArgs = {
agentChatService?: ReturnType<typeof createAgentChatService>;
chatLaunchService?: ChatLaunchService | null;
cursorCloudFleetService?: ReturnType<typeof createCursorCloudFleetService> | null;
cloudAgentsService?: CloudAgentsService | null;
personalChatScope?: Pick<PersonalChatScopeContract, "call" | "streamEvents">;
ctoStateService?: ReturnType<typeof createCtoStateService> | null;
ctoMemoryService?: CtoMemoryService | null;
Expand Down Expand Up @@ -3397,6 +3399,7 @@ const MODEL_CATALOG_REFRESH_PROVIDERS = new Set<AgentChatModelCatalogRefreshProv
"kimi",
"grok",
"copilot",
"devin",
]);

function parseChatModelCatalogArgs(value: Record<string, unknown>): AgentChatModelCatalogArgs {
Expand Down Expand Up @@ -3907,6 +3910,39 @@ async function unarchiveLaneWithRuntimeSetup(
return { ok: true };
}

async function resolveChatCreateArgs<T extends AgentChatCreateArgs>(
service: ReturnType<typeof createAgentChatService>,
payload: T,
): Promise<T> {
if (payload.model.trim().length > 0) return payload;
const available = await service.getAvailableModels({
provider: payload.provider,
// ACP providers are here for the same reason as OpenCode/Pi: their model
// rows are gated on a CLI auth pass, and `activateRuntime` refreshes that
// pass. It does not spawn an agent for them.
...(
payload.provider === "opencode"
|| payload.provider === "pi"
|| payload.provider === "qwen"
|| payload.provider === "kimi"
|| payload.provider === "grok"
|| payload.provider === "copilot"
|| payload.provider === "devin"
? { activateRuntime: true }
: {}
),
});
const chosen = available[0];
if (!chosen) {
throw new Error(`No configured ${payload.provider} chat model is available on the host.`);
}
return {
...payload,
model: chosen.id,
...(!payload.modelId && chosen.modelId ? { modelId: chosen.modelId } : {}),
};
}

function sessionStatusBucket(argsIn: {
status: string;
runtimeState?: string | null;
Expand Down Expand Up @@ -6529,6 +6565,61 @@ function registerMiscRemoteCommands({ args, register }: RemoteCommandRegistratio
requireString(payload.agentId, "ai.cursorCloudStopRun requires agentId."),
);
});

// Cloud agents (Devin Cloud and Cursor Cloud): one contract for both
// providers, the same one the desktop reaches over `ade.cloudAgents.*`. The
// list is read-only; open can create a lane and a chat, and stop, archive
// and launch act on the provider, so those are refused for viewers and run
// now rather than queue.
const cloudAgents = () => requireService(args.cloudAgentsService, "Cloud agents are not available.");
register("ai.listCloudAgents", { viewerAllowed: true }, async (payload) =>
cloudAgents().list({
provider: requireCloudAgentProvider(payload.provider, "ai.listCloudAgents"),
...(payload.force === true ? { force: true } : {}),
}));
register("ai.openCloudAgent", { viewerAllowed: false, controllerAllowed: true, queueable: false }, async (payload) =>
cloudAgents().open({
provider: requireCloudAgentProvider(payload.provider, "ai.openCloudAgent"),
id: requireCloudAgentId(payload.id, "ai.openCloudAgent"),
}));
register("ai.stopCloudAgent", { viewerAllowed: false, controllerAllowed: true, queueable: false }, async (payload) =>
cloudAgents().stop({
provider: requireCloudAgentProvider(payload.provider, "ai.stopCloudAgent"),
id: requireCloudAgentId(payload.id, "ai.stopCloudAgent"),
}));
register("ai.archiveCloudAgent", { viewerAllowed: false, controllerAllowed: true, queueable: false }, async (payload) =>
cloudAgents().archive({
provider: requireCloudAgentProvider(payload.provider, "ai.archiveCloudAgent"),
id: requireCloudAgentId(payload.id, "ai.archiveCloudAgent"),
archived: payload.archived !== false,
}));
register("ai.launchCloudAgent", { viewerAllowed: false, controllerAllowed: true, queueable: false }, async (payload) => {
const prompt = requireString(payload.prompt, "ai.launchCloudAgent requires prompt.");
if (prompt.length > 100_000) throw new Error("ai.launchCloudAgent prompt is too long.");
const shortOptional = (value: unknown, name: string): string | null => {
if (typeof value !== "string" || !value.trim()) return null;
if (value.length > 256) throw new Error(`ai.launchCloudAgent ${name} is too long.`);
return value.trim();
};
return cloudAgents().launch({
provider: requireCloudAgentProvider(payload.provider, "ai.launchCloudAgent"),
prompt,
model: shortOptional(payload.model, "model"),
platform: shortOptional(payload.platform, "platform"),
laneId: shortOptional(payload.laneId, "laneId"),
});
});
}

function requireCloudAgentProvider(value: unknown, action: string): "devin" | "cursor" {
if (value === "devin" || value === "cursor") return value;
throw new Error(`${action} requires provider 'devin' or 'cursor'.`);
}

function requireCloudAgentId(value: unknown, action: string): string {
const id = requireString(value, `${action} requires id.`).trim();
if (id.length > 256) throw new Error(`${action} id is too long.`);
return id;
}

export type PrRefreshSnapshotScope = "all" | "active" | "none";
Expand Down
4 changes: 4 additions & 0 deletions apps/ade-cli/src/services/sync/syncService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
import type { Logger } from "../../../../desktop/src/main/services/logging/logger";
import type { createAgentChatService } from "../../../../desktop/src/main/services/chat/agentChatService";
import type { createCursorCloudFleetService } from "../../../../desktop/src/main/services/chat/cursorCloudFleetService";
import type { CloudAgentsService } from "../../../../desktop/src/main/services/chat/cloudAgentsService";
import type { createAiIntegrationService } from "../../../../desktop/src/main/services/ai/aiIntegrationService";
import type { createCtoStateService } from "../../../../desktop/src/main/services/cto/ctoStateService";
import type { CtoMemoryService } from "../../../../desktop/src/main/services/cto/ctoMemoryService";
Expand Down Expand Up @@ -155,6 +156,7 @@ type SyncServiceArgs = {
agentChatService: ReturnType<typeof createAgentChatService>;
chatLaunchService?: ChatLaunchService | null;
cursorCloudFleetService?: ReturnType<typeof createCursorCloudFleetService> | null;
cloudAgentsService?: CloudAgentsService | null;
personalChatScope?: PersonalChatScopeContract;
/** Brain→push-relay publisher; threaded to the runtime remote-command service. */
pushPublisherService?: PushPublisherService | null;
Expand Down Expand Up @@ -803,6 +805,7 @@ export function createSyncService(args: SyncServiceArgs) {
agentChatService: args.agentChatService,
chatLaunchService: args.chatLaunchService,
cursorCloudFleetService: args.cursorCloudFleetService,
cloudAgentsService: args.cloudAgentsService,
personalChatScope: args.personalChatScope,
pushPublisherService: args.pushPublisherService,
ctoStateService: args.ctoStateService,
Expand Down Expand Up @@ -954,6 +957,7 @@ export function createSyncService(args: SyncServiceArgs) {
agentChatService: args.agentChatService,
chatLaunchService: args.chatLaunchService,
cursorCloudFleetService: args.cursorCloudFleetService,
cloudAgentsService: args.cloudAgentsService,
aiIntegrationService: args.aiIntegrationService,
accountSettingsStore: args.accountSettingsStore,
pushPublisherService: args.pushPublisherService,
Expand Down
3 changes: 2 additions & 1 deletion apps/ade-cli/src/tuiClient/adeApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -460,6 +460,7 @@ const CHAT_BACKED_TERMINAL_TOOL_TYPES = new Set([
"kimi-chat",
"grok-chat",
"copilot-chat",
"devin-chat",
]);

const TRACKED_CLI_PROVIDERS = new Set<AdeCodeProvider>([
Expand Down Expand Up @@ -555,7 +556,7 @@ export async function signalTerminal(
/** Provider CLIs the TUI can launch as tracked terminal sessions. */
export type CliTerminalProvider = Extract<
AdeCodeProvider,
"claude" | "codex" | "cursor" | "droid" | "opencode" | "pi" | "qwen" | "kimi" | "grok" | "copilot"
"claude" | "codex" | "cursor" | "droid" | "opencode" | "pi" | "qwen" | "kimi" | "grok" | "copilot" | "devin"
>;

export type StartCliTerminalSessionResult = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ describe("buildModelPickerLayout", () => {
"claude",
"codex",
"cursor",
"devin",
"opencode",
"pi",
"copilot",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,17 @@ export const PROVIDER_ORDER: readonly AdeCodeProvider[] = MODEL_PICKER_PROVIDER_
const RAIL_PROVIDER_ORDER: readonly AdeCodeProvider[] = PROVIDER_ORDER;

/**
* The four ACP providers. They report through the same optional
* The ACP providers. They report through the same optional
* `availableProviders` / `providerConnections` / `models` slots, so one list
* drives the greying arm instead of four copies of it. Mirrors
* `ACP_PICKER_FAMILIES` in desktop's useProviderAuthStatus.
*/
const ACP_PROVIDERS: readonly Extract<AdeCodeProvider, "qwen" | "kimi" | "grok" | "copilot">[] = [
const ACP_PROVIDERS: readonly Extract<AdeCodeProvider, "qwen" | "kimi" | "grok" | "copilot" | "devin">[] = [
"qwen",
"kimi",
"grok",
"copilot",
"devin",
];

function isAcpProvider(provider: AdeCodeProvider): provider is (typeof ACP_PROVIDERS)[number] {
Expand Down Expand Up @@ -168,6 +169,7 @@ const PROVIDER_BY_CATALOG_GROUP: Record<ProviderGroupKey, AdeCodeProvider> = {
kimi: "kimi",
grok: "grok",
copilot: "copilot",
devin: "devin",
opencode: "opencode",
ollama: "ollama",
lmstudio: "lmstudio",
Expand Down
4 changes: 4 additions & 0 deletions apps/ade-cli/src/tuiClient/providerMetadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const TUI_PROVIDER_LABELS: Record<AdeCodeProvider, string> = {
droid: "Droid",
kimi: "Kimi",
qwen: "Qwen",
devin: "Devin",
ollama: "Ollama",
lmstudio: "LM Studio",
};
Expand All @@ -34,6 +35,7 @@ const PROVIDER_FAMILY_LABELS: Record<AdeCodeProvider, string> = {
kimi: "Moonshot",
grok: "xAI",
copilot: "GitHub Copilot",
devin: "Devin",
ollama: "Ollama",
lmstudio: "LM Studio",
};
Expand All @@ -57,6 +59,7 @@ export const PROVIDER_TOKEN_LABELS: Record<string, string> = {
droid: "Droid",
factory: "Droid",
cursor: "Cursor",
devin: "Devin",
qwen: "Qwen",
copilot: "GitHub Copilot",
githubcopilot: "GitHub Copilot",
Expand Down Expand Up @@ -141,6 +144,7 @@ const REFRESH_PROVIDERS: Record<AdeCodeProvider, AgentChatModelCatalogRefreshPro
kimi: "kimi",
grok: "grok",
copilot: "copilot",
devin: "devin",
ollama: "ollama",
lmstudio: "lmstudio",
};
Expand Down
1 change: 1 addition & 0 deletions apps/ade-cli/src/tuiClient/remoteLauncher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1016,6 +1016,7 @@ const CHAT_BACKED_REMOTE_TOOL_TYPES = new Set([
"kimi-chat",
"grok-chat",
"copilot-chat",
"devin-chat",
]);

const TRACKED_CLI_REMOTE_TOOL_TYPE_PREFIXES = [
Expand Down
Loading
Loading