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 +41 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +16 -3
- package/dist/index.js.map +1 -1
- package/dist/queue-tool.d.ts +4 -0
- package/dist/queue-tool.d.ts.map +1 -0
- package/dist/queue-tool.js +42 -0
- package/dist/queue-tool.js.map +1 -0
- package/dist/work-queue.d.ts +32 -0
- package/dist/work-queue.d.ts.map +1 -0
- package/dist/work-queue.js +244 -0
- package/dist/work-queue.js.map +1 -0
- package/openclaw.plugin.json +1 -0
- package/package.json +2 -1
- package/skills/linear-queue/SKILL.md +47 -0
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
87
|
-
2. [Assigned] ENG-43: Update API docs
|
|
88
|
-
3. [Mentioned] ENG-40: Auth flow: "Can you review this?"
|
|
108
|
+
### Priority
|
|
89
109
|
|
|
90
|
-
|
|
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
|
-
|
|
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
|
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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;
|
|
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
|
-
|
|
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":"AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,EAAE,iBAAiB,EAAqB,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -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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"}
|
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openclaw-linear",
|
|
3
|
-
"version": "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.
|