@trigger.dev/linear 3.3.12 → 4.8.0-webhooks.20261015065339

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,370 @@
1
+ import type { ChannelAckCtx, ChannelConnector, ChannelControl, ChannelInbound, ChannelMessage, ChannelPendingToolCall, ChannelReply, ChannelRunSurface, ChannelSeedMessage } from "@trigger.dev/sdk/ai";
2
+ import type { ValidateWebhookFilter, WebhookEndpoint, WebhookSource } from "@trigger.dev/core/v3";
3
+ export type LinearUserRef = {
4
+ id: string;
5
+ name?: string;
6
+ displayName?: string;
7
+ email?: string;
8
+ url?: string;
9
+ };
10
+ export type LinearIssueRef = {
11
+ id: string;
12
+ identifier?: string;
13
+ title?: string;
14
+ description?: string | null;
15
+ url?: string;
16
+ teamId?: string;
17
+ };
18
+ export type LinearCommentRef = {
19
+ id?: string;
20
+ body?: string;
21
+ userId?: string | null;
22
+ createdAt?: string;
23
+ user?: LinearUserRef | null;
24
+ };
25
+ export type LinearAgentActivityRef = {
26
+ id: string;
27
+ agentSessionId?: string;
28
+ /** `{ type: "prompt", body }` for a user prompt. */
29
+ content: {
30
+ type?: string;
31
+ body?: string;
32
+ [key: string]: unknown;
33
+ };
34
+ /** `"stop"` when the user asked the agent to halt. */
35
+ signal?: string | null;
36
+ signalMetadata?: Record<string, unknown> | null;
37
+ sourceCommentId?: string | null;
38
+ userId?: string | null;
39
+ user?: LinearUserRef | null;
40
+ createdAt?: string;
41
+ };
42
+ export type LinearAgentSessionRef = {
43
+ id: string;
44
+ status?: string;
45
+ appUserId?: string;
46
+ creatorId?: string | null;
47
+ creator?: LinearUserRef | null;
48
+ issueId?: string | null;
49
+ issue?: LinearIssueRef | null;
50
+ /** The thread comment the session lives under. Set on both mentions and delegations. */
51
+ commentId?: string | null;
52
+ comment?: LinearCommentRef | null;
53
+ /** The comment that @mentioned the app. Null when the session came from a delegation. */
54
+ sourceCommentId?: string | null;
55
+ summary?: string | null;
56
+ startedAt?: string | null;
57
+ endedAt?: string | null;
58
+ url?: string | null;
59
+ };
60
+ /**
61
+ * A minimal Linear `AgentSessionEvent` webhook envelope (Linear-API `AgentSessionEventWebhookPayload`).
62
+ * Pass your own type for fuller typing.
63
+ */
64
+ export type LinearAgentSessionEvent = {
65
+ /** `"AgentSessionEvent"`; other Linear webhook types are filtered out server-side. */
66
+ type: string;
67
+ /** `"created"` (mention or delegation) or `"prompted"` (a new user message on the session). */
68
+ action: string;
69
+ createdAt?: string;
70
+ organizationId?: string;
71
+ oauthClientId?: string;
72
+ appUserId?: string;
73
+ webhookId?: string;
74
+ webhookTimestamp?: number;
75
+ agentSession: LinearAgentSessionRef;
76
+ /** The user's prompt on a `prompted` event. */
77
+ agentActivity?: LinearAgentActivityRef | null;
78
+ /** Linear's formatted issue context (identifier, title, description, labels, comments, ...). */
79
+ promptContext?: string | null;
80
+ /** Comments in the thread before the mention. */
81
+ previousComments?: LinearCommentRef[] | null;
82
+ /** Workspace or team guidance rules for agents. */
83
+ guidance?: Array<{
84
+ body?: string;
85
+ [key: string]: unknown;
86
+ }> | string | null;
87
+ };
88
+ export type LinearToken<TEvent> = string | ((event: TEvent) => string | Promise<string>);
89
+ /**
90
+ * The handle `linear()` binds a session to: enough to post activities into the agent session without
91
+ * an inbound event. A turn that no Linear prompt started (a `chat.event` action that returns
92
+ * `chat.turn()`) answers into this session.
93
+ */
94
+ export type LinearSessionHandle = {
95
+ agentSessionId: string;
96
+ organizationId?: string;
97
+ issueId?: string;
98
+ issueIdentifier?: string;
99
+ };
100
+ /** The Linear agent activity types a connector can post. `prompt` is user-only. */
101
+ export type LinearActivityType = "thought" | "action" | "elicitation" | "response" | "error";
102
+ /**
103
+ * How a channel message posts into Linear. The default outbound maps a final reply to a `response`,
104
+ * an error to an `error`, and a non-final stream update to an ephemeral `thought`. A custom `outbound`
105
+ * or `ack` can set this explicitly.
106
+ */
107
+ export type LinearActivitySpec = {
108
+ type: LinearActivityType;
109
+ /** Only for `thought` and `action`: shown until the next activity arrives. */
110
+ ephemeral?: boolean;
111
+ /** `action` only. */
112
+ action?: string;
113
+ parameter?: string;
114
+ result?: string;
115
+ /** `elicitation` only. */
116
+ signal?: "select" | "auth";
117
+ signalMetadata?: Record<string, unknown>;
118
+ /** Client-supplied UUID v4 for idempotent creation across run retries. */
119
+ id?: string;
120
+ };
121
+ /** One step of the plan Linear shows on the session. The whole plan is replaced on every update. */
122
+ export type LinearPlanStep = {
123
+ content: string;
124
+ status: "pending" | "inProgress" | "completed" | "canceled";
125
+ };
126
+ /**
127
+ * What `run({ channel })` carries on a Linear turn: the connector's helpers over the agent session,
128
+ * plus `react`/`unreact` (no-ops on Linear). Cast the `channel` argument to this type.
129
+ */
130
+ export type LinearRunSurface = ChannelRunSurface<{
131
+ /** Replace the session's plan. Linear renders it as a checklist that updates in place. */
132
+ plan: (steps: LinearPlanStep[]) => Promise<void>;
133
+ /** Post a thought. Ephemeral thoughts disappear when the next activity arrives. */
134
+ thought: (text: string, options?: {
135
+ ephemeral?: boolean;
136
+ }) => Promise<void>;
137
+ /** Post an action ("Searching docs", "auth timeout"), optionally with its result. */
138
+ action: (action: string, parameter: string, result?: string) => Promise<void>;
139
+ /** Add a labelled external link to the session header. */
140
+ link: (label: string, url: string) => Promise<void>;
141
+ /**
142
+ * Set the session's one-line summary from the first usable line of `text` (markdown markers and
143
+ * control characters stripped, capped at 255 characters). Text with no usable line sets nothing.
144
+ */
145
+ summary: (text: string) => Promise<void>;
146
+ /** The bound agent session and issue. */
147
+ session: LinearSessionHandle;
148
+ }>;
149
+ /** The name of the tool {@link linearPlanTool} defines; the progress mirror skips it. */
150
+ export declare const LINEAR_PLAN_TOOL_NAME = "updatePlan";
151
+ /**
152
+ * A tool the model calls to set and update its plan for the issue. Add it to the agent's `tools`
153
+ * and ask for a plan in the system prompt; Linear renders it as a checklist that updates in place.
154
+ * The whole list is replaced on every call. On a turn that is not a Linear session the tool
155
+ * reports that nothing was updated, so the same agent can serve other surfaces unchanged.
156
+ */
157
+ export declare function linearPlanTool(): import("ai").Tool<{
158
+ steps: LinearPlanStep[];
159
+ }, {
160
+ updated: boolean;
161
+ reason: string;
162
+ steps?: undefined;
163
+ } | {
164
+ reason?: undefined;
165
+ updated: boolean;
166
+ steps: number;
167
+ }>;
168
+ /** The channel message shape `linear()` posts: text plus how it becomes an activity. */
169
+ export type LinearChannelMessage = ChannelMessage & {
170
+ activity?: LinearActivitySpec;
171
+ /** Also set the session summary (1 to 255 characters) after posting. */
172
+ summary?: string;
173
+ /** Post several activities in order (used by HITL controls, one elicitation per pending tool). */
174
+ activities?: Array<LinearActivitySpec & {
175
+ text: string;
176
+ }>;
177
+ };
178
+ export type LinearChannelOptions<TEvent, TFilter extends string = string, TStartOn extends string = string> = {
179
+ id: string;
180
+ /** The endpoint to subscribe to, declared with `webhooks.endpoint.define({ id, source: webhookSource() })`. */
181
+ endpoint: WebhookEndpoint<TEvent>;
182
+ /**
183
+ * OAuth access token for the workspace the app is installed in (`actor=app`), or a resolver keyed on
184
+ * the event's `organizationId` for multi-workspace installs.
185
+ */
186
+ token: LinearToken<TEvent>;
187
+ /**
188
+ * Session key template over `{event.x}`, `{header.x}` and `{webhook.x}` placeholders. Defaults to one
189
+ * session per Linear agent session.
190
+ */
191
+ key?: string;
192
+ /** Map an event to the turn's message. Defaults to the framing described in the docs. */
193
+ inbound?: (event: TEvent) => ChannelInbound;
194
+ /** Map the agent's reply to an activity. Defaults to `response` (final), `error`, or ephemeral `thought`. */
195
+ outbound?: (reply: ChannelReply) => LinearChannelMessage | null;
196
+ /**
197
+ * The first activity, posted as soon as the turn starts (Linear expects one within ten seconds of
198
+ * `created`). Defaults to an ephemeral thought "Looking into this...". `null` posts nothing.
199
+ */
200
+ ack?: ((event: TEvent, ctx: ChannelAckCtx) => LinearChannelMessage | null) | null;
201
+ /** Extra server-side filter, composed AND with the mandatory agent-session guard. */
202
+ filter?: ValidateWebhookFilter<TEvent, TFilter>;
203
+ /** Only start a NEW session when an event matches this filter; existing sessions always resume. */
204
+ startOn?: ValidateWebhookFilter<TEvent, TStartOn>;
205
+ /** "final" (default): ack then the response. "stream": debounced ephemeral thoughts while streaming. */
206
+ delivery?: "final" | "stream";
207
+ /**
208
+ * Mirror the turn's tool calls into the session as `action` activities: the call as an ephemeral
209
+ * action when its input is ready, then the same action with its `result` when the tool returns.
210
+ * `"off"` posts nothing while the turn runs. Default `"actions"`.
211
+ */
212
+ progress?: "actions" | "off";
213
+ /**
214
+ * Set the session summary (its title in Linear) from the first line of each final reply. Off by
215
+ * default: Linear generates a title from the conversation itself, and a reply's first line
216
+ * overwrites it on every turn.
217
+ */
218
+ summarize?: boolean;
219
+ /**
220
+ * What from the payload is put in front of the model on the first turn. All default to true.
221
+ * `guidance` and `promptContext` are prefixed into the triggering message; `previousComments` are
222
+ * seeded as prior transcript messages.
223
+ */
224
+ context?: {
225
+ guidance?: boolean;
226
+ promptContext?: boolean;
227
+ previousComments?: boolean;
228
+ };
229
+ /**
230
+ * How a pending human-decision tool is described on the approval card, per tool name. Return the
231
+ * card's markdown body (a title line is added when `title` is set); return null or omit a tool to
232
+ * use the default, which renders the tool input as markdown (lists of items, key and value bullets)
233
+ * and falls back to JSON for shapes it cannot lay out.
234
+ */
235
+ approvals?: Record<string, (input: unknown, call: ChannelPendingToolCall) => {
236
+ title?: string;
237
+ body: string;
238
+ } | null>;
239
+ /**
240
+ * Linear UI for tools with no `execute`, keyed by tool name: an elicitation card with a select of
241
+ * `options`. The chosen option resolves the tool to `{ selected: option }`. A tool without an entry
242
+ * gets the Approve and Deny card (see `approvals`).
243
+ */
244
+ interactions?: Record<string, (call: ChannelPendingToolCall) => {
245
+ title?: string;
246
+ body: string;
247
+ options: string[];
248
+ } | null>;
249
+ /**
250
+ * The thought posted as soon as a prompt arrives while the agent is still working. The prompt is
251
+ * either folded into the work in progress (`pendingMessages` on the agent) or answered right
252
+ * after, so the wording should be true either way. Default "Got it, I'll take that into
253
+ * account."; a function of the event for wording per prompt; null posts nothing.
254
+ */
255
+ receivedThought?: string | null | ((event: TEvent) => string | null);
256
+ /**
257
+ * Override the Linear GraphQL URL (for testing against a mock). Files on this URL's origin are
258
+ * also treated as Linear uploads.
259
+ */
260
+ apiUrl?: string;
261
+ };
262
+ /**
263
+ * The Linear source for `webhooks.endpoint.define({ id, source: webhookSource() })`:
264
+ * `Linear-Signature` HMAC verification, a one minute replay window on the body's
265
+ * `webhookTimestamp`, dedup on the signed request (the same config as `webhooks.linear()`), and a
266
+ * setup prompt. Paste the signing secret from the OAuth app's webhook settings as the endpoint
267
+ * secret. Subscribe `webhook()` tasks to the endpoint, or pass it to `linear({ endpoint })` for an
268
+ * agent frontend.
269
+ */
270
+ export declare function webhookSource<TEvent = LinearAgentSessionEvent>(): WebhookSource<TEvent>;
271
+ /**
272
+ * Linear agent sessions as a chat frontend for an agent. List on `chat.agent({ channels: [linear({...})] })`:
273
+ * when a user mentions the app or delegates an issue to it, Linear opens an agent session, the connector
274
+ * routes it to a durable session keyed on the agent session id, runs the prompt as a turn, and posts the
275
+ * reply back as a `response` activity. Install the OAuth app with `actor=app`, request the
276
+ * `app:assignable` and `app:mentionable` scopes, enable the "Agent session events" webhook category, and
277
+ * set the endpoint's signing secret to the webhook signing secret.
278
+ *
279
+ * @example
280
+ * ```ts
281
+ * const linearEvents = webhooks.endpoint.define({ id: "linear", source: webhookSource() });
282
+ * export const agent = chat.agent({
283
+ * id: "triage",
284
+ * channels: [linear({ id: "linear", endpoint: linearEvents, token: process.env.LINEAR_TOKEN! })],
285
+ * run: async ({ messages, streamText }) => streamText({ model, messages }),
286
+ * });
287
+ * ```
288
+ */
289
+ export declare function linear<TEvent = LinearAgentSessionEvent, const TFilter extends string = string, const TStartOn extends string = string>(options: LinearChannelOptions<TEvent, TFilter, TStartOn>): ChannelConnector<TEvent, LinearSessionHandle>;
290
+ /** The agent session an event belongs to, for `bind` and for posting. */
291
+ export declare function linearSessionHandle(event: LinearAgentSessionEvent): LinearSessionHandle | null;
292
+ /**
293
+ * True when the session came from an @mention in a comment. A delegation (the issue assigned to the
294
+ * app) has no `sourceCommentId`; Linear still attaches a placeholder comment ("This thread is for an
295
+ * agent session with ...") to it, so `comment` alone does not tell the two apart.
296
+ */
297
+ export declare function isMention(event: LinearAgentSessionEvent): boolean;
298
+ /**
299
+ * A Stop pressed in Linear while a turn is running aborts that turn: the partial reply posts as the
300
+ * response and the stop prompt is consumed. Pressed while idle, the same prompt runs as a turn and
301
+ * `defaultLinearInbound` turns it into a wind-down instruction. The stop is dated with the
302
+ * activity's `createdAt`, so one pressed while a turn ran but delivered after it ended is consumed
303
+ * rather than run as a wind-down.
304
+ */
305
+ export declare function linearControl(event: LinearAgentSessionEvent): ChannelControl | null;
306
+ /** True when the user asked the agent to halt (`prompted` with the `stop` signal). */
307
+ export declare function isStopSignal(event: LinearAgentSessionEvent): boolean;
308
+ /**
309
+ * Default inbound: the user's prompt, framed so the model can tell Linear-supplied context from
310
+ * instructions. `created` carries the issue context and the mention comment (or an assignment notice
311
+ * when the session came from delegation); `prompted` carries just the new message. A `stop` signal
312
+ * becomes an instruction to wind down.
313
+ */
314
+ export declare function defaultLinearInbound(event: LinearAgentSessionEvent, context?: {
315
+ guidance: boolean;
316
+ promptContext: boolean;
317
+ uploadsOrigin?: string;
318
+ }): ChannelInbound;
319
+ /** A file the model can read, from a Linear upload link in markdown. */
320
+ export type LinearFilePart = {
321
+ type: "file";
322
+ mediaType: string;
323
+ url: string;
324
+ filename: string;
325
+ };
326
+ /**
327
+ * Images and PDFs a Linear body links to on Linear's upload host, as file parts for the model: a
328
+ * `<linear-image>` tag (how Linear embeds images in the issue context) or `![alt](url)` as an image,
329
+ * and `[name.pdf](url)` as a PDF. A signed URL's short-lived `signature` is dropped; `linear()`
330
+ * downloads the file with the app's token when the model reads it. The default `inbound` adds them
331
+ * for the prompt, the mention comment and the issue context; use it in a custom `inbound` to keep
332
+ * that. At most ten per message.
333
+ */
334
+ export declare function linearFileParts(markdown: string | null | undefined, options?: {
335
+ uploadsOrigin?: string;
336
+ }): LinearFilePart[];
337
+ /** Prior comments from the payload become transcript messages ahead of the triggering one. */
338
+ export declare function seedFromPreviousComments(event: LinearAgentSessionEvent): ChannelSeedMessage[];
339
+ /**
340
+ * A reply cut short by Stop: close a code fence the cut left open so the rest of the session renders,
341
+ * and say it was stopped.
342
+ */
343
+ export declare function stoppedMarkdown(text: string): string;
344
+ /** Linear renders GitHub-flavored markdown and turns bare Linear URLs into mentions. Trims only. */
345
+ export declare function toLinearMarkdown(text: string): string;
346
+ /**
347
+ * The first non-empty line of a reply, stripped of markdown markers and control characters, capped at
348
+ * 255 characters: what Linear accepts as a session summary. Undefined when nothing usable is left.
349
+ */
350
+ export declare function summaryLine(text: string): string | undefined;
351
+ /** A minimal GraphQL call against Linear's API with the app's OAuth token. */
352
+ export declare function linearGraphql<T>(apiUrl: string, accessToken: string, query: string, variables: Record<string, unknown>): Promise<T>;
353
+ /**
354
+ * Default HITL controls: one `elicitation` per pending human-decision tool, with a `select` signal
355
+ * offering Approve and Deny. Linear shows the chosen option's value as the user's message and delivers
356
+ * it back as a regular `prompted` activity. The value names the tool and the call
357
+ * ("Approve createSubIssues [call_a]"), so each card resolves exactly the call whose input it shows,
358
+ * even when an earlier turn left a same-named call pending; `onInteraction` still accepts a bare tool
359
+ * name typed by hand.
360
+ */
361
+ type ApprovalRenderer = NonNullable<LinearChannelOptions<unknown>["approvals"]>[string];
362
+ /** The markdown body of one approval card: a custom renderer's output, else the input laid out. */
363
+ export declare function approvalCard(call: ChannelPendingToolCall, custom?: ApprovalRenderer): string;
364
+ /**
365
+ * Lay a tool input out as markdown for a person to read: an array of objects becomes a numbered list
366
+ * with a bold title line and the other fields under it, a flat object becomes key and value bullets,
367
+ * an array of scalars a bullet list. Anything deeper falls back to a JSON block.
368
+ */
369
+ export declare function inputToMarkdown(input: unknown): string;
370
+ export {};