From 1d9e75452a1c2fe86590c864defd62fbe7f7d328 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Sat, 29 Aug 2026 15:15:46 -0700 Subject: [PATCH] fix(orchestration): persist linked pull requests --- .../src/orchestration-v2/Orchestrator.ts | 3 + .../src/orchestration-v2/runtimeLayer.test.ts | 61 +++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/apps/server/src/orchestration-v2/Orchestrator.ts b/apps/server/src/orchestration-v2/Orchestrator.ts index 4e2e8f08fcdb..88fe7598325f 100644 --- a/apps/server/src/orchestration-v2/Orchestrator.ts +++ b/apps/server/src/orchestration-v2/Orchestrator.ts @@ -1712,6 +1712,9 @@ const makeOrchestrator = Effect.fn("orchestrationV2.Orchestrator.layer")(functio ...(command.title === undefined ? {} : { title: command.title }), ...(command.branch === undefined ? {} : { branch: command.branch }), ...(command.worktreePath === undefined ? {} : { worktreePath: command.worktreePath }), + ...(command.linkedPullRequest === undefined + ? {} + : { linkedPullRequest: command.linkedPullRequest }), // regenerateTitle: true arms the in-flight marker; a landing title // or an explicit false (generation failed/abandoned) clears it. ...(command.regenerateTitle === true diff --git a/apps/server/src/orchestration-v2/runtimeLayer.test.ts b/apps/server/src/orchestration-v2/runtimeLayer.test.ts index 8987ea2bc50e..03c48b1b4d65 100644 --- a/apps/server/src/orchestration-v2/runtimeLayer.test.ts +++ b/apps/server/src/orchestration-v2/runtimeLayer.test.ts @@ -1076,6 +1076,67 @@ it.layer(TestLayer)("OrchestrationV2LayerLive lifecycle", (it) => { }), ); + it.effect("persists linked pull requests through projection rebuilds and unlinking", () => + Effect.gen(function* () { + const orchestrator = yield* OrchestratorV2; + const maintenance = yield* ProjectionMaintenanceV2; + const threadId = ThreadId.make("runtime-layer-linked-pull-request-thread"); + const linkedPullRequest = { + projectId: ProjectId.make("runtime-layer-linked-pull-request-project"), + repository: "pingdotgg/t3code", + number: 8160, + url: "https://github.com/pingdotgg/t3code/pull/8160", + } as const; + + yield* orchestrator.dispatch({ + type: "thread.create", + createdBy: "user", + creationSource: "web", + commandId: CommandId.make("runtime-layer-linked-pull-request-create"), + threadId, + projectId: linkedPullRequest.projectId, + title: "Linked pull request thread", + modelSelection, + runtimeMode: "full-access", + interactionMode: "default", + branch: null, + worktreePath: null, + }); + yield* orchestrator.dispatch({ + type: "thread.metadata.update", + commandId: CommandId.make("runtime-layer-linked-pull-request-link"), + threadId, + linkedPullRequest, + }); + + assert.deepEqual( + (yield* orchestrator.getThreadProjection(threadId)).thread.linkedPullRequest, + linkedPullRequest, + ); + const linkedShell = yield* orchestrator.getThreadShell(threadId); + assert.isNotNull(linkedShell); + assert.deepEqual(linkedShell.linkedPullRequest, linkedPullRequest); + + const rebuilt = yield* maintenance.rebuild; + assert.isTrue(rebuilt.valid); + assert.deepEqual( + (yield* orchestrator.getThreadProjection(threadId)).thread.linkedPullRequest, + linkedPullRequest, + ); + + yield* orchestrator.dispatch({ + type: "thread.metadata.update", + commandId: CommandId.make("runtime-layer-linked-pull-request-unlink"), + threadId, + linkedPullRequest: null, + }); + assert.isNull((yield* orchestrator.getThreadProjection(threadId)).thread.linkedPullRequest); + const unlinkedShell = yield* orchestrator.getThreadShell(threadId); + assert.isNotNull(unlinkedShell); + assert.isNull(unlinkedShell.linkedPullRequest); + }), + ); + it.effect("persists rejected command receipts across retries", () => Effect.gen(function* () { const orchestrator = yield* OrchestratorV2;