@calltelemetry/openclaw-linear 0.5.1 → 0.6.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.
Files changed (35) hide show
  1. package/README.md +359 -195
  2. package/index.ts +10 -10
  3. package/openclaw.plugin.json +4 -1
  4. package/package.json +9 -2
  5. package/src/agent/agent.test.ts +127 -0
  6. package/src/{agent.ts → agent/agent.ts} +84 -7
  7. package/src/agent/watchdog.test.ts +266 -0
  8. package/src/agent/watchdog.ts +176 -0
  9. package/src/{cli.ts → infra/cli.ts} +5 -5
  10. package/src/{codex-worktree.ts → infra/codex-worktree.ts} +4 -0
  11. package/src/infra/notify.test.ts +169 -0
  12. package/src/{notify.ts → infra/notify.ts} +6 -1
  13. package/src/pipeline/active-session.test.ts +154 -0
  14. package/src/pipeline/artifacts.test.ts +383 -0
  15. package/src/pipeline/artifacts.ts +273 -0
  16. package/src/{dispatch-service.ts → pipeline/dispatch-service.ts} +1 -1
  17. package/src/pipeline/dispatch-state.test.ts +382 -0
  18. package/src/{dispatch-state.ts → pipeline/dispatch-state.ts} +1 -0
  19. package/src/pipeline/pipeline.test.ts +226 -0
  20. package/src/{pipeline.ts → pipeline/pipeline.ts} +134 -10
  21. package/src/{tier-assess.ts → pipeline/tier-assess.ts} +1 -1
  22. package/src/{webhook.test.ts → pipeline/webhook.test.ts} +1 -1
  23. package/src/{webhook.ts → pipeline/webhook.ts} +30 -8
  24. package/src/{claude-tool.ts → tools/claude-tool.ts} +31 -5
  25. package/src/{cli-shared.ts → tools/cli-shared.ts} +5 -4
  26. package/src/{code-tool.ts → tools/code-tool.ts} +2 -2
  27. package/src/{codex-tool.ts → tools/codex-tool.ts} +31 -5
  28. package/src/{gemini-tool.ts → tools/gemini-tool.ts} +31 -5
  29. package/src/{orchestration-tools.ts → tools/orchestration-tools.ts} +1 -1
  30. package/src/client.ts +0 -94
  31. /package/src/{auth.ts → api/auth.ts} +0 -0
  32. /package/src/{linear-api.ts → api/linear-api.ts} +0 -0
  33. /package/src/{oauth-callback.ts → api/oauth-callback.ts} +0 -0
  34. /package/src/{active-session.ts → pipeline/active-session.ts} +0 -0
  35. /package/src/{tools.ts → tools/tools.ts} +0 -0
@@ -1,7 +1,7 @@
1
1
  import { spawn } from "node:child_process";
2
2
  import { createInterface } from "node:readline";
3
3
  import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
4
- import type { ActivityContent } from "./linear-api.js";
4
+ import type { ActivityContent } from "../api/linear-api.js";
5
5
  import {
6
6
  buildLinearApi,
7
7
  resolveSession,
@@ -11,6 +11,7 @@ import {
11
11
  type CliToolParams,
12
12
  type CliResult,
13
13
  } from "./cli-shared.js";
14
+ import { InactivityWatchdog, resolveWatchdogConfig } from "../agent/watchdog.js";
14
15
 
15
16
  const GEMINI_BIN = "/home/claw/.npm-global/bin/gemini";
16
17
 
@@ -98,7 +99,9 @@ export async function runGemini(
98
99
 
99
100
  api.logger.info(`gemini_run: session=${agentSessionId ?? "none"}, issue=${issueIdentifier ?? "none"}`);
100
101
 
101
- const timeout = timeoutMs ?? (pluginConfig?.geminiTimeoutMs as number) ?? DEFAULT_TIMEOUT_MS;
102
+ const agentId = (params as any).agentId ?? (pluginConfig?.defaultAgentId as string) ?? "default";
103
+ const wdConfig = resolveWatchdogConfig(agentId, pluginConfig ?? undefined);
104
+ const timeout = timeoutMs ?? (pluginConfig?.geminiTimeoutMs as number) ?? wdConfig.toolTimeoutMs;
102
105
  const workingDir = params.workingDir ?? (pluginConfig?.geminiBaseRepo as string) ?? DEFAULT_BASE_REPO;
103
106
 
104
107
  const linearApi = buildLinearApi(api, agentSessionId);
@@ -131,12 +134,27 @@ export async function runGemini(
131
134
  });
132
135
 
133
136
  let killed = false;
137
+ let killedByWatchdog = false;
134
138
  const timer = setTimeout(() => {
135
139
  killed = true;
136
140
  child.kill("SIGTERM");
137
141
  setTimeout(() => { if (!child.killed) child.kill("SIGKILL"); }, 5_000);
138
142
  }, timeout);
139
143
 
144
+ const watchdog = new InactivityWatchdog({
145
+ inactivityMs: wdConfig.inactivityMs,
146
+ label: `gemini:${agentSessionId ?? "unknown"}`,
147
+ logger: api.logger,
148
+ onKill: () => {
149
+ killedByWatchdog = true;
150
+ killed = true;
151
+ clearTimeout(timer);
152
+ child.kill("SIGTERM");
153
+ setTimeout(() => { if (!child.killed) child.kill("SIGKILL"); }, 5_000);
154
+ },
155
+ });
156
+ watchdog.start();
157
+
140
158
  const collectedMessages: string[] = [];
141
159
  const collectedCommands: string[] = [];
142
160
  let stderrOutput = "";
@@ -144,6 +162,7 @@ export async function runGemini(
144
162
  const rl = createInterface({ input: child.stdout! });
145
163
  rl.on("line", (line) => {
146
164
  if (!line.trim()) return;
165
+ watchdog.tick();
147
166
 
148
167
  let event: any;
149
168
  try {
@@ -187,11 +206,13 @@ export async function runGemini(
187
206
  });
188
207
 
189
208
  child.stderr?.on("data", (chunk) => {
209
+ watchdog.tick();
190
210
  stderrOutput += chunk.toString();
191
211
  });
192
212
 
193
213
  child.on("close", (code) => {
194
214
  clearTimeout(timer);
215
+ watchdog.stop();
195
216
  rl.close();
196
217
 
197
218
  const parts: string[] = [];
@@ -200,11 +221,15 @@ export async function runGemini(
200
221
  const output = parts.join("\n\n") || stderrOutput || "(no output)";
201
222
 
202
223
  if (killed) {
203
- api.logger.warn(`Gemini timed out after ${timeout}ms`);
224
+ const errorType = killedByWatchdog ? "inactivity_timeout" : "timeout";
225
+ const reason = killedByWatchdog
226
+ ? `Gemini killed by inactivity watchdog (no I/O for ${Math.round(wdConfig.inactivityMs / 1000)}s)`
227
+ : `Gemini timed out after ${Math.round(timeout / 1000)}s`;
228
+ api.logger.warn(reason);
204
229
  resolve({
205
230
  success: false,
206
- output: `Gemini timed out after ${Math.round(timeout / 1000)}s. Partial output:\n${output}`,
207
- error: "timeout",
231
+ output: `${reason}. Partial output:\n${output}`,
232
+ error: errorType,
208
233
  });
209
234
  return;
210
235
  }
@@ -225,6 +250,7 @@ export async function runGemini(
225
250
 
226
251
  child.on("error", (err) => {
227
252
  clearTimeout(timer);
253
+ watchdog.stop();
228
254
  rl.close();
229
255
  api.logger.error(`Gemini spawn error: ${err}`);
230
256
  resolve({
@@ -1,6 +1,6 @@
1
1
  import type { AnyAgentTool, OpenClawPluginApi } from "openclaw/plugin-sdk";
2
2
  import { jsonResult } from "openclaw/plugin-sdk";
3
- import { runAgent } from "./agent.js";
3
+ import { runAgent } from "../agent/agent.js";
4
4
 
5
5
  /**
6
6
  * Create orchestration tools that let agents delegate work to other crew agents.
package/src/client.ts DELETED
@@ -1,94 +0,0 @@
1
- const LINEAR_GRAPHQL_URL = "https://api.linear.app/graphql";
2
-
3
- export class LinearClient {
4
- constructor(private accessToken: string) {}
5
-
6
- async request<T = any>(query: string, variables?: Record<string, any>): Promise<T> {
7
- const res = await fetch(LINEAR_GRAPHQL_URL, {
8
- method: "POST",
9
- headers: {
10
- "Content-Type": "application/json",
11
- "Authorization": `Bearer ${this.accessToken}`,
12
- },
13
- body: JSON.stringify({ query, variables }),
14
- });
15
-
16
- if (!res.ok) {
17
- const text = await res.text();
18
- throw new Error(`Linear API request failed: ${res.status} ${text}`);
19
- }
20
-
21
- const payload = await res.json();
22
- if (payload.errors) {
23
- throw new Error(`Linear API returned errors: ${JSON.stringify(payload.errors)}`);
24
- }
25
-
26
- return payload.data as T;
27
- }
28
-
29
- async getViewer() {
30
- const query = `
31
- query {
32
- viewer {
33
- id
34
- name
35
- email
36
- }
37
- }
38
- `;
39
- return this.request(query);
40
- }
41
-
42
- async listIssues(params: { limit: number; teamId?: string }) {
43
- const query = `
44
- query ListIssues($limit: Int, $teamId: String) {
45
- issues(first: $limit, filter: { team: { id: { eq: $teamId } } }) {
46
- nodes {
47
- id
48
- identifier
49
- title
50
- description
51
- state {
52
- name
53
- }
54
- assignee {
55
- name
56
- }
57
- }
58
- }
59
- }
60
- `;
61
- return this.request(query, params);
62
- }
63
-
64
- async createIssue(params: { title: string; description?: string; teamId: string }) {
65
- const query = `
66
- mutation CreateIssue($title: String!, $description: String, $teamId: String!) {
67
- issueCreate(input: { title: $title, description: $description, teamId: $teamId }) {
68
- success
69
- issue {
70
- id
71
- identifier
72
- title
73
- }
74
- }
75
- }
76
- `;
77
- return this.request(query, params);
78
- }
79
-
80
- async addComment(params: { issueId: string; body: string }) {
81
- const query = `
82
- mutation AddComment($issueId: String!, $body: String!) {
83
- commentCreate(input: { issueId: $issueId, body: $body }) {
84
- success
85
- comment {
86
- id
87
- }
88
- }
89
- }
90
- `;
91
- return this.request(query, params);
92
- }
93
-
94
- }
File without changes
File without changes
File without changes