@adonis-agora/agent 0.20.0 → 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.
- package/CHANGELOG.md +10 -0
- package/README.md +39 -1
- package/dist/assets/spa/assets/index-BYLm0nq8.css +1 -0
- package/dist/assets/spa/assets/index-CkOAJYbh.js +9 -0
- package/dist/assets/spa/index.html +13 -0
- package/dist/configure.d.ts +11 -6
- package/dist/configure.d.ts.map +1 -1
- package/dist/configure.js +12 -6
- package/dist/configure.js.map +1 -1
- package/dist/providers/dashboard_provider.d.ts +55 -0
- package/dist/providers/dashboard_provider.d.ts.map +1 -0
- package/dist/providers/dashboard_provider.js +152 -0
- package/dist/providers/dashboard_provider.js.map +1 -0
- package/dist/src/dashboard/define_config.d.ts +75 -0
- package/dist/src/dashboard/define_config.d.ts.map +1 -0
- package/dist/src/dashboard/define_config.js +56 -0
- package/dist/src/dashboard/define_config.js.map +1 -0
- package/dist/src/dashboard/gate.d.ts +33 -0
- package/dist/src/dashboard/gate.d.ts.map +1 -0
- package/dist/src/dashboard/gate.js +52 -0
- package/dist/src/dashboard/gate.js.map +1 -0
- package/dist/src/dashboard/index.d.ts +20 -0
- package/dist/src/dashboard/index.d.ts.map +1 -0
- package/dist/src/dashboard/index.js +20 -0
- package/dist/src/dashboard/index.js.map +1 -0
- package/dist/src/dashboard/paths.d.ts +40 -0
- package/dist/src/dashboard/paths.d.ts.map +1 -0
- package/dist/src/dashboard/paths.js +99 -0
- package/dist/src/dashboard/paths.js.map +1 -0
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.js +1 -1
- package/dist/stubs/config/agent.stub +7 -0
- package/package.json +28 -18
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<title>agent · governance console</title>
|
|
7
|
+
<script type="module" crossorigin src="./assets/index-CkOAJYbh.js"></script>
|
|
8
|
+
<link rel="stylesheet" crossorigin href="./assets/index-BYLm0nq8.css">
|
|
9
|
+
</head>
|
|
10
|
+
<body>
|
|
11
|
+
<div id="root"></div>
|
|
12
|
+
</body>
|
|
13
|
+
</html>
|
package/dist/configure.d.ts
CHANGED
|
@@ -3,16 +3,21 @@ import type Configure from '@adonisjs/core/commands/configure';
|
|
|
3
3
|
* `node ace configure @adonis-agora/agent` — auto-wires the package:
|
|
4
4
|
*
|
|
5
5
|
* 1. registers the agent service provider in `adonisrc.ts`;
|
|
6
|
-
* 2. registers the
|
|
6
|
+
* 2. registers the embedded dashboard provider — the governance console SPA, bundled into this
|
|
7
|
+
* package's own `dist/` (`@adonis-agora/agent-dashboard`'s build output, copied in at build time),
|
|
8
|
+
* served with no separate install. Toggle/relocate it with the optional `config('agent').dashboard`
|
|
9
|
+
* block (see `config/agent.ts`); it mounts only once `governanceAuthorize` is configured — see the
|
|
10
|
+
* provider's own doc comment for why;
|
|
11
|
+
* 3. registers the Assembler `init` hook that generates the typed `app/agent_tools` barrel at
|
|
7
12
|
* build/dev time (the provider imports it instead of scanning at runtime; it falls back to the
|
|
8
13
|
* runtime scan when the barrel is absent);
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
14
|
+
* 4. publishes `config/agent.ts`;
|
|
15
|
+
* 5. publishes `config/mcp.ts` (the MCP endpoint config; the MCP provider is separate);
|
|
16
|
+
* 6. publishes the Lucid migration for the five agent tables (run `node ace migration:run`; delete it
|
|
12
17
|
* if you only use the in-memory store);
|
|
13
|
-
*
|
|
18
|
+
* 7. publishes the additive run-tracking migration (`agent_run` table + `run_id` columns) — run it
|
|
14
19
|
* after the base migration; delete it with the base one if you only use the in-memory store;
|
|
15
|
-
*
|
|
20
|
+
* 8. publishes the pgvector migration for the RAG chunk table (Postgres + pgvector only; delete it
|
|
16
21
|
* unless you use `retrievers.pgvector({...})`).
|
|
17
22
|
*/
|
|
18
23
|
export declare function configure(command: Configure): Promise<void>;
|
package/dist/configure.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"configure.d.ts","sourceRoot":"","sources":["../configure.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,SAAS,MAAM,mCAAmC,CAAC;AAG/D
|
|
1
|
+
{"version":3,"file":"configure.d.ts","sourceRoot":"","sources":["../configure.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,SAAS,MAAM,mCAAmC,CAAC;AAG/D;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,SAAS,CAAC,OAAO,EAAE,SAAS,iBAejD"}
|
package/dist/configure.js
CHANGED
|
@@ -3,22 +3,28 @@ import { stubsRoot } from './stubs/main.js';
|
|
|
3
3
|
* `node ace configure @adonis-agora/agent` — auto-wires the package:
|
|
4
4
|
*
|
|
5
5
|
* 1. registers the agent service provider in `adonisrc.ts`;
|
|
6
|
-
* 2. registers the
|
|
6
|
+
* 2. registers the embedded dashboard provider — the governance console SPA, bundled into this
|
|
7
|
+
* package's own `dist/` (`@adonis-agora/agent-dashboard`'s build output, copied in at build time),
|
|
8
|
+
* served with no separate install. Toggle/relocate it with the optional `config('agent').dashboard`
|
|
9
|
+
* block (see `config/agent.ts`); it mounts only once `governanceAuthorize` is configured — see the
|
|
10
|
+
* provider's own doc comment for why;
|
|
11
|
+
* 3. registers the Assembler `init` hook that generates the typed `app/agent_tools` barrel at
|
|
7
12
|
* build/dev time (the provider imports it instead of scanning at runtime; it falls back to the
|
|
8
13
|
* runtime scan when the barrel is absent);
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
14
|
+
* 4. publishes `config/agent.ts`;
|
|
15
|
+
* 5. publishes `config/mcp.ts` (the MCP endpoint config; the MCP provider is separate);
|
|
16
|
+
* 6. publishes the Lucid migration for the five agent tables (run `node ace migration:run`; delete it
|
|
12
17
|
* if you only use the in-memory store);
|
|
13
|
-
*
|
|
18
|
+
* 7. publishes the additive run-tracking migration (`agent_run` table + `run_id` columns) — run it
|
|
14
19
|
* after the base migration; delete it with the base one if you only use the in-memory store;
|
|
15
|
-
*
|
|
20
|
+
* 8. publishes the pgvector migration for the RAG chunk table (Postgres + pgvector only; delete it
|
|
16
21
|
* unless you use `retrievers.pgvector({...})`).
|
|
17
22
|
*/
|
|
18
23
|
export async function configure(command) {
|
|
19
24
|
const codemods = await command.createCodemods();
|
|
20
25
|
await codemods.updateRcFile((rcFile) => {
|
|
21
26
|
rcFile.addProvider('@adonis-agora/agent/agent_provider');
|
|
27
|
+
rcFile.addProvider('@adonis-agora/agent/dashboard_provider');
|
|
22
28
|
// Generate the typed app/agent_tools barrel at build/dev time (replaces the runtime readdir scan).
|
|
23
29
|
rcFile.addAssemblerHook('init', '@adonis-agora/agent/hooks/tools');
|
|
24
30
|
});
|
package/dist/configure.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"configure.js","sourceRoot":"","sources":["../configure.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAE5C
|
|
1
|
+
{"version":3,"file":"configure.js","sourceRoot":"","sources":["../configure.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAE5C;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,OAAkB;IAChD,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,cAAc,EAAE,CAAC;IAEhD,MAAM,QAAQ,CAAC,YAAY,CAAC,CAAC,MAAM,EAAE,EAAE;QACrC,MAAM,CAAC,WAAW,CAAC,oCAAoC,CAAC,CAAC;QACzD,MAAM,CAAC,WAAW,CAAC,wCAAwC,CAAC,CAAC;QAC7D,mGAAmG;QACnG,MAAM,CAAC,gBAAgB,CAAC,MAAM,EAAE,iCAAiC,CAAC,CAAC;IACrE,CAAC,CAAC,CAAC;IAEH,MAAM,QAAQ,CAAC,aAAa,CAAC,SAAS,EAAE,mBAAmB,EAAE,EAAE,CAAC,CAAC;IACjE,MAAM,QAAQ,CAAC,aAAa,CAAC,SAAS,EAAE,iBAAiB,EAAE,EAAE,CAAC,CAAC;IAC/D,MAAM,QAAQ,CAAC,aAAa,CAAC,SAAS,EAAE,8CAA8C,EAAE,EAAE,CAAC,CAAC;IAC5F,MAAM,QAAQ,CAAC,aAAa,CAAC,SAAS,EAAE,oDAAoD,EAAE,EAAE,CAAC,CAAC;IAClG,MAAM,QAAQ,CAAC,aAAa,CAAC,SAAS,EAAE,kDAAkD,EAAE,EAAE,CAAC,CAAC;AAClG,CAAC"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { ApplicationService } from '@adonisjs/core/types';
|
|
2
|
+
/**
|
|
3
|
+
* Serves the `@adonis-agora/agent-dashboard` governance SPA **from this package's own build** — no
|
|
4
|
+
* separate install, no separate provider registration. `node ace configure @adonis-agora/agent`
|
|
5
|
+
* registers this provider alongside `agent_provider`, and the SPA it serves is bundled straight into
|
|
6
|
+
* `@adonis-agora/agent`'s own `dist/` at build time (`package.json`'s `copy:stubs` copies
|
|
7
|
+
* `../dashboard/dist/spa`, the `@adonis-agora/agent-dashboard` package's `vite build` output, into
|
|
8
|
+
* `dist/assets/spa`) — mirrors `@adonis-agora/durable`'s `dashboard_provider.ts` /
|
|
9
|
+
* `src/dashboard/spa.ts` split, which does the identical thing for its own dashboard.
|
|
10
|
+
*
|
|
11
|
+
* The routing/gating logic here is BYTE-FOR-BYTE the same as `@adonis-agora/agent-dashboard`'s own
|
|
12
|
+
* `agent_dashboard_provider.ts` — both call the SAME shared helpers from `../src/dashboard/index.js`
|
|
13
|
+
* (`decideDashboardMount`, `evaluateDashboardGate`, the path/content-type helpers). The two providers
|
|
14
|
+
* differ only in WHERE they read the built `index.html`/assets from: this one reads its own
|
|
15
|
+
* `dist/assets/spa` (copied in at build time); the standalone provider reads
|
|
16
|
+
* `@adonis-agora/agent-dashboard`'s own `dist/spa`.
|
|
17
|
+
*
|
|
18
|
+
* Why the logic lives HERE rather than in `@adonis-agora/agent-dashboard` (which would let this
|
|
19
|
+
* provider stay a one-line re-export): `@adonis-agora/agent-dashboard` already depends on
|
|
20
|
+
* `@adonis-agora/agent` (its provider imports `AgentConfig`/`ActorResolver`), so a dependency the
|
|
21
|
+
* other way — this package depending on the dashboard package's code at runtime — would be circular,
|
|
22
|
+
* and in this monorepo it would ALSO be a real `turbo`/pnpm build cycle (the dashboard package's own
|
|
23
|
+
* build needs `@adonis-agora/agent` built first, to typecheck its provider against it). Keeping the
|
|
24
|
+
* shared logic in `src/dashboard/` (owned by this package) avoids the cycle exactly the way
|
|
25
|
+
* `@adonis-agora/durable` avoids it — by owning its `spa.ts` serving helpers directly rather than
|
|
26
|
+
* depending on `@adonis-agora/durable-dashboard` for anything but the BUILT ASSET FILES (a
|
|
27
|
+
* build-time-only, one-directional filesystem copy in `copy:stubs`, no package.json dependency at
|
|
28
|
+
* all — see that script for how the dashboard's SPA output is located: a plain relative path,
|
|
29
|
+
* `../dashboard/dist/spa`, resolved by directory layout, not by module resolution).
|
|
30
|
+
*
|
|
31
|
+
* `@adonis-agora/agent-dashboard`'s own provider keeps working entirely unchanged for apps that
|
|
32
|
+
* already install and register it directly (e.g. on an older `@adonis-agora/agent` version, or where
|
|
33
|
+
* an app wants the standalone package's independent versioning/release cadence) — this provider is
|
|
34
|
+
* purely additive. An app that registers BOTH providers with the same mount path will hit AdonisJS's
|
|
35
|
+
* "duplicate route" error at boot; pick one (this is the intended migration signal, not a bug).
|
|
36
|
+
*
|
|
37
|
+
* Routes (mount = `<agentPath>/dashboard`, default `/agent/dashboard`) — identical to the standalone
|
|
38
|
+
* provider's:
|
|
39
|
+
* - `GET <mount>` → the SPA shell (`index.html`, with the resolved API base + `<base href>` injected)
|
|
40
|
+
* - `GET <mount>/*` → a built asset, or the SPA shell as a fallback (client-rendered console)
|
|
41
|
+
*
|
|
42
|
+
* Nothing mounts unless the agent config carries a `governanceAuthorize` gate: without it the
|
|
43
|
+
* `/agent/governance/*` routes the console reads do not exist, so the console would load and fail on
|
|
44
|
+
* every panel but Quota. The provider declines and logs why (see `decideDashboardMount`).
|
|
45
|
+
*
|
|
46
|
+
* Enable/disable and override the mount via the optional `config('agent').dashboard` block — the SAME
|
|
47
|
+
* block the standalone provider reads.
|
|
48
|
+
*/
|
|
49
|
+
export default class DashboardProvider {
|
|
50
|
+
#private;
|
|
51
|
+
protected app: ApplicationService;
|
|
52
|
+
constructor(app: ApplicationService);
|
|
53
|
+
boot(): Promise<void>;
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=dashboard_provider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dashboard_provider.d.ts","sourceRoot":"","sources":["../../providers/dashboard_provider.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAiB/D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AACH,MAAM,CAAC,OAAO,OAAO,iBAAiB;;IACxB,SAAS,CAAC,GAAG,EAAE,kBAAkB;gBAAvB,GAAG,EAAE,kBAAkB;IAcvC,IAAI;CAuGX"}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { resolve as resolvePath } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { apiBaseFor, contentTypeFor, decideDashboardMount, evaluateDashboardGate, injectApiBase, injectBaseHref, mountPathFor, resolveDashboardConfig, safeAssetSegments, } from '../src/dashboard/index.js';
|
|
5
|
+
/**
|
|
6
|
+
* Serves the `@adonis-agora/agent-dashboard` governance SPA **from this package's own build** — no
|
|
7
|
+
* separate install, no separate provider registration. `node ace configure @adonis-agora/agent`
|
|
8
|
+
* registers this provider alongside `agent_provider`, and the SPA it serves is bundled straight into
|
|
9
|
+
* `@adonis-agora/agent`'s own `dist/` at build time (`package.json`'s `copy:stubs` copies
|
|
10
|
+
* `../dashboard/dist/spa`, the `@adonis-agora/agent-dashboard` package's `vite build` output, into
|
|
11
|
+
* `dist/assets/spa`) — mirrors `@adonis-agora/durable`'s `dashboard_provider.ts` /
|
|
12
|
+
* `src/dashboard/spa.ts` split, which does the identical thing for its own dashboard.
|
|
13
|
+
*
|
|
14
|
+
* The routing/gating logic here is BYTE-FOR-BYTE the same as `@adonis-agora/agent-dashboard`'s own
|
|
15
|
+
* `agent_dashboard_provider.ts` — both call the SAME shared helpers from `../src/dashboard/index.js`
|
|
16
|
+
* (`decideDashboardMount`, `evaluateDashboardGate`, the path/content-type helpers). The two providers
|
|
17
|
+
* differ only in WHERE they read the built `index.html`/assets from: this one reads its own
|
|
18
|
+
* `dist/assets/spa` (copied in at build time); the standalone provider reads
|
|
19
|
+
* `@adonis-agora/agent-dashboard`'s own `dist/spa`.
|
|
20
|
+
*
|
|
21
|
+
* Why the logic lives HERE rather than in `@adonis-agora/agent-dashboard` (which would let this
|
|
22
|
+
* provider stay a one-line re-export): `@adonis-agora/agent-dashboard` already depends on
|
|
23
|
+
* `@adonis-agora/agent` (its provider imports `AgentConfig`/`ActorResolver`), so a dependency the
|
|
24
|
+
* other way — this package depending on the dashboard package's code at runtime — would be circular,
|
|
25
|
+
* and in this monorepo it would ALSO be a real `turbo`/pnpm build cycle (the dashboard package's own
|
|
26
|
+
* build needs `@adonis-agora/agent` built first, to typecheck its provider against it). Keeping the
|
|
27
|
+
* shared logic in `src/dashboard/` (owned by this package) avoids the cycle exactly the way
|
|
28
|
+
* `@adonis-agora/durable` avoids it — by owning its `spa.ts` serving helpers directly rather than
|
|
29
|
+
* depending on `@adonis-agora/durable-dashboard` for anything but the BUILT ASSET FILES (a
|
|
30
|
+
* build-time-only, one-directional filesystem copy in `copy:stubs`, no package.json dependency at
|
|
31
|
+
* all — see that script for how the dashboard's SPA output is located: a plain relative path,
|
|
32
|
+
* `../dashboard/dist/spa`, resolved by directory layout, not by module resolution).
|
|
33
|
+
*
|
|
34
|
+
* `@adonis-agora/agent-dashboard`'s own provider keeps working entirely unchanged for apps that
|
|
35
|
+
* already install and register it directly (e.g. on an older `@adonis-agora/agent` version, or where
|
|
36
|
+
* an app wants the standalone package's independent versioning/release cadence) — this provider is
|
|
37
|
+
* purely additive. An app that registers BOTH providers with the same mount path will hit AdonisJS's
|
|
38
|
+
* "duplicate route" error at boot; pick one (this is the intended migration signal, not a bug).
|
|
39
|
+
*
|
|
40
|
+
* Routes (mount = `<agentPath>/dashboard`, default `/agent/dashboard`) — identical to the standalone
|
|
41
|
+
* provider's:
|
|
42
|
+
* - `GET <mount>` → the SPA shell (`index.html`, with the resolved API base + `<base href>` injected)
|
|
43
|
+
* - `GET <mount>/*` → a built asset, or the SPA shell as a fallback (client-rendered console)
|
|
44
|
+
*
|
|
45
|
+
* Nothing mounts unless the agent config carries a `governanceAuthorize` gate: without it the
|
|
46
|
+
* `/agent/governance/*` routes the console reads do not exist, so the console would load and fail on
|
|
47
|
+
* every panel but Quota. The provider declines and logs why (see `decideDashboardMount`).
|
|
48
|
+
*
|
|
49
|
+
* Enable/disable and override the mount via the optional `config('agent').dashboard` block — the SAME
|
|
50
|
+
* block the standalone provider reads.
|
|
51
|
+
*/
|
|
52
|
+
export default class DashboardProvider {
|
|
53
|
+
app;
|
|
54
|
+
constructor(app) {
|
|
55
|
+
this.app = app;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Directory of the built SPA (`dist/assets/spa`, copied in by `copy:stubs` — see `package.json`),
|
|
59
|
+
* relative to this compiled provider (`dist/providers`). Resolved lazily (not at module scope):
|
|
60
|
+
* `fileURLToPath` throws on any non-`file:` `import.meta.url`, which would make this provider
|
|
61
|
+
* unimportable under a test runner that doesn't use real file URLs.
|
|
62
|
+
*/
|
|
63
|
+
#spaDir;
|
|
64
|
+
#spaDirectory() {
|
|
65
|
+
this.#spaDir ??= fileURLToPath(new URL('../assets/spa/', import.meta.url));
|
|
66
|
+
return this.#spaDir;
|
|
67
|
+
}
|
|
68
|
+
async boot() {
|
|
69
|
+
const agentConfig = this.app.config.get('agent', {});
|
|
70
|
+
const dashboardConfig = resolveDashboardConfig(this.app.config.get('agent.dashboard', {}));
|
|
71
|
+
// Mount only when the console can actually work: every panel but Quota reads the agent's
|
|
72
|
+
// cross-actor `/agent/governance/*` routes, and those do not exist without a `governanceAuthorize`
|
|
73
|
+
// gate. Refusing here — loudly — beats serving a shell that 404s its own data with no explanation.
|
|
74
|
+
const decision = decideDashboardMount(dashboardConfig.enabled, agentConfig.governanceAuthorize, agentConfig.governanceQueries);
|
|
75
|
+
if (!decision.mount) {
|
|
76
|
+
if (decision.reason !== 'disabled')
|
|
77
|
+
console.warn(decision.warning);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
// Resolve the router from the container, NOT from `@adonisjs/core/services/router`: that service's
|
|
81
|
+
// default export is assigned inside an `app.booted()` hook, which runs AFTER every provider's
|
|
82
|
+
// `boot()` — so at this point the module default is still `undefined` and `router.get(...)` throws.
|
|
83
|
+
const router = await this.app.container.make('router');
|
|
84
|
+
const agentPath = agentConfig.path ?? 'agent';
|
|
85
|
+
const apiBase = apiBaseFor(agentPath);
|
|
86
|
+
const mount = mountPathFor(agentPath, dashboardConfig.path);
|
|
87
|
+
const actorResolver = agentConfig.actorResolver;
|
|
88
|
+
const authorize = dashboardConfig.authorize;
|
|
89
|
+
// The SPA shell, served at the bare mount. We do NOT redirect to a trailing-slash variant: the
|
|
90
|
+
// AdonisJS router normalizes trailing slashes, so `mount` and `${mount}/` are the SAME route
|
|
91
|
+
// pattern — registering both throws "Duplicate route". Instead `sendIndex` injects a
|
|
92
|
+
// `<base href="${mount}/">`, so the SPA's relative `./assets/*` URLs resolve against the mount
|
|
93
|
+
// directory regardless of whether the browser's URL carries a trailing slash.
|
|
94
|
+
router.get(mount, async (ctx) => {
|
|
95
|
+
if (!(await this.#gate(ctx, actorResolver, authorize)))
|
|
96
|
+
return;
|
|
97
|
+
await this.#sendIndex(ctx, apiBase, mount);
|
|
98
|
+
});
|
|
99
|
+
// Built assets (JS/CSS/fonts/...), with an index fallback for any unmatched path so the
|
|
100
|
+
// client-rendered console still boots on a deep link.
|
|
101
|
+
router.get(`${mount}/*`, async (ctx) => {
|
|
102
|
+
if (!(await this.#gate(ctx, actorResolver, authorize)))
|
|
103
|
+
return;
|
|
104
|
+
const segments = safeAssetSegments(ctx.params['*']);
|
|
105
|
+
if (segments === null) {
|
|
106
|
+
return ctx.response.status(400).json({ error: 'bad asset path' });
|
|
107
|
+
}
|
|
108
|
+
await this.#sendAsset(ctx, segments, apiBase, mount);
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Resolve the actor through the agent config's resolver, mirroring the governance routes, then run
|
|
113
|
+
* the optional `authorize` gate. Returns `true` to proceed; replies `401` on a missing/failed
|
|
114
|
+
* resolver and `403` when `authorize` denies the resolved actor. The decision itself lives in the
|
|
115
|
+
* router-free {@link evaluateDashboardGate} so it can be unit tested; this only writes the response.
|
|
116
|
+
*/
|
|
117
|
+
async #gate(ctx, actorResolver, authorize) {
|
|
118
|
+
const verdict = await evaluateDashboardGate(ctx, actorResolver, authorize);
|
|
119
|
+
if (!verdict.ok) {
|
|
120
|
+
ctx.response.status(verdict.status).json({ error: verdict.error });
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Read `dist/assets/spa/index.html`, inject the `<base href>` (so relative `./assets/*` resolve
|
|
127
|
+
* against the mount dir) and the API base, and send it.
|
|
128
|
+
*/
|
|
129
|
+
async #sendIndex(ctx, apiBase, mount) {
|
|
130
|
+
const html = await readFile(resolvePath(this.#spaDirectory(), 'index.html'), 'utf8');
|
|
131
|
+
ctx.response.header('content-type', 'text/html; charset=utf-8');
|
|
132
|
+
ctx.response.header('cache-control', 'no-store, must-revalidate');
|
|
133
|
+
ctx.response.send(injectApiBase(injectBaseHref(html, mount), apiBase));
|
|
134
|
+
}
|
|
135
|
+
/** Send the requested built asset, or fall back to the SPA shell when it does not exist. */
|
|
136
|
+
async #sendAsset(ctx, segments, apiBase, mount) {
|
|
137
|
+
const filename = segments[segments.length - 1] ?? 'index.html';
|
|
138
|
+
try {
|
|
139
|
+
const buffer = await readFile(resolvePath(this.#spaDirectory(), ...segments));
|
|
140
|
+
ctx.response.header('content-type', contentTypeFor(filename));
|
|
141
|
+
ctx.response.send(buffer);
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
if (error.code === 'ENOENT') {
|
|
145
|
+
await this.#sendIndex(ctx, apiBase, mount);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
throw error;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
//# sourceMappingURL=dashboard_provider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dashboard_provider.js","sourceRoot":"","sources":["../../providers/dashboard_provider.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { HttpContext } from '@adonisjs/core/http';
|
|
2
|
+
import type { AgentGovernanceAuthorize } from '../governance-gate.js';
|
|
3
|
+
import type { Actor } from '../types.js';
|
|
4
|
+
/**
|
|
5
|
+
* An extra authorization gate for the dashboard, run AFTER the agent config's `actorResolver` has
|
|
6
|
+
* resolved the caller. Return `false` (or throw) to deny — the request gets `403`. The governance
|
|
7
|
+
* read-model the SPA serves spans EVERY actor's spend/usage, so most apps restrict the console beyond
|
|
8
|
+
* mere authentication, e.g. `(actor) => actor.roles?.includes('ADMIN') ?? false`.
|
|
9
|
+
*/
|
|
10
|
+
export type AgentDashboardAuthorize = (actor: Actor, ctx: HttpContext) => boolean | Promise<boolean>;
|
|
11
|
+
/**
|
|
12
|
+
* Optional `config('agent').dashboard` block. The dashboard reuses the agent config's `path` and
|
|
13
|
+
* `actorResolver` (so it sits behind the SAME actor gating as the governance routes); this block
|
|
14
|
+
* toggles it on/off, optionally overrides the mount path, and optionally adds an `authorize` gate.
|
|
15
|
+
*
|
|
16
|
+
* Shared by BOTH the embedded dashboard provider (`@adonis-agora/agent/dashboard_provider`, wired
|
|
17
|
+
* automatically when you `node ace configure @adonis-agora/agent`) and the standalone
|
|
18
|
+
* `@adonis-agora/agent-dashboard` package's provider — one config block controls whichever one is
|
|
19
|
+
* registered.
|
|
20
|
+
*/
|
|
21
|
+
export interface AgentDashboardConfig {
|
|
22
|
+
/**
|
|
23
|
+
* Mount the SPA. Default `true` — set `false` to keep the routes off entirely. Note `true` is
|
|
24
|
+
* necessary but not sufficient: the console also needs the agent's `governanceAuthorize` gate, see
|
|
25
|
+
* {@link decideDashboardMount}.
|
|
26
|
+
*/
|
|
27
|
+
enabled?: boolean;
|
|
28
|
+
/** Override the mount path. Default `<agentPath>/dashboard`. */
|
|
29
|
+
path?: string;
|
|
30
|
+
/**
|
|
31
|
+
* Extra authorization run after the actor resolves. Return `false` to deny (`403`). Omit to allow
|
|
32
|
+
* any resolved actor (the default) — note this is only an EXTRA gate on the SPA shell; whether the
|
|
33
|
+
* console mounts at all, and whether its data is readable, is decided by the agent config's
|
|
34
|
+
* `governanceAuthorize`.
|
|
35
|
+
*/
|
|
36
|
+
authorize?: AgentDashboardAuthorize;
|
|
37
|
+
}
|
|
38
|
+
export interface ResolvedAgentDashboardConfig {
|
|
39
|
+
enabled: boolean;
|
|
40
|
+
path?: string;
|
|
41
|
+
authorize?: AgentDashboardAuthorize;
|
|
42
|
+
}
|
|
43
|
+
/** Fill defaults for the optional dashboard config block. */
|
|
44
|
+
export declare function resolveDashboardConfig(config: AgentDashboardConfig | undefined): ResolvedAgentDashboardConfig;
|
|
45
|
+
/** Whether the provider registers the console's routes, and (when it refuses) what to say about it. */
|
|
46
|
+
export type DashboardMountDecision = {
|
|
47
|
+
mount: true;
|
|
48
|
+
} | {
|
|
49
|
+
mount: false;
|
|
50
|
+
reason: 'disabled';
|
|
51
|
+
} | {
|
|
52
|
+
mount: false;
|
|
53
|
+
reason: 'no-governance-gate';
|
|
54
|
+
warning: string;
|
|
55
|
+
} | {
|
|
56
|
+
mount: false;
|
|
57
|
+
reason: 'no-governance-queries';
|
|
58
|
+
warning: string;
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* Decide whether to register the console's routes at all — the router-free core of the provider's
|
|
62
|
+
* mount check, extracted so it can be unit tested without booting an app.
|
|
63
|
+
*
|
|
64
|
+
* The console is a pure consumer of the agent's cross-actor `/agent/governance/*` read routes, and
|
|
65
|
+
* those routes are not mounted unless the agent config carries a `governanceAuthorize` gate. Serving
|
|
66
|
+
* a shell that can only 404 its own data is worse than not serving it: the failure shows up as seven
|
|
67
|
+
* dead panels at click time with nothing anywhere explaining why. So the console follows the same
|
|
68
|
+
* fail-closed-by-omission rule one layer down — no gate, no routes — and says so at boot.
|
|
69
|
+
*
|
|
70
|
+
* This costs nothing that still works: every app affected is an app whose console is ALREADY dead in
|
|
71
|
+
* every view but Quota. `dashboard.authorize` is deliberately NOT the trigger; it gates the shell,
|
|
72
|
+
* not the data, and an app can set it and still have a console with nothing to render.
|
|
73
|
+
*/
|
|
74
|
+
export declare function decideDashboardMount(enabled: boolean, governanceAuthorize: AgentGovernanceAuthorize | undefined, governanceQueries?: unknown): DashboardMountDecision;
|
|
75
|
+
//# sourceMappingURL=define_config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"define_config.d.ts","sourceRoot":"","sources":["../../../src/dashboard/define_config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAC;AACtE,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAEzC;;;;;GAKG;AACH,MAAM,MAAM,uBAAuB,GAAG,CACpC,KAAK,EAAE,KAAK,EACZ,GAAG,EAAE,WAAW,KACb,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;AAEhC;;;;;;;;;GASG;AACH,MAAM,WAAW,oBAAoB;IACnC;;;;OAIG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,gEAAgE;IAChE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;;OAKG;IACH,SAAS,CAAC,EAAE,uBAAuB,CAAC;CACrC;AAED,MAAM,WAAW,4BAA4B;IAC3C,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,uBAAuB,CAAC;CACrC;AAED,6DAA6D;AAC7D,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,oBAAoB,GAAG,SAAS,GACvC,4BAA4B,CAK9B;AAED,uGAAuG;AACvG,MAAM,MAAM,sBAAsB,GAC9B;IAAE,KAAK,EAAE,IAAI,CAAA;CAAE,GACf;IAAE,KAAK,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,UAAU,CAAA;CAAE,GACpC;IAAE,KAAK,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,oBAAoB,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAC/D;IAAE,KAAK,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,uBAAuB,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAoBvE;;;;;;;;;;;;;GAaG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,OAAO,EAChB,mBAAmB,EAAE,wBAAwB,GAAG,SAAS,EACzD,iBAAiB,CAAC,EAAE,OAAO,GAC1B,sBAAsB,CAgBxB"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/** Fill defaults for the optional dashboard config block. */
|
|
2
|
+
export function resolveDashboardConfig(config) {
|
|
3
|
+
const resolved = { enabled: config?.enabled ?? true };
|
|
4
|
+
if (config?.path !== undefined)
|
|
5
|
+
resolved.path = config.path;
|
|
6
|
+
if (config?.authorize !== undefined)
|
|
7
|
+
resolved.authorize = config.authorize;
|
|
8
|
+
return resolved;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* The exact wording the provider logs when it declines to mount. Mirrors the agent provider's own
|
|
12
|
+
* governance boot warning — same three facts, same vocabulary — so a reader who sees one recognises
|
|
13
|
+
* the other.
|
|
14
|
+
*/
|
|
15
|
+
const NO_GOVERNANCE_GATE_WARNING = '[@adonis-agora/agent-dashboard] the governance console was NOT mounted: every panel but Quota reads the cross-actor `/agent/governance/*` routes, and those are themselves not mounted because no `governanceAuthorize` gate is configured — so the console would load and then fail on every panel. Set `governanceAuthorize` in config/agent.ts (e.g. an ADMIN check) to mount the routes and this console gated, or `governanceAuthorize: () => true` to deliberately restore the old behaviour of letting ANY authenticated actor read them. `GET /agent/approvals/mine` is unaffected — it stays mounted and scoped to the calling actor. To keep the console off on purpose and silence this, set `dashboard: { enabled: false }`.';
|
|
16
|
+
/**
|
|
17
|
+
* The wording logged when the read-model itself was turned off. Deliberately distinct from
|
|
18
|
+
* {@link NO_GOVERNANCE_GATE_WARNING}: both produce the same dead console, and an operator staring at
|
|
19
|
+
* a 404 must be able to tell from the log ALONE which of the two knobs is responsible. A shared
|
|
20
|
+
* "governance is not configured" message would recreate exactly the diagnosability problem this
|
|
21
|
+
* check exists to remove.
|
|
22
|
+
*/
|
|
23
|
+
const NO_GOVERNANCE_QUERIES_WARNING = '[@adonis-agora/agent-dashboard] the governance console was NOT mounted: `governanceQueries` is set to `false` in config/agent.ts, so the cross-actor read-model was never built and the `/agent/governance/*` routes it serves do not exist — the console would load and then fail on every panel but Quota. A `governanceAuthorize` gate is NOT the missing piece here; it is already configured. Remove `governanceQueries: false` (omit it to get the Lucid read-model when the main store is Lucid, or pass a store/factory explicitly) to mount the read-model and this console. To keep the console off on purpose and silence this, set `dashboard: { enabled: false }`.';
|
|
24
|
+
/**
|
|
25
|
+
* Decide whether to register the console's routes at all — the router-free core of the provider's
|
|
26
|
+
* mount check, extracted so it can be unit tested without booting an app.
|
|
27
|
+
*
|
|
28
|
+
* The console is a pure consumer of the agent's cross-actor `/agent/governance/*` read routes, and
|
|
29
|
+
* those routes are not mounted unless the agent config carries a `governanceAuthorize` gate. Serving
|
|
30
|
+
* a shell that can only 404 its own data is worse than not serving it: the failure shows up as seven
|
|
31
|
+
* dead panels at click time with nothing anywhere explaining why. So the console follows the same
|
|
32
|
+
* fail-closed-by-omission rule one layer down — no gate, no routes — and says so at boot.
|
|
33
|
+
*
|
|
34
|
+
* This costs nothing that still works: every app affected is an app whose console is ALREADY dead in
|
|
35
|
+
* every view but Quota. `dashboard.authorize` is deliberately NOT the trigger; it gates the shell,
|
|
36
|
+
* not the data, and an app can set it and still have a console with nothing to render.
|
|
37
|
+
*/
|
|
38
|
+
export function decideDashboardMount(enabled, governanceAuthorize, governanceQueries) {
|
|
39
|
+
if (!enabled)
|
|
40
|
+
return { mount: false, reason: 'disabled' };
|
|
41
|
+
if (governanceAuthorize === undefined) {
|
|
42
|
+
return { mount: false, reason: 'no-governance-gate', warning: NO_GOVERNANCE_GATE_WARNING };
|
|
43
|
+
}
|
|
44
|
+
// ONLY an explicit `false` is decidable here. `undefined` does NOT mean "no read-model": the agent
|
|
45
|
+
// provider's `#resolveGovernance` defaults it to the Lucid read-model whenever the main store is
|
|
46
|
+
// Lucid, and reproducing that resolution would duplicate provider logic in the dashboard.
|
|
47
|
+
if (governanceQueries === false) {
|
|
48
|
+
return {
|
|
49
|
+
mount: false,
|
|
50
|
+
reason: 'no-governance-queries',
|
|
51
|
+
warning: NO_GOVERNANCE_QUERIES_WARNING,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
return { mount: true };
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=define_config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"define_config.js","sourceRoot":"","sources":["../../../src/dashboard/define_config.ts"],"names":[],"mappings":"AAiDA,6DAA6D;AAC7D,MAAM,UAAU,sBAAsB,CACpC,MAAwC;IAExC,MAAM,QAAQ,GAAiC,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,IAAI,IAAI,EAAE,CAAC;IACpF,IAAI,MAAM,EAAE,IAAI,KAAK,SAAS;QAAE,QAAQ,CAAC,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC;IAC5D,IAAI,MAAM,EAAE,SAAS,KAAK,SAAS;QAAE,QAAQ,CAAC,SAAS,GAAG,MAAM,CAAC,SAAS,CAAC;IAC3E,OAAO,QAAQ,CAAC;AAClB,CAAC;AASD;;;;GAIG;AACH,MAAM,0BAA0B,GAC9B,0sBAA0sB,CAAC;AAE7sB;;;;;;GAMG;AACH,MAAM,6BAA6B,GACjC,ipBAAipB,CAAC;AAEppB;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,oBAAoB,CAClC,OAAgB,EAChB,mBAAyD,EACzD,iBAA2B;IAE3B,IAAI,CAAC,OAAO;QAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC;IAC1D,IAAI,mBAAmB,KAAK,SAAS,EAAE,CAAC;QACtC,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,oBAAoB,EAAE,OAAO,EAAE,0BAA0B,EAAE,CAAC;IAC7F,CAAC;IACD,mGAAmG;IACnG,iGAAiG;IACjG,0FAA0F;IAC1F,IAAI,iBAAiB,KAAK,KAAK,EAAE,CAAC;QAChC,OAAO;YACL,KAAK,EAAE,KAAK;YACZ,MAAM,EAAE,uBAAuB;YAC/B,OAAO,EAAE,6BAA6B;SACvC,CAAC;IACJ,CAAC;IACD,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;AACzB,CAAC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { HttpContext } from '@adonisjs/core/http';
|
|
2
|
+
import type { ActorResolver } from '../spi/actor-resolver.js';
|
|
3
|
+
import type { AgentDashboardAuthorize } from './define_config.js';
|
|
4
|
+
/** The outcome of the dashboard access gate: proceed, or deny with an HTTP status + message. */
|
|
5
|
+
export type DashboardGateVerdict = {
|
|
6
|
+
ok: true;
|
|
7
|
+
} | {
|
|
8
|
+
ok: false;
|
|
9
|
+
status: 401 | 403;
|
|
10
|
+
error: string;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Decide whether a request may reach the dashboard, WITHOUT touching the response — so it is unit
|
|
14
|
+
* testable free of the AdonisJS router/app: resolve the actor through the agent config's resolver
|
|
15
|
+
* (missing/failed → `401`), then run the optional `authorize` gate over the resolved actor
|
|
16
|
+
* (denied/threw → `403`).
|
|
17
|
+
*
|
|
18
|
+
* This gates the SPA **shell**, and only the shell. It is NOT what decides whether the console's data
|
|
19
|
+
* is readable, and it is no longer the last line of defence it once had to be:
|
|
20
|
+
*
|
|
21
|
+
* - Whether the console mounts at all is decided earlier, by {@link decideDashboardMount} — the
|
|
22
|
+
* routes are not registered unless the agent config carries a `governanceAuthorize` gate AND its
|
|
23
|
+
* `governanceQueries` read-model exists. So this function never runs for a console that could only
|
|
24
|
+
* 404 its own panels.
|
|
25
|
+
* - Whether the DATA is readable is decided by the agent's own `governanceAuthorize` on the
|
|
26
|
+
* `/agent/governance/*` routes, not here. Omitting `authorize` while a gate is configured leaves
|
|
27
|
+
* the shell open to any resolved actor but exposes no cross-actor data.
|
|
28
|
+
*
|
|
29
|
+
* Omitting BOTH the resolver and `authorize` would expose the shell to any request; the provider is
|
|
30
|
+
* expected to short-circuit on a missing resolver via the `401` here.
|
|
31
|
+
*/
|
|
32
|
+
export declare function evaluateDashboardGate(ctx: HttpContext, actorResolver: ActorResolver | undefined, authorize?: AgentDashboardAuthorize): Promise<DashboardGateVerdict>;
|
|
33
|
+
//# sourceMappingURL=gate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gate.d.ts","sourceRoot":"","sources":["../../../src/dashboard/gate.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAC9D,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAC;AAElE,gGAAgG;AAChG,MAAM,MAAM,oBAAoB,GAAG;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,GAAG,GAAG,GAAG,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAElG;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,qBAAqB,CACzC,GAAG,EAAE,WAAW,EAChB,aAAa,EAAE,aAAa,GAAG,SAAS,EACxC,SAAS,CAAC,EAAE,uBAAuB,GAClC,OAAO,CAAC,oBAAoB,CAAC,CA4B/B"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Decide whether a request may reach the dashboard, WITHOUT touching the response — so it is unit
|
|
3
|
+
* testable free of the AdonisJS router/app: resolve the actor through the agent config's resolver
|
|
4
|
+
* (missing/failed → `401`), then run the optional `authorize` gate over the resolved actor
|
|
5
|
+
* (denied/threw → `403`).
|
|
6
|
+
*
|
|
7
|
+
* This gates the SPA **shell**, and only the shell. It is NOT what decides whether the console's data
|
|
8
|
+
* is readable, and it is no longer the last line of defence it once had to be:
|
|
9
|
+
*
|
|
10
|
+
* - Whether the console mounts at all is decided earlier, by {@link decideDashboardMount} — the
|
|
11
|
+
* routes are not registered unless the agent config carries a `governanceAuthorize` gate AND its
|
|
12
|
+
* `governanceQueries` read-model exists. So this function never runs for a console that could only
|
|
13
|
+
* 404 its own panels.
|
|
14
|
+
* - Whether the DATA is readable is decided by the agent's own `governanceAuthorize` on the
|
|
15
|
+
* `/agent/governance/*` routes, not here. Omitting `authorize` while a gate is configured leaves
|
|
16
|
+
* the shell open to any resolved actor but exposes no cross-actor data.
|
|
17
|
+
*
|
|
18
|
+
* Omitting BOTH the resolver and `authorize` would expose the shell to any request; the provider is
|
|
19
|
+
* expected to short-circuit on a missing resolver via the `401` here.
|
|
20
|
+
*/
|
|
21
|
+
export async function evaluateDashboardGate(ctx, actorResolver, authorize) {
|
|
22
|
+
if (actorResolver === undefined) {
|
|
23
|
+
return { ok: false, status: 401, error: 'no actor resolver configured' };
|
|
24
|
+
}
|
|
25
|
+
let actor;
|
|
26
|
+
try {
|
|
27
|
+
actor = await actorResolver.resolve(ctx);
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
return {
|
|
31
|
+
ok: false,
|
|
32
|
+
status: 401,
|
|
33
|
+
error: error instanceof Error ? error.message : 'unauthorized',
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
if (authorize !== undefined) {
|
|
37
|
+
try {
|
|
38
|
+
if (!(await authorize(actor, ctx))) {
|
|
39
|
+
return { ok: false, status: 403, error: 'forbidden' };
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
return {
|
|
44
|
+
ok: false,
|
|
45
|
+
status: 403,
|
|
46
|
+
error: error instanceof Error ? error.message : 'forbidden',
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return { ok: true };
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=gate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gate.js","sourceRoot":"","sources":["../../../src/dashboard/gate.ts"],"names":[],"mappings":"AAOA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,GAAgB,EAChB,aAAwC,EACxC,SAAmC;IAEnC,IAAI,aAAa,KAAK,SAAS,EAAE,CAAC;QAChC,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,8BAA8B,EAAE,CAAC;IAC3E,CAAC;IACD,IAAI,KAAoD,CAAC;IACzD,IAAI,CAAC;QACH,KAAK,GAAG,MAAM,aAAa,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAC3C,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO;YACL,EAAE,EAAE,KAAK;YACT,MAAM,EAAE,GAAG;YACX,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,cAAc;SAC/D,CAAC;IACJ,CAAC;IACD,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;QAC5B,IAAI,CAAC;YACH,IAAI,CAAC,CAAC,MAAM,SAAS,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC;gBACnC,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC;YACxD,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO;gBACL,EAAE,EAAE,KAAK;gBACT,MAAM,EAAE,GAAG;gBACX,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,WAAW;aAC5D,CAAC;QACJ,CAAC;IACH,CAAC;IACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;AACtB,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Serving helpers for the `@adonis-agora/agent-dashboard` governance SPA — the config shape, the
|
|
3
|
+
* mount/gate decisions, and the pure path/asset helpers. Framework-light (no AdonisJS router/app
|
|
4
|
+
* dependency), so they're unit-testable in isolation and reusable by BOTH dashboard providers:
|
|
5
|
+
*
|
|
6
|
+
* - `@adonis-agora/agent/dashboard_provider` (this package, `providers/dashboard_provider.ts`) —
|
|
7
|
+
* embedded: registered automatically by `node ace configure @adonis-agora/agent`, serves the SPA
|
|
8
|
+
* bundle copied into THIS package's own `dist/assets/spa` at build time.
|
|
9
|
+
* - `@adonis-agora/agent-dashboard`'s own provider (`agent_dashboard_provider.ts`) — standalone: an
|
|
10
|
+
* independently installable/registrable package some apps already depend on directly.
|
|
11
|
+
*
|
|
12
|
+
* Living here (rather than in `@adonis-agora/agent-dashboard`) keeps the dependency direction
|
|
13
|
+
* one-way: the dashboard package already depends on this one for `AgentConfig`/`ActorResolver`/etc.,
|
|
14
|
+
* so a package.json edge back from here to there would be circular. See
|
|
15
|
+
* `providers/dashboard_provider.ts`'s module doc for the full story.
|
|
16
|
+
*/
|
|
17
|
+
export { type AgentDashboardAuthorize, type AgentDashboardConfig, type DashboardMountDecision, type ResolvedAgentDashboardConfig, decideDashboardMount, resolveDashboardConfig, } from './define_config.js';
|
|
18
|
+
export { type DashboardGateVerdict, evaluateDashboardGate } from './gate.js';
|
|
19
|
+
export { apiBaseFor, contentTypeFor, injectApiBase, injectBaseHref, mountPathFor, safeAssetSegments, trimSlashes, } from './paths.js';
|
|
20
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/dashboard/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EACL,KAAK,uBAAuB,EAC5B,KAAK,oBAAoB,EACzB,KAAK,sBAAsB,EAC3B,KAAK,4BAA4B,EACjC,oBAAoB,EACpB,sBAAsB,GACvB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,KAAK,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,WAAW,CAAC;AAC7E,OAAO,EACL,UAAU,EACV,cAAc,EACd,aAAa,EACb,cAAc,EACd,YAAY,EACZ,iBAAiB,EACjB,WAAW,GACZ,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Serving helpers for the `@adonis-agora/agent-dashboard` governance SPA — the config shape, the
|
|
3
|
+
* mount/gate decisions, and the pure path/asset helpers. Framework-light (no AdonisJS router/app
|
|
4
|
+
* dependency), so they're unit-testable in isolation and reusable by BOTH dashboard providers:
|
|
5
|
+
*
|
|
6
|
+
* - `@adonis-agora/agent/dashboard_provider` (this package, `providers/dashboard_provider.ts`) —
|
|
7
|
+
* embedded: registered automatically by `node ace configure @adonis-agora/agent`, serves the SPA
|
|
8
|
+
* bundle copied into THIS package's own `dist/assets/spa` at build time.
|
|
9
|
+
* - `@adonis-agora/agent-dashboard`'s own provider (`agent_dashboard_provider.ts`) — standalone: an
|
|
10
|
+
* independently installable/registrable package some apps already depend on directly.
|
|
11
|
+
*
|
|
12
|
+
* Living here (rather than in `@adonis-agora/agent-dashboard`) keeps the dependency direction
|
|
13
|
+
* one-way: the dashboard package already depends on this one for `AgentConfig`/`ActorResolver`/etc.,
|
|
14
|
+
* so a package.json edge back from here to there would be circular. See
|
|
15
|
+
* `providers/dashboard_provider.ts`'s module doc for the full story.
|
|
16
|
+
*/
|
|
17
|
+
export { decideDashboardMount, resolveDashboardConfig, } from './define_config.js';
|
|
18
|
+
export { evaluateDashboardGate } from './gate.js';
|
|
19
|
+
export { apiBaseFor, contentTypeFor, injectApiBase, injectBaseHref, mountPathFor, safeAssetSegments, trimSlashes, } from './paths.js';
|
|
20
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/dashboard/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EAKL,oBAAoB,EACpB,sBAAsB,GACvB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAA6B,qBAAqB,EAAE,MAAM,WAAW,CAAC;AAC7E,OAAO,EACL,UAAU,EACV,cAAc,EACd,aAAa,EACb,cAAc,EACd,YAAY,EACZ,iBAAiB,EACjB,WAAW,GACZ,MAAM,YAAY,CAAC"}
|