Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
460ce16
docs(mobile): approvals and steering plan (grounded round-trip + phas…
ardaerzin Jul 27, 2026
410e638
docs(mobile): record approvals-plan decisions and the two standing fo…
ardaerzin Jul 27, 2026
fc3954d
feat(runner): widen the approval warm-park window to 30 minutes
ardaerzin Jul 27, 2026
07a8b1d
feat(mobile): M0.1 project liveness poll + running badge on session rows
ardaerzin Jul 27, 2026
8b2e8c9
feat(mobile): M0.2 project-wide pending-approval badges on the sessio…
ardaerzin Jul 27, 2026
70ed624
feat(api): compose the approval answer for the detached interactions …
ardaerzin Jul 27, 2026
002825d
feat(mobile): M0.3 approval-pending card + tightened records poll in …
ardaerzin Jul 27, 2026
16c177a
feat(chat): M1.1 lite references-only agent resume request builder
ardaerzin Jul 27, 2026
dd446e1
feat(chat): M1.2 resolve an agent invoke URL from workflow references
ardaerzin Jul 27, 2026
76333b4
feat(mobile): M1.3 approve/deny pending approvals from the phone
ardaerzin Jul 27, 2026
c2de8f3
feat(mobile): M1.4 stop a running session (cooperative cancel)
ardaerzin Jul 27, 2026
41b761b
fix(mobile): review fixes for the approval resume path
ardaerzin Jul 27, 2026
e37b526
docs(mobile): record the flows-lite, auth-lite, and approvals execution
ardaerzin Jul 27, 2026
fd50967
fix(mobile): pin the chat and list headers and contain scrolling
ardaerzin Jul 27, 2026
79ea6ae
fix(mobile): keep the transcript pinned to the latest message
ardaerzin Jul 27, 2026
8f00fde
fix(mobile): safe-area, input-zoom, and tap-target mechanics
ardaerzin Jul 27, 2026
37983d3
docs(mobile): M3 live-relay plan (change-notification SSE)
ardaerzin Jul 27, 2026
7730fc2
docs(mobile): record M3 decisions (paragraph-level SSE + lifecycle ev…
ardaerzin Jul 27, 2026
2cc0474
fix(mobile): bubble the transcript text and collapse thoughts by default
ardaerzin Jul 27, 2026
fa7b943
fix(sessions): name the tool in a replayed approval envelope
ardaerzin Aug 2, 2026
95b070e
fix(sessions): read the gated call through the typed interaction request
ardaerzin Aug 2, 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
2 changes: 2 additions & 0 deletions api/entrypoints/routers.py
Original file line number Diff line number Diff line change
Expand Up @@ -854,6 +854,7 @@ async def _dispatch_detached_run(*, project_id, user_id, request) -> str:
_interactions_dispatcher = InteractionsDispatcher(
workflows_service=workflows_service,
interactions_service=interactions_service,
records_service=records_service,
dispatch_fn=_dispatch_detached_run,
)

Expand Down Expand Up @@ -1098,6 +1099,7 @@ async def _dispatch_detached_run(*, project_id, user_id, request) -> str:
turns_service=session_turns_service,
sessions_service=sessions_service,
respond_task=_interactions_worker.respond_interaction,
interactions_dispatcher=_interactions_dispatcher,
)

# PLATFORM ADMIN ---------------------------------------------------------------
Expand Down
13 changes: 12 additions & 1 deletion api/entrypoints/worker_queues.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
from oss.src.core.evaluators.service import EvaluatorsService, SimpleEvaluatorsService
from oss.src.core.queries.service import QueriesService
from oss.src.core.sessions.interactions.service import SessionInteractionsService
from oss.src.core.sessions.records.service import RecordsService
from oss.src.core.testcases.service import TestcasesService
from oss.src.core.testsets.service import SimpleTestsetsService, TestsetsService
from oss.src.core.tracing.service import TracingService
Expand All @@ -67,7 +68,11 @@
QueryVariantDBE,
)
from oss.src.dbs.postgres.sessions.interactions.dao import SessionInteractionsDAO
from oss.src.dbs.postgres.shared.engine import get_transactions_engine
from oss.src.dbs.postgres.sessions.records.dao import RecordsDAO
from oss.src.dbs.postgres.shared.engine import (
get_analytics_engine,
get_transactions_engine,
)
from oss.src.dbs.postgres.testcases.dbes import TestcaseBlobDBE
from oss.src.dbs.postgres.testsets.dbes import (
TestsetArtifactDBE,
Expand Down Expand Up @@ -214,6 +219,11 @@ def _build_interactions_broker() -> tuple[AsyncBroker, int]:
environments_service.embeds_service = embeds_service

interactions_service = SessionInteractionsService(interactions_dao=interactions_dao)
# Approval answers replay the session's durable records into the resume conversation;
# records live on the analytics engine (same as the API composition in routers.py).
records_service = RecordsService(
records_dao=RecordsDAO(engine=get_analytics_engine()),
)

async def _dispatch_detached_run(*, project_id, user_id, request) -> str:
result = await workflows_service.invoke_workflow_detached(
Expand All @@ -226,6 +236,7 @@ async def _dispatch_detached_run(*, project_id, user_id, request) -> str:
interactions_dispatcher = InteractionsDispatcher(
workflows_service=workflows_service,
interactions_service=interactions_service,
records_service=records_service,
dispatch_fn=_dispatch_detached_run,
)
InteractionsWorker(broker=broker, dispatcher=interactions_dispatcher)
Expand Down
3 changes: 3 additions & 0 deletions api/oss/src/apis/fastapi/sessions/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,9 @@ class SessionInteractionsResponse(BaseModel):


class SessionInteractionRespondRequest(BaseModel):
# For a user_approval interaction the answer is {approved: bool, tool_call_id?: str,
# message?: str} — the dispatcher composes the full resume conversation server-side
# (interactions_dispatcher.compose_approval_messages). Other kinds pass through as-is.
answer: Optional[Dict[str, Any]] = None


Expand Down
19 changes: 17 additions & 2 deletions api/oss/src/apis/fastapi/sessions/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -639,10 +639,15 @@ def __init__(
interactions_service: SessionInteractionsService,
workflows_service: WorkflowsService,
respond_task: Optional[Any] = None,
# InteractionsDispatcher (typed loosely, like respond_task: the API layer does not
# import the tasks layer). When present, the no-worker respond fallback goes through
# it so both paths share ONE answer-composition implementation.
interactions_dispatcher: Optional[Any] = None,
) -> None:
self.interactions_service = interactions_service
self.workflows_service = workflows_service
self.respond_task = respond_task
self.interactions_dispatcher = interactions_dispatcher

self.router = APIRouter()

Expand Down Expand Up @@ -910,15 +915,23 @@ async def respond_interaction(
detail="Interaction is no longer pending",
)

# Enqueue onto the interactions worker when wired; otherwise fall back to an
# inline blocking invoke (keeps the route usable in minimal/test compositions).
# Enqueue onto the interactions worker when wired; otherwise fall back to the
# dispatcher directly (same answer composition, fired in-process), or as a last
# resort an inline blocking invoke (keeps minimal/test compositions usable).
if self.respond_task is not None:
await self.respond_task.kiq(
project_id=str(project_id),
user_id=str(user_id),
interaction_id=str(interaction_id),
answer=answer,
)
elif self.interactions_dispatcher is not None:
await self.interactions_dispatcher.respond(
project_id=UUID(str(project_id)),
user_id=UUID(str(user_id)),
interaction_id=interaction_id,
answer=answer,
)
else:
references = (
{
Expand Down Expand Up @@ -1672,6 +1685,7 @@ def __init__(
turns_service: SessionTurnsService,
sessions_service: SessionsService,
respond_task: Optional[Any] = None,
interactions_dispatcher: Optional[Any] = None,
) -> None:
self.streams = SessionStreamsRouter(
service=streams_service,
Expand All @@ -1682,6 +1696,7 @@ def __init__(
interactions_service=interactions_service,
workflows_service=workflows_service,
respond_task=respond_task,
interactions_dispatcher=interactions_dispatcher,
)
self.attachments = SessionAttachmentsRouter(
attachments_service=attachments_service,
Expand Down
Loading
Loading