@h-rig/core 0.0.6-alpha.155 → 0.0.6-alpha.156
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/src/config.d.ts +1 -1
- package/dist/src/config.js +4 -91
- package/dist/src/define-plugin.d.ts +11 -7
- package/dist/src/define-plugin.js +4 -91
- package/dist/src/index.d.ts +1 -11
- package/dist/src/index.js +33 -3704
- package/dist/src/plugin-host.d.ts +25 -18
- package/dist/src/plugin-host.js +28 -149
- package/dist/src/plugin-runtime.d.ts +82 -51
- package/dist/src/project-plugins.d.ts +66 -0
- package/dist/src/project-plugins.js +596 -0
- package/dist/src/task-io.d.ts +54 -0
- package/dist/src/task-io.js +707 -0
- package/package.json +8 -20
- package/dist/src/dependencyGraph.d.ts +0 -43
- package/dist/src/dependencyGraph.js +0 -703
- package/dist/src/engineReadModelReducer.d.ts +0 -12
- package/dist/src/engineReadModelReducer.js +0 -1784
- package/dist/src/rig-init-builder.d.ts +0 -30
- package/dist/src/rig-init-builder.js +0 -61
- package/dist/src/rigSelectors.d.ts +0 -220
- package/dist/src/rigSelectors.js +0 -414
- package/dist/src/rollups.d.ts +0 -6
- package/dist/src/rollups.js +0 -377
- package/dist/src/stageResolve.d.ts +0 -77
- package/dist/src/stageResolve.js +0 -361
- package/dist/src/taskGraph.d.ts +0 -64
- package/dist/src/taskGraph.js +0 -377
- package/dist/src/taskGraphCodes.d.ts +0 -3
- package/dist/src/taskGraphCodes.js +0 -26
- package/dist/src/taskGraphLayout.d.ts +0 -61
- package/dist/src/taskGraphLayout.js +0 -397
- package/dist/src/taskScore.d.ts +0 -17
- package/dist/src/taskScore.js +0 -49
package/dist/src/index.js
CHANGED
|
@@ -1,97 +1,10 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
// packages/core/src/define-plugin.ts
|
|
3
3
|
import { Schema } from "effect";
|
|
4
|
-
import { RigPlugin } from "@rig/contracts";
|
|
5
|
-
function definePlugin(
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
return validated;
|
|
9
|
-
}
|
|
10
|
-
const declaredValidators = new Map((validated.contributes?.validators ?? []).map((v) => [v.id, v]));
|
|
11
|
-
const runtimeValidators = new Map((runtime.validators ?? []).map((v) => [v.id, v]));
|
|
12
|
-
for (const v of runtimeValidators.values()) {
|
|
13
|
-
const metadata = declaredValidators.get(v.id);
|
|
14
|
-
if (!metadata) {
|
|
15
|
-
throw new Error(`definePlugin(${validated.name}): executable validator "${v.id}" has no matching metadata entry in contributes.validators`);
|
|
16
|
-
}
|
|
17
|
-
if (metadata.category !== v.category) {
|
|
18
|
-
throw new Error(`definePlugin(${validated.name}): executable validator "${v.id}" category "${v.category}" does not match metadata category "${metadata.category}"`);
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
if (runtime.validators) {
|
|
22
|
-
for (const v of declaredValidators.values()) {
|
|
23
|
-
if (!runtimeValidators.has(v.id)) {
|
|
24
|
-
throw new Error(`definePlugin(${validated.name}): validator metadata "${v.id}" has no runtime implementation`);
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
const declaredSources = new Map((validated.contributes?.taskSources ?? []).map((s) => [s.id, s]));
|
|
29
|
-
const runtimeSources = new Map((runtime.taskSources ?? []).map((s) => [s.id, s]));
|
|
30
|
-
for (const s of runtimeSources.values()) {
|
|
31
|
-
const metadata = declaredSources.get(s.id);
|
|
32
|
-
if (!metadata) {
|
|
33
|
-
throw new Error(`definePlugin(${validated.name}): executable task source "${s.id}" has no matching metadata entry in contributes.taskSources`);
|
|
34
|
-
}
|
|
35
|
-
if (metadata.kind !== s.kind) {
|
|
36
|
-
throw new Error(`definePlugin(${validated.name}): executable task source "${s.id}" kind "${s.kind}" does not match metadata kind "${metadata.kind}"`);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
if (runtime.taskSources) {
|
|
40
|
-
for (const s of declaredSources.values()) {
|
|
41
|
-
if (!runtimeSources.has(s.id)) {
|
|
42
|
-
throw new Error(`definePlugin(${validated.name}): task source metadata "${s.id}" has no runtime implementation`);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
const declaredHooks = new Map((validated.contributes?.hooks ?? []).map((h) => [h.id, h]));
|
|
47
|
-
for (const hookId of Object.keys(runtime.hooks ?? {})) {
|
|
48
|
-
const metadata = declaredHooks.get(hookId);
|
|
49
|
-
if (!metadata) {
|
|
50
|
-
throw new Error(`definePlugin(${validated.name}): typed hook "${hookId}" has no matching metadata entry in contributes.hooks`);
|
|
51
|
-
}
|
|
52
|
-
if (metadata.command) {
|
|
53
|
-
throw new Error(`definePlugin(${validated.name}): hook "${hookId}" has both a typed implementation and a command string \u2014 pick one`);
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
if (runtime.hooks) {
|
|
57
|
-
for (const h of declaredHooks.values()) {
|
|
58
|
-
if (!runtime.hooks[h.id] && !h.command) {
|
|
59
|
-
throw new Error(`definePlugin(${validated.name}): hook metadata "${h.id}" has no implementation (typed function or command)`);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
const declaredCapabilities = new Map((validated.contributes?.capabilities ?? []).map((capability) => [capability.id, capability]));
|
|
64
|
-
for (const capability of runtime.featureCapabilities ?? []) {
|
|
65
|
-
if (!declaredCapabilities.has(capability.id)) {
|
|
66
|
-
throw new Error(`definePlugin(${validated.name}): executable capability "${capability.id}" has no matching metadata entry in contributes.capabilities`);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
const declaredPanels = new Map((validated.contributes?.panels ?? []).map((panel) => [panel.id, panel]));
|
|
70
|
-
for (const panel of runtime.panels ?? []) {
|
|
71
|
-
const metadata = declaredPanels.get(panel.id);
|
|
72
|
-
if (!metadata) {
|
|
73
|
-
throw new Error(`definePlugin(${validated.name}): executable panel "${panel.id}" has no matching metadata entry in contributes.panels`);
|
|
74
|
-
}
|
|
75
|
-
if (metadata.slot !== panel.slot) {
|
|
76
|
-
throw new Error(`definePlugin(${validated.name}): executable panel "${panel.id}" slot "${panel.slot}" does not match metadata slot "${metadata.slot}"`);
|
|
77
|
-
}
|
|
78
|
-
if (metadata.capabilityId !== panel.capabilityId) {
|
|
79
|
-
throw new Error(`definePlugin(${validated.name}): executable panel "${panel.id}" capabilityId "${panel.capabilityId ?? "(none)"}" does not match metadata capabilityId "${metadata.capabilityId ?? "(none)"}"`);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
const declaredBlockerClassifiers = new Map((validated.contributes?.blockerClassifiers ?? []).map((classifier) => [classifier.id, classifier]));
|
|
83
|
-
for (const classifier of runtime.blockerClassifiers ?? []) {
|
|
84
|
-
if (!declaredBlockerClassifiers.has(classifier.id)) {
|
|
85
|
-
throw new Error(`definePlugin(${validated.name}): executable blocker classifier "${classifier.id}" has no matching metadata entry in contributes.blockerClassifiers`);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
const declaredCliCommands = new Map((validated.contributes?.cliCommands ?? []).map((command) => [command.id, command]));
|
|
89
|
-
for (const command of runtime.cliCommands ?? []) {
|
|
90
|
-
if (!declaredCliCommands.has(command.id)) {
|
|
91
|
-
throw new Error(`definePlugin(${validated.name}): executable cli command "${command.id}" has no matching metadata entry in contributes.cliCommands`);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
return { ...validated, ...runtime };
|
|
4
|
+
import { RigPlugin as RigPluginManifest } from "@rig/contracts";
|
|
5
|
+
function definePlugin(plugin) {
|
|
6
|
+
Schema.decodeUnknownSync(RigPluginManifest)(plugin);
|
|
7
|
+
return plugin;
|
|
95
8
|
}
|
|
96
9
|
// packages/core/src/define-config.ts
|
|
97
10
|
import { Schema as Schema2 } from "effect";
|
|
@@ -144,102 +57,8 @@ function assertUniquePluginNames(plugins) {
|
|
|
144
57
|
seen.add(plugin.name);
|
|
145
58
|
}
|
|
146
59
|
}
|
|
147
|
-
function assertRuntimeMatchesMetadata(plugin) {
|
|
148
|
-
const declaredValidators = new Map((plugin.contributes?.validators ?? []).map((validator) => [validator.id, validator]));
|
|
149
|
-
const runtimeValidators = new Map((plugin?.validators ?? []).map((validator) => [validator.id, validator]));
|
|
150
|
-
for (const validator of runtimeValidators.values()) {
|
|
151
|
-
const metadata = declaredValidators.get(validator.id);
|
|
152
|
-
if (!metadata) {
|
|
153
|
-
throw new Error(`plugin "${plugin.name}" executable validator "${validator.id}" has no matching metadata entry in contributes.validators`);
|
|
154
|
-
}
|
|
155
|
-
if (metadata.category !== validator.category) {
|
|
156
|
-
throw new Error(`plugin "${plugin.name}" executable validator "${validator.id}" category "${validator.category}" does not match metadata category "${metadata.category}"`);
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
if (plugin?.validators) {
|
|
160
|
-
for (const validator of declaredValidators.values()) {
|
|
161
|
-
if (!runtimeValidators.has(validator.id)) {
|
|
162
|
-
throw new Error(`plugin "${plugin.name}" validator metadata "${validator.id}" has no runtime implementation`);
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
const declaredTaskSources = new Map((plugin.contributes?.taskSources ?? []).map((source) => [source.id, source]));
|
|
167
|
-
const runtimeTaskSources = new Map((plugin?.taskSources ?? []).map((source) => [source.id, source]));
|
|
168
|
-
for (const source of runtimeTaskSources.values()) {
|
|
169
|
-
const metadata = declaredTaskSources.get(source.id);
|
|
170
|
-
if (!metadata) {
|
|
171
|
-
throw new Error(`plugin "${plugin.name}" executable task source "${source.id}" has no matching metadata entry in contributes.taskSources`);
|
|
172
|
-
}
|
|
173
|
-
if (metadata.kind !== source.kind) {
|
|
174
|
-
throw new Error(`plugin "${plugin.name}" executable task source "${source.id}" kind "${source.kind}" does not match metadata kind "${metadata.kind}"`);
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
if (plugin?.taskSources) {
|
|
178
|
-
for (const source of declaredTaskSources.values()) {
|
|
179
|
-
if (!runtimeTaskSources.has(source.id)) {
|
|
180
|
-
throw new Error(`plugin "${plugin.name}" task source metadata "${source.id}" has no runtime implementation`);
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
const declaredHooks = new Map((plugin.contributes?.hooks ?? []).map((hook) => [hook.id, hook]));
|
|
185
|
-
const runtimeHooks = plugin?.hooks;
|
|
186
|
-
for (const hookId of Object.keys(runtimeHooks ?? {})) {
|
|
187
|
-
const metadata = declaredHooks.get(hookId);
|
|
188
|
-
if (!metadata) {
|
|
189
|
-
throw new Error(`plugin "${plugin.name}" typed hook "${hookId}" has no matching metadata entry in contributes.hooks`);
|
|
190
|
-
}
|
|
191
|
-
if (metadata.command) {
|
|
192
|
-
throw new Error(`plugin "${plugin.name}" hook "${hookId}" has both a typed implementation and a command string \u2014 pick one`);
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
if (runtimeHooks) {
|
|
196
|
-
for (const hook of declaredHooks.values()) {
|
|
197
|
-
if (!runtimeHooks[hook.id] && !hook.command) {
|
|
198
|
-
throw new Error(`plugin "${plugin.name}" hook metadata "${hook.id}" has no implementation (typed function or command)`);
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
const declaredCapabilities = new Map((plugin.contributes?.capabilities ?? []).map((capability) => [capability.id, capability]));
|
|
203
|
-
const runtimeCapabilities = new Map((plugin?.featureCapabilities ?? []).map((capability) => [capability.id, capability]));
|
|
204
|
-
for (const capability of runtimeCapabilities.values()) {
|
|
205
|
-
if (!declaredCapabilities.has(capability.id)) {
|
|
206
|
-
throw new Error(`plugin "${plugin.name}" executable capability "${capability.id}" has no matching metadata entry in contributes.capabilities`);
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
const declaredPanels = new Map((plugin.contributes?.panels ?? []).map((panel) => [panel.id, panel]));
|
|
210
|
-
const runtimePanels = new Map((plugin?.panels ?? []).map((panel) => [panel.id, panel]));
|
|
211
|
-
for (const panel of runtimePanels.values()) {
|
|
212
|
-
const metadata = declaredPanels.get(panel.id);
|
|
213
|
-
if (!metadata) {
|
|
214
|
-
throw new Error(`plugin "${plugin.name}" executable panel "${panel.id}" has no matching metadata entry in contributes.panels`);
|
|
215
|
-
}
|
|
216
|
-
if (metadata.slot !== panel.slot) {
|
|
217
|
-
throw new Error(`plugin "${plugin.name}" executable panel "${panel.id}" slot "${panel.slot}" does not match metadata slot "${metadata.slot}"`);
|
|
218
|
-
}
|
|
219
|
-
if (metadata.capabilityId !== panel.capabilityId) {
|
|
220
|
-
throw new Error(`plugin "${plugin.name}" executable panel "${panel.id}" capabilityId "${panel.capabilityId ?? "(none)"}" does not match metadata capabilityId "${metadata.capabilityId ?? "(none)"}"`);
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
const declaredBlockerClassifiers = new Map((plugin.contributes?.blockerClassifiers ?? []).map((classifier) => [classifier.id, classifier]));
|
|
224
|
-
const runtimeBlockerClassifiers = new Map((plugin?.blockerClassifiers ?? []).map((classifier) => [classifier.id, classifier]));
|
|
225
|
-
for (const classifier of runtimeBlockerClassifiers.values()) {
|
|
226
|
-
if (!declaredBlockerClassifiers.has(classifier.id)) {
|
|
227
|
-
throw new Error(`plugin "${plugin.name}" executable blocker classifier "${classifier.id}" has no matching metadata entry in contributes.blockerClassifiers`);
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
const declaredCliCommands = new Map((plugin.contributes?.cliCommands ?? []).map((command) => [command.id, command]));
|
|
231
|
-
const runtimeCliCommands = new Map((plugin?.cliCommands ?? []).map((command) => [command.id, command]));
|
|
232
|
-
for (const command of runtimeCliCommands.values()) {
|
|
233
|
-
if (!declaredCliCommands.has(command.id)) {
|
|
234
|
-
throw new Error(`plugin "${plugin.name}" executable cli command "${command.id}" has no matching metadata entry in contributes.cliCommands`);
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
60
|
function createPluginHost(plugins) {
|
|
239
61
|
assertUniquePluginNames(plugins);
|
|
240
|
-
for (const plugin of plugins) {
|
|
241
|
-
assertRuntimeMatchesMetadata(plugin);
|
|
242
|
-
}
|
|
243
62
|
const validators = [];
|
|
244
63
|
const hooks = [];
|
|
245
64
|
const skills = [];
|
|
@@ -251,38 +70,15 @@ function createPluginHost(plugins) {
|
|
|
251
70
|
const capabilities = [];
|
|
252
71
|
const panels = [];
|
|
253
72
|
const blockerClassifiers = [];
|
|
73
|
+
const sessionExtensions = [];
|
|
254
74
|
const stages = [];
|
|
255
75
|
const stageMutations = [];
|
|
256
76
|
const stageExecutors = {};
|
|
257
|
-
const executableValidators = [];
|
|
258
|
-
const executableTaskSources = [];
|
|
259
|
-
const executableCapabilities = [];
|
|
260
|
-
const executablePanels = [];
|
|
261
|
-
const executableBlockerClassifiers = [];
|
|
262
|
-
const executableCliCommands = [];
|
|
263
77
|
for (const plugin of plugins) {
|
|
264
78
|
const c = plugin.contributes;
|
|
265
79
|
if (!c)
|
|
266
80
|
continue;
|
|
267
81
|
const pluginName = plugin.name;
|
|
268
|
-
if (plugin?.validators) {
|
|
269
|
-
executableValidators.push(...plugin.validators.map((item) => ({ item, pluginName })));
|
|
270
|
-
}
|
|
271
|
-
if (plugin?.taskSources) {
|
|
272
|
-
executableTaskSources.push(...plugin.taskSources.map((item) => ({ item, pluginName })));
|
|
273
|
-
}
|
|
274
|
-
if (plugin?.featureCapabilities) {
|
|
275
|
-
executableCapabilities.push(...plugin.featureCapabilities.map((item) => ({ item, pluginName })));
|
|
276
|
-
}
|
|
277
|
-
if (plugin?.panels) {
|
|
278
|
-
executablePanels.push(...plugin.panels.map((item) => ({ item, pluginName })));
|
|
279
|
-
}
|
|
280
|
-
if (plugin?.blockerClassifiers) {
|
|
281
|
-
executableBlockerClassifiers.push(...plugin.blockerClassifiers.map((item) => ({ item, pluginName })));
|
|
282
|
-
}
|
|
283
|
-
if (plugin?.cliCommands) {
|
|
284
|
-
executableCliCommands.push(...plugin.cliCommands.map((item) => ({ item, pluginName })));
|
|
285
|
-
}
|
|
286
82
|
if (c.validators)
|
|
287
83
|
validators.push(...c.validators.map((item) => ({ item, pluginName })));
|
|
288
84
|
if (c.hooks)
|
|
@@ -305,27 +101,17 @@ function createPluginHost(plugins) {
|
|
|
305
101
|
panels.push(...c.panels.map((item) => ({ item, pluginName })));
|
|
306
102
|
if (c.blockerClassifiers)
|
|
307
103
|
blockerClassifiers.push(...c.blockerClassifiers.map((item) => ({ item, pluginName })));
|
|
308
|
-
if (c.
|
|
309
|
-
|
|
104
|
+
if (c.sessionExtensions)
|
|
105
|
+
sessionExtensions.push(...c.sessionExtensions.map((item) => ({ item, pluginName })));
|
|
310
106
|
if (c.stageMutations)
|
|
311
107
|
stageMutations.push(...c.stageMutations.map((item) => ({ item, pluginName })));
|
|
312
|
-
if (
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
indexById(executablePanels, "executablePanel");
|
|
319
|
-
indexById(executableBlockerClassifiers, "executableBlockerClassifier");
|
|
320
|
-
indexById(executableCliCommands, "executableCliCommand");
|
|
321
|
-
const taskSourceFactoryByKind = new Map;
|
|
322
|
-
const taskSourceKindRegistrant = new Map;
|
|
323
|
-
for (const { item, pluginName } of executableTaskSources) {
|
|
324
|
-
if (taskSourceFactoryByKind.has(item.kind)) {
|
|
325
|
-
throw new Error(`duplicate task source kind "${item.kind}": registered by plugins "${taskSourceKindRegistrant.get(item.kind)}" and "${pluginName}"`);
|
|
108
|
+
if (c.stages) {
|
|
109
|
+
for (const stage of c.stages) {
|
|
110
|
+
stages.push({ item: stage, pluginName });
|
|
111
|
+
if (stage.run)
|
|
112
|
+
stageExecutors[stage.id] = stage.run;
|
|
113
|
+
}
|
|
326
114
|
}
|
|
327
|
-
taskSourceFactoryByKind.set(item.kind, item);
|
|
328
|
-
taskSourceKindRegistrant.set(item.kind, pluginName);
|
|
329
115
|
}
|
|
330
116
|
const validatorMap = indexById(validators, "validator");
|
|
331
117
|
const hookMap = indexById(hooks, "hook");
|
|
@@ -338,6 +124,16 @@ function createPluginHost(plugins) {
|
|
|
338
124
|
const capabilityMap = indexById(capabilities, "capability");
|
|
339
125
|
const panelMap = indexById(panels, "panel");
|
|
340
126
|
const blockerClassifierMap = indexById(blockerClassifiers, "blockerClassifier");
|
|
127
|
+
indexById(sessionExtensions, "sessionExtension");
|
|
128
|
+
const taskSourceFactoryByKind = new Map;
|
|
129
|
+
const taskSourceKindRegistrant = new Map;
|
|
130
|
+
for (const { item, pluginName } of taskSources) {
|
|
131
|
+
if (taskSourceFactoryByKind.has(item.kind)) {
|
|
132
|
+
throw new Error(`duplicate task source kind "${item.kind}": registered by plugins "${taskSourceKindRegistrant.get(item.kind)}" and "${pluginName}"`);
|
|
133
|
+
}
|
|
134
|
+
taskSourceFactoryByKind.set(item.kind, item);
|
|
135
|
+
taskSourceKindRegistrant.set(item.kind, pluginName);
|
|
136
|
+
}
|
|
341
137
|
const allValidators = validators.map((c) => c.item);
|
|
342
138
|
const allHooks = hooks.map((c) => c.item);
|
|
343
139
|
const allSkills = skills.map((c) => c.item);
|
|
@@ -351,12 +147,7 @@ function createPluginHost(plugins) {
|
|
|
351
147
|
const allCapabilities = capabilities.map((c) => c.item);
|
|
352
148
|
const allPanels = panels.map((c) => c.item);
|
|
353
149
|
const allBlockerClassifiers = blockerClassifiers.map((c) => c.item);
|
|
354
|
-
const
|
|
355
|
-
const allExecutableTaskSources = executableTaskSources.map((c) => c.item);
|
|
356
|
-
const allExecutableCapabilities = executableCapabilities.map((c) => c.item);
|
|
357
|
-
const allExecutablePanels = executablePanels.map((c) => c.item);
|
|
358
|
-
const allExecutableBlockerClassifiers = executableBlockerClassifiers.map((c) => c.item);
|
|
359
|
-
const allExecutableCliCommands = executableCliCommands.map((c) => c.item);
|
|
150
|
+
const allSessionExtensions = sessionExtensions.map((c) => c.item);
|
|
360
151
|
const executableCliCommandByName = new Map;
|
|
361
152
|
const registerExecutableCliCommandSelector = (selector, contribution) => {
|
|
362
153
|
const existing = executableCliCommandByName.get(selector);
|
|
@@ -365,7 +156,7 @@ function createPluginHost(plugins) {
|
|
|
365
156
|
}
|
|
366
157
|
executableCliCommandByName.set(selector, contribution);
|
|
367
158
|
};
|
|
368
|
-
for (const contribution of
|
|
159
|
+
for (const contribution of cliCommands) {
|
|
369
160
|
const command = contribution.item;
|
|
370
161
|
const family = command.family ?? command.id;
|
|
371
162
|
registerExecutableCliCommandSelector(command.id, contribution);
|
|
@@ -400,3485 +191,23 @@ function createPluginHost(plugins) {
|
|
|
400
191
|
listStages: () => allStages,
|
|
401
192
|
listStageMutations: () => allStageMutations,
|
|
402
193
|
listStageExecutors: () => stageExecutors,
|
|
403
|
-
listExecutableValidators: () =>
|
|
404
|
-
listExecutableTaskSources: () =>
|
|
405
|
-
listExecutableCapabilities: () =>
|
|
406
|
-
listExecutablePanels: () =>
|
|
407
|
-
listExecutableBlockerClassifiers: () =>
|
|
408
|
-
listExecutableCliCommands: () =>
|
|
194
|
+
listExecutableValidators: () => allValidators.filter((v) => typeof v.run === "function"),
|
|
195
|
+
listExecutableTaskSources: () => allTaskSources,
|
|
196
|
+
listExecutableCapabilities: () => allCapabilities.filter((c) => typeof c.run === "function"),
|
|
197
|
+
listExecutablePanels: () => allPanels.filter((p) => typeof p.produce === "function"),
|
|
198
|
+
listExecutableBlockerClassifiers: () => allBlockerClassifiers,
|
|
199
|
+
listExecutableCliCommands: () => allCliCommands,
|
|
200
|
+
listSessionExtensions: () => allSessionExtensions,
|
|
409
201
|
resolveExecutableCliCommand: (requested) => executableCliCommandByName.get(requested)?.item,
|
|
410
202
|
resolveTaskSourceFactoryByKind: (kind) => taskSourceFactoryByKind.get(kind)
|
|
411
203
|
};
|
|
412
204
|
}
|
|
413
|
-
// packages/core/src/rig-init-builder.ts
|
|
414
|
-
function buildRigInitConfigSource(input) {
|
|
415
|
-
const lines = [`import { defineConfig } from "@rig/core/config";`];
|
|
416
|
-
if (input.useStandardPlugin) {
|
|
417
|
-
lines.push(`import { standardPlugins } from "@rig/standard-plugin/bundle";`);
|
|
418
|
-
if (input.taskSource.kind === "github-issues") {
|
|
419
|
-
lines.push(`import { createStateGitHubCredentialProvider } from "@rig/standard-plugin";`);
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
lines.push(``, `export default defineConfig({`);
|
|
423
|
-
const projectRepo = input.projectRepo ?? (input.taskSource.kind === "github-issues" ? `${input.taskSource.owner}/${input.taskSource.repo}` : undefined);
|
|
424
|
-
lines.push(projectRepo ? ` project: { name: ${JSON.stringify(input.projectName)}, repo: ${JSON.stringify(projectRepo)} },` : ` project: { name: ${JSON.stringify(input.projectName)} },`);
|
|
425
|
-
if (input.useStandardPlugin && input.taskSource.kind === "github-issues") {
|
|
426
|
-
lines.push(` plugins: [...standardPlugins({`);
|
|
427
|
-
lines.push(` taskSources: {`);
|
|
428
|
-
lines.push(` githubCredentialProvider: createStateGitHubCredentialProvider(),`);
|
|
429
|
-
lines.push(` githubWorkspaceId: ${JSON.stringify(`${input.taskSource.owner}/${input.taskSource.repo}`)},`);
|
|
430
|
-
lines.push(` githubUserId: process.env.RIG_GITHUB_USER_ID ?? "server-selected-user",`);
|
|
431
|
-
lines.push(` },`);
|
|
432
|
-
lines.push(` })],`);
|
|
433
|
-
} else {
|
|
434
|
-
lines.push(` plugins: [${input.useStandardPlugin ? "...standardPlugins()" : ""}],`);
|
|
435
|
-
}
|
|
436
|
-
if (input.taskSource.kind === "github-issues") {
|
|
437
|
-
lines.push(` taskSource: {`);
|
|
438
|
-
lines.push(` kind: "github-issues",`);
|
|
439
|
-
lines.push(` owner: ${JSON.stringify(input.taskSource.owner)},`);
|
|
440
|
-
lines.push(` repo: ${JSON.stringify(input.taskSource.repo)},`);
|
|
441
|
-
lines.push(` // labels: ["task"], // uncomment to filter by labels`);
|
|
442
|
-
lines.push(` state: "open",`);
|
|
443
|
-
if (input.taskSource.assignee?.trim()) {
|
|
444
|
-
lines.push(` options: { assignee: ${JSON.stringify(input.taskSource.assignee.trim())} },`);
|
|
445
|
-
}
|
|
446
|
-
lines.push(` },`);
|
|
447
|
-
} else {
|
|
448
|
-
lines.push(` taskSource: {`);
|
|
449
|
-
lines.push(` kind: "files",`);
|
|
450
|
-
lines.push(` path: ${JSON.stringify(input.taskSource.path)},`);
|
|
451
|
-
lines.push(` },`);
|
|
452
|
-
}
|
|
453
|
-
lines.push(` workspace: { mainRepo: ".", isolation: "worktree" },`);
|
|
454
|
-
const sshTarget = input.sshTarget?.trim();
|
|
455
|
-
lines.push(sshTarget ? ` runtime: { harness: "pi", mode: "yolo", server: { sshTarget: ${JSON.stringify(sshTarget)} } },` : ` runtime: { harness: "pi", mode: "yolo" }, // server.sshTarget unset = local placement`);
|
|
456
|
-
lines.push(` planning: { mode: "auto" },`);
|
|
457
|
-
lines.push(` github: {`);
|
|
458
|
-
lines.push(` issueUpdates: "lifecycle",`);
|
|
459
|
-
lines.push(` projects: { enabled: false },`);
|
|
460
|
-
lines.push(` },`);
|
|
461
|
-
lines.push(` automation: { maxValidationAttempts: 30, maxPrFixIterations: 100500 },`);
|
|
462
|
-
lines.push(` pr: { mode: "auto", watchChecks: true, autoFixChecks: true, autoFixReview: true },`);
|
|
463
|
-
lines.push(` merge: { mode: "auto", method: "repo-default", deleteBranch: "repo-default", bypass: false },`);
|
|
464
|
-
lines.push(` issueAnalysis: { enabled: true, harness: "pi", mode: "continuous" },`);
|
|
465
|
-
lines.push(`});`);
|
|
466
|
-
lines.push(``);
|
|
467
|
-
return lines.join(`
|
|
468
|
-
`);
|
|
469
|
-
}
|
|
470
|
-
// packages/core/src/engineReadModelReducer.ts
|
|
471
|
-
import {
|
|
472
|
-
ActionId,
|
|
473
|
-
ConversationId,
|
|
474
|
-
EngineRuntimeId,
|
|
475
|
-
MessageId,
|
|
476
|
-
RunId,
|
|
477
|
-
TaskId,
|
|
478
|
-
WorkspaceId,
|
|
479
|
-
WorktreeId
|
|
480
|
-
} from "@rig/contracts";
|
|
481
|
-
var CANONICAL_RUNTIME_ADAPTER = "pi";
|
|
482
|
-
function isRecord(value) {
|
|
483
|
-
return typeof value === "object" && value !== null;
|
|
484
|
-
}
|
|
485
|
-
function readString(payload, key) {
|
|
486
|
-
const value = payload[key];
|
|
487
|
-
return typeof value === "string" ? value : undefined;
|
|
488
|
-
}
|
|
489
|
-
function readNullableString(payload, key) {
|
|
490
|
-
const value = payload[key];
|
|
491
|
-
return typeof value === "string" ? value : value === null ? null : undefined;
|
|
492
|
-
}
|
|
493
|
-
function readRecord(payload, key) {
|
|
494
|
-
const value = payload[key];
|
|
495
|
-
return isRecord(value) ? value : undefined;
|
|
496
|
-
}
|
|
497
|
-
function asWorkspaceId(value) {
|
|
498
|
-
return WorkspaceId.makeUnsafe(value);
|
|
499
|
-
}
|
|
500
|
-
function asTaskId(value) {
|
|
501
|
-
return TaskId.makeUnsafe(value);
|
|
502
|
-
}
|
|
503
|
-
function asRunId(value) {
|
|
504
|
-
return RunId.makeUnsafe(value);
|
|
505
|
-
}
|
|
506
|
-
function conversationIdFromRunId(value) {
|
|
507
|
-
return ConversationId.makeUnsafe(value);
|
|
508
|
-
}
|
|
509
|
-
function asActionId(value) {
|
|
510
|
-
return ActionId.makeUnsafe(value);
|
|
511
|
-
}
|
|
512
|
-
function runtimeIdFromRunId(value) {
|
|
513
|
-
return EngineRuntimeId.makeUnsafe(value);
|
|
514
|
-
}
|
|
515
|
-
function worktreeIdFromRunId(value) {
|
|
516
|
-
return WorktreeId.makeUnsafe(value);
|
|
517
|
-
}
|
|
518
|
-
function asMessageId(value) {
|
|
519
|
-
return MessageId.makeUnsafe(value);
|
|
520
|
-
}
|
|
521
|
-
function upsertById(items, entry) {
|
|
522
|
-
const index = items.findIndex((candidate) => candidate.id === entry.id);
|
|
523
|
-
if (index < 0) {
|
|
524
|
-
return [...items, entry];
|
|
525
|
-
}
|
|
526
|
-
const next = items.slice();
|
|
527
|
-
next[index] = entry;
|
|
528
|
-
return next;
|
|
529
|
-
}
|
|
530
|
-
function removeById(items, id) {
|
|
531
|
-
return items.filter((candidate) => candidate.id !== id);
|
|
532
|
-
}
|
|
533
|
-
function patchById(items, id, updater) {
|
|
534
|
-
const index = items.findIndex((candidate) => candidate.id === id);
|
|
535
|
-
if (index < 0) {
|
|
536
|
-
return items.slice();
|
|
537
|
-
}
|
|
538
|
-
const next = items.slice();
|
|
539
|
-
next[index] = updater(next[index]);
|
|
540
|
-
return next;
|
|
541
|
-
}
|
|
542
|
-
function upsertByKey(items, entry, key) {
|
|
543
|
-
const index = items.findIndex((candidate) => candidate[key] === entry[key]);
|
|
544
|
-
if (index < 0) {
|
|
545
|
-
return [...items, entry];
|
|
546
|
-
}
|
|
547
|
-
const next = items.slice();
|
|
548
|
-
next[index] = entry;
|
|
549
|
-
return next;
|
|
550
|
-
}
|
|
551
|
-
function patchByKey(items, keyValue, key, updater) {
|
|
552
|
-
const index = items.findIndex((candidate) => candidate[key] === keyValue);
|
|
553
|
-
if (index < 0) {
|
|
554
|
-
return items.slice();
|
|
555
|
-
}
|
|
556
|
-
const next = items.slice();
|
|
557
|
-
next[index] = updater(next[index]);
|
|
558
|
-
return next;
|
|
559
|
-
}
|
|
560
|
-
function mergeById(items, incoming) {
|
|
561
|
-
return incoming.reduce((acc, item) => upsertById(acc, item), [...items]);
|
|
562
|
-
}
|
|
563
|
-
function replaceWorkspaceSlice(items, workspaceId, incoming) {
|
|
564
|
-
return [...items.filter((item) => item.workspaceId !== workspaceId), ...incoming];
|
|
565
|
-
}
|
|
566
|
-
function withQueuePositions(items) {
|
|
567
|
-
return items.map((item, index) => Object.assign({}, item, {
|
|
568
|
-
position: index
|
|
569
|
-
}));
|
|
570
|
-
}
|
|
571
|
-
function maxIsoDate(left, right) {
|
|
572
|
-
return left.localeCompare(right) >= 0 ? left : right;
|
|
573
|
-
}
|
|
574
|
-
var REMOTE_HOST_STATUS_ONLINE = new Set([
|
|
575
|
-
"ready",
|
|
576
|
-
"busy",
|
|
577
|
-
"degraded",
|
|
578
|
-
"draining"
|
|
579
|
-
]);
|
|
580
|
-
function deriveRemoteFleetStatus(hosts, warnings) {
|
|
581
|
-
if (hosts.length === 0)
|
|
582
|
-
return "empty";
|
|
583
|
-
if (warnings.length > 0)
|
|
584
|
-
return "degraded";
|
|
585
|
-
if (hosts.some((host) => host.status === "degraded" || host.status === "quarantined")) {
|
|
586
|
-
return "degraded";
|
|
587
|
-
}
|
|
588
|
-
const onlineHostCount = hosts.filter((host) => REMOTE_HOST_STATUS_ONLINE.has(host.status)).length;
|
|
589
|
-
return onlineHostCount > 0 ? "ready" : "degraded";
|
|
590
|
-
}
|
|
591
|
-
function buildRemoteFleetSummary(input) {
|
|
592
|
-
const warnings = input.fleet?.warnings ?? [];
|
|
593
|
-
const manifestCount = input.fleet?.manifestCount ?? 0;
|
|
594
|
-
const onlineHostCount = input.hosts.filter((host) => REMOTE_HOST_STATUS_ONLINE.has(host.status)).length;
|
|
595
|
-
return {
|
|
596
|
-
updatedAt: input.updatedAt,
|
|
597
|
-
status: deriveRemoteFleetStatus(input.hosts, warnings),
|
|
598
|
-
manifestCount,
|
|
599
|
-
hostCount: input.hosts.length,
|
|
600
|
-
onlineHostCount,
|
|
601
|
-
hosts: [...input.hosts].sort((left, right) => left.name.localeCompare(right.name)),
|
|
602
|
-
warnings
|
|
603
|
-
};
|
|
604
|
-
}
|
|
605
|
-
function toRunMode(interactionMode) {
|
|
606
|
-
return interactionMode === "plan" ? "supervised" : "interactive";
|
|
607
|
-
}
|
|
608
|
-
function mapLegacySessionStatusToRunStatus(status) {
|
|
609
|
-
switch (status) {
|
|
610
|
-
case "starting":
|
|
611
|
-
return "preparing";
|
|
612
|
-
case "running":
|
|
613
|
-
return "running";
|
|
614
|
-
case "ready":
|
|
615
|
-
return "completed";
|
|
616
|
-
case "interrupted":
|
|
617
|
-
return "paused";
|
|
618
|
-
case "error":
|
|
619
|
-
return "failed";
|
|
620
|
-
case "stopped":
|
|
621
|
-
return "stopped";
|
|
622
|
-
default:
|
|
623
|
-
return "created";
|
|
624
|
-
}
|
|
625
|
-
}
|
|
626
|
-
function mapLegacySessionStatusToRuntimeStatus(status) {
|
|
627
|
-
switch (status) {
|
|
628
|
-
case "starting":
|
|
629
|
-
return "starting";
|
|
630
|
-
case "running":
|
|
631
|
-
return "running";
|
|
632
|
-
case "interrupted":
|
|
633
|
-
return "interrupted";
|
|
634
|
-
case "error":
|
|
635
|
-
return "failed";
|
|
636
|
-
default:
|
|
637
|
-
return "exited";
|
|
638
|
-
}
|
|
639
|
-
}
|
|
640
|
-
function mapTaskStatusFromRunStatus(status, fallback) {
|
|
641
|
-
if (fallback === "blocked" || fallback === "cancelled" || fallback === "completed" || fallback === "closed" || fallback === "unknown") {
|
|
642
|
-
return fallback === "closed" ? "completed" : fallback;
|
|
643
|
-
}
|
|
644
|
-
switch (status) {
|
|
645
|
-
case "created":
|
|
646
|
-
return fallback;
|
|
647
|
-
case "queued":
|
|
648
|
-
return "queued";
|
|
649
|
-
case "preparing":
|
|
650
|
-
case "running":
|
|
651
|
-
case "waiting-approval":
|
|
652
|
-
case "waiting-user-input":
|
|
653
|
-
case "validating":
|
|
654
|
-
case "paused":
|
|
655
|
-
return "in_progress";
|
|
656
|
-
case "reviewing":
|
|
657
|
-
case "closing-out":
|
|
658
|
-
return "under_review";
|
|
659
|
-
case "needs-attention":
|
|
660
|
-
return "blocked";
|
|
661
|
-
case "completed":
|
|
662
|
-
return "completed";
|
|
663
|
-
case "failed":
|
|
664
|
-
return "ready";
|
|
665
|
-
case "stopped":
|
|
666
|
-
return "cancelled";
|
|
667
|
-
}
|
|
668
|
-
}
|
|
669
|
-
function withSnapshotMetadata(snapshot, event, next) {
|
|
670
|
-
return {
|
|
671
|
-
...snapshot,
|
|
672
|
-
...next,
|
|
673
|
-
snapshotSequence: Math.max(snapshot.snapshotSequence, event.sequence),
|
|
674
|
-
updatedAt: maxIsoDate(snapshot.updatedAt, event.createdAt)
|
|
675
|
-
};
|
|
676
|
-
}
|
|
677
|
-
function ensureConversation(snapshot, run) {
|
|
678
|
-
const conversationId = conversationIdFromRunId(run.id);
|
|
679
|
-
if (snapshot.conversations.some((conversation) => conversation.id === conversationId)) {
|
|
680
|
-
return snapshot;
|
|
681
|
-
}
|
|
682
|
-
return {
|
|
683
|
-
...snapshot,
|
|
684
|
-
conversations: [
|
|
685
|
-
...snapshot.conversations,
|
|
686
|
-
{
|
|
687
|
-
id: conversationId,
|
|
688
|
-
runId: run.id,
|
|
689
|
-
title: run.title,
|
|
690
|
-
createdAt: run.createdAt,
|
|
691
|
-
updatedAt: run.updatedAt
|
|
692
|
-
}
|
|
693
|
-
]
|
|
694
|
-
};
|
|
695
|
-
}
|
|
696
|
-
function updateTaskFromRun(snapshot, run) {
|
|
697
|
-
if (!run.taskId) {
|
|
698
|
-
return snapshot;
|
|
699
|
-
}
|
|
700
|
-
return {
|
|
701
|
-
...snapshot,
|
|
702
|
-
tasks: patchById(snapshot.tasks, run.taskId, (task) => ({
|
|
703
|
-
...task,
|
|
704
|
-
status: mapTaskStatusFromRunStatus(run.status, task.status),
|
|
705
|
-
updatedAt: maxIsoDate(task.updatedAt, run.updatedAt)
|
|
706
|
-
}))
|
|
707
|
-
};
|
|
708
|
-
}
|
|
709
|
-
function applyRun(snapshot, run) {
|
|
710
|
-
const withConversation = ensureConversation(snapshot, run);
|
|
711
|
-
const nextRuns = upsertById(withConversation.runs, run);
|
|
712
|
-
return updateTaskFromRun({ ...withConversation, runs: nextRuns }, run);
|
|
713
|
-
}
|
|
714
|
-
function applyRuntime(snapshot, runtime) {
|
|
715
|
-
return {
|
|
716
|
-
...snapshot,
|
|
717
|
-
runtimes: upsertById(snapshot.runtimes, runtime)
|
|
718
|
-
};
|
|
719
|
-
}
|
|
720
|
-
function applyWorktree(snapshot, worktree) {
|
|
721
|
-
return {
|
|
722
|
-
...snapshot,
|
|
723
|
-
worktrees: upsertById(snapshot.worktrees, worktree)
|
|
724
|
-
};
|
|
725
|
-
}
|
|
726
|
-
function countPendingApprovals(approvals, runId) {
|
|
727
|
-
return approvals.filter((approval) => approval.runId === runId && approval.status === "pending").length;
|
|
728
|
-
}
|
|
729
|
-
function countPendingUserInputs(userInputs, actions, runId) {
|
|
730
|
-
const persistedCount = (userInputs ?? []).filter((request) => request.runId === runId && request.status === "pending").length;
|
|
731
|
-
if (persistedCount > 0) {
|
|
732
|
-
return persistedCount;
|
|
733
|
-
}
|
|
734
|
-
const openRequestIds = new Set;
|
|
735
|
-
for (const action of actions) {
|
|
736
|
-
if (action.runId !== runId || !isRecord(action.payload)) {
|
|
737
|
-
continue;
|
|
738
|
-
}
|
|
739
|
-
const requestId = readString(action.payload, "requestId");
|
|
740
|
-
if (!requestId) {
|
|
741
|
-
continue;
|
|
742
|
-
}
|
|
743
|
-
if (action.actionType === "user-input.requested") {
|
|
744
|
-
openRequestIds.add(requestId);
|
|
745
|
-
}
|
|
746
|
-
if (action.actionType === "user-input.resolved") {
|
|
747
|
-
openRequestIds.delete(requestId);
|
|
748
|
-
}
|
|
749
|
-
}
|
|
750
|
-
return openRequestIds.size;
|
|
751
|
-
}
|
|
752
|
-
function reconcileRunCounts(snapshot, runId, statusOverride) {
|
|
753
|
-
return {
|
|
754
|
-
...snapshot,
|
|
755
|
-
runs: patchById(snapshot.runs, runId, (run) => {
|
|
756
|
-
const pendingApprovalCount = countPendingApprovals(snapshot.approvals, runId);
|
|
757
|
-
const pendingUserInputCount = countPendingUserInputs(snapshot.userInputs, snapshot.actions, runId);
|
|
758
|
-
const nextStatus = statusOverride ?? (pendingApprovalCount > 0 ? "waiting-approval" : pendingUserInputCount > 0 ? "waiting-user-input" : run.status === "waiting-approval" || run.status === "waiting-user-input" ? "running" : run.status);
|
|
759
|
-
return {
|
|
760
|
-
...run,
|
|
761
|
-
pendingApprovalCount,
|
|
762
|
-
pendingUserInputCount,
|
|
763
|
-
status: nextStatus
|
|
764
|
-
};
|
|
765
|
-
})
|
|
766
|
-
};
|
|
767
|
-
}
|
|
768
|
-
function applyApprovalActivity(approvals, runId, action) {
|
|
769
|
-
if (!isRecord(action.payload)) {
|
|
770
|
-
return approvals.slice();
|
|
771
|
-
}
|
|
772
|
-
const requestId = readString(action.payload, "requestId");
|
|
773
|
-
if (!requestId) {
|
|
774
|
-
return approvals.slice();
|
|
775
|
-
}
|
|
776
|
-
if (action.actionType === "approval.requested") {
|
|
777
|
-
const requestKind = readString(action.payload, "requestKind") ?? "command";
|
|
778
|
-
return upsertById(approvals, {
|
|
779
|
-
id: requestId,
|
|
780
|
-
runId,
|
|
781
|
-
actionId: action.id,
|
|
782
|
-
requestKind,
|
|
783
|
-
status: "pending",
|
|
784
|
-
payload: action.payload,
|
|
785
|
-
createdAt: action.startedAt,
|
|
786
|
-
resolvedAt: null
|
|
787
|
-
});
|
|
788
|
-
}
|
|
789
|
-
const shouldResolve = action.actionType === "approval.resolved" || action.actionType === "provider.approval.respond.failed" && ((readString(action.payload, "detail") ?? "").includes("Unknown pending permission request") || (readString(action.payload, "message") ?? "").includes("Unknown pending permission request"));
|
|
790
|
-
if (!shouldResolve) {
|
|
791
|
-
return approvals.slice();
|
|
792
|
-
}
|
|
793
|
-
return approvals.map((approval) => approval.id === requestId ? {
|
|
794
|
-
...approval,
|
|
795
|
-
status: "resolved",
|
|
796
|
-
resolvedAt: action.completedAt ?? action.startedAt
|
|
797
|
-
} : approval);
|
|
798
|
-
}
|
|
799
|
-
function makeRuntimeFromRun(run, occurredAt) {
|
|
800
|
-
return {
|
|
801
|
-
id: run.activeRuntimeId ?? runtimeIdFromRunId(run.id),
|
|
802
|
-
workspaceId: run.workspaceId,
|
|
803
|
-
runId: run.id,
|
|
804
|
-
adapterKind: run.runtimeAdapter,
|
|
805
|
-
executionTarget: run.executionTarget ?? "local",
|
|
806
|
-
remoteHostId: run.remoteHostId ?? null,
|
|
807
|
-
status: run.status === "failed" ? "failed" : run.status === "paused" ? "interrupted" : "starting",
|
|
808
|
-
sandboxMode: "danger-full-access",
|
|
809
|
-
isolationMode: run.worktreePath ? "worktree" : "env",
|
|
810
|
-
workspaceDir: run.worktreePath,
|
|
811
|
-
homeDir: null,
|
|
812
|
-
tmpDir: null,
|
|
813
|
-
cacheDir: null,
|
|
814
|
-
logsDir: null,
|
|
815
|
-
stateDir: null,
|
|
816
|
-
sessionDir: null,
|
|
817
|
-
sessionLogPath: null,
|
|
818
|
-
pid: null,
|
|
819
|
-
startedAt: run.startedAt ?? occurredAt,
|
|
820
|
-
updatedAt: occurredAt,
|
|
821
|
-
exitedAt: run.completedAt
|
|
822
|
-
};
|
|
823
|
-
}
|
|
824
|
-
function applySyntheticRuntimePreparation(snapshot, event) {
|
|
825
|
-
if (!isRecord(event.payload)) {
|
|
826
|
-
return { status: "ignored", snapshot };
|
|
827
|
-
}
|
|
828
|
-
const runId = readString(event.payload, "runId") ?? event.aggregateId;
|
|
829
|
-
const workspaceId = readString(event.payload, "workspaceId");
|
|
830
|
-
const taskId = readString(event.payload, "taskId") ?? null;
|
|
831
|
-
const workspaceDir = readNullableString(event.payload, "workspaceDir");
|
|
832
|
-
const homeDir = readNullableString(event.payload, "homeDir");
|
|
833
|
-
const tmpDir = readNullableString(event.payload, "tmpDir");
|
|
834
|
-
const cacheDir = readNullableString(event.payload, "cacheDir");
|
|
835
|
-
const branchName = readString(event.payload, "taskExternalId") ?? snapshot.runs.find((run) => run.id === runId)?.branch ?? "task";
|
|
836
|
-
const failed = event.type.endsWith(".failed");
|
|
837
|
-
const finished = event.type.endsWith(".finished");
|
|
838
|
-
const existingRun = snapshot.runs.find((run) => run.id === runId);
|
|
839
|
-
if (!existingRun || !workspaceId) {
|
|
840
|
-
return { status: "ignored", snapshot };
|
|
841
|
-
}
|
|
842
|
-
const runtimeRunId = asRunId(runId);
|
|
843
|
-
const nextTaskId = taskId ? asTaskId(taskId) : null;
|
|
844
|
-
const nextWorkspaceId = asWorkspaceId(workspaceId);
|
|
845
|
-
const nextRun = {
|
|
846
|
-
...existingRun,
|
|
847
|
-
taskId: existingRun.taskId ?? nextTaskId,
|
|
848
|
-
runKind: existingRun.runKind === "adhoc" && nextTaskId ? "task" : existingRun.runKind,
|
|
849
|
-
runtimeAdapter: CANONICAL_RUNTIME_ADAPTER,
|
|
850
|
-
initialPrompt: existingRun.initialPrompt ?? null,
|
|
851
|
-
executionTarget: existingRun.executionTarget ?? "local",
|
|
852
|
-
remoteHostId: existingRun.remoteHostId ?? null,
|
|
853
|
-
activeRuntimeId: existingRun.activeRuntimeId ?? runtimeIdFromRunId(runtimeRunId),
|
|
854
|
-
worktreePath: workspaceDir ?? existingRun.worktreePath,
|
|
855
|
-
status: failed ? "failed" : finished ? "preparing" : "preparing",
|
|
856
|
-
errorText: failed ? readString(event.payload, "message") ?? existingRun.errorText : existingRun.errorText,
|
|
857
|
-
updatedAt: event.createdAt,
|
|
858
|
-
completedAt: failed ? event.createdAt : existingRun.completedAt
|
|
859
|
-
};
|
|
860
|
-
const runtimeBase = snapshot.runtimes.find((runtime) => runtime.runId === runId) ?? makeRuntimeFromRun(nextRun, event.createdAt);
|
|
861
|
-
const nextRuntime = {
|
|
862
|
-
...runtimeBase,
|
|
863
|
-
workspaceId: nextWorkspaceId,
|
|
864
|
-
runId: runtimeRunId,
|
|
865
|
-
adapterKind: CANONICAL_RUNTIME_ADAPTER,
|
|
866
|
-
executionTarget: existingRun.executionTarget ?? "local",
|
|
867
|
-
remoteHostId: existingRun.remoteHostId ?? null,
|
|
868
|
-
status: failed ? "failed" : finished ? "prepared" : "starting",
|
|
869
|
-
isolationMode: "worktree",
|
|
870
|
-
workspaceDir: workspaceDir ?? runtimeBase.workspaceDir,
|
|
871
|
-
homeDir: homeDir ?? runtimeBase.homeDir,
|
|
872
|
-
tmpDir: tmpDir ?? runtimeBase.tmpDir,
|
|
873
|
-
cacheDir: cacheDir ?? runtimeBase.cacheDir,
|
|
874
|
-
updatedAt: event.createdAt,
|
|
875
|
-
exitedAt: failed ? event.createdAt : runtimeBase.exitedAt
|
|
876
|
-
};
|
|
877
|
-
let nextSnapshot = applyRun(snapshot, nextRun);
|
|
878
|
-
nextSnapshot = applyRuntime(nextSnapshot, nextRuntime);
|
|
879
|
-
if (workspaceDir) {
|
|
880
|
-
nextSnapshot = applyWorktree(nextSnapshot, {
|
|
881
|
-
id: worktreeIdFromRunId(runtimeRunId),
|
|
882
|
-
workspaceId: nextWorkspaceId,
|
|
883
|
-
runId: runtimeRunId,
|
|
884
|
-
taskId: nextRun.taskId,
|
|
885
|
-
branchName,
|
|
886
|
-
path: workspaceDir,
|
|
887
|
-
status: failed ? "failed" : finished ? "prepared" : "preparing",
|
|
888
|
-
createdAt: existingRun.createdAt,
|
|
889
|
-
cleanedAt: null
|
|
890
|
-
});
|
|
891
|
-
}
|
|
892
|
-
return {
|
|
893
|
-
status: "applied",
|
|
894
|
-
snapshot: withSnapshotMetadata(nextSnapshot, event, {})
|
|
895
|
-
};
|
|
896
|
-
}
|
|
897
|
-
function applySyntheticRuntimePrepared(snapshot, event) {
|
|
898
|
-
if (!isRecord(event.payload)) {
|
|
899
|
-
return { status: "ignored", snapshot };
|
|
900
|
-
}
|
|
901
|
-
const runId = readString(event.payload, "runId") ?? event.aggregateId;
|
|
902
|
-
const workspaceId = readString(event.payload, "workspaceId");
|
|
903
|
-
const worktreePath = readString(event.payload, "worktreePath");
|
|
904
|
-
const existingRun = snapshot.runs.find((run) => run.id === runId);
|
|
905
|
-
if (!existingRun || !workspaceId || !worktreePath) {
|
|
906
|
-
return { status: "ignored", snapshot };
|
|
907
|
-
}
|
|
908
|
-
const runtimeRunId = asRunId(runId);
|
|
909
|
-
const nextWorkspaceId = asWorkspaceId(workspaceId);
|
|
910
|
-
let nextSnapshot = applyRuntime(snapshot, {
|
|
911
|
-
...snapshot.runtimes.find((runtime) => runtime.runId === runId) ?? makeRuntimeFromRun(existingRun, event.createdAt),
|
|
912
|
-
workspaceId: nextWorkspaceId,
|
|
913
|
-
runId: runtimeRunId,
|
|
914
|
-
adapterKind: CANONICAL_RUNTIME_ADAPTER,
|
|
915
|
-
executionTarget: existingRun.executionTarget ?? "local",
|
|
916
|
-
remoteHostId: existingRun.remoteHostId ?? null,
|
|
917
|
-
status: "prepared",
|
|
918
|
-
isolationMode: "worktree",
|
|
919
|
-
workspaceDir: worktreePath,
|
|
920
|
-
updatedAt: event.createdAt
|
|
921
|
-
});
|
|
922
|
-
nextSnapshot = applyWorktree(nextSnapshot, {
|
|
923
|
-
id: worktreeIdFromRunId(runtimeRunId),
|
|
924
|
-
workspaceId: nextWorkspaceId,
|
|
925
|
-
runId: runtimeRunId,
|
|
926
|
-
taskId: existingRun.taskId,
|
|
927
|
-
branchName: existingRun.branch ?? "task",
|
|
928
|
-
path: worktreePath,
|
|
929
|
-
status: "prepared",
|
|
930
|
-
createdAt: existingRun.createdAt,
|
|
931
|
-
cleanedAt: null
|
|
932
|
-
});
|
|
933
|
-
return {
|
|
934
|
-
status: "applied",
|
|
935
|
-
snapshot: withSnapshotMetadata(nextSnapshot, event, {})
|
|
936
|
-
};
|
|
937
|
-
}
|
|
938
|
-
function applyLegacyProjectEvent(snapshot, event) {
|
|
939
|
-
if (!isRecord(event.payload)) {
|
|
940
|
-
return { status: "ignored", snapshot };
|
|
941
|
-
}
|
|
942
|
-
const payload = event.payload;
|
|
943
|
-
if (event.type === "legacy.project.created") {
|
|
944
|
-
const workspaceId = readString(payload, "projectId");
|
|
945
|
-
const title = readString(payload, "title");
|
|
946
|
-
const rootPath = readString(payload, "workspaceRoot");
|
|
947
|
-
const createdAt = readString(payload, "createdAt");
|
|
948
|
-
const updatedAt = readString(payload, "updatedAt");
|
|
949
|
-
if (!workspaceId || !title || !rootPath || !createdAt || !updatedAt) {
|
|
950
|
-
return { status: "ignored", snapshot };
|
|
951
|
-
}
|
|
952
|
-
const workspace = {
|
|
953
|
-
id: asWorkspaceId(workspaceId),
|
|
954
|
-
title,
|
|
955
|
-
rootPath,
|
|
956
|
-
sourceKind: "native",
|
|
957
|
-
defaultRuntimeAdapter: CANONICAL_RUNTIME_ADAPTER,
|
|
958
|
-
defaultModel: readNullableString(payload, "defaultModel") ?? null,
|
|
959
|
-
createdAt,
|
|
960
|
-
updatedAt
|
|
961
|
-
};
|
|
962
|
-
return {
|
|
963
|
-
status: "applied",
|
|
964
|
-
snapshot: withSnapshotMetadata(snapshot, event, {
|
|
965
|
-
workspaces: upsertById(snapshot.workspaces, workspace)
|
|
966
|
-
})
|
|
967
|
-
};
|
|
968
|
-
}
|
|
969
|
-
if (event.type === "legacy.project.meta-updated") {
|
|
970
|
-
const workspaceId = readString(payload, "projectId");
|
|
971
|
-
if (!workspaceId) {
|
|
972
|
-
return { status: "ignored", snapshot };
|
|
973
|
-
}
|
|
974
|
-
return {
|
|
975
|
-
status: "applied",
|
|
976
|
-
snapshot: withSnapshotMetadata(snapshot, event, {
|
|
977
|
-
workspaces: patchById(snapshot.workspaces, workspaceId, (workspace) => ({
|
|
978
|
-
...workspace,
|
|
979
|
-
title: readString(payload, "title") ?? workspace.title,
|
|
980
|
-
rootPath: readString(payload, "workspaceRoot") ?? workspace.rootPath,
|
|
981
|
-
defaultModel: readNullableString(payload, "defaultModel") ?? workspace.defaultModel,
|
|
982
|
-
updatedAt: readString(payload, "updatedAt") ?? event.createdAt
|
|
983
|
-
}))
|
|
984
|
-
})
|
|
985
|
-
};
|
|
986
|
-
}
|
|
987
|
-
if (event.type === "legacy.project.deleted") {
|
|
988
|
-
const workspaceId = readString(payload, "projectId");
|
|
989
|
-
if (!workspaceId) {
|
|
990
|
-
return { status: "ignored", snapshot };
|
|
991
|
-
}
|
|
992
|
-
return {
|
|
993
|
-
status: "applied",
|
|
994
|
-
snapshot: withSnapshotMetadata(snapshot, event, {
|
|
995
|
-
workspaces: removeById(snapshot.workspaces, workspaceId),
|
|
996
|
-
graphs: snapshot.graphs.filter((graph) => graph.workspaceId !== workspaceId),
|
|
997
|
-
tasks: snapshot.tasks.filter((task) => task.workspaceId !== workspaceId),
|
|
998
|
-
runs: snapshot.runs.filter((run) => run.workspaceId !== workspaceId),
|
|
999
|
-
runtimes: snapshot.runtimes.filter((runtime) => runtime.workspaceId !== workspaceId),
|
|
1000
|
-
conversations: snapshot.conversations.filter((conversation) => {
|
|
1001
|
-
const run = snapshot.runs.find((candidate) => candidate.id === conversation.runId);
|
|
1002
|
-
return run?.workspaceId !== workspaceId;
|
|
1003
|
-
}),
|
|
1004
|
-
messages: snapshot.messages.filter((message) => {
|
|
1005
|
-
const conversation = snapshot.conversations.find((candidate) => candidate.id === message.conversationId);
|
|
1006
|
-
const run = snapshot.runs.find((candidate) => candidate.id === conversation?.runId);
|
|
1007
|
-
return run?.workspaceId !== workspaceId;
|
|
1008
|
-
}),
|
|
1009
|
-
actions: snapshot.actions.filter((action) => {
|
|
1010
|
-
const run = snapshot.runs.find((candidate) => candidate.id === action.runId);
|
|
1011
|
-
return run?.workspaceId !== workspaceId;
|
|
1012
|
-
}),
|
|
1013
|
-
approvals: snapshot.approvals.filter((approval) => {
|
|
1014
|
-
const run = snapshot.runs.find((candidate) => candidate.id === approval.runId);
|
|
1015
|
-
return run?.workspaceId !== workspaceId;
|
|
1016
|
-
}),
|
|
1017
|
-
queue: snapshot.queue.filter((entry) => {
|
|
1018
|
-
const task = snapshot.tasks.find((candidate) => candidate.id === entry.taskId);
|
|
1019
|
-
return task?.workspaceId !== workspaceId;
|
|
1020
|
-
}),
|
|
1021
|
-
worktrees: snapshot.worktrees.filter((worktree) => worktree.workspaceId !== workspaceId)
|
|
1022
|
-
})
|
|
1023
|
-
};
|
|
1024
|
-
}
|
|
1025
|
-
return { status: "ignored", snapshot };
|
|
1026
|
-
}
|
|
1027
|
-
function applyLegacyThreadEvent(snapshot, event) {
|
|
1028
|
-
if (!isRecord(event.payload)) {
|
|
1029
|
-
return { status: "ignored", snapshot };
|
|
1030
|
-
}
|
|
1031
|
-
const payload = event.payload;
|
|
1032
|
-
if (event.type === "legacy.thread.created") {
|
|
1033
|
-
const runId = readString(payload, "threadId");
|
|
1034
|
-
const workspaceId = readString(payload, "projectId");
|
|
1035
|
-
const title = readString(payload, "title");
|
|
1036
|
-
const model = readString(payload, "model");
|
|
1037
|
-
const createdAt = readString(payload, "createdAt");
|
|
1038
|
-
const updatedAt = readString(payload, "updatedAt");
|
|
1039
|
-
if (!runId || !workspaceId || !title || !model || !createdAt || !updatedAt) {
|
|
1040
|
-
return { status: "ignored", snapshot };
|
|
1041
|
-
}
|
|
1042
|
-
const run = {
|
|
1043
|
-
id: asRunId(runId),
|
|
1044
|
-
workspaceId: asWorkspaceId(workspaceId),
|
|
1045
|
-
taskId: null,
|
|
1046
|
-
title,
|
|
1047
|
-
runKind: "adhoc",
|
|
1048
|
-
mode: toRunMode(readString(payload, "interactionMode")),
|
|
1049
|
-
runtimeMode: "full-access",
|
|
1050
|
-
interactionMode: "default",
|
|
1051
|
-
status: "created",
|
|
1052
|
-
runtimeAdapter: CANONICAL_RUNTIME_ADAPTER,
|
|
1053
|
-
model,
|
|
1054
|
-
initialPrompt: null,
|
|
1055
|
-
activeRuntimeId: null,
|
|
1056
|
-
latestMessageId: null,
|
|
1057
|
-
pendingApprovalCount: 0,
|
|
1058
|
-
pendingUserInputCount: 0,
|
|
1059
|
-
branch: readNullableString(payload, "branch") ?? null,
|
|
1060
|
-
worktreePath: readNullableString(payload, "worktreePath") ?? null,
|
|
1061
|
-
errorText: null,
|
|
1062
|
-
createdAt,
|
|
1063
|
-
updatedAt,
|
|
1064
|
-
startedAt: null,
|
|
1065
|
-
completedAt: null
|
|
1066
|
-
};
|
|
1067
|
-
let nextSnapshot = applyRun(snapshot, run);
|
|
1068
|
-
if (run.branch && run.worktreePath) {
|
|
1069
|
-
nextSnapshot = applyWorktree(nextSnapshot, {
|
|
1070
|
-
id: worktreeIdFromRunId(asRunId(runId)),
|
|
1071
|
-
workspaceId: asWorkspaceId(workspaceId),
|
|
1072
|
-
runId: asRunId(runId),
|
|
1073
|
-
taskId: null,
|
|
1074
|
-
branchName: run.branch,
|
|
1075
|
-
path: run.worktreePath,
|
|
1076
|
-
status: "active",
|
|
1077
|
-
createdAt,
|
|
1078
|
-
cleanedAt: null
|
|
1079
|
-
});
|
|
1080
|
-
}
|
|
1081
|
-
return {
|
|
1082
|
-
status: "applied",
|
|
1083
|
-
snapshot: withSnapshotMetadata(nextSnapshot, event, {})
|
|
1084
|
-
};
|
|
1085
|
-
}
|
|
1086
|
-
if (event.type === "legacy.thread.deleted") {
|
|
1087
|
-
const runId = readString(payload, "threadId");
|
|
1088
|
-
if (!runId) {
|
|
1089
|
-
return { status: "ignored", snapshot };
|
|
1090
|
-
}
|
|
1091
|
-
return {
|
|
1092
|
-
status: "applied",
|
|
1093
|
-
snapshot: withSnapshotMetadata(snapshot, event, {
|
|
1094
|
-
runs: removeById(snapshot.runs, runId),
|
|
1095
|
-
runtimes: snapshot.runtimes.filter((runtime) => runtime.runId !== runId),
|
|
1096
|
-
conversations: snapshot.conversations.filter((conversation) => conversation.runId !== runId),
|
|
1097
|
-
messages: snapshot.messages.filter((message) => message.conversationId !== runId),
|
|
1098
|
-
actions: snapshot.actions.filter((action) => action.runId !== runId),
|
|
1099
|
-
approvals: snapshot.approvals.filter((approval) => approval.runId !== runId),
|
|
1100
|
-
worktrees: snapshot.worktrees.filter((worktree) => worktree.runId !== runId)
|
|
1101
|
-
})
|
|
1102
|
-
};
|
|
1103
|
-
}
|
|
1104
|
-
if (event.type === "legacy.thread.meta-updated") {
|
|
1105
|
-
const runId = readString(payload, "threadId");
|
|
1106
|
-
if (!runId) {
|
|
1107
|
-
return { status: "ignored", snapshot };
|
|
1108
|
-
}
|
|
1109
|
-
let nextSnapshot = {
|
|
1110
|
-
...snapshot,
|
|
1111
|
-
runs: patchById(snapshot.runs, runId, (run2) => ({
|
|
1112
|
-
...run2,
|
|
1113
|
-
title: readString(payload, "title") ?? run2.title,
|
|
1114
|
-
model: readString(payload, "model") ?? run2.model,
|
|
1115
|
-
branch: readNullableString(payload, "branch") === undefined ? run2.branch : readNullableString(payload, "branch") ?? null,
|
|
1116
|
-
worktreePath: readNullableString(payload, "worktreePath") === undefined ? run2.worktreePath : readNullableString(payload, "worktreePath") ?? null,
|
|
1117
|
-
updatedAt: readString(payload, "updatedAt") ?? event.createdAt
|
|
1118
|
-
})),
|
|
1119
|
-
conversations: patchById(snapshot.conversations, runId, (conversation) => ({
|
|
1120
|
-
...conversation,
|
|
1121
|
-
title: readString(payload, "title") ?? conversation.title,
|
|
1122
|
-
updatedAt: readString(payload, "updatedAt") ?? event.createdAt
|
|
1123
|
-
}))
|
|
1124
|
-
};
|
|
1125
|
-
const run = nextSnapshot.runs.find((candidate) => candidate.id === runId);
|
|
1126
|
-
if (run?.worktreePath && run.branch) {
|
|
1127
|
-
nextSnapshot = applyWorktree(nextSnapshot, {
|
|
1128
|
-
id: worktreeIdFromRunId(run.id),
|
|
1129
|
-
workspaceId: run.workspaceId,
|
|
1130
|
-
runId: run.id,
|
|
1131
|
-
taskId: run.taskId,
|
|
1132
|
-
branchName: run.branch,
|
|
1133
|
-
path: run.worktreePath,
|
|
1134
|
-
status: "active",
|
|
1135
|
-
createdAt: run.createdAt,
|
|
1136
|
-
cleanedAt: null
|
|
1137
|
-
});
|
|
1138
|
-
}
|
|
1139
|
-
if (run && run.worktreePath === null) {
|
|
1140
|
-
nextSnapshot = {
|
|
1141
|
-
...nextSnapshot,
|
|
1142
|
-
worktrees: nextSnapshot.worktrees.filter((worktree) => worktree.runId !== runId)
|
|
1143
|
-
};
|
|
1144
|
-
}
|
|
1145
|
-
return {
|
|
1146
|
-
status: "applied",
|
|
1147
|
-
snapshot: withSnapshotMetadata(nextSnapshot, event, {})
|
|
1148
|
-
};
|
|
1149
|
-
}
|
|
1150
|
-
if (event.type === "legacy.thread.interaction-mode-set") {
|
|
1151
|
-
const runId = readString(payload, "threadId");
|
|
1152
|
-
if (!runId) {
|
|
1153
|
-
return { status: "ignored", snapshot };
|
|
1154
|
-
}
|
|
1155
|
-
return {
|
|
1156
|
-
status: "applied",
|
|
1157
|
-
snapshot: withSnapshotMetadata(snapshot, event, {
|
|
1158
|
-
runs: patchById(snapshot.runs, runId, (run) => ({
|
|
1159
|
-
...run,
|
|
1160
|
-
mode: toRunMode(readString(payload, "interactionMode")),
|
|
1161
|
-
updatedAt: readString(payload, "updatedAt") ?? event.createdAt
|
|
1162
|
-
}))
|
|
1163
|
-
})
|
|
1164
|
-
};
|
|
1165
|
-
}
|
|
1166
|
-
if (event.type === "legacy.thread.runtime-mode-set") {
|
|
1167
|
-
const runId = readString(payload, "threadId");
|
|
1168
|
-
if (!runId) {
|
|
1169
|
-
return { status: "ignored", snapshot };
|
|
1170
|
-
}
|
|
1171
|
-
const runtimeMode = readString(payload, "runtimeMode");
|
|
1172
|
-
return {
|
|
1173
|
-
status: "applied",
|
|
1174
|
-
snapshot: withSnapshotMetadata(snapshot, event, {
|
|
1175
|
-
runtimes: patchById(snapshot.runtimes, runId, (runtime) => ({
|
|
1176
|
-
...runtime,
|
|
1177
|
-
sandboxMode: runtimeMode === "approval-required" ? "workspace-write" : "danger-full-access",
|
|
1178
|
-
updatedAt: readString(payload, "updatedAt") ?? event.createdAt
|
|
1179
|
-
}))
|
|
1180
|
-
})
|
|
1181
|
-
};
|
|
1182
|
-
}
|
|
1183
|
-
if (event.type === "legacy.thread.message-sent") {
|
|
1184
|
-
const runId = readString(payload, "threadId");
|
|
1185
|
-
const messageId = readString(payload, "messageId");
|
|
1186
|
-
const role = readString(payload, "role");
|
|
1187
|
-
const text = readString(payload, "text");
|
|
1188
|
-
const createdAt = readString(payload, "createdAt");
|
|
1189
|
-
const updatedAt = readString(payload, "updatedAt");
|
|
1190
|
-
if (!runId || !messageId || !role || text === undefined || !createdAt || !updatedAt) {
|
|
1191
|
-
return { status: "ignored", snapshot };
|
|
1192
|
-
}
|
|
1193
|
-
const attachments = Array.isArray(payload.attachments) ? payload.attachments : [];
|
|
1194
|
-
const streaming = payload.streaming === true;
|
|
1195
|
-
const message = {
|
|
1196
|
-
id: asMessageId(messageId),
|
|
1197
|
-
conversationId: conversationIdFromRunId(asRunId(runId)),
|
|
1198
|
-
role: role === "assistant" || role === "system" ? role : "user",
|
|
1199
|
-
text,
|
|
1200
|
-
attachments,
|
|
1201
|
-
state: streaming ? "streaming" : "completed",
|
|
1202
|
-
createdAt,
|
|
1203
|
-
completedAt: streaming ? null : updatedAt
|
|
1204
|
-
};
|
|
1205
|
-
const existingRun = snapshot.runs.find((run) => run.id === runId);
|
|
1206
|
-
if (!existingRun) {
|
|
1207
|
-
return { status: "ignored", snapshot };
|
|
1208
|
-
}
|
|
1209
|
-
return {
|
|
1210
|
-
status: "applied",
|
|
1211
|
-
snapshot: withSnapshotMetadata(ensureConversation({
|
|
1212
|
-
...snapshot,
|
|
1213
|
-
messages: upsertById(snapshot.messages, message),
|
|
1214
|
-
runs: patchById(snapshot.runs, runId, (run) => ({
|
|
1215
|
-
...run,
|
|
1216
|
-
latestMessageId: asMessageId(messageId),
|
|
1217
|
-
updatedAt
|
|
1218
|
-
}))
|
|
1219
|
-
}, existingRun), event, {})
|
|
1220
|
-
};
|
|
1221
|
-
}
|
|
1222
|
-
if (event.type === "legacy.thread.session-set") {
|
|
1223
|
-
const runId = readString(event.payload, "threadId");
|
|
1224
|
-
const session = readRecord(event.payload, "session");
|
|
1225
|
-
if (!runId || !session) {
|
|
1226
|
-
return { status: "ignored", snapshot };
|
|
1227
|
-
}
|
|
1228
|
-
const existingRun = snapshot.runs.find((run) => run.id === runId);
|
|
1229
|
-
if (!existingRun) {
|
|
1230
|
-
return { status: "ignored", snapshot };
|
|
1231
|
-
}
|
|
1232
|
-
const sessionUpdatedAt = readString(session, "updatedAt") ?? event.createdAt;
|
|
1233
|
-
const providerName = readNullableString(session, "providerName");
|
|
1234
|
-
const nextRun = {
|
|
1235
|
-
...existingRun,
|
|
1236
|
-
runtimeAdapter: CANONICAL_RUNTIME_ADAPTER,
|
|
1237
|
-
initialPrompt: existingRun.initialPrompt ?? null,
|
|
1238
|
-
activeRuntimeId: runtimeIdFromRunId(asRunId(runId)),
|
|
1239
|
-
status: mapLegacySessionStatusToRunStatus(readString(session, "status")),
|
|
1240
|
-
errorText: readNullableString(session, "lastError") ?? existingRun.errorText,
|
|
1241
|
-
updatedAt: sessionUpdatedAt,
|
|
1242
|
-
completedAt: readString(session, "status") === "ready" || readString(session, "status") === "stopped" || readString(session, "status") === "error" ? sessionUpdatedAt : existingRun.completedAt
|
|
1243
|
-
};
|
|
1244
|
-
let nextSnapshot = applyRun(snapshot, nextRun);
|
|
1245
|
-
nextSnapshot = applyRuntime(nextSnapshot, {
|
|
1246
|
-
...snapshot.runtimes.find((runtime) => runtime.runId === runId) ?? makeRuntimeFromRun(nextRun, sessionUpdatedAt),
|
|
1247
|
-
workspaceId: existingRun.workspaceId,
|
|
1248
|
-
runId: asRunId(runId),
|
|
1249
|
-
adapterKind: CANONICAL_RUNTIME_ADAPTER,
|
|
1250
|
-
status: mapLegacySessionStatusToRuntimeStatus(readString(session, "status")),
|
|
1251
|
-
workspaceDir: existingRun.worktreePath,
|
|
1252
|
-
isolationMode: existingRun.worktreePath ? "worktree" : "env",
|
|
1253
|
-
updatedAt: sessionUpdatedAt,
|
|
1254
|
-
exitedAt: readString(session, "status") === "ready" || readString(session, "status") === "stopped" || readString(session, "status") === "error" ? sessionUpdatedAt : null
|
|
1255
|
-
});
|
|
1256
|
-
return {
|
|
1257
|
-
status: "applied",
|
|
1258
|
-
snapshot: withSnapshotMetadata(nextSnapshot, event, {})
|
|
1259
|
-
};
|
|
1260
|
-
}
|
|
1261
|
-
if (event.type === "legacy.thread.activity-appended") {
|
|
1262
|
-
const runId = readString(event.payload, "threadId");
|
|
1263
|
-
const activity = readRecord(event.payload, "activity");
|
|
1264
|
-
if (!runId || !activity) {
|
|
1265
|
-
return { status: "ignored", snapshot };
|
|
1266
|
-
}
|
|
1267
|
-
const actionId = readString(activity, "id");
|
|
1268
|
-
const actionType = readString(activity, "kind");
|
|
1269
|
-
const title = readString(activity, "summary");
|
|
1270
|
-
const startedAt = readString(activity, "createdAt");
|
|
1271
|
-
if (!actionId || !actionType || !title || !startedAt) {
|
|
1272
|
-
return { status: "ignored", snapshot };
|
|
1273
|
-
}
|
|
1274
|
-
const payload2 = readRecord(activity, "payload") ?? {};
|
|
1275
|
-
const detail = readString(payload2, "detail") ?? null;
|
|
1276
|
-
const action = {
|
|
1277
|
-
id: asActionId(actionId),
|
|
1278
|
-
runId: asRunId(runId),
|
|
1279
|
-
messageId: null,
|
|
1280
|
-
actionType,
|
|
1281
|
-
title,
|
|
1282
|
-
detail,
|
|
1283
|
-
state: "completed",
|
|
1284
|
-
payload: payload2,
|
|
1285
|
-
startedAt,
|
|
1286
|
-
completedAt: startedAt
|
|
1287
|
-
};
|
|
1288
|
-
let nextSnapshot = {
|
|
1289
|
-
...snapshot,
|
|
1290
|
-
actions: upsertById(snapshot.actions, action),
|
|
1291
|
-
approvals: applyApprovalActivity(snapshot.approvals, asRunId(runId), action),
|
|
1292
|
-
runs: patchById(snapshot.runs, runId, (run) => ({
|
|
1293
|
-
...run,
|
|
1294
|
-
updatedAt: maxIsoDate(run.updatedAt, startedAt),
|
|
1295
|
-
status: actionType === "engine.runtime.ready" ? "running" : actionType === "engine.runtime.failed" ? "failed" : run.status,
|
|
1296
|
-
errorText: actionType === "engine.runtime.failed" && detail ? detail : run.errorText,
|
|
1297
|
-
completedAt: actionType === "engine.runtime.failed" ? startedAt : run.completedAt
|
|
1298
|
-
}))
|
|
1299
|
-
};
|
|
1300
|
-
nextSnapshot = reconcileRunCounts(nextSnapshot, asRunId(runId));
|
|
1301
|
-
return {
|
|
1302
|
-
status: "applied",
|
|
1303
|
-
snapshot: withSnapshotMetadata(nextSnapshot, event, {})
|
|
1304
|
-
};
|
|
1305
|
-
}
|
|
1306
|
-
if (event.type === "legacy.thread.reverted") {
|
|
1307
|
-
return { status: "requires-resync", snapshot };
|
|
1308
|
-
}
|
|
1309
|
-
return { status: "ignored", snapshot };
|
|
1310
|
-
}
|
|
1311
|
-
function applyEngineEvent(snapshot, event) {
|
|
1312
|
-
if (event.sequence <= snapshot.snapshotSequence) {
|
|
1313
|
-
return { status: "ignored", snapshot };
|
|
1314
|
-
}
|
|
1315
|
-
if (event.sequence > snapshot.snapshotSequence + 1) {
|
|
1316
|
-
return { status: "requires-resync", snapshot };
|
|
1317
|
-
}
|
|
1318
|
-
const base = {
|
|
1319
|
-
...snapshot,
|
|
1320
|
-
snapshotSequence: event.sequence,
|
|
1321
|
-
updatedAt: event.createdAt
|
|
1322
|
-
};
|
|
1323
|
-
const payload = event.payload && typeof event.payload === "object" ? event.payload : {};
|
|
1324
|
-
switch (event.type) {
|
|
1325
|
-
case "WorkspaceRegistered":
|
|
1326
|
-
case "RigWorkspaceImported": {
|
|
1327
|
-
const workspace = {
|
|
1328
|
-
id: payload.workspaceId,
|
|
1329
|
-
title: payload.title,
|
|
1330
|
-
rootPath: payload.rootPath,
|
|
1331
|
-
sourceKind: payload.sourceKind,
|
|
1332
|
-
defaultModel: payload.defaultModel ?? null,
|
|
1333
|
-
topology: undefined,
|
|
1334
|
-
remoteFleet: undefined,
|
|
1335
|
-
serviceFabric: undefined,
|
|
1336
|
-
createdAt: payload.createdAt,
|
|
1337
|
-
updatedAt: payload.createdAt
|
|
1338
|
-
};
|
|
1339
|
-
return {
|
|
1340
|
-
status: "applied",
|
|
1341
|
-
snapshot: {
|
|
1342
|
-
...base,
|
|
1343
|
-
workspaces: upsertById(base.workspaces, workspace)
|
|
1344
|
-
}
|
|
1345
|
-
};
|
|
1346
|
-
}
|
|
1347
|
-
case "RigStateHydrated": {
|
|
1348
|
-
const workspaceRunIds = new Set([
|
|
1349
|
-
...base.runs.filter((run) => run.workspaceId === payload.workspaceId).map((run) => run.id),
|
|
1350
|
-
...(payload.runs ?? []).map((run) => run.id)
|
|
1351
|
-
]);
|
|
1352
|
-
const withoutWorkspaceRunData = (items) => items.filter((item) => !workspaceRunIds.has(item.runId));
|
|
1353
|
-
const workspaces = payload.rootPath ? patchById(base.workspaces, payload.workspaceId, (workspace) => ({
|
|
1354
|
-
...workspace,
|
|
1355
|
-
rootPath: payload.rootPath,
|
|
1356
|
-
updatedAt: payload.createdAt
|
|
1357
|
-
})) : base.workspaces;
|
|
1358
|
-
return {
|
|
1359
|
-
status: "applied",
|
|
1360
|
-
snapshot: {
|
|
1361
|
-
...base,
|
|
1362
|
-
workspaces,
|
|
1363
|
-
graphs: upsertById(base.graphs, payload.graph),
|
|
1364
|
-
tasks: replaceWorkspaceSlice(base.tasks, payload.workspaceId, payload.tasks ?? []),
|
|
1365
|
-
runs: replaceWorkspaceSlice(base.runs, payload.workspaceId, payload.runs ?? []),
|
|
1366
|
-
runtimes: replaceWorkspaceSlice(base.runtimes, payload.workspaceId, payload.runtimes ?? []),
|
|
1367
|
-
actions: mergeById(withoutWorkspaceRunData(base.actions), payload.actions ?? []),
|
|
1368
|
-
logs: mergeById(withoutWorkspaceRunData(base.logs), payload.logs ?? []),
|
|
1369
|
-
userInputs: (base.userInputs ?? []).filter((request) => !workspaceRunIds.has(request.runId)),
|
|
1370
|
-
validations: mergeById(withoutWorkspaceRunData(base.validations), payload.validations ?? []),
|
|
1371
|
-
reviews: mergeById(withoutWorkspaceRunData(base.reviews), payload.reviews ?? []),
|
|
1372
|
-
artifacts: mergeById(withoutWorkspaceRunData(base.artifacts), payload.artifacts ?? []),
|
|
1373
|
-
policyDecisions: mergeById(withoutWorkspaceRunData(base.policyDecisions), payload.policyDecisions ?? []),
|
|
1374
|
-
queue: [...payload.queue ?? []]
|
|
1375
|
-
}
|
|
1376
|
-
};
|
|
1377
|
-
}
|
|
1378
|
-
case "WorkspaceTopologyCompiled": {
|
|
1379
|
-
return {
|
|
1380
|
-
status: "applied",
|
|
1381
|
-
snapshot: {
|
|
1382
|
-
...base,
|
|
1383
|
-
workspaces: patchById(base.workspaces, payload.workspaceId, (workspace) => ({
|
|
1384
|
-
...workspace,
|
|
1385
|
-
rootPath: payload.rootPath ?? workspace.rootPath,
|
|
1386
|
-
topology: payload.topology,
|
|
1387
|
-
updatedAt: payload.createdAt
|
|
1388
|
-
}))
|
|
1389
|
-
}
|
|
1390
|
-
};
|
|
1391
|
-
}
|
|
1392
|
-
case "WorkspaceRemoteFleetSynced": {
|
|
1393
|
-
return {
|
|
1394
|
-
status: "applied",
|
|
1395
|
-
snapshot: {
|
|
1396
|
-
...base,
|
|
1397
|
-
workspaces: patchById(base.workspaces, payload.workspaceId, (workspace) => ({
|
|
1398
|
-
...workspace,
|
|
1399
|
-
rootPath: payload.rootPath ?? workspace.rootPath,
|
|
1400
|
-
remoteFleet: payload.remoteFleet,
|
|
1401
|
-
updatedAt: payload.createdAt
|
|
1402
|
-
}))
|
|
1403
|
-
}
|
|
1404
|
-
};
|
|
1405
|
-
}
|
|
1406
|
-
case "WorkspaceServiceFabricSynced": {
|
|
1407
|
-
return {
|
|
1408
|
-
status: "applied",
|
|
1409
|
-
snapshot: {
|
|
1410
|
-
...base,
|
|
1411
|
-
workspaces: patchById(base.workspaces, payload.workspaceId, (workspace) => ({
|
|
1412
|
-
...workspace,
|
|
1413
|
-
rootPath: payload.rootPath ?? workspace.rootPath,
|
|
1414
|
-
serviceFabric: payload.serviceFabric,
|
|
1415
|
-
updatedAt: payload.createdAt
|
|
1416
|
-
}))
|
|
1417
|
-
}
|
|
1418
|
-
};
|
|
1419
|
-
}
|
|
1420
|
-
case "WorkspaceRemoteHostRegistered": {
|
|
1421
|
-
return {
|
|
1422
|
-
status: "applied",
|
|
1423
|
-
snapshot: {
|
|
1424
|
-
...base,
|
|
1425
|
-
workspaces: patchById(base.workspaces, payload.workspaceId, (workspace) => {
|
|
1426
|
-
const withoutExisting = (workspace.remoteFleet?.hosts ?? []).filter((host) => host.id !== payload.host.id);
|
|
1427
|
-
return {
|
|
1428
|
-
...workspace,
|
|
1429
|
-
remoteFleet: buildRemoteFleetSummary({
|
|
1430
|
-
fleet: workspace.remoteFleet,
|
|
1431
|
-
hosts: [...withoutExisting, payload.host],
|
|
1432
|
-
updatedAt: payload.createdAt
|
|
1433
|
-
}),
|
|
1434
|
-
updatedAt: payload.createdAt
|
|
1435
|
-
};
|
|
1436
|
-
})
|
|
1437
|
-
}
|
|
1438
|
-
};
|
|
1439
|
-
}
|
|
1440
|
-
case "WorkspaceRemoteHostStatusUpdated": {
|
|
1441
|
-
return {
|
|
1442
|
-
status: "applied",
|
|
1443
|
-
snapshot: {
|
|
1444
|
-
...base,
|
|
1445
|
-
workspaces: patchById(base.workspaces, payload.workspaceId, (workspace) => {
|
|
1446
|
-
const fleet = workspace.remoteFleet;
|
|
1447
|
-
if (!fleet) {
|
|
1448
|
-
return {
|
|
1449
|
-
...workspace,
|
|
1450
|
-
updatedAt: payload.createdAt
|
|
1451
|
-
};
|
|
1452
|
-
}
|
|
1453
|
-
const hosts = fleet.hosts.map((host) => host.id !== payload.hostId ? host : {
|
|
1454
|
-
...host,
|
|
1455
|
-
status: payload.status,
|
|
1456
|
-
currentLeaseCount: typeof payload.currentLeaseCount === "number" ? payload.currentLeaseCount : host.currentLeaseCount,
|
|
1457
|
-
lastHeartbeatAt: payload.lastHeartbeatAt ?? host.lastHeartbeatAt
|
|
1458
|
-
});
|
|
1459
|
-
return {
|
|
1460
|
-
...workspace,
|
|
1461
|
-
remoteFleet: buildRemoteFleetSummary({
|
|
1462
|
-
fleet,
|
|
1463
|
-
hosts,
|
|
1464
|
-
updatedAt: payload.createdAt
|
|
1465
|
-
}),
|
|
1466
|
-
updatedAt: payload.createdAt
|
|
1467
|
-
};
|
|
1468
|
-
})
|
|
1469
|
-
}
|
|
1470
|
-
};
|
|
1471
|
-
}
|
|
1472
|
-
case "RunCreated": {
|
|
1473
|
-
const run = {
|
|
1474
|
-
id: payload.runId,
|
|
1475
|
-
workspaceId: payload.workspaceId,
|
|
1476
|
-
taskId: payload.taskId ?? null,
|
|
1477
|
-
title: payload.title,
|
|
1478
|
-
runKind: payload.runKind,
|
|
1479
|
-
mode: payload.mode,
|
|
1480
|
-
runtimeMode: payload.runtimeMode,
|
|
1481
|
-
interactionMode: payload.interactionMode,
|
|
1482
|
-
status: "created",
|
|
1483
|
-
runtimeAdapter: CANONICAL_RUNTIME_ADAPTER,
|
|
1484
|
-
model: payload.model ?? null,
|
|
1485
|
-
initialPrompt: payload.initialPrompt ?? null,
|
|
1486
|
-
executionTarget: payload.executionTarget ?? (payload.remoteHostId ? "remote" : "local"),
|
|
1487
|
-
remoteHostId: payload.remoteHostId ?? null,
|
|
1488
|
-
remoteLeaseId: null,
|
|
1489
|
-
remoteLeaseClaimedAt: null,
|
|
1490
|
-
activeRuntimeId: null,
|
|
1491
|
-
latestMessageId: null,
|
|
1492
|
-
pendingApprovalCount: 0,
|
|
1493
|
-
pendingUserInputCount: 0,
|
|
1494
|
-
branch: null,
|
|
1495
|
-
worktreePath: null,
|
|
1496
|
-
errorText: null,
|
|
1497
|
-
createdAt: payload.createdAt,
|
|
1498
|
-
updatedAt: payload.createdAt,
|
|
1499
|
-
startedAt: null,
|
|
1500
|
-
completedAt: null
|
|
1501
|
-
};
|
|
1502
|
-
return {
|
|
1503
|
-
status: "applied",
|
|
1504
|
-
snapshot: { ...base, runs: upsertById(base.runs, run) }
|
|
1505
|
-
};
|
|
1506
|
-
}
|
|
1507
|
-
case "RunInterrupted":
|
|
1508
|
-
case "RunCancelled":
|
|
1509
|
-
return {
|
|
1510
|
-
status: "applied",
|
|
1511
|
-
snapshot: {
|
|
1512
|
-
...base,
|
|
1513
|
-
runs: patchById(base.runs, payload.runId, (run) => ({
|
|
1514
|
-
...run,
|
|
1515
|
-
status: "stopped",
|
|
1516
|
-
updatedAt: payload.createdAt,
|
|
1517
|
-
completedAt: payload.createdAt
|
|
1518
|
-
}))
|
|
1519
|
-
}
|
|
1520
|
-
};
|
|
1521
|
-
case "RunCompleted":
|
|
1522
|
-
return {
|
|
1523
|
-
status: "applied",
|
|
1524
|
-
snapshot: {
|
|
1525
|
-
...base,
|
|
1526
|
-
runs: patchById(base.runs, payload.runId, (run) => ({
|
|
1527
|
-
...run,
|
|
1528
|
-
status: "completed",
|
|
1529
|
-
updatedAt: payload.createdAt,
|
|
1530
|
-
completedAt: payload.createdAt
|
|
1531
|
-
}))
|
|
1532
|
-
}
|
|
1533
|
-
};
|
|
1534
|
-
case "RunStatusSet":
|
|
1535
|
-
return {
|
|
1536
|
-
status: "applied",
|
|
1537
|
-
snapshot: {
|
|
1538
|
-
...base,
|
|
1539
|
-
runs: patchById(base.runs, payload.runId, (run) => ({
|
|
1540
|
-
...run,
|
|
1541
|
-
status: payload.status,
|
|
1542
|
-
updatedAt: payload.createdAt,
|
|
1543
|
-
completedAt: null
|
|
1544
|
-
}))
|
|
1545
|
-
}
|
|
1546
|
-
};
|
|
1547
|
-
case "RunFailed":
|
|
1548
|
-
return {
|
|
1549
|
-
status: "applied",
|
|
1550
|
-
snapshot: {
|
|
1551
|
-
...base,
|
|
1552
|
-
runs: patchById(base.runs, payload.runId, (run) => ({
|
|
1553
|
-
...run,
|
|
1554
|
-
status: "failed",
|
|
1555
|
-
errorText: payload.errorText ?? null,
|
|
1556
|
-
updatedAt: payload.createdAt,
|
|
1557
|
-
completedAt: payload.createdAt
|
|
1558
|
-
}))
|
|
1559
|
-
}
|
|
1560
|
-
};
|
|
1561
|
-
case "RuntimeModeSet":
|
|
1562
|
-
return {
|
|
1563
|
-
status: "applied",
|
|
1564
|
-
snapshot: {
|
|
1565
|
-
...base,
|
|
1566
|
-
runs: patchById(base.runs, payload.runId, (run) => ({
|
|
1567
|
-
...run,
|
|
1568
|
-
runtimeMode: payload.runtimeMode,
|
|
1569
|
-
updatedAt: payload.createdAt
|
|
1570
|
-
}))
|
|
1571
|
-
}
|
|
1572
|
-
};
|
|
1573
|
-
case "InteractionModeSet":
|
|
1574
|
-
return {
|
|
1575
|
-
status: "applied",
|
|
1576
|
-
snapshot: {
|
|
1577
|
-
...base,
|
|
1578
|
-
runs: patchById(base.runs, payload.runId, (run) => ({
|
|
1579
|
-
...run,
|
|
1580
|
-
interactionMode: payload.interactionMode,
|
|
1581
|
-
updatedAt: payload.createdAt
|
|
1582
|
-
}))
|
|
1583
|
-
}
|
|
1584
|
-
};
|
|
1585
|
-
case "ConversationAttached": {
|
|
1586
|
-
const conversation = {
|
|
1587
|
-
id: payload.conversationId,
|
|
1588
|
-
runId: payload.runId,
|
|
1589
|
-
title: payload.title,
|
|
1590
|
-
createdAt: payload.createdAt,
|
|
1591
|
-
updatedAt: payload.createdAt
|
|
1592
|
-
};
|
|
1593
|
-
return {
|
|
1594
|
-
status: "applied",
|
|
1595
|
-
snapshot: {
|
|
1596
|
-
...base,
|
|
1597
|
-
conversations: upsertById(base.conversations, conversation)
|
|
1598
|
-
}
|
|
1599
|
-
};
|
|
1600
|
-
}
|
|
1601
|
-
case "MessageAppended": {
|
|
1602
|
-
const conversation = base.conversations.find((item) => item.runId === payload.runId);
|
|
1603
|
-
if (!conversation) {
|
|
1604
|
-
return { status: "ignored", snapshot: base };
|
|
1605
|
-
}
|
|
1606
|
-
const conversationId = conversation.id;
|
|
1607
|
-
const message = {
|
|
1608
|
-
id: payload.messageId,
|
|
1609
|
-
conversationId,
|
|
1610
|
-
role: payload.role,
|
|
1611
|
-
text: payload.text,
|
|
1612
|
-
attachments: payload.attachments ?? [],
|
|
1613
|
-
state: payload.state ?? "completed",
|
|
1614
|
-
createdAt: payload.createdAt,
|
|
1615
|
-
completedAt: payload.completedAt ?? payload.createdAt
|
|
1616
|
-
};
|
|
1617
|
-
return {
|
|
1618
|
-
status: "applied",
|
|
1619
|
-
snapshot: {
|
|
1620
|
-
...base,
|
|
1621
|
-
messages: upsertById(base.messages, message),
|
|
1622
|
-
runs: patchById(base.runs, payload.runId, (run) => ({
|
|
1623
|
-
...run,
|
|
1624
|
-
latestMessageId: payload.messageId,
|
|
1625
|
-
updatedAt: payload.createdAt
|
|
1626
|
-
}))
|
|
1627
|
-
}
|
|
1628
|
-
};
|
|
1629
|
-
}
|
|
1630
|
-
case "ActionStarted": {
|
|
1631
|
-
const action = {
|
|
1632
|
-
id: payload.actionId,
|
|
1633
|
-
runId: payload.runId,
|
|
1634
|
-
messageId: payload.messageId ?? null,
|
|
1635
|
-
actionType: payload.actionType,
|
|
1636
|
-
title: payload.title,
|
|
1637
|
-
detail: payload.detail ?? null,
|
|
1638
|
-
state: payload.state ?? "running",
|
|
1639
|
-
payload: payload.payload ?? null,
|
|
1640
|
-
startedAt: payload.startedAt,
|
|
1641
|
-
completedAt: null
|
|
1642
|
-
};
|
|
1643
|
-
return {
|
|
1644
|
-
status: "applied",
|
|
1645
|
-
snapshot: {
|
|
1646
|
-
...base,
|
|
1647
|
-
actions: upsertById(base.actions, action)
|
|
1648
|
-
}
|
|
1649
|
-
};
|
|
1650
|
-
}
|
|
1651
|
-
case "ActionCompleted":
|
|
1652
|
-
return {
|
|
1653
|
-
status: "applied",
|
|
1654
|
-
snapshot: {
|
|
1655
|
-
...base,
|
|
1656
|
-
actions: patchById(base.actions, payload.actionId, (action) => ({
|
|
1657
|
-
...action,
|
|
1658
|
-
state: payload.state,
|
|
1659
|
-
detail: payload.detail ?? null,
|
|
1660
|
-
payload: payload.payload,
|
|
1661
|
-
completedAt: payload.completedAt
|
|
1662
|
-
}))
|
|
1663
|
-
}
|
|
1664
|
-
};
|
|
1665
|
-
case "RunLogAppended": {
|
|
1666
|
-
const log = {
|
|
1667
|
-
id: payload.logId,
|
|
1668
|
-
runId: payload.runId,
|
|
1669
|
-
title: payload.title,
|
|
1670
|
-
detail: payload.detail ?? null,
|
|
1671
|
-
tone: payload.tone,
|
|
1672
|
-
status: payload.status ?? null,
|
|
1673
|
-
payload: payload.payload ?? null,
|
|
1674
|
-
createdAt: payload.createdAt
|
|
1675
|
-
};
|
|
1676
|
-
return {
|
|
1677
|
-
status: "applied",
|
|
1678
|
-
snapshot: {
|
|
1679
|
-
...base,
|
|
1680
|
-
logs: upsertById(base.logs, log),
|
|
1681
|
-
runs: patchById(base.runs, payload.runId, (run) => ({
|
|
1682
|
-
...run,
|
|
1683
|
-
updatedAt: payload.createdAt
|
|
1684
|
-
}))
|
|
1685
|
-
}
|
|
1686
|
-
};
|
|
1687
|
-
}
|
|
1688
|
-
case "ValidationRecorded":
|
|
1689
|
-
return {
|
|
1690
|
-
status: "applied",
|
|
1691
|
-
snapshot: {
|
|
1692
|
-
...base,
|
|
1693
|
-
validations: upsertById(base.validations, {
|
|
1694
|
-
id: payload.id,
|
|
1695
|
-
runId: payload.runId,
|
|
1696
|
-
taskId: payload.taskId ?? null,
|
|
1697
|
-
validatorKey: payload.validatorKey,
|
|
1698
|
-
status: payload.status,
|
|
1699
|
-
output: payload.output,
|
|
1700
|
-
startedAt: payload.startedAt,
|
|
1701
|
-
completedAt: payload.completedAt ?? null
|
|
1702
|
-
})
|
|
1703
|
-
}
|
|
1704
|
-
};
|
|
1705
|
-
case "ReviewRecorded":
|
|
1706
|
-
return {
|
|
1707
|
-
status: "applied",
|
|
1708
|
-
snapshot: {
|
|
1709
|
-
...base,
|
|
1710
|
-
reviews: upsertById(base.reviews, {
|
|
1711
|
-
id: payload.id,
|
|
1712
|
-
runId: payload.runId,
|
|
1713
|
-
taskId: payload.taskId ?? null,
|
|
1714
|
-
provider: payload.provider,
|
|
1715
|
-
mode: payload.mode,
|
|
1716
|
-
status: payload.status,
|
|
1717
|
-
summary: payload.summary ?? null,
|
|
1718
|
-
output: payload.output,
|
|
1719
|
-
createdAt: payload.createdAt,
|
|
1720
|
-
completedAt: payload.completedAt ?? null
|
|
1721
|
-
})
|
|
1722
|
-
}
|
|
1723
|
-
};
|
|
1724
|
-
case "ArtifactRegistered":
|
|
1725
|
-
return {
|
|
1726
|
-
status: "applied",
|
|
1727
|
-
snapshot: {
|
|
1728
|
-
...base,
|
|
1729
|
-
artifacts: upsertById(base.artifacts, {
|
|
1730
|
-
id: payload.id,
|
|
1731
|
-
runId: payload.runId,
|
|
1732
|
-
taskId: payload.taskId ?? null,
|
|
1733
|
-
kind: payload.kind,
|
|
1734
|
-
label: payload.label,
|
|
1735
|
-
path: payload.path ?? null,
|
|
1736
|
-
url: payload.url ?? null,
|
|
1737
|
-
metadata: payload.metadata ?? {},
|
|
1738
|
-
createdAt: payload.createdAt
|
|
1739
|
-
})
|
|
1740
|
-
}
|
|
1741
|
-
};
|
|
1742
|
-
case "ApprovalRequested": {
|
|
1743
|
-
const run = base.runs.find((item) => item.id === payload.runId);
|
|
1744
|
-
const approval = {
|
|
1745
|
-
id: payload.requestId,
|
|
1746
|
-
runId: payload.runId,
|
|
1747
|
-
actionId: payload.actionId ?? null,
|
|
1748
|
-
requestKind: payload.requestKind,
|
|
1749
|
-
status: "pending",
|
|
1750
|
-
payload: payload.payload ?? null,
|
|
1751
|
-
createdAt: payload.createdAt,
|
|
1752
|
-
resolvedAt: null
|
|
1753
|
-
};
|
|
1754
|
-
return {
|
|
1755
|
-
status: "applied",
|
|
1756
|
-
snapshot: {
|
|
1757
|
-
...base,
|
|
1758
|
-
approvals: upsertById(base.approvals, approval),
|
|
1759
|
-
runs: patchById(base.runs, payload.runId, (item) => ({
|
|
1760
|
-
...item,
|
|
1761
|
-
pendingApprovalCount: (run?.pendingApprovalCount ?? 0) + 1,
|
|
1762
|
-
status: "waiting-approval",
|
|
1763
|
-
updatedAt: payload.createdAt
|
|
1764
|
-
}))
|
|
1765
|
-
}
|
|
1766
|
-
};
|
|
1767
|
-
}
|
|
1768
|
-
case "ApprovalResolved": {
|
|
1769
|
-
const approval = base.approvals.find((item) => item.id === payload.requestId && item.runId === payload.runId);
|
|
1770
|
-
if (!approval) {
|
|
1771
|
-
return { status: "applied", snapshot: base };
|
|
1772
|
-
}
|
|
1773
|
-
const run = base.runs.find((item) => item.id === payload.runId);
|
|
1774
|
-
const pendingApprovalCount = run ? Math.max(0, Number(run.pendingApprovalCount) - 1) : 0;
|
|
1775
|
-
const nextStatus = run?.status === "waiting-approval" && pendingApprovalCount === 0 ? "running" : run?.status;
|
|
1776
|
-
return {
|
|
1777
|
-
status: "applied",
|
|
1778
|
-
snapshot: {
|
|
1779
|
-
...base,
|
|
1780
|
-
approvals: patchById(base.approvals, payload.requestId, (item) => ({
|
|
1781
|
-
...item,
|
|
1782
|
-
status: "resolved",
|
|
1783
|
-
resolvedAt: payload.createdAt
|
|
1784
|
-
})),
|
|
1785
|
-
runs: patchById(base.runs, payload.runId, (item) => ({
|
|
1786
|
-
...item,
|
|
1787
|
-
pendingApprovalCount,
|
|
1788
|
-
...nextStatus ? { status: nextStatus } : {},
|
|
1789
|
-
updatedAt: payload.createdAt
|
|
1790
|
-
}))
|
|
1791
|
-
}
|
|
1792
|
-
};
|
|
1793
|
-
}
|
|
1794
|
-
case "UserInputRequested": {
|
|
1795
|
-
const run = base.runs.find((item) => item.id === payload.runId);
|
|
1796
|
-
return {
|
|
1797
|
-
status: "applied",
|
|
1798
|
-
snapshot: {
|
|
1799
|
-
...base,
|
|
1800
|
-
userInputs: upsertById(base.userInputs ?? [], {
|
|
1801
|
-
id: payload.requestId,
|
|
1802
|
-
runId: payload.runId,
|
|
1803
|
-
status: "pending",
|
|
1804
|
-
payload: payload.payload,
|
|
1805
|
-
createdAt: payload.createdAt,
|
|
1806
|
-
resolvedAt: null
|
|
1807
|
-
}),
|
|
1808
|
-
runs: patchById(base.runs, payload.runId, (item) => ({
|
|
1809
|
-
...item,
|
|
1810
|
-
pendingUserInputCount: (run?.pendingUserInputCount ?? 0) + 1,
|
|
1811
|
-
status: "waiting-user-input",
|
|
1812
|
-
updatedAt: payload.createdAt
|
|
1813
|
-
}))
|
|
1814
|
-
}
|
|
1815
|
-
};
|
|
1816
|
-
}
|
|
1817
|
-
case "UserInputResolved": {
|
|
1818
|
-
const run = base.runs.find((item) => item.id === payload.runId);
|
|
1819
|
-
const pendingUserInputCount = run ? Math.max(0, Number(run.pendingUserInputCount) - 1) : 0;
|
|
1820
|
-
const nextStatus = run?.status === "waiting-user-input" && pendingUserInputCount === 0 ? "running" : run?.status;
|
|
1821
|
-
return {
|
|
1822
|
-
status: "applied",
|
|
1823
|
-
snapshot: {
|
|
1824
|
-
...base,
|
|
1825
|
-
userInputs: patchById(base.userInputs ?? [], payload.requestId, (item) => ({
|
|
1826
|
-
...item,
|
|
1827
|
-
status: "resolved",
|
|
1828
|
-
resolvedAt: payload.createdAt
|
|
1829
|
-
})),
|
|
1830
|
-
runs: patchById(base.runs, payload.runId, (item) => ({
|
|
1831
|
-
...item,
|
|
1832
|
-
pendingUserInputCount,
|
|
1833
|
-
...nextStatus ? { status: nextStatus } : {},
|
|
1834
|
-
updatedAt: payload.createdAt
|
|
1835
|
-
}))
|
|
1836
|
-
}
|
|
1837
|
-
};
|
|
1838
|
-
}
|
|
1839
|
-
case "RuntimePrepared": {
|
|
1840
|
-
const runtime = {
|
|
1841
|
-
id: payload.runtimeId,
|
|
1842
|
-
workspaceId: payload.workspaceId,
|
|
1843
|
-
runId: payload.runId,
|
|
1844
|
-
adapterKind: payload.adapter,
|
|
1845
|
-
executionTarget: payload.executionTarget ?? "local",
|
|
1846
|
-
remoteHostId: payload.remoteHostId ?? null,
|
|
1847
|
-
status: "prepared",
|
|
1848
|
-
sandboxMode: "read-only",
|
|
1849
|
-
isolationMode: "none",
|
|
1850
|
-
workspaceDir: null,
|
|
1851
|
-
homeDir: null,
|
|
1852
|
-
tmpDir: null,
|
|
1853
|
-
cacheDir: null,
|
|
1854
|
-
logsDir: null,
|
|
1855
|
-
stateDir: null,
|
|
1856
|
-
sessionDir: null,
|
|
1857
|
-
sessionLogPath: null,
|
|
1858
|
-
pid: null,
|
|
1859
|
-
startedAt: null,
|
|
1860
|
-
updatedAt: payload.createdAt,
|
|
1861
|
-
exitedAt: null
|
|
1862
|
-
};
|
|
1863
|
-
return {
|
|
1864
|
-
status: "applied",
|
|
1865
|
-
snapshot: {
|
|
1866
|
-
...base,
|
|
1867
|
-
runtimes: upsertById(base.runtimes, runtime),
|
|
1868
|
-
runs: patchById(base.runs, payload.runId, (run) => ({
|
|
1869
|
-
...run,
|
|
1870
|
-
status: "preparing",
|
|
1871
|
-
executionTarget: payload.executionTarget ?? run.executionTarget ?? "local",
|
|
1872
|
-
remoteHostId: payload.remoteHostId ?? run.remoteHostId ?? null,
|
|
1873
|
-
updatedAt: payload.createdAt
|
|
1874
|
-
}))
|
|
1875
|
-
}
|
|
1876
|
-
};
|
|
1877
|
-
}
|
|
1878
|
-
case "RunRemoteLeaseClaimed":
|
|
1879
|
-
return {
|
|
1880
|
-
status: "applied",
|
|
1881
|
-
snapshot: {
|
|
1882
|
-
...base,
|
|
1883
|
-
runs: patchById(base.runs, payload.runId, (run) => ({
|
|
1884
|
-
...run,
|
|
1885
|
-
remoteHostId: payload.hostId,
|
|
1886
|
-
remoteLeaseId: payload.leaseId,
|
|
1887
|
-
remoteLeaseClaimedAt: payload.createdAt,
|
|
1888
|
-
updatedAt: payload.createdAt
|
|
1889
|
-
}))
|
|
1890
|
-
}
|
|
1891
|
-
};
|
|
1892
|
-
case "RunRemoteLeaseReleased":
|
|
1893
|
-
return {
|
|
1894
|
-
status: "applied",
|
|
1895
|
-
snapshot: {
|
|
1896
|
-
...base,
|
|
1897
|
-
runs: patchById(base.runs, payload.runId, (run) => ({
|
|
1898
|
-
...run,
|
|
1899
|
-
remoteLeaseId: null,
|
|
1900
|
-
remoteLeaseClaimedAt: null,
|
|
1901
|
-
updatedAt: payload.createdAt
|
|
1902
|
-
}))
|
|
1903
|
-
}
|
|
1904
|
-
};
|
|
1905
|
-
case "RuntimeAttached": {
|
|
1906
|
-
const tasks = payload.taskId == null ? base.tasks : patchById(base.tasks, payload.taskId, (task) => ({
|
|
1907
|
-
...task,
|
|
1908
|
-
status: "running",
|
|
1909
|
-
updatedAt: payload.createdAt
|
|
1910
|
-
}));
|
|
1911
|
-
return {
|
|
1912
|
-
status: "applied",
|
|
1913
|
-
snapshot: {
|
|
1914
|
-
...base,
|
|
1915
|
-
runs: patchById(base.runs, payload.runId, (run) => ({
|
|
1916
|
-
...run,
|
|
1917
|
-
activeRuntimeId: payload.runtimeId,
|
|
1918
|
-
status: "running",
|
|
1919
|
-
startedAt: payload.createdAt,
|
|
1920
|
-
updatedAt: payload.createdAt
|
|
1921
|
-
})),
|
|
1922
|
-
runtimes: patchById(base.runtimes, payload.runtimeId, (runtime) => ({
|
|
1923
|
-
...runtime,
|
|
1924
|
-
status: "running",
|
|
1925
|
-
startedAt: payload.createdAt,
|
|
1926
|
-
updatedAt: payload.createdAt
|
|
1927
|
-
})),
|
|
1928
|
-
tasks
|
|
1929
|
-
}
|
|
1930
|
-
};
|
|
1931
|
-
}
|
|
1932
|
-
case "RuntimeDetached":
|
|
1933
|
-
return {
|
|
1934
|
-
status: "applied",
|
|
1935
|
-
snapshot: {
|
|
1936
|
-
...base,
|
|
1937
|
-
runs: patchById(base.runs, payload.runId, (run) => ({
|
|
1938
|
-
...run,
|
|
1939
|
-
activeRuntimeId: null,
|
|
1940
|
-
updatedAt: payload.createdAt
|
|
1941
|
-
})),
|
|
1942
|
-
runtimes: patchById(base.runtimes, payload.runtimeId, (runtime) => ({
|
|
1943
|
-
...runtime,
|
|
1944
|
-
status: "exited",
|
|
1945
|
-
exitedAt: payload.createdAt,
|
|
1946
|
-
updatedAt: payload.createdAt
|
|
1947
|
-
}))
|
|
1948
|
-
}
|
|
1949
|
-
};
|
|
1950
|
-
case "RuntimeMetadataUpdated":
|
|
1951
|
-
return {
|
|
1952
|
-
status: "applied",
|
|
1953
|
-
snapshot: {
|
|
1954
|
-
...base,
|
|
1955
|
-
runs: patchById(base.runs, payload.runId, (run) => ({
|
|
1956
|
-
...run,
|
|
1957
|
-
...payload.branch !== undefined ? { branch: payload.branch } : {},
|
|
1958
|
-
...payload.worktreePath !== undefined ? { worktreePath: payload.worktreePath } : {},
|
|
1959
|
-
updatedAt: payload.createdAt
|
|
1960
|
-
})),
|
|
1961
|
-
runtimes: patchById(base.runtimes, payload.runtimeId, (runtime) => ({
|
|
1962
|
-
...runtime,
|
|
1963
|
-
...payload.sandboxMode !== undefined ? { sandboxMode: payload.sandboxMode } : {},
|
|
1964
|
-
...payload.isolationMode !== undefined ? { isolationMode: payload.isolationMode } : {},
|
|
1965
|
-
...payload.workspaceDir !== undefined ? { workspaceDir: payload.workspaceDir } : {},
|
|
1966
|
-
...payload.homeDir !== undefined ? { homeDir: payload.homeDir } : {},
|
|
1967
|
-
...payload.tmpDir !== undefined ? { tmpDir: payload.tmpDir } : {},
|
|
1968
|
-
...payload.cacheDir !== undefined ? { cacheDir: payload.cacheDir } : {},
|
|
1969
|
-
...payload.logsDir !== undefined ? { logsDir: payload.logsDir } : {},
|
|
1970
|
-
...payload.stateDir !== undefined ? { stateDir: payload.stateDir } : {},
|
|
1971
|
-
...payload.sessionDir !== undefined ? { sessionDir: payload.sessionDir } : {},
|
|
1972
|
-
...payload.sessionLogPath !== undefined ? { sessionLogPath: payload.sessionLogPath } : {},
|
|
1973
|
-
...payload.pid !== undefined ? { pid: payload.pid } : {},
|
|
1974
|
-
updatedAt: payload.createdAt
|
|
1975
|
-
}))
|
|
1976
|
-
}
|
|
1977
|
-
};
|
|
1978
|
-
case "TaskStatusChanged": {
|
|
1979
|
-
const queue = payload.status === "queued" ? base.queue : withQueuePositions(base.queue.filter((item) => item.taskId !== payload.taskId));
|
|
1980
|
-
return {
|
|
1981
|
-
status: "applied",
|
|
1982
|
-
snapshot: {
|
|
1983
|
-
...base,
|
|
1984
|
-
tasks: patchById(base.tasks, payload.taskId, (task) => ({
|
|
1985
|
-
...task,
|
|
1986
|
-
status: payload.status,
|
|
1987
|
-
updatedAt: payload.createdAt
|
|
1988
|
-
})),
|
|
1989
|
-
queue
|
|
1990
|
-
}
|
|
1991
|
-
};
|
|
1992
|
-
}
|
|
1993
|
-
case "TaskEnqueued": {
|
|
1994
|
-
const entry = {
|
|
1995
|
-
taskId: payload.taskId,
|
|
1996
|
-
score: payload.score,
|
|
1997
|
-
unblockCount: 0,
|
|
1998
|
-
position: base.queue.length
|
|
1999
|
-
};
|
|
2000
|
-
const positionedQueue = withQueuePositions([
|
|
2001
|
-
...base.queue.filter((item) => item.taskId !== payload.taskId),
|
|
2002
|
-
entry
|
|
2003
|
-
]);
|
|
2004
|
-
return {
|
|
2005
|
-
status: "applied",
|
|
2006
|
-
snapshot: {
|
|
2007
|
-
...base,
|
|
2008
|
-
tasks: patchById(base.tasks, payload.taskId, (task) => ({
|
|
2009
|
-
...task,
|
|
2010
|
-
status: "queued",
|
|
2011
|
-
updatedAt: payload.createdAt
|
|
2012
|
-
})),
|
|
2013
|
-
queue: positionedQueue
|
|
2014
|
-
}
|
|
2015
|
-
};
|
|
2016
|
-
}
|
|
2017
|
-
case "RemoteEndpointRegistered": {
|
|
2018
|
-
const endpoint = payload.endpoint;
|
|
2019
|
-
if (!endpoint || !endpoint.id) {
|
|
2020
|
-
return { status: "ignored", snapshot: base };
|
|
2021
|
-
}
|
|
2022
|
-
return {
|
|
2023
|
-
status: "applied",
|
|
2024
|
-
snapshot: {
|
|
2025
|
-
...base,
|
|
2026
|
-
remoteEndpoints: upsertById(base.remoteEndpoints, endpoint)
|
|
2027
|
-
}
|
|
2028
|
-
};
|
|
2029
|
-
}
|
|
2030
|
-
case "RemoteEndpointRemoved": {
|
|
2031
|
-
const endpointId = payload.endpointId;
|
|
2032
|
-
if (!endpointId) {
|
|
2033
|
-
return { status: "ignored", snapshot: base };
|
|
2034
|
-
}
|
|
2035
|
-
return {
|
|
2036
|
-
status: "applied",
|
|
2037
|
-
snapshot: {
|
|
2038
|
-
...base,
|
|
2039
|
-
remoteEndpoints: removeById(base.remoteEndpoints, endpointId),
|
|
2040
|
-
remoteConnections: base.remoteConnections.filter((conn) => conn.endpointId !== endpointId),
|
|
2041
|
-
remoteOrchestrations: base.remoteOrchestrations.filter((orch) => orch.endpointId !== endpointId)
|
|
2042
|
-
}
|
|
2043
|
-
};
|
|
2044
|
-
}
|
|
2045
|
-
case "RemoteEndpointUpdated": {
|
|
2046
|
-
const endpointId = payload.endpointId;
|
|
2047
|
-
const updates = payload.updates;
|
|
2048
|
-
if (!endpointId || !updates) {
|
|
2049
|
-
return { status: "ignored", snapshot: base };
|
|
2050
|
-
}
|
|
2051
|
-
return {
|
|
2052
|
-
status: "applied",
|
|
2053
|
-
snapshot: {
|
|
2054
|
-
...base,
|
|
2055
|
-
remoteEndpoints: patchById(base.remoteEndpoints, endpointId, (endpoint) => ({
|
|
2056
|
-
...endpoint,
|
|
2057
|
-
...updates
|
|
2058
|
-
}))
|
|
2059
|
-
}
|
|
2060
|
-
};
|
|
2061
|
-
}
|
|
2062
|
-
case "RemoteConnectionChanged": {
|
|
2063
|
-
const endpointId = payload.endpointId;
|
|
2064
|
-
const status = payload.status;
|
|
2065
|
-
if (!endpointId || !status) {
|
|
2066
|
-
return { status: "ignored", snapshot: base };
|
|
2067
|
-
}
|
|
2068
|
-
const existing = base.remoteConnections.find((conn) => conn.endpointId === endpointId);
|
|
2069
|
-
const connection = {
|
|
2070
|
-
...existing ?? {
|
|
2071
|
-
endpointId,
|
|
2072
|
-
status,
|
|
2073
|
-
error: null,
|
|
2074
|
-
connectedAt: null,
|
|
2075
|
-
tokenExpiresAt: null,
|
|
2076
|
-
latencyMs: null,
|
|
2077
|
-
subscribedEvents: []
|
|
2078
|
-
},
|
|
2079
|
-
endpointId,
|
|
2080
|
-
status,
|
|
2081
|
-
...payload.error !== undefined ? { error: payload.error ?? null } : {},
|
|
2082
|
-
...status === "connected" ? { connectedAt: event.createdAt } : {}
|
|
2083
|
-
};
|
|
2084
|
-
return {
|
|
2085
|
-
status: "applied",
|
|
2086
|
-
snapshot: {
|
|
2087
|
-
...base,
|
|
2088
|
-
remoteConnections: upsertByKey(base.remoteConnections, connection, "endpointId")
|
|
2089
|
-
}
|
|
2090
|
-
};
|
|
2091
|
-
}
|
|
2092
|
-
case "RemoteWorkspaceHydrated": {
|
|
2093
|
-
const endpointId = payload.endpointId;
|
|
2094
|
-
const workspace = payload.workspace;
|
|
2095
|
-
const tasks = payload.tasks;
|
|
2096
|
-
const graph = payload.graph;
|
|
2097
|
-
if (!endpointId || !workspace) {
|
|
2098
|
-
return { status: "ignored", snapshot: base };
|
|
2099
|
-
}
|
|
2100
|
-
return {
|
|
2101
|
-
status: "applied",
|
|
2102
|
-
snapshot: {
|
|
2103
|
-
...base,
|
|
2104
|
-
workspaces: upsertById(base.workspaces, workspace),
|
|
2105
|
-
tasks: Array.isArray(tasks) ? replaceWorkspaceSlice(base.tasks, workspace.id, tasks) : base.tasks,
|
|
2106
|
-
graphs: graph ? upsertById(base.graphs, graph) : base.graphs
|
|
2107
|
-
}
|
|
2108
|
-
};
|
|
2109
|
-
}
|
|
2110
|
-
case "RemoteStateRefreshed": {
|
|
2111
|
-
const endpointId = payload.endpointId;
|
|
2112
|
-
const tasks = payload.tasks;
|
|
2113
|
-
if (!endpointId) {
|
|
2114
|
-
return { status: "ignored", snapshot: base };
|
|
2115
|
-
}
|
|
2116
|
-
const remoteWorkspace = base.workspaces.find((ws) => ws.id === `remote-workspace:${endpointId}`);
|
|
2117
|
-
return {
|
|
2118
|
-
status: "applied",
|
|
2119
|
-
snapshot: {
|
|
2120
|
-
...base,
|
|
2121
|
-
tasks: Array.isArray(tasks) && remoteWorkspace ? replaceWorkspaceSlice(base.tasks, remoteWorkspace.id, tasks) : base.tasks
|
|
2122
|
-
}
|
|
2123
|
-
};
|
|
2124
|
-
}
|
|
2125
|
-
case "RemoteEventReceived":
|
|
2126
|
-
return { status: "applied", snapshot: base };
|
|
2127
|
-
case "RemoteOrchestrationStarted": {
|
|
2128
|
-
const orchestration = payload.orchestration;
|
|
2129
|
-
if (!orchestration || !orchestration.orchestrationId) {
|
|
2130
|
-
return { status: "ignored", snapshot: base };
|
|
2131
|
-
}
|
|
2132
|
-
return {
|
|
2133
|
-
status: "applied",
|
|
2134
|
-
snapshot: {
|
|
2135
|
-
...base,
|
|
2136
|
-
remoteOrchestrations: upsertByKey(base.remoteOrchestrations, orchestration, "orchestrationId")
|
|
2137
|
-
}
|
|
2138
|
-
};
|
|
2139
|
-
}
|
|
2140
|
-
case "RemoteOrchestrationUpdated": {
|
|
2141
|
-
const orchestrationId = payload.orchestrationId;
|
|
2142
|
-
const state = payload.state;
|
|
2143
|
-
if (!orchestrationId) {
|
|
2144
|
-
return { status: "ignored", snapshot: base };
|
|
2145
|
-
}
|
|
2146
|
-
return {
|
|
2147
|
-
status: "applied",
|
|
2148
|
-
snapshot: {
|
|
2149
|
-
...base,
|
|
2150
|
-
remoteOrchestrations: patchByKey(base.remoteOrchestrations, orchestrationId, "orchestrationId", (orch) => ({
|
|
2151
|
-
...orch,
|
|
2152
|
-
...isRecord(state) ? state : {}
|
|
2153
|
-
}))
|
|
2154
|
-
}
|
|
2155
|
-
};
|
|
2156
|
-
}
|
|
2157
|
-
}
|
|
2158
|
-
if (event.type === "workspace.imported") {
|
|
2159
|
-
return { status: "requires-resync", snapshot };
|
|
2160
|
-
}
|
|
2161
|
-
if (event.type === "task.run-linked") {
|
|
2162
|
-
const payload2 = isRecord(event.payload) ? event.payload : {};
|
|
2163
|
-
const runId = readString(payload2, "runId");
|
|
2164
|
-
const taskId = readString(payload2, "taskId");
|
|
2165
|
-
const workspaceId = readString(payload2, "workspaceId");
|
|
2166
|
-
if (!runId || !taskId || !workspaceId) {
|
|
2167
|
-
return { status: "ignored", snapshot };
|
|
2168
|
-
}
|
|
2169
|
-
const baseRun = snapshot.runs.find((run) => run.id === runId) ?? {
|
|
2170
|
-
id: asRunId(runId),
|
|
2171
|
-
workspaceId: asWorkspaceId(workspaceId),
|
|
2172
|
-
taskId: asTaskId(taskId),
|
|
2173
|
-
title: "Task run",
|
|
2174
|
-
runKind: "task",
|
|
2175
|
-
mode: "interactive",
|
|
2176
|
-
runtimeMode: "full-access",
|
|
2177
|
-
interactionMode: "default",
|
|
2178
|
-
status: "created",
|
|
2179
|
-
runtimeAdapter: CANONICAL_RUNTIME_ADAPTER,
|
|
2180
|
-
model: null,
|
|
2181
|
-
initialPrompt: null,
|
|
2182
|
-
activeRuntimeId: runtimeIdFromRunId(asRunId(runId)),
|
|
2183
|
-
latestMessageId: null,
|
|
2184
|
-
pendingApprovalCount: 0,
|
|
2185
|
-
pendingUserInputCount: 0,
|
|
2186
|
-
branch: null,
|
|
2187
|
-
worktreePath: null,
|
|
2188
|
-
errorText: null,
|
|
2189
|
-
createdAt: event.createdAt,
|
|
2190
|
-
updatedAt: event.createdAt,
|
|
2191
|
-
startedAt: event.createdAt,
|
|
2192
|
-
completedAt: null
|
|
2193
|
-
};
|
|
2194
|
-
const nextSnapshot = applyRun(snapshot, {
|
|
2195
|
-
...baseRun,
|
|
2196
|
-
workspaceId: asWorkspaceId(workspaceId),
|
|
2197
|
-
taskId: asTaskId(taskId),
|
|
2198
|
-
runKind: "task",
|
|
2199
|
-
runtimeAdapter: CANONICAL_RUNTIME_ADAPTER,
|
|
2200
|
-
activeRuntimeId: runtimeIdFromRunId(asRunId(runId)),
|
|
2201
|
-
updatedAt: event.createdAt
|
|
2202
|
-
});
|
|
2203
|
-
return {
|
|
2204
|
-
status: "applied",
|
|
2205
|
-
snapshot: withSnapshotMetadata(nextSnapshot, event, {})
|
|
2206
|
-
};
|
|
2207
|
-
}
|
|
2208
|
-
if (event.type.startsWith("runtime.prepare.")) {
|
|
2209
|
-
return applySyntheticRuntimePreparation(snapshot, event);
|
|
2210
|
-
}
|
|
2211
|
-
if (event.type === "runtime.prepared") {
|
|
2212
|
-
return applySyntheticRuntimePrepared(snapshot, event);
|
|
2213
|
-
}
|
|
2214
|
-
if (event.type.startsWith("legacy.project.")) {
|
|
2215
|
-
return applyLegacyProjectEvent(snapshot, event);
|
|
2216
|
-
}
|
|
2217
|
-
if (event.type.startsWith("legacy.thread.")) {
|
|
2218
|
-
return applyLegacyThreadEvent(snapshot, event);
|
|
2219
|
-
}
|
|
2220
|
-
return { status: "ignored", snapshot: base };
|
|
2221
|
-
}
|
|
2222
|
-
function applyEngineEvents(snapshot, events) {
|
|
2223
|
-
let nextSnapshot = snapshot;
|
|
2224
|
-
let requiresResync = false;
|
|
2225
|
-
let applied = false;
|
|
2226
|
-
for (const event of events) {
|
|
2227
|
-
const result = applyEngineEvent(nextSnapshot, event);
|
|
2228
|
-
nextSnapshot = result.snapshot;
|
|
2229
|
-
if (result.status === "requires-resync") {
|
|
2230
|
-
requiresResync = true;
|
|
2231
|
-
}
|
|
2232
|
-
if (result.status === "applied") {
|
|
2233
|
-
applied = true;
|
|
2234
|
-
}
|
|
2235
|
-
}
|
|
2236
|
-
return {
|
|
2237
|
-
status: requiresResync ? "requires-resync" : applied ? "applied" : "ignored",
|
|
2238
|
-
snapshot: nextSnapshot
|
|
2239
|
-
};
|
|
2240
|
-
}
|
|
2241
|
-
function pruneQueueEntries(snapshot) {
|
|
2242
|
-
return snapshot.queue.filter((entry) => {
|
|
2243
|
-
const task = snapshot.tasks.find((candidate) => candidate.id === entry.taskId);
|
|
2244
|
-
return task?.status !== "running" && task?.status !== "in_progress" && task?.status !== "under_review";
|
|
2245
|
-
});
|
|
2246
|
-
}
|
|
2247
|
-
// packages/core/src/rigSelectors.ts
|
|
2248
|
-
function selectWorkspaces(snapshot) {
|
|
2249
|
-
return snapshot?.workspaces ?? [];
|
|
2250
|
-
}
|
|
2251
|
-
function selectPrimaryWorkspace(snapshot) {
|
|
2252
|
-
const workspaces = selectWorkspaces(snapshot);
|
|
2253
|
-
return workspaces.find((workspace) => workspace.sourceKind === "rig-import") ?? workspaces[0] ?? null;
|
|
2254
|
-
}
|
|
2255
|
-
function pickDefaultWorkspaceId(snapshot) {
|
|
2256
|
-
return selectPrimaryWorkspace(snapshot)?.id ?? null;
|
|
2257
|
-
}
|
|
2258
|
-
function selectWorkspace(snapshot, workspaceId) {
|
|
2259
|
-
if (!workspaceId)
|
|
2260
|
-
return null;
|
|
2261
|
-
return snapshot?.workspaces.find((workspace) => workspace.id === workspaceId) ?? null;
|
|
2262
|
-
}
|
|
2263
|
-
function selectTask(snapshot, taskId) {
|
|
2264
|
-
if (!taskId)
|
|
2265
|
-
return null;
|
|
2266
|
-
return snapshot?.tasks.find((task) => task.id === taskId) ?? null;
|
|
2267
|
-
}
|
|
2268
|
-
function selectTasksByWorkspace(snapshot, workspaceId) {
|
|
2269
|
-
if (!workspaceId)
|
|
2270
|
-
return [];
|
|
2271
|
-
return snapshot?.tasks.filter((task) => task.workspaceId === workspaceId) ?? [];
|
|
2272
|
-
}
|
|
2273
|
-
var selectTasksForWorkspace = selectTasksByWorkspace;
|
|
2274
|
-
function selectTasksByStatus(snapshot, status) {
|
|
2275
|
-
return snapshot?.tasks.filter((task) => task.status === status) ?? [];
|
|
2276
|
-
}
|
|
2277
|
-
function projectTaskStatusForGrouping(status) {
|
|
2278
|
-
switch (status) {
|
|
2279
|
-
case "failed":
|
|
2280
|
-
return "ready";
|
|
2281
|
-
case "closed":
|
|
2282
|
-
return "completed";
|
|
2283
|
-
case "running":
|
|
2284
|
-
case "in_progress":
|
|
2285
|
-
case "under_review":
|
|
2286
|
-
return "running";
|
|
2287
|
-
case null:
|
|
2288
|
-
case undefined:
|
|
2289
|
-
case "":
|
|
2290
|
-
return "unknown";
|
|
2291
|
-
default:
|
|
2292
|
-
return status;
|
|
2293
|
-
}
|
|
2294
|
-
}
|
|
2295
|
-
function projectRunStatusForTaskGrouping(status) {
|
|
2296
|
-
switch (status) {
|
|
2297
|
-
case "created":
|
|
2298
|
-
case "queued":
|
|
2299
|
-
case "preparing":
|
|
2300
|
-
return "queued";
|
|
2301
|
-
case "running":
|
|
2302
|
-
case "waiting-approval":
|
|
2303
|
-
case "waiting-user-input":
|
|
2304
|
-
case "paused":
|
|
2305
|
-
case "validating":
|
|
2306
|
-
case "reviewing":
|
|
2307
|
-
case "closing-out":
|
|
2308
|
-
return "running";
|
|
2309
|
-
case "needs-attention":
|
|
2310
|
-
return "blocked";
|
|
2311
|
-
case "failed":
|
|
2312
|
-
case "stopped":
|
|
2313
|
-
return "ready";
|
|
2314
|
-
case "completed":
|
|
2315
|
-
return "completed";
|
|
2316
|
-
case null:
|
|
2317
|
-
case undefined:
|
|
2318
|
-
return null;
|
|
2319
|
-
}
|
|
2320
|
-
}
|
|
2321
|
-
var STATUS_PRIORITY = [
|
|
2322
|
-
"running",
|
|
2323
|
-
"blocked",
|
|
2324
|
-
"queued",
|
|
2325
|
-
"ready",
|
|
2326
|
-
"open",
|
|
2327
|
-
"draft",
|
|
2328
|
-
"unknown",
|
|
2329
|
-
"cancelled",
|
|
2330
|
-
"completed"
|
|
2331
|
-
];
|
|
2332
|
-
function taskIdFromSession(session) {
|
|
2333
|
-
return session.taskId ?? session.record.taskId ?? null;
|
|
2334
|
-
}
|
|
2335
|
-
function latestSessionByTask(sessions) {
|
|
2336
|
-
const byTask = new Map;
|
|
2337
|
-
for (const session of sessions) {
|
|
2338
|
-
const taskId = taskIdFromSession(session);
|
|
2339
|
-
if (!taskId)
|
|
2340
|
-
continue;
|
|
2341
|
-
const existing = byTask.get(taskId);
|
|
2342
|
-
if (!existing || (session.lastEventAt ?? "").localeCompare(existing.lastEventAt ?? "") >= 0) {
|
|
2343
|
-
byTask.set(taskId, session);
|
|
2344
|
-
}
|
|
2345
|
-
}
|
|
2346
|
-
return byTask;
|
|
2347
|
-
}
|
|
2348
|
-
function projectTaskStatusWithSessions(task, sessionsByTask = new Map) {
|
|
2349
|
-
const sessionStatus = projectRunStatusForTaskGrouping(sessionsByTask.get(task.id)?.status);
|
|
2350
|
-
return sessionStatus ?? projectTaskStatusForGrouping(task.status);
|
|
2351
|
-
}
|
|
2352
|
-
function groupTasksByProjectedStatus(tasks, sessions = []) {
|
|
2353
|
-
const sessionsByTask = latestSessionByTask(sessions);
|
|
2354
|
-
const byStatus = new Map;
|
|
2355
|
-
for (const task of tasks) {
|
|
2356
|
-
const projectedStatus = projectTaskStatusWithSessions(task, sessionsByTask);
|
|
2357
|
-
const group = byStatus.get(projectedStatus);
|
|
2358
|
-
if (group) {
|
|
2359
|
-
group.push(task);
|
|
2360
|
-
} else {
|
|
2361
|
-
byStatus.set(projectedStatus, [task]);
|
|
2362
|
-
}
|
|
2363
|
-
}
|
|
2364
|
-
const result = [];
|
|
2365
|
-
for (const status of STATUS_PRIORITY) {
|
|
2366
|
-
const group = byStatus.get(status);
|
|
2367
|
-
if (group && group.length > 0) {
|
|
2368
|
-
result.push({ status, tasks: group });
|
|
2369
|
-
}
|
|
2370
|
-
}
|
|
2371
|
-
const overflowStatuses = Array.from(byStatus.keys()).filter((status) => !STATUS_PRIORITY.includes(status)).sort();
|
|
2372
|
-
for (const status of overflowStatuses) {
|
|
2373
|
-
const group = byStatus.get(status);
|
|
2374
|
-
if (group && group.length > 0) {
|
|
2375
|
-
result.push({ status, tasks: group });
|
|
2376
|
-
}
|
|
2377
|
-
}
|
|
2378
|
-
return result;
|
|
2379
|
-
}
|
|
2380
|
-
function isProjectionGroupingInput(value) {
|
|
2381
|
-
return Boolean(value && typeof value === "object" && !("snapshotSequence" in value) && Array.isArray(value.tasks));
|
|
2382
|
-
}
|
|
2383
|
-
function selectTasksGroupedByStatus(input, workspaceId) {
|
|
2384
|
-
if (isProjectionGroupingInput(input)) {
|
|
2385
|
-
const filteredTasks = input.workspaceId ? input.tasks.filter((task) => {
|
|
2386
|
-
const workspaceTask = task;
|
|
2387
|
-
return workspaceTask.workspaceId === input.workspaceId;
|
|
2388
|
-
}) : input.tasks;
|
|
2389
|
-
return groupTasksByProjectedStatus(filteredTasks, input.sessions);
|
|
2390
|
-
}
|
|
2391
|
-
const tasks = selectTasksByWorkspace(input, workspaceId ?? null);
|
|
2392
|
-
return groupTasksByProjectedStatus(tasks, []);
|
|
2393
|
-
}
|
|
2394
|
-
function isObjectRecord(value) {
|
|
2395
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2396
|
-
}
|
|
2397
|
-
function normalizeLogin(value) {
|
|
2398
|
-
return value.trim().replace(/^@+/, "").toLowerCase();
|
|
2399
|
-
}
|
|
2400
|
-
function assigneeLoginsFromValue(value) {
|
|
2401
|
-
if (!Array.isArray(value))
|
|
2402
|
-
return [];
|
|
2403
|
-
return value.flatMap((entry) => {
|
|
2404
|
-
if (typeof entry === "string" && entry.trim())
|
|
2405
|
-
return [normalizeLogin(entry)];
|
|
2406
|
-
if (isObjectRecord(entry) && typeof entry.login === "string" && entry.login.trim()) {
|
|
2407
|
-
return [normalizeLogin(entry.login)];
|
|
2408
|
-
}
|
|
2409
|
-
return [];
|
|
2410
|
-
});
|
|
2411
|
-
}
|
|
2412
|
-
function normalizeTaskAssigneeFilter(assignee, currentUserLogin) {
|
|
2413
|
-
const trimmed = assignee?.trim();
|
|
2414
|
-
if (!trimmed)
|
|
2415
|
-
return null;
|
|
2416
|
-
if (trimmed === "@me" || trimmed.toLowerCase() === "me") {
|
|
2417
|
-
return currentUserLogin?.trim() ? normalizeLogin(currentUserLogin) : null;
|
|
2418
|
-
}
|
|
2419
|
-
return normalizeLogin(trimmed);
|
|
2420
|
-
}
|
|
2421
|
-
function readTaskAssigneeLogins(task) {
|
|
2422
|
-
const taskRecord = task;
|
|
2423
|
-
const metadata = isObjectRecord(task.metadata) ? task.metadata : null;
|
|
2424
|
-
const raw = isObjectRecord(metadata?.raw) ? metadata.raw : null;
|
|
2425
|
-
return Array.from(new Set([
|
|
2426
|
-
...assigneeLoginsFromValue(taskRecord.assignees),
|
|
2427
|
-
...assigneeLoginsFromValue(metadata?.assignees),
|
|
2428
|
-
...assigneeLoginsFromValue(raw?.assignees)
|
|
2429
|
-
]));
|
|
2430
|
-
}
|
|
2431
|
-
function taskMatchesAssigneeFilter(task, assignee, options = {}) {
|
|
2432
|
-
const normalized = normalizeTaskAssigneeFilter(assignee, options.currentUserLogin);
|
|
2433
|
-
if (!normalized)
|
|
2434
|
-
return false;
|
|
2435
|
-
return readTaskAssigneeLogins(task).includes(normalized);
|
|
2436
|
-
}
|
|
2437
|
-
function selectTasksAssignedTo(tasks, assignee, options = {}) {
|
|
2438
|
-
return tasks.filter((task) => taskMatchesAssigneeFilter(task, assignee, options));
|
|
2439
|
-
}
|
|
2440
|
-
function selectTasksAssignedToMe(tasks, currentUserLogin) {
|
|
2441
|
-
return selectTasksAssignedTo(tasks, "@me", currentUserLogin === undefined ? {} : { currentUserLogin });
|
|
2442
|
-
}
|
|
2443
|
-
function selectRun(snapshot, runId) {
|
|
2444
|
-
if (!runId)
|
|
2445
|
-
return null;
|
|
2446
|
-
return snapshot?.runs.find((run) => run.id === runId) ?? null;
|
|
2447
|
-
}
|
|
2448
|
-
function selectRunsByTask(snapshot, taskId) {
|
|
2449
|
-
if (!taskId)
|
|
2450
|
-
return [];
|
|
2451
|
-
return snapshot?.runs.filter((run) => run.taskId === taskId) ?? [];
|
|
2452
|
-
}
|
|
2453
|
-
var selectRunsForTask = selectRunsByTask;
|
|
2454
|
-
function selectRunsForWorkspace(snapshot, workspaceId) {
|
|
2455
|
-
if (!workspaceId)
|
|
2456
|
-
return [];
|
|
2457
|
-
return snapshot?.runs.filter((run) => run.workspaceId === workspaceId) ?? [];
|
|
2458
|
-
}
|
|
2459
|
-
function selectAdhocRuns(snapshot) {
|
|
2460
|
-
return snapshot?.runs.filter((run) => run.taskId === null) ?? [];
|
|
2461
|
-
}
|
|
2462
|
-
function selectAdhocRunsForWorkspace(snapshot, workspaceId) {
|
|
2463
|
-
if (!workspaceId)
|
|
2464
|
-
return [];
|
|
2465
|
-
return snapshot?.runs.filter((run) => run.taskId === null && run.workspaceId === workspaceId) ?? [];
|
|
2466
|
-
}
|
|
2467
|
-
function selectGraphsForWorkspace(snapshot, workspaceId) {
|
|
2468
|
-
if (!workspaceId)
|
|
2469
|
-
return [];
|
|
2470
|
-
return snapshot?.graphs.filter((graph) => graph.workspaceId === workspaceId) ?? [];
|
|
2471
|
-
}
|
|
2472
|
-
function selectQueueForWorkspace(snapshot, workspaceId) {
|
|
2473
|
-
if (!snapshot || !workspaceId)
|
|
2474
|
-
return [];
|
|
2475
|
-
const taskIds = new Set(selectTasksByWorkspace(snapshot, workspaceId).map((task) => task.id));
|
|
2476
|
-
return snapshot.queue.filter((entry) => taskIds.has(entry.taskId));
|
|
2477
|
-
}
|
|
2478
|
-
function selectPendingApprovals(snapshot) {
|
|
2479
|
-
return snapshot?.approvals.filter((approval) => approval.status === "pending") ?? [];
|
|
2480
|
-
}
|
|
2481
|
-
function selectApprovalsForRun(snapshot, runId) {
|
|
2482
|
-
if (!runId)
|
|
2483
|
-
return [];
|
|
2484
|
-
return snapshot?.approvals.filter((approval) => approval.runId === runId) ?? [];
|
|
2485
|
-
}
|
|
2486
|
-
function selectApprovalsForWorkspace(snapshot, workspaceId) {
|
|
2487
|
-
if (!snapshot || !workspaceId)
|
|
2488
|
-
return [];
|
|
2489
|
-
const runIds = new Set(selectRunsForWorkspace(snapshot, workspaceId).map((run) => run.id));
|
|
2490
|
-
return snapshot.approvals.filter((approval) => runIds.has(approval.runId));
|
|
2491
|
-
}
|
|
2492
|
-
function selectUserInputsForRun(snapshot, runId) {
|
|
2493
|
-
if (!runId)
|
|
2494
|
-
return [];
|
|
2495
|
-
return (snapshot?.userInputs ?? []).filter((request) => request.runId === runId);
|
|
2496
|
-
}
|
|
2497
|
-
function selectUserInputsForWorkspace(snapshot, workspaceId) {
|
|
2498
|
-
if (!snapshot || !workspaceId)
|
|
2499
|
-
return [];
|
|
2500
|
-
const runIds = new Set(selectRunsForWorkspace(snapshot, workspaceId).map((run) => run.id));
|
|
2501
|
-
return (snapshot.userInputs ?? []).filter((request) => runIds.has(request.runId));
|
|
2502
|
-
}
|
|
2503
|
-
// packages/core/src/taskScore.ts
|
|
2504
|
-
var ROLE_WEIGHT = {
|
|
2505
|
-
architect: 25,
|
|
2506
|
-
extractor: 10
|
|
2507
|
-
};
|
|
2508
|
-
var CRITICALITY_WEIGHT = {
|
|
2509
|
-
core: 20,
|
|
2510
|
-
high: 10,
|
|
2511
|
-
normal: 0
|
|
2512
|
-
};
|
|
2513
|
-
function finiteNumber(value, fallback) {
|
|
2514
|
-
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
2515
|
-
}
|
|
2516
|
-
function scoreTask(input) {
|
|
2517
|
-
const priority = finiteNumber(input.priority, 3);
|
|
2518
|
-
const unblockCount = Math.max(0, finiteNumber(input.unblockCount, 0));
|
|
2519
|
-
const roleWeight = input.role ? ROLE_WEIGHT[input.role] ?? 0 : 0;
|
|
2520
|
-
const criticalityWeight = input.criticality ? CRITICALITY_WEIGHT[input.criticality] ?? 0 : 0;
|
|
2521
|
-
const validationWeight = (input.validation ?? []).some((entry) => entry.includes("test-contract") || entry.includes("test-boundary")) ? 8 : 0;
|
|
2522
|
-
const queueWeight = finiteNumber(input.queueWeight, 0);
|
|
2523
|
-
return unblockCount * 100 + (10 - priority) + roleWeight + criticalityWeight + validationWeight + queueWeight;
|
|
2524
|
-
}
|
|
2525
|
-
function rankTasks(tasks, scoreInput, idOf) {
|
|
2526
|
-
return tasks.map((task) => {
|
|
2527
|
-
const input = scoreInput(task);
|
|
2528
|
-
return {
|
|
2529
|
-
task,
|
|
2530
|
-
score: scoreTask(input),
|
|
2531
|
-
priority: finiteNumber(input.priority, 3),
|
|
2532
|
-
unblockCount: Math.max(0, finiteNumber(input.unblockCount, 0))
|
|
2533
|
-
};
|
|
2534
|
-
}).toSorted((left, right) => {
|
|
2535
|
-
const scoreDelta = right.score - left.score;
|
|
2536
|
-
if (scoreDelta !== 0)
|
|
2537
|
-
return scoreDelta;
|
|
2538
|
-
const unblockDelta = right.unblockCount - left.unblockCount;
|
|
2539
|
-
if (unblockDelta !== 0)
|
|
2540
|
-
return unblockDelta;
|
|
2541
|
-
const priorityDelta = left.priority - right.priority;
|
|
2542
|
-
if (priorityDelta !== 0)
|
|
2543
|
-
return priorityDelta;
|
|
2544
|
-
return idOf(left.task).localeCompare(idOf(right.task));
|
|
2545
|
-
});
|
|
2546
|
-
}
|
|
2547
|
-
|
|
2548
|
-
// packages/core/src/taskGraph.ts
|
|
2549
|
-
function isObjectRecord2(value) {
|
|
2550
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2551
|
-
}
|
|
2552
|
-
function readStringList(value) {
|
|
2553
|
-
return Array.isArray(value) ? value.filter((entry) => typeof entry === "string" && entry.length > 0) : [];
|
|
2554
|
-
}
|
|
2555
|
-
function unique(values) {
|
|
2556
|
-
return Array.from(new Set(values));
|
|
2557
|
-
}
|
|
2558
|
-
function readTaskMetadataStringList(task, key) {
|
|
2559
|
-
const taskRecord = task;
|
|
2560
|
-
const topLevel = readStringList(taskRecord[key]);
|
|
2561
|
-
if (topLevel.length > 0)
|
|
2562
|
-
return topLevel;
|
|
2563
|
-
const metadata = isObjectRecord2(task.metadata) ? task.metadata : null;
|
|
2564
|
-
const metadataList = readStringList(metadata?.[key]);
|
|
2565
|
-
if (metadataList.length > 0)
|
|
2566
|
-
return metadataList;
|
|
2567
|
-
if (key === "dependencies") {
|
|
2568
|
-
return readStringList(metadata?.deps);
|
|
2569
|
-
}
|
|
2570
|
-
return [];
|
|
2571
|
-
}
|
|
2572
|
-
function readTaskBlockingDependencyRefs(task) {
|
|
2573
|
-
return readTaskMetadataStringList(task, "dependencies");
|
|
2574
|
-
}
|
|
2575
|
-
function readTaskDependencyRefs(task) {
|
|
2576
|
-
return unique([
|
|
2577
|
-
...readTaskMetadataStringList(task, "dependencies"),
|
|
2578
|
-
...readTaskMetadataStringList(task, "parentChildDeps")
|
|
2579
|
-
]);
|
|
2580
|
-
}
|
|
2581
|
-
function readTaskSourceIssueId(task) {
|
|
2582
|
-
if (typeof task.sourceIssueId === "string" && task.sourceIssueId.length > 0) {
|
|
2583
|
-
return task.sourceIssueId;
|
|
2584
|
-
}
|
|
2585
|
-
const metadata = isObjectRecord2(task.metadata) ? task.metadata : null;
|
|
2586
|
-
if (typeof metadata?.sourceIssueId === "string" && metadata.sourceIssueId.length > 0) {
|
|
2587
|
-
return metadata.sourceIssueId;
|
|
2588
|
-
}
|
|
2589
|
-
const rigMetadata = isObjectRecord2(metadata?._rig) ? metadata._rig : null;
|
|
2590
|
-
return typeof rigMetadata?.sourceIssueId === "string" && rigMetadata.sourceIssueId.length > 0 ? rigMetadata.sourceIssueId : null;
|
|
2591
|
-
}
|
|
2592
|
-
function readTaskScope(task) {
|
|
2593
|
-
const taskRecord = task;
|
|
2594
|
-
const topLevel = readStringList(taskRecord.scope);
|
|
2595
|
-
if (topLevel.length > 0)
|
|
2596
|
-
return unique(topLevel.map((entry) => entry.trim()).filter((entry) => entry.length > 0));
|
|
2597
|
-
const metadata = isObjectRecord2(task.metadata) ? task.metadata : null;
|
|
2598
|
-
const metadataScope = readStringList(metadata?.scope);
|
|
2599
|
-
if (metadataScope.length > 0)
|
|
2600
|
-
return unique(metadataScope.map((entry) => entry.trim()).filter((entry) => entry.length > 0));
|
|
2601
|
-
return unique([
|
|
2602
|
-
...readStringList(metadata?.files),
|
|
2603
|
-
...readStringList(metadata?.paths)
|
|
2604
|
-
].map((entry) => entry.trim()).filter((entry) => entry.length > 0));
|
|
2605
|
-
}
|
|
2606
|
-
function isScopeList(input) {
|
|
2607
|
-
return Array.isArray(input);
|
|
2608
|
-
}
|
|
2609
|
-
function normalizeScopeInput(input) {
|
|
2610
|
-
return isScopeList(input) ? unique(input.map((entry) => entry.trim()).filter((entry) => entry.length > 0)) : readTaskScope(input);
|
|
2611
|
-
}
|
|
2612
|
-
function disjointScope(left, right) {
|
|
2613
|
-
const leftScope = new Set(normalizeScopeInput(left));
|
|
2614
|
-
if (leftScope.size === 0)
|
|
2615
|
-
return true;
|
|
2616
|
-
return normalizeScopeInput(right).every((entry) => !leftScope.has(entry));
|
|
2617
|
-
}
|
|
2618
|
-
function resolveTaskReference(ref, tasksById, taskIdByExternalRef, taskIdBySourceIssueId) {
|
|
2619
|
-
if (tasksById.has(ref))
|
|
2620
|
-
return ref;
|
|
2621
|
-
return taskIdBySourceIssueId.get(ref) ?? taskIdByExternalRef.get(ref) ?? null;
|
|
2622
|
-
}
|
|
2623
|
-
function buildTaskReferenceIndex(tasks) {
|
|
2624
|
-
return {
|
|
2625
|
-
tasksById: new Map(tasks.map((task) => [task.id, task])),
|
|
2626
|
-
taskIdByExternalRef: new Map(tasks.flatMap((task) => task.externalId ? [[task.externalId, task.id]] : [])),
|
|
2627
|
-
taskIdBySourceIssueId: new Map(tasks.flatMap((task) => {
|
|
2628
|
-
const sourceIssueId = readTaskSourceIssueId(task);
|
|
2629
|
-
return sourceIssueId ? [[sourceIssueId, task.id]] : [];
|
|
2630
|
-
}))
|
|
2631
|
-
};
|
|
2632
|
-
}
|
|
2633
|
-
function computeTaskBlockingDepths(tasks) {
|
|
2634
|
-
const { tasksById, taskIdByExternalRef, taskIdBySourceIssueId } = buildTaskReferenceIndex(tasks);
|
|
2635
|
-
const memo = new Map;
|
|
2636
|
-
const visit = (taskId, stack) => {
|
|
2637
|
-
const cached = memo.get(taskId);
|
|
2638
|
-
if (cached !== undefined)
|
|
2639
|
-
return cached;
|
|
2640
|
-
if (stack.has(taskId))
|
|
2641
|
-
return 0;
|
|
2642
|
-
const task = tasksById.get(taskId);
|
|
2643
|
-
if (!task)
|
|
2644
|
-
return 0;
|
|
2645
|
-
stack.add(taskId);
|
|
2646
|
-
const blockers = readTaskBlockingDependencyRefs(task).map((ref) => resolveTaskReference(ref, tasksById, taskIdByExternalRef, taskIdBySourceIssueId)).filter((ref) => ref !== null && ref !== taskId);
|
|
2647
|
-
const depth = blockers.length === 0 ? 0 : Math.max(...blockers.map((blockerId) => visit(blockerId, stack) + 1));
|
|
2648
|
-
stack.delete(taskId);
|
|
2649
|
-
memo.set(taskId, depth);
|
|
2650
|
-
return depth;
|
|
2651
|
-
};
|
|
2652
|
-
for (const task of tasks) {
|
|
2653
|
-
visit(task.id, new Set);
|
|
2654
|
-
}
|
|
2655
|
-
return memo;
|
|
2656
|
-
}
|
|
2657
|
-
function isTaskTerminalStatus(status) {
|
|
2658
|
-
switch (status) {
|
|
2659
|
-
case "closed":
|
|
2660
|
-
case "completed":
|
|
2661
|
-
case "done":
|
|
2662
|
-
case "cancelled":
|
|
2663
|
-
case "canceled":
|
|
2664
|
-
return true;
|
|
2665
|
-
default:
|
|
2666
|
-
return false;
|
|
2667
|
-
}
|
|
2668
|
-
}
|
|
2669
|
-
function isTaskBlockedStatus(status) {
|
|
2670
|
-
return status === "blocked";
|
|
2671
|
-
}
|
|
2672
|
-
function isTaskRunnableStatus(status) {
|
|
2673
|
-
if (status === null || status === undefined || status === "")
|
|
2674
|
-
return true;
|
|
2675
|
-
if (isTaskTerminalStatus(status) || isTaskBlockedStatus(status))
|
|
2676
|
-
return false;
|
|
2677
|
-
switch (status) {
|
|
2678
|
-
case "ready":
|
|
2679
|
-
case "open":
|
|
2680
|
-
case "failed":
|
|
2681
|
-
return true;
|
|
2682
|
-
default:
|
|
2683
|
-
return false;
|
|
2684
|
-
}
|
|
2685
|
-
}
|
|
2686
|
-
function priorityValue(task) {
|
|
2687
|
-
return typeof task.priority === "number" && Number.isFinite(task.priority) ? task.priority : Number.MAX_SAFE_INTEGER;
|
|
2688
|
-
}
|
|
2689
|
-
function readTaskRole(task) {
|
|
2690
|
-
if (typeof task.role === "string" && task.role.trim())
|
|
2691
|
-
return task.role.trim();
|
|
2692
|
-
const metadata = isObjectRecord2(task.metadata) ? task.metadata : null;
|
|
2693
|
-
return typeof metadata?.role === "string" && metadata.role.trim() ? metadata.role.trim() : null;
|
|
2694
|
-
}
|
|
2695
|
-
function readTaskCriticality(task) {
|
|
2696
|
-
const metadata = isObjectRecord2(task.metadata) ? task.metadata : null;
|
|
2697
|
-
return typeof metadata?.criticality === "string" && metadata.criticality.trim() ? metadata.criticality.trim() : null;
|
|
2698
|
-
}
|
|
2699
|
-
function readTaskValidationKeys(task) {
|
|
2700
|
-
const taskRecord = task;
|
|
2701
|
-
const topLevel = readStringList(taskRecord.validationKeys);
|
|
2702
|
-
if (topLevel.length > 0)
|
|
2703
|
-
return topLevel;
|
|
2704
|
-
const metadata = isObjectRecord2(task.metadata) ? task.metadata : null;
|
|
2705
|
-
return readStringList(metadata?.validation);
|
|
2706
|
-
}
|
|
2707
|
-
function readTaskQueueWeight(task) {
|
|
2708
|
-
const metadata = isObjectRecord2(task.metadata) ? task.metadata : null;
|
|
2709
|
-
const queueWeight = metadata?.queueWeight ?? metadata?.queue_weight;
|
|
2710
|
-
return typeof queueWeight === "number" && Number.isFinite(queueWeight) ? queueWeight : 0;
|
|
2711
|
-
}
|
|
2712
|
-
function scoreInputForTask(task, unblockCount) {
|
|
2713
|
-
return {
|
|
2714
|
-
priority: task.priority,
|
|
2715
|
-
unblockCount,
|
|
2716
|
-
role: readTaskRole(task),
|
|
2717
|
-
criticality: readTaskCriticality(task),
|
|
2718
|
-
validation: readTaskValidationKeys(task),
|
|
2719
|
-
queueWeight: readTaskQueueWeight(task)
|
|
2720
|
-
};
|
|
2721
|
-
}
|
|
2722
|
-
function computeTaskDependencyBadges(tasks) {
|
|
2723
|
-
const index = buildTaskReferenceIndex(tasks);
|
|
2724
|
-
const blockingDepths = computeTaskBlockingDepths(tasks);
|
|
2725
|
-
const dependencyIdsByTask = new Map;
|
|
2726
|
-
const unresolvedRefsByTask = new Map;
|
|
2727
|
-
const blocksByTask = new Map;
|
|
2728
|
-
for (const task of tasks) {
|
|
2729
|
-
const dependencyIds = [];
|
|
2730
|
-
const unresolvedRefs = [];
|
|
2731
|
-
for (const ref of readTaskBlockingDependencyRefs(task)) {
|
|
2732
|
-
const dependencyId = resolveTaskReference(ref, index.tasksById, index.taskIdByExternalRef, index.taskIdBySourceIssueId);
|
|
2733
|
-
if (dependencyId && dependencyId !== task.id) {
|
|
2734
|
-
dependencyIds.push(dependencyId);
|
|
2735
|
-
const blocks = blocksByTask.get(dependencyId);
|
|
2736
|
-
if (blocks) {
|
|
2737
|
-
blocks.push(task.id);
|
|
2738
|
-
} else {
|
|
2739
|
-
blocksByTask.set(dependencyId, [task.id]);
|
|
2740
|
-
}
|
|
2741
|
-
} else {
|
|
2742
|
-
unresolvedRefs.push(ref);
|
|
2743
|
-
}
|
|
2744
|
-
}
|
|
2745
|
-
dependencyIdsByTask.set(task.id, unique(dependencyIds));
|
|
2746
|
-
unresolvedRefsByTask.set(task.id, unique(unresolvedRefs));
|
|
2747
|
-
}
|
|
2748
|
-
const summaries = new Map;
|
|
2749
|
-
for (const task of tasks) {
|
|
2750
|
-
const dependencyIds = dependencyIdsByTask.get(task.id) ?? [];
|
|
2751
|
-
const unresolvedDependencyRefs = unresolvedRefsByTask.get(task.id) ?? [];
|
|
2752
|
-
const blockedBy = dependencyIds.filter((dependencyId) => {
|
|
2753
|
-
const dependency = index.tasksById.get(dependencyId);
|
|
2754
|
-
return dependency ? !isTaskTerminalStatus(dependency.status) : false;
|
|
2755
|
-
});
|
|
2756
|
-
const blocks = unique(blocksByTask.get(task.id) ?? []);
|
|
2757
|
-
const blocked = isTaskBlockedStatus(task.status) || blockedBy.length > 0;
|
|
2758
|
-
const ready = isTaskRunnableStatus(task.status) && !blocked;
|
|
2759
|
-
const badges = [];
|
|
2760
|
-
if (blocked) {
|
|
2761
|
-
badges.push({
|
|
2762
|
-
kind: "blocked",
|
|
2763
|
-
label: blockedBy.length > 0 ? `blocked \xD7${blockedBy.length}` : "blocked",
|
|
2764
|
-
description: blockedBy.length > 0 ? `Waiting on ${blockedBy.join(", ")}.` : "Task source marks this task blocked.",
|
|
2765
|
-
...blockedBy.length > 0 ? { count: blockedBy.length } : {},
|
|
2766
|
-
taskIds: blockedBy
|
|
2767
|
-
});
|
|
2768
|
-
} else if (ready) {
|
|
2769
|
-
badges.push({
|
|
2770
|
-
kind: "ready",
|
|
2771
|
-
label: "ready",
|
|
2772
|
-
description: "No open dependencies block this task."
|
|
2773
|
-
});
|
|
2774
|
-
}
|
|
2775
|
-
if (dependencyIds.length > 0 || blocks.length > 0 || unresolvedDependencyRefs.length > 0) {
|
|
2776
|
-
badges.push({
|
|
2777
|
-
kind: "dependency",
|
|
2778
|
-
label: `deps ${dependencyIds.length}/${blocks.length}`,
|
|
2779
|
-
description: [
|
|
2780
|
-
dependencyIds.length > 0 ? `Depends on ${dependencyIds.join(", ")}.` : null,
|
|
2781
|
-
blocks.length > 0 ? `Blocks ${blocks.join(", ")}.` : null,
|
|
2782
|
-
unresolvedDependencyRefs.length > 0 ? `Unresolved refs: ${unresolvedDependencyRefs.join(", ")}.` : null
|
|
2783
|
-
].filter((part) => part !== null).join(" "),
|
|
2784
|
-
count: dependencyIds.length + blocks.length,
|
|
2785
|
-
taskIds: unique([...dependencyIds, ...blocks])
|
|
2786
|
-
});
|
|
2787
|
-
}
|
|
2788
|
-
summaries.set(task.id, {
|
|
2789
|
-
taskId: task.id,
|
|
2790
|
-
blockingDepth: blockingDepths.get(task.id) ?? 0,
|
|
2791
|
-
dependencyIds,
|
|
2792
|
-
unresolvedDependencyRefs,
|
|
2793
|
-
blockedBy,
|
|
2794
|
-
blocks,
|
|
2795
|
-
blocked,
|
|
2796
|
-
ready,
|
|
2797
|
-
dependencyCount: dependencyIds.length,
|
|
2798
|
-
dependentCount: blocks.length,
|
|
2799
|
-
badges
|
|
2800
|
-
});
|
|
2801
|
-
}
|
|
2802
|
-
return summaries;
|
|
2803
|
-
}
|
|
2804
|
-
function selectNextReadyTaskByPriority(tasks, options = {}) {
|
|
2805
|
-
const excluded = new Set(options.excludeTaskIds ?? []);
|
|
2806
|
-
const badges = computeTaskDependencyBadges(tasks);
|
|
2807
|
-
const candidates = tasks.filter((task) => !excluded.has(task.id)).filter((task) => options.filter?.(task) ?? true).filter((task) => badges.get(task.id)?.ready === true).toSorted((left, right) => {
|
|
2808
|
-
const priorityDelta = priorityValue(left) - priorityValue(right);
|
|
2809
|
-
if (priorityDelta !== 0)
|
|
2810
|
-
return priorityDelta;
|
|
2811
|
-
const createdDelta = (left.createdAt ?? "").localeCompare(right.createdAt ?? "");
|
|
2812
|
-
if (createdDelta !== 0)
|
|
2813
|
-
return createdDelta;
|
|
2814
|
-
return left.id.localeCompare(right.id);
|
|
2815
|
-
});
|
|
2816
|
-
return candidates[0] ?? null;
|
|
2817
|
-
}
|
|
2818
|
-
function rankReadyTasks(tasks, options = {}) {
|
|
2819
|
-
const excluded = new Set(options.excludeTaskIds ?? []);
|
|
2820
|
-
const activeTaskIds = new Set(options.activeTaskIds ?? []);
|
|
2821
|
-
const badges = computeTaskDependencyBadges(tasks);
|
|
2822
|
-
const tasksById = new Map(tasks.map((task) => [String(task.id), task]));
|
|
2823
|
-
const openBlockCount = (task) => (badges.get(task.id)?.blocks ?? []).filter((blockedTaskId) => {
|
|
2824
|
-
const blockedTask = tasksById.get(blockedTaskId);
|
|
2825
|
-
return blockedTask ? !isTaskTerminalStatus(blockedTask.status) : false;
|
|
2826
|
-
}).length;
|
|
2827
|
-
const readyTasks = tasks.filter((task) => !excluded.has(task.id)).filter((task) => !activeTaskIds.has(task.id)).filter((task) => options.filter?.(task) ?? true).filter((task) => badges.get(task.id)?.ready === true);
|
|
2828
|
-
const maxUnblockCount = Math.max(0, ...readyTasks.map(openBlockCount));
|
|
2829
|
-
const selectedByMode = readyTasks.filter((task) => {
|
|
2830
|
-
const unblockCount = openBlockCount(task);
|
|
2831
|
-
if (options.selection === "blocking-only")
|
|
2832
|
-
return unblockCount > 0;
|
|
2833
|
-
if (options.selection === "max-unblock")
|
|
2834
|
-
return maxUnblockCount > 0 && unblockCount === maxUnblockCount;
|
|
2835
|
-
return true;
|
|
2836
|
-
});
|
|
2837
|
-
return rankTasks(selectedByMode, (task) => scoreInputForTask(task, openBlockCount(task)), (task) => task.id).map((entry) => ({
|
|
2838
|
-
task: entry.task,
|
|
2839
|
-
score: entry.score,
|
|
2840
|
-
priority: entry.priority,
|
|
2841
|
-
unblockCount: entry.unblockCount,
|
|
2842
|
-
scope: readTaskScope(entry.task)
|
|
2843
|
-
}));
|
|
2844
|
-
}
|
|
2845
|
-
function selectRankedReadyTasks(tasks, options = {}) {
|
|
2846
|
-
const ranked = rankReadyTasks(tasks, options);
|
|
2847
|
-
if (options.requireDisjointScopes !== true && !options.disjointWithScopes) {
|
|
2848
|
-
return ranked.slice(0, options.limit).map((entry) => entry.task);
|
|
2849
|
-
}
|
|
2850
|
-
const occupiedScopes = new Set(options.disjointWithScopes ?? []);
|
|
2851
|
-
const selected = [];
|
|
2852
|
-
for (const entry of ranked) {
|
|
2853
|
-
if (entry.scope.some((scope) => occupiedScopes.has(scope)))
|
|
2854
|
-
continue;
|
|
2855
|
-
selected.push(entry.task);
|
|
2856
|
-
for (const scope of entry.scope)
|
|
2857
|
-
occupiedScopes.add(scope);
|
|
2858
|
-
if (options.limit !== undefined && selected.length >= options.limit)
|
|
2859
|
-
break;
|
|
2860
|
-
}
|
|
2861
|
-
return selected;
|
|
2862
|
-
}
|
|
2863
|
-
// packages/core/src/taskGraphCodes.ts
|
|
2864
|
-
var TASK_CODE_RE = /^\[([A-Z0-9]+(?:-[A-Z0-9]+)*)\]\s*/;
|
|
2865
|
-
function extractTaskCode(title) {
|
|
2866
|
-
const match = title.match(TASK_CODE_RE);
|
|
2867
|
-
return match?.[1] ?? null;
|
|
2868
|
-
}
|
|
2869
|
-
function extractTaskGroupKey(title) {
|
|
2870
|
-
const code = extractTaskCode(title);
|
|
2871
|
-
if (!code)
|
|
2872
|
-
return null;
|
|
2873
|
-
const parts = code.split("-");
|
|
2874
|
-
const suffix = parts.at(-1) ?? "";
|
|
2875
|
-
if (/^\d+$/.test(suffix)) {
|
|
2876
|
-
return parts.slice(0, -1).join("-");
|
|
2877
|
-
}
|
|
2878
|
-
return parts[0] ?? code;
|
|
2879
|
-
}
|
|
2880
|
-
function stripTaskCode(label) {
|
|
2881
|
-
return label.replace(TASK_CODE_RE, "");
|
|
2882
|
-
}
|
|
2883
|
-
// packages/core/src/taskGraphLayout.ts
|
|
2884
|
-
var CARD_WIDTH = 200;
|
|
2885
|
-
var CARD_HEIGHT = 110;
|
|
2886
|
-
var CELL_V_PAD = 12;
|
|
2887
|
-
var CELL_H_PAD = 12;
|
|
2888
|
-
var ROW_GAP = 28;
|
|
2889
|
-
var COL_GAP = 40;
|
|
2890
|
-
var LANE_LABEL_W = 120;
|
|
2891
|
-
var STAGE_HDR_H = 32;
|
|
2892
|
-
var PALETTE = [
|
|
2893
|
-
{ bg: "#3a2d12", border: "#8d6b19", edge: "#d6a11d" },
|
|
2894
|
-
{ bg: "#102642", border: "#245fbf", edge: "#66a2ff" },
|
|
2895
|
-
{ bg: "#2c173f", border: "#7b39d4", edge: "#a76df5" },
|
|
2896
|
-
{ bg: "#112d1c", border: "#2d8f4e", edge: "#62d882" },
|
|
2897
|
-
{ bg: "#3a2314", border: "#c86d1c", edge: "#e69654" },
|
|
2898
|
-
{ bg: "#31152b", border: "#bf3d88", edge: "#f07ebb" },
|
|
2899
|
-
{ bg: "#132c35", border: "#1783a6", edge: "#53c4e5" },
|
|
2900
|
-
{ bg: "#26310f", border: "#6d9a19", edge: "#a7da42" }
|
|
2901
|
-
];
|
|
2902
|
-
function isObjectRecord3(value) {
|
|
2903
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2904
|
-
}
|
|
2905
|
-
function readIssueType(task) {
|
|
2906
|
-
const metadata = isObjectRecord3(task.metadata) ? task.metadata : null;
|
|
2907
|
-
if (typeof metadata?.issueType === "string")
|
|
2908
|
-
return metadata.issueType;
|
|
2909
|
-
const raw = isObjectRecord3(metadata?.raw) ? metadata.raw : null;
|
|
2910
|
-
return typeof raw?.issueType === "string" ? raw.issueType : null;
|
|
2911
|
-
}
|
|
2912
|
-
function isGraphTask(task) {
|
|
2913
|
-
return readIssueType(task) !== "epic";
|
|
2914
|
-
}
|
|
2915
|
-
function findEpicAncestor(task, resolve, tasksById) {
|
|
2916
|
-
const visited = new Set;
|
|
2917
|
-
let current = task;
|
|
2918
|
-
let epic = null;
|
|
2919
|
-
while (!visited.has(current.id)) {
|
|
2920
|
-
visited.add(current.id);
|
|
2921
|
-
const parentRef = readTaskMetadataStringList(current, "parentChildDeps")[0];
|
|
2922
|
-
if (!parentRef)
|
|
2923
|
-
break;
|
|
2924
|
-
const parentId = resolve(parentRef);
|
|
2925
|
-
if (!parentId)
|
|
2926
|
-
break;
|
|
2927
|
-
const parent = tasksById.get(parentId);
|
|
2928
|
-
if (!parent)
|
|
2929
|
-
break;
|
|
2930
|
-
if (readIssueType(parent) === "epic") {
|
|
2931
|
-
epic = parent;
|
|
2932
|
-
}
|
|
2933
|
-
current = parent;
|
|
2934
|
-
}
|
|
2935
|
-
return epic;
|
|
2936
|
-
}
|
|
2937
|
-
function getRowKey(task, resolve, tasksById) {
|
|
2938
|
-
const epic = findEpicAncestor(task, resolve, tasksById);
|
|
2939
|
-
if (epic) {
|
|
2940
|
-
return `group:${epic.id}`;
|
|
2941
|
-
}
|
|
2942
|
-
const codeGroup = extractTaskGroupKey(task.title);
|
|
2943
|
-
if (codeGroup) {
|
|
2944
|
-
return `code:${codeGroup}`;
|
|
2945
|
-
}
|
|
2946
|
-
return `type:${readIssueType(task) ?? "task"}`;
|
|
2947
|
-
}
|
|
2948
|
-
function getRowLabel(task, rowKey, resolve, tasksById) {
|
|
2949
|
-
if (rowKey.startsWith("group:")) {
|
|
2950
|
-
const groupId = rowKey.slice("group:".length);
|
|
2951
|
-
return tasksById.get(groupId)?.title ?? groupId;
|
|
2952
|
-
}
|
|
2953
|
-
if (rowKey.startsWith("code:")) {
|
|
2954
|
-
return rowKey.slice("code:".length);
|
|
2955
|
-
}
|
|
2956
|
-
const code = extractTaskCode(task.title);
|
|
2957
|
-
if (code)
|
|
2958
|
-
return code;
|
|
2959
|
-
const issueType = readIssueType(task);
|
|
2960
|
-
if (issueType === "task")
|
|
2961
|
-
return "Tasks";
|
|
2962
|
-
if (issueType)
|
|
2963
|
-
return `${issueType[0]?.toUpperCase() ?? ""}${issueType.slice(1)}`;
|
|
2964
|
-
const parentRef = readTaskMetadataStringList(task, "parentChildDeps")[0];
|
|
2965
|
-
if (parentRef) {
|
|
2966
|
-
const parentId = resolve(parentRef);
|
|
2967
|
-
if (parentId) {
|
|
2968
|
-
return tasksById.get(parentId)?.title ?? "Tasks";
|
|
2969
|
-
}
|
|
2970
|
-
}
|
|
2971
|
-
return "Tasks";
|
|
2972
|
-
}
|
|
2973
|
-
function computeDepths(ids, edges) {
|
|
2974
|
-
const blockers = new Map;
|
|
2975
|
-
for (const edge of edges) {
|
|
2976
|
-
if (!ids.has(edge.source) || !ids.has(edge.target))
|
|
2977
|
-
continue;
|
|
2978
|
-
const current = blockers.get(edge.target) ?? [];
|
|
2979
|
-
current.push(edge.source);
|
|
2980
|
-
blockers.set(edge.target, current);
|
|
2981
|
-
}
|
|
2982
|
-
const memo = new Map;
|
|
2983
|
-
const visit = (id, stack) => {
|
|
2984
|
-
const cached = memo.get(id);
|
|
2985
|
-
if (cached !== undefined)
|
|
2986
|
-
return cached;
|
|
2987
|
-
if (stack.has(id))
|
|
2988
|
-
return 0;
|
|
2989
|
-
stack.add(id);
|
|
2990
|
-
const deps = blockers.get(id);
|
|
2991
|
-
if (!deps || deps.length === 0) {
|
|
2992
|
-
memo.set(id, 0);
|
|
2993
|
-
return 0;
|
|
2994
|
-
}
|
|
2995
|
-
let maxDepth = 0;
|
|
2996
|
-
for (const dep of deps) {
|
|
2997
|
-
maxDepth = Math.max(maxDepth, visit(dep, stack) + 1);
|
|
2998
|
-
}
|
|
2999
|
-
stack.delete(id);
|
|
3000
|
-
memo.set(id, maxDepth);
|
|
3001
|
-
return maxDepth;
|
|
3002
|
-
};
|
|
3003
|
-
for (const id of ids) {
|
|
3004
|
-
visit(id, new Set);
|
|
3005
|
-
}
|
|
3006
|
-
return memo;
|
|
3007
|
-
}
|
|
3008
|
-
function buildTaskGraphLayout(snapshot, tasks, options) {
|
|
3009
|
-
const showParentChild = options?.showParentChild ?? false;
|
|
3010
|
-
const graphTasks = tasks.filter(isGraphTask);
|
|
3011
|
-
if (graphTasks.length === 0) {
|
|
3012
|
-
return {
|
|
3013
|
-
lanes: [],
|
|
3014
|
-
stages: [],
|
|
3015
|
-
nodes: [],
|
|
3016
|
-
edges: [],
|
|
3017
|
-
totalWidth: 0,
|
|
3018
|
-
totalHeight: 0,
|
|
3019
|
-
taskCount: 0
|
|
3020
|
-
};
|
|
3021
|
-
}
|
|
3022
|
-
const { tasksById, taskIdByExternalRef, taskIdBySourceIssueId } = buildTaskReferenceIndex(tasks);
|
|
3023
|
-
const resolve = (ref) => resolveTaskReference(ref, tasksById, taskIdByExternalRef, taskIdBySourceIssueId);
|
|
3024
|
-
const rows = new Map;
|
|
3025
|
-
const rowLabelByKey = new Map;
|
|
3026
|
-
for (const task of graphTasks) {
|
|
3027
|
-
const rowKey = getRowKey(task, resolve, tasksById);
|
|
3028
|
-
const current = rows.get(rowKey) ?? [];
|
|
3029
|
-
current.push(task);
|
|
3030
|
-
rows.set(rowKey, current);
|
|
3031
|
-
if (!rowLabelByKey.has(rowKey)) {
|
|
3032
|
-
rowLabelByKey.set(rowKey, getRowLabel(task, rowKey, resolve, tasksById));
|
|
3033
|
-
}
|
|
3034
|
-
}
|
|
3035
|
-
const orderedRows = [...rows.entries()].sort((left, right) => {
|
|
3036
|
-
if (left[1].length !== right[1].length)
|
|
3037
|
-
return right[1].length - left[1].length;
|
|
3038
|
-
return (rowLabelByKey.get(left[0]) ?? left[0]).localeCompare(rowLabelByKey.get(right[0]) ?? right[0]);
|
|
3039
|
-
});
|
|
3040
|
-
const rowIndexByKey = new Map(orderedRows.map(([key], index) => [key, index]));
|
|
3041
|
-
const rowIndexByTaskId = new Map;
|
|
3042
|
-
for (const task of graphTasks) {
|
|
3043
|
-
rowIndexByTaskId.set(task.id, rowIndexByKey.get(getRowKey(task, resolve, tasksById)) ?? 0);
|
|
3044
|
-
}
|
|
3045
|
-
const blockingEdgesRaw = [];
|
|
3046
|
-
const depsIn = new Map;
|
|
3047
|
-
const depsOut = new Map;
|
|
3048
|
-
const edges = [];
|
|
3049
|
-
for (const task of graphTasks) {
|
|
3050
|
-
for (const ref of readTaskMetadataStringList(task, "dependencies")) {
|
|
3051
|
-
const sourceId = resolve(ref);
|
|
3052
|
-
if (!sourceId)
|
|
3053
|
-
continue;
|
|
3054
|
-
blockingEdgesRaw.push({ source: sourceId, target: task.id });
|
|
3055
|
-
depsOut.set(sourceId, (depsOut.get(sourceId) ?? 0) + 1);
|
|
3056
|
-
depsIn.set(task.id, (depsIn.get(task.id) ?? 0) + 1);
|
|
3057
|
-
const color = PALETTE[(rowIndexByTaskId.get(sourceId) ?? 0) % PALETTE.length].edge;
|
|
3058
|
-
edges.push({
|
|
3059
|
-
id: `blocking:${sourceId}:${task.id}`,
|
|
3060
|
-
sourceId,
|
|
3061
|
-
targetId: task.id,
|
|
3062
|
-
color,
|
|
3063
|
-
kind: "blocking"
|
|
3064
|
-
});
|
|
3065
|
-
}
|
|
3066
|
-
if (!showParentChild)
|
|
3067
|
-
continue;
|
|
3068
|
-
for (const ref of readTaskMetadataStringList(task, "parentChildDeps")) {
|
|
3069
|
-
const sourceId = resolve(ref);
|
|
3070
|
-
if (!sourceId)
|
|
3071
|
-
continue;
|
|
3072
|
-
edges.push({
|
|
3073
|
-
id: `parent:${sourceId}:${task.id}`,
|
|
3074
|
-
sourceId,
|
|
3075
|
-
targetId: task.id,
|
|
3076
|
-
color: "#5b6472",
|
|
3077
|
-
kind: "parent-child"
|
|
3078
|
-
});
|
|
3079
|
-
}
|
|
3080
|
-
}
|
|
3081
|
-
const graphTaskIds = new Set(graphTasks.map((task) => task.id));
|
|
3082
|
-
const depths = computeDepths(graphTaskIds, blockingEdgesRaw);
|
|
3083
|
-
const maxStage = Math.max(0, ...depths.values());
|
|
3084
|
-
const rowMaxStack = new Array(orderedRows.length).fill(0);
|
|
3085
|
-
const cells = new Map;
|
|
3086
|
-
for (const task of graphTasks) {
|
|
3087
|
-
const rowIndex = rowIndexByTaskId.get(task.id) ?? 0;
|
|
3088
|
-
const stage = depths.get(task.id) ?? 0;
|
|
3089
|
-
const key = `${rowIndex}:${stage}`;
|
|
3090
|
-
const current = cells.get(key) ?? [];
|
|
3091
|
-
current.push(task);
|
|
3092
|
-
cells.set(key, current);
|
|
3093
|
-
}
|
|
3094
|
-
for (const [cellKey, cellTasks] of cells) {
|
|
3095
|
-
const [rowIndexText] = cellKey.split(":");
|
|
3096
|
-
const rowIndex = Number.parseInt(rowIndexText ?? "0", 10) || 0;
|
|
3097
|
-
cellTasks.sort((left, right) => {
|
|
3098
|
-
const leftFanout = depsOut.get(left.id) ?? 0;
|
|
3099
|
-
const rightFanout = depsOut.get(right.id) ?? 0;
|
|
3100
|
-
if (leftFanout !== rightFanout)
|
|
3101
|
-
return rightFanout - leftFanout;
|
|
3102
|
-
return left.title.localeCompare(right.title);
|
|
3103
|
-
});
|
|
3104
|
-
rowMaxStack[rowIndex] = Math.max(rowMaxStack[rowIndex] ?? 0, cellTasks.length);
|
|
3105
|
-
}
|
|
3106
|
-
const rowHeights = rowMaxStack.map((count) => Math.max(count, 1) * (CARD_HEIGHT + CELL_V_PAD) - CELL_V_PAD + CELL_V_PAD * 2);
|
|
3107
|
-
const colWidths = new Array(maxStage + 1).fill(CARD_WIDTH + CELL_H_PAD * 2);
|
|
3108
|
-
const colX = [];
|
|
3109
|
-
let currentX = LANE_LABEL_W;
|
|
3110
|
-
for (let index = 0;index <= maxStage; index += 1) {
|
|
3111
|
-
colX.push(currentX);
|
|
3112
|
-
currentX += (colWidths[index] ?? 0) + COL_GAP;
|
|
3113
|
-
}
|
|
3114
|
-
const totalWidth = currentX - COL_GAP;
|
|
3115
|
-
const rowY = [];
|
|
3116
|
-
let currentY = STAGE_HDR_H;
|
|
3117
|
-
for (let rowIndex = 0;rowIndex < orderedRows.length; rowIndex += 1) {
|
|
3118
|
-
rowY.push(currentY);
|
|
3119
|
-
currentY += (rowHeights[rowIndex] ?? 0) + ROW_GAP;
|
|
3120
|
-
}
|
|
3121
|
-
const totalHeight = currentY - ROW_GAP;
|
|
3122
|
-
const lanes = orderedRows.map(([rowKey, rowTasks], rowIndex) => ({
|
|
3123
|
-
key: rowKey,
|
|
3124
|
-
label: rowLabelByKey.get(rowKey) ?? rowKey,
|
|
3125
|
-
rowIndex,
|
|
3126
|
-
x: LANE_LABEL_W - 6,
|
|
3127
|
-
y: (rowY[rowIndex] ?? 0) - 6,
|
|
3128
|
-
width: totalWidth - LANE_LABEL_W + 12,
|
|
3129
|
-
height: (rowHeights[rowIndex] ?? 0) + 12,
|
|
3130
|
-
color: PALETTE[rowIndex % PALETTE.length].border,
|
|
3131
|
-
taskCount: rowTasks.length
|
|
3132
|
-
}));
|
|
3133
|
-
const stages = Array.from({ length: maxStage + 1 }, (_, index) => ({
|
|
3134
|
-
index,
|
|
3135
|
-
label: index === 0 ? "Roots" : `Stage ${index}`,
|
|
3136
|
-
x: (colX[index] ?? 0) + CELL_H_PAD,
|
|
3137
|
-
width: CARD_WIDTH
|
|
3138
|
-
}));
|
|
3139
|
-
const pendingApprovalRunIds = new Set(selectPendingApprovals(snapshot).map((approval) => approval.runId));
|
|
3140
|
-
const nodes = [];
|
|
3141
|
-
for (const [rowIndex, [rowKey]] of orderedRows.entries()) {
|
|
3142
|
-
for (let stage = 0;stage <= maxStage; stage += 1) {
|
|
3143
|
-
const cellTasks = cells.get(`${rowIndex}:${stage}`) ?? [];
|
|
3144
|
-
const baseX = (colX[stage] ?? 0) + CELL_H_PAD;
|
|
3145
|
-
const baseY = (rowY[rowIndex] ?? 0) + CELL_V_PAD;
|
|
3146
|
-
const palette = PALETTE[rowIndex % PALETTE.length];
|
|
3147
|
-
for (const [stackIndex, task] of cellTasks.entries()) {
|
|
3148
|
-
const runs = selectRunsByTask(snapshot, task.id);
|
|
3149
|
-
const runIds = new Set(runs.map((run) => run.id));
|
|
3150
|
-
const hasApprovals = runs.some((run) => pendingApprovalRunIds.has(run.id));
|
|
3151
|
-
const hasPendingUserInput = runs.some((run) => selectUserInputsForRun(snapshot, run.id).some((request) => request.status === "pending"));
|
|
3152
|
-
const hasRejectedReview = (snapshot?.reviews ?? []).some((review) => runIds.has(review.runId) && review.status === "rejected");
|
|
3153
|
-
const hasFailedValidations = (snapshot?.validations ?? []).some((validation) => runIds.has(validation.runId) && validation.status === "failed");
|
|
3154
|
-
const artifactCount = (snapshot?.artifacts ?? []).filter((artifact) => runIds.has(artifact.runId)).length;
|
|
3155
|
-
nodes.push({
|
|
3156
|
-
id: task.id,
|
|
3157
|
-
taskId: task.id,
|
|
3158
|
-
task,
|
|
3159
|
-
rowKey,
|
|
3160
|
-
rowLabel: rowLabelByKey.get(rowKey) ?? rowKey,
|
|
3161
|
-
rowIndex,
|
|
3162
|
-
stage,
|
|
3163
|
-
x: baseX,
|
|
3164
|
-
y: baseY + stackIndex * (CARD_HEIGHT + CELL_V_PAD),
|
|
3165
|
-
width: CARD_WIDTH,
|
|
3166
|
-
height: CARD_HEIGHT,
|
|
3167
|
-
color: palette.border,
|
|
3168
|
-
taskCode: extractTaskCode(task.title),
|
|
3169
|
-
strippedTitle: stripTaskCode(task.title),
|
|
3170
|
-
depsIn: depsIn.get(task.id) ?? 0,
|
|
3171
|
-
depsOut: depsOut.get(task.id) ?? 0,
|
|
3172
|
-
runCount: runs.length,
|
|
3173
|
-
hasApprovals,
|
|
3174
|
-
hasPendingUserInput,
|
|
3175
|
-
hasRejectedReview,
|
|
3176
|
-
hasFailedValidations,
|
|
3177
|
-
artifactCount
|
|
3178
|
-
});
|
|
3179
|
-
}
|
|
3180
|
-
}
|
|
3181
|
-
}
|
|
3182
|
-
return {
|
|
3183
|
-
lanes,
|
|
3184
|
-
stages,
|
|
3185
|
-
nodes,
|
|
3186
|
-
edges,
|
|
3187
|
-
totalWidth,
|
|
3188
|
-
totalHeight,
|
|
3189
|
-
taskCount: graphTasks.length
|
|
3190
|
-
};
|
|
3191
|
-
}
|
|
3192
|
-
// packages/core/src/stageResolve.ts
|
|
3193
|
-
class PipelineUnresolvableError extends Error {
|
|
3194
|
-
cycles;
|
|
3195
|
-
contributors;
|
|
3196
|
-
constructor(message, cycles, contributors) {
|
|
3197
|
-
super(message);
|
|
3198
|
-
this.name = "PipelineUnresolvableError";
|
|
3199
|
-
this.cycles = cycles;
|
|
3200
|
-
this.contributors = contributors;
|
|
3201
|
-
}
|
|
3202
|
-
}
|
|
3203
|
-
function uniqueSorted(values) {
|
|
3204
|
-
return Array.from(new Set(values)).sort((left, right) => left.localeCompare(right));
|
|
3205
|
-
}
|
|
3206
|
-
function wrapperForMutation(mutation) {
|
|
3207
|
-
return "wrapper" in mutation ? mutation.wrapper : mutation.around;
|
|
3208
|
-
}
|
|
3209
|
-
function contributorOf(mutation) {
|
|
3210
|
-
if (mutation.contributedBy?.trim())
|
|
3211
|
-
return mutation.contributedBy;
|
|
3212
|
-
if (mutation.op === "wrap") {
|
|
3213
|
-
const wrapper = wrapperForMutation(mutation);
|
|
3214
|
-
if (wrapper.id?.trim())
|
|
3215
|
-
return wrapper.id;
|
|
3216
|
-
}
|
|
3217
|
-
return "anonymous";
|
|
3218
|
-
}
|
|
3219
|
-
function stableMutationCompare(left, right) {
|
|
3220
|
-
const leftTarget = left.op === "insert" ? left.stage.id : left.id;
|
|
3221
|
-
const rightTarget = right.op === "insert" ? right.stage.id : right.id;
|
|
3222
|
-
const targetDelta = leftTarget.localeCompare(rightTarget);
|
|
3223
|
-
if (targetDelta !== 0)
|
|
3224
|
-
return targetDelta;
|
|
3225
|
-
return contributorOf(left).localeCompare(contributorOf(right));
|
|
3226
|
-
}
|
|
3227
|
-
function ensureUniqueStageIds(stages) {
|
|
3228
|
-
const seen = new Set;
|
|
3229
|
-
for (const stage of stages) {
|
|
3230
|
-
if (seen.has(stage.id)) {
|
|
3231
|
-
throw new PipelineUnresolvableError(`Duplicate stage id: ${stage.id}`, [], []);
|
|
3232
|
-
}
|
|
3233
|
-
seen.add(stage.id);
|
|
3234
|
-
}
|
|
3235
|
-
}
|
|
3236
|
-
function assertNoDuplicateMutationTargets(mutations, op) {
|
|
3237
|
-
const seen = new Map;
|
|
3238
|
-
for (const mutation of mutations.filter((entry) => entry.op === op)) {
|
|
3239
|
-
const id = mutation.op === "insert" ? mutation.stage.id : mutation.id;
|
|
3240
|
-
const previous = seen.get(id);
|
|
3241
|
-
if (previous) {
|
|
3242
|
-
throw new PipelineUnresolvableError(`Duplicate ${op} mutation for stage ${id}: ${previous}, ${contributorOf(mutation)}`, [], uniqueSorted([previous, contributorOf(mutation)]));
|
|
3243
|
-
}
|
|
3244
|
-
seen.set(id, contributorOf(mutation));
|
|
3245
|
-
}
|
|
3246
|
-
}
|
|
3247
|
-
function mergeAnchors(current, incoming) {
|
|
3248
|
-
return uniqueSorted([...current, ...incoming ?? []]);
|
|
3249
|
-
}
|
|
3250
|
-
function stagePriority(stage) {
|
|
3251
|
-
return typeof stage.priority === "number" && Number.isFinite(stage.priority) ? stage.priority : 0;
|
|
3252
|
-
}
|
|
3253
|
-
function readyCompare(states) {
|
|
3254
|
-
return (left, right) => {
|
|
3255
|
-
const leftState = states.get(left);
|
|
3256
|
-
const rightState = states.get(right);
|
|
3257
|
-
const priorityDelta = stagePriority(rightState?.stage ?? { id: right }) - stagePriority(leftState?.stage ?? { id: left });
|
|
3258
|
-
if (priorityDelta !== 0)
|
|
3259
|
-
return priorityDelta;
|
|
3260
|
-
if (leftState?.baseIndex !== null && rightState?.baseIndex !== null && leftState?.baseIndex !== rightState?.baseIndex) {
|
|
3261
|
-
return (leftState?.baseIndex ?? 0) - (rightState?.baseIndex ?? 0);
|
|
3262
|
-
}
|
|
3263
|
-
if (leftState?.baseIndex !== null && rightState?.baseIndex === null)
|
|
3264
|
-
return -1;
|
|
3265
|
-
if (leftState?.baseIndex === null && rightState?.baseIndex !== null)
|
|
3266
|
-
return 1;
|
|
3267
|
-
return left.localeCompare(right);
|
|
3268
|
-
};
|
|
3269
|
-
}
|
|
3270
|
-
function findCycles(nodes, edges) {
|
|
3271
|
-
const adjacency = new Map;
|
|
3272
|
-
for (const node of nodes)
|
|
3273
|
-
adjacency.set(node, []);
|
|
3274
|
-
for (const edge of edges)
|
|
3275
|
-
adjacency.get(edge.from)?.push(edge.to);
|
|
3276
|
-
for (const targets of adjacency.values())
|
|
3277
|
-
targets.sort((left, right) => left.localeCompare(right));
|
|
3278
|
-
let nextIndex = 0;
|
|
3279
|
-
const indexByNode = new Map;
|
|
3280
|
-
const lowByNode = new Map;
|
|
3281
|
-
const stack = [];
|
|
3282
|
-
const onStack = new Set;
|
|
3283
|
-
const cycles = [];
|
|
3284
|
-
const visit = (node) => {
|
|
3285
|
-
indexByNode.set(node, nextIndex);
|
|
3286
|
-
lowByNode.set(node, nextIndex);
|
|
3287
|
-
nextIndex += 1;
|
|
3288
|
-
stack.push(node);
|
|
3289
|
-
onStack.add(node);
|
|
3290
|
-
for (const target of adjacency.get(node) ?? []) {
|
|
3291
|
-
if (!indexByNode.has(target)) {
|
|
3292
|
-
visit(target);
|
|
3293
|
-
lowByNode.set(node, Math.min(lowByNode.get(node) ?? 0, lowByNode.get(target) ?? 0));
|
|
3294
|
-
} else if (onStack.has(target)) {
|
|
3295
|
-
lowByNode.set(node, Math.min(lowByNode.get(node) ?? 0, indexByNode.get(target) ?? 0));
|
|
3296
|
-
}
|
|
3297
|
-
}
|
|
3298
|
-
if (lowByNode.get(node) !== indexByNode.get(node))
|
|
3299
|
-
return;
|
|
3300
|
-
const component = [];
|
|
3301
|
-
let current;
|
|
3302
|
-
do {
|
|
3303
|
-
current = stack.pop();
|
|
3304
|
-
if (current) {
|
|
3305
|
-
onStack.delete(current);
|
|
3306
|
-
component.push(current);
|
|
3307
|
-
}
|
|
3308
|
-
} while (current && current !== node);
|
|
3309
|
-
const hasSelfLoop = edges.some((edge) => edge.from === node && edge.to === node);
|
|
3310
|
-
if (component.length > 1 || hasSelfLoop) {
|
|
3311
|
-
cycles.push(component.sort((left, right) => left.localeCompare(right)));
|
|
3312
|
-
}
|
|
3313
|
-
};
|
|
3314
|
-
for (const node of [...nodes].sort((left, right) => left.localeCompare(right))) {
|
|
3315
|
-
if (!indexByNode.has(node))
|
|
3316
|
-
visit(node);
|
|
3317
|
-
}
|
|
3318
|
-
return cycles.sort((left, right) => left.join("\x00").localeCompare(right.join("\x00")));
|
|
3319
|
-
}
|
|
3320
|
-
function topologicalOrder(states, edges) {
|
|
3321
|
-
const nodes = [...states.keys()];
|
|
3322
|
-
const outgoing = new Map;
|
|
3323
|
-
const indegree = new Map;
|
|
3324
|
-
for (const node of nodes) {
|
|
3325
|
-
outgoing.set(node, []);
|
|
3326
|
-
indegree.set(node, 0);
|
|
3327
|
-
}
|
|
3328
|
-
for (const edge of edges) {
|
|
3329
|
-
outgoing.get(edge.from)?.push(edge.to);
|
|
3330
|
-
indegree.set(edge.to, (indegree.get(edge.to) ?? 0) + 1);
|
|
3331
|
-
}
|
|
3332
|
-
for (const targets of outgoing.values())
|
|
3333
|
-
targets.sort((left, right) => left.localeCompare(right));
|
|
3334
|
-
const compare = readyCompare(states);
|
|
3335
|
-
const ready = nodes.filter((node) => (indegree.get(node) ?? 0) === 0).sort(compare);
|
|
3336
|
-
const order = [];
|
|
3337
|
-
while (ready.length > 0) {
|
|
3338
|
-
const node = ready.shift();
|
|
3339
|
-
if (!node)
|
|
3340
|
-
break;
|
|
3341
|
-
order.push(node);
|
|
3342
|
-
for (const target of outgoing.get(node) ?? []) {
|
|
3343
|
-
const next = (indegree.get(target) ?? 0) - 1;
|
|
3344
|
-
indegree.set(target, next);
|
|
3345
|
-
if (next === 0) {
|
|
3346
|
-
ready.push(target);
|
|
3347
|
-
ready.sort(compare);
|
|
3348
|
-
}
|
|
3349
|
-
}
|
|
3350
|
-
}
|
|
3351
|
-
if (order.length === nodes.length)
|
|
3352
|
-
return order;
|
|
3353
|
-
const cycles = findCycles(nodes, edges);
|
|
3354
|
-
const cycleMembers = new Set(cycles.flat());
|
|
3355
|
-
const contributors = uniqueSorted(edges.filter((edge) => cycleMembers.has(edge.from) && cycleMembers.has(edge.to)).flatMap((edge) => edge.contributors));
|
|
3356
|
-
throw new PipelineUnresolvableError(`Stage pipeline has unresolved cycle: ${cycles.map((cycle) => cycle.join(" -> ")).join("; ")}`, cycles, contributors);
|
|
3357
|
-
}
|
|
3358
|
-
function composeStageWrappers(state) {
|
|
3359
|
-
const wrappers = state.wrappers.toSorted((left, right) => {
|
|
3360
|
-
const priorityDelta = (right.wrapper.priority ?? 0) - (left.wrapper.priority ?? 0);
|
|
3361
|
-
if (priorityDelta !== 0)
|
|
3362
|
-
return priorityDelta;
|
|
3363
|
-
return left.contributedBy.localeCompare(right.contributedBy);
|
|
3364
|
-
});
|
|
3365
|
-
let stage = state.stage;
|
|
3366
|
-
for (const wrapper of wrappers.toReversed()) {
|
|
3367
|
-
if (wrapper.wrapper.apply)
|
|
3368
|
-
stage = wrapper.wrapper.apply(stage);
|
|
3369
|
-
}
|
|
3370
|
-
return stage;
|
|
3371
|
-
}
|
|
3372
|
-
function resolveStagePipeline(input) {
|
|
3373
|
-
ensureUniqueStageIds(input.defaultStages);
|
|
3374
|
-
const mutations = [...input.mutations ?? []];
|
|
3375
|
-
assertNoDuplicateMutationTargets(mutations, "insert");
|
|
3376
|
-
assertNoDuplicateMutationTargets(mutations, "replace");
|
|
3377
|
-
const states = new Map;
|
|
3378
|
-
const removedStates = new Map;
|
|
3379
|
-
for (const [index, stage] of input.defaultStages.entries()) {
|
|
3380
|
-
states.set(stage.id, {
|
|
3381
|
-
stage: { ...stage, protected: false },
|
|
3382
|
-
before: [...stage.before ?? []],
|
|
3383
|
-
after: [...stage.after ?? []],
|
|
3384
|
-
baseIndex: index,
|
|
3385
|
-
contributedBy: "default",
|
|
3386
|
-
wrappers: [],
|
|
3387
|
-
droppedAnchors: [],
|
|
3388
|
-
isProtected: false
|
|
3389
|
-
});
|
|
3390
|
-
}
|
|
3391
|
-
for (const mutation of mutations.filter((entry) => entry.op === "remove").sort(stableMutationCompare)) {
|
|
3392
|
-
const state = states.get(mutation.id);
|
|
3393
|
-
const contributor = contributorOf(mutation);
|
|
3394
|
-
if (!state)
|
|
3395
|
-
continue;
|
|
3396
|
-
const removedState = {
|
|
3397
|
-
...state,
|
|
3398
|
-
removedBy: contributor
|
|
3399
|
-
};
|
|
3400
|
-
removedStates.set(mutation.id, removedState);
|
|
3401
|
-
states.delete(mutation.id);
|
|
3402
|
-
}
|
|
3403
|
-
for (const mutation of mutations.filter((entry) => entry.op === "replace").sort(stableMutationCompare)) {
|
|
3404
|
-
const state = states.get(mutation.id);
|
|
3405
|
-
const contributor = contributorOf(mutation);
|
|
3406
|
-
if (!state)
|
|
3407
|
-
continue;
|
|
3408
|
-
const replacement = { ...mutation.stage, id: mutation.id, protected: false };
|
|
3409
|
-
states.set(mutation.id, {
|
|
3410
|
-
...state,
|
|
3411
|
-
stage: replacement,
|
|
3412
|
-
before: mutation.stage.before ? [...mutation.stage.before] : state.before,
|
|
3413
|
-
after: mutation.stage.after ? [...mutation.stage.after] : state.after,
|
|
3414
|
-
replacedBy: contributor,
|
|
3415
|
-
contributedBy: state.contributedBy,
|
|
3416
|
-
isProtected: false
|
|
3417
|
-
});
|
|
3418
|
-
}
|
|
3419
|
-
for (const mutation of mutations.filter((entry) => entry.op === "insert").sort(stableMutationCompare)) {
|
|
3420
|
-
const contributor = contributorOf(mutation);
|
|
3421
|
-
if (states.has(mutation.stage.id)) {
|
|
3422
|
-
throw new PipelineUnresolvableError(`Inserted stage ${mutation.stage.id} conflicts with an existing stage`, [], [contributor]);
|
|
3423
|
-
}
|
|
3424
|
-
states.set(mutation.stage.id, {
|
|
3425
|
-
stage: { ...mutation.stage, protected: false },
|
|
3426
|
-
before: [...mutation.stage.before ?? []],
|
|
3427
|
-
after: [...mutation.stage.after ?? []],
|
|
3428
|
-
baseIndex: null,
|
|
3429
|
-
contributedBy: contributor,
|
|
3430
|
-
wrappers: [],
|
|
3431
|
-
droppedAnchors: [],
|
|
3432
|
-
isProtected: false
|
|
3433
|
-
});
|
|
3434
|
-
}
|
|
3435
|
-
for (const mutation of mutations.filter((entry) => entry.op === "reorder").sort(stableMutationCompare)) {
|
|
3436
|
-
const state = states.get(mutation.id);
|
|
3437
|
-
if (!state)
|
|
3438
|
-
continue;
|
|
3439
|
-
states.set(mutation.id, {
|
|
3440
|
-
...state,
|
|
3441
|
-
before: mergeAnchors(state.before, mutation.before),
|
|
3442
|
-
after: mergeAnchors(state.after, mutation.after)
|
|
3443
|
-
});
|
|
3444
|
-
}
|
|
3445
|
-
for (const mutation of mutations.filter((entry) => entry.op === "wrap").sort(stableMutationCompare)) {
|
|
3446
|
-
const state = states.get(mutation.id);
|
|
3447
|
-
const contributor = contributorOf(mutation);
|
|
3448
|
-
const wrapper = wrapperForMutation(mutation);
|
|
3449
|
-
if (!state)
|
|
3450
|
-
continue;
|
|
3451
|
-
states.set(mutation.id, {
|
|
3452
|
-
...state,
|
|
3453
|
-
wrappers: [...state.wrappers, { contributedBy: contributor, wrapper }]
|
|
3454
|
-
});
|
|
3455
|
-
}
|
|
3456
|
-
const contributorsForAnchor = (stageId, direction, anchor, state) => {
|
|
3457
|
-
const contributors = new Set;
|
|
3458
|
-
if (state.replacedBy)
|
|
3459
|
-
contributors.add(state.replacedBy);
|
|
3460
|
-
for (const mutation of mutations) {
|
|
3461
|
-
if (mutation.op === "reorder" && mutation.id === stageId && (direction === "before" ? mutation.before : mutation.after)?.includes(anchor)) {
|
|
3462
|
-
contributors.add(contributorOf(mutation));
|
|
3463
|
-
}
|
|
3464
|
-
if (mutation.op === "insert" && mutation.stage.id === stageId && (direction === "before" ? mutation.stage.before : mutation.stage.after)?.includes(anchor)) {
|
|
3465
|
-
contributors.add(contributorOf(mutation));
|
|
3466
|
-
}
|
|
3467
|
-
}
|
|
3468
|
-
if (contributors.size === 0)
|
|
3469
|
-
contributors.add(state.contributedBy);
|
|
3470
|
-
return uniqueSorted(contributors);
|
|
3471
|
-
};
|
|
3472
|
-
const edges = [];
|
|
3473
|
-
for (const [stageId, state] of states) {
|
|
3474
|
-
const before = [];
|
|
3475
|
-
const after = [];
|
|
3476
|
-
for (const anchor of state.before) {
|
|
3477
|
-
if (states.has(anchor))
|
|
3478
|
-
before.push(anchor);
|
|
3479
|
-
else {
|
|
3480
|
-
const removed = removedStates.get(anchor);
|
|
3481
|
-
state.droppedAnchors.push({
|
|
3482
|
-
stageId,
|
|
3483
|
-
anchor,
|
|
3484
|
-
direction: "before",
|
|
3485
|
-
reason: removed ? "removed" : "missing",
|
|
3486
|
-
...removed?.removedBy ? { removedBy: removed.removedBy } : {}
|
|
3487
|
-
});
|
|
3488
|
-
}
|
|
3489
|
-
}
|
|
3490
|
-
for (const anchor of state.after) {
|
|
3491
|
-
if (states.has(anchor))
|
|
3492
|
-
after.push(anchor);
|
|
3493
|
-
else {
|
|
3494
|
-
const removed = removedStates.get(anchor);
|
|
3495
|
-
state.droppedAnchors.push({
|
|
3496
|
-
stageId,
|
|
3497
|
-
anchor,
|
|
3498
|
-
direction: "after",
|
|
3499
|
-
reason: removed ? "removed" : "missing",
|
|
3500
|
-
...removed?.removedBy ? { removedBy: removed.removedBy } : {}
|
|
3501
|
-
});
|
|
3502
|
-
}
|
|
3503
|
-
}
|
|
3504
|
-
state.before = uniqueSorted(before);
|
|
3505
|
-
state.after = uniqueSorted(after);
|
|
3506
|
-
for (const target of state.before)
|
|
3507
|
-
edges.push({ from: stageId, to: target, contributors: contributorsForAnchor(stageId, "before", target, state) });
|
|
3508
|
-
for (const source of state.after)
|
|
3509
|
-
edges.push({ from: source, to: stageId, contributors: contributorsForAnchor(stageId, "after", source, state) });
|
|
3510
|
-
}
|
|
3511
|
-
const order = topologicalOrder(states, edges);
|
|
3512
|
-
const stages = [];
|
|
3513
|
-
const record = [];
|
|
3514
|
-
for (const id of order) {
|
|
3515
|
-
const state = states.get(id);
|
|
3516
|
-
if (!state)
|
|
3517
|
-
continue;
|
|
3518
|
-
stages.push(composeStageWrappers(state));
|
|
3519
|
-
const wrappedBy = state.wrappers.toSorted((left, right) => {
|
|
3520
|
-
const priorityDelta = (right.wrapper.priority ?? 0) - (left.wrapper.priority ?? 0);
|
|
3521
|
-
if (priorityDelta !== 0)
|
|
3522
|
-
return priorityDelta;
|
|
3523
|
-
return left.contributedBy.localeCompare(right.contributedBy);
|
|
3524
|
-
}).map((wrapper) => wrapper.contributedBy);
|
|
3525
|
-
record.push({
|
|
3526
|
-
stageId: id,
|
|
3527
|
-
contributedBy: state.contributedBy,
|
|
3528
|
-
...state.replacedBy ? { replacedBy: state.replacedBy } : {},
|
|
3529
|
-
...wrappedBy.length > 0 ? { wrappedBy } : {},
|
|
3530
|
-
...state.droppedAnchors.length > 0 ? { droppedAnchors: state.droppedAnchors.toSorted((left, right) => left.anchor.localeCompare(right.anchor)) } : {},
|
|
3531
|
-
isProtected: state.isProtected
|
|
3532
|
-
});
|
|
3533
|
-
}
|
|
3534
|
-
record.push(...[...removedStates.entries()].toSorted((left, right) => {
|
|
3535
|
-
const leftIndex = left[1].baseIndex ?? Number.MAX_SAFE_INTEGER;
|
|
3536
|
-
const rightIndex = right[1].baseIndex ?? Number.MAX_SAFE_INTEGER;
|
|
3537
|
-
if (leftIndex !== rightIndex)
|
|
3538
|
-
return leftIndex - rightIndex;
|
|
3539
|
-
return left[0].localeCompare(right[0]);
|
|
3540
|
-
}).map(([stageId, state]) => ({
|
|
3541
|
-
stageId,
|
|
3542
|
-
contributedBy: state.contributedBy,
|
|
3543
|
-
...state.removedBy ? { removedBy: state.removedBy } : {},
|
|
3544
|
-
isProtected: state.isProtected
|
|
3545
|
-
})));
|
|
3546
|
-
return { stages, order, record, cycles: [] };
|
|
3547
|
-
}
|
|
3548
|
-
// packages/core/src/dependencyGraph.ts
|
|
3549
|
-
function uniqueSorted2(values) {
|
|
3550
|
-
return Array.from(new Set(values)).sort((left, right) => left.localeCompare(right));
|
|
3551
|
-
}
|
|
3552
|
-
function deriveGraphId(tasks) {
|
|
3553
|
-
const basis = tasks.map((task) => String(task.id)).toSorted((left, right) => left.localeCompare(right)).join("|");
|
|
3554
|
-
let hash = 2166136261;
|
|
3555
|
-
for (let index = 0;index < basis.length; index += 1) {
|
|
3556
|
-
hash ^= basis.charCodeAt(index);
|
|
3557
|
-
hash = Math.imul(hash, 16777619);
|
|
3558
|
-
}
|
|
3559
|
-
return `graph-${(hash >>> 0).toString(16).padStart(8, "0")}`;
|
|
3560
|
-
}
|
|
3561
|
-
function dedupeEdges(edges) {
|
|
3562
|
-
const byKey = new Map;
|
|
3563
|
-
for (const edge of edges) {
|
|
3564
|
-
byKey.set(`${edge.type}\x00${edge.fromTaskId}\x00${edge.toTaskId}`, edge);
|
|
3565
|
-
}
|
|
3566
|
-
return [...byKey.values()].toSorted((left, right) => {
|
|
3567
|
-
const typeDelta = left.type.localeCompare(right.type);
|
|
3568
|
-
if (typeDelta !== 0)
|
|
3569
|
-
return typeDelta;
|
|
3570
|
-
const fromDelta = left.fromTaskId.localeCompare(right.fromTaskId);
|
|
3571
|
-
if (fromDelta !== 0)
|
|
3572
|
-
return fromDelta;
|
|
3573
|
-
return left.toTaskId.localeCompare(right.toTaskId);
|
|
3574
|
-
});
|
|
3575
|
-
}
|
|
3576
|
-
function detectBlockingCycles(tasks, edges) {
|
|
3577
|
-
const taskIds = tasks.map((task) => String(task.id)).toSorted((left, right) => left.localeCompare(right));
|
|
3578
|
-
const adjacency = new Map;
|
|
3579
|
-
for (const taskId of taskIds)
|
|
3580
|
-
adjacency.set(taskId, []);
|
|
3581
|
-
for (const edge of edges) {
|
|
3582
|
-
if (edge.type === "blocks")
|
|
3583
|
-
adjacency.get(edge.fromTaskId)?.push(edge.toTaskId);
|
|
3584
|
-
}
|
|
3585
|
-
for (const targets of adjacency.values())
|
|
3586
|
-
targets.sort((left, right) => left.localeCompare(right));
|
|
3587
|
-
let nextIndex = 0;
|
|
3588
|
-
const indexByNode = new Map;
|
|
3589
|
-
const lowByNode = new Map;
|
|
3590
|
-
const stack = [];
|
|
3591
|
-
const onStack = new Set;
|
|
3592
|
-
const cycles = [];
|
|
3593
|
-
const visit = (node) => {
|
|
3594
|
-
indexByNode.set(node, nextIndex);
|
|
3595
|
-
lowByNode.set(node, nextIndex);
|
|
3596
|
-
nextIndex += 1;
|
|
3597
|
-
stack.push(node);
|
|
3598
|
-
onStack.add(node);
|
|
3599
|
-
for (const target of adjacency.get(node) ?? []) {
|
|
3600
|
-
if (!indexByNode.has(target)) {
|
|
3601
|
-
visit(target);
|
|
3602
|
-
lowByNode.set(node, Math.min(lowByNode.get(node) ?? 0, lowByNode.get(target) ?? 0));
|
|
3603
|
-
} else if (onStack.has(target)) {
|
|
3604
|
-
lowByNode.set(node, Math.min(lowByNode.get(node) ?? 0, indexByNode.get(target) ?? 0));
|
|
3605
|
-
}
|
|
3606
|
-
}
|
|
3607
|
-
if (lowByNode.get(node) !== indexByNode.get(node))
|
|
3608
|
-
return;
|
|
3609
|
-
const component = [];
|
|
3610
|
-
let current;
|
|
3611
|
-
do {
|
|
3612
|
-
current = stack.pop();
|
|
3613
|
-
if (current) {
|
|
3614
|
-
onStack.delete(current);
|
|
3615
|
-
component.push(current);
|
|
3616
|
-
}
|
|
3617
|
-
} while (current && current !== node);
|
|
3618
|
-
const selfLoop = (adjacency.get(node) ?? []).includes(node);
|
|
3619
|
-
if (component.length > 1 || selfLoop)
|
|
3620
|
-
cycles.push(component.sort((left, right) => left.localeCompare(right)));
|
|
3621
|
-
};
|
|
3622
|
-
for (const taskId of taskIds) {
|
|
3623
|
-
if (!indexByNode.has(taskId))
|
|
3624
|
-
visit(taskId);
|
|
3625
|
-
}
|
|
3626
|
-
return cycles.sort((left, right) => left.join("\x00").localeCompare(right.join("\x00")));
|
|
3627
|
-
}
|
|
3628
|
-
function buildDependencyGraphModel(tasks, options = {}) {
|
|
3629
|
-
const badges = computeTaskDependencyBadges(tasks);
|
|
3630
|
-
const index = buildTaskReferenceIndex(tasks);
|
|
3631
|
-
const edges = [];
|
|
3632
|
-
const unresolvedRefs = [];
|
|
3633
|
-
for (const task of tasks) {
|
|
3634
|
-
for (const ref of readTaskMetadataStringList(task, "dependencies")) {
|
|
3635
|
-
const dependencyId = resolveTaskReference(ref, index.tasksById, index.taskIdByExternalRef, index.taskIdBySourceIssueId);
|
|
3636
|
-
if (dependencyId)
|
|
3637
|
-
edges.push({ fromTaskId: dependencyId, toTaskId: String(task.id), type: "blocks" });
|
|
3638
|
-
else
|
|
3639
|
-
unresolvedRefs.push(ref);
|
|
3640
|
-
}
|
|
3641
|
-
for (const ref of readTaskMetadataStringList(task, "parentChildDeps")) {
|
|
3642
|
-
const parentId = resolveTaskReference(ref, index.tasksById, index.taskIdByExternalRef, index.taskIdBySourceIssueId);
|
|
3643
|
-
if (parentId)
|
|
3644
|
-
edges.push({ fromTaskId: parentId, toTaskId: String(task.id), type: "parent-child" });
|
|
3645
|
-
else
|
|
3646
|
-
unresolvedRefs.push(ref);
|
|
3647
|
-
}
|
|
3648
|
-
}
|
|
3649
|
-
const dedupedEdges = dedupeEdges(edges);
|
|
3650
|
-
const nodes = tasks.map((task) => {
|
|
3651
|
-
const summary = badges.get(task.id);
|
|
3652
|
-
const assignees = readTaskAssigneeLogins(task);
|
|
3653
|
-
const groupKey = extractTaskGroupKey(task.title);
|
|
3654
|
-
return {
|
|
3655
|
-
taskId: String(task.id),
|
|
3656
|
-
title: task.title,
|
|
3657
|
-
status: task.status,
|
|
3658
|
-
priority: task.priority,
|
|
3659
|
-
assignee: assignees[0] ?? null,
|
|
3660
|
-
blockedBy: summary?.blockedBy ?? [],
|
|
3661
|
-
blocks: summary?.blocks ?? [],
|
|
3662
|
-
blockingDepth: summary?.blockingDepth ?? 0,
|
|
3663
|
-
blockerClass: null,
|
|
3664
|
-
actionRiskTier: null,
|
|
3665
|
-
epicKey: groupKey,
|
|
3666
|
-
groupKey,
|
|
3667
|
-
externalId: task.externalId,
|
|
3668
|
-
sourceIssueId: task.sourceIssueId ?? null,
|
|
3669
|
-
scope: task.scope,
|
|
3670
|
-
validationKeys: task.validationKeys
|
|
3671
|
-
};
|
|
3672
|
-
}).toSorted((left, right) => left.taskId.localeCompare(right.taskId));
|
|
3673
|
-
return {
|
|
3674
|
-
graphId: options.graphId ?? deriveGraphId(tasks),
|
|
3675
|
-
nodes,
|
|
3676
|
-
edges: dedupedEdges,
|
|
3677
|
-
layout: buildTaskGraphLayout(options.snapshot ?? null, tasks, { showParentChild: options.showParentChild ?? true }),
|
|
3678
|
-
cycles: detectBlockingCycles(tasks, dedupedEdges),
|
|
3679
|
-
unresolvedRefs: uniqueSorted2(unresolvedRefs),
|
|
3680
|
-
degraded: false,
|
|
3681
|
-
generatedAt: options.generatedAt ?? new Date().toISOString()
|
|
3682
|
-
};
|
|
3683
|
-
}
|
|
3684
|
-
// packages/core/src/rollups.ts
|
|
3685
|
-
import { isOperatorActiveRunStatus } from "@rig/contracts";
|
|
3686
|
-
var UNASSIGNED_EPIC = "(unassigned-epic)";
|
|
3687
|
-
var UNASSIGNED_ASSIGNEE = "(unassigned)";
|
|
3688
|
-
var HUMAN_BLOCKER_CLASSES = {
|
|
3689
|
-
"human-decision": true,
|
|
3690
|
-
"human-approval": true,
|
|
3691
|
-
"external-input": true,
|
|
3692
|
-
unknown: true
|
|
3693
|
-
};
|
|
3694
|
-
function isObjectRecord4(value) {
|
|
3695
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3696
|
-
}
|
|
3697
|
-
function readIssueType2(task) {
|
|
3698
|
-
const metadata = isObjectRecord4(task.metadata) ? task.metadata : null;
|
|
3699
|
-
const raw = isObjectRecord4(metadata?.raw) ? metadata.raw : null;
|
|
3700
|
-
const value = raw?.issueType ?? metadata?.issueType;
|
|
3701
|
-
return typeof value === "string" && value.trim() ? value.trim().toLowerCase() : null;
|
|
3702
|
-
}
|
|
3703
|
-
function isEpicTask(task) {
|
|
3704
|
-
return readIssueType2(task) === "epic";
|
|
3705
|
-
}
|
|
3706
|
-
function isInFlightTaskStatus(status) {
|
|
3707
|
-
switch (status) {
|
|
3708
|
-
case "queued":
|
|
3709
|
-
case "running":
|
|
3710
|
-
case "in_progress":
|
|
3711
|
-
case "under_review":
|
|
3712
|
-
return true;
|
|
3713
|
-
default:
|
|
3714
|
-
return false;
|
|
3715
|
-
}
|
|
3716
|
-
}
|
|
3717
|
-
function epicKeyForTask(task, tasksById, resolve) {
|
|
3718
|
-
const parentRef = readTaskMetadataStringList(task, "parentChildDeps")[0];
|
|
3719
|
-
const parentId = parentRef ? resolve(parentRef) : null;
|
|
3720
|
-
const parent = parentId ? tasksById.get(parentId) : null;
|
|
3721
|
-
if (parent)
|
|
3722
|
-
return extractTaskGroupKey(parent.title) ?? parent.title ?? parent.id;
|
|
3723
|
-
return extractTaskGroupKey(task.title) ?? UNASSIGNED_EPIC;
|
|
3724
|
-
}
|
|
3725
|
-
function rollupByEpic(tasks, classifications = new Map) {
|
|
3726
|
-
const index = buildTaskReferenceIndex(tasks);
|
|
3727
|
-
const badges = computeTaskDependencyBadges(tasks);
|
|
3728
|
-
const resolve = (ref) => resolveTaskReference(ref, index.tasksById, index.taskIdByExternalRef, index.taskIdBySourceIssueId);
|
|
3729
|
-
const buckets = new Map;
|
|
3730
|
-
for (const task of tasks) {
|
|
3731
|
-
if (isEpicTask(task))
|
|
3732
|
-
continue;
|
|
3733
|
-
const epicKey = epicKeyForTask(task, index.tasksById, resolve);
|
|
3734
|
-
const current = buckets.get(epicKey) ?? {
|
|
3735
|
-
total: 0,
|
|
3736
|
-
completed: 0,
|
|
3737
|
-
blockedCount: 0,
|
|
3738
|
-
humanBlockedCount: 0,
|
|
3739
|
-
inFlightCount: 0,
|
|
3740
|
-
byStatus: {}
|
|
3741
|
-
};
|
|
3742
|
-
current.total += 1;
|
|
3743
|
-
if (isTaskTerminalStatus(task.status))
|
|
3744
|
-
current.completed += 1;
|
|
3745
|
-
if (badges.get(task.id)?.blocked === true)
|
|
3746
|
-
current.blockedCount += 1;
|
|
3747
|
-
if (isInFlightTaskStatus(task.status))
|
|
3748
|
-
current.inFlightCount += 1;
|
|
3749
|
-
const classification = classifications.get(task.id);
|
|
3750
|
-
if (classification && HUMAN_BLOCKER_CLASSES[classification])
|
|
3751
|
-
current.humanBlockedCount += 1;
|
|
3752
|
-
current.byStatus[task.status] = (current.byStatus[task.status] ?? 0) + 1;
|
|
3753
|
-
buckets.set(epicKey, current);
|
|
3754
|
-
}
|
|
3755
|
-
return [...buckets.entries()].map(([epicKey, bucket]) => ({
|
|
3756
|
-
epicKey,
|
|
3757
|
-
total: bucket.total,
|
|
3758
|
-
percentComplete: bucket.total === 0 ? 0 : Math.round(100 * bucket.completed / bucket.total),
|
|
3759
|
-
blockedCount: bucket.blockedCount,
|
|
3760
|
-
humanBlockedCount: bucket.humanBlockedCount,
|
|
3761
|
-
inFlightCount: bucket.inFlightCount,
|
|
3762
|
-
byStatus: Object.fromEntries(Object.entries(bucket.byStatus).sort(([left], [right]) => left.localeCompare(right)))
|
|
3763
|
-
})).toSorted((left, right) => left.epicKey.localeCompare(right.epicKey));
|
|
3764
|
-
}
|
|
3765
|
-
function assigneesForTask(task) {
|
|
3766
|
-
const assignees = readTaskAssigneeLogins(task);
|
|
3767
|
-
return assignees.length > 0 ? assignees : [UNASSIGNED_ASSIGNEE];
|
|
3768
|
-
}
|
|
3769
|
-
function rollupByAssignee(tasks, runs) {
|
|
3770
|
-
const tasksById = new Map(tasks.map((task) => [String(task.id), task]));
|
|
3771
|
-
const badges = computeTaskDependencyBadges(tasks);
|
|
3772
|
-
const buckets = new Map;
|
|
3773
|
-
const ensureBucket = (assignee) => {
|
|
3774
|
-
const existing = buckets.get(assignee);
|
|
3775
|
-
if (existing)
|
|
3776
|
-
return existing;
|
|
3777
|
-
const created = { openTaskCount: 0, inFlightRunIds: new Set, prsAwaitingReview: 0, blockers: new Set };
|
|
3778
|
-
buckets.set(assignee, created);
|
|
3779
|
-
return created;
|
|
3780
|
-
};
|
|
3781
|
-
for (const task of tasks) {
|
|
3782
|
-
if (isEpicTask(task))
|
|
3783
|
-
continue;
|
|
3784
|
-
for (const assignee of assigneesForTask(task)) {
|
|
3785
|
-
const bucket = ensureBucket(assignee);
|
|
3786
|
-
if (!isTaskTerminalStatus(task.status))
|
|
3787
|
-
bucket.openTaskCount += 1;
|
|
3788
|
-
if (badges.get(task.id)?.blocked === true)
|
|
3789
|
-
bucket.blockers.add(task.id);
|
|
3790
|
-
if (task.status === "under_review")
|
|
3791
|
-
bucket.prsAwaitingReview += 1;
|
|
3792
|
-
}
|
|
3793
|
-
}
|
|
3794
|
-
for (const run of runs) {
|
|
3795
|
-
const taskId = run.record.taskId;
|
|
3796
|
-
const task = taskId ? tasksById.get(taskId) : null;
|
|
3797
|
-
if (!task)
|
|
3798
|
-
continue;
|
|
3799
|
-
if (isEpicTask(task))
|
|
3800
|
-
continue;
|
|
3801
|
-
for (const assignee of assigneesForTask(task)) {
|
|
3802
|
-
const bucket = ensureBucket(assignee);
|
|
3803
|
-
if (run.status && isOperatorActiveRunStatus(run.status))
|
|
3804
|
-
bucket.inFlightRunIds.add(run.record.runId ?? `${String(task.id)}:${run.lastSeq}`);
|
|
3805
|
-
if (run.status === "reviewing" && run.record.prUrl)
|
|
3806
|
-
bucket.prsAwaitingReview += 1;
|
|
3807
|
-
}
|
|
3808
|
-
}
|
|
3809
|
-
return [...buckets.entries()].map(([assignee, bucket]) => ({
|
|
3810
|
-
assignee,
|
|
3811
|
-
openTaskCount: bucket.openTaskCount,
|
|
3812
|
-
inFlightRunCount: bucket.inFlightRunIds.size,
|
|
3813
|
-
prsAwaitingReview: bucket.prsAwaitingReview,
|
|
3814
|
-
blockers: [...bucket.blockers].toSorted((left, right) => String(left).localeCompare(String(right)))
|
|
3815
|
-
})).toSorted((left, right) => left.assignee.localeCompare(right.assignee));
|
|
3816
|
-
}
|
|
3817
205
|
|
|
3818
206
|
// packages/core/src/index.ts
|
|
3819
207
|
var RIG_CORE_PACKAGE = "@rig/core";
|
|
3820
208
|
export {
|
|
3821
|
-
stripTaskCode,
|
|
3822
|
-
selectWorkspaces,
|
|
3823
|
-
selectWorkspace,
|
|
3824
|
-
selectUserInputsForWorkspace,
|
|
3825
|
-
selectUserInputsForRun,
|
|
3826
|
-
selectTasksGroupedByStatus,
|
|
3827
|
-
selectTasksForWorkspace,
|
|
3828
|
-
selectTasksByWorkspace,
|
|
3829
|
-
selectTasksByStatus,
|
|
3830
|
-
selectTasksAssignedToMe,
|
|
3831
|
-
selectTasksAssignedTo,
|
|
3832
|
-
selectTask,
|
|
3833
|
-
selectRunsForWorkspace,
|
|
3834
|
-
selectRunsForTask,
|
|
3835
|
-
selectRunsByTask,
|
|
3836
|
-
selectRun,
|
|
3837
|
-
selectRankedReadyTasks,
|
|
3838
|
-
selectQueueForWorkspace,
|
|
3839
|
-
selectPrimaryWorkspace,
|
|
3840
|
-
selectPendingApprovals,
|
|
3841
|
-
selectNextReadyTaskByPriority,
|
|
3842
|
-
selectGraphsForWorkspace,
|
|
3843
|
-
selectApprovalsForWorkspace,
|
|
3844
|
-
selectApprovalsForRun,
|
|
3845
|
-
selectAdhocRunsForWorkspace,
|
|
3846
|
-
selectAdhocRuns,
|
|
3847
|
-
scoreTask,
|
|
3848
|
-
rollupByEpic,
|
|
3849
|
-
rollupByAssignee,
|
|
3850
|
-
resolveTaskReference,
|
|
3851
|
-
resolveStagePipeline,
|
|
3852
|
-
readTaskSourceIssueId,
|
|
3853
|
-
readTaskScope,
|
|
3854
|
-
readTaskMetadataStringList,
|
|
3855
|
-
readTaskDependencyRefs,
|
|
3856
|
-
readTaskBlockingDependencyRefs,
|
|
3857
|
-
readTaskAssigneeLogins,
|
|
3858
|
-
rankTasks,
|
|
3859
|
-
rankReadyTasks,
|
|
3860
|
-
pruneQueueEntries,
|
|
3861
|
-
projectTaskStatusWithSessions,
|
|
3862
|
-
projectTaskStatusForGrouping,
|
|
3863
|
-
projectRunStatusForTaskGrouping,
|
|
3864
|
-
pickDefaultWorkspaceId,
|
|
3865
|
-
normalizeTaskAssigneeFilter,
|
|
3866
|
-
isTaskTerminalStatus,
|
|
3867
|
-
extractTaskGroupKey,
|
|
3868
|
-
extractTaskCode,
|
|
3869
|
-
disjointScope,
|
|
3870
209
|
definePlugin,
|
|
3871
210
|
defineConfig,
|
|
3872
211
|
createPluginHost,
|
|
3873
|
-
|
|
3874
|
-
computeTaskBlockingDepths,
|
|
3875
|
-
buildTaskReferenceIndex,
|
|
3876
|
-
buildTaskGraphLayout,
|
|
3877
|
-
buildRigInitConfigSource,
|
|
3878
|
-
buildDependencyGraphModel,
|
|
3879
|
-
applyEngineEvent as applyRigEvent,
|
|
3880
|
-
applyEngineEvents,
|
|
3881
|
-
applyEngineEvent,
|
|
3882
|
-
RIG_CORE_PACKAGE,
|
|
3883
|
-
PipelineUnresolvableError
|
|
212
|
+
RIG_CORE_PACKAGE
|
|
3884
213
|
};
|