@calltelemetry/openclaw-linear 0.9.12 → 0.9.15
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/index.ts +50 -0
- package/package.json +1 -1
- package/src/__test__/fixtures/recorded-sub-issue-flow.ts +38 -32
- package/src/agent/agent.ts +18 -4
- package/src/infra/doctor.test.ts +77 -578
- package/src/pipeline/webhook.test.ts +7 -7
- package/src/pipeline/webhook.ts +158 -8
- package/src/tools/code-tool.ts +12 -0
|
@@ -1261,7 +1261,7 @@ describe("Comment.create intent routing", () => {
|
|
|
1261
1261
|
identifier: "ENG-RW",
|
|
1262
1262
|
title: "Request Work",
|
|
1263
1263
|
description: "desc",
|
|
1264
|
-
state: { name: "
|
|
1264
|
+
state: { name: "In Progress", type: "started" },
|
|
1265
1265
|
team: { id: "team-rw" },
|
|
1266
1266
|
comments: { nodes: [] },
|
|
1267
1267
|
});
|
|
@@ -2219,7 +2219,7 @@ describe("dispatchCommentToAgent via Comment.create intents", () => {
|
|
|
2219
2219
|
identifier: "ENG-DE",
|
|
2220
2220
|
title: "Dispatch Error",
|
|
2221
2221
|
description: "desc",
|
|
2222
|
-
state: { name: "
|
|
2222
|
+
state: { name: "In Progress", type: "started" },
|
|
2223
2223
|
team: { id: "team-de" },
|
|
2224
2224
|
comments: { nodes: [] },
|
|
2225
2225
|
});
|
|
@@ -2709,7 +2709,7 @@ describe("postAgentComment edge cases", () => {
|
|
|
2709
2709
|
identifier: "ENG-IF",
|
|
2710
2710
|
title: "Identity Fail",
|
|
2711
2711
|
description: "desc",
|
|
2712
|
-
state: { name: "
|
|
2712
|
+
state: { name: "In Progress", type: "started" },
|
|
2713
2713
|
team: { id: "team-if" },
|
|
2714
2714
|
comments: { nodes: [] },
|
|
2715
2715
|
});
|
|
@@ -3405,7 +3405,7 @@ describe("Comment.create .catch callbacks on fire-and-forget dispatches", () =>
|
|
|
3405
3405
|
identifier: "ENG-RWC",
|
|
3406
3406
|
title: "Request Work Catch",
|
|
3407
3407
|
description: "desc",
|
|
3408
|
-
state: { name: "
|
|
3408
|
+
state: { name: "In Progress", type: "started" },
|
|
3409
3409
|
team: { id: "team-rwc" },
|
|
3410
3410
|
comments: { nodes: [] },
|
|
3411
3411
|
});
|
|
@@ -3599,7 +3599,7 @@ describe("dispatchCommentToAgent internal .catch callbacks", () => {
|
|
|
3599
3599
|
identifier: "ENG-DCE",
|
|
3600
3600
|
title: "DCA Error",
|
|
3601
3601
|
description: "desc",
|
|
3602
|
-
state: { name: "
|
|
3602
|
+
state: { name: "In Progress", type: "started" },
|
|
3603
3603
|
team: { id: "team-dce" },
|
|
3604
3604
|
comments: { nodes: [] },
|
|
3605
3605
|
});
|
|
@@ -4865,7 +4865,7 @@ describe("session affinity routing", () => {
|
|
|
4865
4865
|
identifier: "ENG-AFF-RW",
|
|
4866
4866
|
title: "Affinity Request Work",
|
|
4867
4867
|
description: "desc",
|
|
4868
|
-
state: { name: "
|
|
4868
|
+
state: { name: "In Progress", type: "started" },
|
|
4869
4869
|
team: { id: "team-aff" },
|
|
4870
4870
|
comments: { nodes: [] },
|
|
4871
4871
|
});
|
|
@@ -4899,7 +4899,7 @@ describe("session affinity routing", () => {
|
|
|
4899
4899
|
identifier: "ENG-NO-AFF",
|
|
4900
4900
|
title: "No Affinity",
|
|
4901
4901
|
description: "desc",
|
|
4902
|
-
state: { name: "
|
|
4902
|
+
state: { name: "In Progress", type: "started" },
|
|
4903
4903
|
team: { id: "team-noaff" },
|
|
4904
4904
|
comments: { nodes: [] },
|
|
4905
4905
|
});
|
package/src/pipeline/webhook.ts
CHANGED
|
@@ -15,7 +15,7 @@ import { readPlanningState, isInPlanningMode, getPlanningSession, endPlanningSes
|
|
|
15
15
|
import { initiatePlanningSession, handlePlannerTurn, runPlanAudit } from "./planner.js";
|
|
16
16
|
import { startProjectDispatch } from "./dag-dispatch.js";
|
|
17
17
|
import { emitDiagnostic } from "../infra/observability.js";
|
|
18
|
-
import { classifyIntent } from "./intent-classify.js";
|
|
18
|
+
import { classifyIntent, type Intent } from "./intent-classify.js";
|
|
19
19
|
import { extractGuidance, formatGuidanceAppendix, cacheGuidanceForTeam, getCachedGuidanceForTeam, isGuidanceEnabled, _resetGuidanceCacheForTesting } from "./guidance.js";
|
|
20
20
|
import { loadAgentProfiles, buildMentionPattern, resolveAgentFromAlias, validateProfiles, _resetProfilesCacheForTesting, type AgentProfile } from "../infra/shared-profiles.js";
|
|
21
21
|
|
|
@@ -35,6 +35,28 @@ export function sanitizePromptInput(text: string, maxLength = 4000): string {
|
|
|
35
35
|
return sanitized;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
/**
|
|
39
|
+
* Check if a work request should be blocked based on issue state.
|
|
40
|
+
* Returns a rejection message if blocked, null if allowed.
|
|
41
|
+
*/
|
|
42
|
+
function shouldBlockWorkRequest(
|
|
43
|
+
intent: Intent,
|
|
44
|
+
stateType: string,
|
|
45
|
+
stateName: string,
|
|
46
|
+
issueRef: string,
|
|
47
|
+
): string | null {
|
|
48
|
+
if (intent !== "request_work") return null;
|
|
49
|
+
if (stateType === "started") return null; // In Progress — allow
|
|
50
|
+
return (
|
|
51
|
+
`This issue (${issueRef}) is in **${stateName}** — it needs planning and scoping before implementation.\n\n` +
|
|
52
|
+
`**To move forward:**\n` +
|
|
53
|
+
`1. Update the issue description with requirements and acceptance criteria\n` +
|
|
54
|
+
`2. Move the issue to **In Progress**\n` +
|
|
55
|
+
`3. Then ask me to implement it\n\n` +
|
|
56
|
+
`I can help you scope and plan — just ask questions or discuss the approach.`
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
38
60
|
// Track issues with active agent runs to prevent concurrent duplicate runs.
|
|
39
61
|
const activeRuns = new Set<string>();
|
|
40
62
|
|
|
@@ -448,7 +470,7 @@ export async function handleLinearWebhook(
|
|
|
448
470
|
: [
|
|
449
471
|
`**Tool access:**`,
|
|
450
472
|
`- \`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".`,
|
|
451
|
-
`- \`code_run\`:
|
|
473
|
+
`- \`code_run\`: **Planning mode only.** Workers may explore code and write plan files (PLAN.md, design docs). Workers MUST NOT create, modify, or delete source code, run deployments, or make system changes. Use for codebase exploration and planning only.`,
|
|
452
474
|
`- \`spawn_agent\`/\`ask_agent\`: Delegate to other crew agents.`,
|
|
453
475
|
`- Standard tools: exec, read, edit, write, web_search, etc.`,
|
|
454
476
|
];
|
|
@@ -461,6 +483,35 @@ export async function handleLinearWebhook(
|
|
|
461
483
|
api.logger.info(`Guidance injected (${guidanceCtx.source}): ${guidanceCtx.guidance?.slice(0, 120)}...`);
|
|
462
484
|
}
|
|
463
485
|
|
|
486
|
+
// ── Intent gate: classify user request and block work requests on untriaged issues ──
|
|
487
|
+
const classifyText = userMessage || promptContext || enrichedIssue?.title || "";
|
|
488
|
+
const projectId = enrichedIssue?.project?.id;
|
|
489
|
+
let isPlanning = false;
|
|
490
|
+
if (projectId) {
|
|
491
|
+
try {
|
|
492
|
+
const planState = await readPlanningState(pluginConfig?.planningStatePath as string | undefined);
|
|
493
|
+
isPlanning = isInPlanningMode(planState, projectId);
|
|
494
|
+
} catch { /* proceed without planning context */ }
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
const intentResult = await classifyIntent(api, {
|
|
498
|
+
commentBody: classifyText,
|
|
499
|
+
issueTitle: enrichedIssue?.title ?? "(untitled)",
|
|
500
|
+
issueStatus: enrichedIssue?.state?.name,
|
|
501
|
+
isPlanning,
|
|
502
|
+
agentNames: Object.keys(profiles),
|
|
503
|
+
hasProject: !!projectId,
|
|
504
|
+
}, pluginConfig as Record<string, unknown> | undefined);
|
|
505
|
+
|
|
506
|
+
api.logger.info(`AgentSession.created intent: ${intentResult.intent}${intentResult.agentId ? ` (agent: ${intentResult.agentId})` : ""} — ${intentResult.reasoning}`);
|
|
507
|
+
|
|
508
|
+
const blockMsg = shouldBlockWorkRequest(intentResult.intent, stateType, enrichedIssue?.state?.name ?? "Unknown", issueRef);
|
|
509
|
+
if (blockMsg) {
|
|
510
|
+
api.logger.info(`AgentSession.created: blocking work request on untriaged issue ${issueRef}`);
|
|
511
|
+
await linearApi.emitActivity(session.id, { type: "response", body: blockMsg }).catch(() => {});
|
|
512
|
+
return true;
|
|
513
|
+
}
|
|
514
|
+
|
|
464
515
|
const message = [
|
|
465
516
|
`You are an orchestrator responding in a Linear issue session. Your text output will be posted as activities visible to the user.`,
|
|
466
517
|
``,
|
|
@@ -477,7 +528,14 @@ export async function handleLinearWebhook(
|
|
|
477
528
|
commentContext ? `\n**Conversation:**\n${commentContext}` : "",
|
|
478
529
|
userMessage ? `\n**Latest message:**\n> ${userMessage}` : "",
|
|
479
530
|
``,
|
|
480
|
-
|
|
531
|
+
`## Scope Rules`,
|
|
532
|
+
`1. **Read the issue first.** The issue title + description define your scope. Everything you do must serve the issue as written.`,
|
|
533
|
+
`2. **\`code_run\` is ONLY for issue-body work.** Only dispatch \`code_run\` when the issue description contains implementation requirements. A greeting, question, or conversational issue gets a conversational response — NOT code_run.`,
|
|
534
|
+
`3. **Comments explore, issue body builds.** User comments may explore scope or ask questions but NEVER trigger \`code_run\` alone. If a comment requests new implementation, update the issue description first, then build from the issue text.`,
|
|
535
|
+
`4. **Plan before building.** For non-trivial work, respond with a plan first. Only dispatch \`code_run\` after the plan is clear and grounded in the issue body.`,
|
|
536
|
+
`5. **Match response to request.** Greeting → greet. Question → answer. No implementation requirements → no code_run.`,
|
|
537
|
+
``,
|
|
538
|
+
`Respond within the scope defined above. Be concise and action-oriented.`,
|
|
481
539
|
].filter(Boolean).join("\n");
|
|
482
540
|
|
|
483
541
|
// Run agent directly (non-blocking)
|
|
@@ -691,7 +749,7 @@ export async function handleLinearWebhook(
|
|
|
691
749
|
: [
|
|
692
750
|
`**Tool access:**`,
|
|
693
751
|
`- \`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".`,
|
|
694
|
-
`- \`code_run\`:
|
|
752
|
+
`- \`code_run\`: **Planning mode only.** Workers may explore code and write plan files (PLAN.md, design docs). Workers MUST NOT create, modify, or delete source code, run deployments, or make system changes. Use for codebase exploration and planning only.`,
|
|
695
753
|
`- \`spawn_agent\`/\`ask_agent\`: Delegate to other crew agents.`,
|
|
696
754
|
`- Standard tools: exec, read, edit, write, web_search, etc.`,
|
|
697
755
|
];
|
|
@@ -704,6 +762,37 @@ export async function handleLinearWebhook(
|
|
|
704
762
|
api.logger.info(`Follow-up guidance injected: ${(guidanceCtxPrompted.guidance ?? "cached").slice(0, 120)}...`);
|
|
705
763
|
}
|
|
706
764
|
|
|
765
|
+
// ── Intent gate: classify follow-up and block work requests on untriaged issues ──
|
|
766
|
+
const followUpProjectId = enrichedIssue?.project?.id;
|
|
767
|
+
let followUpIsPlanning = false;
|
|
768
|
+
if (followUpProjectId) {
|
|
769
|
+
try {
|
|
770
|
+
const planState = await readPlanningState(pluginConfig?.planningStatePath as string | undefined);
|
|
771
|
+
followUpIsPlanning = isInPlanningMode(planState, followUpProjectId);
|
|
772
|
+
} catch { /* proceed without planning context */ }
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
const followUpIntentResult = await classifyIntent(api, {
|
|
776
|
+
commentBody: userMessage,
|
|
777
|
+
issueTitle: enrichedIssue?.title ?? "(untitled)",
|
|
778
|
+
issueStatus: enrichedIssue?.state?.name,
|
|
779
|
+
isPlanning: followUpIsPlanning,
|
|
780
|
+
agentNames: Object.keys(profiles),
|
|
781
|
+
hasProject: !!followUpProjectId,
|
|
782
|
+
}, pluginConfig as Record<string, unknown> | undefined);
|
|
783
|
+
|
|
784
|
+
api.logger.info(`AgentSession.prompted intent: ${followUpIntentResult.intent}${followUpIntentResult.agentId ? ` (agent: ${followUpIntentResult.agentId})` : ""} — ${followUpIntentResult.reasoning}`);
|
|
785
|
+
|
|
786
|
+
const followUpBlockMsg = shouldBlockWorkRequest(
|
|
787
|
+
followUpIntentResult.intent, followUpStateType, enrichedIssue?.state?.name ?? "Unknown", followUpIssueRef,
|
|
788
|
+
);
|
|
789
|
+
if (followUpBlockMsg) {
|
|
790
|
+
api.logger.info(`AgentSession.prompted: blocking work request on untriaged issue ${followUpIssueRef}`);
|
|
791
|
+
await linearApi.emitActivity(session.id, { type: "response", body: followUpBlockMsg }).catch(() => {});
|
|
792
|
+
activeRuns.delete(issue.id);
|
|
793
|
+
return;
|
|
794
|
+
}
|
|
795
|
+
|
|
707
796
|
const message = [
|
|
708
797
|
`You are an orchestrator responding in a Linear issue session. Your text output will be posted as activities visible to the user.`,
|
|
709
798
|
``,
|
|
@@ -720,7 +809,12 @@ export async function handleLinearWebhook(
|
|
|
720
809
|
commentContext ? `\n**Recent conversation:**\n${commentContext}` : "",
|
|
721
810
|
`\n**User's follow-up message:**\n> ${userMessage}`,
|
|
722
811
|
``,
|
|
723
|
-
|
|
812
|
+
`## Scope Rules`,
|
|
813
|
+
`1. **The issue body is your scope.** Re-read the description above before acting.`,
|
|
814
|
+
`2. **Comments explore, issue body builds.** The follow-up may refine understanding or ask questions — NEVER dispatch \`code_run\` from a comment alone. If the user requests implementation, suggest updating the issue description first.`,
|
|
815
|
+
`3. **Match response to request.** Answer questions with answers. Do NOT escalate conversational messages into builds.`,
|
|
816
|
+
``,
|
|
817
|
+
`Respond to the follow-up within the scope defined above. Be concise and action-oriented.`,
|
|
724
818
|
].filter(Boolean).join("\n");
|
|
725
819
|
|
|
726
820
|
setActiveSession({
|
|
@@ -844,7 +938,7 @@ export async function handleLinearWebhook(
|
|
|
844
938
|
const profiles = loadAgentProfiles();
|
|
845
939
|
const agentNames = Object.keys(profiles);
|
|
846
940
|
|
|
847
|
-
// ── @mention fast path —
|
|
941
|
+
// ── @mention fast path — with intent gate ────────────────────
|
|
848
942
|
const mentionPattern = buildMentionPattern(profiles);
|
|
849
943
|
const mentionMatches = mentionPattern ? commentBody.match(mentionPattern) : null;
|
|
850
944
|
if (mentionMatches && mentionMatches.length > 0) {
|
|
@@ -852,6 +946,42 @@ export async function handleLinearWebhook(
|
|
|
852
946
|
const resolved = resolveAgentFromAlias(alias, profiles);
|
|
853
947
|
if (resolved) {
|
|
854
948
|
api.logger.info(`Comment @mention fast path: @${resolved.agentId} on ${issue.identifier ?? issue.id}`);
|
|
949
|
+
|
|
950
|
+
// Classify intent even on @mention path to gate work requests
|
|
951
|
+
let enrichedForGate: any = issue;
|
|
952
|
+
try { enrichedForGate = await linearApi.getIssueDetails(issue.id); } catch {}
|
|
953
|
+
const mentionStateType = enrichedForGate?.state?.type ?? "";
|
|
954
|
+
const mentionProjectId = enrichedForGate?.project?.id;
|
|
955
|
+
let mentionIsPlanning = false;
|
|
956
|
+
if (mentionProjectId) {
|
|
957
|
+
try {
|
|
958
|
+
const planState = await readPlanningState(pluginConfig?.planningStatePath as string | undefined);
|
|
959
|
+
mentionIsPlanning = isInPlanningMode(planState, mentionProjectId);
|
|
960
|
+
} catch {}
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
const mentionIntentResult = await classifyIntent(api, {
|
|
964
|
+
commentBody,
|
|
965
|
+
issueTitle: enrichedForGate?.title ?? "(untitled)",
|
|
966
|
+
issueStatus: enrichedForGate?.state?.name,
|
|
967
|
+
isPlanning: mentionIsPlanning,
|
|
968
|
+
agentNames,
|
|
969
|
+
hasProject: !!mentionProjectId,
|
|
970
|
+
}, pluginConfig);
|
|
971
|
+
|
|
972
|
+
api.logger.info(`Comment @mention intent: ${mentionIntentResult.intent} — ${mentionIntentResult.reasoning}`);
|
|
973
|
+
|
|
974
|
+
const mentionBlockMsg = shouldBlockWorkRequest(
|
|
975
|
+
mentionIntentResult.intent, mentionStateType,
|
|
976
|
+
enrichedForGate?.state?.name ?? "Unknown",
|
|
977
|
+
enrichedForGate?.identifier ?? issue.identifier ?? issue.id,
|
|
978
|
+
);
|
|
979
|
+
if (mentionBlockMsg) {
|
|
980
|
+
api.logger.info(`Comment @mention: blocking work request on untriaged issue ${enrichedForGate?.identifier ?? issue.identifier ?? issue.id}`);
|
|
981
|
+
try { await createCommentWithDedup(linearApi, issue.id, mentionBlockMsg); } catch {}
|
|
982
|
+
return true;
|
|
983
|
+
}
|
|
984
|
+
|
|
855
985
|
void dispatchCommentToAgent(api, linearApi, profiles, resolved.agentId, issue, comment, commentBody, commentor, pluginConfig)
|
|
856
986
|
.catch((err) => api.logger.error(`Comment dispatch error: ${err}`));
|
|
857
987
|
return true;
|
|
@@ -894,6 +1024,19 @@ export async function handleLinearWebhook(
|
|
|
894
1024
|
|
|
895
1025
|
api.logger.info(`Comment intent: ${intentResult.intent}${intentResult.agentId ? ` (agent: ${intentResult.agentId})` : ""} — ${intentResult.reasoning} (fallback: ${intentResult.fromFallback})`);
|
|
896
1026
|
|
|
1027
|
+
// ── Gate work requests on untriaged issues ────────────────────
|
|
1028
|
+
const commentStateType = enrichedIssue?.state?.type ?? "";
|
|
1029
|
+
const commentBlockMsg = shouldBlockWorkRequest(
|
|
1030
|
+
intentResult.intent, commentStateType,
|
|
1031
|
+
enrichedIssue?.state?.name ?? "Unknown",
|
|
1032
|
+
enrichedIssue?.identifier ?? issue.identifier ?? issue.id,
|
|
1033
|
+
);
|
|
1034
|
+
if (commentBlockMsg) {
|
|
1035
|
+
api.logger.info(`Comment: blocking work request on untriaged issue ${enrichedIssue?.identifier ?? issue.identifier ?? issue.id}`);
|
|
1036
|
+
try { await createCommentWithDedup(linearApi, issue.id, commentBlockMsg); } catch {}
|
|
1037
|
+
return true;
|
|
1038
|
+
}
|
|
1039
|
+
|
|
897
1040
|
// ── Route by intent ────────────────────────────────────────────
|
|
898
1041
|
|
|
899
1042
|
switch (intentResult.intent) {
|
|
@@ -1449,7 +1592,7 @@ async function dispatchCommentToAgent(
|
|
|
1449
1592
|
: [
|
|
1450
1593
|
`**Tool access:**`,
|
|
1451
1594
|
`- \`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".`,
|
|
1452
|
-
`- \`code_run\`:
|
|
1595
|
+
`- \`code_run\`: **Planning mode only.** Workers may explore code and write plan files (PLAN.md, design docs). Workers MUST NOT create, modify, or delete source code, run deployments, or make system changes. Use for codebase exploration and planning only.`,
|
|
1453
1596
|
`- Standard tools: exec, read, edit, write, web_search, etc.`,
|
|
1454
1597
|
];
|
|
1455
1598
|
|
|
@@ -1475,7 +1618,14 @@ async function dispatchCommentToAgent(
|
|
|
1475
1618
|
``,
|
|
1476
1619
|
`IMPORTANT: Only reference real users from the issue data above. Do NOT fabricate or guess user names, emails, or identities.`,
|
|
1477
1620
|
``,
|
|
1478
|
-
|
|
1621
|
+
`## Scope Rules`,
|
|
1622
|
+
`1. **Read the issue first.** The issue title + description define your scope. Everything you do must serve the issue as written.`,
|
|
1623
|
+
`2. **\`code_run\` is ONLY for issue-body work.** Only dispatch \`code_run\` when the issue description contains implementation requirements. A greeting, question, or conversational issue gets a conversational response — NOT code_run.`,
|
|
1624
|
+
`3. **Comments explore, issue body builds.** The comment above may explore scope or ask questions but NEVER trigger \`code_run\` from a comment alone. If the comment requests new implementation, suggest updating the issue description or creating a new issue.`,
|
|
1625
|
+
`4. **Plan before building.** For non-trivial work, respond with a plan first. Only dispatch \`code_run\` after the plan is clear and grounded in the issue body.`,
|
|
1626
|
+
`5. **Match response to request.** Greeting → greet. Question → answer. No implementation requirements in the issue body → no code_run.`,
|
|
1627
|
+
``,
|
|
1628
|
+
`Respond within the scope defined above. Be concise and action-oriented.`,
|
|
1479
1629
|
commentGuidanceAppendix,
|
|
1480
1630
|
].filter(Boolean).join("\n");
|
|
1481
1631
|
|
package/src/tools/code-tool.ts
CHANGED
|
@@ -178,6 +178,11 @@ export function createCodeTool(
|
|
|
178
178
|
required: ["prompt"],
|
|
179
179
|
},
|
|
180
180
|
execute: async (toolCallId: string, params: CliToolParams & { backend?: string }, ...rest: unknown[]) => {
|
|
181
|
+
// Extract onUpdate callback for progress reporting to Linear
|
|
182
|
+
const onUpdate = typeof rest[1] === "function"
|
|
183
|
+
? rest[1] as (update: Record<string, unknown>) => void
|
|
184
|
+
: undefined;
|
|
185
|
+
|
|
181
186
|
// Resolve backend: explicit alias → per-agent config → global default
|
|
182
187
|
const currentSession = getCurrentSession();
|
|
183
188
|
const agentId = currentSession?.agentId;
|
|
@@ -189,6 +194,13 @@ export function createCodeTool(
|
|
|
189
194
|
|
|
190
195
|
api.logger.info(`code_run: backend=${backend} agent=${agentId ?? "unknown"}`);
|
|
191
196
|
|
|
197
|
+
// Emit prompt summary so Linear users see what's being built
|
|
198
|
+
const promptSummary = (params.prompt ?? "").slice(0, 200);
|
|
199
|
+
api.logger.info(`code_run prompt: [${backend}] ${promptSummary}`);
|
|
200
|
+
if (onUpdate) {
|
|
201
|
+
try { onUpdate({ status: "running", summary: `[${backend}] ${promptSummary}` }); } catch {}
|
|
202
|
+
}
|
|
203
|
+
|
|
192
204
|
const result = await runner(api, params, pluginConfig);
|
|
193
205
|
|
|
194
206
|
return jsonResult({
|