| @@ -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 | it.effect("maps completed plan items to canonical proposed-plan completion events", () => | 677 | it.effect("maps completed plan items to canonical proposed-plan completion events", () => |
| 618 | Effect.gen(function* () { | 678 | Effect.gen(function* () { |
| 619 | const { adapter, runtime } = yield* startLifecycleRuntime(); | 679 | const { adapter, runtime } = yield* startLifecycleRuntime(); |