History

fix(web): stop marking mixed tool runs as failed (#7893)

Theo Browne committed 2 months agov0.0.34-nightly.20260822.1159

Commit ce912841 parent 11f05137 files · +230 −6Browse files

Showing the changes to apps/server/src (3 of 7 files). Show all

Unified · Split

3 files
  1. apps/server/src/orchestration/ActivityPayloadProjection.ts +8 −2
  2. apps/server/src/provider/Layers/CodexAdapter.test.ts +60 −0
  3. apps/server/src/provider/Layers/CodexAdapter.ts +3 −1
apps/server/src/orchestration/ActivityPayloadProjection.ts+8−2
@@ -342,11 +342,17 @@ export function projectActivityPayload( · 341 unmodified lines
342342 return activity;
343343 }
344344
345+ const itemStatus = asRecord(data.item)?.status;
346+ const projectedPayload =
347+ payload.status === "completed" && (itemStatus === "failed" || itemStatus === "declined")
348+ ? { ...payload, status: itemStatus }
349+ : payload;
350+
345351 if (payload.itemType === "mcp_tool_call") {
346352 return {
347353 ...activity,
348354 payload: {
349− ...payload,
355+ ...projectedPayload,
350356 data: projectMcpToolCallData(data),
351357 },
352358 };
@@ -384,7 +390,7 @@ export function projectActivityPayload( · 31 unmodified lines
384390 return {
385391 ...activity,
386392 payload: {
387− ...payload,
393+ ...projectedPayload,
388394 data: projectedData,
389395 },
390396 };
apps/server/src/provider/Layers/CodexAdapter.test.ts+60−0
@@ -614,6 +614,66 @@ lifecycleLayer("CodexAdapterLive lifecycle", (it) => { · 613 unmodified lines
614614 }),
615615 );
616616
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+
617677 it.effect("maps completed plan items to canonical proposed-plan completion events", () =>
618678 Effect.gen(function* () {
619679 const { adapter, runtime } = yield* startLifecycleRuntime();
apps/server/src/provider/Layers/CodexAdapter.ts+3−1
@@ -480,7 +480,9 @@ function mapItemLifecycle( · 479 unmodified lines
480480 lifecycle === "item.started"
481481 ? "inProgress"
482482 : lifecycle === "item.completed"
483− ? "completed"
483+ ? "status" in item && (item.status === "failed" || item.status === "declined")
484+ ? item.status
485+ : "completed"
484486 : undefined;
485487
486488 return {

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t