patchrelay 0.37.0 → 0.38.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.
Files changed (48) hide show
  1. package/README.md +47 -9
  2. package/dist/awaiting-input-reason.js +9 -0
  3. package/dist/build-info.json +3 -3
  4. package/dist/cli/cluster-health.js +64 -5
  5. package/dist/cli/data.js +1 -0
  6. package/dist/cli/formatters/text.js +5 -1
  7. package/dist/cli/help.js +1 -1
  8. package/dist/cli/output.js +2 -0
  9. package/dist/cli/watch/IssueRow.js +4 -3
  10. package/dist/cli/watch/StatusBar.js +2 -1
  11. package/dist/cli/watch/detail-rows.js +4 -3
  12. package/dist/cli/watch/pr-status.js +2 -1
  13. package/dist/cli/watch/state-visualization.js +5 -1
  14. package/dist/db/issue-session-store.js +0 -14
  15. package/dist/db/issue-store.js +8 -16
  16. package/dist/db/migrations.js +6 -13
  17. package/dist/db.js +1 -3
  18. package/dist/factory-state.js +1 -1
  19. package/dist/github-webhook-handler.js +95 -54
  20. package/dist/github-webhooks.js +4 -0
  21. package/dist/idle-reconciliation.js +38 -22
  22. package/dist/implementation-outcome-policy.js +3 -1
  23. package/dist/issue-overview-query.js +8 -0
  24. package/dist/issue-session-projector.js +1 -0
  25. package/dist/issue-session.js +8 -0
  26. package/dist/linear-session-reporting.js +43 -5
  27. package/dist/linear-session-sync.js +9 -1
  28. package/dist/linear-status-comment-sync.js +47 -2
  29. package/dist/linear-workflow-state-sync.js +2 -2
  30. package/dist/operator-retry-event.js +15 -12
  31. package/dist/paused-issue-state.js +24 -0
  32. package/dist/pr-state.js +49 -0
  33. package/dist/run-launcher.js +0 -1
  34. package/dist/run-orchestrator.js +2 -5
  35. package/dist/run-reconciler.js +10 -0
  36. package/dist/run-recovery-service.js +1 -10
  37. package/dist/service-issue-actions.js +10 -4
  38. package/dist/service-startup-recovery.js +9 -6
  39. package/dist/service.js +0 -1
  40. package/dist/tracked-issue-list-query.js +6 -2
  41. package/dist/tracked-issue-projector.js +8 -0
  42. package/dist/waiting-reason.js +13 -2
  43. package/dist/webhooks/agent-session-handler.js +9 -1
  44. package/dist/webhooks/comment-wake-handler.js +12 -0
  45. package/dist/webhooks/decision-helpers.js +44 -3
  46. package/dist/webhooks/dependency-readiness-handler.js +1 -0
  47. package/dist/webhooks/desired-stage-recorder.js +40 -10
  48. package/package.json +1 -1
@@ -1,4 +1,5 @@
1
1
  import { TERMINAL_STATES } from "../factory-state.js";
2
+ import { deriveIssueSessionReactiveIntent } from "../issue-session.js";
2
3
  export function decideRunIntent(p) {
3
4
  const wakeEligibleState = p.currentState === undefined
4
5
  || p.currentState === "delegated"
@@ -26,10 +27,50 @@ export function decideUnDelegation(p) {
26
27
  return { clearPending: false };
27
28
  if (!p.currentState)
28
29
  return { clearPending: false };
29
- const pastNoReturn = p.currentState === "awaiting_queue" || TERMINAL_STATES.has(p.currentState);
30
- if (pastNoReturn)
30
+ if (TERMINAL_STATES.has(p.currentState))
31
31
  return { clearPending: false };
32
- return { factoryState: "awaiting_input", clearPending: true };
32
+ return { factoryState: p.currentState, clearPending: true };
33
+ }
34
+ export function resolveReDelegationResume(p) {
35
+ if (!p.delegated || p.previouslyDelegated !== false) {
36
+ return {};
37
+ }
38
+ if (p.prState === "merged") {
39
+ return { factoryState: "done", pendingRunType: null };
40
+ }
41
+ const reactiveIntent = deriveIssueSessionReactiveIntent({
42
+ delegatedToPatchRelay: true,
43
+ prNumber: p.prNumber,
44
+ prState: p.prState,
45
+ prReviewState: p.prReviewState,
46
+ prCheckStatus: p.prCheckStatus,
47
+ latestFailureSource: p.latestFailureSource,
48
+ });
49
+ if (reactiveIntent) {
50
+ return {
51
+ factoryState: reactiveIntent.compatibilityFactoryState,
52
+ pendingRunType: reactiveIntent.runType,
53
+ };
54
+ }
55
+ if (p.prNumber !== undefined && (p.prState === undefined || p.prState === "open")) {
56
+ if (p.prReviewState === "approved") {
57
+ return { factoryState: "awaiting_queue", pendingRunType: null };
58
+ }
59
+ return { factoryState: "pr_open", pendingRunType: null };
60
+ }
61
+ if (p.currentState === "awaiting_input" && p.awaitingInputReason === "completion_check_question") {
62
+ return {
63
+ factoryState: "awaiting_input",
64
+ pendingRunType: null,
65
+ };
66
+ }
67
+ if (p.currentState === "awaiting_input" || p.currentState === "delegated" || p.currentState === "implementing") {
68
+ return {
69
+ factoryState: "delegated",
70
+ pendingRunType: (p.unresolvedBlockers ?? 0) === 0 ? "implementation" : null,
71
+ };
72
+ }
73
+ return {};
33
74
  }
34
75
  export function decideAgentSession(p) {
35
76
  if (p.sessionId)
@@ -27,6 +27,7 @@ export class DependencyReadinessHandler {
27
27
  continue;
28
28
  }
29
29
  if (issue.factoryState !== "delegated"
30
+ || !issue.delegatedToPatchRelay
30
31
  || issue.activeRunId !== undefined
31
32
  || this.db.issueSessions.hasPendingIssueSessionEvents(projectId, dependent.linearIssueId)) {
32
33
  continue;
@@ -1,5 +1,7 @@
1
1
  import { triggerEventAllowed } from "../project-resolution.js";
2
- import { decideActiveRunRelease, decideAgentSession, decideRunIntent, decideUnDelegation, isTerminalDelegationState, mergeIssueMetadata, } from "./decision-helpers.js";
2
+ import { resolveAwaitingInputReason } from "../awaiting-input-reason.js";
3
+ import { decideActiveRunRelease, decideAgentSession, decideRunIntent, decideUnDelegation, isTerminalDelegationState, mergeIssueMetadata, resolveReDelegationResume, } from "./decision-helpers.js";
4
+ import { buildOperatorRetryEvent } from "../operator-retry-event.js";
3
5
  export class DesiredStageRecorder {
4
6
  db;
5
7
  linearProvider;
@@ -16,6 +18,7 @@ export class DesiredStageRecorder {
16
18
  }
17
19
  const existingIssue = this.db.issues.getIssue(params.project.id, normalizedIssue.id);
18
20
  const activeRun = existingIssue?.activeRunId ? this.db.runs.getRunById(existingIssue.activeRunId) : undefined;
21
+ const latestRun = existingIssue ? this.db.runs.getLatestRunForIssue(params.project.id, normalizedIssue.id) : undefined;
19
22
  const delegated = this.isDelegatedToPatchRelay(params.project, params.normalized);
20
23
  const triggerAllowed = triggerEventAllowed(params.project, params.normalized.triggerEvent);
21
24
  const incomingAgentSessionId = params.normalized.agentSession?.id;
@@ -46,11 +49,22 @@ export class DesiredStageRecorder {
46
49
  triggerEvent: params.normalized.triggerEvent,
47
50
  delegated,
48
51
  currentState: existingIssue?.factoryState,
52
+ hasPr: existingIssue?.prNumber !== undefined && existingIssue?.prState !== "merged",
53
+ });
54
+ const reDelegationResume = resolveReDelegationResume({
55
+ delegated,
56
+ previouslyDelegated: existingIssue?.delegatedToPatchRelay,
57
+ currentState: existingIssue?.factoryState,
58
+ awaitingInputReason: existingIssue
59
+ ? resolveAwaitingInputReason({ issue: existingIssue, latestRun })
60
+ : undefined,
61
+ unresolvedBlockers,
62
+ prNumber: existingIssue?.prNumber,
63
+ prState: existingIssue?.prState,
64
+ prReviewState: existingIssue?.prReviewState,
65
+ prCheckStatus: existingIssue?.prCheckStatus,
66
+ latestFailureSource: existingIssue?.lastGitHubFailureSource,
49
67
  });
50
- const delegatedStateRecovery = delegated
51
- && !terminal
52
- && existingIssue?.factoryState === "awaiting_input"
53
- && !undelegation.factoryState;
54
68
  const existingWakeRunType = existingIssue
55
69
  ? params.peekPendingSessionWakeRunType(params.project.id, normalizedIssue.id)
56
70
  : undefined;
@@ -73,9 +87,13 @@ export class DesiredStageRecorder {
73
87
  ...(hydratedIssue.estimate != null ? { estimate: hydratedIssue.estimate } : {}),
74
88
  ...(hydratedIssue.stateName ? { currentLinearState: hydratedIssue.stateName } : {}),
75
89
  ...(hydratedIssue.stateType ? { currentLinearStateType: hydratedIssue.stateType } : {}),
90
+ delegatedToPatchRelay: delegated,
76
91
  ...(!existingIssue && !delegated && incomingAgentSessionId ? { factoryState: "awaiting_input" } : {}),
77
- ...(delegatedStateRecovery ? { factoryState: "delegated" } : {}),
78
- ...(desiredStage ? { pendingRunType: null, pendingRunContextJson: null, factoryState: "delegated" } : {}),
92
+ ...(reDelegationResume.factoryState ? { factoryState: reDelegationResume.factoryState } : {}),
93
+ ...(reDelegationResume.pendingRunType !== undefined
94
+ ? { pendingRunType: null, pendingRunContextJson: null }
95
+ : {}),
96
+ ...(!reDelegationResume.factoryState && desiredStage ? { pendingRunType: null, pendingRunContextJson: null, factoryState: "delegated" } : {}),
79
97
  ...(clearPending ? { pendingRunType: null, pendingRunContextJson: null } : {}),
80
98
  ...(agentSessionId !== undefined ? { agentSessionId } : {}),
81
99
  ...(runRelease.release ? { activeRunId: null } : {}),
@@ -111,12 +129,24 @@ export class DesiredStageRecorder {
111
129
  kind: "stage",
112
130
  issueKey: issue.issueKey,
113
131
  projectId: params.project.id,
114
- stage: "awaiting_input",
132
+ stage: issue.factoryState,
115
133
  status: "un_delegated",
116
- summary: "Issue un-delegated from PatchRelay",
134
+ summary: issue.factoryState === "awaiting_input"
135
+ ? "Issue un-delegated from PatchRelay"
136
+ : `Issue un-delegated from PatchRelay; ${issue.factoryState} is now paused`,
137
+ });
138
+ }
139
+ else if (reDelegationResume.pendingRunType) {
140
+ this.db.issueSessions.appendIssueSessionEventRespectingActiveLease(params.project.id, normalizedIssue.id, {
141
+ projectId: params.project.id,
142
+ linearIssueId: normalizedIssue.id,
143
+ ...buildOperatorRetryEvent(issue, reDelegationResume.pendingRunType, "re_delegated"),
117
144
  });
118
145
  }
119
- else if (desiredStage === "implementation"
146
+ else if (!reDelegationResume.factoryState
147
+ && !reDelegationResume.pendingRunType
148
+ &&
149
+ desiredStage === "implementation"
120
150
  && params.normalized.triggerEvent !== "commentCreated"
121
151
  && params.normalized.triggerEvent !== "commentUpdated"
122
152
  && params.normalized.triggerEvent !== "agentPrompted") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "patchrelay",
3
- "version": "0.37.0",
3
+ "version": "0.38.0",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "repository": {