Skip to content

feat(providers): add Devin via the official CLI ACP server - #9483

Closed
abhinav-bhateja wants to merge 1 commit into
pingdotgg:mainfrom
abhinav-bhateja:t3code/add-devin-acp
Closed

feat(providers): add Devin via the official CLI ACP server#9483
abhinav-bhateja wants to merge 1 commit into
pingdotgg:mainfrom
abhinav-bhateja:t3code/add-devin-acp

Conversation

@abhinav-bhateja

@abhinav-bhateja abhinav-bhateja commented Sep 3, 2026

Copy link
Copy Markdown

Devin ships an official ACP server (\devin acp) but T3 Code has no driver for it, so Devin subscriptions cannot be used from T3 Code.

This adds Devin as a seventh built-in provider, off by default and listed last. Everything runs through the local Devin CLI over ACP stdio. No custom install or OAuth stack is needed (unlike Antigravity): the CLI owns credentials via \devin auth login.

What the user does

  1. Install Devin CLI and run \devin auth login.
  2. Enable Devin in Settings > Providers.
  3. Pick a model and chat. Models come from the Devin session (197 today, default \�daptive).

How it works

  • \DevinDriver\ clones the Cursor ACP pattern: \devin\ binary, multiple instances, \devin update\ maintenance, workspace skill probing.
  • \DevinAcpSupport\ spawns \devin acp\ with the \devin-browser\ auth method. Models are set through the session \model\ config option (Devin exposes no top-level model list or \set_model\ catalog).
  • \DevinAdapter\ maps T3 modes onto Devin session modes: Plan interaction -> \plan, Full access -> \�ypass, everything else stays on Code (\�ccept-edits) with approvals flowing through \session/request_permission. Ask mode is never auto-selected since it forbids code changes. Images attach natively; \\ rewrites to /\ like Cursor.
  • \DevinProvider\ health-checks via \devin --version\ + \devin auth status, then discovers models over ACP. Snapshots sort Devin last.
  • \DevinTextGeneration\ drives titles, commit messages, branch names, and PR text through the same ACP runtime in \�sk\ mode.
  • Web and mobile icons use the official Devin chain-link mark (\docs.devin.ai/logo/favicon.svg, nominative use, theme-adapted mono fill). Settings, model picker, and docs pick Devin up from the shared driver definitions.

Verified on the real CLI

Linux/Windows CLI \devin 3000.6.7, logged in as Devin Pro:

  • ACP \initialize\ OK (\�ffogato\ agent, image + embedded-context prompts, \loadSession).
  • \session/new\ returns modes \�ccept-edits/ask/plan/bypass\ plus \mode\ + \model\ config options.
  • \DevinAcpCliProbe\ 3/3 green (init, modes, model discovery incl. \�daptive).
  • Unit: \DevinAcpSupport\ + \DevinProvider\ 13/13, contracts settings 79/79, \providerStatusCache\ 6/6, web settings/model-selection 52/52, registry Cursor-disabled case updated for the new driver.
  • \ sgo --noEmit\ clean on contracts/server/web/mobile; \�p lint\ clean on touched files. One pre-existing Windows-only failure in the Claude home-path registry test, untouched by this change.

Not covered here

  • No billed live turn was sent through Devin (handshake + discovery verified, no prompt issued).
  • No marketing harness card (no bundled brand asset upstream of this repo).
  • Probe run on Windows x64 only; macOS/Linux/ARM paths are wired but unlaunched.

Built with Muse Spark in T3 Code.


Note

Medium Risk
Large new provider surface (child processes, ACP sessions, filesystem skill scans, permission mapping) alongside contracts changes that affect all clients; behavior is mostly isolated to the Devin adapter path but mis-mapped modes or session lifecycle bugs could affect agent safety and turn completion.

Overview
Adds Devin as a seventh built-in provider (opt-in, sorted last), wired to the local Devin CLI over devin acp—no custom OAuth or install stack like Antigravity.

Contracts & settings: New DevinSettings (enabled off by default, binaryPath, customModels), server settings/patch wiring, default chat model adaptive, and display-name defaults.

Server: DevinDriver registers the driver with health checks (devin --version, devin auth status), ACP model discovery, devin update maintenance, workspace Agent Skills scanning (composer $skill → Devin /skill), and DevinTextGeneration for titles/commits/PRs in ask mode. DevinAdapter maps T3 runtime/interaction modes to Devin session modes (plan / bypass / accept-edits), streams ACP events (including steer-in-flight turns), handles permissions, native images, session resume, and MCP hookup.

Clients & docs: Devin icons and provider metadata on web and mobile; settings/model picker/session logic updated. User and contributor docs list Devin install (devin auth login) and note Devin behaves like OpenCode/Antigravity for Auto permission mode (falls back to supervised approvals).

Reviewed by Cursor Bugbot for commit eb63014. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Add Devin provider via official CLI ACP server

  • Adds a full Devin provider stack: status/health-check workflow, ACP session adapter, driver, and text-generation service backed by the devin acp command
  • Provider is disabled by default in DevinSettings; users set a binary path and run devin login to enable it
  • Discovers available models from the running ACP session instead of a fixed list, falling back to the adaptive model when discovery fails or no model is selected
  • Adds Devin Agent Skills discovery from workspace and user roots, rewriting $skill mentions to /skill invocations in prompts
  • Registers Devin across contracts, settings schemas, web/mobile icons, provider picker, and documentation
  • Risk: builtInDrivers.ts now exports DevinDriver and DevinDriverEnv; any code constructing the built-in driver registry must supply Devin driver environment services or compilation fails
📊 Macroscope summarized eb63014. 23 files reviewed, 7 issues evaluated, 1 issue filtered, 3 comments posted

🗂️ Filtered Issues

docs/user/permission-modes.md — 0 comments posted, 1 evaluated, 1 filtered
  • line 21: The added statement says Devin's Auto mode falls back to Supervised (which promises approval before file changes), but the Devin adapter maps every non-full-access runtime mode—including Auto—to Devin's accept-edits mode. That mode auto-approves workspace edits, so users selecting Auto can have changes applied without the prompts the documentation promises. [ Out of scope (triage) ]

Devin ships an official ACP server (devin acp) but T3 Code had no driver for it, so Devin Pro subscriptions could not be used from T3 Code.

This adds Devin as a seventh built-in provider, off by default and listed last. Everything runs through the local Devin CLI over ACP stdio: version check via devin --version, auth via devin auth status, and model catalog from the session model config option (197 models). Sessions default to the Code (accept-edits) mode with T3 permission prompts, Plan interaction maps to Devin's plan mode, and Full access maps to Bypass Permissions. Includes the ACP adapter, snapshot/status provider, skill discovery, text generation, web and mobile icons (official chain-link mark), settings entries, and docs.

Verified against the real CLI (devin 3000.6.7, logged in): ACP initialize, session/new modes and model options, and model discovery through the shared probe runtime (DevinAcpCliProbe 3/3). Unit suites: DevinAcpSupport + DevinProvider 13/13, contracts 79/79, web 52/52; tsgo clean on contracts/server/web/mobile; vp lint clean on touched files.

Built with Muse Spark in T3 Code.
@github-actions github-actions Bot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:XXL 1,000+ changed lines (additions + deletions). labels Sep 3, 2026
function flattenModelSelectOptions(
configOption: EffectAcpSchema.SessionConfigOption | undefined,
): ReadonlyArray<DevinAcpDiscoveredModel> {
if (!configOption || configOption.type !== "select") return [];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 High Layers/DevinProvider.ts:123

When ACP model discovery is unavailable or flattenModelSelectOptions returns no models, the status check passes discoveredModels: [] to buildDevinProviderSnapshot, which removes the built-in adaptive fallback and can leave the enabled provider with no selectable model. Preserve fallbackModels whenever discovery fails, times out, is skipped, or returns an empty/unsupported catalog.

🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/provider/Layers/DevinProvider.ts around line 123:

When ACP model discovery is unavailable or `flattenModelSelectOptions` returns no models, the status check passes `discoveredModels: []` to `buildDevinProviderSnapshot`, which removes the built-in `adaptive` fallback and can leave the enabled provider with no selectable model. Preserve `fallbackModels` whenever discovery fails, times out, is skipped, or returns an empty/unsupported catalog.

Comment on lines +162 to +169
let frontmatter: DevinSkillFrontmatter | undefined = { cliVisible: true };
if (skillInfo.size <= MAX_SKILL_BYTES && skillInfo.size <= input.budget.remainingBytes) {
const contents = yield* orUndefined(fileSystem.readFileString(skillPath));
if (contents !== undefined) {
input.budget.remainingBytes -= skillInfo.size;
frontmatter = parseSkillFrontmatter(contents);
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium Drivers/DevinSkills.ts:162

A SKILL.md that fits MAX_SKILL_BYTES but exceeds remainingBytes is treated as having { cliVisible: true }, so the scan can return skills whose metadata excludes cli and probeDevinSkills reports success after an incomplete scan. Read failures have the same fallback and are silently ignored because orUndefined(fileSystem.readFileString(skillPath)) receives no input.budget; pass the budget, mark the scan exhausted when the file cannot fit, and exclude the skill when its metadata read fails.

-      let frontmatter: DevinSkillFrontmatter | undefined = { cliVisible: true };
-      if (skillInfo.size <= MAX_SKILL_BYTES && skillInfo.size <= input.budget.remainingBytes) {
-        const contents = yield* orUndefined(fileSystem.readFileString(skillPath));
-        if (contents !== undefined) {
-          input.budget.remainingBytes -= skillInfo.size;
-          frontmatter = parseSkillFrontmatter(contents);
-        }
+      let frontmatter: DevinSkillFrontmatter | undefined = { cliVisible: true };
+      if (skillInfo.size <= MAX_SKILL_BYTES) {
+        if (skillInfo.size > input.budget.remainingBytes) {
+          input.budget.exhausted = true;
+          return;
+        }
+        const contents = yield* orUndefined(fileSystem.readFileString(skillPath), input.budget);
+        if (contents === undefined) {
+          frontmatter = undefined;
+        } else {
+          input.budget.remainingBytes -= skillInfo.size;
+          frontmatter = parseSkillFrontmatter(contents);
+        }
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/provider/Drivers/DevinSkills.ts around lines 162-169:

A `SKILL.md` that fits `MAX_SKILL_BYTES` but exceeds `remainingBytes` is treated as having `{ cliVisible: true }`, so the scan can return skills whose metadata excludes `cli` and `probeDevinSkills` reports success after an incomplete scan. Read failures have the same fallback and are silently ignored because `orUndefined(fileSystem.readFileString(skillPath))` receives no `input.budget`; pass the budget, mark the scan exhausted when the file cannot fit, and exclude the skill when its metadata read fails.

// Count this prompt immediately so a superseded in-flight prompt
// resolving from here on does not settle the turn; the matching
// decrement is the `ensuring` below.
ctx.promptsInFlight += 1;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium Layers/DevinAdapter.ts:843

A second concurrent sendTurn opens a separate turn and emits another turn.started event instead of steering the existing turn. The first call sets ctx.activeTurnId only after awaiting applyRequestedSessionConfiguration, so the second call sees promptsInFlight > 0 but activeTurnId === undefined; reserve the active turn ID before that await.

Suggested change
ctx.promptsInFlight += 1;
ctx.promptsInFlight += 1;
ctx.activeTurnId = turnId;
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/provider/Layers/DevinAdapter.ts around line 843:

A second concurrent `sendTurn` opens a separate turn and emits another `turn.started` event instead of steering the existing turn. The first call sets `ctx.activeTurnId` only after awaiting `applyRequestedSessionConfiguration`, so the second call sees `promptsInFlight > 0` but `activeTurnId === undefined`; reserve the active turn ID before that await.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: eb630141b5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

? ({ outcome: "cancelled" } as const)
: {
outcome: "selected" as const,
optionId: acpPermissionOutcome(resolved),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Return Devin's advertised permission option ID

In approval-required sessions, accepting or declining a Devin tool request returns synthetic IDs such as allow-once and reject-once rather than an optionId from params.options. ACP defines these IDs as agent-provided unique identifiers (packages/effect-acp/src/_generated/schema.gen.ts:7663-7666), and the repository's protocol fixtures use values such as allow; therefore any Devin request using opaque IDs—or omitting allow_always—will receive an invalid response and the tool turn can fail or remain blocked. Resolve the selected option by its kind, as the Grok adapter does.

Useful? React with 👍 / 👎.

Comment on lines +459 to +461
discoveredModels: Option.getOrElse(
Option.filter(discoveredModels, (models) => models.length > 0),
() => [] as const,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve fallback models when discovery fails

When ACP discovery times out, errors, or returns no models, this converts the missing result to [], so the completed health refresh replaces the initial adaptive fallback catalog with an empty model list. Mobile builds its picker directly from provider.models, and web model options do likewise, leaving a correctly installed Devin provider with no selectable model after a transient discovery failure. Pass getDevinFallbackModels(devinSettings) when no discovered models are available instead.

AGENTS.md reference: AGENTS.md:L67-L72

Useful? React with 👍 / 👎.

Comment on lines +178 to +179
const modelOption = configOptions.find((option) => option.id === "model");
return buildDevinDiscoveredModels(flattenModelSelectOptions(modelOption));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Honor the ACP model option's current value

Model discovery flattens the model select but discards its currentValue, so none of the resulting ServerProviderModel entries is marked isDefault. Both web and mobile then choose the first catalog entry rather than Devin's active default whenever those differ; ACP does not require the current value to be listed first. Propagate modelOption.currentValue and mark the matching model as default.

Useful? React with 👍 / 👎.

@macroscopeapp

macroscopeapp Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This PR adds a substantial Devin provider integration with ACP session management, authentication and permission handling, model discovery, skill scanning, UI exposure, and text-generation support across production paths. Its size and runtime scope, along with unresolved concerns around model fallback and permission responses, require human review.

Not approved because:

  • 3 blocking correctness issues found at or above your repo's Minimum Blocking Severity

Adjust the Minimum Blocking Severity for this repo — including turning it Off — in Settings. You can add or adjust custom eligibility rules. Learn more.

@t3dotgg

t3dotgg commented Sep 4, 2026

Copy link
Copy Markdown
Member

Note

🤖 GPT-6 Astra (preview) responding on behalf of Theo

This was closed as part of an automated cleanup pass. If you believe it was closed in error, reply here and we will get it reopened.

Closing this duplicate in favor of the dedicated Devin implementation in #9335. That PR handles advertised permission IDs and rejects overlapping turns. This PR's ACP model config-option and skill-discovery findings are recorded there for follow-up. The retained PR still needs review and live-turn verification.

@t3dotgg t3dotgg closed this Sep 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL 1,000+ changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants