patchrelay 0.55.0 → 0.55.2

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "service": "patchrelay",
3
- "version": "0.55.0",
4
- "commit": "bd44ea445936",
5
- "builtAt": "2026-05-01T12:06:57.419Z"
3
+ "version": "0.55.2",
4
+ "commit": "8e07dee509b9",
5
+ "builtAt": "2026-05-01T14:20:04.619Z"
6
6
  }
@@ -65,8 +65,9 @@ export class ReactiveRunPolicy {
65
65
  if (!issue.prNumber || issue.prState !== "open") {
66
66
  return undefined;
67
67
  }
68
- if (!run.sourceHeadSha) {
69
- return `Requested-changes run finished for PR #${issue.prNumber} without a recorded starting head SHA. PatchRelay cannot verify that a new head was published.`;
68
+ const blockingReviewHeadSha = resolveRequestedChangesBlockingHead(run, issue);
69
+ if (!blockingReviewHeadSha) {
70
+ return `Requested-changes run finished for PR #${issue.prNumber} without a recorded blocking review or starting head SHA. PatchRelay cannot verify that a new head was published.`;
70
71
  }
71
72
  try {
72
73
  const snapshot = await readReactivePrSnapshot(this.config, run.projectId, issue.prNumber);
@@ -75,8 +76,8 @@ export class ReactiveRunPolicy {
75
76
  if (!snapshot.headSha) {
76
77
  return `Requested-changes run finished for PR #${issue.prNumber} but GitHub did not report a current head SHA.`;
77
78
  }
78
- if (snapshot.headSha === run.sourceHeadSha) {
79
- return `Requested-changes run finished for PR #${issue.prNumber} without pushing a new head; PatchRelay must not hand the same SHA back to review.`;
79
+ if (snapshot.headSha === blockingReviewHeadSha) {
80
+ return `Requested-changes run finished for PR #${issue.prNumber} without pushing a new head past blocking review SHA ${blockingReviewHeadSha.slice(0, 8)}; PatchRelay must not hand the same SHA back to review.`;
80
81
  }
81
82
  return undefined;
82
83
  }
@@ -153,8 +154,9 @@ export class ReactiveRunPolicy {
153
154
  return this.db.issues.getIssue(run.projectId, run.linearIssueId) ?? issue;
154
155
  }
155
156
  const headAdvanced = Boolean(snapshot.headSha && snapshot.headSha !== issue.lastGitHubFailureHeadSha);
157
+ const blockingReviewHeadSha = resolveRequestedChangesBlockingHead(run, issue);
156
158
  const reviewFixHeadAdvanced = isRequestedChangesRunType(run.runType)
157
- && Boolean(snapshot.headSha && run.sourceHeadSha && snapshot.headSha !== run.sourceHeadSha);
159
+ && Boolean(snapshot.headSha && blockingReviewHeadSha && snapshot.headSha !== blockingReviewHeadSha);
158
160
  this.upsertIssueIfLeaseHeld(run.projectId, run.linearIssueId, {
159
161
  projectId: run.projectId,
160
162
  linearIssueId: run.linearIssueId,
@@ -311,6 +313,9 @@ function resolveReactiveBaselineHead(run, issue) {
311
313
  }
312
314
  return undefined;
313
315
  }
316
+ function resolveRequestedChangesBlockingHead(run, issue) {
317
+ return issue.lastBlockingReviewHeadSha ?? run.sourceHeadSha;
318
+ }
314
319
  function isReactiveScopeRiskPath(filePath) {
315
320
  return REACTIVE_SCOPE_RISK_EXACT_PATHS.has(filePath)
316
321
  || REACTIVE_SCOPE_RISK_PREFIXES.some((prefix) => filePath.startsWith(prefix));
@@ -2,6 +2,13 @@ import { buildAgentSessionPlanForIssue, } from "../agent-session-plan.js";
2
2
  import { buildAgentSessionExternalUrls } from "../agent-session-presentation.js";
3
3
  import { buildAlreadyRunningThought, buildBlockedDelegationActivity, buildDelegationThought, buildPromptDeliveredThought, buildStopConfirmationActivity, } from "../linear-session-reporting.js";
4
4
  import { triggerEventAllowed } from "../project-resolution.js";
5
+ const PATCHRELAY_AGENT_ACTIVITY_TYPES = new Set([
6
+ "action",
7
+ "elicitation",
8
+ "error",
9
+ "response",
10
+ "thought",
11
+ ]);
5
12
  export class AgentSessionHandler {
6
13
  config;
7
14
  db;
@@ -76,6 +83,17 @@ export class AgentSessionHandler {
76
83
  return;
77
84
  if (!triggerEventAllowed(project, normalized.triggerEvent))
78
85
  return;
86
+ if (isPatchRelayAgentActivityEcho(normalized.agentSession)) {
87
+ this.feed?.publish({
88
+ level: "info",
89
+ kind: "agent",
90
+ projectId: project.id,
91
+ issueKey: trackedIssue?.issueKey,
92
+ status: "ignored_echo",
93
+ summary: `Ignored Linear agent activity echo (${normalized.agentSession.activityType})`,
94
+ });
95
+ return;
96
+ }
79
97
  const promptBody = normalized.agentSession.promptBody?.trim();
80
98
  if (!automationEnabled && promptBody && existingIssue) {
81
99
  await this.publishAgentActivity(linear, normalized.agentSession.id, {
@@ -223,3 +241,7 @@ export class AgentSessionHandler {
223
241
  }
224
242
  }
225
243
  }
244
+ function isPatchRelayAgentActivityEcho(agentSession) {
245
+ const activityType = agentSession?.activityType?.trim().toLowerCase();
246
+ return Boolean(activityType && PATCHRELAY_AGENT_ACTIVITY_TYPES.has(activityType));
247
+ }
package/dist/webhooks.js CHANGED
@@ -55,7 +55,8 @@ function deriveTriggerEvent(payload) {
55
55
  ["payload", "agentActivity"],
56
56
  ["resource", "agentActivity"],
57
57
  ]);
58
- if (agentActivityForSignal && getString(agentActivityForSignal, "signal")) {
58
+ const agentActivityContent = asRecord(agentActivityForSignal?.content);
59
+ if (agentActivityForSignal && (getString(agentActivityForSignal, "signal") || getString(agentActivityContent ?? {}, "signal"))) {
59
60
  return "agentSignal";
60
61
  }
61
62
  if (payload.action === "created" || payload.action === "create") {
@@ -331,6 +332,8 @@ function extractAgentSessionMetadata(payload) {
331
332
  ["payload", "agentActivity"],
332
333
  ["resource", "agentActivity"],
333
334
  ]);
335
+ const agentActivityRecord = agentActivity ?? {};
336
+ const agentActivityContent = asRecord(agentActivityRecord.content) ?? {};
334
337
  const commentRecord = getFirstNestedRecord(data, [
335
338
  ["comment"],
336
339
  ["agentSession", "comment"],
@@ -341,17 +344,23 @@ function extractAgentSessionMetadata(payload) {
341
344
  ]) ??
342
345
  getFirstNestedRecord(sessionRecord, [["comment"]]);
343
346
  const promptContext = getString(data, "promptContext") ?? getString(sessionRecord ?? {}, "promptContext");
344
- const promptBody = getString(agentActivity ?? {}, "body") ??
347
+ const activityId = getString(agentActivityRecord, "id");
348
+ const activityType = getString(agentActivityRecord, "type") ?? getString(agentActivityContent, "type");
349
+ const activityBody = getString(agentActivityRecord, "body") ?? getString(agentActivityContent, "body");
350
+ const promptBody = activityBody ??
345
351
  getString(commentRecord ?? {}, "body") ??
346
352
  getString(data, "body");
347
353
  const issueCommentId = getString(commentRecord ?? {}, "id") ?? getString(data, "issueCommentId");
348
- const signal = getString(agentActivity ?? {}, "signal");
349
- const signalMetadata = asRecord((agentActivity ?? {}).signalMetadata);
354
+ const signal = getString(agentActivityRecord, "signal") ?? getString(agentActivityContent, "signal");
355
+ const signalMetadata = asRecord(agentActivityRecord.signalMetadata) ?? asRecord(agentActivityContent.signalMetadata);
350
356
  return {
351
357
  id,
352
358
  ...(promptContext ? { promptContext } : {}),
353
359
  ...(promptBody ? { promptBody } : {}),
354
360
  ...(issueCommentId ? { issueCommentId } : {}),
361
+ ...(activityId ? { activityId } : {}),
362
+ ...(activityType ? { activityType } : {}),
363
+ ...(activityBody ? { activityBody } : {}),
355
364
  ...(signal ? { signal } : {}),
356
365
  ...(signalMetadata ? { signalMetadata } : {}),
357
366
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "patchrelay",
3
- "version": "0.55.0",
3
+ "version": "0.55.2",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "repository": {