feat(providers): add Devin as a first-class provider - #1251
devin-ai-integration[bot] wants to merge 4 commits into
Conversation
Devin joins ADE as one provider id covering local and cloud. Local: devin acp runs as an ACP dialect in the shared host for native Work chats, and the tracked devin CLI row gives PTY sessions with resume. Cloud: the v3 Sessions API powers an org-wide fleet view (list with repo/tag filters, provenance chips Mine/From ADE/All), mirrored transcript chats over GET/POST messages, lane-bound session creation with ade/ade:lane:<id> tags and a devin_mode picker, terminate + archive/unarchive, pull-into-lane for pushed branches/PRs, and a built-in-browser live view via session.url. Cloud sessions join the attention system (waiting_for_user -> Needs you) and sync Devin attachments into the proof drawer. Hand off to Devin Cloud packages lane context into a cloud session; Continue in lane seeds a local CLI from a pulled session. Auth is a pasted v3 PAT (cog_) with a v1 personal-key fallback; CLI chats use devin auth login. Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
|
Important Review skippedBot user detected. To trigger a single review, invoke the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Advanced Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
| >; | ||
| agentChatService: ReturnType<typeof createAgentChatService>; | ||
| cursorCloudFleetService?: ReturnType<typeof createCursorCloudFleetService> | null; | ||
| devinCloudFleetService?: ReturnType<typeof createDevinCloudFleetService> | null; |
There was a problem hiding this comment.
The sync service accepts a devinCloudFleetService, but it does not pass that service to the remote command router. The router also accepts no Devin fleet service and registers no Devin Cloud handlers. Consequently, the Devin Cloud actions listed as optional mobile capabilities are never advertised or executable for paired mobile and web clients.
Knowledge Base Used:
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/ade-cli/src/services/sync/syncService.ts
Line: 148
Comment:
**Remote commands are missing**
The sync service accepts a `devinCloudFleetService`, but it does not pass that service to the remote command router. The router also accepts no Devin fleet service and registers no Devin Cloud handlers. Consequently, the Devin Cloud actions listed as optional mobile capabilities are never advertised or executable for paired mobile and web clients.
**Knowledge Base Used:**
- [CLI runtime and RPC host](https://app.greptile.com/versic/-/custom-context/knowledge-base/arul28/ade/-/docs/cli-runtime.md)
- [Desktop application shell](https://app.greptile.com/versic/-/custom-context/knowledge-base/arul28/ade/-/docs/desktop-application.md)
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.| const page = await aiIntegrationService.listDevinCloudMessages({ | ||
| devinSessionId, | ||
| first: 200, | ||
| }); |
There was a problem hiding this comment.
Cloud transcripts are truncated
Hydration always requests one page of 200 messages and ignores the pagination cursor returned by the client. Once a Devin session exceeds 200 messages, some of its conversation can never be mirrored into ADE, whether the API returns the oldest or newest page. This leaves the local transcript incomplete. Follow endCursor until the relevant history has been consumed and retain a durable watermark.
Knowledge Base Used:
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src/main/services/chat/agentChatService.ts
Line: 43344-43347
Comment:
**Cloud transcripts are truncated**
Hydration always requests one page of 200 messages and ignores the pagination cursor returned by the client. Once a Devin session exceeds 200 messages, some of its conversation can never be mirrored into ADE, whether the API returns the oldest or newest page. This leaves the local transcript incomplete. Follow `endCursor` until the relevant history has been consumed and retain a durable watermark.
**Knowledge Base Used:**
- [Desktop main process and runtime integration](https://app.greptile.com/versic/-/custom-context/knowledge-base/arul28/ade/-/docs/desktop-main-process.md)
- [Desktop application shell](https://app.greptile.com/versic/-/custom-context/knowledge-base/arul28/ade/-/docs/desktop-application.md)
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.| if (items.length > 1) { | ||
| args.logger?.warn?.("devin_cloud.multi_org_defaulting_to_first", { orgId: id }); | ||
| } |
There was a problem hiding this comment.
For a v3 token associated with multiple organizations and no manually supplied organization ID, verification silently chooses and persists the first organization returned by the API. API ordering is not a user ownership choice, so later list, create, message, archive, and terminate operations can run against an unintended organization. Require an explicit selection when discovery returns multiple organizations instead of only logging the ambiguity.
Knowledge Base Used: Desktop main process and runtime integration
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src/main/services/ai/devinCloudClient.ts
Line: 316-318
Comment:
**First organization is assumed**
For a v3 token associated with multiple organizations and no manually supplied organization ID, verification silently chooses and persists the first organization returned by the API. API ordering is not a user ownership choice, so later list, create, message, archive, and terminate operations can run against an unintended organization. Require an explicit selection when discovery returns multiple organizations instead of only logging the ambiguity.
**Knowledge Base Used:** [Desktop main process and runtime integration](https://app.greptile.com/versic/-/custom-context/knowledge-base/arul28/ade/-/docs/desktop-main-process.md)
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.…fleet row, fix review findings Problem - Devin surfaces used a generic diamond icon instead of the Cognition mark, and the Devin fleet duplicated a sidebar row that lives only in the top header for Linear/Cursor. - Devin Review flagged correctness/security issues: seconds-vs-ms timestamps re-sorted remote events, unparseable API bodies were treated as empty success, attachments downloaded without a size cap, cloud sends ran a runtime-backed readiness gate and stayed 'active', pull-into-lane imported a ref that was never fetched, and credential mutation was missing from the CTO-only action policy. Change and boundary - Swap every Devin glyph (top header button, fleet modal, cloud panel, chat header, composer menus, provider logos) to the Cognition mark via devin.svg; remove the sidebar 'Devin Cloud' nav row (header button only); tighten fleet modal to match the Cursor modal. - Cloud client: parse ISO and seconds/ms epochs, throw DevinCloudResponseError on unparseable non-empty 2xx bodies, verify org id + record/items shape, cap attachment downloads at 50MB. - Cloud sends: cloud-specific readiness (disposed/pending-input/ in-flight), in-flight dedup set, idle transition on success/failure. - Pull-into-lane: fetch refs/pull/<n>/head into refs/heads/<branch> before importBranch for new lanes; fetch into the target worktree so FETCH_HEAD resolves for existing lanes. - Persisted-link lookup in openDevinCloudChat so reopened links reuse the original chat; attachment sync marks 'seen' only after ingest. - Add 'devin-chat' toolType mappings; gate setDevinCloudCredentials as CTO-only; restore cursor-fleet default includeArchived behavior. Verification - npm --prefix apps/desktop run typecheck: clean. - vitest ModelPicker.test.tsx: 73 passed. - eslint on changed files: 0 errors. Built with Devin (Cognition AI). Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Problem - typecheck-desktop OOM'd on this branch (tsc exited 134 after ~110s of GC thrashing at the default ~4GB heap). Change and boundary - NODE_OPTIONS=--max-old-space-size=8192 on the desktop typecheck step only; matches the repo's own precedent (the lint script already runs with an 8GB heap). Other typecheck jobs are unchanged. Built with Devin (Cognition AI). Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…streaming cap, attention ownership Problem - The cloud mirror only read the first page of messages (transcripts stop at 200), emitted 'done' while status was TTL-unknown, cleared attention markers owned by newer sources, and wrote remote attachment names to disk unsanitized. - pullIntoLane merged the project's same-numbered PR when a session's PR belonged to another repo; attachment downloads still buffered the full body when Content-Length was absent; a cleared devinCloudOrgId was resurrected from shared config. - The launch shelf's Devin Cloud machine row showed the generic violet cloud icon instead of the Cognition mark. Change and boundary - Follow listMessages endCursor (repeated-cursor guard) so mirrored transcripts pass 200 messages. - Gate the completion 'done' on a known-terminal status; refresh the remote record once when fresh output arrives with status unknown. - clearAttentionRequest gains an optional expectedSource; the mirror clears only provider_structured markers. - Attachment filenames are reduced to their basename before writing. - pullIntoLane compares the PR URL's repo to the project origin before fetching. - downloadAttachment streams the body with the 50MB cap enforced mid-read; Content-Length is only an early-out. - coerceAiConfig preserves explicit null devinCloudOrgId. - DraftMachineOption gains cloudProvider so the Devin Cloud row renders DevinLogo; cursor row tagged too. Verification - npm --prefix apps/desktop run typecheck: clean. - vitest DraftMachinePicker.test.tsx: 4 passed. - eslint on changed files: 0 errors. Built with Devin (Cognition AI). Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
| const prRepoKey = repoMatchKey(githubPullRepo(prUrl)); | ||
| const projectRepoKey = await originMatchKey(); | ||
| if (!prRepoKey || !projectRepoKey || prRepoKey !== projectRepoKey) { | ||
| throw new Error("This session's pull request is not for this project's repository."); |
There was a problem hiding this comment.
If the project origin uses GitHub’s port-bearing SSH form, such as ssh://git@ssh.github.com:443/owner/repo.git, repoMatchKey includes the port in the repository path. That key cannot equal the PR-derived github.com/owner/repo key, so this check rejects Pull into lane even when the Devin session’s PR belongs to the current repository.
Knowledge Base Used: Desktop main process and runtime integration
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src/main/services/chat/devinCloudFleetService.ts
Line: 278-281
Comment:
**Valid SSH origins rejected**
If the project origin uses GitHub’s port-bearing SSH form, such as `ssh://git@ssh.github.com:443/owner/repo.git`, `repoMatchKey` includes the port in the repository path. That key cannot equal the PR-derived `github.com/owner/repo` key, so this check rejects Pull into lane even when the Devin session’s PR belongs to the current repository.
**Knowledge Base Used:** [Desktop main process and runtime integration](https://app.greptile.com/versic/-/custom-context/knowledge-base/arul28/ade/-/docs/desktop-main-process.md)
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.There was a problem hiding this comment.
Devin Review found 2 new potential issues.
6 flags not posted on this PR by your GitHub settings — view them in Devin Review. (Configure)
| if (liveStatus == null) { | ||
| // A TTL-skipped status read is not evidence of a terminal state — | ||
| // check once when fresh output arrived before deciding the turn | ||
| // ended, or a still-running session would emit `done` early. | ||
| remote = remote ?? await aiIntegrationService.getDevinCloudSession(devinSessionId).catch(() => null); | ||
| liveStatus = remote?.status ?? null; | ||
| } | ||
| if (liveStatus != null && !isDevinCloudSessionLive(liveStatus) && !devinCloudDoneAnnounced.has(managed.session.id)) { |
There was a problem hiding this comment.
🟡 Failed status check loses completion
When the forced status request fails after final output, liveStatus remains null and emits no done event. Later polls deduplicate that output, so the terminal status cannot reenter this block. The cloud turn remains incomplete in ADE.
Learn more
Fresh messages are marked hydrated before the forced status lookup runs. If that lookup fails, this pass emits the messages without completion. Later polls can read the terminal status, but deduplication makes emittedVisible false, and completion handling only runs when it is true. The service therefore needs persistent state for an emitted cloud turn awaiting terminal confirmation.
Example: Devin emits its final answer while the cached session status is unavailable. ADE mirrors the answer, but the status request times out. Three seconds later the API reports finished; ADE deduplicates the answer and emits no done, leaving the turn incomplete.
Recommended fix: Track the pending hydrate turn ID after visible output. On every later poll, emit done when a successful status read becomes terminal, then clear the pending turn and mark completion announced.
Was this helpful? React with 👍 or 👎 to provide feedback.
| const seenCursors = new Set<string>(); | ||
| let cursor = page.endCursor?.trim() ?? ""; | ||
| while (cursor && !seenCursors.has(cursor)) { | ||
| seenCursors.add(cursor); | ||
| const nextPage = await aiIntegrationService.listDevinCloudMessages({ | ||
| devinSessionId, | ||
| first: 200, | ||
| after: cursor, | ||
| }); | ||
| items.push(...nextPage.items); | ||
| cursor = nextPage.endCursor?.trim() ?? ""; |
There was a problem hiding this comment.
🟡 Polling reloads entire cloud transcript
For sessions over 200 messages, attachAndHydrateDevinCloudChat traverses every page on each poll. The mirror watcher keeps polling, so work scales with total history instead of new output. Long chats can hit API limits and stall synchronization.
Learn more
The pagination loop fixes initial transcript truncation, but no remote cursor or durable message watermark survives the hydration call. Every watched poll starts again without after, then follows every cursor to the end. The local event-id set suppresses duplicate rendering only after all remote pages have already been downloaded and accumulated.
Example: A 2,000-message session needs ten 200-row requests for its first hydration. Every later poll repeats those ten requests even when Devin added no messages, instead of requesting only the tail.
Recommended fix: Persist a per-session pagination checkpoint after the initial full hydration and request only pages after that checkpoint. Preserve enough overlap or a durable event watermark to handle cursor invalidation and restarts without missing messages.
Was this helpful? React with 👍 or 👎 to provide feedback.
Summary
Devin (Cognition AI) becomes a first-class ADE provider under a single
devinprovider id — covering local (devin acpchats + trackeddevinCLI) and cloud (the org session fleet), with capability gates deciding which parts light up per surface. The shape deliberately mirrors the Cursor integration: ACP dialect for chats, tracked-CLI row for terminals, and adevinCloud*service set behindade.ai.devinCloud.*IPC for the fleet, so Devin inherits ADE's existing chat, drawer, attention, proof, and lane machinery rather than a bespoke UI.Local surface
acpHost/acpDialects/devin.ts— new dialect spawningdevin acp(JSON-RPC stdio), registered inACP_DIALECTS+acpProviderMetadata; works on every surface the ACP host serves (desktop chats, TUI, remote).devinCLI row (launch profiledevin -- <prompt>, tool detection, PTY sessions) — same tier as cursor-agent/claude/codex.devin auth loginbrowser OAuth orWINDSURF_API_KEY; no key needed for local chats. Devin sits next to Cursor inMODEL_PICKER_PROVIDER_ORDERwith adaptive/swe/opus/gpt/fable catalog entries (devin-*aliases — bareopus/fablecollided with the anthropic family).Cloud surface (
services/ai/devinCloudClient.ts+devinCloudFleetService.ts+devinCloudConversation.ts)cog_, self-serve on every Devin account — no org-only gating) with v1apk_user_personal-key fallback; org id collected once and auto-discovered.DevinCloudQuickViewButton(top bar next to Linear/Cursor, Cognition mark, renders only when configured) →DevinCloudFleetModal: org sessions with status/PR/ACU, Mine / From ADE / All provenance chips, status + lane filters; status derived once inshared/devinCloudFleetStatus.ts(waiting_for_user/waiting_for_approval→ needs_you).GET/POST …/messages), livesession.urlin the built-in browser (the only VM/Desktop view — no provider exposes VM control), stop, archive/unarchive, PR, delete, and pull-into-lane for pushed branches (same refusal rules as Cursor: dirty worktrees refused, conflicts abort).devin_modepicker, approval-gate skip); sends create sessions taggedade+ade:lane:<id>bound to the lane's repos.waiting_for_user/waiting_for_approvalraise "Needs you" viarequestAttention(provider_structured); Devin attachments download into the computer-use artifact store and ingest into the proof drawer (deduped by attachment id); Hand off to Devin Cloud attach-menu item packages lane context into a cloud launch; Continue in lane on the pull toast launches the localdevinCLI seeded with the session's context.Cause
ADE had no Devin surface at all; users with Devin accounts had no way to chat with the CLI, watch their cloud fleet, or move work between lanes and Devin's cloud VMs.
Change and boundary
Provider plumbing only — no changes to shared chat semantics, other providers, or launch machinery. Cloud sessions are VM-side: no local file access, no VM screen/exec API (deep-link live view is the interactive surface), and cloud auth is a token paste (no third-party OAuth exists for the REST API — same as Cursor). The v1 API is used only as a fallback credential path.
Verification
npm --prefix apps/desktop run typecheck— clean;npm --prefix apps/ade-cli run typecheck— clean.acpHost.test.ts(141) +modelCatalog.test.ts(4) +orchestrationRuntimePolicy.test.ts(8) — 153 pass.npm run dev:vite, seeded mock): quick-view button, fleet modal rows/badges/provenance chips, row menus, pull-into-lane → Continue-in-lane launch chain, settings provider card + Devin Cloud token section, chat-drawer cloud panel withdevin_modepicker, model picker.cog_/apk_user_token) and a realdevin acpspawn.Screenshots (new surfaces — nothing existed before):
Implemented by Devin (Cognition AI) in ADE.
Link to Devin session: https://app.devin.ai/sessions/6ecdc532fd424266bc43edb520e69168
Open in Devin Desktop: https://app.devin.ai/desktop/session/6ecdc532fd424266bc43edb520e69168?variant=devin
Requested by: @arul28
The PR is not yet safe to merge because remote Devin Cloud commands remain unavailable, multi-organization credentials can target the wrong organization, and valid port-bearing GitHub SSH origins cannot pull Devin work into a lane.
Fix with agent prompt
Summary
This PR adds Devin as a first-class local ACP/CLI provider and introduces Devin Cloud authentication, fleet management, mirrored chats, attachments, lane handoff, remote action plumbing, and desktop UI.
Diagram
%%{init: {'theme': 'neutral'}}%% flowchart LR U[User] --> UI[Desktop / TUI] UI --> A[ADE action and IPC surfaces] A --> L[Local Devin ACP / CLI] A --> C[Devin Cloud client] C --> F[Organization session fleet] F --> M[Mirrored ADE chat] F --> P[GitHub PR head] P --> R[Repository guard] R --> W[Lane worktree] F --> X[Bounded attachment ingestion]Reviews (3) · Last reviewed commit: "fix(devin): second-round review fixes — ..."