openclaw-linear 0.1.0 → 0.3.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
@@ -6,7 +6,9 @@ Linear webhook integration for OpenClaw. Receives Linear events, filters and rou
6
6
 
7
7
  - **Webhook handler** — receives Linear webhook events with HMAC signature verification (timing-safe), duplicate delivery detection, and body size limits
8
8
  - **Event router** — filters by team and event type, routes issue assignments and comment mentions to the configured agent
9
- - **Debounced dispatch** — batches events within a configurable window into a single consolidated message so the agent can triage before acting
9
+ - **Debounced dispatch** — batches events within a configurable window before dispatching
10
+ - **Work queue** — deterministic queue intake writes structured items to `queue/work-queue.json` with priority sorting, deduplication, and 24h auto-cleanup of completed items — no LLM tokens spent on triage
11
+ - **Crash recovery** — resets stale `in_progress` queue items to `pending` on gateway startup
10
12
 
11
13
  ## Install
12
14
 
@@ -75,22 +77,50 @@ Linear webhook POST
75
77
  → Duplicate delivery check (10-min TTL, 10k cap)
76
78
  → Event router filters by team/type, matches user via agentMapping
77
79
  → wake actions enqueued into debouncer (keyed by agent ID)
78
- → After debounce window expires, consolidated message dispatched to agent
80
+ → After debounce window expires:
81
+ → Notifications parsed and written to queue/work-queue.json (deterministic, no LLM)
82
+ → Deduped against existing non-done items — skips agent dispatch if nothing new
83
+ → Agent receives minimal message: "3 new Linear notification(s) queued."
79
84
  ```
80
85
 
81
- When multiple events arrive within the debounce window, the agent receives a single numbered message:
82
-
86
+ ## Work Queue
87
+
88
+ The plugin writes structured items to `queue/work-queue.json`:
89
+
90
+ ```json
91
+ {
92
+ "items": [
93
+ {
94
+ "id": "ENG-42",
95
+ "issueId": "ENG-42",
96
+ "event": "issue.assigned",
97
+ "summary": "Fix login bug",
98
+ "status": "pending",
99
+ "priority": 1,
100
+ "addedAt": "2026-02-14T15:45:00.000Z",
101
+ "startedAt": null,
102
+ "completedAt": null
103
+ }
104
+ ]
105
+ }
83
106
  ```
84
- You have 3 new Linear notifications:
85
107
 
86
- 1. [Assigned] ENG-42: Fix login bug
87
- 2. [Assigned] ENG-43: Update API docs
88
- 3. [Mentioned] ENG-40: Auth flow: "Can you review this?"
108
+ ### Priority
89
109
 
90
- Review and prioritize before starting work.
91
- ```
110
+ | Priority | Event |
111
+ |----------|-------|
112
+ | 1 | `issue.assigned` |
113
+ | 2 | `issue.reassigned` |
114
+ | 3 | `comment.mention` |
115
+ | 4 | `issue.unassigned` |
116
+
117
+ ### Lifecycle
118
+
119
+ Items progress through statuses: `pending` → `in_progress` → `done`.
92
120
 
93
- Single events are passed through as-is (no numbered wrapper).
121
+ - **Deduplication** keyed by `issueId + event`, only checked against non-done items. Completed items can be re-queued (e.g. re-assignment after completion).
122
+ - **Cleanup** — done items older than 24 hours are purged automatically during intake.
123
+ - **Recovery** — on gateway startup, any `in_progress` items are reset to `pending` to recover from crashes.
94
124
 
95
125
  ## Development
96
126
 
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAE7D,OAAO,EAAqB,KAAK,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAYzE,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,YAAY,EAAE,GAAG,MAAM,CAYzE;AA0ED,wBAAgB,QAAQ,CAAC,GAAG,EAAE,iBAAiB,GAAG,IAAI,CAyErD;AAED,wBAAsB,UAAU,CAAC,GAAG,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAStE;AAED,QAAA,MAAM,MAAM;;;;;;CAYX,CAAC;AAEF,eAAe,MAAM,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAE7D,OAAO,EAAqB,KAAK,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAczE,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,YAAY,EAAE,GAAG,MAAM,CAYzE;AAqFD,wBAAgB,QAAQ,CAAC,GAAG,EAAE,iBAAiB,GAAG,IAAI,CA8ErD;AAED,wBAAsB,UAAU,CAAC,GAAG,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAStE;AAED,QAAA,MAAM,MAAM;;;;;;CAYX,CAAC;AAEF,eAAe,MAAM,CAAC"}
package/dist/index.js CHANGED
@@ -1,5 +1,7 @@
1
1
  import { createWebhookHandler } from "./webhook-handler.js";
2
2
  import { createEventRouter } from "./event-router.js";
3
+ import { InboxQueue } from "./work-queue.js";
4
+ import { createQueueTool } from "./queue-tool.js";
3
5
  const CHANNEL_ID = "linear";
4
6
  const DEFAULT_DEBOUNCE_MS = 30_000;
5
7
  const EVENT_LABELS = {
@@ -29,7 +31,7 @@ function formatActionSummary(action) {
29
31
  }
30
32
  return action.issueLabel || action.detail;
31
33
  }
32
- async function dispatchConsolidatedActions(actions, api) {
34
+ async function dispatchConsolidatedActions(actions, api, queue) {
33
35
  if (actions.length === 0)
34
36
  return;
35
37
  const core = api.runtime;
@@ -44,7 +46,15 @@ async function dispatchConsolidatedActions(actions, api) {
44
46
  id: first.linearUserId,
45
47
  },
46
48
  });
47
- const body = formatConsolidatedMessage(actions);
49
+ // Write to queue deterministically — no LLM involved
50
+ const rawBody = formatConsolidatedMessage(actions);
51
+ const added = await queue.enqueue(rawBody);
52
+ if (added === 0) {
53
+ api.logger.info("[linear] All notifications deduped — skipping agent dispatch");
54
+ return;
55
+ }
56
+ // Agent gets a minimal notification pointing to the linear_queue tool
57
+ const body = `${added} new Linear notification(s) queued. Use the linear_queue tool to process them.`;
48
58
  const ctx = core.channel.reply.finalizeInboundContext({
49
59
  Body: body,
50
60
  BodyForAgent: body,
@@ -94,6 +104,9 @@ export function activate(api) {
94
104
  const debounceMs = (typeof rawDebounceMs === "number" && rawDebounceMs > 0)
95
105
  ? rawDebounceMs
96
106
  : DEFAULT_DEBOUNCE_MS;
107
+ const queuePath = api.resolvePath("queue/inbox.jsonl");
108
+ const queue = new InboxQueue(queuePath);
109
+ api.registerTool(createQueueTool(queue));
97
110
  const route = createEventRouter({
98
111
  agentMapping,
99
112
  logger: api.logger,
@@ -105,7 +118,7 @@ export function activate(api) {
105
118
  buildKey: (action) => action.agentId,
106
119
  shouldDebounce: () => true,
107
120
  onFlush: async (actions) => {
108
- await dispatchConsolidatedActions(actions, api);
121
+ await dispatchConsolidatedActions(actions, api, queue);
109
122
  },
110
123
  onError: (err) => {
111
124
  api.logger.error(`[linear] Debounce flush failed: ${err instanceof Error ? err.message : String(err)}`);
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import type { AnyAgentTool } from "openclaw/plugin-sdk";
2
+ import type { InboxQueue } from "./work-queue.js";
3
+ export declare function createQueueTool(queue: InboxQueue): AnyAgentTool;
4
+ //# sourceMappingURL=queue-tool.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queue-tool.d.ts","sourceRoot":"","sources":["../src/queue-tool.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAExD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAiBlD,wBAAgB,eAAe,CAAC,KAAK,EAAE,UAAU,GAAG,YAAY,CA6B/D"}
@@ -0,0 +1,42 @@
1
+ import { Type } from "@sinclair/typebox";
2
+ import { jsonResult } from "openclaw/plugin-sdk";
3
+ const QueueAction = Type.Unsafe({
4
+ type: "string",
5
+ enum: ["peek", "pop", "drain"],
6
+ description: "peek: view all pending items without removing them. " +
7
+ "pop: remove and return the highest-priority item. " +
8
+ "drain: remove and return all items.",
9
+ });
10
+ const QueueToolParams = Type.Object({
11
+ action: QueueAction,
12
+ });
13
+ export function createQueueTool(queue) {
14
+ return {
15
+ name: "linear_queue",
16
+ label: "Linear Queue",
17
+ description: "Manage the Linear notification inbox queue. " +
18
+ "Use 'peek' to see pending items, 'pop' to take the next item, or 'drain' to take all items.",
19
+ parameters: QueueToolParams,
20
+ async execute(_toolCallId, params) {
21
+ switch (params.action) {
22
+ case "peek": {
23
+ const items = await queue.peek();
24
+ return jsonResult({ count: items.length, items });
25
+ }
26
+ case "pop": {
27
+ const item = await queue.pop();
28
+ return jsonResult(item ? { item } : { item: null, message: "Queue is empty" });
29
+ }
30
+ case "drain": {
31
+ const items = await queue.drain();
32
+ return jsonResult({ count: items.length, items });
33
+ }
34
+ default:
35
+ return jsonResult({
36
+ error: `Unknown action: ${params.action}`,
37
+ });
38
+ }
39
+ },
40
+ };
41
+ }
42
+ //# sourceMappingURL=queue-tool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queue-tool.js","sourceRoot":"","sources":["../src/queue-tool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAe,MAAM,mBAAmB,CAAC;AAEtD,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAGjD,MAAM,WAAW,GAAG,IAAI,CAAC,MAAM,CAA2B;IACxD,IAAI,EAAE,QAAQ;IACd,IAAI,EAAE,CAAC,MAAM,EAAE,KAAK,EAAE,OAAO,CAAC;IAC9B,WAAW,EACT,sDAAsD;QACtD,oDAAoD;QACpD,qCAAqC;CACxC,CAAC,CAAC;AAEH,MAAM,eAAe,GAAG,IAAI,CAAC,MAAM,CAAC;IAClC,MAAM,EAAE,WAAW;CACpB,CAAC,CAAC;AAIH,MAAM,UAAU,eAAe,CAAC,KAAiB;IAC/C,OAAO;QACL,IAAI,EAAE,cAAc;QACpB,KAAK,EAAE,cAAc;QACrB,WAAW,EACT,8CAA8C;YAC9C,6FAA6F;QAC/F,UAAU,EAAE,eAAe;QAC3B,KAAK,CAAC,OAAO,CAAC,WAAmB,EAAE,MAAuB;YACxD,QAAQ,MAAM,CAAC,MAAM,EAAE,CAAC;gBACtB,KAAK,MAAM,CAAC,CAAC,CAAC;oBACZ,MAAM,KAAK,GAAG,MAAM,KAAK,CAAC,IAAI,EAAE,CAAC;oBACjC,OAAO,UAAU,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;gBACpD,CAAC;gBACD,KAAK,KAAK,CAAC,CAAC,CAAC;oBACX,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE,CAAC;oBAC/B,OAAO,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,gBAAgB,EAAE,CAAC,CAAC;gBACjF,CAAC;gBACD,KAAK,OAAO,CAAC,CAAC,CAAC;oBACb,MAAM,KAAK,GAAG,MAAM,KAAK,CAAC,KAAK,EAAE,CAAC;oBAClC,OAAO,UAAU,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;gBACpD,CAAC;gBACD;oBACE,OAAO,UAAU,CAAC;wBAChB,KAAK,EAAE,mBAAoB,MAA6B,CAAC,MAAM,EAAE;qBAClE,CAAC,CAAC;YACP,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,32 @@
1
+ export interface QueueItem {
2
+ id: string;
3
+ issueId: string;
4
+ event: string;
5
+ summary: string;
6
+ priority: number;
7
+ addedAt: string;
8
+ }
9
+ export declare const EVENT_PRIORITY: Record<string, number>;
10
+ export declare function parseNotificationMessage(message: string): Array<{
11
+ id: string;
12
+ event: string;
13
+ summary: string;
14
+ }>;
15
+ export declare class Mutex {
16
+ private _lock;
17
+ acquire(): Promise<() => void>;
18
+ }
19
+ export declare class InboxQueue {
20
+ private readonly path;
21
+ private readonly mutex;
22
+ constructor(path: string);
23
+ /** Parse a notification message, dedup, and append new items. Returns count added. */
24
+ enqueue(message: string): Promise<number>;
25
+ /** Return all items sorted by priority (lowest number first). Non-destructive. */
26
+ peek(): Promise<QueueItem[]>;
27
+ /** Remove and return the highest-priority item, or null if empty. */
28
+ pop(): Promise<QueueItem | null>;
29
+ /** Remove and return all items sorted by priority. */
30
+ drain(): Promise<QueueItem[]>;
31
+ }
32
+ //# sourceMappingURL=work-queue.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"work-queue.d.ts","sourceRoot":"","sources":["../src/work-queue.ts"],"names":[],"mappings":"AAcA,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,eAAO,MAAM,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAKjD,CAAC;AAyFF,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,MAAM,GACd,KAAK,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAcvD;AAID,qBAAa,KAAK;IAChB,OAAO,CAAC,KAAK,CAAoC;IAE3C,OAAO,IAAI,OAAO,CAAC,MAAM,IAAI,CAAC;CAUrC;AAyDD,qBAAa,UAAU;IAGT,OAAO,CAAC,QAAQ,CAAC,IAAI;IAFjC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAe;gBAER,IAAI,EAAE,MAAM;IAEzC,sFAAsF;IAChF,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAuC/C,kFAAkF;IAC5E,IAAI,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;IAUlC,qEAAqE;IAC/D,GAAG,IAAI,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC;IAetC,sDAAsD;IAChD,KAAK,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;CAapC"}
@@ -0,0 +1,244 @@
1
+ import { readFileSync, existsSync, mkdirSync, openSync, writeSync, fsyncSync, closeSync, unlinkSync, renameSync, appendFileSync, } from "node:fs";
2
+ import { dirname } from "node:path";
3
+ export const EVENT_PRIORITY = {
4
+ "issue.assigned": 1,
5
+ "issue.reassigned": 2,
6
+ "comment.mention": 3,
7
+ "issue.unassigned": 4,
8
+ };
9
+ // --- Parsing ---
10
+ function parseNotificationLine(line) {
11
+ // Multi-notification with quoted comment: "N. [Mentioned] TEAM-123: "comment text""
12
+ const mentionMatch = line.match(/^\d+\.\s+\[Mentioned\]\s+([A-Z]+-\d+):\s*"(.+)"$/);
13
+ if (mentionMatch) {
14
+ return {
15
+ id: mentionMatch[1],
16
+ event: "comment.mention",
17
+ summary: mentionMatch[2].trim(),
18
+ };
19
+ }
20
+ // Multi-notification format: "N. [Label] TEAM-123: summary"
21
+ const multiMatch = line.match(/^\d+\.\s+\[(\w+(?:\s+\w+)?)\]\s+([A-Z]+-\d+):\s*(.+)$/);
22
+ if (multiMatch) {
23
+ const [, label, id, summary] = multiMatch;
24
+ return { id, event: labelToEvent(label), summary: summary.trim() };
25
+ }
26
+ // Single: "Assigned to issue TEAM-123: summary"
27
+ const assignedMatch = line.match(/^Assigned to issue ([A-Z]+-\d+):\s*(.+)$/);
28
+ if (assignedMatch) {
29
+ return {
30
+ id: assignedMatch[1],
31
+ event: "issue.assigned",
32
+ summary: assignedMatch[2].trim(),
33
+ };
34
+ }
35
+ // Single: "Unassigned from issue TEAM-123: summary"
36
+ const unassignedMatch = line.match(/^Unassigned from issue ([A-Z]+-\d+):\s*(.+)$/);
37
+ if (unassignedMatch) {
38
+ return {
39
+ id: unassignedMatch[1],
40
+ event: "issue.unassigned",
41
+ summary: unassignedMatch[2].trim(),
42
+ };
43
+ }
44
+ // Single: "Reassigned away from issue TEAM-123: summary"
45
+ const reassignedMatch = line.match(/^Reassigned away from issue ([A-Z]+-\d+):\s*(.+)$/);
46
+ if (reassignedMatch) {
47
+ return {
48
+ id: reassignedMatch[1],
49
+ event: "issue.reassigned",
50
+ summary: reassignedMatch[2].trim(),
51
+ };
52
+ }
53
+ // Single: "Mentioned in comment on issue TEAM-123: summary\n\n> body"
54
+ const mentionedMatch = line.match(/^Mentioned in comment on issue ([A-Z]+-\d+):\s*(.+?)(?:\n|$)/);
55
+ if (mentionedMatch) {
56
+ return {
57
+ id: mentionedMatch[1],
58
+ event: "comment.mention",
59
+ summary: mentionedMatch[2].trim(),
60
+ };
61
+ }
62
+ return null;
63
+ }
64
+ function labelToEvent(label) {
65
+ const map = {
66
+ Assigned: "issue.assigned",
67
+ Unassigned: "issue.unassigned",
68
+ Reassigned: "issue.reassigned",
69
+ Mentioned: "comment.mention",
70
+ };
71
+ return map[label] ?? `unknown.${label.toLowerCase()}`;
72
+ }
73
+ export function parseNotificationMessage(message) {
74
+ const results = [];
75
+ if (message.startsWith("You have ")) {
76
+ for (const line of message.split("\n").filter((l) => l.trim())) {
77
+ const parsed = parseNotificationLine(line.trim());
78
+ if (parsed)
79
+ results.push(parsed);
80
+ }
81
+ }
82
+ else {
83
+ const parsed = parseNotificationLine(message.trim());
84
+ if (parsed)
85
+ results.push(parsed);
86
+ }
87
+ return results;
88
+ }
89
+ // --- Mutex ---
90
+ export class Mutex {
91
+ _lock = Promise.resolve();
92
+ async acquire() {
93
+ let release;
94
+ const next = new Promise((r) => {
95
+ release = r;
96
+ });
97
+ const prev = this._lock;
98
+ this._lock = next;
99
+ await prev;
100
+ return release;
101
+ }
102
+ }
103
+ // --- InboxQueue ---
104
+ function readJsonl(path) {
105
+ if (!existsSync(path))
106
+ return [];
107
+ try {
108
+ const content = readFileSync(path, "utf-8");
109
+ const items = [];
110
+ for (const line of content.split("\n")) {
111
+ const trimmed = line.trim();
112
+ if (!trimmed)
113
+ continue;
114
+ try {
115
+ items.push(JSON.parse(trimmed));
116
+ }
117
+ catch {
118
+ // skip malformed lines
119
+ }
120
+ }
121
+ return items;
122
+ }
123
+ catch {
124
+ return [];
125
+ }
126
+ }
127
+ function writeJsonl(path, items) {
128
+ const dir = dirname(path);
129
+ if (!existsSync(dir))
130
+ mkdirSync(dir, { recursive: true });
131
+ const tmpPath = `${path}.tmp.${process.pid}.${Date.now()}`;
132
+ const content = items.map((item) => JSON.stringify(item)).join("\n") + (items.length ? "\n" : "");
133
+ try {
134
+ const fd = openSync(tmpPath, "w");
135
+ try {
136
+ writeSync(fd, content, 0, "utf-8");
137
+ fsyncSync(fd);
138
+ }
139
+ finally {
140
+ closeSync(fd);
141
+ }
142
+ renameSync(tmpPath, path);
143
+ }
144
+ catch (err) {
145
+ try {
146
+ unlinkSync(tmpPath);
147
+ }
148
+ catch {
149
+ /* ignore cleanup errors */
150
+ }
151
+ throw err;
152
+ }
153
+ }
154
+ function appendJsonl(path, items) {
155
+ const dir = dirname(path);
156
+ if (!existsSync(dir))
157
+ mkdirSync(dir, { recursive: true });
158
+ const content = items.map((item) => JSON.stringify(item)).join("\n") + "\n";
159
+ appendFileSync(path, content, "utf-8");
160
+ }
161
+ export class InboxQueue {
162
+ path;
163
+ mutex = new Mutex();
164
+ constructor(path) {
165
+ this.path = path;
166
+ }
167
+ /** Parse a notification message, dedup, and append new items. Returns count added. */
168
+ async enqueue(message) {
169
+ const parsed = parseNotificationMessage(message);
170
+ if (parsed.length === 0)
171
+ return 0;
172
+ const release = await this.mutex.acquire();
173
+ try {
174
+ const existing = readJsonl(this.path);
175
+ const existingKeys = new Set(existing.map((item) => `${item.issueId}:${item.event}`));
176
+ const newItems = [];
177
+ const now = new Date().toISOString();
178
+ for (const entry of parsed) {
179
+ const dedupKey = `${entry.id}:${entry.event}`;
180
+ if (existingKeys.has(dedupKey))
181
+ continue;
182
+ newItems.push({
183
+ id: entry.id,
184
+ issueId: entry.id,
185
+ event: entry.event,
186
+ summary: entry.summary,
187
+ priority: EVENT_PRIORITY[entry.event] ?? 5,
188
+ addedAt: now,
189
+ });
190
+ existingKeys.add(dedupKey);
191
+ }
192
+ if (newItems.length > 0) {
193
+ appendJsonl(this.path, newItems);
194
+ }
195
+ return newItems.length;
196
+ }
197
+ finally {
198
+ release();
199
+ }
200
+ }
201
+ /** Return all items sorted by priority (lowest number first). Non-destructive. */
202
+ async peek() {
203
+ const release = await this.mutex.acquire();
204
+ try {
205
+ const items = readJsonl(this.path);
206
+ return items.sort((a, b) => a.priority - b.priority || a.addedAt.localeCompare(b.addedAt));
207
+ }
208
+ finally {
209
+ release();
210
+ }
211
+ }
212
+ /** Remove and return the highest-priority item, or null if empty. */
213
+ async pop() {
214
+ const release = await this.mutex.acquire();
215
+ try {
216
+ const items = readJsonl(this.path);
217
+ if (items.length === 0)
218
+ return null;
219
+ items.sort((a, b) => a.priority - b.priority || a.addedAt.localeCompare(b.addedAt));
220
+ const [popped, ...rest] = items;
221
+ writeJsonl(this.path, rest);
222
+ return popped;
223
+ }
224
+ finally {
225
+ release();
226
+ }
227
+ }
228
+ /** Remove and return all items sorted by priority. */
229
+ async drain() {
230
+ const release = await this.mutex.acquire();
231
+ try {
232
+ const items = readJsonl(this.path);
233
+ if (items.length === 0)
234
+ return [];
235
+ items.sort((a, b) => a.priority - b.priority || a.addedAt.localeCompare(b.addedAt));
236
+ writeJsonl(this.path, []);
237
+ return items;
238
+ }
239
+ finally {
240
+ release();
241
+ }
242
+ }
243
+ }
244
+ //# sourceMappingURL=work-queue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"work-queue.js","sourceRoot":"","sources":["../src/work-queue.ts"],"names":[],"mappings":"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"}
@@ -2,6 +2,7 @@
2
2
  "id": "linear",
3
3
  "name": "Linear",
4
4
  "description": "Linear project management integration for OpenClaw",
5
+ "skills": ["./skills"],
5
6
  "configSchema": {
6
7
  "type": "object",
7
8
  "additionalProperties": false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openclaw-linear",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "Linear webhook integration for OpenClaw — receives events, routes them, and dispatches consolidated notifications to agents",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -13,6 +13,7 @@
13
13
  },
14
14
  "files": [
15
15
  "dist",
16
+ "skills",
16
17
  "openclaw.plugin.json",
17
18
  "LICENSE",
18
19
  "README.md"
@@ -0,0 +1,47 @@
1
+ ---
2
+ name: linear-queue
3
+ description: Work queue processing for Linear notifications. Guides the agent through reading, prioritizing, and completing queued Linear items.
4
+ metadata: { "openclaw": { "always": true } }
5
+ ---
6
+
7
+ # Linear Work Queue
8
+
9
+ You have a `linear_queue` tool for managing Linear notifications that need your attention. The tool is the source of truth — don't parse raw notification messages yourself.
10
+
11
+ ## Tool actions
12
+
13
+ | Action | Effect |
14
+ |---|---|
15
+ | `peek` | View all pending items sorted by priority. Non-destructive. |
16
+ | `pop` | Remove and return the highest-priority item. |
17
+ | `drain` | Remove and return all items sorted by priority. |
18
+
19
+ ## Queue item structure
20
+
21
+ ```json
22
+ {
23
+ "id": "TEAM-123",
24
+ "issueId": "TEAM-123",
25
+ "event": "issue.assigned",
26
+ "summary": "Issue title or comment text",
27
+ "priority": 1,
28
+ "addedAt": "ISO timestamp"
29
+ }
30
+ ```
31
+
32
+ ## Event types and expected actions
33
+
34
+ | Event | Priority | Action |
35
+ |---|---|---|
36
+ | `issue.assigned` | 1 | You've been assigned an issue. Read the issue details with the Linear tools, understand the requirements, and begin work. |
37
+ | `issue.reassigned` | 2 | An issue was reassigned away from you. Acknowledge the change, stop any related work, and add a handoff comment if you have useful context. |
38
+ | `comment.mention` | 3 | You were mentioned in a comment. Read the comment, understand what's being asked, and respond. |
39
+ | `issue.unassigned` | 4 | You were unassigned from an issue. Acknowledge and stop any related work. |
40
+
41
+ ## Processing workflow
42
+
43
+ 1. **Peek** the queue with `linear_queue { action: "peek" }` to see all pending items.
44
+ 2. **Skip** if there are no items.
45
+ 3. **Pop** the next item with `linear_queue { action: "pop" }`.
46
+ 4. **Act**: use the Linear tools (list-issues, update-issue, add-comment) to handle the item per the event type table above.
47
+ 5. **Repeat** from step 3 until pop returns null.