authorgravatar for me@t3.ggTheo Browne <me@t3.gg> 2026-10-03 14:28:20-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-10-03 14:28:20-07:00
loga21702d0e58ad20263dc965d84472bb6d7eb38c8
treeec4194c169a9e3e0a8756f1293387402a67193f1
parentf1dcd93931b322d587a7253856f44625a6afbce0
signature Signed by PGP key B5690EEEBB952194

test(server): ACP adapter tests no longer race the prompt settle (#15330)

Takes over #14876. Co-authored-by: tris203 <admin@snappeh.com> Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

1 files changed, 80 insertions(+), 198 deletions(-)

apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.test.ts+80-198
......@@ -4925,9 +4925,13 @@ describe("AcpAdapterV2", () => {
49254925 const mockAgentPath = yield* path.fromFileUrl(
49264926 new URL("../../../scripts/acp-mock-agent.ts", import.meta.url),
49274927 );
4928 const protocolEvents = yield* Queue.bounded<EffectAcpProtocol.AcpProtocolLogEvent>(256);
49294928 const instanceId = ProviderInstanceId.make("acp-test");
4929 const promptSettled = yield* Deferred.make<void>();
49304930 const adapter = makeAcpAdapterV2({
4931 testHooks: {
4932 afterPromptSettledWithBackgroundWork: () =>
4933 Deferred.succeed(promptSettled, undefined).pipe(Effect.asVoid),
4934 },
49314935 crypto: yield* Crypto.Crypto,
49324936 instanceId,
49334937 flavor: {
......@@ -4950,7 +4954,6 @@ describe("AcpAdapterV2", () => {
49504954 childProcessSpawner,
49514955 mockAgentPath,
49524956 environment: { T3_ACP_EMIT_GENERIC_TOOL_PLACEHOLDERS: "1" },
4953 protocolEvents,
49544957 }),
49554958 },
49564959 fileSystem,
......@@ -4986,28 +4989,13 @@ describe("AcpAdapterV2", () => {
49864989 makeTurnInput({ threadId, providerThread, instanceId, runtimePolicy, now }),
49874990 );
49884991 // The still-running subagent defers finalize after session/prompt returns.
4989 yield* Stream.fromQueue(protocolEvents).pipe(
4990 Stream.filter(
4991 (event) =>
4992 event.direction === "incoming" &&
4993 event.stage === "raw" &&
4994 typeof event.payload === "string" &&
4995 event.payload.includes('"stopReason"'),
4996 ),
4997 Stream.runHead,
4998 );
4999 yield* Effect.yieldNow;
5000 yield* Effect.yieldNow;
4992 yield* Deferred.await(promptSettled);
50014993
50024994 const providerTurnId = idAllocator.derive.providerTurn({
50034995 driver: ACP_TEST_DRIVER,
50044996 nativeTurnId: acpScopedNativeId(instanceId, "mock-session-1:turn:1"),
50054997 });
5006 const interruptFiber = yield* runtime
5007 .interruptTurn({ providerThread, providerTurnId })
5008 .pipe(Effect.forkScoped);
5009 yield* TestClock.adjust("10 seconds");
5010 yield* Fiber.join(interruptFiber);
4998 yield* runtime.interruptTurn({ providerThread, providerTurnId });
50114999
50125000 let terminalStatus: string | null = null;
50135001 while (terminalStatus === null) {
......@@ -5033,10 +5021,14 @@ describe("AcpAdapterV2", () => {
50335021 const mockAgentPath = yield* path.fromFileUrl(
50345022 new URL("../../../scripts/acp-mock-agent.ts", import.meta.url),
50355023 );
5036 const protocolEvents = yield* Queue.bounded<EffectAcpProtocol.AcpProtocolLogEvent>(256);
50375024 const instanceId = ProviderInstanceId.make("acp-test");
50385025 let subagentPhase: "spawn" | "complete" = "spawn";
5026 const promptSettled = yield* Deferred.make<void>();
50395027 const adapter = makeAcpAdapterV2({
5028 testHooks: {
5029 afterPromptSettledWithBackgroundWork: () =>
5030 Deferred.succeed(promptSettled, undefined).pipe(Effect.asVoid),
5031 },
50405032 crypto: yield* Crypto.Crypto,
50415033 instanceId,
50425034 flavor: {
......@@ -5072,7 +5064,6 @@ describe("AcpAdapterV2", () => {
50725064 childProcessSpawner,
50735065 mockAgentPath,
50745066 environment: { T3_ACP_EMIT_GENERIC_TOOL_PLACEHOLDERS: "1" },
5075 protocolEvents,
50765067 }),
50775068 },
50785069 fileSystem,
......@@ -5107,28 +5098,13 @@ describe("AcpAdapterV2", () => {
51075098 yield* runtime.startTurn(
51085099 makeTurnInput({ threadId, providerThread, instanceId, runtimePolicy, now }),
51095100 );
5110 yield* Stream.fromQueue(protocolEvents).pipe(
5111 Stream.filter(
5112 (event) =>
5113 event.direction === "incoming" &&
5114 event.stage === "raw" &&
5115 typeof event.payload === "string" &&
5116 event.payload.includes('"stopReason"'),
5117 ),
5118 Stream.runHead,
5119 );
5120 yield* Effect.yieldNow;
5121 yield* Effect.yieldNow;
5101 yield* Deferred.await(promptSettled);
51225102
51235103 const firstProviderTurnId = idAllocator.derive.providerTurn({
51245104 driver: ACP_TEST_DRIVER,
51255105 nativeTurnId: acpScopedNativeId(instanceId, "mock-session-1:turn:1"),
51265106 });
5127 const interruptFiber = yield* runtime
5128 .interruptTurn({ providerThread, providerTurnId: firstProviderTurnId })
5129 .pipe(Effect.forkScoped);
5130 yield* TestClock.adjust("10 seconds");
5131 yield* Fiber.join(interruptFiber);
5107 yield* runtime.interruptTurn({ providerThread, providerTurnId: firstProviderTurnId });
51325108
51335109 let subagentTurnItemId: string | null = null;
51345110 let firstTerminalStatus: string | null = null;
......@@ -5193,10 +5169,14 @@ describe("AcpAdapterV2", () => {
51935169 const mockAgentPath = yield* path.fromFileUrl(
51945170 new URL("../../../scripts/acp-mock-agent.ts", import.meta.url),
51955171 );
5196 const protocolEvents = yield* Queue.bounded<EffectAcpProtocol.AcpProtocolLogEvent>(256);
51975172 const instanceId = ProviderInstanceId.make("acp-test");
51985173 let subagentPhase: "spawn" | "complete" = "spawn";
5174 const promptSettled = yield* Deferred.make<void>();
51995175 const adapter = makeAcpAdapterV2({
5176 testHooks: {
5177 afterPromptSettledWithBackgroundWork: () =>
5178 Deferred.succeed(promptSettled, undefined).pipe(Effect.asVoid),
5179 },
52005180 crypto: yield* Crypto.Crypto,
52015181 instanceId,
52025182 flavor: {
......@@ -5230,7 +5210,6 @@ describe("AcpAdapterV2", () => {
52305210 childProcessSpawner,
52315211 mockAgentPath,
52325212 environment: { T3_ACP_EMIT_GENERIC_TOOL_PLACEHOLDERS: "1" },
5233 protocolEvents,
52345213 }),
52355214 },
52365215 fileSystem,
......@@ -5272,28 +5251,13 @@ describe("AcpAdapterV2", () => {
52725251 yield* runtime.startTurn(
52735252 makeTurnInput({ threadId, providerThread, instanceId, runtimePolicy, now }),
52745253 );
5275 yield* Stream.fromQueue(protocolEvents).pipe(
5276 Stream.filter(
5277 (event) =>
5278 event.direction === "incoming" &&
5279 event.stage === "raw" &&
5280 typeof event.payload === "string" &&
5281 event.payload.includes('"stopReason"'),
5282 ),
5283 Stream.runHead,
5284 );
5285 yield* Effect.yieldNow;
5286 yield* Effect.yieldNow;
5254 yield* Deferred.await(promptSettled);
52875255
52885256 const firstProviderTurnId = idAllocator.derive.providerTurn({
52895257 driver: ACP_TEST_DRIVER,
52905258 nativeTurnId: acpScopedNativeId(instanceId, "mock-session-1:turn:1"),
52915259 });
5292 const interruptFiber = yield* runtime
5293 .interruptTurn({ providerThread, providerTurnId: firstProviderTurnId })
5294 .pipe(Effect.forkScoped);
5295 yield* TestClock.adjust("10 seconds");
5296 yield* Fiber.join(interruptFiber);
5260 yield* runtime.interruptTurn({ providerThread, providerTurnId: firstProviderTurnId });
52975261
52985262 let firstTerminalStatus: string | null = null;
52995263 while (firstTerminalStatus === null) {
......@@ -5352,7 +5316,6 @@ describe("AcpAdapterV2", () => {
53525316 const mockAgentPath = yield* path.fromFileUrl(
53535317 new URL("../../../scripts/acp-mock-agent.ts", import.meta.url),
53545318 );
5355 const protocolEvents = yield* Queue.bounded<EffectAcpProtocol.AcpProtocolLogEvent>(256);
53565319 const secondPromptWireReturned = yield* Deferred.make<void>();
53575320 const releaseSecondPromptCompletion = yield* Deferred.make<void>();
53585321 const instanceId = ProviderInstanceId.make("acp-test");
......@@ -5361,7 +5324,12 @@ describe("AcpAdapterV2", () => {
53615324 let subagentPhase: "spawn" | "complete" = "spawn";
53625325 type RuntimeService = AcpSessionRuntime.AcpSessionRuntime["Service"];
53635326 let sessionUpdateHandler: Parameters<RuntimeService["handleSessionUpdate"]>[0] | undefined;
5327 const promptSettled = yield* Deferred.make<void>();
53645328 const adapter = makeAcpAdapterV2({
5329 testHooks: {
5330 afterPromptSettledWithBackgroundWork: () =>
5331 Deferred.succeed(promptSettled, undefined).pipe(Effect.asVoid),
5332 },
53655333 crypto: yield* Crypto.Crypto,
53665334 instanceId,
53675335 flavor: {
......@@ -5395,7 +5363,6 @@ describe("AcpAdapterV2", () => {
53955363 childProcessSpawner,
53965364 mockAgentPath,
53975365 environment: { T3_ACP_EMIT_GENERIC_TOOL_PLACEHOLDERS: "1" },
5398 protocolEvents,
53995366 wrapRuntime: (runtime) => ({
54005367 ...runtime,
54015368 handleSessionUpdate: (handler) =>
......@@ -5456,28 +5423,13 @@ describe("AcpAdapterV2", () => {
54565423 yield* runtime.startTurn(
54575424 makeTurnInput({ threadId, providerThread, instanceId, runtimePolicy, now }),
54585425 );
5459 yield* Stream.fromQueue(protocolEvents).pipe(
5460 Stream.filter(
5461 (event) =>
5462 event.direction === "incoming" &&
5463 event.stage === "raw" &&
5464 typeof event.payload === "string" &&
5465 event.payload.includes('"stopReason"'),
5466 ),
5467 Stream.runHead,
5468 );
5469 yield* Effect.yieldNow;
5470 yield* Effect.yieldNow;
5426 yield* Deferred.await(promptSettled);
54715427
54725428 const firstProviderTurnId = idAllocator.derive.providerTurn({
54735429 driver: ACP_TEST_DRIVER,
54745430 nativeTurnId: acpScopedNativeId(instanceId, "mock-session-1:turn:1"),
54755431 });
5476 const interruptFiber = yield* runtime
5477 .interruptTurn({ providerThread, providerTurnId: firstProviderTurnId })
5478 .pipe(Effect.forkScoped);
5479 yield* TestClock.adjust("10 seconds");
5480 yield* Fiber.join(interruptFiber);
5432 yield* runtime.interruptTurn({ providerThread, providerTurnId: firstProviderTurnId });
54815433
54825434 let firstTerminalStatus: string | null = null;
54835435 while (firstTerminalStatus === null) {
......@@ -5601,7 +5553,6 @@ describe("AcpAdapterV2", () => {
56015553 const mockAgentPath = yield* path.fromFileUrl(
56025554 new URL("../../../scripts/acp-mock-agent.ts", import.meta.url),
56035555 );
5604 const protocolEvents = yield* Queue.bounded<EffectAcpProtocol.AcpProtocolLogEvent>(256);
56055556 const baseClock = yield* Clock.Clock;
56065557 const finalizationClockRead = yield* Deferred.make<void>();
56075558 const releaseFinalizationClockRead = yield* Deferred.make<void>();
......@@ -5629,7 +5580,12 @@ describe("AcpAdapterV2", () => {
56295580 let sessionUpdateHandler:
56305581 | Parameters<RuntimeService["handleSessionUpdate"]>[0]
56315582 | undefined;
5583 const promptSettled = yield* Deferred.make<void>();
56325584 const adapter = makeAcpAdapterV2({
5585 testHooks: {
5586 afterPromptSettledWithBackgroundWork: () =>
5587 Deferred.succeed(promptSettled, undefined).pipe(Effect.asVoid),
5588 },
56335589 crypto: yield* Crypto.Crypto,
56345590 instanceId,
56355591 flavor: {
......@@ -5668,7 +5624,6 @@ describe("AcpAdapterV2", () => {
56685624 childProcessSpawner,
56695625 mockAgentPath,
56705626 environment: { T3_ACP_EMIT_GENERIC_TOOL_PLACEHOLDERS: "1" },
5671 protocolEvents,
56725627 wrapRuntime: (runtime) => ({
56735628 ...runtime,
56745629 handleSessionUpdate: (handler) =>
......@@ -5719,18 +5674,7 @@ describe("AcpAdapterV2", () => {
57195674 yield* runtime.startTurn(
57205675 makeTurnInput({ threadId, providerThread, instanceId, runtimePolicy, now }),
57215676 );
5722 yield* Stream.fromQueue(protocolEvents).pipe(
5723 Stream.filter(
5724 (event) =>
5725 event.direction === "incoming" &&
5726 event.stage === "raw" &&
5727 typeof event.payload === "string" &&
5728 event.payload.includes('"stopReason"'),
5729 ),
5730 Stream.runHead,
5731 );
5732 yield* Effect.yieldNow;
5733 yield* Effect.yieldNow;
5677 yield* Deferred.await(promptSettled);
57345678 yield* TestClock.adjust("1 second");
57355679 assert.isDefined(sessionUpdateHandler, "session update handler must be wired");
57365680
......@@ -6451,14 +6395,18 @@ describe("AcpAdapterV2", () => {
64516395 const mockAgentPath = yield* path.fromFileUrl(
64526396 new URL("../../../scripts/acp-mock-agent.ts", import.meta.url),
64536397 );
6454 const protocolEvents = yield* Queue.bounded<EffectAcpProtocol.AcpProtocolLogEvent>(256);
64556398 const continuationRequests: Array<ProviderContinuationRequest> = [];
64566399 const bufferedAssistantText = "BUFFERED_WAKE_AFTER_USER_ATTACH";
64576400 const instanceId = ProviderInstanceId.make("acp-test");
64586401 let subagentPhase: "spawn" | "complete" = "spawn";
64596402 type RuntimeService = AcpSessionRuntime.AcpSessionRuntime["Service"];
64606403 let sessionUpdateHandler: Parameters<RuntimeService["handleSessionUpdate"]>[0] | undefined;
6404 const promptSettled = yield* Deferred.make<void>();
64616405 const adapter = makeAcpAdapterV2({
6406 testHooks: {
6407 afterPromptSettledWithBackgroundWork: () =>
6408 Deferred.succeed(promptSettled, undefined).pipe(Effect.asVoid),
6409 },
64626410 crypto: yield* Crypto.Crypto,
64636411 instanceId,
64646412 flavor: {
......@@ -6492,7 +6440,6 @@ describe("AcpAdapterV2", () => {
64926440 childProcessSpawner,
64936441 mockAgentPath,
64946442 environment: { T3_ACP_EMIT_GENERIC_TOOL_PLACEHOLDERS: "1" },
6495 protocolEvents,
64966443 wrapRuntime: (runtime) => ({
64976444 ...runtime,
64986445 handleSessionUpdate: (handler) =>
......@@ -6548,28 +6495,13 @@ describe("AcpAdapterV2", () => {
65486495 yield* runtime.startTurn(
65496496 makeTurnInput({ threadId, providerThread, instanceId, runtimePolicy, now }),
65506497 );
6551 yield* Stream.fromQueue(protocolEvents).pipe(
6552 Stream.filter(
6553 (event) =>
6554 event.direction === "incoming" &&
6555 event.stage === "raw" &&
6556 typeof event.payload === "string" &&
6557 event.payload.includes('"stopReason"'),
6558 ),
6559 Stream.runHead,
6560 );
6561 yield* Effect.yieldNow;
6562 yield* Effect.yieldNow;
6498 yield* Deferred.await(promptSettled);
65636499
65646500 const firstProviderTurnId = idAllocator.derive.providerTurn({
65656501 driver: ACP_TEST_DRIVER,
65666502 nativeTurnId: acpScopedNativeId(instanceId, "mock-session-1:turn:1"),
65676503 });
6568 const interruptFiber = yield* runtime
6569 .interruptTurn({ providerThread, providerTurnId: firstProviderTurnId })
6570 .pipe(Effect.forkScoped);
6571 yield* TestClock.adjust("10 seconds");
6572 yield* Fiber.join(interruptFiber);
6504 yield* runtime.interruptTurn({ providerThread, providerTurnId: firstProviderTurnId });
65736505
65746506 let firstTerminalStatus: string | null = null;
65756507 while (firstTerminalStatus === null) {
......@@ -6752,14 +6684,18 @@ describe("AcpAdapterV2", () => {
67526684 new URL("../../../scripts/acp-mock-agent.ts", import.meta.url),
67536685 );
67546686 const continuationRequests: Array<ProviderContinuationRequest> = [];
6755 const protocolEvents = yield* Queue.bounded<EffectAcpProtocol.AcpProtocolLogEvent>(256);
67566687 const promptWireReturned = yield* Deferred.make<void>();
67576688 const releasePromptCompletion = yield* Deferred.make<void>();
67586689 const instanceId = ProviderInstanceId.make("acp-test");
67596690 let subagentPhase: "spawn" | "complete" = "spawn";
67606691 type RuntimeService = AcpSessionRuntime.AcpSessionRuntime["Service"];
67616692 let sessionUpdateHandler: Parameters<RuntimeService["handleSessionUpdate"]>[0] | undefined;
6693 const promptSettled = yield* Deferred.make<void>();
67626694 const adapter = makeAcpAdapterV2({
6695 testHooks: {
6696 afterPromptSettledWithBackgroundWork: () =>
6697 Deferred.succeed(promptSettled, undefined).pipe(Effect.asVoid),
6698 },
67636699 crypto: yield* Crypto.Crypto,
67646700 instanceId,
67656701 flavor: {
......@@ -6795,7 +6731,6 @@ describe("AcpAdapterV2", () => {
67956731 childProcessSpawner,
67966732 mockAgentPath,
67976733 environment: { T3_ACP_EMIT_GENERIC_TOOL_PLACEHOLDERS: "1" },
6798 protocolEvents,
67996734 wrapRuntime: (runtime) => ({
68006735 ...runtime,
68016736 handleSessionUpdate: (handler) =>
......@@ -6875,18 +6810,13 @@ describe("AcpAdapterV2", () => {
68756810 },
68766811 });
68776812 yield* Deferred.succeed(releasePromptCompletion, undefined);
6878 yield* Effect.yieldNow;
6879 yield* Effect.yieldNow;
6813 yield* Deferred.await(promptSettled);
68806814
68816815 const firstProviderTurnId = idAllocator.derive.providerTurn({
68826816 driver: ACP_TEST_DRIVER,
68836817 nativeTurnId: acpScopedNativeId(instanceId, "mock-session-1:turn:1"),
68846818 });
6885 const interruptFiber = yield* runtime
6886 .interruptTurn({ providerThread, providerTurnId: firstProviderTurnId })
6887 .pipe(Effect.forkScoped);
6888 yield* TestClock.adjust("10 seconds");
6889 yield* Fiber.join(interruptFiber);
6819 yield* runtime.interruptTurn({ providerThread, providerTurnId: firstProviderTurnId });
68906820
68916821 let firstTerminalStatus: string | null = null;
68926822 while (firstTerminalStatus === null) {
......@@ -7223,7 +7153,6 @@ describe("AcpAdapterV2", () => {
72237153 const mockAgentPath = yield* path.fromFileUrl(
72247154 new URL("../../../scripts/acp-mock-agent.ts", import.meta.url),
72257155 );
7226 const protocolEvents = yield* Queue.bounded<EffectAcpProtocol.AcpProtocolLogEvent>(256);
72277156 const continuationRequests: Array<ProviderContinuationRequest> = [];
72287157 const instanceId = ProviderInstanceId.make("acp-test");
72297158 const childSessionId = "mock-child-session-pending-continuation";
......@@ -7231,7 +7160,12 @@ describe("AcpAdapterV2", () => {
72317160 let subagentPhase: "spawn" | "complete" = "spawn";
72327161 type RuntimeService = AcpSessionRuntime.AcpSessionRuntime["Service"];
72337162 let sessionUpdateHandler: Parameters<RuntimeService["handleSessionUpdate"]>[0] | undefined;
7163 const promptSettled = yield* Deferred.make<void>();
72347164 const adapter = makeAcpAdapterV2({
7165 testHooks: {
7166 afterPromptSettledWithBackgroundWork: () =>
7167 Deferred.succeed(promptSettled, undefined).pipe(Effect.asVoid),
7168 },
72357169 crypto: yield* Crypto.Crypto,
72367170 instanceId,
72377171 flavor: {
......@@ -7265,7 +7199,6 @@ describe("AcpAdapterV2", () => {
72657199 childProcessSpawner,
72667200 mockAgentPath,
72677201 environment: { T3_ACP_EMIT_GENERIC_TOOL_PLACEHOLDERS: "1" },
7268 protocolEvents,
72697202 wrapRuntime: (runtime) => ({
72707203 ...runtime,
72717204 handleSessionUpdate: (handler) =>
......@@ -7321,28 +7254,13 @@ describe("AcpAdapterV2", () => {
73217254 yield* runtime.startTurn(
73227255 makeTurnInput({ threadId, providerThread, instanceId, runtimePolicy, now }),
73237256 );
7324 yield* Stream.fromQueue(protocolEvents).pipe(
7325 Stream.filter(
7326 (event) =>
7327 event.direction === "incoming" &&
7328 event.stage === "raw" &&
7329 typeof event.payload === "string" &&
7330 event.payload.includes('"stopReason"'),
7331 ),
7332 Stream.runHead,
7333 );
7334 yield* Effect.yieldNow;
7335 yield* Effect.yieldNow;
7257 yield* Deferred.await(promptSettled);
73367258
73377259 const firstProviderTurnId = idAllocator.derive.providerTurn({
73387260 driver: ACP_TEST_DRIVER,
73397261 nativeTurnId: acpScopedNativeId(instanceId, "mock-session-1:turn:1"),
73407262 });
7341 const interruptFiber = yield* runtime
7342 .interruptTurn({ providerThread, providerTurnId: firstProviderTurnId })
7343 .pipe(Effect.forkScoped);
7344 yield* TestClock.adjust("10 seconds");
7345 yield* Fiber.join(interruptFiber);
7263 yield* runtime.interruptTurn({ providerThread, providerTurnId: firstProviderTurnId });
73467264
73477265 let firstTerminalStatus: string | null = null;
73487266 while (firstTerminalStatus === null) {
......@@ -7506,14 +7424,18 @@ describe("AcpAdapterV2", () => {
75067424 const mockAgentPath = yield* path.fromFileUrl(
75077425 new URL("../../../scripts/acp-mock-agent.ts", import.meta.url),
75087426 );
7509 const protocolEvents = yield* Queue.bounded<EffectAcpProtocol.AcpProtocolLogEvent>(256);
75107427 const continuationRequests: Array<ProviderContinuationRequest> = [];
75117428 const instanceId = ProviderInstanceId.make("acp-test");
75127429 const childSessionId = "mock-child-session-root-then-child";
75137430 let subagentPhase: "spawn" | "complete" = "spawn";
75147431 type RuntimeService = AcpSessionRuntime.AcpSessionRuntime["Service"];
75157432 let sessionUpdateHandler: Parameters<RuntimeService["handleSessionUpdate"]>[0] | undefined;
7433 const promptSettled = yield* Deferred.make<void>();
75167434 const adapter = makeAcpAdapterV2({
7435 testHooks: {
7436 afterPromptSettledWithBackgroundWork: () =>
7437 Deferred.succeed(promptSettled, undefined).pipe(Effect.asVoid),
7438 },
75177439 crypto: yield* Crypto.Crypto,
75187440 instanceId,
75197441 flavor: {
......@@ -7547,7 +7469,6 @@ describe("AcpAdapterV2", () => {
75477469 childProcessSpawner,
75487470 mockAgentPath,
75497471 environment: { T3_ACP_EMIT_GENERIC_TOOL_PLACEHOLDERS: "1" },
7550 protocolEvents,
75517472 wrapRuntime: (runtime) => ({
75527473 ...runtime,
75537474 handleSessionUpdate: (handler) =>
......@@ -7603,28 +7524,13 @@ describe("AcpAdapterV2", () => {
76037524 yield* runtime.startTurn(
76047525 makeTurnInput({ threadId, providerThread, instanceId, runtimePolicy, now }),
76057526 );
7606 yield* Stream.fromQueue(protocolEvents).pipe(
7607 Stream.filter(
7608 (event) =>
7609 event.direction === "incoming" &&
7610 event.stage === "raw" &&
7611 typeof event.payload === "string" &&
7612 event.payload.includes('"stopReason"'),
7613 ),
7614 Stream.runHead,
7615 );
7616 yield* Effect.yieldNow;
7617 yield* Effect.yieldNow;
7527 yield* Deferred.await(promptSettled);
76187528
76197529 const firstProviderTurnId = idAllocator.derive.providerTurn({
76207530 driver: ACP_TEST_DRIVER,
76217531 nativeTurnId: acpScopedNativeId(instanceId, "mock-session-1:turn:1"),
76227532 });
7623 const interruptFiber = yield* runtime
7624 .interruptTurn({ providerThread, providerTurnId: firstProviderTurnId })
7625 .pipe(Effect.forkScoped);
7626 yield* TestClock.adjust("10 seconds");
7627 yield* Fiber.join(interruptFiber);
7533 yield* runtime.interruptTurn({ providerThread, providerTurnId: firstProviderTurnId });
76287534
76297535 let firstTerminalStatus: string | null = null;
76307536 while (firstTerminalStatus === null) {
......@@ -7760,12 +7666,16 @@ describe("AcpAdapterV2", () => {
77607666 const mockAgentPath = yield* path.fromFileUrl(
77617667 new URL("../../../scripts/acp-mock-agent.ts", import.meta.url),
77627668 );
7763 const protocolEvents = yield* Queue.bounded<EffectAcpProtocol.AcpProtocolLogEvent>(256);
77647669 const instanceId = ProviderInstanceId.make("acp-test");
77657670 let subagentPhase: "spawn" | "complete" = "spawn";
77667671 let cancelCalled = false;
77677672 let runtimeOrdinalSeen = 0;
7673 const promptSettled = yield* Deferred.make<void>();
77687674 const adapter = makeAcpAdapterV2({
7675 testHooks: {
7676 afterPromptSettledWithBackgroundWork: () =>
7677 Deferred.succeed(promptSettled, undefined).pipe(Effect.asVoid),
7678 },
77697679 crypto: yield* Crypto.Crypto,
77707680 instanceId,
77717681 flavor: {
......@@ -7812,7 +7722,6 @@ describe("AcpAdapterV2", () => {
78127722 runtimeOrdinalSeen = Math.max(runtimeOrdinalSeen, runtimeOrdinal);
78137723 return { T3_ACP_EMIT_GENERIC_TOOL_PLACEHOLDERS: "1" };
78147724 },
7815 protocolEvents,
78167725 wrapCancel: (cancel) =>
78177726 Effect.sync(() => {
78187727 cancelCalled = true;
......@@ -7853,28 +7762,13 @@ describe("AcpAdapterV2", () => {
78537762 );
78547763 // The still-running subagent defers finalize after session/prompt returns,
78557764 // so the interrupt below hits a settled turn held open for background work.
7856 yield* Stream.fromQueue(protocolEvents).pipe(
7857 Stream.filter(
7858 (event) =>
7859 event.direction === "incoming" &&
7860 event.stage === "raw" &&
7861 typeof event.payload === "string" &&
7862 event.payload.includes('"stopReason"'),
7863 ),
7864 Stream.runHead,
7865 );
7866 yield* Effect.yieldNow;
7867 yield* Effect.yieldNow;
7765 yield* Deferred.await(promptSettled);
78687766
78697767 const firstProviderTurnId = idAllocator.derive.providerTurn({
78707768 driver: ACP_TEST_DRIVER,
78717769 nativeTurnId: acpScopedNativeId(instanceId, "mock-session-1:turn:1"),
78727770 });
7873 const interruptFiber = yield* runtime
7874 .interruptTurn({ providerThread, providerTurnId: firstProviderTurnId })
7875 .pipe(Effect.forkScoped);
7876 yield* TestClock.adjust("10 seconds");
7877 yield* Fiber.join(interruptFiber);
7771 yield* runtime.interruptTurn({ providerThread, providerTurnId: firstProviderTurnId });
78787772 assert.isFalse(
78797773 cancelCalled,
78807774 "settled soft steer must not send session/cancel (the real Grok CLI kills background subagents on cancel)",
......@@ -13725,10 +13619,14 @@ describe("AcpAdapterV2", () => {
1372513619 const mockAgentPath = yield* path.fromFileUrl(
1372613620 new URL("../../../scripts/acp-mock-agent.ts", import.meta.url),
1372713621 );
13728 const protocolEvents = yield* Queue.bounded<EffectAcpProtocol.AcpProtocolLogEvent>(256);
1372913622 const instanceId = ProviderInstanceId.make("acp-test");
1373013623 let subagentPhase: "spawn" | "complete" = "spawn";
13624 const promptSettled = yield* Deferred.make<void>();
1373113625 const adapter = makeAcpAdapterV2({
13626 testHooks: {
13627 afterPromptSettledWithBackgroundWork: () =>
13628 Deferred.succeed(promptSettled, undefined).pipe(Effect.asVoid),
13629 },
1373213630 crypto: yield* Crypto.Crypto,
1373313631 instanceId,
1373413632 flavor: {
......@@ -13762,7 +13660,6 @@ describe("AcpAdapterV2", () => {
1376213660 childProcessSpawner,
1376313661 mockAgentPath,
1376413662 environment: { T3_ACP_EMIT_GENERIC_TOOL_PLACEHOLDERS: "1" },
13765 protocolEvents,
1376613663 }),
1376713664 },
1376813665 fileSystem,
......@@ -13799,32 +13696,17 @@ describe("AcpAdapterV2", () => {
1379913696 yield* runtime.startTurn(
1380013697 makeTurnInput({ threadId, providerThread, instanceId, runtimePolicy, now }),
1380113698 );
13802 yield* Stream.fromQueue(protocolEvents).pipe(
13803 Stream.filter(
13804 (event) =>
13805 event.direction === "incoming" &&
13806 event.stage === "raw" &&
13807 typeof event.payload === "string" &&
13808 event.payload.includes('"stopReason"'),
13809 ),
13810 Stream.runHead,
13811 );
13812 yield* Effect.yieldNow;
13813 yield* Effect.yieldNow;
13699 yield* Deferred.await(promptSettled);
1381413700
1381513701 const firstProviderTurnId = idAllocator.derive.providerTurn({
1381613702 driver: ACP_TEST_DRIVER,
1381713703 nativeTurnId: acpScopedNativeId(instanceId, "mock-session-1:turn:1"),
1381813704 });
13819 const interruptFiber = yield* runtime
13820 .interruptTurn({
13821 providerThread,
13822 providerTurnId: firstProviderTurnId,
13823 requestRuntimeRestart: true,
13824 })
13825 .pipe(Effect.forkScoped);
13826 yield* TestClock.adjust("10 seconds");
13827 yield* Fiber.join(interruptFiber);
13705 yield* runtime.interruptTurn({
13706 providerThread,
13707 providerTurnId: firstProviderTurnId,
13708 requestRuntimeRestart: true,
13709 });
1382813710
1382913711 let subagentStatus: string | null = null;
1383013712 let firstTerminalStatus: string | null = null;