Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
7651a40
Support passing scopes as a config to the linear adapter
SamyPesse Apr 7, 2026
d580d4c
Changeset
SamyPesse Apr 7, 2026
5f421b0
Add multi-tenant support for Linear
SamyPesse Apr 7, 2026
b3089bb
Lint
SamyPesse Apr 7, 2026
3e48eac
Cleanup
SamyPesse Apr 7, 2026
1dd014a
Fix tests
SamyPesse Apr 7, 2026
47f69d5
Make organizationId mandatory
SamyPesse Apr 7, 2026
3b0011e
Fix
SamyPesse Apr 7, 2026
2fd2a5b
Fix fetch of comments
SamyPesse Apr 8, 2026
5eb933c
Start agent sessions support
SamyPesse Apr 8, 2026
c2bc172
Continue cleanup
SamyPesse Apr 8, 2026
0e7a5b0
Cleanup more
SamyPesse Apr 8, 2026
d404748
Simplify more
SamyPesse Apr 8, 2026
98acbbd
Fix
SamyPesse Apr 8, 2026
d7a7229
Fix fetchMessages for agent sessions
SamyPesse Apr 8, 2026
5b2604e
Improve streaming to linear
SamyPesse Apr 8, 2026
7f7364b
Fix agent raw messages
SamyPesse Apr 9, 2026
1188916
Fix tests
SamyPesse Apr 9, 2026
998658d
Lint
SamyPesse Apr 9, 2026
d171ae9
Simplify
SamyPesse Apr 9, 2026
561bd8d
Work
SamyPesse Apr 9, 2026
5ea7fd6
Remove debug logs and adapt tests
SamyPesse Apr 9, 2026
03b51d8
Merge remote-tracking branch 'upstream/main' into fix-linear-scopes
SamyPesse Apr 9, 2026
39db356
Fix organizationId
SamyPesse Apr 9, 2026
c40a6c4
Cleanup types
SamyPesse Apr 9, 2026
8643eb0
Cleanup and comment
SamyPesse Apr 9, 2026
8b4ca44
Fix webhook handling
SamyPesse Apr 9, 2026
f5f21b3
Expose prompt context
SamyPesse Apr 9, 2026
ef4ffa8
First review pass
SamyPesse Apr 9, 2026
a66cf14
Rework the changeset
SamyPesse Apr 9, 2026
1bbf617
Improve getInstallation and expose refreshInstallation
SamyPesse Apr 9, 2026
c6821d1
Fix error on agent sessions coming from issue delegation
SamyPesse Apr 10, 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
11 changes: 11 additions & 0 deletions .changeset/dirty-masks-bathe.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
"@chat-adapter/linear": major
---

Add multi-tenant support in the Linear adapter using `clientId` / `clientSecret`.

The Linear adapter now exposes a `handleOAuthCallback()` function for OAuth multi-tenant support.

Add `clientCredentials.scopes` to the Linear adapter so single-tenant client-credentials auth can request custom OAuth scopes.

Add support for agent sessions in Linear, with streaming / task / plan support.
14 changes: 12 additions & 2 deletions examples/nextjs-chat/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,22 @@ BOT_USERNAME=mybot
# GITHUB_WEBHOOK_SECRET=your-webhook-secret
# GITHUB_BOT_USERNAME=my-bot # or my-bot[bot] for GitHub Apps

# Linear (optional) - use API key OR OAuth app, not both
# Linear (optional) - use API key, access token, client credentials, or multi-tenant OAuth installs
# API key authentication (simplest):
# LINEAR_API_KEY=lin_api_xxxxxxxxxxxx
# OR OAuth app (client credentials - recommended for apps):
# OR pre-obtained OAuth access token:
# LINEAR_ACCESS_TOKEN=lin_oauth_xxxxxxxxxxxx
# OR single-tenant client credentials:
# LINEAR_CLIENT_CREDENTIALS_CLIENT_ID=your-client-id
# LINEAR_CLIENT_CREDENTIALS_CLIENT_SECRET=your-client-secret
# LINEAR_CLIENT_CREDENTIALS_SCOPES=read,write,comments:create,issues:create
# OR multi-tenant OAuth installs:
# LINEAR_CLIENT_ID=your-client-id
# LINEAR_CLIENT_SECRET=your-client-secret
# LINEAR_REDIRECT_URI=https://your-domain.com/api/linear/install/callback
# Optional inbound webhook mode:
# LINEAR_MODE=comments
# Use LINEAR_MODE=agent-sessions for app-actor installs
# Required for either auth method:
# LINEAR_WEBHOOK_SECRET=your-webhook-secret
# LINEAR_BOT_USERNAME=my-bot
Expand Down
7 changes: 7 additions & 0 deletions examples/nextjs-chat/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,17 @@ Copy `.env.example` for the full list. At minimum, set `BOT_USERNAME` and creden
| `DISCORD_PUBLIC_KEY` | Discord interaction verification key |
| `GITHUB_TOKEN` | GitHub PAT or App credentials |
| `LINEAR_API_KEY` | Linear API key |
| `LINEAR_CLIENT_ID` | Linear OAuth app client ID for multi-tenant installs |
| `LINEAR_CLIENT_SECRET` | Linear OAuth app client secret for multi-tenant installs |
| `LINEAR_REDIRECT_URI` | Linear OAuth callback URL |
| `LINEAR_MODE` | Linear inbound mode: `comments` or `agent-sessions` |
| `LINEAR_WEBHOOK_SECRET` | Linear webhook signing secret |
| `REDIS_URL` | Redis connection string |

See the [Chat SDK docs](https://chat-sdk.dev/docs) for full platform setup guides.

For Linear app-actor mode, set `LINEAR_MODE=agent-sessions`, enable **Agent session events** on the webhook, install the Linear app with `actor=app` and `app:mentionable`, and keep using the existing `thread.startTyping()` / `thread.post(...)` handler flow. The adapter maps those calls onto Linear agent activities automatically.

## Recording and replay

The app includes a recording system for capturing production webhook interactions and converting them into replay tests.
Expand Down
29 changes: 29 additions & 0 deletions examples/nextjs-chat/src/app/api/linear/install/callback/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { bot } from "@/lib/bot";

export async function GET(request: Request) {
const adapter = bot.getAdapter("linear");
const redirectUri = process.env.LINEAR_REDIRECT_URI;

if (!adapter) {
return new Response("Linear adapter not configured", { status: 500 });
}

if (!redirectUri) {
return new Response("LINEAR_REDIRECT_URI is not configured", {
status: 500,
});
}

try {
await bot.initialize();
const { organizationId } = await adapter.handleOAuthCallback(request, {
redirectUri,
});
return new Response(
`Linear app installed for organization ${organizationId}!`
);
} catch (error) {
console.error("[linear/install/callback] OAuth error:", error);
return new Response("OAuth installation failed", { status: 500 });
}
}
31 changes: 31 additions & 0 deletions examples/nextjs-chat/src/app/api/linear/install/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
const LINEAR_INSTALL_SCOPES = [
"read",
"write",
"comments:create",
"issues:create",
"app:mentionable",
];

export function GET() {
const clientId = process.env.LINEAR_CLIENT_ID;
const redirectUri = process.env.LINEAR_REDIRECT_URI;

if (!clientId) {
return new Response("LINEAR_CLIENT_ID is not configured", { status: 500 });
}

if (!redirectUri) {
return new Response("LINEAR_REDIRECT_URI is not configured", {
status: 500,
});
}

const installUrl = new URL("https://linear.app/oauth/authorize");
installUrl.searchParams.set("client_id", clientId);
installUrl.searchParams.set("redirect_uri", redirectUri);
installUrl.searchParams.set("response_type", "code");
installUrl.searchParams.set("actor", "app");
installUrl.searchParams.set("scope", LINEAR_INSTALL_SCOPES.join(","));

return Response.redirect(installUrl.toString(), 302);
}
9 changes: 8 additions & 1 deletion examples/nextjs-chat/src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,14 @@ GITHUB_WEBHOOK_SECRET=...`}

<h3>Linear</h3>
<pre>
{`LINEAR_API_KEY=lin_api_...
{`# Single-workspace
LINEAR_API_KEY=lin_api_...
LINEAR_WEBHOOK_SECRET=...
# OR multi-tenant OAuth installs
LINEAR_CLIENT_ID=...
LINEAR_CLIENT_SECRET=...
LINEAR_REDIRECT_URI=https://your-domain.com/api/linear/install/callback
LINEAR_WEBHOOK_SECRET=...`}
</pre>
</main>
Expand Down
9 changes: 7 additions & 2 deletions examples/nextjs-chat/src/lib/adapters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,19 +203,24 @@ export function buildAdapters(): Adapters {
}
}

// Linear adapter (optional) - env vars: LINEAR_WEBHOOK_SECRET + (LINEAR_API_KEY or LINEAR_CLIENT_ID/SECRET)
// Linear adapter (optional) - env vars: LINEAR_WEBHOOK_SECRET + (LINEAR_API_KEY, LINEAR_ACCESS_TOKEN, LINEAR_CLIENT_CREDENTIALS_*, or LINEAR_CLIENT_ID/SECRET).
// Set LINEAR_MODE=agent-sessions for app-actor installs with Agent session events + app:mentionable.
if (process.env.LINEAR_WEBHOOK_SECRET) {
try {
adapters.linear = withRecording(
createLinearAdapter({
logger: logger.child("linear"),
mode:
process.env.LINEAR_MODE === "agent-sessions"
? "agent-sessions"
: "comments",
}),
"linear",
LINEAR_METHODS
);
} catch {
console.warn(
"[chat] Failed to create linear adapter (check LINEAR_API_KEY or LINEAR_CLIENT_ID/SECRET)"
"[chat] Failed to create linear adapter (check LINEAR_API_KEY, LINEAR_ACCESS_TOKEN, LINEAR_CLIENT_CREDENTIALS_*, or LINEAR_CLIENT_ID/SECRET)"
);
}
}
Expand Down
Loading
Loading