@h-rig/core 0.0.6-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -0
- package/dist/src/define-config.js +24 -0
- package/dist/src/define-plugin.js +50 -0
- package/dist/src/engineReadModelReducer.js +1780 -0
- package/dist/src/index.js +2616 -0
- package/dist/src/load-config.js +49 -0
- package/dist/src/plugin-host.js +158 -0
- package/dist/src/plugin-runtime.js +1 -0
- package/dist/src/rig-init-builder.js +57 -0
- package/dist/src/rigSelectors.js +293 -0
- package/dist/src/taskGraph.js +64 -0
- package/dist/src/taskGraphCodes.js +26 -0
- package/dist/src/taskGraphLayout.js +374 -0
- package/package.json +37 -0
|
@@ -0,0 +1,2616 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// packages/core/src/define-plugin.ts
|
|
3
|
+
import { Schema } from "effect";
|
|
4
|
+
import { RigPlugin } from "@rig/contracts";
|
|
5
|
+
function definePlugin(meta, runtime) {
|
|
6
|
+
const validated = Schema.decodeUnknownSync(RigPlugin)(meta);
|
|
7
|
+
if (!runtime) {
|
|
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
|
+
return { ...validated, __runtime: runtime };
|
|
47
|
+
}
|
|
48
|
+
// packages/core/src/define-config.ts
|
|
49
|
+
import { Schema as Schema2 } from "effect";
|
|
50
|
+
import { RigConfig } from "@rig/contracts";
|
|
51
|
+
function defineConfig(cfg) {
|
|
52
|
+
const runtimeByName = new Map;
|
|
53
|
+
const plugins = cfg.plugins ?? [];
|
|
54
|
+
for (const plugin of plugins) {
|
|
55
|
+
if (plugin?.__runtime) {
|
|
56
|
+
runtimeByName.set(plugin.name, plugin.__runtime);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
const decoded = Schema2.decodeUnknownSync(RigConfig)(cfg);
|
|
60
|
+
const decodedPlugins = decoded.plugins.map((p) => {
|
|
61
|
+
const runtime = runtimeByName.get(p.name);
|
|
62
|
+
if (!runtime)
|
|
63
|
+
return p;
|
|
64
|
+
return { ...p, __runtime: runtime };
|
|
65
|
+
});
|
|
66
|
+
return { ...decoded, plugins: decodedPlugins };
|
|
67
|
+
}
|
|
68
|
+
// packages/core/src/plugin-host.ts
|
|
69
|
+
function indexById(contributions, kind) {
|
|
70
|
+
const map = new Map;
|
|
71
|
+
const registrant = new Map;
|
|
72
|
+
for (const { item, pluginName } of contributions) {
|
|
73
|
+
if (map.has(item.id)) {
|
|
74
|
+
throw new Error(`duplicate ${kind} id "${item.id}": registered by plugins "${registrant.get(item.id)}" and "${pluginName}"`);
|
|
75
|
+
}
|
|
76
|
+
map.set(item.id, item);
|
|
77
|
+
registrant.set(item.id, pluginName);
|
|
78
|
+
}
|
|
79
|
+
return map;
|
|
80
|
+
}
|
|
81
|
+
function assertUniquePluginNames(plugins) {
|
|
82
|
+
const seen = new Set;
|
|
83
|
+
for (const plugin of plugins) {
|
|
84
|
+
if (seen.has(plugin.name)) {
|
|
85
|
+
throw new Error(`duplicate plugin name "${plugin.name}"`);
|
|
86
|
+
}
|
|
87
|
+
seen.add(plugin.name);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function assertRuntimeMatchesMetadata(plugin) {
|
|
91
|
+
const declaredValidators = new Map((plugin.contributes?.validators ?? []).map((validator) => [validator.id, validator]));
|
|
92
|
+
const runtimeValidators = new Map((plugin.__runtime?.validators ?? []).map((validator) => [validator.id, validator]));
|
|
93
|
+
for (const validator of runtimeValidators.values()) {
|
|
94
|
+
const metadata = declaredValidators.get(validator.id);
|
|
95
|
+
if (!metadata) {
|
|
96
|
+
throw new Error(`plugin "${plugin.name}" executable validator "${validator.id}" has no matching metadata entry in contributes.validators`);
|
|
97
|
+
}
|
|
98
|
+
if (metadata.category !== validator.category) {
|
|
99
|
+
throw new Error(`plugin "${plugin.name}" executable validator "${validator.id}" category "${validator.category}" does not match metadata category "${metadata.category}"`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (plugin.__runtime?.validators) {
|
|
103
|
+
for (const validator of declaredValidators.values()) {
|
|
104
|
+
if (!runtimeValidators.has(validator.id)) {
|
|
105
|
+
throw new Error(`plugin "${plugin.name}" validator metadata "${validator.id}" has no runtime implementation`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
const declaredTaskSources = new Map((plugin.contributes?.taskSources ?? []).map((source) => [source.id, source]));
|
|
110
|
+
const runtimeTaskSources = new Map((plugin.__runtime?.taskSources ?? []).map((source) => [source.id, source]));
|
|
111
|
+
for (const source of runtimeTaskSources.values()) {
|
|
112
|
+
const metadata = declaredTaskSources.get(source.id);
|
|
113
|
+
if (!metadata) {
|
|
114
|
+
throw new Error(`plugin "${plugin.name}" executable task source "${source.id}" has no matching metadata entry in contributes.taskSources`);
|
|
115
|
+
}
|
|
116
|
+
if (metadata.kind !== source.kind) {
|
|
117
|
+
throw new Error(`plugin "${plugin.name}" executable task source "${source.id}" kind "${source.kind}" does not match metadata kind "${metadata.kind}"`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
if (plugin.__runtime?.taskSources) {
|
|
121
|
+
for (const source of declaredTaskSources.values()) {
|
|
122
|
+
if (!runtimeTaskSources.has(source.id)) {
|
|
123
|
+
throw new Error(`plugin "${plugin.name}" task source metadata "${source.id}" has no runtime implementation`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
function createPluginHost(plugins) {
|
|
129
|
+
assertUniquePluginNames(plugins);
|
|
130
|
+
for (const plugin of plugins) {
|
|
131
|
+
assertRuntimeMatchesMetadata(plugin);
|
|
132
|
+
}
|
|
133
|
+
const validators = [];
|
|
134
|
+
const hooks = [];
|
|
135
|
+
const skills = [];
|
|
136
|
+
const repoSources = [];
|
|
137
|
+
const agentRoles = [];
|
|
138
|
+
const taskFieldExtensions = [];
|
|
139
|
+
const taskSources = [];
|
|
140
|
+
const cliCommands = [];
|
|
141
|
+
const executableValidators = [];
|
|
142
|
+
const executableTaskSources = [];
|
|
143
|
+
for (const plugin of plugins) {
|
|
144
|
+
const c = plugin.contributes;
|
|
145
|
+
if (!c)
|
|
146
|
+
continue;
|
|
147
|
+
const pluginName = plugin.name;
|
|
148
|
+
if (plugin.__runtime?.validators) {
|
|
149
|
+
executableValidators.push(...plugin.__runtime.validators.map((item) => ({ item, pluginName })));
|
|
150
|
+
}
|
|
151
|
+
if (plugin.__runtime?.taskSources) {
|
|
152
|
+
executableTaskSources.push(...plugin.__runtime.taskSources.map((item) => ({ item, pluginName })));
|
|
153
|
+
}
|
|
154
|
+
if (c.validators)
|
|
155
|
+
validators.push(...c.validators.map((item) => ({ item, pluginName })));
|
|
156
|
+
if (c.hooks)
|
|
157
|
+
hooks.push(...c.hooks.map((item) => ({ item, pluginName })));
|
|
158
|
+
if (c.skills)
|
|
159
|
+
skills.push(...c.skills.map((item) => ({ item, pluginName })));
|
|
160
|
+
if (c.repoSources)
|
|
161
|
+
repoSources.push(...c.repoSources.map((item) => ({ item, pluginName })));
|
|
162
|
+
if (c.agentRoles)
|
|
163
|
+
agentRoles.push(...c.agentRoles.map((item) => ({ item, pluginName })));
|
|
164
|
+
if (c.taskFieldSchemas)
|
|
165
|
+
taskFieldExtensions.push(...c.taskFieldSchemas.map((item) => ({ item, pluginName })));
|
|
166
|
+
if (c.taskSources)
|
|
167
|
+
taskSources.push(...c.taskSources.map((item) => ({ item, pluginName })));
|
|
168
|
+
if (c.cliCommands)
|
|
169
|
+
cliCommands.push(...c.cliCommands.map((item) => ({ item, pluginName })));
|
|
170
|
+
}
|
|
171
|
+
indexById(executableValidators, "executableValidator");
|
|
172
|
+
indexById(executableTaskSources, "executableTaskSource");
|
|
173
|
+
const taskSourceFactoryByKind = new Map;
|
|
174
|
+
const taskSourceKindRegistrant = new Map;
|
|
175
|
+
for (const { item, pluginName } of executableTaskSources) {
|
|
176
|
+
if (taskSourceFactoryByKind.has(item.kind)) {
|
|
177
|
+
throw new Error(`duplicate task source kind "${item.kind}": registered by plugins "${taskSourceKindRegistrant.get(item.kind)}" and "${pluginName}"`);
|
|
178
|
+
}
|
|
179
|
+
taskSourceFactoryByKind.set(item.kind, item);
|
|
180
|
+
taskSourceKindRegistrant.set(item.kind, pluginName);
|
|
181
|
+
}
|
|
182
|
+
const validatorMap = indexById(validators, "validator");
|
|
183
|
+
const hookMap = indexById(hooks, "hook");
|
|
184
|
+
const skillMap = indexById(skills, "skill");
|
|
185
|
+
const repoSourceMap = indexById(repoSources, "repoSource");
|
|
186
|
+
const agentRoleMap = indexById(agentRoles, "agentRole");
|
|
187
|
+
const taskFieldExtMap = indexById(taskFieldExtensions, "taskFieldExtension");
|
|
188
|
+
const taskSourceMap = indexById(taskSources, "taskSource");
|
|
189
|
+
const cliCommandMap = indexById(cliCommands, "cliCommand");
|
|
190
|
+
const allValidators = validators.map((c) => c.item);
|
|
191
|
+
const allHooks = hooks.map((c) => c.item);
|
|
192
|
+
const allSkills = skills.map((c) => c.item);
|
|
193
|
+
const allRepoSources = repoSources.map((c) => c.item);
|
|
194
|
+
const allAgentRoles = agentRoles.map((c) => c.item);
|
|
195
|
+
const allTaskFieldExtensions = taskFieldExtensions.map((c) => c.item);
|
|
196
|
+
const allTaskSources = taskSources.map((c) => c.item);
|
|
197
|
+
const allCliCommands = cliCommands.map((c) => c.item);
|
|
198
|
+
const allExecutableValidators = executableValidators.map((c) => c.item);
|
|
199
|
+
const allExecutableTaskSources = executableTaskSources.map((c) => c.item);
|
|
200
|
+
return {
|
|
201
|
+
getValidator: (id) => validatorMap.get(id),
|
|
202
|
+
getHook: (id) => hookMap.get(id),
|
|
203
|
+
getSkill: (id) => skillMap.get(id),
|
|
204
|
+
getRepoSource: (id) => repoSourceMap.get(id),
|
|
205
|
+
getAgentRole: (id) => agentRoleMap.get(id),
|
|
206
|
+
getTaskFieldExtension: (id) => taskFieldExtMap.get(id),
|
|
207
|
+
getTaskSource: (id) => taskSourceMap.get(id),
|
|
208
|
+
getCliCommand: (id) => cliCommandMap.get(id),
|
|
209
|
+
listValidators: () => allValidators,
|
|
210
|
+
listHooks: () => allHooks,
|
|
211
|
+
listSkills: () => allSkills,
|
|
212
|
+
listRepoSources: () => allRepoSources,
|
|
213
|
+
listAgentRoles: () => allAgentRoles,
|
|
214
|
+
listTaskFieldExtensions: () => allTaskFieldExtensions,
|
|
215
|
+
listTaskSources: () => allTaskSources,
|
|
216
|
+
listCliCommands: () => allCliCommands,
|
|
217
|
+
listExecutableValidators: () => allExecutableValidators,
|
|
218
|
+
listExecutableTaskSources: () => allExecutableTaskSources,
|
|
219
|
+
resolveTaskSourceFactoryByKind: (kind) => taskSourceFactoryByKind.get(kind)
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
// packages/core/src/rig-init-builder.ts
|
|
223
|
+
function buildRigInitConfigSource(input) {
|
|
224
|
+
const lines = [`import { defineConfig } from "@rig/core";`];
|
|
225
|
+
if (input.useStandardPlugin && input.taskSource.kind === "github-issues") {
|
|
226
|
+
lines.push(`import standard, { createStateGitHubCredentialProvider } from "@rig/standard-plugin";`);
|
|
227
|
+
} else if (input.useStandardPlugin) {
|
|
228
|
+
lines.push(`import standard from "@rig/standard-plugin";`);
|
|
229
|
+
}
|
|
230
|
+
lines.push(``, `export default defineConfig({`);
|
|
231
|
+
const projectRepo = input.projectRepo ?? (input.taskSource.kind === "github-issues" ? `${input.taskSource.owner}/${input.taskSource.repo}` : undefined);
|
|
232
|
+
lines.push(projectRepo ? ` project: { name: ${JSON.stringify(input.projectName)}, repo: ${JSON.stringify(projectRepo)} },` : ` project: { name: ${JSON.stringify(input.projectName)} },`);
|
|
233
|
+
if (input.useStandardPlugin && input.taskSource.kind === "github-issues") {
|
|
234
|
+
lines.push(` plugins: [standard({`);
|
|
235
|
+
lines.push(` githubCredentialProvider: createStateGitHubCredentialProvider(),`);
|
|
236
|
+
lines.push(` githubWorkspaceId: ${JSON.stringify(`${input.taskSource.owner}/${input.taskSource.repo}`)},`);
|
|
237
|
+
lines.push(` githubUserId: process.env.RIG_GITHUB_USER_ID ?? "server-selected-user",`);
|
|
238
|
+
lines.push(` })],`);
|
|
239
|
+
} else {
|
|
240
|
+
lines.push(` plugins: [${input.useStandardPlugin ? "standard()" : ""}],`);
|
|
241
|
+
}
|
|
242
|
+
if (input.taskSource.kind === "github-issues") {
|
|
243
|
+
lines.push(` taskSource: {`);
|
|
244
|
+
lines.push(` kind: "github-issues",`);
|
|
245
|
+
lines.push(` owner: ${JSON.stringify(input.taskSource.owner)},`);
|
|
246
|
+
lines.push(` repo: ${JSON.stringify(input.taskSource.repo)},`);
|
|
247
|
+
lines.push(` // labels: ["task"], // uncomment to filter by labels`);
|
|
248
|
+
lines.push(` state: "open",`);
|
|
249
|
+
if (input.taskSource.assignee?.trim()) {
|
|
250
|
+
lines.push(` options: { assignee: ${JSON.stringify(input.taskSource.assignee.trim())} },`);
|
|
251
|
+
}
|
|
252
|
+
lines.push(` },`);
|
|
253
|
+
} else {
|
|
254
|
+
lines.push(` taskSource: {`);
|
|
255
|
+
lines.push(` kind: "files",`);
|
|
256
|
+
lines.push(` path: ${JSON.stringify(input.taskSource.path)},`);
|
|
257
|
+
lines.push(` },`);
|
|
258
|
+
}
|
|
259
|
+
lines.push(` workspace: { mainRepo: ".", isolation: "worktree" },`);
|
|
260
|
+
lines.push(` runtime: { harness: "pi", mode: "yolo" },`);
|
|
261
|
+
lines.push(` planning: { mode: "auto" },`);
|
|
262
|
+
lines.push(` github: {`);
|
|
263
|
+
lines.push(` issueUpdates: "lifecycle",`);
|
|
264
|
+
lines.push(` projects: { enabled: false },`);
|
|
265
|
+
lines.push(` },`);
|
|
266
|
+
lines.push(` automation: { maxValidationAttempts: 30, maxPrFixIterations: 30 },`);
|
|
267
|
+
lines.push(` pr: { mode: "auto", watchChecks: true, autoFixChecks: true, autoFixReview: true },`);
|
|
268
|
+
lines.push(` merge: { mode: "auto", method: "repo-default", deleteBranch: "repo-default", bypass: false },`);
|
|
269
|
+
lines.push(` issueAnalysis: { enabled: true, harness: "pi", mode: "continuous" },`);
|
|
270
|
+
lines.push(`});`);
|
|
271
|
+
lines.push(``);
|
|
272
|
+
return lines.join(`
|
|
273
|
+
`);
|
|
274
|
+
}
|
|
275
|
+
// packages/core/src/engineReadModelReducer.ts
|
|
276
|
+
import {
|
|
277
|
+
ActionId,
|
|
278
|
+
ConversationId,
|
|
279
|
+
EngineRuntimeId,
|
|
280
|
+
MessageId,
|
|
281
|
+
RunId,
|
|
282
|
+
TaskId,
|
|
283
|
+
WorkspaceId,
|
|
284
|
+
WorktreeId
|
|
285
|
+
} from "@rig/contracts";
|
|
286
|
+
function isRecord(value) {
|
|
287
|
+
return typeof value === "object" && value !== null;
|
|
288
|
+
}
|
|
289
|
+
function readString(payload, key) {
|
|
290
|
+
const value = payload[key];
|
|
291
|
+
return typeof value === "string" ? value : undefined;
|
|
292
|
+
}
|
|
293
|
+
function readNullableString(payload, key) {
|
|
294
|
+
const value = payload[key];
|
|
295
|
+
return typeof value === "string" ? value : value === null ? null : undefined;
|
|
296
|
+
}
|
|
297
|
+
function readRecord(payload, key) {
|
|
298
|
+
const value = payload[key];
|
|
299
|
+
return isRecord(value) ? value : undefined;
|
|
300
|
+
}
|
|
301
|
+
function asWorkspaceId(value) {
|
|
302
|
+
return WorkspaceId.makeUnsafe(value);
|
|
303
|
+
}
|
|
304
|
+
function asTaskId(value) {
|
|
305
|
+
return TaskId.makeUnsafe(value);
|
|
306
|
+
}
|
|
307
|
+
function asRunId(value) {
|
|
308
|
+
return RunId.makeUnsafe(value);
|
|
309
|
+
}
|
|
310
|
+
function conversationIdFromRunId(value) {
|
|
311
|
+
return ConversationId.makeUnsafe(value);
|
|
312
|
+
}
|
|
313
|
+
function asActionId(value) {
|
|
314
|
+
return ActionId.makeUnsafe(value);
|
|
315
|
+
}
|
|
316
|
+
function runtimeIdFromRunId(value) {
|
|
317
|
+
return EngineRuntimeId.makeUnsafe(value);
|
|
318
|
+
}
|
|
319
|
+
function worktreeIdFromRunId(value) {
|
|
320
|
+
return WorktreeId.makeUnsafe(value);
|
|
321
|
+
}
|
|
322
|
+
function asMessageId(value) {
|
|
323
|
+
return MessageId.makeUnsafe(value);
|
|
324
|
+
}
|
|
325
|
+
function upsertById(items, entry) {
|
|
326
|
+
const index = items.findIndex((candidate) => candidate.id === entry.id);
|
|
327
|
+
if (index < 0) {
|
|
328
|
+
return [...items, entry];
|
|
329
|
+
}
|
|
330
|
+
const next = items.slice();
|
|
331
|
+
next[index] = entry;
|
|
332
|
+
return next;
|
|
333
|
+
}
|
|
334
|
+
function removeById(items, id) {
|
|
335
|
+
return items.filter((candidate) => candidate.id !== id);
|
|
336
|
+
}
|
|
337
|
+
function patchById(items, id, updater) {
|
|
338
|
+
const index = items.findIndex((candidate) => candidate.id === id);
|
|
339
|
+
if (index < 0) {
|
|
340
|
+
return items.slice();
|
|
341
|
+
}
|
|
342
|
+
const next = items.slice();
|
|
343
|
+
next[index] = updater(next[index]);
|
|
344
|
+
return next;
|
|
345
|
+
}
|
|
346
|
+
function upsertByKey(items, entry, key) {
|
|
347
|
+
const index = items.findIndex((candidate) => candidate[key] === entry[key]);
|
|
348
|
+
if (index < 0) {
|
|
349
|
+
return [...items, entry];
|
|
350
|
+
}
|
|
351
|
+
const next = items.slice();
|
|
352
|
+
next[index] = entry;
|
|
353
|
+
return next;
|
|
354
|
+
}
|
|
355
|
+
function patchByKey(items, keyValue, key, updater) {
|
|
356
|
+
const index = items.findIndex((candidate) => candidate[key] === keyValue);
|
|
357
|
+
if (index < 0) {
|
|
358
|
+
return items.slice();
|
|
359
|
+
}
|
|
360
|
+
const next = items.slice();
|
|
361
|
+
next[index] = updater(next[index]);
|
|
362
|
+
return next;
|
|
363
|
+
}
|
|
364
|
+
function mergeById(items, incoming) {
|
|
365
|
+
return incoming.reduce((acc, item) => upsertById(acc, item), [...items]);
|
|
366
|
+
}
|
|
367
|
+
function replaceWorkspaceSlice(items, workspaceId, incoming) {
|
|
368
|
+
return [...items.filter((item) => item.workspaceId !== workspaceId), ...incoming];
|
|
369
|
+
}
|
|
370
|
+
function withQueuePositions(items) {
|
|
371
|
+
return items.map((item, index) => Object.assign({}, item, {
|
|
372
|
+
position: index
|
|
373
|
+
}));
|
|
374
|
+
}
|
|
375
|
+
function maxIsoDate(left, right) {
|
|
376
|
+
return left.localeCompare(right) >= 0 ? left : right;
|
|
377
|
+
}
|
|
378
|
+
var REMOTE_HOST_STATUS_ONLINE = new Set([
|
|
379
|
+
"ready",
|
|
380
|
+
"busy",
|
|
381
|
+
"degraded",
|
|
382
|
+
"draining"
|
|
383
|
+
]);
|
|
384
|
+
function deriveRemoteFleetStatus(hosts, warnings) {
|
|
385
|
+
if (hosts.length === 0)
|
|
386
|
+
return "empty";
|
|
387
|
+
if (warnings.length > 0)
|
|
388
|
+
return "degraded";
|
|
389
|
+
if (hosts.some((host) => host.status === "degraded" || host.status === "quarantined")) {
|
|
390
|
+
return "degraded";
|
|
391
|
+
}
|
|
392
|
+
const onlineHostCount = hosts.filter((host) => REMOTE_HOST_STATUS_ONLINE.has(host.status)).length;
|
|
393
|
+
return onlineHostCount > 0 ? "ready" : "degraded";
|
|
394
|
+
}
|
|
395
|
+
function buildRemoteFleetSummary(input) {
|
|
396
|
+
const warnings = input.fleet?.warnings ?? [];
|
|
397
|
+
const manifestCount = input.fleet?.manifestCount ?? 0;
|
|
398
|
+
const onlineHostCount = input.hosts.filter((host) => REMOTE_HOST_STATUS_ONLINE.has(host.status)).length;
|
|
399
|
+
return {
|
|
400
|
+
updatedAt: input.updatedAt,
|
|
401
|
+
status: deriveRemoteFleetStatus(input.hosts, warnings),
|
|
402
|
+
manifestCount,
|
|
403
|
+
hostCount: input.hosts.length,
|
|
404
|
+
onlineHostCount,
|
|
405
|
+
hosts: [...input.hosts].sort((left, right) => left.name.localeCompare(right.name)),
|
|
406
|
+
warnings
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
function toRunMode(interactionMode) {
|
|
410
|
+
return interactionMode === "plan" ? "supervised" : "interactive";
|
|
411
|
+
}
|
|
412
|
+
function mapLegacySessionStatusToRunStatus(status) {
|
|
413
|
+
switch (status) {
|
|
414
|
+
case "starting":
|
|
415
|
+
return "preparing";
|
|
416
|
+
case "running":
|
|
417
|
+
return "running";
|
|
418
|
+
case "ready":
|
|
419
|
+
return "completed";
|
|
420
|
+
case "interrupted":
|
|
421
|
+
return "paused";
|
|
422
|
+
case "error":
|
|
423
|
+
return "failed";
|
|
424
|
+
case "stopped":
|
|
425
|
+
return "cancelled";
|
|
426
|
+
default:
|
|
427
|
+
return "created";
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
function mapLegacySessionStatusToRuntimeStatus(status) {
|
|
431
|
+
switch (status) {
|
|
432
|
+
case "starting":
|
|
433
|
+
return "starting";
|
|
434
|
+
case "running":
|
|
435
|
+
return "running";
|
|
436
|
+
case "interrupted":
|
|
437
|
+
return "interrupted";
|
|
438
|
+
case "error":
|
|
439
|
+
return "failed";
|
|
440
|
+
default:
|
|
441
|
+
return "exited";
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
function mapTaskStatusFromRunStatus(status, fallback) {
|
|
445
|
+
if (fallback === "blocked" || fallback === "cancelled" || fallback === "completed" || fallback === "closed" || fallback === "unknown") {
|
|
446
|
+
return fallback === "closed" ? "completed" : fallback;
|
|
447
|
+
}
|
|
448
|
+
switch (status) {
|
|
449
|
+
case "created":
|
|
450
|
+
return fallback;
|
|
451
|
+
case "queued":
|
|
452
|
+
return "queued";
|
|
453
|
+
case "preparing":
|
|
454
|
+
case "running":
|
|
455
|
+
case "waiting-approval":
|
|
456
|
+
case "waiting-user-input":
|
|
457
|
+
case "validating":
|
|
458
|
+
case "paused":
|
|
459
|
+
return "in_progress";
|
|
460
|
+
case "reviewing":
|
|
461
|
+
return "under_review";
|
|
462
|
+
case "completed":
|
|
463
|
+
return "completed";
|
|
464
|
+
case "failed":
|
|
465
|
+
return "ready";
|
|
466
|
+
case "cancelled":
|
|
467
|
+
return "cancelled";
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
function withSnapshotMetadata(snapshot, event, next) {
|
|
471
|
+
return {
|
|
472
|
+
...snapshot,
|
|
473
|
+
...next,
|
|
474
|
+
snapshotSequence: Math.max(snapshot.snapshotSequence, event.sequence),
|
|
475
|
+
updatedAt: maxIsoDate(snapshot.updatedAt, event.createdAt)
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
function ensureConversation(snapshot, run) {
|
|
479
|
+
const conversationId = conversationIdFromRunId(run.id);
|
|
480
|
+
if (snapshot.conversations.some((conversation) => conversation.id === conversationId)) {
|
|
481
|
+
return snapshot;
|
|
482
|
+
}
|
|
483
|
+
return {
|
|
484
|
+
...snapshot,
|
|
485
|
+
conversations: [
|
|
486
|
+
...snapshot.conversations,
|
|
487
|
+
{
|
|
488
|
+
id: conversationId,
|
|
489
|
+
runId: run.id,
|
|
490
|
+
title: run.title,
|
|
491
|
+
createdAt: run.createdAt,
|
|
492
|
+
updatedAt: run.updatedAt
|
|
493
|
+
}
|
|
494
|
+
]
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
function updateTaskFromRun(snapshot, run) {
|
|
498
|
+
if (!run.taskId) {
|
|
499
|
+
return snapshot;
|
|
500
|
+
}
|
|
501
|
+
return {
|
|
502
|
+
...snapshot,
|
|
503
|
+
tasks: patchById(snapshot.tasks, run.taskId, (task) => ({
|
|
504
|
+
...task,
|
|
505
|
+
status: mapTaskStatusFromRunStatus(run.status, task.status),
|
|
506
|
+
updatedAt: maxIsoDate(task.updatedAt, run.updatedAt)
|
|
507
|
+
}))
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
function applyRun(snapshot, run) {
|
|
511
|
+
const withConversation = ensureConversation(snapshot, run);
|
|
512
|
+
const nextRuns = upsertById(withConversation.runs, run);
|
|
513
|
+
return updateTaskFromRun({ ...withConversation, runs: nextRuns }, run);
|
|
514
|
+
}
|
|
515
|
+
function applyRuntime(snapshot, runtime) {
|
|
516
|
+
return {
|
|
517
|
+
...snapshot,
|
|
518
|
+
runtimes: upsertById(snapshot.runtimes, runtime)
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
function applyWorktree(snapshot, worktree) {
|
|
522
|
+
return {
|
|
523
|
+
...snapshot,
|
|
524
|
+
worktrees: upsertById(snapshot.worktrees, worktree)
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
function countPendingApprovals(approvals, runId) {
|
|
528
|
+
return approvals.filter((approval) => approval.runId === runId && approval.status === "pending").length;
|
|
529
|
+
}
|
|
530
|
+
function countPendingUserInputs(userInputs, actions, runId) {
|
|
531
|
+
const persistedCount = (userInputs ?? []).filter((request) => request.runId === runId && request.status === "pending").length;
|
|
532
|
+
if (persistedCount > 0) {
|
|
533
|
+
return persistedCount;
|
|
534
|
+
}
|
|
535
|
+
const openRequestIds = new Set;
|
|
536
|
+
for (const action of actions) {
|
|
537
|
+
if (action.runId !== runId || !isRecord(action.payload)) {
|
|
538
|
+
continue;
|
|
539
|
+
}
|
|
540
|
+
const requestId = readString(action.payload, "requestId");
|
|
541
|
+
if (!requestId) {
|
|
542
|
+
continue;
|
|
543
|
+
}
|
|
544
|
+
if (action.actionType === "user-input.requested") {
|
|
545
|
+
openRequestIds.add(requestId);
|
|
546
|
+
}
|
|
547
|
+
if (action.actionType === "user-input.resolved") {
|
|
548
|
+
openRequestIds.delete(requestId);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
return openRequestIds.size;
|
|
552
|
+
}
|
|
553
|
+
function reconcileRunCounts(snapshot, runId, statusOverride) {
|
|
554
|
+
return {
|
|
555
|
+
...snapshot,
|
|
556
|
+
runs: patchById(snapshot.runs, runId, (run) => {
|
|
557
|
+
const pendingApprovalCount = countPendingApprovals(snapshot.approvals, runId);
|
|
558
|
+
const pendingUserInputCount = countPendingUserInputs(snapshot.userInputs, snapshot.actions, runId);
|
|
559
|
+
const nextStatus = statusOverride ?? (pendingApprovalCount > 0 ? "waiting-approval" : pendingUserInputCount > 0 ? "waiting-user-input" : run.status === "waiting-approval" || run.status === "waiting-user-input" ? "running" : run.status);
|
|
560
|
+
return {
|
|
561
|
+
...run,
|
|
562
|
+
pendingApprovalCount,
|
|
563
|
+
pendingUserInputCount,
|
|
564
|
+
status: nextStatus
|
|
565
|
+
};
|
|
566
|
+
})
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
function applyApprovalActivity(approvals, runId, action) {
|
|
570
|
+
if (!isRecord(action.payload)) {
|
|
571
|
+
return approvals.slice();
|
|
572
|
+
}
|
|
573
|
+
const requestId = readString(action.payload, "requestId");
|
|
574
|
+
if (!requestId) {
|
|
575
|
+
return approvals.slice();
|
|
576
|
+
}
|
|
577
|
+
if (action.actionType === "approval.requested") {
|
|
578
|
+
const requestKind = readString(action.payload, "requestKind") ?? "command";
|
|
579
|
+
return upsertById(approvals, {
|
|
580
|
+
id: requestId,
|
|
581
|
+
runId,
|
|
582
|
+
actionId: action.id,
|
|
583
|
+
requestKind,
|
|
584
|
+
status: "pending",
|
|
585
|
+
payload: action.payload,
|
|
586
|
+
createdAt: action.startedAt,
|
|
587
|
+
resolvedAt: null
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
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"));
|
|
591
|
+
if (!shouldResolve) {
|
|
592
|
+
return approvals.slice();
|
|
593
|
+
}
|
|
594
|
+
return approvals.map((approval) => approval.id === requestId ? {
|
|
595
|
+
...approval,
|
|
596
|
+
status: "resolved",
|
|
597
|
+
resolvedAt: action.completedAt ?? action.startedAt
|
|
598
|
+
} : approval);
|
|
599
|
+
}
|
|
600
|
+
function makeRuntimeFromRun(run, occurredAt) {
|
|
601
|
+
return {
|
|
602
|
+
id: run.activeRuntimeId ?? runtimeIdFromRunId(run.id),
|
|
603
|
+
workspaceId: run.workspaceId,
|
|
604
|
+
runId: run.id,
|
|
605
|
+
adapterKind: run.runtimeAdapter,
|
|
606
|
+
executionTarget: run.executionTarget ?? "local",
|
|
607
|
+
remoteHostId: run.remoteHostId ?? null,
|
|
608
|
+
status: run.status === "failed" ? "failed" : run.status === "paused" ? "interrupted" : "starting",
|
|
609
|
+
sandboxMode: "danger-full-access",
|
|
610
|
+
isolationMode: run.worktreePath ? "worktree" : "env",
|
|
611
|
+
workspaceDir: run.worktreePath,
|
|
612
|
+
homeDir: null,
|
|
613
|
+
tmpDir: null,
|
|
614
|
+
cacheDir: null,
|
|
615
|
+
logsDir: null,
|
|
616
|
+
stateDir: null,
|
|
617
|
+
sessionDir: null,
|
|
618
|
+
sessionLogPath: null,
|
|
619
|
+
pid: null,
|
|
620
|
+
startedAt: run.startedAt ?? occurredAt,
|
|
621
|
+
updatedAt: occurredAt,
|
|
622
|
+
exitedAt: run.completedAt
|
|
623
|
+
};
|
|
624
|
+
}
|
|
625
|
+
function applySyntheticRuntimePreparation(snapshot, event) {
|
|
626
|
+
if (!isRecord(event.payload)) {
|
|
627
|
+
return { status: "ignored", snapshot };
|
|
628
|
+
}
|
|
629
|
+
const runId = readString(event.payload, "runId") ?? event.aggregateId;
|
|
630
|
+
const workspaceId = readString(event.payload, "workspaceId");
|
|
631
|
+
const taskId = readString(event.payload, "taskId") ?? null;
|
|
632
|
+
const workspaceDir = readNullableString(event.payload, "workspaceDir");
|
|
633
|
+
const homeDir = readNullableString(event.payload, "homeDir");
|
|
634
|
+
const tmpDir = readNullableString(event.payload, "tmpDir");
|
|
635
|
+
const cacheDir = readNullableString(event.payload, "cacheDir");
|
|
636
|
+
const branchName = readString(event.payload, "taskExternalId") ?? snapshot.runs.find((run) => run.id === runId)?.branch ?? "task";
|
|
637
|
+
const failed = event.type.endsWith(".failed");
|
|
638
|
+
const finished = event.type.endsWith(".finished");
|
|
639
|
+
const existingRun = snapshot.runs.find((run) => run.id === runId);
|
|
640
|
+
if (!existingRun || !workspaceId) {
|
|
641
|
+
return { status: "ignored", snapshot };
|
|
642
|
+
}
|
|
643
|
+
const runtimeRunId = asRunId(runId);
|
|
644
|
+
const nextTaskId = taskId ? asTaskId(taskId) : null;
|
|
645
|
+
const nextWorkspaceId = asWorkspaceId(workspaceId);
|
|
646
|
+
const nextRun = {
|
|
647
|
+
...existingRun,
|
|
648
|
+
taskId: existingRun.taskId ?? nextTaskId,
|
|
649
|
+
runKind: existingRun.runKind === "adhoc" && nextTaskId ? "task" : existingRun.runKind,
|
|
650
|
+
runtimeAdapter: "rig-native",
|
|
651
|
+
initialPrompt: existingRun.initialPrompt ?? null,
|
|
652
|
+
executionTarget: existingRun.executionTarget ?? "local",
|
|
653
|
+
remoteHostId: existingRun.remoteHostId ?? null,
|
|
654
|
+
activeRuntimeId: existingRun.activeRuntimeId ?? runtimeIdFromRunId(runtimeRunId),
|
|
655
|
+
worktreePath: workspaceDir ?? existingRun.worktreePath,
|
|
656
|
+
status: failed ? "failed" : finished ? "preparing" : "preparing",
|
|
657
|
+
errorText: failed ? readString(event.payload, "message") ?? existingRun.errorText : existingRun.errorText,
|
|
658
|
+
updatedAt: event.createdAt,
|
|
659
|
+
completedAt: failed ? event.createdAt : existingRun.completedAt
|
|
660
|
+
};
|
|
661
|
+
const runtimeBase = snapshot.runtimes.find((runtime) => runtime.runId === runId) ?? makeRuntimeFromRun(nextRun, event.createdAt);
|
|
662
|
+
const nextRuntime = {
|
|
663
|
+
...runtimeBase,
|
|
664
|
+
workspaceId: nextWorkspaceId,
|
|
665
|
+
runId: runtimeRunId,
|
|
666
|
+
adapterKind: "rig-native",
|
|
667
|
+
executionTarget: existingRun.executionTarget ?? "local",
|
|
668
|
+
remoteHostId: existingRun.remoteHostId ?? null,
|
|
669
|
+
status: failed ? "failed" : finished ? "prepared" : "starting",
|
|
670
|
+
isolationMode: "worktree",
|
|
671
|
+
workspaceDir: workspaceDir ?? runtimeBase.workspaceDir,
|
|
672
|
+
homeDir: homeDir ?? runtimeBase.homeDir,
|
|
673
|
+
tmpDir: tmpDir ?? runtimeBase.tmpDir,
|
|
674
|
+
cacheDir: cacheDir ?? runtimeBase.cacheDir,
|
|
675
|
+
updatedAt: event.createdAt,
|
|
676
|
+
exitedAt: failed ? event.createdAt : runtimeBase.exitedAt
|
|
677
|
+
};
|
|
678
|
+
let nextSnapshot = applyRun(snapshot, nextRun);
|
|
679
|
+
nextSnapshot = applyRuntime(nextSnapshot, nextRuntime);
|
|
680
|
+
if (workspaceDir) {
|
|
681
|
+
nextSnapshot = applyWorktree(nextSnapshot, {
|
|
682
|
+
id: worktreeIdFromRunId(runtimeRunId),
|
|
683
|
+
workspaceId: nextWorkspaceId,
|
|
684
|
+
runId: runtimeRunId,
|
|
685
|
+
taskId: nextRun.taskId,
|
|
686
|
+
branchName,
|
|
687
|
+
path: workspaceDir,
|
|
688
|
+
status: failed ? "failed" : finished ? "prepared" : "preparing",
|
|
689
|
+
createdAt: existingRun.createdAt,
|
|
690
|
+
cleanedAt: null
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
return {
|
|
694
|
+
status: "applied",
|
|
695
|
+
snapshot: withSnapshotMetadata(nextSnapshot, event, {})
|
|
696
|
+
};
|
|
697
|
+
}
|
|
698
|
+
function applySyntheticRuntimePrepared(snapshot, event) {
|
|
699
|
+
if (!isRecord(event.payload)) {
|
|
700
|
+
return { status: "ignored", snapshot };
|
|
701
|
+
}
|
|
702
|
+
const runId = readString(event.payload, "runId") ?? event.aggregateId;
|
|
703
|
+
const workspaceId = readString(event.payload, "workspaceId");
|
|
704
|
+
const worktreePath = readString(event.payload, "worktreePath");
|
|
705
|
+
const existingRun = snapshot.runs.find((run) => run.id === runId);
|
|
706
|
+
if (!existingRun || !workspaceId || !worktreePath) {
|
|
707
|
+
return { status: "ignored", snapshot };
|
|
708
|
+
}
|
|
709
|
+
const runtimeRunId = asRunId(runId);
|
|
710
|
+
const nextWorkspaceId = asWorkspaceId(workspaceId);
|
|
711
|
+
let nextSnapshot = applyRuntime(snapshot, {
|
|
712
|
+
...snapshot.runtimes.find((runtime) => runtime.runId === runId) ?? makeRuntimeFromRun(existingRun, event.createdAt),
|
|
713
|
+
workspaceId: nextWorkspaceId,
|
|
714
|
+
runId: runtimeRunId,
|
|
715
|
+
adapterKind: "rig-native",
|
|
716
|
+
executionTarget: existingRun.executionTarget ?? "local",
|
|
717
|
+
remoteHostId: existingRun.remoteHostId ?? null,
|
|
718
|
+
status: "prepared",
|
|
719
|
+
isolationMode: "worktree",
|
|
720
|
+
workspaceDir: worktreePath,
|
|
721
|
+
updatedAt: event.createdAt
|
|
722
|
+
});
|
|
723
|
+
nextSnapshot = applyWorktree(nextSnapshot, {
|
|
724
|
+
id: worktreeIdFromRunId(runtimeRunId),
|
|
725
|
+
workspaceId: nextWorkspaceId,
|
|
726
|
+
runId: runtimeRunId,
|
|
727
|
+
taskId: existingRun.taskId,
|
|
728
|
+
branchName: existingRun.branch ?? "task",
|
|
729
|
+
path: worktreePath,
|
|
730
|
+
status: "prepared",
|
|
731
|
+
createdAt: existingRun.createdAt,
|
|
732
|
+
cleanedAt: null
|
|
733
|
+
});
|
|
734
|
+
return {
|
|
735
|
+
status: "applied",
|
|
736
|
+
snapshot: withSnapshotMetadata(nextSnapshot, event, {})
|
|
737
|
+
};
|
|
738
|
+
}
|
|
739
|
+
function applyLegacyProjectEvent(snapshot, event) {
|
|
740
|
+
if (!isRecord(event.payload)) {
|
|
741
|
+
return { status: "ignored", snapshot };
|
|
742
|
+
}
|
|
743
|
+
const payload = event.payload;
|
|
744
|
+
if (event.type === "legacy.project.created") {
|
|
745
|
+
const workspaceId = readString(payload, "projectId");
|
|
746
|
+
const title = readString(payload, "title");
|
|
747
|
+
const rootPath = readString(payload, "workspaceRoot");
|
|
748
|
+
const createdAt = readString(payload, "createdAt");
|
|
749
|
+
const updatedAt = readString(payload, "updatedAt");
|
|
750
|
+
if (!workspaceId || !title || !rootPath || !createdAt || !updatedAt) {
|
|
751
|
+
return { status: "ignored", snapshot };
|
|
752
|
+
}
|
|
753
|
+
const workspace = {
|
|
754
|
+
id: asWorkspaceId(workspaceId),
|
|
755
|
+
title,
|
|
756
|
+
rootPath,
|
|
757
|
+
sourceKind: "native",
|
|
758
|
+
defaultRuntimeAdapter: "codex-app-server",
|
|
759
|
+
defaultModel: readNullableString(payload, "defaultModel") ?? null,
|
|
760
|
+
createdAt,
|
|
761
|
+
updatedAt
|
|
762
|
+
};
|
|
763
|
+
return {
|
|
764
|
+
status: "applied",
|
|
765
|
+
snapshot: withSnapshotMetadata(snapshot, event, {
|
|
766
|
+
workspaces: upsertById(snapshot.workspaces, workspace)
|
|
767
|
+
})
|
|
768
|
+
};
|
|
769
|
+
}
|
|
770
|
+
if (event.type === "legacy.project.meta-updated") {
|
|
771
|
+
const workspaceId = readString(payload, "projectId");
|
|
772
|
+
if (!workspaceId) {
|
|
773
|
+
return { status: "ignored", snapshot };
|
|
774
|
+
}
|
|
775
|
+
return {
|
|
776
|
+
status: "applied",
|
|
777
|
+
snapshot: withSnapshotMetadata(snapshot, event, {
|
|
778
|
+
workspaces: patchById(snapshot.workspaces, workspaceId, (workspace) => ({
|
|
779
|
+
...workspace,
|
|
780
|
+
title: readString(payload, "title") ?? workspace.title,
|
|
781
|
+
rootPath: readString(payload, "workspaceRoot") ?? workspace.rootPath,
|
|
782
|
+
defaultModel: readNullableString(payload, "defaultModel") ?? workspace.defaultModel,
|
|
783
|
+
updatedAt: readString(payload, "updatedAt") ?? event.createdAt
|
|
784
|
+
}))
|
|
785
|
+
})
|
|
786
|
+
};
|
|
787
|
+
}
|
|
788
|
+
if (event.type === "legacy.project.deleted") {
|
|
789
|
+
const workspaceId = readString(payload, "projectId");
|
|
790
|
+
if (!workspaceId) {
|
|
791
|
+
return { status: "ignored", snapshot };
|
|
792
|
+
}
|
|
793
|
+
return {
|
|
794
|
+
status: "applied",
|
|
795
|
+
snapshot: withSnapshotMetadata(snapshot, event, {
|
|
796
|
+
workspaces: removeById(snapshot.workspaces, workspaceId),
|
|
797
|
+
graphs: snapshot.graphs.filter((graph) => graph.workspaceId !== workspaceId),
|
|
798
|
+
tasks: snapshot.tasks.filter((task) => task.workspaceId !== workspaceId),
|
|
799
|
+
runs: snapshot.runs.filter((run) => run.workspaceId !== workspaceId),
|
|
800
|
+
runtimes: snapshot.runtimes.filter((runtime) => runtime.workspaceId !== workspaceId),
|
|
801
|
+
conversations: snapshot.conversations.filter((conversation) => {
|
|
802
|
+
const run = snapshot.runs.find((candidate) => candidate.id === conversation.runId);
|
|
803
|
+
return run?.workspaceId !== workspaceId;
|
|
804
|
+
}),
|
|
805
|
+
messages: snapshot.messages.filter((message) => {
|
|
806
|
+
const conversation = snapshot.conversations.find((candidate) => candidate.id === message.conversationId);
|
|
807
|
+
const run = snapshot.runs.find((candidate) => candidate.id === conversation?.runId);
|
|
808
|
+
return run?.workspaceId !== workspaceId;
|
|
809
|
+
}),
|
|
810
|
+
actions: snapshot.actions.filter((action) => {
|
|
811
|
+
const run = snapshot.runs.find((candidate) => candidate.id === action.runId);
|
|
812
|
+
return run?.workspaceId !== workspaceId;
|
|
813
|
+
}),
|
|
814
|
+
approvals: snapshot.approvals.filter((approval) => {
|
|
815
|
+
const run = snapshot.runs.find((candidate) => candidate.id === approval.runId);
|
|
816
|
+
return run?.workspaceId !== workspaceId;
|
|
817
|
+
}),
|
|
818
|
+
queue: snapshot.queue.filter((entry) => {
|
|
819
|
+
const task = snapshot.tasks.find((candidate) => candidate.id === entry.taskId);
|
|
820
|
+
return task?.workspaceId !== workspaceId;
|
|
821
|
+
}),
|
|
822
|
+
worktrees: snapshot.worktrees.filter((worktree) => worktree.workspaceId !== workspaceId)
|
|
823
|
+
})
|
|
824
|
+
};
|
|
825
|
+
}
|
|
826
|
+
return { status: "ignored", snapshot };
|
|
827
|
+
}
|
|
828
|
+
function applyLegacyThreadEvent(snapshot, event) {
|
|
829
|
+
if (!isRecord(event.payload)) {
|
|
830
|
+
return { status: "ignored", snapshot };
|
|
831
|
+
}
|
|
832
|
+
const payload = event.payload;
|
|
833
|
+
if (event.type === "legacy.thread.created") {
|
|
834
|
+
const runId = readString(payload, "threadId");
|
|
835
|
+
const workspaceId = readString(payload, "projectId");
|
|
836
|
+
const title = readString(payload, "title");
|
|
837
|
+
const model = readString(payload, "model");
|
|
838
|
+
const createdAt = readString(payload, "createdAt");
|
|
839
|
+
const updatedAt = readString(payload, "updatedAt");
|
|
840
|
+
if (!runId || !workspaceId || !title || !model || !createdAt || !updatedAt) {
|
|
841
|
+
return { status: "ignored", snapshot };
|
|
842
|
+
}
|
|
843
|
+
const run = {
|
|
844
|
+
id: asRunId(runId),
|
|
845
|
+
workspaceId: asWorkspaceId(workspaceId),
|
|
846
|
+
taskId: null,
|
|
847
|
+
title,
|
|
848
|
+
runKind: "adhoc",
|
|
849
|
+
mode: toRunMode(readString(payload, "interactionMode")),
|
|
850
|
+
runtimeMode: "full-access",
|
|
851
|
+
interactionMode: "default",
|
|
852
|
+
status: "created",
|
|
853
|
+
runtimeAdapter: "codex-app-server",
|
|
854
|
+
model,
|
|
855
|
+
initialPrompt: null,
|
|
856
|
+
activeRuntimeId: null,
|
|
857
|
+
latestMessageId: null,
|
|
858
|
+
pendingApprovalCount: 0,
|
|
859
|
+
pendingUserInputCount: 0,
|
|
860
|
+
branch: readNullableString(payload, "branch") ?? null,
|
|
861
|
+
worktreePath: readNullableString(payload, "worktreePath") ?? null,
|
|
862
|
+
errorText: null,
|
|
863
|
+
createdAt,
|
|
864
|
+
updatedAt,
|
|
865
|
+
startedAt: null,
|
|
866
|
+
completedAt: null
|
|
867
|
+
};
|
|
868
|
+
let nextSnapshot = applyRun(snapshot, run);
|
|
869
|
+
if (run.branch && run.worktreePath) {
|
|
870
|
+
nextSnapshot = applyWorktree(nextSnapshot, {
|
|
871
|
+
id: worktreeIdFromRunId(asRunId(runId)),
|
|
872
|
+
workspaceId: asWorkspaceId(workspaceId),
|
|
873
|
+
runId: asRunId(runId),
|
|
874
|
+
taskId: null,
|
|
875
|
+
branchName: run.branch,
|
|
876
|
+
path: run.worktreePath,
|
|
877
|
+
status: "active",
|
|
878
|
+
createdAt,
|
|
879
|
+
cleanedAt: null
|
|
880
|
+
});
|
|
881
|
+
}
|
|
882
|
+
return {
|
|
883
|
+
status: "applied",
|
|
884
|
+
snapshot: withSnapshotMetadata(nextSnapshot, event, {})
|
|
885
|
+
};
|
|
886
|
+
}
|
|
887
|
+
if (event.type === "legacy.thread.deleted") {
|
|
888
|
+
const runId = readString(payload, "threadId");
|
|
889
|
+
if (!runId) {
|
|
890
|
+
return { status: "ignored", snapshot };
|
|
891
|
+
}
|
|
892
|
+
return {
|
|
893
|
+
status: "applied",
|
|
894
|
+
snapshot: withSnapshotMetadata(snapshot, event, {
|
|
895
|
+
runs: removeById(snapshot.runs, runId),
|
|
896
|
+
runtimes: snapshot.runtimes.filter((runtime) => runtime.runId !== runId),
|
|
897
|
+
conversations: snapshot.conversations.filter((conversation) => conversation.runId !== runId),
|
|
898
|
+
messages: snapshot.messages.filter((message) => message.conversationId !== runId),
|
|
899
|
+
actions: snapshot.actions.filter((action) => action.runId !== runId),
|
|
900
|
+
approvals: snapshot.approvals.filter((approval) => approval.runId !== runId),
|
|
901
|
+
worktrees: snapshot.worktrees.filter((worktree) => worktree.runId !== runId)
|
|
902
|
+
})
|
|
903
|
+
};
|
|
904
|
+
}
|
|
905
|
+
if (event.type === "legacy.thread.meta-updated") {
|
|
906
|
+
const runId = readString(payload, "threadId");
|
|
907
|
+
if (!runId) {
|
|
908
|
+
return { status: "ignored", snapshot };
|
|
909
|
+
}
|
|
910
|
+
let nextSnapshot = {
|
|
911
|
+
...snapshot,
|
|
912
|
+
runs: patchById(snapshot.runs, runId, (run2) => ({
|
|
913
|
+
...run2,
|
|
914
|
+
title: readString(payload, "title") ?? run2.title,
|
|
915
|
+
model: readString(payload, "model") ?? run2.model,
|
|
916
|
+
branch: readNullableString(payload, "branch") === undefined ? run2.branch : readNullableString(payload, "branch") ?? null,
|
|
917
|
+
worktreePath: readNullableString(payload, "worktreePath") === undefined ? run2.worktreePath : readNullableString(payload, "worktreePath") ?? null,
|
|
918
|
+
updatedAt: readString(payload, "updatedAt") ?? event.createdAt
|
|
919
|
+
})),
|
|
920
|
+
conversations: patchById(snapshot.conversations, runId, (conversation) => ({
|
|
921
|
+
...conversation,
|
|
922
|
+
title: readString(payload, "title") ?? conversation.title,
|
|
923
|
+
updatedAt: readString(payload, "updatedAt") ?? event.createdAt
|
|
924
|
+
}))
|
|
925
|
+
};
|
|
926
|
+
const run = nextSnapshot.runs.find((candidate) => candidate.id === runId);
|
|
927
|
+
if (run?.worktreePath && run.branch) {
|
|
928
|
+
nextSnapshot = applyWorktree(nextSnapshot, {
|
|
929
|
+
id: worktreeIdFromRunId(run.id),
|
|
930
|
+
workspaceId: run.workspaceId,
|
|
931
|
+
runId: run.id,
|
|
932
|
+
taskId: run.taskId,
|
|
933
|
+
branchName: run.branch,
|
|
934
|
+
path: run.worktreePath,
|
|
935
|
+
status: "active",
|
|
936
|
+
createdAt: run.createdAt,
|
|
937
|
+
cleanedAt: null
|
|
938
|
+
});
|
|
939
|
+
}
|
|
940
|
+
if (run && run.worktreePath === null) {
|
|
941
|
+
nextSnapshot = {
|
|
942
|
+
...nextSnapshot,
|
|
943
|
+
worktrees: nextSnapshot.worktrees.filter((worktree) => worktree.runId !== runId)
|
|
944
|
+
};
|
|
945
|
+
}
|
|
946
|
+
return {
|
|
947
|
+
status: "applied",
|
|
948
|
+
snapshot: withSnapshotMetadata(nextSnapshot, event, {})
|
|
949
|
+
};
|
|
950
|
+
}
|
|
951
|
+
if (event.type === "legacy.thread.interaction-mode-set") {
|
|
952
|
+
const runId = readString(payload, "threadId");
|
|
953
|
+
if (!runId) {
|
|
954
|
+
return { status: "ignored", snapshot };
|
|
955
|
+
}
|
|
956
|
+
return {
|
|
957
|
+
status: "applied",
|
|
958
|
+
snapshot: withSnapshotMetadata(snapshot, event, {
|
|
959
|
+
runs: patchById(snapshot.runs, runId, (run) => ({
|
|
960
|
+
...run,
|
|
961
|
+
mode: toRunMode(readString(payload, "interactionMode")),
|
|
962
|
+
updatedAt: readString(payload, "updatedAt") ?? event.createdAt
|
|
963
|
+
}))
|
|
964
|
+
})
|
|
965
|
+
};
|
|
966
|
+
}
|
|
967
|
+
if (event.type === "legacy.thread.runtime-mode-set") {
|
|
968
|
+
const runId = readString(payload, "threadId");
|
|
969
|
+
if (!runId) {
|
|
970
|
+
return { status: "ignored", snapshot };
|
|
971
|
+
}
|
|
972
|
+
const runtimeMode = readString(payload, "runtimeMode");
|
|
973
|
+
return {
|
|
974
|
+
status: "applied",
|
|
975
|
+
snapshot: withSnapshotMetadata(snapshot, event, {
|
|
976
|
+
runtimes: patchById(snapshot.runtimes, runId, (runtime) => ({
|
|
977
|
+
...runtime,
|
|
978
|
+
sandboxMode: runtimeMode === "approval-required" ? "workspace-write" : "danger-full-access",
|
|
979
|
+
updatedAt: readString(payload, "updatedAt") ?? event.createdAt
|
|
980
|
+
}))
|
|
981
|
+
})
|
|
982
|
+
};
|
|
983
|
+
}
|
|
984
|
+
if (event.type === "legacy.thread.message-sent") {
|
|
985
|
+
const runId = readString(payload, "threadId");
|
|
986
|
+
const messageId = readString(payload, "messageId");
|
|
987
|
+
const role = readString(payload, "role");
|
|
988
|
+
const text = readString(payload, "text");
|
|
989
|
+
const createdAt = readString(payload, "createdAt");
|
|
990
|
+
const updatedAt = readString(payload, "updatedAt");
|
|
991
|
+
if (!runId || !messageId || !role || text === undefined || !createdAt || !updatedAt) {
|
|
992
|
+
return { status: "ignored", snapshot };
|
|
993
|
+
}
|
|
994
|
+
const attachments = Array.isArray(payload.attachments) ? payload.attachments : [];
|
|
995
|
+
const streaming = payload.streaming === true;
|
|
996
|
+
const message = {
|
|
997
|
+
id: asMessageId(messageId),
|
|
998
|
+
conversationId: conversationIdFromRunId(asRunId(runId)),
|
|
999
|
+
role: role === "assistant" || role === "system" ? role : "user",
|
|
1000
|
+
text,
|
|
1001
|
+
attachments,
|
|
1002
|
+
state: streaming ? "streaming" : "completed",
|
|
1003
|
+
createdAt,
|
|
1004
|
+
completedAt: streaming ? null : updatedAt
|
|
1005
|
+
};
|
|
1006
|
+
const existingRun = snapshot.runs.find((run) => run.id === runId);
|
|
1007
|
+
if (!existingRun) {
|
|
1008
|
+
return { status: "ignored", snapshot };
|
|
1009
|
+
}
|
|
1010
|
+
return {
|
|
1011
|
+
status: "applied",
|
|
1012
|
+
snapshot: withSnapshotMetadata(ensureConversation({
|
|
1013
|
+
...snapshot,
|
|
1014
|
+
messages: upsertById(snapshot.messages, message),
|
|
1015
|
+
runs: patchById(snapshot.runs, runId, (run) => ({
|
|
1016
|
+
...run,
|
|
1017
|
+
latestMessageId: asMessageId(messageId),
|
|
1018
|
+
updatedAt
|
|
1019
|
+
}))
|
|
1020
|
+
}, existingRun), event, {})
|
|
1021
|
+
};
|
|
1022
|
+
}
|
|
1023
|
+
if (event.type === "legacy.thread.session-set") {
|
|
1024
|
+
const runId = readString(event.payload, "threadId");
|
|
1025
|
+
const session = readRecord(event.payload, "session");
|
|
1026
|
+
if (!runId || !session) {
|
|
1027
|
+
return { status: "ignored", snapshot };
|
|
1028
|
+
}
|
|
1029
|
+
const existingRun = snapshot.runs.find((run) => run.id === runId);
|
|
1030
|
+
if (!existingRun) {
|
|
1031
|
+
return { status: "ignored", snapshot };
|
|
1032
|
+
}
|
|
1033
|
+
const sessionUpdatedAt = readString(session, "updatedAt") ?? event.createdAt;
|
|
1034
|
+
const providerName = readNullableString(session, "providerName");
|
|
1035
|
+
const nextRun = {
|
|
1036
|
+
...existingRun,
|
|
1037
|
+
runtimeAdapter: providerName ?? existingRun.runtimeAdapter,
|
|
1038
|
+
initialPrompt: existingRun.initialPrompt ?? null,
|
|
1039
|
+
activeRuntimeId: runtimeIdFromRunId(asRunId(runId)),
|
|
1040
|
+
status: mapLegacySessionStatusToRunStatus(readString(session, "status")),
|
|
1041
|
+
errorText: readNullableString(session, "lastError") ?? existingRun.errorText,
|
|
1042
|
+
updatedAt: sessionUpdatedAt,
|
|
1043
|
+
completedAt: readString(session, "status") === "ready" || readString(session, "status") === "stopped" || readString(session, "status") === "error" ? sessionUpdatedAt : existingRun.completedAt
|
|
1044
|
+
};
|
|
1045
|
+
let nextSnapshot = applyRun(snapshot, nextRun);
|
|
1046
|
+
nextSnapshot = applyRuntime(nextSnapshot, {
|
|
1047
|
+
...snapshot.runtimes.find((runtime) => runtime.runId === runId) ?? makeRuntimeFromRun(nextRun, sessionUpdatedAt),
|
|
1048
|
+
workspaceId: existingRun.workspaceId,
|
|
1049
|
+
runId: asRunId(runId),
|
|
1050
|
+
adapterKind: providerName ?? existingRun.runtimeAdapter,
|
|
1051
|
+
status: mapLegacySessionStatusToRuntimeStatus(readString(session, "status")),
|
|
1052
|
+
workspaceDir: existingRun.worktreePath,
|
|
1053
|
+
isolationMode: existingRun.worktreePath ? "worktree" : "env",
|
|
1054
|
+
updatedAt: sessionUpdatedAt,
|
|
1055
|
+
exitedAt: readString(session, "status") === "ready" || readString(session, "status") === "stopped" || readString(session, "status") === "error" ? sessionUpdatedAt : null
|
|
1056
|
+
});
|
|
1057
|
+
return {
|
|
1058
|
+
status: "applied",
|
|
1059
|
+
snapshot: withSnapshotMetadata(nextSnapshot, event, {})
|
|
1060
|
+
};
|
|
1061
|
+
}
|
|
1062
|
+
if (event.type === "legacy.thread.activity-appended") {
|
|
1063
|
+
const runId = readString(event.payload, "threadId");
|
|
1064
|
+
const activity = readRecord(event.payload, "activity");
|
|
1065
|
+
if (!runId || !activity) {
|
|
1066
|
+
return { status: "ignored", snapshot };
|
|
1067
|
+
}
|
|
1068
|
+
const actionId = readString(activity, "id");
|
|
1069
|
+
const actionType = readString(activity, "kind");
|
|
1070
|
+
const title = readString(activity, "summary");
|
|
1071
|
+
const startedAt = readString(activity, "createdAt");
|
|
1072
|
+
if (!actionId || !actionType || !title || !startedAt) {
|
|
1073
|
+
return { status: "ignored", snapshot };
|
|
1074
|
+
}
|
|
1075
|
+
const payload2 = readRecord(activity, "payload") ?? {};
|
|
1076
|
+
const detail = readString(payload2, "detail") ?? null;
|
|
1077
|
+
const action = {
|
|
1078
|
+
id: asActionId(actionId),
|
|
1079
|
+
runId: asRunId(runId),
|
|
1080
|
+
messageId: null,
|
|
1081
|
+
actionType,
|
|
1082
|
+
title,
|
|
1083
|
+
detail,
|
|
1084
|
+
state: "completed",
|
|
1085
|
+
payload: payload2,
|
|
1086
|
+
startedAt,
|
|
1087
|
+
completedAt: startedAt
|
|
1088
|
+
};
|
|
1089
|
+
let nextSnapshot = {
|
|
1090
|
+
...snapshot,
|
|
1091
|
+
actions: upsertById(snapshot.actions, action),
|
|
1092
|
+
approvals: applyApprovalActivity(snapshot.approvals, asRunId(runId), action),
|
|
1093
|
+
runs: patchById(snapshot.runs, runId, (run) => ({
|
|
1094
|
+
...run,
|
|
1095
|
+
updatedAt: maxIsoDate(run.updatedAt, startedAt),
|
|
1096
|
+
status: actionType === "engine.runtime.ready" ? "running" : actionType === "engine.runtime.failed" ? "failed" : run.status,
|
|
1097
|
+
errorText: actionType === "engine.runtime.failed" && detail ? detail : run.errorText,
|
|
1098
|
+
completedAt: actionType === "engine.runtime.failed" ? startedAt : run.completedAt
|
|
1099
|
+
}))
|
|
1100
|
+
};
|
|
1101
|
+
nextSnapshot = reconcileRunCounts(nextSnapshot, asRunId(runId));
|
|
1102
|
+
return {
|
|
1103
|
+
status: "applied",
|
|
1104
|
+
snapshot: withSnapshotMetadata(nextSnapshot, event, {})
|
|
1105
|
+
};
|
|
1106
|
+
}
|
|
1107
|
+
if (event.type === "legacy.thread.reverted") {
|
|
1108
|
+
return { status: "requires-resync", snapshot };
|
|
1109
|
+
}
|
|
1110
|
+
return { status: "ignored", snapshot };
|
|
1111
|
+
}
|
|
1112
|
+
function applyEngineEvent(snapshot, event) {
|
|
1113
|
+
if (event.sequence <= snapshot.snapshotSequence) {
|
|
1114
|
+
return { status: "ignored", snapshot };
|
|
1115
|
+
}
|
|
1116
|
+
if (event.sequence > snapshot.snapshotSequence + 1) {
|
|
1117
|
+
return { status: "requires-resync", snapshot };
|
|
1118
|
+
}
|
|
1119
|
+
const base = {
|
|
1120
|
+
...snapshot,
|
|
1121
|
+
snapshotSequence: event.sequence,
|
|
1122
|
+
updatedAt: event.createdAt
|
|
1123
|
+
};
|
|
1124
|
+
const payload = event.payload && typeof event.payload === "object" ? event.payload : {};
|
|
1125
|
+
switch (event.type) {
|
|
1126
|
+
case "WorkspaceRegistered":
|
|
1127
|
+
case "RigWorkspaceImported": {
|
|
1128
|
+
const workspace = {
|
|
1129
|
+
id: payload.workspaceId,
|
|
1130
|
+
title: payload.title,
|
|
1131
|
+
rootPath: payload.rootPath,
|
|
1132
|
+
sourceKind: payload.sourceKind,
|
|
1133
|
+
defaultModel: payload.defaultModel ?? null,
|
|
1134
|
+
topology: undefined,
|
|
1135
|
+
remoteFleet: undefined,
|
|
1136
|
+
serviceFabric: undefined,
|
|
1137
|
+
createdAt: payload.createdAt,
|
|
1138
|
+
updatedAt: payload.createdAt
|
|
1139
|
+
};
|
|
1140
|
+
return {
|
|
1141
|
+
status: "applied",
|
|
1142
|
+
snapshot: {
|
|
1143
|
+
...base,
|
|
1144
|
+
workspaces: upsertById(base.workspaces, workspace)
|
|
1145
|
+
}
|
|
1146
|
+
};
|
|
1147
|
+
}
|
|
1148
|
+
case "RigStateHydrated": {
|
|
1149
|
+
const workspaceRunIds = new Set([
|
|
1150
|
+
...base.runs.filter((run) => run.workspaceId === payload.workspaceId).map((run) => run.id),
|
|
1151
|
+
...(payload.runs ?? []).map((run) => run.id)
|
|
1152
|
+
]);
|
|
1153
|
+
const withoutWorkspaceRunData = (items) => items.filter((item) => !workspaceRunIds.has(item.runId));
|
|
1154
|
+
const workspaces = payload.rootPath ? patchById(base.workspaces, payload.workspaceId, (workspace) => ({
|
|
1155
|
+
...workspace,
|
|
1156
|
+
rootPath: payload.rootPath,
|
|
1157
|
+
updatedAt: payload.createdAt
|
|
1158
|
+
})) : base.workspaces;
|
|
1159
|
+
return {
|
|
1160
|
+
status: "applied",
|
|
1161
|
+
snapshot: {
|
|
1162
|
+
...base,
|
|
1163
|
+
workspaces,
|
|
1164
|
+
graphs: upsertById(base.graphs, payload.graph),
|
|
1165
|
+
tasks: replaceWorkspaceSlice(base.tasks, payload.workspaceId, payload.tasks ?? []),
|
|
1166
|
+
runs: replaceWorkspaceSlice(base.runs, payload.workspaceId, payload.runs ?? []),
|
|
1167
|
+
runtimes: replaceWorkspaceSlice(base.runtimes, payload.workspaceId, payload.runtimes ?? []),
|
|
1168
|
+
actions: mergeById(withoutWorkspaceRunData(base.actions), payload.actions ?? []),
|
|
1169
|
+
logs: mergeById(withoutWorkspaceRunData(base.logs), payload.logs ?? []),
|
|
1170
|
+
userInputs: (base.userInputs ?? []).filter((request) => !workspaceRunIds.has(request.runId)),
|
|
1171
|
+
validations: mergeById(withoutWorkspaceRunData(base.validations), payload.validations ?? []),
|
|
1172
|
+
reviews: mergeById(withoutWorkspaceRunData(base.reviews), payload.reviews ?? []),
|
|
1173
|
+
artifacts: mergeById(withoutWorkspaceRunData(base.artifacts), payload.artifacts ?? []),
|
|
1174
|
+
policyDecisions: mergeById(withoutWorkspaceRunData(base.policyDecisions), payload.policyDecisions ?? []),
|
|
1175
|
+
queue: [...payload.queue ?? []]
|
|
1176
|
+
}
|
|
1177
|
+
};
|
|
1178
|
+
}
|
|
1179
|
+
case "WorkspaceTopologyCompiled": {
|
|
1180
|
+
return {
|
|
1181
|
+
status: "applied",
|
|
1182
|
+
snapshot: {
|
|
1183
|
+
...base,
|
|
1184
|
+
workspaces: patchById(base.workspaces, payload.workspaceId, (workspace) => ({
|
|
1185
|
+
...workspace,
|
|
1186
|
+
rootPath: payload.rootPath ?? workspace.rootPath,
|
|
1187
|
+
topology: payload.topology,
|
|
1188
|
+
updatedAt: payload.createdAt
|
|
1189
|
+
}))
|
|
1190
|
+
}
|
|
1191
|
+
};
|
|
1192
|
+
}
|
|
1193
|
+
case "WorkspaceRemoteFleetSynced": {
|
|
1194
|
+
return {
|
|
1195
|
+
status: "applied",
|
|
1196
|
+
snapshot: {
|
|
1197
|
+
...base,
|
|
1198
|
+
workspaces: patchById(base.workspaces, payload.workspaceId, (workspace) => ({
|
|
1199
|
+
...workspace,
|
|
1200
|
+
rootPath: payload.rootPath ?? workspace.rootPath,
|
|
1201
|
+
remoteFleet: payload.remoteFleet,
|
|
1202
|
+
updatedAt: payload.createdAt
|
|
1203
|
+
}))
|
|
1204
|
+
}
|
|
1205
|
+
};
|
|
1206
|
+
}
|
|
1207
|
+
case "WorkspaceServiceFabricSynced": {
|
|
1208
|
+
return {
|
|
1209
|
+
status: "applied",
|
|
1210
|
+
snapshot: {
|
|
1211
|
+
...base,
|
|
1212
|
+
workspaces: patchById(base.workspaces, payload.workspaceId, (workspace) => ({
|
|
1213
|
+
...workspace,
|
|
1214
|
+
rootPath: payload.rootPath ?? workspace.rootPath,
|
|
1215
|
+
serviceFabric: payload.serviceFabric,
|
|
1216
|
+
updatedAt: payload.createdAt
|
|
1217
|
+
}))
|
|
1218
|
+
}
|
|
1219
|
+
};
|
|
1220
|
+
}
|
|
1221
|
+
case "WorkspaceRemoteHostRegistered": {
|
|
1222
|
+
return {
|
|
1223
|
+
status: "applied",
|
|
1224
|
+
snapshot: {
|
|
1225
|
+
...base,
|
|
1226
|
+
workspaces: patchById(base.workspaces, payload.workspaceId, (workspace) => {
|
|
1227
|
+
const withoutExisting = (workspace.remoteFleet?.hosts ?? []).filter((host) => host.id !== payload.host.id);
|
|
1228
|
+
return {
|
|
1229
|
+
...workspace,
|
|
1230
|
+
remoteFleet: buildRemoteFleetSummary({
|
|
1231
|
+
fleet: workspace.remoteFleet,
|
|
1232
|
+
hosts: [...withoutExisting, payload.host],
|
|
1233
|
+
updatedAt: payload.createdAt
|
|
1234
|
+
}),
|
|
1235
|
+
updatedAt: payload.createdAt
|
|
1236
|
+
};
|
|
1237
|
+
})
|
|
1238
|
+
}
|
|
1239
|
+
};
|
|
1240
|
+
}
|
|
1241
|
+
case "WorkspaceRemoteHostStatusUpdated": {
|
|
1242
|
+
return {
|
|
1243
|
+
status: "applied",
|
|
1244
|
+
snapshot: {
|
|
1245
|
+
...base,
|
|
1246
|
+
workspaces: patchById(base.workspaces, payload.workspaceId, (workspace) => {
|
|
1247
|
+
const fleet = workspace.remoteFleet;
|
|
1248
|
+
if (!fleet) {
|
|
1249
|
+
return {
|
|
1250
|
+
...workspace,
|
|
1251
|
+
updatedAt: payload.createdAt
|
|
1252
|
+
};
|
|
1253
|
+
}
|
|
1254
|
+
const hosts = fleet.hosts.map((host) => host.id !== payload.hostId ? host : {
|
|
1255
|
+
...host,
|
|
1256
|
+
status: payload.status,
|
|
1257
|
+
currentLeaseCount: typeof payload.currentLeaseCount === "number" ? payload.currentLeaseCount : host.currentLeaseCount,
|
|
1258
|
+
lastHeartbeatAt: payload.lastHeartbeatAt ?? host.lastHeartbeatAt
|
|
1259
|
+
});
|
|
1260
|
+
return {
|
|
1261
|
+
...workspace,
|
|
1262
|
+
remoteFleet: buildRemoteFleetSummary({
|
|
1263
|
+
fleet,
|
|
1264
|
+
hosts,
|
|
1265
|
+
updatedAt: payload.createdAt
|
|
1266
|
+
}),
|
|
1267
|
+
updatedAt: payload.createdAt
|
|
1268
|
+
};
|
|
1269
|
+
})
|
|
1270
|
+
}
|
|
1271
|
+
};
|
|
1272
|
+
}
|
|
1273
|
+
case "RunCreated": {
|
|
1274
|
+
const run = {
|
|
1275
|
+
id: payload.runId,
|
|
1276
|
+
workspaceId: payload.workspaceId,
|
|
1277
|
+
taskId: payload.taskId ?? null,
|
|
1278
|
+
title: payload.title,
|
|
1279
|
+
runKind: payload.runKind,
|
|
1280
|
+
mode: payload.mode,
|
|
1281
|
+
runtimeMode: payload.runtimeMode,
|
|
1282
|
+
interactionMode: payload.interactionMode,
|
|
1283
|
+
status: "created",
|
|
1284
|
+
runtimeAdapter: payload.runtimeAdapter,
|
|
1285
|
+
model: payload.model ?? null,
|
|
1286
|
+
initialPrompt: payload.initialPrompt ?? null,
|
|
1287
|
+
executionTarget: payload.executionTarget ?? (payload.remoteHostId ? "remote" : "local"),
|
|
1288
|
+
remoteHostId: payload.remoteHostId ?? null,
|
|
1289
|
+
remoteLeaseId: null,
|
|
1290
|
+
remoteLeaseClaimedAt: null,
|
|
1291
|
+
activeRuntimeId: null,
|
|
1292
|
+
latestMessageId: null,
|
|
1293
|
+
pendingApprovalCount: 0,
|
|
1294
|
+
pendingUserInputCount: 0,
|
|
1295
|
+
branch: null,
|
|
1296
|
+
worktreePath: null,
|
|
1297
|
+
errorText: null,
|
|
1298
|
+
createdAt: payload.createdAt,
|
|
1299
|
+
updatedAt: payload.createdAt,
|
|
1300
|
+
startedAt: null,
|
|
1301
|
+
completedAt: null
|
|
1302
|
+
};
|
|
1303
|
+
return {
|
|
1304
|
+
status: "applied",
|
|
1305
|
+
snapshot: { ...base, runs: upsertById(base.runs, run) }
|
|
1306
|
+
};
|
|
1307
|
+
}
|
|
1308
|
+
case "RunInterrupted":
|
|
1309
|
+
case "RunCancelled":
|
|
1310
|
+
return {
|
|
1311
|
+
status: "applied",
|
|
1312
|
+
snapshot: {
|
|
1313
|
+
...base,
|
|
1314
|
+
runs: patchById(base.runs, payload.runId, (run) => ({
|
|
1315
|
+
...run,
|
|
1316
|
+
status: "cancelled",
|
|
1317
|
+
updatedAt: payload.createdAt,
|
|
1318
|
+
completedAt: payload.createdAt
|
|
1319
|
+
}))
|
|
1320
|
+
}
|
|
1321
|
+
};
|
|
1322
|
+
case "RunCompleted":
|
|
1323
|
+
return {
|
|
1324
|
+
status: "applied",
|
|
1325
|
+
snapshot: {
|
|
1326
|
+
...base,
|
|
1327
|
+
runs: patchById(base.runs, payload.runId, (run) => ({
|
|
1328
|
+
...run,
|
|
1329
|
+
status: "completed",
|
|
1330
|
+
updatedAt: payload.createdAt,
|
|
1331
|
+
completedAt: payload.createdAt
|
|
1332
|
+
}))
|
|
1333
|
+
}
|
|
1334
|
+
};
|
|
1335
|
+
case "RunStatusSet":
|
|
1336
|
+
return {
|
|
1337
|
+
status: "applied",
|
|
1338
|
+
snapshot: {
|
|
1339
|
+
...base,
|
|
1340
|
+
runs: patchById(base.runs, payload.runId, (run) => ({
|
|
1341
|
+
...run,
|
|
1342
|
+
status: payload.status,
|
|
1343
|
+
updatedAt: payload.createdAt,
|
|
1344
|
+
completedAt: null
|
|
1345
|
+
}))
|
|
1346
|
+
}
|
|
1347
|
+
};
|
|
1348
|
+
case "RunFailed":
|
|
1349
|
+
return {
|
|
1350
|
+
status: "applied",
|
|
1351
|
+
snapshot: {
|
|
1352
|
+
...base,
|
|
1353
|
+
runs: patchById(base.runs, payload.runId, (run) => ({
|
|
1354
|
+
...run,
|
|
1355
|
+
status: "failed",
|
|
1356
|
+
errorText: payload.errorText ?? null,
|
|
1357
|
+
updatedAt: payload.createdAt,
|
|
1358
|
+
completedAt: payload.createdAt
|
|
1359
|
+
}))
|
|
1360
|
+
}
|
|
1361
|
+
};
|
|
1362
|
+
case "RuntimeModeSet":
|
|
1363
|
+
return {
|
|
1364
|
+
status: "applied",
|
|
1365
|
+
snapshot: {
|
|
1366
|
+
...base,
|
|
1367
|
+
runs: patchById(base.runs, payload.runId, (run) => ({
|
|
1368
|
+
...run,
|
|
1369
|
+
runtimeMode: payload.runtimeMode,
|
|
1370
|
+
updatedAt: payload.createdAt
|
|
1371
|
+
}))
|
|
1372
|
+
}
|
|
1373
|
+
};
|
|
1374
|
+
case "InteractionModeSet":
|
|
1375
|
+
return {
|
|
1376
|
+
status: "applied",
|
|
1377
|
+
snapshot: {
|
|
1378
|
+
...base,
|
|
1379
|
+
runs: patchById(base.runs, payload.runId, (run) => ({
|
|
1380
|
+
...run,
|
|
1381
|
+
interactionMode: payload.interactionMode,
|
|
1382
|
+
updatedAt: payload.createdAt
|
|
1383
|
+
}))
|
|
1384
|
+
}
|
|
1385
|
+
};
|
|
1386
|
+
case "ConversationAttached": {
|
|
1387
|
+
const conversation = {
|
|
1388
|
+
id: payload.conversationId,
|
|
1389
|
+
runId: payload.runId,
|
|
1390
|
+
title: payload.title,
|
|
1391
|
+
createdAt: payload.createdAt,
|
|
1392
|
+
updatedAt: payload.createdAt
|
|
1393
|
+
};
|
|
1394
|
+
return {
|
|
1395
|
+
status: "applied",
|
|
1396
|
+
snapshot: {
|
|
1397
|
+
...base,
|
|
1398
|
+
conversations: upsertById(base.conversations, conversation)
|
|
1399
|
+
}
|
|
1400
|
+
};
|
|
1401
|
+
}
|
|
1402
|
+
case "MessageAppended": {
|
|
1403
|
+
const conversation = base.conversations.find((item) => item.runId === payload.runId);
|
|
1404
|
+
if (!conversation) {
|
|
1405
|
+
return { status: "ignored", snapshot: base };
|
|
1406
|
+
}
|
|
1407
|
+
const conversationId = conversation.id;
|
|
1408
|
+
const message = {
|
|
1409
|
+
id: payload.messageId,
|
|
1410
|
+
conversationId,
|
|
1411
|
+
role: payload.role,
|
|
1412
|
+
text: payload.text,
|
|
1413
|
+
attachments: payload.attachments ?? [],
|
|
1414
|
+
state: payload.state ?? "completed",
|
|
1415
|
+
createdAt: payload.createdAt,
|
|
1416
|
+
completedAt: payload.completedAt ?? payload.createdAt
|
|
1417
|
+
};
|
|
1418
|
+
return {
|
|
1419
|
+
status: "applied",
|
|
1420
|
+
snapshot: {
|
|
1421
|
+
...base,
|
|
1422
|
+
messages: upsertById(base.messages, message),
|
|
1423
|
+
runs: patchById(base.runs, payload.runId, (run) => ({
|
|
1424
|
+
...run,
|
|
1425
|
+
latestMessageId: payload.messageId,
|
|
1426
|
+
updatedAt: payload.createdAt
|
|
1427
|
+
}))
|
|
1428
|
+
}
|
|
1429
|
+
};
|
|
1430
|
+
}
|
|
1431
|
+
case "ActionStarted": {
|
|
1432
|
+
const action = {
|
|
1433
|
+
id: payload.actionId,
|
|
1434
|
+
runId: payload.runId,
|
|
1435
|
+
messageId: payload.messageId ?? null,
|
|
1436
|
+
actionType: payload.actionType,
|
|
1437
|
+
title: payload.title,
|
|
1438
|
+
detail: payload.detail ?? null,
|
|
1439
|
+
state: payload.state ?? "running",
|
|
1440
|
+
payload: payload.payload ?? null,
|
|
1441
|
+
startedAt: payload.startedAt,
|
|
1442
|
+
completedAt: null
|
|
1443
|
+
};
|
|
1444
|
+
return {
|
|
1445
|
+
status: "applied",
|
|
1446
|
+
snapshot: {
|
|
1447
|
+
...base,
|
|
1448
|
+
actions: upsertById(base.actions, action)
|
|
1449
|
+
}
|
|
1450
|
+
};
|
|
1451
|
+
}
|
|
1452
|
+
case "ActionCompleted":
|
|
1453
|
+
return {
|
|
1454
|
+
status: "applied",
|
|
1455
|
+
snapshot: {
|
|
1456
|
+
...base,
|
|
1457
|
+
actions: patchById(base.actions, payload.actionId, (action) => ({
|
|
1458
|
+
...action,
|
|
1459
|
+
state: payload.state,
|
|
1460
|
+
detail: payload.detail ?? null,
|
|
1461
|
+
payload: payload.payload,
|
|
1462
|
+
completedAt: payload.completedAt
|
|
1463
|
+
}))
|
|
1464
|
+
}
|
|
1465
|
+
};
|
|
1466
|
+
case "RunLogAppended": {
|
|
1467
|
+
const log = {
|
|
1468
|
+
id: payload.logId,
|
|
1469
|
+
runId: payload.runId,
|
|
1470
|
+
title: payload.title,
|
|
1471
|
+
detail: payload.detail ?? null,
|
|
1472
|
+
tone: payload.tone,
|
|
1473
|
+
status: payload.status ?? null,
|
|
1474
|
+
payload: payload.payload ?? null,
|
|
1475
|
+
createdAt: payload.createdAt
|
|
1476
|
+
};
|
|
1477
|
+
return {
|
|
1478
|
+
status: "applied",
|
|
1479
|
+
snapshot: {
|
|
1480
|
+
...base,
|
|
1481
|
+
logs: upsertById(base.logs, log),
|
|
1482
|
+
runs: patchById(base.runs, payload.runId, (run) => ({
|
|
1483
|
+
...run,
|
|
1484
|
+
updatedAt: payload.createdAt
|
|
1485
|
+
}))
|
|
1486
|
+
}
|
|
1487
|
+
};
|
|
1488
|
+
}
|
|
1489
|
+
case "ValidationRecorded":
|
|
1490
|
+
return {
|
|
1491
|
+
status: "applied",
|
|
1492
|
+
snapshot: {
|
|
1493
|
+
...base,
|
|
1494
|
+
validations: upsertById(base.validations, {
|
|
1495
|
+
id: payload.id,
|
|
1496
|
+
runId: payload.runId,
|
|
1497
|
+
taskId: payload.taskId ?? null,
|
|
1498
|
+
validatorKey: payload.validatorKey,
|
|
1499
|
+
status: payload.status,
|
|
1500
|
+
output: payload.output,
|
|
1501
|
+
startedAt: payload.startedAt,
|
|
1502
|
+
completedAt: payload.completedAt ?? null
|
|
1503
|
+
})
|
|
1504
|
+
}
|
|
1505
|
+
};
|
|
1506
|
+
case "ReviewRecorded":
|
|
1507
|
+
return {
|
|
1508
|
+
status: "applied",
|
|
1509
|
+
snapshot: {
|
|
1510
|
+
...base,
|
|
1511
|
+
reviews: upsertById(base.reviews, {
|
|
1512
|
+
id: payload.id,
|
|
1513
|
+
runId: payload.runId,
|
|
1514
|
+
taskId: payload.taskId ?? null,
|
|
1515
|
+
provider: payload.provider,
|
|
1516
|
+
mode: payload.mode,
|
|
1517
|
+
status: payload.status,
|
|
1518
|
+
summary: payload.summary ?? null,
|
|
1519
|
+
output: payload.output,
|
|
1520
|
+
createdAt: payload.createdAt,
|
|
1521
|
+
completedAt: payload.completedAt ?? null
|
|
1522
|
+
})
|
|
1523
|
+
}
|
|
1524
|
+
};
|
|
1525
|
+
case "ArtifactRegistered":
|
|
1526
|
+
return {
|
|
1527
|
+
status: "applied",
|
|
1528
|
+
snapshot: {
|
|
1529
|
+
...base,
|
|
1530
|
+
artifacts: upsertById(base.artifacts, {
|
|
1531
|
+
id: payload.id,
|
|
1532
|
+
runId: payload.runId,
|
|
1533
|
+
taskId: payload.taskId ?? null,
|
|
1534
|
+
kind: payload.kind,
|
|
1535
|
+
label: payload.label,
|
|
1536
|
+
path: payload.path ?? null,
|
|
1537
|
+
url: payload.url ?? null,
|
|
1538
|
+
metadata: payload.metadata ?? {},
|
|
1539
|
+
createdAt: payload.createdAt
|
|
1540
|
+
})
|
|
1541
|
+
}
|
|
1542
|
+
};
|
|
1543
|
+
case "ApprovalRequested": {
|
|
1544
|
+
const run = base.runs.find((item) => item.id === payload.runId);
|
|
1545
|
+
const approval = {
|
|
1546
|
+
id: payload.requestId,
|
|
1547
|
+
runId: payload.runId,
|
|
1548
|
+
actionId: payload.actionId ?? null,
|
|
1549
|
+
requestKind: payload.requestKind,
|
|
1550
|
+
status: "pending",
|
|
1551
|
+
payload: payload.payload ?? null,
|
|
1552
|
+
createdAt: payload.createdAt,
|
|
1553
|
+
resolvedAt: null
|
|
1554
|
+
};
|
|
1555
|
+
return {
|
|
1556
|
+
status: "applied",
|
|
1557
|
+
snapshot: {
|
|
1558
|
+
...base,
|
|
1559
|
+
approvals: upsertById(base.approvals, approval),
|
|
1560
|
+
runs: patchById(base.runs, payload.runId, (item) => ({
|
|
1561
|
+
...item,
|
|
1562
|
+
pendingApprovalCount: (run?.pendingApprovalCount ?? 0) + 1,
|
|
1563
|
+
status: "waiting-approval",
|
|
1564
|
+
updatedAt: payload.createdAt
|
|
1565
|
+
}))
|
|
1566
|
+
}
|
|
1567
|
+
};
|
|
1568
|
+
}
|
|
1569
|
+
case "ApprovalResolved": {
|
|
1570
|
+
const approval = base.approvals.find((item) => item.id === payload.requestId && item.runId === payload.runId);
|
|
1571
|
+
if (!approval) {
|
|
1572
|
+
return { status: "applied", snapshot: base };
|
|
1573
|
+
}
|
|
1574
|
+
const run = base.runs.find((item) => item.id === payload.runId);
|
|
1575
|
+
const pendingApprovalCount = run ? Math.max(0, Number(run.pendingApprovalCount) - 1) : 0;
|
|
1576
|
+
const nextStatus = run?.status === "waiting-approval" && pendingApprovalCount === 0 ? "running" : run?.status;
|
|
1577
|
+
return {
|
|
1578
|
+
status: "applied",
|
|
1579
|
+
snapshot: {
|
|
1580
|
+
...base,
|
|
1581
|
+
approvals: patchById(base.approvals, payload.requestId, (item) => ({
|
|
1582
|
+
...item,
|
|
1583
|
+
status: "resolved",
|
|
1584
|
+
resolvedAt: payload.createdAt
|
|
1585
|
+
})),
|
|
1586
|
+
runs: patchById(base.runs, payload.runId, (item) => ({
|
|
1587
|
+
...item,
|
|
1588
|
+
pendingApprovalCount,
|
|
1589
|
+
...nextStatus ? { status: nextStatus } : {},
|
|
1590
|
+
updatedAt: payload.createdAt
|
|
1591
|
+
}))
|
|
1592
|
+
}
|
|
1593
|
+
};
|
|
1594
|
+
}
|
|
1595
|
+
case "UserInputRequested": {
|
|
1596
|
+
const run = base.runs.find((item) => item.id === payload.runId);
|
|
1597
|
+
return {
|
|
1598
|
+
status: "applied",
|
|
1599
|
+
snapshot: {
|
|
1600
|
+
...base,
|
|
1601
|
+
userInputs: upsertById(base.userInputs ?? [], {
|
|
1602
|
+
id: payload.requestId,
|
|
1603
|
+
runId: payload.runId,
|
|
1604
|
+
status: "pending",
|
|
1605
|
+
payload: payload.payload,
|
|
1606
|
+
createdAt: payload.createdAt,
|
|
1607
|
+
resolvedAt: null
|
|
1608
|
+
}),
|
|
1609
|
+
runs: patchById(base.runs, payload.runId, (item) => ({
|
|
1610
|
+
...item,
|
|
1611
|
+
pendingUserInputCount: (run?.pendingUserInputCount ?? 0) + 1,
|
|
1612
|
+
status: "waiting-user-input",
|
|
1613
|
+
updatedAt: payload.createdAt
|
|
1614
|
+
}))
|
|
1615
|
+
}
|
|
1616
|
+
};
|
|
1617
|
+
}
|
|
1618
|
+
case "UserInputResolved": {
|
|
1619
|
+
const run = base.runs.find((item) => item.id === payload.runId);
|
|
1620
|
+
const pendingUserInputCount = run ? Math.max(0, Number(run.pendingUserInputCount) - 1) : 0;
|
|
1621
|
+
const nextStatus = run?.status === "waiting-user-input" && pendingUserInputCount === 0 ? "running" : run?.status;
|
|
1622
|
+
return {
|
|
1623
|
+
status: "applied",
|
|
1624
|
+
snapshot: {
|
|
1625
|
+
...base,
|
|
1626
|
+
userInputs: patchById(base.userInputs ?? [], payload.requestId, (item) => ({
|
|
1627
|
+
...item,
|
|
1628
|
+
status: "resolved",
|
|
1629
|
+
resolvedAt: payload.createdAt
|
|
1630
|
+
})),
|
|
1631
|
+
runs: patchById(base.runs, payload.runId, (item) => ({
|
|
1632
|
+
...item,
|
|
1633
|
+
pendingUserInputCount,
|
|
1634
|
+
...nextStatus ? { status: nextStatus } : {},
|
|
1635
|
+
updatedAt: payload.createdAt
|
|
1636
|
+
}))
|
|
1637
|
+
}
|
|
1638
|
+
};
|
|
1639
|
+
}
|
|
1640
|
+
case "RuntimePrepared": {
|
|
1641
|
+
const runtime = {
|
|
1642
|
+
id: payload.runtimeId,
|
|
1643
|
+
workspaceId: payload.workspaceId,
|
|
1644
|
+
runId: payload.runId,
|
|
1645
|
+
adapterKind: payload.adapter,
|
|
1646
|
+
executionTarget: payload.executionTarget ?? "local",
|
|
1647
|
+
remoteHostId: payload.remoteHostId ?? null,
|
|
1648
|
+
status: "prepared",
|
|
1649
|
+
sandboxMode: "read-only",
|
|
1650
|
+
isolationMode: "none",
|
|
1651
|
+
workspaceDir: null,
|
|
1652
|
+
homeDir: null,
|
|
1653
|
+
tmpDir: null,
|
|
1654
|
+
cacheDir: null,
|
|
1655
|
+
logsDir: null,
|
|
1656
|
+
stateDir: null,
|
|
1657
|
+
sessionDir: null,
|
|
1658
|
+
sessionLogPath: null,
|
|
1659
|
+
pid: null,
|
|
1660
|
+
startedAt: null,
|
|
1661
|
+
updatedAt: payload.createdAt,
|
|
1662
|
+
exitedAt: null
|
|
1663
|
+
};
|
|
1664
|
+
return {
|
|
1665
|
+
status: "applied",
|
|
1666
|
+
snapshot: {
|
|
1667
|
+
...base,
|
|
1668
|
+
runtimes: upsertById(base.runtimes, runtime),
|
|
1669
|
+
runs: patchById(base.runs, payload.runId, (run) => ({
|
|
1670
|
+
...run,
|
|
1671
|
+
status: "preparing",
|
|
1672
|
+
executionTarget: payload.executionTarget ?? run.executionTarget ?? "local",
|
|
1673
|
+
remoteHostId: payload.remoteHostId ?? run.remoteHostId ?? null,
|
|
1674
|
+
updatedAt: payload.createdAt
|
|
1675
|
+
}))
|
|
1676
|
+
}
|
|
1677
|
+
};
|
|
1678
|
+
}
|
|
1679
|
+
case "RunRemoteLeaseClaimed":
|
|
1680
|
+
return {
|
|
1681
|
+
status: "applied",
|
|
1682
|
+
snapshot: {
|
|
1683
|
+
...base,
|
|
1684
|
+
runs: patchById(base.runs, payload.runId, (run) => ({
|
|
1685
|
+
...run,
|
|
1686
|
+
remoteHostId: payload.hostId,
|
|
1687
|
+
remoteLeaseId: payload.leaseId,
|
|
1688
|
+
remoteLeaseClaimedAt: payload.createdAt,
|
|
1689
|
+
updatedAt: payload.createdAt
|
|
1690
|
+
}))
|
|
1691
|
+
}
|
|
1692
|
+
};
|
|
1693
|
+
case "RunRemoteLeaseReleased":
|
|
1694
|
+
return {
|
|
1695
|
+
status: "applied",
|
|
1696
|
+
snapshot: {
|
|
1697
|
+
...base,
|
|
1698
|
+
runs: patchById(base.runs, payload.runId, (run) => ({
|
|
1699
|
+
...run,
|
|
1700
|
+
remoteLeaseId: null,
|
|
1701
|
+
remoteLeaseClaimedAt: null,
|
|
1702
|
+
updatedAt: payload.createdAt
|
|
1703
|
+
}))
|
|
1704
|
+
}
|
|
1705
|
+
};
|
|
1706
|
+
case "RuntimeAttached": {
|
|
1707
|
+
const tasks = payload.taskId == null ? base.tasks : patchById(base.tasks, payload.taskId, (task) => ({
|
|
1708
|
+
...task,
|
|
1709
|
+
status: "running",
|
|
1710
|
+
updatedAt: payload.createdAt
|
|
1711
|
+
}));
|
|
1712
|
+
return {
|
|
1713
|
+
status: "applied",
|
|
1714
|
+
snapshot: {
|
|
1715
|
+
...base,
|
|
1716
|
+
runs: patchById(base.runs, payload.runId, (run) => ({
|
|
1717
|
+
...run,
|
|
1718
|
+
activeRuntimeId: payload.runtimeId,
|
|
1719
|
+
status: "running",
|
|
1720
|
+
startedAt: payload.createdAt,
|
|
1721
|
+
updatedAt: payload.createdAt
|
|
1722
|
+
})),
|
|
1723
|
+
runtimes: patchById(base.runtimes, payload.runtimeId, (runtime) => ({
|
|
1724
|
+
...runtime,
|
|
1725
|
+
status: "running",
|
|
1726
|
+
startedAt: payload.createdAt,
|
|
1727
|
+
updatedAt: payload.createdAt
|
|
1728
|
+
})),
|
|
1729
|
+
tasks
|
|
1730
|
+
}
|
|
1731
|
+
};
|
|
1732
|
+
}
|
|
1733
|
+
case "RuntimeDetached":
|
|
1734
|
+
return {
|
|
1735
|
+
status: "applied",
|
|
1736
|
+
snapshot: {
|
|
1737
|
+
...base,
|
|
1738
|
+
runs: patchById(base.runs, payload.runId, (run) => ({
|
|
1739
|
+
...run,
|
|
1740
|
+
activeRuntimeId: null,
|
|
1741
|
+
updatedAt: payload.createdAt
|
|
1742
|
+
})),
|
|
1743
|
+
runtimes: patchById(base.runtimes, payload.runtimeId, (runtime) => ({
|
|
1744
|
+
...runtime,
|
|
1745
|
+
status: "exited",
|
|
1746
|
+
exitedAt: payload.createdAt,
|
|
1747
|
+
updatedAt: payload.createdAt
|
|
1748
|
+
}))
|
|
1749
|
+
}
|
|
1750
|
+
};
|
|
1751
|
+
case "RuntimeMetadataUpdated":
|
|
1752
|
+
return {
|
|
1753
|
+
status: "applied",
|
|
1754
|
+
snapshot: {
|
|
1755
|
+
...base,
|
|
1756
|
+
runs: patchById(base.runs, payload.runId, (run) => ({
|
|
1757
|
+
...run,
|
|
1758
|
+
...payload.branch !== undefined ? { branch: payload.branch } : {},
|
|
1759
|
+
...payload.worktreePath !== undefined ? { worktreePath: payload.worktreePath } : {},
|
|
1760
|
+
updatedAt: payload.createdAt
|
|
1761
|
+
})),
|
|
1762
|
+
runtimes: patchById(base.runtimes, payload.runtimeId, (runtime) => ({
|
|
1763
|
+
...runtime,
|
|
1764
|
+
...payload.sandboxMode !== undefined ? { sandboxMode: payload.sandboxMode } : {},
|
|
1765
|
+
...payload.isolationMode !== undefined ? { isolationMode: payload.isolationMode } : {},
|
|
1766
|
+
...payload.workspaceDir !== undefined ? { workspaceDir: payload.workspaceDir } : {},
|
|
1767
|
+
...payload.homeDir !== undefined ? { homeDir: payload.homeDir } : {},
|
|
1768
|
+
...payload.tmpDir !== undefined ? { tmpDir: payload.tmpDir } : {},
|
|
1769
|
+
...payload.cacheDir !== undefined ? { cacheDir: payload.cacheDir } : {},
|
|
1770
|
+
...payload.logsDir !== undefined ? { logsDir: payload.logsDir } : {},
|
|
1771
|
+
...payload.stateDir !== undefined ? { stateDir: payload.stateDir } : {},
|
|
1772
|
+
...payload.sessionDir !== undefined ? { sessionDir: payload.sessionDir } : {},
|
|
1773
|
+
...payload.sessionLogPath !== undefined ? { sessionLogPath: payload.sessionLogPath } : {},
|
|
1774
|
+
...payload.pid !== undefined ? { pid: payload.pid } : {},
|
|
1775
|
+
updatedAt: payload.createdAt
|
|
1776
|
+
}))
|
|
1777
|
+
}
|
|
1778
|
+
};
|
|
1779
|
+
case "TaskStatusChanged": {
|
|
1780
|
+
const queue = payload.status === "queued" ? base.queue : withQueuePositions(base.queue.filter((item) => item.taskId !== payload.taskId));
|
|
1781
|
+
return {
|
|
1782
|
+
status: "applied",
|
|
1783
|
+
snapshot: {
|
|
1784
|
+
...base,
|
|
1785
|
+
tasks: patchById(base.tasks, payload.taskId, (task) => ({
|
|
1786
|
+
...task,
|
|
1787
|
+
status: payload.status,
|
|
1788
|
+
updatedAt: payload.createdAt
|
|
1789
|
+
})),
|
|
1790
|
+
queue
|
|
1791
|
+
}
|
|
1792
|
+
};
|
|
1793
|
+
}
|
|
1794
|
+
case "TaskEnqueued": {
|
|
1795
|
+
const entry = {
|
|
1796
|
+
taskId: payload.taskId,
|
|
1797
|
+
score: payload.score,
|
|
1798
|
+
unblockCount: 0,
|
|
1799
|
+
position: base.queue.length
|
|
1800
|
+
};
|
|
1801
|
+
const positionedQueue = withQueuePositions([
|
|
1802
|
+
...base.queue.filter((item) => item.taskId !== payload.taskId),
|
|
1803
|
+
entry
|
|
1804
|
+
]);
|
|
1805
|
+
return {
|
|
1806
|
+
status: "applied",
|
|
1807
|
+
snapshot: {
|
|
1808
|
+
...base,
|
|
1809
|
+
tasks: patchById(base.tasks, payload.taskId, (task) => ({
|
|
1810
|
+
...task,
|
|
1811
|
+
status: "queued",
|
|
1812
|
+
updatedAt: payload.createdAt
|
|
1813
|
+
})),
|
|
1814
|
+
queue: positionedQueue
|
|
1815
|
+
}
|
|
1816
|
+
};
|
|
1817
|
+
}
|
|
1818
|
+
case "RemoteEndpointRegistered": {
|
|
1819
|
+
const endpoint = payload.endpoint;
|
|
1820
|
+
if (!endpoint || !endpoint.id) {
|
|
1821
|
+
return { status: "ignored", snapshot: base };
|
|
1822
|
+
}
|
|
1823
|
+
return {
|
|
1824
|
+
status: "applied",
|
|
1825
|
+
snapshot: {
|
|
1826
|
+
...base,
|
|
1827
|
+
remoteEndpoints: upsertById(base.remoteEndpoints, endpoint)
|
|
1828
|
+
}
|
|
1829
|
+
};
|
|
1830
|
+
}
|
|
1831
|
+
case "RemoteEndpointRemoved": {
|
|
1832
|
+
const endpointId = payload.endpointId;
|
|
1833
|
+
if (!endpointId) {
|
|
1834
|
+
return { status: "ignored", snapshot: base };
|
|
1835
|
+
}
|
|
1836
|
+
return {
|
|
1837
|
+
status: "applied",
|
|
1838
|
+
snapshot: {
|
|
1839
|
+
...base,
|
|
1840
|
+
remoteEndpoints: removeById(base.remoteEndpoints, endpointId),
|
|
1841
|
+
remoteConnections: base.remoteConnections.filter((conn) => conn.endpointId !== endpointId),
|
|
1842
|
+
remoteOrchestrations: base.remoteOrchestrations.filter((orch) => orch.endpointId !== endpointId)
|
|
1843
|
+
}
|
|
1844
|
+
};
|
|
1845
|
+
}
|
|
1846
|
+
case "RemoteEndpointUpdated": {
|
|
1847
|
+
const endpointId = payload.endpointId;
|
|
1848
|
+
const updates = payload.updates;
|
|
1849
|
+
if (!endpointId || !updates) {
|
|
1850
|
+
return { status: "ignored", snapshot: base };
|
|
1851
|
+
}
|
|
1852
|
+
return {
|
|
1853
|
+
status: "applied",
|
|
1854
|
+
snapshot: {
|
|
1855
|
+
...base,
|
|
1856
|
+
remoteEndpoints: patchById(base.remoteEndpoints, endpointId, (endpoint) => ({
|
|
1857
|
+
...endpoint,
|
|
1858
|
+
...updates
|
|
1859
|
+
}))
|
|
1860
|
+
}
|
|
1861
|
+
};
|
|
1862
|
+
}
|
|
1863
|
+
case "RemoteConnectionChanged": {
|
|
1864
|
+
const endpointId = payload.endpointId;
|
|
1865
|
+
const status = payload.status;
|
|
1866
|
+
if (!endpointId || !status) {
|
|
1867
|
+
return { status: "ignored", snapshot: base };
|
|
1868
|
+
}
|
|
1869
|
+
const existing = base.remoteConnections.find((conn) => conn.endpointId === endpointId);
|
|
1870
|
+
const connection = {
|
|
1871
|
+
...existing ?? {
|
|
1872
|
+
endpointId,
|
|
1873
|
+
status,
|
|
1874
|
+
error: null,
|
|
1875
|
+
connectedAt: null,
|
|
1876
|
+
tokenExpiresAt: null,
|
|
1877
|
+
latencyMs: null,
|
|
1878
|
+
subscribedEvents: []
|
|
1879
|
+
},
|
|
1880
|
+
endpointId,
|
|
1881
|
+
status,
|
|
1882
|
+
...payload.error !== undefined ? { error: payload.error ?? null } : {},
|
|
1883
|
+
...status === "connected" ? { connectedAt: event.createdAt } : {}
|
|
1884
|
+
};
|
|
1885
|
+
return {
|
|
1886
|
+
status: "applied",
|
|
1887
|
+
snapshot: {
|
|
1888
|
+
...base,
|
|
1889
|
+
remoteConnections: upsertByKey(base.remoteConnections, connection, "endpointId")
|
|
1890
|
+
}
|
|
1891
|
+
};
|
|
1892
|
+
}
|
|
1893
|
+
case "RemoteWorkspaceHydrated": {
|
|
1894
|
+
const endpointId = payload.endpointId;
|
|
1895
|
+
const workspace = payload.workspace;
|
|
1896
|
+
const tasks = payload.tasks;
|
|
1897
|
+
const graph = payload.graph;
|
|
1898
|
+
if (!endpointId || !workspace) {
|
|
1899
|
+
return { status: "ignored", snapshot: base };
|
|
1900
|
+
}
|
|
1901
|
+
return {
|
|
1902
|
+
status: "applied",
|
|
1903
|
+
snapshot: {
|
|
1904
|
+
...base,
|
|
1905
|
+
workspaces: upsertById(base.workspaces, workspace),
|
|
1906
|
+
tasks: Array.isArray(tasks) ? replaceWorkspaceSlice(base.tasks, workspace.id, tasks) : base.tasks,
|
|
1907
|
+
graphs: graph ? upsertById(base.graphs, graph) : base.graphs
|
|
1908
|
+
}
|
|
1909
|
+
};
|
|
1910
|
+
}
|
|
1911
|
+
case "RemoteStateRefreshed": {
|
|
1912
|
+
const endpointId = payload.endpointId;
|
|
1913
|
+
const tasks = payload.tasks;
|
|
1914
|
+
if (!endpointId) {
|
|
1915
|
+
return { status: "ignored", snapshot: base };
|
|
1916
|
+
}
|
|
1917
|
+
const remoteWorkspace = base.workspaces.find((ws) => ws.id === `remote-workspace:${endpointId}`);
|
|
1918
|
+
return {
|
|
1919
|
+
status: "applied",
|
|
1920
|
+
snapshot: {
|
|
1921
|
+
...base,
|
|
1922
|
+
tasks: Array.isArray(tasks) && remoteWorkspace ? replaceWorkspaceSlice(base.tasks, remoteWorkspace.id, tasks) : base.tasks
|
|
1923
|
+
}
|
|
1924
|
+
};
|
|
1925
|
+
}
|
|
1926
|
+
case "RemoteEventReceived":
|
|
1927
|
+
return { status: "applied", snapshot: base };
|
|
1928
|
+
case "RemoteOrchestrationStarted": {
|
|
1929
|
+
const orchestration = payload.orchestration;
|
|
1930
|
+
if (!orchestration || !orchestration.orchestrationId) {
|
|
1931
|
+
return { status: "ignored", snapshot: base };
|
|
1932
|
+
}
|
|
1933
|
+
return {
|
|
1934
|
+
status: "applied",
|
|
1935
|
+
snapshot: {
|
|
1936
|
+
...base,
|
|
1937
|
+
remoteOrchestrations: upsertByKey(base.remoteOrchestrations, orchestration, "orchestrationId")
|
|
1938
|
+
}
|
|
1939
|
+
};
|
|
1940
|
+
}
|
|
1941
|
+
case "RemoteOrchestrationUpdated": {
|
|
1942
|
+
const orchestrationId = payload.orchestrationId;
|
|
1943
|
+
const state = payload.state;
|
|
1944
|
+
if (!orchestrationId) {
|
|
1945
|
+
return { status: "ignored", snapshot: base };
|
|
1946
|
+
}
|
|
1947
|
+
return {
|
|
1948
|
+
status: "applied",
|
|
1949
|
+
snapshot: {
|
|
1950
|
+
...base,
|
|
1951
|
+
remoteOrchestrations: patchByKey(base.remoteOrchestrations, orchestrationId, "orchestrationId", (orch) => ({
|
|
1952
|
+
...orch,
|
|
1953
|
+
...isRecord(state) ? state : {}
|
|
1954
|
+
}))
|
|
1955
|
+
}
|
|
1956
|
+
};
|
|
1957
|
+
}
|
|
1958
|
+
}
|
|
1959
|
+
if (event.type === "workspace.imported") {
|
|
1960
|
+
return { status: "requires-resync", snapshot };
|
|
1961
|
+
}
|
|
1962
|
+
if (event.type === "task.run-linked") {
|
|
1963
|
+
const payload2 = isRecord(event.payload) ? event.payload : {};
|
|
1964
|
+
const runId = readString(payload2, "runId");
|
|
1965
|
+
const taskId = readString(payload2, "taskId");
|
|
1966
|
+
const workspaceId = readString(payload2, "workspaceId");
|
|
1967
|
+
if (!runId || !taskId || !workspaceId) {
|
|
1968
|
+
return { status: "ignored", snapshot };
|
|
1969
|
+
}
|
|
1970
|
+
const baseRun = snapshot.runs.find((run) => run.id === runId) ?? {
|
|
1971
|
+
id: asRunId(runId),
|
|
1972
|
+
workspaceId: asWorkspaceId(workspaceId),
|
|
1973
|
+
taskId: asTaskId(taskId),
|
|
1974
|
+
title: "Task run",
|
|
1975
|
+
runKind: "task",
|
|
1976
|
+
mode: "interactive",
|
|
1977
|
+
runtimeMode: "full-access",
|
|
1978
|
+
interactionMode: "default",
|
|
1979
|
+
status: "created",
|
|
1980
|
+
runtimeAdapter: "rig-native",
|
|
1981
|
+
model: null,
|
|
1982
|
+
initialPrompt: null,
|
|
1983
|
+
activeRuntimeId: runtimeIdFromRunId(asRunId(runId)),
|
|
1984
|
+
latestMessageId: null,
|
|
1985
|
+
pendingApprovalCount: 0,
|
|
1986
|
+
pendingUserInputCount: 0,
|
|
1987
|
+
branch: null,
|
|
1988
|
+
worktreePath: null,
|
|
1989
|
+
errorText: null,
|
|
1990
|
+
createdAt: event.createdAt,
|
|
1991
|
+
updatedAt: event.createdAt,
|
|
1992
|
+
startedAt: event.createdAt,
|
|
1993
|
+
completedAt: null
|
|
1994
|
+
};
|
|
1995
|
+
const nextSnapshot = applyRun(snapshot, {
|
|
1996
|
+
...baseRun,
|
|
1997
|
+
workspaceId: asWorkspaceId(workspaceId),
|
|
1998
|
+
taskId: asTaskId(taskId),
|
|
1999
|
+
runKind: "task",
|
|
2000
|
+
runtimeAdapter: "rig-native",
|
|
2001
|
+
activeRuntimeId: runtimeIdFromRunId(asRunId(runId)),
|
|
2002
|
+
updatedAt: event.createdAt
|
|
2003
|
+
});
|
|
2004
|
+
return {
|
|
2005
|
+
status: "applied",
|
|
2006
|
+
snapshot: withSnapshotMetadata(nextSnapshot, event, {})
|
|
2007
|
+
};
|
|
2008
|
+
}
|
|
2009
|
+
if (event.type.startsWith("runtime.prepare.")) {
|
|
2010
|
+
return applySyntheticRuntimePreparation(snapshot, event);
|
|
2011
|
+
}
|
|
2012
|
+
if (event.type === "runtime.prepared") {
|
|
2013
|
+
return applySyntheticRuntimePrepared(snapshot, event);
|
|
2014
|
+
}
|
|
2015
|
+
if (event.type.startsWith("legacy.project.")) {
|
|
2016
|
+
return applyLegacyProjectEvent(snapshot, event);
|
|
2017
|
+
}
|
|
2018
|
+
if (event.type.startsWith("legacy.thread.")) {
|
|
2019
|
+
return applyLegacyThreadEvent(snapshot, event);
|
|
2020
|
+
}
|
|
2021
|
+
return { status: "ignored", snapshot: base };
|
|
2022
|
+
}
|
|
2023
|
+
function applyEngineEvents(snapshot, events) {
|
|
2024
|
+
let nextSnapshot = snapshot;
|
|
2025
|
+
let requiresResync = false;
|
|
2026
|
+
let applied = false;
|
|
2027
|
+
for (const event of events) {
|
|
2028
|
+
const result = applyEngineEvent(nextSnapshot, event);
|
|
2029
|
+
nextSnapshot = result.snapshot;
|
|
2030
|
+
if (result.status === "requires-resync") {
|
|
2031
|
+
requiresResync = true;
|
|
2032
|
+
}
|
|
2033
|
+
if (result.status === "applied") {
|
|
2034
|
+
applied = true;
|
|
2035
|
+
}
|
|
2036
|
+
}
|
|
2037
|
+
return {
|
|
2038
|
+
status: requiresResync ? "requires-resync" : applied ? "applied" : "ignored",
|
|
2039
|
+
snapshot: nextSnapshot
|
|
2040
|
+
};
|
|
2041
|
+
}
|
|
2042
|
+
function pruneQueueEntries(snapshot) {
|
|
2043
|
+
return snapshot.queue.filter((entry) => {
|
|
2044
|
+
const task = snapshot.tasks.find((candidate) => candidate.id === entry.taskId);
|
|
2045
|
+
return task?.status !== "running" && task?.status !== "in_progress" && task?.status !== "under_review";
|
|
2046
|
+
});
|
|
2047
|
+
}
|
|
2048
|
+
// packages/core/src/rigSelectors.ts
|
|
2049
|
+
function selectWorkspaces(snapshot) {
|
|
2050
|
+
return snapshot?.workspaces ?? [];
|
|
2051
|
+
}
|
|
2052
|
+
function selectPrimaryWorkspace(snapshot) {
|
|
2053
|
+
const workspaces = selectWorkspaces(snapshot);
|
|
2054
|
+
return workspaces.find((workspace) => workspace.sourceKind === "rig-import") ?? workspaces[0] ?? null;
|
|
2055
|
+
}
|
|
2056
|
+
function pickDefaultWorkspaceId(snapshot) {
|
|
2057
|
+
return selectPrimaryWorkspace(snapshot)?.id ?? null;
|
|
2058
|
+
}
|
|
2059
|
+
function selectWorkspace(snapshot, workspaceId) {
|
|
2060
|
+
if (!workspaceId)
|
|
2061
|
+
return null;
|
|
2062
|
+
return snapshot?.workspaces.find((workspace) => workspace.id === workspaceId) ?? null;
|
|
2063
|
+
}
|
|
2064
|
+
function selectTask(snapshot, taskId) {
|
|
2065
|
+
if (!taskId)
|
|
2066
|
+
return null;
|
|
2067
|
+
return snapshot?.tasks.find((task) => task.id === taskId) ?? null;
|
|
2068
|
+
}
|
|
2069
|
+
function selectTasksByWorkspace(snapshot, workspaceId) {
|
|
2070
|
+
if (!workspaceId)
|
|
2071
|
+
return [];
|
|
2072
|
+
return snapshot?.tasks.filter((task) => task.workspaceId === workspaceId) ?? [];
|
|
2073
|
+
}
|
|
2074
|
+
var selectTasksForWorkspace = selectTasksByWorkspace;
|
|
2075
|
+
function selectTasksByStatus(snapshot, status) {
|
|
2076
|
+
return snapshot?.tasks.filter((task) => task.status === status) ?? [];
|
|
2077
|
+
}
|
|
2078
|
+
function projectTaskStatusForGrouping(status) {
|
|
2079
|
+
switch (status) {
|
|
2080
|
+
case "failed":
|
|
2081
|
+
return "ready";
|
|
2082
|
+
case "closed":
|
|
2083
|
+
return "completed";
|
|
2084
|
+
case "running":
|
|
2085
|
+
case "in_progress":
|
|
2086
|
+
case "under_review":
|
|
2087
|
+
return "running";
|
|
2088
|
+
default:
|
|
2089
|
+
return status;
|
|
2090
|
+
}
|
|
2091
|
+
}
|
|
2092
|
+
var STATUS_PRIORITY = [
|
|
2093
|
+
"running",
|
|
2094
|
+
"blocked",
|
|
2095
|
+
"queued",
|
|
2096
|
+
"ready",
|
|
2097
|
+
"open",
|
|
2098
|
+
"draft",
|
|
2099
|
+
"unknown",
|
|
2100
|
+
"cancelled",
|
|
2101
|
+
"completed"
|
|
2102
|
+
];
|
|
2103
|
+
function selectTasksGroupedByStatus(snapshot, workspaceId) {
|
|
2104
|
+
const tasks = selectTasksByWorkspace(snapshot, workspaceId);
|
|
2105
|
+
const byStatus = new Map;
|
|
2106
|
+
for (const task of tasks) {
|
|
2107
|
+
const projectedStatus = projectTaskStatusForGrouping(task.status);
|
|
2108
|
+
const group = byStatus.get(projectedStatus);
|
|
2109
|
+
if (group) {
|
|
2110
|
+
group.push(task);
|
|
2111
|
+
} else {
|
|
2112
|
+
byStatus.set(projectedStatus, [task]);
|
|
2113
|
+
}
|
|
2114
|
+
}
|
|
2115
|
+
const result = [];
|
|
2116
|
+
for (const status of STATUS_PRIORITY) {
|
|
2117
|
+
const group = byStatus.get(status);
|
|
2118
|
+
if (group && group.length > 0) {
|
|
2119
|
+
result.push({ status, tasks: group });
|
|
2120
|
+
}
|
|
2121
|
+
}
|
|
2122
|
+
const overflowStatuses = Array.from(byStatus.keys()).filter((status) => !STATUS_PRIORITY.includes(status)).sort();
|
|
2123
|
+
for (const status of overflowStatuses) {
|
|
2124
|
+
const group = byStatus.get(status);
|
|
2125
|
+
if (group && group.length > 0) {
|
|
2126
|
+
result.push({ status, tasks: group });
|
|
2127
|
+
}
|
|
2128
|
+
}
|
|
2129
|
+
return result;
|
|
2130
|
+
}
|
|
2131
|
+
function selectRun(snapshot, runId) {
|
|
2132
|
+
if (!runId)
|
|
2133
|
+
return null;
|
|
2134
|
+
return snapshot?.runs.find((run) => run.id === runId) ?? null;
|
|
2135
|
+
}
|
|
2136
|
+
function selectRunsByTask(snapshot, taskId) {
|
|
2137
|
+
if (!taskId)
|
|
2138
|
+
return [];
|
|
2139
|
+
return snapshot?.runs.filter((run) => run.taskId === taskId) ?? [];
|
|
2140
|
+
}
|
|
2141
|
+
var selectRunsForTask = selectRunsByTask;
|
|
2142
|
+
function selectRunsForWorkspace(snapshot, workspaceId) {
|
|
2143
|
+
if (!workspaceId)
|
|
2144
|
+
return [];
|
|
2145
|
+
return snapshot?.runs.filter((run) => run.workspaceId === workspaceId) ?? [];
|
|
2146
|
+
}
|
|
2147
|
+
function selectAdhocRuns(snapshot) {
|
|
2148
|
+
return snapshot?.runs.filter((run) => run.taskId === null) ?? [];
|
|
2149
|
+
}
|
|
2150
|
+
function selectAdhocRunsForWorkspace(snapshot, workspaceId) {
|
|
2151
|
+
if (!workspaceId)
|
|
2152
|
+
return [];
|
|
2153
|
+
return snapshot?.runs.filter((run) => run.taskId === null && run.workspaceId === workspaceId) ?? [];
|
|
2154
|
+
}
|
|
2155
|
+
function selectGraphsForWorkspace(snapshot, workspaceId) {
|
|
2156
|
+
if (!workspaceId)
|
|
2157
|
+
return [];
|
|
2158
|
+
return snapshot?.graphs.filter((graph) => graph.workspaceId === workspaceId) ?? [];
|
|
2159
|
+
}
|
|
2160
|
+
function selectQueueForWorkspace(snapshot, workspaceId) {
|
|
2161
|
+
if (!snapshot || !workspaceId)
|
|
2162
|
+
return [];
|
|
2163
|
+
const taskIds = new Set(selectTasksByWorkspace(snapshot, workspaceId).map((task) => task.id));
|
|
2164
|
+
return snapshot.queue.filter((entry) => taskIds.has(entry.taskId));
|
|
2165
|
+
}
|
|
2166
|
+
function selectPendingApprovals(snapshot) {
|
|
2167
|
+
return snapshot?.approvals.filter((approval) => approval.status === "pending") ?? [];
|
|
2168
|
+
}
|
|
2169
|
+
function selectApprovalsForRun(snapshot, runId) {
|
|
2170
|
+
if (!runId)
|
|
2171
|
+
return [];
|
|
2172
|
+
return snapshot?.approvals.filter((approval) => approval.runId === runId) ?? [];
|
|
2173
|
+
}
|
|
2174
|
+
function selectApprovalsForWorkspace(snapshot, workspaceId) {
|
|
2175
|
+
if (!snapshot || !workspaceId)
|
|
2176
|
+
return [];
|
|
2177
|
+
const runIds = new Set(selectRunsForWorkspace(snapshot, workspaceId).map((run) => run.id));
|
|
2178
|
+
return snapshot.approvals.filter((approval) => runIds.has(approval.runId));
|
|
2179
|
+
}
|
|
2180
|
+
function selectUserInputsForRun(snapshot, runId) {
|
|
2181
|
+
if (!runId)
|
|
2182
|
+
return [];
|
|
2183
|
+
return (snapshot?.userInputs ?? []).filter((request) => request.runId === runId);
|
|
2184
|
+
}
|
|
2185
|
+
function selectUserInputsForWorkspace(snapshot, workspaceId) {
|
|
2186
|
+
if (!snapshot || !workspaceId)
|
|
2187
|
+
return [];
|
|
2188
|
+
const runIds = new Set(selectRunsForWorkspace(snapshot, workspaceId).map((run) => run.id));
|
|
2189
|
+
return (snapshot.userInputs ?? []).filter((request) => runIds.has(request.runId));
|
|
2190
|
+
}
|
|
2191
|
+
// packages/core/src/taskGraph.ts
|
|
2192
|
+
function isObjectRecord(value) {
|
|
2193
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2194
|
+
}
|
|
2195
|
+
function readTaskMetadataStringList(task, key) {
|
|
2196
|
+
const metadata = isObjectRecord(task.metadata) ? task.metadata : null;
|
|
2197
|
+
const value = metadata?.[key];
|
|
2198
|
+
return Array.isArray(value) ? value.filter((entry) => typeof entry === "string" && entry.length > 0) : [];
|
|
2199
|
+
}
|
|
2200
|
+
function readTaskSourceIssueId(task) {
|
|
2201
|
+
const metadata = isObjectRecord(task.metadata) ? task.metadata : null;
|
|
2202
|
+
return typeof metadata?.sourceIssueId === "string" && metadata.sourceIssueId.length > 0 ? metadata.sourceIssueId : null;
|
|
2203
|
+
}
|
|
2204
|
+
function resolveTaskReference(ref, tasksById, taskIdByExternalRef, taskIdBySourceIssueId) {
|
|
2205
|
+
if (tasksById.has(ref))
|
|
2206
|
+
return ref;
|
|
2207
|
+
return taskIdBySourceIssueId.get(ref) ?? taskIdByExternalRef.get(ref) ?? null;
|
|
2208
|
+
}
|
|
2209
|
+
function buildTaskReferenceIndex(tasks) {
|
|
2210
|
+
return {
|
|
2211
|
+
tasksById: new Map(tasks.map((task) => [task.id, task])),
|
|
2212
|
+
taskIdByExternalRef: new Map(tasks.flatMap((task) => task.externalId ? [[task.externalId, task.id]] : [])),
|
|
2213
|
+
taskIdBySourceIssueId: new Map(tasks.flatMap((task) => {
|
|
2214
|
+
const sourceIssueId = readTaskSourceIssueId(task);
|
|
2215
|
+
return sourceIssueId ? [[sourceIssueId, task.id]] : [];
|
|
2216
|
+
}))
|
|
2217
|
+
};
|
|
2218
|
+
}
|
|
2219
|
+
function computeTaskBlockingDepths(tasks) {
|
|
2220
|
+
const { tasksById, taskIdByExternalRef, taskIdBySourceIssueId } = buildTaskReferenceIndex(tasks);
|
|
2221
|
+
const memo = new Map;
|
|
2222
|
+
const visit = (taskId, stack) => {
|
|
2223
|
+
const cached = memo.get(taskId);
|
|
2224
|
+
if (cached !== undefined)
|
|
2225
|
+
return cached;
|
|
2226
|
+
if (stack.has(taskId))
|
|
2227
|
+
return 0;
|
|
2228
|
+
const task = tasksById.get(taskId);
|
|
2229
|
+
if (!task)
|
|
2230
|
+
return 0;
|
|
2231
|
+
stack.add(taskId);
|
|
2232
|
+
const refs = [
|
|
2233
|
+
...readTaskMetadataStringList(task, "dependencies"),
|
|
2234
|
+
...readTaskMetadataStringList(task, "parentChildDeps")
|
|
2235
|
+
];
|
|
2236
|
+
const blockers = refs.map((ref) => resolveTaskReference(ref, tasksById, taskIdByExternalRef, taskIdBySourceIssueId)).filter((ref) => ref !== null && ref !== taskId);
|
|
2237
|
+
const depth = blockers.length === 0 ? 0 : Math.max(...blockers.map((blockerId) => visit(blockerId, stack) + 1));
|
|
2238
|
+
stack.delete(taskId);
|
|
2239
|
+
memo.set(taskId, depth);
|
|
2240
|
+
return depth;
|
|
2241
|
+
};
|
|
2242
|
+
for (const task of tasks) {
|
|
2243
|
+
visit(task.id, new Set);
|
|
2244
|
+
}
|
|
2245
|
+
return memo;
|
|
2246
|
+
}
|
|
2247
|
+
// packages/core/src/taskGraphCodes.ts
|
|
2248
|
+
var TASK_CODE_RE = /^\[([A-Z0-9]+(?:-[A-Z0-9]+)*)\]\s*/;
|
|
2249
|
+
function extractTaskCode(title) {
|
|
2250
|
+
const match = title.match(TASK_CODE_RE);
|
|
2251
|
+
return match?.[1] ?? null;
|
|
2252
|
+
}
|
|
2253
|
+
function extractTaskGroupKey(title) {
|
|
2254
|
+
const code = extractTaskCode(title);
|
|
2255
|
+
if (!code)
|
|
2256
|
+
return null;
|
|
2257
|
+
const parts = code.split("-");
|
|
2258
|
+
const suffix = parts.at(-1) ?? "";
|
|
2259
|
+
if (/^\d+$/.test(suffix)) {
|
|
2260
|
+
return parts.slice(0, -1).join("-");
|
|
2261
|
+
}
|
|
2262
|
+
return parts[0] ?? code;
|
|
2263
|
+
}
|
|
2264
|
+
function stripTaskCode(label) {
|
|
2265
|
+
return label.replace(TASK_CODE_RE, "");
|
|
2266
|
+
}
|
|
2267
|
+
// packages/core/src/taskGraphLayout.ts
|
|
2268
|
+
var CARD_WIDTH = 200;
|
|
2269
|
+
var CARD_HEIGHT = 110;
|
|
2270
|
+
var CELL_V_PAD = 12;
|
|
2271
|
+
var CELL_H_PAD = 12;
|
|
2272
|
+
var ROW_GAP = 28;
|
|
2273
|
+
var COL_GAP = 40;
|
|
2274
|
+
var LANE_LABEL_W = 120;
|
|
2275
|
+
var STAGE_HDR_H = 32;
|
|
2276
|
+
var PALETTE = [
|
|
2277
|
+
{ bg: "#3a2d12", border: "#8d6b19", edge: "#d6a11d" },
|
|
2278
|
+
{ bg: "#102642", border: "#245fbf", edge: "#66a2ff" },
|
|
2279
|
+
{ bg: "#2c173f", border: "#7b39d4", edge: "#a76df5" },
|
|
2280
|
+
{ bg: "#112d1c", border: "#2d8f4e", edge: "#62d882" },
|
|
2281
|
+
{ bg: "#3a2314", border: "#c86d1c", edge: "#e69654" },
|
|
2282
|
+
{ bg: "#31152b", border: "#bf3d88", edge: "#f07ebb" },
|
|
2283
|
+
{ bg: "#132c35", border: "#1783a6", edge: "#53c4e5" },
|
|
2284
|
+
{ bg: "#26310f", border: "#6d9a19", edge: "#a7da42" }
|
|
2285
|
+
];
|
|
2286
|
+
function isObjectRecord2(value) {
|
|
2287
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2288
|
+
}
|
|
2289
|
+
function readIssueType(task) {
|
|
2290
|
+
const metadata = isObjectRecord2(task.metadata) ? task.metadata : null;
|
|
2291
|
+
return typeof metadata?.issueType === "string" ? metadata.issueType : null;
|
|
2292
|
+
}
|
|
2293
|
+
function isGraphTask(task) {
|
|
2294
|
+
return readIssueType(task) !== "epic";
|
|
2295
|
+
}
|
|
2296
|
+
function findEpicAncestor(task, resolve, tasksById) {
|
|
2297
|
+
const visited = new Set;
|
|
2298
|
+
let current = task;
|
|
2299
|
+
let epic = null;
|
|
2300
|
+
while (!visited.has(current.id)) {
|
|
2301
|
+
visited.add(current.id);
|
|
2302
|
+
const parentRef = readTaskMetadataStringList(current, "parentChildDeps")[0];
|
|
2303
|
+
if (!parentRef)
|
|
2304
|
+
break;
|
|
2305
|
+
const parentId = resolve(parentRef);
|
|
2306
|
+
if (!parentId)
|
|
2307
|
+
break;
|
|
2308
|
+
const parent = tasksById.get(parentId);
|
|
2309
|
+
if (!parent)
|
|
2310
|
+
break;
|
|
2311
|
+
if (readIssueType(parent) === "epic") {
|
|
2312
|
+
epic = parent;
|
|
2313
|
+
}
|
|
2314
|
+
current = parent;
|
|
2315
|
+
}
|
|
2316
|
+
return epic;
|
|
2317
|
+
}
|
|
2318
|
+
function getRowKey(task, resolve, tasksById) {
|
|
2319
|
+
const epic = findEpicAncestor(task, resolve, tasksById);
|
|
2320
|
+
if (epic) {
|
|
2321
|
+
return `group:${epic.id}`;
|
|
2322
|
+
}
|
|
2323
|
+
const codeGroup = extractTaskGroupKey(task.title);
|
|
2324
|
+
if (codeGroup) {
|
|
2325
|
+
return `code:${codeGroup}`;
|
|
2326
|
+
}
|
|
2327
|
+
return `type:${readIssueType(task) ?? "task"}`;
|
|
2328
|
+
}
|
|
2329
|
+
function getRowLabel(task, rowKey, resolve, tasksById) {
|
|
2330
|
+
if (rowKey.startsWith("group:")) {
|
|
2331
|
+
const groupId = rowKey.slice("group:".length);
|
|
2332
|
+
return tasksById.get(groupId)?.title ?? groupId;
|
|
2333
|
+
}
|
|
2334
|
+
if (rowKey.startsWith("code:")) {
|
|
2335
|
+
return rowKey.slice("code:".length);
|
|
2336
|
+
}
|
|
2337
|
+
const code = extractTaskCode(task.title);
|
|
2338
|
+
if (code)
|
|
2339
|
+
return code;
|
|
2340
|
+
const issueType = readIssueType(task);
|
|
2341
|
+
if (issueType === "task")
|
|
2342
|
+
return "Tasks";
|
|
2343
|
+
if (issueType)
|
|
2344
|
+
return `${issueType[0]?.toUpperCase() ?? ""}${issueType.slice(1)}`;
|
|
2345
|
+
const parentRef = readTaskMetadataStringList(task, "parentChildDeps")[0];
|
|
2346
|
+
if (parentRef) {
|
|
2347
|
+
const parentId = resolve(parentRef);
|
|
2348
|
+
if (parentId) {
|
|
2349
|
+
return tasksById.get(parentId)?.title ?? "Tasks";
|
|
2350
|
+
}
|
|
2351
|
+
}
|
|
2352
|
+
return "Tasks";
|
|
2353
|
+
}
|
|
2354
|
+
function computeDepths(ids, edges) {
|
|
2355
|
+
const blockers = new Map;
|
|
2356
|
+
for (const edge of edges) {
|
|
2357
|
+
if (!ids.has(edge.source) || !ids.has(edge.target))
|
|
2358
|
+
continue;
|
|
2359
|
+
const current = blockers.get(edge.target) ?? [];
|
|
2360
|
+
current.push(edge.source);
|
|
2361
|
+
blockers.set(edge.target, current);
|
|
2362
|
+
}
|
|
2363
|
+
const memo = new Map;
|
|
2364
|
+
const visit = (id, stack) => {
|
|
2365
|
+
const cached = memo.get(id);
|
|
2366
|
+
if (cached !== undefined)
|
|
2367
|
+
return cached;
|
|
2368
|
+
if (stack.has(id))
|
|
2369
|
+
return 0;
|
|
2370
|
+
stack.add(id);
|
|
2371
|
+
const deps = blockers.get(id);
|
|
2372
|
+
if (!deps || deps.length === 0) {
|
|
2373
|
+
memo.set(id, 0);
|
|
2374
|
+
return 0;
|
|
2375
|
+
}
|
|
2376
|
+
let maxDepth = 0;
|
|
2377
|
+
for (const dep of deps) {
|
|
2378
|
+
maxDepth = Math.max(maxDepth, visit(dep, stack) + 1);
|
|
2379
|
+
}
|
|
2380
|
+
stack.delete(id);
|
|
2381
|
+
memo.set(id, maxDepth);
|
|
2382
|
+
return maxDepth;
|
|
2383
|
+
};
|
|
2384
|
+
for (const id of ids) {
|
|
2385
|
+
visit(id, new Set);
|
|
2386
|
+
}
|
|
2387
|
+
return memo;
|
|
2388
|
+
}
|
|
2389
|
+
function buildTaskGraphLayout(snapshot, tasks, options) {
|
|
2390
|
+
const showParentChild = options?.showParentChild ?? false;
|
|
2391
|
+
const graphTasks = tasks.filter(isGraphTask);
|
|
2392
|
+
if (graphTasks.length === 0) {
|
|
2393
|
+
return {
|
|
2394
|
+
lanes: [],
|
|
2395
|
+
stages: [],
|
|
2396
|
+
nodes: [],
|
|
2397
|
+
edges: [],
|
|
2398
|
+
totalWidth: 0,
|
|
2399
|
+
totalHeight: 0,
|
|
2400
|
+
taskCount: 0
|
|
2401
|
+
};
|
|
2402
|
+
}
|
|
2403
|
+
const { tasksById, taskIdByExternalRef, taskIdBySourceIssueId } = buildTaskReferenceIndex(tasks);
|
|
2404
|
+
const resolve = (ref) => resolveTaskReference(ref, tasksById, taskIdByExternalRef, taskIdBySourceIssueId);
|
|
2405
|
+
const rows = new Map;
|
|
2406
|
+
const rowLabelByKey = new Map;
|
|
2407
|
+
for (const task of graphTasks) {
|
|
2408
|
+
const rowKey = getRowKey(task, resolve, tasksById);
|
|
2409
|
+
const current = rows.get(rowKey) ?? [];
|
|
2410
|
+
current.push(task);
|
|
2411
|
+
rows.set(rowKey, current);
|
|
2412
|
+
if (!rowLabelByKey.has(rowKey)) {
|
|
2413
|
+
rowLabelByKey.set(rowKey, getRowLabel(task, rowKey, resolve, tasksById));
|
|
2414
|
+
}
|
|
2415
|
+
}
|
|
2416
|
+
const orderedRows = [...rows.entries()].sort((left, right) => {
|
|
2417
|
+
if (left[1].length !== right[1].length)
|
|
2418
|
+
return right[1].length - left[1].length;
|
|
2419
|
+
return (rowLabelByKey.get(left[0]) ?? left[0]).localeCompare(rowLabelByKey.get(right[0]) ?? right[0]);
|
|
2420
|
+
});
|
|
2421
|
+
const rowIndexByKey = new Map(orderedRows.map(([key], index) => [key, index]));
|
|
2422
|
+
const rowIndexByTaskId = new Map;
|
|
2423
|
+
for (const task of graphTasks) {
|
|
2424
|
+
rowIndexByTaskId.set(task.id, rowIndexByKey.get(getRowKey(task, resolve, tasksById)) ?? 0);
|
|
2425
|
+
}
|
|
2426
|
+
const blockingEdgesRaw = [];
|
|
2427
|
+
const depsIn = new Map;
|
|
2428
|
+
const depsOut = new Map;
|
|
2429
|
+
const edges = [];
|
|
2430
|
+
for (const task of graphTasks) {
|
|
2431
|
+
for (const ref of readTaskMetadataStringList(task, "dependencies")) {
|
|
2432
|
+
const sourceId = resolve(ref);
|
|
2433
|
+
if (!sourceId)
|
|
2434
|
+
continue;
|
|
2435
|
+
blockingEdgesRaw.push({ source: sourceId, target: task.id });
|
|
2436
|
+
depsOut.set(sourceId, (depsOut.get(sourceId) ?? 0) + 1);
|
|
2437
|
+
depsIn.set(task.id, (depsIn.get(task.id) ?? 0) + 1);
|
|
2438
|
+
const color = PALETTE[(rowIndexByTaskId.get(sourceId) ?? 0) % PALETTE.length].edge;
|
|
2439
|
+
edges.push({
|
|
2440
|
+
id: `blocking:${sourceId}:${task.id}`,
|
|
2441
|
+
sourceId,
|
|
2442
|
+
targetId: task.id,
|
|
2443
|
+
color,
|
|
2444
|
+
kind: "blocking"
|
|
2445
|
+
});
|
|
2446
|
+
}
|
|
2447
|
+
if (!showParentChild)
|
|
2448
|
+
continue;
|
|
2449
|
+
for (const ref of readTaskMetadataStringList(task, "parentChildDeps")) {
|
|
2450
|
+
const sourceId = resolve(ref);
|
|
2451
|
+
if (!sourceId)
|
|
2452
|
+
continue;
|
|
2453
|
+
edges.push({
|
|
2454
|
+
id: `parent:${sourceId}:${task.id}`,
|
|
2455
|
+
sourceId,
|
|
2456
|
+
targetId: task.id,
|
|
2457
|
+
color: "#5b6472",
|
|
2458
|
+
kind: "parent-child"
|
|
2459
|
+
});
|
|
2460
|
+
}
|
|
2461
|
+
}
|
|
2462
|
+
const graphTaskIds = new Set(graphTasks.map((task) => task.id));
|
|
2463
|
+
const depths = computeDepths(graphTaskIds, blockingEdgesRaw);
|
|
2464
|
+
const maxStage = Math.max(0, ...depths.values());
|
|
2465
|
+
const rowMaxStack = new Array(orderedRows.length).fill(0);
|
|
2466
|
+
const cells = new Map;
|
|
2467
|
+
for (const task of graphTasks) {
|
|
2468
|
+
const rowIndex = rowIndexByTaskId.get(task.id) ?? 0;
|
|
2469
|
+
const stage = depths.get(task.id) ?? 0;
|
|
2470
|
+
const key = `${rowIndex}:${stage}`;
|
|
2471
|
+
const current = cells.get(key) ?? [];
|
|
2472
|
+
current.push(task);
|
|
2473
|
+
cells.set(key, current);
|
|
2474
|
+
}
|
|
2475
|
+
for (const [cellKey, cellTasks] of cells) {
|
|
2476
|
+
const [rowIndexText] = cellKey.split(":");
|
|
2477
|
+
const rowIndex = Number.parseInt(rowIndexText ?? "0", 10) || 0;
|
|
2478
|
+
cellTasks.sort((left, right) => {
|
|
2479
|
+
const leftFanout = depsOut.get(left.id) ?? 0;
|
|
2480
|
+
const rightFanout = depsOut.get(right.id) ?? 0;
|
|
2481
|
+
if (leftFanout !== rightFanout)
|
|
2482
|
+
return rightFanout - leftFanout;
|
|
2483
|
+
return left.title.localeCompare(right.title);
|
|
2484
|
+
});
|
|
2485
|
+
rowMaxStack[rowIndex] = Math.max(rowMaxStack[rowIndex] ?? 0, cellTasks.length);
|
|
2486
|
+
}
|
|
2487
|
+
const rowHeights = rowMaxStack.map((count) => Math.max(count, 1) * (CARD_HEIGHT + CELL_V_PAD) - CELL_V_PAD + CELL_V_PAD * 2);
|
|
2488
|
+
const colWidths = new Array(maxStage + 1).fill(CARD_WIDTH + CELL_H_PAD * 2);
|
|
2489
|
+
const colX = [];
|
|
2490
|
+
let currentX = LANE_LABEL_W;
|
|
2491
|
+
for (let index = 0;index <= maxStage; index += 1) {
|
|
2492
|
+
colX.push(currentX);
|
|
2493
|
+
currentX += (colWidths[index] ?? 0) + COL_GAP;
|
|
2494
|
+
}
|
|
2495
|
+
const totalWidth = currentX - COL_GAP;
|
|
2496
|
+
const rowY = [];
|
|
2497
|
+
let currentY = STAGE_HDR_H;
|
|
2498
|
+
for (let rowIndex = 0;rowIndex < orderedRows.length; rowIndex += 1) {
|
|
2499
|
+
rowY.push(currentY);
|
|
2500
|
+
currentY += (rowHeights[rowIndex] ?? 0) + ROW_GAP;
|
|
2501
|
+
}
|
|
2502
|
+
const totalHeight = currentY - ROW_GAP;
|
|
2503
|
+
const lanes = orderedRows.map(([rowKey, rowTasks], rowIndex) => ({
|
|
2504
|
+
key: rowKey,
|
|
2505
|
+
label: rowLabelByKey.get(rowKey) ?? rowKey,
|
|
2506
|
+
rowIndex,
|
|
2507
|
+
x: LANE_LABEL_W - 6,
|
|
2508
|
+
y: (rowY[rowIndex] ?? 0) - 6,
|
|
2509
|
+
width: totalWidth - LANE_LABEL_W + 12,
|
|
2510
|
+
height: (rowHeights[rowIndex] ?? 0) + 12,
|
|
2511
|
+
color: PALETTE[rowIndex % PALETTE.length].border,
|
|
2512
|
+
taskCount: rowTasks.length
|
|
2513
|
+
}));
|
|
2514
|
+
const stages = Array.from({ length: maxStage + 1 }, (_, index) => ({
|
|
2515
|
+
index,
|
|
2516
|
+
label: index === 0 ? "Roots" : `Stage ${index}`,
|
|
2517
|
+
x: (colX[index] ?? 0) + CELL_H_PAD,
|
|
2518
|
+
width: CARD_WIDTH
|
|
2519
|
+
}));
|
|
2520
|
+
const pendingApprovalRunIds = new Set(selectPendingApprovals(snapshot).map((approval) => approval.runId));
|
|
2521
|
+
const nodes = [];
|
|
2522
|
+
for (const [rowIndex, [rowKey]] of orderedRows.entries()) {
|
|
2523
|
+
for (let stage = 0;stage <= maxStage; stage += 1) {
|
|
2524
|
+
const cellTasks = cells.get(`${rowIndex}:${stage}`) ?? [];
|
|
2525
|
+
const baseX = (colX[stage] ?? 0) + CELL_H_PAD;
|
|
2526
|
+
const baseY = (rowY[rowIndex] ?? 0) + CELL_V_PAD;
|
|
2527
|
+
const palette = PALETTE[rowIndex % PALETTE.length];
|
|
2528
|
+
for (const [stackIndex, task] of cellTasks.entries()) {
|
|
2529
|
+
const runs = selectRunsByTask(snapshot, task.id);
|
|
2530
|
+
const runIds = new Set(runs.map((run) => run.id));
|
|
2531
|
+
const hasApprovals = runs.some((run) => pendingApprovalRunIds.has(run.id));
|
|
2532
|
+
const hasPendingUserInput = runs.some((run) => selectUserInputsForRun(snapshot, run.id).some((request) => request.status === "pending"));
|
|
2533
|
+
const hasRejectedReview = (snapshot?.reviews ?? []).some((review) => runIds.has(review.runId) && review.status === "rejected");
|
|
2534
|
+
const hasFailedValidations = (snapshot?.validations ?? []).some((validation) => runIds.has(validation.runId) && validation.status === "failed");
|
|
2535
|
+
const artifactCount = (snapshot?.artifacts ?? []).filter((artifact) => runIds.has(artifact.runId)).length;
|
|
2536
|
+
nodes.push({
|
|
2537
|
+
id: task.id,
|
|
2538
|
+
task,
|
|
2539
|
+
rowKey,
|
|
2540
|
+
rowLabel: rowLabelByKey.get(rowKey) ?? rowKey,
|
|
2541
|
+
rowIndex,
|
|
2542
|
+
stage,
|
|
2543
|
+
x: baseX,
|
|
2544
|
+
y: baseY + stackIndex * (CARD_HEIGHT + CELL_V_PAD),
|
|
2545
|
+
width: CARD_WIDTH,
|
|
2546
|
+
height: CARD_HEIGHT,
|
|
2547
|
+
color: palette.border,
|
|
2548
|
+
taskCode: extractTaskCode(task.title),
|
|
2549
|
+
strippedTitle: stripTaskCode(task.title),
|
|
2550
|
+
depsIn: depsIn.get(task.id) ?? 0,
|
|
2551
|
+
depsOut: depsOut.get(task.id) ?? 0,
|
|
2552
|
+
runCount: runs.length,
|
|
2553
|
+
hasApprovals,
|
|
2554
|
+
hasPendingUserInput,
|
|
2555
|
+
hasRejectedReview,
|
|
2556
|
+
hasFailedValidations,
|
|
2557
|
+
artifactCount
|
|
2558
|
+
});
|
|
2559
|
+
}
|
|
2560
|
+
}
|
|
2561
|
+
}
|
|
2562
|
+
return {
|
|
2563
|
+
lanes,
|
|
2564
|
+
stages,
|
|
2565
|
+
nodes,
|
|
2566
|
+
edges,
|
|
2567
|
+
totalWidth,
|
|
2568
|
+
totalHeight,
|
|
2569
|
+
taskCount: graphTasks.length
|
|
2570
|
+
};
|
|
2571
|
+
}
|
|
2572
|
+
|
|
2573
|
+
// packages/core/src/index.ts
|
|
2574
|
+
var RIG_CORE_PACKAGE = "@rig/core";
|
|
2575
|
+
export {
|
|
2576
|
+
stripTaskCode,
|
|
2577
|
+
selectWorkspaces,
|
|
2578
|
+
selectWorkspace,
|
|
2579
|
+
selectUserInputsForWorkspace,
|
|
2580
|
+
selectUserInputsForRun,
|
|
2581
|
+
selectTasksGroupedByStatus,
|
|
2582
|
+
selectTasksForWorkspace,
|
|
2583
|
+
selectTasksByWorkspace,
|
|
2584
|
+
selectTasksByStatus,
|
|
2585
|
+
selectTask,
|
|
2586
|
+
selectRunsForWorkspace,
|
|
2587
|
+
selectRunsForTask,
|
|
2588
|
+
selectRunsByTask,
|
|
2589
|
+
selectRun,
|
|
2590
|
+
selectQueueForWorkspace,
|
|
2591
|
+
selectPrimaryWorkspace,
|
|
2592
|
+
selectPendingApprovals,
|
|
2593
|
+
selectGraphsForWorkspace,
|
|
2594
|
+
selectApprovalsForWorkspace,
|
|
2595
|
+
selectApprovalsForRun,
|
|
2596
|
+
selectAdhocRunsForWorkspace,
|
|
2597
|
+
selectAdhocRuns,
|
|
2598
|
+
resolveTaskReference,
|
|
2599
|
+
readTaskSourceIssueId,
|
|
2600
|
+
readTaskMetadataStringList,
|
|
2601
|
+
pruneQueueEntries,
|
|
2602
|
+
pickDefaultWorkspaceId,
|
|
2603
|
+
extractTaskGroupKey,
|
|
2604
|
+
extractTaskCode,
|
|
2605
|
+
definePlugin,
|
|
2606
|
+
defineConfig,
|
|
2607
|
+
createPluginHost,
|
|
2608
|
+
computeTaskBlockingDepths,
|
|
2609
|
+
buildTaskReferenceIndex,
|
|
2610
|
+
buildTaskGraphLayout,
|
|
2611
|
+
buildRigInitConfigSource,
|
|
2612
|
+
applyEngineEvent as applyRigEvent,
|
|
2613
|
+
applyEngineEvents,
|
|
2614
|
+
applyEngineEvent,
|
|
2615
|
+
RIG_CORE_PACKAGE
|
|
2616
|
+
};
|