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

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.
Files changed (35) hide show
  1. package/package.json +1 -1
  2. package/src/generators/angular-app/angular-app.js +24 -1
  3. package/src/generators/angular-app/angular-app.js.map +1 -1
  4. package/src/generators/angular-app/schema.d.ts +5 -0
  5. package/src/generators/angular-app/schema.json +10 -0
  6. package/src/generators/express-service/express-service.js +58 -10
  7. package/src/generators/express-service/express-service.js.map +1 -1
  8. package/src/generators/express-service/express-service.spec.ts +2 -0
  9. package/src/generators/express-service/schema.d.ts +6 -0
  10. package/src/generators/express-service/schema.json +11 -1
  11. package/src/generators/mean/mean.js +38 -5
  12. package/src/generators/mean/mean.js.map +1 -1
  13. package/src/generators/mean/mean.spec.ts +1 -0
  14. package/src/generators/mean/schema.d.ts +2 -0
  15. package/src/generators/mean/schema.json +10 -0
  16. package/src/generators/mern/mern.js +38 -5
  17. package/src/generators/mern/mern.js.map +1 -1
  18. package/src/generators/mern/mern.spec.ts +1 -0
  19. package/src/generators/mern/schema.d.ts +2 -0
  20. package/src/generators/mern/schema.json +10 -0
  21. package/src/generators/react-app/files/src/app/config.slice.ts__tmpl__ +2 -2
  22. package/src/generators/react-app/files/src/app/intake.slice.ts__tmpl__ +1 -1
  23. package/src/generators/react-app/files/src/app/start.slice.ts__tmpl__ +7 -7
  24. package/src/generators/react-app/files/src/app/user.slice.ts__tmpl__ +1 -1
  25. package/src/generators/react-app/react-app.js +24 -1
  26. package/src/generators/react-app/react-app.js.map +1 -1
  27. package/src/generators/react-app/schema.d.ts +4 -0
  28. package/src/generators/react-app/schema.json +10 -0
  29. package/src/utils/agent.d.ts +32 -2
  30. package/src/utils/agent.js +359 -93
  31. package/src/utils/agent.js.map +1 -1
  32. package/src/utils/agent.spec.ts +35 -3
  33. package/src/utils/plugin-version.d.ts +1 -0
  34. package/src/utils/plugin-version.js +7 -0
  35. package/src/utils/plugin-version.js.map +1 -0
@@ -1,139 +1,405 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.confirmAfterAgentInterrupt = confirmAfterAgentInterrupt;
3
4
  exports.consultAgent = consultAgent;
4
5
  const tslib_1 = require("tslib");
5
6
  const readline_1 = require("readline");
6
7
  const socket_io_client_1 = require("socket.io-client");
7
8
  const nx_oc_1 = require("@abgov/nx-oc");
8
9
  const AGENT_SERVICE_URN = 'urn:ads:platform:agent-service:v1';
9
- const AGENT_ID = 'nx-adsp-agent';
10
+ const AGENT_ID = 'nxAdspAgent';
11
+ /**
12
+ * After a consultAgent call, check whether the user interrupted before any
13
+ * files were generated and confirm they still want to proceed with the base
14
+ * scaffolding. Throws if the user declines, which aborts the Nx Tree commit.
15
+ */
16
+ function confirmAfterAgentInterrupt(result) {
17
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
18
+ if ((result === null || result === void 0 ? void 0 : result.interrupted) && result.filesWritten === 0) {
19
+ const { prompt } = yield Promise.resolve().then(() => require('enquirer'));
20
+ const { proceed } = yield prompt({
21
+ type: 'confirm',
22
+ name: 'proceed',
23
+ message: 'Agent interaction ended without generating files. Continue with base scaffolding?',
24
+ initial: false,
25
+ });
26
+ if (!proceed) {
27
+ throw new Error('Generation aborted.');
28
+ }
29
+ }
30
+ });
31
+ }
10
32
  /**
11
33
  * Connect to the ADSP agent-service and conduct a multi-turn conversation
12
34
  * with the nx-adsp-agent. The agent uses its workspace tools to write
13
35
  * generated and modified files; this function retrieves the workspace state
14
36
  * after the conversation and applies all files to the Nx Tree.
15
37
  *
38
+ * The socket connection and file upload start immediately. While the files
39
+ * are uploading, the developer is prompted for a brief project description.
40
+ * The initial message is sent as soon as both the upload and the description
41
+ * are ready — whichever finishes last.
42
+ *
16
43
  * Returns null if agent-service is unavailable — callers should skip the
17
44
  * agent step gracefully in that case.
18
45
  */
19
- function consultAgent(directoryServiceUrl, accessToken, projectContext, host, projectRoot) {
46
+ function consultAgent(directoryServiceUrl, accessToken, projectContext, host, projectRoot, options) {
20
47
  return tslib_1.__awaiter(this, void 0, void 0, function* () {
48
+ var _a, _b, _c;
49
+ if (!accessToken) {
50
+ process.stdout.write('\n[nx-adsp] No access token — skipping agent interaction.\n');
51
+ return null;
52
+ }
21
53
  const agentServiceUrl = yield resolveAgentServiceUrl(directoryServiceUrl);
22
54
  if (!agentServiceUrl) {
55
+ process.stdout.write('\n[nx-adsp] Agent-service not found in directory — skipping agent interaction.\n');
23
56
  return null;
24
57
  }
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,
58
+ const isContinuation = (_a = options === null || options === void 0 ? void 0 : options.isContinuation) !== null && _a !== void 0 ? _a : false;
59
+ const threadId = (_b = options === null || options === void 0 ? void 0 : options.threadId) !== null && _b !== void 0 ? _b : crypto.randomUUID();
60
+ process.stdout.write(`\n[nx-adsp] Connecting to agent at ${agentServiceUrl}...\n`);
61
+ // Start socket connection immediately so file upload overlaps with the description prompt.
62
+ const socket = (0, socket_io_client_1.io)(agentServiceUrl, {
63
+ auth: { token: accessToken },
64
+ // Skip polling — go directly to WebSocket to avoid ARO ingress rejecting
65
+ // the polling POST with HTTP 400.
66
+ transports: ['websocket'],
67
+ timeout: 30000,
68
+ reconnection: false,
69
+ });
70
+ // rl is created after the enquirer prompt (below) so that readline gets a
71
+ // clean stdin — enquirer sets raw mode on stdin and restoring it before
72
+ // readline attaches avoids the two libraries leaving stdin in a bad state.
73
+ let rl = null;
74
+ // Coordination: sendInitialMessage is called once BOTH conditions are met:
75
+ // 1. description prompt has been answered (descriptionReady = true)
76
+ // 2. workspace files have been uploaded (workspaceReady = true)
77
+ // Whichever condition is satisfied last triggers the send.
78
+ let description;
79
+ let descriptionReady = false;
80
+ let workspaceReady = false;
81
+ let conversationStarted = false;
82
+ let buffer = '';
83
+ let conversationDone = false;
84
+ let agentHasResponded = false;
85
+ let thinkingInterval = null;
86
+ let interrupted = false;
87
+ let resolveConversation;
88
+ const conversationPromise = new Promise((r) => {
89
+ resolveConversation = r;
90
+ });
91
+ // ANSI helpers — no-op when stdout is not a TTY (e.g. CI, piped output).
92
+ const DIM = process.stdout.isTTY ? '\x1b[2m' : '';
93
+ const RESET = process.stdout.isTTY ? '\x1b[0m' : '';
94
+ const startThinking = () => {
95
+ // Dim colour is left open so the dots inherit it; stopThinking resets.
96
+ process.stdout.write(`${DIM}[nx-adsp] Agent is thinking`);
97
+ thinkingInterval = setInterval(() => {
98
+ if (!conversationDone)
99
+ process.stdout.write('.');
100
+ else
101
+ stopThinking();
102
+ }, 1000);
103
+ };
104
+ const stopThinking = () => {
105
+ if (thinkingInterval) {
106
+ clearInterval(thinkingInterval);
107
+ thinkingInterval = null;
108
+ process.stdout.write(`${RESET}\n`);
109
+ }
110
+ };
111
+ const cleanup = (filesWritten) => {
112
+ conversationDone = true;
113
+ rl === null || rl === void 0 ? void 0 : rl.close();
114
+ socket.disconnect();
115
+ // Return null only for truly silent skips (no agent, no token, connection failed).
116
+ // Return a result whenever the user was actively engaged (agent responded)
117
+ // OR when they explicitly interrupted (Ctrl+C), so the caller always gets
118
+ // a chance to confirm before proceeding.
119
+ resolveConversation(agentHasResponded || interrupted
120
+ ? { filesWritten, userInteracted: agentHasResponded, interrupted }
121
+ : null);
122
+ };
123
+ const buildInitialMessage = () => {
124
+ const { projectType, projectName, tenant, pluginVersion } = projectContext;
125
+ const descriptionLine = description ? `It is described as: "${description}". ` : '';
126
+ if (isContinuation) {
127
+ const stackDetail = projectType === 'react-app'
128
+ ? 'It uses Redux Toolkit slices for state (store.ts, config.slice.ts, intake.slice.ts) and keycloak-js for authentication. '
129
+ : projectType === 'angular-app'
130
+ ? 'It uses Angular standalone components, HttpClient with includeBearerTokenInterceptor for authenticated requests, and keycloak-angular for authentication. '
131
+ : '';
132
+ const fileNames = Object.keys(projectContext.existingFiles).join(', ');
133
+ return (`I've also scaffolded a ${projectType} called "${projectName}" for the same tenant. ` +
134
+ stackDetail +
135
+ `The files (${fileNames}) have been uploaded to your workspace — please read them to understand the current structure before suggesting capabilities. ` +
136
+ `Based on our service discussion, what ADSP frontend integrations would be most useful?`);
137
+ }
138
+ if (projectType === 'mern' || projectType === 'mean') {
139
+ const frontendStack = projectType === 'mern'
140
+ ? 'React frontend using Redux Toolkit slices and keycloak-js'
141
+ : 'Angular frontend using standalone components and keycloak-angular';
142
+ const serviceFiles = Object.keys(projectContext.existingFiles)
143
+ .filter((f) => f.startsWith('service/'))
144
+ .join(', ');
145
+ const appFiles = Object.keys(projectContext.existingFiles)
146
+ .filter((f) => f.startsWith('app/'))
147
+ .join(', ');
148
+ return (`I am setting up a ${projectType.toUpperCase()} full-stack project called "${projectName}" ` +
149
+ `for ADSP tenant "${tenant}" (nx-adsp plugin version ${pluginVersion}). ` +
150
+ descriptionLine +
151
+ `It has an Express service ("${projectName}-service") and a ${frontendStack} ("${projectName}-app"). ` +
152
+ `Both have been uploaded to your workspace: service files (${serviceFiles}) and frontend files (${appFiles}). ` +
153
+ `When writing generated files, use the service/ prefix for backend files and the app/ prefix for frontend files. ` +
154
+ `What ADSP capabilities would be useful for this full-stack project?`);
155
+ }
156
+ const fileNames = Object.keys(projectContext.existingFiles).join(', ');
157
+ return (`I am setting up a new ${projectType} called "${projectName}" ` +
158
+ `for ADSP tenant "${tenant}" (nx-adsp plugin version ${pluginVersion}). ` +
159
+ descriptionLine +
160
+ `The project files (${fileNames}) have been uploaded to your workspace. ` +
161
+ `What ADSP capabilities would be useful to integrate into this service?`);
162
+ };
163
+ const sendInitialMessage = () => {
164
+ if (conversationStarted)
165
+ return;
166
+ conversationStarted = true;
167
+ process.stdout.write('[nx-adsp] Type your replies at the > prompt. Press Ctrl+D or leave blank to apply generated files.\n\n');
168
+ socket.emit('message', {
169
+ agent: AGENT_ID,
170
+ threadId,
171
+ content: buildInitialMessage(),
172
+ rawChunks: true,
30
173
  });
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) {
174
+ startThinking();
175
+ setTimeout(() => {
176
+ stopThinking();
177
+ if (!conversationDone && !agentHasResponded) {
178
+ process.stdout.write('\n[nx-adsp] No response after 2 minutes. ' +
179
+ 'The nxAdspAgent may still be deploying or the LLM is unresponsive. ' +
180
+ 'Press Ctrl+C to skip and continue generation.\n');
181
+ }
182
+ }, 120000);
183
+ };
184
+ const requestWorkspaceState = () => {
185
+ socket.emit('workspace-read', { agent: AGENT_ID, threadId });
186
+ };
187
+ const promptUser = () => {
188
+ rl.question('\n> ', (input) => {
189
+ const trimmed = input.trim();
190
+ if (trimmed) {
191
+ socket.emit('message', {
192
+ agent: AGENT_ID,
193
+ threadId,
194
+ content: trimmed,
195
+ rawChunks: true,
196
+ });
197
+ startThinking();
198
+ }
199
+ else {
200
+ // Empty input — apply whatever the agent has generated.
39
201
  requestWorkspaceState();
40
202
  }
41
203
  });
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);
204
+ };
205
+ const applyWorkspaceFiles = (files) => {
206
+ let count = 0;
207
+ for (const file of files) {
208
+ // Skip files we uploaded that the agent hasn't modified.
209
+ const original = projectContext.existingFiles[file.path];
210
+ if (original !== undefined && original === file.content) {
211
+ continue;
212
+ }
213
+ // Route to the correct project root. For composite generators (mern, mean)
214
+ // files are prefixed with 'service/' or 'app/' to distinguish the two parts.
215
+ let targetRoot = projectRoot;
216
+ let relativePath = file.path;
217
+ if (options === null || options === void 0 ? void 0 : options.additionalRoots) {
218
+ for (const [prefix, root] of Object.entries(options.additionalRoots)) {
219
+ if (file.path.startsWith(prefix + '/')) {
220
+ targetRoot = root;
221
+ relativePath = file.path.slice(prefix.length + 1);
222
+ break;
223
+ }
67
224
  }
68
- else {
69
- // Empty input user is skipping; resolve without files.
70
- cleanup(0);
225
+ // Also strip the default 'service/' prefix when it matches projectRoot
226
+ if (relativePath === file.path && file.path.startsWith('service/')) {
227
+ relativePath = file.path.slice('service/'.length);
71
228
  }
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
229
  }
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());
230
+ host.write(`${targetRoot}/${relativePath}`, file.content);
231
+ count++;
232
+ }
233
+ return count;
234
+ };
235
+ // Register socket handlers upfront so they are active while the description
236
+ // prompt is being shown. readline handlers are registered after the prompt
237
+ // (see below) to avoid enquirer leaving stdin in a bad state.
238
+ socket.on('connect', () => {
239
+ // Connect and upload silently — stdout writes here would interleave with
240
+ // the description prompt that is active concurrently.
241
+ // The server signals handler readiness via socket.send() after its async
242
+ // getServiceConfiguration() resolves; wait for that before uploading.
243
+ socket.once('message', () => {
244
+ socket.emit('workspace-update', {
245
+ agent: AGENT_ID,
246
+ threadId,
247
+ writes: Object.entries(projectContext.existingFiles).map(([path, content]) => ({
248
+ path,
249
+ content,
250
+ })),
251
+ deletes: [],
252
+ });
91
253
  });
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);
254
+ });
255
+ socket.on('workspace-updated', () => {
256
+ workspaceReady = true;
257
+ if (descriptionReady) {
258
+ // Description was entered before upload finished — send now.
259
+ sendInitialMessage();
260
+ }
261
+ // else: description prompt is still open — post-prompt code will call sendInitialMessage.
262
+ });
263
+ const describeToolCall = (toolName, args) => {
264
+ switch (toolName) {
265
+ // Workspace tools (Mastra built-in names)
266
+ case 'mastra_workspace_write_file': return `Writing: ${args['path']}`;
267
+ case 'mastra_workspace_edit_file': return `Editing: ${args['path']}`;
268
+ case 'mastra_workspace_read_file': return `Reading: ${args['path']}`;
269
+ case 'mastra_workspace_list_files': return `Listing workspace files`;
270
+ // nx-adsp template tools (Mastra uses the agent registration key, not the tool id)
271
+ case 'listNxAdspTemplatesTool': return `Listing available ADSP templates`;
272
+ case 'getNxAdspTemplateTool': return `Getting template: ${args['templateId']}`;
273
+ default: return `Tool: ${toolName}`;
274
+ }
275
+ };
276
+ let firstDeltaOfTurn = true;
277
+ socket.on('stream', ({ chunk, done }) => {
278
+ var _a, _b, _c;
279
+ if ((chunk === null || chunk === void 0 ? void 0 : chunk.type) === 'tool-call') {
280
+ const p = chunk.payload;
281
+ if (p === null || p === void 0 ? void 0 : p.toolName) {
282
+ stopThinking();
283
+ process.stdout.write(`${DIM}[nx-adsp] ${describeToolCall(p.toolName, (_a = p.args) !== null && _a !== void 0 ? _a : {})}${RESET}\n`);
98
284
  }
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();
285
+ }
286
+ if ((chunk === null || chunk === void 0 ? void 0 : chunk.type) === 'text-delta') {
287
+ const text = (_c = (_b = chunk.payload) === null || _b === void 0 ? void 0 : _b.text) !== null && _c !== void 0 ? _c : '';
288
+ stopThinking();
289
+ if (firstDeltaOfTurn) {
290
+ // Blank line between the status/tool output and the agent's response text.
291
+ process.stdout.write('\n');
292
+ firstDeltaOfTurn = false;
107
293
  }
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);
294
+ buffer += text;
295
+ agentHasResponded = true;
296
+ process.stdout.write(text);
297
+ }
298
+ if (done) {
299
+ if (buffer.length > 0 && !buffer.endsWith('\n')) {
300
+ process.stdout.write('\n');
114
301
  }
115
- else {
116
- // No files written yet agent may need more input.
117
- promptUser();
302
+ if (firstDeltaOfTurn) {
303
+ // Agent completed its turn (possibly doing tool work) without sending any
304
+ // text. Clear any active thinking indicator and show a dim hint so the
305
+ // user knows the turn ended and they can reply or press Enter to finish.
306
+ stopThinking();
307
+ process.stdout.write(`${DIM}[nx-adsp] Agent completed work without a response — reply or press Enter to apply files.${RESET}\n`);
118
308
  }
119
- });
120
- socket.on('session-expired', () => {
121
- process.stdout.write('\nAgent session expired.\n');
309
+ buffer = '';
310
+ firstDeltaOfTurn = true; // reset for the next turn
311
+ promptUser();
312
+ }
313
+ });
314
+ socket.on('workspace-state', ({ files }) => {
315
+ // Only apply files the agent added or modified — not unchanged uploaded files.
316
+ const written = applyWorkspaceFiles(files !== null && files !== void 0 ? files : []);
317
+ if (written > 0) {
318
+ process.stdout.write(`\nApplied ${written} file(s) from agent workspace.\n`);
319
+ }
320
+ else {
321
+ process.stdout.write('\nNo files generated by agent.\n');
322
+ }
323
+ cleanup(written);
324
+ });
325
+ socket.on('session-expired', () => {
326
+ process.stdout.write('\nAgent session expired.\n');
327
+ requestWorkspaceState();
328
+ });
329
+ socket.on('connect_error', (err) => {
330
+ var _a, _b, _c, _d;
331
+ const errAny = err;
332
+ 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`);
333
+ 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`);
334
+ cleanup(0);
335
+ });
336
+ socket.on('error', (err) => {
337
+ process.stdout.write(`\n[nx-adsp] Agent error: ${JSON.stringify(err)}\n`);
338
+ requestWorkspaceState();
339
+ });
340
+ // For continuation calls (shared thread from composite generator), skip the
341
+ // description prompt — the agent already has context from the prior service
342
+ // interaction on the same thread.
343
+ if (isContinuation) {
344
+ descriptionReady = true;
345
+ }
346
+ else {
347
+ // Show description prompt while socket connects and uploads files in the background.
348
+ try {
349
+ const { prompt } = yield Promise.resolve().then(() => require('enquirer'));
350
+ const answer = yield prompt({
351
+ type: 'input',
352
+ name: 'description',
353
+ message: `Briefly describe what ${projectContext.projectName} does:`,
354
+ });
355
+ description = ((_c = answer.description) === null || _c === void 0 ? void 0 : _c.trim()) || undefined;
356
+ descriptionReady = true;
357
+ }
358
+ catch (_d) {
359
+ // Ctrl+C or cancellation during the description prompt.
360
+ interrupted = true;
361
+ cleanup(0);
362
+ return conversationPromise;
363
+ }
364
+ }
365
+ // Create readline after enquirer has fully released stdin so the two
366
+ // libraries don't conflict over stdin state.
367
+ rl = (0, readline_1.createInterface)({ input: process.stdin, output: process.stdout });
368
+ rl.on('SIGINT', () => {
369
+ interrupted = true;
370
+ process.stdout.write('\n');
371
+ cleanup(0);
372
+ });
373
+ rl.on('close', () => {
374
+ if (!conversationDone && !interrupted) {
122
375
  requestWorkspaceState();
123
- });
124
- socket.on('connect_error', () => cleanup(0));
125
- socket.on('error', () => requestWorkspaceState());
376
+ }
126
377
  });
378
+ if (workspaceReady) {
379
+ // Upload finished while the user was typing — send immediately.
380
+ sendInitialMessage();
381
+ }
382
+ else {
383
+ // Rare: user typed faster than the upload completed. Show a brief status
384
+ // so the prompt doesn't appear to hang.
385
+ process.stdout.write('[nx-adsp] Uploading project files to workspace...\n');
386
+ // workspace-updated handler will call sendInitialMessage once upload completes.
387
+ }
388
+ return conversationPromise;
127
389
  });
128
390
  }
129
391
  function resolveAgentServiceUrl(directoryServiceUrl) {
130
392
  return tslib_1.__awaiter(this, void 0, void 0, function* () {
131
- var _a;
132
393
  try {
133
394
  const urls = yield (0, nx_oc_1.getServiceUrls)(directoryServiceUrl);
134
- return (_a = urls[AGENT_SERVICE_URN]) !== null && _a !== void 0 ? _a : null;
395
+ const apiUrl = urls[AGENT_SERVICE_URN];
396
+ if (!apiUrl)
397
+ return null;
398
+ // The directory URL includes the REST API path (e.g. /agent/v1).
399
+ // Socket.io attaches at the server root, so use only the origin.
400
+ return new URL(apiUrl).origin;
135
401
  }
136
- catch (_b) {
402
+ catch (_a) {
137
403
  return null;
138
404
  }
139
405
  });
@@ -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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"}
@@ -12,6 +12,12 @@ jest.mock('socket.io-client');
12
12
  jest.mock('@abgov/nx-oc', () => ({ getServiceUrls: jest.fn() }));
13
13
  jest.mock('@nx/devkit', () => ({ Tree: jest.fn() }));
14
14
 
15
+ // Enquirer is dynamically imported inside consultAgent; mock it here so the
16
+ // prompt resolves immediately with an empty description in all tests.
17
+ jest.mock('enquirer', () => ({
18
+ prompt: jest.fn().mockResolvedValue({ description: '' }),
19
+ }));
20
+
15
21
  import { io } from 'socket.io-client';
16
22
  import { getServiceUrls } from '@abgov/nx-oc';
17
23
 
@@ -37,6 +43,9 @@ function makeMockSocket() {
37
43
  on: jest.fn((event: string, handler: (...args: unknown[]) => void) => {
38
44
  handlers[event] = handler;
39
45
  }),
46
+ once: jest.fn((event: string, handler: (...args: unknown[]) => void) => {
47
+ handlers[event] = handler;
48
+ }),
40
49
  emit: jest.fn(),
41
50
  disconnect: jest.fn(),
42
51
  _handlers: handlers,
@@ -46,6 +55,9 @@ function makeMockSocket() {
46
55
  return socket;
47
56
  }
48
57
 
58
+ // Flush the microtask queue so async operations inside consultAgent settle.
59
+ // One tick is enough: the dynamic import and the enquirer mock both resolve
60
+ // as microtasks, so after this all handlers are registered and descriptionReady=true.
49
61
  const flushPromises = () => new Promise(resolve => setTimeout(resolve, 0));
50
62
 
51
63
  describe('consultAgent', () => {
@@ -66,6 +78,7 @@ describe('consultAgent', () => {
66
78
  });
67
79
  const socket = makeMockSocket();
68
80
  const resultPromise = consultAgent('https://directory.example.com', 'token', PROJECT_CONTEXT, mockHost, 'apps/test-service');
81
+ // After one tick: enquirer mock resolves, descriptionReady=true, conversationPromise returned.
69
82
  await flushPromises();
70
83
  socket._handlers['connect_error']?.();
71
84
  expect(await resultPromise).toBeNull();
@@ -79,7 +92,13 @@ describe('consultAgent', () => {
79
92
  const resultPromise = consultAgent('https://directory.example.com', 'token', PROJECT_CONTEXT, mockHost, 'apps/test-service');
80
93
  await flushPromises();
81
94
 
95
+ // descriptionReady=true at this point; workspace-updated fires last → triggers sendInitialMessage.
82
96
  socket._handlers['connect']?.();
97
+ // Server signals readiness via 'message' before workspace-update is safe to send.
98
+ socket._handlers['message']?.('Connected as user...');
99
+ socket._handlers['workspace-updated']?.();
100
+ // Simulate agent responding with text (sets agentHasResponded = true)
101
+ socket._handlers['stream']?.({ chunk: { type: 'text-delta', payload: { text: 'I will add events.' } }, done: false });
83
102
  socket._handlers['stream']?.({ chunk: null, done: true });
84
103
  socket._handlers['workspace-state']?.({
85
104
  files: [
@@ -89,12 +108,12 @@ describe('consultAgent', () => {
89
108
  });
90
109
 
91
110
  const result = await resultPromise;
92
- expect(result).toEqual({ filesWritten: 2 });
111
+ expect(result).toEqual({ filesWritten: 2, userInteracted: true, interrupted: false });
93
112
  expect(mockHost.write).toHaveBeenCalledWith('apps/test-service/src/roles.ts', 'export enum ServiceRoles {}');
94
113
  expect(mockHost.write).toHaveBeenCalledWith('apps/test-service/src/main.ts', 'updated main.ts');
95
114
  });
96
115
 
97
- it('includes existing file content in the initial message', async () => {
116
+ it('uploads existing files to workspace before sending the initial message', async () => {
98
117
  mockedGetServiceUrls.mockResolvedValue({
99
118
  'urn:ads:platform:agent-service:v1': 'https://agent.example.com',
100
119
  });
@@ -102,15 +121,28 @@ describe('consultAgent', () => {
102
121
  const resultPromise = consultAgent('https://directory.example.com', 'token', PROJECT_CONTEXT, mockHost, 'apps/test-service');
103
122
  await flushPromises();
104
123
 
124
+ // connect → server sends 'message' readiness signal → workspace-update emitted → workspace-updated → initial message sent
105
125
  socket._handlers['connect']?.();
126
+ socket._handlers['message']?.('Connected as user...');
127
+ socket._handlers['workspace-updated']?.();
128
+ socket._handlers['stream']?.({ chunk: { type: 'text-delta', payload: { text: 'What does this service do?' } }, done: false });
106
129
  socket._handlers['stream']?.({ chunk: null, done: true });
107
130
  socket._handlers['workspace-state']?.({ files: [] });
108
131
  await resultPromise;
109
132
 
133
+ expect(socket.emit).toHaveBeenCalledWith(
134
+ 'workspace-update',
135
+ expect.objectContaining({
136
+ agent: 'nxAdspAgent',
137
+ writes: expect.arrayContaining([
138
+ expect.objectContaining({ path: 'src/main.ts' }),
139
+ ]),
140
+ })
141
+ );
110
142
  expect(socket.emit).toHaveBeenCalledWith(
111
143
  'message',
112
144
  expect.objectContaining({
113
- agent: 'nx-adsp-agent',
145
+ agent: 'nxAdspAgent',
114
146
  content: expect.stringContaining('src/main.ts'),
115
147
  })
116
148
  );
@@ -0,0 +1 @@
1
+ export declare const PLUGIN_VERSION = "12.x";