@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,1780 @@
1
+ // @bun
2
+ // packages/core/src/engineReadModelReducer.ts
3
+ import {
4
+ ActionId,
5
+ ConversationId,
6
+ EngineRuntimeId,
7
+ MessageId,
8
+ RunId,
9
+ TaskId,
10
+ WorkspaceId,
11
+ WorktreeId
12
+ } from "@rig/contracts";
13
+ function isRecord(value) {
14
+ return typeof value === "object" && value !== null;
15
+ }
16
+ function readString(payload, key) {
17
+ const value = payload[key];
18
+ return typeof value === "string" ? value : undefined;
19
+ }
20
+ function readNullableString(payload, key) {
21
+ const value = payload[key];
22
+ return typeof value === "string" ? value : value === null ? null : undefined;
23
+ }
24
+ function readRecord(payload, key) {
25
+ const value = payload[key];
26
+ return isRecord(value) ? value : undefined;
27
+ }
28
+ function asWorkspaceId(value) {
29
+ return WorkspaceId.makeUnsafe(value);
30
+ }
31
+ function asTaskId(value) {
32
+ return TaskId.makeUnsafe(value);
33
+ }
34
+ function asRunId(value) {
35
+ return RunId.makeUnsafe(value);
36
+ }
37
+ function conversationIdFromRunId(value) {
38
+ return ConversationId.makeUnsafe(value);
39
+ }
40
+ function asActionId(value) {
41
+ return ActionId.makeUnsafe(value);
42
+ }
43
+ function runtimeIdFromRunId(value) {
44
+ return EngineRuntimeId.makeUnsafe(value);
45
+ }
46
+ function worktreeIdFromRunId(value) {
47
+ return WorktreeId.makeUnsafe(value);
48
+ }
49
+ function asMessageId(value) {
50
+ return MessageId.makeUnsafe(value);
51
+ }
52
+ function upsertById(items, entry) {
53
+ const index = items.findIndex((candidate) => candidate.id === entry.id);
54
+ if (index < 0) {
55
+ return [...items, entry];
56
+ }
57
+ const next = items.slice();
58
+ next[index] = entry;
59
+ return next;
60
+ }
61
+ function removeById(items, id) {
62
+ return items.filter((candidate) => candidate.id !== id);
63
+ }
64
+ function patchById(items, id, updater) {
65
+ const index = items.findIndex((candidate) => candidate.id === id);
66
+ if (index < 0) {
67
+ return items.slice();
68
+ }
69
+ const next = items.slice();
70
+ next[index] = updater(next[index]);
71
+ return next;
72
+ }
73
+ function upsertByKey(items, entry, key) {
74
+ const index = items.findIndex((candidate) => candidate[key] === entry[key]);
75
+ if (index < 0) {
76
+ return [...items, entry];
77
+ }
78
+ const next = items.slice();
79
+ next[index] = entry;
80
+ return next;
81
+ }
82
+ function patchByKey(items, keyValue, key, updater) {
83
+ const index = items.findIndex((candidate) => candidate[key] === keyValue);
84
+ if (index < 0) {
85
+ return items.slice();
86
+ }
87
+ const next = items.slice();
88
+ next[index] = updater(next[index]);
89
+ return next;
90
+ }
91
+ function mergeById(items, incoming) {
92
+ return incoming.reduce((acc, item) => upsertById(acc, item), [...items]);
93
+ }
94
+ function replaceWorkspaceSlice(items, workspaceId, incoming) {
95
+ return [...items.filter((item) => item.workspaceId !== workspaceId), ...incoming];
96
+ }
97
+ function withQueuePositions(items) {
98
+ return items.map((item, index) => Object.assign({}, item, {
99
+ position: index
100
+ }));
101
+ }
102
+ function maxIsoDate(left, right) {
103
+ return left.localeCompare(right) >= 0 ? left : right;
104
+ }
105
+ var REMOTE_HOST_STATUS_ONLINE = new Set([
106
+ "ready",
107
+ "busy",
108
+ "degraded",
109
+ "draining"
110
+ ]);
111
+ function deriveRemoteFleetStatus(hosts, warnings) {
112
+ if (hosts.length === 0)
113
+ return "empty";
114
+ if (warnings.length > 0)
115
+ return "degraded";
116
+ if (hosts.some((host) => host.status === "degraded" || host.status === "quarantined")) {
117
+ return "degraded";
118
+ }
119
+ const onlineHostCount = hosts.filter((host) => REMOTE_HOST_STATUS_ONLINE.has(host.status)).length;
120
+ return onlineHostCount > 0 ? "ready" : "degraded";
121
+ }
122
+ function buildRemoteFleetSummary(input) {
123
+ const warnings = input.fleet?.warnings ?? [];
124
+ const manifestCount = input.fleet?.manifestCount ?? 0;
125
+ const onlineHostCount = input.hosts.filter((host) => REMOTE_HOST_STATUS_ONLINE.has(host.status)).length;
126
+ return {
127
+ updatedAt: input.updatedAt,
128
+ status: deriveRemoteFleetStatus(input.hosts, warnings),
129
+ manifestCount,
130
+ hostCount: input.hosts.length,
131
+ onlineHostCount,
132
+ hosts: [...input.hosts].sort((left, right) => left.name.localeCompare(right.name)),
133
+ warnings
134
+ };
135
+ }
136
+ function toRunMode(interactionMode) {
137
+ return interactionMode === "plan" ? "supervised" : "interactive";
138
+ }
139
+ function mapLegacySessionStatusToRunStatus(status) {
140
+ switch (status) {
141
+ case "starting":
142
+ return "preparing";
143
+ case "running":
144
+ return "running";
145
+ case "ready":
146
+ return "completed";
147
+ case "interrupted":
148
+ return "paused";
149
+ case "error":
150
+ return "failed";
151
+ case "stopped":
152
+ return "cancelled";
153
+ default:
154
+ return "created";
155
+ }
156
+ }
157
+ function mapLegacySessionStatusToRuntimeStatus(status) {
158
+ switch (status) {
159
+ case "starting":
160
+ return "starting";
161
+ case "running":
162
+ return "running";
163
+ case "interrupted":
164
+ return "interrupted";
165
+ case "error":
166
+ return "failed";
167
+ default:
168
+ return "exited";
169
+ }
170
+ }
171
+ function mapTaskStatusFromRunStatus(status, fallback) {
172
+ if (fallback === "blocked" || fallback === "cancelled" || fallback === "completed" || fallback === "closed" || fallback === "unknown") {
173
+ return fallback === "closed" ? "completed" : fallback;
174
+ }
175
+ switch (status) {
176
+ case "created":
177
+ return fallback;
178
+ case "queued":
179
+ return "queued";
180
+ case "preparing":
181
+ case "running":
182
+ case "waiting-approval":
183
+ case "waiting-user-input":
184
+ case "validating":
185
+ case "paused":
186
+ return "in_progress";
187
+ case "reviewing":
188
+ return "under_review";
189
+ case "completed":
190
+ return "completed";
191
+ case "failed":
192
+ return "ready";
193
+ case "cancelled":
194
+ return "cancelled";
195
+ }
196
+ }
197
+ function withSnapshotMetadata(snapshot, event, next) {
198
+ return {
199
+ ...snapshot,
200
+ ...next,
201
+ snapshotSequence: Math.max(snapshot.snapshotSequence, event.sequence),
202
+ updatedAt: maxIsoDate(snapshot.updatedAt, event.createdAt)
203
+ };
204
+ }
205
+ function ensureConversation(snapshot, run) {
206
+ const conversationId = conversationIdFromRunId(run.id);
207
+ if (snapshot.conversations.some((conversation) => conversation.id === conversationId)) {
208
+ return snapshot;
209
+ }
210
+ return {
211
+ ...snapshot,
212
+ conversations: [
213
+ ...snapshot.conversations,
214
+ {
215
+ id: conversationId,
216
+ runId: run.id,
217
+ title: run.title,
218
+ createdAt: run.createdAt,
219
+ updatedAt: run.updatedAt
220
+ }
221
+ ]
222
+ };
223
+ }
224
+ function updateTaskFromRun(snapshot, run) {
225
+ if (!run.taskId) {
226
+ return snapshot;
227
+ }
228
+ return {
229
+ ...snapshot,
230
+ tasks: patchById(snapshot.tasks, run.taskId, (task) => ({
231
+ ...task,
232
+ status: mapTaskStatusFromRunStatus(run.status, task.status),
233
+ updatedAt: maxIsoDate(task.updatedAt, run.updatedAt)
234
+ }))
235
+ };
236
+ }
237
+ function applyRun(snapshot, run) {
238
+ const withConversation = ensureConversation(snapshot, run);
239
+ const nextRuns = upsertById(withConversation.runs, run);
240
+ return updateTaskFromRun({ ...withConversation, runs: nextRuns }, run);
241
+ }
242
+ function applyRuntime(snapshot, runtime) {
243
+ return {
244
+ ...snapshot,
245
+ runtimes: upsertById(snapshot.runtimes, runtime)
246
+ };
247
+ }
248
+ function applyWorktree(snapshot, worktree) {
249
+ return {
250
+ ...snapshot,
251
+ worktrees: upsertById(snapshot.worktrees, worktree)
252
+ };
253
+ }
254
+ function countPendingApprovals(approvals, runId) {
255
+ return approvals.filter((approval) => approval.runId === runId && approval.status === "pending").length;
256
+ }
257
+ function countPendingUserInputs(userInputs, actions, runId) {
258
+ const persistedCount = (userInputs ?? []).filter((request) => request.runId === runId && request.status === "pending").length;
259
+ if (persistedCount > 0) {
260
+ return persistedCount;
261
+ }
262
+ const openRequestIds = new Set;
263
+ for (const action of actions) {
264
+ if (action.runId !== runId || !isRecord(action.payload)) {
265
+ continue;
266
+ }
267
+ const requestId = readString(action.payload, "requestId");
268
+ if (!requestId) {
269
+ continue;
270
+ }
271
+ if (action.actionType === "user-input.requested") {
272
+ openRequestIds.add(requestId);
273
+ }
274
+ if (action.actionType === "user-input.resolved") {
275
+ openRequestIds.delete(requestId);
276
+ }
277
+ }
278
+ return openRequestIds.size;
279
+ }
280
+ function reconcileRunCounts(snapshot, runId, statusOverride) {
281
+ return {
282
+ ...snapshot,
283
+ runs: patchById(snapshot.runs, runId, (run) => {
284
+ const pendingApprovalCount = countPendingApprovals(snapshot.approvals, runId);
285
+ const pendingUserInputCount = countPendingUserInputs(snapshot.userInputs, snapshot.actions, runId);
286
+ const nextStatus = statusOverride ?? (pendingApprovalCount > 0 ? "waiting-approval" : pendingUserInputCount > 0 ? "waiting-user-input" : run.status === "waiting-approval" || run.status === "waiting-user-input" ? "running" : run.status);
287
+ return {
288
+ ...run,
289
+ pendingApprovalCount,
290
+ pendingUserInputCount,
291
+ status: nextStatus
292
+ };
293
+ })
294
+ };
295
+ }
296
+ function applyApprovalActivity(approvals, runId, action) {
297
+ if (!isRecord(action.payload)) {
298
+ return approvals.slice();
299
+ }
300
+ const requestId = readString(action.payload, "requestId");
301
+ if (!requestId) {
302
+ return approvals.slice();
303
+ }
304
+ if (action.actionType === "approval.requested") {
305
+ const requestKind = readString(action.payload, "requestKind") ?? "command";
306
+ return upsertById(approvals, {
307
+ id: requestId,
308
+ runId,
309
+ actionId: action.id,
310
+ requestKind,
311
+ status: "pending",
312
+ payload: action.payload,
313
+ createdAt: action.startedAt,
314
+ resolvedAt: null
315
+ });
316
+ }
317
+ 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"));
318
+ if (!shouldResolve) {
319
+ return approvals.slice();
320
+ }
321
+ return approvals.map((approval) => approval.id === requestId ? {
322
+ ...approval,
323
+ status: "resolved",
324
+ resolvedAt: action.completedAt ?? action.startedAt
325
+ } : approval);
326
+ }
327
+ function makeRuntimeFromRun(run, occurredAt) {
328
+ return {
329
+ id: run.activeRuntimeId ?? runtimeIdFromRunId(run.id),
330
+ workspaceId: run.workspaceId,
331
+ runId: run.id,
332
+ adapterKind: run.runtimeAdapter,
333
+ executionTarget: run.executionTarget ?? "local",
334
+ remoteHostId: run.remoteHostId ?? null,
335
+ status: run.status === "failed" ? "failed" : run.status === "paused" ? "interrupted" : "starting",
336
+ sandboxMode: "danger-full-access",
337
+ isolationMode: run.worktreePath ? "worktree" : "env",
338
+ workspaceDir: run.worktreePath,
339
+ homeDir: null,
340
+ tmpDir: null,
341
+ cacheDir: null,
342
+ logsDir: null,
343
+ stateDir: null,
344
+ sessionDir: null,
345
+ sessionLogPath: null,
346
+ pid: null,
347
+ startedAt: run.startedAt ?? occurredAt,
348
+ updatedAt: occurredAt,
349
+ exitedAt: run.completedAt
350
+ };
351
+ }
352
+ function applySyntheticRuntimePreparation(snapshot, event) {
353
+ if (!isRecord(event.payload)) {
354
+ return { status: "ignored", snapshot };
355
+ }
356
+ const runId = readString(event.payload, "runId") ?? event.aggregateId;
357
+ const workspaceId = readString(event.payload, "workspaceId");
358
+ const taskId = readString(event.payload, "taskId") ?? null;
359
+ const workspaceDir = readNullableString(event.payload, "workspaceDir");
360
+ const homeDir = readNullableString(event.payload, "homeDir");
361
+ const tmpDir = readNullableString(event.payload, "tmpDir");
362
+ const cacheDir = readNullableString(event.payload, "cacheDir");
363
+ const branchName = readString(event.payload, "taskExternalId") ?? snapshot.runs.find((run) => run.id === runId)?.branch ?? "task";
364
+ const failed = event.type.endsWith(".failed");
365
+ const finished = event.type.endsWith(".finished");
366
+ const existingRun = snapshot.runs.find((run) => run.id === runId);
367
+ if (!existingRun || !workspaceId) {
368
+ return { status: "ignored", snapshot };
369
+ }
370
+ const runtimeRunId = asRunId(runId);
371
+ const nextTaskId = taskId ? asTaskId(taskId) : null;
372
+ const nextWorkspaceId = asWorkspaceId(workspaceId);
373
+ const nextRun = {
374
+ ...existingRun,
375
+ taskId: existingRun.taskId ?? nextTaskId,
376
+ runKind: existingRun.runKind === "adhoc" && nextTaskId ? "task" : existingRun.runKind,
377
+ runtimeAdapter: "rig-native",
378
+ initialPrompt: existingRun.initialPrompt ?? null,
379
+ executionTarget: existingRun.executionTarget ?? "local",
380
+ remoteHostId: existingRun.remoteHostId ?? null,
381
+ activeRuntimeId: existingRun.activeRuntimeId ?? runtimeIdFromRunId(runtimeRunId),
382
+ worktreePath: workspaceDir ?? existingRun.worktreePath,
383
+ status: failed ? "failed" : finished ? "preparing" : "preparing",
384
+ errorText: failed ? readString(event.payload, "message") ?? existingRun.errorText : existingRun.errorText,
385
+ updatedAt: event.createdAt,
386
+ completedAt: failed ? event.createdAt : existingRun.completedAt
387
+ };
388
+ const runtimeBase = snapshot.runtimes.find((runtime) => runtime.runId === runId) ?? makeRuntimeFromRun(nextRun, event.createdAt);
389
+ const nextRuntime = {
390
+ ...runtimeBase,
391
+ workspaceId: nextWorkspaceId,
392
+ runId: runtimeRunId,
393
+ adapterKind: "rig-native",
394
+ executionTarget: existingRun.executionTarget ?? "local",
395
+ remoteHostId: existingRun.remoteHostId ?? null,
396
+ status: failed ? "failed" : finished ? "prepared" : "starting",
397
+ isolationMode: "worktree",
398
+ workspaceDir: workspaceDir ?? runtimeBase.workspaceDir,
399
+ homeDir: homeDir ?? runtimeBase.homeDir,
400
+ tmpDir: tmpDir ?? runtimeBase.tmpDir,
401
+ cacheDir: cacheDir ?? runtimeBase.cacheDir,
402
+ updatedAt: event.createdAt,
403
+ exitedAt: failed ? event.createdAt : runtimeBase.exitedAt
404
+ };
405
+ let nextSnapshot = applyRun(snapshot, nextRun);
406
+ nextSnapshot = applyRuntime(nextSnapshot, nextRuntime);
407
+ if (workspaceDir) {
408
+ nextSnapshot = applyWorktree(nextSnapshot, {
409
+ id: worktreeIdFromRunId(runtimeRunId),
410
+ workspaceId: nextWorkspaceId,
411
+ runId: runtimeRunId,
412
+ taskId: nextRun.taskId,
413
+ branchName,
414
+ path: workspaceDir,
415
+ status: failed ? "failed" : finished ? "prepared" : "preparing",
416
+ createdAt: existingRun.createdAt,
417
+ cleanedAt: null
418
+ });
419
+ }
420
+ return {
421
+ status: "applied",
422
+ snapshot: withSnapshotMetadata(nextSnapshot, event, {})
423
+ };
424
+ }
425
+ function applySyntheticRuntimePrepared(snapshot, event) {
426
+ if (!isRecord(event.payload)) {
427
+ return { status: "ignored", snapshot };
428
+ }
429
+ const runId = readString(event.payload, "runId") ?? event.aggregateId;
430
+ const workspaceId = readString(event.payload, "workspaceId");
431
+ const worktreePath = readString(event.payload, "worktreePath");
432
+ const existingRun = snapshot.runs.find((run) => run.id === runId);
433
+ if (!existingRun || !workspaceId || !worktreePath) {
434
+ return { status: "ignored", snapshot };
435
+ }
436
+ const runtimeRunId = asRunId(runId);
437
+ const nextWorkspaceId = asWorkspaceId(workspaceId);
438
+ let nextSnapshot = applyRuntime(snapshot, {
439
+ ...snapshot.runtimes.find((runtime) => runtime.runId === runId) ?? makeRuntimeFromRun(existingRun, event.createdAt),
440
+ workspaceId: nextWorkspaceId,
441
+ runId: runtimeRunId,
442
+ adapterKind: "rig-native",
443
+ executionTarget: existingRun.executionTarget ?? "local",
444
+ remoteHostId: existingRun.remoteHostId ?? null,
445
+ status: "prepared",
446
+ isolationMode: "worktree",
447
+ workspaceDir: worktreePath,
448
+ updatedAt: event.createdAt
449
+ });
450
+ nextSnapshot = applyWorktree(nextSnapshot, {
451
+ id: worktreeIdFromRunId(runtimeRunId),
452
+ workspaceId: nextWorkspaceId,
453
+ runId: runtimeRunId,
454
+ taskId: existingRun.taskId,
455
+ branchName: existingRun.branch ?? "task",
456
+ path: worktreePath,
457
+ status: "prepared",
458
+ createdAt: existingRun.createdAt,
459
+ cleanedAt: null
460
+ });
461
+ return {
462
+ status: "applied",
463
+ snapshot: withSnapshotMetadata(nextSnapshot, event, {})
464
+ };
465
+ }
466
+ function applyLegacyProjectEvent(snapshot, event) {
467
+ if (!isRecord(event.payload)) {
468
+ return { status: "ignored", snapshot };
469
+ }
470
+ const payload = event.payload;
471
+ if (event.type === "legacy.project.created") {
472
+ const workspaceId = readString(payload, "projectId");
473
+ const title = readString(payload, "title");
474
+ const rootPath = readString(payload, "workspaceRoot");
475
+ const createdAt = readString(payload, "createdAt");
476
+ const updatedAt = readString(payload, "updatedAt");
477
+ if (!workspaceId || !title || !rootPath || !createdAt || !updatedAt) {
478
+ return { status: "ignored", snapshot };
479
+ }
480
+ const workspace = {
481
+ id: asWorkspaceId(workspaceId),
482
+ title,
483
+ rootPath,
484
+ sourceKind: "native",
485
+ defaultRuntimeAdapter: "codex-app-server",
486
+ defaultModel: readNullableString(payload, "defaultModel") ?? null,
487
+ createdAt,
488
+ updatedAt
489
+ };
490
+ return {
491
+ status: "applied",
492
+ snapshot: withSnapshotMetadata(snapshot, event, {
493
+ workspaces: upsertById(snapshot.workspaces, workspace)
494
+ })
495
+ };
496
+ }
497
+ if (event.type === "legacy.project.meta-updated") {
498
+ const workspaceId = readString(payload, "projectId");
499
+ if (!workspaceId) {
500
+ return { status: "ignored", snapshot };
501
+ }
502
+ return {
503
+ status: "applied",
504
+ snapshot: withSnapshotMetadata(snapshot, event, {
505
+ workspaces: patchById(snapshot.workspaces, workspaceId, (workspace) => ({
506
+ ...workspace,
507
+ title: readString(payload, "title") ?? workspace.title,
508
+ rootPath: readString(payload, "workspaceRoot") ?? workspace.rootPath,
509
+ defaultModel: readNullableString(payload, "defaultModel") ?? workspace.defaultModel,
510
+ updatedAt: readString(payload, "updatedAt") ?? event.createdAt
511
+ }))
512
+ })
513
+ };
514
+ }
515
+ if (event.type === "legacy.project.deleted") {
516
+ const workspaceId = readString(payload, "projectId");
517
+ if (!workspaceId) {
518
+ return { status: "ignored", snapshot };
519
+ }
520
+ return {
521
+ status: "applied",
522
+ snapshot: withSnapshotMetadata(snapshot, event, {
523
+ workspaces: removeById(snapshot.workspaces, workspaceId),
524
+ graphs: snapshot.graphs.filter((graph) => graph.workspaceId !== workspaceId),
525
+ tasks: snapshot.tasks.filter((task) => task.workspaceId !== workspaceId),
526
+ runs: snapshot.runs.filter((run) => run.workspaceId !== workspaceId),
527
+ runtimes: snapshot.runtimes.filter((runtime) => runtime.workspaceId !== workspaceId),
528
+ conversations: snapshot.conversations.filter((conversation) => {
529
+ const run = snapshot.runs.find((candidate) => candidate.id === conversation.runId);
530
+ return run?.workspaceId !== workspaceId;
531
+ }),
532
+ messages: snapshot.messages.filter((message) => {
533
+ const conversation = snapshot.conversations.find((candidate) => candidate.id === message.conversationId);
534
+ const run = snapshot.runs.find((candidate) => candidate.id === conversation?.runId);
535
+ return run?.workspaceId !== workspaceId;
536
+ }),
537
+ actions: snapshot.actions.filter((action) => {
538
+ const run = snapshot.runs.find((candidate) => candidate.id === action.runId);
539
+ return run?.workspaceId !== workspaceId;
540
+ }),
541
+ approvals: snapshot.approvals.filter((approval) => {
542
+ const run = snapshot.runs.find((candidate) => candidate.id === approval.runId);
543
+ return run?.workspaceId !== workspaceId;
544
+ }),
545
+ queue: snapshot.queue.filter((entry) => {
546
+ const task = snapshot.tasks.find((candidate) => candidate.id === entry.taskId);
547
+ return task?.workspaceId !== workspaceId;
548
+ }),
549
+ worktrees: snapshot.worktrees.filter((worktree) => worktree.workspaceId !== workspaceId)
550
+ })
551
+ };
552
+ }
553
+ return { status: "ignored", snapshot };
554
+ }
555
+ function applyLegacyThreadEvent(snapshot, event) {
556
+ if (!isRecord(event.payload)) {
557
+ return { status: "ignored", snapshot };
558
+ }
559
+ const payload = event.payload;
560
+ if (event.type === "legacy.thread.created") {
561
+ const runId = readString(payload, "threadId");
562
+ const workspaceId = readString(payload, "projectId");
563
+ const title = readString(payload, "title");
564
+ const model = readString(payload, "model");
565
+ const createdAt = readString(payload, "createdAt");
566
+ const updatedAt = readString(payload, "updatedAt");
567
+ if (!runId || !workspaceId || !title || !model || !createdAt || !updatedAt) {
568
+ return { status: "ignored", snapshot };
569
+ }
570
+ const run = {
571
+ id: asRunId(runId),
572
+ workspaceId: asWorkspaceId(workspaceId),
573
+ taskId: null,
574
+ title,
575
+ runKind: "adhoc",
576
+ mode: toRunMode(readString(payload, "interactionMode")),
577
+ runtimeMode: "full-access",
578
+ interactionMode: "default",
579
+ status: "created",
580
+ runtimeAdapter: "codex-app-server",
581
+ model,
582
+ initialPrompt: null,
583
+ activeRuntimeId: null,
584
+ latestMessageId: null,
585
+ pendingApprovalCount: 0,
586
+ pendingUserInputCount: 0,
587
+ branch: readNullableString(payload, "branch") ?? null,
588
+ worktreePath: readNullableString(payload, "worktreePath") ?? null,
589
+ errorText: null,
590
+ createdAt,
591
+ updatedAt,
592
+ startedAt: null,
593
+ completedAt: null
594
+ };
595
+ let nextSnapshot = applyRun(snapshot, run);
596
+ if (run.branch && run.worktreePath) {
597
+ nextSnapshot = applyWorktree(nextSnapshot, {
598
+ id: worktreeIdFromRunId(asRunId(runId)),
599
+ workspaceId: asWorkspaceId(workspaceId),
600
+ runId: asRunId(runId),
601
+ taskId: null,
602
+ branchName: run.branch,
603
+ path: run.worktreePath,
604
+ status: "active",
605
+ createdAt,
606
+ cleanedAt: null
607
+ });
608
+ }
609
+ return {
610
+ status: "applied",
611
+ snapshot: withSnapshotMetadata(nextSnapshot, event, {})
612
+ };
613
+ }
614
+ if (event.type === "legacy.thread.deleted") {
615
+ const runId = readString(payload, "threadId");
616
+ if (!runId) {
617
+ return { status: "ignored", snapshot };
618
+ }
619
+ return {
620
+ status: "applied",
621
+ snapshot: withSnapshotMetadata(snapshot, event, {
622
+ runs: removeById(snapshot.runs, runId),
623
+ runtimes: snapshot.runtimes.filter((runtime) => runtime.runId !== runId),
624
+ conversations: snapshot.conversations.filter((conversation) => conversation.runId !== runId),
625
+ messages: snapshot.messages.filter((message) => message.conversationId !== runId),
626
+ actions: snapshot.actions.filter((action) => action.runId !== runId),
627
+ approvals: snapshot.approvals.filter((approval) => approval.runId !== runId),
628
+ worktrees: snapshot.worktrees.filter((worktree) => worktree.runId !== runId)
629
+ })
630
+ };
631
+ }
632
+ if (event.type === "legacy.thread.meta-updated") {
633
+ const runId = readString(payload, "threadId");
634
+ if (!runId) {
635
+ return { status: "ignored", snapshot };
636
+ }
637
+ let nextSnapshot = {
638
+ ...snapshot,
639
+ runs: patchById(snapshot.runs, runId, (run2) => ({
640
+ ...run2,
641
+ title: readString(payload, "title") ?? run2.title,
642
+ model: readString(payload, "model") ?? run2.model,
643
+ branch: readNullableString(payload, "branch") === undefined ? run2.branch : readNullableString(payload, "branch") ?? null,
644
+ worktreePath: readNullableString(payload, "worktreePath") === undefined ? run2.worktreePath : readNullableString(payload, "worktreePath") ?? null,
645
+ updatedAt: readString(payload, "updatedAt") ?? event.createdAt
646
+ })),
647
+ conversations: patchById(snapshot.conversations, runId, (conversation) => ({
648
+ ...conversation,
649
+ title: readString(payload, "title") ?? conversation.title,
650
+ updatedAt: readString(payload, "updatedAt") ?? event.createdAt
651
+ }))
652
+ };
653
+ const run = nextSnapshot.runs.find((candidate) => candidate.id === runId);
654
+ if (run?.worktreePath && run.branch) {
655
+ nextSnapshot = applyWorktree(nextSnapshot, {
656
+ id: worktreeIdFromRunId(run.id),
657
+ workspaceId: run.workspaceId,
658
+ runId: run.id,
659
+ taskId: run.taskId,
660
+ branchName: run.branch,
661
+ path: run.worktreePath,
662
+ status: "active",
663
+ createdAt: run.createdAt,
664
+ cleanedAt: null
665
+ });
666
+ }
667
+ if (run && run.worktreePath === null) {
668
+ nextSnapshot = {
669
+ ...nextSnapshot,
670
+ worktrees: nextSnapshot.worktrees.filter((worktree) => worktree.runId !== runId)
671
+ };
672
+ }
673
+ return {
674
+ status: "applied",
675
+ snapshot: withSnapshotMetadata(nextSnapshot, event, {})
676
+ };
677
+ }
678
+ if (event.type === "legacy.thread.interaction-mode-set") {
679
+ const runId = readString(payload, "threadId");
680
+ if (!runId) {
681
+ return { status: "ignored", snapshot };
682
+ }
683
+ return {
684
+ status: "applied",
685
+ snapshot: withSnapshotMetadata(snapshot, event, {
686
+ runs: patchById(snapshot.runs, runId, (run) => ({
687
+ ...run,
688
+ mode: toRunMode(readString(payload, "interactionMode")),
689
+ updatedAt: readString(payload, "updatedAt") ?? event.createdAt
690
+ }))
691
+ })
692
+ };
693
+ }
694
+ if (event.type === "legacy.thread.runtime-mode-set") {
695
+ const runId = readString(payload, "threadId");
696
+ if (!runId) {
697
+ return { status: "ignored", snapshot };
698
+ }
699
+ const runtimeMode = readString(payload, "runtimeMode");
700
+ return {
701
+ status: "applied",
702
+ snapshot: withSnapshotMetadata(snapshot, event, {
703
+ runtimes: patchById(snapshot.runtimes, runId, (runtime) => ({
704
+ ...runtime,
705
+ sandboxMode: runtimeMode === "approval-required" ? "workspace-write" : "danger-full-access",
706
+ updatedAt: readString(payload, "updatedAt") ?? event.createdAt
707
+ }))
708
+ })
709
+ };
710
+ }
711
+ if (event.type === "legacy.thread.message-sent") {
712
+ const runId = readString(payload, "threadId");
713
+ const messageId = readString(payload, "messageId");
714
+ const role = readString(payload, "role");
715
+ const text = readString(payload, "text");
716
+ const createdAt = readString(payload, "createdAt");
717
+ const updatedAt = readString(payload, "updatedAt");
718
+ if (!runId || !messageId || !role || text === undefined || !createdAt || !updatedAt) {
719
+ return { status: "ignored", snapshot };
720
+ }
721
+ const attachments = Array.isArray(payload.attachments) ? payload.attachments : [];
722
+ const streaming = payload.streaming === true;
723
+ const message = {
724
+ id: asMessageId(messageId),
725
+ conversationId: conversationIdFromRunId(asRunId(runId)),
726
+ role: role === "assistant" || role === "system" ? role : "user",
727
+ text,
728
+ attachments,
729
+ state: streaming ? "streaming" : "completed",
730
+ createdAt,
731
+ completedAt: streaming ? null : updatedAt
732
+ };
733
+ const existingRun = snapshot.runs.find((run) => run.id === runId);
734
+ if (!existingRun) {
735
+ return { status: "ignored", snapshot };
736
+ }
737
+ return {
738
+ status: "applied",
739
+ snapshot: withSnapshotMetadata(ensureConversation({
740
+ ...snapshot,
741
+ messages: upsertById(snapshot.messages, message),
742
+ runs: patchById(snapshot.runs, runId, (run) => ({
743
+ ...run,
744
+ latestMessageId: asMessageId(messageId),
745
+ updatedAt
746
+ }))
747
+ }, existingRun), event, {})
748
+ };
749
+ }
750
+ if (event.type === "legacy.thread.session-set") {
751
+ const runId = readString(event.payload, "threadId");
752
+ const session = readRecord(event.payload, "session");
753
+ if (!runId || !session) {
754
+ return { status: "ignored", snapshot };
755
+ }
756
+ const existingRun = snapshot.runs.find((run) => run.id === runId);
757
+ if (!existingRun) {
758
+ return { status: "ignored", snapshot };
759
+ }
760
+ const sessionUpdatedAt = readString(session, "updatedAt") ?? event.createdAt;
761
+ const providerName = readNullableString(session, "providerName");
762
+ const nextRun = {
763
+ ...existingRun,
764
+ runtimeAdapter: providerName ?? existingRun.runtimeAdapter,
765
+ initialPrompt: existingRun.initialPrompt ?? null,
766
+ activeRuntimeId: runtimeIdFromRunId(asRunId(runId)),
767
+ status: mapLegacySessionStatusToRunStatus(readString(session, "status")),
768
+ errorText: readNullableString(session, "lastError") ?? existingRun.errorText,
769
+ updatedAt: sessionUpdatedAt,
770
+ completedAt: readString(session, "status") === "ready" || readString(session, "status") === "stopped" || readString(session, "status") === "error" ? sessionUpdatedAt : existingRun.completedAt
771
+ };
772
+ let nextSnapshot = applyRun(snapshot, nextRun);
773
+ nextSnapshot = applyRuntime(nextSnapshot, {
774
+ ...snapshot.runtimes.find((runtime) => runtime.runId === runId) ?? makeRuntimeFromRun(nextRun, sessionUpdatedAt),
775
+ workspaceId: existingRun.workspaceId,
776
+ runId: asRunId(runId),
777
+ adapterKind: providerName ?? existingRun.runtimeAdapter,
778
+ status: mapLegacySessionStatusToRuntimeStatus(readString(session, "status")),
779
+ workspaceDir: existingRun.worktreePath,
780
+ isolationMode: existingRun.worktreePath ? "worktree" : "env",
781
+ updatedAt: sessionUpdatedAt,
782
+ exitedAt: readString(session, "status") === "ready" || readString(session, "status") === "stopped" || readString(session, "status") === "error" ? sessionUpdatedAt : null
783
+ });
784
+ return {
785
+ status: "applied",
786
+ snapshot: withSnapshotMetadata(nextSnapshot, event, {})
787
+ };
788
+ }
789
+ if (event.type === "legacy.thread.activity-appended") {
790
+ const runId = readString(event.payload, "threadId");
791
+ const activity = readRecord(event.payload, "activity");
792
+ if (!runId || !activity) {
793
+ return { status: "ignored", snapshot };
794
+ }
795
+ const actionId = readString(activity, "id");
796
+ const actionType = readString(activity, "kind");
797
+ const title = readString(activity, "summary");
798
+ const startedAt = readString(activity, "createdAt");
799
+ if (!actionId || !actionType || !title || !startedAt) {
800
+ return { status: "ignored", snapshot };
801
+ }
802
+ const payload2 = readRecord(activity, "payload") ?? {};
803
+ const detail = readString(payload2, "detail") ?? null;
804
+ const action = {
805
+ id: asActionId(actionId),
806
+ runId: asRunId(runId),
807
+ messageId: null,
808
+ actionType,
809
+ title,
810
+ detail,
811
+ state: "completed",
812
+ payload: payload2,
813
+ startedAt,
814
+ completedAt: startedAt
815
+ };
816
+ let nextSnapshot = {
817
+ ...snapshot,
818
+ actions: upsertById(snapshot.actions, action),
819
+ approvals: applyApprovalActivity(snapshot.approvals, asRunId(runId), action),
820
+ runs: patchById(snapshot.runs, runId, (run) => ({
821
+ ...run,
822
+ updatedAt: maxIsoDate(run.updatedAt, startedAt),
823
+ status: actionType === "engine.runtime.ready" ? "running" : actionType === "engine.runtime.failed" ? "failed" : run.status,
824
+ errorText: actionType === "engine.runtime.failed" && detail ? detail : run.errorText,
825
+ completedAt: actionType === "engine.runtime.failed" ? startedAt : run.completedAt
826
+ }))
827
+ };
828
+ nextSnapshot = reconcileRunCounts(nextSnapshot, asRunId(runId));
829
+ return {
830
+ status: "applied",
831
+ snapshot: withSnapshotMetadata(nextSnapshot, event, {})
832
+ };
833
+ }
834
+ if (event.type === "legacy.thread.reverted") {
835
+ return { status: "requires-resync", snapshot };
836
+ }
837
+ return { status: "ignored", snapshot };
838
+ }
839
+ function applyEngineEvent(snapshot, event) {
840
+ if (event.sequence <= snapshot.snapshotSequence) {
841
+ return { status: "ignored", snapshot };
842
+ }
843
+ if (event.sequence > snapshot.snapshotSequence + 1) {
844
+ return { status: "requires-resync", snapshot };
845
+ }
846
+ const base = {
847
+ ...snapshot,
848
+ snapshotSequence: event.sequence,
849
+ updatedAt: event.createdAt
850
+ };
851
+ const payload = event.payload && typeof event.payload === "object" ? event.payload : {};
852
+ switch (event.type) {
853
+ case "WorkspaceRegistered":
854
+ case "RigWorkspaceImported": {
855
+ const workspace = {
856
+ id: payload.workspaceId,
857
+ title: payload.title,
858
+ rootPath: payload.rootPath,
859
+ sourceKind: payload.sourceKind,
860
+ defaultModel: payload.defaultModel ?? null,
861
+ topology: undefined,
862
+ remoteFleet: undefined,
863
+ serviceFabric: undefined,
864
+ createdAt: payload.createdAt,
865
+ updatedAt: payload.createdAt
866
+ };
867
+ return {
868
+ status: "applied",
869
+ snapshot: {
870
+ ...base,
871
+ workspaces: upsertById(base.workspaces, workspace)
872
+ }
873
+ };
874
+ }
875
+ case "RigStateHydrated": {
876
+ const workspaceRunIds = new Set([
877
+ ...base.runs.filter((run) => run.workspaceId === payload.workspaceId).map((run) => run.id),
878
+ ...(payload.runs ?? []).map((run) => run.id)
879
+ ]);
880
+ const withoutWorkspaceRunData = (items) => items.filter((item) => !workspaceRunIds.has(item.runId));
881
+ const workspaces = payload.rootPath ? patchById(base.workspaces, payload.workspaceId, (workspace) => ({
882
+ ...workspace,
883
+ rootPath: payload.rootPath,
884
+ updatedAt: payload.createdAt
885
+ })) : base.workspaces;
886
+ return {
887
+ status: "applied",
888
+ snapshot: {
889
+ ...base,
890
+ workspaces,
891
+ graphs: upsertById(base.graphs, payload.graph),
892
+ tasks: replaceWorkspaceSlice(base.tasks, payload.workspaceId, payload.tasks ?? []),
893
+ runs: replaceWorkspaceSlice(base.runs, payload.workspaceId, payload.runs ?? []),
894
+ runtimes: replaceWorkspaceSlice(base.runtimes, payload.workspaceId, payload.runtimes ?? []),
895
+ actions: mergeById(withoutWorkspaceRunData(base.actions), payload.actions ?? []),
896
+ logs: mergeById(withoutWorkspaceRunData(base.logs), payload.logs ?? []),
897
+ userInputs: (base.userInputs ?? []).filter((request) => !workspaceRunIds.has(request.runId)),
898
+ validations: mergeById(withoutWorkspaceRunData(base.validations), payload.validations ?? []),
899
+ reviews: mergeById(withoutWorkspaceRunData(base.reviews), payload.reviews ?? []),
900
+ artifacts: mergeById(withoutWorkspaceRunData(base.artifacts), payload.artifacts ?? []),
901
+ policyDecisions: mergeById(withoutWorkspaceRunData(base.policyDecisions), payload.policyDecisions ?? []),
902
+ queue: [...payload.queue ?? []]
903
+ }
904
+ };
905
+ }
906
+ case "WorkspaceTopologyCompiled": {
907
+ return {
908
+ status: "applied",
909
+ snapshot: {
910
+ ...base,
911
+ workspaces: patchById(base.workspaces, payload.workspaceId, (workspace) => ({
912
+ ...workspace,
913
+ rootPath: payload.rootPath ?? workspace.rootPath,
914
+ topology: payload.topology,
915
+ updatedAt: payload.createdAt
916
+ }))
917
+ }
918
+ };
919
+ }
920
+ case "WorkspaceRemoteFleetSynced": {
921
+ return {
922
+ status: "applied",
923
+ snapshot: {
924
+ ...base,
925
+ workspaces: patchById(base.workspaces, payload.workspaceId, (workspace) => ({
926
+ ...workspace,
927
+ rootPath: payload.rootPath ?? workspace.rootPath,
928
+ remoteFleet: payload.remoteFleet,
929
+ updatedAt: payload.createdAt
930
+ }))
931
+ }
932
+ };
933
+ }
934
+ case "WorkspaceServiceFabricSynced": {
935
+ return {
936
+ status: "applied",
937
+ snapshot: {
938
+ ...base,
939
+ workspaces: patchById(base.workspaces, payload.workspaceId, (workspace) => ({
940
+ ...workspace,
941
+ rootPath: payload.rootPath ?? workspace.rootPath,
942
+ serviceFabric: payload.serviceFabric,
943
+ updatedAt: payload.createdAt
944
+ }))
945
+ }
946
+ };
947
+ }
948
+ case "WorkspaceRemoteHostRegistered": {
949
+ return {
950
+ status: "applied",
951
+ snapshot: {
952
+ ...base,
953
+ workspaces: patchById(base.workspaces, payload.workspaceId, (workspace) => {
954
+ const withoutExisting = (workspace.remoteFleet?.hosts ?? []).filter((host) => host.id !== payload.host.id);
955
+ return {
956
+ ...workspace,
957
+ remoteFleet: buildRemoteFleetSummary({
958
+ fleet: workspace.remoteFleet,
959
+ hosts: [...withoutExisting, payload.host],
960
+ updatedAt: payload.createdAt
961
+ }),
962
+ updatedAt: payload.createdAt
963
+ };
964
+ })
965
+ }
966
+ };
967
+ }
968
+ case "WorkspaceRemoteHostStatusUpdated": {
969
+ return {
970
+ status: "applied",
971
+ snapshot: {
972
+ ...base,
973
+ workspaces: patchById(base.workspaces, payload.workspaceId, (workspace) => {
974
+ const fleet = workspace.remoteFleet;
975
+ if (!fleet) {
976
+ return {
977
+ ...workspace,
978
+ updatedAt: payload.createdAt
979
+ };
980
+ }
981
+ const hosts = fleet.hosts.map((host) => host.id !== payload.hostId ? host : {
982
+ ...host,
983
+ status: payload.status,
984
+ currentLeaseCount: typeof payload.currentLeaseCount === "number" ? payload.currentLeaseCount : host.currentLeaseCount,
985
+ lastHeartbeatAt: payload.lastHeartbeatAt ?? host.lastHeartbeatAt
986
+ });
987
+ return {
988
+ ...workspace,
989
+ remoteFleet: buildRemoteFleetSummary({
990
+ fleet,
991
+ hosts,
992
+ updatedAt: payload.createdAt
993
+ }),
994
+ updatedAt: payload.createdAt
995
+ };
996
+ })
997
+ }
998
+ };
999
+ }
1000
+ case "RunCreated": {
1001
+ const run = {
1002
+ id: payload.runId,
1003
+ workspaceId: payload.workspaceId,
1004
+ taskId: payload.taskId ?? null,
1005
+ title: payload.title,
1006
+ runKind: payload.runKind,
1007
+ mode: payload.mode,
1008
+ runtimeMode: payload.runtimeMode,
1009
+ interactionMode: payload.interactionMode,
1010
+ status: "created",
1011
+ runtimeAdapter: payload.runtimeAdapter,
1012
+ model: payload.model ?? null,
1013
+ initialPrompt: payload.initialPrompt ?? null,
1014
+ executionTarget: payload.executionTarget ?? (payload.remoteHostId ? "remote" : "local"),
1015
+ remoteHostId: payload.remoteHostId ?? null,
1016
+ remoteLeaseId: null,
1017
+ remoteLeaseClaimedAt: null,
1018
+ activeRuntimeId: null,
1019
+ latestMessageId: null,
1020
+ pendingApprovalCount: 0,
1021
+ pendingUserInputCount: 0,
1022
+ branch: null,
1023
+ worktreePath: null,
1024
+ errorText: null,
1025
+ createdAt: payload.createdAt,
1026
+ updatedAt: payload.createdAt,
1027
+ startedAt: null,
1028
+ completedAt: null
1029
+ };
1030
+ return {
1031
+ status: "applied",
1032
+ snapshot: { ...base, runs: upsertById(base.runs, run) }
1033
+ };
1034
+ }
1035
+ case "RunInterrupted":
1036
+ case "RunCancelled":
1037
+ return {
1038
+ status: "applied",
1039
+ snapshot: {
1040
+ ...base,
1041
+ runs: patchById(base.runs, payload.runId, (run) => ({
1042
+ ...run,
1043
+ status: "cancelled",
1044
+ updatedAt: payload.createdAt,
1045
+ completedAt: payload.createdAt
1046
+ }))
1047
+ }
1048
+ };
1049
+ case "RunCompleted":
1050
+ return {
1051
+ status: "applied",
1052
+ snapshot: {
1053
+ ...base,
1054
+ runs: patchById(base.runs, payload.runId, (run) => ({
1055
+ ...run,
1056
+ status: "completed",
1057
+ updatedAt: payload.createdAt,
1058
+ completedAt: payload.createdAt
1059
+ }))
1060
+ }
1061
+ };
1062
+ case "RunStatusSet":
1063
+ return {
1064
+ status: "applied",
1065
+ snapshot: {
1066
+ ...base,
1067
+ runs: patchById(base.runs, payload.runId, (run) => ({
1068
+ ...run,
1069
+ status: payload.status,
1070
+ updatedAt: payload.createdAt,
1071
+ completedAt: null
1072
+ }))
1073
+ }
1074
+ };
1075
+ case "RunFailed":
1076
+ return {
1077
+ status: "applied",
1078
+ snapshot: {
1079
+ ...base,
1080
+ runs: patchById(base.runs, payload.runId, (run) => ({
1081
+ ...run,
1082
+ status: "failed",
1083
+ errorText: payload.errorText ?? null,
1084
+ updatedAt: payload.createdAt,
1085
+ completedAt: payload.createdAt
1086
+ }))
1087
+ }
1088
+ };
1089
+ case "RuntimeModeSet":
1090
+ return {
1091
+ status: "applied",
1092
+ snapshot: {
1093
+ ...base,
1094
+ runs: patchById(base.runs, payload.runId, (run) => ({
1095
+ ...run,
1096
+ runtimeMode: payload.runtimeMode,
1097
+ updatedAt: payload.createdAt
1098
+ }))
1099
+ }
1100
+ };
1101
+ case "InteractionModeSet":
1102
+ return {
1103
+ status: "applied",
1104
+ snapshot: {
1105
+ ...base,
1106
+ runs: patchById(base.runs, payload.runId, (run) => ({
1107
+ ...run,
1108
+ interactionMode: payload.interactionMode,
1109
+ updatedAt: payload.createdAt
1110
+ }))
1111
+ }
1112
+ };
1113
+ case "ConversationAttached": {
1114
+ const conversation = {
1115
+ id: payload.conversationId,
1116
+ runId: payload.runId,
1117
+ title: payload.title,
1118
+ createdAt: payload.createdAt,
1119
+ updatedAt: payload.createdAt
1120
+ };
1121
+ return {
1122
+ status: "applied",
1123
+ snapshot: {
1124
+ ...base,
1125
+ conversations: upsertById(base.conversations, conversation)
1126
+ }
1127
+ };
1128
+ }
1129
+ case "MessageAppended": {
1130
+ const conversation = base.conversations.find((item) => item.runId === payload.runId);
1131
+ if (!conversation) {
1132
+ return { status: "ignored", snapshot: base };
1133
+ }
1134
+ const conversationId = conversation.id;
1135
+ const message = {
1136
+ id: payload.messageId,
1137
+ conversationId,
1138
+ role: payload.role,
1139
+ text: payload.text,
1140
+ attachments: payload.attachments ?? [],
1141
+ state: payload.state ?? "completed",
1142
+ createdAt: payload.createdAt,
1143
+ completedAt: payload.completedAt ?? payload.createdAt
1144
+ };
1145
+ return {
1146
+ status: "applied",
1147
+ snapshot: {
1148
+ ...base,
1149
+ messages: upsertById(base.messages, message),
1150
+ runs: patchById(base.runs, payload.runId, (run) => ({
1151
+ ...run,
1152
+ latestMessageId: payload.messageId,
1153
+ updatedAt: payload.createdAt
1154
+ }))
1155
+ }
1156
+ };
1157
+ }
1158
+ case "ActionStarted": {
1159
+ const action = {
1160
+ id: payload.actionId,
1161
+ runId: payload.runId,
1162
+ messageId: payload.messageId ?? null,
1163
+ actionType: payload.actionType,
1164
+ title: payload.title,
1165
+ detail: payload.detail ?? null,
1166
+ state: payload.state ?? "running",
1167
+ payload: payload.payload ?? null,
1168
+ startedAt: payload.startedAt,
1169
+ completedAt: null
1170
+ };
1171
+ return {
1172
+ status: "applied",
1173
+ snapshot: {
1174
+ ...base,
1175
+ actions: upsertById(base.actions, action)
1176
+ }
1177
+ };
1178
+ }
1179
+ case "ActionCompleted":
1180
+ return {
1181
+ status: "applied",
1182
+ snapshot: {
1183
+ ...base,
1184
+ actions: patchById(base.actions, payload.actionId, (action) => ({
1185
+ ...action,
1186
+ state: payload.state,
1187
+ detail: payload.detail ?? null,
1188
+ payload: payload.payload,
1189
+ completedAt: payload.completedAt
1190
+ }))
1191
+ }
1192
+ };
1193
+ case "RunLogAppended": {
1194
+ const log = {
1195
+ id: payload.logId,
1196
+ runId: payload.runId,
1197
+ title: payload.title,
1198
+ detail: payload.detail ?? null,
1199
+ tone: payload.tone,
1200
+ status: payload.status ?? null,
1201
+ payload: payload.payload ?? null,
1202
+ createdAt: payload.createdAt
1203
+ };
1204
+ return {
1205
+ status: "applied",
1206
+ snapshot: {
1207
+ ...base,
1208
+ logs: upsertById(base.logs, log),
1209
+ runs: patchById(base.runs, payload.runId, (run) => ({
1210
+ ...run,
1211
+ updatedAt: payload.createdAt
1212
+ }))
1213
+ }
1214
+ };
1215
+ }
1216
+ case "ValidationRecorded":
1217
+ return {
1218
+ status: "applied",
1219
+ snapshot: {
1220
+ ...base,
1221
+ validations: upsertById(base.validations, {
1222
+ id: payload.id,
1223
+ runId: payload.runId,
1224
+ taskId: payload.taskId ?? null,
1225
+ validatorKey: payload.validatorKey,
1226
+ status: payload.status,
1227
+ output: payload.output,
1228
+ startedAt: payload.startedAt,
1229
+ completedAt: payload.completedAt ?? null
1230
+ })
1231
+ }
1232
+ };
1233
+ case "ReviewRecorded":
1234
+ return {
1235
+ status: "applied",
1236
+ snapshot: {
1237
+ ...base,
1238
+ reviews: upsertById(base.reviews, {
1239
+ id: payload.id,
1240
+ runId: payload.runId,
1241
+ taskId: payload.taskId ?? null,
1242
+ provider: payload.provider,
1243
+ mode: payload.mode,
1244
+ status: payload.status,
1245
+ summary: payload.summary ?? null,
1246
+ output: payload.output,
1247
+ createdAt: payload.createdAt,
1248
+ completedAt: payload.completedAt ?? null
1249
+ })
1250
+ }
1251
+ };
1252
+ case "ArtifactRegistered":
1253
+ return {
1254
+ status: "applied",
1255
+ snapshot: {
1256
+ ...base,
1257
+ artifacts: upsertById(base.artifacts, {
1258
+ id: payload.id,
1259
+ runId: payload.runId,
1260
+ taskId: payload.taskId ?? null,
1261
+ kind: payload.kind,
1262
+ label: payload.label,
1263
+ path: payload.path ?? null,
1264
+ url: payload.url ?? null,
1265
+ metadata: payload.metadata ?? {},
1266
+ createdAt: payload.createdAt
1267
+ })
1268
+ }
1269
+ };
1270
+ case "ApprovalRequested": {
1271
+ const run = base.runs.find((item) => item.id === payload.runId);
1272
+ const approval = {
1273
+ id: payload.requestId,
1274
+ runId: payload.runId,
1275
+ actionId: payload.actionId ?? null,
1276
+ requestKind: payload.requestKind,
1277
+ status: "pending",
1278
+ payload: payload.payload ?? null,
1279
+ createdAt: payload.createdAt,
1280
+ resolvedAt: null
1281
+ };
1282
+ return {
1283
+ status: "applied",
1284
+ snapshot: {
1285
+ ...base,
1286
+ approvals: upsertById(base.approvals, approval),
1287
+ runs: patchById(base.runs, payload.runId, (item) => ({
1288
+ ...item,
1289
+ pendingApprovalCount: (run?.pendingApprovalCount ?? 0) + 1,
1290
+ status: "waiting-approval",
1291
+ updatedAt: payload.createdAt
1292
+ }))
1293
+ }
1294
+ };
1295
+ }
1296
+ case "ApprovalResolved": {
1297
+ const approval = base.approvals.find((item) => item.id === payload.requestId && item.runId === payload.runId);
1298
+ if (!approval) {
1299
+ return { status: "applied", snapshot: base };
1300
+ }
1301
+ const run = base.runs.find((item) => item.id === payload.runId);
1302
+ const pendingApprovalCount = run ? Math.max(0, Number(run.pendingApprovalCount) - 1) : 0;
1303
+ const nextStatus = run?.status === "waiting-approval" && pendingApprovalCount === 0 ? "running" : run?.status;
1304
+ return {
1305
+ status: "applied",
1306
+ snapshot: {
1307
+ ...base,
1308
+ approvals: patchById(base.approvals, payload.requestId, (item) => ({
1309
+ ...item,
1310
+ status: "resolved",
1311
+ resolvedAt: payload.createdAt
1312
+ })),
1313
+ runs: patchById(base.runs, payload.runId, (item) => ({
1314
+ ...item,
1315
+ pendingApprovalCount,
1316
+ ...nextStatus ? { status: nextStatus } : {},
1317
+ updatedAt: payload.createdAt
1318
+ }))
1319
+ }
1320
+ };
1321
+ }
1322
+ case "UserInputRequested": {
1323
+ const run = base.runs.find((item) => item.id === payload.runId);
1324
+ return {
1325
+ status: "applied",
1326
+ snapshot: {
1327
+ ...base,
1328
+ userInputs: upsertById(base.userInputs ?? [], {
1329
+ id: payload.requestId,
1330
+ runId: payload.runId,
1331
+ status: "pending",
1332
+ payload: payload.payload,
1333
+ createdAt: payload.createdAt,
1334
+ resolvedAt: null
1335
+ }),
1336
+ runs: patchById(base.runs, payload.runId, (item) => ({
1337
+ ...item,
1338
+ pendingUserInputCount: (run?.pendingUserInputCount ?? 0) + 1,
1339
+ status: "waiting-user-input",
1340
+ updatedAt: payload.createdAt
1341
+ }))
1342
+ }
1343
+ };
1344
+ }
1345
+ case "UserInputResolved": {
1346
+ const run = base.runs.find((item) => item.id === payload.runId);
1347
+ const pendingUserInputCount = run ? Math.max(0, Number(run.pendingUserInputCount) - 1) : 0;
1348
+ const nextStatus = run?.status === "waiting-user-input" && pendingUserInputCount === 0 ? "running" : run?.status;
1349
+ return {
1350
+ status: "applied",
1351
+ snapshot: {
1352
+ ...base,
1353
+ userInputs: patchById(base.userInputs ?? [], payload.requestId, (item) => ({
1354
+ ...item,
1355
+ status: "resolved",
1356
+ resolvedAt: payload.createdAt
1357
+ })),
1358
+ runs: patchById(base.runs, payload.runId, (item) => ({
1359
+ ...item,
1360
+ pendingUserInputCount,
1361
+ ...nextStatus ? { status: nextStatus } : {},
1362
+ updatedAt: payload.createdAt
1363
+ }))
1364
+ }
1365
+ };
1366
+ }
1367
+ case "RuntimePrepared": {
1368
+ const runtime = {
1369
+ id: payload.runtimeId,
1370
+ workspaceId: payload.workspaceId,
1371
+ runId: payload.runId,
1372
+ adapterKind: payload.adapter,
1373
+ executionTarget: payload.executionTarget ?? "local",
1374
+ remoteHostId: payload.remoteHostId ?? null,
1375
+ status: "prepared",
1376
+ sandboxMode: "read-only",
1377
+ isolationMode: "none",
1378
+ workspaceDir: null,
1379
+ homeDir: null,
1380
+ tmpDir: null,
1381
+ cacheDir: null,
1382
+ logsDir: null,
1383
+ stateDir: null,
1384
+ sessionDir: null,
1385
+ sessionLogPath: null,
1386
+ pid: null,
1387
+ startedAt: null,
1388
+ updatedAt: payload.createdAt,
1389
+ exitedAt: null
1390
+ };
1391
+ return {
1392
+ status: "applied",
1393
+ snapshot: {
1394
+ ...base,
1395
+ runtimes: upsertById(base.runtimes, runtime),
1396
+ runs: patchById(base.runs, payload.runId, (run) => ({
1397
+ ...run,
1398
+ status: "preparing",
1399
+ executionTarget: payload.executionTarget ?? run.executionTarget ?? "local",
1400
+ remoteHostId: payload.remoteHostId ?? run.remoteHostId ?? null,
1401
+ updatedAt: payload.createdAt
1402
+ }))
1403
+ }
1404
+ };
1405
+ }
1406
+ case "RunRemoteLeaseClaimed":
1407
+ return {
1408
+ status: "applied",
1409
+ snapshot: {
1410
+ ...base,
1411
+ runs: patchById(base.runs, payload.runId, (run) => ({
1412
+ ...run,
1413
+ remoteHostId: payload.hostId,
1414
+ remoteLeaseId: payload.leaseId,
1415
+ remoteLeaseClaimedAt: payload.createdAt,
1416
+ updatedAt: payload.createdAt
1417
+ }))
1418
+ }
1419
+ };
1420
+ case "RunRemoteLeaseReleased":
1421
+ return {
1422
+ status: "applied",
1423
+ snapshot: {
1424
+ ...base,
1425
+ runs: patchById(base.runs, payload.runId, (run) => ({
1426
+ ...run,
1427
+ remoteLeaseId: null,
1428
+ remoteLeaseClaimedAt: null,
1429
+ updatedAt: payload.createdAt
1430
+ }))
1431
+ }
1432
+ };
1433
+ case "RuntimeAttached": {
1434
+ const tasks = payload.taskId == null ? base.tasks : patchById(base.tasks, payload.taskId, (task) => ({
1435
+ ...task,
1436
+ status: "running",
1437
+ updatedAt: payload.createdAt
1438
+ }));
1439
+ return {
1440
+ status: "applied",
1441
+ snapshot: {
1442
+ ...base,
1443
+ runs: patchById(base.runs, payload.runId, (run) => ({
1444
+ ...run,
1445
+ activeRuntimeId: payload.runtimeId,
1446
+ status: "running",
1447
+ startedAt: payload.createdAt,
1448
+ updatedAt: payload.createdAt
1449
+ })),
1450
+ runtimes: patchById(base.runtimes, payload.runtimeId, (runtime) => ({
1451
+ ...runtime,
1452
+ status: "running",
1453
+ startedAt: payload.createdAt,
1454
+ updatedAt: payload.createdAt
1455
+ })),
1456
+ tasks
1457
+ }
1458
+ };
1459
+ }
1460
+ case "RuntimeDetached":
1461
+ return {
1462
+ status: "applied",
1463
+ snapshot: {
1464
+ ...base,
1465
+ runs: patchById(base.runs, payload.runId, (run) => ({
1466
+ ...run,
1467
+ activeRuntimeId: null,
1468
+ updatedAt: payload.createdAt
1469
+ })),
1470
+ runtimes: patchById(base.runtimes, payload.runtimeId, (runtime) => ({
1471
+ ...runtime,
1472
+ status: "exited",
1473
+ exitedAt: payload.createdAt,
1474
+ updatedAt: payload.createdAt
1475
+ }))
1476
+ }
1477
+ };
1478
+ case "RuntimeMetadataUpdated":
1479
+ return {
1480
+ status: "applied",
1481
+ snapshot: {
1482
+ ...base,
1483
+ runs: patchById(base.runs, payload.runId, (run) => ({
1484
+ ...run,
1485
+ ...payload.branch !== undefined ? { branch: payload.branch } : {},
1486
+ ...payload.worktreePath !== undefined ? { worktreePath: payload.worktreePath } : {},
1487
+ updatedAt: payload.createdAt
1488
+ })),
1489
+ runtimes: patchById(base.runtimes, payload.runtimeId, (runtime) => ({
1490
+ ...runtime,
1491
+ ...payload.sandboxMode !== undefined ? { sandboxMode: payload.sandboxMode } : {},
1492
+ ...payload.isolationMode !== undefined ? { isolationMode: payload.isolationMode } : {},
1493
+ ...payload.workspaceDir !== undefined ? { workspaceDir: payload.workspaceDir } : {},
1494
+ ...payload.homeDir !== undefined ? { homeDir: payload.homeDir } : {},
1495
+ ...payload.tmpDir !== undefined ? { tmpDir: payload.tmpDir } : {},
1496
+ ...payload.cacheDir !== undefined ? { cacheDir: payload.cacheDir } : {},
1497
+ ...payload.logsDir !== undefined ? { logsDir: payload.logsDir } : {},
1498
+ ...payload.stateDir !== undefined ? { stateDir: payload.stateDir } : {},
1499
+ ...payload.sessionDir !== undefined ? { sessionDir: payload.sessionDir } : {},
1500
+ ...payload.sessionLogPath !== undefined ? { sessionLogPath: payload.sessionLogPath } : {},
1501
+ ...payload.pid !== undefined ? { pid: payload.pid } : {},
1502
+ updatedAt: payload.createdAt
1503
+ }))
1504
+ }
1505
+ };
1506
+ case "TaskStatusChanged": {
1507
+ const queue = payload.status === "queued" ? base.queue : withQueuePositions(base.queue.filter((item) => item.taskId !== payload.taskId));
1508
+ return {
1509
+ status: "applied",
1510
+ snapshot: {
1511
+ ...base,
1512
+ tasks: patchById(base.tasks, payload.taskId, (task) => ({
1513
+ ...task,
1514
+ status: payload.status,
1515
+ updatedAt: payload.createdAt
1516
+ })),
1517
+ queue
1518
+ }
1519
+ };
1520
+ }
1521
+ case "TaskEnqueued": {
1522
+ const entry = {
1523
+ taskId: payload.taskId,
1524
+ score: payload.score,
1525
+ unblockCount: 0,
1526
+ position: base.queue.length
1527
+ };
1528
+ const positionedQueue = withQueuePositions([
1529
+ ...base.queue.filter((item) => item.taskId !== payload.taskId),
1530
+ entry
1531
+ ]);
1532
+ return {
1533
+ status: "applied",
1534
+ snapshot: {
1535
+ ...base,
1536
+ tasks: patchById(base.tasks, payload.taskId, (task) => ({
1537
+ ...task,
1538
+ status: "queued",
1539
+ updatedAt: payload.createdAt
1540
+ })),
1541
+ queue: positionedQueue
1542
+ }
1543
+ };
1544
+ }
1545
+ case "RemoteEndpointRegistered": {
1546
+ const endpoint = payload.endpoint;
1547
+ if (!endpoint || !endpoint.id) {
1548
+ return { status: "ignored", snapshot: base };
1549
+ }
1550
+ return {
1551
+ status: "applied",
1552
+ snapshot: {
1553
+ ...base,
1554
+ remoteEndpoints: upsertById(base.remoteEndpoints, endpoint)
1555
+ }
1556
+ };
1557
+ }
1558
+ case "RemoteEndpointRemoved": {
1559
+ const endpointId = payload.endpointId;
1560
+ if (!endpointId) {
1561
+ return { status: "ignored", snapshot: base };
1562
+ }
1563
+ return {
1564
+ status: "applied",
1565
+ snapshot: {
1566
+ ...base,
1567
+ remoteEndpoints: removeById(base.remoteEndpoints, endpointId),
1568
+ remoteConnections: base.remoteConnections.filter((conn) => conn.endpointId !== endpointId),
1569
+ remoteOrchestrations: base.remoteOrchestrations.filter((orch) => orch.endpointId !== endpointId)
1570
+ }
1571
+ };
1572
+ }
1573
+ case "RemoteEndpointUpdated": {
1574
+ const endpointId = payload.endpointId;
1575
+ const updates = payload.updates;
1576
+ if (!endpointId || !updates) {
1577
+ return { status: "ignored", snapshot: base };
1578
+ }
1579
+ return {
1580
+ status: "applied",
1581
+ snapshot: {
1582
+ ...base,
1583
+ remoteEndpoints: patchById(base.remoteEndpoints, endpointId, (endpoint) => ({
1584
+ ...endpoint,
1585
+ ...updates
1586
+ }))
1587
+ }
1588
+ };
1589
+ }
1590
+ case "RemoteConnectionChanged": {
1591
+ const endpointId = payload.endpointId;
1592
+ const status = payload.status;
1593
+ if (!endpointId || !status) {
1594
+ return { status: "ignored", snapshot: base };
1595
+ }
1596
+ const existing = base.remoteConnections.find((conn) => conn.endpointId === endpointId);
1597
+ const connection = {
1598
+ ...existing ?? {
1599
+ endpointId,
1600
+ status,
1601
+ error: null,
1602
+ connectedAt: null,
1603
+ tokenExpiresAt: null,
1604
+ latencyMs: null,
1605
+ subscribedEvents: []
1606
+ },
1607
+ endpointId,
1608
+ status,
1609
+ ...payload.error !== undefined ? { error: payload.error ?? null } : {},
1610
+ ...status === "connected" ? { connectedAt: event.createdAt } : {}
1611
+ };
1612
+ return {
1613
+ status: "applied",
1614
+ snapshot: {
1615
+ ...base,
1616
+ remoteConnections: upsertByKey(base.remoteConnections, connection, "endpointId")
1617
+ }
1618
+ };
1619
+ }
1620
+ case "RemoteWorkspaceHydrated": {
1621
+ const endpointId = payload.endpointId;
1622
+ const workspace = payload.workspace;
1623
+ const tasks = payload.tasks;
1624
+ const graph = payload.graph;
1625
+ if (!endpointId || !workspace) {
1626
+ return { status: "ignored", snapshot: base };
1627
+ }
1628
+ return {
1629
+ status: "applied",
1630
+ snapshot: {
1631
+ ...base,
1632
+ workspaces: upsertById(base.workspaces, workspace),
1633
+ tasks: Array.isArray(tasks) ? replaceWorkspaceSlice(base.tasks, workspace.id, tasks) : base.tasks,
1634
+ graphs: graph ? upsertById(base.graphs, graph) : base.graphs
1635
+ }
1636
+ };
1637
+ }
1638
+ case "RemoteStateRefreshed": {
1639
+ const endpointId = payload.endpointId;
1640
+ const tasks = payload.tasks;
1641
+ if (!endpointId) {
1642
+ return { status: "ignored", snapshot: base };
1643
+ }
1644
+ const remoteWorkspace = base.workspaces.find((ws) => ws.id === `remote-workspace:${endpointId}`);
1645
+ return {
1646
+ status: "applied",
1647
+ snapshot: {
1648
+ ...base,
1649
+ tasks: Array.isArray(tasks) && remoteWorkspace ? replaceWorkspaceSlice(base.tasks, remoteWorkspace.id, tasks) : base.tasks
1650
+ }
1651
+ };
1652
+ }
1653
+ case "RemoteEventReceived":
1654
+ return { status: "applied", snapshot: base };
1655
+ case "RemoteOrchestrationStarted": {
1656
+ const orchestration = payload.orchestration;
1657
+ if (!orchestration || !orchestration.orchestrationId) {
1658
+ return { status: "ignored", snapshot: base };
1659
+ }
1660
+ return {
1661
+ status: "applied",
1662
+ snapshot: {
1663
+ ...base,
1664
+ remoteOrchestrations: upsertByKey(base.remoteOrchestrations, orchestration, "orchestrationId")
1665
+ }
1666
+ };
1667
+ }
1668
+ case "RemoteOrchestrationUpdated": {
1669
+ const orchestrationId = payload.orchestrationId;
1670
+ const state = payload.state;
1671
+ if (!orchestrationId) {
1672
+ return { status: "ignored", snapshot: base };
1673
+ }
1674
+ return {
1675
+ status: "applied",
1676
+ snapshot: {
1677
+ ...base,
1678
+ remoteOrchestrations: patchByKey(base.remoteOrchestrations, orchestrationId, "orchestrationId", (orch) => ({
1679
+ ...orch,
1680
+ ...isRecord(state) ? state : {}
1681
+ }))
1682
+ }
1683
+ };
1684
+ }
1685
+ }
1686
+ if (event.type === "workspace.imported") {
1687
+ return { status: "requires-resync", snapshot };
1688
+ }
1689
+ if (event.type === "task.run-linked") {
1690
+ const payload2 = isRecord(event.payload) ? event.payload : {};
1691
+ const runId = readString(payload2, "runId");
1692
+ const taskId = readString(payload2, "taskId");
1693
+ const workspaceId = readString(payload2, "workspaceId");
1694
+ if (!runId || !taskId || !workspaceId) {
1695
+ return { status: "ignored", snapshot };
1696
+ }
1697
+ const baseRun = snapshot.runs.find((run) => run.id === runId) ?? {
1698
+ id: asRunId(runId),
1699
+ workspaceId: asWorkspaceId(workspaceId),
1700
+ taskId: asTaskId(taskId),
1701
+ title: "Task run",
1702
+ runKind: "task",
1703
+ mode: "interactive",
1704
+ runtimeMode: "full-access",
1705
+ interactionMode: "default",
1706
+ status: "created",
1707
+ runtimeAdapter: "rig-native",
1708
+ model: null,
1709
+ initialPrompt: null,
1710
+ activeRuntimeId: runtimeIdFromRunId(asRunId(runId)),
1711
+ latestMessageId: null,
1712
+ pendingApprovalCount: 0,
1713
+ pendingUserInputCount: 0,
1714
+ branch: null,
1715
+ worktreePath: null,
1716
+ errorText: null,
1717
+ createdAt: event.createdAt,
1718
+ updatedAt: event.createdAt,
1719
+ startedAt: event.createdAt,
1720
+ completedAt: null
1721
+ };
1722
+ const nextSnapshot = applyRun(snapshot, {
1723
+ ...baseRun,
1724
+ workspaceId: asWorkspaceId(workspaceId),
1725
+ taskId: asTaskId(taskId),
1726
+ runKind: "task",
1727
+ runtimeAdapter: "rig-native",
1728
+ activeRuntimeId: runtimeIdFromRunId(asRunId(runId)),
1729
+ updatedAt: event.createdAt
1730
+ });
1731
+ return {
1732
+ status: "applied",
1733
+ snapshot: withSnapshotMetadata(nextSnapshot, event, {})
1734
+ };
1735
+ }
1736
+ if (event.type.startsWith("runtime.prepare.")) {
1737
+ return applySyntheticRuntimePreparation(snapshot, event);
1738
+ }
1739
+ if (event.type === "runtime.prepared") {
1740
+ return applySyntheticRuntimePrepared(snapshot, event);
1741
+ }
1742
+ if (event.type.startsWith("legacy.project.")) {
1743
+ return applyLegacyProjectEvent(snapshot, event);
1744
+ }
1745
+ if (event.type.startsWith("legacy.thread.")) {
1746
+ return applyLegacyThreadEvent(snapshot, event);
1747
+ }
1748
+ return { status: "ignored", snapshot: base };
1749
+ }
1750
+ function applyEngineEvents(snapshot, events) {
1751
+ let nextSnapshot = snapshot;
1752
+ let requiresResync = false;
1753
+ let applied = false;
1754
+ for (const event of events) {
1755
+ const result = applyEngineEvent(nextSnapshot, event);
1756
+ nextSnapshot = result.snapshot;
1757
+ if (result.status === "requires-resync") {
1758
+ requiresResync = true;
1759
+ }
1760
+ if (result.status === "applied") {
1761
+ applied = true;
1762
+ }
1763
+ }
1764
+ return {
1765
+ status: requiresResync ? "requires-resync" : applied ? "applied" : "ignored",
1766
+ snapshot: nextSnapshot
1767
+ };
1768
+ }
1769
+ function pruneQueueEntries(snapshot) {
1770
+ return snapshot.queue.filter((entry) => {
1771
+ const task = snapshot.tasks.find((candidate) => candidate.id === entry.taskId);
1772
+ return task?.status !== "running" && task?.status !== "in_progress" && task?.status !== "under_review";
1773
+ });
1774
+ }
1775
+ export {
1776
+ pruneQueueEntries,
1777
+ mapTaskStatusFromRunStatus,
1778
+ applyEngineEvents,
1779
+ applyEngineEvent
1780
+ };