@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 CHANGED
@@ -1,157 +1,287 @@
1
- # Linear Agent Plugin for OpenClaw
1
+ # @calltelemetry/openclaw-linear
2
2
 
3
- Webhook-driven Linear integration with OAuth support, multi-agent routing, and a 3-stage AI pipeline for issue triage and implementation.
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
- ## What It Does
5
+ ## Features
6
6
 
7
- - **Auto-triage** — New issues are automatically estimated (story points), labeled, and prioritized with a posted assessment
8
- - **Issue triage** — When an issue is assigned/delegated to the app user, an agent estimates story points, applies labels, and posts an assessment
9
- - **Agent sessions** — Full plan-approve-implement-audit pipeline triggered from Linear's agent UI
10
- - **@mention routing** — Comment mentions like `@qa` or `@infra` route to specific role-based agents with different expertise
11
- - **App notifications** — Responds to Linear app mentions and assignments via branded comments
12
- - **Activity tracking** — Emits thought/action/response events visible in Linear's agent session UI
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
- ## Quick Install
15
+ ## Architecture
15
16
 
16
- ```bash
17
- openclaw plugins install @calltelemetry/openclaw-linear
18
- openclaw gateway restart
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
- That's it the plugin is installed and enabled. Continue with the [setup steps](#setup) below to configure Linear OAuth and webhooks.
49
+ ### Two Webhook Systems
50
+
51
+ Linear delivers events through two separate webhook paths:
22
52
 
23
- > To install from a local checkout instead: `openclaw plugins install --link /path/to/linear`
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
- ## First Run
56
+ Both must point to the same URL: `https://<your-domain>/linear/webhook`
26
57
 
27
- After installing, the plugin loads but **will not process any webhooks or agent tools until you authenticate**. You'll see this in the logs:
58
+ ### Source Layout
28
59
 
29
60
  ```
30
- Linear agent extension registered (agent: default, token: missing)
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
- This is normal — the plugin does not crash without auth. It registers all routes and CLI commands, but webhook handlers and tools will return errors until a token is available.
85
+ ## Coding Tool (`code_run`)
34
86
 
35
- To authenticate:
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
- ```bash
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
- # Run the OAuth flow
43
- openclaw openclaw-linear auth
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
- # Verify
46
- openclaw openclaw-linear status
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
- The auth flow stores tokens in `~/.openclaw/auth-profiles.json`. This file is created automatically you do not need to create it manually. After auth, restart the gateway:
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
- openclaw gateway restart
158
+ npx clawhub install linearis
159
+ npm install -g linearis
53
160
  ```
54
161
 
55
- You should now see `token: profile` in the logs:
162
+ ### Auth
56
163
 
164
+ ```bash
165
+ echo "lin_api_..." > ~/.linear_api_token
57
166
  ```
58
- Linear agent extension registered (agent: default, token: profile)
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 (systemd service)
64
- - A Linear workspace with API access
65
- - A Linear OAuth application (Settings > API > Applications)
66
- - A public URL for webhook delivery (e.g., Cloudflare tunnel)
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 Application
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
- 1. Go to **Linear Settings > API > Applications**
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
- ### 2. Create a Workspace Webhook
222
+ ```bash
223
+ export LINEAR_CLIENT_ID="your_client_id"
224
+ export LINEAR_CLIENT_SECRET="your_client_secret"
225
+ ```
82
226
 
83
- Separately from the OAuth app, create a workspace-level webhook:
227
+ For systemd:
84
228
 
85
- 1. Go to **Linear Settings > API > Webhooks**
86
- 2. Create a new webhook pointing to `https://<your-domain>/linear/webhook`
87
- 3. Enable these event types: **Comment**, **Issue**, **User**
229
+ ```ini
230
+ [Service]
231
+ Environment=LINEAR_CLIENT_ID=your_client_id
232
+ Environment=LINEAR_CLIENT_SECRET=your_client_secret
233
+ ```
88
234
 
89
- > **Why two webhooks?** The OAuth app webhook handles `AgentSessionEvent` and `AppUserNotification` events (agent-specific). The workspace webhook handles `Comment` and `Issue` events (workspace-wide). Both must point to the same URL — the plugin routes internally.
235
+ Then reload: `systemctl --user daemon-reload && systemctl --user restart openclaw-gateway`
90
236
 
91
- ### 3. Expose the Gateway via Cloudflare Tunnel
237
+ ### 3. Expose the Gateway
92
238
 
93
- The OpenClaw gateway listens on `localhost:<port>` (default `18789`). Linear must reach it over HTTPS to deliver webhooks. A [Cloudflare Tunnel](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/) is the recommended approach — no open inbound ports, no self-managed TLS.
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
- curl -fsSL https://pkg.cloudflare.com/cloudflare-main.gpg | sudo tee /usr/share/keyrings/cloudflare-main.gpg >/dev/null
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 Cloudflare's authorization page. You must:
260
+ This opens your browser. Log in, select the domain you want to use, and click **Authorize**.
117
261
 
118
- 1. Log in to your Cloudflare account
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
- This outputs a **Tunnel ID** (a UUID like `da1f21bf-856e-...`) and writes a credentials file to `~/.cloudflared/<TUNNEL_ID>.json`.
268
+ Note the **Tunnel ID** (a UUID) from the output.
131
269
 
132
- #### Create a DNS subdomain for the tunnel
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 **CNAME record** in your Cloudflare DNS:
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
- You can verify it in the Cloudflare dashboard under **DNS > Records** for your domain. The subdomain (`linear.yourdomain.com`) is what Linear will use for webhook delivery and OAuth callbacks.
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: /home/<user>/.cloudflared/<TUNNEL_ID>.json
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
- The `ingress` rule routes all traffic for your subdomain to the OpenClaw gateway on localhost. The catch-all `http_status:404` rejects requests for any other hostname.
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
- This installs a system-level service that starts on boot. To test without installing:
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 end-to-end
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
- > **Note:** The hostname you choose here (`linear.yourdomain.com`) is what you'll use for the OAuth redirect URI and both webhook URLs in Linear. Make sure they all match.
315
+ ### 4. Authorize with Linear
187
316
 
188
- ### 4. Set Environment Variables
189
-
190
- Required:
191
317
  ```bash
192
- export LINEAR_CLIENT_ID="your_client_id"
193
- export LINEAR_CLIENT_SECRET="your_client_secret"
318
+ openclaw openclaw-linear auth
194
319
  ```
195
320
 
196
- Optional:
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
- ### 5. Install the Plugin
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
- If you haven't already installed via [Quick Install](#quick-install):
329
+ After authorization, restart the gateway:
205
330
 
206
331
  ```bash
207
- openclaw plugins install @calltelemetry/openclaw-linear
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
- Then restart: `openclaw gateway restart`
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 auth
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
- This file should be `chmod 600` (owner-only). The plugin auto-refreshes tokens 60 seconds before expiry and persists the new tokens back to this file.
341
+ You should see `token: profile` in the gateway logs.
300
342
 
301
- ### 7. Configure Agent Profiles
343
+ ### 5. Configure Agents
302
344
 
303
- Create `~/.openclaw/agent-profiles.json` to define role-based agents:
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, makes scope decisions, prioritizes backlog.",
352
+ "mission": "Product owner. Sets direction, prioritizes backlog.",
311
353
  "isDefault": true,
312
- "mentionAliases": ["lead", "product"],
313
- "appAliases": ["myagent"],
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, release confidence.",
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 and infrastructure engineer. Performance, reliability, observability.",
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
- | Field | Required | Description |
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
- #### How agent-profiles.json connects to openclaw.json
373
+ One agent must be marked `isDefault: true` — this is the agent that handles issue assignments and the pipeline.
350
374
 
351
- Each key in `agent-profiles.json` (e.g., `"lead"`, `"qa"`) must have a matching agent definition in your OpenClaw config (`~/.openclaw/openclaw.json`). The Linear plugin dispatches work via `openclaw agent --agent <id>`, so the agent must actually exist.
375
+ ### 6. Configure Coding Tools
352
376
 
353
- Example — if `agent-profiles.json` defines `"lead"` and `"qa"`, your `openclaw.json` needs:
377
+ Create `coding-tools.json` in the plugin root:
354
378
 
355
379
  ```json
356
380
  {
357
- "agents": {
358
- "lead": {
359
- "model": "claude-sonnet-4-5-20250929",
360
- "systemPrompt": "You are a product lead agent...",
361
- "tools": ["linear_list_issues", "linear_create_issue", "linear_add_comment"]
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
- #### Routing flow
391
+ ### 7. Install linearis
373
392
 
374
- ```
375
- Linear comment "@qa review this test plan"
376
- Plugin matches "qa" in mentionAliases
377
- Looks up agent-profiles.json → finds "qa" profile
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
- For agent sessions (triggered by the Linear agent UI or app @mentions):
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
- ### 8. Verify
405
+ Check the logs for a clean startup:
400
406
 
401
- ```bash
402
- openclaw gateway restart
403
- openclaw logs | grep -i linear
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 is reachable:
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
- ## How It Works
416
-
417
- ### Token Resolution
421
+ ## Usage
418
422
 
419
- The plugin resolves an OAuth token from:
423
+ Once set up, the plugin responds to Linear events automatically:
420
424
 
421
- 1. Plugin config `accessToken` (static, for testing)
422
- 2. Auth profile store `linear:default` (from the OAuth flow — this is the normal path)
423
-
424
- OAuth is required. The plugin needs `app:assignable` and `app:mentionable` scopes to function agent sessions, branded comments, assignment triage, and @mention routing all depend on the application identity that only OAuth provides.
425
-
426
- ### Webhook Event Routing
427
-
428
- ```
429
- POST /linear/webhook
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
- ### Pipeline Stages
435
+ ## Configuration Reference
442
436
 
443
- Triggered by `AgentSessionEvent.created`:
437
+ ### Environment Variables
444
438
 
445
- | Stage | Timeout | What It Does |
439
+ | Variable | Required | Description |
446
440
  |---|---|---|
447
- | **Planner** | 5 min | Analyzes issue, generates implementation plan, posts as comment, waits for approval |
448
- | **Implementor** | 10 min | Follows the approved plan, makes changes, creates commits/PRs |
449
- | **Auditor** | 5 min | Reviews implementation against plan, posts audit report |
450
-
451
- The auditor stage can be disabled via plugin config: `"enableAudit": false`.
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
- 1. Fetches full issue details and available team labels
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
- ### @Mention Routing
449
+ Optional overrides in `openclaw.json` under the plugin entry:
464
450
 
465
- When a comment contains `@qa`, `@infra`, or any configured `mentionAliases`:
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
- 1. Plugin matches the alias to an agent profile
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
- The default agent's `mentionAliases` are excluded from comment routing — the default agent is reached via `appAliases` through the OAuth app webhook instead.
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
- ### Comment Deduplication
469
+ ### Agent Profile Fields
476
470
 
477
- Webhook events are deduplicated for 60 seconds using a key based on:
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
- | `/linear/webhook` | POST | Primary webhook endpoint |
511
- | `/hooks/linear` | POST | Backward-compatible webhook endpoint |
512
- | `/linear/oauth/callback` | GET | OAuth authorization callback |
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
- ## Agent Tools
479
+ ### CLI
515
480
 
516
- Agents have access to these Linear tools during execution:
517
-
518
- | Tool | Description |
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
- **Plugin not loading:**
488
+ Quick checks:
489
+
547
490
  ```bash
548
- openclaw doctor --fix
549
- openclaw logs | grep -i "linear\|plugin\|error"
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
- **Webhook not receiving events:**
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
- **Agent sessions not working:**
558
- - OAuth tokens require `app:assignable` and `app:mentionable` scopes
559
- - Personal API keys cannot create agent sessions use OAuth
560
- - Re-run `openclaw openclaw-linear auth` to get fresh tokens
561
-
562
- **"No defaultAgentId" error:**
563
- - Set `defaultAgentId` in plugin config, OR
564
- - Mark one agent as `"isDefault": true` in `agent-profiles.json`
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
- **Token refresh failures:**
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
- **OAuth callback not working:**
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