@dudousxd/nestjs-agent-dashboard 0.19.1 → 0.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -82,6 +82,8 @@ var AGENT_ACTOR_RESOLVER = /* @__PURE__ */ Symbol.for("@dudousxd/nestjs-agent:ac
82
82
  var DASHBOARD_BASE_PATH = /* @__PURE__ */ Symbol.for("@dudousxd/nestjs-agent-dashboard:base-path");
83
83
  var DASHBOARD_API_PATH = /* @__PURE__ */ Symbol.for("@dudousxd/nestjs-agent-dashboard:api-path");
84
84
  var DASHBOARD_APPROVAL_ACTOR_REF = /* @__PURE__ */ Symbol.for("@dudousxd/nestjs-agent-dashboard:approval-actor-ref");
85
+ var DASHBOARD_CONSOLE_OPTIONS = /* @__PURE__ */ Symbol.for("@dudousxd/nestjs-agent-dashboard:console-options");
86
+ var DASHBOARD_DEV_TOOLS = /* @__PURE__ */ Symbol.for("@dudousxd/nestjs-agent-dashboard:dev-tools");
85
87
 
86
88
  // src/server/dashboard.service.ts
87
89
  function _ts_decorate(decorators, target, key, desc) {
@@ -160,8 +162,8 @@ var DashboardService = class {
160
162
  };
161
163
  }
162
164
  /** Top threads by cost for a day range (default 10, highest cost first). */
163
- async topThreads(range, limit = 10) {
164
- const rows = await this.queries.spendByThread(range, limit);
165
+ async topThreads(range, limit2 = 10) {
166
+ const rows = await this.queries.spendByThread(range, limit2);
165
167
  return this.withActorLabels(rows);
166
168
  }
167
169
  /** Run reliability for a day range: metrics, by-agent/by-error breakdowns and the trend, in parallel. */
@@ -180,12 +182,12 @@ var DashboardService = class {
180
182
  };
181
183
  }
182
184
  /** Most recent runs (status/agent/duration/error) for the Reliability recent-runs table. */
183
- recentRuns(limit) {
184
- return this.queries.recentRuns(limit);
185
+ recentRuns(limit2) {
186
+ return this.queries.recentRuns(limit2);
185
187
  }
186
188
  /** Most recent tool calls (status/type/thread) for the Runs & tools activity feed. */
187
- recentToolCalls(limit) {
188
- return this.queries.recentToolCalls(limit);
189
+ recentToolCalls(limit2) {
190
+ return this.queries.recentToolCalls(limit2);
189
191
  }
190
192
  /** Paged, filterable tool calls for the Runs & tools activity table. */
191
193
  toolCallsPage(query) {
@@ -200,8 +202,8 @@ var DashboardService = class {
200
202
  * Capped and totalless — kept because the SPA still calls `GET approvals`; new callers want
201
203
  * {@link approvalsPage}, which reports how much of the backlog the cap hid.
202
204
  */
203
- pendingApprovals(limit) {
204
- return this.queries.pendingApprovals(limit);
205
+ pendingApprovals(limit2) {
206
+ return this.queries.pendingApprovals(limit2);
205
207
  }
206
208
  /** Paged, filterable approvals inbox with a total — the backlog-safe read of the HITL queue. */
207
209
  approvalsPage(query) {
@@ -289,8 +291,8 @@ var DashboardService = class {
289
291
  });
290
292
  }
291
293
  /** Most recent threads with rolled-up message/token counts. */
292
- async recentThreads(limit) {
293
- const rows = await this.queries.recentThreads(limit);
294
+ async recentThreads(limit2) {
295
+ const rows = await this.queries.recentThreads(limit2);
294
296
  return this.withActorLabels(rows);
295
297
  }
296
298
  /**
@@ -298,10 +300,10 @@ var DashboardService = class {
298
300
  * {@link recentThreads} is.
299
301
  */
300
302
  async threadsPage(query) {
301
- const page = await this.queries.threadsPage(query);
302
- const rows = await this.withActorLabels(page.rows);
303
+ const page2 = await this.queries.threadsPage(query);
304
+ const rows = await this.withActorLabels(page2.rows);
303
305
  return {
304
- ...page,
306
+ ...page2,
305
307
  rows
306
308
  };
307
309
  }
@@ -622,27 +624,27 @@ var AgentApiController = class {
622
624
  return this.dashboard.spend(resolveRange(from, to));
623
625
  }
624
626
  /** Top threads by cost (default 10, max 200) for a day range (defaults to the last 30 days). */
625
- topThreads(from, to, limit) {
626
- return this.dashboard.topThreads(resolveRange(from, to), parseLimit(limit, 10));
627
+ topThreads(from, to, limit2) {
628
+ return this.dashboard.topThreads(resolveRange(from, to), parseLimit(limit2, 10));
627
629
  }
628
630
  /** `{ metrics, byAgent, errors, trend }` for a day range (defaults to the last 30 days). */
629
631
  reliability(from, to) {
630
632
  return this.dashboard.reliability(resolveRange(from, to));
631
633
  }
632
634
  /** Most recent runs (default 50, max 200) for the Reliability recent-runs table. */
633
- runs(limit) {
634
- return this.dashboard.recentRuns(parseLimit(limit, 50));
635
+ runs(limit2) {
636
+ return this.dashboard.recentRuns(parseLimit(limit2, 50));
635
637
  }
636
638
  /**
637
639
  * Paged, filterable runs for the Reliability recent-runs table. `page` (default 1), `limit`
638
640
  * (default 25, max 200), and `where[agentName]`/`where[status]`/`where[errorCode]`/
639
641
  * `where[fromDay]`/`where[toDay]` (`YYYY-MM-DD`) — an unknown `where` field 400s naming it.
640
642
  */
641
- runsPage(page, limit, where) {
642
- const parsedWhere = parseRunWhere(where);
643
+ runsPage(page2, limit2, where2) {
644
+ const parsedWhere = parseRunWhere(where2);
643
645
  return this.dashboard.runsPage({
644
- page: parsePageNumber(page),
645
- pageSize: parseLimit(limit, 25),
646
+ page: parsePageNumber(page2),
647
+ pageSize: parseLimit(limit2, 25),
646
648
  ...parsedWhere !== void 0 ? {
647
649
  where: parsedWhere
648
650
  } : {}
@@ -659,19 +661,19 @@ var AgentApiController = class {
659
661
  return this.dashboard.runDetail(runId);
660
662
  }
661
663
  /** Most recent tool calls (default 50, max 200) for the activity feed. */
662
- toolCalls(limit) {
663
- return this.dashboard.recentToolCalls(parseLimit(limit, 50));
664
+ toolCalls(limit2) {
665
+ return this.dashboard.recentToolCalls(parseLimit(limit2, 50));
664
666
  }
665
667
  /**
666
668
  * Paged, filterable tool calls for the Runs & tools activity table. `page` (default 1), `limit`
667
669
  * (default 25, max 200), and `where[toolName]`/`where[toolType]`/`where[status]`/`where[threadId]`/
668
670
  * `where[fromDay]`/`where[toDay]` (`YYYY-MM-DD`) — an unknown `where` field 400s naming it.
669
671
  */
670
- toolCallsPage(page, limit, where) {
671
- const parsedWhere = parseToolCallWhere(where);
672
+ toolCallsPage(page2, limit2, where2) {
673
+ const parsedWhere = parseToolCallWhere(where2);
672
674
  return this.dashboard.toolCallsPage({
673
- page: parsePageNumber(page),
674
- pageSize: parseLimit(limit, 25),
675
+ page: parsePageNumber(page2),
676
+ pageSize: parseLimit(limit2, 25),
675
677
  ...parsedWhere !== void 0 ? {
676
678
  where: parsedWhere
677
679
  } : {}
@@ -683,8 +685,8 @@ var AgentApiController = class {
683
685
  * Capped with NO total, so a backlog past `limit` is invisible from the response alone. Kept
684
686
  * because the SPA still calls it; anything new should call `approvals-page` instead.
685
687
  */
686
- approvals(limit) {
687
- return this.dashboard.pendingApprovals(parseLimit(limit, 50));
688
+ approvals(limit2) {
689
+ return this.dashboard.pendingApprovals(parseLimit(limit2, 50));
688
690
  }
689
691
  /**
690
692
  * Paged, filterable approvals inbox, oldest first. `page` (default 1), `limit` (default 25, max
@@ -694,11 +696,11 @@ var AgentApiController = class {
694
696
  * Unlike `GET approvals` this reports `total`, so the console can say "showing N of M" instead of
695
697
  * silently hiding pending decisions behind a row cap.
696
698
  */
697
- approvalsPage(page, limit, where) {
698
- const parsedWhere = parseApprovalWhere(where);
699
+ approvalsPage(page2, limit2, where2) {
700
+ const parsedWhere = parseApprovalWhere(where2);
699
701
  return this.dashboard.approvalsPage({
700
- page: parsePageNumber(page),
701
- pageSize: parseLimit(limit, 25),
702
+ page: parsePageNumber(page2),
703
+ pageSize: parseLimit(limit2, 25),
702
704
  ...parsedWhere !== void 0 ? {
703
705
  where: parsedWhere
704
706
  } : {}
@@ -745,19 +747,19 @@ var AgentApiController = class {
745
747
  return this.dashboard.genuiStats(resolveRange(from, to));
746
748
  }
747
749
  /** Most recent threads (default 50, max 200) with rolled-up counts. */
748
- threads(limit) {
749
- return this.dashboard.recentThreads(parseLimit(limit, 50));
750
+ threads(limit2) {
751
+ return this.dashboard.recentThreads(parseLimit(limit2, 50));
750
752
  }
751
753
  /**
752
754
  * Paged, filterable threads for the Runs & tools threads table. `page` (default 1), `limit`
753
755
  * (default 25, max 200), and `where[actorRef]`/`where[title]` (case-insensitive substring)/
754
756
  * `where[fromDay]`/`where[toDay]` (`YYYY-MM-DD`) — an unknown `where` field 400s naming it.
755
757
  */
756
- threadsPage(page, limit, where) {
757
- const parsedWhere = parseThreadWhere(where);
758
+ threadsPage(page2, limit2, where2) {
759
+ const parsedWhere = parseThreadWhere(where2);
758
760
  return this.dashboard.threadsPage({
759
- page: parsePageNumber(page),
760
- pageSize: parseLimit(limit, 25),
761
+ page: parsePageNumber(page2),
762
+ pageSize: parseLimit(limit2, 25),
761
763
  ...parsedWhere !== void 0 ? {
762
764
  where: parsedWhere
763
765
  } : {}
@@ -1043,7 +1045,7 @@ __name(agentDashboardMountPaths, "agentDashboardMountPaths");
1043
1045
  // src/server/agent-dashboard.module.ts
1044
1046
  import { withControllerDecorators } from "@dudousxd/nestjs-agent-core";
1045
1047
  import "reflect-metadata";
1046
- import { Module } from "@nestjs/common";
1048
+ import { Module, UseFilters as UseFilters2, UseGuards as UseGuards2 } from "@nestjs/common";
1047
1049
  import { RouterModule } from "@nestjs/core";
1048
1050
 
1049
1051
  // src/server/agent-ui.controller.ts
@@ -1051,7 +1053,241 @@ import { existsSync, readFileSync } from "fs";
1051
1053
  import { basename, extname, join, resolve, sep } from "path";
1052
1054
  import { fileURLToPath } from "url";
1053
1055
  import { CanAnyone as CanAnyone2 } from "@dudousxd/nestjs-agent-core";
1054
- import { Controller as Controller2, Get as Get2, Header, Inject as Inject3, NotFoundException as NotFoundException2, Param as Param2, Req as Req2, Res, StreamableFile } from "@nestjs/common";
1056
+ import { Controller as Controller2, Get as Get2, Header, Inject as Inject4, NotFoundException as NotFoundException2, Param as Param2, Req as Req2, Res, StreamableFile } from "@nestjs/common";
1057
+
1058
+ // src/server/console-authorize.guard.ts
1059
+ import { Catch, HttpException, Inject as Inject3, Injectable as Injectable2, SetMetadata, UseFilters, UseGuards, applyDecorators } from "@nestjs/common";
1060
+ import { HttpAdapterHost, ModuleRef as ModuleRef2 } from "@nestjs/core";
1061
+
1062
+ // src/server/console-authorize.ts
1063
+ import { createHash, timingSafeEqual } from "crypto";
1064
+ import { Logger } from "@nestjs/common";
1065
+ import { ModuleRef } from "@nestjs/core";
1066
+ var CONSOLE_GATE = /* @__PURE__ */ Symbol.for("@dudousxd/aviary:console-gate");
1067
+ var PACKAGE = "@dudousxd/nestjs-agent-dashboard";
1068
+ var MODULE = "AgentDashboardModule";
1069
+ var logger = new Logger(MODULE);
1070
+ var CONSOLE_AUTHORIZE_MISSING_WARNING = `[${PACKAGE}] console has no \`authorize\` configured: it is open outside production and denies every request in production. Set \`authorize\` in ${MODULE}.forRoot().`;
1071
+ var allow = /* @__PURE__ */ __name((caller) => caller !== void 0 && caller !== "" ? {
1072
+ allowed: true,
1073
+ caller
1074
+ } : {
1075
+ allowed: true
1076
+ }, "allow");
1077
+ var forbidden = /* @__PURE__ */ __name(() => ({
1078
+ allowed: false,
1079
+ reason: "forbidden"
1080
+ }), "forbidden");
1081
+ var unauthenticated = /* @__PURE__ */ __name(() => ({
1082
+ allowed: false,
1083
+ reason: "unauthenticated"
1084
+ }), "unauthenticated");
1085
+ function isProduction() {
1086
+ return (process.env.NODE_ENV ?? "").trim().toLowerCase() === "production";
1087
+ }
1088
+ __name(isProduction, "isProduction");
1089
+ function normalizeConsoleResult(result) {
1090
+ if (result === true) return allow();
1091
+ if (result === null || typeof result !== "object") return forbidden();
1092
+ const value = result;
1093
+ if (typeof value.redirectTo === "string" && value.redirectTo !== "" && value.allowed !== true) {
1094
+ return {
1095
+ allowed: false,
1096
+ reason: "unauthenticated",
1097
+ redirectTo: value.redirectTo
1098
+ };
1099
+ }
1100
+ if (value.allowed === true) {
1101
+ return allow(typeof value.caller === "string" ? value.caller : void 0);
1102
+ }
1103
+ return value.reason === "unauthenticated" ? unauthenticated() : forbidden();
1104
+ }
1105
+ __name(normalizeConsoleResult, "normalizeConsoleResult");
1106
+ function isAuthorizerClass(value) {
1107
+ if (typeof value !== "function") return false;
1108
+ if (typeof value.prototype?.authorize === "function") {
1109
+ return true;
1110
+ }
1111
+ return /^class[\s{]/.test(Function.prototype.toString.call(value));
1112
+ }
1113
+ __name(isAuthorizerClass, "isAuthorizerClass");
1114
+ function isPolicyCheck(value) {
1115
+ return typeof value === "object" && value !== null && typeof value.policy === "function" && typeof value.ability === "string";
1116
+ }
1117
+ __name(isPolicyCheck, "isPolicyCheck");
1118
+ function lazyModuleClass(value) {
1119
+ if (typeof value !== "object" || value === null) return void 0;
1120
+ const candidate = value.default;
1121
+ return isAuthorizerClass(candidate) ? candidate : void 0;
1122
+ }
1123
+ __name(lazyModuleClass, "lazyModuleClass");
1124
+ function tryGet(moduleRef, token) {
1125
+ try {
1126
+ return moduleRef.get(token, {
1127
+ strict: false
1128
+ }) ?? void 0;
1129
+ } catch {
1130
+ return void 0;
1131
+ }
1132
+ }
1133
+ __name(tryGet, "tryGet");
1134
+ function classInstances(moduleRef) {
1135
+ const instances = /* @__PURE__ */ new Map();
1136
+ return (Class) => {
1137
+ let instance = instances.get(Class);
1138
+ if (instance === void 0) {
1139
+ const registered = tryGet(moduleRef, Class);
1140
+ instance = registered !== void 0 ? Promise.resolve(registered) : moduleRef.create(Class);
1141
+ instance.catch(() => instances.delete(Class));
1142
+ instances.set(Class, instance);
1143
+ }
1144
+ return instance;
1145
+ };
1146
+ }
1147
+ __name(classInstances, "classInstances");
1148
+ function hasUser(user) {
1149
+ return user !== null && user !== void 0 && user !== false;
1150
+ }
1151
+ __name(hasUser, "hasUser");
1152
+ async function policyDecider(check, moduleRef) {
1153
+ let mod;
1154
+ try {
1155
+ mod = await import("@nestjs/authorization");
1156
+ } catch {
1157
+ throw new Error(`${MODULE}: \`authorize: { policy, ability }\` needs @nestjs/authorization installed and AuthorizationModule imported; or pass a function, a class, or a gate name.`);
1158
+ }
1159
+ const service = tryGet(moduleRef, mod.AuthorizationService);
1160
+ if (service === void 0) {
1161
+ throw new Error(`${MODULE}: \`authorize: { policy, ability }\` needs AuthorizationModule.forRoot() from @nestjs/authorization imported in the app.`);
1162
+ }
1163
+ const options = tryGet(moduleRef, mod.AUTHORIZATION_MODULE_OPTIONS);
1164
+ const getUser = options?.getUser ?? mod.defaultGetUser;
1165
+ return async (context) => {
1166
+ const user = await getUser(context);
1167
+ const signedIn = hasUser(user);
1168
+ if (await service.can(check.policy, check.ability, signedIn ? user : null)) return allow();
1169
+ return signedIn ? forbidden() : unauthenticated();
1170
+ };
1171
+ }
1172
+ __name(policyDecider, "policyDecider");
1173
+ async function resolveConsoleAuthorize(authorize, moduleRef, options = {}) {
1174
+ const log = options.logger ?? logger;
1175
+ if (authorize === void 0) {
1176
+ if (options.warnIfMissing !== false) log.warn(CONSOLE_AUTHORIZE_MISSING_WARNING);
1177
+ const production = isProduction();
1178
+ return async () => production ? forbidden() : allow("dev-open");
1179
+ }
1180
+ const instanceOf = classInstances(moduleRef);
1181
+ let decide;
1182
+ if (typeof authorize === "string") {
1183
+ const name = authorize;
1184
+ const gate = tryGet(moduleRef, CONSOLE_GATE);
1185
+ if (gate === void 0) {
1186
+ throw new Error(`${MODULE}: \`authorize: '${name}'\` needs @dudousxd/nestjs-authz's AuthzModule (it resolves gate names); or pass a function, a class, or { policy, ability }.`);
1187
+ }
1188
+ decide = /* @__PURE__ */ __name((context) => gate.allows(name, context), "decide");
1189
+ } else if (isPolicyCheck(authorize)) {
1190
+ decide = await policyDecider(authorize, moduleRef);
1191
+ } else if (isAuthorizerClass(authorize)) {
1192
+ const Class = authorize;
1193
+ decide = /* @__PURE__ */ __name(async (context) => (await instanceOf(Class)).authorize(context), "decide");
1194
+ } else if (typeof authorize === "function") {
1195
+ const fn = authorize;
1196
+ let lazyClass;
1197
+ decide = /* @__PURE__ */ __name(async (context) => {
1198
+ if (lazyClass !== void 0) return (await instanceOf(lazyClass)).authorize(context);
1199
+ const result = await fn(context);
1200
+ const Class = lazyModuleClass(result);
1201
+ if (Class === void 0) return result;
1202
+ lazyClass = Class;
1203
+ return (await instanceOf(Class)).authorize(context);
1204
+ }, "decide");
1205
+ } else {
1206
+ throw new Error(`${MODULE}: \`authorize\` must be a function, a class with authorize(), a lazy import, { policy, ability } or a gate name.`);
1207
+ }
1208
+ return async (context) => {
1209
+ try {
1210
+ return normalizeConsoleResult(await decide(context));
1211
+ } catch (error) {
1212
+ log.warn(`console \`authorize\` threw, denying the request: ${error instanceof Error ? error.message : String(error)}`);
1213
+ return forbidden();
1214
+ }
1215
+ };
1216
+ }
1217
+ __name(resolveConsoleAuthorize, "resolveConsoleAuthorize");
1218
+ function sha256(value) {
1219
+ return createHash("sha256").update(value).digest();
1220
+ }
1221
+ __name(sha256, "sha256");
1222
+ function authorizeByToken(options) {
1223
+ const token = options.token;
1224
+ if (typeof token !== "string" || token === "") {
1225
+ throw new Error("authorizeByToken: `token` is empty; set it (e.g. from an env var).");
1226
+ }
1227
+ const expected = sha256(token);
1228
+ const caller = `token:${expected.toString("hex").slice(0, 12)}`;
1229
+ return (context) => {
1230
+ const req = context.switchToHttp().getRequest();
1231
+ const header = req?.headers?.authorization;
1232
+ const value = Array.isArray(header) ? header[0] : header;
1233
+ const match = typeof value === "string" ? /^Bearer\s+(.+)$/i.exec(value.trim()) : null;
1234
+ if (match?.[1] !== void 0 && timingSafeEqual(sha256(match[1]), expected)) {
1235
+ return {
1236
+ allowed: true,
1237
+ caller
1238
+ };
1239
+ }
1240
+ return {
1241
+ allowed: false,
1242
+ reason: "unauthenticated"
1243
+ };
1244
+ };
1245
+ }
1246
+ __name(authorizeByToken, "authorizeByToken");
1247
+ function anyAuthorize(...forms) {
1248
+ if (forms.length === 0) throw new Error("anyAuthorize: pass at least one authorize form.");
1249
+ let AnyConsoleAuthorizer = class AnyConsoleAuthorizer {
1250
+ static {
1251
+ __name(this, "AnyConsoleAuthorizer");
1252
+ }
1253
+ moduleRef;
1254
+ #decides;
1255
+ constructor(moduleRef) {
1256
+ this.moduleRef = moduleRef;
1257
+ }
1258
+ async authorize(context) {
1259
+ this.#decides ??= Promise.all(forms.map((form) => resolveConsoleAuthorize(form, this.moduleRef, {
1260
+ warnIfMissing: false
1261
+ })));
1262
+ this.#decides.catch(() => {
1263
+ this.#decides = void 0;
1264
+ });
1265
+ let unauthenticated2;
1266
+ for (const decide of await this.#decides) {
1267
+ const decision = await decide(context);
1268
+ if (decision.allowed) return decision;
1269
+ if (decision.reason === "unauthenticated") unauthenticated2 ??= decision;
1270
+ }
1271
+ if (unauthenticated2 === void 0) return {
1272
+ allowed: false,
1273
+ reason: "forbidden"
1274
+ };
1275
+ return "redirectTo" in unauthenticated2 && unauthenticated2.redirectTo !== void 0 ? {
1276
+ redirectTo: unauthenticated2.redirectTo
1277
+ } : {
1278
+ allowed: false,
1279
+ reason: "unauthenticated"
1280
+ };
1281
+ }
1282
+ };
1283
+ Reflect.defineMetadata("design:paramtypes", [
1284
+ ModuleRef
1285
+ ], AnyConsoleAuthorizer);
1286
+ return AnyConsoleAuthorizer;
1287
+ }
1288
+ __name(anyAuthorize, "anyAuthorize");
1289
+
1290
+ // src/server/console-authorize.guard.ts
1055
1291
  function _ts_decorate3(decorators, target, key, desc) {
1056
1292
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1057
1293
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
@@ -1078,6 +1314,161 @@ function _ts_param3(paramIndex, decorator) {
1078
1314
  };
1079
1315
  }
1080
1316
  __name(_ts_param3, "_ts_param");
1317
+ var DashboardConsoleAccess = class {
1318
+ static {
1319
+ __name(this, "DashboardConsoleAccess");
1320
+ }
1321
+ options;
1322
+ moduleRef;
1323
+ #decide;
1324
+ constructor(options, moduleRef) {
1325
+ this.options = options;
1326
+ this.moduleRef = moduleRef;
1327
+ }
1328
+ #resolving;
1329
+ /** Resolves `authorize` at boot: a configuration mistake fails the boot, and the warning logs once. */
1330
+ async onModuleInit() {
1331
+ this.#resolving ??= resolveConsoleAuthorize(this.options.authorize, this.moduleRef);
1332
+ this.#decide = await this.#resolving;
1333
+ }
1334
+ /** The decision for one request. */
1335
+ async check(context) {
1336
+ if (this.#decide === void 0) await this.onModuleInit();
1337
+ return this.#decide(context);
1338
+ }
1339
+ };
1340
+ DashboardConsoleAccess = _ts_decorate3([
1341
+ Injectable2(),
1342
+ _ts_param3(0, Inject3(DASHBOARD_CONSOLE_OPTIONS)),
1343
+ _ts_metadata3("design:type", Function),
1344
+ _ts_metadata3("design:paramtypes", [
1345
+ typeof DashboardConsoleOptions === "undefined" ? Object : DashboardConsoleOptions,
1346
+ typeof ModuleRef2 === "undefined" ? Object : ModuleRef2
1347
+ ])
1348
+ ], DashboardConsoleAccess);
1349
+ var CONSOLE_PAGE_METADATA = "@dudousxd/nestjs-agent-dashboard:console-page";
1350
+ var ConsolePage = /* @__PURE__ */ __name(() => SetMetadata(CONSOLE_PAGE_METADATA, true), "ConsolePage");
1351
+ var ConsoleAccessDenied = class extends HttpException {
1352
+ static {
1353
+ __name(this, "ConsoleAccessDenied");
1354
+ }
1355
+ decision;
1356
+ page;
1357
+ constructor(decision, page2) {
1358
+ super(decision.reason === "forbidden" ? {
1359
+ error: "forbidden"
1360
+ } : {
1361
+ error: "unauthenticated",
1362
+ ...decision.redirectTo !== void 0 ? {
1363
+ redirectTo: decision.redirectTo
1364
+ } : {}
1365
+ }, decision.reason === "forbidden" ? 403 : 401), this.decision = decision, this.page = page2;
1366
+ }
1367
+ };
1368
+ function isPageNavigation(context) {
1369
+ if (context.getType() !== "http") return false;
1370
+ const page2 = Reflect.getMetadata(CONSOLE_PAGE_METADATA, context.getHandler()) === true || Reflect.getMetadata(CONSOLE_PAGE_METADATA, context.getClass()) === true;
1371
+ if (!page2) return false;
1372
+ const req = context.switchToHttp().getRequest();
1373
+ if ((req.method ?? "GET").toUpperCase() !== "GET") return false;
1374
+ const accept = req.headers?.accept;
1375
+ const value = Array.isArray(accept) ? accept.join(",") : accept ?? "";
1376
+ return value.includes("text/html");
1377
+ }
1378
+ __name(isPageNavigation, "isPageNavigation");
1379
+ var ConsoleAuthorizeGuard = class {
1380
+ static {
1381
+ __name(this, "ConsoleAuthorizeGuard");
1382
+ }
1383
+ access;
1384
+ constructor(access) {
1385
+ this.access = access;
1386
+ }
1387
+ async canActivate(context) {
1388
+ const decision = await this.access.check(context);
1389
+ if (decision.allowed) return true;
1390
+ throw new ConsoleAccessDenied(decision, isPageNavigation(context));
1391
+ }
1392
+ };
1393
+ ConsoleAuthorizeGuard = _ts_decorate3([
1394
+ Injectable2(),
1395
+ _ts_metadata3("design:type", Function),
1396
+ _ts_metadata3("design:paramtypes", [
1397
+ typeof DashboardConsoleAccess === "undefined" ? Object : DashboardConsoleAccess
1398
+ ])
1399
+ ], ConsoleAuthorizeGuard);
1400
+ function deniedPage(title, message) {
1401
+ const body = "font-family:system-ui,sans-serif;display:grid;place-items:center;min-height:100vh;margin:0";
1402
+ return `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>${title}</title></head><body style="${body}"><main style="text-align:center"><h1>${title}</h1><p>${message}</p></main></body></html>`;
1403
+ }
1404
+ __name(deniedPage, "deniedPage");
1405
+ var FORBIDDEN_PAGE = deniedPage("Access denied", "You do not have access to this console.");
1406
+ var UNAUTHENTICATED_PAGE = deniedPage("Sign in required", "Sign in to open this console.");
1407
+ var ConsoleAuthorizeFilter = class {
1408
+ static {
1409
+ __name(this, "ConsoleAuthorizeFilter");
1410
+ }
1411
+ adapterHost;
1412
+ constructor(adapterHost) {
1413
+ this.adapterHost = adapterHost;
1414
+ }
1415
+ catch(exception, host) {
1416
+ const adapter = this.adapterHost.httpAdapter;
1417
+ const res = host.switchToHttp().getResponse();
1418
+ const { decision, page: page2 } = exception;
1419
+ if (page2 && decision.reason === "unauthenticated" && decision.redirectTo !== void 0) {
1420
+ adapter.redirect(res, 302, decision.redirectTo);
1421
+ return;
1422
+ }
1423
+ if (page2) {
1424
+ const forbidden2 = decision.reason === "forbidden";
1425
+ adapter.setHeader(res, "Content-Type", "text/html; charset=utf-8");
1426
+ adapter.reply(res, forbidden2 ? FORBIDDEN_PAGE : UNAUTHENTICATED_PAGE, forbidden2 ? 403 : 401);
1427
+ return;
1428
+ }
1429
+ adapter.reply(res, exception.getResponse(), exception.getStatus());
1430
+ }
1431
+ };
1432
+ ConsoleAuthorizeFilter = _ts_decorate3([
1433
+ Catch(ConsoleAccessDenied),
1434
+ Injectable2(),
1435
+ _ts_metadata3("design:type", Function),
1436
+ _ts_metadata3("design:paramtypes", [
1437
+ typeof HttpAdapterHost === "undefined" ? Object : HttpAdapterHost
1438
+ ])
1439
+ ], ConsoleAuthorizeFilter);
1440
+ function UseConsoleAuthorize() {
1441
+ return applyDecorators(UseGuards(ConsoleAuthorizeGuard), UseFilters(ConsoleAuthorizeFilter));
1442
+ }
1443
+ __name(UseConsoleAuthorize, "UseConsoleAuthorize");
1444
+
1445
+ // src/server/agent-ui.controller.ts
1446
+ function _ts_decorate4(decorators, target, key, desc) {
1447
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1448
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
1449
+ r = Reflect.decorate(decorators, target, key, desc);
1450
+ } else {
1451
+ for (var i = decorators.length - 1; i >= 0; i--) {
1452
+ if (d = decorators[i]) {
1453
+ r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
1454
+ }
1455
+ }
1456
+ }
1457
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
1458
+ }
1459
+ __name(_ts_decorate4, "_ts_decorate");
1460
+ function _ts_metadata4(metadataKey, metadataValue) {
1461
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") {
1462
+ return Reflect.metadata(metadataKey, metadataValue);
1463
+ }
1464
+ }
1465
+ __name(_ts_metadata4, "_ts_metadata");
1466
+ function _ts_param4(paramIndex, decorator) {
1467
+ return function(target, key) {
1468
+ decorator(target, key, paramIndex);
1469
+ };
1470
+ }
1471
+ __name(_ts_param4, "_ts_param");
1081
1472
  var BUILD_BASE = "/ai-gateway";
1082
1473
  function spaDir() {
1083
1474
  return fileURLToPath(new URL("../spa", import.meta.url));
@@ -1133,45 +1524,452 @@ var AgentUiController = class {
1133
1524
  });
1134
1525
  }
1135
1526
  };
1136
- _ts_decorate3([
1527
+ _ts_decorate4([
1137
1528
  Get2(),
1529
+ ConsolePage(),
1138
1530
  Header("Content-Type", "text/html; charset=utf-8"),
1139
1531
  Header("Cache-Control", "no-store, must-revalidate"),
1140
- _ts_param3(0, Req2()),
1141
- _ts_param3(1, Res({
1532
+ _ts_param4(0, Req2()),
1533
+ _ts_param4(1, Res({
1142
1534
  passthrough: true
1143
1535
  })),
1144
- _ts_metadata3("design:type", Function),
1145
- _ts_metadata3("design:paramtypes", [
1536
+ _ts_metadata4("design:type", Function),
1537
+ _ts_metadata4("design:paramtypes", [
1146
1538
  typeof UiPageRequest === "undefined" ? Object : UiPageRequest,
1147
1539
  typeof UiPageResponse === "undefined" ? Object : UiPageResponse
1148
1540
  ]),
1149
- _ts_metadata3("design:returntype", String)
1541
+ _ts_metadata4("design:returntype", String)
1150
1542
  ], AgentUiController.prototype, "index", null);
1151
- _ts_decorate3([
1543
+ _ts_decorate4([
1152
1544
  Get2("assets/:file"),
1153
1545
  Header("Cache-Control", "public, max-age=31536000, immutable"),
1154
- _ts_param3(0, Param2("file")),
1155
- _ts_metadata3("design:type", Function),
1156
- _ts_metadata3("design:paramtypes", [
1546
+ _ts_param4(0, Param2("file")),
1547
+ _ts_metadata4("design:type", Function),
1548
+ _ts_metadata4("design:paramtypes", [
1157
1549
  String
1158
1550
  ]),
1159
- _ts_metadata3("design:returntype", typeof StreamableFile === "undefined" ? Object : StreamableFile)
1551
+ _ts_metadata4("design:returntype", typeof StreamableFile === "undefined" ? Object : StreamableFile)
1160
1552
  ], AgentUiController.prototype, "asset", null);
1161
- AgentUiController = _ts_decorate3([
1553
+ AgentUiController = _ts_decorate4([
1162
1554
  CanAnyone2(),
1163
1555
  Controller2(),
1164
- _ts_param3(0, Inject3(DASHBOARD_BASE_PATH)),
1165
- _ts_param3(1, Inject3(DASHBOARD_API_PATH)),
1166
- _ts_metadata3("design:type", Function),
1167
- _ts_metadata3("design:paramtypes", [
1556
+ _ts_param4(0, Inject4(DASHBOARD_BASE_PATH)),
1557
+ _ts_param4(1, Inject4(DASHBOARD_API_PATH)),
1558
+ _ts_metadata4("design:type", Function),
1559
+ _ts_metadata4("design:paramtypes", [
1168
1560
  String,
1169
1561
  String
1170
1562
  ])
1171
1563
  ], AgentUiController);
1172
1564
 
1565
+ // src/server/dev-tools.ts
1566
+ import { Inject as Inject5, Injectable as Injectable3, Logger as Logger2, NotFoundException as NotFoundException3 } from "@nestjs/common";
1567
+ function _ts_decorate5(decorators, target, key, desc) {
1568
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1569
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
1570
+ r = Reflect.decorate(decorators, target, key, desc);
1571
+ } else {
1572
+ for (var i = decorators.length - 1; i >= 0; i--) {
1573
+ if (d = decorators[i]) {
1574
+ r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
1575
+ }
1576
+ }
1577
+ }
1578
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
1579
+ }
1580
+ __name(_ts_decorate5, "_ts_decorate");
1581
+ function _ts_metadata5(metadataKey, metadataValue) {
1582
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") {
1583
+ return Reflect.metadata(metadataKey, metadataValue);
1584
+ }
1585
+ }
1586
+ __name(_ts_metadata5, "_ts_metadata");
1587
+ function _ts_param5(paramIndex, decorator) {
1588
+ return function(target, key) {
1589
+ decorator(target, key, paramIndex);
1590
+ };
1591
+ }
1592
+ __name(_ts_param5, "_ts_param");
1593
+ var DEV_TOOLS_SLOT = /* @__PURE__ */ Symbol.for("@dudousxd/aviary:dev-tools");
1594
+ function devToolsSlot() {
1595
+ const holder = globalThis;
1596
+ const existing = holder[DEV_TOOLS_SLOT];
1597
+ if (existing !== void 0) return existing;
1598
+ const libs = [];
1599
+ const slot = {
1600
+ version: 1,
1601
+ libs,
1602
+ register(lib) {
1603
+ if (libs.some((entry) => entry.lib === lib.lib)) {
1604
+ throw new Error(`dev tools: the lib "${lib.lib}" is already registered`);
1605
+ }
1606
+ libs.push(lib);
1607
+ return () => {
1608
+ const index = libs.indexOf(lib);
1609
+ if (index !== -1) libs.splice(index, 1);
1610
+ };
1611
+ }
1612
+ };
1613
+ holder[DEV_TOOLS_SLOT] = slot;
1614
+ return slot;
1615
+ }
1616
+ __name(devToolsSlot, "devToolsSlot");
1617
+ var DEFAULT_LIMIT = 20;
1618
+ var MAX_LIMIT = 100;
1619
+ var MESSAGE_LIMIT = 50;
1620
+ var MESSAGE_BYTES = 2 * 1024;
1621
+ var DAY_MS2 = 864e5;
1622
+ var ISO_DAY3 = /^\d{4}-\d{2}-\d{2}$/;
1623
+ var day = {
1624
+ type: "string",
1625
+ description: "UTC day, YYYY-MM-DD."
1626
+ };
1627
+ var page = {
1628
+ type: "integer",
1629
+ description: "Page, from 1. Pass the `next` of the previous page.",
1630
+ minimum: 1
1631
+ };
1632
+ var limit = {
1633
+ type: "integer",
1634
+ description: `Rows per page (default ${DEFAULT_LIMIT}, max ${MAX_LIMIT}).`,
1635
+ minimum: 1,
1636
+ maximum: MAX_LIMIT
1637
+ };
1638
+ var str = /* @__PURE__ */ __name((value) => typeof value === "string" && value !== "" ? value : void 0, "str");
1639
+ function dayOf(input, key) {
1640
+ const value = str(input[key]);
1641
+ if (value !== void 0 && !ISO_DAY3.test(value)) throw new Error(`"${key}" must be YYYY-MM-DD`);
1642
+ return value;
1643
+ }
1644
+ __name(dayOf, "dayOf");
1645
+ function rangeOf(input) {
1646
+ const utcDay2 = /* @__PURE__ */ __name((daysAgo) => new Date(Date.now() - daysAgo * DAY_MS2).toISOString().slice(0, 10), "utcDay");
1647
+ return {
1648
+ fromDay: dayOf(input, "from") ?? utcDay2(29),
1649
+ toDay: dayOf(input, "to") ?? utcDay2(0)
1650
+ };
1651
+ }
1652
+ __name(rangeOf, "rangeOf");
1653
+ function pageOf(input) {
1654
+ return {
1655
+ page: typeof input.page === "number" ? input.page : 1,
1656
+ pageSize: typeof input.limit === "number" ? input.limit : DEFAULT_LIMIT
1657
+ };
1658
+ }
1659
+ __name(pageOf, "pageOf");
1660
+ function where(fields) {
1661
+ const set = Object.fromEntries(Object.entries(fields).filter(([, v]) => v !== void 0));
1662
+ return Object.keys(set).length > 0 ? {
1663
+ where: set
1664
+ } : {};
1665
+ }
1666
+ __name(where, "where");
1667
+ function listOf(result) {
1668
+ return {
1669
+ items: result.rows,
1670
+ next: result.page * result.pageSize < result.total ? result.page + 1 : null,
1671
+ total: result.total
1672
+ };
1673
+ }
1674
+ __name(listOf, "listOf");
1675
+ function cut(text, maxBytes) {
1676
+ const size = Buffer.byteLength(text, "utf8");
1677
+ if (size <= maxBytes) return text;
1678
+ const suffix = `\u2026(truncated, ${size} bytes)`;
1679
+ const keep = Math.max(0, maxBytes - Buffer.byteLength(suffix, "utf8"));
1680
+ let head = Buffer.from(text, "utf8").subarray(0, keep).toString("utf8");
1681
+ if (head.endsWith("\uFFFD")) head = head.slice(0, -1);
1682
+ return `${head}${suffix}`;
1683
+ }
1684
+ __name(cut, "cut");
1685
+ function agentDevTools(dashboard) {
1686
+ return [
1687
+ {
1688
+ name: "agent_list_runs",
1689
+ kind: "read",
1690
+ description: "Agent runs, newest first: agent, actor, status, duration, cost, error. Filters: agent, status, errorCode, threadId, from/to (UTC days). Returns { items, next, total }; pass next as page.",
1691
+ input: {
1692
+ type: "object",
1693
+ properties: {
1694
+ agent: {
1695
+ type: "string",
1696
+ description: "Agent name."
1697
+ },
1698
+ status: {
1699
+ type: "string",
1700
+ enum: [
1701
+ "running",
1702
+ "completed",
1703
+ "failed",
1704
+ "cancelled"
1705
+ ]
1706
+ },
1707
+ errorCode: {
1708
+ type: "string"
1709
+ },
1710
+ threadId: {
1711
+ type: "string"
1712
+ },
1713
+ from: day,
1714
+ to: day,
1715
+ page,
1716
+ limit
1717
+ }
1718
+ },
1719
+ async run(input) {
1720
+ return listOf(await dashboard.runsPage({
1721
+ ...pageOf(input),
1722
+ ...where({
1723
+ agentName: str(input.agent),
1724
+ status: str(input.status),
1725
+ errorCode: str(input.errorCode),
1726
+ threadId: str(input.threadId),
1727
+ fromDay: dayOf(input, "from"),
1728
+ toDay: dayOf(input, "to")
1729
+ })
1730
+ }));
1731
+ }
1732
+ },
1733
+ {
1734
+ name: "agent_get_run",
1735
+ kind: "read",
1736
+ description: "One run: the run, its thread, its tool calls and timings. With includeMessages: true, the thread's latest 50 messages, each cut to 2 KB. An error when no run has that id.",
1737
+ input: {
1738
+ type: "object",
1739
+ properties: {
1740
+ id: {
1741
+ type: "string",
1742
+ description: "Run id."
1743
+ },
1744
+ includeMessages: {
1745
+ type: "boolean",
1746
+ description: "Include messages (default false)."
1747
+ }
1748
+ },
1749
+ required: [
1750
+ "id"
1751
+ ]
1752
+ },
1753
+ async run(input) {
1754
+ let detail;
1755
+ try {
1756
+ detail = await dashboard.runDetail(String(input.id));
1757
+ } catch (error) {
1758
+ if (error instanceof NotFoundException3) throw new Error(`run ${String(input.id)} not found`);
1759
+ throw error;
1760
+ }
1761
+ if (input.includeMessages !== true) return detail;
1762
+ const thread = await dashboard.threadDetail({
1763
+ threadId: detail.run.threadId,
1764
+ messageLimit: MESSAGE_LIMIT,
1765
+ runLimit: 1
1766
+ });
1767
+ return {
1768
+ ...detail,
1769
+ messages: thread.messages.map((message) => ({
1770
+ ...message,
1771
+ content: cut(message.content, MESSAGE_BYTES)
1772
+ }))
1773
+ };
1774
+ }
1775
+ },
1776
+ {
1777
+ name: "agent_get_spend",
1778
+ kind: "read",
1779
+ description: "Tokens and cost over UTC days from/to (default the last 30 days): by model, by actor, by feature and the daily trend. `by` returns one of them.",
1780
+ input: {
1781
+ type: "object",
1782
+ properties: {
1783
+ by: {
1784
+ type: "string",
1785
+ enum: [
1786
+ "model",
1787
+ "actor",
1788
+ "feature"
1789
+ ]
1790
+ },
1791
+ from: day,
1792
+ to: day
1793
+ }
1794
+ },
1795
+ async run(input) {
1796
+ const spend = await dashboard.spend(rangeOf(input));
1797
+ switch (input.by) {
1798
+ case "model":
1799
+ return {
1800
+ items: spend.byModel
1801
+ };
1802
+ case "actor":
1803
+ return {
1804
+ items: spend.byActor
1805
+ };
1806
+ case "feature":
1807
+ return {
1808
+ items: spend.byFeature ?? []
1809
+ };
1810
+ default:
1811
+ return spend;
1812
+ }
1813
+ }
1814
+ },
1815
+ {
1816
+ name: "agent_get_tool_stats",
1817
+ kind: "read",
1818
+ description: "Per tool: calls, failures, rejections and latency, over UTC days from/to (default the last 30 days).",
1819
+ input: {
1820
+ type: "object",
1821
+ properties: {
1822
+ from: day,
1823
+ to: day
1824
+ }
1825
+ },
1826
+ async run(input) {
1827
+ return {
1828
+ items: await dashboard.toolStats(rangeOf(input))
1829
+ };
1830
+ }
1831
+ },
1832
+ {
1833
+ name: "agent_get_reliability",
1834
+ kind: "read",
1835
+ description: "Run metrics, by-agent and by-error breakdowns and the trend, over UTC days from/to (default the last 30 days).",
1836
+ input: {
1837
+ type: "object",
1838
+ properties: {
1839
+ from: day,
1840
+ to: day
1841
+ }
1842
+ },
1843
+ async run(input) {
1844
+ return dashboard.reliability(rangeOf(input));
1845
+ }
1846
+ },
1847
+ {
1848
+ name: "agent_list_pending_approvals",
1849
+ kind: "read",
1850
+ description: "Tool calls waiting for a human approval, oldest first. Read-only: approve them in the app. Returns { items, next, total }.",
1851
+ input: {
1852
+ type: "object",
1853
+ properties: {
1854
+ toolName: {
1855
+ type: "string"
1856
+ },
1857
+ agent: {
1858
+ type: "string",
1859
+ description: "Agent name."
1860
+ },
1861
+ actor: {
1862
+ type: "string",
1863
+ description: "Actor ref of the thread owner."
1864
+ },
1865
+ threadId: {
1866
+ type: "string"
1867
+ },
1868
+ from: day,
1869
+ to: day,
1870
+ page,
1871
+ limit
1872
+ }
1873
+ },
1874
+ async run(input) {
1875
+ return listOf(await dashboard.approvalsPage({
1876
+ ...pageOf(input),
1877
+ ...where({
1878
+ toolName: str(input.toolName),
1879
+ agentName: str(input.agent),
1880
+ actorRef: str(input.actor),
1881
+ threadId: str(input.threadId),
1882
+ fromDay: dayOf(input, "from"),
1883
+ toDay: dayOf(input, "to")
1884
+ })
1885
+ }));
1886
+ }
1887
+ },
1888
+ {
1889
+ name: "agent_list_recent_tool_calls",
1890
+ kind: "read",
1891
+ description: "Tool calls of every agent, newest first. Filters: toolName, status, threadId, from/to (UTC days). Returns { items, next, total }.",
1892
+ input: {
1893
+ type: "object",
1894
+ properties: {
1895
+ toolName: {
1896
+ type: "string"
1897
+ },
1898
+ status: {
1899
+ type: "string"
1900
+ },
1901
+ threadId: {
1902
+ type: "string"
1903
+ },
1904
+ from: day,
1905
+ to: day,
1906
+ page,
1907
+ limit
1908
+ }
1909
+ },
1910
+ async run(input) {
1911
+ return listOf(await dashboard.toolCallsPage({
1912
+ ...pageOf(input),
1913
+ ...where({
1914
+ toolName: str(input.toolName),
1915
+ status: str(input.status),
1916
+ threadId: str(input.threadId),
1917
+ fromDay: dayOf(input, "from"),
1918
+ toDay: dayOf(input, "to")
1919
+ })
1920
+ }));
1921
+ }
1922
+ }
1923
+ ];
1924
+ }
1925
+ __name(agentDevTools, "agentDevTools");
1926
+ var logger2 = new Logger2("AgentDashboardModule");
1927
+ var AgentDevToolsRegistration = class {
1928
+ static {
1929
+ __name(this, "AgentDevToolsRegistration");
1930
+ }
1931
+ enabled;
1932
+ dashboard;
1933
+ access;
1934
+ #unregister;
1935
+ constructor(enabled, dashboard, access) {
1936
+ this.enabled = enabled;
1937
+ this.dashboard = dashboard;
1938
+ this.access = access;
1939
+ }
1940
+ onModuleInit() {
1941
+ if (!this.enabled) return;
1942
+ const slot = devToolsSlot();
1943
+ if (slot.version !== 1 || typeof slot.register !== "function") return;
1944
+ try {
1945
+ this.#unregister = slot.register({
1946
+ lib: "agent",
1947
+ title: "Agent governance",
1948
+ authorize: /* @__PURE__ */ __name((context) => this.access.check(context), "authorize"),
1949
+ tools: agentDevTools(this.dashboard)
1950
+ });
1951
+ } catch (error) {
1952
+ logger2.warn(`agent dev tools not registered: ${error instanceof Error ? error.message : String(error)}`);
1953
+ }
1954
+ }
1955
+ onModuleDestroy() {
1956
+ this.#unregister?.();
1957
+ this.#unregister = void 0;
1958
+ }
1959
+ };
1960
+ AgentDevToolsRegistration = _ts_decorate5([
1961
+ Injectable3(),
1962
+ _ts_param5(0, Inject5(DASHBOARD_DEV_TOOLS)),
1963
+ _ts_metadata5("design:type", Function),
1964
+ _ts_metadata5("design:paramtypes", [
1965
+ Boolean,
1966
+ typeof DashboardService === "undefined" ? Object : DashboardService,
1967
+ typeof DashboardConsoleAccess === "undefined" ? Object : DashboardConsoleAccess
1968
+ ])
1969
+ ], AgentDevToolsRegistration);
1970
+
1173
1971
  // src/server/agent-dashboard.module.ts
1174
- function _ts_decorate4(decorators, target, key, desc) {
1972
+ function _ts_decorate6(decorators, target, key, desc) {
1175
1973
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1176
1974
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
1177
1975
  r = Reflect.decorate(decorators, target, key, desc);
@@ -1184,23 +1982,29 @@ function _ts_decorate4(decorators, target, key, desc) {
1184
1982
  }
1185
1983
  return c > 3 && r && Object.defineProperty(target, key, r), r;
1186
1984
  }
1187
- __name(_ts_decorate4, "_ts_decorate");
1985
+ __name(_ts_decorate6, "_ts_decorate");
1188
1986
  function normalize(path) {
1189
1987
  return normalizeDashboardPath(path);
1190
1988
  }
1191
1989
  __name(normalize, "normalize");
1192
- function assertProtected(options) {
1193
- if (process.env.NODE_ENV !== "production") return;
1194
- if (options.routes === false) return;
1195
- if ((options.decorators ?? []).length > 0) return;
1196
- throw new Error("AgentDashboardModule: the AI-gateway console would be served open in production. Say who reaches it, with `decorators: [UseGuards(AuthenticationGuard), Can(ConsolePolicy, 'view')]`, or mount its routes yourself with `routes: false` and extend `AgentApiController` / `AgentUiController` under your own guards.");
1990
+ function consoleDecorators(decorators) {
1991
+ return [
1992
+ ...decorators ?? [],
1993
+ UseGuards2(ConsoleAuthorizeGuard),
1994
+ UseFilters2(ConsoleAuthorizeFilter)
1995
+ ];
1197
1996
  }
1198
- __name(assertProtected, "assertProtected");
1997
+ __name(consoleDecorators, "consoleDecorators");
1199
1998
  var AgentApiModule = class _AgentApiModule {
1200
1999
  static {
1201
2000
  __name(this, "AgentApiModule");
1202
2001
  }
1203
2002
  static register(options) {
2003
+ const consoleOptions = {
2004
+ ...options.authorize !== void 0 ? {
2005
+ authorize: options.authorize
2006
+ } : {}
2007
+ };
1204
2008
  return {
1205
2009
  module: _AgentApiModule,
1206
2010
  imports: [
@@ -1208,7 +2012,7 @@ var AgentApiModule = class _AgentApiModule {
1208
2012
  ],
1209
2013
  controllers: options.routes === false ? [] : withControllerDecorators([
1210
2014
  AgentApiController
1211
- ], options.decorators),
2015
+ ], consoleDecorators(options.decorators)),
1212
2016
  providers: [
1213
2017
  DashboardService,
1214
2018
  // `useValue` even when `options.approvalActorRef` is `undefined` — AgentApiController
@@ -1216,17 +2020,29 @@ var AgentApiModule = class _AgentApiModule {
1216
2020
  {
1217
2021
  provide: DASHBOARD_APPROVAL_ACTOR_REF,
1218
2022
  useValue: options.approvalActorRef
1219
- }
2023
+ },
2024
+ {
2025
+ provide: DASHBOARD_CONSOLE_OPTIONS,
2026
+ useValue: consoleOptions
2027
+ },
2028
+ DashboardConsoleAccess,
2029
+ {
2030
+ provide: DASHBOARD_DEV_TOOLS,
2031
+ useValue: options.devTools ?? options.routes !== false
2032
+ },
2033
+ AgentDevToolsRegistration
1220
2034
  ],
1221
- // What a self-mounted `AgentApiController` subclass injects, too (`routes: false`).
2035
+ // What a self-mounted `AgentApiController` subclass injects, too (`routes: false`), and the
2036
+ // access check `UseConsoleAuthorize()` runs.
1222
2037
  exports: [
1223
2038
  DashboardService,
1224
- DASHBOARD_APPROVAL_ACTOR_REF
2039
+ DASHBOARD_APPROVAL_ACTOR_REF,
2040
+ DashboardConsoleAccess
1225
2041
  ]
1226
2042
  };
1227
2043
  }
1228
2044
  };
1229
- AgentApiModule = _ts_decorate4([
2045
+ AgentApiModule = _ts_decorate6([
1230
2046
  Module({})
1231
2047
  ], AgentApiModule);
1232
2048
  var AgentDashboardModule = class _AgentDashboardModule {
@@ -1234,7 +2050,6 @@ var AgentDashboardModule = class _AgentDashboardModule {
1234
2050
  __name(this, "AgentDashboardModule");
1235
2051
  }
1236
2052
  static forRoot(options = {}) {
1237
- assertProtected(options);
1238
2053
  const basePath = normalize(options.basePath ?? "/ai-gateway");
1239
2054
  const apiBasePath = normalize(options.apiBasePath ?? `${basePath}/api`);
1240
2055
  return {
@@ -1254,6 +2069,12 @@ var AgentDashboardModule = class _AgentDashboardModule {
1254
2069
  } : {},
1255
2070
  ...options.approvalActorRef ? {
1256
2071
  approvalActorRef: options.approvalActorRef
2072
+ } : {},
2073
+ ...options.authorize !== void 0 ? {
2074
+ authorize: options.authorize
2075
+ } : {},
2076
+ ...options.devTools !== void 0 ? {
2077
+ devTools: options.devTools
1257
2078
  } : {}
1258
2079
  }),
1259
2080
  ...options.routes === false ? [] : [
@@ -1271,7 +2092,7 @@ var AgentDashboardModule = class _AgentDashboardModule {
1271
2092
  ],
1272
2093
  controllers: options.routes === false ? [] : withControllerDecorators([
1273
2094
  AgentUiController
1274
- ], options.decorators),
2095
+ ], consoleDecorators(options.decorators)),
1275
2096
  providers: [
1276
2097
  {
1277
2098
  provide: DASHBOARD_BASE_PATH,
@@ -1292,7 +2113,7 @@ var AgentDashboardModule = class _AgentDashboardModule {
1292
2113
  };
1293
2114
  }
1294
2115
  };
1295
- AgentDashboardModule = _ts_decorate4([
2116
+ AgentDashboardModule = _ts_decorate6([
1296
2117
  Module({})
1297
2118
  ], AgentDashboardModule);
1298
2119
  export {
@@ -1304,12 +2125,29 @@ export {
1304
2125
  AgentApiController,
1305
2126
  AgentApiModule,
1306
2127
  AgentDashboardModule,
2128
+ AgentDevToolsRegistration,
1307
2129
  AgentUiController,
2130
+ CONSOLE_AUTHORIZE_MISSING_WARNING,
2131
+ CONSOLE_GATE,
2132
+ CONSOLE_PAGE_METADATA,
2133
+ ConsoleAccessDenied,
2134
+ ConsoleAuthorizeFilter,
2135
+ ConsoleAuthorizeGuard,
2136
+ ConsolePage,
1308
2137
  DASHBOARD_API_PATH,
1309
2138
  DASHBOARD_APPROVAL_ACTOR_REF,
1310
2139
  DASHBOARD_BASE_PATH,
2140
+ DASHBOARD_CONSOLE_OPTIONS,
2141
+ DASHBOARD_DEV_TOOLS,
2142
+ DashboardConsoleAccess,
1311
2143
  DashboardService,
2144
+ UseConsoleAuthorize,
1312
2145
  agentDashboardMountPaths,
1313
- parsePriceInput
2146
+ agentDevTools,
2147
+ anyAuthorize,
2148
+ authorizeByToken,
2149
+ normalizeConsoleResult,
2150
+ parsePriceInput,
2151
+ resolveConsoleAuthorize
1314
2152
  };
1315
2153
  //# sourceMappingURL=index.js.map