UNPKG

eve

Version:

Filesystem-first framework for durable backend AI agents that run anywhere.

192 lines (138 loc) 9.39 kB
--- title: "Linear" description: "Reach your agent through Linear Agent Sessions, with native Agent Activities for progress, questions, and responses, and Vercel Connect credentials." type: integration --- The Linear channel uses Linear's Agent Session surface rather than ordinary comments. Users delegate work to the agent from Linear, eve receives `AgentSessionEvent` webhooks at `/eve/v1/linear`, and the channel replies with native Agent Activities, including `thought`, `action`, `elicitation`, `response`, and `error`. Credentials can run through [Vercel Connect](../guides/auth-and-route-protection), which manages the Linear app, its access token, and inbound webhook verification, so there's no API key or webhook secret for you to hold. See [Channels](./overview) for the contract this builds on. ## Set up Connect Create a Linear Connect client and copy its UID (e.g. `linear/my-agent`), then attach this project as the trigger destination at eve's Linear route: ```bash npm install -g vercel@latest vercel connect create linear --triggers vercel connect detach <uid> --yes vercel connect attach <uid> --triggers --trigger-path /eve/v1/linear --yes ``` The `create` step provisions the Linear app — with the `app:assignable` and `app:mentionable` scopes the agent surface needs — and a trigger destination at the default Connect path. `detach` then `attach --trigger-path /eve/v1/linear` re-points the trigger at the eve Linear route, since eve does not serve the default Connect path. `--triggers` makes Connect receive Linear's webhooks, verify them, and forward them to your deployment. During registration, subscribe to the `AgentSessionEvent` webhook category — the managed app defaults to `Comment` and `Issue` — so Linear sends `created` events when the agent is delegated or mentioned and `prompted` events when the user continues the session. You can also create the client from the [Connect dashboard](https://vercel.com/d?to=/%5Bteam%5D/~/connect&title=Go+to+Connect). ## Add the channel ```bash npm install @vercel/connect ``` ```ts title="agent/channels/linear.ts" import { connectLinearCredentials } from "@vercel/connect/eve"; import { linearChannel } from "eve/channels/linear"; export default linearChannel({ credentials: connectLinearCredentials("linear/my-agent"), }); ``` `connectLinearCredentials` returns `{ accessToken, webhookVerifier }`: eve uses the Connect-managed app token for Linear GraphQL calls and verifies Connect-forwarded webhooks by their Vercel OIDC signature instead of a Linear webhook secret. Token rotation, refresh, and multi-workspace tenancy stay inside Connect, so there is no `LINEAR_AGENT_ACCESS_TOKEN` or `LINEAR_WEBHOOK_SECRET` to manage. ### Bring your own Linear app To run a Linear OAuth app you manage yourself, pass its credentials directly instead: ```ts title="agent/channels/linear.ts" import { linearChannel } from "eve/channels/linear"; export default linearChannel({ credentials: { accessToken: process.env.LINEAR_AGENT_ACCESS_TOKEN, webhookSecret: process.env.LINEAR_WEBHOOK_SECRET, }, }); ``` ```bash LINEAR_AGENT_ACCESS_TOKEN=lin_api_... # posts Agent Activities and creates proactive sessions LINEAR_WEBHOOK_SECRET=... # verifies Linear-Signature ``` The sample passes credentials explicitly. To rely on env vars instead, drop the `credentials` block: the access token falls back to `LINEAR_AGENT_ACCESS_TOKEN`, `LINEAR_ACCESS_TOKEN`, `LINEAR_API_KEY`, or `LINEAR_API_TOKEN`, and the webhook secret falls back to `LINEAR_WEBHOOK_SECRET`. Both fields also accept lazy resolver functions. Create the Linear OAuth app, enable Agent Session events, and point the webhook URL at: ```text https://<deployment>/eve/v1/linear ``` For Linear's agent surface, configure the OAuth authorize URL with `actor=app` and grant the app scopes that let it appear as an agent in Linear, including `app:assignable` and `app:mentionable`. Subscribe to the `AgentSessionEvent` webhook category so Linear sends `created` events when the agent is delegated or mentioned and `prompted` events when the user continues the session. Linear sends webhook signatures in `Linear-Signature`; eve verifies the HMAC over the raw body and rejects stale `webhookTimestamp` values. If a trusted gateway verifies Linear before the request reaches eve, pass `credentials.webhookVerifier` instead of a webhook secret. ## How the channel handles messages ### Dispatch The default hook dispatches `created` and `prompted` Agent Session events. eve adds a Linear context block with the agent session, issue, comment, and organization identifiers, then continues the same session with `agent-session:<id>`. ### Delivery Turn start posts an ephemeral `thought`, tool calls post ephemeral `action` activities, final assistant text posts a durable `response`, and failures post `error` activities. When the model emits text before a tool call, eve buffers the first non-empty line and uses it as the next ephemeral Linear `thought`, mirroring Slack's typing-status behavior. ### Human-in-the-loop (HITL) Human-in-the-loop (HITL) input requests render as Linear `elicitation` activities. When the user replies to the Agent Session, the channel resolves that prompt back to the pending eve input request and resumes with `inputResponses`. ### Proactive sessions Start a session without an inbound webhook with `receive(linear, { target })`. See [Proactive sessions](#proactive-sessions) below for the target shape and examples. ### Attachments Markdown images hosted at `https://uploads.linear.app` in Agent Session prompts are fetched with the resolved Linear access token and included as image file parts. eve sends the bearer token only to that exact HTTPS origin; images from other hosts remain markdown text. If a Linear upload fails or returns non-image content, eve preserves its markdown reference and continues the text turn. Other inbound file attachments are not supported on this channel today. ### API handle Event handlers receive `channel.linear`, which exposes `createActivity`, `listActivities`, and `updateSession` for custom Agent Activity delivery and Agent Session metadata. ## Custom hooks Return `{ auth }` to dispatch, or `null` to acknowledge without waking the agent. ```ts import { defaultLinearAuth, linearChannel } from "eve/channels/linear"; export default linearChannel({ onAgentSession: (_ctx, event) => { if (event.action !== "created" && event.action !== "prompted") return null; return { auth: defaultLinearAuth(event) }; }, }); ``` Restrict dispatch to a subset of Linear teams or projects by inspecting `event.agentSession.issue` in `onAgentSession`. Add extra context by returning `context` alongside `auth`. ```ts import { defaultLinearAuth, linearChannel } from "eve/channels/linear"; export default linearChannel({ onAgentSession: (_ctx, event) => { if (event.agentSession.issue?.identifier?.startsWith("OPS-") !== true) return null; return { auth: defaultLinearAuth(event), context: ["Only make reversible changes unless the issue says otherwise."], }; }, }); ``` Override event delivery when you want more specific Agent Activities. ```ts import { linearChannel } from "eve/channels/linear"; export default linearChannel({ events: { async "message.completed"(eventData, channel) { if (eventData.finishReason === "tool-calls" || !eventData.message) return; await channel.linear.createActivity({ body: `Done.\n\n${eventData.message}`, type: "response", }); }, async "input.requested"(eventData, channel) { await channel.linear.createActivity({ body: eventData.requests.map((request) => request.prompt).join("\n\n"), type: "elicitation", }); }, }, }); ``` Add session-level links when your agent creates an external artifact. ```ts await channel.linear.updateSession({ addedExternalUrls: [{ label: "Run log", url: "https://example.com/runs/123" }], }); ``` ## Proactive sessions Use the channel's `receive` target to continue an existing Agent Session or create one from a Linear issue or root comment. The target accepts an existing `agentSessionId`, or an `issueId` or root `commentId` to create a new session before sending the message. The example below runs from a schedule; a route handler uses the same target shape through its own `receive` helper. ```ts import { defineSchedule } from "eve/schedules"; import linear from "../channels/linear"; export default defineSchedule({ cron: "0 14 * * 1", async run({ receive, waitUntil, appAuth }) { waitUntil( receive(linear, { auth: appAuth, message: "Post a concise status update with blockers and next actions.", target: { issueId: "EVE-123", initialActivity: "Preparing the status update.", }, }), ); }, }); ``` For issue or comment targets, the channel calls Linear's proactive Agent Session mutations before starting the eve turn. For an existing `agentSessionId`, it skips session creation and only seeds the continuation token. ## What to read next - [Channels overview](./overview): the channel contract and every built-in channel - [MCP connections](../connections/mcp): use the Linear MCP connection when the agent needs to inspect or edit Linear data from another channel