patchrelay 0.35.16 → 0.36.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/README.md +11 -2
- package/dist/agent-session-plan.js +14 -2
- package/dist/build-info.json +3 -3
- package/dist/cli/args.js +1 -0
- package/dist/cli/cluster-health.js +739 -0
- package/dist/cli/commands/cluster.js +14 -0
- package/dist/cli/data.js +9 -5
- package/dist/cli/help.js +21 -0
- package/dist/cli/index.js +27 -2
- package/dist/cli/output.js +38 -0
- package/dist/cli/watch/StateHistoryView.js +1 -0
- package/dist/cli/watch/TimelineRow.js +1 -0
- package/dist/cli/watch/detail-rows.js +1 -0
- package/dist/cli/watch/history-builder.js +1 -0
- package/dist/db/migrations.js +9 -0
- package/dist/db.js +32 -8
- package/dist/github-webhook-handler.js +5 -78
- package/dist/idle-reconciliation.js +88 -6
- package/dist/issue-query-service.js +2 -0
- package/dist/issue-session-events.js +2 -2
- package/dist/issue-session.js +2 -0
- package/dist/linear-session-reporting.js +2 -0
- package/dist/linear-session-sync.js +2 -0
- package/dist/run-orchestrator.js +196 -31
- package/dist/service.js +13 -5
- package/dist/waiting-reason.js +8 -2
- package/dist/webhook-handler.js +71 -13
- package/package.json +1 -1
package/dist/webhook-handler.js
CHANGED
|
@@ -585,38 +585,55 @@ export class WebhookHandler {
|
|
|
585
585
|
}
|
|
586
586
|
if (!triggerEventAllowed(project, normalized.triggerEvent))
|
|
587
587
|
return;
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
588
|
+
const issue = this.db.getIssue(project.id, normalized.issue.id);
|
|
589
|
+
if (!issue)
|
|
590
|
+
return;
|
|
591
|
+
const trimmedBody = normalized.comment.body.trim();
|
|
592
|
+
// Ignore PatchRelay-managed comments to prevent status-sync feedback loops.
|
|
593
|
+
// Linear commentUpdated/commentCreated events can arrive after PatchRelay
|
|
594
|
+
// refreshes its visible status comment, and those updates should never
|
|
595
|
+
// consume review-fix budget or wake a new run.
|
|
591
596
|
const installation = this.db.linearInstallations.getLinearInstallationForProject(project.id);
|
|
592
|
-
|
|
597
|
+
const selfAuthored = this.isPatchRelayManagedCommentAuthor(installation, normalized.actor, normalized.comment.userName);
|
|
598
|
+
const inertPatchRelayComment = this.isInertPatchRelayComment(issue, normalized.comment.id, trimmedBody, normalized.actor?.type);
|
|
599
|
+
if (selfAuthored || inertPatchRelayComment) {
|
|
593
600
|
this.db.appendIssueSessionEventRespectingActiveLease(project.id, normalized.issue.id, {
|
|
594
601
|
projectId: project.id,
|
|
595
602
|
linearIssueId: normalized.issue.id,
|
|
596
603
|
eventType: "self_comment",
|
|
597
604
|
eventJson: JSON.stringify({
|
|
598
|
-
body:
|
|
605
|
+
body: trimmedBody,
|
|
599
606
|
author: normalized.comment.userName,
|
|
600
607
|
}),
|
|
601
608
|
});
|
|
602
609
|
return;
|
|
603
610
|
}
|
|
604
|
-
const issue = this.db.getIssue(project.id, normalized.issue.id);
|
|
605
|
-
if (!issue)
|
|
606
|
-
return;
|
|
607
611
|
// No active run — enqueue a run with the comment as context if appropriate
|
|
608
612
|
if (!issue.activeRunId) {
|
|
609
613
|
const ENQUEUEABLE_STATES = new Set(["pr_open", "changes_requested", "implementing", "delegated", "awaiting_input"]);
|
|
610
614
|
if (ENQUEUEABLE_STATES.has(issue.factoryState)) {
|
|
615
|
+
const directReply = this.isDirectReplyToOutstandingQuestion(issue);
|
|
616
|
+
const wakeIntent = directReply || this.hasExplicitPatchRelayWakeIntent(trimmedBody);
|
|
617
|
+
if (!wakeIntent) {
|
|
618
|
+
this.feed?.publish({
|
|
619
|
+
level: "info",
|
|
620
|
+
kind: "comment",
|
|
621
|
+
projectId: project.id,
|
|
622
|
+
issueKey: trackedIssue?.issueKey,
|
|
623
|
+
status: "ignored",
|
|
624
|
+
summary: "Ignored comment with no explicit PatchRelay wake intent",
|
|
625
|
+
detail: trimmedBody.slice(0, 200),
|
|
626
|
+
});
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
611
629
|
const runType = issue.prReviewState === "changes_requested" ? "review_fix" : "implementation";
|
|
612
630
|
const hadPendingWake = this.db.peekIssueSessionWake(project.id, normalized.issue.id) !== undefined;
|
|
613
|
-
const directReply = this.isDirectReplyToOutstandingQuestion(issue);
|
|
614
631
|
this.db.appendIssueSessionEventRespectingActiveLease(project.id, normalized.issue.id, {
|
|
615
632
|
projectId: project.id,
|
|
616
633
|
linearIssueId: normalized.issue.id,
|
|
617
634
|
eventType: directReply ? "direct_reply" : "followup_comment",
|
|
618
635
|
eventJson: JSON.stringify({
|
|
619
|
-
body:
|
|
636
|
+
body: trimmedBody,
|
|
620
637
|
author: normalized.comment.userName,
|
|
621
638
|
}),
|
|
622
639
|
});
|
|
@@ -630,7 +647,7 @@ export class WebhookHandler {
|
|
|
630
647
|
issueKey: trackedIssue?.issueKey,
|
|
631
648
|
status: "enqueued",
|
|
632
649
|
summary: `Comment enqueued ${(queuedRunType ?? runType)} run`,
|
|
633
|
-
detail:
|
|
650
|
+
detail: trimmedBody.slice(0, 200),
|
|
634
651
|
});
|
|
635
652
|
}
|
|
636
653
|
return;
|
|
@@ -642,7 +659,7 @@ export class WebhookHandler {
|
|
|
642
659
|
"New Linear comment received while you are working.",
|
|
643
660
|
normalized.comment.userName ? `Author: ${normalized.comment.userName}` : undefined,
|
|
644
661
|
"",
|
|
645
|
-
|
|
662
|
+
trimmedBody,
|
|
646
663
|
].filter(Boolean).join("\n");
|
|
647
664
|
try {
|
|
648
665
|
await this.codex.steerTurn({ threadId: run.threadId, turnId: run.turnId, input: body });
|
|
@@ -665,7 +682,7 @@ export class WebhookHandler {
|
|
|
665
682
|
linearIssueId: normalized.issue.id,
|
|
666
683
|
eventType: directReply ? "direct_reply" : "followup_comment",
|
|
667
684
|
eventJson: JSON.stringify({
|
|
668
|
-
body:
|
|
685
|
+
body: trimmedBody,
|
|
669
686
|
author: normalized.comment.userName,
|
|
670
687
|
}),
|
|
671
688
|
});
|
|
@@ -683,6 +700,47 @@ export class WebhookHandler {
|
|
|
683
700
|
});
|
|
684
701
|
}
|
|
685
702
|
}
|
|
703
|
+
isInertPatchRelayComment(issue, commentId, body, actorType) {
|
|
704
|
+
if (commentId === issue.statusCommentId) {
|
|
705
|
+
return true;
|
|
706
|
+
}
|
|
707
|
+
if (body.startsWith("## PatchRelay status")
|
|
708
|
+
&& body.includes("_PatchRelay updates this comment as it works. Review and merge remain downstream._")) {
|
|
709
|
+
return true;
|
|
710
|
+
}
|
|
711
|
+
const normalizedActorType = actorType?.trim().toLowerCase();
|
|
712
|
+
if (normalizedActorType && normalizedActorType !== "user") {
|
|
713
|
+
return this.isPatchRelayGeneratedActivityComment(body);
|
|
714
|
+
}
|
|
715
|
+
return false;
|
|
716
|
+
}
|
|
717
|
+
isPatchRelayManagedCommentAuthor(installation, actor, commentUserName) {
|
|
718
|
+
const actorName = actor?.name?.trim().toLowerCase();
|
|
719
|
+
const commentAuthor = commentUserName?.trim().toLowerCase();
|
|
720
|
+
const installationName = installation?.actorName?.trim().toLowerCase();
|
|
721
|
+
if (installation?.actorId && actor?.id === installation.actorId) {
|
|
722
|
+
return true;
|
|
723
|
+
}
|
|
724
|
+
if (installationName && actorName === installationName) {
|
|
725
|
+
return true;
|
|
726
|
+
}
|
|
727
|
+
if (actorName === "patchrelay" || commentAuthor === "patchrelay") {
|
|
728
|
+
return true;
|
|
729
|
+
}
|
|
730
|
+
return false;
|
|
731
|
+
}
|
|
732
|
+
isPatchRelayGeneratedActivityComment(body) {
|
|
733
|
+
return body.startsWith("PatchRelay needs human help to continue.")
|
|
734
|
+
|| body.startsWith("PatchRelay is already working on ")
|
|
735
|
+
|| body.startsWith("PatchRelay received the ")
|
|
736
|
+
|| body.startsWith("PatchRelay routed your latest instructions into ")
|
|
737
|
+
|| body.startsWith("PatchRelay has stopped work as requested.")
|
|
738
|
+
|| body.startsWith("Merge preparation failed ")
|
|
739
|
+
|| body === "This thread is for an agent session with patchrelay.";
|
|
740
|
+
}
|
|
741
|
+
hasExplicitPatchRelayWakeIntent(body) {
|
|
742
|
+
return /\bpatchrelay\b/i.test(body);
|
|
743
|
+
}
|
|
686
744
|
peekPendingSessionWakeRunType(projectId, issueId) {
|
|
687
745
|
return this.db.peekIssueSessionWake(projectId, issueId)?.runType;
|
|
688
746
|
}
|