@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 +1 -1
- package/src/generators/express-service/express-service.js +55 -5
- package/src/generators/express-service/express-service.js.map +1 -1
- package/src/generators/express-service/express-service.spec.ts +2 -0
- package/src/generators/express-service/schema.d.ts +4 -0
- package/src/generators/express-service/schema.json +11 -1
- package/src/generators/mean/mean.spec.ts +1 -0
- package/src/generators/mern/mern.spec.ts +1 -0
- package/src/utils/agent.d.ts +4 -0
- package/src/utils/agent.js +104 -26
- package/src/utils/agent.js.map +1 -1
- package/src/utils/agent.spec.ts +18 -3
package/package.json
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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":";;
|
|
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 = {
|
package/src/utils/agent.d.ts
CHANGED
|
@@ -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
|
package/src/utils/agent.js
CHANGED
|
@@ -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 = '
|
|
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
|
-
|
|
36
|
-
|
|
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
|
|
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})
|
|
48
|
-
`
|
|
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 —
|
|
70
|
-
|
|
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
|
-
|
|
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('
|
|
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
|
|
105
|
-
//
|
|
106
|
-
|
|
168
|
+
// Agent finished its turn — prompt 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
|
-
|
|
111
|
-
|
|
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
|
-
|
|
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', () =>
|
|
125
|
-
|
|
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
|
-
|
|
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 (
|
|
214
|
+
catch (_a) {
|
|
137
215
|
return null;
|
|
138
216
|
}
|
|
139
217
|
});
|
package/src/utils/agent.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent.js","sourceRoot":"","sources":["../../../../../packages/nx-adsp/src/utils/agent.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"agent.js","sourceRoot":"","sources":["../../../../../packages/nx-adsp/src/utils/agent.ts"],"names":[],"mappings":";;AA0CA,oCAuNC;;AAjQD,uCAA2C;AAE3C,uDAAsC;AACtC,wCAA8C;AAE9C,MAAM,iBAAiB,GAAG,mCAAmC,CAAC;AAC9D,MAAM,QAAQ,GAAG,aAAa,CAAC;AA2B/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,mFAAmF;gBACnF,2EAA2E;gBAC3E,0EAA0E;gBAC1E,yCAAyC;gBACzC,OAAO,CACL,iBAAiB,IAAI,WAAW;oBAC9B,CAAC,CAAC,EAAE,YAAY,EAAE,cAAc,EAAE,iBAAiB,EAAE,WAAW,EAAE;oBAClE,CAAC,CAAC,IAAI,CACT,CAAC;YACJ,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,IAAI,CAAC,iBAAiB,EAAE,CAAC;wBACvB,wDAAwD;wBACxD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;oBAC7B,CAAC;oBACD,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"}
|
package/src/utils/agent.spec.ts
CHANGED
|
@@ -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('
|
|
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: '
|
|
128
|
+
agent: 'nxAdspAgent',
|
|
114
129
|
content: expect.stringContaining('src/main.ts'),
|
|
115
130
|
})
|
|
116
131
|
);
|