1818from druks .api .exceptions import AgentApiError
1919from druks .api .runs import router as runs_router
2020from druks .api .subjects import router as subjects_router
21+ from druks .apps .loader import iter_apps , load
22+ from druks .apps .routes import router as apps_router
2123from druks .browser .exceptions import BrowserApiError
2224from druks .browser .routes import router as browser_sessions_router
2325from druks .core .templates import render_page
2426from druks .database import configure_session , create_engine_from_url , db_session , session_scope
2527from druks .durable .engine import init_dbos , launch , shutdown
2628from druks .durable .exceptions import AgentCallNotFound
2729from druks .events .routes import router as events_router
28- from druks .extensions .loader import iter_extensions , load
29- from druks .extensions .routes import router as extensions_router
3030from druks .harnesses .routes import router as harness_connection_router
3131from druks .mcp .catalog import load_mcp_catalog
3232from druks .mcp .gateway import exceptions as gate_errors
@@ -82,14 +82,14 @@ async def lifespan(app: FastAPI) -> AsyncIterator[None]:
8282 # reach here — they drive DBOS through their own fixtures.
8383 init_dbos ()
8484 launch ()
85- # Each extension converges its own runtime state (e.g. schedules) here, after
86- # DBOS is live. A failing hook is logged, not fatal — one extension can't wedge boot.
87- for extension in iter_extensions ():
85+ # Each app converges its own runtime state (e.g. schedules) here, after
86+ # DBOS is live. A failing hook is logged, not fatal — one app can't wedge boot.
87+ for registered_app in iter_apps ():
8888 try :
89- await extension .on_startup ()
89+ await registered_app .on_startup ()
9090 except Exception :
9191 logging .getLogger (__name__ ).exception (
92- "extension %r on_startup failed" , extension .name
92+ "app %r on_startup failed" , registered_app .name
9393 )
9494
9595 try :
@@ -108,7 +108,7 @@ async def _release_db_session() -> AsyncIterator[None]:
108108 without committing, so this is the commit boundary. FastAPI runs this
109109 yield-dependency's teardown in the *same* asyncio task as the endpoint
110110 (the scoped_session is keyed by that task), so it acts on exactly the
111- session this request opened. Frontend responses (the SPA, an extension 's
111+ session this request opened. Frontend responses (the SPA, an app 's
112112 dist/) run app dependencies too, so only touch the registry when the
113113 request actually opened a session — never open one just to commit nothing.
114114 """
@@ -248,8 +248,8 @@ async def _unhandled_exception_handler(
248248 )
249249
250250
251- # Platform-core routers, mounted by hand at their own prefixes. Extension routers
252- # (core, ship, usage, …) are discovered and mounted under /api/<extension > by load().
251+ # Platform-core routers, mounted by hand at their own prefixes. App routers
252+ # (core, ship, usage, …) are discovered and mounted under /api/<app > by load().
253253# /api sits behind the identity gate except the identity/connection surface and
254254# the health probe; /_external routes carry their own authentication. The
255255# boundary test pins the split. The auth and harness-connection routers mount
@@ -265,7 +265,7 @@ async def _unhandled_exception_handler(
265265app .include_router (harness_connection_router )
266266app .include_router (browser_sessions_router )
267267app .include_router (settings_router , dependencies = _identity_gate )
268- app .include_router (extensions_router , dependencies = _identity_gate )
268+ app .include_router (apps_router , dependencies = _identity_gate )
269269app .include_router (service_identities_router , dependencies = _identity_gate )
270270app .include_router (oauth_router , dependencies = _identity_gate )
271271app .include_router (skills_router , dependencies = _identity_gate )
@@ -279,8 +279,8 @@ async def _unhandled_exception_handler(
279279load (app )
280280
281281# Tools derive from every route tagged "agent", so the endpoint composes
282- # after load() — an extension 's tagged routes join tools/list too.
283- from druks .mcp .app import create_mcp_app # noqa: E402
282+ # after load() — an app 's tagged routes join tools/list too.
283+ from druks .mcp .server import create_mcp_app # noqa: E402
284284
285285# A bare Route at exactly /mcp (a Mount would 307 the no-slash path); PATs
286286# authenticate it, so it sits outside the identity gate.
@@ -320,17 +320,17 @@ async def mcp_not_found(path: str) -> None:
320320
321321
322322def serve_spa (app : FastAPI , dist : Path = _SPA_DIST ) -> None :
323- """Serve the platform's own dashboard the way an extension 's ``dist/`` is
323+ """Serve the platform's own dashboard the way an app 's ``dist/`` is
324324 served — FastAPI's low-priority frontend routes, so every API path (and every
325- extension frontend, registered earlier) wins, and unknown paths fall back to
325+ app frontend, registered earlier) wins, and unknown paths fall back to
326326 index.html for client-side routing. A bare pip install ships no SPA build;
327327 then the API serves JSON only."""
328328 if (dist / "index.html" ).is_file ():
329329 app .frontend ("/" , directory = dist )
330330
331331
332332class SpaCacheControl :
333- """Cache policy for the served frontends (the SPA, an extension 's dist/) —
333+ """Cache policy for the served frontends (the SPA, an app 's dist/) —
334334 it lives with their server, not the edge proxy. Vite fingerprints every
335335 asset filename (index-<hash>.js), so an asset URL's content never changes:
336336 cache it forever. index.html is the un-fingerprinted entry point that
@@ -350,7 +350,7 @@ async def __call__(self, scope: Any, receive: Any, send: Any) -> None:
350350 await self .app (scope , receive , send )
351351 return
352352 path = scope ["path" ]
353- # The SPA's /assets/* and an extension 's /app/<name>/assets/* — never
353+ # The SPA's /assets/* and an app 's /app/<name>/assets/* — never
354354 # /api/*, whose responses must not inherit frontend cache policy.
355355 fingerprinted = path .startswith ("/assets/" ) or (
356356 path .startswith ("/app/" ) and "/assets/" in path
0 commit comments