Skip to content

feat(platform/copilot): message timestamps + accurate thought-for time - #12890

Merged
majdyz merged 20 commits into
devfrom
feat/copilot-message-timestamps
Apr 23, 2026
Merged

feat(platform/copilot): message timestamps + accurate thought-for time#12890
majdyz merged 20 commits into
devfrom
feat/copilot-message-timestamps

Conversation

@majdyz

@majdyz majdyz commented Apr 23, 2026

Copy link
Copy Markdown
Contributor

Why

The "Thought for 1m 46s" label under assistant replies has been misleading
because the backend persists the whole-turn wall clock (from turn start to
stream end) — which includes tool execution, browser sessions, graph runs,
etc. Users also had no way to see when a message was actually sent / received.

What

  • Per-message timestampsChatMessage.created_at (already on the DB row)
    is now serialised through the pydantic model and the SessionDetailResponse,
    then plumbed into the UI. Hovering the "Thought for X" label now shows the
    absolute local date/time via a tooltip.
  • Accurate reasoning duration — new ChatMessage.reasoningDurationMs
    column. Backend accumulates time between reasoning-start and
    reasoning-end SSE events inside publish_chunk (via the session meta
    hash). mark_session_completed reads the total and persists it alongside
    the existing durationMs. Frontend prefers reasoning_duration_ms when
    present, falls back to duration_ms for legacy rows.

How

  • schema.prisma gains reasoningDurationMs Int?; migration
    20260423120000_add_reasoning_duration_ms adds the column.
  • publish_chunk gains a side-effect that writes reasoning_started_at /
    reasoning_ms_total into the existing per-session Redis meta hash when
    reasoning events pass through. No extra IO path, no extra Redis key.
  • set_turn_duration accepts an optional reasoning_duration_ms arg and
    patches both the DB row and the cached session in place, mirroring the
    existing behaviour for duration_ms.
  • Frontend: convertChatSessionMessagesToUiMessages now returns
    durations, reasoningDurations, and timestamps maps. TurnStatsBar
    picks the best available value and wraps the label in the design-system
    BaseTooltip so hover reveals the local timestamp.

Test plan

  • poetry run pytest backend/copilot/db_test.py::test_set_turn_duration_*
  • poetry run pytest backend/copilot/stream_registry_test.py
  • pnpm format / pnpm lint / pnpm types (copilot area)
  • pnpm test:unit src/app/\(platform\)/copilot — 705 tests pass (4 pre-existing jszip module resolution failures unrelated to this change)
  • Manual: open a session with a long tool run and confirm the new
    "Thought for X" reflects only reasoning time (falls back for old rows)
    and the tooltip surfaces the local timestamp.

Exposes per-message `created_at` to the copilot UI and computes "Thought for X"
from actual reasoning block durations instead of whole-turn wall clock.

Why: the "Thought for 1m 46s" label conflates real model thinking with tool
execution time (browser clicks, graph runs, etc.), so the number felt
misleading. Users also had no way to see when a message was actually sent.

What:
- DB: new `ChatMessage.reasoningDurationMs` column (migration included) to
  persist the reasoning-only duration independently of the full turn clock.
- Backend: `publish_chunk` watches `reasoning-start`/`reasoning-end` SSE
  events and accumulates elapsed time into the session meta hash.
  `mark_session_completed` reads the total and persists it alongside the
  existing `durationMs`. `ChatMessage` pydantic model now also carries
  `created_at` sourced from the Prisma row.
- Frontend: converter collects `created_at`/`reasoning_duration_ms` maps and
  plumbs them down to `TurnStatsBar`, which prefers the reasoning duration
  and falls back to the old whole-turn value for legacy rows. The "Thought
  for X" label is wrapped in a tooltip that shows the full local date/time.
@majdyz
majdyz requested a review from a team as a code owner April 23, 2026 00:29
@majdyz
majdyz requested review from Bentlybro and kcze and removed request for a team April 23, 2026 00:29
@github-project-automation github-project-automation Bot moved this to 🆕 Needs initial review in AutoGPT development kanban Apr 23, 2026
@github-actions github-actions Bot added platform/frontend AutoGPT Platform - Front end platform/backend AutoGPT Platform - Back end labels Apr 23, 2026
@coderabbitai

coderabbitai Bot commented Apr 23, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds per-turn reasoning-duration and message timestamp capture: DB schema + migration, Redis-based reasoning-time accumulation and conditional persistence, backend API signature update, tests, and frontend plumbing to surface reasoningDurationMs and createdAt timestamps to the UI.

Changes

Cohort / File(s) Summary
Database Schema & Migration
autogpt_platform/backend/schema.prisma, autogpt_platform/backend/migrations/20260423120000_add_reasoning_duration_ms/migration.sql
Add nullable reasoningDurationMs Int column to ChatMessage via migration.
Backend Model & Persistence
autogpt_platform/backend/backend/copilot/model.py, autogpt_platform/backend/backend/copilot/db.py, autogpt_platform/backend/backend/copilot/db_test.py
Extend ChatMessage with reasoning_duration_ms and created_at; set_turn_duration(...) gains optional reasoning_duration_ms and conditionally persists it; test verifies Redis cache and DB both reflect reasoningDurationMs.
Backend Session Tracking
autogpt_platform/backend/backend/copilot/stream_registry.py, autogpt_platform/backend/backend/copilot/stream_registry_test.py
Accumulate reasoning start/end timestamps in Redis (reasoning_started_at, reasoning_ms_total), snapshot on session completion, forward reasoning total to set_turn_duration, and add tests for start/end/reset and edge cases.
Frontend Conversion & Hooks
autogpt_platform/frontend/src/app/(platform)/copilot/helpers/convertChatSessionToUiMessages.ts, .../useLoadMoreMessages.ts, .../useChatSession.ts, .../useCopilotPage.ts, .../__tests__/*
Extract reasoning_duration_ms and created_at into two new Maps (reasoningDurations, timestamps); hooks and pagination merge paged + session maps and expose historicalReasoningDurations and messageTimestamps; tests updated.
Frontend Props & UI Rendering
autogpt_platform/frontend/src/app/(platform)/copilot/CopilotPage.tsx, .../components/ChatContainer/ChatContainer.tsx, .../components/ChatMessagesContainer/ChatMessagesContainer.tsx, .../components/JobStatsBar/TurnStatsBar.tsx
Propagate new maps through component tree; TurnStatsBar accepts reasoningDurationMs and timestamp, and updates display logic to prefer live elapsed → reasoningDuration → durationMs and render localized timestamp/tooltip.
Tests: Integration & Unit
autogpt_platform/backend/backend/copilot/db_test.py, autogpt_platform/backend/backend/copilot/stream_registry_test.py, autogpt_platform/frontend/src/app/(platform)/copilot/helpers/__tests__/convertChatSessionToUiMessages.test.ts, .../__tests__/*
New/updated tests cover DB+Redis persistence for reasoning durations, stream_registry reasoning start/end/reset behavior, and timestamp extraction/conversion in frontend helpers.

Sequence Diagram(s)

sequenceDiagram
    participant UI as Frontend (UI)
    participant Hooks as Frontend Hooks
    participant API as Backend API
    participant Registry as Stream Registry
    participant Redis as Redis Session Meta
    participant DB as Prisma/Database

    Note over UI,DB: Hydration of historical messages + metadata
    UI->>Hooks: request session/messages
    Hooks->>API: fetch session messages
    API-->>Hooks: rows including reasoningDurationMs, createdAt
    Hooks-->>UI: hydrated messages + reasoningDurations & timestamps Maps

    Note over Registry,Redis: Live reasoning accumulation during streaming
    Registry->>Redis: StreamReasoningStart -> set `reasoning_started_at`
    Registry->>Redis: StreamReasoningEnd -> compute elapsed, INCR `reasoning_ms_total`, clear `reasoning_started_at`

    Note over Registry,DB: Persist on session completion
    Registry->>Redis: read `reasoning_ms_total` on mark_session_completed
    Registry->>API: call set_turn_duration(session_id, duration_ms, reasoning_duration_ms)
    API->>DB: persist `durationMs` and optional `reasoningDurationMs` on ChatMessage
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • Bentlybro
  • kcze

Poem

🐇 I hopped through Redis, timing each thought,
I tallied the pauses the model had wrought,
From stream to DB each millisecond placed,
Now UI and logs show the reasoning traced,
A rabbit's small ledger—neat and taut. 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 43.24% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main changes: adding message timestamps and fixing the 'thought-for' time to reflect accurate reasoning duration instead of whole-turn wall-clock time.
Description check ✅ Passed The description thoroughly explains the 'Why', 'What', and 'How' of the changes, covering per-message timestamps, accurate reasoning duration tracking, implementation details, and test coverage validation.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/copilot-message-timestamps

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

github-actions Bot commented Apr 23, 2026

Copy link
Copy Markdown
Contributor

🔍 PR Overlap Detection

This check compares your PR against all other open PRs targeting the same branch to detect potential merge conflicts early.

🔴 Merge Conflicts Detected

The following PRs have been tested and will have merge conflicts if merged after this PR. Consider coordinating with the authors.

  • fix(copilot): prevent 524 timeout on chat deletion by deferring cleanup #12668 (Otto-AGPT · updated 6d ago)

    • autogpt_platform/backend/backend/api/features/library/db.py (5 conflicts, ~67 lines)
    • autogpt_platform/backend/backend/api/features/library/model.py (1 conflict, ~4 lines)
    • autogpt_platform/backend/backend/api/features/subscription_routes_test.py (5 conflicts, ~376 lines)
    • autogpt_platform/backend/backend/api/features/v1.py (9 conflicts, ~88 lines)
    • autogpt_platform/backend/backend/copilot/baseline/service.py (2 conflicts, ~15 lines)
    • autogpt_platform/backend/backend/copilot/model_test.py (1 conflict, ~5 lines)
    • autogpt_platform/backend/backend/copilot/prompting.py (1 conflict, ~5 lines)
    • autogpt_platform/backend/backend/copilot/sdk/service.py (3 conflicts, ~51 lines)
    • autogpt_platform/backend/backend/copilot/transcript.py (1 conflict, ~11 lines)
    • autogpt_platform/backend/backend/data/credit.py (5 conflicts, ~727 lines)
    • autogpt_platform/backend/backend/data/credit_subscription_test.py (14 conflicts, ~1333 lines)
    • autogpt_platform/frontend/src/app/(platform)/copilot/components/PulseChips/usePulseChips.ts (1 conflict, ~13 lines)
    • autogpt_platform/frontend/src/app/(platform)/copilot/components/usageHelpers.ts (1 conflict, ~9 lines)
    • autogpt_platform/frontend/src/app/(platform)/library/components/AgentBriefingPanel/BriefingTabContent.tsx (9 conflicts, ~147 lines)
    • autogpt_platform/frontend/src/app/(platform)/library/components/AgentBriefingPanel/StatsGrid.tsx (2 conflicts, ~9 lines)
    • autogpt_platform/frontend/src/app/(platform)/library/components/ContextualActionButton/ContextualActionButton.tsx (2 conflicts, ~12 lines)
    • autogpt_platform/frontend/src/app/(platform)/library/components/SitrepItem/SitrepItem.tsx (2 conflicts, ~15 lines)
    • autogpt_platform/frontend/src/app/(platform)/library/components/SitrepItem/useSitrepItems.ts (4 conflicts, ~97 lines)
    • autogpt_platform/frontend/src/app/(platform)/library/hooks/useAgentStatus.ts (2 conflicts, ~10 lines)
    • autogpt_platform/frontend/src/app/(platform)/library/hooks/useLibraryFleetSummary.ts (7 conflicts, ~57 lines)
    • autogpt_platform/frontend/src/app/(platform)/library/types.ts (1 conflict, ~4 lines)
    • autogpt_platform/frontend/src/app/(platform)/profile/(user)/credits/components/SubscriptionTierSection/SubscriptionTierSection.tsx (9 conflicts, ~159 lines)
    • autogpt_platform/frontend/src/app/(platform)/profile/(user)/credits/components/SubscriptionTierSection/__tests__/SubscriptionTierSection.test.tsx (7 conflicts, ~256 lines)
    • autogpt_platform/frontend/src/app/(platform)/profile/(user)/credits/components/SubscriptionTierSection/useSubscriptionTierSection.ts (2 conflicts, ~48 lines)
    • autogpt_platform/frontend/src/app/api/openapi.json (1 conflict, ~23 lines)
    • docs/integrations/block-integrations/misc.md (1 conflict, ~5 lines)
  • fix(frontend/copilot): fix streaming reconnect races, hydration ordering, and reasoning split #12813 (0ubbe · updated 2h ago)

    • 📁 autogpt_platform/frontend/src/app/(platform)/copilot/
      • CopilotPage.tsx (2 conflicts, ~163 lines)
      • __tests__/CopilotPage.test.tsx (1 conflict, ~19 lines)
      • __tests__/useCopilotPage.test.ts (2 conflicts, ~51 lines)
      • components/ChatMessagesContainer/ChatMessagesContainer.tsx (1 conflict, ~33 lines)
      • useCopilotPage.ts (1 conflict, ~23 lines)
  • feat(frontend): redesign sidebar layout with improved UX #12829 (Abhi1992002 · updated 6d ago)

    • 📁 autogpt_platform/frontend/src/
      • app/(platform)/copilot/CopilotPage.tsx (1 conflict, ~101 lines)
      • app/(platform)/layout.tsx (1 conflict, ~22 lines)
      • components/layout/Navbar/Navbar.tsx (1 conflict, ~30 lines)
      • tests/agent-activity.spec.ts (deleted here, modified there)
      • tests/pages/library.page.ts (deleted here, modified there)
      • tests/signin.spec.ts (deleted here, modified there)
  • feat(platform): estimate CoPilot turn cost and require approval for high-cost requests #12877 (Rushi-Balapure · updated 1d ago)

    • 📁 autogpt_platform/backend/backend/
      • api/features/chat/routes.py (2 conflicts, ~32 lines)
      • util/feature_flag.py (1 conflict, ~8 lines)

🟢 Low Risk — File Overlap Only

These PRs touch the same files but different sections (click to expand)

Summary: 4 conflict(s), 0 medium risk, 4 low risk (out of 8 PRs with file overlap)


Auto-generated on push. Ignores: openapi.json, lock files.

Comment thread autogpt_platform/backend/backend/copilot/stream_registry.py Outdated
Comment thread autogpt_platform/backend/backend/copilot/db.py
Comment thread autogpt_platform/backend/backend/copilot/stream_registry.py Outdated
Comment thread autogpt_platform/backend/backend/copilot/stream_registry.py Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
autogpt_platform/backend/backend/copilot/stream_registry.py (1)

187-199: ⚠️ Potential issue | 🟠 Major

Reset reasoning timing state when a new turn starts.

meta_key is keyed by session_id, so multiple turns in the same chat reuse the same Redis hash. Since create_session() only overwrites the core fields, a previous turn’s reasoning_ms_total can carry into the next turn and be persisted on the new assistant message, inflating “Thought for X”.

🐛 Proposed fix
-    await redis.hset(  # type: ignore[misc]
-        meta_key,
-        mapping={
-            "session_id": session_id,
-            "user_id": user_id or "",
-            "tool_call_id": tool_call_id,
-            "tool_name": tool_name,
-            "turn_id": turn_id,
-            "blocking": "1" if blocking else "0",
-            "status": session.status,
-            "created_at": session.created_at.isoformat(),
-        },
-    )
+    async with redis.pipeline(transaction=True) as pipe:
+        pipe.hset(  # type: ignore[misc]
+            meta_key,
+            mapping={
+                "session_id": session_id,
+                "user_id": user_id or "",
+                "tool_call_id": tool_call_id,
+                "tool_name": tool_name,
+                "turn_id": turn_id,
+                "blocking": "1" if blocking else "0",
+                "status": session.status,
+                "created_at": session.created_at.isoformat(),
+                "reasoning_ms_total": "0",
+            },
+        )
+        pipe.hdel(meta_key, "reasoning_started_at")  # type: ignore[misc]
+        await pipe.execute()

As per coding guidelines, “Use transaction=True for Redis pipelines to ensure atomicity on multi-step operations.”

Also applies to: 227-260, 937-957

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@autogpt_platform/backend/backend/copilot/stream_registry.py` around lines 187
- 199, Reset the per-turn reasoning timing state when creating a new turn: when
writing to the Redis hash keyed by meta_key (the block that calls redis.hset in
create_session()/session creation), explicitly set/reset "reasoning_ms_total"
(and any per-turn timing fields) to "0" (or empty) so previous turn timings
don't carry over; also perform multi-step updates using redis pipelines with
transaction=True to ensure atomicity as per guidelines (apply the same fixes in
the other similar blocks around the 227-260 and 937-957 regions).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@autogpt_platform/backend/backend/copilot/db.py`:
- Around line 640-646: The local variable data is currently typed as dict[str,
int] and causes a linter suppressor on the PrismaChatMessage.prisma().update
call; change data's type to ChatMessageUpdateInput and build it to match that
TypedDict (include "durationMs": duration_ms and conditionally set
"reasoningDurationMs" when reasoning_duration_ms is not None) so you can remove
the # type: ignore[arg-type]; update the code around
PrismaChatMessage.prisma().update(where={"id": last_msg.id}, data=data) to pass
the properly typed ChatMessageUpdateInput instead.

In
`@autogpt_platform/frontend/src/app/`(platform)/copilot/components/JobStatsBar/TurnStatsBar.tsx:
- Around line 74-83: The Tooltip in TurnStatsBar.tsx around the {!hasTime &&
localTime} branch is redundant because TooltipTrigger and TooltipContent both
show the identical localTime; remove the Tooltip wrapper and
TooltipTrigger/TooltipContent and render the span with className="cursor-default
text-[11px] tabular-nums text-neutral-500" directly (or alternatively change
TooltipContent to a different-granularity timestamp like ISO string if you
prefer a hover detail), keeping the conditional on hasTime and the localTime
variable.

---

Outside diff comments:
In `@autogpt_platform/backend/backend/copilot/stream_registry.py`:
- Around line 187-199: Reset the per-turn reasoning timing state when creating a
new turn: when writing to the Redis hash keyed by meta_key (the block that calls
redis.hset in create_session()/session creation), explicitly set/reset
"reasoning_ms_total" (and any per-turn timing fields) to "0" (or empty) so
previous turn timings don't carry over; also perform multi-step updates using
redis pipelines with transaction=True to ensure atomicity as per guidelines
(apply the same fixes in the other similar blocks around the 227-260 and 937-957
regions).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 740db8e1-0bb3-477c-8854-94c3fde23133

📥 Commits

Reviewing files that changed from the base of the PR and between cf6d703 and f8ec02b.

📒 Files selected for processing (15)
  • autogpt_platform/backend/backend/copilot/db.py
  • autogpt_platform/backend/backend/copilot/model.py
  • autogpt_platform/backend/backend/copilot/stream_registry.py
  • autogpt_platform/backend/migrations/20260423120000_add_reasoning_duration_ms/migration.sql
  • autogpt_platform/backend/schema.prisma
  • autogpt_platform/frontend/src/app/(platform)/copilot/CopilotPage.tsx
  • autogpt_platform/frontend/src/app/(platform)/copilot/__tests__/useChatSession.test.ts
  • autogpt_platform/frontend/src/app/(platform)/copilot/__tests__/useLoadMoreMessages.test.ts
  • autogpt_platform/frontend/src/app/(platform)/copilot/components/ChatContainer/ChatContainer.tsx
  • autogpt_platform/frontend/src/app/(platform)/copilot/components/ChatMessagesContainer/ChatMessagesContainer.tsx
  • autogpt_platform/frontend/src/app/(platform)/copilot/components/JobStatsBar/TurnStatsBar.tsx
  • autogpt_platform/frontend/src/app/(platform)/copilot/helpers/convertChatSessionToUiMessages.ts
  • autogpt_platform/frontend/src/app/(platform)/copilot/useChatSession.ts
  • autogpt_platform/frontend/src/app/(platform)/copilot/useCopilotPage.ts
  • autogpt_platform/frontend/src/app/(platform)/copilot/useLoadMoreMessages.ts

Comment thread autogpt_platform/backend/backend/copilot/db.py Outdated
@codecov

codecov Bot commented Apr 23, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 87.50000% with 11 lines in your changes missing coverage. Please review.
✅ Project coverage is 67.49%. Comparing base (4242da7) to head (80e6882).
⚠️ Report is 2 commits behind head on dev.

❌ Your patch status has failed because the patch coverage (68.42%) is below the target coverage (80.00%). You can increase the patch coverage or adjust the target coverage.

Additional details and impacted files
@@           Coverage Diff           @@
##              dev   #12890   +/-   ##
=======================================
  Coverage   67.49%   67.49%           
=======================================
  Files        1906     1906           
  Lines      146975   147036   +61     
  Branches    15400    15419   +19     
=======================================
+ Hits        99198    99243   +45     
- Misses      44813    44815    +2     
- Partials     2964     2978   +14     
Flag Coverage Δ
platform-backend 77.39% <68.42%> (-0.01%) ⬇️
platform-frontend 24.20% <94.02%> (+0.35%) ⬆️
platform-frontend-e2e 30.01% <30.95%> (-0.95%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Components Coverage Δ
Platform Backend 77.39% <68.42%> (-0.01%) ⬇️
Platform Frontend 31.19% <92.75%> (+0.07%) ⬆️
AutoGPT Libs ∅ <ø> (∅)
Classic AutoGPT 28.43% <ø> (ø)
🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Addresses PR review on #12890:

- Blocker: `reasoning_ms_total` leaked across turns because the meta hash is
  session-scoped. After snapshotting it into the DB row in
  `mark_session_completed`, HDEL both `reasoning_ms_total` and
  `reasoning_started_at` so the next turn starts from zero.
- Should-fix: new test covers `set_turn_duration` carrying
  `reasoning_duration_ms` through both the DB write and the cache patch.
- Should-fix: new unit tests for `_record_reasoning_event` — happy
  start→end pair accumulates, end-without-start is a no-op, malformed
  `reasoning_started_at` is a no-op, and a second start without end
  overwrites the first stamp.
@github-actions github-actions Bot added size/xl and removed size/l labels Apr 23, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@autogpt_platform/backend/backend/copilot/stream_registry_test.py`:
- Around line 333-336: Remove the "# noqa: ARG001" from the fake function
_fake_set_turn_duration and explicitly reference the unused parameters to
satisfy the linter; e.g., use a trivial reference such as _ = (session_id,
duration_ms) or rename them to _session_id/_duration_ms before using them, then
keep the existing assignment to captured["reasoning_duration_ms"] so the test
behavior is unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9e00dd57-de64-466a-807f-237826423682

📥 Commits

Reviewing files that changed from the base of the PR and between f8ec02b and 5b5f20e.

📒 Files selected for processing (3)
  • autogpt_platform/backend/backend/copilot/db_test.py
  • autogpt_platform/backend/backend/copilot/stream_registry.py
  • autogpt_platform/backend/backend/copilot/stream_registry_test.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • autogpt_platform/backend/backend/copilot/stream_registry.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (12)
  • GitHub Check: check API types
  • GitHub Check: integration_test
  • GitHub Check: end-to-end tests
  • GitHub Check: test (3.11)
  • GitHub Check: type-check (3.12)
  • GitHub Check: test (3.13)
  • GitHub Check: type-check (3.13)
  • GitHub Check: test (3.12)
  • GitHub Check: Seer Code Review
  • GitHub Check: Analyze (python)
  • GitHub Check: Analyze (typescript)
  • GitHub Check: Check PR Status
🧰 Additional context used
📓 Path-based instructions (3)
autogpt_platform/backend/**/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

autogpt_platform/backend/**/*.py: Use Python 3.11 (required; managed by Poetry via pyproject.toml) for backend development
Always run 'poetry run format' (Black + isort) before linting in backend development
Always run 'poetry run lint' (ruff) after formatting in backend development

autogpt_platform/backend/**/*.py: Use poetry run ... command for executing Python package dependencies
Use top-level imports only — avoid local/inner imports except for lazy imports of heavy optional dependencies like openpyxl
Use absolute imports with from backend.module import ... for cross-package imports; single-dot relative imports are acceptable for sibling modules within the same package; avoid double-dot relative imports
Do not use duck typing — avoid hasattr/getattr/isinstance for type dispatch; use typed interfaces/unions/protocols instead
Use Pydantic models over dataclass/namedtuple/dict for structured data
Do not use linter suppressors — no # type: ignore, # noqa, # pyright: ignore; fix the type/code instead
Prefer list comprehensions over manual loop-and-append patterns
Use early return with guard clauses first to avoid deep nesting
Use %s for deferred interpolation in debug log statements for efficiency; use f-strings elsewhere for readability (e.g., logger.debug("Processing %s items", count) vs logger.info(f"Processing {count} items"))
Sanitize error paths by using os.path.basename() in error messages to avoid leaking directory structure
Be aware of TOCTOU (Time-Of-Check-Time-Of-Use) issues — avoid check-then-act patterns for file access and credit charging
Use transaction=True for Redis pipelines to ensure atomicity on multi-step operations
Use max(0, value) guards for computed values that should never be negative
Keep files under ~300 lines; if a file grows beyond this, split by responsibility (extract helpers, models, or a sub-module into a new file)
Keep functions under ~40 lines; extract named helpers when a function grows longer
...

Files:

  • autogpt_platform/backend/backend/copilot/db_test.py
  • autogpt_platform/backend/backend/copilot/stream_registry_test.py
autogpt_platform/{backend,autogpt_libs}/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

Format Python code with poetry run format

Files:

  • autogpt_platform/backend/backend/copilot/db_test.py
  • autogpt_platform/backend/backend/copilot/stream_registry_test.py
autogpt_platform/backend/**/*_test.py

📄 CodeRabbit inference engine (autogpt_platform/backend/AGENTS.md)

autogpt_platform/backend/**/*_test.py: Use pytest with snapshot testing for API responses
Colocate test files with source files using *_test.py naming convention
Mock at boundaries — mock where the symbol is used, not where it's defined; after refactoring, update mock targets to match new module paths
Use AsyncMock from unittest.mock for async functions in tests
When writing tests, use Test-Driven Development (TDD): write failing tests marked with @pytest.mark.xfail before implementation, then remove the marker once the implementation is complete
When creating snapshots in tests, use poetry run pytest path/to/test.py --snapshot-update; always review snapshot changes with git diff before committing

Files:

  • autogpt_platform/backend/backend/copilot/db_test.py
  • autogpt_platform/backend/backend/copilot/stream_registry_test.py
🧠 Learnings (17)
📓 Common learnings
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12773
File: autogpt_platform/backend/backend/copilot/pending_messages.py:52-64
Timestamp: 2026-04-14T14:36:25.545Z
Learning: In `autogpt_platform/backend/backend/copilot` (PR `#12773`, commit d7bced0c6): when draining pending messages into `session.messages`, each message's text is sanitized via `strip_user_context_tags` before persistence to prevent user-controlled `<user_context>` injection from bypassing the trusted server-side context prefix. Additionally, if `upsert_chat_session` fails after draining, the drained `PendingMessage` objects are requeued back to Redis to avoid silent message loss. Do NOT flag the drain-then-requeue pattern as redundant — it is the intentional failure-resilience strategy for the pending buffer.
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12796
File: autogpt_platform/backend/backend/api/features/chat/routes.py:504-527
Timestamp: 2026-04-16T12:33:44.990Z
Learning: In `autogpt_platform/backend/backend/api/features/chat/routes.py`, `get_session` (PR `#12796`, commit 3771bfad9c1) closes the TOCTOU race between the initial `stream_registry.get_active_session()` pre-check and `get_chat_messages_paginated()` with a post-check re-verification: after the DB fetch, if `is_initial_load and active_session is not None`, it calls `get_active_session` a second time; if `post_active is None` (stream completed during the window), it resets `from_start=True`, `forward_paginated=True`, and re-fetches messages from sequence 0. Do NOT flag the double `get_active_session` call pattern as redundant — it is the intentional TOCTOU mitigation for pagination direction selection.
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12873
File: autogpt_platform/backend/backend/copilot/baseline/reasoning.py:0-0
Timestamp: 2026-04-21T17:31:23.683Z
Learning: In `autogpt_platform/backend/backend/copilot/baseline/reasoning.py` (`BaselineReasoningEmitter`), when `render_in_ui=False`, BOTH the `StreamReasoning*` wire events AND the `ChatMessage(role="reasoning")` persistence append must be suppressed together. `convertChatSessionToUiMessages.ts` unconditionally re-renders all persisted `role="reasoning"` rows as `{type:"reasoning"}` UI parts on reload, so persisting rows while silencing live wire events would resurrect the reasoning collapse on page refresh. The audit trail is preserved through the provider transcript and `_format_sdk_content_blocks` (SDK path) instead. The baseline and SDK paths mirror each other: flag off → no live wire event, no persisted row, no hydrated collapse. This was established in PR `#12873`, commit 7ef10b26c.
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12814
File: autogpt_platform/backend/backend/copilot/model.py:661-679
Timestamp: 2026-04-16T13:28:20.824Z
Learning: In `autogpt_platform/backend/backend/copilot/model.py` (PR `#12814`, commit 259d37083): `append_and_save_message` acquires `_get_session_lock` — a redis-py built-in Lock at key `copilot:session_lock:{session_id}` (timeout=10s, blocking_timeout=2s) — to serialize concurrent writers across replicas. On Redis failure the lock is skipped with a warning and the function continues. Inside the lock it re-fetches the session via `get_chat_session` (cache-first), performs an idempotency check (`session.messages[-1].role == message.role and session.messages[-1].content == message.content`), and returns early if matched. On successful DB write but failed cache write, it calls `invalidate_session_cache(session_id)` (the pre-existing best-effort helper) to evict the stale cache entry so subsequent retries fall back to the authoritative DB. Do NOT expect `asyncio.Lock` or a manual NX poll loop (`copilot:msg_append:{session_id}`) — those were removed. Do NOT flag the `invalidate_session_cache` call on ...
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12814
File: autogpt_platform/backend/backend/copilot/model.py:0-0
Timestamp: 2026-04-16T13:28:28.641Z
Learning: In `autogpt_platform/backend/backend/copilot/model.py` (PR `#12814`, commit 259d37083): `append_and_save_message` uses `async with _get_session_lock(session_id)` — the same shared context manager used across the module — which internally acquires `redis-py`'s built-in `Lock` (key `copilot:session_lock:{session_id}`, timeout=10s, blocking_timeout=2s) via an atomic Lua-script. Lock release is also owner-verified via Lua so a slow pod can never delete a lock it no longer holds. On Redis failure the lock is skipped with a warning; the in-function idempotency check (`session.messages[-1].role` and `.content` comparison) still runs as a fallback. Do NOT expect a raw `redis.set(nx=True)` / `redis.delete()` pattern here — that intermediate approach was replaced in commit 259d37083.
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12773
File: autogpt_platform/backend/backend/blocks/autopilot.py:631-638
Timestamp: 2026-04-14T07:35:11.464Z
Learning: In `autogpt_platform/backend/backend/copilot/executor/utils.py`, `CoPilotExecutionEntry` includes a `permissions: CopilotPermissions | None` field (added in PR `#12773` / commit a0184c87b9). `enqueue_copilot_turn` accepts and serializes this field into the queue entry, `_enqueue_for_recovery` in `autopilot.py` accepts and forwards `permissions` to `enqueue_copilot_turn`, and `_execute_async` in `processor.py` restores `entry.permissions` and passes it into `stream_chat_completion_sdk`/`stream_chat_completion_baseline` via `set_execution_context`. This ensures recovered sub-agent turns respect the same tool/block permission ceiling as the original in-process execution (mirroring `_merge_inherited_permissions`). Do NOT flag recovered turns as losing their permission ceiling — it is now fully propagated through the queue.
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12445
File: autogpt_platform/backend/backend/copilot/sdk/service.py:1071-1072
Timestamp: 2026-03-17T06:48:26.471Z
Learning: In Significant-Gravitas/AutoGPT (autogpt_platform), the AI SDK enforces `z.strictObject({type, errorText})` on SSE `StreamError` responses, so additional fields like `retryable: bool` cannot be added to `StreamError` or serialized via `to_sse()`. Instead, retry signaling for transient Anthropic API errors is done via the `COPILOT_RETRYABLE_ERROR_PREFIX` constant prepended to persisted session messages (in `ChatMessage.content`). The frontend detects retryable errors by checking `markerType === "retryable_error"` from `parseSpecialMarkers()` — no SSE schema changes and no string matching on error text. This pattern was established in PR `#12445`, commit 64d82797b.
📚 Learning: 2026-04-15T13:44:34.273Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12797
File: autogpt_platform/backend/backend/copilot/sdk/service.py:1991-2021
Timestamp: 2026-04-15T13:44:34.273Z
Learning: In `autogpt_platform/backend/backend/copilot/sdk/service.py` (`_run_stream_attempt`), the pre-create block (PR `#12797`) intentionally does NOT call `state.transcript_builder.append_assistant(...)` when inserting the empty assistant placeholder into `ctx.session.messages`. The transcript is left ending at the `tool_result` entry (N entries) while `message_count` metadata is N+1. This mismatch is benign and deliberate: on the next `--resume`, the SDK sees the transcript ending at `tool_result` and correctly regenerates the assistant response. Pre-appending the assistant turn to the transcript would suppress regeneration while leaving `session.messages[-1].content = ""` permanently (worse outcome). On the gap-fallback path, `transcript_msg_count (N+1) >= msg_count-1 (N)` means no gap is injected for the empty placeholder, which is correct because injecting an empty assistant message as context would mislead the SDK. Do NOT flag this transcript/message_count discrepancy as a bug.

Applied to files:

  • autogpt_platform/backend/backend/copilot/db_test.py
📚 Learning: 2026-04-16T13:28:20.824Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12814
File: autogpt_platform/backend/backend/copilot/model.py:661-679
Timestamp: 2026-04-16T13:28:20.824Z
Learning: In `autogpt_platform/backend/backend/copilot/model.py` (PR `#12814`, commit 259d37083): `append_and_save_message` acquires `_get_session_lock` — a redis-py built-in Lock at key `copilot:session_lock:{session_id}` (timeout=10s, blocking_timeout=2s) — to serialize concurrent writers across replicas. On Redis failure the lock is skipped with a warning and the function continues. Inside the lock it re-fetches the session via `get_chat_session` (cache-first), performs an idempotency check (`session.messages[-1].role == message.role and session.messages[-1].content == message.content`), and returns early if matched. On successful DB write but failed cache write, it calls `invalidate_session_cache(session_id)` (the pre-existing best-effort helper) to evict the stale cache entry so subsequent retries fall back to the authoritative DB. Do NOT expect `asyncio.Lock` or a manual NX poll loop (`copilot:msg_append:{session_id}`) — those were removed. Do NOT flag the `invalidate_session_cache` call on ...

Applied to files:

  • autogpt_platform/backend/backend/copilot/db_test.py
📚 Learning: 2026-02-26T17:02:22.448Z
Learnt from: Pwuts
Repo: Significant-Gravitas/AutoGPT PR: 12211
File: .pre-commit-config.yaml:160-179
Timestamp: 2026-02-26T17:02:22.448Z
Learning: Keep the pre-commit hook pattern broad for autogpt_platform/backend to ensure OpenAPI schema changes are captured. Do not narrow to backend/api/ alone, since the generated schema depends on Pydantic models across multiple directories (backend/data/, backend/blocks/, backend/copilot/, backend/integrations/, backend/util/). Narrowing could miss schema changes and cause frontend type desynchronization.

Applied to files:

  • autogpt_platform/backend/backend/copilot/db_test.py
  • autogpt_platform/backend/backend/copilot/stream_registry_test.py
📚 Learning: 2026-03-04T08:04:35.881Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12273
File: autogpt_platform/backend/backend/copilot/tools/workspace_files.py:216-220
Timestamp: 2026-03-04T08:04:35.881Z
Learning: In the AutoGPT Copilot backend, ensure that SVG images are not treated as vision image types by excluding 'image/svg+xml' from INLINEABLE_MIME_TYPES and MULTIMODAL_TYPES in tool_adapter.py; the Claude API supports PNG, JPEG, GIF, and WebP for vision. SVGs (XML text) should be handled via the text path instead, not the vision path.

Applied to files:

  • autogpt_platform/backend/backend/copilot/db_test.py
  • autogpt_platform/backend/backend/copilot/stream_registry_test.py
📚 Learning: 2026-04-01T04:17:41.600Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12632
File: autogpt_platform/backend/backend/copilot/tools/workspace_files.py:0-0
Timestamp: 2026-04-01T04:17:41.600Z
Learning: When reviewing AutoGPT Copilot tool implementations, accept that `readOnlyHint=True` (provided via `ToolAnnotations`) may be applied unconditionally to *all* tools—even tools that have side effects (e.g., `bash_exec`, `write_workspace_file`, or other write/save operations). Do **not** flag these tools for having `readOnlyHint=True`; this is intentional to enable fully-parallel dispatch by the Anthropic SDK/CLI and has been E2E validated. Only flag `readOnlyHint` issues if they conflict with the established `ToolAnnotations` behavior (e.g., missing/incorrect propagation relative to the intended annotation mechanism).

Applied to files:

  • autogpt_platform/backend/backend/copilot/db_test.py
  • autogpt_platform/backend/backend/copilot/stream_registry_test.py
📚 Learning: 2026-03-05T15:42:08.207Z
Learnt from: ntindle
Repo: Significant-Gravitas/AutoGPT PR: 12297
File: .claude/skills/backend-check/SKILL.md:14-16
Timestamp: 2026-03-05T15:42:08.207Z
Learning: In Python files under autogpt_platform/backend (recursively), rely on poetry run format to perform formatting (Black + isort) and linting (ruff). Do not run poetry run lint as a separate step after poetry run format, since format already includes linting checks.

Applied to files:

  • autogpt_platform/backend/backend/copilot/db_test.py
  • autogpt_platform/backend/backend/copilot/stream_registry_test.py
📚 Learning: 2026-03-16T16:35:40.236Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12440
File: autogpt_platform/backend/backend/api/features/workflow_import.py:54-63
Timestamp: 2026-03-16T16:35:40.236Z
Learning: Avoid using the word 'competitor' in public-facing identifiers and text. Use neutral naming for API paths, model names, function names, and UI text. Examples: rename 'CompetitorFormat' to 'SourcePlatform', 'convert_competitor_workflow' to 'convert_workflow', '/competitor-workflow' to '/workflow'. Apply this guideline to files under autogpt_platform/backend and autogpt_platform/frontend.

Applied to files:

  • autogpt_platform/backend/backend/copilot/db_test.py
  • autogpt_platform/backend/backend/copilot/stream_registry_test.py
📚 Learning: 2026-03-31T15:37:38.626Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12623
File: autogpt_platform/backend/backend/copilot/tools/agent_generator/fixer.py:37-47
Timestamp: 2026-03-31T15:37:38.626Z
Learning: When validating/constructing Anthropic API model IDs in Significant-Gravitas/AutoGPT, allow the hyphen-separated Claude Opus 4.6 model ID `claude-opus-4-6` (it corresponds to `LlmModel.CLAUDE_4_6_OPUS` in `autogpt_platform/backend/backend/blocks/llm.py`). Do NOT require the dot-separated form in Anthropic contexts. Only OpenRouter routing variants should use the dot separator (e.g., `anthropic/claude-opus-4.6`); `claude-opus-4-6` should be treated as correct when passed to Anthropic, and flagged only if it’s used in the OpenRouter path where the dot form is expected.

Applied to files:

  • autogpt_platform/backend/backend/copilot/db_test.py
  • autogpt_platform/backend/backend/copilot/stream_registry_test.py
📚 Learning: 2026-04-15T02:43:36.890Z
Learnt from: ntindle
Repo: Significant-Gravitas/AutoGPT PR: 12780
File: autogpt_platform/backend/backend/copilot/tools/workspace_files.py:0-0
Timestamp: 2026-04-15T02:43:36.890Z
Learning: When reviewing Python exception handlers, do not flag `isinstance(e, X)` checks as dead/unreachable if the caught exception `X` is a subclass of the exception type being handled. For example, if `X` (e.g., `VirusScanError`) inherits from `ValueError` (directly or via an intermediate class) and it can be raised within an `except ValueError:` block, then `isinstance(e, X)` inside that handler is reachable and should not be treated as dead code.

Applied to files:

  • autogpt_platform/backend/backend/copilot/db_test.py
  • autogpt_platform/backend/backend/copilot/stream_registry_test.py
📚 Learning: 2026-04-22T11:46:04.431Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12881
File: autogpt_platform/backend/backend/copilot/config.py:0-0
Timestamp: 2026-04-22T11:46:04.431Z
Learning: Do not flag the Claude Sonnet 4.6 model ID as incorrect when it uses the project’s established hyphenated convention: `anthropic/claude-sonnet-4-6`. This hyphen form is the intentional, production convention and should be treated as valid (including in files like llm.py, blocks tests, reasoning.py, `_is_anthropic_model` tests, and config defaults). Note that OpenRouter also accepts the dot variant `anthropic/claude-sonnet-4.6`, so either form may be tolerated, but `anthropic/claude-sonnet-4-6` should be considered the standard to match project usage.

Applied to files:

  • autogpt_platform/backend/backend/copilot/db_test.py
  • autogpt_platform/backend/backend/copilot/stream_registry_test.py
📚 Learning: 2026-04-22T11:46:12.892Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12881
File: autogpt_platform/backend/backend/copilot/baseline/service.py:322-332
Timestamp: 2026-04-22T11:46:12.892Z
Learning: In this codebase (Significant-Gravitas/AutoGPT), OpenRouter-routed Anthropic model IDs should use the hyphen-separated convention (e.g., `anthropic/claude-sonnet-4-6`, `anthropic/claude-opus-4-6`). Although OpenRouter may accept both hyphen and dot variants, treat the hyphen-separated form as the intended, correct codebase-wide convention and do not flag it as an error. Only flag the dot-separated variant (e.g., `anthropic/claude-sonnet-4.6`) as incorrect when reviewing/validating model ID strings for OpenRouter-routed Anthropic models.

Applied to files:

  • autogpt_platform/backend/backend/copilot/db_test.py
  • autogpt_platform/backend/backend/copilot/stream_registry_test.py
📚 Learning: 2026-04-21T17:31:23.683Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12873
File: autogpt_platform/backend/backend/copilot/baseline/reasoning.py:0-0
Timestamp: 2026-04-21T17:31:23.683Z
Learning: In `autogpt_platform/backend/backend/copilot/baseline/reasoning.py` (`BaselineReasoningEmitter`), when `render_in_ui=False`, BOTH the `StreamReasoning*` wire events AND the `ChatMessage(role="reasoning")` persistence append must be suppressed together. `convertChatSessionToUiMessages.ts` unconditionally re-renders all persisted `role="reasoning"` rows as `{type:"reasoning"}` UI parts on reload, so persisting rows while silencing live wire events would resurrect the reasoning collapse on page refresh. The audit trail is preserved through the provider transcript and `_format_sdk_content_blocks` (SDK path) instead. The baseline and SDK paths mirror each other: flag off → no live wire event, no persisted row, no hydrated collapse. This was established in PR `#12873`, commit 7ef10b26c.

Applied to files:

  • autogpt_platform/backend/backend/copilot/stream_registry_test.py
📚 Learning: 2026-04-16T12:33:44.990Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12796
File: autogpt_platform/backend/backend/api/features/chat/routes.py:504-527
Timestamp: 2026-04-16T12:33:44.990Z
Learning: In `autogpt_platform/backend/backend/api/features/chat/routes.py`, `get_session` (PR `#12796`, commit 3771bfad9c1) closes the TOCTOU race between the initial `stream_registry.get_active_session()` pre-check and `get_chat_messages_paginated()` with a post-check re-verification: after the DB fetch, if `is_initial_load and active_session is not None`, it calls `get_active_session` a second time; if `post_active is None` (stream completed during the window), it resets `from_start=True`, `forward_paginated=True`, and re-fetches messages from sequence 0. Do NOT flag the double `get_active_session` call pattern as redundant — it is the intentional TOCTOU mitigation for pagination direction selection.

Applied to files:

  • autogpt_platform/backend/backend/copilot/stream_registry_test.py
📚 Learning: 2026-04-21T11:41:05.877Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 0
File: :0-0
Timestamp: 2026-04-21T11:41:05.877Z
Learning: In `autogpt_platform/backend/backend/copilot/baseline/service.py` (PR `#12870`, commits 080d42b9d and 3d7b38162), the `_close_reasoning_block_if_open(state)` helper centralises all four reasoning-block-close call sites (text branch, tool_calls branch, stream-end, exception path). The outer `finally` block of `_baseline_llm_caller` calls this helper plus stripper flush + `StreamTextEnd` to guarantee matched end events are emitted before `StreamFinishStep` on both normal and exception paths. Do NOT flag duplicated close logic or missing reasoning-end-on-exception as issues in this function.

Applied to files:

  • autogpt_platform/backend/backend/copilot/stream_registry_test.py
📚 Learning: 2026-03-30T11:49:37.770Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12604
File: autogpt_platform/backend/backend/copilot/sdk/security_hooks.py:165-171
Timestamp: 2026-03-30T11:49:37.770Z
Learning: In `autogpt_platform/backend/backend/copilot/sdk/security_hooks.py`, the `web_search_count` and `total_tool_call_count` circuit-breaker counters in `create_security_hooks` are intentionally per-turn (closure-local), not per-session. Hooks are recreated per stream invocation in `service.py`, so counters reset each turn. This is an accepted v1 design: it caps a single runaway turn (incident d2f7cba3: 179 WebSearch calls, $20.66). True per-session persistence via Redis is deferred to a later iteration. Do not flag these as a per-session vs. per-turn mismatch bug.

Applied to files:

  • autogpt_platform/backend/backend/copilot/stream_registry_test.py
📚 Learning: 2026-04-14T14:45:42.706Z
Learnt from: 0ubbe
Repo: Significant-Gravitas/AutoGPT PR: 12766
File: autogpt_platform/backend/backend/copilot/stream_registry.py:1175-1193
Timestamp: 2026-04-14T14:45:42.706Z
Learning: In `autogpt_platform/backend/backend/copilot/stream_registry.py`, `disconnect_all_listeners(session_id)` is intentionally pod-local (inspects in-memory `_listener_sessions`) and session-scoped (not subscriber-scoped). It cancels all listener tasks for the session on the current pod only. If the DELETE request hits a different pod, nothing is cancelled on that pod — the XREAD timeout (5 s block + status poll) bounds the worst-case release time. In the rare two-tabs-same-session case both listeners on the same pod would be torn down. A subscriber-scoped cross-pod fan-out (per-listener tokens + Redis pub/sub) is deferred as a follow-up. Do NOT re-flag this as a blocking issue; the limitation is explicitly documented in the function's docstring (PR `#12766`, commit 1f3ebafd5).

Applied to files:

  • autogpt_platform/backend/backend/copilot/stream_registry_test.py
🔇 Additional comments (2)
autogpt_platform/backend/backend/copilot/db_test.py (1)

525-559: LGTM — covers both cache patch and DB persistence for reasoning_duration_ms.

The round-trip assertion (cache via get_chat_session + DB via PrismaChatMessage.prisma().find_first) is the right regression guard for the paired durationMs / reasoningDurationMs write path.

autogpt_platform/backend/backend/copilot/stream_registry_test.py (1)

445-474: Tolerance range is asymmetric but safe.

700 <= total <= 2000 gives 50 ms of under-slack vs. 1250 ms of over-slack for a nominal 750 ms delta. Under CI load the real risk is overshoot rather than undershoot, so this is fine; just noting that if flakes ever appear, widen the lower bound (e.g., >= 500) rather than the upper.

Comment thread autogpt_platform/backend/backend/copilot/stream_registry_test.py Outdated
- db.py: type `set_turn_duration` patch dict as `ChatMessageUpdateInput`
  to drop the noqa and get proper prisma key validation.
- TurnStatsBar: drop the redundant tooltip when only a timestamp is
  shown (trigger == content was noise). Counters-only / time-only rows
  render plain text.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
autogpt_platform/backend/backend/copilot/db.py (1)

641-657: ⚠️ Potential issue | 🟡 Minor

Clamp durations before writing DB/cache.

reasoning_duration_ms is now persisted and rendered to users, so a negative computed value would leak into both Prisma and Redis cache.

🛡️ Proposed fix
+        duration_ms = max(0, duration_ms)
+        if reasoning_duration_ms is not None:
+            reasoning_duration_ms = max(0, reasoning_duration_ms)
+
         data: ChatMessageUpdateInput = {"durationMs": duration_ms}
         if reasoning_duration_ms is not None:
             data["reasoningDurationMs"] = reasoning_duration_ms

As per coding guidelines, "Use max(0, value) guards for computed values that should never be negative".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@autogpt_platform/backend/backend/copilot/db.py` around lines 641 - 657, Clamp
negative durations before persisting and caching: compute non-negative values
for duration_ms and reasoning_duration_ms (use max(0, ...)) prior to calling
PrismaChatMessage.prisma().update(...) and before mutating cached session
messages returned by get_chat_session_cached(session_id). Update the data dict
passed to Prisma (used with last_msg.id) to use the clamped values, and when
iterating session.messages to assign msg.duration_ms and
msg.reasoning_duration_ms, assign the same clamped values so neither DB nor
Redis cache can receive negative durations.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@autogpt_platform/backend/backend/copilot/db.py`:
- Around line 641-657: Clamp negative durations before persisting and caching:
compute non-negative values for duration_ms and reasoning_duration_ms (use
max(0, ...)) prior to calling PrismaChatMessage.prisma().update(...) and before
mutating cached session messages returned by
get_chat_session_cached(session_id). Update the data dict passed to Prisma (used
with last_msg.id) to use the clamped values, and when iterating session.messages
to assign msg.duration_ms and msg.reasoning_duration_ms, assign the same clamped
values so neither DB nor Redis cache can receive negative durations.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: fd45dd3f-cc1a-46a1-a9b2-559f430896d2

📥 Commits

Reviewing files that changed from the base of the PR and between 5b5f20e and f4692e6.

📒 Files selected for processing (2)
  • autogpt_platform/backend/backend/copilot/db.py
  • autogpt_platform/frontend/src/app/(platform)/copilot/components/JobStatsBar/TurnStatsBar.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • autogpt_platform/frontend/src/app/(platform)/copilot/components/JobStatsBar/TurnStatsBar.tsx
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (14)
  • GitHub Check: check API types
  • GitHub Check: integration_test
  • GitHub Check: lint
  • GitHub Check: end-to-end tests
  • GitHub Check: type-check (3.11)
  • GitHub Check: test (3.11)
  • GitHub Check: test (3.13)
  • GitHub Check: type-check (3.13)
  • GitHub Check: type-check (3.12)
  • GitHub Check: test (3.12)
  • GitHub Check: Seer Code Review
  • GitHub Check: Check PR Status
  • GitHub Check: Analyze (python)
  • GitHub Check: Analyze (typescript)
🧰 Additional context used
📓 Path-based instructions (2)
autogpt_platform/backend/**/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

autogpt_platform/backend/**/*.py: Use Python 3.11 (required; managed by Poetry via pyproject.toml) for backend development
Always run 'poetry run format' (Black + isort) before linting in backend development
Always run 'poetry run lint' (ruff) after formatting in backend development

autogpt_platform/backend/**/*.py: Use poetry run ... command for executing Python package dependencies
Use top-level imports only — avoid local/inner imports except for lazy imports of heavy optional dependencies like openpyxl
Use absolute imports with from backend.module import ... for cross-package imports; single-dot relative imports are acceptable for sibling modules within the same package; avoid double-dot relative imports
Do not use duck typing — avoid hasattr/getattr/isinstance for type dispatch; use typed interfaces/unions/protocols instead
Use Pydantic models over dataclass/namedtuple/dict for structured data
Do not use linter suppressors — no # type: ignore, # noqa, # pyright: ignore; fix the type/code instead
Prefer list comprehensions over manual loop-and-append patterns
Use early return with guard clauses first to avoid deep nesting
Use %s for deferred interpolation in debug log statements for efficiency; use f-strings elsewhere for readability (e.g., logger.debug("Processing %s items", count) vs logger.info(f"Processing {count} items"))
Sanitize error paths by using os.path.basename() in error messages to avoid leaking directory structure
Be aware of TOCTOU (Time-Of-Check-Time-Of-Use) issues — avoid check-then-act patterns for file access and credit charging
Use transaction=True for Redis pipelines to ensure atomicity on multi-step operations
Use max(0, value) guards for computed values that should never be negative
Keep files under ~300 lines; if a file grows beyond this, split by responsibility (extract helpers, models, or a sub-module into a new file)
Keep functions under ~40 lines; extract named helpers when a function grows longer
...

Files:

  • autogpt_platform/backend/backend/copilot/db.py
autogpt_platform/{backend,autogpt_libs}/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

Format Python code with poetry run format

Files:

  • autogpt_platform/backend/backend/copilot/db.py
🧠 Learnings (14)
📓 Common learnings
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12773
File: autogpt_platform/backend/backend/copilot/pending_messages.py:52-64
Timestamp: 2026-04-14T14:36:25.545Z
Learning: In `autogpt_platform/backend/backend/copilot` (PR `#12773`, commit d7bced0c6): when draining pending messages into `session.messages`, each message's text is sanitized via `strip_user_context_tags` before persistence to prevent user-controlled `<user_context>` injection from bypassing the trusted server-side context prefix. Additionally, if `upsert_chat_session` fails after draining, the drained `PendingMessage` objects are requeued back to Redis to avoid silent message loss. Do NOT flag the drain-then-requeue pattern as redundant — it is the intentional failure-resilience strategy for the pending buffer.
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12796
File: autogpt_platform/backend/backend/api/features/chat/routes.py:504-527
Timestamp: 2026-04-16T12:33:44.990Z
Learning: In `autogpt_platform/backend/backend/api/features/chat/routes.py`, `get_session` (PR `#12796`, commit 3771bfad9c1) closes the TOCTOU race between the initial `stream_registry.get_active_session()` pre-check and `get_chat_messages_paginated()` with a post-check re-verification: after the DB fetch, if `is_initial_load and active_session is not None`, it calls `get_active_session` a second time; if `post_active is None` (stream completed during the window), it resets `from_start=True`, `forward_paginated=True`, and re-fetches messages from sequence 0. Do NOT flag the double `get_active_session` call pattern as redundant — it is the intentional TOCTOU mitigation for pagination direction selection.
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12873
File: autogpt_platform/backend/backend/copilot/baseline/reasoning.py:0-0
Timestamp: 2026-04-21T17:31:23.683Z
Learning: In `autogpt_platform/backend/backend/copilot/baseline/reasoning.py` (`BaselineReasoningEmitter`), when `render_in_ui=False`, BOTH the `StreamReasoning*` wire events AND the `ChatMessage(role="reasoning")` persistence append must be suppressed together. `convertChatSessionToUiMessages.ts` unconditionally re-renders all persisted `role="reasoning"` rows as `{type:"reasoning"}` UI parts on reload, so persisting rows while silencing live wire events would resurrect the reasoning collapse on page refresh. The audit trail is preserved through the provider transcript and `_format_sdk_content_blocks` (SDK path) instead. The baseline and SDK paths mirror each other: flag off → no live wire event, no persisted row, no hydrated collapse. This was established in PR `#12873`, commit 7ef10b26c.
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12814
File: autogpt_platform/backend/backend/copilot/model.py:661-679
Timestamp: 2026-04-16T13:28:20.824Z
Learning: In `autogpt_platform/backend/backend/copilot/model.py` (PR `#12814`, commit 259d37083): `append_and_save_message` acquires `_get_session_lock` — a redis-py built-in Lock at key `copilot:session_lock:{session_id}` (timeout=10s, blocking_timeout=2s) — to serialize concurrent writers across replicas. On Redis failure the lock is skipped with a warning and the function continues. Inside the lock it re-fetches the session via `get_chat_session` (cache-first), performs an idempotency check (`session.messages[-1].role == message.role and session.messages[-1].content == message.content`), and returns early if matched. On successful DB write but failed cache write, it calls `invalidate_session_cache(session_id)` (the pre-existing best-effort helper) to evict the stale cache entry so subsequent retries fall back to the authoritative DB. Do NOT expect `asyncio.Lock` or a manual NX poll loop (`copilot:msg_append:{session_id}`) — those were removed. Do NOT flag the `invalidate_session_cache` call on ...
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12814
File: autogpt_platform/backend/backend/copilot/model.py:0-0
Timestamp: 2026-04-16T13:28:28.641Z
Learning: In `autogpt_platform/backend/backend/copilot/model.py` (PR `#12814`, commit 259d37083): `append_and_save_message` uses `async with _get_session_lock(session_id)` — the same shared context manager used across the module — which internally acquires `redis-py`'s built-in `Lock` (key `copilot:session_lock:{session_id}`, timeout=10s, blocking_timeout=2s) via an atomic Lua-script. Lock release is also owner-verified via Lua so a slow pod can never delete a lock it no longer holds. On Redis failure the lock is skipped with a warning; the in-function idempotency check (`session.messages[-1].role` and `.content` comparison) still runs as a fallback. Do NOT expect a raw `redis.set(nx=True)` / `redis.delete()` pattern here — that intermediate approach was replaced in commit 259d37083.
📚 Learning: 2026-04-14T07:35:11.464Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12773
File: autogpt_platform/backend/backend/blocks/autopilot.py:631-638
Timestamp: 2026-04-14T07:35:11.464Z
Learning: In `autogpt_platform/backend/backend/copilot/executor/utils.py`, `CoPilotExecutionEntry` includes a `permissions: CopilotPermissions | None` field (added in PR `#12773` / commit a0184c87b9). `enqueue_copilot_turn` accepts and serializes this field into the queue entry, `_enqueue_for_recovery` in `autopilot.py` accepts and forwards `permissions` to `enqueue_copilot_turn`, and `_execute_async` in `processor.py` restores `entry.permissions` and passes it into `stream_chat_completion_sdk`/`stream_chat_completion_baseline` via `set_execution_context`. This ensures recovered sub-agent turns respect the same tool/block permission ceiling as the original in-process execution (mirroring `_merge_inherited_permissions`). Do NOT flag recovered turns as losing their permission ceiling — it is now fully propagated through the queue.

Applied to files:

  • autogpt_platform/backend/backend/copilot/db.py
📚 Learning: 2026-03-13T15:49:44.961Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12385
File: autogpt_platform/backend/backend/copilot/rate_limit.py:0-0
Timestamp: 2026-03-13T15:49:44.961Z
Learning: In `autogpt_platform/backend/backend/copilot/rate_limit.py`, the original per-session token window (with a TTL-based reset) was replaced with fixed daily and weekly windows. `resets_at` is now derived from `_daily_reset_time()` (midnight UTC) and `_weekly_reset_time()` (next Monday 00:00 UTC) — deterministic fixed-boundary calculations that require no Redis TTL introspection.

Applied to files:

  • autogpt_platform/backend/backend/copilot/db.py
📚 Learning: 2026-04-08T17:28:23.439Z
Learnt from: CR
Repo: Significant-Gravitas/AutoGPT PR: 0
File: autogpt_platform/backend/AGENTS.md:0-0
Timestamp: 2026-04-08T17:28:23.439Z
Learning: Applies to autogpt_platform/backend/**/*.py : Do not use linter suppressors — no `# type: ignore`, `# noqa`, `# pyright: ignore`; fix the type/code instead

Applied to files:

  • autogpt_platform/backend/backend/copilot/db.py
📚 Learning: 2026-03-31T14:22:29.127Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12622
File: autogpt_platform/backend/backend/copilot/tools/agent_search.py:223-236
Timestamp: 2026-03-31T14:22:29.127Z
Learning: When reviewing code under autogpt_platform/backend/backend/copilot/tools/, the `AgentInfo.graph` field (in agent_search.py / models.py) uses `Graph | None` (the typed `backend.data.graph.Graph` Pydantic model), NOT `dict[str, Any]`. The enrichment function `_enrich_agents_with_graph` calls `graph_db().get_graph(graph_id, version=None, user_id=user_id)` directly rather than going through `get_agent_as_json()` / `graph_to_json()`. This was updated in PR `#12622` (commit 22d05bc).

Applied to files:

  • autogpt_platform/backend/backend/copilot/db.py
📚 Learning: 2026-02-26T17:02:22.448Z
Learnt from: Pwuts
Repo: Significant-Gravitas/AutoGPT PR: 12211
File: .pre-commit-config.yaml:160-179
Timestamp: 2026-02-26T17:02:22.448Z
Learning: Keep the pre-commit hook pattern broad for autogpt_platform/backend to ensure OpenAPI schema changes are captured. Do not narrow to backend/api/ alone, since the generated schema depends on Pydantic models across multiple directories (backend/data/, backend/blocks/, backend/copilot/, backend/integrations/, backend/util/). Narrowing could miss schema changes and cause frontend type desynchronization.

Applied to files:

  • autogpt_platform/backend/backend/copilot/db.py
📚 Learning: 2026-03-04T08:04:35.881Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12273
File: autogpt_platform/backend/backend/copilot/tools/workspace_files.py:216-220
Timestamp: 2026-03-04T08:04:35.881Z
Learning: In the AutoGPT Copilot backend, ensure that SVG images are not treated as vision image types by excluding 'image/svg+xml' from INLINEABLE_MIME_TYPES and MULTIMODAL_TYPES in tool_adapter.py; the Claude API supports PNG, JPEG, GIF, and WebP for vision. SVGs (XML text) should be handled via the text path instead, not the vision path.

Applied to files:

  • autogpt_platform/backend/backend/copilot/db.py
📚 Learning: 2026-04-01T04:17:41.600Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12632
File: autogpt_platform/backend/backend/copilot/tools/workspace_files.py:0-0
Timestamp: 2026-04-01T04:17:41.600Z
Learning: When reviewing AutoGPT Copilot tool implementations, accept that `readOnlyHint=True` (provided via `ToolAnnotations`) may be applied unconditionally to *all* tools—even tools that have side effects (e.g., `bash_exec`, `write_workspace_file`, or other write/save operations). Do **not** flag these tools for having `readOnlyHint=True`; this is intentional to enable fully-parallel dispatch by the Anthropic SDK/CLI and has been E2E validated. Only flag `readOnlyHint` issues if they conflict with the established `ToolAnnotations` behavior (e.g., missing/incorrect propagation relative to the intended annotation mechanism).

Applied to files:

  • autogpt_platform/backend/backend/copilot/db.py
📚 Learning: 2026-03-05T15:42:08.207Z
Learnt from: ntindle
Repo: Significant-Gravitas/AutoGPT PR: 12297
File: .claude/skills/backend-check/SKILL.md:14-16
Timestamp: 2026-03-05T15:42:08.207Z
Learning: In Python files under autogpt_platform/backend (recursively), rely on poetry run format to perform formatting (Black + isort) and linting (ruff). Do not run poetry run lint as a separate step after poetry run format, since format already includes linting checks.

Applied to files:

  • autogpt_platform/backend/backend/copilot/db.py
📚 Learning: 2026-03-16T16:35:40.236Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12440
File: autogpt_platform/backend/backend/api/features/workflow_import.py:54-63
Timestamp: 2026-03-16T16:35:40.236Z
Learning: Avoid using the word 'competitor' in public-facing identifiers and text. Use neutral naming for API paths, model names, function names, and UI text. Examples: rename 'CompetitorFormat' to 'SourcePlatform', 'convert_competitor_workflow' to 'convert_workflow', '/competitor-workflow' to '/workflow'. Apply this guideline to files under autogpt_platform/backend and autogpt_platform/frontend.

Applied to files:

  • autogpt_platform/backend/backend/copilot/db.py
📚 Learning: 2026-03-31T15:37:38.626Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12623
File: autogpt_platform/backend/backend/copilot/tools/agent_generator/fixer.py:37-47
Timestamp: 2026-03-31T15:37:38.626Z
Learning: When validating/constructing Anthropic API model IDs in Significant-Gravitas/AutoGPT, allow the hyphen-separated Claude Opus 4.6 model ID `claude-opus-4-6` (it corresponds to `LlmModel.CLAUDE_4_6_OPUS` in `autogpt_platform/backend/backend/blocks/llm.py`). Do NOT require the dot-separated form in Anthropic contexts. Only OpenRouter routing variants should use the dot separator (e.g., `anthropic/claude-opus-4.6`); `claude-opus-4-6` should be treated as correct when passed to Anthropic, and flagged only if it’s used in the OpenRouter path where the dot form is expected.

Applied to files:

  • autogpt_platform/backend/backend/copilot/db.py
📚 Learning: 2026-04-15T02:43:36.890Z
Learnt from: ntindle
Repo: Significant-Gravitas/AutoGPT PR: 12780
File: autogpt_platform/backend/backend/copilot/tools/workspace_files.py:0-0
Timestamp: 2026-04-15T02:43:36.890Z
Learning: When reviewing Python exception handlers, do not flag `isinstance(e, X)` checks as dead/unreachable if the caught exception `X` is a subclass of the exception type being handled. For example, if `X` (e.g., `VirusScanError`) inherits from `ValueError` (directly or via an intermediate class) and it can be raised within an `except ValueError:` block, then `isinstance(e, X)` inside that handler is reachable and should not be treated as dead code.

Applied to files:

  • autogpt_platform/backend/backend/copilot/db.py
📚 Learning: 2026-04-22T11:46:04.431Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12881
File: autogpt_platform/backend/backend/copilot/config.py:0-0
Timestamp: 2026-04-22T11:46:04.431Z
Learning: Do not flag the Claude Sonnet 4.6 model ID as incorrect when it uses the project’s established hyphenated convention: `anthropic/claude-sonnet-4-6`. This hyphen form is the intentional, production convention and should be treated as valid (including in files like llm.py, blocks tests, reasoning.py, `_is_anthropic_model` tests, and config defaults). Note that OpenRouter also accepts the dot variant `anthropic/claude-sonnet-4.6`, so either form may be tolerated, but `anthropic/claude-sonnet-4-6` should be considered the standard to match project usage.

Applied to files:

  • autogpt_platform/backend/backend/copilot/db.py
📚 Learning: 2026-04-22T11:46:12.892Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12881
File: autogpt_platform/backend/backend/copilot/baseline/service.py:322-332
Timestamp: 2026-04-22T11:46:12.892Z
Learning: In this codebase (Significant-Gravitas/AutoGPT), OpenRouter-routed Anthropic model IDs should use the hyphen-separated convention (e.g., `anthropic/claude-sonnet-4-6`, `anthropic/claude-opus-4-6`). Although OpenRouter may accept both hyphen and dot variants, treat the hyphen-separated form as the intended, correct codebase-wide convention and do not flag it as an error. Only flag the dot-separated variant (e.g., `anthropic/claude-sonnet-4.6`) as incorrect when reviewing/validating model ID strings for OpenRouter-routed Anthropic models.

Applied to files:

  • autogpt_platform/backend/backend/copilot/db.py
🔇 Additional comments (2)
autogpt_platform/backend/backend/copilot/db.py (2)

11-19: LGTM — typed Prisma update payload.

Using ChatMessageUpdateInput here keeps the update data aligned with Prisma’s generated types and avoids the previous suppressor workaround.


622-628: LGTM — backwards-compatible reasoning duration support.

The optional parameter keeps existing callers working while allowing completion logic to persist the more accurate reasoning timer.

The API mutator runs transformDates() on every response, which converts
ISO date strings to Date objects before the data reaches the copilot UI
converter. The coerceSessionChatMessages type-guard rejected Date values
(typeof new Date() === "object"), so created_at silently became null and
the "Thought for X" tooltip (which only renders when timestamp is
present) never wrapped the label.

Accept both string and Date in the coerce step and serialise Date back
to ISO so downstream formatLocalTimestamp() keeps its single string
input contract.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
autogpt_platform/frontend/src/app/(platform)/copilot/helpers/convertChatSessionToUiMessages.ts (1)

302-310: ⚠️ Potential issue | 🟠 Major

Update timestamps when assistant rows are merged.

Line 302 merges the following assistant row into an existing assistant UIMessage, but Line 326 records created_at only for newly-created messages. For reasoning→assistant turns, the tooltip can stay missing or point at the reasoning row instead of the final assistant row.

🐛 Proposed fix
     if (uiRole === "assistant" && prevUI && prevUI.role === "assistant") {
       prevUI.parts.push(...parts);
+      if (msg.created_at) {
+        timestamps.set(prevUI.id, msg.created_at);
+      }
       // Capture duration on merged message (last assistant msg wins)
       if (msg.duration_ms != null) {
         durations.set(prevUI.id, msg.duration_ms);
       }

Also applies to: 326-328

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@autogpt_platform/frontend/src/app/`(platform)/copilot/helpers/convertChatSessionToUiMessages.ts
around lines 302 - 310, When merging an assistant message into an existing
UIMessage (inside convertChatSessionToUiMessages) update the created_at
timestamp for the merged prevUI the same way durations and reasoningDurations
are set: when uiRole === "assistant" and prevUI exists, after
prevUI.parts.push(...parts) also set the createdAt map (or the existing
created-timestamp storage used when creating new UI messages) for prevUI.id
using msg.created_at (or msg.created_at_ms) so the final assistant row shows the
correct tooltip/time; mirror this change for any other timestamp fields handled
at creation (the block that records created_at for new messages around where
durations/reasoningDurations are set).
🧹 Nitpick comments (1)
autogpt_platform/frontend/src/app/(platform)/copilot/helpers/__tests__/convertChatSessionToUiMessages.test.ts (1)

218-245: Add a merged assistant timestamp regression.

These tests cover standalone coercion, but they won’t catch timestamp loss in the reasoning→assistant merge branch. Add a case where the following assistant row has created_at and assert the merged message id maps to that timestamp.

🧪 Suggested test coverage
   it("captures created_at when the API mutator has already converted the field to a Date object", () => {
     // The generated `customMutator` runs `transformDates()` on every response,
     // which turns ISO date strings into Date objects before they reach the
     // UI-shape converter.  A literal `typeof === "string"` check would reject
     // the Date and silently drop the timestamp — breaking the "Thought for X"
     // tooltip.  Assert we still recover the ISO value.
     const date = new Date("2026-04-23T01:32:09.871Z");
     const result = convertChatSessionMessagesToUiMessages(
       SESSION_ID,
       [{ role: "user", content: "hi", sequence: 0, created_at: date }],
       { isComplete: true },
     );
 
     const userId = result.messages[0].id;
     expect(result.timestamps.get(userId)).toBe(date.toISOString());
   });
+
+  it("captures created_at from the assistant row when reasoning and assistant rows merge", () => {
+    const reasoningIso = "2026-04-23T01:32:09.000Z";
+    const assistantIso = "2026-04-23T01:32:12.000Z";
+    const result = convertChatSessionMessagesToUiMessages(
+      SESSION_ID,
+      [
+        {
+          role: "reasoning",
+          content: "thinking",
+          sequence: 0,
+          created_at: reasoningIso,
+        },
+        {
+          role: "assistant",
+          content: "reply",
+          sequence: 1,
+          created_at: assistantIso,
+        },
+      ],
+      { isComplete: true },
+    );
+
+    const mergedId = result.messages[0].id;
+    expect(result.timestamps.get(mergedId)).toBe(assistantIso);
+  });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@autogpt_platform/frontend/src/app/`(platform)/copilot/helpers/__tests__/convertChatSessionToUiMessages.test.ts
around lines 218 - 245, Add a test to cover the reasoning→assistant merge path
by calling convertChatSessionMessagesToUiMessages with two rows where a
reasoning/assistant merge will occur: first a reasoning (or assistant with
intermediary content) row and then an assistant row that includes created_at
(e.g., a Date or ISO string), using SESSION_ID and appropriate sequence numbers
so they merge; after conversion, locate the merged message id from
result.messages and assert result.timestamps.get(mergedMessageId) equals the
assistant row's timestamp (normalized to ISO if needed). Ensure the test
exercises convertChatSessionMessagesToUiMessages and checks the merged message
id maps to the assistant's created_at value.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In
`@autogpt_platform/frontend/src/app/`(platform)/copilot/helpers/convertChatSessionToUiMessages.ts:
- Around line 302-310: When merging an assistant message into an existing
UIMessage (inside convertChatSessionToUiMessages) update the created_at
timestamp for the merged prevUI the same way durations and reasoningDurations
are set: when uiRole === "assistant" and prevUI exists, after
prevUI.parts.push(...parts) also set the createdAt map (or the existing
created-timestamp storage used when creating new UI messages) for prevUI.id
using msg.created_at (or msg.created_at_ms) so the final assistant row shows the
correct tooltip/time; mirror this change for any other timestamp fields handled
at creation (the block that records created_at for new messages around where
durations/reasoningDurations are set).

---

Nitpick comments:
In
`@autogpt_platform/frontend/src/app/`(platform)/copilot/helpers/__tests__/convertChatSessionToUiMessages.test.ts:
- Around line 218-245: Add a test to cover the reasoning→assistant merge path by
calling convertChatSessionMessagesToUiMessages with two rows where a
reasoning/assistant merge will occur: first a reasoning (or assistant with
intermediary content) row and then an assistant row that includes created_at
(e.g., a Date or ISO string), using SESSION_ID and appropriate sequence numbers
so they merge; after conversion, locate the merged message id from
result.messages and assert result.timestamps.get(mergedMessageId) equals the
assistant row's timestamp (normalized to ISO if needed). Ensure the test
exercises convertChatSessionMessagesToUiMessages and checks the merged message
id maps to the assistant's created_at value.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4e85dc46-313a-4560-8556-a2c55143d11e

📥 Commits

Reviewing files that changed from the base of the PR and between f4692e6 and f9eac1d.

📒 Files selected for processing (2)
  • autogpt_platform/frontend/src/app/(platform)/copilot/helpers/__tests__/convertChatSessionToUiMessages.test.ts
  • autogpt_platform/frontend/src/app/(platform)/copilot/helpers/convertChatSessionToUiMessages.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (14)
  • GitHub Check: integration_test
  • GitHub Check: lint
  • GitHub Check: check API types
  • GitHub Check: Seer Code Review
  • GitHub Check: type-check (3.13)
  • GitHub Check: type-check (3.11)
  • GitHub Check: type-check (3.12)
  • GitHub Check: test (3.11)
  • GitHub Check: test (3.12)
  • GitHub Check: test (3.13)
  • GitHub Check: end-to-end tests
  • GitHub Check: Check PR Status
  • GitHub Check: Analyze (typescript)
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (10)
autogpt_platform/frontend/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

autogpt_platform/frontend/**/*.{ts,tsx,js,jsx}: Use Node.js 21+ with pnpm package manager for frontend development
Always run 'pnpm format' for formatting and linting code in frontend development

Format frontend code using pnpm format

Files:

  • autogpt_platform/frontend/src/app/(platform)/copilot/helpers/__tests__/convertChatSessionToUiMessages.test.ts
  • autogpt_platform/frontend/src/app/(platform)/copilot/helpers/convertChatSessionToUiMessages.ts
autogpt_platform/frontend/**/*.{tsx,ts}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

autogpt_platform/frontend/**/*.{tsx,ts}: Use function declarations for components and handlers (not arrow functions) in React components
Only use arrow functions for small inline lambdas (map, filter, etc.) in React components
Use PascalCase for component names and camelCase with 'use' prefix for hook names in React
Use Tailwind CSS utilities only for styling in frontend components
Use design system components from 'src/components/' (atoms, molecules, organisms) in frontend development
Never use 'src/components/legacy/' in frontend code
Only use Phosphor Icons (@phosphor-icons/react) for icons in frontend components
Use generated API hooks from '@/app/api/generated/endpoints/' instead of deprecated 'BackendAPI' or 'src/lib/autogpt-server-api/
'
Use React Query for server state (via generated hooks) in frontend development
Default to client components ('use client') in Next.js; only use server components for SEO or extreme TTFB needs
Use '' component for rendering errors in frontend UI; use toast notifications for mutation errors; use 'Sentry.captureException()' for manual exceptions
Separate render logic from data/behavior in React components; keep comments minimal (code should be self-documenting)

Files:

  • autogpt_platform/frontend/src/app/(platform)/copilot/helpers/__tests__/convertChatSessionToUiMessages.test.ts
  • autogpt_platform/frontend/src/app/(platform)/copilot/helpers/convertChatSessionToUiMessages.ts
autogpt_platform/frontend/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

autogpt_platform/frontend/**/*.{ts,tsx}: No barrel files or 'index.ts' re-exports in frontend code
Regenerate API hooks with 'pnpm generate:api' after backend OpenAPI spec changes in frontend development

autogpt_platform/frontend/**/*.{ts,tsx}: Fully capitalize acronyms in symbols, e.g. graphID, useBackendAPI
Use function declarations (not arrow functions) for components and handlers
No dark: Tailwind classes — the design system handles dark mode
Use Next.js <Link> for internal navigation — never raw <a> tags
No any types unless the value genuinely can be anything
No linter suppressors (// @ts-ignore``, // eslint-disable) — fix the actual issue
Keep files under ~200 lines; extract sub-components or hooks into their own files when a file grows beyond this
Keep render functions and hooks under ~50 lines; extract named helpers or sub-components when they grow longer
Use generated API hooks from `@/app/api/generated/endpoints/` with pattern `use{Method}{Version}{OperationName}` and regenerate with `pnpm generate:api`
Do not use `useCallback` or `useMemo` unless asked to optimise a given function
Separate render logic (`.tsx`) from business logic (`use*.ts` hooks)
Use ErrorCard for render errors, toast for mutations, and Sentry for exceptions in the frontend

Files:

  • autogpt_platform/frontend/src/app/(platform)/copilot/helpers/__tests__/convertChatSessionToUiMessages.test.ts
  • autogpt_platform/frontend/src/app/(platform)/copilot/helpers/convertChatSessionToUiMessages.ts
autogpt_platform/frontend/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

autogpt_platform/frontend/src/**/*.{ts,tsx}: Use generated API hooks from @/app/api/__generated__/endpoints/ following the pattern use{Method}{Version}{OperationName}, and regenerate with pnpm generate:api
Separate render logic from business logic using component.tsx + useComponent.ts + helpers.ts pattern, colocate state when possible and avoid creating large components, use sub-components in local /components folder
Use function declarations for components and handlers, use arrow functions only for callbacks
Do not use useCallback or useMemo unless asked to optimise a given function

Files:

  • autogpt_platform/frontend/src/app/(platform)/copilot/helpers/__tests__/convertChatSessionToUiMessages.test.ts
  • autogpt_platform/frontend/src/app/(platform)/copilot/helpers/convertChatSessionToUiMessages.ts
autogpt_platform/frontend/**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

No barrel files or index.ts re-exports in the frontend

Do not type hook returns, let Typescript infer as much as possible

Files:

  • autogpt_platform/frontend/src/app/(platform)/copilot/helpers/__tests__/convertChatSessionToUiMessages.test.ts
  • autogpt_platform/frontend/src/app/(platform)/copilot/helpers/convertChatSessionToUiMessages.ts
autogpt_platform/frontend/src/**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

Do not type hook returns, let Typescript infer as much as possible

Extract component logic into custom hooks grouped by concern, not by component. Each hook should represent a cohesive domain of functionality (e.g., useSearch, useFilters, usePagination) rather than bundling all state into one useComponentState hook. Put each hook in its own .ts file.

Files:

  • autogpt_platform/frontend/src/app/(platform)/copilot/helpers/__tests__/convertChatSessionToUiMessages.test.ts
  • autogpt_platform/frontend/src/app/(platform)/copilot/helpers/convertChatSessionToUiMessages.ts
autogpt_platform/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Never type with any, if no types available use unknown

Files:

  • autogpt_platform/frontend/src/app/(platform)/copilot/helpers/__tests__/convertChatSessionToUiMessages.test.ts
  • autogpt_platform/frontend/src/app/(platform)/copilot/helpers/convertChatSessionToUiMessages.ts
autogpt_platform/frontend/**/*.{test,spec}.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

autogpt_platform/frontend/**/*.{test,spec}.{ts,tsx}: Use Vitest + RTL + MSW for integration tests as the primary testing approach (~90%, page-level), use Playwright for E2E critical flows, and use Storybook for design system components
Run frontend integration tests with pnpm test:unit (Vitest + RTL + MSW)

Files:

  • autogpt_platform/frontend/src/app/(platform)/copilot/helpers/__tests__/convertChatSessionToUiMessages.test.ts
autogpt_platform/frontend/src/app/(platform)/**/__tests__/**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (autogpt_platform/frontend/AGENTS.md)

Write integration tests in __tests__/ next to page.tsx using Vitest + RTL + MSW for new pages/features

Files:

  • autogpt_platform/frontend/src/app/(platform)/copilot/helpers/__tests__/convertChatSessionToUiMessages.test.ts
autogpt_platform/frontend/src/**/__tests__/**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (autogpt_platform/frontend/AGENTS.md)

Use Orval-generated MSW handlers from @/app/api/__generated__/endpoints/{tag}/{tag}.msw.ts for API mocking

Files:

  • autogpt_platform/frontend/src/app/(platform)/copilot/helpers/__tests__/convertChatSessionToUiMessages.test.ts
🧠 Learnings (18)
📓 Common learnings
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12773
File: autogpt_platform/backend/backend/copilot/pending_messages.py:52-64
Timestamp: 2026-04-14T14:36:25.545Z
Learning: In `autogpt_platform/backend/backend/copilot` (PR `#12773`, commit d7bced0c6): when draining pending messages into `session.messages`, each message's text is sanitized via `strip_user_context_tags` before persistence to prevent user-controlled `<user_context>` injection from bypassing the trusted server-side context prefix. Additionally, if `upsert_chat_session` fails after draining, the drained `PendingMessage` objects are requeued back to Redis to avoid silent message loss. Do NOT flag the drain-then-requeue pattern as redundant — it is the intentional failure-resilience strategy for the pending buffer.
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12796
File: autogpt_platform/backend/backend/api/features/chat/routes.py:504-527
Timestamp: 2026-04-16T12:33:44.990Z
Learning: In `autogpt_platform/backend/backend/api/features/chat/routes.py`, `get_session` (PR `#12796`, commit 3771bfad9c1) closes the TOCTOU race between the initial `stream_registry.get_active_session()` pre-check and `get_chat_messages_paginated()` with a post-check re-verification: after the DB fetch, if `is_initial_load and active_session is not None`, it calls `get_active_session` a second time; if `post_active is None` (stream completed during the window), it resets `from_start=True`, `forward_paginated=True`, and re-fetches messages from sequence 0. Do NOT flag the double `get_active_session` call pattern as redundant — it is the intentional TOCTOU mitigation for pagination direction selection.
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12873
File: autogpt_platform/backend/backend/copilot/baseline/reasoning.py:0-0
Timestamp: 2026-04-21T17:31:23.683Z
Learning: In `autogpt_platform/backend/backend/copilot/baseline/reasoning.py` (`BaselineReasoningEmitter`), when `render_in_ui=False`, BOTH the `StreamReasoning*` wire events AND the `ChatMessage(role="reasoning")` persistence append must be suppressed together. `convertChatSessionToUiMessages.ts` unconditionally re-renders all persisted `role="reasoning"` rows as `{type:"reasoning"}` UI parts on reload, so persisting rows while silencing live wire events would resurrect the reasoning collapse on page refresh. The audit trail is preserved through the provider transcript and `_format_sdk_content_blocks` (SDK path) instead. The baseline and SDK paths mirror each other: flag off → no live wire event, no persisted row, no hydrated collapse. This was established in PR `#12873`, commit 7ef10b26c.
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12445
File: autogpt_platform/backend/backend/copilot/sdk/service.py:1071-1072
Timestamp: 2026-03-17T06:48:26.471Z
Learning: In Significant-Gravitas/AutoGPT (autogpt_platform), the AI SDK enforces `z.strictObject({type, errorText})` on SSE `StreamError` responses, so additional fields like `retryable: bool` cannot be added to `StreamError` or serialized via `to_sse()`. Instead, retry signaling for transient Anthropic API errors is done via the `COPILOT_RETRYABLE_ERROR_PREFIX` constant prepended to persisted session messages (in `ChatMessage.content`). The frontend detects retryable errors by checking `markerType === "retryable_error"` from `parseSpecialMarkers()` — no SSE schema changes and no string matching on error text. This pattern was established in PR `#12445`, commit 64d82797b.
Learnt from: Pwuts
Repo: Significant-Gravitas/AutoGPT PR: 12284
File: autogpt_platform/frontend/src/app/api/openapi.json:11897-11900
Timestamp: 2026-03-04T23:58:18.476Z
Learning: Repo: Significant-Gravitas/AutoGPT — PR `#12284`
Backend/frontend OpenAPI codegen convention: In backend/api/features/store/model.py, the StoreSubmission and StoreSubmissionAdminView models define submitted_at: datetime | None, changes_summary: str | None, and instructions: str | None with no default. This is intentional to produce “required but nullable” fields in OpenAPI (properties appear in required[] and use anyOf [type, null]). This matches Prisma’s submittedAt DateTime? and changesSummary String?. Do not flag this as a required/nullable mismatch.
📚 Learning: 2026-04-15T14:10:52.947Z
Learnt from: CR
Repo: Significant-Gravitas/AutoGPT PR: 0
File: autogpt_platform/frontend/src/tests/AGENTS.md:0-0
Timestamp: 2026-04-15T14:10:52.947Z
Learning: Applies to autogpt_platform/frontend/src/tests/src/playwright/**/*.spec.ts : E2E tests must import `test` and `expect` from `./coverage-fixture` instead of `playwright/test` to auto-collect V8 coverage per test for Codecov reporting

Applied to files:

  • autogpt_platform/frontend/src/app/(platform)/copilot/helpers/__tests__/convertChatSessionToUiMessages.test.ts
📚 Learning: 2026-04-08T17:28:40.841Z
Learnt from: CR
Repo: Significant-Gravitas/AutoGPT PR: 0
File: autogpt_platform/frontend/AGENTS.md:0-0
Timestamp: 2026-04-08T17:28:40.841Z
Learning: Applies to autogpt_platform/frontend/src/**/__tests__/**/*.test.{ts,tsx} : Use Orval-generated MSW handlers from `@/app/api/__generated__/endpoints/{tag}/{tag}.msw.ts` for API mocking

Applied to files:

  • autogpt_platform/frontend/src/app/(platform)/copilot/helpers/__tests__/convertChatSessionToUiMessages.test.ts
📚 Learning: 2026-04-15T14:10:52.947Z
Learnt from: CR
Repo: Significant-Gravitas/AutoGPT PR: 0
File: autogpt_platform/frontend/src/tests/AGENTS.md:0-0
Timestamp: 2026-04-15T14:10:52.947Z
Learning: Applies to autogpt_platform/frontend/src/tests/**/__tests__/**/*.test.{ts,tsx} : Test behavior, not implementation—query elements by role/text, not class names

Applied to files:

  • autogpt_platform/frontend/src/app/(platform)/copilot/helpers/__tests__/convertChatSessionToUiMessages.test.ts
📚 Learning: 2026-04-08T17:27:57.501Z
Learnt from: CR
Repo: Significant-Gravitas/AutoGPT PR: 0
File: autogpt_platform/AGENTS.md:0-0
Timestamp: 2026-04-08T17:27:57.501Z
Learning: Applies to autogpt_platform/frontend/**/*.spec.{ts,tsx} : Create a failing test first using `.fixme` marker (Playwright) when fixing a bug or adding a feature, then implement the fix and remove the fixme marker

Applied to files:

  • autogpt_platform/frontend/src/app/(platform)/copilot/helpers/__tests__/convertChatSessionToUiMessages.test.ts
📚 Learning: 2026-04-15T14:10:52.947Z
Learnt from: CR
Repo: Significant-Gravitas/AutoGPT PR: 0
File: autogpt_platform/frontend/src/tests/AGENTS.md:0-0
Timestamp: 2026-04-15T14:10:52.947Z
Learning: Applies to autogpt_platform/frontend/src/tests/**/*.test.{ts,tsx} : Place unit tests co-located with the file being tested: `Component.test.tsx` next to `Component.tsx` or `utils.test.ts` next to `utils.ts`

Applied to files:

  • autogpt_platform/frontend/src/app/(platform)/copilot/helpers/__tests__/convertChatSessionToUiMessages.test.ts
📚 Learning: 2026-04-08T17:28:40.841Z
Learnt from: CR
Repo: Significant-Gravitas/AutoGPT PR: 0
File: autogpt_platform/frontend/AGENTS.md:0-0
Timestamp: 2026-04-08T17:28:40.841Z
Learning: Applies to autogpt_platform/frontend/src/app/(platform)/**/__tests__/**/*.test.{ts,tsx} : Write integration tests in `__tests__/` next to `page.tsx` using Vitest + RTL + MSW for new pages/features

Applied to files:

  • autogpt_platform/frontend/src/app/(platform)/copilot/helpers/__tests__/convertChatSessionToUiMessages.test.ts
📚 Learning: 2026-04-08T17:27:45.740Z
Learnt from: CR
Repo: Significant-Gravitas/AutoGPT PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-08T17:27:45.740Z
Learning: Applies to autogpt_platform/frontend/**/*.{test,spec}.{ts,tsx} : Run frontend integration tests with `pnpm test:unit` (Vitest + RTL + MSW)

Applied to files:

  • autogpt_platform/frontend/src/app/(platform)/copilot/helpers/__tests__/convertChatSessionToUiMessages.test.ts
📚 Learning: 2026-04-15T14:10:52.947Z
Learnt from: CR
Repo: Significant-Gravitas/AutoGPT PR: 0
File: autogpt_platform/frontend/src/tests/AGENTS.md:0-0
Timestamp: 2026-04-15T14:10:52.947Z
Learning: Applies to autogpt_platform/frontend/src/tests/**/__tests__/**/*.test.{ts,tsx} : Use `findBy...` methods in integration tests most of the time as they wait for elements to appear, preventing flaky tests caused by async code

Applied to files:

  • autogpt_platform/frontend/src/app/(platform)/copilot/helpers/__tests__/convertChatSessionToUiMessages.test.ts
📚 Learning: 2026-04-15T14:10:52.947Z
Learnt from: CR
Repo: Significant-Gravitas/AutoGPT PR: 0
File: autogpt_platform/frontend/src/tests/AGENTS.md:0-0
Timestamp: 2026-04-15T14:10:52.947Z
Learning: Applies to autogpt_platform/frontend/src/tests/**/__tests__/**/*.test.{ts,tsx} : Place integration tests in a `__tests__` folder next to the component being tested

Applied to files:

  • autogpt_platform/frontend/src/app/(platform)/copilot/helpers/__tests__/convertChatSessionToUiMessages.test.ts
📚 Learning: 2026-04-15T14:10:18.177Z
Learnt from: CR
Repo: Significant-Gravitas/AutoGPT PR: 0
File: autogpt_platform/backend/backend/copilot/graphiti/CLAUDE.md:0-0
Timestamp: 2026-04-15T14:10:18.177Z
Learning: Applies to autogpt_platform/backend/backend/copilot/graphiti/**/*agent*.{ts,tsx} : Use dependency injection for agent dependencies to improve testability

Applied to files:

  • autogpt_platform/frontend/src/app/(platform)/copilot/helpers/__tests__/convertChatSessionToUiMessages.test.ts
📚 Learning: 2026-03-24T02:23:31.305Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12526
File: autogpt_platform/frontend/src/app/(platform)/copilot/components/RateLimitResetDialog/RateLimitResetDialog.tsx:0-0
Timestamp: 2026-03-24T02:23:31.305Z
Learning: In the Copilot platform UI code, follow the established Orval hook `onError` error-handling convention: first explicitly detect/handle `ApiError`, then read `error.response?.detail` (if present) as the primary message; if not available, fall back to `error.message`; and finally fall back to a generic string message. This convention should be used for generated Orval hooks even if the custom Orval mutator already maps details into `ApiError.message`, to keep consistency across hooks/components (e.g., `useCronSchedulerDialog.ts`, `useRunGraph.ts`, and rate-limit/reset flows).

Applied to files:

  • autogpt_platform/frontend/src/app/(platform)/copilot/helpers/__tests__/convertChatSessionToUiMessages.test.ts
  • autogpt_platform/frontend/src/app/(platform)/copilot/helpers/convertChatSessionToUiMessages.ts
📚 Learning: 2026-04-01T18:54:16.035Z
Learnt from: Bentlybro
Repo: Significant-Gravitas/AutoGPT PR: 12633
File: autogpt_platform/frontend/src/app/(platform)/library/components/AgentFilterMenu/AgentFilterMenu.tsx:3-10
Timestamp: 2026-04-01T18:54:16.035Z
Learning: In the frontend, the legacy Select component at `@/components/__legacy__/ui/select` is an intentional, codebase-wide visual-consistency pattern. During code reviews, do not flag or block PRs merely for continuing to use this legacy Select. If a migration to the newer design-system Select is desired, bundle it into a single dedicated cleanup/migration PR that updates all Select usages together (e.g., avoid piecemeal replacements).

Applied to files:

  • autogpt_platform/frontend/src/app/(platform)/copilot/helpers/__tests__/convertChatSessionToUiMessages.test.ts
  • autogpt_platform/frontend/src/app/(platform)/copilot/helpers/convertChatSessionToUiMessages.ts
📚 Learning: 2026-04-07T09:24:16.582Z
Learnt from: 0ubbe
Repo: Significant-Gravitas/AutoGPT PR: 12686
File: autogpt_platform/frontend/src/app/(no-navbar)/onboarding/steps/__tests__/PainPointsStep.test.tsx:1-19
Timestamp: 2026-04-07T09:24:16.582Z
Learning: In Significant-Gravitas/AutoGPT’s `autogpt_platform/frontend` (Vite + `vitejs/plugin-react` with the automatic JSX transform), do not flag usages of React types/components (e.g., `React.ReactNode`) in `.ts`/`.tsx` files as missing `React` imports. Since the React namespace is made available by the project’s TS/Vite setup, an explicit `import React from 'react'` or `import type { ReactNode } ...` is not required; only treat it as missing if typechecking (e.g., `pnpm types`) would actually fail.

Applied to files:

  • autogpt_platform/frontend/src/app/(platform)/copilot/helpers/__tests__/convertChatSessionToUiMessages.test.ts
  • autogpt_platform/frontend/src/app/(platform)/copilot/helpers/convertChatSessionToUiMessages.ts
📚 Learning: 2026-04-02T05:43:49.128Z
Learnt from: 0ubbe
Repo: Significant-Gravitas/AutoGPT PR: 12640
File: autogpt_platform/frontend/src/app/(no-navbar)/onboarding/steps/WelcomeStep.tsx:13-13
Timestamp: 2026-04-02T05:43:49.128Z
Learning: Do not flag `import { Question } from "phosphor-icons/react"` as an invalid import. `Question` is a valid named export from `phosphor-icons/react` (as reflected in the package’s generated `.d.ts` files and re-exports via `dist/index.d.ts`), so it should be treated as a supported named export during code reviews.

Applied to files:

  • autogpt_platform/frontend/src/app/(platform)/copilot/helpers/__tests__/convertChatSessionToUiMessages.test.ts
  • autogpt_platform/frontend/src/app/(platform)/copilot/helpers/convertChatSessionToUiMessages.ts
📚 Learning: 2026-04-20T20:07:22.981Z
Learnt from: ntindle
Repo: Significant-Gravitas/AutoGPT PR: 11235
File: autogpt_platform/frontend/src/app/(platform)/admin/diagnostics/__tests__/ExecutionsTable.test.tsx:27-76
Timestamp: 2026-04-20T20:07:22.981Z
Learning: In this codebase, Orval-generated API modules under `src/app/api/__generated__/` are not committed to git and must be generated via `pnpm generate:api` (requires a running backend). In integration tests, it’s acceptable—and expected—to stub generated hooks/modules by mocking them with `vi.mock("@/app/api/__generated__/endpoints/{tag}/{tag}")`. Do not treat `vi.mock` of these generated hook modules as a violation of the MSW handler guideline, since the corresponding MSW handlers cannot be imported at test time when generated files are absent.

Applied to files:

  • autogpt_platform/frontend/src/app/(platform)/copilot/helpers/__tests__/convertChatSessionToUiMessages.test.ts
📚 Learning: 2026-04-21T17:31:23.683Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12873
File: autogpt_platform/backend/backend/copilot/baseline/reasoning.py:0-0
Timestamp: 2026-04-21T17:31:23.683Z
Learning: In `autogpt_platform/backend/backend/copilot/baseline/reasoning.py` (`BaselineReasoningEmitter`), when `render_in_ui=False`, BOTH the `StreamReasoning*` wire events AND the `ChatMessage(role="reasoning")` persistence append must be suppressed together. `convertChatSessionToUiMessages.ts` unconditionally re-renders all persisted `role="reasoning"` rows as `{type:"reasoning"}` UI parts on reload, so persisting rows while silencing live wire events would resurrect the reasoning collapse on page refresh. The audit trail is preserved through the provider transcript and `_format_sdk_content_blocks` (SDK path) instead. The baseline and SDK paths mirror each other: flag off → no live wire event, no persisted row, no hydrated collapse. This was established in PR `#12873`, commit 7ef10b26c.

Applied to files:

  • autogpt_platform/frontend/src/app/(platform)/copilot/helpers/convertChatSessionToUiMessages.ts
📚 Learning: 2026-03-17T06:48:26.471Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12445
File: autogpt_platform/backend/backend/copilot/sdk/service.py:1071-1072
Timestamp: 2026-03-17T06:48:26.471Z
Learning: In Significant-Gravitas/AutoGPT (autogpt_platform), the AI SDK enforces `z.strictObject({type, errorText})` on SSE `StreamError` responses, so additional fields like `retryable: bool` cannot be added to `StreamError` or serialized via `to_sse()`. Instead, retry signaling for transient Anthropic API errors is done via the `COPILOT_RETRYABLE_ERROR_PREFIX` constant prepended to persisted session messages (in `ChatMessage.content`). The frontend detects retryable errors by checking `markerType === "retryable_error"` from `parseSpecialMarkers()` — no SSE schema changes and no string matching on error text. This pattern was established in PR `#12445`, commit 64d82797b.

Applied to files:

  • autogpt_platform/frontend/src/app/(platform)/copilot/helpers/convertChatSessionToUiMessages.ts
🔇 Additional comments (1)
autogpt_platform/frontend/src/app/(platform)/copilot/helpers/convertChatSessionToUiMessages.ts (1)

44-55: Good normalization for API-mutated timestamps.

Accepting both ISO strings and Date objects keeps created_at stable after the response mutator transforms date fields.

Codecov patch was failing at 55.55% (target 80%) — the large new bundle
of display logic in TurnStatsBar had zero coverage.

Added:
- TurnStatsBar: 9 tests covering elapsed vs reasoning vs wall-clock
  preference, sub-second reasoning flooring, timestamp tooltip,
  timestamp-only row, malformed timestamp passthrough, and work-done
  counters with the pluralisation edge case.
- useCopilotPage: 1 test covering the paged + historical duration /
  reasoning / timestamp map merge with current-page winning on overlap.
…ation is enough

The reasoning-only tracking shaved tool-execution time off "Thought for X",
but the user feedback was the whole-turn wall clock (which already existed
before this PR) is the right number.  Ripping out the rest of the machinery:

- DB: drop `ChatMessage.reasoningDurationMs` column + migration.
- Backend: remove `_record_reasoning_event` from stream_registry, the
  publish_chunk side-effect, the `reasoning_ms_total` meta hash, the
  per-turn HDEL cleanup, and the `reasoning_duration_ms` kwarg on
  `set_turn_duration`. `mark_session_completed` now just snapshots
  `duration_ms` as before.
- Pydantic: `ChatMessage.reasoning_duration_ms` gone.
- Tests: drop the `_record_reasoning_event` suite + the
  `mark_session_completed_clears_reasoning_counters` test + the
  `set_turn_duration_persists_reasoning_duration` test.
- Frontend: `TurnStats.reasoningDurationMs` gone from the interface,
  the coerce step, the patch logic, and `resolveDisplaySeconds` (now
  just `elapsedSeconds || durationMs`).  Tests trimmed accordingly.

Kept: `created_at` per message (powers the hover-to-show-date label),
`duration_ms` (whole-turn, existed before this PR), and the single
`TurnStats` map plumbing.

Net: -386 lines across 12 files.
@github-actions github-actions Bot added size/l and removed size/xl labels Apr 23, 2026
majdyz added a commit that referenced this pull request Apr 23, 2026
@majdyz

majdyz commented Apr 23, 2026

Copy link
Copy Markdown
Contributor Author

E2E Test Report — PR #12890

Date: 2026-04-23
Branch: feat/copilot-message-timestamps
HEAD: a2514e213 (tip)
Worktree: /Users/majdyz/Code/AutoGPT4
Mode: native (stack already running, not started or stopped by test run)

Summary

Result: PASS

During the run, the PR scope changed twice on top of what the test brief described.
The brief asked to validate "prefer reasoning_duration_ms over duration_ms" + Radix
tooltip. The current PR HEAD (a2514e213) is the opposite:

  1. ae5b38231 replaced the Radix tooltip with an in-place button that swaps
    label text on hover/click between "Thought for X" and the local date.
  2. a2514e213 dropped reasoningDurationMs entirely — the column, the
    migration, the backend bookkeeping, and the frontend branch in
    resolveDisplaySeconds. duration_ms (whole-turn wall clock, pre-existing)
    is now the only duration source.

All 5 brief scenarios mapped to the revised behaviour pass.

Environment notes

  • Backend (/tmp/pr12890-backend.log): poetry run app already running on :8006.
  • Frontend (/tmp/pr12890-frontend.log): pnpm run dev already running on :3000.
  • Login: test@test.com / testtest123 (local supabase — the dev-builder creds
    in memory were for the hosted dev env, not the native local stack).
  • DB state on arrival: prisma_migrations row for 20260423120000_add_reasoning_duration_ms
    was marked finished, but the reasoningDurationMs column was NOT present. I
    added it manually for scenario 1-testing only; a2514e213 later removed the
    feature so the column is now legitimately gone from the PR surface.
  • At the start of my run the working tree had staged, unpushed edits
    mirroring what a2514e213 later landed. I reset + restored from HEAD to test
    each version cleanly; no edits of my own were committed.

Scenarios

1. Fresh extended-thinking message

Expected (revised): "Thought for X" populated from duration_ms.
Actual: Could not run a fresh live turn — the backend's
CLAUDE_CODE_OAUTH_TOKEN in .env is older than the keychain copy. I cannot
restart the process per the brief, so subscription-mode requests return 401
(authentication_error). I updated .env with the fresh keychain token for
future runs but the already-running process won't pick it up until restart.
I validated the code path using an existing turn where both durationMs
(53575 ms) and reasoningDurationMs (backfilled to 7000 ms) were present
against the older commit (ae5b38231): UI correctly rendered "Thought for 7s",
confirming the pre-a2514e213 preference worked. After a2514e213 landed
mid-run, the UI with durationMs=53575 correctly renders "Thought for 54s".
Result: PASS (label renders from persisted duration)
Screenshots: 03-initial-turn-stats.png, 09-final-durationms-label.png

2. Hover label → local date

Expected (revised): hover swaps "Thought for X" → "Apr 23, 2026".
Actual: Hover replaces the button text with toLocaleDateString({dateStyle:"medium"})
output ("Apr 23, 2026"). Note: no tooltip popover — the brief's "Radix tooltip"
wording is stale per ae5b38231.
Result: PASS
Screenshots: 10-final-hover-date.png, 08-legacy-hover-shows-date.png

3. Reload persists label + hover

Expected: "Thought for 54s" survives a hard reload and hover still swaps to date.
Actual: Reload keeps "Thought for 54s" (sourced from the ChatMessage row);
re-hover still yields the date. The created_at field round-trips through the
session API as ISO string and the frontend converts it client-side.
Result: PASS
Screenshots: 12-reload-persists.png, 04-reload-persists-initial.png

4. Legacy row (durationMs only, no reasoning data)

Expected: rounded whole-turn seconds, hover still shows date (if created_at present).
Actual: "Simple Math Question" session with durationMs=53575, reasoningDurationMs=NULL
renders "Thought for 54s"; hover → "Apr 23, 2026".
Result: PASS
Screenshots: 05-legacy-durationms-label.png, 07-legacy-label-rechecked.png, 08-legacy-hover-shows-date.png

5. Both durations null

Expected: date-only rendering.
Actual: "Four" session after manually nulling durationMs renders just
"Apr 23, 2026" (no "Thought for" prefix).
Result: PASS
Screenshot: 06-both-null-date-only.png, 11-both-null-final.png

Bugs found

None against the current PR HEAD. Two observations worth the reviewer's eye:

  1. Migration drift between the test brief and HEAD. The brief lists a
    20260423120000_add_reasoning_duration_ms migration as applied; the PR's
    final state has removed both the migration file and the column. Any
    database where the earlier migration ran will now carry a dangling
    reasoningDurationMs column and no corresponding DROP COLUMN migration
    to reverse it. If this is intentional (no rollback migration) that's fine
    for a feature that never shipped to prod; if not, a
    20260423120001_drop_reasoning_duration_ms migration might be wanted.

  2. Test brief staleness. The scenarios in the brief still reference Radix
    tooltip + reasoning preference. Future /pr-test runs should pull the
    latest HEAD diff before acting on a stale brief.

Fixes applied

None — the scope change from the user's own commits made the "bug" the
feature in the original brief, and the revised feature passes.

Screenshots

01-copilot-logged-in.png

02-copilot-landing.png

03-initial-turn-stats.png

04-reload-persists-initial.png

05-legacy-durationms-label.png

06-both-null-date-only.png

07-legacy-label-rechecked.png

08-legacy-hover-shows-date.png

09-final-durationms-label.png

10-final-hover-date.png

11-both-null-final.png

12-reload-persists.png

… timestamp

Two fixes based on user feedback:

- "Thought for X" is hover-only on rows that HAVE a duration. Drop the
  fallback that rendered the raw timestamp as a permanent span on
  duration-less rows — it broke "show on hover" semantics.
- `useElapsedTimer` now accepts an optional `anchorIso` ISO timestamp. When
  provided it counts from that absolute wall-clock point instead of from the
  hook's first-render moment. `ChatMessagesContainer` picks the newest
  `createdAt` among the current turn's messages (last user message / tool
  result / whatever) and passes it. Result: refreshing the page mid-turn
  shows the accurate "Considering 42s" that reflects real elapsed time
  since the last server-recorded activity, not 2s from remount.
majdyz added 3 commits April 23, 2026 17:33
…ser message only

Previous anchor walked backward through every role — on a fresh send it
picked up the PREVIOUS turn's assistant (persisted 30s+ ago), making the
live counter start at "Thinking... • 34s" before the new turn had done any
actual thinking.

New rule: only use messages[-2] as the anchor, and only when it's a user
message AND has a turnStats createdAt (i.e. the server has persisted it
and we've rehydrated turnStats).  On a fresh send the just-optimistic
user message isn't in turnStats yet → anchor is null → the timer falls
back to mount-time counting from 0 (same as pre-PR behaviour).  On a
page refresh mid-turn → turnStats has the user message → anchor is the
real send time → the counter shows actual elapsed wall clock.
- Anchor: simpler walk-backward; messages are already in chronological
  order, so the first createdAt we hit is the latest.  Keep the turn
  boundary (stop at the user message) so a fresh send doesn't anchor
  to the previous turn's assistant.
- Converter: on assistant-merge, advance createdAt to the latest sub-row
  so the live counter reflects "time since most recent step" instead of
  "time since turn start" for multi-step turns.
- Hover: a 200ms fade + subtle text-color darkening on the label swap
  so it doesn't feel clunky.  Keyed remount triggers tailwindcss-animate.
@github-actions github-actions Bot added size/xl and removed size/l labels Apr 23, 2026
@github-actions github-actions Bot added size/l and removed size/xl labels Apr 23, 2026
majdyz added 2 commits April 23, 2026 17:53
- TurnStatsBar: hide 'Thought for 0s' when durationMs rounds to 0 (1-499ms)
- useElapsedTimer: re-sync start time when anchorIso changes mid-run
@github-actions github-actions Bot added size/xl and removed size/l labels Apr 23, 2026
majdyz added 4 commits April 23, 2026 18:06
- convertChatSessionToUiMessages: regression for the merge path's "advance
  createdAt to the latest row" behaviour — load-bearing for the live
  "Thinking Xs" anchor accuracy on multi-step turns.
- ChatMessagesContainer: cover the user-message timestamp render path
  (hover reveal) and the null-turnStats fallback.
- model_test: unit test ChatMessage.from_db round-tripping createdAt.
@majdyz
majdyz force-pushed the feat/copilot-message-timestamps branch from a95ce6a to 80e6882 Compare April 23, 2026 11:42
@majdyz
majdyz merged commit 81d6e91 into dev Apr 23, 2026
42 of 44 checks passed
@majdyz
majdyz deleted the feat/copilot-message-timestamps branch April 23, 2026 11:55
@github-project-automation github-project-automation Bot moved this to Done in Frontend Apr 23, 2026
@github-project-automation github-project-automation Bot moved this from 🆕 Needs initial review to ✅ Done in AutoGPT development kanban Apr 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

platform/backend AutoGPT Platform - Back end platform/frontend AutoGPT Platform - Front end size/xl

Projects

Status: ✅ Done
Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant