@dudousxd/nestjs-agent-dashboard 0.10.0 → 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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/server/agent-api.controller.ts","../../src/server/auth/dashboard-auth.guard.ts","../../src/server/tokens.ts","../../src/server/auth/cookie-header.ts","../../src/server/auth/http-request.ts","../../src/server/auth/response.ts","../../src/server/auth/session-cookie.ts","../../src/server/auth/session-cookie-io.ts","../../src/server/dashboard.service.ts","../../src/server/parse-approval-decision.ts","../../src/server/parse-price-input.ts","../../src/server/parse-page-query.ts","../../src/server/agent-dashboard-auth.controller.ts","../../src/server/auth/login-page.ts","../../src/server/auth/sanitize-return-to.ts","../../src/server/normalize-path.ts","../../src/server/agent-dashboard-mount-paths.ts","../../src/server/agent-dashboard.module.ts","../../src/server/agent-ui.controller.ts","../../src/server/auth/dashboard-auth-page.guard.ts","../../src/server/auth/dashboard-auth-redirect.ts","../../src/server/auth/dashboard-auth-config.ts","../../src/server/guards.ts"],"sourcesContent":["import type {\n ActorResolver,\n CurrentModelPrice,\n GovernancePage,\n PendingApprovalRow,\n RecentRunRow,\n ToolCallActivityRow,\n ToolStatRow,\n} from '@dudousxd/nestjs-agent-core';\nimport {\n Body,\n Controller,\n Get,\n HttpCode,\n Inject,\n Optional,\n Param,\n Post,\n Query,\n Req,\n Sse,\n UseGuards,\n} from '@nestjs/common';\nimport type { Observable } from 'rxjs';\nimport { DashboardAuthGuard } from './auth/dashboard-auth.guard.js';\nimport {\n DashboardService,\n type LiveAgentEvent,\n type ReliabilityOverview,\n type SpendOverview,\n type ThreadActivityRowWithLabel,\n type ThreadSpendRowWithLabel,\n} from './dashboard.service.js';\nimport {\n parsePageNumber,\n parseRunWhere,\n parseThreadWhere,\n parseToolCallWhere,\n} from './parse-page-query.js';\nimport { AGENT_ACTOR_RESOLVER, DASHBOARD_APPROVAL_ACTOR_REF } from './tokens.js';\n\nconst DAY_MS = 86_400_000;\nconst ISO_DAY = /^\\d{4}-\\d{2}-\\d{2}$/;\n\n/** A `YYYY-MM-DD` UTC day string `daysAgo` days before now (0 = today). */\nfunction utcDay(daysAgo: number): string {\n return new Date(Date.now() - daysAgo * DAY_MS).toISOString().slice(0, 10);\n}\n\n/** Accept a client-supplied `YYYY-MM-DD`, or fall back to `fallback`; guards against junk input. */\nfunction dayOr(value: string | undefined, fallback: string): string {\n return value !== undefined && ISO_DAY.test(value) ? value : fallback;\n}\n\n/** Resolve the `from`/`to` query params into a validated range, defaulting to the last 30 days. */\nfunction resolveRange(\n from: string | undefined,\n to: string | undefined,\n): {\n fromDay: string;\n toDay: string;\n} {\n return { fromDay: dayOr(from, utcDay(29)), toDay: dayOr(to, utcDay(0)) };\n}\n\n/** Parse a `limit` query param, clamped to a sane window; falls back to `fallback` when absent/junk. */\nfunction parseLimit(value: string | undefined, fallback: number): number {\n const parsed = value === undefined ? Number.NaN : Number.parseInt(value, 10);\n if (!Number.isFinite(parsed)) return fallback;\n return Math.max(1, Math.min(200, parsed));\n}\n\n/**\n * JSON + SSE API consumed by the AI-gateway console SPA. Mounted at `apiBasePath` (set by\n * `RouterModule` in {@link AgentDashboardModule.forRoot}), so the controller routes are relative.\n *\n * `@UseGuards(DashboardAuthGuard)` is a NO-OP unless the host configured `dashboardAuth` — see\n * that guard's own doc. Host `guards` (see {@link AgentDashboardOptions.guards}) are APPENDED onto\n * this baseline by `stampGuards` (`./guards.js`), never replacing it.\n */\n@Controller()\n@UseGuards(DashboardAuthGuard)\nexport class AgentApiController {\n constructor(\n private readonly dashboard: DashboardService,\n // `useValue` even when the host omitted `approvalActorRef` — no `@Optional()` needed (the token\n // is always bound; see DASHBOARD_APPROVAL_ACTOR_REF's TSDoc).\n @Inject(DASHBOARD_APPROVAL_ACTOR_REF)\n private readonly approvalActorRef: ((req: unknown) => string | undefined) | undefined,\n // The app's identity seam, bound + exported by the (global) AgentModule — the DEFAULT decider\n // attribution when no `approvalActorRef` override is configured. Genuinely `@Optional()`: the\n // dashboard can be mounted without AgentModule (read-model only), where nothing binds it.\n @Optional()\n @Inject(AGENT_ACTOR_RESOLVER)\n private readonly actorResolver?: ActorResolver,\n ) {}\n\n /** `{ byModel, byActor, trend }` for a day range (defaults to the last 30 days). */\n @Get('spend')\n spend(@Query('from') from?: string, @Query('to') to?: string): Promise<SpendOverview> {\n return this.dashboard.spend(resolveRange(from, to));\n }\n\n /** Top threads by cost (default 10, max 200) for a day range (defaults to the last 30 days). */\n @Get('top-threads')\n topThreads(\n @Query('from') from?: string,\n @Query('to') to?: string,\n @Query('limit') limit?: string,\n ): Promise<ThreadSpendRowWithLabel[]> {\n return this.dashboard.topThreads(resolveRange(from, to), parseLimit(limit, 10));\n }\n\n /** `{ metrics, byAgent, errors, trend }` for a day range (defaults to the last 30 days). */\n @Get('reliability')\n reliability(\n @Query('from') from?: string,\n @Query('to') to?: string,\n ): Promise<ReliabilityOverview> {\n return this.dashboard.reliability(resolveRange(from, to));\n }\n\n /** Most recent runs (default 50, max 200) for the Reliability recent-runs table. */\n @Get('runs')\n runs(@Query('limit') limit?: string): Promise<RecentRunRow[]> {\n return this.dashboard.recentRuns(parseLimit(limit, 50));\n }\n\n /**\n * Paged, filterable runs for the Reliability recent-runs table. `page` (default 1), `limit`\n * (default 25, max 200), and `where[agentName]`/`where[status]`/`where[errorCode]`/\n * `where[fromDay]`/`where[toDay]` (`YYYY-MM-DD`) — an unknown `where` field 400s naming it.\n */\n @Get('runs-page')\n runsPage(\n @Query('page') page?: string,\n @Query('limit') limit?: string,\n @Query('where') where?: Record<string, string>,\n ): Promise<GovernancePage<RecentRunRow>> {\n const parsedWhere = parseRunWhere(where);\n return this.dashboard.runsPage({\n page: parsePageNumber(page),\n pageSize: parseLimit(limit, 25),\n ...(parsedWhere !== undefined ? { where: parsedWhere } : {}),\n });\n }\n\n /** Most recent tool calls (default 50, max 200) for the activity feed. */\n @Get('tool-calls')\n toolCalls(@Query('limit') limit?: string): Promise<ToolCallActivityRow[]> {\n return this.dashboard.recentToolCalls(parseLimit(limit, 50));\n }\n\n /**\n * Paged, filterable tool calls for the Runs & tools activity table. `page` (default 1), `limit`\n * (default 25, max 200), and `where[toolName]`/`where[toolType]`/`where[status]`/`where[threadId]`/\n * `where[fromDay]`/`where[toDay]` (`YYYY-MM-DD`) — an unknown `where` field 400s naming it.\n */\n @Get('tool-calls-page')\n toolCallsPage(\n @Query('page') page?: string,\n @Query('limit') limit?: string,\n @Query('where') where?: Record<string, string>,\n ): Promise<GovernancePage<ToolCallActivityRow>> {\n const parsedWhere = parseToolCallWhere(where);\n return this.dashboard.toolCallsPage({\n page: parsePageNumber(page),\n pageSize: parseLimit(limit, 25),\n ...(parsedWhere !== undefined ? { where: parsedWhere } : {}),\n });\n }\n\n /** Tool calls sitting `pending_approval` (default 50, max 200), oldest first — the approvals inbox. */\n @Get('approvals')\n approvals(@Query('limit') limit?: string): Promise<PendingApprovalRow[]> {\n return this.dashboard.pendingApprovals(parseLimit(limit, 50));\n }\n\n /**\n * Decide a pending HITL tool call. Body `{ approved: boolean; reason?: string }`. 501s (via\n * `DashboardService.decideApproval`) when no `AGENT_APPROVAL_PORT` is bound. `executedByRef`\n * comes from {@link deciderRef} run against the live request.\n */\n @Post('approvals/:toolCallId')\n @HttpCode(204)\n async decideApproval(\n @Param('toolCallId') toolCallId: string,\n @Body() body: unknown,\n @Req() req: unknown,\n ): Promise<void> {\n await this.dashboard.decideApproval(toolCallId, body, await this.deciderRef(req));\n }\n\n /**\n * WHO decided, as an opaque ref: an explicit `approvalActorRef` override wins outright (no\n * resolver fallback, even when it returns `undefined`); otherwise the AgentModule-configured\n * actor resolver — the same identity seam chat requests use. A resolver that throws is an\n * unauthenticated/unreadable request, not an error to surface: the decision itself was already\n * authorized by the dashboard's guards, so the ref is simply omitted.\n */\n private async deciderRef(req: unknown): Promise<string | undefined> {\n if (this.approvalActorRef !== undefined) {\n return this.approvalActorRef(req);\n }\n if (this.actorResolver === undefined) {\n return undefined;\n }\n try {\n return (await this.actorResolver.resolve(req)).id;\n } catch {\n return undefined;\n }\n }\n\n /** Per-tool call/failure/rejection/latency rollup for a day range (defaults to the last 30 days). */\n @Get('tools')\n tools(@Query('from') from?: string, @Query('to') to?: string): Promise<ToolStatRow[]> {\n return this.dashboard.toolStats(resolveRange(from, to));\n }\n\n /** Most recent threads (default 50, max 200) with rolled-up counts. */\n @Get('threads')\n threads(@Query('limit') limit?: string): Promise<ThreadActivityRowWithLabel[]> {\n return this.dashboard.recentThreads(parseLimit(limit, 50));\n }\n\n /**\n * Paged, filterable threads for the Runs & tools threads table. `page` (default 1), `limit`\n * (default 25, max 200), and `where[actorRef]`/`where[title]` (case-insensitive substring)/\n * `where[fromDay]`/`where[toDay]` (`YYYY-MM-DD`) — an unknown `where` field 400s naming it.\n */\n @Get('threads-page')\n threadsPage(\n @Query('page') page?: string,\n @Query('limit') limit?: string,\n @Query('where') where?: Record<string, string>,\n ): Promise<GovernancePage<ThreadActivityRowWithLabel>> {\n const parsedWhere = parseThreadWhere(where);\n return this.dashboard.threadsPage({\n page: parsePageNumber(page),\n pageSize: parseLimit(limit, 25),\n ...(parsedWhere !== undefined ? { where: parsedWhere } : {}),\n });\n }\n\n /**\n * Current price row per model, for the pricing tab. 501s (via `DashboardService.listPrices`) when\n * no `AGENT_PRICING_STORE` is bound.\n */\n @Get('pricing')\n listPrices(): Promise<CurrentModelPrice[]> {\n return this.dashboard.listPrices();\n }\n\n /**\n * Set a model's current price. Body shape mirrors core's `ModelPriceInput`\n * (`{ modelId, inputPricePer1m, outputPricePer1m, cacheWritePricePer1m?, cacheReadPricePer1m? }`).\n * 501s when no `AGENT_PRICING_STORE` is bound; 400s on a malformed body.\n */\n @Post('pricing')\n upsertPrice(@Body() body: unknown): Promise<void> {\n return this.dashboard.upsertPrice(body);\n }\n\n /** Server-Sent Events stream of live `aviary:agent:*` events — the Live feed tails it. */\n @Sse('stream')\n stream(): Observable<{ data: LiveAgentEvent }> {\n return this.dashboard.streamEvents();\n }\n}\n","// packages/dashboard/src/server/auth/dashboard-auth.guard.ts\nimport {\n type CanActivate,\n type ExecutionContext,\n Inject,\n Injectable,\n Optional,\n UnauthorizedException,\n} from '@nestjs/common';\nimport { DASHBOARD_AUTH } from '../tokens.js';\nimport { parseCookieHeader } from './cookie-header.js';\nimport type { ResolvedDashboardAuth } from './dashboard-auth-config.js';\nimport { attachSession, readCookieHeader } from './http-request.js';\nimport { SESSION_COOKIE_NAME, maybeRenewSession } from './session-cookie-io.js';\nimport { type DashboardSession, verifySessionCookie } from './session-cookie.js';\n\n/**\n * Gates `AgentApiController` on a valid session cookie — but ONLY when the host configured\n * `dashboardAuth`. With no `dashboardAuth` the resolved value is `null` and the guard is a no-op\n * (today's behavior byte-for-byte; the API stays open, front it with your own `guards`). Composes\n * with host `guards` via `stampGuards`'s APPEND semantics (see `../guards.ts`) — AND semantics,\n * both gates must pass.\n *\n * Absent/invalid/expired cookie => `401` (not `403`): \"not authenticated\", distinct from a host\n * guard's `403` \"authenticated, not allowed\".\n */\n@Injectable()\nexport class DashboardAuthGuard implements CanActivate {\n constructor(\n @Optional() @Inject(DASHBOARD_AUTH) private readonly auth: ResolvedDashboardAuth | null,\n ) {}\n\n async canActivate(context: ExecutionContext): Promise<boolean> {\n if (!this.auth) return true;\n const http = context.switchToHttp();\n const request = http.getRequest<unknown>();\n const session = this.verifyRequestSession(request);\n if (!session) throw new UnauthorizedException();\n attachSession(request, session);\n if (!(await maybeRenewSession(this.auth, session, request, http.getResponse<unknown>()))) {\n // Revoked mid-session: same 401 as an absent cookie.\n throw new UnauthorizedException();\n }\n return true;\n }\n\n private verifyRequestSession(request: unknown): DashboardSession | null {\n const auth = this.auth;\n if (!auth) return null;\n const cookieValue = parseCookieHeader(readCookieHeader(request))[SESSION_COOKIE_NAME];\n if (cookieValue === undefined) return null;\n return verifySessionCookie(cookieValue, { secret: auth.secret });\n }\n}\n","/**\n * DI tokens for the standalone AI-gateway dashboard.\n *\n * All use `Symbol.for(...)` (the global symbol registry) on purpose: pnpm peer multiplexing + dual\n * ESM/CJS can load a package more than once, and a plain `Symbol()` would mint a distinct token per\n * copy and break DI across the ESM/CJS split. A registered symbol collapses every copy onto the same\n * token.\n */\n\n/**\n * The governance read-model, owned by `@dudousxd/nestjs-agent-core`. We re-declare it here BY VALUE\n * (not by import) so DI does not depend on a runtime value-import of core resolving — `Symbol.for`\n * with the identical key resolves to the SAME symbol instance as core's own\n * `packages/core/src/tokens.ts` export. The key MUST stay byte-identical with that export.\n */\nexport const AGENT_GOVERNANCE_QUERIES = Symbol.for('@dudousxd/nestjs-agent:governance-queries');\n\n/**\n * Optional actor→label resolver (see {@link ActorDirectory} in `./actor-directory.js`), owned by\n * `@dudousxd/nestjs-agent-core`. Re-declared here BY VALUE for the same reason as\n * {@link AGENT_GOVERNANCE_QUERIES} above — the key MUST stay byte-identical with core's own\n * `AGENT_ACTOR_DIRECTORY` export so both copies collapse onto the same registered symbol. Optional:\n * the dashboard works with actorRef-only rows when nothing is bound.\n */\nexport const AGENT_ACTOR_DIRECTORY = Symbol.for('@dudousxd/nestjs-agent:actor-directory');\n\n/**\n * The pricing WRITE side (`AgentPricingStore`), owned by `@dudousxd/nestjs-agent-core`. Re-declared\n * here BY VALUE for the same reason as {@link AGENT_GOVERNANCE_QUERIES} above. Optional: the pricing\n * tab/endpoints 501 with a clear message when nothing is bound.\n */\nexport const AGENT_PRICING_STORE = Symbol.for('@dudousxd/nestjs-agent:pricing-store');\n\n/**\n * Console-side HITL decisions (`AgentApprovalPort`), owned by `@dudousxd/nestjs-agent-core`.\n * Re-declared here BY VALUE for the same reason as {@link AGENT_GOVERNANCE_QUERIES} above. Optional:\n * bound by `@dudousxd/nestjs-agent` (which adapts it to the same signal path chat approvals use) —\n * absent, `POST <api>/approvals/:toolCallId` 501s and the approvals inbox renders read-only.\n */\nexport const AGENT_APPROVAL_PORT = Symbol.for('@dudousxd/nestjs-agent:approval-port');\n\n/**\n * The app's request-identity seam (`ActorResolver`), owned by `@dudousxd/nestjs-agent-core` and\n * bound + exported by the (global) `AgentModule` from the host's `actorResolver` option. Re-declared\n * here BY VALUE for the same reason as {@link AGENT_GOVERNANCE_QUERIES} above. Optional: the default\n * decider attribution for `POST <api>/approvals/:toolCallId` — absent (or throwing, i.e.\n * unauthenticated), `executedByRef` is simply omitted.\n */\nexport const AGENT_ACTOR_RESOLVER = Symbol.for('@dudousxd/nestjs-agent:actor-resolver');\n\n/** DI token carrying the UI mount base (e.g. `/ai-gateway`). */\nexport const DASHBOARD_BASE_PATH = Symbol.for('@dudousxd/nestjs-agent-dashboard:base-path');\n\n/** DI token carrying the JSON API base the SPA fetches from (e.g. `/ai-gateway/api`). */\nexport const DASHBOARD_API_PATH = Symbol.for('@dudousxd/nestjs-agent-dashboard:api-path');\n\n/**\n * DI token carrying the host-provided {@link AgentDashboardOptions.approvalActorRef} extractor (or\n * `undefined` when the host didn't set one — the controller then falls back to\n * {@link AGENT_ACTOR_RESOLVER} for decider attribution). Threaded to `AgentApiModule` (where the API\n * controller actually lives) same as the pattern above — `useValue` even when `undefined`, so\n * injecting it needs no `@Optional()` (mirrors `AGENT_QUOTA_STORE`'s factory in\n * `@dudousxd/nestjs-agent`).\n */\nexport const DASHBOARD_APPROVAL_ACTOR_REF = Symbol.for(\n '@dudousxd/nestjs-agent-dashboard:approval-actor-ref',\n);\n\n/**\n * DI token carrying the resolved `dashboardAuth` config (`ResolvedDashboardAuth | null`) — `null`\n * when the host didn't configure it (the built-in guards are then a no-op, today's behavior\n * byte-for-byte). Bound once by `AgentApiModule.register` and re-exported so `AgentUiController`\n * and `AgentDashboardAuthController` (hosted directly by `AgentDashboardModule`, which imports\n * `AgentApiModule`) resolve the SAME instance rather than re-running a host's async factory twice.\n */\nexport const DASHBOARD_AUTH = Symbol.for('@dudousxd/nestjs-agent-dashboard:dashboard-auth');\n","// packages/dashboard/src/server/auth/cookie-header.ts\n\n/**\n * Parse a raw `Cookie` request header into a name→value map. Dependency-free so it works on raw\n * Express AND Fastify requests without `cookie-parser`. Values are URL-decoded; malformed segments\n * are skipped, never thrown.\n */\nexport function parseCookieHeader(header: string | undefined): Record<string, string> {\n const cookies: Record<string, string> = {};\n if (typeof header !== 'string' || header === '') return cookies;\n for (const segment of header.split(';')) {\n const eq = segment.indexOf('=');\n if (eq <= 0) continue;\n const name = segment.slice(0, eq).trim();\n if (name === '') continue;\n let rawValue = segment.slice(eq + 1).trim();\n // Strip a single pair of wrapping double-quotes (RFC 6265 quoted form).\n if (rawValue.length >= 2 && rawValue.startsWith('\"') && rawValue.endsWith('\"')) {\n rawValue = rawValue.slice(1, -1);\n }\n // First occurrence wins; don't clobber an earlier value with a later dup.\n if (name in cookies) continue;\n try {\n cookies[name] = decodeURIComponent(rawValue);\n } catch {\n cookies[name] = rawValue;\n }\n }\n return cookies;\n}\n\nexport interface SetCookieOptions {\n path: string;\n /** Cookie lifetime in seconds. Ignored (and forced to 0) when `clear` is set. */\n maxAgeSeconds: number;\n /** Add `Secure` when the request is https. */\n secure: boolean;\n /** Clear the cookie: empty value + `Max-Age=0` (and `Expires` in the past). */\n clear?: boolean;\n}\n\n/**\n * Serialize a `Set-Cookie` header value. Always `HttpOnly` + `SameSite=Lax` (Lax blocks\n * cross-site POSTs carrying the cookie — CSRF coverage for the login/logout mutations).\n * Platform-agnostic: just a string.\n */\nexport function serializeSetCookie(name: string, value: string, options: SetCookieOptions): string {\n const parts = [`${name}=${options.clear ? '' : encodeURIComponent(value)}`];\n parts.push(`Path=${options.path}`);\n parts.push('HttpOnly');\n parts.push('SameSite=Lax');\n if (options.secure) parts.push('Secure');\n if (options.clear) {\n parts.push('Max-Age=0');\n parts.push('Expires=Thu, 01 Jan 1970 00:00:00 GMT');\n } else {\n parts.push(`Max-Age=${options.maxAgeSeconds}`);\n }\n return parts.join('; ');\n}\n","// packages/dashboard/src/server/auth/http-request.ts\nimport type { DashboardSession } from './session-cookie.js';\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\nfunction readHeaders(request: unknown): Record<string, unknown> {\n if (!isRecord(request)) return {};\n const headers = request.headers;\n return isRecord(headers) ? headers : {};\n}\n\n/** Coerce a single header (string or string[]) to a string, else undefined. */\nfunction headerToString(value: unknown): string | undefined {\n if (typeof value === 'string') return value;\n if (Array.isArray(value) && value.every((part) => typeof part === 'string')) {\n return value.join(',');\n }\n return undefined;\n}\n\n/** Read the raw `Cookie` request header (Express and Fastify both expose `headers`). */\nexport function readCookieHeader(request: unknown): string | undefined {\n return headerToString(readHeaders(request).cookie);\n}\n\n/**\n * Decide whether the cookie should carry `Secure`. True when the request is https directly\n * (`request.protocol === 'https'`) OR a proxy forwarded https (`x-forwarded-proto` includes\n * 'https'). Defensive structural reads — never throws, defaults to insecure (works on plain http\n * in dev).\n */\nexport function isHttpsRequest(request: unknown): boolean {\n if (isRecord(request) && request.protocol === 'https') return true;\n const forwarded = headerToString(readHeaders(request)['x-forwarded-proto']);\n if (forwarded === undefined) return false;\n return forwarded\n .split(',')\n .map((proto) => proto.trim().toLowerCase())\n .includes('https');\n}\n\n/** The original path + query as received (Express `originalUrl` / Fastify `url`). */\nexport function readOriginalUrl(request: unknown): string {\n if (!isRecord(request)) return '/';\n const originalUrl = request.originalUrl;\n if (typeof originalUrl === 'string' && originalUrl !== '') return originalUrl;\n const url = request.url;\n return typeof url === 'string' && url !== '' ? url : '/';\n}\n\n/** A request with the verified session attached for downstream reads. */\nexport interface RequestWithSession {\n dashboardSession: DashboardSession;\n}\n\n/**\n * Attach the verified session onto the raw request so downstream handlers can read\n * `request.dashboardSession`. Narrow structural mutation — avoids an `as` cast at the guard call\n * site.\n */\nexport function attachSession(request: unknown, session: DashboardSession): void {\n if (!isRecord(request)) return;\n const target: Partial<RequestWithSession> = request;\n target.dashboardSession = session;\n}\n","// packages/dashboard/src/server/auth/response.ts\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\ninterface HeaderCapableResponse {\n getHeader(name: string): number | string | string[] | undefined;\n setHeader(name: string, value: string | string[]): unknown;\n}\n\nfunction isHeaderCapableResponse(value: unknown): value is HeaderCapableResponse {\n return (\n isRecord(value) &&\n typeof value.getHeader === 'function' &&\n typeof value.setHeader === 'function'\n );\n}\n\n/**\n * Resolve the header-capable response from a platform response. Express' response IS the Node\n * response; Fastify's reply wraps it on `.raw`. Returns the outer response when it already exposes\n * get/setHeader (Express), otherwise the unwrapped `.raw` (Fastify) — so a single write path serves\n * both. Only requires get/setHeader (not `end`) so it also works against a passthrough-mode\n * response, which Nest still owns sending.\n */\nfunction resolveHeaderCapableResponse(response: unknown): HeaderCapableResponse | null {\n if (isHeaderCapableResponse(response)) return response;\n if (isRecord(response) && isHeaderCapableResponse(response.raw)) return response.raw;\n return null;\n}\n\nfunction existingSetCookies(response: HeaderCapableResponse): string[] {\n const current = response.getHeader('set-cookie');\n if (Array.isArray(current)) return current;\n if (typeof current === 'string') return [current];\n return [];\n}\n\n/**\n * Append a `Set-Cookie` header to the response WITHOUT clobbering any cookies already queued by\n * the host (auth or otherwise). Platform-agnostic raw write — no-ops gracefully if the response\n * can't be unwrapped.\n */\nexport function appendSetCookie(response: unknown, cookie: string): void {\n const raw = resolveHeaderCapableResponse(response);\n if (!raw) return;\n raw.setHeader('set-cookie', [...existingSetCookies(raw), cookie]);\n}\n\ninterface RawNodeResponse extends HeaderCapableResponse {\n statusCode: number;\n end(chunk?: string): unknown;\n}\n\nfunction isRawNodeResponse(value: unknown): value is RawNodeResponse {\n return (\n isRecord(value) &&\n typeof value.getHeader === 'function' &&\n typeof value.setHeader === 'function' &&\n typeof value.end === 'function'\n );\n}\n\nfunction resolveRawResponse(response: unknown): RawNodeResponse | null {\n if (isRawNodeResponse(response)) return response;\n if (isRecord(response) && isRawNodeResponse(response.raw)) return response.raw;\n return null;\n}\n\n/**\n * Fully send a redirect response (status + `Location` + end the response) — used by the guard's\n * exception-filter path, which owns the ENTIRE response (unlike a passthrough controller handler\n * that can just set headers and return a body for Nest to send). No-ops gracefully if the\n * response can't be unwrapped.\n */\nexport function sendRedirect(response: unknown, status: number, location: string): void {\n const raw = resolveRawResponse(response);\n if (!raw) return;\n raw.statusCode = status;\n raw.setHeader('location', location);\n raw.end();\n}\n","// packages/dashboard/src/server/auth/session-cookie.ts\nimport { createHmac, timingSafeEqual } from 'node:crypto';\n\n/** The validated dashboard session attached to a request once a cookie verifies. */\nexport interface DashboardSession {\n /** Stable user id (the session user's `id`). */\n sub: string;\n /** Optional display name. */\n name?: string;\n /** Free-form role strings; the dashboard does not interpret them. */\n roles: string[];\n /** Issued-at, epoch milliseconds. */\n iat: number;\n /** Expiry, epoch milliseconds. */\n exp: number;\n}\n\n/** The session user the host's `login` hook returns to mint a cookie. */\nexport interface DashboardSessionUser {\n id: string;\n name?: string;\n roles?: string[];\n}\n\nexport interface SignOptions {\n secret: string;\n ttlMs: number;\n /** Injectable clock (epoch ms) for deterministic tests. Defaults to `Date.now()`. */\n now?: number;\n}\n\nexport interface VerifyOptions {\n secret: string;\n /** Injectable clock (epoch ms) for deterministic tests. Defaults to `Date.now()`. */\n now?: number;\n}\n\n/** Clock-skew grace applied to `exp` so a marginally-late clock doesn't bounce a valid cookie. */\nconst EXP_GRACE_MS = 30_000;\n\nfunction base64urlEncode(value: string): string {\n return Buffer.from(value, 'utf8').toString('base64url');\n}\n\nfunction base64urlDecode(value: string): string {\n return Buffer.from(value, 'base64url').toString('utf8');\n}\n\nfunction hmac(secret: string, payload: string): string {\n return createHmac('sha256', secret).update(payload).digest('base64url');\n}\n\n/**\n * Sign a session into the cookie value `base64url(payload).base64url(hmac)`. Stateless\n * HMAC-SHA256, no store — `node:crypto` only, no JWT dependency. Mirrors\n * `@dudousxd/nestjs-telescope`'s `dashboardAuth` cookie codec.\n */\nexport function signSessionCookie(user: DashboardSessionUser, options: SignOptions): string {\n const issuedAt = options.now ?? Date.now();\n const session: DashboardSession = {\n sub: user.id,\n ...(user.name !== undefined ? { name: user.name } : {}),\n roles: user.roles ?? [],\n iat: issuedAt,\n exp: issuedAt + options.ttlMs,\n };\n const encodedPayload = base64urlEncode(JSON.stringify(session));\n const signature = hmac(options.secret, encodedPayload);\n return `${encodedPayload}.${signature}`;\n}\n\n/** Type guard for the decoded payload shape — defends against tampered/legacy payloads. */\nfunction isSessionPayload(value: unknown): value is DashboardSession {\n if (typeof value !== 'object' || value === null) return false;\n const record: Record<string, unknown> = { ...value };\n if (typeof record.sub !== 'string') return false;\n if (record.name !== undefined && typeof record.name !== 'string') return false;\n if (!Array.isArray(record.roles)) return false;\n if (!record.roles.every((role) => typeof role === 'string')) return false;\n if (typeof record.iat !== 'number' || !Number.isFinite(record.iat)) return false;\n if (typeof record.exp !== 'number' || !Number.isFinite(record.exp)) return false;\n return true;\n}\n\n/**\n * Verify a cookie value and return the session, or `null` for anything that's tampered,\n * malformed, or expired (past `exp` + a 30s grace). Constant-time signature comparison. NEVER\n * throws — any parse failure yields `null`.\n */\nexport function verifySessionCookie(\n value: string,\n options: VerifyOptions,\n): DashboardSession | null {\n try {\n const dot = value.indexOf('.');\n if (dot <= 0 || dot === value.length - 1) return null;\n const encodedPayload = value.slice(0, dot);\n const providedSignature = value.slice(dot + 1);\n const expectedSignature = hmac(options.secret, encodedPayload);\n const provided = Buffer.from(providedSignature, 'base64url');\n const expected = Buffer.from(expectedSignature, 'base64url');\n // timingSafeEqual throws on a length mismatch — guard it so a wrong-length (tampered)\n // signature returns null instead of throwing.\n if (provided.length !== expected.length) return null;\n if (!timingSafeEqual(provided, expected)) return null;\n const decoded: unknown = JSON.parse(base64urlDecode(encodedPayload));\n if (!isSessionPayload(decoded)) return null;\n const now = options.now ?? Date.now();\n if (now > decoded.exp + EXP_GRACE_MS) return null;\n const session: DashboardSession = {\n sub: decoded.sub,\n ...(decoded.name !== undefined ? { name: decoded.name } : {}),\n roles: decoded.roles,\n iat: decoded.iat,\n exp: decoded.exp,\n };\n return session;\n } catch {\n return null;\n }\n}\n","import { serializeSetCookie } from './cookie-header.js';\n// packages/dashboard/src/server/auth/session-cookie-io.ts\nimport type { ResolvedDashboardAuth } from './dashboard-auth-config.js';\nimport { isHttpsRequest } from './http-request.js';\nimport { appendSetCookie } from './response.js';\nimport {\n type DashboardSession,\n type DashboardSessionUser,\n signSessionCookie,\n} from './session-cookie.js';\n\n/** Cookie name carrying the signed dashboard session. */\nexport const SESSION_COOKIE_NAME = 'agent_dashboard_session';\n\n/**\n * Cookie `Path`. Unlike `@dudousxd/nestjs-telescope` (UI + API always share one mount root, so its\n * cookie scopes to that root), this dashboard supports an `apiBasePath` configured OUTSIDE\n * `basePath` (e.g. `/api/ai-gateway` alongside a `/ai-gateway` UI) — a path scoped to `basePath`\n * would never ride to a separately-mounted API. `Path=/` is the only scoping that is correct for\n * every mount configuration; `HttpOnly` + `SameSite=Lax` already do the real security work.\n */\nconst COOKIE_PATH = '/';\n\n/**\n * Sign a fresh session for `user` and append it as a `Set-Cookie` on the response, `Secure` when\n * the request is https.\n */\nexport function issueSessionCookie(\n user: DashboardSessionUser,\n context: {\n auth: ResolvedDashboardAuth;\n request: unknown;\n response: unknown;\n now?: number;\n },\n): void {\n const value = signSessionCookie(user, {\n secret: context.auth.secret,\n ttlMs: context.auth.ttlMs,\n ...(context.now !== undefined ? { now: context.now } : {}),\n });\n const cookie = serializeSetCookie(SESSION_COOKIE_NAME, value, {\n path: COOKIE_PATH,\n maxAgeSeconds: Math.floor(context.auth.ttlMs / 1000),\n secure: isHttpsRequest(context.request),\n });\n appendSetCookie(context.response, cookie);\n}\n\n/** Append a cookie-clearing `Set-Cookie` (Max-Age=0). */\nexport function clearSessionCookie(context: { request: unknown; response: unknown }): void {\n const cookie = serializeSetCookie(SESSION_COOKIE_NAME, '', {\n path: COOKIE_PATH,\n maxAgeSeconds: 0,\n secure: isHttpsRequest(context.request),\n clear: true,\n });\n appendSetCookie(context.response, cookie);\n}\n\n/**\n * Sliding renewal + revalidation. When a valid cookie is past 50% of its TTL, re-issue a fresh one\n * so an active session never expires mid-use — but first give the host's `revalidate` hook a say,\n * so a deactivated or demoted user loses access instead of riding a self-renewing cookie forever.\n *\n * Returns `false` when the session was revoked (the clearing `Set-Cookie` is already queued and the\n * caller must deny the request); `true` otherwise, including when no renewal was due.\n */\nexport async function maybeRenewSession(\n auth: ResolvedDashboardAuth,\n session: DashboardSession,\n request: unknown,\n response: unknown,\n now: number = Date.now(),\n): Promise<boolean> {\n if (now - session.iat <= auth.ttlMs / 2) return true;\n const user: DashboardSessionUser = {\n id: session.sub,\n ...(session.name !== undefined ? { name: session.name } : {}),\n roles: session.roles,\n };\n if (auth.revalidate) {\n let allowed: boolean;\n try {\n allowed = await auth.revalidate(user);\n } catch {\n // Fail closed: a throwing hook revokes rather than silently extending the session.\n allowed = false;\n }\n if (!allowed) {\n clearSessionCookie({ request, response });\n return false;\n }\n }\n issueSessionCookie(user, { auth, request, response, now });\n return true;\n}\n","import { subscribe, unsubscribe } from 'node:diagnostics_channel';\nimport type {\n ActorSpendRow,\n AgentApprovalPort,\n AgentGovernanceQueries,\n AgentPricingStore,\n CurrentModelPrice,\n GovernancePage,\n GovernancePageQuery,\n GovernanceRange,\n ModelSpendRow,\n PendingApprovalRow,\n RecentRunRow,\n RunAgentBreakdownRow,\n RunErrorBreakdownRow,\n RunMetrics,\n RunTrendPoint,\n RunWhere,\n ThreadActivityRow,\n ThreadSpendRow,\n ThreadWhere,\n ToolCallActivityRow,\n ToolCallWhere,\n ToolStatRow,\n UsageTrendPoint,\n} from '@dudousxd/nestjs-agent-core';\nimport { channelName } from '@dudousxd/nestjs-diagnostics';\nimport { Inject, Injectable, NotImplementedException, Optional } from '@nestjs/common';\nimport { Observable } from 'rxjs';\nimport type { ActorDirectory } from './actor-directory.js';\nimport { parseApprovalDecision } from './parse-approval-decision.js';\nimport { parsePriceInput } from './parse-price-input.js';\nimport {\n AGENT_ACTOR_DIRECTORY,\n AGENT_APPROVAL_PORT,\n AGENT_GOVERNANCE_QUERIES,\n AGENT_PRICING_STORE,\n} from './tokens.js';\n\n/** An actor-scoped row decorated with its resolved display label — `null` when unbound or unresolved. */\nexport interface WithActorLabel {\n actorLabel: string | null;\n}\n\nexport type ActorSpendRowWithLabel = ActorSpendRow & WithActorLabel;\nexport type ThreadSpendRowWithLabel = ThreadSpendRow & WithActorLabel;\nexport type ThreadActivityRowWithLabel = ThreadActivityRow & WithActorLabel;\n\n/** The spend/usage overview the SPA renders on its headline section (`GET <api>/spend`). */\nexport interface SpendOverview {\n byModel: ModelSpendRow[];\n byActor: ActorSpendRowWithLabel[];\n trend: UsageTrendPoint[];\n}\n\n/** The run-reliability overview the SPA renders on its Reliability section (`GET <api>/reliability`). */\nexport interface ReliabilityOverview {\n metrics: RunMetrics;\n byAgent: RunAgentBreakdownRow[];\n errors: RunErrorBreakdownRow[];\n trend: RunTrendPoint[];\n}\n\n/** The message returned as a 501 when no `AGENT_PRICING_STORE` is bound. */\nconst PRICING_STORE_UNBOUND_MESSAGE =\n 'Pricing CRUD is unavailable: no AGENT_PRICING_STORE is bound. Bind a pricing store (e.g. ' +\n 'MikroOrmPricingStore from @dudousxd/nestjs-agent-store-mikro-orm) to enable it.';\n\n/** The message returned as a 501 when no `AGENT_APPROVAL_PORT` is bound. */\nconst APPROVAL_PORT_UNBOUND_MESSAGE =\n 'Approve/reject is unavailable: no AGENT_APPROVAL_PORT is bound. Import AgentModule from ' +\n '@dudousxd/nestjs-agent alongside this dashboard to enable it — the approvals inbox stays ' +\n 'read-only until then.';\n\n/** One live agent event forwarded over SSE, flattened from the `aviary:agent:*` diagnostics envelope. */\nexport interface LiveAgentEvent {\n /** The event name, e.g. `run.started` / `tool-call` / `quota.exceeded`. */\n event: string;\n /** Epoch millis the event was emitted. */\n ts: number;\n /** The library-defined payload (see the `Agent*Event` shapes in core's diagnostics). */\n payload: Record<string, unknown>;\n}\n\n/** The `aviary:agent:*` events the Live feed tails. Mirrors the telescope watcher's subscription. */\nconst AGENT_EVENTS = [\n 'run.started',\n 'message',\n 'tool-call',\n 'quota.exceeded',\n 'run.finished',\n 'delegated',\n] as const;\n\n/** The `node:diagnostics_channel` envelope `emit()` publishes (see `@dudousxd/nestjs-diagnostics`). */\ninterface AgentDiagnosticEnvelope {\n event: string;\n ts?: number;\n payload?: Record<string, unknown>;\n}\n\n/** Narrow the untyped diagnostics-channel message to the envelope we forward. */\nfunction isAgentEnvelope(message: unknown): message is AgentDiagnosticEnvelope {\n return (\n typeof message === 'object' &&\n message !== null &&\n 'event' in message &&\n typeof (message as { event: unknown }).event === 'string'\n );\n}\n\n/**\n * Read-model + live bridge backing the AI-gateway console.\n *\n * - Historical, restart-surviving spend/usage/threads come from the injected\n * {@link AGENT_GOVERNANCE_QUERIES} read-model (backed by a store adapter). The host must provide\n * that token — bind it via your `@dudousxd/nestjs-agent` module (global) alongside this dashboard.\n * - Live activity comes off the `aviary:agent:*` diagnostics channel, subscribed per SSE client and\n * unsubscribed when the client disconnects.\n * - `actorLabel` on actor-scoped rows comes from the OPTIONAL {@link AGENT_ACTOR_DIRECTORY} — `null`\n * on every row when nothing is bound, so the console degrades to raw `actorRef`s instead of failing.\n * - Pricing CRUD (`listPrices`/`upsertPrice`) reads/writes the OPTIONAL {@link AGENT_PRICING_STORE} —\n * a 501 with a clear message when nothing is bound.\n */\n@Injectable()\nexport class DashboardService {\n constructor(\n @Inject(AGENT_GOVERNANCE_QUERIES) private readonly queries: AgentGovernanceQueries,\n @Optional()\n @Inject(AGENT_ACTOR_DIRECTORY)\n private readonly actorDirectory?: ActorDirectory,\n @Optional()\n @Inject(AGENT_PRICING_STORE)\n private readonly pricingStore?: AgentPricingStore,\n @Optional()\n @Inject(AGENT_APPROVAL_PORT)\n private readonly approvalPort?: AgentApprovalPort,\n ) {}\n\n /** Spend/usage overview for a day range: by-model + by-actor spend and the daily trend, in parallel. */\n async spend(range: GovernanceRange): Promise<SpendOverview> {\n const [byModel, byActorRaw, trend] = await Promise.all([\n this.queries.spendByModel(range),\n this.queries.spendByActor(range),\n this.queries.usageTrend(range),\n ]);\n const byActor = await this.withActorLabels(byActorRaw);\n return { byModel, byActor, trend };\n }\n\n /** Top threads by cost for a day range (default 10, highest cost first). */\n async topThreads(range: GovernanceRange, limit = 10): Promise<ThreadSpendRowWithLabel[]> {\n const rows = await this.queries.spendByThread(range, limit);\n return this.withActorLabels(rows);\n }\n\n /** Run reliability for a day range: metrics, by-agent/by-error breakdowns and the trend, in parallel. */\n async reliability(range: GovernanceRange): Promise<ReliabilityOverview> {\n const [metrics, byAgent, errors, trend] = await Promise.all([\n this.queries.runMetrics(range),\n this.queries.runsByAgent(range),\n this.queries.runErrors(range),\n this.queries.runTrend(range),\n ]);\n return { metrics, byAgent, errors, trend };\n }\n\n /** Most recent runs (status/agent/duration/error) for the Reliability recent-runs table. */\n recentRuns(limit: number): Promise<RecentRunRow[]> {\n return this.queries.recentRuns(limit);\n }\n\n /** Most recent tool calls (status/type/thread) for the Runs & tools activity feed. */\n recentToolCalls(limit: number): Promise<ToolCallActivityRow[]> {\n return this.queries.recentToolCalls(limit);\n }\n\n /** Paged, filterable tool calls for the Runs & tools activity table. */\n toolCallsPage(\n query: GovernancePageQuery<ToolCallWhere>,\n ): Promise<GovernancePage<ToolCallActivityRow>> {\n return this.queries.toolCallsPage(query);\n }\n\n /** Paged, filterable runs for the Reliability recent-runs table. */\n runsPage(query: GovernancePageQuery<RunWhere>): Promise<GovernancePage<RecentRunRow>> {\n return this.queries.runsPage(query);\n }\n\n /** Tool calls sitting `pending_approval`, oldest first, for the cross-thread approvals inbox. */\n pendingApprovals(limit: number): Promise<PendingApprovalRow[]> {\n return this.queries.pendingApprovals(limit);\n }\n\n /** Per-tool call/failure/rejection/latency rollup for a day range, for the Tools section. */\n toolStats(range: GovernanceRange): Promise<ToolStatRow[]> {\n return this.queries.toolStats(range);\n }\n\n /**\n * Decide a pending HITL tool call from the console. Routes through the OPTIONAL\n * {@link AGENT_APPROVAL_PORT} — bound by `@dudousxd/nestjs-agent` to the SAME signal path chat\n * approvals use — so a 501 here (checked BEFORE body validation, same ordering as\n * {@link upsertPrice}) means \"no approval port bound\", not \"your body was invalid\". `executedByRef`\n * is an OPAQUE decider ref the caller resolved from the live request (see\n * `AgentDashboardOptions.approvalActorRef`); omitted when the host didn't configure one.\n */\n async decideApproval(\n toolCallId: string,\n body: unknown,\n executedByRef: string | undefined,\n ): Promise<void> {\n if (this.approvalPort === undefined) {\n throw new NotImplementedException(APPROVAL_PORT_UNBOUND_MESSAGE);\n }\n const decision = parseApprovalDecision(body);\n const opts = executedByRef !== undefined ? { executedByRef } : {};\n if (decision.approved) {\n await this.approvalPort.approve(toolCallId, opts);\n return;\n }\n await this.approvalPort.reject(toolCallId, {\n ...opts,\n ...(decision.reason !== undefined ? { reason: decision.reason } : {}),\n });\n }\n\n /** Most recent threads with rolled-up message/token counts. */\n async recentThreads(limit: number): Promise<ThreadActivityRowWithLabel[]> {\n const rows = await this.queries.recentThreads(limit);\n return this.withActorLabels(rows);\n }\n\n /**\n * Paged, filterable threads for the Runs & tools threads table — actor-labeled the same way\n * {@link recentThreads} is.\n */\n async threadsPage(\n query: GovernancePageQuery<ThreadWhere>,\n ): Promise<GovernancePage<ThreadActivityRowWithLabel>> {\n const page = await this.queries.threadsPage(query);\n const rows = await this.withActorLabels(page.rows);\n return { ...page, rows };\n }\n\n /**\n * Decorate actor-scoped rows with `actorLabel`, batching the distinct `actorRef`s into ONE\n * {@link ActorDirectory.resolveDisplay} call per response. `null` for every row when no directory\n * is bound, or for a ref the directory didn't resolve.\n */\n private async withActorLabels<Row extends { actorRef: string }>(\n rows: Row[],\n ): Promise<(Row & WithActorLabel)[]> {\n if (rows.length === 0) {\n return [];\n }\n if (this.actorDirectory === undefined) {\n return rows.map((row) => ({ ...row, actorLabel: null }));\n }\n const refs = [...new Set(rows.map((row) => row.actorRef))];\n const resolved = await this.actorDirectory.resolveDisplay(refs);\n return rows.map((row) => ({ ...row, actorLabel: resolved[row.actorRef] ?? null }));\n }\n\n /** Current price row per model, for the pricing tab. 501s when no `AGENT_PRICING_STORE` is bound. */\n async listPrices(): Promise<CurrentModelPrice[]> {\n if (this.pricingStore === undefined) {\n throw new NotImplementedException(PRICING_STORE_UNBOUND_MESSAGE);\n }\n return this.pricingStore.listCurrentPrices();\n }\n\n /**\n * Set a model's current price (`POST <api>/pricing` body). 501s when no `AGENT_PRICING_STORE` is\n * bound (checked BEFORE body validation, so an unbound store always reports as unimplemented rather\n * than as a validation error); otherwise the body is minimally validated via {@link parsePriceInput}.\n */\n async upsertPrice(body: unknown): Promise<void> {\n if (this.pricingStore === undefined) {\n throw new NotImplementedException(PRICING_STORE_UNBOUND_MESSAGE);\n }\n await this.pricingStore.upsertModelPrice(parsePriceInput(body));\n }\n\n /**\n * Live SSE stream of `aviary:agent:*` diagnostics events. One subscription per SSE client:\n * subscribing wires a handler onto each agent channel; the returned teardown removes them all when\n * the client disconnects (or the observable is otherwise unsubscribed).\n */\n streamEvents(): Observable<{ data: LiveAgentEvent }> {\n return new Observable<{ data: LiveAgentEvent }>((subscriber) => {\n const bindings = AGENT_EVENTS.map((event) => {\n const name = channelName('agent', event);\n const handler = (message: unknown): void => {\n if (!isAgentEnvelope(message)) return;\n subscriber.next({\n data: {\n event: message.event,\n ts: message.ts ?? Date.now(),\n payload: message.payload ?? {},\n },\n });\n };\n subscribe(name, handler);\n return { name, handler };\n });\n return () => {\n for (const binding of bindings) unsubscribe(binding.name, binding.handler);\n };\n });\n }\n}\n","import { BadRequestException } from '@nestjs/common';\n\n/** The parsed, minimally-validated `POST <api>/approvals/:toolCallId` body. */\nexport interface ApprovalDecisionInput {\n approved: boolean;\n reason?: string;\n}\n\n/**\n * Minimal shape guard for a `POST <api>/approvals/:toolCallId` body — rejects junk before it reaches\n * `AgentApprovalPort.approve`/`reject`. Mirrors `parsePriceInput`'s role for the pricing endpoint.\n */\nexport function parseApprovalDecision(body: unknown): ApprovalDecisionInput {\n if (typeof body !== 'object' || body === null) {\n throw new BadRequestException('Expected a JSON object body.');\n }\n const { approved, reason } = body as Record<string, unknown>;\n\n if (typeof approved !== 'boolean') {\n throw new BadRequestException('\"approved\" must be a boolean.');\n }\n if (reason !== undefined && typeof reason !== 'string') {\n throw new BadRequestException('\"reason\" must be a string when present.');\n }\n\n return { approved, ...(reason !== undefined ? { reason } : {}) };\n}\n","import type { ModelPriceInput } from '@dudousxd/nestjs-agent-core';\nimport { BadRequestException } from '@nestjs/common';\n\n/**\n * Minimal shape guard for a `POST <api>/pricing` body — rejects junk before it reaches\n * `AgentPricingStore.upsertModelPrice`. Not a full schema validator (the store adapter owns real\n * constraints, e.g. uniqueness); this only checks the wire shape core's `ModelPriceInput` requires.\n */\nexport function parsePriceInput(body: unknown): ModelPriceInput {\n if (typeof body !== 'object' || body === null) {\n throw new BadRequestException('Expected a JSON object body.');\n }\n const { modelId, inputPricePer1m, outputPricePer1m, cacheWritePricePer1m, cacheReadPricePer1m } =\n body as Record<string, unknown>;\n\n if (typeof modelId !== 'string' || modelId.trim().length === 0) {\n throw new BadRequestException('\"modelId\" must be a non-empty string.');\n }\n if (!isFiniteNonNegative(inputPricePer1m)) {\n throw new BadRequestException('\"inputPricePer1m\" must be a non-negative number.');\n }\n if (!isFiniteNonNegative(outputPricePer1m)) {\n throw new BadRequestException('\"outputPricePer1m\" must be a non-negative number.');\n }\n if (cacheWritePricePer1m !== undefined && !isFiniteNonNegative(cacheWritePricePer1m)) {\n throw new BadRequestException(\n '\"cacheWritePricePer1m\" must be a non-negative number when present.',\n );\n }\n if (cacheReadPricePer1m !== undefined && !isFiniteNonNegative(cacheReadPricePer1m)) {\n throw new BadRequestException(\n '\"cacheReadPricePer1m\" must be a non-negative number when present.',\n );\n }\n\n return {\n modelId,\n inputPricePer1m,\n outputPricePer1m,\n ...(cacheWritePricePer1m !== undefined ? { cacheWritePricePer1m } : {}),\n ...(cacheReadPricePer1m !== undefined ? { cacheReadPricePer1m } : {}),\n };\n}\n\nfunction isFiniteNonNegative(value: unknown): value is number {\n return typeof value === 'number' && Number.isFinite(value) && value >= 0;\n}\n","import type { RunWhere, ThreadWhere, ToolCallWhere } from '@dudousxd/nestjs-agent-core';\nimport { BadRequestException } from '@nestjs/common';\n\nconst ISO_DAY = /^\\d{4}-\\d{2}-\\d{2}$/;\n\n/** Parse a `page` query param (1-based); falls back to 1 when absent or not a positive integer. */\nexport function parsePageNumber(value: string | undefined): number {\n const parsed = value === undefined ? Number.NaN : Number.parseInt(value, 10);\n if (!Number.isFinite(parsed) || parsed < 1) return 1;\n return parsed;\n}\n\n/** Rejects a `where[<field>]` whose field name isn't in `allowed`, naming the offending field. */\nfunction assertKnownFields(raw: Record<string, string>, allowed: readonly string[]): void {\n for (const field of Object.keys(raw)) {\n if (!allowed.includes(field)) {\n throw new BadRequestException(`Unknown where field \"${field}\".`);\n }\n }\n}\n\n/** Rejects a day-bound `where[<field>]` that isn't `YYYY-MM-DD`, mirroring the range endpoints' check. */\nfunction assertDayFields(raw: Record<string, string>, dayFields: readonly string[]): void {\n for (const field of dayFields) {\n const value = raw[field];\n if (value !== undefined && !ISO_DAY.test(value)) {\n throw new BadRequestException(`\"where[${field}]\" must be YYYY-MM-DD.`);\n }\n }\n}\n\nconst DAY_FIELDS = ['fromDay', 'toDay'] as const;\nconst TOOL_CALL_WHERE_FIELDS = [\n 'toolName',\n 'toolType',\n 'status',\n 'threadId',\n 'fromDay',\n 'toDay',\n] as const;\n\n/**\n * Parse `GET tool-calls-page`'s `where[...]` query params into a `ToolCallWhere`. `undefined` (no\n * `where` params at all) means no filter. Throws `BadRequestException` on an unknown field name or a\n * malformed day bound.\n */\nexport function parseToolCallWhere(\n raw: Record<string, string> | undefined,\n): ToolCallWhere | undefined {\n if (raw === undefined || Object.keys(raw).length === 0) return undefined;\n assertKnownFields(raw, TOOL_CALL_WHERE_FIELDS);\n assertDayFields(raw, DAY_FIELDS);\n return {\n ...(raw.toolName !== undefined ? { toolName: raw.toolName } : {}),\n ...(raw.toolType !== undefined ? { toolType: raw.toolType } : {}),\n ...(raw.status !== undefined ? { status: raw.status } : {}),\n ...(raw.threadId !== undefined ? { threadId: raw.threadId } : {}),\n ...(raw.fromDay !== undefined ? { fromDay: raw.fromDay } : {}),\n ...(raw.toDay !== undefined ? { toDay: raw.toDay } : {}),\n };\n}\n\nconst THREAD_WHERE_FIELDS = ['actorRef', 'title', 'fromDay', 'toDay'] as const;\n\n/**\n * Parse `GET threads-page`'s `where[...]` query params into a `ThreadWhere`. `undefined` means no\n * filter. Throws `BadRequestException` on an unknown field name or a malformed day bound.\n */\nexport function parseThreadWhere(raw: Record<string, string> | undefined): ThreadWhere | undefined {\n if (raw === undefined || Object.keys(raw).length === 0) return undefined;\n assertKnownFields(raw, THREAD_WHERE_FIELDS);\n assertDayFields(raw, DAY_FIELDS);\n return {\n ...(raw.actorRef !== undefined ? { actorRef: raw.actorRef } : {}),\n ...(raw.title !== undefined ? { title: raw.title } : {}),\n ...(raw.fromDay !== undefined ? { fromDay: raw.fromDay } : {}),\n ...(raw.toDay !== undefined ? { toDay: raw.toDay } : {}),\n };\n}\n\nconst RUN_WHERE_FIELDS = ['agentName', 'status', 'errorCode', 'fromDay', 'toDay'] as const;\n\n/**\n * Parse `GET runs-page`'s `where[...]` query params into a `RunWhere`. `undefined` means no filter.\n * Throws `BadRequestException` on an unknown field name or a malformed day bound.\n */\nexport function parseRunWhere(raw: Record<string, string> | undefined): RunWhere | undefined {\n if (raw === undefined || Object.keys(raw).length === 0) return undefined;\n assertKnownFields(raw, RUN_WHERE_FIELDS);\n assertDayFields(raw, DAY_FIELDS);\n return {\n ...(raw.agentName !== undefined ? { agentName: raw.agentName } : {}),\n ...(raw.status !== undefined ? { status: raw.status } : {}),\n ...(raw.errorCode !== undefined ? { errorCode: raw.errorCode } : {}),\n ...(raw.fromDay !== undefined ? { fromDay: raw.fromDay } : {}),\n ...(raw.toDay !== undefined ? { toDay: raw.toDay } : {}),\n };\n}\n","import {\n Body,\n Controller,\n Get,\n Header,\n HttpCode,\n Inject,\n Logger,\n NotFoundException,\n Optional,\n Post,\n Query,\n Req,\n Res,\n UnauthorizedException,\n} from '@nestjs/common';\nimport { parseCookieHeader } from './auth/cookie-header.js';\nimport type { ResolvedDashboardAuth } from './auth/dashboard-auth-config.js';\nimport { readCookieHeader } from './auth/http-request.js';\nimport { renderLoginPage, renderSessionRequiredPage } from './auth/login-page.js';\nimport { sanitizeReturnTo } from './auth/sanitize-return-to.js';\nimport {\n SESSION_COOKIE_NAME,\n clearSessionCookie,\n issueSessionCookie,\n} from './auth/session-cookie-io.js';\nimport { type DashboardSessionUser, verifySessionCookie } from './auth/session-cookie.js';\nimport { DASHBOARD_AUTH, DASHBOARD_BASE_PATH } from './tokens.js';\n\n/** The slice of the platform request this controller reads — structural, no Express/Fastify dependency. */\ninterface AuthPageRequest {\n headers: Record<string, string | string[] | undefined>;\n}\n\n/** The slice of the platform response this controller writes (passthrough mode — Nest still sends). */\ninterface AuthPageResponse {\n status(code: number): unknown;\n setHeader(name: string, value: string): unknown;\n}\n\ninterface LoginFormBody {\n username?: unknown;\n password?: unknown;\n returnTo?: unknown;\n}\n\nfunction isNonEmptyString(value: unknown): value is string {\n return typeof value === 'string' && value.trim() !== '';\n}\n\nfunction isString(value: unknown): value is string {\n return typeof value === 'string';\n}\n\n/**\n * Mounted directly on `AgentDashboardModule` at `<basePath>/auth/*` — a SEPARATE controller from\n * `AgentUiController`/`AgentApiController` so it carries neither `DashboardAuthPageGuard`,\n * `DashboardAuthGuard`, nor any host `guards`: these routes MINT (and clear) the session cookie the\n * built-in gates check for, so they can never be made to require the very auth they grant. Mirrors\n * `@dudousxd/nestjs-telescope`'s own auth controller staying outside its `stampGuards` call.\n *\n * Two ways to mint that cookie, matching `resolveDashboardAuth`'s modes: Mode A (`session`) — the\n * host frontend, already carrying its own auth, POSTs to `<basePath>/auth/session` and the host\n * hook decides — or Mode B (`login`), a dependency-free, server-rendered login form (GET/POST) —\n * the bundled React SPA in this package is a built Vite artifact with no auth-aware UI of its own,\n * so gating the actual page navigation needs a self-contained flow that doesn't touch it. Each\n * mode's routes 404 when that mode isn't configured (`auth.session`/`auth.login` absent), and\n * every route 404s outright when `dashboardAuth` is unconfigured (`DASHBOARD_AUTH` resolves to\n * `null`) — no dangling login page when the feature is off.\n */\n@Controller('auth')\nexport class AgentDashboardAuthController {\n private readonly logger = new Logger(AgentDashboardAuthController.name);\n /** Warn once PER HOOK KIND (not per request) so a broken hook can't be used to flood the logs. */\n private readonly warnedHooks = new Set<string>();\n\n constructor(\n @Optional() @Inject(DASHBOARD_AUTH) private readonly auth: ResolvedDashboardAuth | null,\n @Inject(DASHBOARD_BASE_PATH) private readonly basePath: string,\n ) {}\n\n @Get('login')\n @Header('Content-Type', 'text/html; charset=utf-8')\n @Header('Cache-Control', 'no-store')\n loginPage(\n @Req() req: AuthPageRequest,\n @Res({ passthrough: true }) res: AuthPageResponse,\n @Query('returnTo') returnToQuery?: string,\n @Query('error') error?: string,\n ): string {\n const auth = this.requireAuth();\n // Mode A only: no login screen exists — the host mints the session.\n if (!auth.login) throw new NotFoundException();\n const returnTo = sanitizeReturnTo(this.basePath, returnToQuery);\n // Already signed in — a fresh GET (e.g. a bookmarked login URL) just goes back in, no need to\n // re-prompt for credentials.\n if (this.currentSession(req)) {\n res.status(302);\n res.setHeader('Location', returnTo);\n return '';\n }\n return renderLoginPage({\n actionUrl: `${this.basePath}/auth/login`,\n returnTo,\n error: error !== undefined,\n });\n }\n\n @Post('login')\n async login(\n @Body() body: LoginFormBody,\n @Req() req: unknown,\n @Res({ passthrough: true }) res: AuthPageResponse,\n ): Promise<string> {\n const auth = this.requireAuth();\n if (!auth.login) throw new NotFoundException();\n const returnTo = sanitizeReturnTo(this.basePath, body?.returnTo);\n const username = body?.username;\n const password = body?.password;\n // Username is required (non-empty, trimmed). Password is OPTIONAL end-to-end: it reaches the\n // host `login` hook AS-IS — `''` when blank/omitted — because some hosts gate on username\n // alone (e.g. flip: email must belong to an active ADMIN, password deliberately ignored). Only\n // reject here on a malformed shape (password present but not a string), never on emptiness.\n if (!isNonEmptyString(username) || (password !== undefined && !isString(password))) {\n this.redirectToLoginError(res, returnTo);\n return '';\n }\n const user = await this.runHook(\n 'login',\n () => auth.login?.(username.trim(), isString(password) ? password : '') ?? null,\n );\n if (!user) {\n this.redirectToLoginError(res, returnTo);\n return '';\n }\n issueSessionCookie(user, { auth, request: req, response: res });\n res.status(302);\n res.setHeader('Location', returnTo);\n return '';\n }\n\n @Post('logout')\n logout(@Req() req: unknown, @Res({ passthrough: true }) res: AuthPageResponse): string {\n // Best-effort: clearing is harmless even without dashboardAuth configured.\n clearSessionCookie({ request: req, response: res });\n res.status(302);\n // Runs WITHOUT requireAuth() (logout must succeed even when auth is off/misconfigured), so\n // read `this.auth?.login` directly rather than calling it. Mirrors `DashboardAuthPageGuard`'s\n // `deny()` mode check: Mode-A-only has no login screen to land on (`loginPage` above 404s\n // under Mode A), so bounce to the instruction page instead. When `dashboardAuth` isn't\n // configured at all, `this.auth` is `null` and today's target is preserved unchanged.\n res.setHeader(\n 'Location',\n this.auth && !this.auth.login\n ? `${this.basePath}/auth/session-required`\n : `${this.basePath}/auth/login`,\n );\n return '';\n }\n\n // Mode A: the host frontend (carrying its own auth) POSTs here; the host hook validates the raw\n // request and returns the session user, or `null` to deny. No credential reaches this library.\n @Post('session')\n @HttpCode(204)\n async session(\n @Req() request: unknown,\n @Res({ passthrough: true }) response: unknown,\n ): Promise<void> {\n const auth = this.requireAuth();\n if (!auth.session) throw new NotFoundException();\n const user = await this.runHook('session', () => auth.session?.(request) ?? null);\n if (!user) throw new UnauthorizedException();\n issueSessionCookie(user, { auth, request, response });\n }\n\n // Mode-A-only landing: the host mints the session, so there is nothing to submit here. Serves\n // as the redirect target `DashboardAuthPageGuard` bounces to instead of `/auth/login`, which\n // 404s under Mode A (see `loginPage` above).\n @Get('session-required')\n @Header('Content-Type', 'text/html; charset=utf-8')\n @Header('Cache-Control', 'no-store, must-revalidate')\n sessionRequiredPage(): string {\n const auth = this.requireAuth();\n if (auth.login) throw new NotFoundException();\n return renderSessionRequiredPage();\n }\n\n /** Uniform failure: same redirect (generic error flag) whether the user is unknown or the password is wrong. */\n private redirectToLoginError(res: AuthPageResponse, returnTo: string): void {\n res.status(303);\n res.setHeader(\n 'Location',\n `${this.basePath}/auth/login?error=1&returnTo=${encodeURIComponent(returnTo)}`,\n );\n }\n\n private currentSession(req: AuthPageRequest): boolean {\n const auth = this.auth;\n if (!auth) return false;\n const cookieValue = parseCookieHeader(readCookieHeader(req))[SESSION_COOKIE_NAME];\n if (cookieValue === undefined) return false;\n return verifySessionCookie(cookieValue, { secret: auth.secret }) !== null;\n }\n\n /**\n * Run a host hook (`login` or `session`) defensively: a throw (sync or async) is treated as a\n * denial (`null`) and warn-logged ONCE PER KIND, so a buggy/unreachable hook never 500s the\n * endpoint into a stack-trace leak nor floods the logs on repeated bad attempts.\n */\n private async runHook(\n kind: string,\n run: () => Promise<DashboardSessionUser | null> | DashboardSessionUser | null,\n ): Promise<DashboardSessionUser | null> {\n try {\n return (await run()) ?? null;\n } catch (error) {\n if (!this.warnedHooks.has(kind)) {\n this.warnedHooks.add(kind);\n this.logger.warn(\n `AgentDashboardModule dashboardAuth ${kind} hook threw; treating as denial. ${\n error instanceof Error ? error.message : String(error)\n }`,\n );\n }\n return null;\n }\n }\n\n private requireAuth(): ResolvedDashboardAuth {\n // The auth controller only does anything useful when dashboardAuth is configured — a\n // defensive 404 rather than a reachable runtime path for a properly-wired module.\n if (!this.auth) throw new NotFoundException();\n return this.auth;\n }\n}\n","// packages/dashboard/src/server/auth/login-page.ts\n\n/** Escape untrusted text placed into the HTML body/attributes below. */\nfunction escapeHtml(value: string): string {\n return value\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&#39;');\n}\n\nexport interface LoginPageOptions {\n /** Where the login form POSTs (`<basePath>/auth/login`). */\n actionUrl: string;\n /** Carried through as a hidden field so a successful login returns to where the visit started. */\n returnTo: string;\n /** Render the generic \"invalid credentials\" banner. */\n error?: boolean;\n}\n\n/**\n * Shared HTML shell for the `dashboardAuth` server-rendered pages (the Mode B login form below and\n * `renderSessionRequiredPage`'s Mode A instruction page) — same dark zinc card, mono type, emerald\n * accent, so every auth-adjacent page in this console reads as one product. `content` is placed\n * inside the `.card` div verbatim — always caller-controlled static/escaped markup, never raw user\n * input, so this sidesteps HTML-escaping entirely.\n */\nfunction pageShell(content: string, title: string): string {\n return `<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\" />\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n<meta name=\"robots\" content=\"noindex, nofollow\" />\n<title>${title}</title>\n<style>\n :root { color-scheme: dark; }\n * { box-sizing: border-box; }\n body {\n margin: 0;\n min-height: 100vh;\n display: flex;\n align-items: center;\n justify-content: center;\n background: #09090b;\n color: #e4e4e7;\n font: 14px/1.5 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n padding: 16px;\n }\n .card {\n width: 100%;\n max-width: 384px;\n border: 1px solid #27272a;\n background: #18181b;\n border-radius: 8px;\n padding: 32px;\n box-shadow: 0 25px 50px -12px rgb(0 0 0 / 0.5);\n }\n .brand {\n margin: 0 0 24px;\n text-align: center;\n font-size: 18px;\n font-weight: 600;\n color: #34d399;\n }\n h1 {\n margin: 0 0 12px;\n font-size: 16px;\n font-weight: 600;\n color: #f4f4f5;\n }\n p { margin: 0 0 16px; color: #a1a1aa; }\n form { display: flex; flex-direction: column; gap: 16px; }\n label { display: flex; flex-direction: column; gap: 6px; }\n .field-label {\n font-size: 11px;\n text-transform: uppercase;\n letter-spacing: 0.05em;\n color: #71717a;\n }\n input {\n border-radius: 4px;\n border: 1px solid #3f3f46;\n background: #09090b;\n color: #f4f4f5;\n padding: 8px 12px;\n font: inherit;\n outline: none;\n }\n input:focus { border-color: rgb(52 211 153 / 0.6); }\n .error { margin: 0; font-size: 12px; color: #fb7185; }\n button {\n margin-top: 8px;\n border-radius: 4px;\n border: 1px solid rgb(52 211 153 / 0.4);\n background: rgb(52 211 153 / 0.1);\n color: #6ee7b7;\n padding: 8px 12px;\n font: inherit;\n font-size: 12px;\n font-weight: 500;\n text-transform: uppercase;\n letter-spacing: 0.05em;\n cursor: pointer;\n }\n button:hover { background: rgb(52 211 153 / 0.2); }\n</style>\n</head>\n<body>\n <div class=\"card\">\n ${content}\n </div>\n</body>\n</html>`;\n}\n\n/**\n * A dependency-free, server-rendered login page — no build step, no client JS framework. The\n * visual language (dark zinc card, mono type, emerald accent) mirrors\n * `@dudousxd/nestjs-telescope`'s built-in `AuthScreen` so the two consoles feel like one family.\n */\nexport function renderLoginPage(options: LoginPageOptions): string {\n const action = escapeHtml(options.actionUrl);\n const returnTo = escapeHtml(options.returnTo);\n const errorBanner =\n options.error === true ? '<p role=\"alert\" class=\"error\">Invalid username or password.</p>' : '';\n return pageShell(\n `<p class=\"brand\">AI Gateway</p>\n <form method=\"post\" action=\"${action}\">\n <input type=\"hidden\" name=\"returnTo\" value=\"${returnTo}\" />\n <label>\n <span class=\"field-label\">Username</span>\n <input type=\"text\" name=\"username\" autocomplete=\"username\" autofocus />\n </label>\n <label>\n <span class=\"field-label\">Password</span>\n <input type=\"password\" name=\"password\" autocomplete=\"current-password\" />\n </label>\n ${errorBanner}\n <button type=\"submit\">Sign in</button>\n </form>`,\n 'Sign in — AI Gateway',\n );\n}\n\n/**\n * Mode-A-only landing (`GET <basePath>/auth/login` 404s under Mode A — see\n * `AgentDashboardAuthController.loginPage`): what `DashboardAuthPageGuard` bounces an\n * unauthenticated page navigation to instead, since there is no login form to redirect to — the\n * host mints the session itself, typically from its own console launcher. Static and\n * parameter-free: nothing on this page varies per request, so there's no `basePath` (or anything\n * else) to interpolate — no `<script>`/inline handler either, so the page stays inert under a host\n * CSP that omits `'unsafe-inline'` from `script-src`.\n */\nexport function renderSessionRequiredPage(): string {\n return pageShell(\n `<p class=\"brand\">AI Gateway</p>\n <h1>Open this console from your application</h1>\n <p>Your session is minted by the host app. Use its console launcher to sign in, then reload.</p>`,\n 'Sign in — AI Gateway',\n );\n}\n","// packages/dashboard/src/server/auth/sanitize-return-to.ts\n\n/**\n * Validate a client-supplied `returnTo` so the login flow can never be turned into an open\n * redirect: it must be a same-origin path (no scheme, no `//host` prefix) that stays within\n * `basePath`. Anything else — absent, a protocol-relative URL, a foreign path — falls back to\n * `basePath` itself.\n */\nexport function sanitizeReturnTo(basePath: string, returnTo: unknown): string {\n if (typeof returnTo !== 'string' || returnTo === '') return basePath;\n // Reject anything that isn't a plain same-origin path: no scheme (`http:`), no\n // protocol-relative prefix (`//evil.com`), must start with `/`.\n if (!returnTo.startsWith('/') || returnTo.startsWith('//')) return basePath;\n if (\n returnTo !== basePath &&\n !returnTo.startsWith(`${basePath}/`) &&\n !returnTo.startsWith(`${basePath}?`)\n ) {\n return basePath;\n }\n return returnTo;\n}\n","/**\n * Leading slash, no trailing slash (`'ai-gateway/'` -> `'/ai-gateway'`). Shared by\n * {@link AgentDashboardModule.forRoot} and {@link agentDashboardMountPaths} so the mount-path math\n * behind the module and the pure helper that mirrors it can never drift apart.\n */\nexport function normalizeDashboardPath(path: string): string {\n return `/${path.replace(/^\\/+|\\/+$/g, '')}`;\n}\n","import { normalizeDashboardPath } from './normalize-path.js';\n\n/** Same shape {@link AgentDashboardModule.forRoot} accepts — kept local so this stays a pure, DI-free helper. */\nexport interface AgentDashboardMountPathsOptions {\n basePath?: string;\n apiBasePath?: string;\n}\n\n/** Strip the leading slash `normalizeDashboardPath` adds — `setGlobalPrefix`'s `exclude` roots are unprefixed. */\nfunction unprefixed(path: string): string {\n return path.replace(/^\\/+/, '');\n}\n\n/**\n * Route roots a host must EXCLUDE from a global prefix (`setGlobalPrefix('api', { exclude })`) so\n * the AI-gateway dashboard's SPA and JSON API keep resolving at their configured mount paths instead\n * of being shifted under the prefix.\n *\n * Unlike a single-surface dashboard (e.g. `telescopeMountPaths()`), this one mounts TWO route roots —\n * the UI at `basePath` and its JSON API at `apiBasePath` — so excluding only one leaves the other\n * shadowed. `options` mirrors {@link AgentDashboardOptions} and resolves through the exact same\n * defaulting (`apiBasePath` falls back to `<basePath>/api`) as {@link AgentDashboardModule.forRoot},\n * so the excluded roots always agree with what actually got mounted.\n *\n * @example\n * ```ts\n * // Raw defaults (basePath `/ai-gateway`, apiBasePath `/ai-gateway/api`):\n * app.setGlobalPrefix('api', { exclude: agentDashboardMountPaths() });\n * // -> ['ai-gateway', 'ai-gateway/{*splat}', 'ai-gateway/api', 'ai-gateway/api/{*splat}']\n *\n * // The recommended pattern — apiBasePath nested under the app's own `/api` prefix — MUST pass the\n * // same options given to `forRoot(...)`:\n * const dashboardOptions = { apiBasePath: '/api/ai-gateway' };\n * app.setGlobalPrefix('api', { exclude: agentDashboardMountPaths(dashboardOptions) });\n * // -> ['ai-gateway', 'ai-gateway/{*splat}', 'api/ai-gateway', 'api/ai-gateway/{*splat}']\n * ```\n */\nexport function agentDashboardMountPaths(options?: AgentDashboardMountPathsOptions): string[] {\n const basePath = normalizeDashboardPath(options?.basePath ?? '/ai-gateway');\n const apiBasePath = normalizeDashboardPath(options?.apiBasePath ?? `${basePath}/api`);\n const base = unprefixed(basePath);\n const api = unprefixed(apiBasePath);\n return [base, `${base}/{*splat}`, api, `${api}/{*splat}`];\n}\n","import 'reflect-metadata';\nimport {\n type CanActivate,\n type DynamicModule,\n type InjectionToken,\n Module,\n type OptionalFactoryDependency,\n type Provider,\n type Type,\n} from '@nestjs/common';\nimport { RouterModule } from '@nestjs/core';\nimport { AgentApiController } from './agent-api.controller.js';\nimport { AgentDashboardAuthController } from './agent-dashboard-auth.controller.js';\nimport { AgentUiController } from './agent-ui.controller.js';\nimport type { DashboardAuthOptions } from './auth/dashboard-auth-config.js';\nimport { resolveDashboardAuth } from './auth/dashboard-auth-config.js';\nimport { DashboardAuthPageGuard } from './auth/dashboard-auth-page.guard.js';\nimport { DashboardAuthGuard } from './auth/dashboard-auth.guard.js';\nimport { DashboardService } from './dashboard.service.js';\nimport { baseGuards, isGuardClass, stampGuards } from './guards.js';\nimport { normalizeDashboardPath } from './normalize-path.js';\nimport {\n DASHBOARD_API_PATH,\n DASHBOARD_APPROVAL_ACTOR_REF,\n DASHBOARD_AUTH,\n DASHBOARD_BASE_PATH,\n} from './tokens.js';\n\n/**\n * `AgentApiController`/`AgentUiController`'s own, pristine `@UseGuards(...)` metadata — the\n * built-in `dashboardAuth` guard, always present — captured once at module-load time. Every\n * `stampGuards` call below appends host `guards` onto THIS baseline (never onto whatever a prior\n * `forRoot`/`forRootAsync` call last stamped), so repeated calls in the same process never compound\n * (see `./guards.ts`).\n */\nconst API_BASE_GUARDS = baseGuards(AgentApiController);\nconst UI_BASE_GUARDS = baseGuards(AgentUiController);\n\n/**\n * `TReq` mirrors core's `ActorResolver<TReq>` convention: the transport request type\n * {@link AgentDashboardOptions.approvalActorRef} receives. Defaults to `unknown` so untyped usage\n * compiles unchanged; a host may narrow it (e.g. `forRoot<Request>({ approvalActorRef: ... })`)\n * instead of writing its own `unknown`-narrowing type guard.\n */\nexport interface AgentDashboardOptions<TReq = unknown> {\n /**\n * Where the SPA (UI) is served. Default `/ai-gateway`. This is a page route — keep it out of an\n * `/api` prefix so it reads as a UI, not an endpoint.\n */\n basePath?: string;\n /**\n * Where the JSON API is mounted (what the SPA fetches). Default `<basePath>/api`. Set it under\n * your app's `/api` prefix — e.g. `/api/ai-gateway` — so the API inherits the app's auth/proxy\n * rules while the UI stays at `basePath`.\n */\n apiBasePath?: string;\n /**\n * Guard classes fronting BOTH dashboard controllers (the SPA at `basePath` and its JSON API at\n * `apiBasePath`) — APPENDED onto the built-in `dashboardAuth` gate (a no-op unless configured;\n * see {@link dashboardAuth}), so a request must pass BOTH. Omit to leave the routes gated by\n * `dashboardAuth` alone (or fully open when that's unset too).\n *\n * A guard's own DEPENDENCIES resolve from this module's `imports` (see {@link imports}) — the\n * dashboard module has no application context of its own to pull them from otherwise.\n */\n guards?: Type<CanActivate>[];\n /**\n * Extra `imports` merged into the dashboard's dynamic module — the DI resolution path for a class\n * passed to {@link guards} (or any other provider the controllers need reachable). Typically the\n * host's own auth module, e.g. `imports: [AuthModule]` alongside `guards: [JwtAuthGuard]`.\n */\n imports?: DynamicModule['imports'];\n /**\n * OVERRIDE for WHO is deciding a HITL approval — invoked on the incoming\n * `POST <api>/approvals/:toolCallId` request to stamp `AgentApprovalPort`'s `opts.executedByRef`.\n *\n * DEFAULT (option omitted): the AgentModule-configured actor resolver (`AGENT_ACTOR_RESOLVER`) is\n * consulted — the same identity seam chat requests use — and its actor id becomes the decider ref\n * (a throwing resolver, i.e. an unauthenticated request, just omits the ref). Set this only when\n * console auth differs from chat auth (e.g. the console sits behind a separate SSO whose principal\n * the chat resolver can't read); returning `undefined` leaves `executedByRef` unset — the explicit\n * override wins outright, with no resolver fallback.\n */\n approvalActorRef?: (req: TReq) => string | undefined;\n /**\n * Gate the console (SPA + API) behind a built-in, signed session cookie. Two ways to mint it:\n *\n * - **Mode A (`session`, recommended)**: your host app already has its own auth (SSO/OIDC/\n * whatever) — the host frontend, carrying that auth, POSTs to `<basePath>/auth/session`, your\n * `session` hook validates the raw request and returns the session user, and this library\n * mints its cookie from that. No credential this library understands ever exists; you keep\n * your own identity provider as the source of truth. A page-level request with no valid\n * session (and only Mode A configured) gets a small server-rendered instruction page telling\n * the visitor to sign in through your host app, since there is no login page to redirect to.\n * - **Mode B (`login`, standalone fallback)**: no host frontend/IdP to lean on — the console\n * serves its own small, dependency-free, server-rendered login page (`GET\n * <basePath>/auth/login`) and your `login` hook validates submitted username/password. A\n * missing/invalid/expired session redirects a page-level request to that login page with\n * `?returnTo=<original url>`.\n *\n * Either mode can be used alone, or both together (e.g. Mode A for normal use, Mode B as a\n * break-glass fallback) — at least one is required, or `forRoot`/`forRootAsync` throws at boot\n * (an un-mintable gate is a boot error, not a silently-open or silently-stuck console). An\n * unauthenticated API call always gets a plain `401`, regardless of mode — the caller reads the\n * status code, not HTML. Composes with `guards` — BOTH gates must pass. See\n * `DashboardAuthOptions` for the `secret`/`ttl`/`session`/`login` shape.\n */\n dashboardAuth?: DashboardAuthOptions;\n}\n\n/**\n * Async variant of {@link AgentDashboardOptions}, for a `session`/`login` hook that needs injected\n * services (e.g. an EntityManager to check credentials against a DB). Everything else stays\n * static/sync — `basePath`/`apiBasePath`/`guards` are needed at module-build time (the same\n * constraint `@dudousxd/nestjs-telescope`'s and `@dudousxd/nestjs-media`'s own `forRootAsync`s\n * document), so only the auth piece itself is resolved through DI.\n */\nexport interface AgentDashboardAsyncOptions<TReq = unknown> {\n basePath?: string;\n apiBasePath?: string;\n guards?: Type<CanActivate>[];\n imports?: DynamicModule['imports'];\n approvalActorRef?: (req: TReq) => string | undefined;\n /** Providers injected into `useDashboardAuth`, in order. Shared with a guard class's own deps via `imports`. */\n inject?: Array<InjectionToken | OptionalFactoryDependency>;\n /**\n * Build the `dashboardAuth` config from injected deps, or return `undefined` to leave the\n * console open (or fronted only by `guards`). See {@link AgentDashboardOptions.dashboardAuth}.\n */\n useDashboardAuth: (\n ...deps: any[]\n ) => DashboardAuthOptions | undefined | Promise<DashboardAuthOptions | undefined>;\n}\n\n/** Leading slash, no trailing slash. */\nfunction normalize(path: string): string {\n return normalizeDashboardPath(path);\n}\n\n/**\n * Holds the JSON API + SSE controller and its read service, mounted on its own path by `forRoot`.\n * Dynamic: guards are DI-instantiated by the CONTROLLER's host module, so this module — not the\n * outer wrapper — must carry the guard classes as providers plus the host's `imports` that resolve\n * their dependencies. A static module here made `guards: [SomeGuardWithDeps]` fail at boot with\n * \"Nest can't resolve dependencies ... in the AgentApiModule context\" even when the host passed\n * the right `imports` to `forRoot`.\n */\n@Module({})\nexport class AgentApiModule {\n static register<TReq = unknown>(options: {\n imports?: DynamicModule['imports'];\n guards?: Type<CanActivate>[];\n approvalActorRef?: (req: TReq) => string | undefined;\n /** Provider for `DASHBOARD_AUTH` — a `useValue` (`forRoot`) or a `useFactory` (`forRootAsync`). */\n dashboardAuth: Provider;\n }): DynamicModule {\n stampGuards(options.guards, [[AgentApiController, API_BASE_GUARDS]]);\n return {\n module: AgentApiModule,\n imports: [...(options.imports ?? [])],\n controllers: [AgentApiController],\n providers: [\n DashboardService,\n DashboardAuthGuard,\n options.dashboardAuth,\n ...(options.guards ?? []).filter(isGuardClass),\n // `useValue` even when `options.approvalActorRef` is `undefined` — AgentApiController\n // injects this WITHOUT `@Optional()` (same pattern as `AGENT_QUOTA_STORE`'s factory).\n { provide: DASHBOARD_APPROVAL_ACTOR_REF, useValue: options.approvalActorRef },\n ],\n // DASHBOARD_AUTH re-exported so AgentUiController/AgentDashboardAuthController (hosted\n // directly by AgentDashboardModule, which imports this module) resolve the SAME instance\n // instead of re-running a host's async factory a second time.\n exports: [DashboardService, DASHBOARD_AUTH],\n };\n }\n}\n\n/**\n * Mounts the AI-gateway governance console: the bundled React SPA at `basePath` and its JSON + SSE\n * API at `apiBasePath` (default `<basePath>/api`), plus the built-in `dashboardAuth` routes at\n * `<basePath>/auth/*` when {@link AgentDashboardOptions.dashboardAuth} is set — `POST\n * <basePath>/auth/session` for Mode A (the host mints the session from its own auth) and/or `GET`/\n * `POST <basePath>/auth/login` for Mode B (the built-in login screen).\n *\n * Import via `AgentDashboardModule.forRoot(...)` alongside your `@dudousxd/nestjs-agent` module\n * (global), which must provide `AGENT_GOVERNANCE_QUERIES` (bound by a store adapter). Front the\n * routes with the first-class `guards` option (plus `imports` for the guards' own dependencies) —\n * see {@link AgentDashboardOptions.guards} — and/or the built-in `dashboardAuth` session cookie.\n *\n * Inertia hosts: the console is a full-page app, not an Inertia page. An in-app `<Link>` visit to\n * `basePath` (an XHR carrying `X-Inertia`) is bounced with the protocol's own external-redirect\n * mechanism — `409 Conflict` + `X-Inertia-Location: <the visited URL>` — so the Inertia client\n * performs a full `window.location` load and the console renders normally. In-app links to the\n * console therefore just work; no host-side special-casing needed.\n */\n@Module({})\nexport class AgentDashboardModule {\n static forRoot<TReq = unknown>(options: AgentDashboardOptions<TReq> = {}): DynamicModule {\n const basePath = normalize(options.basePath ?? '/ai-gateway');\n const apiBasePath = normalize(options.apiBasePath ?? `${basePath}/api`);\n stampGuards(options.guards, [[AgentUiController, UI_BASE_GUARDS]]);\n const dashboardAuthProvider: Provider = {\n provide: DASHBOARD_AUTH,\n useValue: resolveDashboardAuth(options.dashboardAuth),\n };\n return {\n module: AgentDashboardModule,\n imports: [\n ...(options.imports ?? []),\n // Guards + host imports must reach the API controller's HOST module — enhancers resolve\n // from their controller's own module, never from a parent (see AgentApiModule.register).\n // Spread-only-when-set: exactOptionalPropertyTypes rejects an explicit `undefined`.\n AgentApiModule.register({\n ...(options.imports ? { imports: options.imports } : {}),\n ...(options.guards ? { guards: options.guards } : {}),\n ...(options.approvalActorRef ? { approvalActorRef: options.approvalActorRef } : {}),\n dashboardAuth: dashboardAuthProvider,\n }),\n RouterModule.register([\n { path: basePath, module: AgentDashboardModule }, // the UI + auth controllers below\n { path: apiBasePath, module: AgentApiModule },\n ]),\n ],\n controllers: [AgentUiController, AgentDashboardAuthController],\n providers: [\n { provide: DASHBOARD_BASE_PATH, useValue: basePath },\n { provide: DASHBOARD_API_PATH, useValue: apiBasePath },\n DashboardAuthPageGuard,\n // AgentUiController is hosted HERE, so its guards DI-instantiate from this module.\n ...(options.guards ?? []).filter(isGuardClass),\n ],\n // Re-export the API module so its DashboardService reaches importers (e.g. the host's own controllers).\n exports: [AgentApiModule],\n };\n }\n\n static forRootAsync<TReq = unknown>(options: AgentDashboardAsyncOptions<TReq>): DynamicModule {\n const basePath = normalize(options.basePath ?? '/ai-gateway');\n const apiBasePath = normalize(options.apiBasePath ?? `${basePath}/api`);\n stampGuards(options.guards, [[AgentUiController, UI_BASE_GUARDS]]);\n const dashboardAuthProvider: Provider = {\n provide: DASHBOARD_AUTH,\n inject: options.inject ?? [],\n useFactory: async (...deps: unknown[]) =>\n resolveDashboardAuth(await options.useDashboardAuth(...deps)),\n };\n return {\n module: AgentDashboardModule,\n imports: [\n ...(options.imports ?? []),\n AgentApiModule.register({\n ...(options.imports ? { imports: options.imports } : {}),\n ...(options.guards ? { guards: options.guards } : {}),\n ...(options.approvalActorRef ? { approvalActorRef: options.approvalActorRef } : {}),\n dashboardAuth: dashboardAuthProvider,\n }),\n RouterModule.register([\n { path: basePath, module: AgentDashboardModule },\n { path: apiBasePath, module: AgentApiModule },\n ]),\n ],\n controllers: [AgentUiController, AgentDashboardAuthController],\n providers: [\n { provide: DASHBOARD_BASE_PATH, useValue: basePath },\n { provide: DASHBOARD_API_PATH, useValue: apiBasePath },\n DashboardAuthPageGuard,\n ...(options.guards ?? []).filter(isGuardClass),\n ],\n exports: [AgentApiModule],\n };\n }\n}\n","import { existsSync, readFileSync } from 'node:fs';\nimport { basename, extname, join, resolve, sep } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport {\n Controller,\n Get,\n Header,\n Inject,\n NotFoundException,\n Param,\n Req,\n Res,\n StreamableFile,\n UseFilters,\n UseGuards,\n} from '@nestjs/common';\nimport { DashboardAuthPageGuard } from './auth/dashboard-auth-page.guard.js';\nimport { DashboardAuthRedirectFilter } from './auth/dashboard-auth-redirect.js';\nimport { DASHBOARD_API_PATH, DASHBOARD_BASE_PATH } from './tokens.js';\n\n/**\n * The slice of the express request the Inertia bounce below reads — structural, so the package\n * needs no express dependency (the API controller similarly takes `@Req() req: unknown`).\n */\ninterface UiPageRequest {\n headers: Record<string, string | string[] | undefined>;\n /** Path + query as received (express `originalUrl`) — what the full-page visit must reload. */\n originalUrl: string;\n}\n\n/** The slice of the express response the Inertia bounce writes (passthrough mode — Nest still sends). */\ninterface UiPageResponse {\n status(code: number): unknown;\n setHeader(name: string, value: string): unknown;\n}\n\n/** The base the SPA bundle was built with (Vite `base`); rewritten to the configured base at serve time. */\nconst BUILD_BASE = '/ai-gateway';\n\n/** dist/server/agent-ui.controller.js -> ../spa (the Vite build output). */\nfunction spaDir(): string {\n return fileURLToPath(new URL('../spa', import.meta.url));\n}\n\nconst CONTENT_TYPES: Record<string, string> = {\n '.js': 'text/javascript; charset=utf-8',\n '.css': 'text/css; charset=utf-8',\n '.map': 'application/json; charset=utf-8',\n '.svg': 'image/svg+xml',\n '.json': 'application/json; charset=utf-8',\n '.woff2': 'font/woff2',\n '.ico': 'image/x-icon',\n};\n\n/**\n * Serves the bundled AI-gateway console SPA at the configured base (+ hashed assets at\n * `<base>/assets`). The path comes from `RouterModule` (set by\n * {@link AgentDashboardModule.forRoot}({ basePath })), so the controller routes are relative.\n *\n * `@UseGuards(DashboardAuthPageGuard)` is a NO-OP unless the host configured `dashboardAuth` — see\n * that guard's own doc. Host `guards` (see {@link AgentDashboardOptions.guards}) are APPENDED onto\n * this baseline by `stampGuards` (`./guards.js`), never replacing it. `@UseFilters` turns the\n * guard's redirect-on-deny into an actual 302 response; it is a no-op when the guard never throws\n * (i.e. `dashboardAuth` unconfigured).\n */\n@Controller()\n@UseGuards(DashboardAuthPageGuard)\n@UseFilters(new DashboardAuthRedirectFilter())\nexport class AgentUiController {\n private readonly dir = spaDir();\n\n constructor(\n @Inject(DASHBOARD_BASE_PATH) private readonly basePath: string,\n @Inject(DASHBOARD_API_PATH) private readonly apiBasePath: string,\n ) {}\n\n // index.html references hash-named bundles, so it MUST NOT be cached (stale bundle = the classic\n // \"stuck loading after a deploy\"). The hashed assets below are immutable.\n @Get()\n @Header('Content-Type', 'text/html; charset=utf-8')\n @Header('Cache-Control', 'no-store, must-revalidate')\n index(@Req() req: UiPageRequest, @Res({ passthrough: true }) res: UiPageResponse): string {\n // An Inertia <Link> in the host app visits this page route as an XHR expecting an Inertia JSON\n // page object; serving the SPA's HTML instead lands it in the Inertia client's `srcdoc` error\n // modal, where the page's relative asset URLs die on CORS (origin null). The protocol's own\n // escape hatch for \"this URL is not an Inertia page\" is a 409 Conflict carrying\n // `X-Inertia-Location`: the client responds with a full `window.location` visit, which renders\n // the console normally. Only HTML page routes need this — asset binaries are never fetched with\n // the header.\n if (req.headers['x-inertia'] !== undefined) {\n res.status(409);\n res.setHeader('X-Inertia-Location', req.originalUrl);\n return '';\n }\n const indexPath = join(this.dir, 'index.html');\n if (!existsSync(indexPath)) {\n throw new NotFoundException('Dashboard is not built. Run the package build.');\n }\n // The bundle was built with Vite base `/ai-gateway/`; rewrite asset URLs to the configured base\n // so the SPA loads from `<base>/assets` wherever it's mounted, and tell the client its API base.\n const html = readFileSync(indexPath, 'utf8').replaceAll(\n `=\"${BUILD_BASE}/`,\n `=\"${this.basePath}/`,\n );\n // __AGENT_BASE__ = where assets load; __AGENT_API__ = where the SPA fetches the JSON API.\n const inject = `<script>window.__AGENT_BASE__='${this.basePath}';window.__AGENT_API__='${this.apiBasePath}';</script>`;\n return html.includes('</head>') ? html.replace('</head>', `${inject}</head>`) : inject + html;\n }\n\n @Get('assets/:file')\n @Header('Cache-Control', 'public, max-age=31536000, immutable')\n asset(@Param('file') file: string): StreamableFile {\n const safe = basename(file);\n if (safe !== file) throw new NotFoundException();\n const root = resolve(this.dir, 'assets');\n const assetPath = resolve(root, safe);\n if (!assetPath.startsWith(root + sep) || !existsSync(assetPath)) {\n throw new NotFoundException();\n }\n const type = CONTENT_TYPES[extname(safe)] ?? 'application/octet-stream';\n return new StreamableFile(readFileSync(assetPath), { type });\n }\n}\n","// packages/dashboard/src/server/auth/dashboard-auth-page.guard.ts\nimport {\n type CanActivate,\n type ExecutionContext,\n Inject,\n Injectable,\n Optional,\n} from '@nestjs/common';\nimport { DASHBOARD_AUTH, DASHBOARD_BASE_PATH } from '../tokens.js';\nimport { parseCookieHeader } from './cookie-header.js';\nimport type { ResolvedDashboardAuth } from './dashboard-auth-config.js';\nimport { DashboardAuthRedirect } from './dashboard-auth-redirect.js';\nimport { attachSession, readCookieHeader, readOriginalUrl } from './http-request.js';\nimport { SESSION_COOKIE_NAME, maybeRenewSession } from './session-cookie-io.js';\nimport { type DashboardSession, verifySessionCookie } from './session-cookie.js';\n\n/**\n * Gates `AgentUiController` (the page + its assets) on a valid session cookie — but ONLY when the\n * host configured `dashboardAuth`. With no `dashboardAuth` the resolved value is `null` and the\n * guard is a no-op (today's behavior byte-for-byte). Composes with host `guards` via\n * `stampGuards`'s APPEND semantics (see `../guards.ts`) — AND semantics, both gates must pass.\n *\n * Unlike {@link DashboardAuthGuard} (the API's `401`), a denied PAGE navigation throws\n * {@link DashboardAuthRedirect} — a browser hitting a protected URL gets bounced to the built-in\n * login screen (with a `returnTo` back to the page it came from) rather than a bare JSON 401.\n */\n@Injectable()\nexport class DashboardAuthPageGuard implements CanActivate {\n constructor(\n @Optional() @Inject(DASHBOARD_AUTH) private readonly auth: ResolvedDashboardAuth | null,\n @Inject(DASHBOARD_BASE_PATH) private readonly basePath: string,\n ) {}\n\n async canActivate(context: ExecutionContext): Promise<boolean> {\n if (!this.auth) return true;\n const auth = this.auth;\n const http = context.switchToHttp();\n const request = http.getRequest<unknown>();\n const session = this.verifyRequestSession(request);\n if (!session) this.deny(auth, request);\n attachSession(request, session);\n if (!(await maybeRenewSession(auth, session, request, http.getResponse<unknown>()))) {\n // Revoked mid-session: same Mode-aware treatment as an absent cookie.\n this.deny(auth, request);\n }\n return true;\n }\n\n /**\n * No valid session: bounce the browser navigation onward. Under Mode B (`login` configured)\n * that's the built-in login screen, carrying `returnTo` back to the page that was requested.\n * Under Mode-A-only there is no login screen to redirect to — the host mints the session\n * itself — so this instead serves the instruction page rather than redirecting into a 404.\n * Pulled out of `canActivate` so both the \"no session at all\" and the \"revalidate revoked a\n * renewable session\" paths reuse the exact same mode-aware deny logic.\n *\n * Takes `auth` explicitly rather than reading `this.auth`: `canActivate`'s `!this.auth` early\n * return narrows the field within that method only — TS doesn't carry it across a method call —\n * so the caller passes the already-narrowed value instead of this method re-deriving (or\n * asserting away) the nullability.\n */\n private deny(auth: ResolvedDashboardAuth, request: unknown): never {\n if (!auth.login) {\n throw new DashboardAuthRedirect(`${this.basePath}/auth/session-required`);\n }\n const returnTo = readOriginalUrl(request);\n throw new DashboardAuthRedirect(\n `${this.basePath}/auth/login?returnTo=${encodeURIComponent(returnTo)}`,\n );\n }\n\n private verifyRequestSession(request: unknown): DashboardSession | null {\n const auth = this.auth;\n if (!auth) return null;\n const cookieValue = parseCookieHeader(readCookieHeader(request))[SESSION_COOKIE_NAME];\n if (cookieValue === undefined) return null;\n return verifySessionCookie(cookieValue, { secret: auth.secret });\n }\n}\n","// packages/dashboard/src/server/auth/dashboard-auth-redirect.ts\nimport { type ArgumentsHost, Catch, type ExceptionFilter, Injectable } from '@nestjs/common';\nimport { sendRedirect } from './response.js';\n\n/**\n * Thrown by `DashboardAuthPageGuard` to deny an unauthenticated PAGE navigation with a 302 to the\n * login screen — deliberately NOT an `HttpException`: a guard returning `false` makes Nest send\n * its OWN 403 body, which would clobber a `Location` header set beforehand. Throwing this instead\n * routes through `DashboardAuthRedirectFilter` below, which owns the entire response.\n */\nexport class DashboardAuthRedirect extends Error {\n constructor(readonly redirectTo: string) {\n super(`Redirect to ${redirectTo}`);\n this.name = 'DashboardAuthRedirect';\n }\n}\n\n/**\n * Turns a thrown {@link DashboardAuthRedirect} into an actual 302 response. Scoped directly onto\n * `AgentUiController` via `@UseFilters(new DashboardAuthRedirectFilter())` — harmless when\n * `dashboardAuth` is unconfigured, since the guard never throws this exception in that case.\n */\n@Injectable()\n@Catch(DashboardAuthRedirect)\nexport class DashboardAuthRedirectFilter implements ExceptionFilter<DashboardAuthRedirect> {\n catch(exception: DashboardAuthRedirect, host: ArgumentsHost): void {\n const response = host.switchToHttp().getResponse<unknown>();\n sendRedirect(response, 302, exception.redirectTo);\n }\n}\n","// packages/dashboard/src/server/auth/dashboard-auth-config.ts\nimport type { DashboardSessionUser } from './session-cookie.js';\n\n/**\n * Host hook for Mode A: validates the host's own auth on the raw request POSTed to\n * `<basePath>/auth/session`. Return the session user to grant access, or `null` to deny.\n */\nexport type SessionHook = (\n request: unknown,\n) => Promise<DashboardSessionUser | null> | DashboardSessionUser | null;\n\n/** Host hook for Mode B: validates submitted credentials for the built-in login screen. */\nexport type LoginHook = (\n username: string,\n password: string,\n) => Promise<DashboardSessionUser | null> | DashboardSessionUser | null;\n\n/**\n * Re-checks a LIVE session when the cookie is slid forward. Runs at most once per `ttl/2` PER\n * COOKIE GENERATION, per in-flight request — the gate is on the inbound cookie's `iat`, so every\n * request still carrying a not-yet-renewed cookie invokes this hook, not just the first: a page\n * load that fires N parallel API calls can trigger up to N calls before the refreshed cookie lands\n * on the client. Bounded and cheap for typical console traffic, but not a per-session-lifetime\n * cap — a slow or expensive check should debounce/cache on its own. Return `false` to revoke (the\n * cookie is cleared and the request denied). Distinct from `session`: that hook reads the host's\n * auth off a fresh request, which a console XHR does not carry — this one receives the\n * already-minted session.\n */\nexport type RevalidateHook = (session: DashboardSessionUser) => Promise<boolean> | boolean;\n\n/** Which `dashboardAuth` hook(s) a resolved config was given — see `resolveDashboardAuth`. */\nexport type AuthMode = 'session' | 'login';\n\n/**\n * Author-facing `dashboardAuth` option (see `AgentDashboardOptions.dashboardAuth`). Gates the\n * console (SPA + API) behind a signed session cookie. Two ways to mint that cookie: Mode A\n * (`session`) — the host frontend, already carrying its own auth, POSTs to\n * `<basePath>/auth/session` and the host hook decides — or Mode B (`login`) — the built-in,\n * dependency-free server-rendered login screen. Mirrors `@dudousxd/nestjs-telescope`'s\n * `dashboardAuth` two-mode shape. At least one of the two is required so an un-mintable gate is a\n * boot error, not a silently-open (or silently-stuck) console.\n */\nexport interface DashboardAuthOptions {\n /** REQUIRED HMAC-SHA256 signing key (32+ bytes recommended). Missing/empty => boot error (fail closed). */\n secret: string;\n /** Cookie TTL (duration string `'<n><ms|s|m|h|d>'`, e.g. `'30m'`, `'7d'`). Default `'8h'`. */\n ttl?: string;\n /**\n * Mode A: validates the host's own auth on the raw request POSTed to `<basePath>/auth/session`.\n * Return the session user to grant access, or `null` to deny. No credential reaches this\n * library — the host decides from whatever it already has (a header, an SSO cookie, ...).\n */\n session?: SessionHook;\n /**\n * Mode B: validates submitted username/password. Return the session user to grant access, or\n * `null` to deny — the login endpoint responds identically (generic failure) for an unknown user\n * and a wrong password, so it never reveals which one was wrong.\n */\n login?: LoginHook;\n /** Re-checks a live session on sliding renewal; see `RevalidateHook`. Not a mode — it cannot\n * mint a session, only revoke one already minted by `session`/`login`. */\n revalidate?: RevalidateHook;\n}\n\n/** Resolved, validated `dashboardAuth` config used by the guards/controller. */\nexport interface ResolvedDashboardAuth {\n secret: string;\n ttlMs: number;\n modes: AuthMode[];\n session?: SessionHook;\n login?: LoginHook;\n revalidate?: RevalidateHook;\n}\n\nconst DEFAULT_TTL = '8h';\n\n/** Unit → ms multiplier. A `switch` (not an indexed lookup) so no cast is needed to narrow the regex capture. */\nfunction unitToMs(unit: string): number | undefined {\n switch (unit) {\n case 'ms':\n return 1;\n case 's':\n return 1_000;\n case 'm':\n return 60_000;\n case 'h':\n return 3_600_000;\n case 'd':\n return 86_400_000;\n default:\n return undefined;\n }\n}\n\n/** Convert a `'<int><ms|s|m|h|d>'` duration string to ms. Throws on an unparseable string. */\nfunction durationToMs(duration: string): number {\n const match = /^(\\d+)(ms|s|m|h|d)$/.exec(duration.trim());\n const amount = match?.[1];\n const unit = match?.[2] !== undefined ? unitToMs(match[2]) : undefined;\n if (amount === undefined || unit === undefined) {\n throw new Error(`AgentDashboardModule dashboardAuth: invalid \\`ttl\\` duration: ${duration}`);\n }\n return Number(amount) * unit;\n}\n\n/**\n * Validate + resolve `dashboardAuth`. Returns `null` when unconfigured (behavior unchanged — the\n * console stays open, front it with your own `guards`). Throws at boot (fail closed) when\n * configured but missing a secret, when neither `session` nor `login` is given, or when a given\n * hook isn't a function — the host learns immediately rather than shipping an open, stuck, or\n * un-mintable console.\n */\nexport function resolveDashboardAuth(\n options: DashboardAuthOptions | undefined,\n): ResolvedDashboardAuth | null {\n if (options === undefined) return null;\n if (typeof options.secret !== 'string' || options.secret === '') {\n throw new Error(\n 'AgentDashboardModule dashboardAuth: `secret` is required and must be a non-empty string ' +\n '(HMAC-SHA256 signing key, 32+ bytes recommended). Failing closed.',\n );\n }\n const modes: AuthMode[] = [];\n if (options.session !== undefined) {\n if (typeof options.session !== 'function') {\n throw new Error('AgentDashboardModule dashboardAuth: `session` must be a function.');\n }\n modes.push('session');\n }\n if (options.login !== undefined) {\n if (typeof options.login !== 'function') {\n throw new Error('AgentDashboardModule dashboardAuth: `login` must be a function.');\n }\n modes.push('login');\n }\n if (options.revalidate !== undefined && typeof options.revalidate !== 'function') {\n throw new Error('AgentDashboardModule dashboardAuth: `revalidate` must be a function.');\n }\n if (modes.length === 0) {\n throw new Error(\n 'AgentDashboardModule dashboardAuth: at least one of `session` (the host mints the session ' +\n 'from its own auth) or `login` (the built-in login screen) is required — otherwise the ' +\n 'cookie can never be minted. Failing closed.',\n );\n }\n return {\n secret: options.secret,\n ttlMs: durationToMs(options.ttl ?? DEFAULT_TTL),\n modes,\n ...(options.session !== undefined ? { session: options.session } : {}),\n ...(options.login !== undefined ? { login: options.login } : {}),\n ...(options.revalidate !== undefined ? { revalidate: options.revalidate } : {}),\n };\n}\n","import 'reflect-metadata';\nimport type { CanActivate, Type } from '@nestjs/common';\n\n/**\n * `@nestjs/common`'s own `GUARDS_METADATA` key, INLINED rather than deep-imported from\n * '@nestjs/common/constants' — that subpath has no extension and a strict ESM resolver (which the\n * built dual ESM/CJS output of this package is loaded under) 404s on it. Same convention as\n * `@dudousxd/nestjs-telescope`'s `telescope.module.ts` and `@dudousxd/nestjs-media`'s `guards.ts`.\n * A drift spec asserts this literal stays byte-identical to the real constant.\n */\nexport const GUARDS_METADATA = '__guards__';\n\n/**\n * Narrows a `guards` entry to a class (constructor) as opposed to an already-instantiated\n * `CanActivate`. Only a class needs a DI provider so Nest can instantiate it — an instance is used\n * by the guards consumer as-is.\n */\nexport function isGuardClass(guard: Type<CanActivate> | CanActivate): guard is Type<CanActivate> {\n return typeof guard === 'function';\n}\n\n/**\n * Reads a controller's OWN `@UseGuards` metadata (not the inherited/prototype-chain one), or `[]`\n * when it carries none. MUST be called at module-load time — i.e. assigned to a top-level `const`\n * right after importing the controller — so it captures the pristine, decorator-defined baseline\n * BEFORE any `stampGuards` call below can mutate it.\n */\nexport function baseGuards(controller: Type): Array<Type<CanActivate> | CanActivate> {\n return Reflect.getOwnMetadata(GUARDS_METADATA, controller) ?? [];\n}\n\n/**\n * Stamp host guards onto a dashboard controller — ALWAYS recomputed as exactly\n * `[...base, ...(guards ?? [])]`, its captured `base` (the controller's own pristine `@UseGuards`\n * metadata, or `[]` when it has none), never appended onto whatever is CURRENTLY stamped on the\n * class. `AgentApiController`/`AgentUiController` are static classes reused across every\n * `forRoot`/`forRootAsync` call (unlike `@dudousxd/nestjs-telescope`'s `dynamicController`\n * subclass-per-call pattern), so this call is fully deterministic from `base` + the CURRENT call's\n * own `guards` — independent of whatever a prior call (in the same process, e.g. two tests in one\n * file) stamped, and never compounding across repeated calls.\n *\n * Omitting `guards` resets the controller to exactly `base` — the built-in gate alone (a no-op\n * when `dashboardAuth` is also unconfigured), reproducing today's behavior byte-for-byte.\n */\nexport function stampGuards(\n guards: Array<Type<CanActivate> | CanActivate> | undefined,\n entries: Array<[controller: Type, base: Array<Type<CanActivate> | CanActivate>]>,\n): void {\n for (const [controller, base] of entries) {\n Reflect.defineMetadata(GUARDS_METADATA, [...base, ...(guards ?? [])], controller);\n }\n}\n"],"mappings":"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","names":["Body","Controller","Get","HttpCode","Inject","Optional","Param","Post","Query","Req","Sse","UseGuards","Inject","Injectable","Optional","UnauthorizedException","AGENT_GOVERNANCE_QUERIES","Symbol","for","AGENT_ACTOR_DIRECTORY","AGENT_PRICING_STORE","AGENT_APPROVAL_PORT","AGENT_ACTOR_RESOLVER","DASHBOARD_BASE_PATH","DASHBOARD_API_PATH","DASHBOARD_APPROVAL_ACTOR_REF","DASHBOARD_AUTH","parseCookieHeader","header","cookies","segment","split","eq","indexOf","name","slice","trim","rawValue","length","startsWith","endsWith","decodeURIComponent","serializeSetCookie","value","options","parts","clear","encodeURIComponent","push","path","secure","maxAgeSeconds","join","isRecord","value","readHeaders","request","headers","headerToString","Array","isArray","every","part","join","undefined","readCookieHeader","cookie","isHttpsRequest","protocol","forwarded","split","map","proto","trim","toLowerCase","includes","readOriginalUrl","originalUrl","url","attachSession","session","target","dashboardSession","isRecord","value","isHeaderCapableResponse","getHeader","setHeader","resolveHeaderCapableResponse","response","raw","existingSetCookies","current","Array","isArray","appendSetCookie","cookie","isRawNodeResponse","end","resolveRawResponse","sendRedirect","status","location","statusCode","createHmac","timingSafeEqual","EXP_GRACE_MS","base64urlEncode","value","Buffer","from","toString","base64urlDecode","hmac","secret","payload","createHmac","update","digest","signSessionCookie","user","options","issuedAt","now","Date","session","sub","id","name","undefined","roles","iat","exp","ttlMs","encodedPayload","JSON","stringify","signature","isSessionPayload","record","Array","isArray","every","role","Number","isFinite","verifySessionCookie","dot","indexOf","length","slice","providedSignature","expectedSignature","provided","expected","timingSafeEqual","decoded","parse","SESSION_COOKIE_NAME","COOKIE_PATH","issueSessionCookie","user","context","value","signSessionCookie","secret","auth","ttlMs","now","undefined","cookie","serializeSetCookie","path","maxAgeSeconds","Math","floor","secure","isHttpsRequest","request","appendSetCookie","response","clearSessionCookie","clear","maybeRenewSession","session","Date","iat","id","sub","name","roles","revalidate","allowed","DashboardAuthGuard","auth","canActivate","context","http","switchToHttp","request","getRequest","session","verifyRequestSession","UnauthorizedException","attachSession","maybeRenewSession","getResponse","cookieValue","parseCookieHeader","readCookieHeader","SESSION_COOKIE_NAME","undefined","verifySessionCookie","secret","subscribe","unsubscribe","channelName","Inject","Injectable","NotImplementedException","Optional","Observable","BadRequestException","parseApprovalDecision","body","BadRequestException","approved","reason","undefined","BadRequestException","parsePriceInput","body","BadRequestException","modelId","inputPricePer1m","outputPricePer1m","cacheWritePricePer1m","cacheReadPricePer1m","trim","length","isFiniteNonNegative","undefined","value","Number","isFinite","PRICING_STORE_UNBOUND_MESSAGE","APPROVAL_PORT_UNBOUND_MESSAGE","AGENT_EVENTS","isAgentEnvelope","message","event","DashboardService","queries","actorDirectory","pricingStore","approvalPort","spend","range","byModel","byActorRaw","trend","Promise","all","spendByModel","spendByActor","usageTrend","byActor","withActorLabels","topThreads","limit","rows","spendByThread","reliability","metrics","byAgent","errors","runMetrics","runsByAgent","runErrors","runTrend","recentRuns","recentToolCalls","toolCallsPage","query","runsPage","pendingApprovals","toolStats","decideApproval","toolCallId","body","executedByRef","undefined","NotImplementedException","decision","parseApprovalDecision","opts","approved","approve","reject","reason","recentThreads","threadsPage","page","length","map","row","actorLabel","refs","Set","actorRef","resolved","resolveDisplay","listPrices","listCurrentPrices","upsertPrice","upsertModelPrice","parsePriceInput","streamEvents","Observable","subscriber","bindings","name","channelName","handler","next","data","ts","Date","now","payload","subscribe","binding","unsubscribe","BadRequestException","ISO_DAY","parsePageNumber","value","parsed","undefined","Number","NaN","parseInt","isFinite","assertKnownFields","raw","allowed","field","Object","keys","includes","BadRequestException","assertDayFields","dayFields","test","DAY_FIELDS","TOOL_CALL_WHERE_FIELDS","parseToolCallWhere","length","toolName","toolType","status","threadId","fromDay","toDay","THREAD_WHERE_FIELDS","parseThreadWhere","actorRef","title","RUN_WHERE_FIELDS","parseRunWhere","agentName","errorCode","DAY_MS","ISO_DAY","utcDay","daysAgo","Date","now","toISOString","slice","dayOr","value","fallback","undefined","test","resolveRange","from","to","fromDay","toDay","parseLimit","parsed","Number","NaN","parseInt","isFinite","Math","max","min","AgentApiController","dashboard","approvalActorRef","actorResolver","spend","topThreads","limit","reliability","runs","recentRuns","runsPage","page","where","parsedWhere","parseRunWhere","parsePageNumber","pageSize","toolCalls","recentToolCalls","toolCallsPage","parseToolCallWhere","approvals","pendingApprovals","decideApproval","toolCallId","body","req","deciderRef","resolve","id","tools","toolStats","threads","recentThreads","threadsPage","parseThreadWhere","listPrices","upsertPrice","stream","streamEvents","Body","Controller","Get","Header","HttpCode","Inject","Logger","NotFoundException","Optional","Post","Query","Req","Res","UnauthorizedException","escapeHtml","value","replace","pageShell","content","title","renderLoginPage","options","action","actionUrl","returnTo","errorBanner","error","renderSessionRequiredPage","sanitizeReturnTo","basePath","returnTo","startsWith","isNonEmptyString","value","trim","isString","AgentDashboardAuthController","logger","Logger","name","warnedHooks","Set","auth","basePath","loginPage","req","res","returnToQuery","error","requireAuth","login","NotFoundException","returnTo","sanitizeReturnTo","currentSession","status","setHeader","renderLoginPage","actionUrl","undefined","body","username","password","redirectToLoginError","user","runHook","issueSessionCookie","request","response","logout","clearSessionCookie","session","UnauthorizedException","sessionRequiredPage","renderSessionRequiredPage","encodeURIComponent","cookieValue","parseCookieHeader","readCookieHeader","SESSION_COOKIE_NAME","verifySessionCookie","secret","kind","run","has","add","warn","Error","message","String","passthrough","normalizeDashboardPath","path","replace","unprefixed","path","replace","agentDashboardMountPaths","options","basePath","normalizeDashboardPath","apiBasePath","base","api","Module","RouterModule","existsSync","readFileSync","basename","extname","join","resolve","sep","fileURLToPath","Controller","Get","Header","Inject","NotFoundException","Param","Req","Res","StreamableFile","UseFilters","UseGuards","Inject","Injectable","Optional","Catch","Injectable","DashboardAuthRedirect","Error","redirectTo","name","DashboardAuthRedirectFilter","catch","exception","host","response","switchToHttp","getResponse","sendRedirect","DashboardAuthPageGuard","auth","basePath","canActivate","context","http","switchToHttp","request","getRequest","session","verifyRequestSession","deny","attachSession","maybeRenewSession","getResponse","login","DashboardAuthRedirect","returnTo","readOriginalUrl","encodeURIComponent","cookieValue","parseCookieHeader","readCookieHeader","SESSION_COOKIE_NAME","undefined","verifySessionCookie","secret","BUILD_BASE","spaDir","fileURLToPath","URL","url","CONTENT_TYPES","AgentUiController","dir","basePath","apiBasePath","index","req","res","headers","undefined","status","setHeader","originalUrl","indexPath","join","existsSync","NotFoundException","html","readFileSync","replaceAll","inject","includes","replace","asset","file","safe","basename","root","resolve","assetPath","startsWith","sep","type","extname","StreamableFile","passthrough","DashboardAuthRedirectFilter","DEFAULT_TTL","unitToMs","unit","undefined","durationToMs","duration","match","exec","trim","amount","Error","Number","resolveDashboardAuth","options","secret","modes","session","push","login","revalidate","length","ttlMs","ttl","GUARDS_METADATA","isGuardClass","guard","baseGuards","controller","Reflect","getOwnMetadata","stampGuards","guards","entries","base","defineMetadata","API_BASE_GUARDS","baseGuards","AgentApiController","UI_BASE_GUARDS","AgentUiController","normalize","path","normalizeDashboardPath","AgentApiModule","register","options","stampGuards","guards","module","imports","controllers","providers","DashboardService","DashboardAuthGuard","dashboardAuth","filter","isGuardClass","provide","DASHBOARD_APPROVAL_ACTOR_REF","useValue","approvalActorRef","exports","DASHBOARD_AUTH","AgentDashboardModule","forRoot","basePath","apiBasePath","dashboardAuthProvider","resolveDashboardAuth","RouterModule","AgentDashboardAuthController","DASHBOARD_BASE_PATH","DASHBOARD_API_PATH","DashboardAuthPageGuard","forRootAsync","inject","useFactory","deps","useDashboardAuth"]}
1
+ {"version":3,"sources":["../../src/server/agent-api.controller.ts","../../src/server/auth/dashboard-auth.guard.ts","../../src/server/tokens.ts","../../src/server/auth/cookie-header.ts","../../src/server/auth/http-request.ts","../../src/server/auth/response.ts","../../src/server/auth/session-cookie.ts","../../src/server/auth/session-cookie-io.ts","../../src/server/dashboard.service.ts","../../src/server/parse-approval-decision.ts","../../src/server/parse-price-input.ts","../../src/server/parse-page-query.ts","../../src/server/agent-dashboard-auth.controller.ts","../../src/server/auth/login-page.ts","../../src/server/auth/sanitize-return-to.ts","../../src/server/normalize-path.ts","../../src/server/agent-dashboard-mount-paths.ts","../../src/server/agent-dashboard.module.ts","../../src/server/agent-ui.controller.ts","../../src/server/auth/dashboard-auth-page.guard.ts","../../src/server/auth/dashboard-auth-redirect.ts","../../src/server/auth/dashboard-auth-config.ts","../../src/server/guards.ts"],"sourcesContent":["import type {\n ActorResolver,\n CurrentModelPrice,\n GovernancePage,\n PendingApprovalRow,\n RecentRunRow,\n ToolCallActivityRow,\n ToolStatRow,\n} from '@dudousxd/nestjs-agent-core';\nimport {\n Body,\n Controller,\n Get,\n HttpCode,\n Inject,\n Optional,\n Param,\n Post,\n Query,\n Req,\n Sse,\n UseGuards,\n} from '@nestjs/common';\nimport type { Observable } from 'rxjs';\nimport { DashboardAuthGuard } from './auth/dashboard-auth.guard.js';\nimport {\n DashboardService,\n type LiveAgentEvent,\n type ReliabilityOverview,\n type SpendOverview,\n type ThreadActivityRowWithLabel,\n type ThreadSpendRowWithLabel,\n} from './dashboard.service.js';\nimport {\n parsePageNumber,\n parseRunWhere,\n parseThreadWhere,\n parseToolCallWhere,\n} from './parse-page-query.js';\nimport { AGENT_ACTOR_RESOLVER, DASHBOARD_APPROVAL_ACTOR_REF } from './tokens.js';\n\nconst DAY_MS = 86_400_000;\nconst ISO_DAY = /^\\d{4}-\\d{2}-\\d{2}$/;\n\n/** A `YYYY-MM-DD` UTC day string `daysAgo` days before now (0 = today). */\nfunction utcDay(daysAgo: number): string {\n return new Date(Date.now() - daysAgo * DAY_MS).toISOString().slice(0, 10);\n}\n\n/** Accept a client-supplied `YYYY-MM-DD`, or fall back to `fallback`; guards against junk input. */\nfunction dayOr(value: string | undefined, fallback: string): string {\n return value !== undefined && ISO_DAY.test(value) ? value : fallback;\n}\n\n/** Resolve the `from`/`to` query params into a validated range, defaulting to the last 30 days. */\nfunction resolveRange(\n from: string | undefined,\n to: string | undefined,\n): {\n fromDay: string;\n toDay: string;\n} {\n return { fromDay: dayOr(from, utcDay(29)), toDay: dayOr(to, utcDay(0)) };\n}\n\n/** Parse a `limit` query param, clamped to a sane window; falls back to `fallback` when absent/junk. */\nfunction parseLimit(value: string | undefined, fallback: number): number {\n const parsed = value === undefined ? Number.NaN : Number.parseInt(value, 10);\n if (!Number.isFinite(parsed)) return fallback;\n return Math.max(1, Math.min(200, parsed));\n}\n\n/**\n * JSON + SSE API consumed by the AI-gateway console SPA. Mounted at `apiBasePath` (set by\n * `RouterModule` in {@link AgentDashboardModule.forRoot}), so the controller routes are relative.\n *\n * `@UseGuards(DashboardAuthGuard)` is a NO-OP unless the host configured `dashboardAuth` — see\n * that guard's own doc. Host `guards` (see {@link AgentDashboardOptions.guards}) are APPENDED onto\n * this baseline by `stampGuards` (`./guards.js`), never replacing it.\n */\n@Controller()\n@UseGuards(DashboardAuthGuard)\nexport class AgentApiController {\n constructor(\n private readonly dashboard: DashboardService,\n // `useValue` even when the host omitted `approvalActorRef` — no `@Optional()` needed (the token\n // is always bound; see DASHBOARD_APPROVAL_ACTOR_REF's TSDoc).\n @Inject(DASHBOARD_APPROVAL_ACTOR_REF)\n private readonly approvalActorRef: ((req: unknown) => string | undefined) | undefined,\n // The app's identity seam, bound + exported by the (global) AgentModule — the DEFAULT decider\n // attribution when no `approvalActorRef` override is configured. Genuinely `@Optional()`: the\n // dashboard can be mounted without AgentModule (read-model only), where nothing binds it.\n @Optional()\n @Inject(AGENT_ACTOR_RESOLVER)\n private readonly actorResolver?: ActorResolver,\n ) {}\n\n /** `{ byModel, byActor, trend }` for a day range (defaults to the last 30 days). */\n @Get('spend')\n spend(@Query('from') from?: string, @Query('to') to?: string): Promise<SpendOverview> {\n return this.dashboard.spend(resolveRange(from, to));\n }\n\n /** Top threads by cost (default 10, max 200) for a day range (defaults to the last 30 days). */\n @Get('top-threads')\n topThreads(\n @Query('from') from?: string,\n @Query('to') to?: string,\n @Query('limit') limit?: string,\n ): Promise<ThreadSpendRowWithLabel[]> {\n return this.dashboard.topThreads(resolveRange(from, to), parseLimit(limit, 10));\n }\n\n /** `{ metrics, byAgent, errors, trend }` for a day range (defaults to the last 30 days). */\n @Get('reliability')\n reliability(\n @Query('from') from?: string,\n @Query('to') to?: string,\n ): Promise<ReliabilityOverview> {\n return this.dashboard.reliability(resolveRange(from, to));\n }\n\n /** Most recent runs (default 50, max 200) for the Reliability recent-runs table. */\n @Get('runs')\n runs(@Query('limit') limit?: string): Promise<RecentRunRow[]> {\n return this.dashboard.recentRuns(parseLimit(limit, 50));\n }\n\n /**\n * Paged, filterable runs for the Reliability recent-runs table. `page` (default 1), `limit`\n * (default 25, max 200), and `where[agentName]`/`where[status]`/`where[errorCode]`/\n * `where[fromDay]`/`where[toDay]` (`YYYY-MM-DD`) — an unknown `where` field 400s naming it.\n */\n @Get('runs-page')\n runsPage(\n @Query('page') page?: string,\n @Query('limit') limit?: string,\n @Query('where') where?: Record<string, string>,\n ): Promise<GovernancePage<RecentRunRow>> {\n const parsedWhere = parseRunWhere(where);\n return this.dashboard.runsPage({\n page: parsePageNumber(page),\n pageSize: parseLimit(limit, 25),\n ...(parsedWhere !== undefined ? { where: parsedWhere } : {}),\n });\n }\n\n /** Most recent tool calls (default 50, max 200) for the activity feed. */\n @Get('tool-calls')\n toolCalls(@Query('limit') limit?: string): Promise<ToolCallActivityRow[]> {\n return this.dashboard.recentToolCalls(parseLimit(limit, 50));\n }\n\n /**\n * Paged, filterable tool calls for the Runs & tools activity table. `page` (default 1), `limit`\n * (default 25, max 200), and `where[toolName]`/`where[toolType]`/`where[status]`/`where[threadId]`/\n * `where[fromDay]`/`where[toDay]` (`YYYY-MM-DD`) — an unknown `where` field 400s naming it.\n */\n @Get('tool-calls-page')\n toolCallsPage(\n @Query('page') page?: string,\n @Query('limit') limit?: string,\n @Query('where') where?: Record<string, string>,\n ): Promise<GovernancePage<ToolCallActivityRow>> {\n const parsedWhere = parseToolCallWhere(where);\n return this.dashboard.toolCallsPage({\n page: parsePageNumber(page),\n pageSize: parseLimit(limit, 25),\n ...(parsedWhere !== undefined ? { where: parsedWhere } : {}),\n });\n }\n\n /** Tool calls sitting `pending_approval` (default 50, max 200), oldest first — the approvals inbox. */\n @Get('approvals')\n approvals(@Query('limit') limit?: string): Promise<PendingApprovalRow[]> {\n return this.dashboard.pendingApprovals(parseLimit(limit, 50));\n }\n\n /**\n * Decide a pending HITL tool call. Body `{ approved: boolean; reason?: string }`. 501s (via\n * `DashboardService.decideApproval`) when no `AGENT_APPROVAL_PORT` is bound. `executedByRef`\n * comes from {@link deciderRef} run against the live request.\n */\n @Post('approvals/:toolCallId')\n @HttpCode(204)\n async decideApproval(\n @Param('toolCallId') toolCallId: string,\n @Body() body: unknown,\n @Req() req: unknown,\n ): Promise<void> {\n await this.dashboard.decideApproval(toolCallId, body, await this.deciderRef(req));\n }\n\n /**\n * WHO decided, as an opaque ref: an explicit `approvalActorRef` override wins outright (no\n * resolver fallback, even when it returns `undefined`); otherwise the AgentModule-configured\n * actor resolver — the same identity seam chat requests use. A resolver that throws is an\n * unauthenticated/unreadable request, not an error to surface: the decision itself was already\n * authorized by the dashboard's guards, so the ref is simply omitted.\n */\n private async deciderRef(req: unknown): Promise<string | undefined> {\n if (this.approvalActorRef !== undefined) {\n return this.approvalActorRef(req);\n }\n if (this.actorResolver === undefined) {\n return undefined;\n }\n try {\n return (await this.actorResolver.resolve(req)).id;\n } catch {\n return undefined;\n }\n }\n\n /** Per-tool call/failure/rejection/latency rollup for a day range (defaults to the last 30 days). */\n @Get('tools')\n tools(@Query('from') from?: string, @Query('to') to?: string): Promise<ToolStatRow[]> {\n return this.dashboard.toolStats(resolveRange(from, to));\n }\n\n /** Most recent threads (default 50, max 200) with rolled-up counts. */\n @Get('threads')\n threads(@Query('limit') limit?: string): Promise<ThreadActivityRowWithLabel[]> {\n return this.dashboard.recentThreads(parseLimit(limit, 50));\n }\n\n /**\n * Paged, filterable threads for the Runs & tools threads table. `page` (default 1), `limit`\n * (default 25, max 200), and `where[actorRef]`/`where[title]` (case-insensitive substring)/\n * `where[fromDay]`/`where[toDay]` (`YYYY-MM-DD`) — an unknown `where` field 400s naming it.\n */\n @Get('threads-page')\n threadsPage(\n @Query('page') page?: string,\n @Query('limit') limit?: string,\n @Query('where') where?: Record<string, string>,\n ): Promise<GovernancePage<ThreadActivityRowWithLabel>> {\n const parsedWhere = parseThreadWhere(where);\n return this.dashboard.threadsPage({\n page: parsePageNumber(page),\n pageSize: parseLimit(limit, 25),\n ...(parsedWhere !== undefined ? { where: parsedWhere } : {}),\n });\n }\n\n /**\n * Current price row per model, for the pricing tab. 501s (via `DashboardService.listPrices`) when\n * no `AGENT_PRICING_STORE` is bound.\n */\n @Get('pricing')\n listPrices(): Promise<CurrentModelPrice[]> {\n return this.dashboard.listPrices();\n }\n\n /**\n * Set a model's current price. Body shape mirrors core's `ModelPriceInput`\n * (`{ modelId, inputPricePer1m, outputPricePer1m, cacheWritePricePer1m?, cacheReadPricePer1m? }`).\n * 501s when no `AGENT_PRICING_STORE` is bound; 400s on a malformed body.\n */\n @Post('pricing')\n upsertPrice(@Body() body: unknown): Promise<void> {\n return this.dashboard.upsertPrice(body);\n }\n\n /** Server-Sent Events stream of live `aviary:agent:*` events — the Live feed tails it. */\n @Sse('stream')\n stream(): Observable<{ data: LiveAgentEvent }> {\n return this.dashboard.streamEvents();\n }\n}\n","// packages/dashboard/src/server/auth/dashboard-auth.guard.ts\nimport {\n type CanActivate,\n type ExecutionContext,\n Inject,\n Injectable,\n Optional,\n UnauthorizedException,\n} from '@nestjs/common';\nimport { DASHBOARD_AUTH } from '../tokens.js';\nimport { parseCookieHeader } from './cookie-header.js';\nimport type { ResolvedDashboardAuth } from './dashboard-auth-config.js';\nimport { attachSession, readCookieHeader } from './http-request.js';\nimport { SESSION_COOKIE_NAME, maybeRenewSession } from './session-cookie-io.js';\nimport { type DashboardSession, verifySessionCookie } from './session-cookie.js';\n\n/**\n * Gates `AgentApiController` on a valid session cookie — but ONLY when the host configured\n * `dashboardAuth`. With no `dashboardAuth` the resolved value is `null` and the guard is a no-op\n * (today's behavior byte-for-byte; the API stays open, front it with your own `guards`). Composes\n * with host `guards` via `stampGuards`'s APPEND semantics (see `../guards.ts`) — AND semantics,\n * both gates must pass.\n *\n * Absent/invalid/expired cookie => `401` (not `403`): \"not authenticated\", distinct from a host\n * guard's `403` \"authenticated, not allowed\".\n */\n@Injectable()\nexport class DashboardAuthGuard implements CanActivate {\n constructor(\n @Optional() @Inject(DASHBOARD_AUTH) private readonly auth: ResolvedDashboardAuth | null,\n ) {}\n\n async canActivate(context: ExecutionContext): Promise<boolean> {\n if (!this.auth) return true;\n const http = context.switchToHttp();\n const request = http.getRequest<unknown>();\n const session = this.verifyRequestSession(request);\n if (!session) throw new UnauthorizedException();\n attachSession(request, session);\n if (!(await maybeRenewSession(this.auth, session, request, http.getResponse<unknown>()))) {\n // Revoked mid-session: same 401 as an absent cookie.\n throw new UnauthorizedException();\n }\n return true;\n }\n\n private verifyRequestSession(request: unknown): DashboardSession | null {\n const auth = this.auth;\n if (!auth) return null;\n const cookieValue = parseCookieHeader(readCookieHeader(request))[SESSION_COOKIE_NAME];\n if (cookieValue === undefined) return null;\n return verifySessionCookie(cookieValue, { secret: auth.secret });\n }\n}\n","/**\n * DI tokens for the standalone AI-gateway dashboard.\n *\n * All use `Symbol.for(...)` (the global symbol registry) on purpose: pnpm peer multiplexing + dual\n * ESM/CJS can load a package more than once, and a plain `Symbol()` would mint a distinct token per\n * copy and break DI across the ESM/CJS split. A registered symbol collapses every copy onto the same\n * token.\n */\n\n/**\n * The governance read-model, owned by `@dudousxd/nestjs-agent-core`. We re-declare it here BY VALUE\n * (not by import) so DI does not depend on a runtime value-import of core resolving — `Symbol.for`\n * with the identical key resolves to the SAME symbol instance as core's own\n * `packages/core/src/tokens.ts` export. The key MUST stay byte-identical with that export.\n */\nexport const AGENT_GOVERNANCE_QUERIES = Symbol.for('@dudousxd/nestjs-agent:governance-queries');\n\n/**\n * Optional actor→label resolver (see {@link ActorDirectory} in `./actor-directory.js`), owned by\n * `@dudousxd/nestjs-agent-core`. Re-declared here BY VALUE for the same reason as\n * {@link AGENT_GOVERNANCE_QUERIES} above — the key MUST stay byte-identical with core's own\n * `AGENT_ACTOR_DIRECTORY` export so both copies collapse onto the same registered symbol. Optional:\n * the dashboard works with actorRef-only rows when nothing is bound.\n */\nexport const AGENT_ACTOR_DIRECTORY = Symbol.for('@dudousxd/nestjs-agent:actor-directory');\n\n/**\n * The pricing WRITE side (`AgentPricingStore`), owned by `@dudousxd/nestjs-agent-core`. Re-declared\n * here BY VALUE for the same reason as {@link AGENT_GOVERNANCE_QUERIES} above. Optional: the pricing\n * tab/endpoints 501 with a clear message when nothing is bound.\n */\nexport const AGENT_PRICING_STORE = Symbol.for('@dudousxd/nestjs-agent:pricing-store');\n\n/**\n * Console-side HITL decisions (`AgentApprovalPort`), owned by `@dudousxd/nestjs-agent-core`.\n * Re-declared here BY VALUE for the same reason as {@link AGENT_GOVERNANCE_QUERIES} above. Optional:\n * bound by `@dudousxd/nestjs-agent` (which adapts it to the same signal path chat approvals use) —\n * absent, `POST <api>/approvals/:toolCallId` 501s and the approvals inbox renders read-only.\n */\nexport const AGENT_APPROVAL_PORT = Symbol.for('@dudousxd/nestjs-agent:approval-port');\n\n/**\n * The app's request-identity seam (`ActorResolver`), owned by `@dudousxd/nestjs-agent-core` and\n * bound + exported by the (global) `AgentModule` from the host's `actorResolver` option. Re-declared\n * here BY VALUE for the same reason as {@link AGENT_GOVERNANCE_QUERIES} above. Optional: the default\n * decider attribution for `POST <api>/approvals/:toolCallId` — absent (or throwing, i.e.\n * unauthenticated), `executedByRef` is simply omitted.\n */\nexport const AGENT_ACTOR_RESOLVER = Symbol.for('@dudousxd/nestjs-agent:actor-resolver');\n\n/** DI token carrying the UI mount base (e.g. `/ai-gateway`). */\nexport const DASHBOARD_BASE_PATH = Symbol.for('@dudousxd/nestjs-agent-dashboard:base-path');\n\n/** DI token carrying the JSON API base the SPA fetches from (e.g. `/ai-gateway/api`). */\nexport const DASHBOARD_API_PATH = Symbol.for('@dudousxd/nestjs-agent-dashboard:api-path');\n\n/**\n * DI token carrying the host-provided {@link AgentDashboardOptions.approvalActorRef} extractor (or\n * `undefined` when the host didn't set one — the controller then falls back to\n * {@link AGENT_ACTOR_RESOLVER} for decider attribution). Threaded to `AgentApiModule` (where the API\n * controller actually lives) same as the pattern above — `useValue` even when `undefined`, so\n * injecting it needs no `@Optional()` (mirrors `AGENT_QUOTA_STORE`'s factory in\n * `@dudousxd/nestjs-agent`).\n */\nexport const DASHBOARD_APPROVAL_ACTOR_REF = Symbol.for(\n '@dudousxd/nestjs-agent-dashboard:approval-actor-ref',\n);\n\n/**\n * DI token carrying the resolved `dashboardAuth` config (`ResolvedDashboardAuth | null`) — `null`\n * when the host didn't configure it (the built-in guards are then a no-op, today's behavior\n * byte-for-byte). Bound once by `AgentApiModule.register` and re-exported so `AgentUiController`\n * and `AgentDashboardAuthController` (hosted directly by `AgentDashboardModule`, which imports\n * `AgentApiModule`) resolve the SAME instance rather than re-running a host's async factory twice.\n */\nexport const DASHBOARD_AUTH = Symbol.for('@dudousxd/nestjs-agent-dashboard:dashboard-auth');\n","// packages/dashboard/src/server/auth/cookie-header.ts\n\n/**\n * Parse a raw `Cookie` request header into a name→value map. Dependency-free so it works on raw\n * Express AND Fastify requests without `cookie-parser`. Values are URL-decoded; malformed segments\n * are skipped, never thrown.\n */\nexport function parseCookieHeader(header: string | undefined): Record<string, string> {\n const cookies: Record<string, string> = {};\n if (typeof header !== 'string' || header === '') return cookies;\n for (const segment of header.split(';')) {\n const eq = segment.indexOf('=');\n if (eq <= 0) continue;\n const name = segment.slice(0, eq).trim();\n if (name === '') continue;\n let rawValue = segment.slice(eq + 1).trim();\n // Strip a single pair of wrapping double-quotes (RFC 6265 quoted form).\n if (rawValue.length >= 2 && rawValue.startsWith('\"') && rawValue.endsWith('\"')) {\n rawValue = rawValue.slice(1, -1);\n }\n // First occurrence wins; don't clobber an earlier value with a later dup.\n if (name in cookies) continue;\n try {\n cookies[name] = decodeURIComponent(rawValue);\n } catch {\n cookies[name] = rawValue;\n }\n }\n return cookies;\n}\n\nexport interface SetCookieOptions {\n path: string;\n /** Cookie lifetime in seconds. Ignored (and forced to 0) when `clear` is set. */\n maxAgeSeconds: number;\n /** Add `Secure` when the request is https. */\n secure: boolean;\n /** Clear the cookie: empty value + `Max-Age=0` (and `Expires` in the past). */\n clear?: boolean;\n}\n\n/**\n * Serialize a `Set-Cookie` header value. Always `HttpOnly` + `SameSite=Lax` (Lax blocks\n * cross-site POSTs carrying the cookie — CSRF coverage for the login/logout mutations).\n * Platform-agnostic: just a string.\n */\nexport function serializeSetCookie(name: string, value: string, options: SetCookieOptions): string {\n const parts = [`${name}=${options.clear ? '' : encodeURIComponent(value)}`];\n parts.push(`Path=${options.path}`);\n parts.push('HttpOnly');\n parts.push('SameSite=Lax');\n if (options.secure) parts.push('Secure');\n if (options.clear) {\n parts.push('Max-Age=0');\n parts.push('Expires=Thu, 01 Jan 1970 00:00:00 GMT');\n } else {\n parts.push(`Max-Age=${options.maxAgeSeconds}`);\n }\n return parts.join('; ');\n}\n","// packages/dashboard/src/server/auth/http-request.ts\nimport type { DashboardSession } from './session-cookie.js';\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\nfunction readHeaders(request: unknown): Record<string, unknown> {\n if (!isRecord(request)) return {};\n const headers = request.headers;\n return isRecord(headers) ? headers : {};\n}\n\n/** Coerce a single header (string or string[]) to a string, else undefined. */\nfunction headerToString(value: unknown): string | undefined {\n if (typeof value === 'string') return value;\n if (Array.isArray(value) && value.every((part) => typeof part === 'string')) {\n return value.join(',');\n }\n return undefined;\n}\n\n/** Read the raw `Cookie` request header (Express and Fastify both expose `headers`). */\nexport function readCookieHeader(request: unknown): string | undefined {\n return headerToString(readHeaders(request).cookie);\n}\n\n/**\n * Decide whether the cookie should carry `Secure`. True when the request is https directly\n * (`request.protocol === 'https'`) OR a proxy forwarded https (`x-forwarded-proto` includes\n * 'https'). Defensive structural reads — never throws, defaults to insecure (works on plain http\n * in dev).\n */\nexport function isHttpsRequest(request: unknown): boolean {\n if (isRecord(request) && request.protocol === 'https') return true;\n const forwarded = headerToString(readHeaders(request)['x-forwarded-proto']);\n if (forwarded === undefined) return false;\n return forwarded\n .split(',')\n .map((proto) => proto.trim().toLowerCase())\n .includes('https');\n}\n\n/** The original path + query as received (Express `originalUrl` / Fastify `url`). */\nexport function readOriginalUrl(request: unknown): string {\n if (!isRecord(request)) return '/';\n const originalUrl = request.originalUrl;\n if (typeof originalUrl === 'string' && originalUrl !== '') return originalUrl;\n const url = request.url;\n return typeof url === 'string' && url !== '' ? url : '/';\n}\n\n/** A request with the verified session attached for downstream reads. */\nexport interface RequestWithSession {\n dashboardSession: DashboardSession;\n}\n\n/**\n * Attach the verified session onto the raw request so downstream handlers can read\n * `request.dashboardSession`. Narrow structural mutation — avoids an `as` cast at the guard call\n * site.\n */\nexport function attachSession(request: unknown, session: DashboardSession): void {\n if (!isRecord(request)) return;\n const target: Partial<RequestWithSession> = request;\n target.dashboardSession = session;\n}\n","// packages/dashboard/src/server/auth/response.ts\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\ninterface HeaderCapableResponse {\n getHeader(name: string): number | string | string[] | undefined;\n setHeader(name: string, value: string | string[]): unknown;\n}\n\nfunction isHeaderCapableResponse(value: unknown): value is HeaderCapableResponse {\n return (\n isRecord(value) &&\n typeof value.getHeader === 'function' &&\n typeof value.setHeader === 'function'\n );\n}\n\n/**\n * Resolve the header-capable response from a platform response. Express' response IS the Node\n * response; Fastify's reply wraps it on `.raw`. Returns the outer response when it already exposes\n * get/setHeader (Express), otherwise the unwrapped `.raw` (Fastify) — so a single write path serves\n * both. Only requires get/setHeader (not `end`) so it also works against a passthrough-mode\n * response, which Nest still owns sending.\n */\nfunction resolveHeaderCapableResponse(response: unknown): HeaderCapableResponse | null {\n if (isHeaderCapableResponse(response)) return response;\n if (isRecord(response) && isHeaderCapableResponse(response.raw)) return response.raw;\n return null;\n}\n\nfunction existingSetCookies(response: HeaderCapableResponse): string[] {\n const current = response.getHeader('set-cookie');\n if (Array.isArray(current)) return current;\n if (typeof current === 'string') return [current];\n return [];\n}\n\n/**\n * Append a `Set-Cookie` header to the response WITHOUT clobbering any cookies already queued by\n * the host (auth or otherwise). Platform-agnostic raw write — no-ops gracefully if the response\n * can't be unwrapped.\n */\nexport function appendSetCookie(response: unknown, cookie: string): void {\n const raw = resolveHeaderCapableResponse(response);\n if (!raw) return;\n raw.setHeader('set-cookie', [...existingSetCookies(raw), cookie]);\n}\n\ninterface RawNodeResponse extends HeaderCapableResponse {\n statusCode: number;\n end(chunk?: string): unknown;\n}\n\nfunction isRawNodeResponse(value: unknown): value is RawNodeResponse {\n return (\n isRecord(value) &&\n typeof value.getHeader === 'function' &&\n typeof value.setHeader === 'function' &&\n typeof value.end === 'function'\n );\n}\n\nfunction resolveRawResponse(response: unknown): RawNodeResponse | null {\n if (isRawNodeResponse(response)) return response;\n if (isRecord(response) && isRawNodeResponse(response.raw)) return response.raw;\n return null;\n}\n\n/**\n * Fully send a redirect response (status + `Location` + end the response) — used by the guard's\n * exception-filter path, which owns the ENTIRE response (unlike a passthrough controller handler\n * that can just set headers and return a body for Nest to send). No-ops gracefully if the\n * response can't be unwrapped.\n */\nexport function sendRedirect(response: unknown, status: number, location: string): void {\n const raw = resolveRawResponse(response);\n if (!raw) return;\n raw.statusCode = status;\n raw.setHeader('location', location);\n raw.end();\n}\n","// packages/dashboard/src/server/auth/session-cookie.ts\nimport { createHmac, timingSafeEqual } from 'node:crypto';\n\n/** The validated dashboard session attached to a request once a cookie verifies. */\nexport interface DashboardSession {\n /** Stable user id (the session user's `id`). */\n sub: string;\n /** Optional display name. */\n name?: string;\n /** Free-form role strings; the dashboard does not interpret them. */\n roles: string[];\n /** Issued-at, epoch milliseconds. */\n iat: number;\n /** Expiry, epoch milliseconds. */\n exp: number;\n}\n\n/** The session user the host's `login` hook returns to mint a cookie. */\nexport interface DashboardSessionUser {\n id: string;\n name?: string;\n roles?: string[];\n}\n\nexport interface SignOptions {\n secret: string;\n ttlMs: number;\n /** Injectable clock (epoch ms) for deterministic tests. Defaults to `Date.now()`. */\n now?: number;\n}\n\nexport interface VerifyOptions {\n secret: string;\n /** Injectable clock (epoch ms) for deterministic tests. Defaults to `Date.now()`. */\n now?: number;\n}\n\n/** Clock-skew grace applied to `exp` so a marginally-late clock doesn't bounce a valid cookie. */\nconst EXP_GRACE_MS = 30_000;\n\nfunction base64urlEncode(value: string): string {\n return Buffer.from(value, 'utf8').toString('base64url');\n}\n\nfunction base64urlDecode(value: string): string {\n return Buffer.from(value, 'base64url').toString('utf8');\n}\n\nfunction hmac(secret: string, payload: string): string {\n return createHmac('sha256', secret).update(payload).digest('base64url');\n}\n\n/**\n * Sign a session into the cookie value `base64url(payload).base64url(hmac)`. Stateless\n * HMAC-SHA256, no store — `node:crypto` only, no JWT dependency. Mirrors\n * `@dudousxd/nestjs-telescope`'s `dashboardAuth` cookie codec.\n */\nexport function signSessionCookie(user: DashboardSessionUser, options: SignOptions): string {\n const issuedAt = options.now ?? Date.now();\n const session: DashboardSession = {\n sub: user.id,\n ...(user.name !== undefined ? { name: user.name } : {}),\n roles: user.roles ?? [],\n iat: issuedAt,\n exp: issuedAt + options.ttlMs,\n };\n const encodedPayload = base64urlEncode(JSON.stringify(session));\n const signature = hmac(options.secret, encodedPayload);\n return `${encodedPayload}.${signature}`;\n}\n\n/** Type guard for the decoded payload shape — defends against tampered/legacy payloads. */\nfunction isSessionPayload(value: unknown): value is DashboardSession {\n if (typeof value !== 'object' || value === null) return false;\n const record: Record<string, unknown> = { ...value };\n if (typeof record.sub !== 'string') return false;\n if (record.name !== undefined && typeof record.name !== 'string') return false;\n if (!Array.isArray(record.roles)) return false;\n if (!record.roles.every((role) => typeof role === 'string')) return false;\n if (typeof record.iat !== 'number' || !Number.isFinite(record.iat)) return false;\n if (typeof record.exp !== 'number' || !Number.isFinite(record.exp)) return false;\n return true;\n}\n\n/**\n * Verify a cookie value and return the session, or `null` for anything that's tampered,\n * malformed, or expired (past `exp` + a 30s grace). Constant-time signature comparison. NEVER\n * throws — any parse failure yields `null`.\n */\nexport function verifySessionCookie(\n value: string,\n options: VerifyOptions,\n): DashboardSession | null {\n try {\n const dot = value.indexOf('.');\n if (dot <= 0 || dot === value.length - 1) return null;\n const encodedPayload = value.slice(0, dot);\n const providedSignature = value.slice(dot + 1);\n const expectedSignature = hmac(options.secret, encodedPayload);\n const provided = Buffer.from(providedSignature, 'base64url');\n const expected = Buffer.from(expectedSignature, 'base64url');\n // timingSafeEqual throws on a length mismatch — guard it so a wrong-length (tampered)\n // signature returns null instead of throwing.\n if (provided.length !== expected.length) return null;\n if (!timingSafeEqual(provided, expected)) return null;\n const decoded: unknown = JSON.parse(base64urlDecode(encodedPayload));\n if (!isSessionPayload(decoded)) return null;\n const now = options.now ?? Date.now();\n if (now > decoded.exp + EXP_GRACE_MS) return null;\n const session: DashboardSession = {\n sub: decoded.sub,\n ...(decoded.name !== undefined ? { name: decoded.name } : {}),\n roles: decoded.roles,\n iat: decoded.iat,\n exp: decoded.exp,\n };\n return session;\n } catch {\n return null;\n }\n}\n","import { serializeSetCookie } from './cookie-header.js';\n// packages/dashboard/src/server/auth/session-cookie-io.ts\nimport type { ResolvedDashboardAuth } from './dashboard-auth-config.js';\nimport { isHttpsRequest } from './http-request.js';\nimport { appendSetCookie } from './response.js';\nimport {\n type DashboardSession,\n type DashboardSessionUser,\n signSessionCookie,\n} from './session-cookie.js';\n\n/** Cookie name carrying the signed dashboard session. */\nexport const SESSION_COOKIE_NAME = 'agent_dashboard_session';\n\n/**\n * Cookie `Path`. Unlike `@dudousxd/nestjs-telescope` (UI + API always share one mount root, so its\n * cookie scopes to that root), this dashboard supports an `apiBasePath` configured OUTSIDE\n * `basePath` (e.g. `/api/ai-gateway` alongside a `/ai-gateway` UI) — a path scoped to `basePath`\n * would never ride to a separately-mounted API. `Path=/` is the only scoping that is correct for\n * every mount configuration; `HttpOnly` + `SameSite=Lax` already do the real security work.\n */\nconst COOKIE_PATH = '/';\n\n/**\n * Sign a fresh session for `user` and append it as a `Set-Cookie` on the response, `Secure` when\n * the request is https.\n */\nexport function issueSessionCookie(\n user: DashboardSessionUser,\n context: {\n auth: ResolvedDashboardAuth;\n request: unknown;\n response: unknown;\n now?: number;\n },\n): void {\n const value = signSessionCookie(user, {\n secret: context.auth.secret,\n ttlMs: context.auth.ttlMs,\n ...(context.now !== undefined ? { now: context.now } : {}),\n });\n const cookie = serializeSetCookie(SESSION_COOKIE_NAME, value, {\n path: COOKIE_PATH,\n maxAgeSeconds: Math.floor(context.auth.ttlMs / 1000),\n secure: isHttpsRequest(context.request),\n });\n appendSetCookie(context.response, cookie);\n}\n\n/** Append a cookie-clearing `Set-Cookie` (Max-Age=0). */\nexport function clearSessionCookie(context: { request: unknown; response: unknown }): void {\n const cookie = serializeSetCookie(SESSION_COOKIE_NAME, '', {\n path: COOKIE_PATH,\n maxAgeSeconds: 0,\n secure: isHttpsRequest(context.request),\n clear: true,\n });\n appendSetCookie(context.response, cookie);\n}\n\n/**\n * In-flight `revalidate()` calls, keyed by session identity (`sub:iat` — the exact cookie\n * generation), so concurrent requests carrying the same not-yet-renewed cookie share ONE host\n * round-trip instead of each starting their own. Entries are removed as soon as they settle (see\n * `getOrStartRevalidation`), so this never accumulates beyond \"sessions currently mid-revalidation\"\n * — bounded by concurrent in-flight requests, not by session count over time.\n */\nconst inFlightRevalidations = new Map<string, Promise<boolean>>();\n\n/** The exact cookie generation being revalidated — a renewal mints a new `iat`, so this can't\n * collide across generations of the same user's session. */\nfunction revalidationKey(session: DashboardSession): string {\n return `${session.sub}:${session.iat}`;\n}\n\n/**\n * Runs (or joins an already-running) `revalidate` call for this exact session. Cleans up on BOTH\n * resolve and reject (`finally`) so a rejecting/throwing hook can't wedge the session behind a\n * dead cached promise — the next call after a failure reaches the host again, not a stale rejection.\n */\nfunction getOrStartRevalidation(\n auth: ResolvedDashboardAuth,\n session: DashboardSession,\n user: DashboardSessionUser,\n): Promise<boolean> {\n const hook = auth.revalidate;\n if (!hook) return Promise.resolve(true);\n const key = revalidationKey(session);\n const inFlight = inFlightRevalidations.get(key);\n if (inFlight) return inFlight;\n const outcome = (async () => {\n try {\n return await hook(user);\n } catch {\n // Fail closed: a throwing hook revokes rather than silently extending the session.\n return false;\n }\n })().finally(() => {\n inFlightRevalidations.delete(key);\n });\n inFlightRevalidations.set(key, outcome);\n return outcome;\n}\n\n/**\n * Sliding renewal + revalidation. When a valid cookie is past 50% of its TTL, re-issue a fresh one\n * so an active session never expires mid-use — but first give the host's `revalidate` hook a say,\n * so a deactivated or demoted user loses access instead of riding a self-renewing cookie forever.\n *\n * Concurrent requests carrying the same past-half-life cookie share ONE `revalidate` call (see\n * `getOrStartRevalidation`) — a page load firing N parallel API calls costs the host one round-trip,\n * not N, matching `RevalidateHook`'s documented \"at most once per `ttl/2`\" cost. Each caller still\n * gets its own `Set-Cookie` written to its own response; only the host round-trip is shared.\n *\n * Returns `false` when the session was revoked (the clearing `Set-Cookie` is already queued and the\n * caller must deny the request); `true` otherwise, including when no renewal was due.\n */\nexport async function maybeRenewSession(\n auth: ResolvedDashboardAuth,\n session: DashboardSession,\n request: unknown,\n response: unknown,\n now: number = Date.now(),\n): Promise<boolean> {\n if (now - session.iat <= auth.ttlMs / 2) return true;\n const user: DashboardSessionUser = {\n id: session.sub,\n ...(session.name !== undefined ? { name: session.name } : {}),\n roles: session.roles,\n };\n const allowed = await getOrStartRevalidation(auth, session, user);\n if (!allowed) {\n clearSessionCookie({ request, response });\n return false;\n }\n issueSessionCookie(user, { auth, request, response, now });\n return true;\n}\n","import { subscribe, unsubscribe } from 'node:diagnostics_channel';\nimport type {\n ActorSpendRow,\n AgentApprovalPort,\n AgentGovernanceQueries,\n AgentPricingStore,\n CurrentModelPrice,\n GovernancePage,\n GovernancePageQuery,\n GovernanceRange,\n ModelSpendRow,\n PendingApprovalRow,\n RecentRunRow,\n RunAgentBreakdownRow,\n RunErrorBreakdownRow,\n RunMetrics,\n RunTrendPoint,\n RunWhere,\n ThreadActivityRow,\n ThreadSpendRow,\n ThreadWhere,\n ToolCallActivityRow,\n ToolCallWhere,\n ToolStatRow,\n UsageTrendPoint,\n} from '@dudousxd/nestjs-agent-core';\nimport { channelName } from '@dudousxd/nestjs-diagnostics';\nimport { Inject, Injectable, NotImplementedException, Optional } from '@nestjs/common';\nimport { Observable } from 'rxjs';\nimport type { ActorDirectory } from './actor-directory.js';\nimport { parseApprovalDecision } from './parse-approval-decision.js';\nimport { parsePriceInput } from './parse-price-input.js';\nimport {\n AGENT_ACTOR_DIRECTORY,\n AGENT_APPROVAL_PORT,\n AGENT_GOVERNANCE_QUERIES,\n AGENT_PRICING_STORE,\n} from './tokens.js';\n\n/** An actor-scoped row decorated with its resolved display label — `null` when unbound or unresolved. */\nexport interface WithActorLabel {\n actorLabel: string | null;\n}\n\nexport type ActorSpendRowWithLabel = ActorSpendRow & WithActorLabel;\nexport type ThreadSpendRowWithLabel = ThreadSpendRow & WithActorLabel;\nexport type ThreadActivityRowWithLabel = ThreadActivityRow & WithActorLabel;\n\n/** The spend/usage overview the SPA renders on its headline section (`GET <api>/spend`). */\nexport interface SpendOverview {\n byModel: ModelSpendRow[];\n byActor: ActorSpendRowWithLabel[];\n trend: UsageTrendPoint[];\n}\n\n/** The run-reliability overview the SPA renders on its Reliability section (`GET <api>/reliability`). */\nexport interface ReliabilityOverview {\n metrics: RunMetrics;\n byAgent: RunAgentBreakdownRow[];\n errors: RunErrorBreakdownRow[];\n trend: RunTrendPoint[];\n}\n\n/** The message returned as a 501 when no `AGENT_PRICING_STORE` is bound. */\nconst PRICING_STORE_UNBOUND_MESSAGE =\n 'Pricing CRUD is unavailable: no AGENT_PRICING_STORE is bound. Bind a pricing store (e.g. ' +\n 'MikroOrmPricingStore from @dudousxd/nestjs-agent-store-mikro-orm) to enable it.';\n\n/** The message returned as a 501 when no `AGENT_APPROVAL_PORT` is bound. */\nconst APPROVAL_PORT_UNBOUND_MESSAGE =\n 'Approve/reject is unavailable: no AGENT_APPROVAL_PORT is bound. Import AgentModule from ' +\n '@dudousxd/nestjs-agent alongside this dashboard to enable it — the approvals inbox stays ' +\n 'read-only until then.';\n\n/** One live agent event forwarded over SSE, flattened from the `aviary:agent:*` diagnostics envelope. */\nexport interface LiveAgentEvent {\n /** The event name, e.g. `run.started` / `tool-call` / `quota.exceeded`. */\n event: string;\n /** Epoch millis the event was emitted. */\n ts: number;\n /** The library-defined payload (see the `Agent*Event` shapes in core's diagnostics). */\n payload: Record<string, unknown>;\n}\n\n/** The `aviary:agent:*` events the Live feed tails. Mirrors the telescope watcher's subscription. */\nconst AGENT_EVENTS = [\n 'run.started',\n 'message',\n 'tool-call',\n 'quota.exceeded',\n 'run.finished',\n 'delegated',\n] as const;\n\n/** The `node:diagnostics_channel` envelope `emit()` publishes (see `@dudousxd/nestjs-diagnostics`). */\ninterface AgentDiagnosticEnvelope {\n event: string;\n ts?: number;\n payload?: Record<string, unknown>;\n}\n\n/** Narrow the untyped diagnostics-channel message to the envelope we forward. */\nfunction isAgentEnvelope(message: unknown): message is AgentDiagnosticEnvelope {\n return (\n typeof message === 'object' &&\n message !== null &&\n 'event' in message &&\n typeof (message as { event: unknown }).event === 'string'\n );\n}\n\n/**\n * Read-model + live bridge backing the AI-gateway console.\n *\n * - Historical, restart-surviving spend/usage/threads come from the injected\n * {@link AGENT_GOVERNANCE_QUERIES} read-model (backed by a store adapter). The host must provide\n * that token — bind it via your `@dudousxd/nestjs-agent` module (global) alongside this dashboard.\n * - Live activity comes off the `aviary:agent:*` diagnostics channel, subscribed per SSE client and\n * unsubscribed when the client disconnects.\n * - `actorLabel` on actor-scoped rows comes from the OPTIONAL {@link AGENT_ACTOR_DIRECTORY} — `null`\n * on every row when nothing is bound, so the console degrades to raw `actorRef`s instead of failing.\n * - Pricing CRUD (`listPrices`/`upsertPrice`) reads/writes the OPTIONAL {@link AGENT_PRICING_STORE} —\n * a 501 with a clear message when nothing is bound.\n */\n@Injectable()\nexport class DashboardService {\n constructor(\n @Inject(AGENT_GOVERNANCE_QUERIES) private readonly queries: AgentGovernanceQueries,\n @Optional()\n @Inject(AGENT_ACTOR_DIRECTORY)\n private readonly actorDirectory?: ActorDirectory,\n @Optional()\n @Inject(AGENT_PRICING_STORE)\n private readonly pricingStore?: AgentPricingStore,\n @Optional()\n @Inject(AGENT_APPROVAL_PORT)\n private readonly approvalPort?: AgentApprovalPort,\n ) {}\n\n /** Spend/usage overview for a day range: by-model + by-actor spend and the daily trend, in parallel. */\n async spend(range: GovernanceRange): Promise<SpendOverview> {\n const [byModel, byActorRaw, trend] = await Promise.all([\n this.queries.spendByModel(range),\n this.queries.spendByActor(range),\n this.queries.usageTrend(range),\n ]);\n const byActor = await this.withActorLabels(byActorRaw);\n return { byModel, byActor, trend };\n }\n\n /** Top threads by cost for a day range (default 10, highest cost first). */\n async topThreads(range: GovernanceRange, limit = 10): Promise<ThreadSpendRowWithLabel[]> {\n const rows = await this.queries.spendByThread(range, limit);\n return this.withActorLabels(rows);\n }\n\n /** Run reliability for a day range: metrics, by-agent/by-error breakdowns and the trend, in parallel. */\n async reliability(range: GovernanceRange): Promise<ReliabilityOverview> {\n const [metrics, byAgent, errors, trend] = await Promise.all([\n this.queries.runMetrics(range),\n this.queries.runsByAgent(range),\n this.queries.runErrors(range),\n this.queries.runTrend(range),\n ]);\n return { metrics, byAgent, errors, trend };\n }\n\n /** Most recent runs (status/agent/duration/error) for the Reliability recent-runs table. */\n recentRuns(limit: number): Promise<RecentRunRow[]> {\n return this.queries.recentRuns(limit);\n }\n\n /** Most recent tool calls (status/type/thread) for the Runs & tools activity feed. */\n recentToolCalls(limit: number): Promise<ToolCallActivityRow[]> {\n return this.queries.recentToolCalls(limit);\n }\n\n /** Paged, filterable tool calls for the Runs & tools activity table. */\n toolCallsPage(\n query: GovernancePageQuery<ToolCallWhere>,\n ): Promise<GovernancePage<ToolCallActivityRow>> {\n return this.queries.toolCallsPage(query);\n }\n\n /** Paged, filterable runs for the Reliability recent-runs table. */\n runsPage(query: GovernancePageQuery<RunWhere>): Promise<GovernancePage<RecentRunRow>> {\n return this.queries.runsPage(query);\n }\n\n /** Tool calls sitting `pending_approval`, oldest first, for the cross-thread approvals inbox. */\n pendingApprovals(limit: number): Promise<PendingApprovalRow[]> {\n return this.queries.pendingApprovals(limit);\n }\n\n /** Per-tool call/failure/rejection/latency rollup for a day range, for the Tools section. */\n toolStats(range: GovernanceRange): Promise<ToolStatRow[]> {\n return this.queries.toolStats(range);\n }\n\n /**\n * Decide a pending HITL tool call from the console. Routes through the OPTIONAL\n * {@link AGENT_APPROVAL_PORT} — bound by `@dudousxd/nestjs-agent` to the SAME signal path chat\n * approvals use — so a 501 here (checked BEFORE body validation, same ordering as\n * {@link upsertPrice}) means \"no approval port bound\", not \"your body was invalid\". `executedByRef`\n * is an OPAQUE decider ref the caller resolved from the live request (see\n * `AgentDashboardOptions.approvalActorRef`); omitted when the host didn't configure one.\n */\n async decideApproval(\n toolCallId: string,\n body: unknown,\n executedByRef: string | undefined,\n ): Promise<void> {\n if (this.approvalPort === undefined) {\n throw new NotImplementedException(APPROVAL_PORT_UNBOUND_MESSAGE);\n }\n const decision = parseApprovalDecision(body);\n const opts = executedByRef !== undefined ? { executedByRef } : {};\n if (decision.approved) {\n await this.approvalPort.approve(toolCallId, opts);\n return;\n }\n await this.approvalPort.reject(toolCallId, {\n ...opts,\n ...(decision.reason !== undefined ? { reason: decision.reason } : {}),\n });\n }\n\n /** Most recent threads with rolled-up message/token counts. */\n async recentThreads(limit: number): Promise<ThreadActivityRowWithLabel[]> {\n const rows = await this.queries.recentThreads(limit);\n return this.withActorLabels(rows);\n }\n\n /**\n * Paged, filterable threads for the Runs & tools threads table — actor-labeled the same way\n * {@link recentThreads} is.\n */\n async threadsPage(\n query: GovernancePageQuery<ThreadWhere>,\n ): Promise<GovernancePage<ThreadActivityRowWithLabel>> {\n const page = await this.queries.threadsPage(query);\n const rows = await this.withActorLabels(page.rows);\n return { ...page, rows };\n }\n\n /**\n * Decorate actor-scoped rows with `actorLabel`, batching the distinct `actorRef`s into ONE\n * {@link ActorDirectory.resolveDisplay} call per response. `null` for every row when no directory\n * is bound, or for a ref the directory didn't resolve.\n */\n private async withActorLabels<Row extends { actorRef: string }>(\n rows: Row[],\n ): Promise<(Row & WithActorLabel)[]> {\n if (rows.length === 0) {\n return [];\n }\n if (this.actorDirectory === undefined) {\n return rows.map((row) => ({ ...row, actorLabel: null }));\n }\n const refs = [...new Set(rows.map((row) => row.actorRef))];\n const resolved = await this.actorDirectory.resolveDisplay(refs);\n return rows.map((row) => ({ ...row, actorLabel: resolved[row.actorRef] ?? null }));\n }\n\n /** Current price row per model, for the pricing tab. 501s when no `AGENT_PRICING_STORE` is bound. */\n async listPrices(): Promise<CurrentModelPrice[]> {\n if (this.pricingStore === undefined) {\n throw new NotImplementedException(PRICING_STORE_UNBOUND_MESSAGE);\n }\n return this.pricingStore.listCurrentPrices();\n }\n\n /**\n * Set a model's current price (`POST <api>/pricing` body). 501s when no `AGENT_PRICING_STORE` is\n * bound (checked BEFORE body validation, so an unbound store always reports as unimplemented rather\n * than as a validation error); otherwise the body is minimally validated via {@link parsePriceInput}.\n */\n async upsertPrice(body: unknown): Promise<void> {\n if (this.pricingStore === undefined) {\n throw new NotImplementedException(PRICING_STORE_UNBOUND_MESSAGE);\n }\n await this.pricingStore.upsertModelPrice(parsePriceInput(body));\n }\n\n /**\n * Live SSE stream of `aviary:agent:*` diagnostics events. One subscription per SSE client:\n * subscribing wires a handler onto each agent channel; the returned teardown removes them all when\n * the client disconnects (or the observable is otherwise unsubscribed).\n */\n streamEvents(): Observable<{ data: LiveAgentEvent }> {\n return new Observable<{ data: LiveAgentEvent }>((subscriber) => {\n const bindings = AGENT_EVENTS.map((event) => {\n const name = channelName('agent', event);\n const handler = (message: unknown): void => {\n if (!isAgentEnvelope(message)) return;\n subscriber.next({\n data: {\n event: message.event,\n ts: message.ts ?? Date.now(),\n payload: message.payload ?? {},\n },\n });\n };\n subscribe(name, handler);\n return { name, handler };\n });\n return () => {\n for (const binding of bindings) unsubscribe(binding.name, binding.handler);\n };\n });\n }\n}\n","import { BadRequestException } from '@nestjs/common';\n\n/** The parsed, minimally-validated `POST <api>/approvals/:toolCallId` body. */\nexport interface ApprovalDecisionInput {\n approved: boolean;\n reason?: string;\n}\n\n/**\n * Minimal shape guard for a `POST <api>/approvals/:toolCallId` body — rejects junk before it reaches\n * `AgentApprovalPort.approve`/`reject`. Mirrors `parsePriceInput`'s role for the pricing endpoint.\n */\nexport function parseApprovalDecision(body: unknown): ApprovalDecisionInput {\n if (typeof body !== 'object' || body === null) {\n throw new BadRequestException('Expected a JSON object body.');\n }\n const { approved, reason } = body as Record<string, unknown>;\n\n if (typeof approved !== 'boolean') {\n throw new BadRequestException('\"approved\" must be a boolean.');\n }\n if (reason !== undefined && typeof reason !== 'string') {\n throw new BadRequestException('\"reason\" must be a string when present.');\n }\n\n return { approved, ...(reason !== undefined ? { reason } : {}) };\n}\n","import type { ModelPriceInput } from '@dudousxd/nestjs-agent-core';\nimport { BadRequestException } from '@nestjs/common';\n\n/**\n * Minimal shape guard for a `POST <api>/pricing` body — rejects junk before it reaches\n * `AgentPricingStore.upsertModelPrice`. Not a full schema validator (the store adapter owns real\n * constraints, e.g. uniqueness); this only checks the wire shape core's `ModelPriceInput` requires.\n */\nexport function parsePriceInput(body: unknown): ModelPriceInput {\n if (typeof body !== 'object' || body === null) {\n throw new BadRequestException('Expected a JSON object body.');\n }\n const { modelId, inputPricePer1m, outputPricePer1m, cacheWritePricePer1m, cacheReadPricePer1m } =\n body as Record<string, unknown>;\n\n if (typeof modelId !== 'string' || modelId.trim().length === 0) {\n throw new BadRequestException('\"modelId\" must be a non-empty string.');\n }\n if (!isFiniteNonNegative(inputPricePer1m)) {\n throw new BadRequestException('\"inputPricePer1m\" must be a non-negative number.');\n }\n if (!isFiniteNonNegative(outputPricePer1m)) {\n throw new BadRequestException('\"outputPricePer1m\" must be a non-negative number.');\n }\n if (cacheWritePricePer1m !== undefined && !isFiniteNonNegative(cacheWritePricePer1m)) {\n throw new BadRequestException(\n '\"cacheWritePricePer1m\" must be a non-negative number when present.',\n );\n }\n if (cacheReadPricePer1m !== undefined && !isFiniteNonNegative(cacheReadPricePer1m)) {\n throw new BadRequestException(\n '\"cacheReadPricePer1m\" must be a non-negative number when present.',\n );\n }\n\n return {\n modelId,\n inputPricePer1m,\n outputPricePer1m,\n ...(cacheWritePricePer1m !== undefined ? { cacheWritePricePer1m } : {}),\n ...(cacheReadPricePer1m !== undefined ? { cacheReadPricePer1m } : {}),\n };\n}\n\nfunction isFiniteNonNegative(value: unknown): value is number {\n return typeof value === 'number' && Number.isFinite(value) && value >= 0;\n}\n","import type { RunWhere, ThreadWhere, ToolCallWhere } from '@dudousxd/nestjs-agent-core';\nimport { BadRequestException } from '@nestjs/common';\n\nconst ISO_DAY = /^\\d{4}-\\d{2}-\\d{2}$/;\n\n/** Parse a `page` query param (1-based); falls back to 1 when absent or not a positive integer. */\nexport function parsePageNumber(value: string | undefined): number {\n const parsed = value === undefined ? Number.NaN : Number.parseInt(value, 10);\n if (!Number.isFinite(parsed) || parsed < 1) return 1;\n return parsed;\n}\n\n/** Rejects a `where[<field>]` whose field name isn't in `allowed`, naming the offending field. */\nfunction assertKnownFields(raw: Record<string, string>, allowed: readonly string[]): void {\n for (const field of Object.keys(raw)) {\n if (!allowed.includes(field)) {\n throw new BadRequestException(`Unknown where field \"${field}\".`);\n }\n }\n}\n\n/** Rejects a day-bound `where[<field>]` that isn't `YYYY-MM-DD`, mirroring the range endpoints' check. */\nfunction assertDayFields(raw: Record<string, string>, dayFields: readonly string[]): void {\n for (const field of dayFields) {\n const value = raw[field];\n if (value !== undefined && !ISO_DAY.test(value)) {\n throw new BadRequestException(`\"where[${field}]\" must be YYYY-MM-DD.`);\n }\n }\n}\n\nconst DAY_FIELDS = ['fromDay', 'toDay'] as const;\nconst TOOL_CALL_WHERE_FIELDS = [\n 'toolName',\n 'toolType',\n 'status',\n 'threadId',\n 'fromDay',\n 'toDay',\n] as const;\n\n/**\n * Parse `GET tool-calls-page`'s `where[...]` query params into a `ToolCallWhere`. `undefined` (no\n * `where` params at all) means no filter. Throws `BadRequestException` on an unknown field name or a\n * malformed day bound.\n */\nexport function parseToolCallWhere(\n raw: Record<string, string> | undefined,\n): ToolCallWhere | undefined {\n if (raw === undefined || Object.keys(raw).length === 0) return undefined;\n assertKnownFields(raw, TOOL_CALL_WHERE_FIELDS);\n assertDayFields(raw, DAY_FIELDS);\n return {\n ...(raw.toolName !== undefined ? { toolName: raw.toolName } : {}),\n ...(raw.toolType !== undefined ? { toolType: raw.toolType } : {}),\n ...(raw.status !== undefined ? { status: raw.status } : {}),\n ...(raw.threadId !== undefined ? { threadId: raw.threadId } : {}),\n ...(raw.fromDay !== undefined ? { fromDay: raw.fromDay } : {}),\n ...(raw.toDay !== undefined ? { toDay: raw.toDay } : {}),\n };\n}\n\nconst THREAD_WHERE_FIELDS = ['actorRef', 'title', 'fromDay', 'toDay'] as const;\n\n/**\n * Parse `GET threads-page`'s `where[...]` query params into a `ThreadWhere`. `undefined` means no\n * filter. Throws `BadRequestException` on an unknown field name or a malformed day bound.\n */\nexport function parseThreadWhere(raw: Record<string, string> | undefined): ThreadWhere | undefined {\n if (raw === undefined || Object.keys(raw).length === 0) return undefined;\n assertKnownFields(raw, THREAD_WHERE_FIELDS);\n assertDayFields(raw, DAY_FIELDS);\n return {\n ...(raw.actorRef !== undefined ? { actorRef: raw.actorRef } : {}),\n ...(raw.title !== undefined ? { title: raw.title } : {}),\n ...(raw.fromDay !== undefined ? { fromDay: raw.fromDay } : {}),\n ...(raw.toDay !== undefined ? { toDay: raw.toDay } : {}),\n };\n}\n\nconst RUN_WHERE_FIELDS = ['agentName', 'status', 'errorCode', 'fromDay', 'toDay'] as const;\n\n/**\n * Parse `GET runs-page`'s `where[...]` query params into a `RunWhere`. `undefined` means no filter.\n * Throws `BadRequestException` on an unknown field name or a malformed day bound.\n */\nexport function parseRunWhere(raw: Record<string, string> | undefined): RunWhere | undefined {\n if (raw === undefined || Object.keys(raw).length === 0) return undefined;\n assertKnownFields(raw, RUN_WHERE_FIELDS);\n assertDayFields(raw, DAY_FIELDS);\n return {\n ...(raw.agentName !== undefined ? { agentName: raw.agentName } : {}),\n ...(raw.status !== undefined ? { status: raw.status } : {}),\n ...(raw.errorCode !== undefined ? { errorCode: raw.errorCode } : {}),\n ...(raw.fromDay !== undefined ? { fromDay: raw.fromDay } : {}),\n ...(raw.toDay !== undefined ? { toDay: raw.toDay } : {}),\n };\n}\n","import {\n Body,\n Controller,\n Get,\n Header,\n HttpCode,\n Inject,\n Logger,\n NotFoundException,\n Optional,\n Post,\n Query,\n Req,\n Res,\n UnauthorizedException,\n} from '@nestjs/common';\nimport { parseCookieHeader } from './auth/cookie-header.js';\nimport type { ResolvedDashboardAuth } from './auth/dashboard-auth-config.js';\nimport { readCookieHeader } from './auth/http-request.js';\nimport { renderLoginPage, renderSessionRequiredPage } from './auth/login-page.js';\nimport { sanitizeReturnTo } from './auth/sanitize-return-to.js';\nimport {\n SESSION_COOKIE_NAME,\n clearSessionCookie,\n issueSessionCookie,\n} from './auth/session-cookie-io.js';\nimport { type DashboardSessionUser, verifySessionCookie } from './auth/session-cookie.js';\nimport { DASHBOARD_AUTH, DASHBOARD_BASE_PATH } from './tokens.js';\n\n/** The slice of the platform request this controller reads — structural, no Express/Fastify dependency. */\ninterface AuthPageRequest {\n headers: Record<string, string | string[] | undefined>;\n}\n\n/** The slice of the platform response this controller writes (passthrough mode — Nest still sends). */\ninterface AuthPageResponse {\n status(code: number): unknown;\n setHeader(name: string, value: string): unknown;\n}\n\ninterface LoginFormBody {\n username?: unknown;\n password?: unknown;\n returnTo?: unknown;\n}\n\nfunction isNonEmptyString(value: unknown): value is string {\n return typeof value === 'string' && value.trim() !== '';\n}\n\nfunction isString(value: unknown): value is string {\n return typeof value === 'string';\n}\n\n/**\n * Mounted directly on `AgentDashboardModule` at `<basePath>/auth/*` — a SEPARATE controller from\n * `AgentUiController`/`AgentApiController` so it carries neither `DashboardAuthPageGuard`,\n * `DashboardAuthGuard`, nor any host `guards`: these routes MINT (and clear) the session cookie the\n * built-in gates check for, so they can never be made to require the very auth they grant. Mirrors\n * `@dudousxd/nestjs-telescope`'s own auth controller staying outside its `stampGuards` call.\n *\n * Two ways to mint that cookie, matching `resolveDashboardAuth`'s modes: Mode A (`session`) — the\n * host frontend, already carrying its own auth, POSTs to `<basePath>/auth/session` and the host\n * hook decides — or Mode B (`login`), a dependency-free, server-rendered login form (GET/POST) —\n * the bundled React SPA in this package is a built Vite artifact with no auth-aware UI of its own,\n * so gating the actual page navigation needs a self-contained flow that doesn't touch it. Each\n * mode's routes 404 when that mode isn't listed in `auth.modes` — the single source of truth for\n * which mode(s) are configured, not hook-presence truthiness — and every route 404s outright when\n * `dashboardAuth` is unconfigured (`DASHBOARD_AUTH` resolves to `null`) — no dangling login page\n * when the feature is off.\n */\n@Controller('auth')\nexport class AgentDashboardAuthController {\n private readonly logger = new Logger(AgentDashboardAuthController.name);\n /** Warn once PER HOOK KIND (not per request) so a broken hook can't be used to flood the logs. */\n private readonly warnedHooks = new Set<string>();\n\n constructor(\n @Optional() @Inject(DASHBOARD_AUTH) private readonly auth: ResolvedDashboardAuth | null,\n @Inject(DASHBOARD_BASE_PATH) private readonly basePath: string,\n ) {}\n\n @Get('login')\n @Header('Content-Type', 'text/html; charset=utf-8')\n @Header('Cache-Control', 'no-store')\n loginPage(\n @Req() req: AuthPageRequest,\n @Res({ passthrough: true }) res: AuthPageResponse,\n @Query('returnTo') returnToQuery?: string,\n @Query('error') error?: string,\n ): string {\n const auth = this.requireAuth();\n // Mode A only: no login screen exists — the host mints the session.\n if (!auth.modes.includes('login')) throw new NotFoundException();\n const returnTo = sanitizeReturnTo(this.basePath, returnToQuery);\n // Already signed in — a fresh GET (e.g. a bookmarked login URL) just goes back in, no need to\n // re-prompt for credentials.\n if (this.currentSession(req)) {\n res.status(302);\n res.setHeader('Location', returnTo);\n return '';\n }\n return renderLoginPage({\n actionUrl: `${this.basePath}/auth/login`,\n returnTo,\n error: error !== undefined,\n });\n }\n\n @Post('login')\n async login(\n @Body() body: LoginFormBody,\n @Req() req: unknown,\n @Res({ passthrough: true }) res: AuthPageResponse,\n ): Promise<string> {\n const auth = this.requireAuth();\n if (!auth.modes.includes('login')) throw new NotFoundException();\n const returnTo = sanitizeReturnTo(this.basePath, body?.returnTo);\n const username = body?.username;\n const password = body?.password;\n // Username is required (non-empty, trimmed). Password is OPTIONAL end-to-end: it reaches the\n // host `login` hook AS-IS — `''` when blank/omitted — because some hosts gate on username\n // alone (e.g. flip: email must belong to an active ADMIN, password deliberately ignored). Only\n // reject here on a malformed shape (password present but not a string), never on emptiness.\n if (!isNonEmptyString(username) || (password !== undefined && !isString(password))) {\n this.redirectToLoginError(res, returnTo);\n return '';\n }\n const user = await this.runHook(\n 'login',\n () => auth.login?.(username.trim(), isString(password) ? password : '') ?? null,\n );\n if (!user) {\n this.redirectToLoginError(res, returnTo);\n return '';\n }\n issueSessionCookie(user, { auth, request: req, response: res });\n res.status(302);\n res.setHeader('Location', returnTo);\n return '';\n }\n\n @Post('logout')\n logout(@Req() req: unknown, @Res({ passthrough: true }) res: AuthPageResponse): string {\n // Best-effort: clearing is harmless even without dashboardAuth configured.\n clearSessionCookie({ request: req, response: res });\n res.status(302);\n // Runs WITHOUT requireAuth() (logout must succeed even when auth is off/misconfigured), so\n // read `this.auth?.login` directly rather than calling it. Mirrors `DashboardAuthPageGuard`'s\n // `deny()` mode check: Mode-A-only has no login screen to land on (`loginPage` above 404s\n // under Mode A), so bounce to the instruction page instead. When `dashboardAuth` isn't\n // configured at all, `this.auth` is `null` and today's target is preserved unchanged.\n res.setHeader(\n 'Location',\n this.auth && !this.auth.modes.includes('login')\n ? `${this.basePath}/auth/session-required`\n : `${this.basePath}/auth/login`,\n );\n return '';\n }\n\n // Mode A: the host frontend (carrying its own auth) POSTs here; the host hook validates the raw\n // request and returns the session user, or `null` to deny. No credential reaches this library.\n @Post('session')\n @HttpCode(204)\n async session(\n @Req() request: unknown,\n @Res({ passthrough: true }) response: unknown,\n ): Promise<void> {\n const auth = this.requireAuth();\n if (!auth.modes.includes('session')) throw new NotFoundException();\n const user = await this.runHook('session', () => auth.session?.(request) ?? null);\n if (!user) throw new UnauthorizedException();\n issueSessionCookie(user, { auth, request, response });\n }\n\n // Mode-A-only landing: the host mints the session, so there is nothing to submit here. Serves\n // as the redirect target `DashboardAuthPageGuard` bounces to instead of `/auth/login`, which\n // 404s under Mode A (see `loginPage` above).\n @Get('session-required')\n @Header('Content-Type', 'text/html; charset=utf-8')\n @Header('Cache-Control', 'no-store, must-revalidate')\n sessionRequiredPage(): string {\n const auth = this.requireAuth();\n if (auth.modes.includes('login')) throw new NotFoundException();\n return renderSessionRequiredPage();\n }\n\n /** Uniform failure: same redirect (generic error flag) whether the user is unknown or the password is wrong. */\n private redirectToLoginError(res: AuthPageResponse, returnTo: string): void {\n res.status(303);\n res.setHeader(\n 'Location',\n `${this.basePath}/auth/login?error=1&returnTo=${encodeURIComponent(returnTo)}`,\n );\n }\n\n private currentSession(req: AuthPageRequest): boolean {\n const auth = this.auth;\n if (!auth) return false;\n const cookieValue = parseCookieHeader(readCookieHeader(req))[SESSION_COOKIE_NAME];\n if (cookieValue === undefined) return false;\n return verifySessionCookie(cookieValue, { secret: auth.secret }) !== null;\n }\n\n /**\n * Run a host hook (`login` or `session`) defensively: a throw (sync or async) is treated as a\n * denial (`null`) and warn-logged ONCE PER KIND, so a buggy/unreachable hook never 500s the\n * endpoint into a stack-trace leak nor floods the logs on repeated bad attempts.\n */\n private async runHook(\n kind: string,\n run: () => Promise<DashboardSessionUser | null> | DashboardSessionUser | null,\n ): Promise<DashboardSessionUser | null> {\n try {\n return (await run()) ?? null;\n } catch (error) {\n if (!this.warnedHooks.has(kind)) {\n this.warnedHooks.add(kind);\n this.logger.warn(\n `AgentDashboardModule dashboardAuth ${kind} hook threw; treating as denial. ${\n error instanceof Error ? error.message : String(error)\n }`,\n );\n }\n return null;\n }\n }\n\n private requireAuth(): ResolvedDashboardAuth {\n // The auth controller only does anything useful when dashboardAuth is configured — a\n // defensive 404 rather than a reachable runtime path for a properly-wired module.\n if (!this.auth) throw new NotFoundException();\n return this.auth;\n }\n}\n","// packages/dashboard/src/server/auth/login-page.ts\n\n/** Escape untrusted text placed into the HTML body/attributes below. */\nfunction escapeHtml(value: string): string {\n return value\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&#39;');\n}\n\nexport interface LoginPageOptions {\n /** Where the login form POSTs (`<basePath>/auth/login`). */\n actionUrl: string;\n /** Carried through as a hidden field so a successful login returns to where the visit started. */\n returnTo: string;\n /** Render the generic \"invalid credentials\" banner. */\n error?: boolean;\n}\n\n/**\n * Shared HTML shell for the `dashboardAuth` server-rendered pages (the Mode B login form below and\n * `renderSessionRequiredPage`'s Mode A instruction page) — same dark zinc card, mono type, emerald\n * accent, so every auth-adjacent page in this console reads as one product. `content` is placed\n * inside the `.card` div verbatim — always caller-controlled static/escaped markup, never raw user\n * input, so this sidesteps HTML-escaping entirely.\n */\nfunction pageShell(content: string, title: string): string {\n return `<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\" />\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n<meta name=\"robots\" content=\"noindex, nofollow\" />\n<title>${title}</title>\n<style>\n :root { color-scheme: dark; }\n * { box-sizing: border-box; }\n body {\n margin: 0;\n min-height: 100vh;\n display: flex;\n align-items: center;\n justify-content: center;\n background: #09090b;\n color: #e4e4e7;\n font: 14px/1.5 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n padding: 16px;\n }\n .card {\n width: 100%;\n max-width: 384px;\n border: 1px solid #27272a;\n background: #18181b;\n border-radius: 8px;\n padding: 32px;\n box-shadow: 0 25px 50px -12px rgb(0 0 0 / 0.5);\n }\n .brand {\n margin: 0 0 24px;\n text-align: center;\n font-size: 18px;\n font-weight: 600;\n color: #34d399;\n }\n h1 {\n margin: 0 0 12px;\n font-size: 16px;\n font-weight: 600;\n color: #f4f4f5;\n }\n p { margin: 0 0 16px; color: #a1a1aa; }\n form { display: flex; flex-direction: column; gap: 16px; }\n label { display: flex; flex-direction: column; gap: 6px; }\n .field-label {\n font-size: 11px;\n text-transform: uppercase;\n letter-spacing: 0.05em;\n color: #71717a;\n }\n input {\n border-radius: 4px;\n border: 1px solid #3f3f46;\n background: #09090b;\n color: #f4f4f5;\n padding: 8px 12px;\n font: inherit;\n outline: none;\n }\n input:focus { border-color: rgb(52 211 153 / 0.6); }\n .error { margin: 0; font-size: 12px; color: #fb7185; }\n button {\n margin-top: 8px;\n border-radius: 4px;\n border: 1px solid rgb(52 211 153 / 0.4);\n background: rgb(52 211 153 / 0.1);\n color: #6ee7b7;\n padding: 8px 12px;\n font: inherit;\n font-size: 12px;\n font-weight: 500;\n text-transform: uppercase;\n letter-spacing: 0.05em;\n cursor: pointer;\n }\n button:hover { background: rgb(52 211 153 / 0.2); }\n</style>\n</head>\n<body>\n <div class=\"card\">\n ${content}\n </div>\n</body>\n</html>`;\n}\n\n/**\n * A dependency-free, server-rendered login page — no build step, no client JS framework. The\n * visual language (dark zinc card, mono type, emerald accent) mirrors\n * `@dudousxd/nestjs-telescope`'s built-in `AuthScreen` so the two consoles feel like one family.\n */\nexport function renderLoginPage(options: LoginPageOptions): string {\n const action = escapeHtml(options.actionUrl);\n const returnTo = escapeHtml(options.returnTo);\n const errorBanner =\n options.error === true ? '<p role=\"alert\" class=\"error\">Invalid username or password.</p>' : '';\n return pageShell(\n `<p class=\"brand\">AI Gateway</p>\n <form method=\"post\" action=\"${action}\">\n <input type=\"hidden\" name=\"returnTo\" value=\"${returnTo}\" />\n <label>\n <span class=\"field-label\">Username</span>\n <input type=\"text\" name=\"username\" autocomplete=\"username\" autofocus />\n </label>\n <label>\n <span class=\"field-label\">Password</span>\n <input type=\"password\" name=\"password\" autocomplete=\"current-password\" />\n </label>\n ${errorBanner}\n <button type=\"submit\">Sign in</button>\n </form>`,\n 'Sign in — AI Gateway',\n );\n}\n\n/**\n * Mode-A-only landing (`GET <basePath>/auth/login` 404s under Mode A — see\n * `AgentDashboardAuthController.loginPage`): what `DashboardAuthPageGuard` bounces an\n * unauthenticated page navigation to instead, since there is no login form to redirect to — the\n * host mints the session itself, typically from its own console launcher. Static and\n * parameter-free: nothing on this page varies per request, so there's no `basePath` (or anything\n * else) to interpolate — no `<script>`/inline handler either, so the page stays inert under a host\n * CSP that omits `'unsafe-inline'` from `script-src`.\n */\nexport function renderSessionRequiredPage(): string {\n return pageShell(\n `<p class=\"brand\">AI Gateway</p>\n <h1>Open this console from your application</h1>\n <p>Your session is minted by the host app. Use its console launcher to sign in, then reload.</p>`,\n 'Sign in — AI Gateway',\n );\n}\n","// packages/dashboard/src/server/auth/sanitize-return-to.ts\n\n/**\n * Validate a client-supplied `returnTo` so the login flow can never be turned into an open\n * redirect: it must be a same-origin path (no scheme, no `//host` prefix) that stays within\n * `basePath`. Anything else — absent, a protocol-relative URL, a foreign path — falls back to\n * `basePath` itself.\n */\nexport function sanitizeReturnTo(basePath: string, returnTo: unknown): string {\n if (typeof returnTo !== 'string' || returnTo === '') return basePath;\n // Reject anything that isn't a plain same-origin path: no scheme (`http:`), no\n // protocol-relative prefix (`//evil.com`), must start with `/`.\n if (!returnTo.startsWith('/') || returnTo.startsWith('//')) return basePath;\n if (\n returnTo !== basePath &&\n !returnTo.startsWith(`${basePath}/`) &&\n !returnTo.startsWith(`${basePath}?`)\n ) {\n return basePath;\n }\n return returnTo;\n}\n","/**\n * Leading slash, no trailing slash (`'ai-gateway/'` -> `'/ai-gateway'`). Shared by\n * {@link AgentDashboardModule.forRoot} and {@link agentDashboardMountPaths} so the mount-path math\n * behind the module and the pure helper that mirrors it can never drift apart.\n */\nexport function normalizeDashboardPath(path: string): string {\n return `/${path.replace(/^\\/+|\\/+$/g, '')}`;\n}\n","import { normalizeDashboardPath } from './normalize-path.js';\n\n/** Same shape {@link AgentDashboardModule.forRoot} accepts — kept local so this stays a pure, DI-free helper. */\nexport interface AgentDashboardMountPathsOptions {\n basePath?: string;\n apiBasePath?: string;\n}\n\n/** Strip the leading slash `normalizeDashboardPath` adds — `setGlobalPrefix`'s `exclude` roots are unprefixed. */\nfunction unprefixed(path: string): string {\n return path.replace(/^\\/+/, '');\n}\n\n/**\n * Route roots a host must EXCLUDE from a global prefix (`setGlobalPrefix('api', { exclude })`) so\n * the AI-gateway dashboard's SPA and JSON API keep resolving at their configured mount paths instead\n * of being shifted under the prefix.\n *\n * Unlike a single-surface dashboard (e.g. `telescopeMountPaths()`), this one mounts TWO route roots —\n * the UI at `basePath` and its JSON API at `apiBasePath` — so excluding only one leaves the other\n * shadowed. `options` mirrors {@link AgentDashboardOptions} and resolves through the exact same\n * defaulting (`apiBasePath` falls back to `<basePath>/api`) as {@link AgentDashboardModule.forRoot},\n * so the excluded roots always agree with what actually got mounted.\n *\n * @example\n * ```ts\n * // Raw defaults (basePath `/ai-gateway`, apiBasePath `/ai-gateway/api`):\n * app.setGlobalPrefix('api', { exclude: agentDashboardMountPaths() });\n * // -> ['ai-gateway', 'ai-gateway/{*splat}', 'ai-gateway/api', 'ai-gateway/api/{*splat}']\n *\n * // The recommended pattern — apiBasePath nested under the app's own `/api` prefix — MUST pass the\n * // same options given to `forRoot(...)`:\n * const dashboardOptions = { apiBasePath: '/api/ai-gateway' };\n * app.setGlobalPrefix('api', { exclude: agentDashboardMountPaths(dashboardOptions) });\n * // -> ['ai-gateway', 'ai-gateway/{*splat}', 'api/ai-gateway', 'api/ai-gateway/{*splat}']\n * ```\n */\nexport function agentDashboardMountPaths(options?: AgentDashboardMountPathsOptions): string[] {\n const basePath = normalizeDashboardPath(options?.basePath ?? '/ai-gateway');\n const apiBasePath = normalizeDashboardPath(options?.apiBasePath ?? `${basePath}/api`);\n const base = unprefixed(basePath);\n const api = unprefixed(apiBasePath);\n return [base, `${base}/{*splat}`, api, `${api}/{*splat}`];\n}\n","import 'reflect-metadata';\nimport {\n type CanActivate,\n type DynamicModule,\n type InjectionToken,\n Module,\n type OptionalFactoryDependency,\n type Provider,\n type Type,\n} from '@nestjs/common';\nimport { RouterModule } from '@nestjs/core';\nimport { AgentApiController } from './agent-api.controller.js';\nimport { AgentDashboardAuthController } from './agent-dashboard-auth.controller.js';\nimport { AgentUiController } from './agent-ui.controller.js';\nimport type { DashboardAuthOptions } from './auth/dashboard-auth-config.js';\nimport { resolveDashboardAuth } from './auth/dashboard-auth-config.js';\nimport { DashboardAuthPageGuard } from './auth/dashboard-auth-page.guard.js';\nimport { DashboardAuthGuard } from './auth/dashboard-auth.guard.js';\nimport { DashboardService } from './dashboard.service.js';\nimport { baseGuards, isGuardClass, stampGuards } from './guards.js';\nimport { normalizeDashboardPath } from './normalize-path.js';\nimport {\n DASHBOARD_API_PATH,\n DASHBOARD_APPROVAL_ACTOR_REF,\n DASHBOARD_AUTH,\n DASHBOARD_BASE_PATH,\n} from './tokens.js';\n\n/**\n * `AgentApiController`/`AgentUiController`'s own, pristine `@UseGuards(...)` metadata — the\n * built-in `dashboardAuth` guard, always present — captured once at module-load time. Every\n * `stampGuards` call below appends host `guards` onto THIS baseline (never onto whatever a prior\n * `forRoot`/`forRootAsync` call last stamped), so repeated calls in the same process never compound\n * (see `./guards.ts`).\n */\nconst API_BASE_GUARDS = baseGuards(AgentApiController);\nconst UI_BASE_GUARDS = baseGuards(AgentUiController);\n\n/**\n * `TReq` mirrors core's `ActorResolver<TReq>` convention: the transport request type\n * {@link AgentDashboardOptions.approvalActorRef} receives. Defaults to `unknown` so untyped usage\n * compiles unchanged; a host may narrow it (e.g. `forRoot<Request>({ approvalActorRef: ... })`)\n * instead of writing its own `unknown`-narrowing type guard.\n */\nexport interface AgentDashboardOptions<TReq = unknown> {\n /**\n * Where the SPA (UI) is served. Default `/ai-gateway`. This is a page route — keep it out of an\n * `/api` prefix so it reads as a UI, not an endpoint.\n */\n basePath?: string;\n /**\n * Where the JSON API is mounted (what the SPA fetches). Default `<basePath>/api`. Set it under\n * your app's `/api` prefix — e.g. `/api/ai-gateway` — so the API inherits the app's auth/proxy\n * rules while the UI stays at `basePath`.\n */\n apiBasePath?: string;\n /**\n * Guard classes fronting BOTH dashboard controllers (the SPA at `basePath` and its JSON API at\n * `apiBasePath`) — APPENDED onto the built-in `dashboardAuth` gate (a no-op unless configured;\n * see {@link dashboardAuth}), so a request must pass BOTH. Omit to leave the routes gated by\n * `dashboardAuth` alone (or fully open when that's unset too).\n *\n * A guard's own DEPENDENCIES resolve from this module's `imports` (see {@link imports}) — the\n * dashboard module has no application context of its own to pull them from otherwise.\n */\n guards?: Type<CanActivate>[];\n /**\n * Extra `imports` merged into the dashboard's dynamic module — the DI resolution path for a class\n * passed to {@link guards} (or any other provider the controllers need reachable). Typically the\n * host's own auth module, e.g. `imports: [AuthModule]` alongside `guards: [JwtAuthGuard]`.\n */\n imports?: DynamicModule['imports'];\n /**\n * OVERRIDE for WHO is deciding a HITL approval — invoked on the incoming\n * `POST <api>/approvals/:toolCallId` request to stamp `AgentApprovalPort`'s `opts.executedByRef`.\n *\n * DEFAULT (option omitted): the AgentModule-configured actor resolver (`AGENT_ACTOR_RESOLVER`) is\n * consulted — the same identity seam chat requests use — and its actor id becomes the decider ref\n * (a throwing resolver, i.e. an unauthenticated request, just omits the ref). Set this only when\n * console auth differs from chat auth (e.g. the console sits behind a separate SSO whose principal\n * the chat resolver can't read); returning `undefined` leaves `executedByRef` unset — the explicit\n * override wins outright, with no resolver fallback.\n */\n approvalActorRef?: (req: TReq) => string | undefined;\n /**\n * Gate the console (SPA + API) behind a built-in, signed session cookie. Two ways to mint it:\n *\n * - **Mode A (`session`, recommended)**: your host app already has its own auth (SSO/OIDC/\n * whatever) — the host frontend, carrying that auth, POSTs to `<basePath>/auth/session`, your\n * `session` hook validates the raw request and returns the session user, and this library\n * mints its cookie from that. No credential this library understands ever exists; you keep\n * your own identity provider as the source of truth. A page-level request with no valid\n * session (and only Mode A configured) gets a small server-rendered instruction page telling\n * the visitor to sign in through your host app, since there is no login page to redirect to.\n * - **Mode B (`login`, standalone fallback)**: no host frontend/IdP to lean on — the console\n * serves its own small, dependency-free, server-rendered login page (`GET\n * <basePath>/auth/login`) and your `login` hook validates submitted username/password. A\n * missing/invalid/expired session redirects a page-level request to that login page with\n * `?returnTo=<original url>`.\n *\n * Either mode can be used alone, or both together (e.g. Mode A for normal use, Mode B as a\n * break-glass fallback) — at least one is required, or `forRoot`/`forRootAsync` throws at boot\n * (an un-mintable gate is a boot error, not a silently-open or silently-stuck console). An\n * unauthenticated API call always gets a plain `401`, regardless of mode — the caller reads the\n * status code, not HTML. Composes with `guards` — BOTH gates must pass. See\n * `DashboardAuthOptions` for the `secret`/`ttl`/`session`/`login` shape.\n */\n dashboardAuth?: DashboardAuthOptions;\n}\n\n/**\n * Async variant of {@link AgentDashboardOptions}, for a `session`/`login` hook that needs injected\n * services (e.g. an EntityManager to check credentials against a DB). Everything else stays\n * static/sync — `basePath`/`apiBasePath`/`guards` are needed at module-build time (the same\n * constraint `@dudousxd/nestjs-telescope`'s and `@dudousxd/nestjs-media`'s own `forRootAsync`s\n * document), so only the auth piece itself is resolved through DI.\n */\nexport interface AgentDashboardAsyncOptions<TReq = unknown> {\n basePath?: string;\n apiBasePath?: string;\n guards?: Type<CanActivate>[];\n imports?: DynamicModule['imports'];\n approvalActorRef?: (req: TReq) => string | undefined;\n /** Providers injected into `useDashboardAuth`, in order. Shared with a guard class's own deps via `imports`. */\n inject?: Array<InjectionToken | OptionalFactoryDependency>;\n /**\n * Build the `dashboardAuth` config from injected deps, or return `undefined` to leave the\n * console open (or fronted only by `guards`). See {@link AgentDashboardOptions.dashboardAuth}.\n */\n useDashboardAuth: (\n ...deps: any[]\n ) => DashboardAuthOptions | undefined | Promise<DashboardAuthOptions | undefined>;\n}\n\n/** Leading slash, no trailing slash. */\nfunction normalize(path: string): string {\n return normalizeDashboardPath(path);\n}\n\n/**\n * Holds the JSON API + SSE controller and its read service, mounted on its own path by `forRoot`.\n * Dynamic: guards are DI-instantiated by the CONTROLLER's host module, so this module — not the\n * outer wrapper — must carry the guard classes as providers plus the host's `imports` that resolve\n * their dependencies. A static module here made `guards: [SomeGuardWithDeps]` fail at boot with\n * \"Nest can't resolve dependencies ... in the AgentApiModule context\" even when the host passed\n * the right `imports` to `forRoot`.\n */\n@Module({})\nexport class AgentApiModule {\n static register<TReq = unknown>(options: {\n imports?: DynamicModule['imports'];\n guards?: Type<CanActivate>[];\n approvalActorRef?: (req: TReq) => string | undefined;\n /** Provider for `DASHBOARD_AUTH` — a `useValue` (`forRoot`) or a `useFactory` (`forRootAsync`). */\n dashboardAuth: Provider;\n }): DynamicModule {\n stampGuards(options.guards, [[AgentApiController, API_BASE_GUARDS]]);\n return {\n module: AgentApiModule,\n imports: [...(options.imports ?? [])],\n controllers: [AgentApiController],\n providers: [\n DashboardService,\n DashboardAuthGuard,\n options.dashboardAuth,\n ...(options.guards ?? []).filter(isGuardClass),\n // `useValue` even when `options.approvalActorRef` is `undefined` — AgentApiController\n // injects this WITHOUT `@Optional()` (same pattern as `AGENT_QUOTA_STORE`'s factory).\n { provide: DASHBOARD_APPROVAL_ACTOR_REF, useValue: options.approvalActorRef },\n ],\n // DASHBOARD_AUTH re-exported so AgentUiController/AgentDashboardAuthController (hosted\n // directly by AgentDashboardModule, which imports this module) resolve the SAME instance\n // instead of re-running a host's async factory a second time.\n exports: [DashboardService, DASHBOARD_AUTH],\n };\n }\n}\n\n/**\n * Mounts the AI-gateway governance console: the bundled React SPA at `basePath` and its JSON + SSE\n * API at `apiBasePath` (default `<basePath>/api`), plus the built-in `dashboardAuth` routes at\n * `<basePath>/auth/*` when {@link AgentDashboardOptions.dashboardAuth} is set — `POST\n * <basePath>/auth/session` for Mode A (the host mints the session from its own auth) and/or `GET`/\n * `POST <basePath>/auth/login` for Mode B (the built-in login screen).\n *\n * Import via `AgentDashboardModule.forRoot(...)` alongside your `@dudousxd/nestjs-agent` module\n * (global), which must provide `AGENT_GOVERNANCE_QUERIES` (bound by a store adapter). Front the\n * routes with the first-class `guards` option (plus `imports` for the guards' own dependencies) —\n * see {@link AgentDashboardOptions.guards} — and/or the built-in `dashboardAuth` session cookie.\n *\n * Inertia hosts: the console is a full-page app, not an Inertia page. An in-app `<Link>` visit to\n * `basePath` (an XHR carrying `X-Inertia`) is bounced with the protocol's own external-redirect\n * mechanism — `409 Conflict` + `X-Inertia-Location: <the visited URL>` — so the Inertia client\n * performs a full `window.location` load and the console renders normally. In-app links to the\n * console therefore just work; no host-side special-casing needed.\n */\n@Module({})\nexport class AgentDashboardModule {\n static forRoot<TReq = unknown>(options: AgentDashboardOptions<TReq> = {}): DynamicModule {\n const basePath = normalize(options.basePath ?? '/ai-gateway');\n const apiBasePath = normalize(options.apiBasePath ?? `${basePath}/api`);\n stampGuards(options.guards, [[AgentUiController, UI_BASE_GUARDS]]);\n const dashboardAuthProvider: Provider = {\n provide: DASHBOARD_AUTH,\n useValue: resolveDashboardAuth(options.dashboardAuth),\n };\n return {\n module: AgentDashboardModule,\n imports: [\n ...(options.imports ?? []),\n // Guards + host imports must reach the API controller's HOST module — enhancers resolve\n // from their controller's own module, never from a parent (see AgentApiModule.register).\n // Spread-only-when-set: exactOptionalPropertyTypes rejects an explicit `undefined`.\n AgentApiModule.register({\n ...(options.imports ? { imports: options.imports } : {}),\n ...(options.guards ? { guards: options.guards } : {}),\n ...(options.approvalActorRef ? { approvalActorRef: options.approvalActorRef } : {}),\n dashboardAuth: dashboardAuthProvider,\n }),\n RouterModule.register([\n { path: basePath, module: AgentDashboardModule }, // the UI + auth controllers below\n { path: apiBasePath, module: AgentApiModule },\n ]),\n ],\n controllers: [AgentUiController, AgentDashboardAuthController],\n providers: [\n { provide: DASHBOARD_BASE_PATH, useValue: basePath },\n { provide: DASHBOARD_API_PATH, useValue: apiBasePath },\n DashboardAuthPageGuard,\n // AgentUiController is hosted HERE, so its guards DI-instantiate from this module.\n ...(options.guards ?? []).filter(isGuardClass),\n ],\n // Re-export the API module so its DashboardService reaches importers (e.g. the host's own controllers).\n exports: [AgentApiModule],\n };\n }\n\n static forRootAsync<TReq = unknown>(options: AgentDashboardAsyncOptions<TReq>): DynamicModule {\n const basePath = normalize(options.basePath ?? '/ai-gateway');\n const apiBasePath = normalize(options.apiBasePath ?? `${basePath}/api`);\n stampGuards(options.guards, [[AgentUiController, UI_BASE_GUARDS]]);\n const dashboardAuthProvider: Provider = {\n provide: DASHBOARD_AUTH,\n inject: options.inject ?? [],\n useFactory: async (...deps: unknown[]) =>\n resolveDashboardAuth(await options.useDashboardAuth(...deps)),\n };\n return {\n module: AgentDashboardModule,\n imports: [\n ...(options.imports ?? []),\n AgentApiModule.register({\n ...(options.imports ? { imports: options.imports } : {}),\n ...(options.guards ? { guards: options.guards } : {}),\n ...(options.approvalActorRef ? { approvalActorRef: options.approvalActorRef } : {}),\n dashboardAuth: dashboardAuthProvider,\n }),\n RouterModule.register([\n { path: basePath, module: AgentDashboardModule },\n { path: apiBasePath, module: AgentApiModule },\n ]),\n ],\n controllers: [AgentUiController, AgentDashboardAuthController],\n providers: [\n { provide: DASHBOARD_BASE_PATH, useValue: basePath },\n { provide: DASHBOARD_API_PATH, useValue: apiBasePath },\n DashboardAuthPageGuard,\n ...(options.guards ?? []).filter(isGuardClass),\n ],\n exports: [AgentApiModule],\n };\n }\n}\n","import { existsSync, readFileSync } from 'node:fs';\nimport { basename, extname, join, resolve, sep } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport {\n Controller,\n Get,\n Header,\n Inject,\n NotFoundException,\n Param,\n Req,\n Res,\n StreamableFile,\n UseFilters,\n UseGuards,\n} from '@nestjs/common';\nimport { DashboardAuthPageGuard } from './auth/dashboard-auth-page.guard.js';\nimport { DashboardAuthRedirectFilter } from './auth/dashboard-auth-redirect.js';\nimport { DASHBOARD_API_PATH, DASHBOARD_BASE_PATH } from './tokens.js';\n\n/**\n * The slice of the express request the Inertia bounce below reads — structural, so the package\n * needs no express dependency (the API controller similarly takes `@Req() req: unknown`).\n */\ninterface UiPageRequest {\n headers: Record<string, string | string[] | undefined>;\n /** Path + query as received (express `originalUrl`) — what the full-page visit must reload. */\n originalUrl: string;\n}\n\n/** The slice of the express response the Inertia bounce writes (passthrough mode — Nest still sends). */\ninterface UiPageResponse {\n status(code: number): unknown;\n setHeader(name: string, value: string): unknown;\n}\n\n/** The base the SPA bundle was built with (Vite `base`); rewritten to the configured base at serve time. */\nconst BUILD_BASE = '/ai-gateway';\n\n/** dist/server/agent-ui.controller.js -> ../spa (the Vite build output). */\nfunction spaDir(): string {\n return fileURLToPath(new URL('../spa', import.meta.url));\n}\n\nconst CONTENT_TYPES: Record<string, string> = {\n '.js': 'text/javascript; charset=utf-8',\n '.css': 'text/css; charset=utf-8',\n '.map': 'application/json; charset=utf-8',\n '.svg': 'image/svg+xml',\n '.json': 'application/json; charset=utf-8',\n '.woff2': 'font/woff2',\n '.ico': 'image/x-icon',\n};\n\n/**\n * Serves the bundled AI-gateway console SPA at the configured base (+ hashed assets at\n * `<base>/assets`). The path comes from `RouterModule` (set by\n * {@link AgentDashboardModule.forRoot}({ basePath })), so the controller routes are relative.\n *\n * `@UseGuards(DashboardAuthPageGuard)` is a NO-OP unless the host configured `dashboardAuth` — see\n * that guard's own doc. Host `guards` (see {@link AgentDashboardOptions.guards}) are APPENDED onto\n * this baseline by `stampGuards` (`./guards.js`), never replacing it. `@UseFilters` turns the\n * guard's redirect-on-deny into an actual 302 response; it is a no-op when the guard never throws\n * (i.e. `dashboardAuth` unconfigured).\n */\n@Controller()\n@UseGuards(DashboardAuthPageGuard)\n@UseFilters(new DashboardAuthRedirectFilter())\nexport class AgentUiController {\n private readonly dir = spaDir();\n\n constructor(\n @Inject(DASHBOARD_BASE_PATH) private readonly basePath: string,\n @Inject(DASHBOARD_API_PATH) private readonly apiBasePath: string,\n ) {}\n\n // index.html references hash-named bundles, so it MUST NOT be cached (stale bundle = the classic\n // \"stuck loading after a deploy\"). The hashed assets below are immutable.\n @Get()\n @Header('Content-Type', 'text/html; charset=utf-8')\n @Header('Cache-Control', 'no-store, must-revalidate')\n index(@Req() req: UiPageRequest, @Res({ passthrough: true }) res: UiPageResponse): string {\n // An Inertia <Link> in the host app visits this page route as an XHR expecting an Inertia JSON\n // page object; serving the SPA's HTML instead lands it in the Inertia client's `srcdoc` error\n // modal, where the page's relative asset URLs die on CORS (origin null). The protocol's own\n // escape hatch for \"this URL is not an Inertia page\" is a 409 Conflict carrying\n // `X-Inertia-Location`: the client responds with a full `window.location` visit, which renders\n // the console normally. Only HTML page routes need this — asset binaries are never fetched with\n // the header.\n if (req.headers['x-inertia'] !== undefined) {\n res.status(409);\n res.setHeader('X-Inertia-Location', req.originalUrl);\n return '';\n }\n const indexPath = join(this.dir, 'index.html');\n if (!existsSync(indexPath)) {\n throw new NotFoundException('Dashboard is not built. Run the package build.');\n }\n // The bundle was built with Vite base `/ai-gateway/`; rewrite asset URLs to the configured base\n // so the SPA loads from `<base>/assets` wherever it's mounted, and tell the client its API base.\n const html = readFileSync(indexPath, 'utf8').replaceAll(\n `=\"${BUILD_BASE}/`,\n `=\"${this.basePath}/`,\n );\n // __AGENT_BASE__ = where assets load; __AGENT_API__ = where the SPA fetches the JSON API.\n const inject = `<script>window.__AGENT_BASE__='${this.basePath}';window.__AGENT_API__='${this.apiBasePath}';</script>`;\n return html.includes('</head>') ? html.replace('</head>', `${inject}</head>`) : inject + html;\n }\n\n @Get('assets/:file')\n @Header('Cache-Control', 'public, max-age=31536000, immutable')\n asset(@Param('file') file: string): StreamableFile {\n const safe = basename(file);\n if (safe !== file) throw new NotFoundException();\n const root = resolve(this.dir, 'assets');\n const assetPath = resolve(root, safe);\n if (!assetPath.startsWith(root + sep) || !existsSync(assetPath)) {\n throw new NotFoundException();\n }\n const type = CONTENT_TYPES[extname(safe)] ?? 'application/octet-stream';\n return new StreamableFile(readFileSync(assetPath), { type });\n }\n}\n","// packages/dashboard/src/server/auth/dashboard-auth-page.guard.ts\nimport {\n type CanActivate,\n type ExecutionContext,\n Inject,\n Injectable,\n Optional,\n} from '@nestjs/common';\nimport { DASHBOARD_AUTH, DASHBOARD_BASE_PATH } from '../tokens.js';\nimport { parseCookieHeader } from './cookie-header.js';\nimport type { ResolvedDashboardAuth } from './dashboard-auth-config.js';\nimport { DashboardAuthRedirect } from './dashboard-auth-redirect.js';\nimport { attachSession, readCookieHeader, readOriginalUrl } from './http-request.js';\nimport { SESSION_COOKIE_NAME, maybeRenewSession } from './session-cookie-io.js';\nimport { type DashboardSession, verifySessionCookie } from './session-cookie.js';\n\n/**\n * Gates `AgentUiController` (the page + its assets) on a valid session cookie — but ONLY when the\n * host configured `dashboardAuth`. With no `dashboardAuth` the resolved value is `null` and the\n * guard is a no-op (today's behavior byte-for-byte). Composes with host `guards` via\n * `stampGuards`'s APPEND semantics (see `../guards.ts`) — AND semantics, both gates must pass.\n *\n * Unlike {@link DashboardAuthGuard} (the API's `401`), a denied PAGE navigation throws\n * {@link DashboardAuthRedirect} — a browser hitting a protected URL gets bounced to the built-in\n * login screen (with a `returnTo` back to the page it came from) rather than a bare JSON 401.\n */\n@Injectable()\nexport class DashboardAuthPageGuard implements CanActivate {\n constructor(\n @Optional() @Inject(DASHBOARD_AUTH) private readonly auth: ResolvedDashboardAuth | null,\n @Inject(DASHBOARD_BASE_PATH) private readonly basePath: string,\n ) {}\n\n async canActivate(context: ExecutionContext): Promise<boolean> {\n if (!this.auth) return true;\n const auth = this.auth;\n const http = context.switchToHttp();\n const request = http.getRequest<unknown>();\n const session = this.verifyRequestSession(request);\n if (!session) this.deny(auth, request);\n attachSession(request, session);\n if (!(await maybeRenewSession(auth, session, request, http.getResponse<unknown>()))) {\n // Revoked mid-session: same Mode-aware treatment as an absent cookie.\n this.deny(auth, request);\n }\n return true;\n }\n\n /**\n * No valid session: bounce the browser navigation onward. Under Mode B (`login` configured)\n * that's the built-in login screen, carrying `returnTo` back to the page that was requested.\n * Under Mode-A-only there is no login screen to redirect to — the host mints the session\n * itself — so this instead serves the instruction page rather than redirecting into a 404.\n * Pulled out of `canActivate` so both the \"no session at all\" and the \"revalidate revoked a\n * renewable session\" paths reuse the exact same mode-aware deny logic.\n *\n * Takes `auth` explicitly rather than reading `this.auth`: `canActivate`'s `!this.auth` early\n * return narrows the field within that method only — TS doesn't carry it across a method call —\n * so the caller passes the already-narrowed value instead of this method re-deriving (or\n * asserting away) the nullability.\n */\n private deny(auth: ResolvedDashboardAuth, request: unknown): never {\n if (!auth.modes.includes('login')) {\n throw new DashboardAuthRedirect(`${this.basePath}/auth/session-required`);\n }\n const returnTo = readOriginalUrl(request);\n throw new DashboardAuthRedirect(\n `${this.basePath}/auth/login?returnTo=${encodeURIComponent(returnTo)}`,\n );\n }\n\n private verifyRequestSession(request: unknown): DashboardSession | null {\n const auth = this.auth;\n if (!auth) return null;\n const cookieValue = parseCookieHeader(readCookieHeader(request))[SESSION_COOKIE_NAME];\n if (cookieValue === undefined) return null;\n return verifySessionCookie(cookieValue, { secret: auth.secret });\n }\n}\n","// packages/dashboard/src/server/auth/dashboard-auth-redirect.ts\nimport { type ArgumentsHost, Catch, type ExceptionFilter, Injectable } from '@nestjs/common';\nimport { sendRedirect } from './response.js';\n\n/**\n * Thrown by `DashboardAuthPageGuard` to deny an unauthenticated PAGE navigation with a 302 to the\n * login screen — deliberately NOT an `HttpException`: a guard returning `false` makes Nest send\n * its OWN 403 body, which would clobber a `Location` header set beforehand. Throwing this instead\n * routes through `DashboardAuthRedirectFilter` below, which owns the entire response.\n */\nexport class DashboardAuthRedirect extends Error {\n constructor(readonly redirectTo: string) {\n super(`Redirect to ${redirectTo}`);\n this.name = 'DashboardAuthRedirect';\n }\n}\n\n/**\n * Turns a thrown {@link DashboardAuthRedirect} into an actual 302 response. Scoped directly onto\n * `AgentUiController` via `@UseFilters(new DashboardAuthRedirectFilter())` — harmless when\n * `dashboardAuth` is unconfigured, since the guard never throws this exception in that case.\n */\n@Injectable()\n@Catch(DashboardAuthRedirect)\nexport class DashboardAuthRedirectFilter implements ExceptionFilter<DashboardAuthRedirect> {\n catch(exception: DashboardAuthRedirect, host: ArgumentsHost): void {\n const response = host.switchToHttp().getResponse<unknown>();\n sendRedirect(response, 302, exception.redirectTo);\n }\n}\n","// packages/dashboard/src/server/auth/dashboard-auth-config.ts\nimport type { DashboardSessionUser } from './session-cookie.js';\n\n/**\n * Host hook for Mode A: validates the host's own auth on the raw request POSTed to\n * `<basePath>/auth/session`. Return the session user to grant access, or `null` to deny.\n */\nexport type SessionHook = (\n request: unknown,\n) => Promise<DashboardSessionUser | null> | DashboardSessionUser | null;\n\n/** Host hook for Mode B: validates submitted credentials for the built-in login screen. */\nexport type LoginHook = (\n username: string,\n password: string,\n) => Promise<DashboardSessionUser | null> | DashboardSessionUser | null;\n\n/**\n * Re-checks a LIVE session when the cookie is slid forward. Runs at most once per `ttl/2` PER\n * COOKIE GENERATION — the gate is on the inbound cookie's `iat`, so every request still carrying a\n * not-yet-renewed cookie would, on its own, invoke this hook again; `maybeRenewSession` de-dupes\n * concurrent calls for the same session (`sub` + `iat`) into a single in-flight call, so a page\n * load that fires N parallel API calls still costs exactly ONE host round-trip, not N. Still not a\n * per-session-lifetime cap — this can run again every `ttl/2` as the session keeps sliding, so a\n * slow or expensive check should still debounce/cache across renewal windows on its own. Return\n * `false` to revoke (the cookie is cleared and the request denied). Distinct from `session`: that\n * hook reads the host's auth off a fresh request, which a console XHR does not carry — this one\n * receives the already-minted session.\n */\nexport type RevalidateHook = (session: DashboardSessionUser) => Promise<boolean> | boolean;\n\n/** Which `dashboardAuth` hook(s) a resolved config was given — see `resolveDashboardAuth`. */\nexport type AuthMode = 'session' | 'login';\n\n/**\n * Author-facing `dashboardAuth` option (see `AgentDashboardOptions.dashboardAuth`). Gates the\n * console (SPA + API) behind a signed session cookie. Two ways to mint that cookie: Mode A\n * (`session`) — the host frontend, already carrying its own auth, POSTs to\n * `<basePath>/auth/session` and the host hook decides — or Mode B (`login`) — the built-in,\n * dependency-free server-rendered login screen. Mirrors `@dudousxd/nestjs-telescope`'s\n * `dashboardAuth` two-mode shape. At least one of the two is required so an un-mintable gate is a\n * boot error, not a silently-open (or silently-stuck) console.\n */\nexport interface DashboardAuthOptions {\n /** REQUIRED HMAC-SHA256 signing key (32+ bytes recommended). Missing/empty => boot error (fail closed). */\n secret: string;\n /** Cookie TTL (duration string `'<n><ms|s|m|h|d>'`, e.g. `'30m'`, `'7d'`). Default `'8h'`. */\n ttl?: string;\n /**\n * Mode A: validates the host's own auth on the raw request POSTed to `<basePath>/auth/session`.\n * Return the session user to grant access, or `null` to deny. No credential reaches this\n * library — the host decides from whatever it already has (a header, an SSO cookie, ...).\n */\n session?: SessionHook;\n /**\n * Mode B: validates submitted username/password. Return the session user to grant access, or\n * `null` to deny — the login endpoint responds identically (generic failure) for an unknown user\n * and a wrong password, so it never reveals which one was wrong.\n */\n login?: LoginHook;\n /** Re-checks a live session on sliding renewal; see `RevalidateHook`. Not a mode — it cannot\n * mint a session, only revoke one already minted by `session`/`login`. */\n revalidate?: RevalidateHook;\n}\n\n/** Resolved, validated `dashboardAuth` config used by the guards/controller. */\nexport interface ResolvedDashboardAuth {\n secret: string;\n ttlMs: number;\n modes: AuthMode[];\n session?: SessionHook;\n login?: LoginHook;\n revalidate?: RevalidateHook;\n}\n\nconst DEFAULT_TTL = '8h';\n\n/** Unit → ms multiplier. A `switch` (not an indexed lookup) so no cast is needed to narrow the regex capture. */\nfunction unitToMs(unit: string): number | undefined {\n switch (unit) {\n case 'ms':\n return 1;\n case 's':\n return 1_000;\n case 'm':\n return 60_000;\n case 'h':\n return 3_600_000;\n case 'd':\n return 86_400_000;\n default:\n return undefined;\n }\n}\n\n/** Convert a `'<int><ms|s|m|h|d>'` duration string to ms. Throws on an unparseable string. */\nfunction durationToMs(duration: string): number {\n const match = /^(\\d+)(ms|s|m|h|d)$/.exec(duration.trim());\n const amount = match?.[1];\n const unit = match?.[2] !== undefined ? unitToMs(match[2]) : undefined;\n if (amount === undefined || unit === undefined) {\n throw new Error(`AgentDashboardModule dashboardAuth: invalid \\`ttl\\` duration: ${duration}`);\n }\n return Number(amount) * unit;\n}\n\n/**\n * Validate + resolve `dashboardAuth`. Returns `null` when unconfigured (behavior unchanged — the\n * console stays open, front it with your own `guards`). Throws at boot (fail closed) when\n * configured but missing a secret, when neither `session` nor `login` is given, or when a given\n * hook isn't a function — the host learns immediately rather than shipping an open, stuck, or\n * un-mintable console.\n */\nexport function resolveDashboardAuth(\n options: DashboardAuthOptions | undefined,\n): ResolvedDashboardAuth | null {\n if (options === undefined) return null;\n if (typeof options.secret !== 'string' || options.secret === '') {\n throw new Error(\n 'AgentDashboardModule dashboardAuth: `secret` is required and must be a non-empty string ' +\n '(HMAC-SHA256 signing key, 32+ bytes recommended). Failing closed.',\n );\n }\n const modes: AuthMode[] = [];\n if (options.session !== undefined) {\n if (typeof options.session !== 'function') {\n throw new Error('AgentDashboardModule dashboardAuth: `session` must be a function.');\n }\n modes.push('session');\n }\n if (options.login !== undefined) {\n if (typeof options.login !== 'function') {\n throw new Error('AgentDashboardModule dashboardAuth: `login` must be a function.');\n }\n modes.push('login');\n }\n if (options.revalidate !== undefined && typeof options.revalidate !== 'function') {\n throw new Error('AgentDashboardModule dashboardAuth: `revalidate` must be a function.');\n }\n if (modes.length === 0) {\n throw new Error(\n 'AgentDashboardModule dashboardAuth: at least one of `session` (the host mints the session ' +\n 'from its own auth) or `login` (the built-in login screen) is required — otherwise the ' +\n 'cookie can never be minted. Failing closed.',\n );\n }\n return {\n secret: options.secret,\n ttlMs: durationToMs(options.ttl ?? DEFAULT_TTL),\n modes,\n ...(options.session !== undefined ? { session: options.session } : {}),\n ...(options.login !== undefined ? { login: options.login } : {}),\n ...(options.revalidate !== undefined ? { revalidate: options.revalidate } : {}),\n };\n}\n","import 'reflect-metadata';\nimport type { CanActivate, Type } from '@nestjs/common';\n\n/**\n * `@nestjs/common`'s own `GUARDS_METADATA` key, INLINED rather than deep-imported from\n * '@nestjs/common/constants' — that subpath has no extension and a strict ESM resolver (which the\n * built dual ESM/CJS output of this package is loaded under) 404s on it. Same convention as\n * `@dudousxd/nestjs-telescope`'s `telescope.module.ts` and `@dudousxd/nestjs-media`'s `guards.ts`.\n * A drift spec asserts this literal stays byte-identical to the real constant.\n */\nexport const GUARDS_METADATA = '__guards__';\n\n/**\n * Narrows a `guards` entry to a class (constructor) as opposed to an already-instantiated\n * `CanActivate`. Only a class needs a DI provider so Nest can instantiate it — an instance is used\n * by the guards consumer as-is.\n */\nexport function isGuardClass(guard: Type<CanActivate> | CanActivate): guard is Type<CanActivate> {\n return typeof guard === 'function';\n}\n\n/**\n * Reads a controller's OWN `@UseGuards` metadata (not the inherited/prototype-chain one), or `[]`\n * when it carries none. MUST be called at module-load time — i.e. assigned to a top-level `const`\n * right after importing the controller — so it captures the pristine, decorator-defined baseline\n * BEFORE any `stampGuards` call below can mutate it.\n */\nexport function baseGuards(controller: Type): Array<Type<CanActivate> | CanActivate> {\n return Reflect.getOwnMetadata(GUARDS_METADATA, controller) ?? [];\n}\n\n/**\n * Stamp host guards onto a dashboard controller — ALWAYS recomputed as exactly\n * `[...base, ...(guards ?? [])]`, its captured `base` (the controller's own pristine `@UseGuards`\n * metadata, or `[]` when it has none), never appended onto whatever is CURRENTLY stamped on the\n * class. `AgentApiController`/`AgentUiController` are static classes reused across every\n * `forRoot`/`forRootAsync` call (unlike `@dudousxd/nestjs-telescope`'s `dynamicController`\n * subclass-per-call pattern), so this call is fully deterministic from `base` + the CURRENT call's\n * own `guards` — independent of whatever a prior call (in the same process, e.g. two tests in one\n * file) stamped, and never compounding across repeated calls.\n *\n * Omitting `guards` resets the controller to exactly `base` — the built-in gate alone (a no-op\n * when `dashboardAuth` is also unconfigured), reproducing today's behavior byte-for-byte.\n */\nexport function stampGuards(\n guards: Array<Type<CanActivate> | CanActivate> | undefined,\n entries: Array<[controller: Type, base: Array<Type<CanActivate> | CanActivate>]>,\n): void {\n for (const [controller, base] of entries) {\n Reflect.defineMetadata(GUARDS_METADATA, [...base, ...(guards ?? [])], controller);\n }\n}\n"],"mappings":"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","names":["Body","Controller","Get","HttpCode","Inject","Optional","Param","Post","Query","Req","Sse","UseGuards","Inject","Injectable","Optional","UnauthorizedException","AGENT_GOVERNANCE_QUERIES","Symbol","for","AGENT_ACTOR_DIRECTORY","AGENT_PRICING_STORE","AGENT_APPROVAL_PORT","AGENT_ACTOR_RESOLVER","DASHBOARD_BASE_PATH","DASHBOARD_API_PATH","DASHBOARD_APPROVAL_ACTOR_REF","DASHBOARD_AUTH","parseCookieHeader","header","cookies","segment","split","eq","indexOf","name","slice","trim","rawValue","length","startsWith","endsWith","decodeURIComponent","serializeSetCookie","value","options","parts","clear","encodeURIComponent","push","path","secure","maxAgeSeconds","join","isRecord","value","readHeaders","request","headers","headerToString","Array","isArray","every","part","join","undefined","readCookieHeader","cookie","isHttpsRequest","protocol","forwarded","split","map","proto","trim","toLowerCase","includes","readOriginalUrl","originalUrl","url","attachSession","session","target","dashboardSession","isRecord","value","isHeaderCapableResponse","getHeader","setHeader","resolveHeaderCapableResponse","response","raw","existingSetCookies","current","Array","isArray","appendSetCookie","cookie","isRawNodeResponse","end","resolveRawResponse","sendRedirect","status","location","statusCode","createHmac","timingSafeEqual","EXP_GRACE_MS","base64urlEncode","value","Buffer","from","toString","base64urlDecode","hmac","secret","payload","createHmac","update","digest","signSessionCookie","user","options","issuedAt","now","Date","session","sub","id","name","undefined","roles","iat","exp","ttlMs","encodedPayload","JSON","stringify","signature","isSessionPayload","record","Array","isArray","every","role","Number","isFinite","verifySessionCookie","dot","indexOf","length","slice","providedSignature","expectedSignature","provided","expected","timingSafeEqual","decoded","parse","SESSION_COOKIE_NAME","COOKIE_PATH","issueSessionCookie","user","context","value","signSessionCookie","secret","auth","ttlMs","now","undefined","cookie","serializeSetCookie","path","maxAgeSeconds","Math","floor","secure","isHttpsRequest","request","appendSetCookie","response","clearSessionCookie","clear","inFlightRevalidations","Map","revalidationKey","session","sub","iat","getOrStartRevalidation","hook","revalidate","Promise","resolve","key","inFlight","get","outcome","finally","delete","set","maybeRenewSession","Date","id","name","roles","allowed","DashboardAuthGuard","auth","canActivate","context","http","switchToHttp","request","getRequest","session","verifyRequestSession","UnauthorizedException","attachSession","maybeRenewSession","getResponse","cookieValue","parseCookieHeader","readCookieHeader","SESSION_COOKIE_NAME","undefined","verifySessionCookie","secret","subscribe","unsubscribe","channelName","Inject","Injectable","NotImplementedException","Optional","Observable","BadRequestException","parseApprovalDecision","body","BadRequestException","approved","reason","undefined","BadRequestException","parsePriceInput","body","BadRequestException","modelId","inputPricePer1m","outputPricePer1m","cacheWritePricePer1m","cacheReadPricePer1m","trim","length","isFiniteNonNegative","undefined","value","Number","isFinite","PRICING_STORE_UNBOUND_MESSAGE","APPROVAL_PORT_UNBOUND_MESSAGE","AGENT_EVENTS","isAgentEnvelope","message","event","DashboardService","queries","actorDirectory","pricingStore","approvalPort","spend","range","byModel","byActorRaw","trend","Promise","all","spendByModel","spendByActor","usageTrend","byActor","withActorLabels","topThreads","limit","rows","spendByThread","reliability","metrics","byAgent","errors","runMetrics","runsByAgent","runErrors","runTrend","recentRuns","recentToolCalls","toolCallsPage","query","runsPage","pendingApprovals","toolStats","decideApproval","toolCallId","body","executedByRef","undefined","NotImplementedException","decision","parseApprovalDecision","opts","approved","approve","reject","reason","recentThreads","threadsPage","page","length","map","row","actorLabel","refs","Set","actorRef","resolved","resolveDisplay","listPrices","listCurrentPrices","upsertPrice","upsertModelPrice","parsePriceInput","streamEvents","Observable","subscriber","bindings","name","channelName","handler","next","data","ts","Date","now","payload","subscribe","binding","unsubscribe","BadRequestException","ISO_DAY","parsePageNumber","value","parsed","undefined","Number","NaN","parseInt","isFinite","assertKnownFields","raw","allowed","field","Object","keys","includes","BadRequestException","assertDayFields","dayFields","test","DAY_FIELDS","TOOL_CALL_WHERE_FIELDS","parseToolCallWhere","length","toolName","toolType","status","threadId","fromDay","toDay","THREAD_WHERE_FIELDS","parseThreadWhere","actorRef","title","RUN_WHERE_FIELDS","parseRunWhere","agentName","errorCode","DAY_MS","ISO_DAY","utcDay","daysAgo","Date","now","toISOString","slice","dayOr","value","fallback","undefined","test","resolveRange","from","to","fromDay","toDay","parseLimit","parsed","Number","NaN","parseInt","isFinite","Math","max","min","AgentApiController","dashboard","approvalActorRef","actorResolver","spend","topThreads","limit","reliability","runs","recentRuns","runsPage","page","where","parsedWhere","parseRunWhere","parsePageNumber","pageSize","toolCalls","recentToolCalls","toolCallsPage","parseToolCallWhere","approvals","pendingApprovals","decideApproval","toolCallId","body","req","deciderRef","resolve","id","tools","toolStats","threads","recentThreads","threadsPage","parseThreadWhere","listPrices","upsertPrice","stream","streamEvents","Body","Controller","Get","Header","HttpCode","Inject","Logger","NotFoundException","Optional","Post","Query","Req","Res","UnauthorizedException","escapeHtml","value","replace","pageShell","content","title","renderLoginPage","options","action","actionUrl","returnTo","errorBanner","error","renderSessionRequiredPage","sanitizeReturnTo","basePath","returnTo","startsWith","isNonEmptyString","value","trim","isString","AgentDashboardAuthController","logger","Logger","name","warnedHooks","Set","auth","basePath","loginPage","req","res","returnToQuery","error","requireAuth","modes","includes","NotFoundException","returnTo","sanitizeReturnTo","currentSession","status","setHeader","renderLoginPage","actionUrl","undefined","login","body","username","password","redirectToLoginError","user","runHook","issueSessionCookie","request","response","logout","clearSessionCookie","session","UnauthorizedException","sessionRequiredPage","renderSessionRequiredPage","encodeURIComponent","cookieValue","parseCookieHeader","readCookieHeader","SESSION_COOKIE_NAME","verifySessionCookie","secret","kind","run","has","add","warn","Error","message","String","passthrough","normalizeDashboardPath","path","replace","unprefixed","path","replace","agentDashboardMountPaths","options","basePath","normalizeDashboardPath","apiBasePath","base","api","Module","RouterModule","existsSync","readFileSync","basename","extname","join","resolve","sep","fileURLToPath","Controller","Get","Header","Inject","NotFoundException","Param","Req","Res","StreamableFile","UseFilters","UseGuards","Inject","Injectable","Optional","Catch","Injectable","DashboardAuthRedirect","Error","redirectTo","name","DashboardAuthRedirectFilter","catch","exception","host","response","switchToHttp","getResponse","sendRedirect","DashboardAuthPageGuard","auth","basePath","canActivate","context","http","switchToHttp","request","getRequest","session","verifyRequestSession","deny","attachSession","maybeRenewSession","getResponse","modes","includes","DashboardAuthRedirect","returnTo","readOriginalUrl","encodeURIComponent","cookieValue","parseCookieHeader","readCookieHeader","SESSION_COOKIE_NAME","undefined","verifySessionCookie","secret","BUILD_BASE","spaDir","fileURLToPath","URL","url","CONTENT_TYPES","AgentUiController","dir","basePath","apiBasePath","index","req","res","headers","undefined","status","setHeader","originalUrl","indexPath","join","existsSync","NotFoundException","html","readFileSync","replaceAll","inject","includes","replace","asset","file","safe","basename","root","resolve","assetPath","startsWith","sep","type","extname","StreamableFile","passthrough","DashboardAuthRedirectFilter","DEFAULT_TTL","unitToMs","unit","undefined","durationToMs","duration","match","exec","trim","amount","Error","Number","resolveDashboardAuth","options","secret","modes","session","push","login","revalidate","length","ttlMs","ttl","GUARDS_METADATA","isGuardClass","guard","baseGuards","controller","Reflect","getOwnMetadata","stampGuards","guards","entries","base","defineMetadata","API_BASE_GUARDS","baseGuards","AgentApiController","UI_BASE_GUARDS","AgentUiController","normalize","path","normalizeDashboardPath","AgentApiModule","register","options","stampGuards","guards","module","imports","controllers","providers","DashboardService","DashboardAuthGuard","dashboardAuth","filter","isGuardClass","provide","DASHBOARD_APPROVAL_ACTOR_REF","useValue","approvalActorRef","exports","DASHBOARD_AUTH","AgentDashboardModule","forRoot","basePath","apiBasePath","dashboardAuthProvider","resolveDashboardAuth","RouterModule","AgentDashboardAuthController","DASHBOARD_BASE_PATH","DASHBOARD_API_PATH","DashboardAuthPageGuard","forRootAsync","inject","useFactory","deps","useDashboardAuth"]}