@calltelemetry/openclaw-linear 0.3.1 → 0.4.0
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.
- package/README.md +322 -386
- package/index.ts +50 -2
- package/openclaw.plugin.json +7 -1
- package/package.json +3 -2
- package/src/active-session.ts +66 -0
- package/src/agent.ts +173 -1
- package/src/auth.ts +6 -2
- package/src/claude-tool.ts +280 -0
- package/src/cli-shared.ts +75 -0
- package/src/cli.ts +39 -0
- package/src/client.ts +1 -0
- package/src/code-tool.ts +202 -0
- package/src/codex-tool.ts +240 -0
- package/src/codex-worktree.ts +264 -0
- package/src/gemini-tool.ts +238 -0
- package/src/orchestration-tools.ts +134 -0
- package/src/pipeline.ts +68 -10
- package/src/tools.ts +29 -79
- package/src/webhook.ts +321 -90
package/README.md
CHANGED
|
@@ -1,157 +1,287 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @calltelemetry/openclaw-linear
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
An OpenClaw plugin that connects your Linear workspace to AI agents. Issues get triaged automatically, agents respond to @mentions, and a full plan-implement-audit pipeline runs when you assign work to the agent.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Features
|
|
6
6
|
|
|
7
|
-
- **Auto-triage** — New issues
|
|
8
|
-
-
|
|
9
|
-
- **Agent
|
|
10
|
-
-
|
|
11
|
-
- **
|
|
12
|
-
- **
|
|
7
|
+
- **Auto-triage** — New issues get story point estimates, labels, and priority automatically
|
|
8
|
+
- **@mention routing** — `@qa`, `@infra`, `@docs` in comments route to specialized agents
|
|
9
|
+
- **Agent pipeline** — Assign an issue to the agent and it plans, implements, and audits the work
|
|
10
|
+
- **Branded replies** — Each agent posts with its own name and avatar in Linear
|
|
11
|
+
- **Real-time progress** — Agent activity (thinking, acting, responding) shows in Linear's UI
|
|
12
|
+
- **Unified `code_run` tool** — One tool, three coding CLI backends (Codex, Claude Code, Gemini CLI), configurable per agent
|
|
13
|
+
- **Issue management via `linearis`** — Agents use the `linearis` CLI to update status, close issues, add comments, and more
|
|
13
14
|
|
|
14
|
-
##
|
|
15
|
+
## Architecture
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
### Webhook Flow
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
Linear OpenClaw Gateway AI Agents
|
|
21
|
+
| | |
|
|
22
|
+
| Webhook (issue created) | |
|
|
23
|
+
| ────────────────────────>| |
|
|
24
|
+
| | Dispatch triage agent |
|
|
25
|
+
| | ───────────────────────>|
|
|
26
|
+
| | |
|
|
27
|
+
| | Estimate + labels |
|
|
28
|
+
| | <───────────────────────|
|
|
29
|
+
| Update issue | |
|
|
30
|
+
| <────────────────────────| |
|
|
31
|
+
| Post assessment comment | |
|
|
32
|
+
| <────────────────────────| |
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
Linear OpenClaw Gateway AI Agents
|
|
37
|
+
| | |
|
|
38
|
+
| "@qa check this" | |
|
|
39
|
+
| ────────────────────────>| |
|
|
40
|
+
| | Route to QA agent |
|
|
41
|
+
| | ───────────────────────>|
|
|
42
|
+
| | |
|
|
43
|
+
| | Response |
|
|
44
|
+
| | <───────────────────────|
|
|
45
|
+
| Comment from "QA" | |
|
|
46
|
+
| <────────────────────────| |
|
|
19
47
|
```
|
|
20
48
|
|
|
21
|
-
|
|
49
|
+
### Two Webhook Systems
|
|
50
|
+
|
|
51
|
+
Linear delivers events through two separate webhook paths:
|
|
22
52
|
|
|
23
|
-
|
|
53
|
+
1. **Workspace webhook** (Settings > API > Webhooks) — handles Comment, Issue, and User events
|
|
54
|
+
2. **OAuth app webhook** (Settings > API > Applications > your app) — handles `AgentSessionEvent` (created/prompted)
|
|
24
55
|
|
|
25
|
-
|
|
56
|
+
Both must point to the same URL: `https://<your-domain>/linear/webhook`
|
|
26
57
|
|
|
27
|
-
|
|
58
|
+
### Source Layout
|
|
28
59
|
|
|
29
60
|
```
|
|
30
|
-
|
|
61
|
+
index.ts Plugin entry point, CLI checks, tool/webhook registration
|
|
62
|
+
src/
|
|
63
|
+
webhook.ts Webhook handler — routes events to agents, builds prompts
|
|
64
|
+
pipeline.ts 3-stage pipeline: plan → implement → audit
|
|
65
|
+
agent.ts Agent execution wrapper
|
|
66
|
+
active-session.ts In-process session registry (issueId → session)
|
|
67
|
+
|
|
68
|
+
code-tool.ts Unified code_run tool — dispatches to configured backend
|
|
69
|
+
cli-shared.ts Shared helpers for CLI tools (buildLinearApi, resolveSession)
|
|
70
|
+
codex-tool.ts Codex CLI runner (JSONL stream → Linear activities)
|
|
71
|
+
claude-tool.ts Claude Code CLI runner (JSONL stream → Linear activities)
|
|
72
|
+
gemini-tool.ts Gemini CLI runner (JSONL stream → Linear activities)
|
|
73
|
+
coding-tools.json Backend config (default tool, per-agent overrides, aliases)
|
|
74
|
+
|
|
75
|
+
tools.ts Tool registration (code_run + orchestration)
|
|
76
|
+
orchestration-tools.ts spawn_agent / ask_agent for multi-agent delegation
|
|
77
|
+
linear-api.ts Linear GraphQL API client, token resolution, activity streaming
|
|
78
|
+
client.ts Lightweight Linear GraphQL client (legacy, unused by tools)
|
|
79
|
+
auth.ts OAuth token management and profile storage
|
|
80
|
+
oauth-callback.ts OAuth callback handler
|
|
81
|
+
cli.ts CLI subcommands (auth, status)
|
|
82
|
+
codex-worktree.ts Git worktree management for isolated Codex runs
|
|
31
83
|
```
|
|
32
84
|
|
|
33
|
-
|
|
85
|
+
## Coding Tool (`code_run`)
|
|
34
86
|
|
|
35
|
-
|
|
87
|
+
The plugin provides a single `code_run` tool that dispatches to one of three coding CLI backends. Agents call `code_run` without needing to know which backend is active — the dispatcher handles routing.
|
|
36
88
|
|
|
37
|
-
|
|
38
|
-
# Set your OAuth app credentials
|
|
39
|
-
export LINEAR_CLIENT_ID="your_client_id"
|
|
40
|
-
export LINEAR_CLIENT_SECRET="your_client_secret"
|
|
89
|
+
### Supported Backends
|
|
41
90
|
|
|
42
|
-
|
|
43
|
-
|
|
91
|
+
| Backend | CLI | Stream Format | Key Flags |
|
|
92
|
+
|---|---|---|---|
|
|
93
|
+
| **Codex** (OpenAI) | `codex` | JSONL | `--full-auto`, `-q` |
|
|
94
|
+
| **Claude Code** (Anthropic) | `claude` | JSONL (`stream-json`) | `--print`, `--dangerously-skip-permissions`, `--verbose` |
|
|
95
|
+
| **Gemini CLI** (Google) | `gemini` | JSONL (`stream-json`) | `--yolo`, `-o stream-json` |
|
|
44
96
|
|
|
45
|
-
|
|
46
|
-
|
|
97
|
+
All three stream JSONL events that get mapped to Linear agent activities in real-time (thoughts, actions, tool results).
|
|
98
|
+
|
|
99
|
+
### Backend Resolution Priority
|
|
100
|
+
|
|
101
|
+
When `code_run` is called:
|
|
102
|
+
|
|
103
|
+
1. **Explicit `backend` parameter** — Agent passes `backend: "gemini"` (or any alias)
|
|
104
|
+
2. **Per-agent override** — `agentCodingTools` in `coding-tools.json`
|
|
105
|
+
3. **Global default** — `codingTool` in `coding-tools.json`
|
|
106
|
+
4. **Hardcoded fallback** — `"claude"`
|
|
107
|
+
|
|
108
|
+
### Configuration (`coding-tools.json`)
|
|
109
|
+
|
|
110
|
+
```json
|
|
111
|
+
{
|
|
112
|
+
"codingTool": "codex",
|
|
113
|
+
"agentCodingTools": {
|
|
114
|
+
"kaylee": "claude",
|
|
115
|
+
"inara": "gemini"
|
|
116
|
+
},
|
|
117
|
+
"backends": {
|
|
118
|
+
"claude": {
|
|
119
|
+
"aliases": ["claude", "claude code", "anthropic"]
|
|
120
|
+
},
|
|
121
|
+
"codex": {
|
|
122
|
+
"aliases": ["codex", "openai"]
|
|
123
|
+
},
|
|
124
|
+
"gemini": {
|
|
125
|
+
"aliases": ["gemini", "google"]
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
47
129
|
```
|
|
48
130
|
|
|
49
|
-
|
|
131
|
+
- **`codingTool`** — Default backend for all agents
|
|
132
|
+
- **`agentCodingTools`** — Per-agent overrides (keyed by agent ID)
|
|
133
|
+
- **`backends.*.aliases`** — Alias strings so the agent (or user) can say "use google" and it resolves to `gemini`
|
|
134
|
+
|
|
135
|
+
### Backend-Specific Notes
|
|
136
|
+
|
|
137
|
+
**Claude Code:**
|
|
138
|
+
- Must unset `CLAUDECODE` env var to avoid "nested session" error
|
|
139
|
+
- Requires `--verbose` alongside `stream-json` for full event output
|
|
140
|
+
- Content blocks are arrays: `message.content[].type` can be `text` or `tool_use`
|
|
141
|
+
|
|
142
|
+
**Gemini CLI:**
|
|
143
|
+
- Working directory set via `spawn()` `cwd` option (no `-C` flag)
|
|
144
|
+
- Model override via `-m <model>` flag
|
|
145
|
+
- Stderr may include "YOLO mode" warnings — filtered from output
|
|
146
|
+
|
|
147
|
+
**Codex:**
|
|
148
|
+
- Uses git worktrees for isolated runs (see `codex-worktree.ts`)
|
|
149
|
+
- Model/timeout configurable via plugin config (`codexModel`, `codexTimeoutMs`)
|
|
150
|
+
|
|
151
|
+
## Linear Issue Management (`linearis` Skill)
|
|
152
|
+
|
|
153
|
+
Issue management (update status, close, assign, comment, labels, etc.) is handled by the **`linearis`** CLI, installed as an OpenClaw skill. This replaces custom GraphQL tools — agents use `linearis` via exec.
|
|
154
|
+
|
|
155
|
+
### Install
|
|
50
156
|
|
|
51
157
|
```bash
|
|
52
|
-
|
|
158
|
+
npx clawhub install linearis
|
|
159
|
+
npm install -g linearis
|
|
53
160
|
```
|
|
54
161
|
|
|
55
|
-
|
|
162
|
+
### Auth
|
|
56
163
|
|
|
164
|
+
```bash
|
|
165
|
+
echo "lin_api_..." > ~/.linear_api_token
|
|
57
166
|
```
|
|
58
|
-
|
|
59
|
-
|
|
167
|
+
|
|
168
|
+
Or set `LINEAR_API_TOKEN` env var.
|
|
169
|
+
|
|
170
|
+
### Key Commands
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
linearis issues list -l 20 # List recent issues
|
|
174
|
+
linearis issues list --team UAT # Filter by team
|
|
175
|
+
linearis issues search "auth bug" # Full-text search
|
|
176
|
+
linearis issues read API-123 # Get issue details
|
|
177
|
+
linearis issues update API-123 --status "Done" # Close issue
|
|
178
|
+
linearis issues update API-123 --status "In Progress"
|
|
179
|
+
linearis issues update API-123 --assignee user123
|
|
180
|
+
linearis issues update API-123 --labels "Bug" --label-by adding
|
|
181
|
+
linearis issues create --title "Fix it" --team UAT --priority 2
|
|
182
|
+
linearis comments create API-123 --body "Fixed in PR #456"
|
|
183
|
+
linearis teams list
|
|
184
|
+
linearis users list --active
|
|
185
|
+
linearis projects list
|
|
186
|
+
linearis documents list
|
|
187
|
+
linearis usage # Full command reference
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
All output is JSON, suitable for piping to `jq`.
|
|
60
191
|
|
|
61
192
|
## Prerequisites
|
|
62
193
|
|
|
63
|
-
- OpenClaw gateway running (
|
|
64
|
-
-
|
|
65
|
-
-
|
|
66
|
-
-
|
|
194
|
+
- **OpenClaw** gateway running (v2026.2+)
|
|
195
|
+
- **Linear** workspace with API access
|
|
196
|
+
- **Public URL** for webhook delivery (Cloudflare Tunnel recommended)
|
|
197
|
+
- **Coding CLIs** (at least one): `codex`, `claude`, `gemini` — installed in PATH
|
|
198
|
+
- **linearis** CLI — for issue management
|
|
199
|
+
|
|
200
|
+
## Install
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
openclaw plugins install @calltelemetry/openclaw-linear
|
|
204
|
+
```
|
|
67
205
|
|
|
68
206
|
## Setup
|
|
69
207
|
|
|
70
|
-
### 1. Create a Linear OAuth
|
|
208
|
+
### 1. Create a Linear OAuth App
|
|
209
|
+
|
|
210
|
+
Go to **Linear Settings > API > Applications** and create a new application:
|
|
211
|
+
|
|
212
|
+
- **Webhook URL:** `https://<your-domain>/linear/webhook`
|
|
213
|
+
- **Redirect URI:** `https://<your-domain>/linear/oauth/callback`
|
|
214
|
+
- Enable webhook events: **Agent Sessions**, **Comments**, **Issues**
|
|
215
|
+
|
|
216
|
+
Save the **Client ID** and **Client Secret**.
|
|
217
|
+
|
|
218
|
+
### 2. Set Credentials
|
|
71
219
|
|
|
72
|
-
|
|
73
|
-
2. Click **Create new application**
|
|
74
|
-
3. Fill in:
|
|
75
|
-
- **Application name:** your agent's name
|
|
76
|
-
- **Redirect URI:** `https://<your-domain>/linear/oauth/callback`
|
|
77
|
-
- **Webhook URL:** `https://<your-domain>/linear/webhook`
|
|
78
|
-
4. Note the **Client ID** and **Client Secret**
|
|
79
|
-
5. Enable the webhook events you need (Agent Sessions, Issues)
|
|
220
|
+
Add to your gateway's environment (systemd service or shell):
|
|
80
221
|
|
|
81
|
-
|
|
222
|
+
```bash
|
|
223
|
+
export LINEAR_CLIENT_ID="your_client_id"
|
|
224
|
+
export LINEAR_CLIENT_SECRET="your_client_secret"
|
|
225
|
+
```
|
|
82
226
|
|
|
83
|
-
|
|
227
|
+
For systemd:
|
|
84
228
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
229
|
+
```ini
|
|
230
|
+
[Service]
|
|
231
|
+
Environment=LINEAR_CLIENT_ID=your_client_id
|
|
232
|
+
Environment=LINEAR_CLIENT_SECRET=your_client_secret
|
|
233
|
+
```
|
|
88
234
|
|
|
89
|
-
|
|
235
|
+
Then reload: `systemctl --user daemon-reload && systemctl --user restart openclaw-gateway`
|
|
90
236
|
|
|
91
|
-
### 3. Expose the Gateway
|
|
237
|
+
### 3. Expose the Gateway
|
|
92
238
|
|
|
93
|
-
|
|
239
|
+
Linear needs to reach your gateway over HTTPS to deliver webhooks. A [Cloudflare Tunnel](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/) is the recommended approach — no open ports, no TLS certificates to manage.
|
|
94
240
|
|
|
95
|
-
#### Install `cloudflared`
|
|
241
|
+
#### a. Install `cloudflared`
|
|
96
242
|
|
|
97
243
|
```bash
|
|
98
244
|
# RHEL / Rocky / Alma
|
|
99
245
|
sudo dnf install -y cloudflared
|
|
100
246
|
|
|
101
247
|
# Debian / Ubuntu
|
|
102
|
-
|
|
103
|
-
echo "deb [signed-by=/usr/share/keyrings/cloudflare-main.gpg] https://pkg.cloudflare.com/cloudflared $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/cloudflared.list
|
|
104
|
-
sudo apt update && sudo apt install -y cloudflared
|
|
248
|
+
sudo apt install -y cloudflared
|
|
105
249
|
|
|
106
250
|
# macOS
|
|
107
251
|
brew install cloudflare/cloudflare/cloudflared
|
|
108
252
|
```
|
|
109
253
|
|
|
110
|
-
#### Authenticate with Cloudflare
|
|
254
|
+
#### b. Authenticate with Cloudflare
|
|
111
255
|
|
|
112
256
|
```bash
|
|
113
257
|
cloudflared tunnel login
|
|
114
258
|
```
|
|
115
259
|
|
|
116
|
-
This opens your browser to
|
|
260
|
+
This opens your browser. Log in, select the domain you want to use, and click **Authorize**.
|
|
117
261
|
|
|
118
|
-
|
|
119
|
-
2. **Select the domain** (zone) you want the tunnel to use (e.g., `yourdomain.com`)
|
|
120
|
-
3. Click **Authorize**
|
|
121
|
-
|
|
122
|
-
Cloudflare writes an origin certificate to `~/.cloudflared/cert.pem`. This certificate grants `cloudflared` permission to create tunnels and DNS records under that domain. Without it, tunnel creation will fail.
|
|
123
|
-
|
|
124
|
-
#### Create a tunnel
|
|
262
|
+
#### c. Create a tunnel
|
|
125
263
|
|
|
126
264
|
```bash
|
|
127
265
|
cloudflared tunnel create openclaw
|
|
128
266
|
```
|
|
129
267
|
|
|
130
|
-
|
|
268
|
+
Note the **Tunnel ID** (a UUID) from the output.
|
|
131
269
|
|
|
132
|
-
####
|
|
270
|
+
#### d. Point a subdomain at the tunnel
|
|
133
271
|
|
|
134
272
|
```bash
|
|
135
273
|
cloudflared tunnel route dns openclaw linear.yourdomain.com
|
|
136
274
|
```
|
|
137
275
|
|
|
138
|
-
This creates a
|
|
139
|
-
|
|
140
|
-
```
|
|
141
|
-
linear.yourdomain.com CNAME <TUNNEL_ID>.cfargotunnel.com
|
|
142
|
-
```
|
|
276
|
+
This creates a DNS record so `linear.yourdomain.com` routes through the tunnel.
|
|
143
277
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
> **Important:** Your domain must already be on Cloudflare (nameservers pointed to Cloudflare). If it's not, add it in the Cloudflare dashboard first.
|
|
147
|
-
|
|
148
|
-
#### Configure the tunnel
|
|
278
|
+
#### e. Configure the tunnel
|
|
149
279
|
|
|
150
280
|
Create `~/.cloudflared/config.yml`:
|
|
151
281
|
|
|
152
282
|
```yaml
|
|
153
283
|
tunnel: <TUNNEL_ID>
|
|
154
|
-
credentials-file:
|
|
284
|
+
credentials-file: ~/.cloudflared/<TUNNEL_ID>.json
|
|
155
285
|
|
|
156
286
|
ingress:
|
|
157
287
|
- hostname: linear.yourdomain.com
|
|
@@ -159,22 +289,21 @@ ingress:
|
|
|
159
289
|
- service: http_status:404
|
|
160
290
|
```
|
|
161
291
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
#### Run as a systemd service
|
|
292
|
+
#### f. Start the tunnel
|
|
165
293
|
|
|
166
294
|
```bash
|
|
295
|
+
# Install as a system service (starts on boot)
|
|
167
296
|
sudo cloudflared service install
|
|
168
297
|
sudo systemctl enable --now cloudflared
|
|
169
298
|
```
|
|
170
299
|
|
|
171
|
-
|
|
300
|
+
To test without installing as a service:
|
|
172
301
|
|
|
173
302
|
```bash
|
|
174
303
|
cloudflared tunnel run openclaw
|
|
175
304
|
```
|
|
176
305
|
|
|
177
|
-
#### Verify
|
|
306
|
+
#### g. Verify the tunnel
|
|
178
307
|
|
|
179
308
|
```bash
|
|
180
309
|
curl -s https://linear.yourdomain.com/linear/webhook \
|
|
@@ -183,228 +312,105 @@ curl -s https://linear.yourdomain.com/linear/webhook \
|
|
|
183
312
|
# Should return: "ok"
|
|
184
313
|
```
|
|
185
314
|
|
|
186
|
-
|
|
315
|
+
### 4. Authorize with Linear
|
|
187
316
|
|
|
188
|
-
### 4. Set Environment Variables
|
|
189
|
-
|
|
190
|
-
Required:
|
|
191
317
|
```bash
|
|
192
|
-
|
|
193
|
-
export LINEAR_CLIENT_SECRET="your_client_secret"
|
|
318
|
+
openclaw openclaw-linear auth
|
|
194
319
|
```
|
|
195
320
|
|
|
196
|
-
|
|
197
|
-
```bash
|
|
198
|
-
export LINEAR_REDIRECT_URI="https://your-domain.com/linear/oauth/callback"
|
|
199
|
-
export OPENCLAW_GATEWAY_PORT="18789" # if non-default
|
|
200
|
-
```
|
|
321
|
+
This opens your browser to authorize the agent. The plugin needs these OAuth scopes:
|
|
201
322
|
|
|
202
|
-
|
|
323
|
+
| Scope | What it enables |
|
|
324
|
+
|---|---|
|
|
325
|
+
| `read` / `write` | Read and update issues, post comments |
|
|
326
|
+
| `app:assignable` | Agent appears in Linear's assignment menus |
|
|
327
|
+
| `app:mentionable` | Users can @mention the agent in comments |
|
|
203
328
|
|
|
204
|
-
|
|
329
|
+
After authorization, restart the gateway:
|
|
205
330
|
|
|
206
331
|
```bash
|
|
207
|
-
|
|
208
|
-
openclaw gateway restart
|
|
209
|
-
```
|
|
210
|
-
|
|
211
|
-
This registers the plugin in your OpenClaw config and restarts the gateway to load it.
|
|
212
|
-
|
|
213
|
-
<details>
|
|
214
|
-
<summary>Manual config (advanced)</summary>
|
|
215
|
-
|
|
216
|
-
If you prefer to manage config by hand, add the plugin path to `~/.openclaw/openclaw.json`:
|
|
217
|
-
|
|
218
|
-
```json
|
|
219
|
-
{
|
|
220
|
-
"plugins": {
|
|
221
|
-
"load": {
|
|
222
|
-
"paths": ["/path/to/linear"]
|
|
223
|
-
},
|
|
224
|
-
"entries": {
|
|
225
|
-
"openclaw-linear": {
|
|
226
|
-
"enabled": true
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
}
|
|
332
|
+
systemctl --user restart openclaw-gateway
|
|
231
333
|
```
|
|
232
334
|
|
|
233
|
-
|
|
234
|
-
</details>
|
|
235
|
-
|
|
236
|
-
### 6. Run the OAuth Flow
|
|
237
|
-
|
|
238
|
-
There are two ways to authorize the plugin with Linear.
|
|
239
|
-
|
|
240
|
-
#### Option A: CLI Flow (Recommended)
|
|
335
|
+
Verify it's working:
|
|
241
336
|
|
|
242
337
|
```bash
|
|
243
|
-
openclaw openclaw-linear
|
|
244
|
-
```
|
|
245
|
-
|
|
246
|
-
This launches the OAuth flow interactively:
|
|
247
|
-
|
|
248
|
-
1. The plugin constructs the authorization URL with the required scopes
|
|
249
|
-
2. Your browser opens to Linear's authorization page
|
|
250
|
-
3. You approve the permissions
|
|
251
|
-
4. Linear redirects to the callback URL with an authorization code
|
|
252
|
-
5. The plugin exchanges the code for access + refresh tokens
|
|
253
|
-
6. Tokens are stored in `~/.openclaw/auth-profiles.json`
|
|
254
|
-
|
|
255
|
-
#### Option B: Manual URL
|
|
256
|
-
|
|
257
|
-
If the CLI flow doesn't work (headless server, tunnel issues), construct the URL yourself:
|
|
258
|
-
|
|
259
|
-
```
|
|
260
|
-
https://linear.app/oauth/authorize
|
|
261
|
-
?client_id=YOUR_CLIENT_ID
|
|
262
|
-
&redirect_uri=https://your-domain.com/linear/oauth/callback
|
|
263
|
-
&response_type=code
|
|
264
|
-
&scope=read,write,app:assignable,app:mentionable
|
|
265
|
-
&state=random_string
|
|
266
|
-
&actor=app
|
|
267
|
-
```
|
|
268
|
-
|
|
269
|
-
Key parameters:
|
|
270
|
-
|
|
271
|
-
| Parameter | Value | Why |
|
|
272
|
-
|---|---|---|
|
|
273
|
-
| `scope` | `read,write,app:assignable,app:mentionable` | `app:assignable` lets the agent appear in assignment menus. `app:mentionable` lets users @mention the agent. |
|
|
274
|
-
| `actor` | `app` | Makes the token act as the **application identity**, not a personal user. Agent sessions require this. |
|
|
275
|
-
| `redirect_uri` | Your callback URL | Must match what you registered in the OAuth app settings. |
|
|
276
|
-
|
|
277
|
-
Click the URL, authorize in Linear, and the callback handler at `/linear/oauth/callback` will exchange the code and store the tokens automatically.
|
|
278
|
-
|
|
279
|
-
#### What Gets Stored
|
|
280
|
-
|
|
281
|
-
After a successful OAuth flow, `~/.openclaw/auth-profiles.json` will contain:
|
|
282
|
-
|
|
283
|
-
```json
|
|
284
|
-
{
|
|
285
|
-
"version": 1,
|
|
286
|
-
"profiles": {
|
|
287
|
-
"linear:default": {
|
|
288
|
-
"type": "oauth",
|
|
289
|
-
"provider": "linear",
|
|
290
|
-
"accessToken": "...",
|
|
291
|
-
"refreshToken": "...",
|
|
292
|
-
"expiresAt": 1708109280000,
|
|
293
|
-
"scope": "app:assignable app:mentionable read write"
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
}
|
|
338
|
+
openclaw openclaw-linear status
|
|
297
339
|
```
|
|
298
340
|
|
|
299
|
-
|
|
341
|
+
You should see `token: profile` in the gateway logs.
|
|
300
342
|
|
|
301
|
-
###
|
|
343
|
+
### 5. Configure Agents
|
|
302
344
|
|
|
303
|
-
Create `~/.openclaw/agent-profiles.json` to define
|
|
345
|
+
Create `~/.openclaw/agent-profiles.json` to define your agent team:
|
|
304
346
|
|
|
305
347
|
```json
|
|
306
348
|
{
|
|
307
349
|
"agents": {
|
|
308
350
|
"lead": {
|
|
309
351
|
"label": "Lead",
|
|
310
|
-
"mission": "Product owner. Sets direction,
|
|
352
|
+
"mission": "Product owner. Sets direction, prioritizes backlog.",
|
|
311
353
|
"isDefault": true,
|
|
312
|
-
"mentionAliases": ["lead"
|
|
313
|
-
"
|
|
314
|
-
"avatarUrl": "https://example.com/lead-avatar.png"
|
|
354
|
+
"mentionAliases": ["lead"],
|
|
355
|
+
"avatarUrl": "https://example.com/lead.png"
|
|
315
356
|
},
|
|
316
357
|
"qa": {
|
|
317
358
|
"label": "QA",
|
|
318
|
-
"mission": "Test engineer. Quality guardian, test strategy
|
|
359
|
+
"mission": "Test engineer. Quality guardian, test strategy.",
|
|
319
360
|
"mentionAliases": ["qa", "tester"]
|
|
320
361
|
},
|
|
321
362
|
"infra": {
|
|
322
363
|
"label": "Infra",
|
|
323
|
-
"mission": "Backend
|
|
364
|
+
"mission": "Backend engineer. Performance, reliability, observability.",
|
|
324
365
|
"mentionAliases": ["infra", "backend"]
|
|
325
|
-
},
|
|
326
|
-
"ux": {
|
|
327
|
-
"label": "UX",
|
|
328
|
-
"mission": "User experience advocate. Accessibility, user journeys, pain points.",
|
|
329
|
-
"mentionAliases": ["ux", "design"]
|
|
330
|
-
},
|
|
331
|
-
"docs": {
|
|
332
|
-
"label": "Docs",
|
|
333
|
-
"mission": "Technical writer. Setup guides, API references, release notes.",
|
|
334
|
-
"mentionAliases": ["docs", "writer"]
|
|
335
366
|
}
|
|
336
367
|
}
|
|
337
368
|
}
|
|
338
369
|
```
|
|
339
370
|
|
|
340
|
-
|
|
341
|
-
|---|---|---|
|
|
342
|
-
| `label` | Yes | Display name in Linear comments |
|
|
343
|
-
| `mission` | Yes | Agent's role description (injected as system context when the agent is dispatched) |
|
|
344
|
-
| `isDefault` | One agent | The default agent handles OAuth app events, agent sessions, and assignment triage |
|
|
345
|
-
| `mentionAliases` | Yes | @mention triggers in comments (e.g., `@qa` in a comment routes to the QA agent) |
|
|
346
|
-
| `appAliases` | No | Triggers via OAuth app webhook (default agent only, for app-level @mentions) |
|
|
347
|
-
| `avatarUrl` | No | Avatar displayed on branded comments. Falls back to `[Label]` prefix if not set. |
|
|
371
|
+
Each agent name must match an agent definition in your `~/.openclaw/openclaw.json`.
|
|
348
372
|
|
|
349
|
-
|
|
373
|
+
One agent must be marked `isDefault: true` — this is the agent that handles issue assignments and the pipeline.
|
|
350
374
|
|
|
351
|
-
|
|
375
|
+
### 6. Configure Coding Tools
|
|
352
376
|
|
|
353
|
-
|
|
377
|
+
Create `coding-tools.json` in the plugin root:
|
|
354
378
|
|
|
355
379
|
```json
|
|
356
380
|
{
|
|
357
|
-
"
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
}
|
|
363
|
-
"qa": {
|
|
364
|
-
"model": "claude-sonnet-4-5-20250929",
|
|
365
|
-
"systemPrompt": "You are a QA engineer agent...",
|
|
366
|
-
"tools": ["linear_list_issues", "linear_add_comment"]
|
|
367
|
-
}
|
|
381
|
+
"codingTool": "codex",
|
|
382
|
+
"agentCodingTools": {},
|
|
383
|
+
"backends": {
|
|
384
|
+
"claude": { "aliases": ["claude", "claude code", "anthropic"] },
|
|
385
|
+
"codex": { "aliases": ["codex", "openai"] },
|
|
386
|
+
"gemini": { "aliases": ["gemini", "google"] }
|
|
368
387
|
}
|
|
369
388
|
}
|
|
370
389
|
```
|
|
371
390
|
|
|
372
|
-
|
|
391
|
+
### 7. Install linearis
|
|
373
392
|
|
|
374
|
-
```
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
→ Dispatches: openclaw agent --agent qa --message "<issue context + comment>"
|
|
379
|
-
→ OpenClaw loads "qa" agent config from openclaw.json
|
|
380
|
-
→ Agent runs with the qa profile's mission as context
|
|
381
|
-
→ Response posted back to Linear as a branded comment with qa's label/avatar
|
|
393
|
+
```bash
|
|
394
|
+
npm install -g linearis
|
|
395
|
+
npx clawhub install linearis
|
|
396
|
+
echo "lin_api_YOUR_KEY" > ~/.linear_api_token
|
|
382
397
|
```
|
|
383
398
|
|
|
384
|
-
|
|
399
|
+
### 8. Verify
|
|
385
400
|
|
|
401
|
+
```bash
|
|
402
|
+
systemctl --user restart openclaw-gateway
|
|
386
403
|
```
|
|
387
|
-
Linear AgentSessionEvent.created
|
|
388
|
-
→ Plugin resolves the default agent (isDefault: true)
|
|
389
|
-
→ Runs the 3-stage pipeline (plan → implement → audit)
|
|
390
|
-
→ Each stage dispatches via the default agent's openclaw.json config
|
|
391
|
-
```
|
|
392
|
-
|
|
393
|
-
#### What happens if they don't match
|
|
394
|
-
|
|
395
|
-
- **Agent profile exists but no matching openclaw.json agent:** The dispatch fails and an error is logged. The webhook returns 200 (Linear requirement) but no comment is posted.
|
|
396
|
-
- **openclaw.json agent exists but no profile:** The agent works for direct CLI use but won't be reachable from Linear. No @mention alias maps to it.
|
|
397
|
-
- **No agent marked `isDefault`:** Agent sessions and assignment triage will fail with `"No defaultAgentId"` error.
|
|
398
404
|
|
|
399
|
-
|
|
405
|
+
Check the logs for a clean startup:
|
|
400
406
|
|
|
401
|
-
```
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
# Should show: "Linear agent extension registered (agent: default, token: profile)"
|
|
407
|
+
```
|
|
408
|
+
[plugins] Linear agent extension registered (agent: default, token: profile,
|
|
409
|
+
codex: codex-cli 0.101.0, claude: 2.1.45, gemini: 0.28.2, orchestration: enabled)
|
|
405
410
|
```
|
|
406
411
|
|
|
407
|
-
Test the webhook
|
|
412
|
+
Test the webhook:
|
|
413
|
+
|
|
408
414
|
```bash
|
|
409
415
|
curl -s -X POST https://your-domain.com/linear/webhook \
|
|
410
416
|
-H "Content-Type: application/json" \
|
|
@@ -412,163 +418,93 @@ curl -s -X POST https://your-domain.com/linear/webhook \
|
|
|
412
418
|
# Should return: "ok"
|
|
413
419
|
```
|
|
414
420
|
|
|
415
|
-
##
|
|
416
|
-
|
|
417
|
-
### Token Resolution
|
|
421
|
+
## Usage
|
|
418
422
|
|
|
419
|
-
|
|
423
|
+
Once set up, the plugin responds to Linear events automatically:
|
|
420
424
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
|
431
|
-
+-- AgentSessionEvent.created --> 3-stage pipeline (plan -> implement -> audit)
|
|
432
|
-
+-- AgentSessionEvent.prompted --> Resume pipeline (user approved plan)
|
|
433
|
-
+-- AppUserNotification --> Direct agent response to mention/assignment
|
|
434
|
-
+-- Comment.create --> Route @mention to role-based agent
|
|
435
|
-
+-- Issue.create --> Auto-triage new issues (estimate, labels, priority)
|
|
436
|
-
+-- Issue.update --> Triage if assigned/delegated to app user
|
|
437
|
-
```
|
|
438
|
-
|
|
439
|
-
All handlers respond `200 OK` within 5 seconds (Linear requirement), then process asynchronously.
|
|
425
|
+
| What you do in Linear | What happens |
|
|
426
|
+
|---|---|
|
|
427
|
+
| Create a new issue | Agent triages it (estimate, labels, priority) and posts an assessment |
|
|
428
|
+
| Assign an issue to the agent | Agent triages and posts assessment |
|
|
429
|
+
| Trigger an agent session | 3-stage pipeline: plan, implement, audit |
|
|
430
|
+
| Comment `@qa check the tests` | QA agent responds with its expertise |
|
|
431
|
+
| Comment `@infra why is this slow` | Infra agent investigates and replies |
|
|
432
|
+
| Ask "close this issue" | Agent runs `linearis issues update API-123 --status Done` |
|
|
433
|
+
| Ask "use gemini to review" | Agent calls `code_run` with `backend: "gemini"` |
|
|
440
434
|
|
|
441
|
-
|
|
435
|
+
## Configuration Reference
|
|
442
436
|
|
|
443
|
-
|
|
437
|
+
### Environment Variables
|
|
444
438
|
|
|
445
|
-
|
|
|
439
|
+
| Variable | Required | Description |
|
|
446
440
|
|---|---|---|
|
|
447
|
-
|
|
|
448
|
-
|
|
|
449
|
-
|
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
### Assignment Triage
|
|
454
|
-
|
|
455
|
-
When an issue is assigned or delegated to the app user:
|
|
441
|
+
| `LINEAR_CLIENT_ID` | Yes | OAuth app client ID |
|
|
442
|
+
| `LINEAR_CLIENT_SECRET` | Yes | OAuth app client secret |
|
|
443
|
+
| `LINEAR_API_KEY` | No | Personal API key (fallback if no OAuth) |
|
|
444
|
+
| `LINEAR_REDIRECT_URI` | No | Override the OAuth callback URL |
|
|
445
|
+
| `OPENCLAW_GATEWAY_PORT` | No | Gateway port (default: 18789) |
|
|
456
446
|
|
|
457
|
-
|
|
458
|
-
2. Dispatches the default agent with a triage prompt
|
|
459
|
-
3. Agent returns JSON with story point estimate and label IDs
|
|
460
|
-
4. Plugin applies the estimate and labels to the issue
|
|
461
|
-
5. Posts the assessment as a branded comment
|
|
447
|
+
### Plugin Config
|
|
462
448
|
|
|
463
|
-
|
|
449
|
+
Optional overrides in `openclaw.json` under the plugin entry:
|
|
464
450
|
|
|
465
|
-
|
|
451
|
+
| Key | Type | Default | Description |
|
|
452
|
+
|---|---|---|---|
|
|
453
|
+
| `defaultAgentId` | string | — | Override which agent handles pipeline/triage |
|
|
454
|
+
| `enableAudit` | boolean | `true` | Run the auditor stage after implementation |
|
|
455
|
+
| `enableOrchestration` | boolean | `true` | Allow agents to use `spawn_agent`/`ask_agent` |
|
|
456
|
+
| `codexBaseRepo` | string | `/home/claw/ai-workspace` | Git repo path for Codex worktrees |
|
|
457
|
+
| `codexModel` | string | — | Default Codex model |
|
|
458
|
+
| `codexTimeoutMs` | number | `600000` | Default timeout for coding CLIs |
|
|
466
459
|
|
|
467
|
-
|
|
468
|
-
2. Reacts with eyes emoji to acknowledge
|
|
469
|
-
3. Fetches full issue context (description, recent comments, labels, state)
|
|
470
|
-
4. Dispatches the matched agent with the comment context
|
|
471
|
-
5. Posts the agent's response as a branded comment on the issue
|
|
460
|
+
### Coding Tools Config (`coding-tools.json`)
|
|
472
461
|
|
|
473
|
-
|
|
462
|
+
| Key | Type | Default | Description |
|
|
463
|
+
|---|---|---|---|
|
|
464
|
+
| `codingTool` | string | `"claude"` | Default coding backend |
|
|
465
|
+
| `agentCodingTools` | object | `{}` | Per-agent backend overrides (`agentId → backendId`) |
|
|
466
|
+
| `backends` | object | `{}` | Per-backend config (aliases, etc.) |
|
|
467
|
+
| `backends.*.aliases` | string[] | `[backendId]` | Alias names that resolve to this backend |
|
|
474
468
|
|
|
475
|
-
###
|
|
469
|
+
### Agent Profile Fields
|
|
476
470
|
|
|
477
|
-
|
|
478
|
-
- Comment ID (for `Comment.create`)
|
|
479
|
-
- Session ID (for `AgentSessionEvent`)
|
|
480
|
-
- Assignment tuple (for `Issue.update`)
|
|
481
|
-
|
|
482
|
-
## Plugin Config Schema
|
|
483
|
-
|
|
484
|
-
Optional settings in `openclaw.json` under the plugin entry:
|
|
485
|
-
|
|
486
|
-
```json
|
|
487
|
-
{
|
|
488
|
-
"plugins": {
|
|
489
|
-
"entries": {
|
|
490
|
-
"openclaw-linear": {
|
|
491
|
-
"enabled": true,
|
|
492
|
-
"clientId": "...",
|
|
493
|
-
"clientSecret": "...",
|
|
494
|
-
"redirectUri": "...",
|
|
495
|
-
"accessToken": "...",
|
|
496
|
-
"defaultAgentId": "...",
|
|
497
|
-
"enableAudit": true
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
```
|
|
503
|
-
|
|
504
|
-
All fields are optional — environment variables and auth profiles are the preferred configuration method.
|
|
505
|
-
|
|
506
|
-
## HTTP Routes
|
|
507
|
-
|
|
508
|
-
| Route | Method | Purpose |
|
|
471
|
+
| Field | Required | Description |
|
|
509
472
|
|---|---|---|
|
|
510
|
-
|
|
|
511
|
-
|
|
|
512
|
-
|
|
|
473
|
+
| `label` | Yes | Display name shown on comments in Linear |
|
|
474
|
+
| `mission` | Yes | Role description (injected as context when the agent runs) |
|
|
475
|
+
| `isDefault` | One agent | Handles issue triage and the pipeline |
|
|
476
|
+
| `mentionAliases` | Yes | `@mention` triggers (e.g., `["qa", "tester"]`) |
|
|
477
|
+
| `avatarUrl` | No | Avatar for branded comments |
|
|
513
478
|
|
|
514
|
-
|
|
479
|
+
### CLI
|
|
515
480
|
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|---|---|
|
|
520
|
-
| `linear_list_issues` | List issues (with optional team filter) |
|
|
521
|
-
| `linear_create_issue` | Create a new issue |
|
|
522
|
-
| `linear_add_comment` | Add a comment to an issue |
|
|
523
|
-
|
|
524
|
-
## File Structure
|
|
525
|
-
|
|
526
|
-
```
|
|
527
|
-
linear/
|
|
528
|
-
├── index.ts # Entry point, registers routes and provider
|
|
529
|
-
├── openclaw.plugin.json # Plugin metadata and config schema
|
|
530
|
-
├── package.json # Package definition (zero runtime deps)
|
|
531
|
-
├── README.md
|
|
532
|
-
└── src/
|
|
533
|
-
├── agent.ts # Agent dispatch via openclaw CLI
|
|
534
|
-
├── auth.ts # OAuth provider registration and token refresh
|
|
535
|
-
├── client.ts # Basic GraphQL client (for agent tools)
|
|
536
|
-
├── linear-api.ts # Full GraphQL API wrapper (LinearAgentApi)
|
|
537
|
-
├── oauth-callback.ts # OAuth callback handler
|
|
538
|
-
├── pipeline.ts # 3-stage pipeline (plan -> implement -> audit)
|
|
539
|
-
├── tools.ts # Agent tools (list, create, comment)
|
|
540
|
-
├── webhook.ts # Webhook dispatcher (5 event handlers)
|
|
541
|
-
└── webhook.test.ts # Tests (vitest)
|
|
481
|
+
```bash
|
|
482
|
+
openclaw openclaw-linear auth # Run OAuth authorization
|
|
483
|
+
openclaw openclaw-linear status # Check connection and token status
|
|
542
484
|
```
|
|
543
485
|
|
|
544
486
|
## Troubleshooting
|
|
545
487
|
|
|
546
|
-
|
|
488
|
+
Quick checks:
|
|
489
|
+
|
|
547
490
|
```bash
|
|
548
|
-
openclaw
|
|
549
|
-
openclaw
|
|
491
|
+
systemctl --user status openclaw-gateway # Is the gateway running?
|
|
492
|
+
openclaw openclaw-linear status # Is the token valid?
|
|
493
|
+
journalctl --user -u openclaw-gateway -f # Watch live logs
|
|
494
|
+
linearis issues list -l 1 # Is linearis authenticated?
|
|
550
495
|
```
|
|
551
496
|
|
|
552
|
-
|
|
553
|
-
- Verify both webhooks (workspace + OAuth app) point to the same URL
|
|
554
|
-
- Check that your tunnel/proxy is forwarding to the gateway port
|
|
555
|
-
- Linear requires `200 OK` within 5 seconds — check for gateway latency
|
|
497
|
+
### Common Issues
|
|
556
498
|
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
-
|
|
499
|
+
| Problem | Cause | Fix |
|
|
500
|
+
|---|---|---|
|
|
501
|
+
| Agent says "closing" but doesn't | No issue management tool available | Install `linearis` skill: `npx clawhub install linearis` |
|
|
502
|
+
| `code_run` uses wrong backend | Default/per-agent config mismatch | Check `coding-tools.json` |
|
|
503
|
+
| Claude Code "nested session" error | `CLAUDECODE` env var set | Plugin handles this automatically (unsets the var) |
|
|
504
|
+
| Gateway rejects plugin config keys | Strict validator in `openclaw.json` | Custom config goes in `coding-tools.json`, not `openclaw.json` |
|
|
505
|
+
| Webhook events not arriving | Wrong webhook URL | Both workspace and OAuth app webhooks must point to `/linear/webhook` |
|
|
506
|
+
| OAuth token expired | Tokens expire ~24h | Auto-refreshes via refresh token; restart gateway if stuck |
|
|
565
507
|
|
|
566
|
-
|
|
567
|
-
- Ensure `LINEAR_CLIENT_ID` and `LINEAR_CLIENT_SECRET` are set
|
|
568
|
-
- Check that the refresh token in `auth-profiles.json` hasn't been revoked
|
|
569
|
-
- Re-run the OAuth flow to get new tokens
|
|
508
|
+
## License
|
|
570
509
|
|
|
571
|
-
|
|
572
|
-
- Verify the redirect URI in Linear's app settings matches your gateway URL
|
|
573
|
-
- If behind a reverse proxy, ensure `X-Forwarded-Proto` and `Host` headers are forwarded
|
|
574
|
-
- For local dev, the callback defaults to `http://localhost:<gateway-port>/linear/oauth/callback`
|
|
510
|
+
MIT
|