| ... | @@ -11614,9 +11614,22 @@ it.layer(NodeServices.layer)("server router seam", (it) => { | ... | @@ -11614,9 +11614,22 @@ it.layer(NodeServices.layer)("server router seam", (it) => { |
| 11614 | ); | 11614 | ); |
| 11615 | | 11615 | |
| 11616 | it.effect.each([ | 11616 | it.effect.each([ |
| 11617 | { caseName: "async setup scripts let the turn start before the script exits", async: true }, | 11617 | { |
| 11618 | { caseName: "sync setup scripts hold the turn until the script exits", async: false }, | 11618 | caseName: "async setup scripts let the turn start before the script exits", |
| 11619 | ])("$caseName", ({ async }) => | 11619 | async: true, |
| | 11620 | cancel: false, |
| | 11621 | }, |
| | 11622 | { |
| | 11623 | caseName: "sync setup scripts hold the turn until the script exits", |
| | 11624 | async: false, |
| | 11625 | cancel: false, |
| | 11626 | }, |
| | 11627 | { |
| | 11628 | caseName: "cancelling worktree setup publishes its outcome and cleans up the thread", |
| | 11629 | async: false, |
| | 11630 | cancel: true, |
| | 11631 | }, |
| | 11632 | ])("$caseName", ({ async, cancel }) => |
| 11620 | Effect.gen(function* () { | 11633 | Effect.gen(function* () { |
| 11621 | const dispatchedCommands: Array<OrchestrationCommand> = []; | 11634 | const dispatchedCommands: Array<OrchestrationCommand> = []; |
| 11622 | const scriptExit = yield* Deferred.make<void>(); | 11635 | const scriptExit = yield* Deferred.make<void>(); |
| ... | @@ -11750,6 +11763,26 @@ it.layer(NodeServices.layer)("server router seam", (it) => { | ... | @@ -11750,6 +11763,26 @@ it.layer(NodeServices.layer)("server router seam", (it) => { |
| 11750 | assert.equal(stageStatus(running, "agent"), "pending"); | 11763 | assert.equal(stageStatus(running, "agent"), "pending"); |
| 11751 | assert.isFalse(turnStarted()); | 11764 | assert.isFalse(turnStarted()); |
| 11752 | | 11765 | |
| | 11766 | if (cancel) { |
| | 11767 | const cancelled = yield* Effect.scoped( |
| | 11768 | withWsRpcClient(wsUrl, (client) => client[WS_METHODS.worktreeSetupCancel]({ threadId })), |
| | 11769 | ); |
| | 11770 | assert.isTrue(cancelled.cancelled); |
| | 11771 | assertTrue(dispatchedCommands.some((command) => command.type === "thread.delete")); |
| | 11772 | const outcome = dispatchedCommands.findLast( |
| | 11773 | (command) => |
| | 11774 | command.type === "thread.activity.append" && command.activity.kind === "worktree-setup", |
| | 11775 | ); |
| | 11776 | assertTrue(outcome?.type === "thread.activity.append"); |
| | 11777 | assert.propertyVal(outcome.activity.payload, "phase", "cancelled"); |
| | 11778 | const result = yield* Fiber.join(dispatchFiber).pipe(Effect.result); |
| | 11779 | assertTrue(result._tag === "Failure"); |
| | 11780 | assert.propertyVal(result.failure, "message", "Worktree setup cancelled."); |
| | 11781 | assert.propertyVal(result.failure, "bootstrapThreadDisposition", "deleted"); |
| | 11782 | assert.isFalse(turnStarted()); |
| | 11783 | return; |
| | 11784 | } |
| | 11785 | |
| 11753 | // The client that sent the message goes away mid-setup (a reload or a | 11786 | // The client that sent the message goes away mid-setup (a reload or a |
| 11754 | // dropped socket). The bootstrap belongs to the server, not the | 11787 | // dropped socket). The bootstrap belongs to the server, not the |
| 11755 | // connection: the thread already exists for every client, so it must | 11788 | // connection: the thread already exists for every client, so it must |