Repository navigation
Expand file tree
/
Copy pathRunFinalizationService.ts
More file actions
130 lines (121 loc) · 5.09 KB
/
Copy pathRunFinalizationService.ts
File metadata and controls
130 lines (121 loc) · 5.09 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
import { CheckpointScopeId, ProjectId, RunId, ThreadId } from "@t3tools/contracts";
import * as Context from "effect/Context";
import * as Effect from "effect/Effect";
import * as Layer from "effect/Layer";
import * as Schema from "effect/Schema";
import * as CodeIntelligence from "../project/knowledge/CodeIntelligence.ts";
import * as PullRequestService from "../pullRequest/PullRequestService.ts";
import * as VcsStatusBroadcaster from "../vcs/VcsStatusBroadcaster.ts";
import * as WorkspaceEntries from "../workspace/WorkspaceEntries.ts";
import * as CheckpointCapture from "./CheckpointCaptureService.ts";
import * as ProjectionStore from "./ProjectionStore.ts";
export class RunFinalizationError extends Schema.TaggedError<RunFinalizationError>()(
"RunFinalizationError",
{
threadId: ThreadId,
runId: RunId,
scopeId: CheckpointScopeId,
operation: Schema.Literals(["capture-checkpoint", "refresh-workspace"]),
cause: Schema.Defect(),
},
) {}
export class RunFinalizationRefreshError extends Schema.TaggedError<RunFinalizationRefreshError>()(
"RunFinalizationRefreshError",
{ cwd: Schema.String, cause: Schema.Defect() },
) {}
export class RunFinalizationObserver extends Context.Reference<{
readonly refreshAfterTurn: (projectId: ProjectId) => Effect.Effect<void>;
readonly refresh: (input: {
readonly cwd: string;
readonly threadId: ThreadId;
readonly runId: RunId;
}) => Effect.Effect<void, RunFinalizationRefreshError>;
}>("t3/orchestration-v2/RunFinalizationObserver", {
defaultValue: () => ({ refresh: () => Effect.void, refreshAfterTurn: () => Effect.void }),
}) {}
export class RunFinalizationService extends Context.Service<
RunFinalizationService,
{
readonly finalize: (input: {
readonly threadId: ThreadId;
readonly runId: RunId;
readonly scopeId: CheckpointScopeId;
}) => Effect.Effect<void, RunFinalizationError>;
}
>()("t3/orchestration-v2/RunFinalizationService") {}
const make = Effect.gen(function* () {
const checkpointCapture = yield* CheckpointCapture.CheckpointCaptureServiceV2;
const projections = yield* ProjectionStore.ProjectionStoreV2;
const observer = yield* RunFinalizationObserver;
const finalize: RunFinalizationService["Service"]["finalize"] = Effect.fn(
"RunFinalizationService.finalize",
)(function* (input) {
yield* checkpointCapture
.execute(input)
.pipe(
Effect.mapError(
(cause) => new RunFinalizationError({ ...input, operation: "capture-checkpoint", cause }),
),
);
const projection = yield* projections
.getCheckpointContext(input.threadId)
.pipe(
Effect.mapError(
(cause) => new RunFinalizationError({ ...input, operation: "refresh-workspace", cause }),
),
);
const cwd = projection.checkpointScopes.find((scope) => scope.id === input.scopeId)?.cwd;
if (cwd !== undefined) {
// Retrying a display refresh after the checkpoint is durable blocks the next turn.
yield* observer.refresh({ cwd, threadId: input.threadId, runId: input.runId }).pipe(
Effect.catchTags({
RunFinalizationRefreshError: (cause) =>
Effect.logWarning("Failed to refresh workspace after checkpoint capture", {
...input,
cause,
}),
}),
);
}
});
return RunFinalizationService.of({ finalize });
});
export const layer = Layer.effect(RunFinalizationService, make);
export const layerObserver = Layer.effect(
RunFinalizationObserver,
Effect.gen(function* () {
const workspaceEntries = yield* WorkspaceEntries.WorkspaceEntries;
const vcsStatus = yield* VcsStatusBroadcaster.VcsStatusBroadcaster;
const projections = yield* ProjectionStore.ProjectionStoreV2;
const pullRequests = yield* PullRequestService.PullRequestService;
const codeIntelligence = yield* CodeIntelligence.CodeIntelligence;
return {
refreshAfterTurn: pullRequests.refreshAfterTurn,
refresh: Effect.fn("RunFinalizationService.refresh")(
function* ({ cwd, threadId, runId }) {
// The index builds in the code intelligence service, off this turn's lane.
yield* codeIntelligence.refreshIfIndexed(cwd);
const [, local] = yield* Effect.all(
[workspaceEntries.refresh(cwd), vcsStatus.refreshLocalStatus(cwd)],
{ concurrency: "unbounded" },
);
if (local.refName === null || local.isDefaultRef) return;
const thread = yield* projections.getThreadShell(threadId);
if (!thread || thread.branch !== local.refName) return;
if (thread.activeRunId !== null && thread.activeRunId !== runId) return;
yield* vcsStatus.refreshPullRequestStatus(cwd).pipe(
Effect.catch((error) =>
Effect.logWarning("failed to refresh pull request status after run completion", {
threadId,
cwd,
detail: error.message,
}),
),
);
},
(effect, { cwd }) =>
effect.pipe(Effect.mapError((cause) => new RunFinalizationRefreshError({ cwd, cause }))),
),
};
}),
);