@abgov/nx-adsp 12.5.0 → 12.7.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abgov/nx-adsp",
3
- "version": "12.5.0",
3
+ "version": "12.7.0",
4
4
  "license": "Apache-2.0",
5
5
  "main": "src/index.js",
6
6
  "description": "Government of Alberta - Nx plugin for ADSP apps.",
@@ -13,16 +13,17 @@
13
13
  "@abgov/nx-oc": "^12.0.0",
14
14
  "@nx-dotnet/core": "^3.0.0",
15
15
  "@nx/angular": "^22.0.0",
16
- "@nx/express": "^22.0.0",
17
16
  "@nx/devkit": "^22.0.0",
18
- "@nx/react": "^22.0.0",
19
17
  "@nx/eslint": "^22.0.0",
18
+ "@nx/express": "^22.0.0",
19
+ "@nx/react": "^22.0.0",
20
20
  "tslib": "^2.0.0"
21
21
  },
22
22
  "dependencies": {
23
23
  "axios": "^1.16.0",
24
24
  "enquirer": "^2.3.6",
25
- "json-schema-to-typescript": "^13.0.1"
25
+ "json-schema-to-typescript": "^13.0.1",
26
+ "socket.io-client": "^4.8.3"
26
27
  },
27
28
  "generators": "./generators.json",
28
29
  "scripts": {},
@@ -50,10 +50,6 @@ function addFiles(host, options) {
50
50
  }
51
51
  return addProxyConf;
52
52
  }
53
- function removeFiles(host, options) {
54
- host.delete(`${options.projectRoot}/src/app/logo.svg`);
55
- host.delete(`${options.projectRoot}/src/app/star.svg`);
56
- }
57
53
  function default_1(host, options) {
58
54
  return tslib_1.__awaiter(this, void 0, void 0, function* () {
59
55
  var _a, _b;
@@ -1 +1 @@
1
- {"version":3,"file":"angular-app.js","sourceRoot":"","sources":["../../../../../../packages/nx-adsp/src/generators/angular-app/angular-app.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"angular-app.js","sourceRoot":"","sources":["../../../../../../packages/nx-adsp/src/generators/angular-app/angular-app.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,96 @@
1
+ # AGENTS.md — <%= projectName %>
2
+
3
+ Angular 19 frontend for the Alberta Digital Service Platform (ADSP).
4
+ Generated by `nx g @abgov/nx-adsp:angular-app`.
5
+
6
+ ## Stack
7
+
8
+ - **UI**: Angular 19 standalone + GoA design system (`@abgov/angular-components` — `Goab*` components)
9
+ - **Auth**: `keycloak-angular` with `keycloak-js`
10
+ - **Router**: Angular Router with `createAuthGuard` from `keycloak-angular`
11
+
12
+ ## Key files
13
+
14
+ | File | Purpose |
15
+ |------|---------|
16
+ | `src/main.ts` | Entry — bootstraps `AppComponent` with `appConfig`, imports `zone.js` |
17
+ | `src/app/app.config.ts` | `provideKeycloak` (PKCE, silent SSO), `provideRouter`, `provideHttpClient` |
18
+ | `src/app/app.component.ts` | Shell — auth state, hero banner background workaround, public API call |
19
+ | `src/app/app.routes.ts` | Routes — `/protected` with `createAuthGuard` |
20
+ | `src/app/protected/protected.component.ts` | Protected route — shows authenticated user info |
21
+ | `src/environments/environment.ts` | Access URL, realm, client ID — pre-set from ADSP tenant |
22
+
23
+ ## Auth pattern
24
+
25
+ ```typescript
26
+ import Keycloak from 'keycloak-js';
27
+ import { inject } from '@angular/core';
28
+
29
+ private keycloak = inject(Keycloak); // provided by provideKeycloak in app.config.ts
30
+
31
+ this.keycloak.authenticated // boolean
32
+ this.keycloak.tokenParsed?.['name'] // user display name
33
+ this.keycloak.login()
34
+ this.keycloak.logout({ redirectUri: window.location.origin })
35
+ ```
36
+
37
+ Route guard using `createAuthGuard` from `keycloak-angular`:
38
+
39
+ ```typescript
40
+ const authGuard = createAuthGuard(async (_route, _state, { authenticated, keycloak }) => {
41
+ if (authenticated) return true;
42
+ await keycloak.login({ redirectUri: window.location.href });
43
+ return false;
44
+ });
45
+ ```
46
+
47
+ ## GoA design system
48
+
49
+ Components are imported from `@abgov/angular-components` with the `Goab` prefix
50
+ and added to each standalone component's `imports` array:
51
+
52
+ ```typescript
53
+ import { GoabButton, GoabAppHeader } from '@abgov/angular-components';
54
+
55
+ @Component({ imports: [GoabButton, GoabAppHeader], ... })
56
+ ```
57
+
58
+ **Known limitation:** `GoabHeroBanner.backgroundUrl` does not bind reliably via
59
+ Angular's template binding due to an `@if(isReady)` timing issue inside the
60
+ component wrapper. Use `MutationObserver` to set the property directly after
61
+ the inner `<goa-hero-banner>` element appears — see `app.component.ts` for the
62
+ established pattern.
63
+
64
+ ## Backend API calls (mean / proxy setup)
65
+
66
+ If `proxy.conf.json` exists at the project root, API calls are proxied to the
67
+ backend service in development. Use relative `/api/` paths — do not hardcode
68
+ the service URL:
69
+
70
+ ```typescript
71
+ // ✓ correct — works through proxy in dev, nginx in production
72
+ this.http.get('/api/v1/my-resource')
73
+
74
+ // ✗ wrong — bypasses proxy, won't work in production
75
+ this.http.get('http://localhost:3333/my-service/v1/my-resource')
76
+ ```
77
+
78
+ `includeBearerTokenInterceptor` (configured in `app.config.ts`) automatically
79
+ attaches the Keycloak access token to all outgoing HTTP requests.
80
+ In production, `nginx.conf` contains the same proxy rule routing `/api/` to
81
+ the backend service hostname.
82
+
83
+ ## Adding a new route
84
+
85
+ 1. Create `src/app/my-feature/my-feature.component.ts` as a standalone component
86
+ 2. Add the route to `src/app/app.routes.ts`
87
+ 3. Add required `Goab*` components to the new component's `imports` array
88
+
89
+ ## What NOT to change
90
+
91
+ - `app.config.ts` — `provideKeycloak` initialises keycloak-js once; do not
92
+ create a second Keycloak instance elsewhere
93
+ - `silent-check-sso.html` — served from `public/`; required for keycloak-js
94
+ silent SSO check on page load
95
+ - `environments/environment.ts` — access URL and realm are pre-configured for
96
+ the ADSP tenant; override at runtime via environment variables
@@ -6,6 +6,10 @@ const nx_oc_1 = require("@abgov/nx-oc");
6
6
  const devkit_1 = require("@nx/devkit");
7
7
  const eslint_1 = require("@nx/eslint");
8
8
  const path = require("path");
9
+ const agent_1 = require("../../utils/agent");
10
+ // Version of nx-adsp passed to the agent for template compatibility checks.
11
+ // Keep in sync with package.json version.
12
+ const PLUGIN_VERSION = '12.x';
9
13
  function normalizeOptions(host, options) {
10
14
  return tslib_1.__awaiter(this, void 0, void 0, function* () {
11
15
  const projectName = (0, devkit_1.names)(options.name).fileName;
@@ -22,6 +26,7 @@ function addFiles(host, options) {
22
26
  }
23
27
  function default_1(host, options) {
24
28
  return tslib_1.__awaiter(this, void 0, void 0, function* () {
29
+ var _a, _b, _c, _d;
25
30
  const normalizedOptions = yield normalizeOptions(host, options);
26
31
  const { applicationGenerator: initExpress } = yield Promise.resolve().then(() => require('@nx/express'));
27
32
  yield initExpress(host, Object.assign(Object.assign({}, options), { skipFormat: true, skipPackageJson: false, linter: eslint_1.Linter.EsLint, unitTestRunner: 'jest', js: false, directory: `apps/${options.name}` }));
@@ -42,6 +47,25 @@ function default_1(host, options) {
42
47
  });
43
48
  addFiles(host, normalizedOptions);
44
49
  yield (0, devkit_1.formatFiles)(host);
50
+ // Consult the nx-adsp-agent to augment the project with ADSP capabilities.
51
+ // The agent has access to template tools and a workspace; it generates new
52
+ // files and modifications to integration files (main.ts, environment.ts)
53
+ // which are applied directly to the Nx Tree.
54
+ // Falls back silently if agent-service is unreachable or no accessToken.
55
+ if (normalizedOptions.adsp) {
56
+ const mainTs = (_b = (_a = host.read(`${normalizedOptions.projectRoot}/src/main.ts`)) === null || _a === void 0 ? void 0 : _a.toString()) !== null && _b !== void 0 ? _b : '';
57
+ const environmentTs = (_d = (_c = host.read(`${normalizedOptions.projectRoot}/src/environment.ts`)) === null || _c === void 0 ? void 0 : _c.toString()) !== null && _d !== void 0 ? _d : '';
58
+ yield (0, agent_1.consultAgent)(normalizedOptions.adsp.directoryServiceUrl, options.accessToken, {
59
+ projectName: normalizedOptions.projectName,
60
+ projectType: 'express-service',
61
+ tenant: normalizedOptions.adsp.tenant,
62
+ pluginVersion: PLUGIN_VERSION,
63
+ existingFiles: {
64
+ 'src/main.ts': mainTs,
65
+ 'src/environment.ts': environmentTs,
66
+ },
67
+ }, host, normalizedOptions.projectRoot);
68
+ }
45
69
  yield (0, nx_oc_1.deploymentGenerator)(host, Object.assign(Object.assign({}, normalizedOptions), { appType: 'node', project: normalizedOptions.projectName }));
46
70
  return () => {
47
71
  (0, devkit_1.installPackagesTask)(host);
@@ -1 +1 @@
1
- {"version":3,"file":"express-service.js","sourceRoot":"","sources":["../../../../../../packages/nx-adsp/src/generators/express-service/express-service.ts"],"names":[],"mappings":";;AA6CA,4BA8CC;;AA3FD,wCAAyE;AACzE,uCAQoB;AACpB,uCAAoC;AACpC,6BAA6B;AAG7B,SAAe,gBAAgB,CAC7B,IAAU,EACV,OAAe;;QAEf,MAAM,WAAW,GAAG,IAAA,cAAK,EAAC,OAAO,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC;QACjD,MAAM,WAAW,GAAG,GAAG,IAAA,2BAAkB,EAAC,IAAI,CAAC,CAAC,OAAO,IAAI,WAAW,EAAE,CAAC;QAEzE,MAAM,IAAI,GAAG,MAAM,IAAA,4BAAoB,EAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAEvD,uCACK,OAAO,KACV,WAAW;YACX,WAAW;YACX,IAAI,IACJ;IACJ,CAAC;CAAA;AAED,SAAS,QAAQ,CAAC,IAAU,EAAE,OAAyB;IACrD,MAAM,eAAe,iDAChB,OAAO,GACP,OAAO,CAAC,IAAI,KACf,IAAI,EAAE,EAAE,GACT,CAAC;IACF,IAAA,sBAAa,EACX,IAAI,EACJ,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,OAAO,CAAC,EAC7B,OAAO,CAAC,WAAW,EACnB,eAAe,CAChB,CAAC;AACJ,CAAC;AAED,mBAA+B,IAAU,EAAE,OAAe;;QACxD,MAAM,iBAAiB,GAAG,MAAM,gBAAgB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAEhE,MAAM,EAAE,oBAAoB,EAAE,WAAW,EAAE,GAAG,2CAAa,aAAa,EAAC,CAAC;QAC1E,MAAM,WAAW,CAAC,IAAI,kCACjB,OAAO,KACV,UAAU,EAAE,IAAI,EAChB,eAAe,EAAE,KAAK,EACtB,MAAM,EAAE,eAAM,CAAC,MAAM,EACrB,cAAc,EAAE,MAAM,EACtB,EAAE,EAAE,KAAK,EACT,SAAS,EAAE,QAAQ,OAAO,CAAC,IAAI,EAAE,IACjC,CAAC;QAEH,IAAA,qCAA4B,EAC1B,IAAI,EACJ;YACE,yBAAyB,EAAE,QAAQ;YACnC,WAAW,EAAE,QAAQ;YACrB,IAAI,EAAE,QAAQ;YACd,MAAM,EAAE,SAAS;YACjB,OAAO,EAAE,QAAQ;YACjB,MAAM,EAAE,QAAQ;YAChB,QAAQ,EAAE,QAAQ;YAClB,oBAAoB,EAAE,QAAQ;SAC/B,EACD;YACE,oBAAoB,EAAE,QAAQ;YAC9B,aAAa,EAAE,SAAS;YACxB,iBAAiB,EAAE,SAAS;YAC5B,2BAA2B,EAAE,QAAQ;SACtC,CACF,CAAC;QAEF,QAAQ,CAAC,IAAI,EAAE,iBAAiB,CAAC,CAAC;QAClC,MAAM,IAAA,oBAAW,EAAC,IAAI,CAAC,CAAC;QAExB,MAAM,IAAA,2BAAmB,EAAC,IAAI,kCACzB,iBAAiB,KACpB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,iBAAiB,CAAC,WAAW,IACtC,CAAC;QAEH,OAAO,GAAG,EAAE;YACV,IAAA,4BAAmB,EAAC,IAAI,CAAC,CAAC;QAC5B,CAAC,CAAC;IACJ,CAAC;CAAA"}
1
+ {"version":3,"file":"express-service.js","sourceRoot":"","sources":["../../../../../../packages/nx-adsp/src/generators/express-service/express-service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,76 @@
1
+ # AGENTS.md — <%= projectName %>
2
+
3
+ Node/Express backend service for the Alberta Digital Service Platform (ADSP).
4
+ Generated by `nx g @abgov/nx-adsp:express-service`.
5
+
6
+ ## Stack
7
+
8
+ - **Runtime**: Node.js + Express
9
+ - **Auth**: passport.js with tenant strategy from `@abgov/adsp-service-sdk`
10
+ - **Config**: `envalid` with `.env` file (see `src/environment.ts`)
11
+ - **SDK**: `@abgov/adsp-service-sdk` — provides service registration, auth,
12
+ event publishing, configuration management, and service discovery
13
+
14
+ ## Key files
15
+
16
+ | File | Purpose |
17
+ |------|---------|
18
+ | `src/main.ts` | App entry — SDK init, middleware, routes, server start |
19
+ | `src/environment.ts` | Validated env config with defaults pre-set from ADSP tenant |
20
+
21
+ ## SDK capabilities
22
+
23
+ `initializeService()` returns `capabilities`:
24
+
25
+ ```typescript
26
+ const { logger, tenantStrategy, traceHandler, configurationHandler,
27
+ healthCheck, directory, tokenProvider, eventService } = capabilities;
28
+ ```
29
+
30
+ To resolve another ADSP service URL:
31
+
32
+ ```typescript
33
+ const url = await directory.getServiceUrl(AdspId.parse('urn:ads:platform:file-service'));
34
+ ```
35
+
36
+ To call another service with an access token:
37
+
38
+ ```typescript
39
+ const token = await tokenProvider.getAccessToken();
40
+ ```
41
+
42
+ ## Adding routes
43
+
44
+ Add route handlers in `main.ts` under the `/<%= projectName %>/v1` path prefix:
45
+
46
+ ```typescript
47
+ app.get('/<%= projectName %>/v1/my-resource', (req, res) => {
48
+ const user = req.user; // null for anonymous, populated for authenticated
49
+ res.json({ ... });
50
+ });
51
+ ```
52
+
53
+ `passport.authenticate(['tenant', 'anonymous'])` is applied to the entire
54
+ `/<%= projectName %>/v1` prefix — check `req.user` inside handlers to
55
+ distinguish authenticated from anonymous access.
56
+
57
+ ## Frontend proxy integration (mern / mean stacks)
58
+
59
+ When this service is paired with a React or Angular frontend via the `mern` or
60
+ `mean` generators, the frontend proxies `/api/` to this service:
61
+
62
+ - **Dev** (webpack / Vite): `/api/v1/my-resource` → `/<%= projectName %>/v1/my-resource` on `localhost:3333`
63
+ - **Production** (nginx): same rewrite via the nginx proxy block in the frontend app
64
+
65
+ All API routes in this service live under `/<%= projectName %>/v1/`. The proxy
66
+ rewrites the frontend's `/api/` prefix to `/<%= projectName %>/` before the
67
+ request reaches Express, so `/<%= projectName %>/v1/public` maps to the
68
+ frontend's `/api/v1/public`.
69
+
70
+ ## What NOT to change
71
+
72
+ - `initializeService(...)` config — `CLIENT_ID` and `CLIENT_SECRET` are read
73
+ from environment; do not hardcode credentials
74
+ - The passport strategy setup — the tenant strategy handles JWT validation
75
+ - `configurationHandler` on the API path — provides configuration service
76
+ integration; keep it applied before route handlers
@@ -0,0 +1,76 @@
1
+ # AGENTS.md — <%= projectName %>
2
+
3
+ React frontend for the Alberta Digital Service Platform (ADSP).
4
+ Generated by `nx g @abgov/nx-adsp:react-app`.
5
+
6
+ ## Stack
7
+
8
+ - **UI**: React 18 + GoA design system (`@abgov/react-components` — `Goab*` components)
9
+ - **Auth**: `keycloak-js` via Redux Toolkit slice (`src/app/user.slice.ts`)
10
+ - **State**: Redux Toolkit — store in `src/store.ts`
11
+ - **Router**: React Router v6
12
+
13
+ ## Key files
14
+
15
+ | File | Purpose |
16
+ |------|---------|
17
+ | `src/main.tsx` | Entry — Redux Provider, BrowserRouter, dispatches `initializeUser()` after render |
18
+ | `src/app/user.slice.ts` | Keycloak auth — login, logout, token refresh, `getAccessToken()` |
19
+ | `src/app/start.slice.ts` | Example public/private API calls |
20
+ | `src/app/config.slice.ts` | Loads ADSP directory service endpoints on startup |
21
+ | `src/app/intake.slice.ts` | Application domain state — extend this for your feature |
22
+ | `src/store.ts` | Redux store — add new slices here |
23
+ | `src/environments/environment.ts` | Access URL, realm, client ID — pre-set from ADSP tenant |
24
+
25
+ ## Auth pattern
26
+
27
+ ```typescript
28
+ import { getAccessToken, loginUser, logoutUser, userSelector } from './user.slice';
29
+
30
+ // In a component:
31
+ const { authenticated, name } = useSelector(userSelector);
32
+ dispatch(loginUser()); // redirects to Keycloak
33
+ dispatch(logoutUser()); // redirects to Keycloak logout
34
+
35
+ // In a thunk (for authenticated API calls):
36
+ const token = await getAccessToken(); // refreshes token if needed
37
+ ```
38
+
39
+ ## GoA design system
40
+
41
+ Components are imported from `@abgov/react-components` with the `Goab` prefix:
42
+
43
+ ```typescript
44
+ import { GoabButton, GoabAppHeader, GoabButtonGroup } from '@abgov/react-components';
45
+ ```
46
+
47
+ ## Adding a new Redux slice
48
+
49
+ 1. Create `src/app/my-feature.slice.ts` using `createSlice` / `createAsyncThunk`
50
+ 2. Export the reducer and add it to `src/store.ts`
51
+
52
+ ## Backend API calls (mern / proxy setup)
53
+
54
+ If `proxy.conf.json` exists at the project root, API calls are proxied to the
55
+ backend service in development. Use relative `/api/` paths — do not hardcode
56
+ the service URL:
57
+
58
+ ```typescript
59
+ // ✓ correct — works through proxy in dev, nginx in production
60
+ const response = await fetch('/api/v1/my-resource');
61
+
62
+ // ✗ wrong — bypasses proxy, won't work in production
63
+ const response = await fetch('http://localhost:3333/my-service/v1/my-resource');
64
+ ```
65
+
66
+ In production, `nginx.conf` contains the same proxy rule routing `/api/` to
67
+ the backend service hostname.
68
+
69
+ ## What NOT to change
70
+
71
+ - `user.slice.ts` — keycloak-js is initialised once per app lifecycle; do not
72
+ create a second Keycloak instance
73
+ - `environments/environment.ts` — access URL and realm are pre-configured for
74
+ the ADSP tenant; override at runtime via deployment config
75
+ - `silent-check-sso.html` — required for keycloak-js silent SSO; must be served
76
+ from the app root
@@ -0,0 +1,34 @@
1
+ import { Tree } from '@nx/devkit';
2
+ export interface AgentCapability {
3
+ generator: string;
4
+ params: Record<string, unknown>;
5
+ description: string;
6
+ }
7
+ export interface CapabilitySpec {
8
+ capabilities: AgentCapability[];
9
+ }
10
+ /**
11
+ * Result returned when the agent conversation completes.
12
+ * filesWritten is the number of files applied to the Nx Tree from the
13
+ * agent workspace (new files and modified integration files like main.ts).
14
+ */
15
+ export interface AgentResult {
16
+ filesWritten: number;
17
+ }
18
+ /**
19
+ * Connect to the ADSP agent-service and conduct a multi-turn conversation
20
+ * with the nx-adsp-agent. The agent uses its workspace tools to write
21
+ * generated and modified files; this function retrieves the workspace state
22
+ * after the conversation and applies all files to the Nx Tree.
23
+ *
24
+ * Returns null if agent-service is unavailable — callers should skip the
25
+ * agent step gracefully in that case.
26
+ */
27
+ export declare function consultAgent(directoryServiceUrl: string, accessToken: string, projectContext: {
28
+ projectName: string;
29
+ projectType: 'express-service' | 'react-app' | 'angular-app';
30
+ tenant: string;
31
+ pluginVersion: string;
32
+ /** Content of key integration files for the agent to read and potentially modify. */
33
+ existingFiles: Record<string, string>;
34
+ }, host: Tree, projectRoot: string): Promise<AgentResult | null>;
@@ -0,0 +1,141 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.consultAgent = consultAgent;
4
+ const tslib_1 = require("tslib");
5
+ const readline_1 = require("readline");
6
+ const socket_io_client_1 = require("socket.io-client");
7
+ const nx_oc_1 = require("@abgov/nx-oc");
8
+ const AGENT_SERVICE_URN = 'urn:ads:platform:agent-service:v1';
9
+ const AGENT_ID = 'nx-adsp-agent';
10
+ /**
11
+ * Connect to the ADSP agent-service and conduct a multi-turn conversation
12
+ * with the nx-adsp-agent. The agent uses its workspace tools to write
13
+ * generated and modified files; this function retrieves the workspace state
14
+ * after the conversation and applies all files to the Nx Tree.
15
+ *
16
+ * Returns null if agent-service is unavailable — callers should skip the
17
+ * agent step gracefully in that case.
18
+ */
19
+ function consultAgent(directoryServiceUrl, accessToken, projectContext, host, projectRoot) {
20
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
21
+ const agentServiceUrl = yield resolveAgentServiceUrl(directoryServiceUrl);
22
+ if (!agentServiceUrl) {
23
+ return null;
24
+ }
25
+ return new Promise((resolve) => {
26
+ const socket = (0, socket_io_client_1.io)(agentServiceUrl, {
27
+ auth: { token: accessToken },
28
+ timeout: 30000,
29
+ reconnection: false,
30
+ });
31
+ const rl = (0, readline_1.createInterface)({ input: process.stdin, output: process.stdout });
32
+ const threadId = crypto.randomUUID();
33
+ let buffer = '';
34
+ let conversationDone = false;
35
+ // If stdin closes while waiting for user input, apply whatever the agent
36
+ // wrote to the workspace and continue generation.
37
+ rl.on('close', () => {
38
+ if (!conversationDone) {
39
+ requestWorkspaceState();
40
+ }
41
+ });
42
+ const buildInitialMessage = () => {
43
+ const fileSection = Object.entries(projectContext.existingFiles)
44
+ .map(([path, content]) => `${path}:\n\`\`\`typescript\n${content}\n\`\`\``)
45
+ .join('\n\n');
46
+ return (`I am setting up a new ${projectContext.projectType} called "${projectContext.projectName}" ` +
47
+ `for ADSP tenant "${projectContext.tenant}" (nx-adsp plugin version ${projectContext.pluginVersion}).\n\n` +
48
+ `Existing project files:\n\n${fileSection}\n\n` +
49
+ `What ADSP capabilities would be useful to integrate into this service?`);
50
+ };
51
+ const sendMessage = (content) => {
52
+ socket.emit('message', {
53
+ agent: AGENT_ID,
54
+ threadId,
55
+ content,
56
+ rawChunks: true,
57
+ });
58
+ };
59
+ const requestWorkspaceState = () => {
60
+ socket.emit('workspace-read', { agent: AGENT_ID, threadId });
61
+ };
62
+ const promptUser = () => {
63
+ rl.question('\n> ', (input) => {
64
+ const trimmed = input.trim();
65
+ if (trimmed) {
66
+ sendMessage(trimmed);
67
+ }
68
+ else {
69
+ // Empty input — user is skipping; resolve without files.
70
+ cleanup(0);
71
+ }
72
+ });
73
+ };
74
+ const applyWorkspaceFiles = (files) => {
75
+ let count = 0;
76
+ for (const file of files) {
77
+ const fullPath = `${projectRoot}/${file.path}`;
78
+ host.write(fullPath, file.content);
79
+ count++;
80
+ }
81
+ return count;
82
+ };
83
+ const cleanup = (filesWritten) => {
84
+ conversationDone = true;
85
+ rl.close();
86
+ socket.disconnect();
87
+ resolve(filesWritten > 0 ? { filesWritten } : null);
88
+ };
89
+ socket.on('connect', () => {
90
+ sendMessage(buildInitialMessage());
91
+ });
92
+ socket.on('stream', ({ chunk, done }) => {
93
+ var _a, _b;
94
+ if ((chunk === null || chunk === void 0 ? void 0 : chunk.type) === 'text-delta') {
95
+ const text = (_b = (_a = chunk.payload) === null || _a === void 0 ? void 0 : _a.text) !== null && _b !== void 0 ? _b : '';
96
+ buffer += text;
97
+ process.stdout.write(text);
98
+ }
99
+ if (done) {
100
+ if (buffer.length > 0 && !buffer.endsWith('\n')) {
101
+ process.stdout.write('\n');
102
+ }
103
+ buffer = '';
104
+ // Agent has finished its response — request workspace state to get
105
+ // any files it wrote, or ask user for follow-up if needed.
106
+ requestWorkspaceState();
107
+ }
108
+ });
109
+ socket.on('workspace-state', ({ files }) => {
110
+ if ((files === null || files === void 0 ? void 0 : files.length) > 0) {
111
+ const written = applyWorkspaceFiles(files);
112
+ process.stdout.write(`\nApplied ${written} file(s) from agent workspace.\n`);
113
+ cleanup(written);
114
+ }
115
+ else {
116
+ // No files written yet — agent may need more input.
117
+ promptUser();
118
+ }
119
+ });
120
+ socket.on('session-expired', () => {
121
+ process.stdout.write('\nAgent session expired.\n');
122
+ requestWorkspaceState();
123
+ });
124
+ socket.on('connect_error', () => cleanup(0));
125
+ socket.on('error', () => requestWorkspaceState());
126
+ });
127
+ });
128
+ }
129
+ function resolveAgentServiceUrl(directoryServiceUrl) {
130
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
131
+ var _a;
132
+ try {
133
+ const urls = yield (0, nx_oc_1.getServiceUrls)(directoryServiceUrl);
134
+ return (_a = urls[AGENT_SERVICE_URN]) !== null && _a !== void 0 ? _a : null;
135
+ }
136
+ catch (_b) {
137
+ return null;
138
+ }
139
+ });
140
+ }
141
+ //# sourceMappingURL=agent.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent.js","sourceRoot":"","sources":["../../../../../packages/nx-adsp/src/utils/agent.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,118 @@
1
+ import { consultAgent } from './agent';
2
+
3
+ jest.mock('readline', () => ({
4
+ createInterface: jest.fn(() => ({
5
+ question: jest.fn((_prompt: string, cb: (answer: string) => void) => cb('')),
6
+ close: jest.fn(),
7
+ on: jest.fn(),
8
+ })),
9
+ }));
10
+
11
+ jest.mock('socket.io-client');
12
+ jest.mock('@abgov/nx-oc', () => ({ getServiceUrls: jest.fn() }));
13
+ jest.mock('@nx/devkit', () => ({ Tree: jest.fn() }));
14
+
15
+ import { io } from 'socket.io-client';
16
+ import { getServiceUrls } from '@abgov/nx-oc';
17
+
18
+ const mockedIo = jest.mocked(io);
19
+ const mockedGetServiceUrls = jest.mocked(getServiceUrls);
20
+
21
+ const PROJECT_CONTEXT = {
22
+ projectName: 'test-service',
23
+ projectType: 'express-service' as const,
24
+ tenant: 'test-tenant',
25
+ pluginVersion: '12.x',
26
+ existingFiles: {
27
+ 'src/main.ts': 'const app = express();',
28
+ 'src/environment.ts': 'export const environment = {};',
29
+ },
30
+ };
31
+
32
+ const mockHost = { write: jest.fn(), read: jest.fn() } as unknown as import('@nx/devkit').Tree;
33
+
34
+ function makeMockSocket() {
35
+ const handlers: Record<string, (...args: unknown[]) => void> = {};
36
+ const socket = {
37
+ on: jest.fn((event: string, handler: (...args: unknown[]) => void) => {
38
+ handlers[event] = handler;
39
+ }),
40
+ emit: jest.fn(),
41
+ disconnect: jest.fn(),
42
+ _handlers: handlers,
43
+ };
44
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
45
+ mockedIo.mockReturnValue(socket as any);
46
+ return socket;
47
+ }
48
+
49
+ const flushPromises = () => new Promise(resolve => setTimeout(resolve, 0));
50
+
51
+ describe('consultAgent', () => {
52
+ beforeEach(() => {
53
+ jest.clearAllMocks();
54
+ jest.spyOn(process.stdout, 'write').mockImplementation(() => true);
55
+ });
56
+
57
+ it('returns null when agent-service is not in directory', async () => {
58
+ mockedGetServiceUrls.mockResolvedValue({});
59
+ const result = await consultAgent('https://directory.example.com', 'token', PROJECT_CONTEXT, mockHost, 'apps/test-service');
60
+ expect(result).toBeNull();
61
+ });
62
+
63
+ it('returns null on socket connect error', async () => {
64
+ mockedGetServiceUrls.mockResolvedValue({
65
+ 'urn:ads:platform:agent-service:v1': 'https://agent.example.com',
66
+ });
67
+ const socket = makeMockSocket();
68
+ const resultPromise = consultAgent('https://directory.example.com', 'token', PROJECT_CONTEXT, mockHost, 'apps/test-service');
69
+ await flushPromises();
70
+ socket._handlers['connect_error']?.();
71
+ expect(await resultPromise).toBeNull();
72
+ });
73
+
74
+ it('applies workspace files to Nx Tree and returns filesWritten count', async () => {
75
+ mockedGetServiceUrls.mockResolvedValue({
76
+ 'urn:ads:platform:agent-service:v1': 'https://agent.example.com',
77
+ });
78
+ const socket = makeMockSocket();
79
+ const resultPromise = consultAgent('https://directory.example.com', 'token', PROJECT_CONTEXT, mockHost, 'apps/test-service');
80
+ await flushPromises();
81
+
82
+ socket._handlers['connect']?.();
83
+ socket._handlers['stream']?.({ chunk: null, done: true });
84
+ socket._handlers['workspace-state']?.({
85
+ files: [
86
+ { path: 'src/roles.ts', content: 'export enum ServiceRoles {}' },
87
+ { path: 'src/main.ts', content: 'updated main.ts' },
88
+ ],
89
+ });
90
+
91
+ const result = await resultPromise;
92
+ expect(result).toEqual({ filesWritten: 2 });
93
+ expect(mockHost.write).toHaveBeenCalledWith('apps/test-service/src/roles.ts', 'export enum ServiceRoles {}');
94
+ expect(mockHost.write).toHaveBeenCalledWith('apps/test-service/src/main.ts', 'updated main.ts');
95
+ });
96
+
97
+ it('includes existing file content in the initial message', async () => {
98
+ mockedGetServiceUrls.mockResolvedValue({
99
+ 'urn:ads:platform:agent-service:v1': 'https://agent.example.com',
100
+ });
101
+ const socket = makeMockSocket();
102
+ const resultPromise = consultAgent('https://directory.example.com', 'token', PROJECT_CONTEXT, mockHost, 'apps/test-service');
103
+ await flushPromises();
104
+
105
+ socket._handlers['connect']?.();
106
+ socket._handlers['stream']?.({ chunk: null, done: true });
107
+ socket._handlers['workspace-state']?.({ files: [] });
108
+ await resultPromise;
109
+
110
+ expect(socket.emit).toHaveBeenCalledWith(
111
+ 'message',
112
+ expect.objectContaining({
113
+ agent: 'nx-adsp-agent',
114
+ content: expect.stringContaining('src/main.ts'),
115
+ })
116
+ );
117
+ });
118
+ });