patchrelay 0.36.17 → 0.36.19

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.
@@ -0,0 +1,104 @@
1
+ export class ServiceStartupRecovery {
2
+ db;
3
+ linearProvider;
4
+ linearSync;
5
+ enqueueIssue;
6
+ logger;
7
+ constructor(db, linearProvider, linearSync, enqueueIssue, logger) {
8
+ this.db = db;
9
+ this.linearProvider = linearProvider;
10
+ this.linearSync = linearSync;
11
+ this.enqueueIssue = enqueueIssue;
12
+ this.logger = logger;
13
+ }
14
+ async syncKnownAgentSessions() {
15
+ for (const issue of this.db.issues.listIssues()) {
16
+ if (issue.factoryState === "done") {
17
+ continue;
18
+ }
19
+ const syncedIssue = issue.agentSessionId
20
+ ? issue
21
+ : (() => {
22
+ const recoveredAgentSessionId = this.db.webhookEvents.findLatestAgentSessionIdForIssue(issue.linearIssueId);
23
+ return recoveredAgentSessionId
24
+ ? this.db.issues.upsertIssue({
25
+ projectId: issue.projectId,
26
+ linearIssueId: issue.linearIssueId,
27
+ agentSessionId: recoveredAgentSessionId,
28
+ })
29
+ : issue;
30
+ })();
31
+ if (!syncedIssue.agentSessionId) {
32
+ continue;
33
+ }
34
+ const activeRun = syncedIssue.activeRunId ? this.db.runs.getRunById(syncedIssue.activeRunId) : undefined;
35
+ await this.linearSync.syncSession(syncedIssue, activeRun ? { activeRunType: activeRun.runType } : undefined);
36
+ }
37
+ }
38
+ async recoverDelegatedIssueStateFromLinear() {
39
+ for (const issue of this.db.issues.listIssuesWithAgentSessions()) {
40
+ if (issue.factoryState === "done" || issue.activeRunId !== undefined) {
41
+ continue;
42
+ }
43
+ const linear = await this.linearProvider.forProject(issue.projectId).catch(() => undefined);
44
+ if (!linear) {
45
+ continue;
46
+ }
47
+ const installation = this.db.linearInstallations.getLinearInstallationForProject(issue.projectId);
48
+ if (!installation?.actorId) {
49
+ continue;
50
+ }
51
+ const liveIssue = await linear.getIssue(issue.linearIssueId).catch(() => undefined);
52
+ if (!liveIssue) {
53
+ continue;
54
+ }
55
+ this.db.issues.replaceIssueDependencies({
56
+ projectId: issue.projectId,
57
+ linearIssueId: issue.linearIssueId,
58
+ blockers: liveIssue.blockedBy.map((blocker) => ({
59
+ blockerLinearIssueId: blocker.id,
60
+ ...(blocker.identifier ? { blockerIssueKey: blocker.identifier } : {}),
61
+ ...(blocker.title ? { blockerTitle: blocker.title } : {}),
62
+ ...(blocker.stateName ? { blockerCurrentLinearState: blocker.stateName } : {}),
63
+ ...(blocker.stateType ? { blockerCurrentLinearStateType: blocker.stateType } : {}),
64
+ })),
65
+ });
66
+ const delegated = liveIssue.delegateId === installation.actorId;
67
+ const unresolvedBlockers = this.db.issues.countUnresolvedBlockers(issue.projectId, issue.linearIssueId);
68
+ const shouldRecoverAwaitingInput = delegated
69
+ && issue.factoryState === "awaiting_input"
70
+ && this.db.issueSessions.peekIssueSessionWake(issue.projectId, issue.linearIssueId) === undefined;
71
+ const updated = this.db.issues.upsertIssue({
72
+ projectId: issue.projectId,
73
+ linearIssueId: issue.linearIssueId,
74
+ ...(liveIssue.identifier ? { issueKey: liveIssue.identifier } : {}),
75
+ ...(liveIssue.title ? { title: liveIssue.title } : {}),
76
+ ...(liveIssue.description ? { description: liveIssue.description } : {}),
77
+ ...(liveIssue.url ? { url: liveIssue.url } : {}),
78
+ ...(liveIssue.priority != null ? { priority: liveIssue.priority } : {}),
79
+ ...(liveIssue.estimate != null ? { estimate: liveIssue.estimate } : {}),
80
+ ...(liveIssue.stateName ? { currentLinearState: liveIssue.stateName } : {}),
81
+ ...(liveIssue.stateType ? { currentLinearStateType: liveIssue.stateType } : {}),
82
+ ...(shouldRecoverAwaitingInput ? { factoryState: "delegated" } : {}),
83
+ });
84
+ if (!shouldRecoverAwaitingInput) {
85
+ continue;
86
+ }
87
+ if (unresolvedBlockers === 0) {
88
+ this.db.issueSessions.appendIssueSessionEventRespectingActiveLease(issue.projectId, issue.linearIssueId, {
89
+ projectId: issue.projectId,
90
+ linearIssueId: issue.linearIssueId,
91
+ eventType: "delegated",
92
+ dedupeKey: `delegated:${issue.linearIssueId}`,
93
+ });
94
+ if (this.db.issueSessions.peekIssueSessionWake(issue.projectId, issue.linearIssueId)) {
95
+ this.enqueueIssue(issue.projectId, issue.linearIssueId);
96
+ }
97
+ this.logger.info({ issueKey: updated.issueKey }, "Recovered delegated issue from stale awaiting_input state and re-queued implementation");
98
+ }
99
+ else {
100
+ this.logger.info({ issueKey: updated.issueKey, unresolvedBlockers }, "Recovered delegated blocked issue from stale awaiting_input state");
101
+ }
102
+ }
103
+ }
104
+ }