Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
74 commits
Select commit Hold shift + click to select a range
518abd8
fix(runner): shell-quote interpolated values in mount.ts sh -c calls
jp-agenta Jul 13, 2026
01cd022
fix(sdk): apply SSRF guard to MCP server url (PY-SEC-2)
jp-agenta Jul 13, 2026
080f4b1
fix(sdk): mask WireToolCallback.authorization from repr (PY-C5 residual)
jp-agenta Jul 13, 2026
28aaa6c
fix(runner): cap the tool result text returned to the model
jp-agenta Jul 13, 2026
2400723
docs(runner): record why the dev image stays root while Dockerfile.gh…
jp-agenta Jul 13, 2026
ec334e3
fix(runner): enforce the SDK's skill size caps at the untrusted wire …
jp-agenta Jul 13, 2026
58e4cd4
fix(sdk): mask ResolvedConnection.env on model_dump (F-SDK-DUMP)
jp-agenta Jul 13, 2026
7f6342c
fix(sdk): give UnsupportedDeploymentError its documented 422 (XP-3)
jp-agenta Jul 13, 2026
e3f3e43
fix(runner): scope /kill to a session instead of tearing down every t…
jp-agenta Jul 13, 2026
fdca4a6
fix(runner): give the harness only its declared provider's keys, not …
jp-agenta Jul 13, 2026
bf5724f
fix(runner): stop leaking upstream tool-error bodies into the model's…
jp-agenta Jul 13, 2026
4f7cdd7
fix(runner): resolve the real tool spec for the ACP approval card
jp-agenta Jul 13, 2026
6b38546
fix(runner): wire the known-value Redactor into the record and span s…
jp-agenta Jul 13, 2026
3d7e283
merge(fix/v7-posture): v7 findings
jp-agenta Jul 13, 2026
b355972
merge(fix/v7-runner-hardening): v7 findings
jp-agenta Jul 13, 2026
83dd72e
merge(fix/v7-acp-spec): v7 findings
jp-agenta Jul 13, 2026
76a788b
merge(fix/v7-redaction): v7 findings
jp-agenta Jul 13, 2026
5ad8621
merge(fix/v7-sdk-service): v7 findings
jp-agenta Jul 13, 2026
663024e
fix(sdk): guarantee stream terminators in the dev twin (SDK-3)
jp-agenta Jul 13, 2026
73e8fcd
fix(sdk): widen the client idle timeout past the runner's authority
jp-agenta Jul 13, 2026
1d4e791
fix(sdk): count conformed parts so the zero-content backstop can fire
jp-agenta Jul 13, 2026
ae54b74
fix(api): stop persisting unset flags as false, and make oss000000010…
jp-agenta Jul 13, 2026
9762b7c
fix(hosting): declare AGENTA_RUNNER_TOKEN in every operator-facing ar…
jp-agenta Jul 13, 2026
64142ee
fix(api): dedup scheduled trigger dispatch so a retry cannot re-fire …
jp-agenta Jul 13, 2026
af368ee
fix(hosting): add the missing commonEnv include to the helm runner de…
jp-agenta Jul 13, 2026
3a58951
merge(fix/v7-runner-security): v7 findings
jp-agenta Jul 13, 2026
41c2479
merge(fix/v7-cheap-defects): v7 findings
jp-agenta Jul 13, 2026
998015d
fix(services): gate before resolve, de-duplicate the gates (SVC-1..5)
jp-agenta Jul 13, 2026
fb074c4
fix(web): surface the recent-samples fetch error (WEB-2)
jp-agenta Jul 13, 2026
ea3e3c2
merge(fix/v7-sdk-service): SVC-1..5, SDK-3, WEB-2, URL audit
jp-agenta Jul 13, 2026
48a1733
fix(api): dedup per-row run_flags lookups in evaluations DAO
jp-agenta Jul 13, 2026
35691b4
fix(api): batch session-record appends instead of one connection per …
jp-agenta Jul 13, 2026
c08868b
fix(api): dedup workflow fetch when normalizing many revisions for read
jp-agenta Jul 13, 2026
c884ccc
merge(fix/v7-api-pool): API-2 connection-per-row exhaustion
jp-agenta Jul 13, 2026
05c3f38
fix(api): add expression index for gateway_connections provider accou…
jp-agenta Jul 13, 2026
b21b309
fix(api): hoist the per-poll DB session out of test_subscription's wa…
jp-agenta Jul 13, 2026
0c18ad6
fix(api): move session Redis TTLs and CONCURRENCY_LIMIT into env.py
jp-agenta Jul 13, 2026
5b5726f
fix(api): cache the OAuth callback's integration metadata lookup
jp-agenta Jul 13, 2026
81c01a0
fix(api): drop indexes redundant with an identical unique constraint
jp-agenta Jul 13, 2026
1cf6405
fix(api): type AuthService.discover's return as a DiscoverResponse DTO
jp-agenta Jul 13, 2026
e01da7d
Merge remote-tracking branch 'origin/big-agents' into merge/v7-integr…
jp-agenta Jul 14, 2026
aabc2e6
Merge remote-tracking branch 'origin/big-agents' into merge/v7-integr…
jp-agenta Jul 14, 2026
94d99fb
fix(sdk): use a literal public IP in MCP resolver tests, not a fake h…
jp-agenta Jul 14, 2026
c5ddb8a
fix(api): close the trigger dispatch TOCTOU with an atomic claim
jp-agenta Jul 14, 2026
fdc547a
fix(runner): redact span events/status, and scope redactors per trace…
jp-agenta Jul 14, 2026
3b921de
fix(runner): stream-bound the tool-call response body and fix UTF-8 t…
jp-agenta Jul 14, 2026
ccb593b
fix(runner): normalize blank /kill projectId and cap its request body
jp-agenta Jul 14, 2026
fab0ac1
merge(fix/v7-review): PR #5286 review findings
jp-agenta Jul 14, 2026
f3ff558
fix(runner): require projectId on /kill so pool and sandbox teardown …
jp-agenta Jul 14, 2026
0c6c28b
fix(api): RecordsDAO.append must not commit a caller-supplied session
jp-agenta Jul 14, 2026
c139e46
merge(fix/v7-review2): /kill scoping + DAO commit ownership
jp-agenta Jul 14, 2026
8cff8cd
test(runner): update the /kill acceptance contract to require a proje…
jp-agenta Jul 14, 2026
4435091
fix(runner): scope the in-flight sandbox kill filter to the run's pro…
jp-agenta Jul 14, 2026
f731932
test(runner): await park() calls before asserting pool state
jp-agenta Jul 14, 2026
aa74587
test(runner): replace duplicate whitespace-projectId /kill test with …
jp-agenta Jul 14, 2026
9380516
merge(fix/v7-review3): scoped /kill matches mountless sandboxes
jp-agenta Jul 14, 2026
376a961
fix(runner): give traceTargets per-run lifecycle accounting, matching…
jp-agenta Jul 14, 2026
2be8813
fix(runner): count skill size caps by Unicode code point, not UTF-16 …
jp-agenta Jul 14, 2026
0ca89b1
merge(fix/v7-review4): per-run span targets + code-point skill caps
jp-agenta Jul 14, 2026
837bf95
Merge branch 'big-agents' into fix/v7-integration
jp-agenta Jul 14, 2026
c6f8233
fix(api): revert unset-flag persistence change; fix real dedup regres…
jp-agenta Jul 14, 2026
8b41657
merge(fix/v7-api1-regression): fix the revision dedup AttributeError;…
jp-agenta Jul 14, 2026
fc569a5
fix(services): retry acceptance HTTP calls on transient gateway 502/5…
jp-agenta Jul 14, 2026
403681f
fix(api): keep the oss000000010 backfill provenance out of the flags …
jp-agenta Jul 14, 2026
57aa1dd
fix(services): retry acceptance HTTP calls on transient gateway 502/5…
jp-agenta Jul 14, 2026
563e5a9
Merge remote-tracking branch 'origin/big-agents' into fix/v7-integration
jp-agenta Jul 14, 2026
91659f9
merge(fix/v7-services-502): v7 findings
jp-agenta Jul 14, 2026
0e19772
merge(fix/v7-api-perf): API-4..9
jp-agenta Jul 14, 2026
5757221
Merge remote-tracking branch 'origin/big-agents' into merge/v7-integr…
jp-agenta Jul 14, 2026
4025b66
fix(merge): fix YAML whitespace-trim bug and update RUN-SEC-1 test fo…
jp-agenta Jul 14, 2026
a0eaf81
merge(big-agents): trigger subscription test fixes
jp-agenta Jul 14, 2026
ede25c6
feat(runner): require AGENTA_RUNNER_TOKEN, like AGENTA_AUTH_KEY
jp-agenta Jul 14, 2026
32ac8a6
Merge branch 'big-agents' into fix/v7-integration
jp-agenta Jul 14, 2026
4a1e9cc
fix(runner-token): repair the deploy surfaces the required-token chan…
jp-agenta Jul 14, 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
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,43 @@
def upgrade() -> None:
conn = op.get_bind()

# Only add each key where absent — never overwrite an already-set value.
# Record which rows this backfill touched, and which keys it added to each, in a
# side table — not inside `flags` itself. An explicit false is indistinguishable
# from one a caller stored, so downgrade needs provenance; but `flags` is
# user-facing and typed (Dict[str, bool | str | dict]), so bookkeeping must not
# live there.
conn.execute(
sa.text(
"""
CREATE TABLE IF NOT EXISTS oss000000010_backfilled_flags (
revision_id uuid NOT NULL,
project_id uuid NOT NULL,
added_keys text[] NOT NULL,
PRIMARY KEY (project_id, revision_id)
)
"""
)
)

conn.execute(
sa.text(
"""
INSERT INTO oss000000010_backfilled_flags (revision_id, project_id, added_keys)
SELECT id, project_id,
ARRAY(
SELECT k FROM unnest(ARRAY['is_agent', 'is_skill']) AS k
WHERE NOT (COALESCE(flags, '{}'::jsonb) ? k)
)
FROM workflow_revisions
WHERE flags IS NULL
OR NOT (flags ? 'is_agent')
OR NOT (flags ? 'is_skill')
ON CONFLICT DO NOTHING
"""
)
)

# Add each key only where absent; never overwrite a value a caller already stored.
conn.execute(
sa.text(
"""
Expand All @@ -47,16 +83,24 @@ def upgrade() -> None:


def downgrade() -> None:
# Strip only the false values this backfill added; leave rows that carried
# a real (true) value or predate the keys untouched beyond the removal.
conn = op.get_bind()

# Strip only the keys this backfill added, per row, from its recorded provenance.
conn.execute(
sa.text(
"""
UPDATE workflow_revisions
SET flags = flags - 'is_agent' - 'is_skill'
WHERE (flags ->> 'is_agent') = 'false'
OR (flags ->> 'is_skill') = 'false'
UPDATE workflow_revisions AS wr
SET flags = (
SELECT COALESCE(jsonb_object_agg(kv.key, kv.value), '{}'::jsonb)
FROM jsonb_each(wr.flags) AS kv
WHERE NOT (kv.key = ANY(b.added_keys))
)
FROM oss000000010_backfilled_flags AS b
WHERE wr.id = b.revision_id
AND wr.project_id = b.project_id
AND wr.flags IS NOT NULL
"""
)
)

conn.execute(sa.text("DROP TABLE IF EXISTS oss000000010_backfilled_flags"))
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
"""add expression index for gateway_connections provider account lookups

find_connection_by_provider_id / activate_connection_by_provider_connection_id
(the OAuth-callback hot path, called on every Composio connect completion)
filter on project_id + data->>'connected_account_id' with no index backing the
JSON key extraction, forcing a per-project sequential scan. Add a btree
expression index on the extracted text (the `data` column is plain `json`,
not `jsonb`, so a GIN path-ops index isn't an option here).

Revision ID: oss000000012
Revises: oss000000011
Create Date: 2026-07-13 00:00:00.000000

"""

from typing import Sequence, Union

from alembic import op
from sqlalchemy import text

revision: str = "oss000000012"
down_revision: Union[str, None] = "oss000000011"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


def upgrade() -> None:
# CREATE INDEX CONCURRENTLY cannot run inside a transaction.
with op.get_context().autocommit_block():
op.execute(
text(
"""
CREATE INDEX CONCURRENTLY IF NOT EXISTS
ix_gateway_connections_project_id_connected_account_id
ON gateway_connections (project_id, (data ->> 'connected_account_id'));
"""
)
)


def downgrade() -> None:
with op.get_context().autocommit_block():
op.execute(
text(
"DROP INDEX CONCURRENTLY IF EXISTS "
"ix_gateway_connections_project_id_connected_account_id;"
)
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
"""drop indexes redundant with an identical unique constraint

Postgres backs every UNIQUE constraint with its own implicit b-tree index,
so an explicit Index() on the exact same column tuple/order is dead weight
(extra write cost + storage, no read benefit). Drops the redundant explicit
indexes across the git-artifact/variant/revision tables (project_id, slug)
and the two session tables + folders (project_id, session_id / path).

Revision ID: oss000000013
Revises: oss000000012
Create Date: 2026-07-13 00:00:01.000000

"""

from typing import Sequence, Union

from alembic import op

revision: str = "oss000000013"
down_revision: Union[str, None] = "oss000000012"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None

# (index_name, table_name, [columns...]) — columns/order match the surviving
# UniqueConstraint exactly, so downgrade recreates a byte-identical index.
_REDUNDANT_INDEXES = [
(
"ix_environment_artifacts_project_id_slug",
"environment_artifacts",
["project_id", "slug"],
),
(
"ix_environment_variants_project_id_slug",
"environment_variants",
["project_id", "slug"],
),
(
"ix_environment_revisions_project_id_slug",
"environment_revisions",
["project_id", "slug"],
),
(
"ix_workflow_artifacts_project_id_slug",
"workflow_artifacts",
["project_id", "slug"],
),
(
"ix_workflow_variants_project_id_slug",
"workflow_variants",
["project_id", "slug"],
),
(
"ix_workflow_revisions_project_id_slug",
"workflow_revisions",
["project_id", "slug"],
),
(
"ix_testset_artifacts_project_id_slug",
"testset_artifacts",
["project_id", "slug"],
),
("ix_testset_variants_project_id_slug", "testset_variants", ["project_id", "slug"]),
(
"ix_testset_revisions_project_id_slug",
"testset_revisions",
["project_id", "slug"],
),
("ix_query_artifacts_project_id_slug", "query_artifacts", ["project_id", "slug"]),
("ix_query_variants_project_id_slug", "query_variants", ["project_id", "slug"]),
("ix_query_revisions_project_id_slug", "query_revisions", ["project_id", "slug"]),
(
"ix_session_states_project_id_session_id",
"session_states",
["project_id", "session_id"],
),
(
"ix_session_streams_project_id_session_id",
"session_streams",
["project_id", "session_id"],
),
("ix_folders_project_path", "folders", ["project_id", "path"]),
]


def upgrade() -> None:
for index_name, table_name, _columns in _REDUNDANT_INDEXES:
op.drop_index(index_name, table_name=table_name, if_exists=True)


def downgrade() -> None:
for index_name, table_name, columns in _REDUNDANT_INDEXES:
op.create_index(index_name, table_name, columns, unique=False)
32 changes: 16 additions & 16 deletions api/oss/src/apis/fastapi/auth/models.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
from pydantic import BaseModel, EmailStr
from typing import Optional, List, Dict
from typing import Optional

from oss.src.core.auth.dtos import (
DiscoverResponse,
SSOProviderInfo,
SSOProviders,
)

__all__ = [
"DiscoverRequest",
"DiscoverResponse",
"SSOProviderInfo",
"SSOProviders",
"OIDCAuthorizeRequest",
"OIDCCallbackRequest",
]


# ============================================================================
Expand All @@ -11,21 +26,6 @@ class DiscoverRequest(BaseModel):
email: EmailStr


class SSOProviderInfo(BaseModel):
id: str
slug: str
third_party_id: str


class SSOProviders(BaseModel):
providers: List[SSOProviderInfo]


class DiscoverResponse(BaseModel):
exists: bool
methods: Dict[str, bool | SSOProviders]


# ============================================================================
# OIDC AUTHORIZE
# ============================================================================
Expand Down
3 changes: 1 addition & 2 deletions api/oss/src/apis/fastapi/auth/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,7 @@ async def discover(request: DiscoverRequest):
Returns minimal information needed for authentication flow.
"""
try:
result = await auth_service.discover(request.email)
return DiscoverResponse(**result)
return await auth_service.discover(request.email)
except Exception as e:
log.error("[DISCOVERY]", exc_info=True)
raise HTTPException(status_code=500, detail=str(e))
Expand Down
33 changes: 30 additions & 3 deletions api/oss/src/apis/fastapi/tools/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -987,14 +987,41 @@ async def callback_connection(
)

# Fetch integration metadata for the success card (best-effort decoration).
# Shares the catalog cache keyed identically to get_integration() below so a
# burst of OAuth callbacks doesn't re-fetch Composio live on every redirect.
integration_label = conn.integration_key.replace("_", " ").title()
integration_logo = None
integration_url = None
try:
integration = await self.tools_service.get_integration(
provider_key=conn.provider_key.value,
integration_key=conn.integration_key,
integration_cache_key = {
"provider_key": conn.provider_key.value,
"integration_key": conn.integration_key,
"full_details": True,
}
cached_integration = await get_cache(
project_id=None, # catalog is global; not per-project
namespace="tools:catalog:integration",
key=integration_cache_key,
model=ToolCatalogIntegrationResponse,
)
if cached_integration:
integration = cached_integration.integration
else:
integration = await self.tools_service.get_integration(
provider_key=conn.provider_key.value,
integration_key=conn.integration_key,
)
if integration:
await set_cache(
project_id=None, # catalog is global; not per-project
namespace="tools:catalog:integration",
key=integration_cache_key,
value=ToolCatalogIntegrationResponse(
count=1,
integration=integration,
),
ttl=5 * 60, # 5 minutes
)
if integration:
integration_logo = integration.logo
integration_url = integration.url
Expand Down
18 changes: 18 additions & 0 deletions api/oss/src/core/auth/dtos.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from typing import Dict, List

from pydantic import BaseModel


class SSOProviderInfo(BaseModel):
id: str
slug: str
third_party_id: str


class SSOProviders(BaseModel):
providers: List[SSOProviderInfo]


class DiscoverResponse(BaseModel):
exists: bool
methods: Dict[str, bool | SSOProviders]
17 changes: 5 additions & 12 deletions api/oss/src/core/auth/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
from oss.src.utils.logging import get_module_logger
from oss.src.utils.caching import invalidate_cache

from oss.src.core.auth.dtos import DiscoverResponse

from oss.src.models.db_models import InvitationDB, ProjectDB, OrganizationDB
from oss.src.services import db_manager

Expand Down Expand Up @@ -52,7 +54,7 @@ def __init__(self):
# DISCOVERY: Determine available authentication methods
# ============================================================================

async def discover(self, email: str) -> Dict[str, Any]:
async def discover(self, email: str) -> DiscoverResponse:
"""
Discover authentication methods available for a given email.

Expand Down Expand Up @@ -287,11 +289,7 @@ async def discover(self, email: str) -> Dict[str, Any]:
# SSO enforcement: only SSO providers, no email or social
if sso_providers:
methods["sso"] = {"providers": sso_providers}
response = {
"exists": user_exists,
"methods": methods,
}
return response
return DiscoverResponse(exists=user_exists, methods=methods)

# Otherwise, include all allowed methods based on policy
# Email methods - check both specific method and wildcard
Expand Down Expand Up @@ -369,12 +367,7 @@ async def discover(self, email: str) -> Dict[str, Any]:
if sso_providers:
methods["sso"] = {"providers": sso_providers}

response = {
"exists": user_exists,
"methods": methods,
}

return response
return DiscoverResponse(exists=user_exists, methods=methods)

# ============================================================================
# AUTHENTICATION: Support authentication flows
Expand Down
Loading
Loading