@calltelemetry/openclaw-linear 0.3.0 → 0.4.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/src/pipeline.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
2
2
  import type { LinearAgentApi, ActivityContent } from "./linear-api.js";
3
3
  import { runAgent } from "./agent.js";
4
+ import { setActiveSession, clearActiveSession } from "./active-session.js";
4
5
 
5
6
  export interface PipelineContext {
6
7
  api: OpenClawPluginApi;
@@ -14,6 +15,10 @@ export interface PipelineContext {
14
15
  description?: string | null;
15
16
  };
16
17
  promptContext?: unknown;
18
+ /** Populated by implementor stage if Codex creates a worktree */
19
+ worktreePath?: string | null;
20
+ /** Codex branch name, e.g. codex/UAT-123 */
21
+ codexBranch?: string | null;
17
22
  }
18
23
 
19
24
  function emit(ctx: PipelineContext, content: ActivityContent): Promise<void> {
@@ -22,6 +27,16 @@ function emit(ctx: PipelineContext, content: ActivityContent): Promise<void> {
22
27
  });
23
28
  }
24
29
 
30
+ function toolContext(ctx: PipelineContext): string {
31
+ return [
32
+ `\n## Tool Context`,
33
+ `When calling \`code_run\`, you MUST pass these parameters:`,
34
+ `- \`agentSessionId\`: \`"${ctx.agentSessionId}"\``,
35
+ `- \`issueIdentifier\`: \`"${ctx.issue.identifier}"\``,
36
+ `This enables real-time progress streaming to Linear and isolated worktree creation.`,
37
+ ].join("\n");
38
+ }
39
+
25
40
  // ── Stage 1: Planner ───────────────────────────────────────────────
26
41
 
27
42
  export async function runPlannerStage(ctx: PipelineContext): Promise<string | null> {
@@ -54,6 +69,8 @@ ${ctx.promptContext ? `**Additional context:**\n${JSON.stringify(ctx.promptConte
54
69
  4. Note any risks or dependencies
55
70
  5. Output your plan in markdown format
56
71
 
72
+ IMPORTANT: Do NOT call code_run or any coding tools. Your job is ONLY to analyze and write a plan. The implementor stage will execute the plan using code_run after you're done.
73
+
57
74
  Output ONLY the plan, nothing else.`;
58
75
 
59
76
  await emit(ctx, { type: "action", action: "Planning", parameter: ctx.issue.identifier });
@@ -64,6 +81,7 @@ Output ONLY the plan, nothing else.`;
64
81
  sessionId: `linear-plan-${ctx.agentSessionId}`,
65
82
  message,
66
83
  timeoutMs: 5 * 60_000,
84
+
67
85
  });
68
86
 
69
87
  if (!result.success) {
@@ -104,14 +122,16 @@ ${plan}
104
122
 
105
123
  ## Instructions
106
124
  1. Follow the plan step by step
107
- 2. Write the code changes
108
- 3. Create commits for each logical change
109
- 4. If the plan involves creating a PR, do so
110
- 5. Report what you did and any files changed
125
+ 2. Use \`code_run\` to write code, create files, run tests, and refactor — it works in an isolated git worktree
126
+ 3. Use \`spawn_agent\` / \`ask_agent\` to delegate to other crew agents if needed
127
+ 4. Create commits for each logical change
128
+ 5. If the plan involves creating a PR, do so
129
+ 6. Report what you did, any files changed, and the worktree/branch path
130
+ ${toolContext(ctx)}
111
131
 
112
132
  Be thorough but stay within scope of the plan.`;
113
133
 
114
- await emit(ctx, { type: "action", action: "Implementing", parameter: ctx.issue.identifier });
134
+ await emit(ctx, { type: "action", action: "Calling coding provider", parameter: "Codex" });
115
135
 
116
136
  const result = await runAgent({
117
137
  api: ctx.api,
@@ -119,6 +139,7 @@ Be thorough but stay within scope of the plan.`;
119
139
  sessionId: `linear-impl-${ctx.agentSessionId}`,
120
140
  message,
121
141
  timeoutMs: 10 * 60_000,
142
+
122
143
  });
123
144
 
124
145
  if (!result.success) {
@@ -126,7 +147,13 @@ Be thorough but stay within scope of the plan.`;
126
147
  return null;
127
148
  }
128
149
 
129
- await emit(ctx, { type: "action", action: "Implementation complete", result: "Proceeding to audit" });
150
+ // Try to extract worktree info from agent output (Codex results include it)
151
+ const worktreeMatch = result.output.match(/worktreePath["\s:]+([/\w.-]+)/);
152
+ const branchMatch = result.output.match(/branch["\s:]+([/\w.-]+)/);
153
+ if (worktreeMatch) ctx.worktreePath = worktreeMatch[1];
154
+ if (branchMatch) ctx.codexBranch = branchMatch[1];
155
+
156
+ await emit(ctx, { type: "action", action: "Implementation complete", parameter: ctx.issue.identifier });
130
157
  return result.output;
131
158
  }
132
159
 
@@ -139,6 +166,10 @@ export async function runAuditorStage(
139
166
  ): Promise<void> {
140
167
  await emit(ctx, { type: "thought", body: "Auditing implementation against the plan..." });
141
168
 
169
+ const worktreeInfo = ctx.worktreePath
170
+ ? `\n## Worktree\nCode changes are at: \`${ctx.worktreePath}\` (branch: \`${ctx.codexBranch ?? "unknown"}\`)\n`
171
+ : "";
172
+
142
173
  const message = `You are an auditor. Review this implementation against the original plan.
143
174
 
144
175
  ## Issue: ${ctx.issue.identifier} — ${ctx.issue.title}
@@ -148,13 +179,14 @@ ${plan}
148
179
 
149
180
  ## Implementation Result:
150
181
  ${implResult}
151
-
182
+ ${worktreeInfo}
152
183
  ## Instructions
153
184
  1. Verify each plan step was completed
154
- 2. Check for any missed items
185
+ 2. Check for any missed items — use \`ask_agent\` / \`spawn_agent\` for specialized review if needed
155
186
  3. Note any concerns or improvements needed
156
187
  4. Provide a pass/fail verdict with reasoning
157
188
  5. Output a concise audit summary in markdown
189
+ ${toolContext(ctx)}
158
190
 
159
191
  Output ONLY the audit summary.`;
160
192
 
@@ -166,6 +198,7 @@ Output ONLY the audit summary.`;
166
198
  sessionId: `linear-audit-${ctx.agentSessionId}`,
167
199
  message,
168
200
  timeoutMs: 5 * 60_000,
201
+
169
202
  });
170
203
 
171
204
  const auditSummary = result.success ? result.output : `Audit failed: ${result.output.slice(0, 500)}`;
@@ -184,20 +217,43 @@ Output ONLY the audit summary.`;
184
217
  // ── Full Pipeline ─────────────────────────────────────────────────
185
218
 
186
219
  export async function runFullPipeline(ctx: PipelineContext): Promise<void> {
220
+ // Register active session so tools (code_run) can resolve it
221
+ setActiveSession({
222
+ agentSessionId: ctx.agentSessionId,
223
+ issueIdentifier: ctx.issue.identifier,
224
+ issueId: ctx.issue.id,
225
+ agentId: ctx.agentId,
226
+ startedAt: Date.now(),
227
+ });
228
+
187
229
  try {
188
230
  // Stage 1: Plan
189
231
  const plan = await runPlannerStage(ctx);
190
- if (!plan) return;
232
+ if (!plan) {
233
+ clearActiveSession(ctx.issue.id);
234
+ return;
235
+ }
191
236
 
192
237
  // Pipeline pauses here — user must reply to approve.
193
- // The "prompted" webhook will call resumePipeline().
238
+ // The "prompted" / "created" webhook will call resumePipeline().
239
+ // Active session stays registered until resume completes.
194
240
  } catch (err) {
241
+ clearActiveSession(ctx.issue.id);
195
242
  ctx.api.logger.error(`Pipeline error: ${err}`);
196
243
  await emit(ctx, { type: "error", body: `Pipeline failed: ${String(err).slice(0, 500)}` });
197
244
  }
198
245
  }
199
246
 
200
247
  export async function resumePipeline(ctx: PipelineContext, plan: string): Promise<void> {
248
+ // Register (or update) active session for tool resolution
249
+ setActiveSession({
250
+ agentSessionId: ctx.agentSessionId,
251
+ issueIdentifier: ctx.issue.identifier,
252
+ issueId: ctx.issue.id,
253
+ agentId: ctx.agentId,
254
+ startedAt: Date.now(),
255
+ });
256
+
201
257
  try {
202
258
  // Stage 2: Implement
203
259
  const implResult = await runImplementorStage(ctx, plan);
@@ -208,5 +264,7 @@ export async function resumePipeline(ctx: PipelineContext, plan: string): Promis
208
264
  } catch (err) {
209
265
  ctx.api.logger.error(`Pipeline error: ${err}`);
210
266
  await emit(ctx, { type: "error", body: `Pipeline failed: ${String(err).slice(0, 500)}` });
267
+ } finally {
268
+ clearActiveSession(ctx.issue.id);
211
269
  }
212
270
  }
package/src/tools.ts CHANGED
@@ -1,84 +1,34 @@
1
- import type { AnyAgentTool, OpenClawPluginApi, OpenClawPluginToolContext } from "openclaw/plugin-sdk";
2
- import { jsonResult } from "openclaw/plugin-sdk";
3
- import { LinearClient } from "./client.js";
1
+ import type { AnyAgentTool, OpenClawPluginApi } from "openclaw/plugin-sdk";
2
+ import { createCodeTool } from "./code-tool.js";
3
+ import { createOrchestrationTools } from "./orchestration-tools.js";
4
4
 
5
- export function createLinearTools(api: OpenClawPluginApi, ctx: OpenClawPluginToolContext): AnyAgentTool[] {
6
- const getClient = () => {
7
- // In a real implementation, we would resolve the token from auth profiles
8
- // For now, we'll try to get it from environment or a known profile
9
- const token = process.env.LINEAR_ACCESS_TOKEN;
10
- if (!token) {
11
- throw new Error("Linear access token not found. Please authenticate first.");
5
+ export function createLinearTools(api: OpenClawPluginApi, ctx: Record<string, unknown>): any[] {
6
+ const pluginConfig = (api as any).pluginConfig as Record<string, unknown> | undefined;
7
+
8
+ // Unified code_run tool dispatches to configured backend (claude/codex/gemini)
9
+ const codeTools: AnyAgentTool[] = [];
10
+ try {
11
+ codeTools.push(createCodeTool(api, ctx));
12
+ } catch (err) {
13
+ api.logger.warn(`code_run tool not available: ${err}`);
14
+ }
15
+
16
+ // Orchestration tools (conditional on config — defaults to enabled)
17
+ const orchestrationTools: AnyAgentTool[] = [];
18
+ const enableOrchestration = pluginConfig?.enableOrchestration !== false;
19
+ if (enableOrchestration) {
20
+ try {
21
+ orchestrationTools.push(...createOrchestrationTools(api, ctx));
22
+ } catch (err) {
23
+ api.logger.warn(`Orchestration tools not available: ${err}`);
12
24
  }
13
- return new LinearClient(token);
14
- };
15
-
25
+ }
26
+
27
+ // Linear issue management (list, create, update, close, comment, etc.)
28
+ // is handled by the `linearis` skill — no custom tools needed here.
29
+
16
30
  return [
17
- {
18
- name: "linear_list_issues",
19
- description: "List issues from a Linear workspace",
20
- parameters: {
21
- type: "object",
22
- properties: {
23
- limit: { type: "number", description: "Max issues to return", default: 10 },
24
- teamId: { type: "string", description: "Filter by team ID" }
25
- }
26
- },
27
- execute: async ({ limit, teamId }) => {
28
- const client = getClient();
29
- const data = await client.listIssues({ limit, teamId });
30
- return jsonResult({
31
- message: `Found ${data.issues.nodes.length} issues`,
32
- issues: data.issues.nodes
33
- });
34
- }
35
- },
36
- {
37
- name: "linear_create_issue",
38
- description: "Create a new issue in Linear",
39
- parameters: {
40
- type: "object",
41
- properties: {
42
- title: { type: "string", description: "Issue title" },
43
- description: { type: "string", description: "Issue description" },
44
- teamId: { type: "string", description: "Team ID" }
45
- },
46
- required: ["title", "teamId"]
47
- },
48
- execute: async ({ title, description, teamId }) => {
49
- const client = getClient();
50
- const data = await client.createIssue({ title, description, teamId });
51
- if (data.issueCreate.success) {
52
- return jsonResult({
53
- message: "Created issue successfully",
54
- issue: data.issueCreate.issue
55
- });
56
- }
57
- return jsonResult({ message: "Failed to create issue" });
58
- }
59
- },
60
- {
61
- name: "linear_add_comment",
62
- description: "Add a comment to a Linear issue",
63
- parameters: {
64
- type: "object",
65
- properties: {
66
- issueId: { type: "string", description: "Issue ID" },
67
- body: { type: "string", description: "Comment body" }
68
- },
69
- required: ["issueId", "body"]
70
- },
71
- execute: async ({ issueId, body }) => {
72
- const client = getClient();
73
- const data = await client.addComment({ issueId, body });
74
- if (data.commentCreate.success) {
75
- return jsonResult({
76
- message: "Added comment successfully",
77
- commentId: data.commentCreate.comment.id
78
- });
79
- }
80
- return jsonResult({ message: "Failed to add comment" });
81
- }
82
- }
31
+ ...codeTools,
32
+ ...orchestrationTools,
83
33
  ];
84
34
  }