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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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.1",
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,17 @@ 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
+ // getAdspConfiguration authenticates but doesn't return the token.
83
+ // Re-authenticate with the tenant realm to get a token for the agent call.
84
+ process.stdout.write('\nSigning in to connect to the ADSP agent...\n');
85
+ const accessToken = (_a = options.accessToken) !== null && _a !== void 0 ? _a : (yield (0, nx_oc_1.realmLogin)(normalizedOptions.adsp.accessServiceUrl, normalizedOptions.adsp.tenantRealm).catch((err) => {
86
+ var _a;
87
+ 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`);
88
+ return undefined;
89
+ }));
90
+ const mainTs = (_c = (_b = host.read(`${normalizedOptions.projectRoot}/src/main.ts`)) === null || _b === void 0 ? void 0 : _b.toString()) !== null && _c !== void 0 ? _c : '';
91
+ const environmentTs = (_e = (_d = host.read(`${normalizedOptions.projectRoot}/src/environment.ts`)) === null || _d === void 0 ? void 0 : _d.toString()) !== null && _e !== void 0 ? _e : '';
92
+ const agentResult = yield (0, agent_1.consultAgent)(normalizedOptions.adsp.directoryServiceUrl, accessToken, {
59
93
  projectName: normalizedOptions.projectName,
60
94
  projectType: 'express-service',
61
95
  tenant: normalizedOptions.adsp.tenant,
@@ -65,6 +99,20 @@ function default_1(host, options) {
65
99
  'src/environment.ts': environmentTs,
66
100
  },
67
101
  }, host, normalizedOptions.projectRoot);
102
+ // When the user was in an active conversation but it ended without the
103
+ // agent generating files, confirm whether to proceed with base scaffolding.
104
+ if ((agentResult === null || agentResult === void 0 ? void 0 : agentResult.userInteracted) && agentResult.filesWritten === 0) {
105
+ const { prompt } = yield Promise.resolve().then(() => require('enquirer'));
106
+ const { proceed } = yield prompt({
107
+ type: 'confirm',
108
+ name: 'proceed',
109
+ message: 'Agent interaction ended without generating files. Continue with base scaffolding?',
110
+ initial: true,
111
+ });
112
+ if (!proceed) {
113
+ throw new Error('Generation aborted.');
114
+ }
115
+ }
68
116
  }
69
117
  yield (0, nx_oc_1.deploymentGenerator)(host, Object.assign(Object.assign({}, normalizedOptions), { appType: 'node', project: normalizedOptions.projectName }));
70
118
  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":"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"}
@@ -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,8 @@ 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;
17
19
  }
18
20
  /**
19
21
  * 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,42 @@ 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 for silent skips (no agent, no token, connection failed).
119
+ // Return a result with userInteracted:true when the user was in a
120
+ // conversation so the caller can ask whether to proceed.
121
+ resolve(agentHasResponded ? { filesWritten, userInteracted: true } : null);
88
122
  };
89
- socket.on('connect', () => {
123
+ socket.on('workspace-updated', () => {
124
+ process.stdout.write('[nx-adsp] Project files uploaded to workspace.\n');
125
+ process.stdout.write('[nx-adsp] Type your replies at the > prompt. Press Ctrl+D or leave blank to apply generated files.\n\n');
90
126
  sendMessage(buildInitialMessage());
91
127
  });
128
+ socket.on('connect', () => {
129
+ process.stdout.write('[nx-adsp] Connected to agent-service.\n');
130
+ process.stdout.write('[nx-adsp] Uploading project files to workspace...\n');
131
+ uploadFilesToWorkspace();
132
+ // Show periodic dots while waiting for first response, then a warning at 2 minutes.
133
+ const dotInterval = setInterval(() => {
134
+ if (!conversationDone && !agentHasResponded)
135
+ process.stdout.write('.');
136
+ else
137
+ clearInterval(dotInterval);
138
+ }, 3000);
139
+ setTimeout(() => {
140
+ clearInterval(dotInterval);
141
+ if (!conversationDone && !agentHasResponded) {
142
+ process.stdout.write('\n[nx-adsp] No response after 2 minutes. ' +
143
+ 'The nxAdspAgent may still be deploying or the LLM is unresponsive. ' +
144
+ 'Press Ctrl+C to skip and continue generation.\n');
145
+ }
146
+ }, 120000);
147
+ });
92
148
  socket.on('stream', ({ chunk, done }) => {
93
149
  var _a, _b;
94
150
  if ((chunk === null || chunk === void 0 ? void 0 : chunk.type) === 'text-delta') {
95
151
  const text = (_b = (_a = chunk.payload) === null || _a === void 0 ? void 0 : _a.text) !== null && _b !== void 0 ? _b : '';
96
152
  buffer += text;
153
+ agentHasResponded = true;
97
154
  process.stdout.write(text);
98
155
  }
99
156
  if (done) {
@@ -101,39 +158,53 @@ function consultAgent(directoryServiceUrl, accessToken, projectContext, host, pr
101
158
  process.stdout.write('\n');
102
159
  }
103
160
  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();
161
+ // Agent finished its turnprompt the user to continue the conversation
162
+ // or press Enter to apply whatever the agent has written so far.
163
+ promptUser();
107
164
  }
108
165
  });
109
166
  socket.on('workspace-state', ({ files }) => {
110
- if ((files === null || files === void 0 ? void 0 : files.length) > 0) {
111
- const written = applyWorkspaceFiles(files);
167
+ // Only apply files the agent added or modified not unchanged uploaded files.
168
+ const written = applyWorkspaceFiles(files !== null && files !== void 0 ? files : []);
169
+ if (written > 0) {
112
170
  process.stdout.write(`\nApplied ${written} file(s) from agent workspace.\n`);
113
- cleanup(written);
114
171
  }
115
172
  else {
116
- // No files written yet agent may need more input.
117
- promptUser();
173
+ process.stdout.write('\nNo files generated by agent.\n');
118
174
  }
175
+ cleanup(written);
119
176
  });
120
177
  socket.on('session-expired', () => {
121
178
  process.stdout.write('\nAgent session expired.\n');
122
179
  requestWorkspaceState();
123
180
  });
124
- socket.on('connect_error', () => cleanup(0));
125
- socket.on('error', () => requestWorkspaceState());
181
+ socket.on('connect_error', (err) => {
182
+ var _a, _b, _c, _d;
183
+ // Log the full error object so we can diagnose the root cause.
184
+ const errAny = err;
185
+ 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`);
186
+ 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`);
187
+ cleanup(0);
188
+ });
189
+ socket.on('error', (err) => {
190
+ process.stdout.write(`\n[nx-adsp] Agent error: ${JSON.stringify(err)}\n`);
191
+ requestWorkspaceState();
192
+ });
126
193
  });
127
194
  });
128
195
  }
129
196
  function resolveAgentServiceUrl(directoryServiceUrl) {
130
197
  return tslib_1.__awaiter(this, void 0, void 0, function* () {
131
- var _a;
132
198
  try {
133
199
  const urls = yield (0, nx_oc_1.getServiceUrls)(directoryServiceUrl);
134
- return (_a = urls[AGENT_SERVICE_URN]) !== null && _a !== void 0 ? _a : null;
200
+ const apiUrl = urls[AGENT_SERVICE_URN];
201
+ if (!apiUrl)
202
+ return null;
203
+ // The directory URL includes the REST API path (e.g. /agent/v1).
204
+ // Socket.io attaches at the server root, so use only the origin.
205
+ return new URL(apiUrl).origin;
135
206
  }
136
- catch (_b) {
207
+ catch (_a) {
137
208
  return null;
138
209
  }
139
210
  });
@@ -1 +1 @@
1
- {"version":3,"file":"agent.js","sourceRoot":"","sources":["../../../../../packages/nx-adsp/src/utils/agent.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"agent.js","sourceRoot":"","sources":["../../../../../packages/nx-adsp/src/utils/agent.ts"],"names":[],"mappings":";;AAwCA,oCA8MC;;AAtPD,uCAA2C;AAE3C,uDAAsC;AACtC,wCAA8C;AAE9C,MAAM,iBAAiB,GAAG,mCAAmC,CAAC;AAC9D,MAAM,QAAQ,GAAG,aAAa,CAAC;AAyB/B;;;;;;;;GAQG;AACH,SAAsB,YAAY,CAChC,mBAA2B,EAC3B,WAAmB,EACnB,cAOC,EACD,IAAU,EACV,WAAmB;;QAEnB,IAAI,CAAC,WAAW,EAAE,CAAC;YACjB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,6DAA6D,CAAC,CAAC;YACpF,OAAO,IAAI,CAAC;QACd,CAAC;QAED,MAAM,eAAe,GAAG,MAAM,sBAAsB,CAAC,mBAAmB,CAAC,CAAC;QAC1E,IAAI,CAAC,eAAe,EAAE,CAAC;YACrB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,kFAAkF,CAAC,CAAC;YACzG,OAAO,IAAI,CAAC;QACd,CAAC;QAED,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,sCAAsC,eAAe,OAAO,CAAC,CAAC;QAEnF,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,yEAAyE;gBACzE,kCAAkC;gBAClC,UAAU,EAAE,CAAC,WAAW,CAAC;gBACzB,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;YAC7B,IAAI,iBAAiB,GAAG,KAAK,CAAC;YAC9B,IAAI,WAAW,GAAG,KAAK,CAAC;YAExB,mEAAmE;YACnE,EAAE,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE;gBACnB,WAAW,GAAG,IAAI,CAAC;gBACnB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;gBAC3B,OAAO,CAAC,CAAC,CAAC,CAAC;YACb,CAAC,CAAC,CAAC;YAEH,4EAA4E;YAC5E,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;gBAClB,IAAI,CAAC,gBAAgB,IAAI,CAAC,WAAW,EAAE,CAAC;oBACtC,qBAAqB,EAAE,CAAC;gBAC1B,CAAC;YACH,CAAC,CAAC,CAAC;YAEH,MAAM,mBAAmB,GAAG,GAAG,EAAE;gBAC/B,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,cAAc,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBACvE,OAAO,CACL,yBAAyB,cAAc,CAAC,WAAW,YAAY,cAAc,CAAC,WAAW,IAAI;oBAC7F,oBAAoB,cAAc,CAAC,MAAM,6BAA6B,cAAc,CAAC,aAAa,KAAK;oBACvG,sBAAsB,SAAS,0CAA0C;oBACzE,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,sBAAsB,GAAG,GAAG,EAAE;gBAClC,MAAM,CAAC,IAAI,CAAC,kBAAkB,EAAE;oBAC9B,KAAK,EAAE,QAAQ;oBACf,QAAQ;oBACR,MAAM,EAAE,MAAM,CAAC,OAAO,CAAC,cAAc,CAAC,aAAa,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,OAAO,CAAC,EAAE,EAAE,CAAC,CAAC;wBAC7E,IAAI;wBACJ,OAAO;qBACR,CAAC,CAAC;oBACH,OAAO,EAAE,EAAE;iBACZ,CAAC,CAAC;YACL,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,wDAAwD;wBACxD,qBAAqB,EAAE,CAAC;oBAC1B,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,qEAAqE;oBACrE,2DAA2D;oBAC3D,MAAM,QAAQ,GAAG,cAAc,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;oBACzD,IAAI,QAAQ,KAAK,SAAS,IAAI,QAAQ,KAAK,IAAI,CAAC,OAAO,EAAE,CAAC;wBACxD,SAAS;oBACX,CAAC;oBACD,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,wEAAwE;gBACxE,kEAAkE;gBAClE,yDAAyD;gBACzD,OAAO,CAAC,iBAAiB,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,cAAc,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;YAC7E,CAAC,CAAC;YAEF,MAAM,CAAC,EAAE,CAAC,mBAAmB,EAAE,GAAG,EAAE;gBAClC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,kDAAkD,CAAC,CAAC;gBACzE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,wGAAwG,CAAC,CAAC;gBAC/H,WAAW,CAAC,mBAAmB,EAAE,CAAC,CAAC;YACrC,CAAC,CAAC,CAAC;YAEH,MAAM,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE;gBACxB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,yCAAyC,CAAC,CAAC;gBAChE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,qDAAqD,CAAC,CAAC;gBAC5E,sBAAsB,EAAE,CAAC;gBAEzB,oFAAoF;gBACpF,MAAM,WAAW,GAAG,WAAW,CAAC,GAAG,EAAE;oBACnC,IAAI,CAAC,gBAAgB,IAAI,CAAC,iBAAiB;wBAAE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;;wBAClE,aAAa,CAAC,WAAW,CAAC,CAAC;gBAClC,CAAC,EAAE,IAAI,CAAC,CAAC;gBACT,UAAU,CAAC,GAAG,EAAE;oBACd,aAAa,CAAC,WAAW,CAAC,CAAC;oBAC3B,IAAI,CAAC,gBAAgB,IAAI,CAAC,iBAAiB,EAAE,CAAC;wBAC5C,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,2CAA2C;4BAC3C,qEAAqE;4BACrE,iDAAiD,CAClD,CAAC;oBACJ,CAAC;gBACH,CAAC,EAAE,MAAM,CAAC,CAAC;YACb,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,iBAAiB,GAAG,IAAI,CAAC;oBACzB,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,yEAAyE;oBACzE,iEAAiE;oBACjE,UAAU,EAAE,CAAC;gBACf,CAAC;YACH,CAAC,CAAC,CAAC;YAEH,MAAM,CAAC,EAAE,CAAC,iBAAiB,EAAE,CAAC,EAAE,KAAK,EAAkD,EAAE,EAAE;gBACzF,+EAA+E;gBAC/E,MAAM,OAAO,GAAG,mBAAmB,CAAC,KAAK,aAAL,KAAK,cAAL,KAAK,GAAI,EAAE,CAAC,CAAC;gBACjD,IAAI,OAAO,GAAG,CAAC,EAAE,CAAC;oBAChB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,aAAa,OAAO,kCAAkC,CAAC,CAAC;gBAC/E,CAAC;qBAAM,CAAC;oBACN,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,kCAAkC,CAAC,CAAC;gBAC3D,CAAC;gBACD,OAAO,CAAC,OAAO,CAAC,CAAC;YACnB,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,CAAC,GAAG,EAAE,EAAE;;gBACjC,+DAA+D;gBAC/D,MAAM,MAAM,GAAG,GAAyC,CAAC;gBACzD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,kCAAkC,MAAA,GAAG,aAAH,GAAG,uBAAH,GAAG,CAAE,OAAO,mCAAI,GAAG,IAAI,CAAC,CAAC;gBAChF,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,2BAA2B,IAAI,CAAC,SAAS,CAAC,MAAA,MAAA,MAAA,MAAM,aAAN,MAAM,uBAAN,MAAM,CAAE,WAAW,mCAAI,MAAM,aAAN,MAAM,uBAAN,MAAM,CAAE,OAAO,mCAAI,MAAM,aAAN,MAAM,uBAAN,MAAM,CAAE,KAAK,mCAAI,MAAM,CAAC,IAAI,CAAC,CAAC;gBACvI,OAAO,CAAC,CAAC,CAAC,CAAC;YACb,CAAC,CAAC,CAAC;YACH,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;gBACzB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,4BAA4B,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;gBAC1E,qBAAqB,EAAE,CAAC;YAC1B,CAAC,CAAC,CAAC;QACL,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,MAAM,MAAM,GAAG,IAAI,CAAC,iBAAiB,CAAC,CAAC;YACvC,IAAI,CAAC,MAAM;gBAAE,OAAO,IAAI,CAAC;YACzB,iEAAiE;YACjE,iEAAiE;YACjE,OAAO,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC;QAChC,CAAC;QAAC,WAAM,CAAC;YACP,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;CAAA"}
@@ -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 });
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
  );