patchrelay 0.8.9 → 0.9.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 (57) hide show
  1. package/README.md +64 -62
  2. package/dist/agent-session-plan.js +17 -17
  3. package/dist/build-info.json +3 -3
  4. package/dist/cli/commands/issues.js +12 -12
  5. package/dist/cli/data.js +109 -298
  6. package/dist/cli/formatters/text.js +22 -28
  7. package/dist/config.js +13 -166
  8. package/dist/db/migrations.js +46 -154
  9. package/dist/db.js +369 -45
  10. package/dist/factory-state.js +55 -0
  11. package/dist/github-webhook-handler.js +199 -0
  12. package/dist/github-webhooks.js +166 -0
  13. package/dist/hook-runner.js +28 -0
  14. package/dist/http.js +48 -22
  15. package/dist/issue-query-service.js +33 -38
  16. package/dist/linear-workflow.js +5 -118
  17. package/dist/preflight.js +1 -6
  18. package/dist/project-resolution.js +12 -1
  19. package/dist/run-orchestrator.js +446 -0
  20. package/dist/{stage-reporting.js → run-reporting.js} +11 -13
  21. package/dist/service-runtime.js +12 -61
  22. package/dist/service-webhooks.js +7 -52
  23. package/dist/service.js +39 -61
  24. package/dist/webhook-handler.js +387 -0
  25. package/dist/webhook-installation-handler.js +3 -8
  26. package/package.json +2 -1
  27. package/dist/db/authoritative-ledger-store.js +0 -536
  28. package/dist/db/issue-projection-store.js +0 -54
  29. package/dist/db/issue-workflow-coordinator.js +0 -320
  30. package/dist/db/issue-workflow-store.js +0 -194
  31. package/dist/db/run-report-store.js +0 -33
  32. package/dist/db/stage-event-store.js +0 -33
  33. package/dist/db/webhook-event-store.js +0 -59
  34. package/dist/db-ports.js +0 -5
  35. package/dist/ledger-ports.js +0 -1
  36. package/dist/reconciliation-action-applier.js +0 -68
  37. package/dist/reconciliation-actions.js +0 -1
  38. package/dist/reconciliation-engine.js +0 -350
  39. package/dist/reconciliation-snapshot-builder.js +0 -135
  40. package/dist/reconciliation-types.js +0 -1
  41. package/dist/service-stage-finalizer.js +0 -753
  42. package/dist/service-stage-runner.js +0 -336
  43. package/dist/service-webhook-processor.js +0 -411
  44. package/dist/stage-agent-activity-publisher.js +0 -59
  45. package/dist/stage-event-ports.js +0 -1
  46. package/dist/stage-failure.js +0 -92
  47. package/dist/stage-handoff.js +0 -107
  48. package/dist/stage-launch.js +0 -84
  49. package/dist/stage-lifecycle-publisher.js +0 -284
  50. package/dist/stage-turn-input-dispatcher.js +0 -104
  51. package/dist/webhook-agent-session-handler.js +0 -228
  52. package/dist/webhook-comment-handler.js +0 -141
  53. package/dist/webhook-desired-stage-recorder.js +0 -122
  54. package/dist/webhook-event-ports.js +0 -1
  55. package/dist/workflow-policy.js +0 -149
  56. package/dist/workflow-ports.js +0 -1
  57. /package/dist/{installation-ports.js → github-types.js} +0 -0
@@ -1,68 +0,0 @@
1
- export class ReconciliationActionApplier {
2
- callbacks;
3
- constructor(callbacks) {
4
- this.callbacks = callbacks;
5
- }
6
- async apply(params) {
7
- const { snapshot, decision } = params;
8
- const threadId = snapshot.runLease.threadId;
9
- const turnId = snapshot.runLease.turnId;
10
- const obligationTargetAction = decision.actions.find((action) => action.type === "deliver_obligation" || action.type === "route_obligation");
11
- const targetThreadId = obligationTargetAction?.type === "deliver_obligation" || obligationTargetAction?.type === "route_obligation"
12
- ? obligationTargetAction.threadId
13
- : threadId;
14
- const targetTurnId = obligationTargetAction?.type === "deliver_obligation" || obligationTargetAction?.type === "route_obligation"
15
- ? obligationTargetAction.turnId
16
- : turnId;
17
- const clearAction = decision.actions.find((action) => action.type === "clear_active_run" || action.type === "release_issue_ownership");
18
- const nextLifecycleStatus = clearAction?.type === "clear_active_run" || clearAction?.type === "release_issue_ownership"
19
- ? clearAction.nextLifecycleStatus
20
- : undefined;
21
- if (decision.outcome === "launch") {
22
- this.callbacks.enqueueIssue(snapshot.runLease.projectId, snapshot.runLease.linearIssueId);
23
- return;
24
- }
25
- if (decision.outcome === "continue") {
26
- if (targetThreadId) {
27
- await this.callbacks.deliverPendingObligations(snapshot.runLease.projectId, snapshot.runLease.linearIssueId, targetThreadId, targetTurnId);
28
- }
29
- return;
30
- }
31
- const completedAction = decision.actions.find((action) => action.type === "mark_run_completed");
32
- if (decision.outcome === "complete" || (decision.outcome === "release" && completedAction?.type === "mark_run_completed")) {
33
- const liveThread = snapshot.input.live?.codex?.status === "found" ? snapshot.input.live.codex.thread : undefined;
34
- if (!liveThread) {
35
- return;
36
- }
37
- const latestTurn = liveThread.turns.at(-1);
38
- this.callbacks.completeRun(snapshot.runLease.projectId, snapshot.runLease.linearIssueId, liveThread, {
39
- threadId: liveThread.id,
40
- ...(latestTurn?.id ? { turnId: latestTurn.id } : {}),
41
- ...(nextLifecycleStatus ? { nextLifecycleStatus } : {}),
42
- });
43
- return;
44
- }
45
- if (decision.outcome === "fail" || decision.outcome === "release") {
46
- const failedAction = decision.actions.find((action) => action.type === "mark_run_failed");
47
- if (decision.outcome === "release" && failedAction?.type !== "mark_run_failed") {
48
- const releasedAction = decision.actions.find((action) => action.type === "release_issue_ownership");
49
- if (releasedAction?.type !== "release_issue_ownership") {
50
- return;
51
- }
52
- await this.callbacks.releaseRunDuringReconciliation(snapshot.runLease.projectId, snapshot.runLease.linearIssueId, {
53
- runId: snapshot.runLease.id,
54
- ...(threadId ? { threadId } : {}),
55
- ...(turnId ? { turnId } : {}),
56
- ...(nextLifecycleStatus ? { nextLifecycleStatus } : {}),
57
- ...(snapshot.input.live?.linear?.status === "known" && snapshot.input.live.linear.issue?.stateName
58
- ? { currentLinearState: snapshot.input.live.linear.issue.stateName }
59
- : {}),
60
- });
61
- return;
62
- }
63
- await this.callbacks.failRunDuringReconciliation(snapshot.runLease.projectId, snapshot.runLease.linearIssueId, failedAction?.type === "mark_run_failed" && failedAction.threadId
64
- ? failedAction.threadId
65
- : threadId ?? `missing-thread-${snapshot.runLease.id}`, decision.reasons[0] ?? "Thread was not found during startup reconciliation", ...(failedAction?.type === "mark_run_failed" && failedAction.turnId ? [{ turnId: failedAction.turnId }] : []));
66
- }
67
- }
68
- }
@@ -1 +0,0 @@
1
- export {};
@@ -1,350 +0,0 @@
1
- export class ReconciliationEngine {
2
- reconcile(input) {
3
- const actions = [];
4
- const issue = input.issue;
5
- const policy = input.policy ?? {};
6
- const liveLinear = input.live?.linear ?? { status: "unknown" };
7
- const liveCodex = input.live?.codex ?? { status: "unknown" };
8
- const obligations = relevantObligations(issue, input.obligations ?? []);
9
- if (!issue.activeRun) {
10
- if (!issue.desiredStage) {
11
- return {
12
- outcome: "noop",
13
- reasons: ["issue has no active run and no desired stage"],
14
- actions,
15
- };
16
- }
17
- return {
18
- outcome: "launch",
19
- reasons: [`desired stage ${issue.desiredStage} is ready to launch`],
20
- actions: [
21
- {
22
- type: "launch_desired_stage",
23
- projectId: issue.projectId,
24
- linearIssueId: issue.linearIssueId,
25
- stage: issue.desiredStage,
26
- reason: "desired stage exists without an active run",
27
- },
28
- ],
29
- };
30
- }
31
- if (needsLinearState(issue, policy, liveLinear)) {
32
- actions.push({
33
- type: "read_linear_issue",
34
- projectId: issue.projectId,
35
- linearIssueId: issue.linearIssueId,
36
- reason: "active reconciliation needs the live Linear state",
37
- });
38
- }
39
- if (needsCodexState(issue.activeRun, liveCodex)) {
40
- actions.push({
41
- type: "read_codex_thread",
42
- projectId: issue.projectId,
43
- linearIssueId: issue.linearIssueId,
44
- runId: issue.activeRun.id,
45
- threadId: issue.activeRun.threadId,
46
- reason: "active reconciliation needs the live Codex thread",
47
- });
48
- }
49
- if (actions.length > 0) {
50
- return {
51
- outcome: "hydrate_live_state",
52
- reasons: ["reconciliation needs fresh live state before deciding"],
53
- actions,
54
- };
55
- }
56
- return reconcileActiveRun({
57
- issue,
58
- liveLinear,
59
- liveCodex,
60
- obligations,
61
- policy,
62
- });
63
- }
64
- }
65
- export function reconcileIssue(input) {
66
- return new ReconciliationEngine().reconcile(input);
67
- }
68
- function reconcileActiveRun(params) {
69
- const { issue, liveLinear, liveCodex, obligations, policy } = params;
70
- const run = issue.activeRun;
71
- const authoritativeStopState = resolveAuthoritativeStopState(liveLinear);
72
- if (authoritativeStopState) {
73
- return {
74
- outcome: "release",
75
- reasons: [`live Linear state is already ${authoritativeStopState.stateName}`],
76
- actions: [
77
- {
78
- type: "release_issue_ownership",
79
- projectId: issue.projectId,
80
- linearIssueId: issue.linearIssueId,
81
- runId: run.id,
82
- nextLifecycleStatus: authoritativeStopState.lifecycleStatus,
83
- reason: `live Linear state is already ${authoritativeStopState.stateName}`,
84
- },
85
- ],
86
- };
87
- }
88
- if (run.status === "queued") {
89
- return {
90
- outcome: "launch",
91
- reasons: ["queued run has not been materialized yet"],
92
- actions: [
93
- {
94
- type: "launch_desired_stage",
95
- projectId: issue.projectId,
96
- linearIssueId: issue.linearIssueId,
97
- stage: run.stage,
98
- runId: run.id,
99
- reason: "active run is queued and should be launched",
100
- },
101
- ],
102
- };
103
- }
104
- if (!run.threadId) {
105
- return failRun(issue, run, liveLinear, policy, "active run is missing a persisted thread id");
106
- }
107
- if (liveCodex.status === "error") {
108
- return {
109
- outcome: "continue",
110
- reasons: [liveCodex.errorMessage ?? "codex thread lookup failed"],
111
- actions: [
112
- {
113
- type: "await_codex_retry",
114
- projectId: issue.projectId,
115
- linearIssueId: issue.linearIssueId,
116
- runId: run.id,
117
- reason: liveCodex.errorMessage ?? "codex thread lookup failed",
118
- },
119
- ],
120
- };
121
- }
122
- if (liveCodex.status === "missing" || !liveCodex.thread) {
123
- return failRun(issue, run, liveLinear, policy, "thread was not found during reconciliation");
124
- }
125
- const latestTurn = latestThreadTurn(liveCodex.thread);
126
- const targetTurnId = latestTurn?.id ?? run.turnId;
127
- if (!latestTurn || latestTurn.status === "inProgress") {
128
- const actions = routePendingObligations(issue, run, obligations, liveCodex.thread.id, targetTurnId);
129
- actions.push({
130
- type: "keep_run_active",
131
- projectId: issue.projectId,
132
- linearIssueId: issue.linearIssueId,
133
- runId: run.id,
134
- reason: !latestTurn ? "thread has not produced a turn yet" : "latest turn is still in progress",
135
- });
136
- return {
137
- outcome: "continue",
138
- reasons: [!latestTurn ? "thread has no completed turns yet" : "latest turn is still in progress"],
139
- actions,
140
- };
141
- }
142
- if (latestTurn.status !== "completed") {
143
- return failRun(issue, run, liveLinear, policy, "thread completed reconciliation in a failed state", latestTurn.id);
144
- }
145
- const actions = [
146
- {
147
- type: "mark_run_completed",
148
- projectId: issue.projectId,
149
- linearIssueId: issue.linearIssueId,
150
- runId: run.id,
151
- threadId: liveCodex.thread.id,
152
- ...(latestTurn.id ? { turnId: latestTurn.id } : {}),
153
- reason: "latest turn completed successfully during reconciliation",
154
- },
155
- ];
156
- if (shouldAwaitHandoff(liveLinear, policy)) {
157
- actions.push({
158
- type: "clear_active_run",
159
- projectId: issue.projectId,
160
- linearIssueId: issue.linearIssueId,
161
- runId: run.id,
162
- nextLifecycleStatus: "paused",
163
- reason: "stage completed while the issue still matches the service-owned active Linear state",
164
- }, {
165
- type: "refresh_status_comment",
166
- projectId: issue.projectId,
167
- linearIssueId: issue.linearIssueId,
168
- runId: run.id,
169
- ...(issue.statusCommentId ? { commentId: issue.statusCommentId } : {}),
170
- mode: "awaiting_handoff",
171
- reason: "stage completed and should publish an awaiting handoff status",
172
- });
173
- return {
174
- outcome: "complete",
175
- reasons: ["stage completed and should pause for human handoff"],
176
- actions,
177
- };
178
- }
179
- actions.push({
180
- type: "release_issue_ownership",
181
- projectId: issue.projectId,
182
- linearIssueId: issue.linearIssueId,
183
- runId: run.id,
184
- nextLifecycleStatus: "completed",
185
- reason: "stage completed after the live Linear state moved on",
186
- });
187
- return {
188
- outcome: "release",
189
- reasons: ["stage completed and the live Linear state already moved on"],
190
- actions,
191
- };
192
- }
193
- function failRun(issue, run, liveLinear, policy, message, turnId) {
194
- const actions = [
195
- {
196
- type: "mark_run_failed",
197
- projectId: issue.projectId,
198
- linearIssueId: issue.linearIssueId,
199
- runId: run.id,
200
- ...(run.threadId ? { threadId: run.threadId } : {}),
201
- ...(turnId ? { turnId } : run.turnId ? { turnId: run.turnId } : {}),
202
- reason: message,
203
- },
204
- ];
205
- if (shouldFailBack(liveLinear, policy)) {
206
- actions.push({
207
- type: "sync_linear_failure",
208
- projectId: issue.projectId,
209
- linearIssueId: issue.linearIssueId,
210
- runId: run.id,
211
- ...(policy.activeLinearStateName ? { expectedStateName: policy.activeLinearStateName } : {}),
212
- ...(policy.fallbackLinearStateName ? { fallbackStateName: policy.fallbackLinearStateName } : {}),
213
- message,
214
- }, {
215
- type: "clear_active_run",
216
- projectId: issue.projectId,
217
- linearIssueId: issue.linearIssueId,
218
- runId: run.id,
219
- nextLifecycleStatus: "failed",
220
- reason: "run failed while PatchRelay still owned the expected active Linear state",
221
- });
222
- if (issue.statusCommentId) {
223
- actions.push({
224
- type: "refresh_status_comment",
225
- projectId: issue.projectId,
226
- linearIssueId: issue.linearIssueId,
227
- runId: run.id,
228
- commentId: issue.statusCommentId,
229
- mode: "failed",
230
- reason: "run failed and should refresh the service-owned status comment",
231
- });
232
- }
233
- return {
234
- outcome: "fail",
235
- reasons: [message],
236
- actions,
237
- };
238
- }
239
- actions.push({
240
- type: "release_issue_ownership",
241
- projectId: issue.projectId,
242
- linearIssueId: issue.linearIssueId,
243
- runId: run.id,
244
- nextLifecycleStatus: "failed",
245
- reason: "run failed after the live Linear state moved on",
246
- });
247
- return {
248
- outcome: "release",
249
- reasons: [message, "live Linear state no longer matches the expected service-owned active state"],
250
- actions,
251
- };
252
- }
253
- function routePendingObligations(issue, run, obligations, threadId, turnId) {
254
- if (!turnId) {
255
- return [];
256
- }
257
- const actions = [];
258
- for (const obligation of obligations) {
259
- const needsRouting = obligation.threadId !== threadId || obligation.turnId !== turnId;
260
- if (needsRouting) {
261
- actions.push({
262
- type: "route_obligation",
263
- projectId: issue.projectId,
264
- linearIssueId: issue.linearIssueId,
265
- obligationId: obligation.id,
266
- runId: run.id,
267
- threadId,
268
- turnId,
269
- reason: "pending obligation should target the latest live turn",
270
- });
271
- }
272
- actions.push({
273
- type: "deliver_obligation",
274
- projectId: issue.projectId,
275
- linearIssueId: issue.linearIssueId,
276
- obligationId: obligation.id,
277
- runId: run.id,
278
- threadId,
279
- turnId,
280
- reason: "pending obligation can be delivered to the active turn",
281
- });
282
- }
283
- return actions;
284
- }
285
- function needsLinearState(issue, policy, liveLinear) {
286
- if (!issue.activeRun) {
287
- return false;
288
- }
289
- if (!policy.activeLinearStateName && !policy.fallbackLinearStateName) {
290
- return false;
291
- }
292
- return liveLinear.status === "unknown";
293
- }
294
- function needsCodexState(run, liveCodex) {
295
- if (!run.threadId) {
296
- return false;
297
- }
298
- return liveCodex.status === "unknown";
299
- }
300
- function shouldFailBack(liveLinear, policy) {
301
- return matchesActiveLinearOwnership(liveLinear, policy);
302
- }
303
- function shouldAwaitHandoff(liveLinear, policy) {
304
- return matchesActiveLinearOwnership(liveLinear, policy);
305
- }
306
- function matchesActiveLinearOwnership(liveLinear, policy) {
307
- if (!policy.activeLinearStateName) {
308
- return true;
309
- }
310
- if (liveLinear.status !== "known") {
311
- return false;
312
- }
313
- return liveLinear.issue?.stateName === policy.activeLinearStateName;
314
- }
315
- function resolveAuthoritativeStopState(liveLinear) {
316
- if (liveLinear.status !== "known" || !liveLinear.issue?.stateName) {
317
- return undefined;
318
- }
319
- const stateName = liveLinear.issue.stateName.trim();
320
- const normalizedName = stateName.toLowerCase();
321
- const normalizedType = liveLinear.issue.stateType?.trim().toLowerCase();
322
- if (normalizedType === "completed" || normalizedName === "done" || normalizedName === "completed" || normalizedName === "complete") {
323
- return {
324
- stateName,
325
- lifecycleStatus: "completed",
326
- };
327
- }
328
- if (normalizedName === "human needed") {
329
- return {
330
- stateName,
331
- lifecycleStatus: "paused",
332
- };
333
- }
334
- return undefined;
335
- }
336
- function relevantObligations(issue, obligations) {
337
- const activeRunId = issue.activeRun?.id;
338
- return obligations.filter((obligation) => {
339
- if (obligation.status === "completed" || obligation.status === "cancelled") {
340
- return false;
341
- }
342
- if (activeRunId === undefined) {
343
- return false;
344
- }
345
- return obligation.runId === undefined || obligation.runId === activeRunId;
346
- });
347
- }
348
- function latestThreadTurn(thread) {
349
- return thread.turns.at(-1);
350
- }
@@ -1,135 +0,0 @@
1
- import { resolveWorkflowStageConfig } from "./workflow-policy.js";
2
- import { safeJsonParse } from "./utils.js";
3
- export async function buildReconciliationSnapshot(params) {
4
- const runLease = params.stores.runLeases.getRunLease(params.runLeaseId);
5
- if (!runLease) {
6
- return undefined;
7
- }
8
- const issueControl = params.stores.issueControl.getIssueControl(runLease.projectId, runLease.linearIssueId);
9
- if (!issueControl) {
10
- return undefined;
11
- }
12
- const workspaceOwnership = params.stores.workspaceOwnership.getWorkspaceOwnership(runLease.workspaceOwnershipId);
13
- const project = params.config.projects.find((candidate) => candidate.id === runLease.projectId);
14
- const workflowConfig = project ? resolveWorkflowStageConfig(project, runLease.stage, issueControl.selectedWorkflowId) : undefined;
15
- const liveLinear = project
16
- ? await params.linearProvider
17
- .forProject(runLease.projectId)
18
- .then((linear) => linear
19
- ? linear.getIssue(runLease.linearIssueId).then((issue) => issue.stateName
20
- ? {
21
- status: "known",
22
- issue: {
23
- id: issue.id,
24
- stateName: issue.stateName,
25
- ...(() => {
26
- const currentState = issue.workflowStates?.find((state) => state.name === issue.stateName);
27
- return currentState?.type ? { stateType: currentState.type } : {};
28
- })(),
29
- },
30
- }
31
- : ({ status: "unknown" }))
32
- : ({ status: "unknown" }))
33
- .catch(() => ({ status: "unknown" }))
34
- : ({ status: "unknown" });
35
- const liveCodex = runLease.threadId
36
- ? await hydrateLiveCodexState({
37
- codex: params.codex,
38
- threadId: runLease.threadId,
39
- ...(workspaceOwnership?.worktreePath ? { cwd: workspaceOwnership.worktreePath } : {}),
40
- })
41
- : ({ status: "unknown" });
42
- const obligations = params.stores.obligations
43
- .listPendingObligations({ runLeaseId: runLease.id, includeInProgress: true })
44
- .map((obligation) => {
45
- const payload = safeJsonParse(obligation.payloadJson);
46
- return {
47
- id: obligation.id,
48
- kind: obligation.kind,
49
- status: obligation.status,
50
- ...(obligation.runLeaseId !== undefined ? { runId: obligation.runLeaseId } : {}),
51
- ...(obligation.threadId ? { threadId: obligation.threadId } : {}),
52
- ...(obligation.turnId ? { turnId: obligation.turnId } : {}),
53
- ...(payload !== undefined ? { payload } : {}),
54
- };
55
- });
56
- return {
57
- issueControl,
58
- runLease,
59
- ...(workspaceOwnership ? { workspaceOwnership } : {}),
60
- input: {
61
- issue: {
62
- projectId: runLease.projectId,
63
- linearIssueId: runLease.linearIssueId,
64
- ...(issueControl.desiredStage ? { desiredStage: issueControl.desiredStage } : {}),
65
- lifecycleStatus: issueControl.lifecycleStatus,
66
- ...(issueControl.serviceOwnedCommentId ? { statusCommentId: issueControl.serviceOwnedCommentId } : {}),
67
- activeRun: {
68
- id: runLease.id,
69
- stage: runLease.stage,
70
- status: runLease.status,
71
- ...(runLease.threadId ? { threadId: runLease.threadId } : {}),
72
- ...(runLease.turnId ? { turnId: runLease.turnId } : {}),
73
- ...(runLease.parentThreadId ? { parentThreadId: runLease.parentThreadId } : {}),
74
- },
75
- },
76
- ...(obligations.length > 0 ? { obligations } : {}),
77
- ...(workflowConfig
78
- ? {
79
- policy: {
80
- ...(workflowConfig.activeState ? { activeLinearStateName: workflowConfig.activeState } : {}),
81
- ...(workflowConfig.fallbackState ? { fallbackLinearStateName: workflowConfig.fallbackState } : {}),
82
- },
83
- }
84
- : {}),
85
- live: {
86
- linear: liveLinear,
87
- codex: liveCodex,
88
- },
89
- },
90
- };
91
- }
92
- async function hydrateLiveCodexState(params) {
93
- try {
94
- const thread = await params.codex.readThread(params.threadId, true);
95
- if (latestThreadTurn(thread)?.status === "interrupted" && params.cwd) {
96
- const resumedThread = await tryResumeThread(params.codex, params.threadId, params.cwd);
97
- if (resumedThread) {
98
- return { status: "found", thread: resumedThread };
99
- }
100
- }
101
- return { status: "found", thread };
102
- }
103
- catch (error) {
104
- const mapped = mapCodexReadFailure(error);
105
- if (mapped.status === "missing" && params.cwd) {
106
- const resumedThread = await tryResumeThread(params.codex, params.threadId, params.cwd);
107
- if (resumedThread) {
108
- return { status: "found", thread: resumedThread };
109
- }
110
- }
111
- return mapped;
112
- }
113
- }
114
- async function tryResumeThread(codex, threadId, cwd) {
115
- try {
116
- return await codex.resumeThread(threadId, cwd);
117
- }
118
- catch {
119
- return undefined;
120
- }
121
- }
122
- function latestThreadTurn(thread) {
123
- return thread.turns.at(-1);
124
- }
125
- function mapCodexReadFailure(error) {
126
- const message = error instanceof Error ? error.message : String(error);
127
- const normalized = message.trim().toLowerCase();
128
- if (normalized.includes("not found") || normalized.includes("missing")) {
129
- return { status: "missing" };
130
- }
131
- return {
132
- status: "error",
133
- errorMessage: message,
134
- };
135
- }
@@ -1 +0,0 @@
1
- export {};