@calltelemetry/openclaw-linear 0.8.4 → 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.
- package/README.md +318 -2
- package/openclaw.plugin.json +3 -1
- package/package.json +1 -1
- package/prompts.yaml +24 -12
- package/src/__test__/fixtures/linear-responses.ts +2 -1
- package/src/__test__/fixtures/webhook-payloads.ts +9 -2
- package/src/__test__/webhook-scenarios.test.ts +150 -0
- package/src/api/linear-api.ts +2 -0
- package/src/infra/cli.ts +27 -0
- package/src/infra/doctor.test.ts +130 -3
- package/src/infra/doctor.ts +216 -11
- package/src/pipeline/guidance.test.ts +222 -0
- package/src/pipeline/guidance.ts +156 -0
- package/src/pipeline/pipeline.ts +23 -2
- package/src/pipeline/webhook.ts +150 -30
- package/src/tools/linear-issues-tool.test.ts +453 -0
- package/src/tools/linear-issues-tool.ts +338 -0
- package/src/tools/tools.test.ts +36 -7
- package/src/tools/tools.ts +9 -2
package/src/pipeline/webhook.ts
CHANGED
|
@@ -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
|
|
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 ??
|
|
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)
|
|
@@ -325,16 +335,37 @@ export async function handleLinearWebhook(
|
|
|
325
335
|
.join("\n\n");
|
|
326
336
|
|
|
327
337
|
const issueRef = enrichedIssue?.identifier ?? issue.identifier ?? issue.id;
|
|
338
|
+
const stateType = enrichedIssue?.state?.type ?? "";
|
|
339
|
+
const isTriaged = stateType === "started" || stateType === "completed" || stateType === "canceled";
|
|
340
|
+
|
|
341
|
+
const toolAccessLines = isTriaged
|
|
342
|
+
? [
|
|
343
|
+
`**Tool access:**`,
|
|
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.`,
|
|
346
|
+
`- \`spawn_agent\`/\`ask_agent\`: Delegate to other crew agents.`,
|
|
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.`,
|
|
350
|
+
]
|
|
351
|
+
: [
|
|
352
|
+
`**Tool access:**`,
|
|
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.`,
|
|
355
|
+
`- \`spawn_agent\`/\`ask_agent\`: Delegate to other crew agents.`,
|
|
356
|
+
`- Standard tools: exec, read, edit, write, web_search, etc.`,
|
|
357
|
+
];
|
|
358
|
+
|
|
359
|
+
const roleLines = isTriaged
|
|
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.`]
|
|
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.`];
|
|
362
|
+
|
|
328
363
|
const message = [
|
|
329
364
|
`You are an orchestrator responding in a Linear issue session. Your text output will be posted as activities visible to the user.`,
|
|
330
365
|
``,
|
|
331
|
-
|
|
332
|
-
`- \`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.`,
|
|
333
|
-
`- \`code_run\`: Dispatch coding work to a worker. Workers return text — they cannot access linearis.`,
|
|
334
|
-
`- \`spawn_agent\`/\`ask_agent\`: Delegate to other crew agents.`,
|
|
335
|
-
`- Standard tools: exec, read, edit, write, web_search, etc.`,
|
|
366
|
+
...toolAccessLines,
|
|
336
367
|
``,
|
|
337
|
-
|
|
368
|
+
...roleLines,
|
|
338
369
|
``,
|
|
339
370
|
`## Issue: ${issueRef} — ${enrichedIssue?.title ?? issue.title ?? "(untitled)"}`,
|
|
340
371
|
`**Status:** ${enrichedIssue?.state?.name ?? "Unknown"} | **Assignee:** ${enrichedIssue?.assignee?.name ?? "Unassigned"}`,
|
|
@@ -345,6 +376,7 @@ export async function handleLinearWebhook(
|
|
|
345
376
|
userMessage ? `\n**Latest message:**\n> ${userMessage}` : "",
|
|
346
377
|
``,
|
|
347
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,
|
|
348
380
|
].filter(Boolean).join("\n");
|
|
349
381
|
|
|
350
382
|
// Run agent directly (non-blocking)
|
|
@@ -367,7 +399,7 @@ export async function handleLinearWebhook(
|
|
|
367
399
|
// Emit initial thought
|
|
368
400
|
await linearApi.emitActivity(session.id, {
|
|
369
401
|
type: "thought",
|
|
370
|
-
body:
|
|
402
|
+
body: `${label} is processing request for ${enrichedIssue?.identifier ?? issue.id}...`,
|
|
371
403
|
}).catch(() => {});
|
|
372
404
|
|
|
373
405
|
// Run agent with streaming to Linear
|
|
@@ -454,13 +486,11 @@ export async function handleLinearWebhook(
|
|
|
454
486
|
return true;
|
|
455
487
|
}
|
|
456
488
|
|
|
457
|
-
// Extract user message from the activity
|
|
458
|
-
const
|
|
489
|
+
// Extract user message from the activity (not from promptContext which contains issue data + guidance)
|
|
490
|
+
const guidanceCtxPrompted = extractGuidance(payload);
|
|
459
491
|
const userMessage =
|
|
460
492
|
activity?.content?.body ??
|
|
461
493
|
activity?.body ??
|
|
462
|
-
promptContext?.message ??
|
|
463
|
-
promptContext ??
|
|
464
494
|
"";
|
|
465
495
|
|
|
466
496
|
if (!userMessage || typeof userMessage !== "string" || userMessage.trim().length === 0) {
|
|
@@ -495,6 +525,13 @@ export async function handleLinearWebhook(
|
|
|
495
525
|
|
|
496
526
|
const description = enrichedIssue?.description ?? issue?.description ?? "(no description)";
|
|
497
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
|
+
|
|
498
535
|
// Build context from recent comments
|
|
499
536
|
const recentComments = enrichedIssue?.comments?.nodes ?? [];
|
|
500
537
|
const commentContext = recentComments
|
|
@@ -503,16 +540,37 @@ export async function handleLinearWebhook(
|
|
|
503
540
|
.join("\n\n");
|
|
504
541
|
|
|
505
542
|
const followUpIssueRef = enrichedIssue?.identifier ?? issue.identifier ?? issue.id;
|
|
543
|
+
const followUpStateType = enrichedIssue?.state?.type ?? "";
|
|
544
|
+
const followUpIsTriaged = followUpStateType === "started" || followUpStateType === "completed" || followUpStateType === "canceled";
|
|
545
|
+
|
|
546
|
+
const followUpToolAccessLines = followUpIsTriaged
|
|
547
|
+
? [
|
|
548
|
+
`**Tool access:**`,
|
|
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.`,
|
|
551
|
+
`- \`spawn_agent\`/\`ask_agent\`: Delegate to other crew agents.`,
|
|
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.`,
|
|
555
|
+
]
|
|
556
|
+
: [
|
|
557
|
+
`**Tool access:**`,
|
|
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.`,
|
|
560
|
+
`- \`spawn_agent\`/\`ask_agent\`: Delegate to other crew agents.`,
|
|
561
|
+
`- Standard tools: exec, read, edit, write, web_search, etc.`,
|
|
562
|
+
];
|
|
563
|
+
|
|
564
|
+
const followUpRoleLines = followUpIsTriaged
|
|
565
|
+
? [`**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.`]
|
|
566
|
+
: [`**Your role:** Dispatcher. For work requests, use \`code_run\`. You do NOT update issue status — the audit system handles lifecycle.`];
|
|
567
|
+
|
|
506
568
|
const message = [
|
|
507
569
|
`You are an orchestrator responding in a Linear issue session. Your text output will be posted as activities visible to the user.`,
|
|
508
570
|
``,
|
|
509
|
-
|
|
510
|
-
`- \`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.`,
|
|
511
|
-
`- \`code_run\`: Dispatch coding work to a worker. Workers return text — they cannot access linearis.`,
|
|
512
|
-
`- \`spawn_agent\`/\`ask_agent\`: Delegate to other crew agents.`,
|
|
513
|
-
`- Standard tools: exec, read, edit, write, web_search, etc.`,
|
|
571
|
+
...followUpToolAccessLines,
|
|
514
572
|
``,
|
|
515
|
-
|
|
573
|
+
...followUpRoleLines,
|
|
516
574
|
``,
|
|
517
575
|
`## Issue: ${followUpIssueRef} — ${enrichedIssue?.title ?? issue.title ?? "(untitled)"}`,
|
|
518
576
|
`**Status:** ${enrichedIssue?.state?.name ?? "Unknown"} | **Assignee:** ${enrichedIssue?.assignee?.name ?? "Unassigned"}`,
|
|
@@ -523,6 +581,7 @@ export async function handleLinearWebhook(
|
|
|
523
581
|
`\n**User's follow-up message:**\n> ${userMessage}`,
|
|
524
582
|
``,
|
|
525
583
|
`Respond to the user's follow-up. For work requests, dispatch via \`code_run\`. Be concise and action-oriented.`,
|
|
584
|
+
followUpGuidanceAppendix,
|
|
526
585
|
].filter(Boolean).join("\n");
|
|
527
586
|
|
|
528
587
|
setActiveSession({
|
|
@@ -536,7 +595,7 @@ export async function handleLinearWebhook(
|
|
|
536
595
|
try {
|
|
537
596
|
await linearApi.emitActivity(session.id, {
|
|
538
597
|
type: "thought",
|
|
539
|
-
body:
|
|
598
|
+
body: `${label} is processing follow-up for ${enrichedIssue?.identifier ?? issue.id}...`,
|
|
540
599
|
}).catch(() => {});
|
|
541
600
|
|
|
542
601
|
const sessionId = `linear-session-${session.id}`;
|
|
@@ -597,7 +656,6 @@ export async function handleLinearWebhook(
|
|
|
597
656
|
const commentBody = comment?.body ?? "";
|
|
598
657
|
const commentor = comment?.user?.name ?? "Unknown";
|
|
599
658
|
const issue = comment?.issue ?? payload.issue;
|
|
600
|
-
const pluginConfig = (api as any).pluginConfig as Record<string, unknown> | undefined;
|
|
601
659
|
|
|
602
660
|
if (!issue?.id) {
|
|
603
661
|
api.logger.error("Comment webhook: missing issue data");
|
|
@@ -1006,11 +1064,25 @@ export async function handleLinearWebhook(
|
|
|
1006
1064
|
}).catch(() => {});
|
|
1007
1065
|
}
|
|
1008
1066
|
|
|
1067
|
+
const creatorName = enrichedIssue?.creator?.name ?? "Unknown";
|
|
1068
|
+
const creatorEmail = enrichedIssue?.creator?.email ?? null;
|
|
1069
|
+
const creatorLine = creatorEmail
|
|
1070
|
+
? `**Created by:** ${creatorName} (${creatorEmail})`
|
|
1071
|
+
: `**Created by:** ${creatorName}`;
|
|
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
|
+
|
|
1009
1080
|
const message = [
|
|
1010
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.`,
|
|
1011
1082
|
``,
|
|
1012
1083
|
`## Issue: ${enrichedIssue?.identifier ?? issue.identifier ?? issue.id} — ${enrichedIssue?.title ?? issue.title ?? "(untitled)"}`,
|
|
1013
1084
|
`**Status:** ${enrichedIssue?.state?.name ?? "Unknown"} | **Current Estimate:** ${enrichedIssue?.estimate ?? "None"} | **Current Labels:** ${currentLabelNames}`,
|
|
1085
|
+
creatorLine,
|
|
1014
1086
|
``,
|
|
1015
1087
|
`**Description:**`,
|
|
1016
1088
|
description,
|
|
@@ -1038,7 +1110,10 @@ export async function handleLinearWebhook(
|
|
|
1038
1110
|
`}`,
|
|
1039
1111
|
'```',
|
|
1040
1112
|
``,
|
|
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.`,
|
|
1114
|
+
``,
|
|
1041
1115
|
`Then write your full assessment as markdown below the JSON block.`,
|
|
1116
|
+
triageGuidanceAppendix,
|
|
1042
1117
|
].filter(Boolean).join("\n");
|
|
1043
1118
|
|
|
1044
1119
|
const sessionId = `linear-triage-${issue.id}-${Date.now()}`;
|
|
@@ -1190,26 +1265,57 @@ async function dispatchCommentToAgent(
|
|
|
1190
1265
|
.map((c: any) => `**${c.user?.name ?? "Unknown"}**: ${(c.body ?? "").slice(0, 200)}`)
|
|
1191
1266
|
.join("\n");
|
|
1192
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
|
+
|
|
1193
1275
|
const issueRef = enrichedIssue?.identifier ?? issue.identifier ?? issue.id;
|
|
1276
|
+
const stateType = enrichedIssue?.state?.type ?? "";
|
|
1277
|
+
const isTriaged = stateType === "started" || stateType === "completed" || stateType === "canceled";
|
|
1278
|
+
|
|
1279
|
+
const toolAccessLines = isTriaged
|
|
1280
|
+
? [
|
|
1281
|
+
`**Tool access:**`,
|
|
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.`,
|
|
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.`,
|
|
1287
|
+
]
|
|
1288
|
+
: [
|
|
1289
|
+
`**Tool access:**`,
|
|
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.`,
|
|
1292
|
+
`- Standard tools: exec, read, edit, write, web_search, etc.`,
|
|
1293
|
+
];
|
|
1294
|
+
|
|
1295
|
+
const roleLines = isTriaged
|
|
1296
|
+
? [`**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.`]
|
|
1297
|
+
: [`**Your role:** Dispatcher. For work requests, use \`code_run\`. You do NOT update issue status — the audit system handles lifecycle.`];
|
|
1298
|
+
|
|
1194
1299
|
const message = [
|
|
1195
1300
|
`You are an orchestrator responding to a Linear comment. Your text output will be automatically posted as a comment on the issue (do NOT post a comment yourself — the handler does it).`,
|
|
1196
1301
|
``,
|
|
1197
|
-
|
|
1198
|
-
`- \`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.`,
|
|
1199
|
-
`- \`code_run\`: Dispatch coding work to a worker. Workers return text — they cannot access linearis.`,
|
|
1200
|
-
`- Standard tools: exec, read, edit, write, web_search, etc.`,
|
|
1302
|
+
...toolAccessLines,
|
|
1201
1303
|
``,
|
|
1202
|
-
|
|
1304
|
+
...roleLines,
|
|
1203
1305
|
``,
|
|
1204
1306
|
`## Issue: ${issueRef} — ${enrichedIssue?.title ?? issue.title ?? "(untitled)"}`,
|
|
1205
1307
|
`**Status:** ${enrichedIssue?.state?.name ?? "Unknown"} | **Assignee:** ${enrichedIssue?.assignee?.name ?? "Unassigned"}`,
|
|
1308
|
+
enrichedIssue?.creator ? `**Created by:** ${enrichedIssue.creator.name}${enrichedIssue.creator.email ? ` (${enrichedIssue.creator.email})` : ""}` : "",
|
|
1206
1309
|
``,
|
|
1207
1310
|
`**Description:**`,
|
|
1208
1311
|
description,
|
|
1209
1312
|
commentSummary ? `\n**Recent comments:**\n${commentSummary}` : "",
|
|
1210
1313
|
`\n**${commentor} says:**\n> ${commentBody}`,
|
|
1211
1314
|
``,
|
|
1315
|
+
`IMPORTANT: Only reference real users from the issue data above. Do NOT fabricate or guess user names, emails, or identities.`,
|
|
1316
|
+
``,
|
|
1212
1317
|
`Respond concisely. For work requests, dispatch via \`code_run\` and summarize the result.`,
|
|
1318
|
+
commentGuidanceAppendix,
|
|
1213
1319
|
].filter(Boolean).join("\n");
|
|
1214
1320
|
|
|
1215
1321
|
// Dispatch with session lifecycle
|
|
@@ -1348,24 +1454,34 @@ async function handleCloseIssue(
|
|
|
1348
1454
|
.map((c: any) => `**${c.user?.name ?? "Unknown"}**: ${(c.body ?? "").slice(0, 300)}`)
|
|
1349
1455
|
.join("\n");
|
|
1350
1456
|
|
|
1457
|
+
// Look up cached guidance
|
|
1458
|
+
const closeGuidance = teamId ? getCachedGuidanceForTeam(teamId) : null;
|
|
1459
|
+
const closeGuidanceAppendix = isGuidanceEnabled(pluginConfig, teamId)
|
|
1460
|
+
? formatGuidanceAppendix(closeGuidance)
|
|
1461
|
+
: "";
|
|
1462
|
+
|
|
1351
1463
|
const message = [
|
|
1352
1464
|
`You are writing a closure report for a Linear issue that is being marked as done.`,
|
|
1353
1465
|
`Your text output will be posted as the closing comment on the issue.`,
|
|
1354
1466
|
``,
|
|
1355
1467
|
`## Issue: ${issueRef} — ${enrichedIssue?.title ?? issue.title ?? "(untitled)"}`,
|
|
1356
1468
|
`**Status:** ${enrichedIssue?.state?.name ?? "Unknown"} | **Assignee:** ${enrichedIssue?.assignee?.name ?? "Unassigned"}`,
|
|
1469
|
+
enrichedIssue?.creator ? `**Created by:** ${enrichedIssue.creator.name}${enrichedIssue.creator.email ? ` (${enrichedIssue.creator.email})` : ""}` : "",
|
|
1357
1470
|
``,
|
|
1358
1471
|
`**Description:**`,
|
|
1359
1472
|
description,
|
|
1360
1473
|
commentSummary ? `\n**Comment history:**\n${commentSummary}` : "",
|
|
1361
1474
|
`\n**${commentor} says (closure request):**\n> ${commentBody}`,
|
|
1362
1475
|
``,
|
|
1476
|
+
`IMPORTANT: Only reference real users from the issue data above. Do NOT fabricate or guess user names, emails, or identities.`,
|
|
1477
|
+
``,
|
|
1363
1478
|
`Write a concise closure report with:`,
|
|
1364
1479
|
`- **Summary**: What was done (1-2 sentences)`,
|
|
1365
1480
|
`- **Resolution**: How it was resolved`,
|
|
1366
1481
|
`- **Notes**: Any follow-up items or caveats (if applicable)`,
|
|
1367
1482
|
``,
|
|
1368
1483
|
`Keep it brief and factual. Use markdown formatting.`,
|
|
1484
|
+
closeGuidanceAppendix,
|
|
1369
1485
|
].filter(Boolean).join("\n");
|
|
1370
1486
|
|
|
1371
1487
|
// Execute with session lifecycle
|
|
@@ -1404,9 +1520,13 @@ async function handleCloseIssue(
|
|
|
1404
1520
|
readOnly: true,
|
|
1405
1521
|
});
|
|
1406
1522
|
|
|
1523
|
+
if (!result.success) {
|
|
1524
|
+
api.logger.error(`Closure report agent failed for ${issueRef}: ${(result.output ?? "no output").slice(0, 500)}`);
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1407
1527
|
const closureReport = result.success
|
|
1408
1528
|
? result.output
|
|
1409
|
-
:
|
|
1529
|
+
: `Issue closed by ${commentor}.\n\n> ${commentBody}\n\n*Closure report generation failed — agent returned: ${(result.output ?? "no output").slice(0, 200)}*`;
|
|
1410
1530
|
|
|
1411
1531
|
const fullReport = `## Closure Report\n\n${closureReport}`;
|
|
1412
1532
|
|