openclaw-linear 0.1.0 → 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 +41 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +20 -3
- package/dist/index.js.map +1 -1
- package/dist/work-queue.d.ts +38 -0
- package/dist/work-queue.d.ts.map +1 -0
- package/dist/work-queue.js +218 -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 +48 -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;AAazE,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,YAAY,EAAE,GAAG,MAAM,CAYzE;AAqFD,wBAAgB,QAAQ,CAAC,GAAG,EAAE,iBAAiB,GAAG,IAAI,CAmFrD;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,6 @@
|
|
|
1
1
|
import { createWebhookHandler } from "./webhook-handler.js";
|
|
2
2
|
import { createEventRouter } from "./event-router.js";
|
|
3
|
+
import { handleIntake, handleRecovery } from "./work-queue.js";
|
|
3
4
|
const CHANNEL_ID = "linear";
|
|
4
5
|
const DEFAULT_DEBOUNCE_MS = 30_000;
|
|
5
6
|
const EVENT_LABELS = {
|
|
@@ -29,7 +30,7 @@ function formatActionSummary(action) {
|
|
|
29
30
|
}
|
|
30
31
|
return action.issueLabel || action.detail;
|
|
31
32
|
}
|
|
32
|
-
async function dispatchConsolidatedActions(actions, api) {
|
|
33
|
+
async function dispatchConsolidatedActions(actions, api, queuePath) {
|
|
33
34
|
if (actions.length === 0)
|
|
34
35
|
return;
|
|
35
36
|
const core = api.runtime;
|
|
@@ -44,7 +45,15 @@ async function dispatchConsolidatedActions(actions, api) {
|
|
|
44
45
|
id: first.linearUserId,
|
|
45
46
|
},
|
|
46
47
|
});
|
|
47
|
-
|
|
48
|
+
// Write to queue deterministically — no LLM involved
|
|
49
|
+
const rawBody = formatConsolidatedMessage(actions);
|
|
50
|
+
const added = handleIntake(rawBody, queuePath);
|
|
51
|
+
if (added === 0) {
|
|
52
|
+
api.logger.info("[linear] All notifications deduped — skipping agent dispatch");
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
// Agent gets a minimal notification pointing to the queue
|
|
56
|
+
const body = `${added} new Linear notification(s) queued. Pending items in queue/work-queue.json.`;
|
|
48
57
|
const ctx = core.channel.reply.finalizeInboundContext({
|
|
49
58
|
Body: body,
|
|
50
59
|
BodyForAgent: body,
|
|
@@ -94,6 +103,14 @@ export function activate(api) {
|
|
|
94
103
|
const debounceMs = (typeof rawDebounceMs === "number" && rawDebounceMs > 0)
|
|
95
104
|
? rawDebounceMs
|
|
96
105
|
: DEFAULT_DEBOUNCE_MS;
|
|
106
|
+
const queuePath = api.resolvePath("queue/work-queue.json");
|
|
107
|
+
// Reset stale in_progress items on gateway startup
|
|
108
|
+
api.on("gateway_start", () => {
|
|
109
|
+
const recovered = handleRecovery(queuePath);
|
|
110
|
+
if (recovered > 0) {
|
|
111
|
+
api.logger.info(`[linear] Recovered ${recovered} stale queue item(s)`);
|
|
112
|
+
}
|
|
113
|
+
});
|
|
97
114
|
const route = createEventRouter({
|
|
98
115
|
agentMapping,
|
|
99
116
|
logger: api.logger,
|
|
@@ -105,7 +122,7 @@ export function activate(api) {
|
|
|
105
122
|
buildKey: (action) => action.agentId,
|
|
106
123
|
shouldDebounce: () => true,
|
|
107
124
|
onFlush: async (actions) => {
|
|
108
|
-
await dispatchConsolidatedActions(actions, api);
|
|
125
|
+
await dispatchConsolidatedActions(actions, api, queuePath);
|
|
109
126
|
},
|
|
110
127
|
onError: (err) => {
|
|
111
128
|
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,38 @@
|
|
|
1
|
+
export interface QueueItem {
|
|
2
|
+
id: string;
|
|
3
|
+
issueId: string;
|
|
4
|
+
event: string;
|
|
5
|
+
summary: string;
|
|
6
|
+
status: "pending" | "in_progress" | "done";
|
|
7
|
+
priority: number;
|
|
8
|
+
addedAt: string;
|
|
9
|
+
startedAt: string | null;
|
|
10
|
+
completedAt: string | null;
|
|
11
|
+
}
|
|
12
|
+
export interface WorkQueue {
|
|
13
|
+
items: QueueItem[];
|
|
14
|
+
}
|
|
15
|
+
export declare function parseNotificationMessage(message: string): Array<{
|
|
16
|
+
id: string;
|
|
17
|
+
event: string;
|
|
18
|
+
summary: string;
|
|
19
|
+
}>;
|
|
20
|
+
export declare function readQueue(queuePath: string): WorkQueue;
|
|
21
|
+
export declare function writeQueue(queuePath: string, queue: WorkQueue): void;
|
|
22
|
+
/**
|
|
23
|
+
* Parse a consolidated notification message and append new items to the work queue.
|
|
24
|
+
* Deduplicates against non-done items only (completed items can be re-queued).
|
|
25
|
+
* Returns the number of new items added.
|
|
26
|
+
*/
|
|
27
|
+
export declare function handleIntake(message: string, queuePath: string): number;
|
|
28
|
+
/**
|
|
29
|
+
* Reset stale in_progress items to pending.
|
|
30
|
+
* Called on gateway startup to recover from crashes/restarts.
|
|
31
|
+
*/
|
|
32
|
+
export declare function handleRecovery(queuePath: string): number;
|
|
33
|
+
/**
|
|
34
|
+
* Remove done items older than maxAgeMs from the queue.
|
|
35
|
+
* Mutates the queue in place. Returns number of items purged.
|
|
36
|
+
*/
|
|
37
|
+
export declare function cleanupDoneItems(queue: WorkQueue, maxAgeMs?: number): number;
|
|
38
|
+
//# 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":"AAaA,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,SAAS,GAAG,aAAa,GAAG,MAAM,CAAC;IAC3C,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,SAAS,EAAE,CAAC;CACpB;AAgGD,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,wBAAgB,SAAS,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,CAOtD;AAED,wBAAgB,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,GAAG,IAAI,CAuBpE;AAID;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CA0CvE;AAID;;;GAGG;AACH,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CA0BxD;AAMD;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,SAAS,EAChB,QAAQ,GAAE,MAAwB,GACjC,MAAM,CAWR"}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { readFileSync, existsSync, mkdirSync, openSync, writeSync, fsyncSync, closeSync, unlinkSync, renameSync, } from "node:fs";
|
|
2
|
+
import { dirname } from "node:path";
|
|
3
|
+
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
|
+
// --- File I/O ---
|
|
90
|
+
export function readQueue(queuePath) {
|
|
91
|
+
if (!existsSync(queuePath))
|
|
92
|
+
return { items: [] };
|
|
93
|
+
try {
|
|
94
|
+
return JSON.parse(readFileSync(queuePath, "utf-8"));
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
return { items: [] };
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
export function writeQueue(queuePath, queue) {
|
|
101
|
+
const dir = dirname(queuePath);
|
|
102
|
+
if (!existsSync(dir))
|
|
103
|
+
mkdirSync(dir, { recursive: true });
|
|
104
|
+
const tmpPath = `${queuePath}.tmp.${process.pid}.${Date.now()}`;
|
|
105
|
+
const content = JSON.stringify(queue, null, 2) + "\n";
|
|
106
|
+
try {
|
|
107
|
+
const fd = openSync(tmpPath, "w");
|
|
108
|
+
try {
|
|
109
|
+
writeSync(fd, content, 0, "utf-8");
|
|
110
|
+
fsyncSync(fd);
|
|
111
|
+
}
|
|
112
|
+
finally {
|
|
113
|
+
closeSync(fd);
|
|
114
|
+
}
|
|
115
|
+
renameSync(tmpPath, queuePath);
|
|
116
|
+
}
|
|
117
|
+
catch (err) {
|
|
118
|
+
try {
|
|
119
|
+
unlinkSync(tmpPath);
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
/* ignore cleanup errors */
|
|
123
|
+
}
|
|
124
|
+
throw err;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
// --- Intake ---
|
|
128
|
+
/**
|
|
129
|
+
* Parse a consolidated notification message and append new items to the work queue.
|
|
130
|
+
* Deduplicates against non-done items only (completed items can be re-queued).
|
|
131
|
+
* Returns the number of new items added.
|
|
132
|
+
*/
|
|
133
|
+
export function handleIntake(message, queuePath) {
|
|
134
|
+
const parsed = parseNotificationMessage(message);
|
|
135
|
+
if (parsed.length === 0)
|
|
136
|
+
return 0;
|
|
137
|
+
const queue = readQueue(queuePath);
|
|
138
|
+
// Only dedup against non-done items so re-assignments after completion work
|
|
139
|
+
const existingKeys = new Set(queue.items
|
|
140
|
+
.filter((item) => item.status !== "done")
|
|
141
|
+
.map((item) => `${item.issueId ?? item.id}:${item.event}`));
|
|
142
|
+
let added = 0;
|
|
143
|
+
const now = new Date().toISOString();
|
|
144
|
+
for (const entry of parsed) {
|
|
145
|
+
const dedupKey = `${entry.id}:${entry.event}`;
|
|
146
|
+
if (existingKeys.has(dedupKey))
|
|
147
|
+
continue;
|
|
148
|
+
queue.items.push({
|
|
149
|
+
id: entry.id,
|
|
150
|
+
issueId: entry.id,
|
|
151
|
+
event: entry.event,
|
|
152
|
+
summary: entry.summary,
|
|
153
|
+
status: "pending",
|
|
154
|
+
priority: EVENT_PRIORITY[entry.event] ?? 5,
|
|
155
|
+
addedAt: now,
|
|
156
|
+
startedAt: null,
|
|
157
|
+
completedAt: null,
|
|
158
|
+
});
|
|
159
|
+
existingKeys.add(dedupKey);
|
|
160
|
+
added++;
|
|
161
|
+
}
|
|
162
|
+
if (added > 0) {
|
|
163
|
+
cleanupDoneItems(queue);
|
|
164
|
+
queue.items.sort((a, b) => a.priority - b.priority);
|
|
165
|
+
writeQueue(queuePath, queue);
|
|
166
|
+
}
|
|
167
|
+
return added;
|
|
168
|
+
}
|
|
169
|
+
// --- Recovery ---
|
|
170
|
+
/**
|
|
171
|
+
* Reset stale in_progress items to pending.
|
|
172
|
+
* Called on gateway startup to recover from crashes/restarts.
|
|
173
|
+
*/
|
|
174
|
+
export function handleRecovery(queuePath) {
|
|
175
|
+
if (!existsSync(queuePath))
|
|
176
|
+
return 0;
|
|
177
|
+
let queue;
|
|
178
|
+
try {
|
|
179
|
+
queue = JSON.parse(readFileSync(queuePath, "utf-8"));
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
return 0;
|
|
183
|
+
}
|
|
184
|
+
if (!Array.isArray(queue.items) || queue.items.length === 0)
|
|
185
|
+
return 0;
|
|
186
|
+
let recovered = 0;
|
|
187
|
+
for (const item of queue.items) {
|
|
188
|
+
if (item.status === "in_progress") {
|
|
189
|
+
item.status = "pending";
|
|
190
|
+
item.startedAt = null;
|
|
191
|
+
recovered++;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
if (recovered > 0) {
|
|
195
|
+
writeQueue(queuePath, queue);
|
|
196
|
+
}
|
|
197
|
+
return recovered;
|
|
198
|
+
}
|
|
199
|
+
// --- Cleanup ---
|
|
200
|
+
const DONE_MAX_AGE_MS = 24 * 60 * 60 * 1000; // 24 hours
|
|
201
|
+
/**
|
|
202
|
+
* Remove done items older than maxAgeMs from the queue.
|
|
203
|
+
* Mutates the queue in place. Returns number of items purged.
|
|
204
|
+
*/
|
|
205
|
+
export function cleanupDoneItems(queue, maxAgeMs = DONE_MAX_AGE_MS) {
|
|
206
|
+
const cutoff = Date.now() - maxAgeMs;
|
|
207
|
+
const before = queue.items.length;
|
|
208
|
+
queue.items = queue.items.filter((item) => {
|
|
209
|
+
if (item.status !== "done")
|
|
210
|
+
return true;
|
|
211
|
+
const completedAt = item.completedAt
|
|
212
|
+
? new Date(item.completedAt).getTime()
|
|
213
|
+
: 0;
|
|
214
|
+
return completedAt > cutoff;
|
|
215
|
+
});
|
|
216
|
+
return before - queue.items.length;
|
|
217
|
+
}
|
|
218
|
+
//# sourceMappingURL=work-queue.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"work-queue.js","sourceRoot":"","sources":["../src/work-queue.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,EACZ,UAAU,EACV,SAAS,EACT,QAAQ,EACR,SAAS,EACT,SAAS,EACT,SAAS,EACT,UAAU,EACV,UAAU,GACX,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAkBpC,MAAM,cAAc,GAA2B;IAC7C,gBAAgB,EAAE,CAAC;IACnB,kBAAkB,EAAE,CAAC;IACrB,iBAAiB,EAAE,CAAC;IACpB,kBAAkB,EAAE,CAAC;CACtB,CAAC;AAEF,kBAAkB;AAElB,SAAS,qBAAqB,CAC5B,IAAY;IAEZ,oFAAoF;IACpF,MAAM,YAAY,GAAG,IAAI,CAAC,KAAK,CAC7B,kDAAkD,CACnD,CAAC;IACF,IAAI,YAAY,EAAE,CAAC;QACjB,OAAO;YACL,EAAE,EAAE,YAAY,CAAC,CAAC,CAAC;YACnB,KAAK,EAAE,iBAAiB;YACxB,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE;SAChC,CAAC;IACJ,CAAC;IAED,4DAA4D;IAC5D,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAC3B,uDAAuD,CACxD,CAAC;IACF,IAAI,UAAU,EAAE,CAAC;QACf,MAAM,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,OAAO,CAAC,GAAG,UAAU,CAAC;QAC1C,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,YAAY,CAAC,KAAK,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC;IACrE,CAAC;IAED,gDAAgD;IAChD,MAAM,aAAa,GAAG,IAAI,CAAC,KAAK,CAC9B,0CAA0C,CAC3C,CAAC;IACF,IAAI,aAAa,EAAE,CAAC;QAClB,OAAO;YACL,EAAE,EAAE,aAAa,CAAC,CAAC,CAAC;YACpB,KAAK,EAAE,gBAAgB;YACvB,OAAO,EAAE,aAAa,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE;SACjC,CAAC;IACJ,CAAC;IAED,oDAAoD;IACpD,MAAM,eAAe,GAAG,IAAI,CAAC,KAAK,CAChC,8CAA8C,CAC/C,CAAC;IACF,IAAI,eAAe,EAAE,CAAC;QACpB,OAAO;YACL,EAAE,EAAE,eAAe,CAAC,CAAC,CAAC;YACtB,KAAK,EAAE,kBAAkB;YACzB,OAAO,EAAE,eAAe,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE;SACnC,CAAC;IACJ,CAAC;IAED,yDAAyD;IACzD,MAAM,eAAe,GAAG,IAAI,CAAC,KAAK,CAChC,mDAAmD,CACpD,CAAC;IACF,IAAI,eAAe,EAAE,CAAC;QACpB,OAAO;YACL,EAAE,EAAE,eAAe,CAAC,CAAC,CAAC;YACtB,KAAK,EAAE,kBAAkB;YACzB,OAAO,EAAE,eAAe,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE;SACnC,CAAC;IACJ,CAAC;IAED,sEAAsE;IACtE,MAAM,cAAc,GAAG,IAAI,CAAC,KAAK,CAC/B,8DAA8D,CAC/D,CAAC;IACF,IAAI,cAAc,EAAE,CAAC;QACnB,OAAO;YACL,EAAE,EAAE,cAAc,CAAC,CAAC,CAAC;YACrB,KAAK,EAAE,iBAAiB;YACxB,OAAO,EAAE,cAAc,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE;SAClC,CAAC;IACJ,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,YAAY,CAAC,KAAa;IACjC,MAAM,GAAG,GAA2B;QAClC,QAAQ,EAAE,gBAAgB;QAC1B,UAAU,EAAE,kBAAkB;QAC9B,UAAU,EAAE,kBAAkB;QAC9B,SAAS,EAAE,iBAAiB;KAC7B,CAAC;IACF,OAAO,GAAG,CAAC,KAAK,CAAC,IAAI,WAAW,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;AACxD,CAAC;AAED,MAAM,UAAU,wBAAwB,CACtC,OAAe;IAEf,MAAM,OAAO,GAA0D,EAAE,CAAC;IAE1E,IAAI,OAAO,CAAC,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC;QACpC,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC;YAC/D,MAAM,MAAM,GAAG,qBAAqB,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;YAClD,IAAI,MAAM;gBAAE,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACnC,CAAC;IACH,CAAC;SAAM,CAAC;QACN,MAAM,MAAM,GAAG,qBAAqB,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;QACrD,IAAI,MAAM;YAAE,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACnC,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,mBAAmB;AAEnB,MAAM,UAAU,SAAS,CAAC,SAAiB;IACzC,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC;QAAE,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;IACjD,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,SAAS,EAAE,OAAO,CAAC,CAAc,CAAC;IACnE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;IACvB,CAAC;AACH,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,SAAiB,EAAE,KAAgB;IAC5D,MAAM,GAAG,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;IAC/B,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAE1D,MAAM,OAAO,GAAG,GAAG,SAAS,QAAQ,OAAO,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;IAChE,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC;IACtD,IAAI,CAAC;QACH,MAAM,EAAE,GAAG,QAAQ,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;QAClC,IAAI,CAAC;YACH,SAAS,CAAC,EAAE,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,CAAC,CAAC;YACnC,SAAS,CAAC,EAAE,CAAC,CAAC;QAChB,CAAC;gBAAS,CAAC;YACT,SAAS,CAAC,EAAE,CAAC,CAAC;QAChB,CAAC;QACD,UAAU,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;IACjC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,CAAC;YACH,UAAU,CAAC,OAAO,CAAC,CAAC;QACtB,CAAC;QAAC,MAAM,CAAC;YACP,2BAA2B;QAC7B,CAAC;QACD,MAAM,GAAG,CAAC;IACZ,CAAC;AACH,CAAC;AAED,iBAAiB;AAEjB;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAAC,OAAe,EAAE,SAAiB;IAC7D,MAAM,MAAM,GAAG,wBAAwB,CAAC,OAAO,CAAC,CAAC;IACjD,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC;IAElC,MAAM,KAAK,GAAG,SAAS,CAAC,SAAS,CAAC,CAAC;IAEnC,4EAA4E;IAC5E,MAAM,YAAY,GAAG,IAAI,GAAG,CAC1B,KAAK,CAAC,KAAK;SACR,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,KAAK,MAAM,CAAC;SACxC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,GAAG,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,EAAE,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC,CAC7D,CAAC;IAEF,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAErC,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,MAAM,QAAQ,GAAG,GAAG,KAAK,CAAC,EAAE,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;QAC9C,IAAI,YAAY,CAAC,GAAG,CAAC,QAAQ,CAAC;YAAE,SAAS;QAEzC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC;YACf,EAAE,EAAE,KAAK,CAAC,EAAE;YACZ,OAAO,EAAE,KAAK,CAAC,EAAE;YACjB,KAAK,EAAE,KAAK,CAAC,KAAK;YAClB,OAAO,EAAE,KAAK,CAAC,OAAO;YACtB,MAAM,EAAE,SAAS;YACjB,QAAQ,EAAE,cAAc,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC;YAC1C,OAAO,EAAE,GAAG;YACZ,SAAS,EAAE,IAAI;YACf,WAAW,EAAE,IAAI;SAClB,CAAC,CAAC;QACH,YAAY,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC3B,KAAK,EAAE,CAAC;IACV,CAAC;IAED,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;QACd,gBAAgB,CAAC,KAAK,CAAC,CAAC;QACxB,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAC;QACpD,UAAU,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;IAC/B,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAED,mBAAmB;AAEnB;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,SAAiB;IAC9C,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC;QAAE,OAAO,CAAC,CAAC;IAErC,IAAI,KAAgB,CAAC;IACrB,IAAI,CAAC;QACH,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,SAAS,EAAE,OAAO,CAAC,CAAc,CAAC;IACpE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,CAAC,CAAC;IACX,CAAC;IAED,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC;IAEtE,IAAI,SAAS,GAAG,CAAC,CAAC;IAClB,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;QAC/B,IAAI,IAAI,CAAC,MAAM,KAAK,aAAa,EAAE,CAAC;YAClC,IAAI,CAAC,MAAM,GAAG,SAAS,CAAC;YACxB,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC;YACtB,SAAS,EAAE,CAAC;QACd,CAAC;IACH,CAAC;IAED,IAAI,SAAS,GAAG,CAAC,EAAE,CAAC;QAClB,UAAU,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;IAC/B,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,kBAAkB;AAElB,MAAM,eAAe,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,WAAW;AAExD;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAC9B,KAAgB,EAChB,WAAmB,eAAe;IAElC,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,CAAC;IACrC,MAAM,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC;IAClC,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE;QACxC,IAAI,IAAI,CAAC,MAAM,KAAK,MAAM;YAAE,OAAO,IAAI,CAAC;QACxC,MAAM,WAAW,GAAG,IAAI,CAAC,WAAW;YAClC,CAAC,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,OAAO,EAAE;YACtC,CAAC,CAAC,CAAC,CAAC;QACN,OAAO,WAAW,GAAG,MAAM,CAAC;IAC9B,CAAC,CAAC,CAAC;IACH,OAAO,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC;AACrC,CAAC"}
|
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.2.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,48 @@
|
|
|
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 work queue at `queue/work-queue.json` containing Linear notifications that need your attention. The queue is the source of truth — don't parse raw notification messages yourself.
|
|
10
|
+
|
|
11
|
+
## Queue item structure
|
|
12
|
+
|
|
13
|
+
```json
|
|
14
|
+
{
|
|
15
|
+
"id": "TEAM-123",
|
|
16
|
+
"issueId": "TEAM-123",
|
|
17
|
+
"event": "issue.assigned",
|
|
18
|
+
"summary": "Issue title or comment text",
|
|
19
|
+
"status": "pending",
|
|
20
|
+
"priority": 1,
|
|
21
|
+
"addedAt": "ISO timestamp",
|
|
22
|
+
"startedAt": null,
|
|
23
|
+
"completedAt": null
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Event types and expected actions
|
|
28
|
+
|
|
29
|
+
| Event | Priority | Action |
|
|
30
|
+
|---|---|---|
|
|
31
|
+
| `issue.assigned` | 1 | You've been assigned an issue. Read the issue details with the Linear tools, understand the requirements, and begin work. |
|
|
32
|
+
| `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. |
|
|
33
|
+
| `comment.mention` | 3 | You were mentioned in a comment. Read the comment, understand what's being asked, and respond. |
|
|
34
|
+
| `issue.unassigned` | 4 | You were unassigned from an issue. Acknowledge and stop any related work. |
|
|
35
|
+
|
|
36
|
+
## Processing workflow
|
|
37
|
+
|
|
38
|
+
1. **Read** the queue file (`queue/work-queue.json`).
|
|
39
|
+
2. **Skip** if there are no `pending` items.
|
|
40
|
+
3. **Pick** the highest-priority `pending` item (lowest priority number first; break ties by `addedAt`, oldest first).
|
|
41
|
+
4. **Mark `in_progress`**: set `status` to `"in_progress"` and `startedAt` to the current ISO timestamp. Write the queue back to disk.
|
|
42
|
+
5. **Act**: use the Linear tools (list-issues, update-issue, add-comment) to handle the item per the event type table above.
|
|
43
|
+
6. **Mark `done`**: set `status` to `"done"` and `completedAt` to the current ISO timestamp. Write the queue back to disk.
|
|
44
|
+
7. **Repeat** from step 1 until no `pending` items remain.
|
|
45
|
+
|
|
46
|
+
## File update rules
|
|
47
|
+
|
|
48
|
+
Always do atomic read-modify-write cycles: read the full file, modify the target item in memory, write the entire file back. Never partially update the file.
|