authorgravatar for maria@kuuro.netmaria <maria@kuuro.net> 2026-09-02 01:19:48-04:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-02 01:19:48-04:00
logc15735dd8894f829ce396c922ce4b5a48073f05a
treef49662a6a5ce16139a0d2ed26c74b5dc8add0244
parent133db22fae34cfb2e62a806e99d22674b0376f35
signature Signed by PGP key B5690EEEBB952194

fix(chat): replace failed tools with thinking (#9165)

Co-authored-by: maria-rcks <254055478+maria-rcks@users.noreply.github.com>

2 files changed, 16 insertions(+), 4 deletions(-)

apps/web/src/components/chat/MessagesTimeline.logic.test.ts+8-1
......@@ -1411,7 +1411,9 @@ describe("deriveMessagesTimelineRows", () => {
14111411 );
14121412
14131413 it("reuses one activity row for initial thinking and the latest tool", () => {
1414 const deriveRows = (toolLifecycleStatus: "inProgress" | "completed" | "declined" | null) =>
1414 const deriveRows = (
1415 toolLifecycleStatus: "inProgress" | "completed" | "failed" | "declined" | null,
1416 ) =>
14151417 deriveMessagesTimelineRows({
14161418 timelineEntries:
14171419 toolLifecycleStatus === null
......@@ -1430,6 +1432,7 @@ describe("deriveMessagesTimelineRows", () => {
14301432 requestKind: "command",
14311433 tone: "tool" as const,
14321434 toolLifecycleStatus,
1435 ...(toolLifecycleStatus === "inProgress" ? { detail: "exit code 1" } : {}),
14331436 },
14341437 },
14351438 ],
......@@ -1448,6 +1451,7 @@ describe("deriveMessagesTimelineRows", () => {
14481451 const initialRows = deriveRows(null);
14491452 const runningRows = deriveRows("inProgress");
14501453 const completedRows = deriveRows("completed");
1454 const failedRows = deriveRows("failed");
14511455 const declinedRows = deriveRows("declined");
14521456 const initialActivityRow = initialRows.find((row) => row.id === "live-activity-row");
14531457 const runningActivityRow = runningRows.find((row) => row.id === "live-activity-row");
......@@ -1456,11 +1460,14 @@ describe("deriveMessagesTimelineRows", () => {
14561460 expect(initialActivityRow).toMatchObject({ kind: "thinking" });
14571461 expect(runningActivityRow).toMatchObject({ kind: "work-live", active: true });
14581462 expect(completedActivityRow).toMatchObject({ kind: "work-live", active: true });
1463 expect(failedRows.some((row) => row.kind === "work-live")).toBe(false);
1464 expect(failedRows.at(-1)).toMatchObject({ kind: "thinking", id: "live-activity-row" });
14591465 expect(declinedRows.find((row) => row.kind === "work-live")).toMatchObject({ active: false });
14601466 expect(declinedRows.at(-1)).toMatchObject({ kind: "thinking", id: "live-activity-row" });
14611467 expect(initialRows.filter((row) => row.id === "live-activity-row")).toHaveLength(1);
14621468 expect(runningRows.filter((row) => row.id === "live-activity-row")).toHaveLength(1);
14631469 expect(completedRows.filter((row) => row.id === "live-activity-row")).toHaveLength(1);
1470 expect(failedRows.filter((row) => row.id === "live-activity-row")).toHaveLength(1);
14641471 expect(declinedRows.filter((row) => row.id === "live-activity-row")).toHaveLength(1);
14651472 });
14661473
apps/web/src/components/chat/MessagesTimeline.logic.ts+8-3
......@@ -691,13 +691,18 @@ export function deriveMessagesTimelineRows(input: {
691691 const latestRunningToolEntry = visibleActiveToolEntries.findLast((entry) =>
692692 workEntryIsActiveTurnActivity(entry.entry),
693693 );
694 const latestToolFailed =
695 latestRunningToolEntry === undefined &&
696 latestVisibleToolEntry !== undefined &&
697 latestVisibleToolEntry.entry.toolLifecycleStatus !== "declined" &&
698 workEntryDisplayIndicatesToolFailure(latestVisibleToolEntry.entry);
694699 const latestToolKeepsActivityLive =
695700 latestRunningToolEntry !== undefined ||
696701 (latestVisibleToolEntry !== undefined &&
697702 workEntryIndicatesToolSuccess(latestVisibleToolEntry.entry));
698703 const activeWorkPlacementEntryId = latestVisibleToolEntry?.id;
699704 const activeWorkRow =
700 activeWorkAnchor && latestVisibleToolEntry
705 activeWorkAnchor && latestVisibleToolEntry && !latestToolFailed
701706 ? (() => {
702707 const groupId = workGroupId(activeWorkAnchor.id, activeWorkAnchor.entry);
703708 return {
......@@ -715,7 +720,7 @@ export function deriveMessagesTimelineRows(input: {
715720 })()
716721 : null;
717722 const activeWorkEntryIds = new Set(
718 activeWorkRow === null ? [] : activeToolEntries.map((entry) => entry.id),
723 activeWorkRow !== null || latestToolFailed ? activeToolEntries.map((entry) => entry.id) : [],
719724 );
720725 const appendWorkingRow = () => {
721726 nextRows.push({
......@@ -912,7 +917,7 @@ export function deriveMessagesTimelineRows(input: {
912917 if (input.isWorking && activeTurnHeaderIndex === input.timelineEntries.length) {
913918 appendWorkingRow();
914919 }
915 if (input.isWorking && !hasActivityRow) {
920 if (input.isWorking && (!hasActivityRow || latestToolFailed)) {
916921 nextRows.push({
917922 kind: "thinking",
918923 id: LIVE_ACTIVITY_ROW_ID,