Repository navigation
Conversation
…Gravitas#13666) ### Why / What / How The Library could only be sorted by creation date or last-modified date, so an agent you just ran could be buried far down the list with no way to surface it. Users want to quickly find the agents they've been running. This PR adds a **"Last Run"** sort option to the Library that orders agents by the timestamp of their most recent execution. **How:** - A new nullable `lastRunAt` column on `LibraryAgent` records when each agent was last run. - `create_graph_execution` stamps `lastRunAt = now()` on the user's matching `LibraryAgent`(s) whenever a run is created. This is best-effort: a failure is logged and does not block execution. - The Library listing can be sorted by `lastRunAt` (**"Last Run"**) Closes Significant-Gravitas#9860 ### Changes 🏗️ **Backend** - `schema.prisma`: add nullable `LibraryAgent.lastRunAt`, with migration `20260723120000_add_library_agent_last_run_at`. - `data/execution.py`: update `LibraryAgent.lastRunAt` on graph-execution creation (best-effort, non-blocking). - `api/features/library/model.py`: expose `last_run_at` on the `LibraryAgent` model and add `LAST_RUN` to `LibraryAgentSort`. - `api/features/library/db.py`: order by `lastRunAt` descending when sorting by last run. **Frontend** - `LibrarySortMenu`: add the "Last Run" option and its label. - Regenerated `openapi.json` (generated API client follows at build time). ### Checklist 📋 #### For code changes: - [x] I have clearly listed my changes in the PR description - [x] I have made a test plan - [x] I have tested my changes according to the test plan: - [x] Run an agent, open the Library, sort by "Last Run" — the just-run agent appears at the top - [x] Run a second agent and confirm it moves above the first - [x] Confirm "Creation Date" and "Last Modified" sorts are unaffected - [x] Confirm agents that have never been run still appear in the list --- Supersedes and builds on the earlier implementations in Significant-Gravitas#11891 and Significant-Gravitas#13351. Co-authored-by: @Medyan-Naser <73041442+Medyan-Naser@users.noreply.github.com> Co-authored-by: @jashwanth-reddy-g <289575991+jashwanth-reddy-g@users.noreply.github.com> Co-authored-by: Reinier van der Leer <pwuts@agpt.co>
…smatch (Significant-Gravitas#13559) ### Why / What / How **Why.** The webhook ingress route (`webhook_ingress_generic`) picks the `WebhooksManager` from the `{provider}` in the URL path, then loads the stored webhook row by ID — but never checks that the two agree. Because the manager is chosen from the (untrusted) path, a webhook registered under one provider can be processed by a *different* provider's manager. Managers for providers whose protocol has no signing scheme (Compass, Slant3D) inherit the no-op `verify_signature` from `BaseWebhooksManager`, so routing a webhook's UUID through one of those paths skips the verification the webhook's real provider would enforce. Concretely: a generic webhook configured with a `secret_token` correctly rejects a request missing `X-Webhook-Secret` on its own `/generic_webhook/...` path, but the same UUID sent to `/compass/webhooks/{id}/ingress` runs Compass's no-op verifier instead of the generic manager's secret check, and the attached graph executes. This is a gap from when signature verification was consolidated into a separate, provider-overridable `verify_signature` step (Significant-Gravitas#13135): the step keys off the URL provider rather than the stored webhook's provider. **What.** Reject requests whose URL provider doesn't match the stored `webhook.provider`, before verification and payload validation. **How.** After the webhook row loads, compare `webhook.provider` to the path `provider`. On mismatch, return **403** (consistent with the existing "don't 404 — that leaks webhook existence" handling) and log the mismatch. No change to any valid-path behavior. ### Changes 🏗️ | File | Change | |---|---| | `api/features/integrations/router.py` | Guard `webhook_ingress_generic`: 403 when `webhook.provider != provider`, before `verify_signature` / `validate_payload` | | `api/features/integrations/webhook_ingress_test.py` | Add `TestProviderPathConfusion`: a secret-protected generic webhook can't be triggered via `/compass/...` or `/slant3d/...`; matching-provider path still processes | ### Checklist 📋 #### For code changes: - [x] I have clearly listed my changes in the PR description - [x] I have made a test plan - [x] I have tested my changes according to the test plan: - [x] `poetry run pytest backend/api/features/integrations/webhook_ingress_test.py` — 27 passed - [x] Verified the two new cross-provider tests fail with the guard removed and pass with it in place - [x] `ruff` / `black` / `isort` / `pyright` clean on changed files --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This PR fixes 1 broken outbound documentation link: - `README.md`: `https://polyformproject.org/licenses/shield/1.0.0` (removed breaking trailing slash) --------- Co-authored-by: mirkosalvato1-ctrl <227404546+mirkosalvato1-ctrl@users.noreply.github.com> Co-authored-by: Reinier van der Leer <github@pwuts.nl>
…its don't drop it (Significant-Gravitas#13441) ### Why / What / How **Why** — When AutoPilot edits an agent, the agent-graph tools frequently receive no usable `agent_json` (OPEN-3188): the tools declared it as a bare `{"type": "object"}`, and constrained tool-argument decoders (SDK/OpenRouter path) commonly collapse an unstructured object to `{}` or drop it — worst exactly when editing a large existing agent. **What** - Structured `AGENT_JSON_SCHEMA` (typed `["object", "string"]`, `nodes`/`links`/… properties) so decoders have keys to follow instead of collapsing the graph. - `agent_json_ref` param on all five agent-graph tools: point at the workspace `agent.json` instead of re-emitting the graph inline. - `write_graph_to` param on `find_library_agent` (review request): writes the agent's graph server-side to a workspace file and returns an `@@agptfile` ref for `edit_agent`, so existing graphs never transit context. **How** — A shared `agent_json_input` module resolves all input forms server-side (inline object, stringified JSON, `@@agptfile:` token, `workspace://` path) with sanitized errors, on both the SDK and baseline paths. Reconciled with Significant-Gravitas#13579 after it merged: its `"@@agptfile:<path>"`-string contract is kept and now also works on the baseline path; its `coerce_agent_json` is subsumed by the shared resolver; its `fix_agent_graph` `write_to`/`fix_diff` are preserved (write helper now shared). Also fixes a regression where the structured schema made SDK file-ref expansion recurse into inline graphs and expand tokens inside `name`/`description`. Deferred (per ticket): diff/patch-style `edit_agent`. ### Changes 🏗️ - **New** `copilot/tools/agent_json_input.py` — shared schema, input resolver, workspace-write helper - `create_agent` / `edit_agent` / `customize_agent` / `validate_agent_graph` / `fix_agent_graph` — structured schema + `agent_json_ref` - `find_library_agent` — `write_graph_to` file-output mode - `sdk/file_ref.py` — union `object/string` slots skip dict recursion - `sdk/agent_generation_guide.md` — editing flow leads with `write_graph_to`; single canonical ref spelling - Tests: resolver suite (15 cases) + ref/write coverage across the tool suites; schema char budget re-measured 47800→51500 ### Checklist 📋 #### For code changes: - [x] I have clearly listed my changes in the PR description - [x] I have made a test plan - [x] I have tested my changes according to the test plan: - [x] `agent_json_input_test.py`, `edit_agent_test.py`, `find_library_agent_test.py`, `fix_agent_test.py`, `validate_agent_test.py`, `tool_schema_test.py`, `file_ref_test.py` — all green locally and in CI - [x] `poetry run format` / `lint` clean --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…icant-Gravitas#13645) ### Why / What / How **Why:** Users sharing a screenshot with CoPilot currently have to save it to disk, click the "+" button, and pick the file. Pasting an image straight from the clipboard (Ctrl/Cmd+V) — standard UX in ChatGPT, Claude, Slack, Discord — did nothing. Fixes OPEN-3151. **What:** Pasting into the CoPilot chat input now attaches clipboard files (screenshots, copied images, files copied from the OS) to the message being composed, reusing the exact same local-attachment flow as the "+" menu and drag-and-drop. Plain-text paste is untouched. **How:** A `handlePaste` handler on the composer textarea reads `ClipboardEvent.clipboardData.files`. When files are present it prevents the default paste and appends them to the existing `attachments` state (`kind: "local"`), so upload, validation (max 10 files / 100MB in `useSendMessage`), chips display, and multimodal handling all work unchanged. Generic clipboard screenshot names (`image.png`) are renamed to timestamped `pasted-image-…` names so multiple pastes stay distinguishable. Paste-attach is ignored while the composer is busy (disabled/streaming/uploading), matching the disabled "+" menu. ### Changes 🏗️ - `ChatInput.tsx`: add `handlePaste` wired to `PromptInputTextarea`'s `onPaste`; attaches clipboard files via the existing `handleFilesSelected` path - `helpers.ts`: add `getFilesFromClipboard` — extracts files from the clipboard and renames generic `image.*` names to unique `pasted-image-<timestamp>` names - `ChatInput.test.tsx`: 7 new integration tests covering paste-attach + send, attachment-only send, name preservation for non-generic files, distinct names for multiple generic images, default-paste prevention, plain-text paste passthrough, and no-attach while uploading ### Checklist 📋 #### For code changes: - [x] I have clearly listed my changes in the PR description - [x] I have made a test plan - [x] I have tested my changes according to the test plan: - [x] `pnpm vitest run src/app/(platform)/copilot` — all 106 files / 1352 tests pass, including 7 new clipboard-paste tests - [x] `prettier --check`, `next lint`, `tsc --noEmit` all clean - [ ] Manual verification on preview: copy a screenshot, focus the CoPilot input, press Ctrl/Cmd+V → image attaches as a chip and sends <details> <summary>Example test plan</summary> - [ ] Create from scratch and execute an agent with at least 3 blocks - [ ] Import an agent from file upload, and confirm it executes correctly - [ ] Upload agent to marketplace - [ ] Import an agent from marketplace and confirm it executes correctly - [ ] Edit an agent from monitor, and confirm it executes correctly </details> #### For configuration changes: - [ ] `.env.default` is updated or already compatible with my changes - [ ] `docker-compose.yml` is updated or already compatible with my changes - [ ] I have included a list of my configuration changes in the PR description (under **Changes**) <details> <summary>Examples of configuration changes</summary> - Changing ports - Adding new services that need to communicate with each other - Secrets or environment variable changes - New or infrastructure changes such as databases </details> 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_019Vb399jnB1hWqSWPi6UPCc --- _Generated by [Claude Code](https://claude.ai/code/session_019Vb399jnB1hWqSWPi6UPCc)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
…3675) ## Summary - Adds a visually hidden description to the search command dialog content. - Adds focused regression coverage that verifies the dialog exposes an aria-describedby target. Fixes Significant-Gravitas#11035 ## Testing - Focused Vitest run for `SearchCommandModal.accessibility.test.tsx` passed with a local no-setup config. - `node_modules/.bin/prettier.cmd --check src/components/organisms/SearchCommandModal/SearchCommandModal.tsx src/components/organisms/SearchCommandModal/__tests__/SearchCommandModal.accessibility.test.tsx src/components/organisms/SearchCommandModal/__tests__/SearchCommandModal.test.tsx` ## Notes - `node_modules/.bin/tsc.cmd --noEmit --pretty false` is currently blocked locally by missing generated API modules under `src/app/api/__generated__`, also present outside this change. Co-authored-by: zq <zhouquan1511@163.com>
…ificant-Gravitas#13677) ### Why / What / How <!-- Why: Why does this PR exist? What problem does it solve, or what's broken/missing without it? --> **Why:** This PR addresses an issue where accessing a nonexistent creator page (e.g., `/marketplace/creator/nonexistent-slug`) would cause an unhandled `ApiError: Not Found` during Next.js metadata generation. Previously, the `generateMetadata` function in `autogpt_platform/frontend/src/app/(platform)/marketplace/creator/[creator]/page.tsx` called `getV2GetCreatorDetails` without error handling. When the backend returned a 404 for a missing creator, `customMutator` would throw an `ApiError`, which propagated as an unhandled exception, leading to errors in production logs (primarily from bots/crawlers). <!-- What: What does this PR change? Summarize the changes at a high level. --> **What:** This change introduces robust error handling within the `generateMetadata` function for creator pages. <!-- How: How does it work? Describe the approach, key implementation details, or architecture decisions. --> **How:** 1. Imported `ApiError` from `@/lib/autogpt-server-api/helpers` and `notFound` from `next/navigation`. 2. Wrapped the `await getV2GetCreatorDetails(...)` call in a `try/catch` block. 3. Inside the `catch` block, if the error is an `ApiError` with a `status` of 404, `notFound()` is called. This correctly signals to Next.js that the resource is not found, rendering the nearest `not-found.tsx` boundary and returning a 404 HTTP status. 4. Any other type of error or `ApiError` with a non-404 status is re-thrown, ensuring that genuine server errors or unexpected issues are still surfaced and handled by the global error boundary or Sentry. ### Changes 🏗️ <!-- List the key changes. Keep it higher level than the diff but specific enough to highlight what's new/modified. --> - Implemented `try/catch` block for `getV2GetCreatorDetails` call within `generateMetadata` function in `autogpt_platform/frontend/src/app/(platform)/marketplace/creator/[creator]/page.tsx`. - Added specific error handling for `ApiError` with a 404 status, utilizing `notFound()` from `next/navigation`. - Ensured other errors are re-thrown for proper surfacing. - Imported necessary modules: `ApiError` and `notFound`. ### Checklist 📋 #### For code changes: - [x] I have clearly listed my changes in the PR description - [x] I have made a test plan - [ ] I have tested my changes according to the test plan: <!-- Put your test plan here: --> - [ ] Navigate to a valid creator page (e.g., `/marketplace/creator/some-existing-creator-slug`) and verify it loads correctly. - [ ] Navigate to a nonexistent creator page (e.g., `/marketplace/creator/nonexistent-slug`) and verify that the 404 page is displayed. - [ ] Check browser console and server logs to ensure no unhandled errors or exceptions are thrown when accessing a nonexistent creator page. <details> <summary>Example test plan</summary> - [ ] Create from scratch and execute an agent with at least 3 blocks - [ ] Import an agent from file upload, and confirm it executes correctly - [ ] Upload agent to marketplace - [ ] Import an agent from marketplace and confirm it executes correctly - [ ] Edit an agent from monitor, and confirm it executes correctly </details> #### For configuration changes: - [ ] `.env.default` is updated or already compatible with my changes - [ ] `docker-compose.yml` is updated or already compatible with my changes - [ ] I have included a list of my configuration changes in the PR description (under **Changes**) <details> <summary>Examples of configuration changes</summary> - Changing ports - Adding new services that need to communicate with each other - Secrets or environment variable changes - New or infrastructure changes such as databases </details> Fixes [BUILDER-733](https://significant-gravitas.sentry.io/issues/7261265514/?seerDrawer=true) Fixes [SENTRY-1124](https://linear.app/autogpt/issue/SENTRY-1124/apierror-not-found) --------- Co-authored-by: Nicholas Tindle <nicholas.tindle@agpt.co> Co-authored-by: seer-by-sentry[bot] <157164994+seer-by-sentry[bot]@users.noreply.github.com> Co-authored-by: Lluis Agusti <hi@llu.lu> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: Ubbe <hi@ubbe.dev>
Significant-Gravitas#13679) ### Why / What / How **Why:** The block-docs generator (`generate_block_docs.py`) emitted "Creating Your Own Blocks" guide links pointing at the legacy `docs.agpt.co` host, which flowed into the generated `docs/integrations/README.md`. That host still 301-redirects to `agpt.co/docs`, but PR Significant-Gravitas#13598 already moved the copilot docs tools onto the canonical `agpt.co/docs` host; this generator (and a few stray manual docs) were left behind, so the links took an extra redirect hop and weren't single-sourced. Fixes [OPEN-3209](https://linear.app/autogpt/issue/OPEN-3209). **What:** Repoint the generator's guide links to the canonical `agpt.co/docs` host, regenerate the overview, and repoint the stray `docs.agpt.co` references in a few manual docs files. **How:** Build the two guide URLs via `backend.util.docs.make_doc_url` (the single-source helper Significant-Gravitas#13598 added) instead of hardcoding them, so the host and URL shape can't drift from the copilot docs tools. The helper strips the `.md` extension and canonicalizes `_`→`-`, producing `https://agpt.co/docs/platform/new-blocks` and `.../block-sdk-guide`, both verified to render live. **Note on the ticket's second gap (site-recategorized pages 404ing):** verified no longer reproducing. Both synthesized URLs now render correctly on the live site via GitBook redirects added since the ticket was filed (`agpt.co/docs/platform/new-blocks` → og:title "Build your own Blocks"), so no sitemap-fetch or redirect machinery is needed here. ### Changes 🏗️ - `scripts/generate_block_docs.py`: build the overview's guide links via `make_doc_url` (module-level import) instead of hardcoded `docs.agpt.co` URLs. - `docs/integrations/README.md`: regenerated — the two guide links now point at `agpt.co/docs`. - `scripts/test_generate_block_docs.py`: add `TestOverviewGuideLinks` (fails without the fix) asserting the overview never emits `docs.agpt.co` and uses the `agpt.co/docs` host. - Repointed the stray `docs.agpt.co` references in these manual files: `docs/content/index.md`, `docs/platform/aimlapi.md`, `docs/platform/gemini.md`, `autogpt_platform/backend/load-tests/README.md`. (Other `docs.agpt.co` references elsewhere in the repo — e.g. root `README.md`, `classic/*`, frontend styleguide — are outside this ticket's scope and left untouched.) ### Checklist 📋 #### For code changes: - [x] I have clearly listed my changes in the PR description - [x] I have made a test plan - [x] I have tested my changes according to the test plan: - [x] `poetry run pytest scripts/test_generate_block_docs.py` — 30 passed - [x] Verified `TestOverviewGuideLinks` fails on the pre-fix generator and passes after - [x] `poetry run python scripts/generate_block_docs.py --check` — "All documentation is in sync!" - [x] Confirmed the new URLs render live (`agpt.co/docs/platform/new-blocks`, `.../block-sdk-guide`, `.../getting-started`, `/platform`, `/docs`)
…f dropping memory ops (Significant-Gravitas#13686) ### Why / What / How **Why.** Sentry issue [SENTRY-1384](https://linear.app/autogpt/issue/SENTRY-1384) — `Error executing FalkorDB query: Max pending queries exceeded`. FalkorDB (the Graphiti-backed CoPilot memory store) is a single shared server for every user's graph. It runs queries on a bounded worker pool and parks the overflow in a pending-query queue with a finite cap. Under concurrent memory traffic — warm-context recall on every chat turn, ingestion `add_episode` fan-out, and community rebuilds, none of which are globally rate-limited — the combined arrival rate can exceed what the pool drains plus what the queue holds, and FalkorDB rejects the surplus with this message. The rejection is **transient and self-clearing** (the queue drains in milliseconds) and a **pre-execution** rejection (the query never runs). But today two bad things happen: the memory op is silently dropped (warm-context returns empty / an episode fails to store), and graphiti-core's `execute_query` logs an `ERROR` that Sentry captures — recurring noise for a condition that isn't a bug. **What.** `AutoGPTFalkorDriver.execute_query` now retries this specific backpressure with bounded, jittered exponential backoff, so a load spike degrades into a slightly slower memory op instead of a dropped one plus a Sentry alert. Only a genuinely exhausted retry budget surfaces the error. **How.** - Retry is **scoped strictly** to the `max pending queries exceeded` signature. Because it's a pre-execution rejection, retrying is side-effect-free even for writes. Every other error (Cypher typo, missing graph, connection teardown) still fails fast on the first attempt, exactly as upstream. - **Bounded + jittered exponential backoff** (`GRAPHITI_FALKORDB_QUERY_MAX_ATTEMPTS`, default 3; `GRAPHITI_FALKORDB_QUERY_BACKOFF_BASE`, default 0.1s). The backoff drains the queue *between* attempts rather than amplifying the pileup, and jitter prevents concurrent callers from retrying in lockstep. Total backoff fits inside the existing 8s warm-context timeout. - **Sentry-on-exhaustion only.** The override reimplements the query body (rather than delegating to `super()`) so intermediate retries emit no `ERROR` — only the terminal failure logs, under the same `graphiti_core.driver.falkordb_driver` logger name so Sentry grouping and the SENTRY-1387 benign-teardown filter keep working. ### Changes 🏗️ - `falkordb_driver.py`: `execute_query` override with scoped, bounded, jittered-backoff retry on pending-queue overflow; `_pending_queue_retry_delay` + `_to_records` helpers. Result-shaping mirrors upstream; typed so downstream consumers keep their inference. - `config.py`: `falkordb_query_max_attempts` (3) and `falkordb_query_backoff_base` (0.1s) tunables under the `GRAPHITI_` env prefix. - `falkordb_driver_test.py`: 5 tests — success passthrough, retry-then-succeed, exhaust-then-raise-with-single-terminal-log, non-overflow-fails-fast, and the `already indexed` short-circuit. ### Checklist 📋 #### For code changes: - [x] I have clearly listed my changes in the PR description - [x] I have made a test plan - [x] I have tested my changes according to the test plan: - [x] `poetry run pytest backend/copilot/graphiti/falkordb_driver_test.py` — all pass; the retry test fails without the fix (upstream raises on the first overflow) - [x] `poetry run format` and `poetry run lint` clean (ruff, isort, black, pyright) - [x] Package-level pyright green — verified the new return annotation preserves downstream inference in `migrations/backfill_edge_props.py` #### For configuration changes: - [x] `.env.default` is updated or already compatible with my changes (new settings have safe defaults; no `.env` change required) - [x] `docker-compose.yml` is updated or already compatible with my changes (no infra change)
…entry (Significant-Gravitas#13678) ### Why / What / How **Why:** Sentry issue [SENTRY-1387](https://linear.app/autogpt/issue/SENTRY-1387) — `Error executing FalkorDB query: Buffer is closed.` fires repeatedly. It's a benign connection-teardown race in the CoPilot Graphiti memory path, not a real failure: `graphiti-core`'s `FalkorDriver.execute_query` logs at `ERROR` and re-raises whenever a query races the closing of its `redis.asyncio` connection — either the fire-and-forget cache-eviction close (`client.py` `_close_client_driver` → `loop.create_task(driver.close())`) or a per-request `driver.close()` in a `finally` block. CoPilot memory callers swallow the exception and chat degrades gracefully, so the only impact is Sentry noise that buries real errors. The existing `build_indices=False` mitigation only removes one source of the race; other teardown paths (eviction, deploy, shutdown) still trip it. **What:** Extend the Sentry `_before_send` filter to drop the two benign FalkorDB teardown signatures — `"Buffer is closed"` and `"Connection closed by server"` — scoped to the `graphiti_core.driver.falkordb_driver` logger. Genuine query errors (Cypher typos, missing graphs) from the same logger still reach Sentry. **How:** Mirrors the existing pika reconnect-noise filter already in `metrics.py`: - Log-based events (the dominant signal, captured via `LoggingIntegration`): dropped when `logger == graphiti_core.driver.falkordb_driver` and the message contains a teardown signature. - `exc_info` events (a caller that doesn't swallow the re-raised error): dropped when the exception message contains a teardown signature **and** the exception type originates from a `falkordb`/`redis` module — so the scope stays tight. ### Changes 🏗️ - `backend/util/metrics.py`: add `_FALKORDB_DRIVER_LOGGER`, `_FALKORDB_TEARDOWN_SIGNATURES`, `_FALKORDB_MODULE_INDICATORS`; add the log-based and `exc_info` drop branches in `_before_send`. - `backend/util/metrics_test.py`: 6 new tests — both teardown messages dropped, the `exc_info` redis path dropped, a genuine query error kept, the same string from a non-graphiti logger kept, and a signature-coverage sanity check. ### Checklist 📋 #### For code changes: - [x] I have clearly listed my changes in the PR description - [x] I have made a test plan - [x] I have tested my changes according to the test plan: - [x] `poetry run pytest backend/util/metrics_test.py` — 14 passed (7 pre-existing + 7 new) - [x] Verified before the fix that a `graphiti_core.driver.falkordb_driver` "Buffer is closed" event is **not** dropped (reaches Sentry), and after the fix that it **is** dropped - [x] Confirmed a genuine (non-teardown) FalkorDB query error from the same logger still passes through the filter - [x] `poetry run format` and `poetry run lint` clean
…the full set (Significant-Gravitas#13681) ### Why / What / How **Why** — Resolves [OPEN-3212](https://linear.app/autogpt/issue/OPEN-3212) / Significant-Gravitas#13633. On the library agent view's **Triggers** tab, the router decides whether a selected item is a *trigger agent* or a *webhook-trigger preset* by checking membership in the agent's presets list. That list was fetched as a single page capped at `PRESETS_PAGE_SIZE = 100`. For an agent with more than 100 presets, `presetsComplete` stayed `false`, so an unknown/stale selection fell back to a throwaway by-ID preset fetch (a graceful not-found card, but one wasted request per unknown selection) and lost the "resolve type before mount" guarantee. The sidebar trigger/template counts were also capped at the first 100. **What** — `useAgentPresetsQuery` now pages through *all* of an agent's presets so membership routing is authoritative, instead of only reading the first 100. No backend or API-contract change (the shared `/presets` endpoint, its copilot RPC, and the external paginated API are untouched). **How** - Switched `useAgentPresetsQuery` from the single-page `useGetV2ListPresets` to the generated `useGetV2ListPresetsInfinite`, and eagerly fetch each next page (via a `useEffect` on `hasNextPage`) up to a safety cap of `MAX_PRESET_PAGES = 20` (2000 presets/agent). - The hook flattens the pages into one `presets` list (`unpaginate`) and exposes: - `presetsComplete` — every page has loaded, so membership is authoritative. - `presetsSettled` — no more pages will be fetched (complete **or** stopped at the cap). - The detail-pane router now treats `presetsSettled` as `listsResolved`, so while pagination is still in flight it shows the loading state rather than prematurely resolving a selection against a partial list. The graceful by-ID fallback (`deriveSelectedTriggerKind` → `webhook-trigger` when `!presetsComplete`) is preserved only for the beyond-cap edge case. - Common case (≤100 presets) is unchanged: one request, `presetsComplete` true immediately. ### Changes 🏗️ - `NewAgentLibraryView/hooks/useAgentPresetsQuery.ts` — eager full pagination via the infinite presets query; expose `presets` / `presetsComplete` / `presetsSettled`. - `NewAgentLibraryView/useNewAgentLibraryView.ts` — consume the new shape; gate `listsResolved` on `presetsSettled`. - `NewAgentLibraryView/.../SidebarRunsList/useSidebarRunsList.ts` — read the flattened `presets` (fixes capped trigger/template counts). - `agents/[id]/__tests__/trigger-agents.test.tsx` — two multi-page tests: a preset on a later page resolves from membership, and an unknown ID across a fully-paginated list lands on not-found **without** a by-ID fetch. Repurposed the former single-page "incomplete page" test. ### Checklist 📋 #### For code changes: - [x] I have clearly listed my changes in the PR description - [x] I have made a test plan - [x] I have tested my changes according to the test plan: - [x] `pnpm exec vitest run` on `trigger-agents.test.tsx` — 19 passed (2 new tests fail without the fix, pass with it) - [x] `pnpm format`, `pnpm lint`, `pnpm types` all clean - [x] Agent with ≤100 presets: Triggers tab resolves selections with a single presets request (unchanged behavior) - [x] Agent with >100 presets: all trigger/template rows and counts show; a webhook trigger beyond the first 100 opens its detail view; a stale/deleted trigger ID shows the not-found card without a wasted by-ID request --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ignificant-Gravitas#13684) ### Why / What / How **Why:** When a user uploads an image or logo that is too large (e.g. an agent logo), the server returns HTTP 413 and the UI shows only a generic error — it never tells the user the file is too large or what the size limit is. Root cause: image/logo uploads went through the Next.js proxy, which rejects request bodies over ~4.5MB (Vercel serverless limit) with a **413 before the request ever reaches the backend**. That 413 has no backend JSON body, so the error message collapsed to a bare `HTTP 413`. Meanwhile the real backend limits are much higher (store media 50MB, OAuth logo 3MB), so files the backend would happily accept were being rejected upstream with an unhelpful message. Closes OPEN-2844 / Significant-Gravitas#11448. **What:** Route all image/logo uploads directly to the backend (bypassing the proxy, the same approach PR Significant-Gravitas#12315 used for workspace/CoPilot uploads), and add clear file-size messaging — both a client-side pre-check and 413-aware error parsing. **How:** - Added `uploadSubmissionMediaDirect` and `uploadOAuthAppLogoDirect` in `src/lib/direct-upload.ts` that `POST` straight to the backend (`/api/store/submissions/media`, `/api/oauth/apps/{app_id}/logo/upload`) with the auth token, bypassing `/api/proxy`. Now the real backend size limits apply and the backend's own error messages surface. - Added a shared `getFileSizeError(file, maxSizeMB)` helper + size constants (`SUBMISSION_MEDIA_MAX_SIZE_MB = 50`, `OAUTH_LOGO_MAX_SIZE_MB = 3`, mirroring the backend) so each call site rejects oversized files *before* uploading with a clear "File is too large … Maximum size is NMB" message. - Added a 413-aware `readUploadError` as a safety net for any proxy/ingress that still returns 413 with a non-JSON body. - Rewired the four image/logo upload paths to use the new helpers. ### Changes 🏗️ - **`src/lib/direct-upload.ts`**: new `uploadSubmissionMediaDirect`, `uploadOAuthAppLogoDirect`, `getFileSizeError`, size constants, 413-aware error parsing; refactored the existing `uploadFileDirect` onto a shared `postFileToBackend` helper. - **`useThumbnailImages.ts`** (marketplace/store submission agent thumbnail — the path in the ticket): direct upload + size pre-check + toast. - **`useProfilePage.ts`** (modern profile avatar): direct upload + size pre-check; `isUploading` now backed by local state. - **`useOAuthApps.ts`** (OAuth app logo): direct upload + size pre-check; query invalidation moved to a `finally`. - **`__legacy__/ProfileInfoForm.tsx`** (legacy profile avatar — previously failed **silently** with only a `console.error`): direct upload + size pre-check + toast feedback. - **Tests**: new `src/lib/__tests__/direct-upload.test.ts` (URL bypass, 413 message, backend `detail` message, size helper); updated `ThumbnailImages`, settings-profile, and existing suites to mock the direct uploader and added "file too large" pre-check tests. ### Checklist 📋 #### For code changes: - [x] I have clearly listed my changes in the PR description - [x] I have made a test plan - [x] I have tested my changes according to the test plan: - [x] `pnpm test:unit` for the affected suites (53 tests) — green, including new oversized-file pre-check tests that assert the "File too large" toast and that no upload is attempted - [x] `pnpm types` and `pnpm lint` clean - [ ] Manually upload a >5MB agent thumbnail in the marketplace/store submission flow and confirm it now succeeds (was 413) up to the 50MB backend limit - [ ] Upload a >50MB thumbnail and confirm a clear "File is too large … Maximum size is 50MB" message appears before any request - [ ] Upload a >3MB OAuth app logo and confirm the size message cites the 3MB limit --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…pped (Significant-Gravitas#13682) ### Why / What / How **Why:** Follow-up to [OPEN-3169](https://linear.app/autogpt/issue/OPEN-3169) / Significant-Gravitas#13394, which made webhook-preset version migration trigger-compatibility-aware. When a user activates a new graph version whose trigger block differs from the one a preset's webhook was registered for, the preset is intentionally **left pinned** to its old version (so its existing webhook keeps working) instead of being silently migrated into a broken state. Until now the only signal that this happened was a server-side `logger.warning` — users who swap their agent's trigger (e.g. Telegram → GitHub, or GitHub "on PR" → "on issue") got **no UI notification** that some presets were left behind and need reconfiguration. **What:** Surface the skipped/incompatible presets to the user at the moment of the swap so they know to reconfigure the trigger on the new version. **How:** The migration helper now reports which presets it skipped; the graph-activation endpoints return that in their response; the builder shows a warning toast on save. > Note: the backend notification server (`queue_notification`) is disabled in production, so a "send a notification" approach would be a no-op in prod. The effective path is to return the skipped presets in the activation response and surface them in the frontend — which is what this PR does. No change to the correctness behavior shipped in OPEN-3169. ### Changes 🏗️ **Backend** - `migrate_webhook_presets_to_new_version` now returns a `WebhookPresetMigrationResult` (migrated count + list of skipped presets with `id`, `name`, `pinned_version`) instead of just an `int`. - `PUT /graphs/{id}` now returns a new `UpdateGraphResponse` (`{ graph, skipped_webhook_presets }`) — **response shape change** for this endpoint. - `PUT /graphs/{id}/versions/active` now returns a new `SetActiveGraphVersionResponse` (`{ skipped_webhook_presets }`) — previously returned `None`. - New models `SkippedWebhookPreset` / `WebhookPresetMigrationResult` in `library/model.py`. - The CoPilot/AutoPilot path (`update_graph_in_library`) intentionally discards the migration result (no user-facing channel there); documented inline. **Frontend** - Builder save (`useSaveGraph`) shows an info toast listing the affected preset name(s) with a "reconfigure the trigger" hint when activation returns skipped presets. - Shared helper `notifySkippedWebhookPresets` (unit-tested directly). - Regenerated `openapi.json` (client `__generated__` is gitignored and regenerated by CI). **Scope note:** Frontend surfacing is wired on the builder-save path (the ticket's core trigger-swap scenario). The version-**revert** path (`useBuilderChatPanel`) gates on internal `revertTargetVersion` state that isn't reachable at unit-test level, so it's intentionally left unwired for now — but `set_graph_active_version` already returns the skipped presets, so wiring a toast there later is a drop-in. ### Checklist 📋 #### For code changes: - [x] I have clearly listed my changes in the PR description - [x] I have made a test plan - [x] I have tested my changes according to the test plan: - [x] Backend unit tests (`test/test_migrate_webhook_presets.py`, 16): migration returns skipped presets; both endpoints surface them in the response; happy path (same trigger) still migrates with no skipped presets. - [x] Frontend unit tests (`useSaveGraph.test.tsx`, `skippedWebhookPresets.test.ts`): warning toast fires (singular + plural) when presets are skipped, and does not fire when none are. - [x] `pnpm types` / `pnpm format` / `pnpm lint`, `poetry run format` / `lint` all clean. - [ ] Manual: build an agent with a webhook trigger + a preset, save a new active version that swaps the trigger block, confirm the warning toast lists the preset and the preset stays on its old version.
Updated the token used for dispatch events from DISPATCH_TOKEN to DEPLOY_TOKEN.
…library agents (Significant-Gravitas#13680) ### Why / What / How **Why:** When you add an agent from the marketplace to your library, it shows up with the creator's *original* graph title and description — not the title/description it was published under — and with no image. The intended behaviour (OPEN-2478 / Significant-Gravitas#9879) is that a downloaded agent appears in your library just as it does in the marketplace. **What:** Snapshot the marketplace listing's published name, description and image onto the `LibraryAgent` at download time, and surface them in the library. **How:** `LibraryAgent` already had an `imageUrl` column that was never populated on marketplace downloads. This PR follows that same snapshot-at-download pattern: - Adds nullable `name` / `description` columns to `LibraryAgent` (migration included). Null for user-created agents, which keep falling back to the graph's own values. - `add_graph_to_library` now reads the `StoreListingVersion` and writes its `name`, `description` and first `imageUrls` entry onto the `LibraryAgent` — on both the create and the restore-existing (soft-deleted) paths, so re-downloading also refreshes the snapshot. - `LibraryAgent.from_db` prefers the snapshotted `name`/`description`, falling back to `graph.name`/`graph.description`. This is scoped to the "Add to Library" download path; user-created agents (via `create_library_agent`) are unaffected. ### Changes 🏗️ - `schema.prisma`: add `name` / `description` (both `String?`) to `LibraryAgent`; new migration `20260727120000_add_library_agent_marketplace_metadata`. - `_add_to_library.py`: new `_get_marketplace_metadata()` helper; `add_graph_to_library` snapshots name/description/imageUrl on create and update. - `library/model.py`: `from_db` uses `agent.name or graph.name` and `agent.description or graph.description`. - Tests: `_add_to_library_test.py` (snapshot written on create + restore; helper edge cases) and `model_test.py` (override precedence + fallback). ### Checklist 📋 #### For code changes: - [x] I have clearly listed my changes in the PR description - [x] I have made a test plan - [x] I have tested my changes according to the test plan: - [x] `poetry run pytest backend/api/features/library/_add_to_library_test.py backend/api/features/library/model_test.py` — 9 passed - [x] Verified the `from_db` fallback path keeps user-created agents on their graph name/description - [ ] Publish an agent to the marketplace under a different title/description + image, add it to a fresh account's library, confirm the library card shows the published title/description/image
…nificant-Gravitas#13685) ### Why / What / How **Why:** First slice of the "Hire Experts" MVP (Hire-or-Raise product direction): users will hire curated Experts from the marketplace, see them on a Team page, and chat with them in per-expert CoPilot threads that run installed Workflows. This PR lays the backend foundation — the `Expert` entity, the hire/install API, and the dev roster — so the copilot and frontend slices can build on it. Ships dark; nothing user-facing changes. **What:** - New first-class `Expert` Prisma model (one table for roster templates and hired instances) + `ExpertWorkflow` join to `LibraryAgent`/`StoreListingVersion`, and a nullable `ChatSession.expertId` column. Migration is purely additive. - New feature module `backend/api/features/experts/`: data layer (`experts_db.py`), Pydantic models, REST routes mounted at `/api/experts`, and a dev roster seed script (Maria/Marketing, Max/Sales, Frankie/Ops). **How:** - Hire resolves each template preload through the existing `add_store_agent_to_library` seam, producing the hiring user's `LibraryAgent` plus an `ExpertWorkflow` instance row. Hire is idempotent per (user, template) — enforced by `@@unique([ownerUserId, sourceTemplateId])` and a find-first in `hire_expert`. Duplicate workflow install returns the existing row. - Partial hire is honest: a failed preload install is caught per-item and reported in `HireResult.failed_preloads`; the hire itself succeeds. - All non-template queries are scoped by `ownerUserId` (+ tenancy trio columns carried for org-readiness); templates are never owned and never chat targets. Auth via `Security(autogpt_auth_lib.get_user_id)` like the library routes. - Endpoints (operation IDs drive the frontend Orval hooks in the follow-up PR): `GET /api/experts/templates`, `POST /api/experts` (hire), `GET /api/experts`, `GET /api/experts/{id}`, `POST /api/experts/{id}/workflows`, `DELETE /api/experts/{id}` (archive → 204). ### Changes 🏗️ - `backend/schema.prisma`: `Expert`, `ExpertWorkflow` models; `ChatSession.expertId` (nullable); back-relations on `User`, `Team`, `LibraryAgent`, `StoreListingVersion` - `backend/migrations/20260727070306_add_expert_entity/` — additive only (2 CREATE TABLE, 1 ALTER TABLE ADD COLUMN, indexes + FKs) - `backend/backend/api/features/experts/models.py` — `Expert`, `ExpertWorkflowRef`, `HireResult` - `backend/backend/api/features/experts/experts_db.py` — `list_templates`, `list_experts`, `get_expert`, `hire_expert` (idempotent), `install_workflow` (duplicate-safe), `archive_expert` - `backend/backend/api/features/experts/routes.py` + mount in `backend/backend/api/rest_api.py` - `backend/backend/api/features/experts/seed.py` — dev roster upsert by template name; preloads resolved from real dev marketplace listing slugs; missing listings logged and skipped, never fatal - Tests: `experts_db_test.py` (5, real-DB), `routes_test.py` (10, mocked db + snapshot) Note on user-ID scoping: this PR touches `api/features/experts/experts_db.py` (a data-layer module); every non-template query is scoped by `ownerUserId`, hire/install/archive validate ownership, and cross-user access returns None/404 (covered by tests). ### Checklist 📋 #### For code changes: - [x] I have clearly listed my changes in the PR description - [x] I have made a test plan - [x] I have tested my changes according to the test plan: - [x] `poetry run pytest backend/api/features/experts/experts_db_test.py -xvs` — 5 passed (idempotent hire, preload install, owner scoping, duplicate install, seed round-trip) - [x] `poetry run pytest backend/api/features/experts/routes_test.py -xvs` — 10 passed (all endpoints, 404 paths, snapshot) - [ ] Manual: run `poetry run python -m backend.api.features.experts.seed` against dev DB and hire via API (deferred to PR 2/3 integration) #### For configuration changes: - [x] `.env.default` is updated or already compatible with my changes - [x] `docker-compose.yml` is updated or already compatible with my changes - [x] I have included a list of my configuration changes in the PR description (under **Changes**) --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…ignificant-Gravitas#13687) ### Why / What / How **Why:** Second slice of the "Hire Experts" MVP (stacked on Significant-Gravitas#13685). Hired Experts need their own CoPilot threads: sessions must bind to an expert, and the copilot must speak in the expert's persona and know which Workflows are installed on it. Plain Autopilot sessions additionally get team awareness so Autopilot can *suggest* the right expert with a handoff — never auto-route. **What:** - `POST /api/chat/sessions` accepts optional `expert_id` (validated: owned, non-template, non-archived → else 404); `GET /api/chat/sessions?expert_id=...` filters; session responses carry `expert_id`. - New `backend/copilot/expert_context.py`: `build_expert_context(user_id, expert_id)` renders an `<expert_identity>` + `<expert_workflows>` block for expert sessions, or a `<team_context>` block (the user's hired experts) for plain sessions. - Wired into the existing first-message injection (`inject_user_context`) — one call site, plus the two stream callers passing `session.expert_id`. **How:** - No engine changes. The `_CACHEABLE_SYSTEM_PROMPT` stays byte-identical (locked by a SHA-256 assertion test); all personalization rides the existing dynamic first-message path, so Anthropic prompt caching is unaffected. The expert block is prepended on the dynamic side of the cache breakpoint. - `expert_id` flows: `CreateSessionRequest` → `create_chat_session` → Prisma `ChatSession.expertId` (column added in Significant-Gravitas#13685) → `ChatSessionInfo.from_db`, so every loaded session exposes it with no extra fetch. - Failure behavior per spec: archived/missing expert at stream time → context block omitted, turn proceeds as plain Autopilot. All `experts_db` lookups are wrapped in try/except returning `""` — chat never hard-fails on expert lookup. - Workflow execution reuses the existing `run_agent` tool and execution pipeline unchanged; the injected `<expert_workflows>` block hands the model the `library_agent_id`/`graph_id`s and the instruction to prefer them. ### Changes 🏗️ - `backend/api/features/chat/routes.py` — `expert_id` on create/list sessions + create-time validation via `experts_db.get_expert`; `expert_id` on `CreateSessionResponse`/`SessionSummaryResponse` - `backend/copilot/model.py` — `ChatSessionInfo.expert_id` (+ `from_db` mapping), `expert_id` pass-through on `ChatSession.new` / `create_chat_session` / `get_user_sessions` - `backend/copilot/db.py` — persist `expertId` on create; `expertId` filter on list/count - `backend/copilot/expert_context.py` — new; pure string assembly, ~105 lines - `backend/copilot/service.py` — `inject_user_context()` gains `expert_id` kwarg and one prepend; stream callers in `copilot/baseline/service.py` + `copilot/sdk/service.py` pass `session.expert_id` - Tests: 4 new chat-route tests (103 passed in file); 10 new `expert_context_test.py` tests incl. system-prompt hash lock and injection tests (63 passed with existing prompt-cache tests) Stacked on Significant-Gravitas#13685 (base retargets to `dev` once that merges). ### Checklist 📋 #### For code changes: - [x] I have clearly listed my changes in the PR description - [x] I have made a test plan - [x] I have tested my changes according to the test plan: - [x] `poetry run pytest backend/api/features/chat/routes_test.py` — 103 passed (incl. 4 new: persist expert_id, other-user 404, archived 404, list filter) - [x] `poetry run pytest backend/copilot/expert_context_test.py backend/copilot/prompt_cache_test.py` — 63 passed (identity/team blocks, degradation to "", system prompt byte-identical) - [ ] Manual: create a session with an expert and confirm the first turn shows persona + can run an installed workflow via `run_agent` (needs local stack + seeded roster) #### For configuration changes: - [x] `.env.default` is updated or already compatible with my changes - [x] `docker-compose.yml` is updated or already compatible with my changes - [x] I have included a list of my configuration changes in the PR description (under **Changes**) --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…reads (Significant-Gravitas#13689) ### Why / What / How **Why:** Third and final slice of the "Hire Experts" MVP (stacked on Significant-Gravitas#13687, which stacks on Significant-Gravitas#13685). The backend lets users hire Experts and chat with them; this PR ships the flag-gated surfaces that make the loop usable end-to-end: hire from the marketplace, see your Team, chat in per-expert CoPilot threads, and install Workflows on an expert. **What:** - Marketplace: "Meet the Experts" section (curated roster from `GET /api/experts/templates`) with a profile sheet and Hire flow; hired templates show a Hired state. - New `/team` page: Autopilot card first (the generalist — "runs the shop"), then hired expert cards with installed workflow names/counts and Chat / Install workflow actions. - CoPilot: per-expert threads — `/copilot?expertId=<id>` opens the expert's latest thread or creates one with `expert_id`; the sidebar groups threads under expert headers (Autopilot is the default group); thread header and assistant messages show the expert's name/avatar. - "Install on Expert…" action on marketplace agent detail + "Install workflow" on Team cards, both via one `InstallWorkflowPicker` component (two modes: pick-expert / pick-workflow) calling `POST /api/experts/{id}/workflows`. **How:** - Everything sits behind `Flag.HIRE_EXPERTS` (`hire-experts` LaunchDarkly flag; env override `NEXT_PUBLIC_FORCE_FLAG_HIRE_EXPERTS`). Flag off → sections hidden, `/team` 404s, copilot behavior byte-for-byte as today. - Data fetching uses Orval-generated React Query hooks (`useListExpertTemplates`, `useHireExpert`, `useListExperts`, `useInstallExpertWorkflow`, …) regenerated from the stacked backend's OpenAPI spec; hire/install mutations invalidate the `["/api/experts"]` query key so all surfaces refresh. - Copilot diffs are surgical: sidebar grouping is a pure partition of the existing session list by `expert_id` (renders only when the flag is on and a grouped session exists — flat list untouched otherwise); identity comes from one shared `useExpertMap` hook; stream transport, chat registry/stream store, tool cards, and reconnect logic are untouched. - Failure behavior: templates query error → section renders nothing (marketplace never breaks); missing/archived expert → default header; duplicate install → "Already installed on {name}" toast without a POST (client pre-check; the API is also duplicate-safe). ### Changes 🏗️ - `src/services/feature-flags/use-get-flag.ts` — `Flag.HIRE_EXPERTS` + `src/app/api/openapi.json` regenerated (experts tag, `expert_id` on chat sessions). Generated hooks are gitignored per repo convention; run `pnpm generate:api` after checkout. - `marketplace/components/ExpertsSection/` (+ `ExpertProfileSheet`) and `MainMarketplacePage` mount; `InstallOnExpertButton` in `AgentInfo` - `team/` route: `page.tsx`, `useTeamPage.ts`, `AutopilotCard`, `ExpertTeamCard`, `EmptyTeamState` - `copilot/`: `useChatSession` (expert_id on create + latest-thread adoption), `useSessionList` grouping, `ChatSidebar` grouped rendering, `ChatMessagesContainer` thread header + assistant identity, `useExpertMap` - `components/molecules/InstallWorkflowPicker/` wired into marketplace detail and Team page - **Backend slice** (supports the surfaces above): `Expert.bio` + `Expert.skills` columns (migrations `20260728100000_add_expert_bio`, `20260728200000_add_expert_skills` — additive only) exposed on the experts API; roster seed gains avatars (`/experts/*.svg`), bios, and skills; expert lookups routed through `DatabaseManager` for the Prisma-less copilot executor; expert identity moved from the first-message context block to a per-session system-prompt suffix (`build_expert_identity_suffix`) so it outranks the base persona — the cacheable prompt prefix stays byte-identical - Tests: page-level Vitest + RTL + MSW integration tests for all four surfaces; global `getExpertsMock()` added to `mock-handlers.ts` Note: the `hire-experts` flag must be created in LaunchDarkly before enabling in any environment. Screenshots deferred — will attach from a running stack before undrafting. Stacked on Significant-Gravitas#13687 (base retargets to `dev` once parents merge). ### Checklist 📋 #### For code changes: - [x] I have clearly listed my changes in the PR description - [x] I have made a test plan - [x] I have tested my changes according to the test plan: - [x] `marketplace/__tests__/experts-section.test.tsx` — 2 passed (render + hire flow; hired state) - [x] `marketplace/__tests__/install-on-expert.test.tsx` — 3 passed (install, duplicate, hidden without experts) - [x] `team/__tests__/main.test.tsx` — 6 passed (Autopilot first, expert cards, Chat links, empty state, flag-off 404) - [x] `copilot/__tests__/expert-threads.test.tsx` — 7 passed (expert_id on create, grouping, thread header, plain sessions unchanged) - [x] Regression: copilot sidebar/messages/page suites + marketplace main suites re-run green - [ ] Manual E2E on a running stack with seeded roster (before undrafting) - [ ] Screenshots of the three surfaces (before undrafting) #### For configuration changes: - [x] `.env.default` is updated or already compatible with my changes - [x] `docker-compose.yml` is updated or already compatible with my changes - [x] I have included a list of my configuration changes in the PR description (under **Changes**) --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…t-Gravitas#13699) ### Why / What / How **Why.** The wallet popover is `28.5rem` wide with an inline top-up form, an auto-refill config form, collapsible task groups, embedded videos and progress bars. That was designed for the classic top navbar. In the new sidebar layout the wallet lives in the sidebar footer as a small `$1.00` pill, and opening it throws a panel wider than the sidebar itself over the page. It's far too heavy for what a user wants there: how many credits do I have, how do I get more. **What.** A compact popover used *only* when the wallet renders in the new layout (`compact` prop, set by `SidebarUserActions`). The classic navbar keeps the existing panel, byte-for-byte. **How.** `Wallet.tsx` had grown to 360 lines mixing trigger markup, popover content, credits fetching, confetti and websocket wiring. Rather than add a second panel to it, the shared parts were pulled out first: - `helpers.ts` — task-group definitions + `getEarnRows()` - `useWallet.ts` — credits, balance-flash, completion counts, onboarding websocket + confetti - `components/WalletFullPanel.tsx` — the existing classic panel, moved verbatim - `components/WalletCompactPanel.tsx` — the new one `Wallet.tsx` is now ~120 lines and just picks a panel. The compact panel shows three things: balance, an **Add credits** row, and a collapsible **Earn credits** list. To keep the list short, `getEarnRows()` flattens the three task groups — a group with every task done collapses to one `First Wins · 3 of 3 — Done` row, while a group still in progress contributes one row per task. **Add credits** opens `TopUpDialog` instead of embedding `TopUpForm` inline. That dialog's copy was hardcoded to *"You're out of automation credits"*, which is wrong when you open it deliberately with a positive balance, so it gained an `add-credits` variant. Default is unchanged, so the low-credit banner and daily auto-opener read exactly as before. Completed rows reuse `SealCheckIcon` (`weight="fill"`, `#00a656`) from the copilot `TaskProgressBar`'s "All tasks complete" header, so completion reads the same across the app. ### Changes 🏗️ - Add `WalletCompactPanel` — balance, Add credits row, collapsible Earn credits list; rendered when `compact` is set (new layout only) - Add `getEarnRows()` to flatten task groups; completed groups collapse to a single Done row - Extract `helpers.ts` (task groups + row flattening) and `useWallet.ts` (credits, confetti, websocket) out of `Wallet.tsx`; move the classic panel to `WalletFullPanel.tsx`. `Wallet.tsx` 360 → ~120 lines - Compact popover opens `side="top" align="start"` at `22rem` with `rounded-2xlarge`, sized so long task names wrap rather than truncate - Add a `variant` prop to `TopUpDialog` (`out-of-credits` default | `add-credits`) so the wallet entry point gets accurate copy - Reuse `SealCheckIcon` / `CircleIcon` from the copilot task-progress treatment for row status - `Task` / `TaskGroup` interfaces moved from `Wallet.tsx` to `helpers.ts` (only importer, `WalletTaskGroups.tsx`, updated) No backend changes. No API changes. Reward amounts are unchanged and still mirror `backend/data/onboarding.py`. ### Checklist 📋 #### For code changes: - [x] I have clearly listed my changes in the PR description - [x] I have made a test plan - [ ] I have tested my changes according to the test plan: - [x] `pnpm format`, `pnpm lint`, `pnpm types` clean - [x] `pnpm test:unit` — 379 files pass - [x] New `WalletCompactPanel.test.tsx`: row flattening (completed group collapses, in-progress group lists tasks, no task dropped), panel render, accordion collapse, Add credits callback - [ ] **New layout** (`AUTOGPT_NEW_LAYOUT` on): open the wallet from the sidebar footer — compact panel opens up and to the right, fits the viewport, long task names wrap - [ ] Click **Add credits** — popover closes, dialog opens titled "Add automation credits", Stripe checkout still reachable - [ ] Toggle **Earn credits** — list collapses and expands - [ ] With `ENABLE_PLATFORM_PAYMENT` off — Add credits row is hidden, rest of the panel still renders - [ ] **Classic navbar** (`AUTOGPT_NEW_LAYOUT` off): wallet is visually unchanged — inline top-up, auto-refill, task videos, progress bars, confetti on step completion - [ ] Hit zero credits — low-credit banner and daily auto-opener still say "You're out of automation credits"
…ravitas#13330) ### Why / What / How **Why:** The platform's only hard dependency on the Supabase stack was auth (GoTrue + Kong + the `auth.users` schema). That dependency forced every deployment — including local/self-hosted — to run the full docker-compose Supabase sidecar, which blocks the longer-term goal of shipping the platform as a single container or packaged app. **What:** Replaces Supabase Auth with [Better Auth](https://better-auth.com) (1.6.x) embedded in the Next.js frontend, with zero session loss for existing users and zero password resets. **How:** - **Auth service** lives in the Next.js server at `/api/auth/*` (cookie-based DB sessions, tables in the `platform` schema of the existing Postgres, owned by Prisma migrations — one migration authority). - **Backend validation**: the JWT plugin signs ES256 tokens published at `/api/auth/jwks`; `autogpt_libs.auth` now validates asymmetric tokens against `JWT_JWKS_URL` while keeping the legacy HS256 shared-secret path active, dispatched by token algorithm. JWT claims are shaped identically to GoTrue's (`sub`, `email`, `role`, `user_metadata.name`, `aud: "authenticated"`), so no route/handler changes were needed. - **No session loss**: three mechanisms layered — 1. backend dual-validation keeps in-flight legacy JWTs working, 2. a `supabase-bridge` Better Auth plugin silently upgrades a legacy Supabase cookie (signature-verified, accepted up to `SUPABASE_BRIDGE_MAX_TOKEN_AGE_DAYS` past expiry since refresh tokens have no issuer anymore) into a Better Auth session, triggered by middleware exactly once per browser, 3. `scripts/migrate-supabase-auth.ts` copies `auth.users`/`auth.identities` into the Better Auth tables preserving UUIDs (no FK orphans) and bcrypt hashes (Better Auth is configured with bcrypt hashing, so GoTrue passwords keep verifying). - **Token plumbing**: browser API calls already go through `/api/proxy`, so backend JWTs are minted server-side from the cookie session (with a per-session mint cache) — the browser never holds a JWT. - **Compose**: `kong`, `auth` (GoTrue), `studio`, `meta` removed; `db` is now plain `pgvector/pgvector:pg16` with an init shim that creates a minimal `auth.users` so the historical migration chain applies on fresh databases. ### Changes 🏗️ - `autogpt_libs.auth`: JWKS (ES256/RS256/EdDSA) validation via `JWT_JWKS_URL` + legacy `JWT_VERIFY_KEY` HS256 path; config validation requires at least one - Prisma: Better Auth tables (`UserAuthIdentity`, `UserAuthSession`, `UserAuthAccount`, `UserAuthVerification`, `UserAuthJwks`) + migration that also drops the `auth.users` sync trigger - Backend: removed `supabase` SDK; feature-flag LD context now reads the auth user table; e2e test data seeds bcrypt credential accounts - Frontend: new `src/lib/auth` module (server config w/ admin+jwt+bridge plugins, actions, hooks, middleware); all ~50 `useSupabase` consumers migrated to `useAuth` (same surface); login/signup/OAuth/password-reset/email-change flows rewritten; `@supabase/*` packages removed - Infra: compose without the Supabase stack; CI provisions plain pgvector Postgres; env defaults updated (see below); installer + docs updated - kysely pinned to 0.28.17 (upstream `@better-auth/kysely-adapter` imports a constant kysely 0.29 removed from the root export) **Configuration changes:** - Backend: `+JWT_JWKS_URL`, `SUPABASE_URL`/`SUPABASE_SERVICE_ROLE_KEY` removed, `JWT_VERIFY_KEY` retained for the migration window - Frontend: `+BETTER_AUTH_SECRET`, `+BETTER_AUTH_URL`, `+DATABASE_URL`, `+AUTH_DB_SCHEMA`, optional `AUTH_{GOOGLE,GITHUB,DISCORD}_CLIENT_ID/SECRET`, optional `SMTP_*`, optional `SUPABASE_JWT_SECRET` (bridge window only); `NEXT_PUBLIC_SUPABASE_*` removed - OAuth provider consoles need redirect URIs updated from GoTrue (`/auth/v1/callback`) to `https://<frontend>/api/auth/callback/{provider}` **Cloud rollout order (automated on merge):** the deploy workflow's `migrate` job runs the schema migrations *including the bulk GoTrue→Better Auth copy* (`20260716120000_copy_supabase_users_to_better_auth`), and the backend deploy dispatches only after it succeeds. The Vercel frontend build runs in parallel off the same push — if it finishes first, auth 500s briefly until the migrate job lands (self-heals; no action needed). After the frontend flip, run the **“AutoGPT Platform - Supabase Auth Sweep”** workflow (workflow_dispatch, environment picker) once to copy any users who signed up via GoTrue in between. Keep `SUPABASE_JWT_SECRET` set (`JWT_VERIFY_KEY` falls back to it) through the bridge window → after the window, drop the legacy secrets and the GoTrue infra. ### Checklist 📋 #### For code changes: - [x] I have clearly listed my changes in the PR description - [x] I have made a test plan - [x] I have tested my changes according to the test plan: - [x] Fresh plain-Postgres DB: full Prisma migration chain applies (auth shim verified) - [x] Signup via Better Auth → UUID user + session cookie; platform User created via backend with same id - [x] `/api/auth/jwks` serves ES256 key; minted JWT carries `sub`/`email`/`role`/`user_metadata`/`aud=authenticated` - [x] Python backend accepts the ES256 JWT via JWKS (`POST /api/auth/user` → 200) - [x] Legacy GoTrue-style HS256 JWT still accepted by the backend (dual validation) - [x] Bridge: expired-but-valid legacy `sb-*` cookie on a protected page → Better Auth session minted, sb cookies cleared, redirected to original destination - [x] bcrypt sign-in 200, wrong password 401; protected pages redirect without session, render with one; `/api/proxy` mints backend JWT (credits endpoint 200) - [x] Backend: 627 util tests + user-data tests green; autogpt_libs auth suite green (new JWKS tests incl. rotation/kid-miss) - [x] Frontend: `pnpm types`/`lint` clean; 3215+ unit tests green incl. new bridge/middleware/mapping tests #### For configuration changes: - [x] `.env.default` is updated or already compatible with my changes - [x] `docker-compose.yml` is updated or already compatible with my changes - [x] I have included a list of my configuration changes in the PR description (under **Changes**) 🤖 Generated with [Claude Code](https://claude.com/claude-code) ### Cloud deploy checklist ☁️ Things that live outside this repo's code and must be handled at rollout (in order): 1. **Waitlist**: signup gating was a Postgres trigger on `auth.users`; new signups insert into `platform."user"` where no trigger exists. Port the waitlist check (trigger on `platform."user"` or a Better Auth `databaseHooks.user.create.before` hook) **before** deploy, or signups are open. The frontend's `not_allowed` error handling is preserved and ready. 2. **Infra repo env/manifests**: set `BETTER_AUTH_SECRET` (the compose default is dev-only), frontend `DATABASE_URL` + `AUTH_DB_SCHEMA=platform`, backend `JWT_JWKS_URL`, SMTP credentials (Postmark SMTP works; without SMTP, password reset/verification/email-change flows fail loudly by design); keep `JWT_VERIFY_KEY` + `SUPABASE_JWT_SECRET` set for the bridge window; remove GoTrue/Kong workloads after the window. 3. **OAuth consoles**: change Google/GitHub/Discord redirect URIs from GoTrue `/auth/v1/callback` to `https://<frontend>/api/auth/callback/{provider}`, then smoke-test each provider in staging — including a sign-in by a *migrated* OAuth user (identity rows are migrated and account linking is enabled, but no live provider flow runs in CI). 4. **Data migration**: automated — the bulk copy rides the deploy workflow's migrate job. Post-flip, run the “AutoGPT Platform - Supabase Auth Sweep” workflow once for stragglers (idempotent / re-runnable; `frontend/scripts/migrate-supabase-auth.ts` under the hood). 5. Known accepted trade-offs: bridge accepts signature-valid Supabase tokens ≤30 days past expiry (`SUPABASE_BRIDGE_MAX_TOKEN_AGE_DAYS`); open SPA tabs at cutover 401 until one page reload; migrated *temporary* bans become permanent; GoTrue 2FA enrollments don't migrate (no 2FA UI exists today). 6. Post-merge hardening: back Better Auth's rate limiter with Redis for multi-replica (per-instance memory by default); admin role assignment is now `role='admin'` on `platform."user"`; delete the inert `db/docker/` tree in a cleanup PR. ### Rollback ⏮️ **Precondition — the rollback window:** keep the GoTrue/Kong workloads **and** `JWT_VERIFY_KEY` + `SUPABASE_JWT_SECRET` running until the cutover is confirmed healthy. Dropping the legacy infra/secrets (final step of the rollout order) is the **point of no return** — don't do it until you're confident. 1. **Roll the frontend back in Vercel** — Instant Rollback to the previous deployment of the environment (or revert the merge commit on the branch). The backend needs **no** revert — dual-validation still accepts GoTrue HS256 tokens, and the old frontend re-issues them. (Backend JWKS fetches fail while rolled back, which is what makes already-bridged ES256 sessions 401 as described below — expected, not a fault.) 2. **Sessions:** anyone still holding a valid GoTrue cookie continues uninterrupted; anyone who had already bridged to a Better Auth session 401s once → one reload → re-logs-in via GoTrue (their `auth.users` credential still exists). 3. **No DB rollback needed** — leave the Better Auth tables in place; the old system ignores them. **One-way caveat:** users who **signed up or changed their password on Better Auth during the cutover window** exist only in `platform."user"`/`account`, not `auth.users`, so after rollback they can't log in via GoTrue. Mitigate by picking one: (a) keep the window short and accept the few re-signups, (b) freeze new signups during the window, or (c) reverse-sync new `platform."user"` rows into `auth.users` before rolling back. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **High Risk** > This is a full authentication and deployment-model change (JWT verification, session migration, CI database setup) touching security-critical paths; rollout order and env secrets (`JWT_JWKS_URL`, bridge window) matter for production access. > > **Overview** > **Replaces the Supabase auth stack** (Kong, GoTrue, Studio, related env) with **plain `pgvector` Postgres** and **Better Auth** on the frontend. Platform `.env.default` is trimmed to database credentials; local DB data moves to `data/db/data`. > > **`autogpt_libs.auth` is reworked for Better Auth:** `JWT_JWKS_URL` is **required** at startup (with cleartext remote `http://` blocked unless `JWKS_ALLOW_INSECURE_TRANSPORT`). Tokens verify via **JWKS (ES256, etc.)** or legacy **HS256** (`JWT_VERIFY_KEY` / `SUPABASE_JWT_SECRET`) by algorithm. New **`requires_frontend_service`** guards pre-login backend calls with JWKS-signed service tokens (separate audience from user JWTs). Missing `role` claims **fail closed** (403 admin, default `"user"` elsewhere) instead of `KeyError`/500. > > **CI and workflows:** Docker image caches swap Supabase images for **`pgvector/pgvector:pg15`**; backend tests use a **pgvector container + init shim** instead of `supabase start`; pytest drops `SUPABASE_*` env; new **`autogpt-libs-test`** job. Full-stack E2E starts **`db` only** (no GoTrue), caches/dumps **platform schema** (Better Auth tables), and **fails** if `rest_server` is unhealthy after 180s. Adds **manual “Supabase Auth Sweep”** workflow running `migrate-supabase-auth.ts` post-cutover. > > Also: **`SECURITY.md`** clarifies JWKS transport warnings are operator guidance; **`.gitignore`** for `supabase/.temp/`; removal of **`supabase_integration_credentials_store/types.py`** from the diff hunk. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit ffc0264. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
… useChat state (Significant-Gravitas#13766) ### Why / What / How <!-- Why: Why does this PR exist? What problem does it solve, or what's broken/missing without it? --> **Why:** This PR addresses the `TypeError: Cannot read properties of undefined (reading 'state')` occurring in the `/copilot` section of the application. The error originated in `frontend/src/app/(platform)/copilot/useCopilotStream.ts` within the `resumeStreamFromStart` function. This function was calling `resumeStream()` from the `@ai-sdk/react`'s `useChat` hook. The `useChat` hook was sometimes initialized with a fallback `{ id: "new" }` configuration when the `chatRuntime` object was `null` (e.g., if `sessionId` was falsy or `getOrCreateCopilotChatRuntime` returned null). In this uninitialized state, calling `resumeStream()` would cause an internal crash as it attempted to read `.state` from an `undefined` object. <!-- What: What does this PR change? Summarize the changes at a high level. --> **What:** To prevent this, a null guard for `chatRuntime` has been added to `resumeStreamFromStart()`. <!-- How: How does it work? Describe the approach, key implementation details, or architecture decisions. --> **How:** An `if (!chatRuntime) return;` statement was inserted as the first line within the `resumeStreamFromStart` function, ensuring that `resumeStream()` is only called when a valid `chatRuntime` object is present. ### Changes 🏗️ <!-- List the key changes. Keep it higher level than the diff but specific enough to highlight what's new/modified. --> - Added a null guard (`if (!chatRuntime) return;`) to the `resumeStreamFromStart` function in `frontend/src/app/(platform)/copilot/useCopilotStream.ts` to prevent calling `resumeStream()` when `chatRuntime` is null. ### Checklist 📋 #### For code changes: - [x] I have clearly listed my changes in the PR description - [ ] I have made a test plan - [ ] I have tested my changes according to the test plan: <!-- Put your test plan here: --> - [ ] Navigate to the `/copilot` section. - [ ] Verify that the `TypeError: Cannot read properties of undefined (reading 'state')` no longer occurs, especially when `chatRuntime` might be null or uninitialized. - [ ] Ensure the copilot chat functionality works as expected under normal conditions. <details> <summary>Example test plan</summary> - [ ] Create from scratch and execute an agent with at least 3 blocks - [ ] Import an agent from file upload, and confirm it executes correctly - [ ] Upload agent to marketplace - [ ] Import an agent from marketplace and confirm it executes correctly - [ ] Edit an agent from monitor, and confirm it executes correctly </details> #### For configuration changes: - [ ] `.env.default` is updated or already compatible with my changes - [ ] `docker-compose.yml` is updated or already compatible with my changes - [ ] I have included a list of my configuration changes in the PR description (under **Changes**) <details> <summary>Examples of configuration changes</summary> - Changing ports - Adding new services that need to communicate with each other - Secrets or environment variable changes - New or infrastructure changes such as databases </details> Fixes [BUILDER-7V4](https://significant-gravitas.sentry.io/issues/7432474741/?seerDrawer=true) Co-authored-by: seer-by-sentry[bot] <157164994+seer-by-sentry[bot]@users.noreply.github.com>
## Summary Automates CLA tracking with labels and reminders for open PRs. ## What it does ### Labels - Creates `cla: pending` 🟡 and `cla: signed` 🟢 labels - Auto-applies based on `license/cla` check status - Makes it easy to filter PRs by CLA status ### Timeline for unsigned CLAs | Day | Action | |-----|--------| | 0 | PR opened → `cla: pending` label added | | 7 | Reminder comment with signing link + troubleshooting tips | | 23 | Warning: "PR will close in 7 days" | | 30 | Auto-close with friendly message | ### Triggers - **Real-time:** `check_run` completed (catches CLA status changes) - **PR events:** opened, synchronize, reopened - **Daily sweep:** 9 AM UTC (catches edge cases) - **Manual:** `workflow_dispatch` for testing ## Sample Comments <details> <summary>7-Day Reminder</summary> > 👋 **Friendly reminder:** This PR is waiting on a signed CLA. > > All contributors need to sign our Contributor License Agreement before we can merge this PR. > > **➡️ [Sign the CLA here](https://cla-assistant.io/Significant-Gravitas/AutoGPT?pullRequest=123)** > > <details> > <summary>Why do we need a CLA?</summary> > > The CLA protects both you and the project by clarifying the terms under which your contribution is made. It's a one-time process — once signed, it covers all your future contributions. > > </details> > > <details> > <summary>Common issues</summary> > > - **Email mismatch:** Make sure your Git commit email matches your GitHub account email > - **Merge commits:** If you merged `dev` into your branch, try rebasing instead: `git rebase origin/dev && git push --force-with-lease` > - **Multiple authors:** All commit authors need to sign, not just the PR author > > </details> > > If you have questions, just ask! 🙂 </details> <details> <summary>23-Day Close Warning</summary> >⚠️ **This PR will be automatically closed in 7 days** if the CLA is not signed. > > We haven't received a signed CLA from all contributors yet. Please sign it to keep this PR open: > > **➡️ [Sign the CLA here](https://cla-assistant.io/Significant-Gravitas/AutoGPT?pullRequest=123)** > > If you're unable to sign or have questions, please let us know — we're happy to help! </details> <details> <summary>30-Day Auto-Close</summary> > 👋 Closing this PR due to unsigned CLA after 30 days. > > Thank you for your contribution! If you'd still like to contribute: > > 1. [Sign the CLA](https://cla-assistant.io/Significant-Gravitas/AutoGPT) > 2. Re-open this PR or create a new one > > We appreciate your interest in AutoGPT and hope to see you back! 🚀 </details> ## Configuration Timing is configurable via env vars: ```yaml env: REMINDER_DAYS: 7 # Days before first reminder CLOSE_DAYS: 30 # Days before auto-close ``` ## Testing Can be tested with: ```bash gh workflow run cla-label-sync.yml -f pr_number=11919 ``` --------- Co-authored-by: Nicholas Tindle <nicholas.tindle@agpt.co> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: ntindle <8845353+ntindle@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…eader button (Significant-Gravitas#13767) ### Why / What / How <!-- Why: Why does this PR exist? What problem does it solve, or what's broken/missing without it? --> This PR addresses a `NotFoundError` occurring in the `/tour/chat` page when browser translation extensions (like Google Translate) are active. The `TourChatHeader` component's `Button` conditionally renders bare text (`'Link copied'` or `'Share this demo'`). Browser translation extensions often wrap these bare text nodes in `<font>` tags, mutating the DOM. When the `isCopied` state changes, React attempts to reconcile the DOM by inserting or replacing a text node, but it fails because the original text node is no longer a direct child of the `Button` (it's now nested within a `<font>` tag), leading to a `NotFoundError`. <!-- What: What does this PR change? Summarize the changes at a high level. --> The fix involves wrapping the conditionally rendered text inside the `Button` in `TourChatHeader.tsx` with a `<span>` element. <!-- How: How does it work? Describe the approach, key implementation details, or architecture decisions. --> By wrapping the text in a `<span>`, React has a stable DOM element to update. When the `isCopied` state toggles, React updates the `textContent` of the `<span>` element, rather than attempting to replace a bare text node. This approach is resilient to external DOM mutations by browser extensions, as the `<span>` provides a consistent structural anchor for React's reconciliation process. ### Changes 🏗️ <!-- List the key changes. Keep it higher level than the diff but specific enough to highlight what's new/modified. --> - Wrapped conditionally rendered text ('Link copied' or 'Share this demo') within the `Button` component in `TourChatHeader.tsx` with a `<span>` element. ### Checklist 📋 #### For code changes: - [x] I have clearly listed my changes in the PR description - [x] I have made a test plan - [x] I have tested my changes according to the test plan: <!-- Put your test plan here: --> - [x] Navigate to the `/tour/chat` page. - [x] Activate a browser translation extension (e.g., Google Translate) and translate the page. - [x] Click the 'Share this demo' button. - [x] Verify that the text changes to 'Link copied' without any `NotFoundError` in the console. - [x] Click the button again and verify the text reverts to 'Share this demo' without errors. <details> <summary>Example test plan</summary> - [ ] Create from scratch and execute an agent with at least 3 blocks - [ ] Import an agent from file upload, and confirm it executes correctly - [ ] Upload agent to marketplace - [ ] Import an agent from marketplace and confirm it executes correctly - [ ] Edit an agent from monitor, and confirm it executes correctly </details> #### For configuration changes: - [ ] `.env.default` is updated or already compatible with my changes - [ ] `docker-compose.yml` is updated or already compatible with my changes - [ ] I have included a list of my configuration changes in the PR description (under **Changes**) <details> <summary>Examples of configuration changes</summary> - Changing ports - Adding new services that need to communicate with each other - Secrets or environment variable changes - New or infrastructure changes such as databases </details> Fixes [BUILDER-7WC](https://significant-gravitas.sentry.io/issues/7439524689/?seerDrawer=true) Co-authored-by: seer-by-sentry[bot] <157164994+seer-by-sentry[bot]@users.noreply.github.com>
…ignificant-Gravitas#13627) ### Why 🤔 Model management was scattered across three hand-maintained dicts (`MODEL_METADATA`, `MODEL_COST`, `TOKEN_COST`), env vars, LD payloads, and handwritten retirement migrations. Adding one model meant editing four places; retiring one meant writing SQL. This PR makes one file — `backend/data/llm_registry/catalog.py` — the single source of truth for every model fact, and proves the swap changes nothing that wasn't meant to change. Consolidates and supersedes the stacked PRs Significant-Gravitas#13621–Significant-Gravitas#13626 (same commits, one landing) and Significant-Gravitas#13596 (K3 ships here, through the new path). ### Changes 🏗️ **The catalog system** (from the reviewed stack): - `catalog.py` — 86 models / 8 providers / 17 creators with metadata, credit pricing, visibility (GA/employees/admins/hidden), enable flags, and copilot routing cells (ship empty; env stays authoritative until a cell is claimed) - Copilot model resolution: LD override → catalog cell (cloud-only) → env default, with serve-time gating (unknown/disabled slugs refused loudly) and `ChatMessage.model`/`routingSource` stamping for product-intelligence - Retirement CLI (`python -m backend.data.llm_registry.retire`) — rewrites stored graphs with a revertible record; kills retirement-by-migration - One migration: ChatMessage stamp columns + `LlmModelMigration` records table - Docs: `docs/platform/contributing/managing-llm-models.md` runbook **The cutover (this PR removes what it replaces):** - `MODEL_METADATA`, `MODEL_COST`, `TOKEN_COST` literals (~600 lines) deleted; same names now derived from the catalog at import, so blocks/billing/stagehand callers are untouched - Drift tripwires deleted (nothing left to drift); replaced by a cutover-parity snapshot test proving billing + metadata identical for all 85 pre-existing models - `LlmModel` stays as pure identifiers (block schemas serialize it); an import-time check keeps enum ⊆ catalog - The public `GET /api/llm/catalog` endpoint from the original stack is **dropped entirely** (−793 lines: routes, per-IP rate limiter, XFF trust logic, cache-path carve-out, settings). It existed to bootstrap DB-seeded installs — a premise the in-repo catalog removes. A future model picker gets a small authenticated route instead (documented in the runbook). **Kimi K3** — first model added the new way: - One catalog entry (`moonshotai/kimi-k3`, 1M ctx, tier 3, run 9cr, 450/2250 cr per 1M) + one enum name line - Split-price Moonshot rate override ($3/$15 per Mtok, verified against live OpenRouter) for copilot in-turn cost estimates **Post-review hardening** (three parallel deep-review agents: correctness / architecture-skeptic / goals-audit): - Model identity moved into the catalog package (`llm_registry/llm_models.py`) — identity and facts share one home; `blocks/llm.py` re-exports, zero call sites changed. `DEFAULT_LLM_MODEL` now derives from the catalog's `is_recommended`; kill-switched models drop out of picker metadata while stored graphs keep validating. - Router fixes: catalog gating is cloud-only (self-hosted operators' LD slugs serve again), date-suffixed Anthropic catalog slugs match their OpenRouter spellings (`anthropic/claude-haiku-4-5` ↔ `claude-haiku-4-5-20251001`), Sentry refusals deduped to once per slug per process. - Perimeter cut (~250 lines): write-only `route_warnings` Redis sink, consumer-less registry picker API, shapeless `capabilities`/`metadata` dict bags. - Catalog now also carries provider USD list prices (`provider_*_usd_per_1m`); `moonshot.py`'s split-price overrides read them (Kimi K3 $3/$15). Retire CLI defaults `--replacement` from `fallback_model_slug`, making the field's documented behavior true. - Runbook/AGENTS/ollama docs rewritten for the post-cutover world; the snapshot's 4 intentional provider display renames (`V0`→`v0 by Vercel`, `AI/ML`→`AI/ML API`) are disclosed in the parity test itself. ### Proof it's safe 🧪 - **Billing parity**: `pre_catalog_costs_snapshot.json` + test — catalog-derived costs byte-identical to the deleted dicts for every model × rate field. Only delta anywhere: two provider *display* names improved (`V0`→`v0 by Vercel`, `AI/ML`→`AI/ML API`) - **No stranded graphs**: `stored_graph_models_test.py` — every distinct `constantInput->>'model'` value from **real prod and dev databases** (92 values, queried 2026-07-20) resolves through the catalog or is owned by a non-LLM block - 126 targeted tests green locally (registry, catalog guards, router, moonshot); full formatter chain incl. pyright clean ### Checklist 📋 #### For code changes: - [x] I have clearly listed my changes in the PR description - [x] I have made a test plan - [x] I have tested my changes according to the test plan: - [x] Parity snapshot test (85 models × all cost/metadata fields) - [x] Prod/dev stored-graph fixture test (92 values) - [x] Registry/router/retirement suites green locally - [x] Preview deploy: verified live by review-bot QA (hosted + local variants) — catalog boots fail-hard with 86 models, dropdown intact incl. K3, copilot serves end-to-end, dropped endpoint 404s, migration applies cleanly 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01Hx4NPwAHAMWU78bfoV3C2b <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **High Risk** > Changes core copilot routing, billing/metadata derivation, and API boot behavior; misconfigured catalog or routing cells can block startup or mis-route production turns until fixed. > > **Overview** > This PR **cuts over** LLM identity and facts from the giant inline `blocks/llm.py` enum/metadata dicts to **`backend/data/llm_registry/`** (`catalog.py` + `llm_models.py`), with **`blocks/llm.py` re-exporting** `LLMModel`, `DEFAULT_LLM_MODEL`, `MODEL_METADATA`, and related symbols so block code keeps the same import surface. Call sites rename **`LlmModel` → `LLMModel`**; admin execution analytics now marks the recommended dropdown option from **`DEFAULT_LLM_MODEL`** instead of a hardcoded slug. > > **Copilot model resolution** becomes **`resolve_model_route`**: LaunchDarkly → catalog **copilot** routing cells → env defaults, returning **`ResolvedModel(model, source)`**. On **managed cloud** transports, unknown or kill-switched slugs are **refused** (log + deduped Sentry) and fall through; **self-hosted** and **local** baselines skip catalog cells/gating. **`load_catalog()`** runs at **REST API** startup and in the **copilot executor** (fail-hard if broken). > > **Analytics persistence** adds assistant-turn **`model`** and **`routingSource`** on chat messages (baseline + SDK paths), with **back-fill** for rows flushed mid-turn before stamping; **`routing_source`** stays server-only in API payloads. **Moonshot** split pricing (e.g. **Kimi K3**) is pulled from catalog provider USD fields instead of a hand-maintained override map. > > Docs/ops updates describe **catalog-only hotfixes to `master`**, env vs catalog precedence for AutoPilot, and the **managing-llm-models** runbook in AGENTS. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit c69a74b. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Bentlybro <Github@bentlybro.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…dBlock (Significant-Gravitas#12702) ### Why / What / How **Why:** VideoDownloadBlock (currently disabled) has three security vulnerabilities identified in PR Significant-Gravitas#11796 review and tracked as SECRT-1898: no SSRF protection on URL input, no download size limit in yt-dlp options, and no playlist restriction. These must be fixed before the block can ever be re-enabled. **What:** Adds SSRF validation, download size enforcement, and playlist restriction to VideoDownloadBlock, plus 10 tests covering all attack vectors. **How:** Reuses the existing `validate_url_host()` from `backend.util.request` (which blocks private IPs, non-http protocols, cloud metadata endpoints) by calling it in `run()` before yt-dlp touches the URL. Adds `max_filesize` and `noplaylist` to yt-dlp's `ydl_opts` so limits are enforced during download rather than after. Promotes `MAX_FILE_SIZE_BYTES` to a module-level export in `util/file.py` for reuse. ### Changes 🏗️ - **`backend/blocks/video/download.py`**: Added `validate_url()` method wrapping `validate_url_host()`, called in `run()` before `_download_video()`. Added `max_filesize: MAX_FILE_SIZE_BYTES` and `noplaylist: True` to `ydl_opts`. - **`backend/util/file.py`**: Promoted `MAX_FILE_SIZE_BYTES` from local variable inside `store_media_file()` to module-level constant for import by other modules. - **`backend/blocks/video/download_test.py`** (new): 10 tests across 4 classes — SSRF vectors (file://, loopback, private ranges, cloud metadata, IPv6), size limit verification, playlist restriction, and valid URL passthrough. Block remains `disabled=True` pending sandbox work. ### Checklist 📋 #### For code changes: - [x] I have clearly listed my changes in the PR description - [x] I have made a test plan - [x] I have tested my changes according to the test plan: - [x] All 10 new tests pass (`poetry run pytest backend/blocks/video/download_test.py -xvs`) - [x] Formatter and linter pass (`poetry run format && poetry run lint`) - [ ] CI passes on this PR #### For configuration changes: - [x] `.env.default` is updated or already compatible with my changes - [x] `docker-compose.yml` is updated or already compatible with my changes - [x] I have included a list of my configuration changes in the PR description (under **Changes**) No configuration changes needed. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Touches security-sensitive URL handling and download enforcement for `yt-dlp`; incorrect validation/limits could still allow SSRF/DoS if the block is re-enabled, though scope is localized to this block and shared file-size constant. > > **Overview** > Adds SSRF protection to `VideoDownloadBlock` by validating the input URL via `validate_url_host()` before invoking `yt-dlp`, and surfaces validation failures as `BlockExecutionError`. > > Hardens downloads by passing `max_filesize` (using the newly exported `MAX_FILE_SIZE_BYTES`) and `noplaylist=True` into `yt-dlp` options to prevent oversized downloads and playlist pulls. > > Introduces `download_test.py` coverage for blocked URL vectors, max filesize option wiring, playlist restriction, and a success path, and promotes `MAX_FILE_SIZE_BYTES` in `backend/util/file.py` to a module-level constant for reuse. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit d958221. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ded (Significant-Gravitas#13751) > Only for testing purpose ### Why / What / How **Why:** We're standardising on [Hugeicons stroke-rounded](https://hugeicons.com/icons/stroke-rounded) as the platform icon set. Phosphor was the previous standard and was used in 382 frontend files, so the switch has to be done in one pass — a partial migration would leave two icon systems with visibly different stroke weights side by side. **What:** Every Phosphor icon in the frontend is replaced by its Hugeicons stroke-rounded equivalent, rendered through a new `Icon` atom that applies a uniform **2px** stroke width. `@phosphor-icons/react` is removed from `package.json`. **How:** Phosphor icons are React *components*; Hugeicons ship as icon *data* (`IconSvgElement` — an array of SVG path tuples) rendered by a single component. That difference drives the shape of the whole diff: ```tsx // before <TrashIcon size={16} weight="bold" className="text-red-500" /> // after import { Delete02Icon } from "@hugeicons/core-free-icons"; import { Icon } from "@/components/atoms/Icon/Icon"; <Icon icon={Delete02Icon} size={16} className="text-red-500" /> ``` The mechanical rewrite was done with a `ts-morph` AST codemod (not regex — many Phosphor exports are common words like `Check`, `File`, `X`, `Table`, and a textual replace would have corrupted strings and JSX text). It rewrote 852 JSX sites and 321 value/type references. The remaining ~135 sites — places where an icon was stored in a variable or record and rendered indirectly — were fixed by hand, since they need a real decision about whether the field should become `IconSvgElement` or stay a component type. Stroke width lives in exactly one place (`ICON_STROKE_WIDTH` in the `Icon` atom) rather than being repeated on ~850 call sites, so changing it later is a one-line edit. ### Changes 🏗️ - **New `Icon` atom** (`src/components/atoms/Icon/Icon.tsx`) — wraps `HugeiconsIcon`, defaults `strokeWidth` to `1.75` and `size` to `"1em"` so icons keep scaling with surrounding text exactly as Phosphor's did. Also exports `createIconComponent()` for the few records that legitimately mix Hugeicons with `react-icons` / `@radix-ui/react-icons` components and therefore need a uniform component type. - **Icon mapping** — all 291 distinct Phosphor exports mapped to Hugeicons equivalents, chosen by visual/semantic match and validated against the package's export list (e.g. `TrashIcon` → `Delete02Icon`, `CaretDownIcon` → `ArrowDown01Icon`, `CircleNotchIcon` → `Loading03Icon`, `MagnifyingGlassIcon` → `Search01Icon`). - **`weight` prop dropped everywhere** (389 usages). Hugeicons free is stroke-only and stroke width is now owned by the atom. In the one place where `weight` encoded state rather than style — connected vs unconnected node handles in the builder — it's replaced by `fill={isInputConnected ? "currentColor" : "none"}` so the distinction survives. - **Icon-carrying types retyped** from component types to `IconSvgElement` (nav item configs, artifact classification, sitrep priority config, etc.). - `getAccountMenuPhosphorIcon` → `getAccountMenuIcon`, dropping its now-unused `weight` argument. - **Docs/config updated** to the new convention: `frontend/AGENTS.md`, `frontend/CONTRIBUTING.md`, `frontend/README.md`, `.github/copilot-instructions.md`, and the `components/tokens/icons.stories.tsx` catalog story. - **Test mocks migrated** — the four suites that stubbed `@phosphor-icons/react` now stub the `Icon` atom; two of those mocks were dead (their testids were never asserted) and were removed. - `@phosphor-icons/react` removed; `@hugeicons/react` + `@hugeicons/core-free-icons` added. ### Checklist 📋 #### For code changes: - [x] I have clearly listed my changes in the PR description - [x] I have made a test plan - [ ] I have tested my changes according to the test plan: - [x] `pnpm types` — clean, 0 errors - [x] `pnpm lint` — clean, 0 errors - [x] `pnpm test:unit` — 405 test files / 4237 tests pass, 2 skipped - [x] `pnpm build` — production build succeeds - [x] `git grep phosphor` returns nothing outside the lockfile - [ ] **Visual review needed** (see below) #### Visual review This is the part CI can't cover. The mapping is 1:1 in meaning but Hugeicons glyphs are not pixel-identical to Phosphor's, and Phosphor's `weight="fill"` / `"duotone"` variants have no Hugeicons equivalent, so some previously-filled icons are now outlines. Worth a pass over: - [ ] Marketplace + Library cards and empty states - [ ] Copilot chat (tool accordions, artifact panel, chat input chips) - [ ] Builder canvas — node handles specifically, since connected/unconnected now reads via `fill` rather than icon weight - [ ] Settings (sidebar, mobile nav, creator dashboard status pills) - [ ] Navbar account menu and wallet - [ ] Storybook `Tokens/Icons` catalog for a side-by-side of the whole set A few mappings were judgement calls where Hugeicons has no close twin — `ChalkboardIcon` → `Presentation01Icon`, `CardsThreeIcon` → `Album01Icon`, `FoldersIcon` → `FolderLibraryIcon`, `WarningOctagonIcon` → `Alert02Icon`. Happy to swap any of these if a reviewer prefers a different glyph. #### For configuration changes: - [x] `.env.default` is updated or already compatible with my changes - [x] `docker-compose.yml` is updated or already compatible with my changes - [x] I have included a list of my configuration changes in the PR description (under **Changes**)
…dream memory (Significant-Gravitas#13391) ## Why The dream sanitize phase let two kinds of pollution land as durable memory (observed on the dev canary, Significant-Gravitas#13388): - **Transient intent** — questions captured as facts: "User is asking how Kubernetes works", "User is interested in knowing which PRs are open". - **Generic world-knowledge** — definitional/tutorial content the assistant explained in passing ("Kubernetes uses pods as the smallest unit"), not facts *about the user*. These dilute warm-context retrieval and are exactly what the sanitizer (phase 3) exists to drop. ## What / How Two layers, because prompt-only sanitization was already leaking these: 1. **Sanitize prompt** (`prompts.py`): explicit rules to reject transient-intent and generic-non-user-knowledge writes/proposals — with a deliberate carve-out keeping durable user **goals** ("wants to build X", "is migrating to Y"). 2. **Deterministic filter** (`orchestrator.py::_clamp_operations` — the single sync+batch chokepoint before `apply_operations`) for the mechanical transient-intent cases the prompt leaks. The regex is **high-precision by design**: it matches knowledge-seeking intent only (`asking`, `wondering`, `wants to know/understand/find out`, `interested in knowing/understanding`, `asked how/what/…`). It deliberately does **not** match: - durable goals (`wants to create/build` — never matched) - skill goals (`wants to learn Spanish`, `interested in learning Rust`) — `learn` counts as transient only with an interrogative complement (`wants to learn HOW X works`) - bare `interested in <noun>` ("interested in lighthouses") Generic world-knowledge is intentionally left to the **prompt**, not the regex: deciding "is the subject the user?" needs LLM judgment and can't be regex'd without false positives. ## Review Adversarially reviewed specifically for false positives (the only real risk — dropping a legitimate memory). The review found one real should-fix — `learn`/`interested in learning` bundled with the interrogative verbs was dropping durable skill goals — which is **fixed** in this PR (the interrogative-complement carve-out) and pinned by tests, including bare `interested in <noun>` keep-cases so a future over-broad edit fails loudly. Known limitation (nice-to-have, low frequency): a standing notification preference phrased "wants to know when X happens" is dropped by the regex; separating it from a one-off "wants to know when the deploy is" isn't reliably regex-able, so it's left to the prompt + human review. Noted in a code comment. ## Testing TDD — 46 passing across `orchestrator_test` + `prompts_test`, including the keep/drop boundary (transient dropped; goals, skill-goals, bare-interest, user-facts, and generic-knowledge all kept), the real `_clamp_operations` integration path for both writes and proposals, and prompt-rule presence. `pyright`/`black`/`isort` clean. > Authored while the requester is away and can't review closely; required approval still gates merge. The one judgment call (high-precision regex + prompt-for-the-rest, rather than an aggressive filter that risks eating real memories) is called out above for a reviewer. Closes Significant-Gravitas#13388 🤖 Generated with [Claude Code](https://claude.com/claude-code) Fixes SECRT-2478 --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: Krzysztof Czerwinski <kpczerwinski@gmail.com>
…ptional PR coverage refresh (Significant-Gravitas#13434) ## Summary Adds a manual `workflow_dispatch` trigger to **`.github/workflows/platform-backend-ci.yml`** so the full backend **lint / type-check / test + coverage** suite can be run **on demand** against any branch, and — when that branch has an open PR — can refresh that PR's coverage status. ```bash # Plain on-demand run for a branch: gh workflow run platform-backend-ci.yml --ref <branch> # Run + attach the coverage upload to an open PR (refreshes its codecov status): gh workflow run platform-backend-ci.yml --ref <pr-head-branch> -f pr_number=<PR#> ``` ## (a) What it does - **`workflow_dispatch` trigger** on `platform-backend-ci.yml` — runs the same `test` job as the automatic triggers (full matrix + the existing Codecov upload) for the dispatched branch's HEAD commit. - **Optional `pr_number` input**, wired to the Codecov action's `override_pr` on the upload step. When provided, the dispatched run's coverage upload is attached to that PR, so Codecov re-evaluates the PR's `codecov/project/platform-backend` status against the PR base. This lets a manual run refresh a coverage check that's stale only because the branch never triggered backend CI (e.g. a frontend/docs-only branch, where the path filter skips the automatic backend run and Codecov falls back to carried-forward coverage). - **Automatic runs are unaffected:** the existing `push` / `pull_request` (with their `paths:` filters) and `merge_group` triggers are unchanged, and `override_pr` resolves to empty for every non-`workflow_dispatch` event, so normal PR/commit detection is untouched. ## (b) Activation caveat GitHub only makes a `workflow_dispatch` workflow manually triggerable once the trigger exists on the repository's **default branch** (`master`) — even when dispatching another branch via `--ref`. This PR targets `dev`, so **the trigger stays inert until it propagates `dev → master`** through the normal cadence. Documented in `TESTING.md`. ## (c) Validation note Because it isn't dispatchable until it reaches `master`, the `pr_number` / `override_pr` refresh path **could not be live-validated in this PR**. After this lands on `master`, it should be validated with one real dispatch against an open PR (confirm the `codecov/project/platform-backend` check refreshes against the PR base). Note: `codecov.yml` has `require_ci_to_pass: true`, so Codecov waits for the dispatched run's CI to finish before updating the status. ## Files changed - `.github/workflows/platform-backend-ci.yml` — `workflow_dispatch` trigger + `pr_number` input + `override_pr` on the upload step. - `autogpt_platform/backend/TESTING.md` — "Running backend CI on demand" (how to invoke, the PR-refresh path, and the activation/validation caveats). Scope is intentionally minimal — no `codecov.yml` changes, nothing else touched. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_014Sw1B531emT5AyhbWp46Hh --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
… viewport (Significant-Gravitas#13797) ### Why / What / How **Why:** On `/copilot` in the new layout, opening the Files drawer and previewing a file (artifact panel) pushes the page wider than the viewport — a horizontal page scrollbar appears and the panel's right-edge header buttons (download / all files / close) end up off-screen. **Root cause:** `SidebarInset` is a `flex-1` item in the sidebar layout row but has no `min-w-0`. Its default `min-width: auto` resolves to its content's intrinsic min width. The copilot right-side panels are `shrink-0` with fixed widths (context panel 432px default, artifact panel 640px default and freely resizable/persisted), and the chat column's intrinsic min is ~480px, so on typical laptop widths the inset's min-content exceeds the space next to the 292px sidebar. Instead of shrinking, the inset grows the page past the viewport. The width clamp added in Significant-Gravitas#13630 can't catch this: it clamps the artifact panel to `parent.offsetWidth - PANEL_RESERVED_WIDTH`, but the parent row has already grown along with the inset, so the measurement reports enough room and the clamp never engages. **What/How:** Add `min-w-0` to `SidebarInset`'s base classes so the flex layout can shrink it to the space the sidebar leaves. With the inset held to the available width, the existing Significant-Gravitas#13630 clamp measures the real row width and correctly sizes the artifact panel, keeping its close button in view. `TourCopilot` already works around the same issue locally with `overflow-hidden` on its inset; this fixes it at the source for all `SidebarInset` usages. ### Changes 🏗️ - `components/ui/sidebar.tsx`: add `min-w-0` to `SidebarInset` base classes (one-line CSS fix + comment). ### Checklist 📋 #### For code changes: - [x] I have clearly listed my changes in the PR description - [x] I have made a test plan - [x] I have tested my changes according to the test plan: - [x] `pnpm format`, `pnpm lint`, `pnpm types` pass - [ ] On `/copilot` (new layout) with a chat that produced a document artifact: open the file preview, resize the panel wide, verify no horizontal page scrollbar and the panel close button stays visible - [ ] Verify chat + Files drawer (context panel) still lay out correctly at 1280px and 1024px widths No integration test added: the bug is flex-layout behavior (`min-width: auto` intrinsic sizing) which jsdom does not implement, so it can only be verified in a real browser. #### For configuration changes: - [ ] `.env.default` is updated or already compatible with my changes - [ ] `docker-compose.yml` is updated or already compatible with my changes - [ ] I have included a list of my configuration changes in the PR description (under **Changes**) Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…ignificant-Gravitas#13798) ### Why / What / How **Why:** Since Significant-Gravitas#13787 / Significant-Gravitas#13772, an agent scheduled from an expert's chat (e.g. asking Maria to create and schedule an agent) claims success and shows up in the chat `list_schedules` tool, but never appears on the Team card, the `/team/[expertId]` page, or the chat schedules drawer — the card keeps saying "No schedules yet". **What:** Stamp the chat session's `expert_id` on schedules created through the copilot `run_agent` tool, so expert-chat-created schedules are attributed like install-time and REST-created ones. **How:** All expert schedule surfaces filter on `schedule.expert_id` (`getExpertSchedules` in `team/helpers.ts`, with a fallback join on `ExpertWorkflow.schedule_id`). The install-time path (`experts/scheduling.py`) and the REST endpoint (`POST /graphs/{id}/schedules`) both pass `expert_id` to `add_execution_schedule`, but the chat tool's `_schedule_agent` did not — even though the `ChatSession` already carries the expert it is scoped to. The chat `list_schedules` tool lists all user schedules unfiltered, which is why the expert could "see" the schedule while every expert surface could not. Passing `session.expert_id` follows the same session-anchored pattern already used for org/team tenancy in the same call. Attribution also means these schedules now count toward the expert's weekly credit guardrail and get deleted by `detach_expert_triggers` on archive — previously they escaped both. Plain Autopilot sessions have `expert_id = None`, so nothing changes for them. ### Changes 🏗️ - `copilot/tools/run_agent.py`: `_schedule_agent` passes `expert_id=session.expert_id` to `add_execution_schedule`. - `executor/scheduler.py`: `GraphExecutionJobArgs.expert_id` comment now mentions the chat-created source. - `copilot/tools/_test_data.py`: `make_session` accepts an optional `expert_id`. - `copilot/tools/run_agent_test.py`: regression tests — expert-scoped session stamps `expert_id` on the scheduler call; plain session leaves it `None`. ### Checklist 📋 #### For code changes: - [x] I have clearly listed my changes in the PR description - [x] I have made a test plan - [ ] I have tested my changes according to the test plan: - [ ] `poetry run pytest backend/copilot/tools/run_agent_test.py -k expert` (not run locally; please verify via CI) - [ ] Manual: in an expert chat, ask the expert to schedule an agent → schedule appears on the Team card, `/team/[expertId]`, and the chat schedules drawer 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…ype (Significant-Gravitas#13796) ## Why The dev deploy is failing and onboarding is broken on dev. `20260805120000_onboarding_step_to_string` (from Significant-Gravitas#13119) failed to apply: ``` ERROR: cannot alter type of a column used by a view or rule DETAIL: rule _RETURN on view analytics.user_onboarding depends on column "completedSteps" ``` Postgres won't retype a column a view depends on. The analytics views read `completedSteps`, but they are generated out-of-band by `poetry run analytics-views` (`backend/scripts/generate_views.py`) from `analytics/queries/*.sql` — so they exist in deployed databases and never in one built from migrations alone. Local runs and CI both build from migrations only, which is why this passed everywhere and failed only on a real environment. Two consequences today: 1. The columns are still `OnboardingStep[]` while the deployed backend expects `String[]`, so onboarding reads fail with `ApiError: Error converting field "completedSteps" ... found incompatible value of "BUILDER_RUN_AGENT"`. 2. Prisma recorded the migration as failed, so **no migration can apply to dev** (`P3009`) until it is resolved. ## What The migration now **drops** the dependent relations and lets `analytics-views` recreate them. It does not restore them itself. ## How An earlier revision captured each view's definition, owner and ACL and restored them. Review showed that to be both unnecessary and wrong: the deployed `user_onboarding_funnel` still hardcodes the **pre-rename** step grid, so restoring it verbatim would resurrect a funnel reporting 0 for `ONBOARDING_COMPLETE` and `LIBRARY_RUN_AGENT`. Re-running the generator instead rebuilds every view from the repo, which already carries the new names, and re-grants `analytics_readonly`. Dropping rather than restoring also removes a whole class of restore bugs raised in review: lost column-level grants (`pg_attribute.attacl`), lost reloptions and comments, non-round-tripping pretty-printed viewdefs, ACLs widened by `ALTER DEFAULT PRIVILEGES`, and swallowed owner-restore failures leaving views owned by the migration role. Discovery covers materialized views and uses `CASCADE`, so transitive dependents are handled; `pg_depend` is constrained to `classid = 'pg_rewrite'` to avoid false positives; and `lock_timeout` makes the drop fail fast rather than queue behind a long-running BI query. ##⚠️ Required step (dev only, no DB access needed) The failed `_prisma_migrations` row must be cleared before `migrate deploy` will do anything — it returns `P3009` until then. Thanks to the `resolve_rolled_back` input on `platform-autogpt-deploy-dev.yaml`, this is a workflow dispatch rather than a manual database connection: ```bash gh workflow run platform-autogpt-deploy-dev.yaml \ -f git_ref=dev \ -f resolve_rolled_back=20260805120000_onboarding_step_to_string ``` That job runs `prisma migrate resolve --rolled-back` and then `prisma migrate deploy` against the dev database using the CI secrets, then triggers the deploy. **Order matters: merge this PR to `dev` first.** The workflow checks out `git_ref`, so if it runs before the merge it will clear the failed row and then re-apply the *broken* migration, putting the row straight back. `--rolled-back` (not `--applied`) is correct: Prisma runs each migration in a transaction and this one aborted on the first `ALTER`, so nothing from it persisted. **Local databases** — anyone who pulled `dev` and migrated since Significant-Gravitas#13119 merged will hit "migration … was modified after it was applied", since this edits that file. Local databases are disposable: `prisma migrate reset`. ## Does production need manual steps? **No.** `master` has never applied this migration (newest there is `20260802120000`), so there is no failed record to clear, and the views are rebuilt in-migration. Two things worth checking before promoting, since neither is visible from here: **1. The deploy role must be able to drop the views** (`DROP VIEW` requires ownership or superuser): ```sql SELECT current_user, (SELECT usesuper FROM pg_user WHERE usename = current_user) AS is_superuser; ``` **2. Enumerate the dependency closure**, to see whether prod carries dependents beyond the two tracked views. A materialized view, or any view not in `analytics/queries/`, is dropped by `CASCADE` and **cannot** be rebuilt by this migration — it would need recreating manually. The migration raises a warning naming each, but Prisma does not surface server notices, so check in advance: ```sql SELECT n.nspname, c.relname, c.relkind FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace WHERE c.relkind IN ('v','m') AND EXISTS ( SELECT 1 FROM pg_depend d JOIN pg_rewrite r ON r.oid = d.objid WHERE d.classid = 'pg_rewrite'::regclass AND r.ev_class = c.oid AND d.refobjid = '"UserOnboarding"'::regclass AND d.refobjsubid IN ( SELECT attnum FROM pg_attribute WHERE attrelid = '"UserOnboarding"'::regclass AND attname IN ('completedSteps','notified','rewardedFor'))); ``` If that returns only `analytics.user_onboarding` and `analytics.user_onboarding_funnel`, promotion is fully hands-off. Recommended sequence: fix dev, confirm the deploy goes green there, then promote. ## Test plan Verified against a database rebuilt to the deployed shape — full migration chain, `analytics` schema, `analytics_readonly`, views created exactly as `generate_views.py` creates them (`WITH (security_invoker = false)`), plus a view layered on `user_onboarding_funnel` and a materialized view on the base table: - [x] The original statement reproduces the **exact** production error - [x] Fixed migration applies; all four dependents (incl. transitive view and matview) dropped via `CASCADE` - [x] Columns end as `TEXT[]`; `OnboardingStep` dropped; renames applied; `BUILDER_RUN_AGENT` and other values preserved - [x] `analytics-views` then recreates the views with `reloptions = {security_invoker=false}` and `analytics_readonly` SELECT restored - [x] Funnel reports the **renamed** steps (`LIBRARY_RUN_AGENT` 11, `ONBOARDING_COMPLETE` 15) — the regression the restore-from-catalog approach would have shipped - [x] Backend onboarding tests pass ## Follow-up Local and CI databases are built from migrations only, so nothing can catch a dependency on these hand-applied views. Bringing `analytics/queries/*.sql` into CI setup (or running `analytics-views` as part of deploy) would close the gap — this is the second time these views have caused trouble. --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ificant-Gravitas#13792) ### Why / What / How **Why:** A transcription request succeeding is not the same as the user saying anything. Silence, background noise, STT hallucinations, or a few seconds of filler all come back as a "successful" transcript — and the whole personalized pipeline (greeting, suggested prompts, provider picks) would confidently personalize from garbage. **What:** Adds a quality gate between transcription and personalized generation, a recovery UI when the gate rejects, and an offline eval harness (STT WER corpus + gate accuracy corpus) to measure both the transcription and the gate. **How:** The gate is layered so the common cases stay free: 1. **Deterministic reject** — empty/no-letters text, looping transcripts (low unique-word ratio, high zlib compression ratio) never reach a model. 2. **Deterministic pass** — clearly substantial text (≥40 words, or char-based route for space-less scripts) never pays for a model call. 3. **Ambiguous middle** — one cheap Haiku call judges "is there enough concrete information to personalize from?". Any judge failure (no client, timeout, malformed output) resolves to a recoverable reject, never a silent pass. Rejected dumps keep their audio + transcript on the row (`errorCode` = `no_usable_speech` / `insufficient_content`, plain string column — no migration). The frontend shows an insufficient-content recovery state offering retry / type instead / skip, and the intro card falls back to Path B instead of greeting the user with content the gate refused. ### Changes 🏗️ **Backend** - `quality.py` — new layered quality gate (deterministic reject → deterministic pass → LLM judge) - `service.py` — gate wired into both voice (`finalize_voice_dump`) and typed (`finalize_typed_dump`) paths; intro card treats quality-rejected dumps as Path B - `brain_dump_gate_eval.py` + `eval_corpus.py` + `eval_data/` — offline evals: STT word-error-rate corpus (clean / 15dB / 5dB noise) and gate accuracy corpus (noise/silence fixtures + text cases) - `EVAL.md` — how to run the evals and current baselines - Tests: `quality_test.py`, `brain_dump_gate_eval_test.py`, plus service/route coverage for the reject paths **Frontend** - `InsufficientState.tsx` — recovery screen for gate rejections (retry / type / skip) - `useBrainDumpStep.ts` / `helpers.ts` — handle `no_usable_speech` / `insufficient_content` error codes as a recoverable state instead of a generic failure - Onboarding funnel analytics: track quality rejections and typed-vs-suggested prompt usage (`brain-dump-analytics.ts`) - Intro card / welcome dialog fall back cleanly when the dump was rejected ### Checklist 📋 #### For code changes: - [x] I have clearly listed my changes in the PR description - [x] I have made a test plan - [x] I have tested my changes according to the test plan: - [x] Backend unit tests for the gate: deterministic reject (empty, symbols-only, looping transcript), deterministic pass (long dump, space-less script), ambiguous → LLM judge, judge failure → recoverable reject - [x] Service tests: voice + typed finalize reject paths mark the row failed and return the error code; transcript/audio retained - [x] Route tests: finalize response surfaces `error_code` - [x] Frontend integration tests: insufficient state renders with retry/type/skip, error-code mapping, analytics events - [ ] Gate eval run against `eval_data` corpus (requires LLM credentials — see EVAL.md) #### For configuration changes: - [x] `.env.default` is updated or already compatible with my changes (optional `BRAIN_DUMP_QUALITY_MODEL` env override, defaults to `anthropic/claude-haiku-4-5`) - [x] `docker-compose.yml` is updated or already compatible with my changes - [x] I have included a list of my configuration changes in the PR description (under **Changes**)
…tas#13771) ### Why / What / How Hired experts need one editable Soul document that consistently controls their identity and personality across conversations. This adds persisted Soul fields, a protected owner-only update API, and a Team-page drawer for editing an expert's name, identity, voice, and boundaries. Every message rebuilds the existing expert identity suffix from the latest owned Expert while preserving the cacheable base prompt, escaping user XML, and enforcing server-defined protected rules. ### Changes 🏗️ - Prisma migration and Expert API models for voice preferences, boundaries, and protected rules - Atomic owner-only Soul updates with templates and archived experts remaining read-only - Hiring copies all relevant Soul fields from the selected template - Dynamic prompt suffix rendering for existing sessions with XML escaping and permanent protected rules - Right-side Team Soul editor using generated API hooks, query invalidation, and success/error feedback - Static empty "What I've learned" placeholder; memory-note persistence remains deferred to OPEN-3232 - Team Soul UI remains under the existing `hire-experts` flag; backend routes follow the existing ungated Expert API convention - OpenAPI regeneration plus backend and frontend coverage for the new behavior - No environment-variable or Docker Compose changes ### Checklist 📋 #### For code changes: - [x] I have clearly listed my changes in the PR description - [x] I have made a test plan - [x] I have tested my changes according to the test plan: - [x] Relevant backend suite: 151 passed - [x] Backend formatting and linting - [x] Frontend `pnpm format`, `pnpm lint`, `pnpm types`, and `pnpm test:unit` in order: 405 files passed, 4,241 tests passed, 2 skipped - [x] React Doctor completed; no new Soul drawer finding - [ ] Manual hire, edit, existing-chat response, and reload verification with a matching local stack <details> <summary>Example test plan</summary> - [ ] Create from scratch and execute an agent with at least 3 blocks - [ ] Import an agent from file upload, and confirm it executes correctly - [ ] Upload agent to marketplace - [ ] Import an agent from marketplace and confirm it executes correctly - [ ] Edit an agent from monitor, and confirm it executes correctly </details> #### For configuration changes: - [x] `.env.default` is updated or already compatible with my changes - [x] `docker-compose.yml` is updated or already compatible with my changes - [x] I have included a list of my configuration changes in the PR description (under **Changes**) <details> <summary>Examples of configuration changes</summary> - Changing ports - Adding new services that need to communicate with each other - Secrets or environment variable changes - New or infrastructure changes such as databases </details>
…ng-first home (Significant-Gravitas#13800) ### Why / What / How **Why:** Closes Significant-Gravitas#13726, Significant-Gravitas#13728, Significant-Gravitas#13727. The morning test: within 5 seconds of opening the copilot home, a beta user should see what happened overnight, what was found, and what needs a decision. Today the home is a greeting plus an activity strip, overnight expert runs are invisible unless you open each thread, and pending human-in-the-loop approvals are scattered across thread, builder, and run views. **What:** Three stacked features on one branch: 1. **Morning briefing job** (Significant-Gravitas#13726, backend) — a daily per-user job that compiles overnight expert runs, findings (per-run activity summaries), and pending approvals into one briefing, persists it, and posts it into the user's primary (non-expert) copilot thread by 9am local time. 2. **Needs-attention unification** (Significant-Gravitas#13728) — `GET /api/review/pending` now returns expert attribution (name/avatar), agent name, and deep-link ids for every pending review, batched (kills an N+1); a reusable `NeedsAttentionList` organism gives one-tap Approve/Skip with shared React Query invalidation so home and thread views reconcile. 3. **Home surface v1** (Significant-Gravitas#13727, frontend) — behind the existing `hire-experts` flag, the copilot home becomes briefing-first: greeting → briefing card (three sections with deep links) → needs-attention list → team strip with derived member status → composer. Flag off: behavior is unchanged (covered by a dedicated flag-off test). **How:** - New `UserBriefing` table (unique per user + local date; nullable `deliveredAt` is stamped only after the session post succeeds, so a failed post is retried next run instead of skipped) with a typed `GET /api/briefings/latest` endpoint for the home card; the thread post is rendered markdown via a new `append_plain_session_message` helper that mirrors `append_expert_run_message` for the latest non-expert session. - Scheduling mirrors the dream-system pattern: per-user APScheduler cron (`0 9 * * *` in the user's timezone), Redis-deduped registration with timezone-drift detection, registered fire-and-forget from `stream_chat_post` and re-registered on timezone change. The `hire-experts` LaunchDarkly flag (same key the frontend evaluates — no separate briefing flag) gates generation; briefing is skipped when there is nothing to say (no runs, no pending decisions), with a plain-agent-activity fallback for users with zero experts. - Review enrichment resolves attribution in four batched, userId-scoped queries (executions→expert, graph→library agent, copilot session→expert, node execs for node ids), three of which run concurrently; fields are additive/optional so all existing consumers are untouched. - The home mounts *inside* `EmptySession` behind `Flag.HIRE_EXPERTS` — it takes the pulse chips' slot and falls back to them. `EmptySession` is modified for that one conditional mount (it owns onboarding and the composer recipient picker, so it can't be swapped out wholesale); everything it rendered before is untouched with the flag off. ### Changes 🏗️ **Backend** - Prisma migrations: new `UserBriefing` model (`briefingDate @db.Date`, `content Json`, unique `[userId, briefingDate]` — the only index; every read is by user + date) + nullable `deliveredAt` delivery marker - `backend/data/briefing.py` — briefing records data layer (idempotent create, latest-by-date; `get_latest_briefings` returns the newest few so the route can fall back past an unreadable row) - `backend/copilot/briefing/` — content models, pure `compose_briefing` + `render_briefing_markdown` (markdown metacharacters in agent/expert-supplied text are escaped before interpolation), `generate_and_deliver_briefing` orchestration (its four reads run under one `asyncio.gather`), per-user cron registration helper - `backend/copilot/db.py` — `append_plain_session_message` (dedup by message id, session lock, creates a plain session if none) - `backend/executor/scheduler.py` — `add_morning_briefing_schedule` (@expose + client wiring) and job body - `backend/api/features/briefings/` — `GET /api/briefings/latest` (router-level auth gate, typed `BriefingResponse`, skips past invalid stored content to the newest readable briefing) - `backend/data/human_review.py` + review model — six optional attribution fields, batched enrichment, N+1 fix - `backend/data/db_manager.py` — RPC exposure for the new functions (incl. previously missing `get_pending_reviews_for_user`) - `util/feature_flag.py` — backend `HIRE_EXPERTS` flag (mirror of the frontend key); chat routes + `update_user_timezone` registration call sites **Frontend** - No new frontend flag — the home rides the existing `HIRE_EXPERTS` flag - `src/hooks/useNeedsAttention.ts` + `useProcessReviews.ts` — shared query + mutation wrapper with dual-key invalidation - `src/components/organisms/NeedsAttentionList/` — one-tap approve/decline rows with expert avatars, deep links (built by the shared `src/lib/review-links.ts`), per-row pending state, and a toast on both success and failure - `PendingReviewsList` moved onto `useProcessReviews` so thread/builder/library approvals update the home count immediately - `copilot/components/CopilotHome/` — `BriefingCard` (runs + findings; decisions are left to the needs-attention list below it, and stay in the thread markdown), `TeamStrip`, shared `ExpertAvatar` molecule, assembled home behind the flag, mounted from `EmptySession` in the pulse-chips slot - `openapi.json` regenerated (briefings endpoint + review model fields) No `.env.default` or `docker-compose.yml` changes. ### Checklist 📋 #### For code changes: - [x] I have clearly listed my changes in the PR description - [x] I have made a test plan - [x] I have tested my changes according to the test plan: - [x] Backend: new pytest suites for briefing data layer, plain-session posts, compose/render (12 cases), scheduling registration (14 cases), briefings routes (5 cases incl. auth + corrupt content), review enrichment (real-DB, incl. copilot-session flavor) — all green alongside pre-existing human-review/user/chat suites (174+ tests in touched areas) - [x] Frontend: Vitest+RTL+MSW component tests for NeedsAttentionList (4), CopilotHome assembly (7: greeting, strip fallback, briefing card sections/links, team strip statuses incl. paused + needs-setup grammar, needs-attention mount/absence); full copilot suite green (119 files / 1503 tests); `pnpm types` clean - [x] Flag-off regression: existing PulseChips/CopilotPage/expert-threads tests pass unchanged, plus `CopilotHome/__tests__/flag-off.test.tsx` asserting the briefing home neither renders nor fetches when `hire-experts` is off - [ ] E2E manual pass with the full stack (briefing cron firing at 9am local against a running backend) — not run in this worktree #### For configuration changes: - [x] `.env.default` is updated or already compatible with my changes - [x] `docker-compose.yml` is updated or already compatible with my changes - [x] I have included a list of my configuration changes in the PR description (under **Changes**) --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: abhi1992002 <abhimanyu1992002@gmail.com> Co-authored-by: Abhimanyu Yadav <122007096+Abhi1992002@users.noreply.github.com>
…ignificant-Gravitas#13804) ### Why / What / How The brain-dump greeting could greet a user who was already mid-conversation (`greetingSeen` only covers first sends that went through the copilot home while the flag was on), the LLM promised integrations we cannot actually run, and the "still generating" state was faked by a second hero component that had to render the heading row identically to the intro card. This PR retires the greeting for anyone with an existing chat session, feeds the live provider registry into both LLM jobs, and replaces the hero stand-in with a centered orb that flies into the intro card heading under a shared framer `layoutId`. ### Changes 🏗️ - **Greeting eligibility**: `get_intro_card` now checks `get_user_session_count` — one session retires the greeting (and records `greetingSeen` so the count is paid once). A failure there answers "no" so a genuinely new user keeps their greeting. - **`greeting_pending` flag**: the pending state is stated outright instead of inferred from an empty Path A greeting, so the client can tell "still coming" from "there isn't one"; polling and the loader gate read it directly. - **Shared provider registry** (`onboarding_dump/providers.py`): extracted from `recommend.py` and now also prepended to the greeting prompt, so the automations it proposes are ones we can run. New `intro_test.py` covers prompt assembly, including an empty registry. - **Latency**: the greeting now runs concurrently with understanding extraction (`asyncio.gather`), recommendations moved to `claude-haiku-4-5`, and the onboarding loading screen's give-up ceiling dropped 60s → 15s. - **Greeting loader**: new `GreetingLoader` renders a pulsing orb alone, centered; `EmptyHero` is back to being just the regular hero. `OnboardingIntroCard` reveals its heading and shares `GREETING_ORB_LAYOUT_ID` with the loader so the orb travels rather than being swapped. - **Connect-tools fallback**: when the recommender answers with nothing, the panel shows six popular providers under "Popular places to start" instead of an empty section — only once the job has actually resolved, so real picks are never swapped out from under the user. - Regenerated `openapi.json` for `greeting_pending`. ### Checklist 📋 #### For code changes: - [x] I have clearly listed my changes in the PR description - [x] I have made a test plan - [ ] I have tested my changes according to the test plan: - [ ] Fresh account through onboarding brain dump → loading screen advances, copilot shows the centered pulsing orb, then the orb flies into the greeting heading when it lands - [ ] Greeting names only real integrations (cross-check against `/providers`) - [ ] Reload copilot after sending one message → no greeting reappears - [ ] Existing account with prior sessions → no greeting, regular hero - [ ] Connect-tools panel with a thin/skipped transcript → "Popular places to start" with six providers - [ ] `poetry run pytest backend/api/features/onboarding_dump` and `pnpm test:unit` for the touched suites
…ce, Build (Significant-Gravitas#13805) ### Why / What / How **Why:** The Agents, Marketplace and Build tabs give a new user no orientation — you land on a fleet view, a long storefront, or an empty canvas with no idea what the tab is for or what to do first. **What:** Each of those three tabs now shows a single first-visit intro card: an icon, a short title, one sentence, one way forward and one way out. **How:** A shared `TabIntroCard` shell plus a thin per-tab wrapper, gated behind the existing `onboarding-brain-dump` LaunchDarkly flag (off by default). Dismissal is recorded as a per-tab onboarding step so the backend record is the source of truth across devices, with a user-scoped `localStorage` cache covering the window before that write lands. ### Changes 🏗️ - **New shared component** `(platform)/components/TabIntroCard` — card shell, the `useTabIntroCard` first-visit gate, and `helpers.ts` holding the step map, the CTA union and the `localStorage` cache. The card dismisses on "Got it", Escape, and a backdrop click. It moves focus into itself on open, contains `Tab`/`Shift+Tab` while open, and restores focus to the opener on close. - **Three per-tab wrappers**: `AgentsTabIntro` (Library), `MarketplaceTabIntro` (CTA scrolls the featured carousel into view, falling back to the full listing when nothing is featured), `BuildTabIntro` (AutoPilot as the primary route, the existing builder tutorial as a quiet alternative). - **Build vetoes the card** when deep-linked into a saved graph (`?flowID=`) — the intro is about starting something and its tutorial clears the canvas. Vetoing leaves the step unrecorded, so a later blank `/build` still gets the introduction. - **The gate is derived, not latched.** `isOpen` waits on the flag, the resolved user id, the onboarding record and the local cache, all evaluated during render. Nothing is copied into state, so the card cannot flash open for a returning user in the frame before their id resolves. Dismissal is tracked per user id, so a second account signing in behind the same mounted hook still gets its own intro. The cache read sits last in the expression, so a user with the flag off never touches `localStorage`. - **Backend**: three new `OnboardingStep` values (`AGENTS_TAB_INTRO`, `MARKETPLACE_TAB_INTRO`, `BUILD_TAB_INTRO`) added to the enum and to `FrontendOnboardingStep` so `POST /onboarding/step` accepts them instead of 422-ing. They are unrewarded, and there is a test asserting the credit model is never reached for them. - **Flag**: the tab intros ride the existing `Flag.ONBOARDING_BRAIN_DUMP` rather than one of their own, so they ship and roll back with the rest of the new onboarding. No new flag key. - **Analytics**: `tab_intro_shown` / `tab_intro_cta_clicked` / `tab_intro_dismissed`, each carrying its tab, so one funnel shows both which tabs get discovered and whether the CTA earns its place. The tab and CTA properties are typed unions, not strings. - Scroll targets for the Marketplace CTA (`FEATURED_SECTION_ID`, `AGENTS_SECTION_ID`) live in `MarketplaceTabIntro/helpers.ts` so `FeaturedSection` and `MainMarketplacePage` can carry the ids without the intro importing its own parent page. ### Checklist 📋 #### For code changes: - [x] I have clearly listed my changes in the PR description - [x] I have made a test plan - [x] I have tested my changes according to the test plan: - [x] `TabIntroCard` renders its copy, CTA and "Got it"; renders nothing while closed - [x] Dismisses on "Got it", on Escape, and on a backdrop click — but *not* on a click inside the card that drifts out (text selection) - [x] The CTA fires its own action without also reporting a plain dismissal - [x] Focus lands inside the card on open, `Tab`/`Shift+Tab` wrap inside it rather than reaching the page behind, and focus returns to the opener on close - [x] Gate stays closed while the flag is off, before LaunchDarkly answers, before auth resolves the user, and before the onboarding record loads - [x] Gate stays closed when the step was already recorded on another device, and only consults its own tab's step - [x] A returning user whose id arrives after mount never opens the card (and never reports it shown); a first-time user does, exactly once - [x] A second account signing in without a remount gets its own intro - [x] Dismissal records the step, caches locally, never reopens — and still closes when `localStorage` throws - [x] Agents CTA steps aside; Marketplace CTA scrolls the carousel, falls back to the listing when nothing is featured, and still closes when neither exists; Build CTA routes to `/copilot` and the alternative launches the builder tutorial - [x] Build stays out of the way of `?flowID=` and leaves the step unrecorded for later - [x] A blocked analytics host does not break a first visit - [x] Backend: `POST /onboarding/step` accepts all three new steps, still 422s on rewarded backend-only steps, and grants no credits for the tab intros (parametrised) - [x] No regressions in the touched areas — full library, marketplace, build, shared-component and services suites green (832 tests) - [ ] Manual pass in the browser with the flag forced on, across all three tabs #### For configuration changes: - [ ] `.env.default` is updated or already compatible with my changes - [ ] `docker-compose.yml` is updated or already compatible with my changes - [ ] I have included a list of my configuration changes in the PR description (under **Changes**) No configuration changes. The feature is read through the existing `onboarding-brain-dump` LaunchDarkly wiring and defaults to `false`, so an environment with no flag key behaves exactly as before.
…mmary (Significant-Gravitas#13869) Part of [SECRT-2523](https://linear.app/autogpt/issue/SECRT-2523) — Home surface v1 (Home & Briefing). ### Why / What / How **Why:** `get_user_cost_summary()` is the shared aggregation behind the usage/cost views, but it only returns money — total cents, billable runs, failed spend. Any consumer that wants an *activity* chart (runs over time, split by outcome) or an "avg run duration" stat has to issue a second query over the same `AgentGraphExecution` rows. The data is already in the scanned rows; we just weren't projecting it. **What:** Adds per-status run counts and duration aggregates to the summary — both at the window level (`UserExecutionCostSummary`) and per day (`UserDailyCost`) — so a stacked activity chart can be built from the existing single call. **How:** Extends the two existing aggregate queries (totals + daily) with `COUNT(*) FILTER (...)` clauses and a `SUM((stats->>'walltime')::numeric)`. No new query, no new round trip, same `WHERE` clause, so the `@@index([userId, isDeleted, createdAt])` hit is unchanged. Rows map into the models with the same `int()` / `float()` + `or 0` guards used for the existing columns. Purely additive: every new field has a default, so existing consumers and the existing fields' behavior are untouched. Two things worth calling out, both from review: - **Duration reads `walltime`, not `duration`.** `update_graph_execution_stats()` persists `GraphExecutionStats.model_dump()`, whose field is `walltime` — nothing writes a `duration` key. The first cut of this PR aggregated `stats->>'duration'`, which would have been `0` for every real run, and the pre-existing `top_runs.duration_seconds` projection had the same bug. Both now read `walltime`, and the test fixture writes the real key so the assertions actually bite. - **`total_duration_seconds` ships with its own denominator.** `run_count` includes QUEUED/RUNNING rows and rows with no stats, which `SUM` skips — dividing by it biases "avg run duration" downward. `duration_run_count` counts exactly the rows the sum draws from (same idea as `walltime_count` in `backend/data/generate_data.py`). ### Changes 🏗️ - `UserDailyCost`: `success_count`, `failed_count`, `review_count` (default `0`). - `UserExecutionCostSummary`: `success_run_count`, `failed_run_count`, `review_run_count`, `total_duration_seconds`, `duration_run_count` (default `0`). - Totals SQL: three `COUNT(*) FILTER` clauses (`COMPLETED`; `FAILED`/`TERMINATED`; `REVIEW`), `COALESCE(SUM((stats->>'walltime')::numeric), 0)::float`, and `COUNT(*) FILTER (WHERE (stats->>'walltime') IS NOT NULL)`. - Daily SQL: the same three per-status `FILTER` counts, per bucket. - Top-runs SQL: `duration_seconds` now projects `stats->>'walltime'` (pre-existing bug fix). - The four aggregate queries moved into `_fetch_totals` / `_fetch_by_agent` / `_fetch_top_runs` / `_fetch_daily`, each owning its SQL and row→model mapping, with the shared predicate hoisted to `_BASE_WHERE`. `get_user_cost_summary` is window validation + `asyncio.gather` + assembly. - `openapi.json` re-exported — the new response fields are optional with defaults, so no frontend change is required. - Tests extended in `execution_cost_summary_test.py`: the fixture writes `walltime` (and can omit it entirely), a `REVIEW` run and a stats-less `RUNNING` run added to the mixed-status fixture (counts, duration sum, and duration denominator asserted), `FAILED` + `REVIEW` runs added to the daily-bucket fixture (per-day split asserted), and the empty-window test asserts the new fields are zeroed. ### Checklist 📋 #### For code changes: - [x] I have clearly listed my changes in the PR description - [x] I have made a test plan - [x] I have tested my changes according to the test plan: - [x] `poetry run pytest backend/data/execution_cost_summary_test.py` — 5 passed against real Postgres (mixed statuses produce the right per-status counts, duration sum, and duration denominator; the RUNNING run stays out of both; empty window returns zeros; existing cost/daily/dry-run assertions still hold) - [x] `poetry run pytest backend/api/features/v1_test.py -k cost` — 5 passed, the `/api/v1` cost-summary route is unaffected - [x] `poetry run format && poetry run lint` clean (ruff, isort, black, pyright) - [x] `poetry run export-api-schema` + prettier produces a 5-line diff (the new field only)
…ignificant-Gravitas#12495) Requested by @ab20145 Radix UI's `<Select.Item>` throws `Error: A <Select.Item /> must have a value prop that is not an empty string` when any enum option has `value=""`. This crashes the `/build` page when a block's schema includes an empty-string enum option. ## Problem Sentry issue: **BUILDER-5CS** (3 occurrences since Dec 2025) `SelectWidget.tsx` (the JSON Schema form widget for enum/select fields) had two paths producing empty strings: 1. `value={value ?? ""}` — passes `""` as the Select value when the form value is null/undefined 2. `option.value` mapping — passes backend enum options through without filtering empty strings ## Fix - Use `value || undefined` instead of `value ?? ""` — shows placeholder for unset values instead of crashing - Filter out empty-string enum options before rendering `SelectItem`s Single file, minimal change. No behavior change for valid (non-empty) options. Resolves SECRT-2157 --- Co-authored-by: Abhimanyu Yadav (@Abhi1992002) <122007096+Abhi1992002@users.noreply.github.com> --------- Co-authored-by: Nicholas Tindle <nicholas.tindle@agpt.co> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: majdyz <zamil.majdy@agpt.co>
…as#13758) ### Why / What / How **Why** AutoGPT Platform should be runnable as a complete local appliance with one `docker run`, including FalkorDB-backed memory, without changing the images or deployment behavior used by hosted infrastructure. **What** Adds a dedicated single-container appliance containing the frontend, backend services, PostgreSQL, a three-node Valkey cluster, RabbitMQ, FalkorDB, nginx, Supervisor, and Tini. The appliance persists application and service state under `/data`, generates installation secrets on first boot, and exposes only nginx on container port `3000`. **How** Buildx Bake builds the existing backend `server` target, then uses it as the base for `autogpt_platform/single-container/Dockerfile`. Internal services bind to loopback and Supervisor manages their lifecycle. Health checks and a fail-stop watchdog allow Docker restart policies to recover the appliance when a required service cannot recover internally. The frontend server runs as its own Unix user with a clean environment and a passwordless local PostgreSQL role limited to the Better Auth tables and required `User` columns. It does not receive PostgreSQL superuser credentials. Internal AppService traffic stays loopback-only inside the appliance trust boundary. The effective PR diff adds only `autogpt_platform/single-container/` plus its CI workflow and smoke script. Existing backend source, frontend source, Dockerfiles, the root `.dockerignore`, and Compose files do not differ from `dev`. ### Changes 🏗️ - Adds the dedicated appliance Dockerfile, runtime scripts, nginx configuration, Supervisor configuration, and Bake targets. - Enables FalkorDB-backed Graphiti memory by default. - Persists generated application secrets, databases, queues, caches, and memory data under `/data`. - Runs startup migrations without downloading Prisma tooling at runtime. - Adds health checks, required-service recovery, clean shutdown, and whole-container fail-stop handling. - Defaults new-account signup to disabled until explicitly enabled by the operator. - Isolates the frontend and nginx with dedicated operating-system users and a least-privilege frontend database role. - Pins internal topology to loopback so persisted backend configuration cannot expose internal listeners. - Adds native `linux/amd64` and `linux/arm64` build, runtime-smoke, vulnerability-scan, and secret-scan CI. - Keeps the validation workflow non-publishing. ### Test plan - [x] Run all 59 appliance Python tests. - [x] Run ShellCheck for the appliance and smoke scripts. - [x] Run `bash -n` and `git diff --check`. - [x] Run the repository pre-commit suite under Node 24, including backend/frontend type checks and API generation checks. - [x] Build and smoke-test the exact current-head image on `linux/amd64` and `linux/arm64`. - [x] Verify literal `docker run`, `/data` reuse, generated-secret persistence, FalkorDB graph persistence, frontend database isolation, and service recovery on the exact current head. - [x] Pass current-head Trivy vulnerability and embedded-secret scans. ### Checklist 📋 #### For code changes: - [x] I have clearly listed my changes in the PR description. - [x] I have made a test plan. - [x] I have tested my changes according to the complete test plan above. #### For configuration changes: - [x] `.env.default` is already compatible with these changes. - [x] `docker-compose.yml` is already compatible with these changes. - [x] I have included a list of configuration changes under **Changes**. ### Stack navigation This appliance is based directly on `dev`. Publication and end-user documentation are handled separately. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Large new packaging surface with auth, secrets, and DB privilege boundaries; mitigated by extensive smoke tests and isolation checks, but it is still a new distribution path operators may run in production. > > **Overview** > Introduces an **experimental single-container AutoGPT Platform** you can run with one `docker run`, bundling the frontend, backend services, PostgreSQL, Valkey, RabbitMQ, FalkorDB (Graphiti memory on by default), nginx, and Supervisor. State and generated secrets live on a **`/data` volume**; only **port 3000** is exposed. > > The image is built via **Docker Bake** from the existing backend `server` target plus a new `autogpt_platform/single-container/` stack (entrypoint/bootstrap, runtime config, least-privilege **frontend DB role**, loopback-pinned internal ports, health checks, watchdog). Operators get **`.env.example`** for public URL, signup policy, and LLM keys. > > **CI** adds `platform-single-container-docker.yml`: unit tests for appliance helpers, per-arch image build, a long **end-to-end smoke script** (health, auth, hostile `backend.json`, isolation, FalkorDB/nginx recovery), and **Trivy** critical vuln + secret scans. Hosted multi-container deployments are unchanged. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 2d36895. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Claude <noreply@anthropic.com>
…ignificant-Gravitas#13742) ### Why / What / How The new sidebar layout (`isNewLayoutActive`) had already landed for copilot, library and settings, but several pages still assumed the old top-Navbar shell: `/admin` rendered the legacy sidebar, `/build` lost half its canvas to the app sidebar, copilot stacked floating mobile buttons on top of the inset header, and the marketplace/library pages forced viewport-width or fixed-width containers that overflowed next to the sidebar. This PR cleans up those remaining pages, plus two sidebar/wallet polish items that were queued behind the same layout work. The approach keeps the classic layout untouched everywhere: `/admin` switches between `AdminClassicShell` (legacy sidebar, verbatim move) and a new `AdminNewShell` that mirrors the `/settings` shell, and every other change is either gated on `isNewLayoutActive`/`showNewLayout` or a width fix that is correct in both shells. The builder's collapsed sidebar is seeded via `SidebarProvider defaultOpen` for hard loads and restored on exit by a small `BuilderSidebarAutoClose` effect for client-side navigation. ### Changes 🏗️ - **Admin**: new-layout shell with its own `AdminSidebar` (Back link, grouped nav) and `AdminMobileNav`; legacy sidebar extracted to `AdminClassicShell` so it can be deleted when the flag ships. `PlatformChrome` now renders the bare shell for `/admin` like it already does for `/settings`. - **Builder**: `/build` opens with the app sidebar collapsed and restores the previous open state when you navigate away. - **Copilot**: floating `MobileHeader` is dropped under the new layout; the workspace-files toggle moves into the inset header as `WorkspaceFilesTrigger`, and banners get top padding so they clear the floating controls. - **Library**: agent detail becomes a two-column, self-scrolling `lg` grid (runs list on the right) instead of assuming a top-navbar page. - **Marketplace**: search/loading/error containers use `w-full max-w-*` + responsive padding instead of `w-screen` / `lg:min-w-[1440px]`. - **Profile**: legacy `/profile/*` pages redirect to their `/settings/*` equivalents under the new layout. - **Recent chats**: pinned section + pin/unpin menu action behind the existing `chat-pinning` flag, and a per-link `useLinkStatus` spinner. - **Wallet**: "Earn credits" becomes a per-group accordion (`getEarnRows` → `getEarnGroups`) — completed groups collapse, in-progress groups stay open and show their remaining reward total. - `SidebarTrigger` now forwards `className`; `SidebarInset` gets `min-w-0` so wide content can't push the inset past the viewport. ### Checklist 📋 #### For code changes: - [x] I have clearly listed my changes in the PR description - [x] I have made a test plan - [ ] I have tested my changes according to the test plan: - [ ] With the new layout enabled: `/admin/*` shows the new sidebar shell (desktop) and mobile nav (narrow), Back link returns to the app, and every nav entry routes correctly - [ ] With the new layout disabled: `/admin/*` still renders the legacy sidebar under the top Navbar - [ ] Navigate into `/build` — sidebar collapses; re-open it manually and it stays open; navigate away and the previous state is restored; hard-reload on `/build` starts collapsed - [ ] `/copilot` on mobile/tablet: no duplicate floating buttons, sidebar toggle + workspace-files toggle both reachable in the header, banners are not clipped - [ ] `/library/agents/[id]` at `lg`+: detail panel and runs list scroll independently with no page-level overflow; still fine at mobile width - [ ] `/marketplace` and `/marketplace/search?...` have no horizontal scrollbar next to the open sidebar - [ ] Visit each legacy `/profile/*` URL with the new layout on — redirects to the matching `/settings/*` page with no flash of the old shell - [ ] With `chat-pinning` on: pin a chat → it moves to the Pinned section; unpin → it returns to its date/expert group; clicking a chat shows the inline spinner - [ ] Wallet popover: completed earn groups render collapsed with "Done", in-progress groups render expanded with the remaining total, headers toggle - [ ] `pnpm test:unit` passes (`RecentChatItem`, `WalletCompactPanel` suites updated) #### For configuration changes: - [ ] `.env.default` is updated or already compatible with my changes - [ ] `docker-compose.yml` is updated or already compatible with my changes - [ ] I have included a list of my configuration changes in the PR description (under **Changes**) No configuration changes — frontend only. The `chat-pinning` flag and its backend endpoint already exist on `dev`.
…tas#13904) ### Why / What / How **Why.** The briefing-first home page needs pending reviews, the last 24h of run outcomes, in-flight and scheduled work, per-expert status, and the 7-day activity rollup. Today that is six-plus round trips the frontend has to fan out and stitch together itself, each with its own loading state, and the cross-cutting rules (what counts as "needs attention", how items are ordered, when an expert is `working` vs `paused`) end up duplicated in the client. There is no endpoint that answers "what should I look at right now?". **What.** One new read-only endpoint, `GET /api/home` (`get_home_dashboard`), returning a single `HomeDashboardResponse`. Additive only — no existing endpoint, model, or query is touched. **How.** A new `backend/api/features/home/` package with a thin route, a service that loads every source concurrently, and pure composers that turn that raw data into the payload: - `service.py` fans out experts, week-window executions, pending reviews, the cost summary, schedules, credits and the user's timezone as concurrent tasks, then resolves library agent refs for every graph id seen in executions or reviews. Schedules and credits are best-effort: a scheduler or billing hiccup degrades those sections instead of failing the whole page. - `compose.py` + `attention.py` / `activity.py` / `agents.py` are pure functions over that data, so the ranking and status rules are unit-testable without a database. - Attention items are ordered high priority first, then oldest first. A review flips to high after 24h unacknowledged. The full `PendingHumanReviewModel` rides along on each approval item so the frontend can approve in one tap without a second fetch. - Expert status uses a fixed precedence: `working` > `paused` > `needs_setup` > `failed` > `ready`. ### Changes 🏗️ - **New** `backend/api/features/home/` — `routes`, `models`, `service`, `compose`, `attention`, `activity`, `agents`, `helpers` (each file well under the 300-line guideline). - **New** `GET /api/home`, org-aware: resolves `team_ids` via `get_user_team_ids` only when the request carries an `org_id`. - Registered the router in `rest_api.py` and spliced the new path plus the twelve `Home*` schemas into `openapi.json` (370 insertions, 0 deletions). - `HomeWeekSummary` exposes `timed_run_count` alongside `total_runtime_seconds`, so an average run duration divides by the runs that actually contributed walltime rather than by `run_count` — which also counts queued, running, and stats-less rows and would bias the average down. - Fixed a singular/plural bug while writing the tests: the setup item rendered "1 scheduled workflow **need** setup". ### Checklist 📋 #### For code changes: - [x] I have clearly listed my changes in the PR description - [x] I have made a test plan - [x] I have tested my changes according to the test plan: - [x] `poetry run pytest backend/api/features/home/` — 15 tests pass - [x] `poetry run pytest backend/data/execution_cost_summary_test.py` — 5 tests pass (base branch, unaffected) - [x] `poetry run format` and `poetry run lint` clean - [x] Route returns a single payload for a personal user and for an org user; team lookup is skipped without an `org_id` - [x] Attention ordering, the 24h priority flip, and the credits item only appearing on a zero balance *with* schedules - [x] Briefing 24h window filtering, failures listed before successes, unlisted successes counted as `routine_count` - [x] Agent status precedence and the team summary rollup - [x] Week summary maps status counts, walltime and credits, including the daily breakdown Test coverage is on the pure composers and the route boundary. `service.py`'s fan-out is thin glue over already-tested data functions; the degradation paths (`_get_schedules`, `_get_credits`) are try/except returning `[]` / `None`. #### For configuration changes: - [x] `.env.default` is updated or already compatible with my changes - [x] `docker-compose.yml` is updated or already compatible with my changes - [x] I have included a list of my configuration changes in the PR description (under **Changes**) No configuration changes.
…avitas#14002) ## Problem CLA-assistant re-verifies the `license/cla` status **per commit SHA**, not per person/PR. For an already-signed contributor, that status can transiently flip through pending/failure states before settling back to success on every push. `cla-label-sync.yml` (introduced in Significant-Gravitas#12002) reacts to every `status` event, so it was flipping `cla: signed` off and `cla: pending` on for these transient states, then flipping back moments later — visible as rapid label churn on PRs. Reported by @Bentlybro on Significant-Gravitas#13999. ## Fix Add a `DEMOTE_DEBOUNCE_SECONDS` (default 75s) debounce before demoting a PR that's currently labeled `cla: signed`: - If the latest CLA check for a previously-signed PR doesn't pass, wait `DEMOTE_DEBOUNCE_SECONDS` and re-check once before actually removing `cla: signed` / adding `cla: pending`. - If the re-check now passes, skip the demotion — this filters the same-commit transient noise. - If it's still failing after the delay, demote as before. This still correctly handles a genuinely new unsigned commit (e.g. pushed under a different git identity), since that state persists well past the debounce window rather than resolving. Promotions (`cla: pending` → `cla: signed`) are unaffected and remain instant. ## Why this approach Considered "once signed, never auto-demote except via the scheduled sweep," but that would mask a new commit pushed under a different, unsigned identity for up to a day. The debounce preserves correctness for that case while removing the flip-flop noise for the common transient case. cc @ntindle @0ubbe — flagging since you were involved in Significant-Gravitas#12002. --------- Co-authored-by: AutoGPT <257977679+Otto-AGPT@users.noreply.github.com>
…3994) ### Why / What / How The single-container distribution is built and validated on `dev`, but it needs a production Docker Hub release path. This publishes only the exact multi-architecture image content that passed the complete appliance smoke and security scans, and gives self-hosters a usable first-run account flow. Manual publication from `dev` uses the reviewer-gated `dockerhub-publish` environment and creates only an immutable `sha-${GITHUB_SHA}` tag. A stable GitHub release named `autogpt-platform-beta-vX.Y.Z` automatically uses the tag-restricted `dockerhub-release` environment and creates that SHA tag, immutable `vX.Y.Z`, and `latest` without a separate deployment approval. Prereleases, malformed platform tags, other releases, and non-`dev` dispatches cannot reach the Docker credentials or push jobs. Native AMD64 and ARM64 jobs push by digest, pull those exact registry digests back, rerun the full smoke and Trivy scans, and pass only the verified digests to manifest assembly. Existing tags are reusable only when they contain the exact tested digests and source revision. Registry reads are retried with bounded validation, release tags are immutable, and `latest` cannot move backward or replace the same version with different content. The successful stable-release manifest job also syncs the checked-in single-container README to the Docker Hub Overview. The full workflow must be present on the default branch before the next GitHub release is published so GitHub can deliver the release event. Fresh single-container installs start with account registration open, matching the frontend default and making literal `docker run` usable. Startup and documentation warn that anyone who can reach the app can register until the operator promotes the intended administrator and recreates the container with `AUTH_ALLOW_NEW_ACCOUNTS=false`. Canonical Docker commands bind only to localhost. ### Changes 🏗️ - Publish native `linux/amd64` and `linux/arm64` images through separate manual and stable-release environments. - Publish immutable `sha-*` tags from approved manual `dev` dispatches without changing `latest`. - Map stable `autogpt-platform-beta-vX.Y.Z` GitHub releases automatically to immutable `vX.Y.Z` plus monotonic `latest`. - Fail closed before credential use for unsupported events, refs, prereleases, and malformed tags. - Verify exact smoke-tested child digests, OCI source revisions, manifest platforms, repository visibility, tag immutability, and release ordering. - Retry registry digest resolution after writes and reject missing or malformed digest responses. - Keep publication policy and fixture coverage in a standalone ShellCheck-tested helper. - Sync a checked-in quick start, tag guide, resource guidance, and FalkorDB memory description to Docker Hub. - Open first-run signup by default, with explicit warnings and a tested `false` override. ### Checklist 📋 #### For code changes: - [x] I have clearly listed my changes in the PR description - [x] I have made a test plan - [x] I have tested my changes according to the test plan: - [x] `actionlint .github/workflows/platform-single-container-docker.yml` - [x] ShellCheck and `bash -n` for both changed publication/smoke scripts - [x] Publication fixtures cover authorization, malformed tags, Buildx metadata forms, attestations, repository visibility, bounded registry retries, empty optional paths, release annotations, semantic-version ordering, and rollback rejection - [x] 59/59 single-container appliance unit tests, including default-open and explicit-closed signup behavior - [x] Bake target resolves the current dedicated single-container Dockerfile and linked backend target - [x] Protected canary run [31454312554](https://github.com/Significant-Gravitas/AutoGPT/actions/runs/31454312554) built, pushed, pulled, fully smoked, scanned, and assembled both architectures without changing `latest` - [x] Final canary run [31519892189](https://github.com/Significant-Gravitas/AutoGPT/actions/runs/31519892189) passed both native pre-push and exact-registry-digest smoke/scan lanes, published the correct attested manifest, and exposed the post-write verification race now covered by regression tests and a successful live-manifest replay - [x] Public canary manifest was pulled and remained healthy locally on ARM64 and on Unraid AMD64 using persistent named volumes - [x] Disposable-account parity flow: signup/sign-in, real two-block Builder execution, actionable missing-provider error from the normal stream API, signup closure, existing-account sign-in, and rejected second signup - [x] Current-head PR CI builds, smokes, and scans natively on AMD64 and ARM64 - [ ] Full workflow replaces the inert default-branch registration frame before the next stable GitHub release #### For configuration changes: - [x] `.env.example` documents the open-first-run account policy and how to close it - [x] No Compose files are changed - [x] No shared backend service source is changed - [x] Configuration changes are listed under **Changes**
…nificant-Gravitas#13761) > [!IMPORTANT] > Preview release for user-owned ChatGPT/Codex usage. `LOCAL` deployments allow it for every authenticated user. `CLOUD` deployments allow it for AutoGPT `MAX`, `BUSINESS`, and `ENTERPRISE` users. There is no Codex feature flag or staff-only allowlist, so deploying this branch to hosted production exposes discovery, sign-in, and execution to every user in those tiers. The connected ChatGPT account's own plan, workspace policy, allowance, and credits still determine what Codex can execute. ### Why / What / How This PR adds a user-funded ChatGPT connection as a distinct Codex subscription transport, without requiring an OpenAI API key. | Supported surface | Execution path | |---|---| | AutoPilot chat | Existing Claude Agent SDK and bundled Claude Code CLI, with a loopback Anthropic Messages gateway translating text and tool rounds to Codex App Server | | AutoPilot graph block | The same AutoPilot path when a ChatGPT credential is explicitly selected | | Code Generation block | Direct Codex App Server invocation when `Codex App Server` is selected | Existing platform-funded, self-hosted, and OpenAI API-key routes remain available and keep their existing behavior. The frontend presents **Sign in with ChatGPT** under the existing OpenAI integration card and uses OpenAI branding. The stored credential and backend auth provider remain `codex`, while the API-key method remains `openai`. Keeping those canonical identities separate prevents a ChatGPT subscription credential from being mistaken for an OpenAI API key. ### Changes 🏗️ #### Authentication and credentials - Add ChatGPT device-code sign-in, login status and cancellation, authenticated account and rate-limit read endpoints, and disconnect with provider logout where available. This PR does not add a frontend plan/rate-limit dashboard. - Store one active user-owned Codex connection per user in the existing encrypted `IntegrationCredential` payload as `OAuth2Credentials(provider="codex", refresh_strategy="provider_runtime")`. - Strictly validate ChatGPT auth bundles and reject direct API creation of Codex or provider-runtime OAuth credentials. - Let the Codex runtime own token refresh, then checkpoint rotated auth back into the encrypted credential while holding the existing distributed credential lease. - Materialize `auth.json` only inside an isolated temporary `CODEX_HOME`. RabbitMQ messages, Redis login state, chat sessions, and Claude CLI environments contain credential IDs or short-lived capabilities, not ChatGPT access, refresh, or ID tokens. - Starting another sign-in supersedes the previous AutoGPT attempt through Redis-backed state. Popup close, timeout, unmount, explicit abort, and blocked-popup paths request server cancellation exactly once; successful completion does not. This clears AutoGPT-owned stale state but cannot bypass OpenAI-side device-code throttling or cooldowns. - Recheck entitlement before persisting a completed login and recheck authoritative credential ownership, provider, type, and validity at execution boundaries. #### Runtime and execution - Pin `openai-codex==0.144.4` together with its bundled Codex runtime. - Add bounded App Server capacity, startup, control, invocation, turn, and tool timeouts; lease heartbeats; refresh checkpointing; redacted errors; and temporary-home cleanup on success, failure, timeout, cancellation, or shutdown. - Keep every Codex-backed AutoPilot mode flowing through the existing Claude Agent SDK and bundled Claude Code CLI. A request-scoped gateway bound to `127.0.0.1` translates Claude text and MCP tool rounds to Codex App Server `dynamicTools`. Claude Code retains the MCP tool loop, permission/security hooks, transcripts and resume, compaction, attachments, and sub-sessions. The CLI receives only a random per-turn capability. - Keep the Code Generation block separate from the Claude harness: subscription-backed Code Generation invokes Codex App Server directly and exposes no workspace or tool access. - Reuse one process-local Codex runtime actor per user and credential. Overlapping AutoPilot turns reaching the same Copilot executor process use separate Codex threads while sharing the actor and its exclusive credential lease; canceling one borrower does not stop its siblings. #### Routing and model behavior - Add an authenticated `/api/chat/transports` inventory and validate every requested route against what is currently usable for that user and deployment. - Keep **AutoGPT Platform** as the default on hosted deployments. Connecting ChatGPT adds it as another route. - Keep **Self-hosted chat** as the default when a local chat provider is configured. Connecting ChatGPT adds it as another route. - On a keyless self-host with one valid ChatGPT connection, automatically use that sole route and hide the selector. With no usable route, show setup guidance instead of inventing a platform route. - Show the route selector only when more than one usable route exists and refetch the inventory after pairing or disconnecting. - Keep the selected provider and credential ID immutable for the life of a session, including retries, queued turns, and resume. Entitlement, ownership, and credential validity are still checked at execution time. Downgrade, deletion, revocation, or malformed state makes the session fail visibly; it never reroutes to platform funding, another credential, or another user's account. - Prefer these AutoPilot routes when the connected account advertises the corresponding model and effort: | Mode | Preferred model | Effort | |---|---|---| | Fast / Balanced | GPT-5.6 Luna | low | | Fast / Advanced | GPT-5.6 Terra | medium | | Thinking / Balanced | GPT-5.6 Terra | high | | Thinking / Advanced | GPT-5.6 Sol | xhigh | If a preferred model or effort is unavailable, routing uses an allowed account-advertised default or available OpenAI model and a supported/default effort. For Code Generation, the model dropdown applies only to the OpenAI API transport. The subscription transport lets App Server choose its live account-compatible model while still applying the selected supported reasoning effort. #### Entitlement and cost behavior - Add the shared `require_entitlement(user_id, entitlement)` policy shell. - In `LOCAL` mode, allow `codex_subscription_transport` before any subscription database lookup. - In `CLOUD` mode, read the authenticated user's authoritative AutoGPT `User.subscriptionTier` through `DatabaseManager` on every check in this PR and allow `MAX`, `BUSINESS`, and `ENTERPRISE`. - Hide Codex from optional discovery when tier state cannot be resolved. Actual connect and execution requests fail with a retryable service-availability error rather than treating an outage as a valid route. - Keep pending-login cancellation and credential deletion available even after downgrade so users can clean up. - Do not separately resolve organization-seat entitlements in this PR; cloud access is based on the user's authoritative `subscriptionTier`. - Keep entitlement reads deliberately uncached here. Shared caching and tier-mutation invalidation are isolated in follow-up [Significant-Gravitas#14004](Significant-Gravitas#14004). - Record subscription-backed token usage with `billing_mode=user_subscription` and the execution path. Do not report public OpenAI API USD pricing as AutoGPT provider spend. - Skip platform model-paywall and platform-provider USD usage windows for Codex AutoPilot. Subscription-backed Code Generation does not apply the API-token wallet charge. Ordinary graph execution, turn concurrency, queueing, bridge compute, and infrastructure policies still apply, and usage consumes the connected ChatGPT account's allowance or credits. - Preserve existing OpenAI API-key billing behavior. ### Configuration / rollout No new secret, Codex rollout flag, or Codex-specific frontend build argument is required. - Cloud previews must still set `FRONTEND_BASE_URL`, build-time `NEXT_PUBLIC_FRONTEND_BASE_URL`, and runtime `BETTER_AUTH_URL` to the exact public origin. Otherwise the device-login popup can use the wrong host or lack the preview authentication cookie. - Outside the single-container appliance, `CODEX_TEMP_ROOT` is optional and otherwise falls back to the operating-system temporary directory. - Local Compose sets `CODEX_TEMP_ROOT=/run/autogpt-codex` and mounts a 128 MiB memory-backed temporary filesystem in the REST, graph-executor, and Copilot-executor containers. - The single-container appliance fixes `CODEX_TEMP_ROOT=/dev/shm/autogpt-codex`, creates it as `0700` for the unprivileged backend user, and verifies that REST, graph executor, and Copilot executor inherit it. Docker's `/dev/shm` keeps auth homes out of the writable layer; the documented quick-start sizes it to 2 GiB. - Narrow Git attributes keep the appliance shell scripts LF-normalized in Windows checkouts so a Windows source build uses the same executable bytes as Linux CI. - Capacity, process startup/control, checkpoint, invocation, Copilot-turn, tool, and login intervals have bounded defaults and optional `CODEX_*` overrides. - Deployments need outbound HTTPS access to OpenAI. Device-code login does not require an inbound OpenAI callback. ### Explicit exclusions and known limitations This PR does not route the following through a user's ChatGPT connection: - Orchestrator or shared LLM blocks; - image generation or embeddings; - `ClaudeCodeBlock` / E2B; - builder-panel-bound sessions; - background or system-owned LLM work. It also does not add credential pooling, silent fallback to a platform credential, multiple active Codex credentials per user, per-user Kubernetes resources, persistent Codex homes, or persistent volumes. Operational limits that remain: - Device-code authorization must be enabled for the ChatGPT account or workspace. A pending AutoGPT attempt is bounded by `CODEX_LOGIN_TIMEOUT_SECONDS`, 15 minutes by default. - Login actors are process-local. Redis shares status, supersession, and cancellation across REST replicas, but a restart of the owner process interrupts the active sign-in. - Runtime actors are process-local. A different Copilot executor process or replica cannot join the current owner and may receive the bounded, retryable `codex_credential_busy` error. Multi-replica owner routing or a dedicated bridge is not included. - Codex App Server and device-login actors currently run inside the ordinary REST, graph-executor, and Copilot-executor containers. A dedicated non-root, least-privilege bridge with memory-only homes, restricted egress, process limits, and credential-owner routing remains follow-up work. - Commercial/policy approval for hosted subscription passthrough and the Claude-harness compatibility route is a deployment decision outside this implementation; this PR does not claim that approval. ### Validation At review-fix parent `9cbe549d36dd3197b22871dbb7ae79d85c26c03f` against `dev@5c980c2`, the formatted tree passed the following focused validation: - Backend auth/runtime suite: 95 passed, with 3 Windows-specific skips. - Backend model routing, compatibility gateway, and SDK environment suite: 74 passed. - Backend platform-cost, metrics, and Code Generation suite: 94 passed. - Backend billing/cost-leak suite: 19 passed. - Authenticated integrations API suite: 25 passed. - Isolated Linux executor/gateway suites: 65 passed, including processor cleanup, lease ownership, Codex billing-gate, and compatibility-gateway behavior; some cases overlap the focused Windows suites above. - Frontend: 83 tests across five affected files, TypeScript `tsc --noEmit`, and targeted Prettier checks pass. - Backend isort, Black, Ruff, AST/import compilation checks, and Git diff hygiene pass for every changed Python file. Final head `aa4bcb445c450d2da1c0de2a4c1ea03b9c3b70e5` restores the framework-compatible optional AutoPilot credential annotation, regenerates the public provider-discovery OpenAPI contract, safely canonicalizes macOS system temp aliases, and includes the exact generated Code Generation documentation sync emitted by CI. On that final diff, the AutoPilot schema/import assertion passes; the temp-home suite passes 12 tests with 6 expected Windows symlink skips; Ruff, Black, Prettier, Git diff hygiene, and full TypeScript `tsc --noEmit` pass. The final test-only commit also updates the shutdown mock to patch the manager module's bound transport symbol; that exact regression passes locally. Current-head CI remains the authoritative broad verification below. The prior head `9eb8283db22d6ddf624f6c891c5b1579cdd07516` also completed a real-account, keyless local single-container smoke: four Claude Agent SDK turns used the Codex subscription route successfully, persisted 40 chat messages, recorded subscription token usage, and recorded zero platform model cost. The review-fix head changes authentication cleanup, model-routing structure, compatibility edge cases, cost reporting, and selector cleanup, so exact-head external-account smoke remains explicitly unchecked below. - [x] Focused backend tests cover auth-bundle validation, temporary-home isolation, refresh/checkpointing, login supersession and cancellation, entitlement enforcement, authoritative credential identity, transport inventory, immutable session routing, process-local runtime sharing, queue/executor behavior, costs, Code Generation, and AutoPilot. - [x] The bundled Claude Agent SDK/CLI conformance suite covers streaming text, tool call/result, cancellation, and resume against the compatibility gateway. - [x] Frontend tests cover OpenAI/ChatGPT presentation, credential selection, popup cancellation, transport inventory, selector defaults and visibility, setup guidance, and session creation. - [x] Current-head backend, frontend, full-stack, dual-architecture single-container, CodeQL, Classic, overlap, documentation, and PR-status automation passed on `aa4bcb445c450d2da1c0de2a4c1ea03b9c3b70e5`. - [ ] Fresh deployed-preview device-code sign-in at the current head with a real ChatGPT account. - [ ] Fresh deployed-preview Code Generation and AutoPilot smoke at the current head with a real ChatGPT account. The unchecked real-account checks are intentionally not represented as automated coverage; they consume an external account's allowance and should be completed on the exact deployment selected for release. ### Checklist 📋 #### For code changes: - [x] Changes and explicit exclusions are listed above. - [x] Test and rollout evidence is documented above. - [x] Local focused backend, frontend, conformance, formatting, and diff checks finish successfully. - [x] Credential access is user-scoped and execution revalidates authoritative credential identity. #### For configuration changes: - [x] Existing environment defaults remain compatible; no new required secret or rollout flag was added. - [x] Compose supplies memory-backed temporary storage for local execution. - [x] Required cloud-preview origin settings and optional Codex runtime settings are documented above. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **High Risk** > Touches authentication, credential storage/refresh, chat session routing, and billing/paywall behavior for a new user-funded LLM path. A routing or entitlement bug could leak paid platform usage or expose ChatGPT credentials incorrectly. > > **Overview** > Adds a **user-funded ChatGPT/Codex subscription transport** so AutoPilot chat, the AutoPilot graph block, and Code Generation can run on a connected ChatGPT plan instead of platform-funded or OpenAI API-key routes. > > **Auth & credentials.** Introduces ChatGPT device-code sign-in under the existing OpenAI integration card, stores one `codex` OAuth credential with runtime-owned refresh/checkpointing, and blocks direct API creation of Codex credentials. Discovery, connect, and execution are entitlement-gated (`LOCAL` for all users; `CLOUD` for Max/Business/Enterprise). > > **Routing & billing.** Adds `/api/chat/transports` and persists an immutable `llm_auth_provider` / `llm_credential_id` on each session. Codex AutoPilot skips the platform paywall and USD usage windows; subscription usage is recorded as `user_subscription` rather than platform spend. Builder-bound sessions stay platform-only. > > **Runtime.** Pins the Codex App Server runtime, isolates auth homes under `CODEX_TEMP_ROOT`, and reuses a process-local runtime actor under an exclusive credential lease. AutoPilot still goes through the Claude Agent SDK via a loopback compatibility gateway; Code Generation can invoke App Server directly when that transport is selected. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 1441864. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Bentlybro <Github@bentlybro.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…ificant-Gravitas#13999) ### Why / What / How **Why** — the Slack install flow dead-ended. After OAuth you landed on a blank browser tab with no route back; the Bots settings page showed no sign an install had happened (it looked untouched); and once you did link, the success page told you to close the tab rather than return to the bot. Every step forward left the user to find their own way to the next one. **What** — closes the loop: the install now hands you to the bot chat and returns the tab to Settings → Bots, the Bots page reflects a half-finished install, and the success page routes you back to the bot. Plus each platform is finally called by its own name — Slack has *workspaces*, Telegram *groups*, Discord *servers*. **How** — the install is attributed to the signed-in user via an HMAC-signed param folded into the OAuth state, so the (unauthenticated) callback can record a short-lived pending marker in Redis without trusting the query string. The callback then redirects to a small handoff page of ours, which opens the bot DM through the Slack desktop deep link and returns the tab to settings — keeping the whole round-trip in one tab, so nothing is stranded. `server_noun` is declared once per platform in the registry and flows to every surface. ### Changes 🏗️ **Backend** - `slack/oauth.py`: signed `u` param → OAuth state → user-attributed install; callback redirects to the handoff page (falls back to Slack's redirect when no frontend is configured) - `slack/pending.py` (new): Redis-backed pending-install marker, 7-day TTL, best-effort — losing Redis degrades the hint, never the install - `platform_linking/registry.py`: `server_noun` per platform + `server_noun_for()` lookup - `platform_linking/models.py`: `PendingInstallInfo`, plus `server_noun` / `pending_install` / `return_url` fields (all optional, defaulted) - `platform_linking/routes.py`: user-bound install URL, pending surfacing, `return_url` on both confirm endpoints, marker retired on DM link **Frontend** - `BotCard`: pending badge + "Open AutoGPT in Slack", per-platform section headings, install now same-tab - `BotCardDmTile`: explains the one remaining step, echoing the workspace name - `BotCardServerList`: per-platform nouns; a link with no name no longer claims the bot is missing - `SuccessView` / `ReadyView`: return-to-bot action, roomier layout, smaller buttons - `link/slack/installed` (new): deep-link handoff page that returns the tab to settings - `useBotsList`: refetches on tab focus/visibility — the install round-trip ends outside the app, so the page must notice you coming back - `AuthCard`: applies its `className` prop (it was declared and silently dropped) **Config**: none. No `.env` or `docker-compose.yml` changes. **API**: `openapi.json` regenerated (additive: 3 optional fields + 1 schema). ### Checklist 📋 #### For code changes: - [x] I have clearly listed my changes in the PR description - [x] I have made a test plan - [x] I have tested my changes according to the test plan: - [x] Install from Settings → Bots: OAuth completes, Slack desktop opens at the bot DM, tab returns to Settings → Bots - [x] Bots page shows "Pending — finish in Slack" with the workspace name, without a manual reload - [x] DM the bot → Link Account → confirm → success page offers "Return to Slack", which lands back in the DM - [x] Pending badge clears once the DM is linked (marker retired) - [x] Section headings read "Linked workspaces" (Slack), "Linked groups" (Telegram), "Linked servers" (Discord) - [x] Discord and Telegram cards render unchanged where the new fields are absent - [x] Frontend tests: 25/25 pass (`settings/bots`, `link/[token]`) - [x] Backend: ruff + black clean; new/updated tests cover state and `u`-param round-trips, tamper and expiry, pending recording, both redirect paths, `return_url` on both confirms, and non-Slack platforms untouched Verified end-to-end against a live Slack workspace, not just mocks. #### For configuration changes: - [x] `.env.default` is updated or already compatible with my changes - [x] `docker-compose.yml` is updated or already compatible with my changes - [x] I have included a list of my configuration changes in the PR description (under **Changes**) ### Notes for reviewers - **Backend tests couldn't be run locally.** Current `dev` makes `graph_cleanup` a session-autouse fixture depending on `SpinTestServer`, so every backend test boots the full service stack — which can't come up on a Windows host. The Python tests here are written but unverified until CI runs them. - The pending marker is deliberately **Redis, not a table**: it's transient journey state, not a record. It fails open — if Redis is unavailable the hint disappears but the install and link still work. - **Slack is the only platform with the full loop**, because it's the only one whose install round-trips our backend. Discord invites, Telegram deep links and Teams sideloads never touch us, so we can't know an install happened. The UI treatment is generic, so any platform that can populate the fields gets it for free.
… layouts (Significant-Gravitas#14000) ### Why / What / How **Why:** The legacy `/profile/*` pages redirect to their `/settings/*` replacements, but only when the `autogpt-new-layout` flag is on — and the redirect drops the hash and query string. Two things break as a result: 1. **Notification unsubscribe emails 404 in spirit.** Every notification email links to `/profile/settings#notifications` (`backend/notifications/email.py:79`, `notifications/notifications.py:788`, also used for the `List-Unsubscribe` header). The `#notifications` anchor was silently discarded on the hop, and classic-layout users were never redirected at all. 2. **Stripe return params were eaten.** `PaywallGate` sends Stripe to `/profile/credits?subscription=success` (`usePaywallModal.ts:106`); the query string never reached `/settings/billing`, so the success toast at `settings/billing/page.tsx:97` could never fire. The `/settings/*` pages render fine under both the classic and the new shell — only the chrome differs (`PlatformChrome.tsx:118,128-135`) — so gating the redirect on the layout flag was never necessary. **What:** Ungate the redirect and preserve `search` + `hash` across it. Keep `/profile/oauth-apps` on the legacy page. **How:** `useNewSettingsRedirect` no longer reads `usePlatformChrome`; it redirects on any `/profile/*` pathname. Since `usePathname()` never carries the fragment, the effect reads `window.location` at redirect time and appends both parts to `router.replace()`. A client-side redirect is the correct mechanism here — a server redirect cannot see `#hash` at all, because browsers never send it. Dropping the `isMounted` gate does not reintroduce a hydration mismatch: `layout.tsx` renders `null` for redirecting paths on both server and client, so the two agree. The legacy pages are **not** removed — they stay routable and simply redirect, per the requirement that old links must never 404. ### Changes 🏗️ **`useNewSettingsRedirect.ts`** - Removed the `isNewLayoutActive` gate — legacy links now redirect under both layouts - Redirect target is now `` `${newPath}${search}${hash}` ``, so `/profile/settings#notifications` → `/settings/account#notifications` and `/profile/credits?subscription=success` → `/settings/billing?subscription=success` - Added `KEEP_ON_LEGACY` with `/profile/oauth-apps`: `/settings/oauth-apps` is still a `"Coming soon."` placeholder (`settings/oauth-apps/page.tsx:12`) and isn't in the settings sidebar, so redirecting there would take a working page away from every user. One-line removal once the real page ships. - Guarded on `pathname.startsWith("/profile")` so the fallback can't fire outside the legacy tree **`profile/(user)/layout.tsx`** - Comment updated — the redirect is no longer new-layout-specific **Tests** (`__tests__/useNewSettingsRedirect.test.tsx`) - Dropped the `usePlatformChrome` mock and the "does not redirect while the classic layout is active" case, which asserted the behaviour this PR removes - Added: `#notifications` anchor preserved; `?subscription=success` preserved; `/profile/oauth-apps` stays put; non-`/profile` paths are untouched **Out of scope / follow-ups** - `/settings/account` still does not render `NotificationsCard` (hidden since Significant-Gravitas#12976 pending copy/UX). The redirect now lands correctly, but there are still no notification toggles on the other side — deliberately left for the follow-up. - `backend/notifications/email.py:79` could append `#notifications` to the unsubscribe link so recipients land directly on the toggles. Backend change, separate PR. - `PreferencesSkeleton.tsx:13` still renders a notifications placeholder that vanishes on load. ### Checklist 📋 #### For code changes: - [x] I have clearly listed my changes in the PR description - [x] I have made a test plan - [x] I have tested my changes according to the test plan: - [x] `pnpm test:unit` — `useNewSettingsRedirect.test.tsx` passes (9 cases) - [x] `pnpm types` clean, `pnpm format` / `pnpm lint` clean - [ ] With **no** feature flag set, open `/profile/settings#notifications` → lands on `/settings/account#notifications` - [ ] With `NEXT_PUBLIC_FORCE_FLAG_AUTOGPT_NEW_LAYOUT=true`, same URL → same result - [ ] `/profile`, `/profile/dashboard`, `/profile/credits`, `/profile/integrations`, `/profile/api-keys` each land on their `/settings/*` counterpart - [ ] `/profile/credits?subscription=success` → `/settings/billing?subscription=success` - [ ] `/profile/oauth-apps` still renders the legacy OAuth apps page - [ ] `/profile/does-not-exist` → `/settings/profile` - [ ] No flash of the legacy sidebar shell before the redirect #### For configuration changes: - [x] `.env.default` is updated or already compatible with my changes - [x] `docker-compose.yml` is updated or already compatible with my changes
# Conflicts: # .github/workflows/platform-single-container-docker.yml
…questBlock The content-type check previously only matched 'application/json' exactly, causing responses with types like 'application/vnd.api+json', 'application/hal+json', etc. to be returned as raw strings instead of parsed JSON objects. Fix: also match content types ending with '+json' per RFC 6839. Fixes Significant-Gravitas#14007
- Make credentials Optional in llm_call, _llm_call, and all AIStructuredResponseGeneratorBlock methods - Handle None credentials gracefully (empty string for Ollama/local models) - Update AICredentialsField to allow null defaults - Replace FormCreator with textarea in StickyNoteBlock - Add cursor-position preservation in StickyNoteBlock useCustomNode Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Use ensure_ascii=True in json.dumps() when serializing variables into the AGPT_VARIABLES env var. Without this, surrogate pairs in emoji strings cause os.environ.__setitem__ to raise UnicodeEncodeError on Python 3.13, crashing ExecuteCodeBlock before user code runs. Closes Significant-Gravitas#13551
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes Significant-Gravitas#13551. Fix: use ensure_ascii=True in json.dumps() when serializing variables into AGPT_VARIABLES env var so emoji/surrogate pairs are safely escaped to \uXXXX sequences. Added test_emoji_in_variables_is_safe_for_env_var covering emoji and non-ASCII chars.