@dudousxd/nestjs-agent-dashboard 0.7.0 → 0.9.0

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.
@@ -1,7 +1,7 @@
1
- import { ActorSpendRow, AgentGovernanceQueries, ActorDirectory, AgentPricingStore, AgentApprovalPort, GovernanceRange, ModelSpendRow, UsageTrendPoint, ThreadSpendRow, RunMetrics, RunAgentBreakdownRow, RunErrorBreakdownRow, RunTrendPoint, RecentRunRow, ToolCallActivityRow, PendingApprovalRow, ToolStatRow, ThreadActivityRow, CurrentModelPrice, ActorResolver, ModelPriceInput } from '@dudousxd/nestjs-agent-core';
1
+ import { ActorSpendRow, AgentGovernanceQueries, ActorDirectory, AgentPricingStore, AgentApprovalPort, GovernanceRange, ModelSpendRow, UsageTrendPoint, ThreadSpendRow, RunMetrics, RunAgentBreakdownRow, RunErrorBreakdownRow, RunTrendPoint, RecentRunRow, ToolCallActivityRow, GovernancePageQuery, ToolCallWhere, GovernancePage, RunWhere, PendingApprovalRow, ToolStatRow, ThreadActivityRow, ThreadWhere, CurrentModelPrice, ActorResolver, ModelPriceInput } from '@dudousxd/nestjs-agent-core';
2
2
  export { ActorDirectory } from '@dudousxd/nestjs-agent-core';
3
3
  import { Observable } from 'rxjs';
4
- import { DynamicModule, Type, CanActivate, StreamableFile } from '@nestjs/common';
4
+ import { DynamicModule, Type, CanActivate, Provider, InjectionToken, OptionalFactoryDependency, StreamableFile } from '@nestjs/common';
5
5
 
6
6
  /** An actor-scoped row decorated with its resolved display label — `null` when unbound or unresolved. */
7
7
  interface WithActorLabel {
@@ -61,6 +61,10 @@ declare class DashboardService {
61
61
  recentRuns(limit: number): Promise<RecentRunRow[]>;
62
62
  /** Most recent tool calls (status/type/thread) for the Runs & tools activity feed. */
63
63
  recentToolCalls(limit: number): Promise<ToolCallActivityRow[]>;
64
+ /** Paged, filterable tool calls for the Runs & tools activity table. */
65
+ toolCallsPage(query: GovernancePageQuery<ToolCallWhere>): Promise<GovernancePage<ToolCallActivityRow>>;
66
+ /** Paged, filterable runs for the Reliability recent-runs table. */
67
+ runsPage(query: GovernancePageQuery<RunWhere>): Promise<GovernancePage<RecentRunRow>>;
64
68
  /** Tool calls sitting `pending_approval`, oldest first, for the cross-thread approvals inbox. */
65
69
  pendingApprovals(limit: number): Promise<PendingApprovalRow[]>;
66
70
  /** Per-tool call/failure/rejection/latency rollup for a day range, for the Tools section. */
@@ -76,6 +80,11 @@ declare class DashboardService {
76
80
  decideApproval(toolCallId: string, body: unknown, executedByRef: string | undefined): Promise<void>;
77
81
  /** Most recent threads with rolled-up message/token counts. */
78
82
  recentThreads(limit: number): Promise<ThreadActivityRowWithLabel[]>;
83
+ /**
84
+ * Paged, filterable threads for the Runs & tools threads table — actor-labeled the same way
85
+ * {@link recentThreads} is.
86
+ */
87
+ threadsPage(query: GovernancePageQuery<ThreadWhere>): Promise<GovernancePage<ThreadActivityRowWithLabel>>;
79
88
  /**
80
89
  * Decorate actor-scoped rows with `actorLabel`, batching the distinct `actorRef`s into ONE
81
90
  * {@link ActorDirectory.resolveDisplay} call per response. `null` for every row when no directory
@@ -103,6 +112,10 @@ declare class DashboardService {
103
112
  /**
104
113
  * JSON + SSE API consumed by the AI-gateway console SPA. Mounted at `apiBasePath` (set by
105
114
  * `RouterModule` in {@link AgentDashboardModule.forRoot}), so the controller routes are relative.
115
+ *
116
+ * `@UseGuards(DashboardAuthGuard)` is a NO-OP unless the host configured `dashboardAuth` — see
117
+ * that guard's own doc. Host `guards` (see {@link AgentDashboardOptions.guards}) are APPENDED onto
118
+ * this baseline by `stampGuards` (`./guards.js`), never replacing it.
106
119
  */
107
120
  declare class AgentApiController {
108
121
  private readonly dashboard;
@@ -117,8 +130,20 @@ declare class AgentApiController {
117
130
  reliability(from?: string, to?: string): Promise<ReliabilityOverview>;
118
131
  /** Most recent runs (default 50, max 200) for the Reliability recent-runs table. */
119
132
  runs(limit?: string): Promise<RecentRunRow[]>;
133
+ /**
134
+ * Paged, filterable runs for the Reliability recent-runs table. `page` (default 1), `limit`
135
+ * (default 25, max 200), and `where[agentName]`/`where[status]`/`where[errorCode]`/
136
+ * `where[fromDay]`/`where[toDay]` (`YYYY-MM-DD`) — an unknown `where` field 400s naming it.
137
+ */
138
+ runsPage(page?: string, limit?: string, where?: Record<string, string>): Promise<GovernancePage<RecentRunRow>>;
120
139
  /** Most recent tool calls (default 50, max 200) for the activity feed. */
121
140
  toolCalls(limit?: string): Promise<ToolCallActivityRow[]>;
141
+ /**
142
+ * Paged, filterable tool calls for the Runs & tools activity table. `page` (default 1), `limit`
143
+ * (default 25, max 200), and `where[toolName]`/`where[toolType]`/`where[status]`/`where[threadId]`/
144
+ * `where[fromDay]`/`where[toDay]` (`YYYY-MM-DD`) — an unknown `where` field 400s naming it.
145
+ */
146
+ toolCallsPage(page?: string, limit?: string, where?: Record<string, string>): Promise<GovernancePage<ToolCallActivityRow>>;
122
147
  /** Tool calls sitting `pending_approval` (default 50, max 200), oldest first — the approvals inbox. */
123
148
  approvals(limit?: string): Promise<PendingApprovalRow[]>;
124
149
  /**
@@ -139,6 +164,12 @@ declare class AgentApiController {
139
164
  tools(from?: string, to?: string): Promise<ToolStatRow[]>;
140
165
  /** Most recent threads (default 50, max 200) with rolled-up counts. */
141
166
  threads(limit?: string): Promise<ThreadActivityRowWithLabel[]>;
167
+ /**
168
+ * Paged, filterable threads for the Runs & tools threads table. `page` (default 1), `limit`
169
+ * (default 25, max 200), and `where[actorRef]`/`where[title]` (case-insensitive substring)/
170
+ * `where[fromDay]`/`where[toDay]` (`YYYY-MM-DD`) — an unknown `where` field 400s naming it.
171
+ */
172
+ threadsPage(page?: string, limit?: string, where?: Record<string, string>): Promise<GovernancePage<ThreadActivityRowWithLabel>>;
142
173
  /**
143
174
  * Current price row per model, for the pricing tab. 501s (via `DashboardService.listPrices`) when
144
175
  * no `AGENT_PRICING_STORE` is bound.
@@ -156,6 +187,96 @@ declare class AgentApiController {
156
187
  }>;
157
188
  }
158
189
 
190
+ /** The session user the host's `login` hook returns to mint a cookie. */
191
+ interface DashboardSessionUser {
192
+ id: string;
193
+ name?: string;
194
+ roles?: string[];
195
+ }
196
+
197
+ /** Host hook: validates submitted credentials for the built-in login screen. */
198
+ type LoginHook = (username: string, password: string) => Promise<DashboardSessionUser | null> | DashboardSessionUser | null;
199
+ /**
200
+ * 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`).
203
+ */
204
+ interface DashboardAuthOptions {
205
+ /** REQUIRED HMAC-SHA256 signing key (32+ bytes recommended). Missing/empty => boot error (fail closed). */
206
+ secret: string;
207
+ /** Cookie TTL (duration string `'<n><ms|s|m|h|d>'`, e.g. `'30m'`, `'7d'`). Default `'8h'`. */
208
+ ttl?: string;
209
+ /**
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.
213
+ */
214
+ login: LoginHook;
215
+ }
216
+ /** Resolved, validated `dashboardAuth` config used by the guards/controller. */
217
+ interface ResolvedDashboardAuth {
218
+ secret: string;
219
+ ttlMs: number;
220
+ login: LoginHook;
221
+ }
222
+ /**
223
+ * Validate + resolve `dashboardAuth`. Returns `null` when unconfigured (behavior unchanged — the
224
+ * 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.
227
+ */
228
+ declare function resolveDashboardAuth(options: DashboardAuthOptions | undefined): ResolvedDashboardAuth | null;
229
+
230
+ /** The slice of the platform request this controller reads — structural, no Express/Fastify dependency. */
231
+ interface AuthPageRequest {
232
+ headers: Record<string, string | string[] | undefined>;
233
+ }
234
+ /** The slice of the platform response this controller writes (passthrough mode — Nest still sends). */
235
+ interface AuthPageResponse {
236
+ status(code: number): unknown;
237
+ setHeader(name: string, value: string): unknown;
238
+ }
239
+ interface LoginFormBody {
240
+ username?: unknown;
241
+ password?: unknown;
242
+ returnTo?: unknown;
243
+ }
244
+ /**
245
+ * Mounted directly on `AgentDashboardModule` at `<basePath>/auth/*` — a SEPARATE controller from
246
+ * `AgentUiController`/`AgentApiController` so it carries neither `DashboardAuthPageGuard`,
247
+ * `DashboardAuthGuard`, nor any host `guards`: these routes MINT (and clear) the session cookie the
248
+ * built-in gates check for, so they can never be made to require the very auth they grant. Mirrors
249
+ * `@dudousxd/nestjs-telescope`'s own auth controller staying outside its `stampGuards` call.
250
+ *
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.
257
+ */
258
+ declare class AgentDashboardAuthController {
259
+ private readonly auth;
260
+ private readonly basePath;
261
+ private readonly logger;
262
+ /** Warn once (not per request) so a broken hook can't be used to flood the logs. */
263
+ private warnedLoginHookThrow;
264
+ constructor(auth: ResolvedDashboardAuth | null, basePath: string);
265
+ loginPage(req: AuthPageRequest, res: AuthPageResponse, returnToQuery?: string, error?: string): string;
266
+ login(body: LoginFormBody, req: unknown, res: AuthPageResponse): Promise<string>;
267
+ logout(req: unknown, res: AuthPageResponse): string;
268
+ /** Uniform failure: same redirect (generic error flag) whether the user is unknown or the password is wrong. */
269
+ private redirectToLoginError;
270
+ private currentSession;
271
+ /**
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.
275
+ */
276
+ private runLoginHook;
277
+ private requireAuth;
278
+ }
279
+
159
280
  /** Same shape {@link AgentDashboardModule.forRoot} accepts — kept local so this stays a pure, DI-free helper. */
160
281
  interface AgentDashboardMountPathsOptions {
161
282
  basePath?: string;
@@ -207,10 +328,9 @@ interface AgentDashboardOptions<TReq = unknown> {
207
328
  apiBasePath?: string;
208
329
  /**
209
330
  * Guard classes fronting BOTH dashboard controllers (the SPA at `basePath` and its JSON API at
210
- * `apiBasePath`). Stamped onto each controller via `@nestjs/common`'s own `@UseGuards` metadata key
211
- * — REPLACE semantics, so a second `forRoot(...)` call overwrites (not appends to) whatever a prior
212
- * call stamped, same as re-applying `@UseGuards` by hand. Omit to leave the routes unguarded (the
213
- * host fronts them another way, e.g. a global guard or reverse-proxy auth).
331
+ * `apiBasePath`) — APPENDED onto the built-in `dashboardAuth` gate (a no-op unless configured;
332
+ * see {@link dashboardAuth}), so a request must pass BOTH. Omit to leave the routes gated by
333
+ * `dashboardAuth` alone (or fully open when that's unset too).
214
334
  *
215
335
  * A guard's own DEPENDENCIES resolve from this module's `imports` (see {@link imports}) — the
216
336
  * dashboard module has no application context of its own to pull them from otherwise.
@@ -234,6 +354,36 @@ interface AgentDashboardOptions<TReq = unknown> {
234
354
  * override wins outright, with no resolver fallback.
235
355
  */
236
356
  approvalActorRef?: (req: TReq) => string | undefined;
357
+ /**
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.
364
+ */
365
+ dashboardAuth?: DashboardAuthOptions;
366
+ }
367
+ /**
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.
373
+ */
374
+ interface AgentDashboardAsyncOptions<TReq = unknown> {
375
+ basePath?: string;
376
+ apiBasePath?: string;
377
+ guards?: Type<CanActivate>[];
378
+ imports?: DynamicModule['imports'];
379
+ approvalActorRef?: (req: TReq) => string | undefined;
380
+ /** Providers injected into `useDashboardAuth`, in order. Shared with a guard class's own deps via `imports`. */
381
+ inject?: Array<InjectionToken | OptionalFactoryDependency>;
382
+ /**
383
+ * Build the `dashboardAuth` config from injected deps, or return `undefined` to leave the
384
+ * console open (or fronted only by `guards`). See {@link AgentDashboardOptions.dashboardAuth}.
385
+ */
386
+ useDashboardAuth: (...deps: any[]) => DashboardAuthOptions | undefined | Promise<DashboardAuthOptions | undefined>;
237
387
  }
238
388
  /**
239
389
  * Holds the JSON API + SSE controller and its read service, mounted on its own path by `forRoot`.
@@ -248,16 +398,19 @@ declare class AgentApiModule {
248
398
  imports?: DynamicModule['imports'];
249
399
  guards?: Type<CanActivate>[];
250
400
  approvalActorRef?: (req: TReq) => string | undefined;
401
+ /** Provider for `DASHBOARD_AUTH` — a `useValue` (`forRoot`) or a `useFactory` (`forRootAsync`). */
402
+ dashboardAuth: Provider;
251
403
  }): DynamicModule;
252
404
  }
253
405
  /**
254
406
  * Mounts the AI-gateway governance console: the bundled React SPA at `basePath` and its JSON + SSE
255
- * API at `apiBasePath` (default `<basePath>/api`).
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.
256
409
  *
257
410
  * Import via `AgentDashboardModule.forRoot(...)` alongside your `@dudousxd/nestjs-agent` module
258
411
  * (global), which must provide `AGENT_GOVERNANCE_QUERIES` (bound by a store adapter). Front the
259
412
  * routes with the first-class `guards` option (plus `imports` for the guards' own dependencies) —
260
- * see {@link AgentDashboardOptions.guards}.
413
+ * see {@link AgentDashboardOptions.guards} — and/or the built-in `dashboardAuth` cookie login.
261
414
  *
262
415
  * Inertia hosts: the console is a full-page app, not an Inertia page. An in-app `<Link>` visit to
263
416
  * `basePath` (an XHR carrying `X-Inertia`) is bounced with the protocol's own external-redirect
@@ -267,6 +420,7 @@ declare class AgentApiModule {
267
420
  */
268
421
  declare class AgentDashboardModule {
269
422
  static forRoot<TReq = unknown>(options?: AgentDashboardOptions<TReq>): DynamicModule;
423
+ static forRootAsync<TReq = unknown>(options: AgentDashboardAsyncOptions<TReq>): DynamicModule;
270
424
  }
271
425
 
272
426
  /**
@@ -287,6 +441,12 @@ interface UiPageResponse {
287
441
  * Serves the bundled AI-gateway console SPA at the configured base (+ hashed assets at
288
442
  * `<base>/assets`). The path comes from `RouterModule` (set by
289
443
  * {@link AgentDashboardModule.forRoot}({ basePath })), so the controller routes are relative.
444
+ *
445
+ * `@UseGuards(DashboardAuthPageGuard)` is a NO-OP unless the host configured `dashboardAuth` — see
446
+ * that guard's own doc. Host `guards` (see {@link AgentDashboardOptions.guards}) are APPENDED onto
447
+ * this baseline by `stampGuards` (`./guards.js`), never replacing it. `@UseFilters` turns the
448
+ * guard's redirect-on-deny into an actual 302 response; it is a no-op when the guard never throws
449
+ * (i.e. `dashboardAuth` unconfigured).
290
450
  */
291
451
  declare class AgentUiController {
292
452
  private readonly basePath;
@@ -361,5 +521,13 @@ declare const DASHBOARD_API_PATH: unique symbol;
361
521
  * `@dudousxd/nestjs-agent`).
362
522
  */
363
523
  declare const DASHBOARD_APPROVAL_ACTOR_REF: unique symbol;
524
+ /**
525
+ * DI token carrying the resolved `dashboardAuth` config (`ResolvedDashboardAuth | null`) — `null`
526
+ * when the host didn't configure it (the built-in guards are then a no-op, today's behavior
527
+ * byte-for-byte). Bound once by `AgentApiModule.register` and re-exported so `AgentUiController`
528
+ * and `AgentDashboardAuthController` (hosted directly by `AgentDashboardModule`, which imports
529
+ * `AgentApiModule`) resolve the SAME instance rather than re-running a host's async factory twice.
530
+ */
531
+ declare const DASHBOARD_AUTH: unique symbol;
364
532
 
365
- export { AGENT_ACTOR_DIRECTORY, AGENT_ACTOR_RESOLVER, AGENT_APPROVAL_PORT, AGENT_GOVERNANCE_QUERIES, AGENT_PRICING_STORE, type ActorSpendRowWithLabel, AgentApiController, AgentApiModule, AgentDashboardModule, type AgentDashboardMountPathsOptions, type AgentDashboardOptions, AgentUiController, DASHBOARD_API_PATH, DASHBOARD_APPROVAL_ACTOR_REF, DASHBOARD_BASE_PATH, DashboardService, type LiveAgentEvent, type ReliabilityOverview, type SpendOverview, type ThreadActivityRowWithLabel, type ThreadSpendRowWithLabel, type WithActorLabel, agentDashboardMountPaths, parsePriceInput };
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 };
@@ -1,7 +1,7 @@
1
- import { ActorSpendRow, AgentGovernanceQueries, ActorDirectory, AgentPricingStore, AgentApprovalPort, GovernanceRange, ModelSpendRow, UsageTrendPoint, ThreadSpendRow, RunMetrics, RunAgentBreakdownRow, RunErrorBreakdownRow, RunTrendPoint, RecentRunRow, ToolCallActivityRow, PendingApprovalRow, ToolStatRow, ThreadActivityRow, CurrentModelPrice, ActorResolver, ModelPriceInput } from '@dudousxd/nestjs-agent-core';
1
+ import { ActorSpendRow, AgentGovernanceQueries, ActorDirectory, AgentPricingStore, AgentApprovalPort, GovernanceRange, ModelSpendRow, UsageTrendPoint, ThreadSpendRow, RunMetrics, RunAgentBreakdownRow, RunErrorBreakdownRow, RunTrendPoint, RecentRunRow, ToolCallActivityRow, GovernancePageQuery, ToolCallWhere, GovernancePage, RunWhere, PendingApprovalRow, ToolStatRow, ThreadActivityRow, ThreadWhere, CurrentModelPrice, ActorResolver, ModelPriceInput } from '@dudousxd/nestjs-agent-core';
2
2
  export { ActorDirectory } from '@dudousxd/nestjs-agent-core';
3
3
  import { Observable } from 'rxjs';
4
- import { DynamicModule, Type, CanActivate, StreamableFile } from '@nestjs/common';
4
+ import { DynamicModule, Type, CanActivate, Provider, InjectionToken, OptionalFactoryDependency, StreamableFile } from '@nestjs/common';
5
5
 
6
6
  /** An actor-scoped row decorated with its resolved display label — `null` when unbound or unresolved. */
7
7
  interface WithActorLabel {
@@ -61,6 +61,10 @@ declare class DashboardService {
61
61
  recentRuns(limit: number): Promise<RecentRunRow[]>;
62
62
  /** Most recent tool calls (status/type/thread) for the Runs & tools activity feed. */
63
63
  recentToolCalls(limit: number): Promise<ToolCallActivityRow[]>;
64
+ /** Paged, filterable tool calls for the Runs & tools activity table. */
65
+ toolCallsPage(query: GovernancePageQuery<ToolCallWhere>): Promise<GovernancePage<ToolCallActivityRow>>;
66
+ /** Paged, filterable runs for the Reliability recent-runs table. */
67
+ runsPage(query: GovernancePageQuery<RunWhere>): Promise<GovernancePage<RecentRunRow>>;
64
68
  /** Tool calls sitting `pending_approval`, oldest first, for the cross-thread approvals inbox. */
65
69
  pendingApprovals(limit: number): Promise<PendingApprovalRow[]>;
66
70
  /** Per-tool call/failure/rejection/latency rollup for a day range, for the Tools section. */
@@ -76,6 +80,11 @@ declare class DashboardService {
76
80
  decideApproval(toolCallId: string, body: unknown, executedByRef: string | undefined): Promise<void>;
77
81
  /** Most recent threads with rolled-up message/token counts. */
78
82
  recentThreads(limit: number): Promise<ThreadActivityRowWithLabel[]>;
83
+ /**
84
+ * Paged, filterable threads for the Runs & tools threads table — actor-labeled the same way
85
+ * {@link recentThreads} is.
86
+ */
87
+ threadsPage(query: GovernancePageQuery<ThreadWhere>): Promise<GovernancePage<ThreadActivityRowWithLabel>>;
79
88
  /**
80
89
  * Decorate actor-scoped rows with `actorLabel`, batching the distinct `actorRef`s into ONE
81
90
  * {@link ActorDirectory.resolveDisplay} call per response. `null` for every row when no directory
@@ -103,6 +112,10 @@ declare class DashboardService {
103
112
  /**
104
113
  * JSON + SSE API consumed by the AI-gateway console SPA. Mounted at `apiBasePath` (set by
105
114
  * `RouterModule` in {@link AgentDashboardModule.forRoot}), so the controller routes are relative.
115
+ *
116
+ * `@UseGuards(DashboardAuthGuard)` is a NO-OP unless the host configured `dashboardAuth` — see
117
+ * that guard's own doc. Host `guards` (see {@link AgentDashboardOptions.guards}) are APPENDED onto
118
+ * this baseline by `stampGuards` (`./guards.js`), never replacing it.
106
119
  */
107
120
  declare class AgentApiController {
108
121
  private readonly dashboard;
@@ -117,8 +130,20 @@ declare class AgentApiController {
117
130
  reliability(from?: string, to?: string): Promise<ReliabilityOverview>;
118
131
  /** Most recent runs (default 50, max 200) for the Reliability recent-runs table. */
119
132
  runs(limit?: string): Promise<RecentRunRow[]>;
133
+ /**
134
+ * Paged, filterable runs for the Reliability recent-runs table. `page` (default 1), `limit`
135
+ * (default 25, max 200), and `where[agentName]`/`where[status]`/`where[errorCode]`/
136
+ * `where[fromDay]`/`where[toDay]` (`YYYY-MM-DD`) — an unknown `where` field 400s naming it.
137
+ */
138
+ runsPage(page?: string, limit?: string, where?: Record<string, string>): Promise<GovernancePage<RecentRunRow>>;
120
139
  /** Most recent tool calls (default 50, max 200) for the activity feed. */
121
140
  toolCalls(limit?: string): Promise<ToolCallActivityRow[]>;
141
+ /**
142
+ * Paged, filterable tool calls for the Runs & tools activity table. `page` (default 1), `limit`
143
+ * (default 25, max 200), and `where[toolName]`/`where[toolType]`/`where[status]`/`where[threadId]`/
144
+ * `where[fromDay]`/`where[toDay]` (`YYYY-MM-DD`) — an unknown `where` field 400s naming it.
145
+ */
146
+ toolCallsPage(page?: string, limit?: string, where?: Record<string, string>): Promise<GovernancePage<ToolCallActivityRow>>;
122
147
  /** Tool calls sitting `pending_approval` (default 50, max 200), oldest first — the approvals inbox. */
123
148
  approvals(limit?: string): Promise<PendingApprovalRow[]>;
124
149
  /**
@@ -139,6 +164,12 @@ declare class AgentApiController {
139
164
  tools(from?: string, to?: string): Promise<ToolStatRow[]>;
140
165
  /** Most recent threads (default 50, max 200) with rolled-up counts. */
141
166
  threads(limit?: string): Promise<ThreadActivityRowWithLabel[]>;
167
+ /**
168
+ * Paged, filterable threads for the Runs & tools threads table. `page` (default 1), `limit`
169
+ * (default 25, max 200), and `where[actorRef]`/`where[title]` (case-insensitive substring)/
170
+ * `where[fromDay]`/`where[toDay]` (`YYYY-MM-DD`) — an unknown `where` field 400s naming it.
171
+ */
172
+ threadsPage(page?: string, limit?: string, where?: Record<string, string>): Promise<GovernancePage<ThreadActivityRowWithLabel>>;
142
173
  /**
143
174
  * Current price row per model, for the pricing tab. 501s (via `DashboardService.listPrices`) when
144
175
  * no `AGENT_PRICING_STORE` is bound.
@@ -156,6 +187,96 @@ declare class AgentApiController {
156
187
  }>;
157
188
  }
158
189
 
190
+ /** The session user the host's `login` hook returns to mint a cookie. */
191
+ interface DashboardSessionUser {
192
+ id: string;
193
+ name?: string;
194
+ roles?: string[];
195
+ }
196
+
197
+ /** Host hook: validates submitted credentials for the built-in login screen. */
198
+ type LoginHook = (username: string, password: string) => Promise<DashboardSessionUser | null> | DashboardSessionUser | null;
199
+ /**
200
+ * 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`).
203
+ */
204
+ interface DashboardAuthOptions {
205
+ /** REQUIRED HMAC-SHA256 signing key (32+ bytes recommended). Missing/empty => boot error (fail closed). */
206
+ secret: string;
207
+ /** Cookie TTL (duration string `'<n><ms|s|m|h|d>'`, e.g. `'30m'`, `'7d'`). Default `'8h'`. */
208
+ ttl?: string;
209
+ /**
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.
213
+ */
214
+ login: LoginHook;
215
+ }
216
+ /** Resolved, validated `dashboardAuth` config used by the guards/controller. */
217
+ interface ResolvedDashboardAuth {
218
+ secret: string;
219
+ ttlMs: number;
220
+ login: LoginHook;
221
+ }
222
+ /**
223
+ * Validate + resolve `dashboardAuth`. Returns `null` when unconfigured (behavior unchanged — the
224
+ * 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.
227
+ */
228
+ declare function resolveDashboardAuth(options: DashboardAuthOptions | undefined): ResolvedDashboardAuth | null;
229
+
230
+ /** The slice of the platform request this controller reads — structural, no Express/Fastify dependency. */
231
+ interface AuthPageRequest {
232
+ headers: Record<string, string | string[] | undefined>;
233
+ }
234
+ /** The slice of the platform response this controller writes (passthrough mode — Nest still sends). */
235
+ interface AuthPageResponse {
236
+ status(code: number): unknown;
237
+ setHeader(name: string, value: string): unknown;
238
+ }
239
+ interface LoginFormBody {
240
+ username?: unknown;
241
+ password?: unknown;
242
+ returnTo?: unknown;
243
+ }
244
+ /**
245
+ * Mounted directly on `AgentDashboardModule` at `<basePath>/auth/*` — a SEPARATE controller from
246
+ * `AgentUiController`/`AgentApiController` so it carries neither `DashboardAuthPageGuard`,
247
+ * `DashboardAuthGuard`, nor any host `guards`: these routes MINT (and clear) the session cookie the
248
+ * built-in gates check for, so they can never be made to require the very auth they grant. Mirrors
249
+ * `@dudousxd/nestjs-telescope`'s own auth controller staying outside its `stampGuards` call.
250
+ *
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.
257
+ */
258
+ declare class AgentDashboardAuthController {
259
+ private readonly auth;
260
+ private readonly basePath;
261
+ private readonly logger;
262
+ /** Warn once (not per request) so a broken hook can't be used to flood the logs. */
263
+ private warnedLoginHookThrow;
264
+ constructor(auth: ResolvedDashboardAuth | null, basePath: string);
265
+ loginPage(req: AuthPageRequest, res: AuthPageResponse, returnToQuery?: string, error?: string): string;
266
+ login(body: LoginFormBody, req: unknown, res: AuthPageResponse): Promise<string>;
267
+ logout(req: unknown, res: AuthPageResponse): string;
268
+ /** Uniform failure: same redirect (generic error flag) whether the user is unknown or the password is wrong. */
269
+ private redirectToLoginError;
270
+ private currentSession;
271
+ /**
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.
275
+ */
276
+ private runLoginHook;
277
+ private requireAuth;
278
+ }
279
+
159
280
  /** Same shape {@link AgentDashboardModule.forRoot} accepts — kept local so this stays a pure, DI-free helper. */
160
281
  interface AgentDashboardMountPathsOptions {
161
282
  basePath?: string;
@@ -207,10 +328,9 @@ interface AgentDashboardOptions<TReq = unknown> {
207
328
  apiBasePath?: string;
208
329
  /**
209
330
  * Guard classes fronting BOTH dashboard controllers (the SPA at `basePath` and its JSON API at
210
- * `apiBasePath`). Stamped onto each controller via `@nestjs/common`'s own `@UseGuards` metadata key
211
- * — REPLACE semantics, so a second `forRoot(...)` call overwrites (not appends to) whatever a prior
212
- * call stamped, same as re-applying `@UseGuards` by hand. Omit to leave the routes unguarded (the
213
- * host fronts them another way, e.g. a global guard or reverse-proxy auth).
331
+ * `apiBasePath`) — APPENDED onto the built-in `dashboardAuth` gate (a no-op unless configured;
332
+ * see {@link dashboardAuth}), so a request must pass BOTH. Omit to leave the routes gated by
333
+ * `dashboardAuth` alone (or fully open when that's unset too).
214
334
  *
215
335
  * A guard's own DEPENDENCIES resolve from this module's `imports` (see {@link imports}) — the
216
336
  * dashboard module has no application context of its own to pull them from otherwise.
@@ -234,6 +354,36 @@ interface AgentDashboardOptions<TReq = unknown> {
234
354
  * override wins outright, with no resolver fallback.
235
355
  */
236
356
  approvalActorRef?: (req: TReq) => string | undefined;
357
+ /**
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.
364
+ */
365
+ dashboardAuth?: DashboardAuthOptions;
366
+ }
367
+ /**
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.
373
+ */
374
+ interface AgentDashboardAsyncOptions<TReq = unknown> {
375
+ basePath?: string;
376
+ apiBasePath?: string;
377
+ guards?: Type<CanActivate>[];
378
+ imports?: DynamicModule['imports'];
379
+ approvalActorRef?: (req: TReq) => string | undefined;
380
+ /** Providers injected into `useDashboardAuth`, in order. Shared with a guard class's own deps via `imports`. */
381
+ inject?: Array<InjectionToken | OptionalFactoryDependency>;
382
+ /**
383
+ * Build the `dashboardAuth` config from injected deps, or return `undefined` to leave the
384
+ * console open (or fronted only by `guards`). See {@link AgentDashboardOptions.dashboardAuth}.
385
+ */
386
+ useDashboardAuth: (...deps: any[]) => DashboardAuthOptions | undefined | Promise<DashboardAuthOptions | undefined>;
237
387
  }
238
388
  /**
239
389
  * Holds the JSON API + SSE controller and its read service, mounted on its own path by `forRoot`.
@@ -248,16 +398,19 @@ declare class AgentApiModule {
248
398
  imports?: DynamicModule['imports'];
249
399
  guards?: Type<CanActivate>[];
250
400
  approvalActorRef?: (req: TReq) => string | undefined;
401
+ /** Provider for `DASHBOARD_AUTH` — a `useValue` (`forRoot`) or a `useFactory` (`forRootAsync`). */
402
+ dashboardAuth: Provider;
251
403
  }): DynamicModule;
252
404
  }
253
405
  /**
254
406
  * Mounts the AI-gateway governance console: the bundled React SPA at `basePath` and its JSON + SSE
255
- * API at `apiBasePath` (default `<basePath>/api`).
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.
256
409
  *
257
410
  * Import via `AgentDashboardModule.forRoot(...)` alongside your `@dudousxd/nestjs-agent` module
258
411
  * (global), which must provide `AGENT_GOVERNANCE_QUERIES` (bound by a store adapter). Front the
259
412
  * routes with the first-class `guards` option (plus `imports` for the guards' own dependencies) —
260
- * see {@link AgentDashboardOptions.guards}.
413
+ * see {@link AgentDashboardOptions.guards} — and/or the built-in `dashboardAuth` cookie login.
261
414
  *
262
415
  * Inertia hosts: the console is a full-page app, not an Inertia page. An in-app `<Link>` visit to
263
416
  * `basePath` (an XHR carrying `X-Inertia`) is bounced with the protocol's own external-redirect
@@ -267,6 +420,7 @@ declare class AgentApiModule {
267
420
  */
268
421
  declare class AgentDashboardModule {
269
422
  static forRoot<TReq = unknown>(options?: AgentDashboardOptions<TReq>): DynamicModule;
423
+ static forRootAsync<TReq = unknown>(options: AgentDashboardAsyncOptions<TReq>): DynamicModule;
270
424
  }
271
425
 
272
426
  /**
@@ -287,6 +441,12 @@ interface UiPageResponse {
287
441
  * Serves the bundled AI-gateway console SPA at the configured base (+ hashed assets at
288
442
  * `<base>/assets`). The path comes from `RouterModule` (set by
289
443
  * {@link AgentDashboardModule.forRoot}({ basePath })), so the controller routes are relative.
444
+ *
445
+ * `@UseGuards(DashboardAuthPageGuard)` is a NO-OP unless the host configured `dashboardAuth` — see
446
+ * that guard's own doc. Host `guards` (see {@link AgentDashboardOptions.guards}) are APPENDED onto
447
+ * this baseline by `stampGuards` (`./guards.js`), never replacing it. `@UseFilters` turns the
448
+ * guard's redirect-on-deny into an actual 302 response; it is a no-op when the guard never throws
449
+ * (i.e. `dashboardAuth` unconfigured).
290
450
  */
291
451
  declare class AgentUiController {
292
452
  private readonly basePath;
@@ -361,5 +521,13 @@ declare const DASHBOARD_API_PATH: unique symbol;
361
521
  * `@dudousxd/nestjs-agent`).
362
522
  */
363
523
  declare const DASHBOARD_APPROVAL_ACTOR_REF: unique symbol;
524
+ /**
525
+ * DI token carrying the resolved `dashboardAuth` config (`ResolvedDashboardAuth | null`) — `null`
526
+ * when the host didn't configure it (the built-in guards are then a no-op, today's behavior
527
+ * byte-for-byte). Bound once by `AgentApiModule.register` and re-exported so `AgentUiController`
528
+ * and `AgentDashboardAuthController` (hosted directly by `AgentDashboardModule`, which imports
529
+ * `AgentApiModule`) resolve the SAME instance rather than re-running a host's async factory twice.
530
+ */
531
+ declare const DASHBOARD_AUTH: unique symbol;
364
532
 
365
- export { AGENT_ACTOR_DIRECTORY, AGENT_ACTOR_RESOLVER, AGENT_APPROVAL_PORT, AGENT_GOVERNANCE_QUERIES, AGENT_PRICING_STORE, type ActorSpendRowWithLabel, AgentApiController, AgentApiModule, AgentDashboardModule, type AgentDashboardMountPathsOptions, type AgentDashboardOptions, AgentUiController, DASHBOARD_API_PATH, DASHBOARD_APPROVAL_ACTOR_REF, DASHBOARD_BASE_PATH, DashboardService, type LiveAgentEvent, type ReliabilityOverview, type SpendOverview, type ThreadActivityRowWithLabel, type ThreadSpendRowWithLabel, type WithActorLabel, agentDashboardMountPaths, parsePriceInput };
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 };