| @@ -614,6 +614,66 @@ lifecycleLayer("CodexAdapterLive lifecycle", (it) => { · 613 unmodified lines |
| 614 | 614 | }), |
| 615 | 615 | ); |
| 616 | 616 | |
| 617 | + it.effect("preserves failed and declined outcomes on completed tool items", () => |
| 618 | + Effect.gen(function* () { |
| 619 | + const { adapter, runtime } = yield* startLifecycleRuntime(); |
| 620 | + const items = [ |
| 621 | + { |
| 622 | + type: "commandExecution", |
| 623 | + id: "failed-command", |
| 624 | + command: "vp test run", |
| 625 | + commandActions: [], |
| 626 | + cwd: "/tmp", |
| 627 | + exitCode: 1, |
| 628 | + status: "failed", |
| 629 | + }, |
| 630 | + { |
| 631 | + type: "mcpToolCall", |
| 632 | + id: "failed-mcp", |
| 633 | + server: "simulator", |
| 634 | + tool: "build", |
| 635 | + arguments: {}, |
| 636 | + error: { message: "Build failed" }, |
| 637 | + status: "failed", |
| 638 | + }, |
| 639 | + { |
| 640 | + type: "fileChange", |
| 641 | + id: "declined-change", |
| 642 | + changes: [], |
| 643 | + status: "declined", |
| 644 | + }, |
| 645 | + ] as const; |
| 646 | + |
| 647 | + for (const item of items) { |
| 648 | + const firstEventFiber = yield* Stream.runHead(adapter.streamEvents).pipe(Effect.forkChild); |
| 649 | + |
| 650 | + yield* runtime.emit({ |
| 651 | + id: asEventId(`evt-${item.id}`), |
| 652 | + kind: "notification", |
| 653 | + provider: ProviderDriverKind.make("codex"), |
| 654 | + createdAt: "2026-01-01T00:00:00.000Z", |
| 655 | + method: "item/completed", |
| 656 | + threadId: asThreadId("thread-1"), |
| 657 | + turnId: asTurnId("turn-1"), |
| 658 | + itemId: asItemId(item.id), |
| 659 | + payload: { |
| 660 | + completedAtMs: 1_778_000_000_000, |
| 661 | + threadId: "thread-1", |
| 662 | + turnId: "turn-1", |
| 663 | + item, |
| 664 | + }, |
| 665 | + }); |
| 666 | + |
| 667 | + const firstEvent = yield* Fiber.join(firstEventFiber); |
| 668 | + NodeAssert.equal(firstEvent._tag, "Some"); |
| 669 | + if (firstEvent._tag !== "Some" || firstEvent.value.type !== "item.completed") { |
| 670 | + return; |
| 671 | + } |
| 672 | + NodeAssert.equal(firstEvent.value.payload.status, item.status); |
| 673 | + } |
| 674 | + }), |
| 675 | + ); |
| 676 | + |
| 617 | 677 | it.effect("maps completed plan items to canonical proposed-plan completion events", () => |
| 618 | 678 | Effect.gen(function* () { |
| 619 | 679 | const { adapter, runtime } = yield* startLifecycleRuntime(); |