@calltelemetry/openclaw-linear 0.8.5 → 0.8.6

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.
@@ -16,6 +16,7 @@ import { initiatePlanningSession, handlePlannerTurn, runPlanAudit } from "./plan
16
16
  import { startProjectDispatch } from "./dag-dispatch.js";
17
17
  import { emitDiagnostic } from "../infra/observability.js";
18
18
  import { classifyIntent } from "./intent-classify.js";
19
+ import { extractGuidance, formatGuidanceAppendix, cacheGuidanceForTeam, getCachedGuidanceForTeam, isGuidanceEnabled, _resetGuidanceCacheForTesting } from "./guidance.js";
19
20
 
20
21
  // ── Agent profiles (loaded from config, no hardcoded names) ───────
21
22
  interface AgentProfile {
@@ -101,6 +102,7 @@ export function _resetForTesting(): void {
101
102
  profilesCache = null;
102
103
  linearApiCache = null;
103
104
  lastSweep = Date.now();
105
+ _resetGuidanceCacheForTesting();
104
106
  }
105
107
 
106
108
  /** @internal — test-only; add an issue ID to the activeRuns set. */
@@ -236,6 +238,8 @@ export async function handleLinearWebhook(
236
238
  }
237
239
 
238
240
  const payload = body.value;
241
+ const pluginConfig = (api as any).pluginConfig as Record<string, unknown> | undefined;
242
+
239
243
  // Debug: log full payload structure for diagnosing webhook types
240
244
  const payloadKeys = Object.keys(payload).join(", ");
241
245
  api.logger.info(`Linear webhook received: type=${payload.type} action=${payload.action} keys=[${payloadKeys}]`);
@@ -297,16 +301,15 @@ export async function handleLinearWebhook(
297
301
 
298
302
  const agentId = resolveAgentId(api);
299
303
  const previousComments = payload.previousComments ?? [];
300
- const guidance = payload.guidance;
304
+ const guidanceCtx = extractGuidance(payload);
301
305
 
302
- api.logger.info(`AgentSession created: ${session.id} for issue ${issue?.identifier ?? issue?.id} (comments: ${previousComments.length}, guidance: ${guidance ? "yes" : "no"})`)
306
+ api.logger.info(`AgentSession created: ${session.id} for issue ${issue?.identifier ?? issue?.id} (comments: ${previousComments.length}, guidance: ${guidanceCtx.guidance ? "yes" : "no"})`)
303
307
 
304
- // Extract the user's latest message from previousComments
305
- // The last comment is the most recent user message
308
+ // Extract the user's latest message from previousComments (NOT from guidance)
306
309
  const lastComment = previousComments.length > 0
307
310
  ? previousComments[previousComments.length - 1]
308
311
  : null;
309
- const userMessage = lastComment?.body ?? guidance ?? "";
312
+ const userMessage = lastComment?.body ?? "";
310
313
 
311
314
  // Fetch full issue details
312
315
  let enrichedIssue: any = issue;
@@ -318,6 +321,13 @@ export async function handleLinearWebhook(
318
321
 
319
322
  const description = enrichedIssue?.description ?? issue?.description ?? "(no description)";
320
323
 
324
+ // Cache guidance for this team (enables Comment webhook paths)
325
+ const teamId = enrichedIssue?.team?.id;
326
+ if (guidanceCtx.guidance && teamId) cacheGuidanceForTeam(teamId, guidanceCtx.guidance);
327
+ const guidanceAppendix = isGuidanceEnabled(pluginConfig as Record<string, unknown> | undefined, teamId)
328
+ ? formatGuidanceAppendix(guidanceCtx.guidance)
329
+ : "";
330
+
321
331
  // Build conversation context from previous comments
322
332
  const commentContext = previousComments
323
333
  .slice(-5)
@@ -331,22 +341,24 @@ export async function handleLinearWebhook(
331
341
  const toolAccessLines = isTriaged
332
342
  ? [
333
343
  `**Tool access:**`,
334
- `- \`linearis\` CLI: Full access. You can read, update, close, and comment on issues. Use \`linearis issues update ${issueRef} --state <state>\` to change status, \`linearis issues update ${issueRef} --priority <1-4>\` to set priority, etc.`,
335
- `- \`code_run\`: Dispatch coding work to a worker. Workers return text — they cannot access linearis.`,
344
+ `- \`linear_issues\` tool: Full access. Use action="read" with issueId="${issueRef}" to get details, action="create" to create issues (with parentIssueId to create sub-issues for granular work breakdown), action="update" with status/priority/labels/estimate to modify issues, action="comment" to post comments, action="list_states" to see available workflow states.`,
345
+ `- \`code_run\`: Dispatch coding work to a worker. Workers return text — they cannot access linear_issues.`,
336
346
  `- \`spawn_agent\`/\`ask_agent\`: Delegate to other crew agents.`,
337
347
  `- Standard tools: exec, read, edit, write, web_search, etc.`,
348
+ ``,
349
+ `**Sub-issue guidance:** When a task is too large or has multiple distinct parts, break it into sub-issues using action="create" with parentIssueId="${issueRef}". Each sub-issue should be an atomic, independently testable unit of work with its own acceptance criteria. This enables parallel dispatch and clearer progress tracking.`,
338
350
  ]
339
351
  : [
340
352
  `**Tool access:**`,
341
- `- \`linearis\` CLI: READ ONLY. You can read issues (\`linearis issues read ${issueRef}\`), list issues (\`linearis issues list\`), and search (\`linearis issues search "..."\`). Do NOT use linearis to update, close, comment, or modify issues.`,
342
- `- \`code_run\`: Dispatch coding work to a worker. Workers return text — they cannot access linearis.`,
353
+ `- \`linear_issues\` tool: READ ONLY. Use action="read" with issueId="${issueRef}" to get details, action="list_states"/"list_labels" for metadata. Do NOT use action="update", action="create", or action="comment".`,
354
+ `- \`code_run\`: Dispatch coding work to a worker. Workers return text — they cannot access linear_issues.`,
343
355
  `- \`spawn_agent\`/\`ask_agent\`: Delegate to other crew agents.`,
344
356
  `- Standard tools: exec, read, edit, write, web_search, etc.`,
345
357
  ];
346
358
 
347
359
  const roleLines = isTriaged
348
360
  ? [`**Your role:** Orchestrator with full Linear access. You can update issue fields, change status, and dispatch work via \`code_run\`. Do NOT post comments yourself — the handler posts your text output.`]
349
- : [`**Your role:** You are the dispatcher. For any coding or implementation work, use \`code_run\` to dispatch it. Workers return text output. You summarize results. You do NOT update issue status or post linearis comments — the audit system handles lifecycle transitions.`];
361
+ : [`**Your role:** You are the dispatcher. For any coding or implementation work, use \`code_run\` to dispatch it. Workers return text output. You summarize results. You do NOT update issue status or post comments via linear_issues — the audit system handles lifecycle transitions.`];
350
362
 
351
363
  const message = [
352
364
  `You are an orchestrator responding in a Linear issue session. Your text output will be posted as activities visible to the user.`,
@@ -364,6 +376,7 @@ export async function handleLinearWebhook(
364
376
  userMessage ? `\n**Latest message:**\n> ${userMessage}` : "",
365
377
  ``,
366
378
  `Respond to the user's request. For work requests, dispatch via \`code_run\` and summarize the result. Be concise and action-oriented.`,
379
+ guidanceAppendix,
367
380
  ].filter(Boolean).join("\n");
368
381
 
369
382
  // Run agent directly (non-blocking)
@@ -473,13 +486,11 @@ export async function handleLinearWebhook(
473
486
  return true;
474
487
  }
475
488
 
476
- // Extract user message from the activity or prompt context
477
- const promptContext = payload.promptContext;
489
+ // Extract user message from the activity (not from promptContext which contains issue data + guidance)
490
+ const guidanceCtxPrompted = extractGuidance(payload);
478
491
  const userMessage =
479
492
  activity?.content?.body ??
480
493
  activity?.body ??
481
- promptContext?.message ??
482
- promptContext ??
483
494
  "";
484
495
 
485
496
  if (!userMessage || typeof userMessage !== "string" || userMessage.trim().length === 0) {
@@ -514,6 +525,13 @@ export async function handleLinearWebhook(
514
525
 
515
526
  const description = enrichedIssue?.description ?? issue?.description ?? "(no description)";
516
527
 
528
+ // Resolve guidance for follow-up
529
+ const followUpTeamId = enrichedIssue?.team?.id;
530
+ if (guidanceCtxPrompted.guidance && followUpTeamId) cacheGuidanceForTeam(followUpTeamId, guidanceCtxPrompted.guidance);
531
+ const followUpGuidanceAppendix = isGuidanceEnabled(pluginConfig as Record<string, unknown> | undefined, followUpTeamId)
532
+ ? formatGuidanceAppendix(guidanceCtxPrompted.guidance ?? (followUpTeamId ? getCachedGuidanceForTeam(followUpTeamId) : null))
533
+ : "";
534
+
517
535
  // Build context from recent comments
518
536
  const recentComments = enrichedIssue?.comments?.nodes ?? [];
519
537
  const commentContext = recentComments
@@ -528,15 +546,17 @@ export async function handleLinearWebhook(
528
546
  const followUpToolAccessLines = followUpIsTriaged
529
547
  ? [
530
548
  `**Tool access:**`,
531
- `- \`linearis\` CLI: Full access. You can read, update, close, and comment on issues. Use \`linearis issues update ${followUpIssueRef} --state <state>\` to change status, \`linearis issues update ${followUpIssueRef} --priority <1-4>\` to set priority, etc.`,
532
- `- \`code_run\`: Dispatch coding work to a worker. Workers return text — they cannot access linearis.`,
549
+ `- \`linear_issues\` tool: Full access. Use action="read" with issueId="${followUpIssueRef}" to get details, action="create" to create issues (with parentIssueId to create sub-issues for granular work breakdown), action="update" with status/priority/labels/estimate to modify issues, action="comment" to post comments, action="list_states" to see available workflow states.`,
550
+ `- \`code_run\`: Dispatch coding work to a worker. Workers return text — they cannot access linear_issues.`,
533
551
  `- \`spawn_agent\`/\`ask_agent\`: Delegate to other crew agents.`,
534
552
  `- Standard tools: exec, read, edit, write, web_search, etc.`,
553
+ ``,
554
+ `**Sub-issue guidance:** When a task is too large or has multiple distinct parts, break it into sub-issues using action="create" with parentIssueId="${followUpIssueRef}". Each sub-issue should be an atomic, independently testable unit of work with its own acceptance criteria. This enables parallel dispatch and clearer progress tracking.`,
535
555
  ]
536
556
  : [
537
557
  `**Tool access:**`,
538
- `- \`linearis\` CLI: READ ONLY. You can read issues (\`linearis issues read ${followUpIssueRef}\`), list, and search. Do NOT use linearis to update, close, comment, or modify issues.`,
539
- `- \`code_run\`: Dispatch coding work to a worker. Workers return text — they cannot access linearis.`,
558
+ `- \`linear_issues\` tool: READ ONLY. Use action="read" with issueId="${followUpIssueRef}" to get details, action="list_states"/"list_labels" for metadata. Do NOT use action="update", action="create", or action="comment".`,
559
+ `- \`code_run\`: Dispatch coding work to a worker. Workers return text — they cannot access linear_issues.`,
540
560
  `- \`spawn_agent\`/\`ask_agent\`: Delegate to other crew agents.`,
541
561
  `- Standard tools: exec, read, edit, write, web_search, etc.`,
542
562
  ];
@@ -561,6 +581,7 @@ export async function handleLinearWebhook(
561
581
  `\n**User's follow-up message:**\n> ${userMessage}`,
562
582
  ``,
563
583
  `Respond to the user's follow-up. For work requests, dispatch via \`code_run\`. Be concise and action-oriented.`,
584
+ followUpGuidanceAppendix,
564
585
  ].filter(Boolean).join("\n");
565
586
 
566
587
  setActiveSession({
@@ -635,7 +656,6 @@ export async function handleLinearWebhook(
635
656
  const commentBody = comment?.body ?? "";
636
657
  const commentor = comment?.user?.name ?? "Unknown";
637
658
  const issue = comment?.issue ?? payload.issue;
638
- const pluginConfig = (api as any).pluginConfig as Record<string, unknown> | undefined;
639
659
 
640
660
  if (!issue?.id) {
641
661
  api.logger.error("Comment webhook: missing issue data");
@@ -1050,6 +1070,13 @@ export async function handleLinearWebhook(
1050
1070
  ? `**Created by:** ${creatorName} (${creatorEmail})`
1051
1071
  : `**Created by:** ${creatorName}`;
1052
1072
 
1073
+ // Look up cached guidance for triage
1074
+ const triageTeamId = enrichedIssue?.team?.id ?? issue?.team?.id;
1075
+ const triageGuidance = triageTeamId ? getCachedGuidanceForTeam(triageTeamId) : null;
1076
+ const triageGuidanceAppendix = isGuidanceEnabled(pluginConfig as Record<string, unknown> | undefined, triageTeamId)
1077
+ ? formatGuidanceAppendix(triageGuidance)
1078
+ : "";
1079
+
1053
1080
  const message = [
1054
1081
  `IMPORTANT: You are triaging a new Linear issue. You MUST respond with a JSON block containing your triage decisions, followed by your assessment as plain text.`,
1055
1082
  ``,
@@ -1086,6 +1113,7 @@ export async function handleLinearWebhook(
1086
1113
  `IMPORTANT: Only reference real users from the issue data above. Do NOT fabricate or guess user names, emails, or identities. The issue creator is shown in the "Created by" field.`,
1087
1114
  ``,
1088
1115
  `Then write your full assessment as markdown below the JSON block.`,
1116
+ triageGuidanceAppendix,
1089
1117
  ].filter(Boolean).join("\n");
1090
1118
 
1091
1119
  const sessionId = `linear-triage-${issue.id}-${Date.now()}`;
@@ -1237,6 +1265,13 @@ async function dispatchCommentToAgent(
1237
1265
  .map((c: any) => `**${c.user?.name ?? "Unknown"}**: ${(c.body ?? "").slice(0, 200)}`)
1238
1266
  .join("\n");
1239
1267
 
1268
+ // Look up cached guidance for this team (Comment webhooks don't carry guidance)
1269
+ const commentTeamId = enrichedIssue?.team?.id;
1270
+ const cachedGuidance = commentTeamId ? getCachedGuidanceForTeam(commentTeamId) : null;
1271
+ const commentGuidanceAppendix = isGuidanceEnabled(pluginConfig, commentTeamId)
1272
+ ? formatGuidanceAppendix(cachedGuidance)
1273
+ : "";
1274
+
1240
1275
  const issueRef = enrichedIssue?.identifier ?? issue.identifier ?? issue.id;
1241
1276
  const stateType = enrichedIssue?.state?.type ?? "";
1242
1277
  const isTriaged = stateType === "started" || stateType === "completed" || stateType === "canceled";
@@ -1244,14 +1279,16 @@ async function dispatchCommentToAgent(
1244
1279
  const toolAccessLines = isTriaged
1245
1280
  ? [
1246
1281
  `**Tool access:**`,
1247
- `- \`linearis\` CLI: Full access. You can read, update, close, and comment on issues. Use \`linearis issues update ${issueRef} --state <state>\` to change status, \`linearis issues update ${issueRef} --priority <1-4>\` to set priority, etc.`,
1248
- `- \`code_run\`: Dispatch coding work to a worker. Workers return text — they cannot access linearis.`,
1282
+ `- \`linear_issues\` tool: Full access. Use action="read" with issueId="${issueRef}" to get details, action="create" to create issues (with parentIssueId to create sub-issues for granular work breakdown), action="update" with status/priority/labels/estimate to modify issues, action="comment" to post comments, action="list_states" to see available workflow states.`,
1283
+ `- \`code_run\`: Dispatch coding work to a worker. Workers return text — they cannot access linear_issues.`,
1249
1284
  `- Standard tools: exec, read, edit, write, web_search, etc.`,
1285
+ ``,
1286
+ `**Sub-issue guidance:** When a task is too large or has multiple distinct parts, break it into sub-issues using action="create" with parentIssueId="${issueRef}". Each sub-issue should be an atomic, independently testable unit of work with its own acceptance criteria. This enables parallel dispatch and clearer progress tracking.`,
1250
1287
  ]
1251
1288
  : [
1252
1289
  `**Tool access:**`,
1253
- `- \`linearis\` CLI: READ ONLY. You can read issues (\`linearis issues read ${issueRef}\`), list, and search. Do NOT use linearis to update, close, comment, or modify issues.`,
1254
- `- \`code_run\`: Dispatch coding work to a worker. Workers return text — they cannot access linearis.`,
1290
+ `- \`linear_issues\` tool: READ ONLY. Use action="read" with issueId="${issueRef}" to get details, action="list_states"/"list_labels" for metadata. Do NOT use action="update", action="create", or action="comment".`,
1291
+ `- \`code_run\`: Dispatch coding work to a worker. Workers return text — they cannot access linear_issues.`,
1255
1292
  `- Standard tools: exec, read, edit, write, web_search, etc.`,
1256
1293
  ];
1257
1294
 
@@ -1278,6 +1315,7 @@ async function dispatchCommentToAgent(
1278
1315
  `IMPORTANT: Only reference real users from the issue data above. Do NOT fabricate or guess user names, emails, or identities.`,
1279
1316
  ``,
1280
1317
  `Respond concisely. For work requests, dispatch via \`code_run\` and summarize the result.`,
1318
+ commentGuidanceAppendix,
1281
1319
  ].filter(Boolean).join("\n");
1282
1320
 
1283
1321
  // Dispatch with session lifecycle
@@ -1416,6 +1454,12 @@ async function handleCloseIssue(
1416
1454
  .map((c: any) => `**${c.user?.name ?? "Unknown"}**: ${(c.body ?? "").slice(0, 300)}`)
1417
1455
  .join("\n");
1418
1456
 
1457
+ // Look up cached guidance
1458
+ const closeGuidance = teamId ? getCachedGuidanceForTeam(teamId) : null;
1459
+ const closeGuidanceAppendix = isGuidanceEnabled(pluginConfig, teamId)
1460
+ ? formatGuidanceAppendix(closeGuidance)
1461
+ : "";
1462
+
1419
1463
  const message = [
1420
1464
  `You are writing a closure report for a Linear issue that is being marked as done.`,
1421
1465
  `Your text output will be posted as the closing comment on the issue.`,
@@ -1437,6 +1481,7 @@ async function handleCloseIssue(
1437
1481
  `- **Notes**: Any follow-up items or caveats (if applicable)`,
1438
1482
  ``,
1439
1483
  `Keep it brief and factual. Use markdown formatting.`,
1484
+ closeGuidanceAppendix,
1440
1485
  ].filter(Boolean).join("\n");
1441
1486
 
1442
1487
  // Execute with session lifecycle