@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 +1 -1
- package/src/generators/express-service/express-service.js +53 -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 +2 -0
- package/src/utils/agent.js +97 -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,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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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":";;
|
|
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,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
|
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,42 @@ function consultAgent(directoryServiceUrl, accessToken, projectContext, host, pr
|
|
|
84
115
|
conversationDone = true;
|
|
85
116
|
rl.close();
|
|
86
117
|
socket.disconnect();
|
|
87
|
-
|
|
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('
|
|
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
|
|
105
|
-
//
|
|
106
|
-
|
|
161
|
+
// Agent finished its turn — prompt 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
|
-
|
|
111
|
-
|
|
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
|
-
|
|
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', () =>
|
|
125
|
-
|
|
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
|
-
|
|
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 (
|
|
207
|
+
catch (_a) {
|
|
137
208
|
return null;
|
|
138
209
|
}
|
|
139
210
|
});
|
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":"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"}
|
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 });
|
|
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
|
);
|