@abgov/nx-adsp 12.7.0 → 12.8.0-beta.2

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.7.0",
3
+ "version": "12.8.0-beta.2",
4
4
  "license": "Apache-2.0",
5
5
  "main": "src/index.js",
6
6
  "description": "Government of Alberta - Nx plugin for ADSP apps.",
@@ -12,9 +12,35 @@ const agent_1 = require("../../utils/agent");
12
12
  const PLUGIN_VERSION = '12.x';
13
13
  function normalizeOptions(host, options) {
14
14
  return tslib_1.__awaiter(this, void 0, void 0, function* () {
15
+ var _a, _b, _c;
15
16
  const projectName = (0, devkit_1.names)(options.name).fileName;
16
17
  const projectRoot = `${(0, devkit_1.getWorkspaceLayout)(host).appsDir}/${projectName}`;
17
- const adsp = yield (0, nx_oc_1.getAdspConfiguration)(host, options);
18
+ let adsp;
19
+ if (options.tenant) {
20
+ // Resolve realm from tenant name via the public tenant service API (no auth required),
21
+ // then do a single browser login for the tenant realm.
22
+ const env = nx_oc_1.environments[(_a = options.env) !== null && _a !== void 0 ? _a : 'prod'];
23
+ const tenantServiceUrl = (yield (0, nx_oc_1.getServiceUrls)(env.directoryServiceUrl))['urn:ads:platform:tenant-service'];
24
+ const { default: axios } = yield Promise.resolve().then(() => require('axios'));
25
+ const { data } = yield axios.get(new URL('/api/tenant/v2/tenants', tenantServiceUrl).href, { params: { name: options.tenant } });
26
+ const tenantInfo = (_b = data === null || data === void 0 ? void 0 : data.results) === null || _b === void 0 ? void 0 : _b[0];
27
+ if (!tenantInfo) {
28
+ throw new Error(`Tenant "${options.tenant}" not found in ${env.directoryServiceUrl}.`);
29
+ }
30
+ const tenantRealm = (_c = options.tenantRealm) !== null && _c !== void 0 ? _c : tenantInfo.realm;
31
+ if (!options.accessToken) {
32
+ options = Object.assign(Object.assign({}, options), { accessToken: yield (0, nx_oc_1.realmLogin)(env.accessServiceUrl, tenantRealm).catch(() => undefined) });
33
+ }
34
+ adsp = {
35
+ tenant: tenantInfo.name,
36
+ tenantRealm,
37
+ accessServiceUrl: env.accessServiceUrl,
38
+ directoryServiceUrl: env.directoryServiceUrl,
39
+ };
40
+ }
41
+ else {
42
+ adsp = yield (0, nx_oc_1.getAdspConfiguration)(host, options);
43
+ }
18
44
  return Object.assign(Object.assign({}, options), { projectName,
19
45
  projectRoot,
20
46
  adsp });
@@ -26,7 +52,7 @@ function addFiles(host, options) {
26
52
  }
27
53
  function default_1(host, options) {
28
54
  return tslib_1.__awaiter(this, void 0, void 0, function* () {
29
- var _a, _b, _c, _d;
55
+ var _a, _b, _c, _d, _e;
30
56
  const normalizedOptions = yield normalizeOptions(host, options);
31
57
  const { applicationGenerator: initExpress } = yield Promise.resolve().then(() => require('@nx/express'));
32
58
  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}` }));
@@ -53,9 +79,18 @@ function default_1(host, options) {
53
79
  // which are applied directly to the Nx Tree.
54
80
  // Falls back silently if agent-service is unreachable or no accessToken.
55
81
  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, {
82
+ // When --tenant was provided, normalizedOptions.accessToken holds the token
83
+ // from the single realm login already performed during normalizeOptions.
84
+ // token from the single realm login. Fall back to a new login only when the
85
+ // full interactive flow was used and no token is available.
86
+ const accessToken = (_a = normalizedOptions.accessToken) !== null && _a !== void 0 ? _a : (yield (0, nx_oc_1.realmLogin)(normalizedOptions.adsp.accessServiceUrl, normalizedOptions.adsp.tenantRealm).catch((err) => {
87
+ var _a;
88
+ process.stdout.write(`Agent sign-in failed (${(_a = err === null || err === void 0 ? void 0 : err.message) !== null && _a !== void 0 ? _a : err}) — skipping agent interaction.\n`);
89
+ return undefined;
90
+ }));
91
+ const mainTs = (_c = (_b = host.read(`${normalizedOptions.projectRoot}/src/main.ts`)) === null || _b === void 0 ? void 0 : _b.toString()) !== null && _c !== void 0 ? _c : '';
92
+ const environmentTs = (_e = (_d = host.read(`${normalizedOptions.projectRoot}/src/environment.ts`)) === null || _d === void 0 ? void 0 : _d.toString()) !== null && _e !== void 0 ? _e : '';
93
+ const agentResult = yield (0, agent_1.consultAgent)(normalizedOptions.adsp.directoryServiceUrl, accessToken, {
59
94
  projectName: normalizedOptions.projectName,
60
95
  projectType: 'express-service',
61
96
  tenant: normalizedOptions.adsp.tenant,
@@ -65,6 +100,21 @@ function default_1(host, options) {
65
100
  'src/environment.ts': environmentTs,
66
101
  },
67
102
  }, host, normalizedOptions.projectRoot);
103
+ // When the agent interaction ended without generating files — whether the
104
+ // user was in a conversation or Ctrl+C'd before the agent responded —
105
+ // confirm whether to proceed. Default to false when Ctrl+C was used.
106
+ if (agentResult && agentResult.filesWritten === 0) {
107
+ const { prompt } = yield Promise.resolve().then(() => require('enquirer'));
108
+ const { proceed } = yield prompt({
109
+ type: 'confirm',
110
+ name: 'proceed',
111
+ message: 'Agent interaction ended without generating files. Continue with base scaffolding?',
112
+ initial: !agentResult.interrupted,
113
+ });
114
+ if (!proceed) {
115
+ throw new Error('Generation aborted.');
116
+ }
117
+ }
68
118
  }
69
119
  yield (0, nx_oc_1.deploymentGenerator)(host, Object.assign(Object.assign({}, normalizedOptions), { appType: 'node', project: normalizedOptions.projectName }));
70
120
  return () => {
@@ -1 +1 @@
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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"}
1
+ {"version":3,"file":"express-service.js","sourceRoot":"","sources":["../../../../../../packages/nx-adsp/src/generators/express-service/express-service.ts"],"names":[],"mappings":";;AAsFA,4BAuGC;;AA7LD,wCAAmH;AACnH,uCAQoB;AACpB,uCAAoC;AACpC,6BAA6B;AAC7B,6CAAiD;AAGjD,4EAA4E;AAC5E,0CAA0C;AAC1C,MAAM,cAAc,GAAG,MAAM,CAAC;AAE9B,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,IAAI,IAA8C,CAAC;QAEnD,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;YACnB,uFAAuF;YACvF,uDAAuD;YACvD,MAAM,GAAG,GAAG,oBAAY,CAAC,MAAA,OAAO,CAAC,GAAG,mCAAI,MAAM,CAAC,CAAC;YAChD,MAAM,gBAAgB,GAAG,CAAC,MAAM,IAAA,sBAAc,EAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC,CAAC,iCAAiC,CAAC,CAAC;YAE5G,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,2CAAa,OAAO,EAAC,CAAC;YACjD,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,KAAK,CAAC,GAAG,CAC9B,IAAI,GAAG,CAAC,wBAAwB,EAAE,gBAAgB,CAAC,CAAC,IAAI,EACxD,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,OAAO,CAAC,MAAM,EAAE,EAAE,CACrC,CAAC;YAEF,MAAM,UAAU,GAAG,MAAA,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,OAAO,0CAAG,CAAC,CAAC,CAAC;YACtC,IAAI,CAAC,UAAU,EAAE,CAAC;gBAChB,MAAM,IAAI,KAAK,CAAC,WAAW,OAAO,CAAC,MAAM,kBAAkB,GAAG,CAAC,mBAAmB,GAAG,CAAC,CAAC;YACzF,CAAC;YAED,MAAM,WAAW,GAAG,MAAA,OAAO,CAAC,WAAW,mCAAI,UAAU,CAAC,KAAK,CAAC;YAE5D,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC;gBACzB,OAAO,mCACF,OAAO,KACV,WAAW,EAAE,MAAM,IAAA,kBAAU,EAAC,GAAG,CAAC,gBAAgB,EAAE,WAAW,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,GACxF,CAAC;YACJ,CAAC;YAED,IAAI,GAAG;gBACL,MAAM,EAAE,UAAU,CAAC,IAAI;gBACvB,WAAW;gBACX,gBAAgB,EAAE,GAAG,CAAC,gBAAgB;gBACtC,mBAAmB,EAAE,GAAG,CAAC,mBAAmB;aAC7C,CAAC;QACJ,CAAC;aAAM,CAAC;YACN,IAAI,GAAG,MAAM,IAAA,4BAAoB,EAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QACnD,CAAC;QAED,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,2EAA2E;QAC3E,2EAA2E;QAC3E,yEAAyE;QACzE,6CAA6C;QAC7C,yEAAyE;QACzE,IAAI,iBAAiB,CAAC,IAAI,EAAE,CAAC;YAC3B,4EAA4E;YAC5E,yEAAyE;YACzE,4EAA4E;YAC5E,4DAA4D;YAC5D,MAAM,WAAW,GACf,MAAA,iBAAiB,CAAC,WAAW,mCAC7B,CAAC,MAAM,IAAA,kBAAU,EACf,iBAAiB,CAAC,IAAI,CAAC,gBAAgB,EACvC,iBAAiB,CAAC,IAAI,CAAC,WAAW,CACnC,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;;gBACd,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,yBAAyB,MAAA,GAAG,aAAH,GAAG,uBAAH,GAAG,CAAE,OAAO,mCAAI,GAAG,mCAAmC,CAAC,CAAC;gBACtG,OAAO,SAAS,CAAC;YACnB,CAAC,CAAC,CAAC,CAAC;YAEN,MAAM,MAAM,GAAG,MAAA,MAAA,IAAI,CAAC,IAAI,CAAC,GAAG,iBAAiB,CAAC,WAAW,cAAc,CAAC,0CAAE,QAAQ,EAAE,mCAAI,EAAE,CAAC;YAC3F,MAAM,aAAa,GAAG,MAAA,MAAA,IAAI,CAAC,IAAI,CAAC,GAAG,iBAAiB,CAAC,WAAW,qBAAqB,CAAC,0CAAE,QAAQ,EAAE,mCAAI,EAAE,CAAC;YAEzG,MAAM,WAAW,GAAG,MAAM,IAAA,oBAAY,EACpC,iBAAiB,CAAC,IAAI,CAAC,mBAAmB,EAC1C,WAAW,EACX;gBACE,WAAW,EAAE,iBAAiB,CAAC,WAAW;gBAC1C,WAAW,EAAE,iBAAiB;gBAC9B,MAAM,EAAE,iBAAiB,CAAC,IAAI,CAAC,MAAM;gBACrC,aAAa,EAAE,cAAc;gBAC7B,aAAa,EAAE;oBACb,aAAa,EAAE,MAAM;oBACrB,oBAAoB,EAAE,aAAa;iBACpC;aACF,EACD,IAAI,EACJ,iBAAiB,CAAC,WAAW,CAC9B,CAAC;YAEF,0EAA0E;YAC1E,sEAAsE;YACtE,qEAAqE;YACrE,IAAI,WAAW,IAAI,WAAW,CAAC,YAAY,KAAK,CAAC,EAAE,CAAC;gBAClD,MAAM,EAAE,MAAM,EAAE,GAAG,2CAAa,UAAU,EAAC,CAAC;gBAC5C,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,MAAM,CAAuB;oBACrD,IAAI,EAAE,SAAS;oBACf,IAAI,EAAE,SAAS;oBACf,OAAO,EAAE,mFAAmF;oBAC5F,OAAO,EAAE,CAAC,WAAW,CAAC,WAAW;iBAClC,CAAC,CAAC;gBACH,IAAI,CAAC,OAAO,EAAE,CAAC;oBACb,MAAM,IAAI,KAAK,CAAC,qBAAqB,CAAC,CAAC;gBACzC,CAAC;YACH,CAAC;QACH,CAAC;QAED,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"}
@@ -14,6 +14,8 @@ utilsMock.getAdspConfiguration.mockResolvedValue({
14
14
  accessServiceUrl: environments.test.accessServiceUrl,
15
15
  directoryServiceUrl: environments.test.directoryServiceUrl,
16
16
  });
17
+ utilsMock.deploymentGenerator.mockResolvedValue(undefined);
18
+ utilsMock.realmLogin.mockResolvedValue('test-token');
17
19
 
18
20
  describe('Express Service Generator', () => {
19
21
  const options: Schema = {
@@ -4,6 +4,10 @@ export interface Schema {
4
4
  name: string;
5
5
  env: EnvironmentName;
6
6
  accessToken?: string;
7
+ /** Keycloak realm UUID. When provided with tenant, skips interactive tenant selection. */
8
+ tenantRealm?: string;
9
+ /** ADSP tenant name (e.g. 'my-org'). Required when tenantRealm is provided. */
10
+ tenant?: string;
7
11
  }
8
12
 
9
13
  export interface NormalizedSchema extends Schema {
@@ -35,6 +35,16 @@
35
35
  "type": "string",
36
36
  "description": "Access token for retrieving configuration from ADSP APIs.",
37
37
  "alias": "at"
38
+ },
39
+ "tenantRealm": {
40
+ "type": "string",
41
+ "description": "Keycloak realm UUID. Optional when --tenant is provided \u2014 overrides the realm looked up from the tenant service.",
42
+ "alias": "tr"
43
+ },
44
+ "tenant": {
45
+ "type": "string",
46
+ "description": "ADSP tenant name. When provided, the realm is looked up anonymously and a single browser login is used instead of the full interactive flow.",
47
+ "alias": "t"
38
48
  }
39
49
  },
40
50
  "required": [
@@ -42,4 +52,4 @@
42
52
  "env"
43
53
  ],
44
54
  "additionalProperties": false
45
- }
55
+ }
@@ -14,6 +14,7 @@ utilsMock.getAdspConfiguration.mockResolvedValue({
14
14
  directoryServiceUrl: environments.test.directoryServiceUrl,
15
15
  });
16
16
  utilsMock.deploymentGenerator.mockResolvedValue(undefined);
17
+ utilsMock.realmLogin.mockResolvedValue('test-token');
17
18
 
18
19
  describe('MEAN Generator', () => {
19
20
  const options: Schema = {
@@ -14,6 +14,7 @@ utilsMock.getAdspConfiguration.mockResolvedValue({
14
14
  accessServiceUrl: environments.test.accessServiceUrl,
15
15
  directoryServiceUrl: environments.test.directoryServiceUrl,
16
16
  });
17
+ utilsMock.realmLogin.mockResolvedValue('test-token');
17
18
 
18
19
  describe('MERN Generator', () => {
19
20
  const options: Schema = {
@@ -14,6 +14,10 @@ export interface CapabilitySpec {
14
14
  */
15
15
  export interface AgentResult {
16
16
  filesWritten: number;
17
+ /** True when the user was in an active conversation before it ended. */
18
+ userInteracted: boolean;
19
+ /** True when the conversation ended via Ctrl+C (SIGINT). */
20
+ interrupted?: boolean;
17
21
  }
18
22
  /**
19
23
  * Connect to the ADSP agent-service and conduct a multi-turn conversation
@@ -6,7 +6,7 @@ const readline_1 = require("readline");
6
6
  const socket_io_client_1 = require("socket.io-client");
7
7
  const nx_oc_1 = require("@abgov/nx-oc");
8
8
  const AGENT_SERVICE_URN = 'urn:ads:platform:agent-service:v1';
9
- const AGENT_ID = 'nx-adsp-agent';
9
+ const AGENT_ID = 'nxAdspAgent';
10
10
  /**
11
11
  * Connect to the ADSP agent-service and conduct a multi-turn conversation
12
12
  * with the nx-adsp-agent. The agent uses its workspace tools to write
@@ -18,13 +18,22 @@ const AGENT_ID = 'nx-adsp-agent';
18
18
  */
19
19
  function consultAgent(directoryServiceUrl, accessToken, projectContext, host, projectRoot) {
20
20
  return tslib_1.__awaiter(this, void 0, void 0, function* () {
21
+ if (!accessToken) {
22
+ process.stdout.write('\n[nx-adsp] No access token — skipping agent interaction.\n');
23
+ return null;
24
+ }
21
25
  const agentServiceUrl = yield resolveAgentServiceUrl(directoryServiceUrl);
22
26
  if (!agentServiceUrl) {
27
+ process.stdout.write('\n[nx-adsp] Agent-service not found in directory — skipping agent interaction.\n');
23
28
  return null;
24
29
  }
30
+ process.stdout.write(`\n[nx-adsp] Connecting to agent at ${agentServiceUrl}...\n`);
25
31
  return new Promise((resolve) => {
26
32
  const socket = (0, socket_io_client_1.io)(agentServiceUrl, {
27
33
  auth: { token: accessToken },
34
+ // Skip polling — go directly to WebSocket to avoid ARO ingress rejecting
35
+ // the polling POST with HTTP 400.
36
+ transports: ['websocket'],
28
37
  timeout: 30000,
29
38
  reconnection: false,
30
39
  });
@@ -32,20 +41,25 @@ function consultAgent(directoryServiceUrl, accessToken, projectContext, host, pr
32
41
  const threadId = crypto.randomUUID();
33
42
  let buffer = '';
34
43
  let conversationDone = false;
35
- // If stdin closes while waiting for user input, apply whatever the agent
36
- // wrote to the workspace and continue generation.
44
+ let agentHasResponded = false;
45
+ let interrupted = false;
46
+ // Ctrl+C — abort the interaction without applying generated files.
47
+ rl.on('SIGINT', () => {
48
+ interrupted = true;
49
+ process.stdout.write('\n');
50
+ cleanup(0);
51
+ });
52
+ // Ctrl+D / stdin EOF — intentional finish: fetch workspace and apply files.
37
53
  rl.on('close', () => {
38
- if (!conversationDone) {
54
+ if (!conversationDone && !interrupted) {
39
55
  requestWorkspaceState();
40
56
  }
41
57
  });
42
58
  const buildInitialMessage = () => {
43
- const fileSection = Object.entries(projectContext.existingFiles)
44
- .map(([path, content]) => `${path}:\n\`\`\`typescript\n${content}\n\`\`\``)
45
- .join('\n\n');
59
+ const fileNames = Object.keys(projectContext.existingFiles).join(', ');
46
60
  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` +
61
+ `for ADSP tenant "${projectContext.tenant}" (nx-adsp plugin version ${projectContext.pluginVersion}). ` +
62
+ `The project files (${fileNames}) have been uploaded to your workspace. ` +
49
63
  `What ADSP capabilities would be useful to integrate into this service?`);
50
64
  };
51
65
  const sendMessage = (content) => {
@@ -59,6 +73,17 @@ function consultAgent(directoryServiceUrl, accessToken, projectContext, host, pr
59
73
  const requestWorkspaceState = () => {
60
74
  socket.emit('workspace-read', { agent: AGENT_ID, threadId });
61
75
  };
76
+ const uploadFilesToWorkspace = () => {
77
+ socket.emit('workspace-update', {
78
+ agent: AGENT_ID,
79
+ threadId,
80
+ writes: Object.entries(projectContext.existingFiles).map(([path, content]) => ({
81
+ path,
82
+ content,
83
+ })),
84
+ deletes: [],
85
+ });
86
+ };
62
87
  const promptUser = () => {
63
88
  rl.question('\n> ', (input) => {
64
89
  const trimmed = input.trim();
@@ -66,14 +91,20 @@ function consultAgent(directoryServiceUrl, accessToken, projectContext, host, pr
66
91
  sendMessage(trimmed);
67
92
  }
68
93
  else {
69
- // Empty input — user is skipping; resolve without files.
70
- cleanup(0);
94
+ // Empty input — apply whatever the agent has generated.
95
+ requestWorkspaceState();
71
96
  }
72
97
  });
73
98
  };
74
99
  const applyWorkspaceFiles = (files) => {
75
100
  let count = 0;
76
101
  for (const file of files) {
102
+ // Skip files we uploaded that the agent hasn't modified — only apply
103
+ // new files and agent-modified versions of existing files.
104
+ const original = projectContext.existingFiles[file.path];
105
+ if (original !== undefined && original === file.content) {
106
+ continue;
107
+ }
77
108
  const fullPath = `${projectRoot}/${file.path}`;
78
109
  host.write(fullPath, file.content);
79
110
  count++;
@@ -84,16 +115,49 @@ function consultAgent(directoryServiceUrl, accessToken, projectContext, host, pr
84
115
  conversationDone = true;
85
116
  rl.close();
86
117
  socket.disconnect();
87
- resolve(filesWritten > 0 ? { filesWritten } : null);
118
+ // Return null only for truly silent skips (no agent, no token, connection failed).
119
+ // Return a result whenever the user was actively engaged (agent responded)
120
+ // OR when they explicitly interrupted (Ctrl+C), so the caller always gets
121
+ // a chance to confirm before proceeding.
122
+ resolve(agentHasResponded || interrupted
123
+ ? { filesWritten, userInteracted: agentHasResponded, interrupted }
124
+ : null);
88
125
  };
89
- socket.on('connect', () => {
126
+ socket.on('workspace-updated', () => {
127
+ process.stdout.write('[nx-adsp] Project files uploaded to workspace.\n');
128
+ process.stdout.write('[nx-adsp] Type your replies at the > prompt. Press Ctrl+D or leave blank to apply generated files.\n\n');
90
129
  sendMessage(buildInitialMessage());
91
130
  });
131
+ socket.on('connect', () => {
132
+ process.stdout.write('[nx-adsp] Connected to agent-service.\n');
133
+ process.stdout.write('[nx-adsp] Uploading project files to workspace...\n');
134
+ uploadFilesToWorkspace();
135
+ // Show periodic dots while waiting for first response, then a warning at 2 minutes.
136
+ const dotInterval = setInterval(() => {
137
+ if (!conversationDone && !agentHasResponded)
138
+ process.stdout.write('.');
139
+ else
140
+ clearInterval(dotInterval);
141
+ }, 3000);
142
+ setTimeout(() => {
143
+ clearInterval(dotInterval);
144
+ if (!conversationDone && !agentHasResponded) {
145
+ process.stdout.write('\n[nx-adsp] No response after 2 minutes. ' +
146
+ 'The nxAdspAgent may still be deploying or the LLM is unresponsive. ' +
147
+ 'Press Ctrl+C to skip and continue generation.\n');
148
+ }
149
+ }, 120000);
150
+ });
92
151
  socket.on('stream', ({ chunk, done }) => {
93
152
  var _a, _b;
94
153
  if ((chunk === null || chunk === void 0 ? void 0 : chunk.type) === 'text-delta') {
95
154
  const text = (_b = (_a = chunk.payload) === null || _a === void 0 ? void 0 : _a.text) !== null && _b !== void 0 ? _b : '';
155
+ if (!agentHasResponded) {
156
+ // Clear the dots line before the first response starts.
157
+ process.stdout.write('\n');
158
+ }
96
159
  buffer += text;
160
+ agentHasResponded = true;
97
161
  process.stdout.write(text);
98
162
  }
99
163
  if (done) {
@@ -101,39 +165,53 @@ function consultAgent(directoryServiceUrl, accessToken, projectContext, host, pr
101
165
  process.stdout.write('\n');
102
166
  }
103
167
  buffer = '';
104
- // Agent has finished its responserequest workspace state to get
105
- // any files it wrote, or ask user for follow-up if needed.
106
- requestWorkspaceState();
168
+ // Agent finished its turnprompt the user to continue the conversation
169
+ // or press Enter to apply whatever the agent has written so far.
170
+ promptUser();
107
171
  }
108
172
  });
109
173
  socket.on('workspace-state', ({ files }) => {
110
- if ((files === null || files === void 0 ? void 0 : files.length) > 0) {
111
- const written = applyWorkspaceFiles(files);
174
+ // Only apply files the agent added or modified not unchanged uploaded files.
175
+ const written = applyWorkspaceFiles(files !== null && files !== void 0 ? files : []);
176
+ if (written > 0) {
112
177
  process.stdout.write(`\nApplied ${written} file(s) from agent workspace.\n`);
113
- cleanup(written);
114
178
  }
115
179
  else {
116
- // No files written yet agent may need more input.
117
- promptUser();
180
+ process.stdout.write('\nNo files generated by agent.\n');
118
181
  }
182
+ cleanup(written);
119
183
  });
120
184
  socket.on('session-expired', () => {
121
185
  process.stdout.write('\nAgent session expired.\n');
122
186
  requestWorkspaceState();
123
187
  });
124
- socket.on('connect_error', () => cleanup(0));
125
- socket.on('error', () => requestWorkspaceState());
188
+ socket.on('connect_error', (err) => {
189
+ var _a, _b, _c, _d;
190
+ // Log the full error object so we can diagnose the root cause.
191
+ const errAny = err;
192
+ process.stdout.write(`\n[nx-adsp] Connection failed: ${(_a = err === null || err === void 0 ? void 0 : err.message) !== null && _a !== void 0 ? _a : err}\n`);
193
+ process.stdout.write(`[nx-adsp] Error detail: ${JSON.stringify((_d = (_c = (_b = errAny === null || errAny === void 0 ? void 0 : errAny.description) !== null && _b !== void 0 ? _b : errAny === null || errAny === void 0 ? void 0 : errAny.context) !== null && _c !== void 0 ? _c : errAny === null || errAny === void 0 ? void 0 : errAny.cause) !== null && _d !== void 0 ? _d : 'none')}\n`);
194
+ cleanup(0);
195
+ });
196
+ socket.on('error', (err) => {
197
+ process.stdout.write(`\n[nx-adsp] Agent error: ${JSON.stringify(err)}\n`);
198
+ requestWorkspaceState();
199
+ });
126
200
  });
127
201
  });
128
202
  }
129
203
  function resolveAgentServiceUrl(directoryServiceUrl) {
130
204
  return tslib_1.__awaiter(this, void 0, void 0, function* () {
131
- var _a;
132
205
  try {
133
206
  const urls = yield (0, nx_oc_1.getServiceUrls)(directoryServiceUrl);
134
- return (_a = urls[AGENT_SERVICE_URN]) !== null && _a !== void 0 ? _a : null;
207
+ const apiUrl = urls[AGENT_SERVICE_URN];
208
+ if (!apiUrl)
209
+ return null;
210
+ // The directory URL includes the REST API path (e.g. /agent/v1).
211
+ // Socket.io attaches at the server root, so use only the origin.
212
+ return new URL(apiUrl).origin;
135
213
  }
136
- catch (_b) {
214
+ catch (_a) {
137
215
  return null;
138
216
  }
139
217
  });
@@ -1 +1 @@
1
- {"version":3,"file":"agent.js","sourceRoot":"","sources":["../../../../../packages/nx-adsp/src/utils/agent.ts"],"names":[],"mappings":";;AAsCA,oCAuIC;;AA7KD,uCAA2C;AAE3C,uDAAsC;AACtC,wCAA8C;AAE9C,MAAM,iBAAiB,GAAG,mCAAmC,CAAC;AAC9D,MAAM,QAAQ,GAAG,eAAe,CAAC;AAuBjC;;;;;;;;GAQG;AACH,SAAsB,YAAY,CAChC,mBAA2B,EAC3B,WAAmB,EACnB,cAOC,EACD,IAAU,EACV,WAAmB;;QAEnB,MAAM,eAAe,GAAG,MAAM,sBAAsB,CAAC,mBAAmB,CAAC,CAAC;QAC1E,IAAI,CAAC,eAAe,EAAE,CAAC;YACrB,OAAO,IAAI,CAAC;QACd,CAAC;QAED,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;YAC7B,MAAM,MAAM,GAAG,IAAA,qBAAE,EAAC,eAAe,EAAE;gBACjC,IAAI,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE;gBAC5B,OAAO,EAAE,KAAK;gBACd,YAAY,EAAE,KAAK;aACpB,CAAC,CAAC;YAEH,MAAM,EAAE,GAAG,IAAA,0BAAe,EAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;YAC7E,MAAM,QAAQ,GAAG,MAAM,CAAC,UAAU,EAAE,CAAC;YACrC,IAAI,MAAM,GAAG,EAAE,CAAC;YAChB,IAAI,gBAAgB,GAAG,KAAK,CAAC;YAE7B,yEAAyE;YACzE,kDAAkD;YAClD,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;gBAClB,IAAI,CAAC,gBAAgB,EAAE,CAAC;oBACtB,qBAAqB,EAAE,CAAC;gBAC1B,CAAC;YACH,CAAC,CAAC,CAAC;YAEH,MAAM,mBAAmB,GAAG,GAAG,EAAE;gBAC/B,MAAM,WAAW,GAAG,MAAM,CAAC,OAAO,CAAC,cAAc,CAAC,aAAa,CAAC;qBAC7D,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,OAAO,CAAC,EAAE,EAAE,CAAC,GAAG,IAAI,wBAAwB,OAAO,UAAU,CAAC;qBAC1E,IAAI,CAAC,MAAM,CAAC,CAAC;gBAEhB,OAAO,CACL,yBAAyB,cAAc,CAAC,WAAW,YAAY,cAAc,CAAC,WAAW,IAAI;oBAC7F,oBAAoB,cAAc,CAAC,MAAM,6BAA6B,cAAc,CAAC,aAAa,QAAQ;oBAC1G,8BAA8B,WAAW,MAAM;oBAC/C,wEAAwE,CACzE,CAAC;YACJ,CAAC,CAAC;YAEF,MAAM,WAAW,GAAG,CAAC,OAAe,EAAE,EAAE;gBACtC,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE;oBACrB,KAAK,EAAE,QAAQ;oBACf,QAAQ;oBACR,OAAO;oBACP,SAAS,EAAE,IAAI;iBAChB,CAAC,CAAC;YACL,CAAC,CAAC;YAEF,MAAM,qBAAqB,GAAG,GAAG,EAAE;gBACjC,MAAM,CAAC,IAAI,CAAC,gBAAgB,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,CAAC;YAC/D,CAAC,CAAC;YAEF,MAAM,UAAU,GAAG,GAAG,EAAE;gBACtB,EAAE,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE;oBAC5B,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;oBAC7B,IAAI,OAAO,EAAE,CAAC;wBACZ,WAAW,CAAC,OAAO,CAAC,CAAC;oBACvB,CAAC;yBAAM,CAAC;wBACN,yDAAyD;wBACzD,OAAO,CAAC,CAAC,CAAC,CAAC;oBACb,CAAC;gBACH,CAAC,CAAC,CAAC;YACL,CAAC,CAAC;YAEF,MAAM,mBAAmB,GAAG,CAAC,KAA0C,EAAE,EAAE;gBACzE,IAAI,KAAK,GAAG,CAAC,CAAC;gBACd,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;oBACzB,MAAM,QAAQ,GAAG,GAAG,WAAW,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;oBAC/C,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;oBACnC,KAAK,EAAE,CAAC;gBACV,CAAC;gBACD,OAAO,KAAK,CAAC;YACf,CAAC,CAAC;YAEF,MAAM,OAAO,GAAG,CAAC,YAAoB,EAAE,EAAE;gBACvC,gBAAgB,GAAG,IAAI,CAAC;gBACxB,EAAE,CAAC,KAAK,EAAE,CAAC;gBACX,MAAM,CAAC,UAAU,EAAE,CAAC;gBACpB,OAAO,CAAC,YAAY,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;YACtD,CAAC,CAAC;YAEF,MAAM,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE;gBACxB,WAAW,CAAC,mBAAmB,EAAE,CAAC,CAAC;YACrC,CAAC,CAAC,CAAC;YAEH,MAAM,CAAC,EAAE,CAAC,QAAQ,EAAE,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,EAAE;;gBACtC,IAAI,CAAA,KAAK,aAAL,KAAK,uBAAL,KAAK,CAAE,IAAI,MAAK,YAAY,EAAE,CAAC;oBACjC,MAAM,IAAI,GAAW,MAAA,MAAA,KAAK,CAAC,OAAO,0CAAE,IAAI,mCAAI,EAAE,CAAC;oBAC/C,MAAM,IAAI,IAAI,CAAC;oBACf,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;gBAC7B,CAAC;gBAED,IAAI,IAAI,EAAE,CAAC;oBACT,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;wBAChD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;oBAC7B,CAAC;oBACD,MAAM,GAAG,EAAE,CAAC;oBACZ,mEAAmE;oBACnE,2DAA2D;oBAC3D,qBAAqB,EAAE,CAAC;gBAC1B,CAAC;YACH,CAAC,CAAC,CAAC;YAEH,MAAM,CAAC,EAAE,CAAC,iBAAiB,EAAE,CAAC,EAAE,KAAK,EAAkD,EAAE,EAAE;gBACzF,IAAI,CAAA,KAAK,aAAL,KAAK,uBAAL,KAAK,CAAE,MAAM,IAAG,CAAC,EAAE,CAAC;oBACtB,MAAM,OAAO,GAAG,mBAAmB,CAAC,KAAK,CAAC,CAAC;oBAC3C,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,aAAa,OAAO,kCAAkC,CAAC,CAAC;oBAC7E,OAAO,CAAC,OAAO,CAAC,CAAC;gBACnB,CAAC;qBAAM,CAAC;oBACN,oDAAoD;oBACpD,UAAU,EAAE,CAAC;gBACf,CAAC;YACH,CAAC,CAAC,CAAC;YAEH,MAAM,CAAC,EAAE,CAAC,iBAAiB,EAAE,GAAG,EAAE;gBAChC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,4BAA4B,CAAC,CAAC;gBACnD,qBAAqB,EAAE,CAAC;YAC1B,CAAC,CAAC,CAAC;YAEH,MAAM,CAAC,EAAE,CAAC,eAAe,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;YAC7C,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,qBAAqB,EAAE,CAAC,CAAC;QACpD,CAAC,CAAC,CAAC;IACL,CAAC;CAAA;AAED,SAAe,sBAAsB,CACnC,mBAA2B;;;QAE3B,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,IAAA,sBAAc,EAAC,mBAAmB,CAAC,CAAC;YACvD,OAAO,MAAA,IAAI,CAAC,iBAAiB,CAAC,mCAAI,IAAI,CAAC;QACzC,CAAC;QAAC,WAAM,CAAC;YACP,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;CAAA"}
1
+ {"version":3,"file":"agent.js","sourceRoot":"","sources":["../../../../../packages/nx-adsp/src/utils/agent.ts"],"names":[],"mappings":"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"}
@@ -80,6 +80,9 @@ describe('consultAgent', () => {
80
80
  await flushPromises();
81
81
 
82
82
  socket._handlers['connect']?.();
83
+ socket._handlers['workspace-updated']?.();
84
+ // Simulate agent responding with text (sets agentHasResponded = true)
85
+ socket._handlers['stream']?.({ chunk: { type: 'text-delta', payload: { text: 'I will add events.' } }, done: false });
83
86
  socket._handlers['stream']?.({ chunk: null, done: true });
84
87
  socket._handlers['workspace-state']?.({
85
88
  files: [
@@ -89,12 +92,12 @@ describe('consultAgent', () => {
89
92
  });
90
93
 
91
94
  const result = await resultPromise;
92
- expect(result).toEqual({ filesWritten: 2 });
95
+ expect(result).toEqual({ filesWritten: 2, userInteracted: true, interrupted: false });
93
96
  expect(mockHost.write).toHaveBeenCalledWith('apps/test-service/src/roles.ts', 'export enum ServiceRoles {}');
94
97
  expect(mockHost.write).toHaveBeenCalledWith('apps/test-service/src/main.ts', 'updated main.ts');
95
98
  });
96
99
 
97
- it('includes existing file content in the initial message', async () => {
100
+ it('uploads existing files to workspace before sending the initial message', async () => {
98
101
  mockedGetServiceUrls.mockResolvedValue({
99
102
  'urn:ads:platform:agent-service:v1': 'https://agent.example.com',
100
103
  });
@@ -102,15 +105,27 @@ describe('consultAgent', () => {
102
105
  const resultPromise = consultAgent('https://directory.example.com', 'token', PROJECT_CONTEXT, mockHost, 'apps/test-service');
103
106
  await flushPromises();
104
107
 
108
+ // connect → workspace-update emitted, then workspace-updated → message emitted
105
109
  socket._handlers['connect']?.();
110
+ socket._handlers['workspace-updated']?.();
111
+ socket._handlers['stream']?.({ chunk: { type: 'text-delta', payload: { text: 'What does this service do?' } }, done: false });
106
112
  socket._handlers['stream']?.({ chunk: null, done: true });
107
113
  socket._handlers['workspace-state']?.({ files: [] });
108
114
  await resultPromise;
109
115
 
116
+ expect(socket.emit).toHaveBeenCalledWith(
117
+ 'workspace-update',
118
+ expect.objectContaining({
119
+ agent: 'nxAdspAgent',
120
+ writes: expect.arrayContaining([
121
+ expect.objectContaining({ path: 'src/main.ts' }),
122
+ ]),
123
+ })
124
+ );
110
125
  expect(socket.emit).toHaveBeenCalledWith(
111
126
  'message',
112
127
  expect.objectContaining({
113
- agent: 'nx-adsp-agent',
128
+ agent: 'nxAdspAgent',
114
129
  content: expect.stringContaining('src/main.ts'),
115
130
  })
116
131
  );