@dudousxd/nestjs-agent-dashboard 0.9.1 → 0.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -194,12 +194,36 @@ interface DashboardSessionUser {
194
194
  roles?: string[];
195
195
  }
196
196
 
197
- /** Host hook: validates submitted credentials for the built-in login screen. */
197
+ /**
198
+ * Host hook for Mode A: validates the host's own auth on the raw request POSTed to
199
+ * `<basePath>/auth/session`. Return the session user to grant access, or `null` to deny.
200
+ */
201
+ type SessionHook = (request: unknown) => Promise<DashboardSessionUser | null> | DashboardSessionUser | null;
202
+ /** Host hook for Mode B: validates submitted credentials for the built-in login screen. */
198
203
  type LoginHook = (username: string, password: string) => Promise<DashboardSessionUser | null> | DashboardSessionUser | null;
204
+ /**
205
+ * Re-checks a LIVE session when the cookie is slid forward. Runs at most once per `ttl/2` PER
206
+ * COOKIE GENERATION — the gate is on the inbound cookie's `iat`, so every request still carrying a
207
+ * not-yet-renewed cookie would, on its own, invoke this hook again; `maybeRenewSession` de-dupes
208
+ * concurrent calls for the same session (`sub` + `iat`) into a single in-flight call, so a page
209
+ * load that fires N parallel API calls still costs exactly ONE host round-trip, not N. Still not a
210
+ * per-session-lifetime cap — this can run again every `ttl/2` as the session keeps sliding, so a
211
+ * slow or expensive check should still debounce/cache across renewal windows on its own. Return
212
+ * `false` to revoke (the cookie is cleared and the request denied). Distinct from `session`: that
213
+ * hook reads the host's auth off a fresh request, which a console XHR does not carry — this one
214
+ * receives the already-minted session.
215
+ */
216
+ type RevalidateHook = (session: DashboardSessionUser) => Promise<boolean> | boolean;
217
+ /** Which `dashboardAuth` hook(s) a resolved config was given — see `resolveDashboardAuth`. */
218
+ type AuthMode = 'session' | 'login';
199
219
  /**
200
220
  * Author-facing `dashboardAuth` option (see `AgentDashboardOptions.dashboardAuth`). Gates the
201
- * console (SPA + API) behind a built-in cookie-session login screen — no host frontend changes,
202
- * no SSO required. Mirrors `@dudousxd/nestjs-telescope`'s `dashboardAuth` Mode B (`login`).
221
+ * console (SPA + API) behind a signed session cookie. Two ways to mint that cookie: Mode A
222
+ * (`session`) — the host frontend, already carrying its own auth, POSTs to
223
+ * `<basePath>/auth/session` and the host hook decides — or Mode B (`login`) — the built-in,
224
+ * dependency-free server-rendered login screen. Mirrors `@dudousxd/nestjs-telescope`'s
225
+ * `dashboardAuth` two-mode shape. At least one of the two is required so an un-mintable gate is a
226
+ * boot error, not a silently-open (or silently-stuck) console.
203
227
  */
204
228
  interface DashboardAuthOptions {
205
229
  /** REQUIRED HMAC-SHA256 signing key (32+ bytes recommended). Missing/empty => boot error (fail closed). */
@@ -207,23 +231,36 @@ interface DashboardAuthOptions {
207
231
  /** Cookie TTL (duration string `'<n><ms|s|m|h|d>'`, e.g. `'30m'`, `'7d'`). Default `'8h'`. */
208
232
  ttl?: string;
209
233
  /**
210
- * Validates submitted username/password. Return the session user to grant access, or `null` to
211
- * deny — the login endpoint responds identically (generic failure) for an unknown user and a
212
- * wrong password, so it never reveals which one was wrong.
234
+ * Mode A: validates the host's own auth on the raw request POSTed to `<basePath>/auth/session`.
235
+ * Return the session user to grant access, or `null` to deny. No credential reaches this
236
+ * library — the host decides from whatever it already has (a header, an SSO cookie, ...).
213
237
  */
214
- login: LoginHook;
238
+ session?: SessionHook;
239
+ /**
240
+ * Mode B: validates submitted username/password. Return the session user to grant access, or
241
+ * `null` to deny — the login endpoint responds identically (generic failure) for an unknown user
242
+ * and a wrong password, so it never reveals which one was wrong.
243
+ */
244
+ login?: LoginHook;
245
+ /** Re-checks a live session on sliding renewal; see `RevalidateHook`. Not a mode — it cannot
246
+ * mint a session, only revoke one already minted by `session`/`login`. */
247
+ revalidate?: RevalidateHook;
215
248
  }
216
249
  /** Resolved, validated `dashboardAuth` config used by the guards/controller. */
217
250
  interface ResolvedDashboardAuth {
218
251
  secret: string;
219
252
  ttlMs: number;
220
- login: LoginHook;
253
+ modes: AuthMode[];
254
+ session?: SessionHook;
255
+ login?: LoginHook;
256
+ revalidate?: RevalidateHook;
221
257
  }
222
258
  /**
223
259
  * Validate + resolve `dashboardAuth`. Returns `null` when unconfigured (behavior unchanged — the
224
260
  * console stays open, front it with your own `guards`). Throws at boot (fail closed) when
225
- * configured but missing a secret or a `login` hook — the host learns immediately rather than
226
- * shipping an open or un-mintable console.
261
+ * configured but missing a secret, when neither `session` nor `login` is given, or when a given
262
+ * hook isn't a function — the host learns immediately rather than shipping an open, stuck, or
263
+ * un-mintable console.
227
264
  */
228
265
  declare function resolveDashboardAuth(options: DashboardAuthOptions | undefined): ResolvedDashboardAuth | null;
229
266
 
@@ -248,32 +285,37 @@ interface LoginFormBody {
248
285
  * built-in gates check for, so they can never be made to require the very auth they grant. Mirrors
249
286
  * `@dudousxd/nestjs-telescope`'s own auth controller staying outside its `stampGuards` call.
250
287
  *
251
- * A dependency-free, server-rendered login form (GET/POST) rather than a JSON API + client-side
252
- * screen — the bundled React SPA in this package is a built Vite artifact with no auth-aware UI of
253
- * its own, so gating the actual page navigation needs a self-contained flow that doesn't touch it.
254
- *
255
- * 404s every route when `dashboardAuth` is unconfigured (`DASHBOARD_AUTH` resolves to `null`) — no
256
- * dangling login page when the feature is off.
288
+ * Two ways to mint that cookie, matching `resolveDashboardAuth`'s modes: Mode A (`session`) — the
289
+ * host frontend, already carrying its own auth, POSTs to `<basePath>/auth/session` and the host
290
+ * hook decides — or Mode B (`login`), a dependency-free, server-rendered login form (GET/POST) —
291
+ * the bundled React SPA in this package is a built Vite artifact with no auth-aware UI of its own,
292
+ * so gating the actual page navigation needs a self-contained flow that doesn't touch it. Each
293
+ * mode's routes 404 when that mode isn't listed in `auth.modes` — the single source of truth for
294
+ * which mode(s) are configured, not hook-presence truthiness — and every route 404s outright when
295
+ * `dashboardAuth` is unconfigured (`DASHBOARD_AUTH` resolves to `null`) — no dangling login page
296
+ * when the feature is off.
257
297
  */
258
298
  declare class AgentDashboardAuthController {
259
299
  private readonly auth;
260
300
  private readonly basePath;
261
301
  private readonly logger;
262
- /** Warn once (not per request) so a broken hook can't be used to flood the logs. */
263
- private warnedLoginHookThrow;
302
+ /** Warn once PER HOOK KIND (not per request) so a broken hook can't be used to flood the logs. */
303
+ private readonly warnedHooks;
264
304
  constructor(auth: ResolvedDashboardAuth | null, basePath: string);
265
305
  loginPage(req: AuthPageRequest, res: AuthPageResponse, returnToQuery?: string, error?: string): string;
266
306
  login(body: LoginFormBody, req: unknown, res: AuthPageResponse): Promise<string>;
267
307
  logout(req: unknown, res: AuthPageResponse): string;
308
+ session(request: unknown, response: unknown): Promise<void>;
309
+ sessionRequiredPage(): string;
268
310
  /** Uniform failure: same redirect (generic error flag) whether the user is unknown or the password is wrong. */
269
311
  private redirectToLoginError;
270
312
  private currentSession;
271
313
  /**
272
- * Run the host's `login` hook defensively: a throw (sync or async) is treated as a denial
273
- * (`null`) and warn-logged ONCE, so a buggy/unreachable hook never 500s the endpoint into a
274
- * stack-trace leak nor floods the logs.
314
+ * Run a host hook (`login` or `session`) defensively: a throw (sync or async) is treated as a
315
+ * denial (`null`) and warn-logged ONCE PER KIND, so a buggy/unreachable hook never 500s the
316
+ * endpoint into a stack-trace leak nor floods the logs on repeated bad attempts.
275
317
  */
276
- private runLoginHook;
318
+ private runHook;
277
319
  private requireAuth;
278
320
  }
279
321
 
@@ -355,21 +397,36 @@ interface AgentDashboardOptions<TReq = unknown> {
355
397
  */
356
398
  approvalActorRef?: (req: TReq) => string | undefined;
357
399
  /**
358
- * Gate the console (SPA + API) behind a BUILT-IN cookie-session login screen — the simplest way
359
- * to protect `basePath` when the host has no ready-made guard (e.g. header-only auth that a
360
- * browser navigation can't attach). Omit to leave the console open, or fronted only by
361
- * {@link guards}. When set, an unauthenticated page navigation is redirected (302) to
362
- * `<basePath>/auth/login`; an unauthenticated API call gets a `401`. Composes with `guards` —
363
- * BOTH gates must pass. See `DashboardAuthOptions` for the `secret`/`ttl`/`login` shape.
400
+ * Gate the console (SPA + API) behind a built-in, signed session cookie. Two ways to mint it:
401
+ *
402
+ * - **Mode A (`session`, recommended)**: your host app already has its own auth (SSO/OIDC/
403
+ * whatever) — the host frontend, carrying that auth, POSTs to `<basePath>/auth/session`, your
404
+ * `session` hook validates the raw request and returns the session user, and this library
405
+ * mints its cookie from that. No credential this library understands ever exists; you keep
406
+ * your own identity provider as the source of truth. A page-level request with no valid
407
+ * session (and only Mode A configured) gets a small server-rendered instruction page telling
408
+ * the visitor to sign in through your host app, since there is no login page to redirect to.
409
+ * - **Mode B (`login`, standalone fallback)**: no host frontend/IdP to lean on — the console
410
+ * serves its own small, dependency-free, server-rendered login page (`GET
411
+ * <basePath>/auth/login`) and your `login` hook validates submitted username/password. A
412
+ * missing/invalid/expired session redirects a page-level request to that login page with
413
+ * `?returnTo=<original url>`.
414
+ *
415
+ * Either mode can be used alone, or both together (e.g. Mode A for normal use, Mode B as a
416
+ * break-glass fallback) — at least one is required, or `forRoot`/`forRootAsync` throws at boot
417
+ * (an un-mintable gate is a boot error, not a silently-open or silently-stuck console). An
418
+ * unauthenticated API call always gets a plain `401`, regardless of mode — the caller reads the
419
+ * status code, not HTML. Composes with `guards` — BOTH gates must pass. See
420
+ * `DashboardAuthOptions` for the `secret`/`ttl`/`session`/`login` shape.
364
421
  */
365
422
  dashboardAuth?: DashboardAuthOptions;
366
423
  }
367
424
  /**
368
- * Async variant of {@link AgentDashboardOptions}, for a `login` hook that needs injected services
369
- * (e.g. an EntityManager to check credentials against a DB). Everything else stays static/sync —
370
- * `basePath`/`apiBasePath`/`guards` are needed at module-build time (the same constraint
371
- * `@dudousxd/nestjs-telescope`'s and `@dudousxd/nestjs-media`'s own `forRootAsync`s document), so
372
- * only the auth piece itself is resolved through DI.
425
+ * Async variant of {@link AgentDashboardOptions}, for a `session`/`login` hook that needs injected
426
+ * services (e.g. an EntityManager to check credentials against a DB). Everything else stays
427
+ * static/sync — `basePath`/`apiBasePath`/`guards` are needed at module-build time (the same
428
+ * constraint `@dudousxd/nestjs-telescope`'s and `@dudousxd/nestjs-media`'s own `forRootAsync`s
429
+ * document), so only the auth piece itself is resolved through DI.
373
430
  */
374
431
  interface AgentDashboardAsyncOptions<TReq = unknown> {
375
432
  basePath?: string;
@@ -404,13 +461,15 @@ declare class AgentApiModule {
404
461
  }
405
462
  /**
406
463
  * Mounts the AI-gateway governance console: the bundled React SPA at `basePath` and its JSON + SSE
407
- * API at `apiBasePath` (default `<basePath>/api`), plus the built-in login screen's routes at
408
- * `<basePath>/auth/*` when {@link AgentDashboardOptions.dashboardAuth} is set.
464
+ * API at `apiBasePath` (default `<basePath>/api`), plus the built-in `dashboardAuth` routes at
465
+ * `<basePath>/auth/*` when {@link AgentDashboardOptions.dashboardAuth} is set — `POST
466
+ * <basePath>/auth/session` for Mode A (the host mints the session from its own auth) and/or `GET`/
467
+ * `POST <basePath>/auth/login` for Mode B (the built-in login screen).
409
468
  *
410
469
  * Import via `AgentDashboardModule.forRoot(...)` alongside your `@dudousxd/nestjs-agent` module
411
470
  * (global), which must provide `AGENT_GOVERNANCE_QUERIES` (bound by a store adapter). Front the
412
471
  * routes with the first-class `guards` option (plus `imports` for the guards' own dependencies) —
413
- * see {@link AgentDashboardOptions.guards} — and/or the built-in `dashboardAuth` cookie login.
472
+ * see {@link AgentDashboardOptions.guards} — and/or the built-in `dashboardAuth` session cookie.
414
473
  *
415
474
  * Inertia hosts: the console is a full-page app, not an Inertia page. An in-app `<Link>` visit to
416
475
  * `basePath` (an XHR carrying `X-Inertia`) is bounced with the protocol's own external-redirect
@@ -530,4 +589,4 @@ declare const DASHBOARD_APPROVAL_ACTOR_REF: unique symbol;
530
589
  */
531
590
  declare const DASHBOARD_AUTH: unique symbol;
532
591
 
533
- export { AGENT_ACTOR_DIRECTORY, AGENT_ACTOR_RESOLVER, AGENT_APPROVAL_PORT, AGENT_GOVERNANCE_QUERIES, AGENT_PRICING_STORE, type ActorSpendRowWithLabel, AgentApiController, AgentApiModule, type AgentDashboardAsyncOptions, AgentDashboardAuthController, AgentDashboardModule, type AgentDashboardMountPathsOptions, type AgentDashboardOptions, AgentUiController, DASHBOARD_API_PATH, DASHBOARD_APPROVAL_ACTOR_REF, DASHBOARD_AUTH, DASHBOARD_BASE_PATH, type DashboardAuthOptions, DashboardService, type LiveAgentEvent, type LoginHook, type ReliabilityOverview, type ResolvedDashboardAuth, type SpendOverview, type ThreadActivityRowWithLabel, type ThreadSpendRowWithLabel, type WithActorLabel, agentDashboardMountPaths, parsePriceInput, resolveDashboardAuth };
592
+ export { AGENT_ACTOR_DIRECTORY, AGENT_ACTOR_RESOLVER, AGENT_APPROVAL_PORT, AGENT_GOVERNANCE_QUERIES, AGENT_PRICING_STORE, type ActorSpendRowWithLabel, AgentApiController, AgentApiModule, type AgentDashboardAsyncOptions, AgentDashboardAuthController, AgentDashboardModule, type AgentDashboardMountPathsOptions, type AgentDashboardOptions, AgentUiController, type AuthMode, DASHBOARD_API_PATH, DASHBOARD_APPROVAL_ACTOR_REF, DASHBOARD_AUTH, DASHBOARD_BASE_PATH, type DashboardAuthOptions, DashboardService, type LiveAgentEvent, type LoginHook, type ReliabilityOverview, type ResolvedDashboardAuth, type RevalidateHook, type SessionHook, type SpendOverview, type ThreadActivityRowWithLabel, type ThreadSpendRowWithLabel, type WithActorLabel, agentDashboardMountPaths, parsePriceInput, resolveDashboardAuth };
@@ -194,12 +194,36 @@ interface DashboardSessionUser {
194
194
  roles?: string[];
195
195
  }
196
196
 
197
- /** Host hook: validates submitted credentials for the built-in login screen. */
197
+ /**
198
+ * Host hook for Mode A: validates the host's own auth on the raw request POSTed to
199
+ * `<basePath>/auth/session`. Return the session user to grant access, or `null` to deny.
200
+ */
201
+ type SessionHook = (request: unknown) => Promise<DashboardSessionUser | null> | DashboardSessionUser | null;
202
+ /** Host hook for Mode B: validates submitted credentials for the built-in login screen. */
198
203
  type LoginHook = (username: string, password: string) => Promise<DashboardSessionUser | null> | DashboardSessionUser | null;
204
+ /**
205
+ * Re-checks a LIVE session when the cookie is slid forward. Runs at most once per `ttl/2` PER
206
+ * COOKIE GENERATION — the gate is on the inbound cookie's `iat`, so every request still carrying a
207
+ * not-yet-renewed cookie would, on its own, invoke this hook again; `maybeRenewSession` de-dupes
208
+ * concurrent calls for the same session (`sub` + `iat`) into a single in-flight call, so a page
209
+ * load that fires N parallel API calls still costs exactly ONE host round-trip, not N. Still not a
210
+ * per-session-lifetime cap — this can run again every `ttl/2` as the session keeps sliding, so a
211
+ * slow or expensive check should still debounce/cache across renewal windows on its own. Return
212
+ * `false` to revoke (the cookie is cleared and the request denied). Distinct from `session`: that
213
+ * hook reads the host's auth off a fresh request, which a console XHR does not carry — this one
214
+ * receives the already-minted session.
215
+ */
216
+ type RevalidateHook = (session: DashboardSessionUser) => Promise<boolean> | boolean;
217
+ /** Which `dashboardAuth` hook(s) a resolved config was given — see `resolveDashboardAuth`. */
218
+ type AuthMode = 'session' | 'login';
199
219
  /**
200
220
  * Author-facing `dashboardAuth` option (see `AgentDashboardOptions.dashboardAuth`). Gates the
201
- * console (SPA + API) behind a built-in cookie-session login screen — no host frontend changes,
202
- * no SSO required. Mirrors `@dudousxd/nestjs-telescope`'s `dashboardAuth` Mode B (`login`).
221
+ * console (SPA + API) behind a signed session cookie. Two ways to mint that cookie: Mode A
222
+ * (`session`) — the host frontend, already carrying its own auth, POSTs to
223
+ * `<basePath>/auth/session` and the host hook decides — or Mode B (`login`) — the built-in,
224
+ * dependency-free server-rendered login screen. Mirrors `@dudousxd/nestjs-telescope`'s
225
+ * `dashboardAuth` two-mode shape. At least one of the two is required so an un-mintable gate is a
226
+ * boot error, not a silently-open (or silently-stuck) console.
203
227
  */
204
228
  interface DashboardAuthOptions {
205
229
  /** REQUIRED HMAC-SHA256 signing key (32+ bytes recommended). Missing/empty => boot error (fail closed). */
@@ -207,23 +231,36 @@ interface DashboardAuthOptions {
207
231
  /** Cookie TTL (duration string `'<n><ms|s|m|h|d>'`, e.g. `'30m'`, `'7d'`). Default `'8h'`. */
208
232
  ttl?: string;
209
233
  /**
210
- * Validates submitted username/password. Return the session user to grant access, or `null` to
211
- * deny — the login endpoint responds identically (generic failure) for an unknown user and a
212
- * wrong password, so it never reveals which one was wrong.
234
+ * Mode A: validates the host's own auth on the raw request POSTed to `<basePath>/auth/session`.
235
+ * Return the session user to grant access, or `null` to deny. No credential reaches this
236
+ * library — the host decides from whatever it already has (a header, an SSO cookie, ...).
213
237
  */
214
- login: LoginHook;
238
+ session?: SessionHook;
239
+ /**
240
+ * Mode B: validates submitted username/password. Return the session user to grant access, or
241
+ * `null` to deny — the login endpoint responds identically (generic failure) for an unknown user
242
+ * and a wrong password, so it never reveals which one was wrong.
243
+ */
244
+ login?: LoginHook;
245
+ /** Re-checks a live session on sliding renewal; see `RevalidateHook`. Not a mode — it cannot
246
+ * mint a session, only revoke one already minted by `session`/`login`. */
247
+ revalidate?: RevalidateHook;
215
248
  }
216
249
  /** Resolved, validated `dashboardAuth` config used by the guards/controller. */
217
250
  interface ResolvedDashboardAuth {
218
251
  secret: string;
219
252
  ttlMs: number;
220
- login: LoginHook;
253
+ modes: AuthMode[];
254
+ session?: SessionHook;
255
+ login?: LoginHook;
256
+ revalidate?: RevalidateHook;
221
257
  }
222
258
  /**
223
259
  * Validate + resolve `dashboardAuth`. Returns `null` when unconfigured (behavior unchanged — the
224
260
  * console stays open, front it with your own `guards`). Throws at boot (fail closed) when
225
- * configured but missing a secret or a `login` hook — the host learns immediately rather than
226
- * shipping an open or un-mintable console.
261
+ * configured but missing a secret, when neither `session` nor `login` is given, or when a given
262
+ * hook isn't a function — the host learns immediately rather than shipping an open, stuck, or
263
+ * un-mintable console.
227
264
  */
228
265
  declare function resolveDashboardAuth(options: DashboardAuthOptions | undefined): ResolvedDashboardAuth | null;
229
266
 
@@ -248,32 +285,37 @@ interface LoginFormBody {
248
285
  * built-in gates check for, so they can never be made to require the very auth they grant. Mirrors
249
286
  * `@dudousxd/nestjs-telescope`'s own auth controller staying outside its `stampGuards` call.
250
287
  *
251
- * A dependency-free, server-rendered login form (GET/POST) rather than a JSON API + client-side
252
- * screen — the bundled React SPA in this package is a built Vite artifact with no auth-aware UI of
253
- * its own, so gating the actual page navigation needs a self-contained flow that doesn't touch it.
254
- *
255
- * 404s every route when `dashboardAuth` is unconfigured (`DASHBOARD_AUTH` resolves to `null`) — no
256
- * dangling login page when the feature is off.
288
+ * Two ways to mint that cookie, matching `resolveDashboardAuth`'s modes: Mode A (`session`) — the
289
+ * host frontend, already carrying its own auth, POSTs to `<basePath>/auth/session` and the host
290
+ * hook decides — or Mode B (`login`), a dependency-free, server-rendered login form (GET/POST) —
291
+ * the bundled React SPA in this package is a built Vite artifact with no auth-aware UI of its own,
292
+ * so gating the actual page navigation needs a self-contained flow that doesn't touch it. Each
293
+ * mode's routes 404 when that mode isn't listed in `auth.modes` — the single source of truth for
294
+ * which mode(s) are configured, not hook-presence truthiness — and every route 404s outright when
295
+ * `dashboardAuth` is unconfigured (`DASHBOARD_AUTH` resolves to `null`) — no dangling login page
296
+ * when the feature is off.
257
297
  */
258
298
  declare class AgentDashboardAuthController {
259
299
  private readonly auth;
260
300
  private readonly basePath;
261
301
  private readonly logger;
262
- /** Warn once (not per request) so a broken hook can't be used to flood the logs. */
263
- private warnedLoginHookThrow;
302
+ /** Warn once PER HOOK KIND (not per request) so a broken hook can't be used to flood the logs. */
303
+ private readonly warnedHooks;
264
304
  constructor(auth: ResolvedDashboardAuth | null, basePath: string);
265
305
  loginPage(req: AuthPageRequest, res: AuthPageResponse, returnToQuery?: string, error?: string): string;
266
306
  login(body: LoginFormBody, req: unknown, res: AuthPageResponse): Promise<string>;
267
307
  logout(req: unknown, res: AuthPageResponse): string;
308
+ session(request: unknown, response: unknown): Promise<void>;
309
+ sessionRequiredPage(): string;
268
310
  /** Uniform failure: same redirect (generic error flag) whether the user is unknown or the password is wrong. */
269
311
  private redirectToLoginError;
270
312
  private currentSession;
271
313
  /**
272
- * Run the host's `login` hook defensively: a throw (sync or async) is treated as a denial
273
- * (`null`) and warn-logged ONCE, so a buggy/unreachable hook never 500s the endpoint into a
274
- * stack-trace leak nor floods the logs.
314
+ * Run a host hook (`login` or `session`) defensively: a throw (sync or async) is treated as a
315
+ * denial (`null`) and warn-logged ONCE PER KIND, so a buggy/unreachable hook never 500s the
316
+ * endpoint into a stack-trace leak nor floods the logs on repeated bad attempts.
275
317
  */
276
- private runLoginHook;
318
+ private runHook;
277
319
  private requireAuth;
278
320
  }
279
321
 
@@ -355,21 +397,36 @@ interface AgentDashboardOptions<TReq = unknown> {
355
397
  */
356
398
  approvalActorRef?: (req: TReq) => string | undefined;
357
399
  /**
358
- * Gate the console (SPA + API) behind a BUILT-IN cookie-session login screen — the simplest way
359
- * to protect `basePath` when the host has no ready-made guard (e.g. header-only auth that a
360
- * browser navigation can't attach). Omit to leave the console open, or fronted only by
361
- * {@link guards}. When set, an unauthenticated page navigation is redirected (302) to
362
- * `<basePath>/auth/login`; an unauthenticated API call gets a `401`. Composes with `guards` —
363
- * BOTH gates must pass. See `DashboardAuthOptions` for the `secret`/`ttl`/`login` shape.
400
+ * Gate the console (SPA + API) behind a built-in, signed session cookie. Two ways to mint it:
401
+ *
402
+ * - **Mode A (`session`, recommended)**: your host app already has its own auth (SSO/OIDC/
403
+ * whatever) — the host frontend, carrying that auth, POSTs to `<basePath>/auth/session`, your
404
+ * `session` hook validates the raw request and returns the session user, and this library
405
+ * mints its cookie from that. No credential this library understands ever exists; you keep
406
+ * your own identity provider as the source of truth. A page-level request with no valid
407
+ * session (and only Mode A configured) gets a small server-rendered instruction page telling
408
+ * the visitor to sign in through your host app, since there is no login page to redirect to.
409
+ * - **Mode B (`login`, standalone fallback)**: no host frontend/IdP to lean on — the console
410
+ * serves its own small, dependency-free, server-rendered login page (`GET
411
+ * <basePath>/auth/login`) and your `login` hook validates submitted username/password. A
412
+ * missing/invalid/expired session redirects a page-level request to that login page with
413
+ * `?returnTo=<original url>`.
414
+ *
415
+ * Either mode can be used alone, or both together (e.g. Mode A for normal use, Mode B as a
416
+ * break-glass fallback) — at least one is required, or `forRoot`/`forRootAsync` throws at boot
417
+ * (an un-mintable gate is a boot error, not a silently-open or silently-stuck console). An
418
+ * unauthenticated API call always gets a plain `401`, regardless of mode — the caller reads the
419
+ * status code, not HTML. Composes with `guards` — BOTH gates must pass. See
420
+ * `DashboardAuthOptions` for the `secret`/`ttl`/`session`/`login` shape.
364
421
  */
365
422
  dashboardAuth?: DashboardAuthOptions;
366
423
  }
367
424
  /**
368
- * Async variant of {@link AgentDashboardOptions}, for a `login` hook that needs injected services
369
- * (e.g. an EntityManager to check credentials against a DB). Everything else stays static/sync —
370
- * `basePath`/`apiBasePath`/`guards` are needed at module-build time (the same constraint
371
- * `@dudousxd/nestjs-telescope`'s and `@dudousxd/nestjs-media`'s own `forRootAsync`s document), so
372
- * only the auth piece itself is resolved through DI.
425
+ * Async variant of {@link AgentDashboardOptions}, for a `session`/`login` hook that needs injected
426
+ * services (e.g. an EntityManager to check credentials against a DB). Everything else stays
427
+ * static/sync — `basePath`/`apiBasePath`/`guards` are needed at module-build time (the same
428
+ * constraint `@dudousxd/nestjs-telescope`'s and `@dudousxd/nestjs-media`'s own `forRootAsync`s
429
+ * document), so only the auth piece itself is resolved through DI.
373
430
  */
374
431
  interface AgentDashboardAsyncOptions<TReq = unknown> {
375
432
  basePath?: string;
@@ -404,13 +461,15 @@ declare class AgentApiModule {
404
461
  }
405
462
  /**
406
463
  * Mounts the AI-gateway governance console: the bundled React SPA at `basePath` and its JSON + SSE
407
- * API at `apiBasePath` (default `<basePath>/api`), plus the built-in login screen's routes at
408
- * `<basePath>/auth/*` when {@link AgentDashboardOptions.dashboardAuth} is set.
464
+ * API at `apiBasePath` (default `<basePath>/api`), plus the built-in `dashboardAuth` routes at
465
+ * `<basePath>/auth/*` when {@link AgentDashboardOptions.dashboardAuth} is set — `POST
466
+ * <basePath>/auth/session` for Mode A (the host mints the session from its own auth) and/or `GET`/
467
+ * `POST <basePath>/auth/login` for Mode B (the built-in login screen).
409
468
  *
410
469
  * Import via `AgentDashboardModule.forRoot(...)` alongside your `@dudousxd/nestjs-agent` module
411
470
  * (global), which must provide `AGENT_GOVERNANCE_QUERIES` (bound by a store adapter). Front the
412
471
  * routes with the first-class `guards` option (plus `imports` for the guards' own dependencies) —
413
- * see {@link AgentDashboardOptions.guards} — and/or the built-in `dashboardAuth` cookie login.
472
+ * see {@link AgentDashboardOptions.guards} — and/or the built-in `dashboardAuth` session cookie.
414
473
  *
415
474
  * Inertia hosts: the console is a full-page app, not an Inertia page. An in-app `<Link>` visit to
416
475
  * `basePath` (an XHR carrying `X-Inertia`) is bounced with the protocol's own external-redirect
@@ -530,4 +589,4 @@ declare const DASHBOARD_APPROVAL_ACTOR_REF: unique symbol;
530
589
  */
531
590
  declare const DASHBOARD_AUTH: unique symbol;
532
591
 
533
- export { AGENT_ACTOR_DIRECTORY, AGENT_ACTOR_RESOLVER, AGENT_APPROVAL_PORT, AGENT_GOVERNANCE_QUERIES, AGENT_PRICING_STORE, type ActorSpendRowWithLabel, AgentApiController, AgentApiModule, type AgentDashboardAsyncOptions, AgentDashboardAuthController, AgentDashboardModule, type AgentDashboardMountPathsOptions, type AgentDashboardOptions, AgentUiController, DASHBOARD_API_PATH, DASHBOARD_APPROVAL_ACTOR_REF, DASHBOARD_AUTH, DASHBOARD_BASE_PATH, type DashboardAuthOptions, DashboardService, type LiveAgentEvent, type LoginHook, type ReliabilityOverview, type ResolvedDashboardAuth, type SpendOverview, type ThreadActivityRowWithLabel, type ThreadSpendRowWithLabel, type WithActorLabel, agentDashboardMountPaths, parsePriceInput, resolveDashboardAuth };
592
+ export { AGENT_ACTOR_DIRECTORY, AGENT_ACTOR_RESOLVER, AGENT_APPROVAL_PORT, AGENT_GOVERNANCE_QUERIES, AGENT_PRICING_STORE, type ActorSpendRowWithLabel, AgentApiController, AgentApiModule, type AgentDashboardAsyncOptions, AgentDashboardAuthController, AgentDashboardModule, type AgentDashboardMountPathsOptions, type AgentDashboardOptions, AgentUiController, type AuthMode, DASHBOARD_API_PATH, DASHBOARD_APPROVAL_ACTOR_REF, DASHBOARD_AUTH, DASHBOARD_BASE_PATH, type DashboardAuthOptions, DashboardService, type LiveAgentEvent, type LoginHook, type ReliabilityOverview, type ResolvedDashboardAuth, type RevalidateHook, type SessionHook, type SpendOverview, type ThreadActivityRowWithLabel, type ThreadSpendRowWithLabel, type WithActorLabel, agentDashboardMountPaths, parsePriceInput, resolveDashboardAuth };