@calltelemetry/openclaw-linear 0.2.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 ADDED
@@ -0,0 +1,468 @@
1
+ # Linear Agent Plugin for OpenClaw
2
+
3
+ Webhook-driven Linear integration with OAuth support, multi-agent routing, and a 3-stage AI pipeline for issue triage and implementation.
4
+
5
+ ## What It Does
6
+
7
+ - **Issue triage** — When an issue is assigned/delegated to the app user, an agent estimates story points, applies labels, and posts an assessment
8
+ - **Agent sessions** — Full plan-approve-implement-audit pipeline triggered from Linear's agent UI
9
+ - **@mention routing** — Comment mentions like `@qa` or `@infra` route to specific role-based agents with different expertise
10
+ - **App notifications** — Responds to Linear app mentions and assignments via branded comments
11
+ - **Activity tracking** — Emits thought/action/response events visible in Linear's agent session UI
12
+
13
+ ## Prerequisites
14
+
15
+ - OpenClaw gateway running (systemd service)
16
+ - A Linear workspace with API access
17
+ - A Linear OAuth application (Settings > API > Applications)
18
+ - A public URL for webhook delivery (e.g., Cloudflare tunnel)
19
+
20
+ ## Setup
21
+
22
+ ### 1. Create a Linear OAuth Application
23
+
24
+ 1. Go to **Linear Settings > API > Applications**
25
+ 2. Click **Create new application**
26
+ 3. Fill in:
27
+ - **Application name:** your agent's name
28
+ - **Redirect URI:** `https://<your-domain>/linear/oauth/callback`
29
+ - **Webhook URL:** `https://<your-domain>/linear/webhook`
30
+ 4. Note the **Client ID** and **Client Secret**
31
+ 5. Enable the webhook events you need (Agent Sessions, Issues)
32
+
33
+ ### 2. Create a Workspace Webhook
34
+
35
+ Separately from the OAuth app, create a workspace-level webhook:
36
+
37
+ 1. Go to **Linear Settings > API > Webhooks**
38
+ 2. Create a new webhook pointing to `https://<your-domain>/linear/webhook`
39
+ 3. Enable these event types: **Comment**, **Issue**, **User**
40
+
41
+ > **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.
42
+
43
+ ### 3. Expose the Gateway via Cloudflare Tunnel
44
+
45
+ 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.
46
+
47
+ #### Install `cloudflared`
48
+
49
+ ```bash
50
+ # RHEL / Rocky / Alma
51
+ sudo dnf install -y cloudflared
52
+
53
+ # Debian / Ubuntu
54
+ curl -fsSL https://pkg.cloudflare.com/cloudflare-main.gpg | sudo tee /usr/share/keyrings/cloudflare-main.gpg >/dev/null
55
+ 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
56
+ sudo apt update && sudo apt install -y cloudflared
57
+
58
+ # macOS
59
+ brew install cloudflare/cloudflare/cloudflared
60
+ ```
61
+
62
+ #### Authenticate with Cloudflare
63
+
64
+ ```bash
65
+ cloudflared tunnel login
66
+ ```
67
+
68
+ This opens your browser to Cloudflare's authorization page. You must:
69
+
70
+ 1. Log in to your Cloudflare account
71
+ 2. **Select the domain** (zone) you want the tunnel to use (e.g., `yourdomain.com`)
72
+ 3. Click **Authorize**
73
+
74
+ 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.
75
+
76
+ #### Create a tunnel
77
+
78
+ ```bash
79
+ cloudflared tunnel create openclaw
80
+ ```
81
+
82
+ This outputs a **Tunnel ID** (a UUID like `da1f21bf-856e-...`) and writes a credentials file to `~/.cloudflared/<TUNNEL_ID>.json`.
83
+
84
+ #### Create a DNS subdomain for the tunnel
85
+
86
+ ```bash
87
+ cloudflared tunnel route dns openclaw linear.yourdomain.com
88
+ ```
89
+
90
+ This creates a **CNAME record** in your Cloudflare DNS:
91
+
92
+ ```
93
+ linear.yourdomain.com CNAME <TUNNEL_ID>.cfargotunnel.com
94
+ ```
95
+
96
+ 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.
97
+
98
+ > **Important:** Your domain must already be on Cloudflare (nameservers pointed to Cloudflare). If it's not, add it in the Cloudflare dashboard first.
99
+
100
+ #### Configure the tunnel
101
+
102
+ Create `~/.cloudflared/config.yml`:
103
+
104
+ ```yaml
105
+ tunnel: <TUNNEL_ID>
106
+ credentials-file: /home/<user>/.cloudflared/<TUNNEL_ID>.json
107
+
108
+ ingress:
109
+ - hostname: linear.yourdomain.com
110
+ service: http://localhost:18789
111
+ - service: http_status:404
112
+ ```
113
+
114
+ 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.
115
+
116
+ #### Run as a systemd service
117
+
118
+ ```bash
119
+ sudo cloudflared service install
120
+ sudo systemctl enable --now cloudflared
121
+ ```
122
+
123
+ This installs a system-level service that starts on boot. To test without installing:
124
+
125
+ ```bash
126
+ cloudflared tunnel run openclaw
127
+ ```
128
+
129
+ #### Verify end-to-end
130
+
131
+ ```bash
132
+ curl -s https://linear.yourdomain.com/linear/webhook \
133
+ -X POST -H "Content-Type: application/json" \
134
+ -d '{"type":"test","action":"ping"}'
135
+ # Should return: "ok"
136
+ ```
137
+
138
+ > **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.
139
+
140
+ ### 4. Set Environment Variables
141
+
142
+ Required:
143
+ ```bash
144
+ export LINEAR_CLIENT_ID="your_client_id"
145
+ export LINEAR_CLIENT_SECRET="your_client_secret"
146
+ ```
147
+
148
+ Optional:
149
+ ```bash
150
+ export LINEAR_REDIRECT_URI="https://your-domain.com/linear/oauth/callback"
151
+ export OPENCLAW_GATEWAY_PORT="18789" # if non-default
152
+ ```
153
+
154
+ ### 5. Install the Plugin
155
+
156
+ Add the plugin path to your OpenClaw config (`~/.openclaw/openclaw.json`):
157
+
158
+ ```json
159
+ {
160
+ "plugins": {
161
+ "load": {
162
+ "paths": ["/path/to/claw-extensions/linear"]
163
+ },
164
+ "entries": {
165
+ "linear": {
166
+ "enabled": true
167
+ }
168
+ }
169
+ }
170
+ }
171
+ ```
172
+
173
+ Restart the gateway to load the plugin:
174
+
175
+ ```bash
176
+ openclaw gateway restart
177
+ ```
178
+
179
+ ### 6. Run the OAuth Flow
180
+
181
+ There are two ways to authorize the plugin with Linear.
182
+
183
+ #### Option A: CLI Flow (Recommended)
184
+
185
+ ```bash
186
+ openclaw auth linear oauth
187
+ ```
188
+
189
+ This launches the OAuth flow interactively:
190
+
191
+ 1. The plugin constructs the authorization URL with the required scopes
192
+ 2. Your browser opens to Linear's authorization page
193
+ 3. You approve the permissions
194
+ 4. Linear redirects to the callback URL with an authorization code
195
+ 5. The plugin exchanges the code for access + refresh tokens
196
+ 6. Tokens are stored in `~/.openclaw/auth-profiles.json`
197
+
198
+ #### Option B: Manual URL
199
+
200
+ If the CLI flow doesn't work (headless server, tunnel issues), construct the URL yourself:
201
+
202
+ ```
203
+ https://linear.app/oauth/authorize
204
+ ?client_id=YOUR_CLIENT_ID
205
+ &redirect_uri=https://your-domain.com/linear/oauth/callback
206
+ &response_type=code
207
+ &scope=read,write,app:assignable,app:mentionable
208
+ &state=random_string
209
+ &actor=app
210
+ ```
211
+
212
+ Key parameters:
213
+
214
+ | Parameter | Value | Why |
215
+ |---|---|---|
216
+ | `scope` | `read,write,app:assignable,app:mentionable` | `app:assignable` lets the agent appear in assignment menus. `app:mentionable` lets users @mention the agent. |
217
+ | `actor` | `app` | Makes the token act as the **application identity**, not a personal user. Agent sessions require this. |
218
+ | `redirect_uri` | Your callback URL | Must match what you registered in the OAuth app settings. |
219
+
220
+ Click the URL, authorize in Linear, and the callback handler at `/linear/oauth/callback` will exchange the code and store the tokens automatically.
221
+
222
+ #### What Gets Stored
223
+
224
+ After a successful OAuth flow, `~/.openclaw/auth-profiles.json` will contain:
225
+
226
+ ```json
227
+ {
228
+ "version": 1,
229
+ "profiles": {
230
+ "linear:default": {
231
+ "type": "oauth",
232
+ "provider": "linear",
233
+ "accessToken": "...",
234
+ "refreshToken": "...",
235
+ "expiresAt": 1708109280000,
236
+ "scope": "app:assignable app:mentionable read write"
237
+ }
238
+ }
239
+ }
240
+ ```
241
+
242
+ 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.
243
+
244
+ ### 7. Configure Agent Profiles
245
+
246
+ Create `~/.openclaw/agent-profiles.json` to define role-based agents:
247
+
248
+ ```json
249
+ {
250
+ "agents": {
251
+ "lead": {
252
+ "label": "Lead",
253
+ "mission": "Product owner. Sets direction, makes scope decisions, prioritizes backlog.",
254
+ "isDefault": true,
255
+ "mentionAliases": ["lead", "product"],
256
+ "appAliases": ["myagent"],
257
+ "avatarUrl": "https://example.com/lead-avatar.png"
258
+ },
259
+ "qa": {
260
+ "label": "QA",
261
+ "mission": "Test engineer. Quality guardian, test strategy, release confidence.",
262
+ "mentionAliases": ["qa", "tester"]
263
+ },
264
+ "infra": {
265
+ "label": "Infra",
266
+ "mission": "Backend and infrastructure engineer. Performance, reliability, observability.",
267
+ "mentionAliases": ["infra", "backend"]
268
+ },
269
+ "ux": {
270
+ "label": "UX",
271
+ "mission": "User experience advocate. Accessibility, user journeys, pain points.",
272
+ "mentionAliases": ["ux", "design"]
273
+ },
274
+ "docs": {
275
+ "label": "Docs",
276
+ "mission": "Technical writer. Setup guides, API references, release notes.",
277
+ "mentionAliases": ["docs", "writer"]
278
+ }
279
+ }
280
+ }
281
+ ```
282
+
283
+ | Field | Required | Description |
284
+ |---|---|---|
285
+ | `label` | Yes | Display name in Linear comments |
286
+ | `mission` | Yes | Agent's role description (provided as context when dispatched) |
287
+ | `isDefault` | One agent | The default agent handles OAuth app events and assignment triage |
288
+ | `mentionAliases` | Yes | @mention triggers in comments (e.g., `@qa` in a comment routes to the QA agent) |
289
+ | `appAliases` | No | Triggers via OAuth app webhook (default agent only, for app-level @mentions) |
290
+ | `avatarUrl` | No | Avatar displayed on branded comments. Falls back to `[Label]` prefix if not set. |
291
+
292
+ Each agent ID (the JSON key) must match a configured OpenClaw agent in `openclaw.json`. The plugin dispatches to agents via `openclaw agent --agent <id>`.
293
+
294
+ ### 8. Verify
295
+
296
+ ```bash
297
+ openclaw gateway restart
298
+ openclaw logs | grep -i linear
299
+ # Should show: "Linear agent extension registered (agent: default, token: profile)"
300
+ ```
301
+
302
+ Test the webhook is reachable:
303
+ ```bash
304
+ curl -s -X POST https://your-domain.com/linear/webhook \
305
+ -H "Content-Type: application/json" \
306
+ -d '{"type":"test","action":"ping"}'
307
+ # Should return: "ok"
308
+ ```
309
+
310
+ ## How It Works
311
+
312
+ ### Token Resolution
313
+
314
+ The plugin resolves an OAuth token from:
315
+
316
+ 1. Plugin config `accessToken` (static, for testing)
317
+ 2. Auth profile store `linear:default` (from the OAuth flow — this is the normal path)
318
+
319
+ 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.
320
+
321
+ ### Webhook Event Routing
322
+
323
+ ```
324
+ POST /linear/webhook
325
+ |
326
+ +-- AgentSessionEvent.created --> 3-stage pipeline (plan -> implement -> audit)
327
+ +-- AgentSessionEvent.prompted --> Resume pipeline (user approved plan)
328
+ +-- AppUserNotification --> Direct agent response to mention/assignment
329
+ +-- Comment.create --> Route @mention to role-based agent
330
+ +-- Issue.update --> Triage if assigned/delegated to app user
331
+ ```
332
+
333
+ All handlers respond `200 OK` within 5 seconds (Linear requirement), then process asynchronously.
334
+
335
+ ### Pipeline Stages
336
+
337
+ Triggered by `AgentSessionEvent.created`:
338
+
339
+ | Stage | Timeout | What It Does |
340
+ |---|---|---|
341
+ | **Planner** | 5 min | Analyzes issue, generates implementation plan, posts as comment, waits for approval |
342
+ | **Implementor** | 10 min | Follows the approved plan, makes changes, creates commits/PRs |
343
+ | **Auditor** | 5 min | Reviews implementation against plan, posts audit report |
344
+
345
+ The auditor stage can be disabled via plugin config: `"enableAudit": false`.
346
+
347
+ ### Assignment Triage
348
+
349
+ When an issue is assigned or delegated to the app user:
350
+
351
+ 1. Fetches full issue details and available team labels
352
+ 2. Dispatches the default agent with a triage prompt
353
+ 3. Agent returns JSON with story point estimate and label IDs
354
+ 4. Plugin applies the estimate and labels to the issue
355
+ 5. Posts the assessment as a branded comment
356
+
357
+ ### @Mention Routing
358
+
359
+ When a comment contains `@qa`, `@infra`, or any configured `mentionAliases`:
360
+
361
+ 1. Plugin matches the alias to an agent profile
362
+ 2. Reacts with eyes emoji to acknowledge
363
+ 3. Fetches full issue context (description, recent comments, labels, state)
364
+ 4. Dispatches the matched agent with the comment context
365
+ 5. Posts the agent's response as a branded comment on the issue
366
+
367
+ The default agent's `mentionAliases` are excluded from comment routing — the default agent is reached via `appAliases` through the OAuth app webhook instead.
368
+
369
+ ### Comment Deduplication
370
+
371
+ Webhook events are deduplicated for 60 seconds using a key based on:
372
+ - Comment ID (for `Comment.create`)
373
+ - Session ID (for `AgentSessionEvent`)
374
+ - Assignment tuple (for `Issue.update`)
375
+
376
+ ## Plugin Config Schema
377
+
378
+ Optional settings in `openclaw.json` under the plugin entry:
379
+
380
+ ```json
381
+ {
382
+ "plugins": {
383
+ "entries": {
384
+ "linear": {
385
+ "enabled": true,
386
+ "clientId": "...",
387
+ "clientSecret": "...",
388
+ "redirectUri": "...",
389
+ "accessToken": "...",
390
+ "defaultAgentId": "...",
391
+ "enableAudit": true
392
+ }
393
+ }
394
+ }
395
+ }
396
+ ```
397
+
398
+ All fields are optional — environment variables and auth profiles are the preferred configuration method.
399
+
400
+ ## HTTP Routes
401
+
402
+ | Route | Method | Purpose |
403
+ |---|---|---|
404
+ | `/linear/webhook` | POST | Primary webhook endpoint |
405
+ | `/hooks/linear` | POST | Backward-compatible webhook endpoint |
406
+ | `/linear/oauth/callback` | GET | OAuth authorization callback |
407
+
408
+ ## Agent Tools
409
+
410
+ Agents have access to these Linear tools during execution:
411
+
412
+ | Tool | Description |
413
+ |---|---|
414
+ | `linear_list_issues` | List issues (with optional team filter) |
415
+ | `linear_create_issue` | Create a new issue |
416
+ | `linear_add_comment` | Add a comment to an issue |
417
+
418
+ ## File Structure
419
+
420
+ ```
421
+ linear/
422
+ ├── index.ts # Entry point, registers routes and provider
423
+ ├── openclaw.plugin.json # Plugin metadata and config schema
424
+ ├── package.json # Package definition (zero runtime deps)
425
+ ├── README.md
426
+ └── src/
427
+ ├── agent.ts # Agent dispatch via openclaw CLI
428
+ ├── auth.ts # OAuth provider registration and token refresh
429
+ ├── client.ts # Basic GraphQL client (for agent tools)
430
+ ├── linear-api.ts # Full GraphQL API wrapper (LinearAgentApi)
431
+ ├── oauth-callback.ts # OAuth callback handler
432
+ ├── pipeline.ts # 3-stage pipeline (plan -> implement -> audit)
433
+ ├── tools.ts # Agent tools (list, create, comment)
434
+ ├── webhook.ts # Webhook dispatcher (5 event handlers)
435
+ └── webhook.test.ts # Tests (vitest)
436
+ ```
437
+
438
+ ## Troubleshooting
439
+
440
+ **Plugin not loading:**
441
+ ```bash
442
+ openclaw doctor --fix
443
+ openclaw logs | grep -i "linear\|plugin\|error"
444
+ ```
445
+
446
+ **Webhook not receiving events:**
447
+ - Verify both webhooks (workspace + OAuth app) point to the same URL
448
+ - Check that your tunnel/proxy is forwarding to the gateway port
449
+ - Linear requires `200 OK` within 5 seconds — check for gateway latency
450
+
451
+ **Agent sessions not working:**
452
+ - OAuth tokens require `app:assignable` and `app:mentionable` scopes
453
+ - Personal API keys cannot create agent sessions — use OAuth
454
+ - Re-run `openclaw auth linear oauth` to get fresh tokens
455
+
456
+ **"No defaultAgentId" error:**
457
+ - Set `defaultAgentId` in plugin config, OR
458
+ - Mark one agent as `"isDefault": true` in `agent-profiles.json`
459
+
460
+ **Token refresh failures:**
461
+ - Ensure `LINEAR_CLIENT_ID` and `LINEAR_CLIENT_SECRET` are set
462
+ - Check that the refresh token in `auth-profiles.json` hasn't been revoked
463
+ - Re-run the OAuth flow to get new tokens
464
+
465
+ **OAuth callback not working:**
466
+ - Verify the redirect URI in Linear's app settings matches your gateway URL
467
+ - If behind a reverse proxy, ensure `X-Forwarded-Proto` and `Host` headers are forwarded
468
+ - For local dev, the callback defaults to `http://localhost:<gateway-port>/linear/oauth/callback`
package/index.ts ADDED
@@ -0,0 +1,56 @@
1
+ import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
2
+ import { registerLinearProvider } from "./src/auth.js";
3
+ import { createLinearTools } from "./src/tools.js";
4
+ import { handleLinearWebhook } from "./src/webhook.js";
5
+ import { handleOAuthCallback } from "./src/oauth-callback.js";
6
+ import { resolveLinearToken } from "./src/linear-api.js";
7
+
8
+ export default function register(api: OpenClawPluginApi) {
9
+ const pluginConfig = (api as any).pluginConfig as Record<string, unknown> | undefined;
10
+
11
+ // Check token availability (config → env → auth profile store)
12
+ const tokenInfo = resolveLinearToken(pluginConfig);
13
+ if (!tokenInfo.accessToken) {
14
+ api.logger.warn(
15
+ "Linear: no access token found. Options: (1) run OAuth flow, (2) set LINEAR_ACCESS_TOKEN env var, " +
16
+ "(3) add accessToken to plugin config. Agent pipeline will not function without it.",
17
+ );
18
+ }
19
+
20
+ // Register Linear as an auth provider (OAuth flow with agent scopes)
21
+ registerLinearProvider(api);
22
+
23
+ // Register Linear tools for the agent
24
+ api.registerTool((ctx) => {
25
+ return createLinearTools(api, ctx);
26
+ });
27
+
28
+ // Register Linear webhook handler on a dedicated route
29
+ api.registerHttpRoute({
30
+ path: "/linear/webhook",
31
+ handler: async (req, res) => {
32
+ await handleLinearWebhook(api, req, res);
33
+ },
34
+ });
35
+
36
+ // Back-compat route so existing production webhook URLs keep working.
37
+ api.registerHttpRoute({
38
+ path: "/hooks/linear",
39
+ handler: async (req, res) => {
40
+ await handleLinearWebhook(api, req, res);
41
+ },
42
+ });
43
+
44
+ // Register OAuth callback route
45
+ api.registerHttpRoute({
46
+ path: "/linear/oauth/callback",
47
+ handler: async (req, res) => {
48
+ await handleOAuthCallback(api, req, res);
49
+ },
50
+ });
51
+
52
+ const agentId = (pluginConfig?.defaultAgentId as string) ?? "default";
53
+ api.logger.info(
54
+ `Linear agent extension registered (agent: ${agentId}, token: ${tokenInfo.source !== "none" ? `${tokenInfo.source}` : "missing"})`,
55
+ );
56
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "id": "linear",
3
+ "name": "Linear Agent",
4
+ "description": "Linear integration with OAuth support, agent pipeline, and webhook-driven AI agent lifecycle",
5
+ "version": "0.2.0",
6
+ "configSchema": {
7
+ "type": "object",
8
+ "properties": {
9
+ "clientId": { "type": "string", "description": "Linear OAuth Client ID" },
10
+ "clientSecret": { "type": "string", "description": "Linear OAuth Client Secret", "sensitive": true },
11
+ "redirectUri": { "type": "string", "description": "Linear OAuth Redirect URI (optional, defaults to gateway URL)" },
12
+ "accessToken": { "type": "string", "description": "Linear API access token for agent activities", "sensitive": true },
13
+ "defaultAgentId": { "type": "string", "description": "OpenClaw agent ID to use for pipeline stages" },
14
+ "enableAudit": { "type": "boolean", "description": "Run auditor stage after implementation", "default": true }
15
+ }
16
+ }
17
+ }
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@calltelemetry/openclaw-linear",
3
+ "version": "0.2.0",
4
+ "description": "Linear Agent plugin for OpenClaw — webhook-driven AI pipeline with OAuth, multi-agent routing, and issue triage",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/calltelemetry/openclaw-linear-plugin.git"
10
+ },
11
+ "homepage": "https://github.com/calltelemetry/openclaw-linear-plugin#readme",
12
+ "keywords": [
13
+ "openclaw",
14
+ "linear",
15
+ "agent",
16
+ "webhook",
17
+ "oauth",
18
+ "ai-pipeline",
19
+ "issue-triage"
20
+ ],
21
+ "files": [
22
+ "index.ts",
23
+ "src/",
24
+ "openclaw.plugin.json",
25
+ "README.md"
26
+ ],
27
+ "publishConfig": {
28
+ "access": "public"
29
+ },
30
+ "devDependencies": {
31
+ "openclaw": "^2026.2.13"
32
+ },
33
+ "openclaw": {
34
+ "extensions": [
35
+ "./index.ts"
36
+ ]
37
+ }
38
+ }
package/src/agent.ts ADDED
@@ -0,0 +1,57 @@
1
+ import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
2
+
3
+ export interface AgentRunResult {
4
+ success: boolean;
5
+ output: string;
6
+ }
7
+
8
+ export async function runAgent(params: {
9
+ api: OpenClawPluginApi;
10
+ agentId: string;
11
+ sessionId: string;
12
+ message: string;
13
+ timeoutMs?: number;
14
+ }): Promise<AgentRunResult> {
15
+ const { api, agentId, sessionId, message, timeoutMs = 5 * 60_000 } = params;
16
+
17
+ api.logger.info(`Dispatching agent ${agentId} for session ${sessionId}`);
18
+
19
+ const command = [
20
+ "openclaw",
21
+ "agent",
22
+ "--agent",
23
+ agentId,
24
+ "--session-id",
25
+ sessionId,
26
+ "--message",
27
+ message,
28
+ "--timeout",
29
+ String(Math.floor(timeoutMs / 1000)),
30
+ "--json",
31
+ ];
32
+
33
+ const result = await api.runtime.system.runCommandWithTimeout(command, { timeoutMs });
34
+
35
+ if (result.code !== 0) {
36
+ const error = result.stderr || result.stdout || "no output";
37
+ api.logger.error(`Agent ${agentId} failed (${result.code}): ${error}`);
38
+ return { success: false, output: error };
39
+ }
40
+
41
+ const raw = result.stdout || "";
42
+ api.logger.info(`Agent ${agentId} completed for session ${sessionId}`);
43
+
44
+ // Extract clean text from --json output
45
+ try {
46
+ const parsed = JSON.parse(raw);
47
+ const payloads = parsed?.result?.payloads;
48
+ if (Array.isArray(payloads) && payloads.length > 0) {
49
+ const text = payloads.map((p: any) => p.text).filter(Boolean).join("\n\n");
50
+ if (text) return { success: true, output: text };
51
+ }
52
+ } catch {
53
+ // Not JSON — use raw output as-is
54
+ }
55
+
56
+ return { success: true, output: raw };
57
+ }