Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
19f2030
feat: add Devin provider support
rafael-franca Aug 11, 2026
7adc2cc
Merge branch 'main' into rafaelfranca/add-devin-provider
rafael-franca Aug 11, 2026
80cab23
fix(server): improve Devin usage tracking and reasoning variant matching
rafael-franca Aug 12, 2026
22c04f9
Merge branch 'main' into rafaelfranca/add-devin-provider
rafael-franca Aug 12, 2026
b117ec1
fix(devin): apply Macroscope review feedback and add mobile support
rafael-franca Aug 12, 2026
c109ae9
Merge branch 'main' into rafaelfranca/add-devin-provider
rafael-franca Aug 12, 2026
71544c9
fix(server,web,acp): apply Macroscope round-2 review feedback
rafael-franca Aug 12, 2026
ab6f2d8
fix(server): remove devin thread lock entries on session stop
rafael-franca Aug 12, 2026
b7afac7
fix(web): show model description tooltip when description exists
rafael-franca Aug 12, 2026
03d4094
test(web): update Devin settings field order expectation
rafael-franca Aug 12, 2026
771c598
Merge remote-tracking branch 'origin/main' into rafaelfranca/add-devi…
rafael-franca Aug 12, 2026
7c04fd9
Merge branch 'main' into rafaelfranca/add-devin-provider
rafael-franca Aug 12, 2026
02aa3f6
feat(web,contracts): add Devin Desktop to editor picker
rafael-franca Aug 12, 2026
f379487
feat(marketing,server): add Devin to marketing page and improve adapter
rafael-franca Aug 13, 2026
d98293f
Merge branch 'main' into rafaelfranca/add-devin-provider
rafael-franca Aug 13, 2026
d6385c0
test(server): reformat DevinAcpSupport tests and remove unused import
rafael-franca Aug 13, 2026
0b6c835
test(server): add token usage snapshot tests and refactor calculation…
rafael-franca Aug 13, 2026
de511f8
Merge remote-tracking branch 'origin/main' into rafaelfranca/add-devi…
rafael-franca Aug 13, 2026
6d51d47
Increase Devin version probe timeout from 4s to 15s
rafael-franca Aug 13, 2026
71edcec
fix(server): resolve Devin adapter PR feedback
rafael-franca Aug 13, 2026
86dbbd4
Merge branch 'pingdotgg:main' into rafaelfranca/add-devin-provider
rafael-franca Aug 13, 2026
5886ba6
fix(server): make Devin session stop uninterruptible
rafael-franca Aug 13, 2026
b74da06
test(server): rehydrate version dots in Devin model slugs
rafael-franca Aug 13, 2026
1d9451c
fix(server): ProviderProbeError carries stage and exit code structurally
rafael-franca Aug 13, 2026
00e4b44
Merge branch 'pingdotgg:main' into rafaelfranca/add-devin-provider
rafael-franca Aug 14, 2026
5d7375e
fix(server): make ProviderProbeError failure kinds and stages typed
rafael-franca Aug 14, 2026
a1c6197
Update apps/server/src/provider/Drivers/DevinDriver.ts
rafael-franca Aug 14, 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
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

T3 Code is an "agent harness control surface". It enables control of the agents on your machine with a best-in-class mobile app ([iOS](https://apps.apple.com/us/app/t3-code-remote-claude-more/id6787819824), [Android](https://play.google.com/store/apps/details?id=com.t3tools.t3code)), [web app](https://app.t3.codes) and [Electron-based desktop app](https://t3.codes).

Works with your subscriptions on Claude Code, Codex, Cursor, Grok Build, and OpenCode. If they're set up on your computer, T3 Code can control them.
Works with your subscriptions on Claude Code, Codex, Cursor, Devin, Grok Build, and OpenCode. If they're set up on your computer, T3 Code can control them.

## "Wait, what are you selling me?"

Expand All @@ -13,11 +13,12 @@ We wanted something performant, remote-ready, and truly open. If we ever go the
## Installation

> [!WARNING]
> T3 Code currently supports Codex, Claude, Cursor, Grok Build and OpenCode. Install and authenticate at least one provider before use:
> T3 Code currently supports Codex, Claude, Cursor, Devin, Grok Build and OpenCode. Install and authenticate at least one provider before use:
>
> - Codex: install [Codex CLI](https://developers.openai.com/codex/cli) and run `codex login`
> - Claude: install [Claude Code](https://claude.com/product/claude-code) and run `claude auth login`
> - Cursor: install [Cursor CLI](https://cursor.com/cli) and run `agent login`
> - Devin: install the [Devin CLI](https://devin.ai) and follow the Devin CLI documentation to authenticate
> - Grok Build: install [Grok Build CLI](https://x.ai/cli) and run `grok login`
> - OpenCode: install [OpenCode](https://opencode.ai) and run `opencode auth login`

Expand Down Expand Up @@ -72,7 +73,7 @@ Full docs live in [docs/](./docs). There's no docs site yet.
- [Remote access from a phone or another machine](./docs/user/remote-access.md)
- [Keeping app and server in sync](./docs/user/updating.md)
- [Source control integrations](./docs/user/source-control.md)
- Multiple accounts: [Codex](./docs/user/providers-codex.md) · [Claude](./docs/user/providers-claude.md)
- Multiple accounts: [Codex](./docs/user/providers-codex.md) · [Claude](./docs/user/providers-claude.md) · [Devin](./docs/user/providers-devin.md)
- Linux: [run T3 Code as a background service](./docs/user/background-service.md)

Building from source? Start at [docs/internals/overview.md](./docs/internals/overview.md).
Expand Down
3 changes: 3 additions & 0 deletions apps/marketing/public/harnesses/devin.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
40 changes: 34 additions & 6 deletions apps/marketing/src/pages/index.astro
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ const mobileEndorsementRows = [
<span class="hero-float-mark hf-grok" title="Grok">
<img src="/harnesses/grok-dark.svg" alt="" />
</span>
<span class="hero-float-mark hf-devin" title="Devin">
<img src="/harnesses/devin.svg" alt="" />
</span>
</div>

<div class="container hero-inner">
Expand All @@ -49,7 +52,7 @@ const mobileEndorsementRows = [
</h1>

<p class="hero-sub">
Orchestrate Claude Code, Codex, OpenCode, Cursor, and Grok from one surface.
Orchestrate Claude Code, Codex, OpenCode, Cursor, Grok, and Devin from one surface.
Bring your own subscription. Fork the whole thing.
</p>

Expand Down Expand Up @@ -228,6 +231,13 @@ const mobileEndorsementRows = [
<div class="harness-tag">grok login</div>
</div>
</div>
<div class="harness">
<div class="harness-mark"><img src="/harnesses/devin.svg" alt="" /></div>
<div class="harness-meta">
<div class="harness-name">Devin</div>
<div class="harness-tag">devin auth login</div>
</div>
</div>
</div>

<div class="harness-footer">
Expand Down Expand Up @@ -661,6 +671,7 @@ const mobileEndorsementRows = [
.hero-float-mark.hf-opencode { background: radial-gradient(circle at 30% 25%, rgba(255, 255, 255, 0.06), rgba(20, 20, 24, 0.92) 65%); top: 30%; left: 5%; animation-delay: -5s; transform: rotate(4deg); }
.hero-float-mark.hf-cursor { background: radial-gradient(circle at 30% 25%, rgba(255, 255, 255, 0.08), rgba(20, 20, 24, 0.92) 65%); top: 30%; right: 5%; animation-delay: -7s; transform: rotate(-5deg); }
.hero-float-mark.hf-grok { background: radial-gradient(circle at 30% 25%, rgba(255, 255, 255, 0.08), rgba(20, 20, 24, 0.92) 65%); top: 52%; left: 6%; animation-delay: -1.2s; transform: rotate(3deg); }
.hero-float-mark.hf-devin { background: radial-gradient(circle at 30% 25%, rgba(255, 255, 255, 0.06), rgba(20, 20, 24, 0.92) 65%); top: 52%; right: 6%; animation-delay: -4s; transform: rotate(-3deg); }

@media (max-width: 1180px) {
.hero-float-mark { width: 76px; height: 76px; border-radius: 20px; }
Expand All @@ -670,6 +681,7 @@ const mobileEndorsementRows = [
.hero-float-mark.hf-opencode { top: 26%; left: 2%; }
.hero-float-mark.hf-cursor { top: 24%; right: 2%; }
.hero-float-mark.hf-grok { top: 48%; left: 2%; transform: rotate(3deg); }
.hero-float-mark.hf-devin { top: 48%; right: 2%; transform: rotate(-3deg); }
}

@media (max-width: 820px) {
Expand Down Expand Up @@ -704,7 +716,7 @@ const mobileEndorsementRows = [
height: 44px;
}

/* Three stacked on the left, two on the right — keeps the center CTA clear. */
/* Three stacked on the left, three on the right — keeps the center CTA clear. */
.hero-float-mark.hf-claude {
top: 44px;
left: 10px;
Expand All @@ -731,6 +743,13 @@ const mobileEndorsementRows = [
transform: rotate(8deg);
}

.hero-float-mark.hf-devin {
top: 240px;
right: 4px;
left: auto;
transform: rotate(4deg);
}

.hero-float-mark.hf-cursor {
top: 474px;
right: 4px;
Expand All @@ -740,14 +759,22 @@ const mobileEndorsementRows = [
}

@media (max-width: 340px) {
.hero-float-mark.hf-grok {
.hero-float-mark.hf-grok,
.hero-float-mark.hf-devin {
top: 220px;
left: 0;
width: 52px;
height: 52px;
border-radius: 14px;
}

.hero-float-mark.hf-grok {
left: 0;
}

.hero-float-mark.hf-devin {
right: 0;
}

.hero-float-mark.hf-opencode,
.hero-float-mark.hf-cursor {
top: 580px;
Expand All @@ -765,6 +792,7 @@ const mobileEndorsementRows = [
}

.hero-float-mark.hf-grok img,
.hero-float-mark.hf-devin img,
.hero-float-mark.hf-opencode img,
.hero-float-mark.hf-cursor img {
width: 30px;
Expand Down Expand Up @@ -811,7 +839,7 @@ const mobileEndorsementRows = [

.harness-grid {
display: grid;
grid-template-columns: repeat(5, 1fr);
grid-template-columns: repeat(6, 1fr);
gap: 0;
border: 1px solid var(--border);
border-radius: var(--radius);
Expand Down Expand Up @@ -1266,7 +1294,7 @@ const mobileEndorsementRows = [
.harness-grid { grid-template-columns: 1fr 1fr; }
.harness { border-right: 0; border-bottom: 1px solid var(--border); }
.harness:nth-child(odd) { border-right: 1px solid var(--border); }
.harness:nth-child(5):last-child {
.harness:nth-child(odd):last-child {
grid-column: 1 / -1;
border-right: 0;
}
Expand Down
4 changes: 3 additions & 1 deletion apps/mobile/src/features/usage/usageProviders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ import { useColorScheme } from "react-native";
* Series and table order. The chart stacks providers from the bottom in this
* order, so it also fixes which band sits on top of the bars.
*/
export const PROVIDER_ORDER: readonly UsageProviderKind[] = ["codex", "claude"];
export const PROVIDER_ORDER: readonly UsageProviderKind[] = ["codex", "claude", "devin"];

export const PROVIDER_LABEL: Record<UsageProviderKind, string> = {
claude: "Claude Code",
codex: "Codex",
devin: "Devin",
};

/**
Expand All @@ -21,5 +22,6 @@ export function useProviderColors(): Record<UsageProviderKind, string> {
return {
claude: "#d97757",
codex: scheme === "dark" ? "#e6e6e6" : "#3c3c43",
devin: "#8a63d2",
};
}
1 change: 1 addition & 0 deletions apps/mobile/src/lib/modelOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ function providerDisplayLabel(provider: {
if (provider.displayName) return provider.displayName;
if (provider.driver === "codex") return "Codex";
if (provider.driver === "claudeAgent") return "Claude";
if (provider.driver === "devin") return "Devin";
return provider.instanceId;
}

Expand Down
39 changes: 34 additions & 5 deletions apps/server/scripts/acp-mock-agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,17 @@ const failSetConfigOption = process.env.T3_ACP_FAIL_SET_CONFIG_OPTION === "1";
const exitOnSetConfigOption = process.env.T3_ACP_EXIT_ON_SET_CONFIG_OPTION === "1";
const promptResponseText = process.env.T3_ACP_PROMPT_RESPONSE_TEXT;
const promptDelayMs = Number(process.env.T3_ACP_PROMPT_DELAY_MS ?? "0");
const advertisedAuthMethods = (() => {
const raw = process.env.T3_ACP_AUTH_METHODS;
if (!raw) {
return undefined;
}
try {
return JSON.parse(raw) as ReadonlyArray<AcpSchema.AuthMethod>;
} catch {
return undefined;
}
})();
const permissionOptionIds = {
allowOnce: process.env.T3_ACP_ALLOW_ONCE_OPTION_ID ?? "allow-once",
allowAlways: process.env.T3_ACP_ALLOW_ALWAYS_OPTION_ID ?? "allow-always",
Expand Down Expand Up @@ -218,7 +229,10 @@ function configOptions(): ReadonlyArray<AcpSchema.SessionConfigOption> {
{ value: "default", name: "Auto" },
{ value: "composer-2", name: "Composer 2" },
{ value: "composer-2[fast=true]", name: "Composer 2 Fast" },
{ value: "gpt-5.3-codex[reasoning=medium,fast=false]", name: "Codex 5.3" },
{
value: "gpt-5.3-codex[reasoning=medium,fast=false]",
name: "Codex 5.3",
},
],
},
];
Expand Down Expand Up @@ -303,6 +317,7 @@ const program = Effect.gen(function* () {
return {
protocolVersion: 1,
agentCapabilities: { loadSession: true },
...(advertisedAuthMethods !== undefined ? { authMethods: advertisedAuthMethods } : {}),
};
}),
);
Expand Down Expand Up @@ -674,13 +689,21 @@ const program = Effect.gen(function* () {
],
},
options: [
{ optionId: permissionOptionIds.allowOnce, name: "Allow once", kind: "allow_once" },
{
optionId: permissionOptionIds.allowOnce,
name: "Allow once",
kind: "allow_once",
},
{
optionId: permissionOptionIds.allowAlways,
name: "Allow always",
kind: "allow_always",
},
{ optionId: permissionOptionIds.rejectOnce, name: "Reject", kind: "reject_once" },
{
optionId: permissionOptionIds.rejectOnce,
name: "Reject",
kind: "reject_once",
},
],
});

Expand Down Expand Up @@ -784,7 +807,10 @@ const program = Effect.gen(function* () {
question: "Which scope should Grok use?",
multiSelect: null,
options: [
{ label: "Workspace", description: "Use the current workspace" },
{
label: "Workspace",
description: "Use the current workspace",
},
{ label: "Session", description: "Only use this session" },
],
},
Expand Down Expand Up @@ -869,7 +895,10 @@ const program = Effect.gen(function* () {
sessionId: requestedSessionId,
update: {
sessionUpdate: "agent_message_chunk",
content: { type: "text", text: promptResponseText ?? "hello from mock" },
content: {
type: "text",
text: promptResponseText ?? "hello from mock",
},
},
});

Expand Down
32 changes: 32 additions & 0 deletions apps/server/src/provider/Drivers/DevinBinary.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import * as Effect from "effect/Effect";
import * as FileSystem from "effect/FileSystem";
import * as Path from "effect/Path";
import { isCommandAvailable } from "@t3tools/shared/shell";

const DEFAULT_DEVIN_BINARIES = ["devin", "devin-desktop"] as const;

export const resolveEffectiveDevinBinary = Effect.fn("resolveEffectiveDevinBinary")(function* (
binaryPath: string | null | undefined,
environment?: NodeJS.ProcessEnv,
): Effect.fn.Return<string, never, FileSystem.FileSystem | Path.Path> {
const configured = (binaryPath ?? "").trim() || "devin";

const env = environment ?? process.env;

if (!DEFAULT_DEVIN_BINARIES.includes(configured as (typeof DEFAULT_DEVIN_BINARIES)[number])) {
return configured;
}

if (yield* isCommandAvailable(configured, { env })) {
return configured;
}

for (const candidate of DEFAULT_DEVIN_BINARIES) {
if (candidate === configured) continue;
if (yield* isCommandAvailable(candidate, { env })) {
return candidate;
}
}

return configured;
});
Loading
Loading