patchrelay 0.8.1 → 0.8.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.
package/dist/build-info.json
CHANGED
package/dist/config.js
CHANGED
|
@@ -128,10 +128,28 @@ const configSchema = z.object({
|
|
|
128
128
|
});
|
|
129
129
|
function defaultTriggerEvents(actor) {
|
|
130
130
|
if (actor === "app") {
|
|
131
|
-
return ["agentSessionCreated", "agentPrompted"];
|
|
131
|
+
return ["delegateChanged", "statusChanged", "agentSessionCreated", "agentPrompted", "commentCreated", "commentUpdated"];
|
|
132
132
|
}
|
|
133
133
|
return ["statusChanged"];
|
|
134
134
|
}
|
|
135
|
+
function normalizeTriggerEvents(actor, configured) {
|
|
136
|
+
if (actor !== "app") {
|
|
137
|
+
return configured ?? defaultTriggerEvents(actor);
|
|
138
|
+
}
|
|
139
|
+
const required = defaultTriggerEvents(actor);
|
|
140
|
+
if (!configured || configured.length === 0) {
|
|
141
|
+
return required;
|
|
142
|
+
}
|
|
143
|
+
const seen = new Set(required);
|
|
144
|
+
const extras = configured.filter((event) => {
|
|
145
|
+
if (seen.has(event)) {
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
seen.add(event);
|
|
149
|
+
return true;
|
|
150
|
+
});
|
|
151
|
+
return [...required, ...extras];
|
|
152
|
+
}
|
|
135
153
|
const builtinWorkflows = [
|
|
136
154
|
{
|
|
137
155
|
id: "development",
|
|
@@ -489,9 +507,8 @@ export function loadConfig(configPath = process.env.PATCHRELAY_CONFIG ?? getDefa
|
|
|
489
507
|
issueKeyPrefixes: project.issue_key_prefixes,
|
|
490
508
|
linearTeamIds: project.linear_team_ids,
|
|
491
509
|
allowLabels: project.allow_labels,
|
|
492
|
-
triggerEvents: repoSettings?.trigger_events ??
|
|
493
|
-
project.trigger_events
|
|
494
|
-
defaultTriggerEvents(parsed.linear.oauth.actor),
|
|
510
|
+
triggerEvents: normalizeTriggerEvents(parsed.linear.oauth.actor, repoSettings?.trigger_events ??
|
|
511
|
+
project.trigger_events),
|
|
495
512
|
branchPrefix: repoSettings?.branch_prefix ?? project.branch_prefix ?? defaultBranchPrefix(project.id),
|
|
496
513
|
...(repoSettings?.configPath ? { repoSettingsPath: repoSettings.configPath } : {}),
|
|
497
514
|
};
|
package/dist/preflight.js
CHANGED
|
@@ -40,8 +40,14 @@ export async function runPreflight(config) {
|
|
|
40
40
|
checks.push(pass("linear_oauth", "Linear app actor includes assignable and mentionable scopes"));
|
|
41
41
|
}
|
|
42
42
|
for (const project of config.projects) {
|
|
43
|
+
if (!project.triggerEvents.includes("delegateChanged")) {
|
|
44
|
+
checks.push(warn(`project:${project.id}:triggers`, "Automatic pipeline pickup works best when trigger_events includes delegateChanged"));
|
|
45
|
+
}
|
|
46
|
+
if (!project.triggerEvents.includes("statusChanged")) {
|
|
47
|
+
checks.push(warn(`project:${project.id}:triggers`, "Automatic stage-to-stage continuation works best when trigger_events includes statusChanged"));
|
|
48
|
+
}
|
|
43
49
|
if (!project.triggerEvents.includes("agentSessionCreated")) {
|
|
44
|
-
checks.push(warn(`project:${project.id}:triggers`, "
|
|
50
|
+
checks.push(warn(`project:${project.id}:triggers`, "Native Linear agent sessions work best when trigger_events includes agentSessionCreated"));
|
|
45
51
|
}
|
|
46
52
|
if (!project.triggerEvents.includes("agentPrompted")) {
|
|
47
53
|
checks.push(warn(`project:${project.id}:triggers`, "Native follow-up agent prompts will not reach an active run unless trigger_events includes agentPrompted"));
|
|
@@ -3,13 +3,11 @@ import { reconcileIssue } from "./reconciliation-engine.js";
|
|
|
3
3
|
import { buildReconciliationSnapshot } from "./reconciliation-snapshot-builder.js";
|
|
4
4
|
import { syncFailedStageToLinear } from "./stage-failure.js";
|
|
5
5
|
import { parseStageHandoff } from "./stage-handoff.js";
|
|
6
|
-
import {
|
|
6
|
+
import { resolveFallbackLinearState } from "./linear-workflow.js";
|
|
7
7
|
import { resolveDefaultTransitionTarget, transitionTargetAllowed } from "./workflow-policy.js";
|
|
8
8
|
import { buildFailedStageReport, buildPendingMaterializationThread, buildStageReport, countEventMethods, extractStageSummary, extractTurnId, resolveStageRunStatus, summarizeCurrentThread, } from "./stage-reporting.js";
|
|
9
9
|
import { StageLifecyclePublisher } from "./stage-lifecycle-publisher.js";
|
|
10
10
|
import { StageTurnInputDispatcher } from "./stage-turn-input-dispatcher.js";
|
|
11
|
-
import { safeJsonParse } from "./utils.js";
|
|
12
|
-
import { normalizeWebhook } from "./webhooks.js";
|
|
13
11
|
const MAX_AUTOMATIC_TRANSITION_ATTEMPTS = 3;
|
|
14
12
|
export class ServiceStageFinalizer {
|
|
15
13
|
config;
|
|
@@ -261,7 +259,7 @@ export class ServiceStageFinalizer {
|
|
|
261
259
|
if (!linearIssue) {
|
|
262
260
|
return;
|
|
263
261
|
}
|
|
264
|
-
const continuationPrecondition = await this.checkAutomaticContinuationPreconditions(
|
|
262
|
+
const continuationPrecondition = await this.checkAutomaticContinuationPreconditions(stageRun, refreshedIssue, linear, linearIssue);
|
|
265
263
|
if (!continuationPrecondition.allowed) {
|
|
266
264
|
this.feed?.publish({
|
|
267
265
|
level: "info",
|
|
@@ -339,14 +337,7 @@ export class ServiceStageFinalizer {
|
|
|
339
337
|
lifecycleStatus: "queued",
|
|
340
338
|
});
|
|
341
339
|
}
|
|
342
|
-
async checkAutomaticContinuationPreconditions(
|
|
343
|
-
const activeState = resolveActiveLinearState(project, stageRun.stage, issue.selectedWorkflowId);
|
|
344
|
-
if (activeState && normalizeLinearState(linearIssue.stateName) !== normalizeLinearState(activeState)) {
|
|
345
|
-
return {
|
|
346
|
-
allowed: false,
|
|
347
|
-
reason: `Linear moved from ${activeState} to ${linearIssue.stateName ?? "an unknown state"} while the stage was running.`,
|
|
348
|
-
};
|
|
349
|
-
}
|
|
340
|
+
async checkAutomaticContinuationPreconditions(stageRun, issue, linear, linearIssue) {
|
|
350
341
|
const actorProfile = await linear.getActorProfile().catch(() => undefined);
|
|
351
342
|
if (actorProfile?.actorId && linearIssue.delegateId && linearIssue.delegateId !== actorProfile.actorId) {
|
|
352
343
|
return {
|
|
@@ -354,48 +345,8 @@ export class ServiceStageFinalizer {
|
|
|
354
345
|
reason: "The issue is no longer delegated to PatchRelay.",
|
|
355
346
|
};
|
|
356
347
|
}
|
|
357
|
-
const stageSession = stageRun.threadId ? this.stores.issueSessions.getIssueSessionByThreadId(stageRun.threadId) : undefined;
|
|
358
|
-
if (stageSession?.linkedAgentSessionId && issue.activeAgentSessionId !== stageSession.linkedAgentSessionId) {
|
|
359
|
-
return {
|
|
360
|
-
allowed: false,
|
|
361
|
-
reason: "The active Linear agent session changed while the stage was running.",
|
|
362
|
-
};
|
|
363
|
-
}
|
|
364
|
-
const recentInterruptions = this.listInterruptingWebhooksSince(stageRun, actorProfile?.actorId);
|
|
365
|
-
if (recentInterruptions.length > 0) {
|
|
366
|
-
return {
|
|
367
|
-
allowed: false,
|
|
368
|
-
reason: `A newer human webhook (${recentInterruptions[0]}) arrived while the stage was running.`,
|
|
369
|
-
};
|
|
370
|
-
}
|
|
371
348
|
return { allowed: true };
|
|
372
349
|
}
|
|
373
|
-
listInterruptingWebhooksSince(stageRun, patchRelayActorId) {
|
|
374
|
-
const events = this.stores.webhookEvents.listWebhookEventsForIssueSince(stageRun.linearIssueId, stageRun.startedAt);
|
|
375
|
-
const interrupts = [];
|
|
376
|
-
for (const event of events) {
|
|
377
|
-
const payload = safeJsonParse(event.payloadJson);
|
|
378
|
-
if (!payload) {
|
|
379
|
-
continue;
|
|
380
|
-
}
|
|
381
|
-
const normalized = normalizeWebhook({
|
|
382
|
-
webhookId: event.webhookId,
|
|
383
|
-
payload: payload,
|
|
384
|
-
});
|
|
385
|
-
if (patchRelayActorId && normalized.actor?.id === patchRelayActorId) {
|
|
386
|
-
continue;
|
|
387
|
-
}
|
|
388
|
-
if (normalized.triggerEvent === "commentCreated" ||
|
|
389
|
-
normalized.triggerEvent === "commentUpdated" ||
|
|
390
|
-
normalized.triggerEvent === "agentPrompted" ||
|
|
391
|
-
normalized.triggerEvent === "agentSessionCreated" ||
|
|
392
|
-
normalized.triggerEvent === "delegateChanged" ||
|
|
393
|
-
normalized.triggerEvent === "statusChanged") {
|
|
394
|
-
interrupts.push(normalized.triggerEvent);
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
return interrupts;
|
|
398
|
-
}
|
|
399
350
|
resolveTransitionTarget(project, stageRun, workflowDefinitionId, handoff) {
|
|
400
351
|
if (!handoff) {
|
|
401
352
|
return "human_needed";
|
|
@@ -154,8 +154,7 @@ export class ServiceWebhookProcessor {
|
|
|
154
154
|
});
|
|
155
155
|
}
|
|
156
156
|
if (issueState.issue?.selectedWorkflowId &&
|
|
157
|
-
issueState.issue.selectedWorkflowId !== priorIssue?.selectedWorkflowId
|
|
158
|
-
(hydrated.triggerEvent === "agentSessionCreated" || hydrated.triggerEvent === "agentPrompted")) {
|
|
157
|
+
issueState.issue.selectedWorkflowId !== priorIssue?.selectedWorkflowId) {
|
|
159
158
|
this.feed?.publish({
|
|
160
159
|
level: "info",
|
|
161
160
|
kind: "workflow",
|
|
@@ -78,9 +78,6 @@ export class WebhookDesiredStageRecorder {
|
|
|
78
78
|
if (!normalizedIssue) {
|
|
79
79
|
return undefined;
|
|
80
80
|
}
|
|
81
|
-
if (normalized.triggerEvent !== "agentSessionCreated" && normalized.triggerEvent !== "agentPrompted") {
|
|
82
|
-
return undefined;
|
|
83
|
-
}
|
|
84
81
|
if (!delegatedToPatchRelay || !triggerEventAllowed(project, normalized.triggerEvent)) {
|
|
85
82
|
return undefined;
|
|
86
83
|
}
|
|
@@ -102,9 +99,6 @@ export class WebhookDesiredStageRecorder {
|
|
|
102
99
|
if (activeStageRun) {
|
|
103
100
|
return issue?.selectedWorkflowId;
|
|
104
101
|
}
|
|
105
|
-
if (normalized.triggerEvent !== "agentSessionCreated" && normalized.triggerEvent !== "agentPrompted") {
|
|
106
|
-
return issue?.selectedWorkflowId;
|
|
107
|
-
}
|
|
108
102
|
if (!delegatedToPatchRelay || !triggerEventAllowed(project, normalized.triggerEvent) || !normalized.issue) {
|
|
109
103
|
return issue?.selectedWorkflowId;
|
|
110
104
|
}
|