Skip to content
Open
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
e1df24c
fix(hosting): scope gh.local traefik to its own compose project
mmabrouk Jun 15, 2026
47f113d
feat(editor): enhance sticky header behavior and improve paste handling
ashrafchowdury Jun 16, 2026
69088bd
fix(sdk): normalize chat_v0 output to the canonical Message shape
jp-agenta Jun 16, 2026
fd8210d
Merge branch 'main' into fix/evaluator-code-eval-code-block
ardaerzin Jun 17, 2026
3bc43ae
fix(frontend): cap sidebar banner queue
mmabrouk Jun 17, 2026
e4fb43e
fix playwright browser cache fallback
mmabrouk Jun 17, 2026
492a8b4
fix(railway): select service when redeploying previews
mmabrouk Jun 17, 2026
1465b06
fix(hosting): scope every traefik instance to its own compose project
mmabrouk Jun 17, 2026
f8034fe
v0.104.1
junaway Jun 18, 2026
3abf559
fix(SessionDrawerContent): default-select the "Session" root node for…
ashrafchowdury Jun 18, 2026
f7797af
fix(SessionIdCell): adjust Tag styling for improved layout and alignment
ashrafchowdury Jun 18, 2026
1fe344b
non
ashrafchowdury Jun 18, 2026
afc1ba4
Merge branch 'release/v0.104.1' into fix/session-table-issue
ashrafchowdury Jun 18, 2026
44dd417
[4645] fix(frontend): wrap API key inside the create-key modal
Koushik-Salammagari Jun 18, 2026
d8405d5
test(api): align default-workspace test with oldest-membership behavior
jp-agenta Jun 19, 2026
47f6881
Merge pull request #4748 from Agenta-AI/fix-default-workspace-test
junaway Jun 19, 2026
bbbe0fb
Merge branch 'release/v0.104.1' into fix/session-table-issue
bekossy Jun 21, 2026
b3e7f1e
Merge branch 'release/v0.104.1' into fix/sidebar-banner-cap
bekossy Jun 21, 2026
3388329
Merge branch 'release/v0.104.1' into fix/evaluator-code-eval-code-block
bekossy Jun 21, 2026
7036931
Merge branch 'release/v0.104.1' into normalize-chat-output-message
bekossy Jun 21, 2026
4f38098
Merge pull request #4724 from Agenta-AI/fix/sidebar-banner-cap
bekossy Jun 21, 2026
6bfb6d8
Merge pull request #4736 from Agenta-AI/fix/session-table-issue
bekossy Jun 21, 2026
c500291
Merge pull request #4719 from Agenta-AI/normalize-chat-output-message
bekossy Jun 21, 2026
36fbc91
Merge branch 'release/v0.104.1' into fix/evaluator-code-eval-code-block
bekossy Jun 21, 2026
0e0a86c
Merge branch 'release/v0.104.1' into fix/railway-explicit-redeploy-se…
bekossy Jun 22, 2026
cef8285
Merge branch 'release/v0.104.1' into codex/playwright-cache-fallback
bekossy Jun 22, 2026
1d7dd2d
Merge pull request #4715 from Agenta-AI/fix/evaluator-code-eval-code-…
bekossy Jun 22, 2026
4097ef9
Merge branch 'release/v0.104.1' into fix/ghlocal-traefik-project-cons…
bekossy Jun 22, 2026
9388bf5
Merge pull request #4703 from Agenta-AI/fix/ghlocal-traefik-project-c…
bekossy Jun 22, 2026
3508288
Merge pull request #4723 from Agenta-AI/codex/playwright-cache-fallback
bekossy Jun 22, 2026
9a8869b
Merge pull request #4725 from Agenta-AI/fix/railway-explicit-redeploy…
bekossy Jun 22, 2026
9719904
Merge branch 'release/v0.104.1' into fix/4645-api-key-modal-overflow
bekossy Jun 22, 2026
4e2e42a
Merge pull request #4746 from Koushik-Salammagari/fix/4645-api-key-mo…
bekossy Jun 22, 2026
de228ef
Revert "[fix] Select Railway services when redeploying previews"
bekossy Jun 22, 2026
bb8915b
Merge pull request #4792 from Agenta-AI/revert-4725-fix/railway-expli…
bekossy Jun 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 19 additions & 4 deletions .github/workflows/12-check-unit-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,9 @@ jobs:
uses: actions/cache@v4
with:
path: ~/.cache/ms-playwright
key: ${{ runner.os }}-playwright-browsers
key: ${{ runner.os }}-playwright-browsers-${{ hashFiles('web/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-playwright-browsers-

# System deps for chromium only (not the full webkit/firefox apt set,
# which pulls in the gstreamer/audio/video stack and takes ~14m). Runs
Expand All @@ -98,10 +100,23 @@ jobs:
working-directory: web/tests
run: pnpm exec playwright install-deps chromium

- name: Install Playwright if not cached
if: (github.event_name != 'workflow_dispatch' || contains(fromJSON('["all","web-only"]'), inputs.packages)) && steps.restore-playwright-cache.outputs.cache-hit != 'true'
- name: Install Playwright browser
if: github.event_name != 'workflow_dispatch' || contains(fromJSON('["all","web-only"]'), inputs.packages)
working-directory: web/tests
run: pnpm exec playwright install chromium
run: |
for attempt in 1 2 3; do
echo "::group::playwright install chromium (attempt ${attempt}/3)"
if timeout 180 pnpm exec playwright install chromium; then
echo "::endgroup::"
echo "browser install succeeded on attempt ${attempt}"
exit 0
fi
echo "::endgroup::"
echo "attempt ${attempt} stalled or failed; retrying after 5s..."
sleep 5
done
echo "playwright browser install failed after 3 attempts" >&2
exit 1

- name: Run web unit tests
if: github.event_name != 'workflow_dispatch' || contains(fromJSON('["all","web-only"]'), inputs.packages)
Expand Down
6 changes: 4 additions & 2 deletions api/oss/tests/pytest/unit/services/test_db_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,9 @@ def _patch_core_session(monkeypatch, memberships):


@pytest.mark.asyncio
async def test_get_default_workspace_id_prefers_owner_membership(monkeypatch):
async def test_get_default_workspace_id_ignores_owner_role(monkeypatch):
# Owner-role is NOT preferred: under multi-org an invitee owns their own
# empty personal workspace, so the oldest membership wins regardless of role.
owner_workspace_id = uuid4()
editor_workspace_id = uuid4()

Expand All @@ -77,7 +79,7 @@ async def test_get_default_workspace_id_prefers_owner_membership(monkeypatch):

workspace_id = await db_manager.get_default_workspace_id(str(uuid4()))

assert workspace_id == str(owner_workspace_id)
assert workspace_id == str(editor_workspace_id)


@pytest.mark.asyncio
Expand Down
2 changes: 1 addition & 1 deletion api/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "api"
version = "0.104.0"
version = "0.104.1"
description = "Agenta API"
requires-python = ">=3.11,<3.14"
authors = [
Expand Down
6 changes: 3 additions & 3 deletions api/uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion clients/python/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "agenta-client"
version = "0.104.0"
version = "0.104.1"
description = "Fern-generated Python client for the Agenta API."
requires-python = ">=3.11,<3.14"
authors = [
Expand Down
2 changes: 1 addition & 1 deletion clients/python/uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions hosting/docker-compose/ee/docker-compose.gh.local.yml
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,7 @@ services:
- --api.dashboard=true
- --api.insecure=true
- --providers.docker
- --providers.docker.constraints=Label(`com.docker.compose.project`,`${COMPOSE_PROJECT_NAME:-agenta-ee-gh-local}`)
- --entrypoints.web.address=:80
# === STORAGE ============================================== #
volumes:
Expand Down
1 change: 1 addition & 0 deletions hosting/docker-compose/ee/docker-compose.gh.yml
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,7 @@ services:
command:
- --api.dashboard=true
- --providers.docker
- --providers.docker.constraints=Label(`com.docker.compose.project`,`${COMPOSE_PROJECT_NAME:-agenta-ee-gh}`)
- --entrypoints.web.address=:80
# === STORAGE ============================================== #
volumes:
Expand Down
1 change: 1 addition & 0 deletions hosting/docker-compose/oss/docker-compose.gh.local.yml
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,7 @@ services:
command:
- --api.dashboard=true
- --providers.docker
- --providers.docker.constraints=Label(`com.docker.compose.project`,`${COMPOSE_PROJECT_NAME:-agenta-oss-gh-local}`)
- --entrypoints.web.address=:80
# === STORAGE ============================================== #
volumes:
Expand Down
1 change: 1 addition & 0 deletions hosting/docker-compose/oss/docker-compose.gh.yml
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,7 @@ services:
command:
- --api.dashboard=true
- --providers.docker
- --providers.docker.constraints=Label(`com.docker.compose.project`,`${COMPOSE_PROJECT_NAME:-agenta-oss-gh}`)
- --entrypoints.web.address=:80
# === STORAGE ============================================== #
volumes:
Expand Down
3 changes: 2 additions & 1 deletion hosting/docker-compose/oss/ssl/traefik.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,5 @@ certificatesResolvers:
entryPoint: "web" # Validates domain ownership via port 80

providers:
docker: {}
docker:
constraints: "Label(`com.docker.compose.project`,`agenta-gh-ssl`)"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Honor custom compose project names in SSL Traefik

When the SSL stack is started with a custom COMPOSE_PROJECT_NAME/-p to run an isolated copy, Compose labels the app containers with that custom project name, but this Traefik constraint only watches agenta-gh-ssl. In that context Traefik filters out the web/api/service router labels and the HTTPS stack comes up without routes; the non-SSL compose files handle this by interpolating the project name, so the SSL path needs the same treatment or the constraint should be omitted there.

Useful? React with 👍 / 👎.

4 changes: 2 additions & 2 deletions hosting/kubernetes/helm/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ apiVersion: v2
name: agenta
description: A Helm chart for deploying Agenta (OSS or EE) on Kubernetes
type: application
version: 0.104.0
appVersion: "v0.104.0"
version: 0.104.1
appVersion: "v0.104.1"
keywords:
- agenta
- llm
Expand Down
2 changes: 1 addition & 1 deletion hosting/railway/oss/scripts/deploy-from-images.sh
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ redeploy_service_if_exists() {
local service="$1"

if railway_call service "$service" >/dev/null 2>&1; then
railway_call redeploy --yes >/dev/null
railway_call redeploy --service "$service" --environment "$ENV_NAME" --yes >/dev/null
fi
}

Expand Down
2 changes: 1 addition & 1 deletion hosting/railway/oss/scripts/deploy-services.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ fi
railway link --project "$PROJECT_NAME" --environment "$ENV_NAME" --json >/dev/null

# Bring stateful infra up first so credentials/volumes are applied.
railway service "$POSTGRES_SERVICE" >/dev/null && railway redeploy --yes
railway service "$POSTGRES_SERVICE" >/dev/null && railway redeploy --service "$POSTGRES_SERVICE" --environment "$ENV_NAME" --yes
if railway service "$REDIS_SERVICE" >/dev/null 2>&1; then
railway up hosting/railway/oss/redis --path-as-root --service "$REDIS_SERVICE" --detach
fi
Expand Down
12 changes: 6 additions & 6 deletions hosting/railway/oss/scripts/smoke.sh
Original file line number Diff line number Diff line change
Expand Up @@ -92,16 +92,16 @@ repair_path() {

case "$path" in
"/w")
railway service web >/dev/null && railway redeploy --yes >/dev/null
railway service gateway >/dev/null && railway redeploy --yes >/dev/null
railway service web >/dev/null && railway redeploy --service web --environment "$ENV_NAME" --yes >/dev/null
railway service gateway >/dev/null && railway redeploy --service gateway --environment "$ENV_NAME" --yes >/dev/null
;;
"/api/health")
railway service api >/dev/null && railway redeploy --yes >/dev/null
railway service gateway >/dev/null && railway redeploy --yes >/dev/null
railway service api >/dev/null && railway redeploy --service api --environment "$ENV_NAME" --yes >/dev/null
railway service gateway >/dev/null && railway redeploy --service gateway --environment "$ENV_NAME" --yes >/dev/null
;;
"/services/health")
railway service services >/dev/null && railway redeploy --yes >/dev/null
railway service gateway >/dev/null && railway redeploy --yes >/dev/null
railway service services >/dev/null && railway redeploy --service services --environment "$ENV_NAME" --yes >/dev/null
railway service gateway >/dev/null && railway redeploy --service gateway --environment "$ENV_NAME" --yes >/dev/null
;;
*)
return 1
Expand Down
5 changes: 4 additions & 1 deletion sdks/python/agenta/sdk/engines/running/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2220,7 +2220,10 @@ async def chat_v0(

message = response.choices[0].message # type: ignore

return message.model_dump(exclude_none=True) # type: ignore
# Normalize to the canonical Message shape (drops provider-specific fields).
return Message.model_validate(message.model_dump(exclude_none=True)).model_dump(
exclude_none=True
)
Comment on lines +2224 to +2226

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve refusal-only chat responses

When a provider returns an assistant refusal with content=None and a populated refusal field, this validation drops the refusal because the canonical Message model only includes role/content/name/tool fields; the result returned from chat_v0 is just {"role": "assistant"}. The sibling completion_v0 path still handles message.refusal, so chat callers in the same scenario lose the model's actual response text.

Useful? React with 👍 / 👎.



@instrument(ignore_inputs=["parameters"])
Expand Down
2 changes: 1 addition & 1 deletion sdks/python/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "agenta"
version = "0.104.0"
version = "0.104.1"
description = "The SDK for agenta is an open-source LLMOps platform."
readme = "README.md"
requires-python = ">=3.11,<3.14"
Expand Down
4 changes: 2 additions & 2 deletions sdks/python/uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion services/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "services"
version = "0.104.0"
version = "0.104.1"
description = "Agenta Services (Chat & Completion)"
requires-python = ">=3.11,<3.14"
authors = [
Expand Down
6 changes: 3 additions & 3 deletions services/uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion web/ee/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@agenta/ee",
"version": "0.104.0",
"version": "0.104.1",
"private": true,
"engines": {
"node": "24.x"
Expand Down
2 changes: 1 addition & 1 deletion web/oss/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@agenta/oss",
"version": "0.104.0",
"version": "0.104.1",
"private": true,
"engines": {
"node": "24.x"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,10 @@ const PlaygroundVariantConfig: React.FC<
revisionId={variantId}
onRefinePrompt={handleRefinePrompt}
viewMode={viewMode}
// Embedded (drawer) renders the variant config
// header non-sticky, so the section headers have
// nothing to clear — pin them at the scroll top.
stickyHeaderTop={embedded ? 0 : 48}
/>
</PlaygroundNodeTokenPathProvider>
</FieldsDetectionProvider>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@ interface TraceDrawerContentProps {
}

const SessionDrawerContent = ({onClose, onToggleWidth, isExpanded}: TraceDrawerContentProps) => {
const [selected, setSelected] = useState<string>("")
// Default-select the "Session" root node so the tree opens with a selection
// instead of nothing highlighted. The root node's key is always "root", and
// selecting it is a no-op in handleSelect (it early-returns for "root").
const [selected, setSelected] = useState<string>("root")
const {isLoading} = useSessionDrawer()
if (isLoading) {
return (
Expand Down
18 changes: 17 additions & 1 deletion web/oss/src/components/SidebarBanners/state/atoms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@ export const PRIORITY_ORDER: Record<BannerType, number> = {
trial: 3, // Lowest priority - show after other banners are dismissed
}

/**
* Maximum number of dismissible sidebar banners a user should have to clear.
* Apply this before dismissal filtering so older changelog entries do not
* backfill the sidebar after each close.
*/
export const MAX_DISMISSIBLE_SIDEBAR_BANNERS = 2

/**
* Persisted atom for dismissed banner IDs.
* Uses localStorage to remember which banners the user has dismissed.
Expand Down Expand Up @@ -107,8 +114,17 @@ export const activeBannersAtom = atom((get) => {
export const visibleBannersAtom = atom((get) => {
const allBanners = get(activeBannersAtom)
const dismissedIds = get(dismissedBannerIdsAtom)
const sortedBanners = [...allBanners].sort(
(a, b) => PRIORITY_ORDER[a.type] - PRIORITY_ORDER[b.type],
)

const cappedDismissibleBanners = sortedBanners
.filter((banner) => banner.dismissible)
.slice(0, MAX_DISMISSIBLE_SIDEBAR_BANNERS)
Comment on lines +121 to +123

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep upgrade banners from being capped out

In the EE free-plan context, web/ee/src/components/SidebarBanners/state/atoms.ts injects a dismissible upgrade-banner-v1, but this caps all dismissible banners before dismissal filtering. Because the OSS list always contains the higher-priority star banner and newest changelog banner first, the upgrade banner is never included in cappedDismissibleBanners; after those two are dismissed they are filtered out and no lower-priority dismissible banner backfills, so free-plan users never see the sidebar upgrade CTA.

Useful? React with 👍 / 👎.


const nonDismissibleBanners = sortedBanners.filter((banner) => !banner.dismissible)

return allBanners
return [...cappedDismissibleBanners, ...nonDismissibleBanners]
.filter((banner) => !dismissedIds.includes(banner.id))
.sort((a, b) => PRIORITY_ORDER[a.type] - PRIORITY_ORDER[b.type])
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ import {Tag} from "antd"
export const SessionIdCell = ({sessionId}: {sessionId: string}) => {
return (
<TooltipWithCopyAction copyText={sessionId || ""} title="Copy session id">
<Tag className="font-mono bg-[var(--ag-c-0517290F)] w-fit truncate" bordered={false}>
<Tag
className="font-mono bg-[var(--ag-c-0517290F)] max-w-full truncate inline-block align-middle"
bordered={false}
>
# {sessionId}
</Tag>
</TooltipWithCopyAction>
Expand Down
Loading
Loading