@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.
@@ -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
+ };