@calltelemetry/openclaw-linear 0.9.7 → 0.9.8

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@calltelemetry/openclaw-linear",
3
- "version": "0.9.7",
3
+ "version": "0.9.8",
4
4
  "description": "Linear Agent plugin for OpenClaw — webhook-driven AI pipeline with OAuth, multi-agent routing, and issue triage",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -2,7 +2,7 @@
2
2
  * Recorded API responses from sub-issue decomposition smoke test.
3
3
  * Auto-generated — do not edit manually.
4
4
  * Re-generate by running: npx vitest run src/__test__/smoke-linear-api.test.ts
5
- * Last recorded: 2026-02-22T02:01:43.760Z
5
+ * Last recorded: 2026-02-22T02:12:13.118Z
6
6
  */
7
7
 
8
8
  export const RECORDED = {
@@ -44,20 +44,20 @@ export const RECORDED = {
44
44
  }
45
45
  ],
46
46
  "createParent": {
47
- "id": "7b5cc5d3-ec16-47e0-b0c4-caf8eea47609",
48
- "identifier": "UAT-265"
47
+ "id": "b2794dc5-82a1-4942-b999-36c7093b7477",
48
+ "identifier": "UAT-285"
49
49
  },
50
50
  "createSubIssue1": {
51
- "id": "de6d1ea0-3358-406a-bcfd-1d19b1516442",
52
- "identifier": "UAT-266"
51
+ "id": "5a928cd6-7dd8-4e3d-9aa0-4699ee2f3990",
52
+ "identifier": "UAT-286"
53
53
  },
54
54
  "createSubIssue2": {
55
- "id": "7ca89fc7-b246-44b6-8400-0e2a4d169f47",
56
- "identifier": "UAT-267"
55
+ "id": "b5d88918-6f3f-43d9-9e3b-e98608707efe",
56
+ "identifier": "UAT-287"
57
57
  },
58
58
  "subIssue1Details": {
59
- "id": "de6d1ea0-3358-406a-bcfd-1d19b1516442",
60
- "identifier": "UAT-266",
59
+ "id": "5a928cd6-7dd8-4e3d-9aa0-4699ee2f3990",
60
+ "identifier": "UAT-286",
61
61
  "title": "[SMOKE TEST] Sub-Issue 1: Backend API",
62
62
  "description": "Implement the backend search API endpoint.\n\nGiven a search query, when the API is called, then matching results are returned.",
63
63
  "estimate": 2,
@@ -83,16 +83,16 @@ export const RECORDED = {
83
83
  },
84
84
  "project": null,
85
85
  "parent": {
86
- "id": "7b5cc5d3-ec16-47e0-b0c4-caf8eea47609",
87
- "identifier": "UAT-265"
86
+ "id": "b2794dc5-82a1-4942-b999-36c7093b7477",
87
+ "identifier": "UAT-285"
88
88
  },
89
89
  "relations": {
90
90
  "nodes": []
91
91
  }
92
92
  },
93
93
  "subIssue2Details": {
94
- "id": "7ca89fc7-b246-44b6-8400-0e2a4d169f47",
95
- "identifier": "UAT-267",
94
+ "id": "b5d88918-6f3f-43d9-9e3b-e98608707efe",
95
+ "identifier": "UAT-287",
96
96
  "title": "[SMOKE TEST] Sub-Issue 2: Frontend UI",
97
97
  "description": "Build the frontend search UI component.\n\nGiven the search page loads, when the user types a query, then results display in real-time.",
98
98
  "estimate": 3,
@@ -118,18 +118,18 @@ export const RECORDED = {
118
118
  },
119
119
  "project": null,
120
120
  "parent": {
121
- "id": "7b5cc5d3-ec16-47e0-b0c4-caf8eea47609",
122
- "identifier": "UAT-265"
121
+ "id": "b2794dc5-82a1-4942-b999-36c7093b7477",
122
+ "identifier": "UAT-285"
123
123
  },
124
124
  "relations": {
125
125
  "nodes": []
126
126
  }
127
127
  },
128
128
  "parentDetails": {
129
- "id": "7b5cc5d3-ec16-47e0-b0c4-caf8eea47609",
130
- "identifier": "UAT-265",
129
+ "id": "b2794dc5-82a1-4942-b999-36c7093b7477",
130
+ "identifier": "UAT-285",
131
131
  "title": "[SMOKE TEST] Sub-Issue Parent: Search Feature",
132
- "description": "Auto-generated by smoke test to verify sub-issue decomposition.\n\nThis parent issue should have two sub-issues created under it.\n\nCreated: 2026-02-22T02:01:42.469Z",
132
+ "description": "Auto-generated by smoke test to verify sub-issue decomposition.\n\nThis parent issue should have two sub-issues created under it.\n\nCreated: 2026-02-22T02:12:11.695Z",
133
133
  "estimate": null,
134
134
  "state": {
135
135
  "name": "Backlog",
@@ -158,11 +158,11 @@ export const RECORDED = {
158
158
  }
159
159
  },
160
160
  "createRelation": {
161
- "id": "4c97b0aa-563e-4aad-84aa-826078f012fd"
161
+ "id": "6db881b7-5a05-449f-85f2-3c03dbb1e3f0"
162
162
  },
163
163
  "subIssue1WithRelation": {
164
- "id": "de6d1ea0-3358-406a-bcfd-1d19b1516442",
165
- "identifier": "UAT-266",
164
+ "id": "5a928cd6-7dd8-4e3d-9aa0-4699ee2f3990",
165
+ "identifier": "UAT-286",
166
166
  "title": "[SMOKE TEST] Sub-Issue 1: Backend API",
167
167
  "description": "Implement the backend search API endpoint.\n\nGiven a search query, when the API is called, then matching results are returned.",
168
168
  "estimate": 2,
@@ -188,16 +188,16 @@ export const RECORDED = {
188
188
  },
189
189
  "project": null,
190
190
  "parent": {
191
- "id": "7b5cc5d3-ec16-47e0-b0c4-caf8eea47609",
192
- "identifier": "UAT-265"
191
+ "id": "b2794dc5-82a1-4942-b999-36c7093b7477",
192
+ "identifier": "UAT-285"
193
193
  },
194
194
  "relations": {
195
195
  "nodes": [
196
196
  {
197
197
  "type": "blocks",
198
198
  "relatedIssue": {
199
- "id": "7ca89fc7-b246-44b6-8400-0e2a4d169f47",
200
- "identifier": "UAT-267",
199
+ "id": "b5d88918-6f3f-43d9-9e3b-e98608707efe",
200
+ "identifier": "UAT-287",
201
201
  "title": "[SMOKE TEST] Sub-Issue 2: Frontend UI"
202
202
  }
203
203
  }
@@ -205,8 +205,8 @@ export const RECORDED = {
205
205
  }
206
206
  },
207
207
  "subIssue2WithRelation": {
208
- "id": "7ca89fc7-b246-44b6-8400-0e2a4d169f47",
209
- "identifier": "UAT-267",
208
+ "id": "b5d88918-6f3f-43d9-9e3b-e98608707efe",
209
+ "identifier": "UAT-287",
210
210
  "title": "[SMOKE TEST] Sub-Issue 2: Frontend UI",
211
211
  "description": "Build the frontend search UI component.\n\nGiven the search page loads, when the user types a query, then results display in real-time.",
212
212
  "estimate": 3,
@@ -232,14 +232,14 @@ export const RECORDED = {
232
232
  {
233
233
  "body": "This thread is for an agent session with ctclaw.",
234
234
  "user": null,
235
- "createdAt": "2026-02-22T02:01:43.418Z"
235
+ "createdAt": "2026-02-22T02:12:12.989Z"
236
236
  }
237
237
  ]
238
238
  },
239
239
  "project": null,
240
240
  "parent": {
241
- "id": "7b5cc5d3-ec16-47e0-b0c4-caf8eea47609",
242
- "identifier": "UAT-265"
241
+ "id": "b2794dc5-82a1-4942-b999-36c7093b7477",
242
+ "identifier": "UAT-285"
243
243
  },
244
244
  "relations": {
245
245
  "nodes": []
@@ -204,6 +204,52 @@ describe("runAgent subprocess", () => {
204
204
  });
205
205
  });
206
206
 
207
+ it("extracts text from flat envelope (payloads at top level)", async () => {
208
+ const api = createApi();
209
+ (api.runtime.system as any).runCommandWithTimeout = vi.fn().mockResolvedValue({
210
+ code: 0,
211
+ stdout: JSON.stringify({ payloads: [{ text: "flat response" }], meta: {} }),
212
+ stderr: "",
213
+ });
214
+
215
+ const result = await runAgent({
216
+ api,
217
+ agentId: "test",
218
+ sessionId: "s1",
219
+ message: "test",
220
+ });
221
+
222
+ expect(result.success).toBe(true);
223
+ expect(result.output).toBe("flat response");
224
+ });
225
+
226
+ it("strips plugin init log noise before JSON blob", async () => {
227
+ const api = createApi();
228
+ const noisyOutput = [
229
+ "[plugins] Dispatch gateway methods registered",
230
+ "[plugins] Linear agent extension registered (agent: zoe)",
231
+ '[plugins] code_run: default backend=codex, aliases={"claude":"claude"}',
232
+ JSON.stringify({ payloads: [{ text: "clean response" }], meta: {} }),
233
+ ].join("\n");
234
+ (api.runtime.system as any).runCommandWithTimeout = vi.fn().mockResolvedValue({
235
+ code: 0,
236
+ stdout: noisyOutput,
237
+ stderr: "",
238
+ });
239
+
240
+ const result = await runAgent({
241
+ api,
242
+ agentId: "test",
243
+ sessionId: "s1",
244
+ message: "test",
245
+ });
246
+
247
+ expect(result.success).toBe(true);
248
+ expect(result.output).toBe("clean response");
249
+ expect(result.output).not.toContain("[plugins]");
250
+ expect(result.output).not.toContain("payloads");
251
+ });
252
+
207
253
  describe("runAgent date/time injection", () => {
208
254
  it("injects current date/time into the message sent to subprocess", async () => {
209
255
  const api = createApi();
@@ -38,9 +38,12 @@ function resolveAgentDirs(agentId: string, config: Record<string, any>): AgentDi
38
38
  let _extensionAPI: any | null = null;
39
39
  async function getExtensionAPI() {
40
40
  if (!_extensionAPI) {
41
- // Resolve the openclaw package location dynamically, then import extensionAPI
41
+ // Resolve the openclaw package location dynamically, then import extensionAPI.
42
+ // openclaw's package.json exports don't expose ./package.json, so resolve
43
+ // via the main entry point and walk up to the package root.
42
44
  const _require = createRequire(import.meta.url);
43
- const openclawDir = dirname(_require.resolve("openclaw/package.json"));
45
+ const mainEntry = _require.resolve("openclaw");
46
+ const openclawDir = dirname(dirname(mainEntry));
44
47
  _extensionAPI = await import(join(openclawDir, "dist", "extensionAPI.js"));
45
48
  }
46
49
  return _extensionAPI;
@@ -415,17 +418,47 @@ async function runSubprocess(
415
418
  const raw = result.stdout || "";
416
419
  api.logger.info(`Agent ${agentId} completed for session ${sessionId}`);
417
420
 
418
- // Extract clean text from --json output
419
- try {
420
- const parsed = JSON.parse(raw);
421
- const payloads = parsed?.result?.payloads;
422
- if (Array.isArray(payloads) && payloads.length > 0) {
423
- const text = payloads.map((p: any) => p.text).filter(Boolean).join("\n\n");
424
- if (text) return { success: true, output: text };
421
+ // Extract clean text from --json output.
422
+ // The subprocess stdout may contain plugin init log lines before the JSON blob.
423
+ // Strip everything before the first `{` to isolate the JSON envelope.
424
+ const extracted = extractJsonFromOutput(raw);
425
+ if (extracted) return { success: true, output: extracted };
426
+
427
+ return { success: true, output: raw };
428
+ }
429
+
430
+ /**
431
+ * Extract text from subprocess --json output. Handles:
432
+ * - Log noise before the JSON blob (plugin init lines)
433
+ * - Both envelope shapes: `{ payloads }` (flat) and `{ result: { payloads } }` (nested)
434
+ */
435
+ function extractJsonFromOutput(raw: string): string | null {
436
+ // The subprocess stdout may contain plugin init log lines before the JSON
437
+ // result blob. Try parsing the whole thing first; if that fails, scan lines
438
+ // backwards for a `{` that starts a valid JSON envelope with payloads.
439
+ const candidates: string[] = [raw];
440
+
441
+ // Also try from each line that starts with `{` (the JSON blob typically
442
+ // starts on its own line after log noise).
443
+ const lines = raw.split("\n");
444
+ for (let i = 0; i < lines.length; i++) {
445
+ if (lines[i].trimStart().startsWith("{")) {
446
+ candidates.push(lines.slice(i).join("\n"));
425
447
  }
426
- } catch {
427
- // Not JSON — use raw output as-is
428
448
  }
429
449
 
430
- return { success: true, output: raw };
450
+ for (const candidate of candidates) {
451
+ try {
452
+ const parsed = JSON.parse(candidate);
453
+ // Try both envelope shapes: flat `{ payloads }` and nested `{ result: { payloads } }`
454
+ const payloads = parsed?.payloads ?? parsed?.result?.payloads;
455
+ if (Array.isArray(payloads) && payloads.length > 0) {
456
+ const text = payloads.map((p: any) => p.text).filter(Boolean).join("\n\n");
457
+ if (text) return text;
458
+ }
459
+ } catch {
460
+ // Not valid JSON at this position — try next candidate
461
+ }
462
+ }
463
+ return null;
431
464
  }