@agents-at-scale/ark 0.1.35-rc.1 → 0.1.35-rc2
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/dist/arkServices.d.ts +4 -12
- package/dist/arkServices.js +19 -34
- package/dist/arkServices.spec.d.ts +1 -0
- package/dist/arkServices.spec.js +24 -0
- package/dist/commands/agents/index.d.ts +2 -1
- package/dist/commands/agents/index.js +2 -7
- package/dist/commands/agents/index.spec.d.ts +1 -0
- package/dist/commands/agents/index.spec.js +67 -0
- package/dist/commands/chat/index.d.ts +2 -1
- package/dist/commands/chat/index.js +5 -21
- package/dist/commands/cluster/get.spec.d.ts +1 -0
- package/dist/commands/cluster/get.spec.js +92 -0
- package/dist/commands/cluster/index.d.ts +2 -1
- package/dist/commands/cluster/index.js +1 -1
- package/dist/commands/cluster/index.spec.d.ts +1 -0
- package/dist/commands/cluster/index.spec.js +24 -0
- package/dist/commands/completion/index.d.ts +2 -1
- package/dist/commands/completion/index.js +24 -2
- package/dist/commands/completion/index.spec.d.ts +1 -0
- package/dist/commands/completion/index.spec.js +34 -0
- package/dist/commands/config/index.d.ts +2 -1
- package/dist/commands/config/index.js +2 -2
- package/dist/commands/config/index.spec.d.ts +1 -0
- package/dist/commands/config/index.spec.js +78 -0
- package/dist/commands/dashboard/index.d.ts +2 -1
- package/dist/commands/dashboard/index.js +1 -1
- package/dist/commands/dev/index.d.ts +2 -1
- package/dist/commands/dev/index.js +1 -1
- package/dist/commands/dev/tool-generate.spec.d.ts +1 -0
- package/dist/commands/dev/tool-generate.spec.js +163 -0
- package/dist/commands/dev/tool.spec.d.ts +1 -0
- package/dist/commands/dev/tool.spec.js +48 -0
- package/dist/commands/docs/index.d.ts +4 -0
- package/dist/commands/docs/index.js +18 -0
- package/dist/commands/generate/generators/project.js +22 -41
- package/dist/commands/generate/index.d.ts +2 -1
- package/dist/commands/generate/index.js +1 -1
- package/dist/commands/install/index.d.ts +4 -2
- package/dist/commands/install/index.js +225 -90
- package/dist/commands/install/index.spec.d.ts +1 -0
- package/dist/commands/install/index.spec.js +143 -0
- package/dist/commands/models/create.spec.d.ts +1 -0
- package/dist/commands/models/create.spec.js +125 -0
- package/dist/commands/models/index.d.ts +2 -1
- package/dist/commands/models/index.js +2 -7
- package/dist/commands/models/index.spec.d.ts +1 -0
- package/dist/commands/models/index.spec.js +76 -0
- package/dist/commands/query/index.d.ts +3 -0
- package/dist/commands/query/index.js +131 -0
- package/dist/commands/routes/index.d.ts +2 -1
- package/dist/commands/routes/index.js +1 -9
- package/dist/commands/status/index.d.ts +3 -2
- package/dist/commands/status/index.js +240 -11
- package/dist/commands/targets/index.d.ts +2 -1
- package/dist/commands/targets/index.js +1 -1
- package/dist/commands/targets/index.spec.d.ts +1 -0
- package/dist/commands/targets/index.spec.js +105 -0
- package/dist/commands/teams/index.d.ts +2 -1
- package/dist/commands/teams/index.js +2 -7
- package/dist/commands/teams/index.spec.d.ts +1 -0
- package/dist/commands/teams/index.spec.js +70 -0
- package/dist/commands/tools/index.d.ts +2 -1
- package/dist/commands/tools/index.js +2 -7
- package/dist/commands/tools/index.spec.d.ts +1 -0
- package/dist/commands/tools/index.spec.js +70 -0
- package/dist/commands/uninstall/index.d.ts +2 -1
- package/dist/commands/uninstall/index.js +66 -44
- package/dist/commands/uninstall/index.spec.d.ts +1 -0
- package/dist/commands/uninstall/index.spec.js +125 -0
- package/dist/components/ChatUI.js +4 -4
- package/dist/components/statusChecker.d.ts +5 -12
- package/dist/components/statusChecker.js +193 -90
- package/dist/config.d.ts +3 -22
- package/dist/config.js +7 -151
- package/dist/index.js +26 -19
- package/dist/lib/arkServiceProxy.js +4 -2
- package/dist/lib/arkStatus.d.ts +5 -0
- package/dist/lib/arkStatus.js +61 -2
- package/dist/lib/arkStatus.spec.d.ts +1 -0
- package/dist/lib/arkStatus.spec.js +49 -0
- package/dist/lib/chatClient.js +1 -3
- package/dist/lib/cluster.js +11 -14
- package/dist/lib/cluster.spec.d.ts +1 -0
- package/dist/lib/cluster.spec.js +338 -0
- package/dist/lib/commandUtils.js +7 -7
- package/dist/lib/commands.d.ts +16 -0
- package/dist/lib/commands.js +29 -0
- package/dist/lib/commands.spec.d.ts +1 -0
- package/dist/lib/commands.spec.js +146 -0
- package/dist/lib/config.d.ts +4 -0
- package/dist/lib/config.js +6 -4
- package/dist/lib/config.spec.d.ts +1 -0
- package/dist/lib/config.spec.js +99 -0
- package/dist/lib/consts.d.ts +0 -1
- package/dist/lib/consts.js +0 -2
- package/dist/lib/consts.spec.d.ts +1 -0
- package/dist/lib/consts.spec.js +15 -0
- package/dist/lib/errors.js +1 -1
- package/dist/lib/errors.spec.d.ts +1 -0
- package/dist/lib/errors.spec.js +221 -0
- package/dist/lib/exec.d.ts +0 -4
- package/dist/lib/exec.js +0 -11
- package/dist/lib/nextSteps.d.ts +4 -0
- package/dist/lib/nextSteps.js +20 -0
- package/dist/lib/nextSteps.spec.d.ts +1 -0
- package/dist/lib/nextSteps.spec.js +59 -0
- package/dist/lib/output.spec.d.ts +1 -0
- package/dist/lib/output.spec.js +123 -0
- package/dist/lib/portUtils.d.ts +8 -0
- package/dist/lib/portUtils.js +39 -0
- package/dist/lib/startup.d.ts +9 -0
- package/dist/lib/startup.js +93 -0
- package/dist/lib/startup.spec.d.ts +1 -0
- package/dist/lib/startup.spec.js +168 -0
- package/dist/lib/types.d.ts +9 -0
- package/dist/ui/AgentSelector.d.ts +8 -0
- package/dist/ui/AgentSelector.js +53 -0
- package/dist/ui/MainMenu.d.ts +5 -1
- package/dist/ui/MainMenu.js +117 -54
- package/dist/ui/ModelSelector.d.ts +8 -0
- package/dist/ui/ModelSelector.js +53 -0
- package/dist/ui/TeamSelector.d.ts +8 -0
- package/dist/ui/TeamSelector.js +55 -0
- package/dist/ui/ToolSelector.d.ts +8 -0
- package/dist/ui/ToolSelector.js +53 -0
- package/dist/ui/statusFormatter.d.ts +22 -10
- package/dist/ui/statusFormatter.js +37 -109
- package/dist/ui/statusFormatter.spec.d.ts +1 -0
- package/dist/ui/statusFormatter.spec.js +58 -0
- package/package.json +3 -3
|
@@ -1,11 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { KubernetesConfigManager } from '../lib/kubernetes.js';
|
|
4
|
-
import * as k8s from '@kubernetes/client-node';
|
|
5
|
-
import { isCommandAvailable } from '../lib/commandUtils.js';
|
|
1
|
+
import { execa } from 'execa';
|
|
2
|
+
import { checkCommandExists } from '../lib/commands.js';
|
|
6
3
|
import { arkServices } from '../arkServices.js';
|
|
7
4
|
import { isArkReady } from '../lib/arkStatus.js';
|
|
8
|
-
const execAsync = promisify(exec);
|
|
9
5
|
export const getNodeVersion = () => ({
|
|
10
6
|
command: 'node',
|
|
11
7
|
versionArgs: '--version',
|
|
@@ -66,30 +62,62 @@ function createErrorServiceStatus(name, url, error, defaultStatus = 'unhealthy',
|
|
|
66
62
|
};
|
|
67
63
|
}
|
|
68
64
|
export class StatusChecker {
|
|
69
|
-
constructor(arkClient) {
|
|
70
|
-
this.arkClient = arkClient;
|
|
71
|
-
this.kubernetesManager = new KubernetesConfigManager();
|
|
72
|
-
}
|
|
73
65
|
/**
|
|
74
66
|
* Get version of a command
|
|
75
67
|
*/
|
|
76
68
|
async getCommandVersion(config) {
|
|
77
69
|
try {
|
|
78
|
-
const
|
|
79
|
-
const { stdout } = await
|
|
70
|
+
const args = config.versionArgs.split(' ');
|
|
71
|
+
const { stdout } = await execa(config.command, args);
|
|
80
72
|
return config.versionExtract(stdout);
|
|
81
73
|
}
|
|
82
74
|
catch (error) {
|
|
83
75
|
throw new Error(`Failed to get ${config.command} version: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
84
76
|
}
|
|
85
77
|
}
|
|
78
|
+
/**
|
|
79
|
+
* Check combined deployment and helm status
|
|
80
|
+
* Gets health from deployment, version/revision from Helm
|
|
81
|
+
*/
|
|
82
|
+
async checkCombinedStatus(serviceName, deploymentName, helmReleaseName, namespace, devDeploymentName) {
|
|
83
|
+
// Get deployment status for health
|
|
84
|
+
const deploymentStatus = await this.checkDeploymentStatus(serviceName, deploymentName, namespace, devDeploymentName);
|
|
85
|
+
// Try to get version info from Helm if it's a healthy deployment
|
|
86
|
+
if (deploymentStatus.status === 'healthy' ||
|
|
87
|
+
deploymentStatus.status === 'warning') {
|
|
88
|
+
try {
|
|
89
|
+
const { stdout } = await execa('helm', ['list', '-n', namespace, '-o', 'json'], { timeout: 5000 });
|
|
90
|
+
const releases = JSON.parse(stdout);
|
|
91
|
+
const release = releases.find((r) => r.name === helmReleaseName);
|
|
92
|
+
if (release) {
|
|
93
|
+
// Merge Helm version info into deployment status
|
|
94
|
+
return {
|
|
95
|
+
...deploymentStatus,
|
|
96
|
+
version: release.app_version,
|
|
97
|
+
revision: release.revision,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
// If Helm check fails, return deployment status as-is
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return deploymentStatus;
|
|
106
|
+
}
|
|
86
107
|
/**
|
|
87
108
|
* Check deployment status
|
|
88
109
|
*/
|
|
89
|
-
async checkDeploymentStatus(serviceName, deploymentName, namespace) {
|
|
110
|
+
async checkDeploymentStatus(serviceName, deploymentName, namespace, devDeploymentName) {
|
|
90
111
|
try {
|
|
91
|
-
const
|
|
92
|
-
|
|
112
|
+
const { stdout } = await execa('kubectl', [
|
|
113
|
+
'get',
|
|
114
|
+
'deployment',
|
|
115
|
+
deploymentName,
|
|
116
|
+
'--namespace',
|
|
117
|
+
namespace,
|
|
118
|
+
'-o',
|
|
119
|
+
'json',
|
|
120
|
+
]);
|
|
93
121
|
const deployment = JSON.parse(stdout);
|
|
94
122
|
const replicas = deployment.spec?.replicas || 0;
|
|
95
123
|
const readyReplicas = deployment.status?.readyReplicas || 0;
|
|
@@ -109,19 +137,113 @@ export class StatusChecker {
|
|
|
109
137
|
else {
|
|
110
138
|
status = 'warning';
|
|
111
139
|
}
|
|
140
|
+
// If main deployment has 0 replicas and we have a dev deployment, check it
|
|
141
|
+
if (replicas === 0 && devDeploymentName) {
|
|
142
|
+
try {
|
|
143
|
+
const { stdout: devStdout } = await execa('kubectl', [
|
|
144
|
+
'get',
|
|
145
|
+
'deployment',
|
|
146
|
+
devDeploymentName,
|
|
147
|
+
'--namespace',
|
|
148
|
+
namespace,
|
|
149
|
+
'-o',
|
|
150
|
+
'json',
|
|
151
|
+
]);
|
|
152
|
+
const devDeployment = JSON.parse(devStdout);
|
|
153
|
+
const devReplicas = devDeployment.spec?.replicas || 0;
|
|
154
|
+
const devReadyReplicas = devDeployment.status?.readyReplicas || 0;
|
|
155
|
+
const devAvailableReplicas = devDeployment.status?.availableReplicas || 0;
|
|
156
|
+
if (devReplicas > 0) {
|
|
157
|
+
const devAvailableCondition = devDeployment.status?.conditions?.find((condition) => condition.type === 'Available');
|
|
158
|
+
const devIsAvailable = devAvailableCondition?.status === 'True';
|
|
159
|
+
const devAllReplicasReady = devReadyReplicas === devReplicas &&
|
|
160
|
+
devAvailableReplicas === devReplicas;
|
|
161
|
+
let devStatus;
|
|
162
|
+
if (devReplicas === 0 || devReadyReplicas === 0) {
|
|
163
|
+
devStatus = 'not ready';
|
|
164
|
+
}
|
|
165
|
+
else if (devIsAvailable && devAllReplicasReady) {
|
|
166
|
+
devStatus = 'healthy';
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
devStatus = 'warning';
|
|
170
|
+
}
|
|
171
|
+
return {
|
|
172
|
+
name: serviceName,
|
|
173
|
+
status: devStatus,
|
|
174
|
+
details: `${devReadyReplicas}/${devReplicas} replicas ready`,
|
|
175
|
+
isDev: true,
|
|
176
|
+
namespace,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
catch {
|
|
181
|
+
// If dev deployment check fails, return the original status
|
|
182
|
+
}
|
|
183
|
+
}
|
|
112
184
|
return {
|
|
113
185
|
name: serviceName,
|
|
114
186
|
status,
|
|
115
187
|
details: `${readyReplicas}/${replicas} replicas ready`,
|
|
188
|
+
namespace,
|
|
116
189
|
};
|
|
117
190
|
}
|
|
118
191
|
catch (error) {
|
|
119
192
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
120
|
-
|
|
193
|
+
// If main deployment not found or not healthy, try dev deployment
|
|
194
|
+
if (errorMessage.includes('not found') ||
|
|
195
|
+
errorMessage.includes('NotFound')) {
|
|
196
|
+
if (devDeploymentName) {
|
|
197
|
+
try {
|
|
198
|
+
const { stdout } = await execa('kubectl', [
|
|
199
|
+
'get',
|
|
200
|
+
'deployment',
|
|
201
|
+
devDeploymentName,
|
|
202
|
+
'--namespace',
|
|
203
|
+
namespace,
|
|
204
|
+
'-o',
|
|
205
|
+
'json',
|
|
206
|
+
]);
|
|
207
|
+
const devDeployment = JSON.parse(stdout);
|
|
208
|
+
const replicas = devDeployment.spec?.replicas || 0;
|
|
209
|
+
const readyReplicas = devDeployment.status?.readyReplicas || 0;
|
|
210
|
+
const availableReplicas = devDeployment.status?.availableReplicas || 0;
|
|
211
|
+
const availableCondition = devDeployment.status?.conditions?.find((condition) => condition.type === 'Available');
|
|
212
|
+
const isAvailable = availableCondition?.status === 'True';
|
|
213
|
+
const allReplicasReady = readyReplicas === replicas && availableReplicas === replicas;
|
|
214
|
+
let status;
|
|
215
|
+
if (replicas === 0 || readyReplicas === 0) {
|
|
216
|
+
status = 'not ready';
|
|
217
|
+
}
|
|
218
|
+
else if (isAvailable && allReplicasReady) {
|
|
219
|
+
status = 'healthy';
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
status = 'warning';
|
|
223
|
+
}
|
|
224
|
+
return {
|
|
225
|
+
name: serviceName,
|
|
226
|
+
status,
|
|
227
|
+
details: `${readyReplicas}/${replicas} replicas ready`,
|
|
228
|
+
isDev: true,
|
|
229
|
+
namespace,
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
catch {
|
|
233
|
+
// If dev deployment also not found, return not installed
|
|
234
|
+
return {
|
|
235
|
+
name: serviceName,
|
|
236
|
+
status: 'not installed',
|
|
237
|
+
details: `Deployment '${deploymentName}' not found`,
|
|
238
|
+
namespace,
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
}
|
|
121
242
|
return {
|
|
122
243
|
name: serviceName,
|
|
123
244
|
status: 'not installed',
|
|
124
|
-
details: `Deployment '${deploymentName}' not found
|
|
245
|
+
details: `Deployment '${deploymentName}' not found`,
|
|
246
|
+
namespace,
|
|
125
247
|
};
|
|
126
248
|
}
|
|
127
249
|
return createErrorServiceStatus(serviceName, '', error, 'unhealthy', `Failed to check deployment: ${errorMessage}`);
|
|
@@ -132,14 +254,22 @@ export class StatusChecker {
|
|
|
132
254
|
*/
|
|
133
255
|
async checkHelmStatus(serviceName, helmReleaseName, namespace) {
|
|
134
256
|
try {
|
|
135
|
-
const
|
|
136
|
-
|
|
257
|
+
const { stdout } = await execa('helm', [
|
|
258
|
+
'list',
|
|
259
|
+
'--filter',
|
|
260
|
+
helmReleaseName,
|
|
261
|
+
'--namespace',
|
|
262
|
+
namespace,
|
|
263
|
+
'--output',
|
|
264
|
+
'json',
|
|
265
|
+
]);
|
|
137
266
|
const helmList = JSON.parse(stdout);
|
|
138
267
|
if (helmList.length === 0) {
|
|
139
268
|
return {
|
|
140
269
|
name: serviceName,
|
|
141
270
|
status: 'not installed',
|
|
142
|
-
details: `Helm release '${helmReleaseName}' not found
|
|
271
|
+
details: `Helm release '${helmReleaseName}' not found`,
|
|
272
|
+
namespace,
|
|
143
273
|
};
|
|
144
274
|
}
|
|
145
275
|
const release = helmList[0];
|
|
@@ -153,6 +283,7 @@ export class StatusChecker {
|
|
|
153
283
|
version: appVersion,
|
|
154
284
|
revision: revision,
|
|
155
285
|
details: `Status: ${status}`,
|
|
286
|
+
namespace,
|
|
156
287
|
};
|
|
157
288
|
}
|
|
158
289
|
catch (error) {
|
|
@@ -160,69 +291,6 @@ export class StatusChecker {
|
|
|
160
291
|
return createErrorServiceStatus(serviceName, '', error, 'unhealthy', `Failed to check helm status: ${errorMessage}`);
|
|
161
292
|
}
|
|
162
293
|
}
|
|
163
|
-
/**
|
|
164
|
-
* Return a "not installed" status for a service
|
|
165
|
-
*/
|
|
166
|
-
createNotInstalledStatus(serviceName) {
|
|
167
|
-
return {
|
|
168
|
-
name: serviceName,
|
|
169
|
-
status: 'not installed',
|
|
170
|
-
details: `${serviceName} is not configured or not part of this deployment`,
|
|
171
|
-
};
|
|
172
|
-
}
|
|
173
|
-
/**
|
|
174
|
-
* Check Kubernetes service health via pods and endpoints
|
|
175
|
-
*/
|
|
176
|
-
async checkKubernetesService(serviceName, kubernetesServiceName, namespace = 'default') {
|
|
177
|
-
try {
|
|
178
|
-
await this.kubernetesManager.initializeConfig();
|
|
179
|
-
const kc = this.kubernetesManager.getKubeConfig();
|
|
180
|
-
const k8sApi = kc.makeApiClient(k8s.CoreV1Api);
|
|
181
|
-
// Check if service exists and has endpoints
|
|
182
|
-
const service = await k8sApi.readNamespacedService({
|
|
183
|
-
name: kubernetesServiceName,
|
|
184
|
-
namespace,
|
|
185
|
-
});
|
|
186
|
-
const endpoints = await k8sApi.readNamespacedEndpoints({
|
|
187
|
-
name: kubernetesServiceName,
|
|
188
|
-
namespace,
|
|
189
|
-
});
|
|
190
|
-
// Check if service has ready endpoints
|
|
191
|
-
const readyAddresses = endpoints.subsets?.reduce((total, subset) => {
|
|
192
|
-
return total + (subset.addresses?.length || 0);
|
|
193
|
-
}, 0) || 0;
|
|
194
|
-
if (readyAddresses > 0) {
|
|
195
|
-
const serviceIP = service.spec?.clusterIP;
|
|
196
|
-
const servicePort = service.spec?.ports?.[0]?.port;
|
|
197
|
-
return {
|
|
198
|
-
name: serviceName,
|
|
199
|
-
status: 'healthy',
|
|
200
|
-
url: `cluster://${serviceIP}:${servicePort}`,
|
|
201
|
-
details: `${serviceName} running in cluster (${readyAddresses} ready endpoints)`,
|
|
202
|
-
};
|
|
203
|
-
}
|
|
204
|
-
else {
|
|
205
|
-
return {
|
|
206
|
-
name: serviceName,
|
|
207
|
-
status: 'unhealthy',
|
|
208
|
-
details: `${serviceName} service exists but has no ready endpoints`,
|
|
209
|
-
};
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
catch (error) {
|
|
213
|
-
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
214
|
-
// If service not found, it's not installed
|
|
215
|
-
if (errorMessage.includes('not found')) {
|
|
216
|
-
return this.createNotInstalledStatus(serviceName);
|
|
217
|
-
}
|
|
218
|
-
// Other errors indicate unhealthy
|
|
219
|
-
return {
|
|
220
|
-
name: serviceName,
|
|
221
|
-
status: 'unhealthy',
|
|
222
|
-
details: `Failed to check ${serviceName}: ${errorMessage}`,
|
|
223
|
-
};
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
294
|
/**
|
|
227
295
|
* Check system dependencies
|
|
228
296
|
*/
|
|
@@ -238,7 +306,8 @@ export class StatusChecker {
|
|
|
238
306
|
];
|
|
239
307
|
const results = [];
|
|
240
308
|
for (const dep of dependencies) {
|
|
241
|
-
const
|
|
309
|
+
const args = dep.versionArgs.split(' ');
|
|
310
|
+
const installed = await checkCommandExists(dep.command, args);
|
|
242
311
|
const version = installed
|
|
243
312
|
? await this.getCommandVersion({
|
|
244
313
|
command: dep.command,
|
|
@@ -264,8 +333,16 @@ export class StatusChecker {
|
|
|
264
333
|
// Check dependencies first
|
|
265
334
|
const dependencies = await this.checkDependencies();
|
|
266
335
|
// Test cluster access
|
|
267
|
-
|
|
268
|
-
|
|
336
|
+
let clusterAccess = false;
|
|
337
|
+
try {
|
|
338
|
+
await execa('kubectl', ['get', 'namespaces', '-o', 'name'], {
|
|
339
|
+
timeout: 5000,
|
|
340
|
+
});
|
|
341
|
+
clusterAccess = true;
|
|
342
|
+
}
|
|
343
|
+
catch {
|
|
344
|
+
clusterAccess = false;
|
|
345
|
+
}
|
|
269
346
|
// Get cluster info if accessible
|
|
270
347
|
let clusterInfo;
|
|
271
348
|
if (clusterAccess) {
|
|
@@ -277,11 +354,17 @@ export class StatusChecker {
|
|
|
277
354
|
if (clusterAccess) {
|
|
278
355
|
const serviceChecks = [];
|
|
279
356
|
for (const [serviceName, service] of Object.entries(arkServices)) {
|
|
357
|
+
// Skip disabled services
|
|
358
|
+
if (!service.enabled)
|
|
359
|
+
continue;
|
|
360
|
+
// Use service namespace if defined, otherwise use current namespace from clusterInfo
|
|
361
|
+
const namespace = service.namespace || clusterInfo?.namespace || 'default';
|
|
280
362
|
if (service.k8sDeploymentName) {
|
|
281
|
-
|
|
363
|
+
// For deployments, get health from deployment and version from Helm
|
|
364
|
+
serviceChecks.push(this.checkCombinedStatus(serviceName, service.k8sDeploymentName, service.helmReleaseName, namespace, service.k8sDevDeploymentName));
|
|
282
365
|
}
|
|
283
366
|
else {
|
|
284
|
-
serviceChecks.push(this.checkHelmStatus(serviceName, service.helmReleaseName,
|
|
367
|
+
serviceChecks.push(this.checkHelmStatus(serviceName, service.helmReleaseName, namespace));
|
|
285
368
|
}
|
|
286
369
|
}
|
|
287
370
|
services = await Promise.all(serviceChecks);
|
|
@@ -289,16 +372,35 @@ export class StatusChecker {
|
|
|
289
372
|
// Check if ARK is ready (controller is running)
|
|
290
373
|
let arkReady = false;
|
|
291
374
|
let defaultModelExists = false;
|
|
375
|
+
let defaultModel;
|
|
292
376
|
if (clusterAccess) {
|
|
293
377
|
arkReady = await isArkReady();
|
|
294
|
-
// Check for default model
|
|
378
|
+
// Check for default model with detailed status
|
|
295
379
|
if (arkReady) {
|
|
296
380
|
try {
|
|
297
|
-
|
|
381
|
+
const { stdout } = await execa('kubectl', [
|
|
382
|
+
'get',
|
|
383
|
+
'model',
|
|
384
|
+
'default',
|
|
385
|
+
'-o',
|
|
386
|
+
'json',
|
|
387
|
+
]);
|
|
388
|
+
const model = JSON.parse(stdout);
|
|
298
389
|
defaultModelExists = true;
|
|
390
|
+
// Extract model details
|
|
391
|
+
const available = model.status?.conditions?.find((c) => c.type === 'Available')?.status === 'True';
|
|
392
|
+
defaultModel = {
|
|
393
|
+
exists: true,
|
|
394
|
+
available,
|
|
395
|
+
provider: model.spec?.provider,
|
|
396
|
+
details: model.spec?.model || model.spec?.apiEndpoint,
|
|
397
|
+
};
|
|
299
398
|
}
|
|
300
399
|
catch {
|
|
301
400
|
defaultModelExists = false;
|
|
401
|
+
defaultModel = {
|
|
402
|
+
exists: false,
|
|
403
|
+
};
|
|
302
404
|
}
|
|
303
405
|
}
|
|
304
406
|
}
|
|
@@ -309,6 +411,7 @@ export class StatusChecker {
|
|
|
309
411
|
clusterInfo,
|
|
310
412
|
arkReady,
|
|
311
413
|
defaultModelExists,
|
|
414
|
+
defaultModel,
|
|
312
415
|
};
|
|
313
416
|
}
|
|
314
417
|
}
|
package/dist/config.d.ts
CHANGED
|
@@ -1,27 +1,14 @@
|
|
|
1
|
-
import { ArkConfig, KubernetesConfig } from './lib/types.js';
|
|
2
1
|
/**
|
|
3
|
-
* ConfigManager handles
|
|
4
|
-
*
|
|
5
|
-
* setting DEBUG=ark:config or DEBUG=ark:* environment variable.
|
|
2
|
+
* ConfigManager handles API URL discovery and cluster access testing.
|
|
3
|
+
* Complex discovery logic can be debugged by setting DEBUG=ark:config
|
|
6
4
|
*
|
|
7
5
|
* Example usage:
|
|
8
|
-
* DEBUG=ark:config ark
|
|
9
|
-
* DEBUG=ark:* ark dashboard
|
|
6
|
+
* DEBUG=ark:config ark status
|
|
10
7
|
*/
|
|
11
8
|
export declare class ConfigManager {
|
|
12
|
-
private configDir;
|
|
13
|
-
private configFile;
|
|
14
9
|
private kubernetesManager;
|
|
15
|
-
private gatewayManager;
|
|
16
10
|
private kubeConfig;
|
|
17
11
|
constructor();
|
|
18
|
-
ensureConfigDir(): Promise<void>;
|
|
19
|
-
loadConfig(): Promise<ArkConfig>;
|
|
20
|
-
saveConfig(config: ArkConfig): Promise<void>;
|
|
21
|
-
updateConfig(updates: Partial<ArkConfig>): Promise<ArkConfig>;
|
|
22
|
-
private getDefaultConfig;
|
|
23
|
-
initializeConfig(): Promise<ArkConfig>;
|
|
24
|
-
getConfigPath(): string;
|
|
25
12
|
getApiBaseUrl(): Promise<string>;
|
|
26
13
|
/**
|
|
27
14
|
* Check if localhost-gateway is running by testing port 8080
|
|
@@ -32,11 +19,5 @@ export declare class ConfigManager {
|
|
|
32
19
|
*/
|
|
33
20
|
private getLocalhostGatewayUrls;
|
|
34
21
|
private initKubernetesConfig;
|
|
35
|
-
getKubernetesConfig(): Promise<KubernetesConfig | null>;
|
|
36
22
|
testClusterAccess(): Promise<boolean>;
|
|
37
|
-
/**
|
|
38
|
-
* Discover service URLs from ark-api service discovery
|
|
39
|
-
*/
|
|
40
|
-
private discoverServicesFromApi;
|
|
41
|
-
getServiceUrls(): Promise<Record<string, string>>;
|
|
42
23
|
}
|
package/dist/config.js
CHANGED
|
@@ -1,119 +1,22 @@
|
|
|
1
|
-
import { promises as fs } from 'fs';
|
|
2
|
-
import { homedir } from 'os';
|
|
3
|
-
import { join } from 'path';
|
|
4
1
|
import axios from 'axios';
|
|
5
2
|
import Debug from 'debug';
|
|
6
|
-
import {
|
|
7
|
-
import { DEFAULT_ADDRESS_ARK_API, CONFIG_DIR_NAME, CONFIG_FILE_NAME, } from './lib/consts.js';
|
|
8
|
-
import { GatewayManager } from './lib/gatewayManager.js';
|
|
3
|
+
import { DEFAULT_ADDRESS_ARK_API } from './lib/consts.js';
|
|
9
4
|
import { KubernetesConfigManager } from './lib/kubernetes.js';
|
|
10
5
|
import { getStatusCheckableServices } from './arkServices.js';
|
|
11
6
|
const debug = Debug('ark:config');
|
|
12
7
|
/**
|
|
13
|
-
* ConfigManager handles
|
|
14
|
-
*
|
|
15
|
-
* setting DEBUG=ark:config or DEBUG=ark:* environment variable.
|
|
8
|
+
* ConfigManager handles API URL discovery and cluster access testing.
|
|
9
|
+
* Complex discovery logic can be debugged by setting DEBUG=ark:config
|
|
16
10
|
*
|
|
17
11
|
* Example usage:
|
|
18
|
-
* DEBUG=ark:config ark
|
|
19
|
-
* DEBUG=ark:* ark dashboard
|
|
12
|
+
* DEBUG=ark:config ark status
|
|
20
13
|
*/
|
|
21
14
|
export class ConfigManager {
|
|
22
15
|
constructor() {
|
|
23
16
|
this.kubeConfig = null;
|
|
24
|
-
this.configDir = join(homedir(), '.config', CONFIG_DIR_NAME);
|
|
25
|
-
this.configFile = join(this.configDir, CONFIG_FILE_NAME);
|
|
26
17
|
this.kubernetesManager = new KubernetesConfigManager();
|
|
27
|
-
this.gatewayManager = new GatewayManager();
|
|
28
|
-
}
|
|
29
|
-
async ensureConfigDir() {
|
|
30
|
-
try {
|
|
31
|
-
await fs.mkdir(this.configDir, { recursive: true });
|
|
32
|
-
}
|
|
33
|
-
catch (_error) {
|
|
34
|
-
// Directory might already exist
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
async loadConfig() {
|
|
38
|
-
await this.ensureConfigDir();
|
|
39
|
-
try {
|
|
40
|
-
// Check if config file exists
|
|
41
|
-
await fs.access(this.configFile);
|
|
42
|
-
}
|
|
43
|
-
catch (error) {
|
|
44
|
-
// Config file doesn't exist - start with default config and enrich with kube config
|
|
45
|
-
if (error &&
|
|
46
|
-
typeof error === 'object' &&
|
|
47
|
-
'code' in error &&
|
|
48
|
-
error.code === 'ENOENT') {
|
|
49
|
-
return await this.getDefaultConfig();
|
|
50
|
-
}
|
|
51
|
-
// Other access errors (permissions, etc.) should be thrown
|
|
52
|
-
throw error;
|
|
53
|
-
}
|
|
54
|
-
try {
|
|
55
|
-
// Config file exists - try to read and parse it
|
|
56
|
-
const data = await fs.readFile(this.configFile, 'utf-8');
|
|
57
|
-
const config = JSON.parse(data);
|
|
58
|
-
// Merge with current Kubernetes config if not present
|
|
59
|
-
await this.initKubernetesConfig();
|
|
60
|
-
return {
|
|
61
|
-
...config,
|
|
62
|
-
kubeconfig: config.kubeconfig || this.kubeConfig?.kubeconfig,
|
|
63
|
-
currentContext: config.currentContext || this.kubeConfig?.currentContext,
|
|
64
|
-
kubeNamespace: config.kubeNamespace || this.kubeConfig?.namespace,
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
catch (error) {
|
|
68
|
-
// If it's a JSON parsing error, throw a clear error message
|
|
69
|
-
if (error instanceof SyntaxError) {
|
|
70
|
-
throw new Error(`Invalid JSON in config file ${this.configFile}: ${error.message}`);
|
|
71
|
-
}
|
|
72
|
-
// Other errors (read errors, etc.) should be thrown as-is
|
|
73
|
-
throw error;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
async saveConfig(config) {
|
|
77
|
-
await this.ensureConfigDir();
|
|
78
|
-
const jsonData = JSON.stringify(config, null, 2);
|
|
79
|
-
await fs.writeFile(this.configFile, jsonData);
|
|
80
|
-
}
|
|
81
|
-
async updateConfig(updates) {
|
|
82
|
-
const currentConfig = await this.loadConfig();
|
|
83
|
-
const newConfig = { ...currentConfig, ...updates };
|
|
84
|
-
await this.saveConfig(newConfig);
|
|
85
|
-
return newConfig;
|
|
86
|
-
}
|
|
87
|
-
async getDefaultConfig() {
|
|
88
|
-
// Initialize Kubernetes config to get defaults
|
|
89
|
-
await this.initKubernetesConfig();
|
|
90
|
-
return {
|
|
91
|
-
defaultAgent: 'default',
|
|
92
|
-
defaultModel: 'default',
|
|
93
|
-
defaultNamespace: this.kubeConfig?.namespace || 'default',
|
|
94
|
-
apiBaseUrl: DEFAULT_ADDRESS_ARK_API,
|
|
95
|
-
kubeconfig: this.kubeConfig?.kubeconfig,
|
|
96
|
-
currentContext: this.kubeConfig?.currentContext,
|
|
97
|
-
kubeNamespace: this.kubeConfig?.namespace,
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
async initializeConfig() {
|
|
101
|
-
const config = await this.loadConfig();
|
|
102
|
-
const defaultConfig = await this.getDefaultConfig();
|
|
103
|
-
const mergedConfig = { ...defaultConfig, ...config };
|
|
104
|
-
await this.saveConfig(mergedConfig);
|
|
105
|
-
return mergedConfig;
|
|
106
|
-
}
|
|
107
|
-
getConfigPath() {
|
|
108
|
-
return this.configFile;
|
|
109
18
|
}
|
|
110
19
|
async getApiBaseUrl() {
|
|
111
|
-
const config = await this.loadConfig();
|
|
112
|
-
// If apiBaseUrl is explicitly set in config, use it
|
|
113
|
-
if (config.apiBaseUrl && config.apiBaseUrl !== DEFAULT_ADDRESS_ARK_API) {
|
|
114
|
-
debug('using explicit config apiBaseUrl: %s', config.apiBaseUrl);
|
|
115
|
-
return config.apiBaseUrl;
|
|
116
|
-
}
|
|
117
20
|
// First try to detect localhost-gateway (works for everyone with standard setup)
|
|
118
21
|
if (await this.isLocalhostGatewayRunning()) {
|
|
119
22
|
const gatewayUrls = this.getLocalhostGatewayUrls();
|
|
@@ -127,7 +30,7 @@ export class ConfigManager {
|
|
|
127
30
|
await this.initKubernetesConfig();
|
|
128
31
|
if (this.kubeConfig) {
|
|
129
32
|
try {
|
|
130
|
-
const namespace =
|
|
33
|
+
const namespace = 'default';
|
|
131
34
|
const discoveredUrl = await this.kubernetesManager.getArkApiUrl(namespace);
|
|
132
35
|
debug('kubernetes discovery successful in %s: %s', namespace, discoveredUrl);
|
|
133
36
|
return discoveredUrl;
|
|
@@ -137,9 +40,8 @@ export class ConfigManager {
|
|
|
137
40
|
// Fall back to default if discovery fails
|
|
138
41
|
}
|
|
139
42
|
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
return fallbackUrl;
|
|
43
|
+
debug('falling back to default: %s', DEFAULT_ADDRESS_ARK_API);
|
|
44
|
+
return DEFAULT_ADDRESS_ARK_API;
|
|
143
45
|
}
|
|
144
46
|
/**
|
|
145
47
|
* Check if localhost-gateway is running by testing port 8080
|
|
@@ -180,10 +82,6 @@ export class ConfigManager {
|
|
|
180
82
|
}
|
|
181
83
|
}
|
|
182
84
|
}
|
|
183
|
-
async getKubernetesConfig() {
|
|
184
|
-
await this.initKubernetesConfig();
|
|
185
|
-
return this.kubeConfig;
|
|
186
|
-
}
|
|
187
85
|
async testClusterAccess() {
|
|
188
86
|
await this.initKubernetesConfig();
|
|
189
87
|
if (!this.kubeConfig) {
|
|
@@ -191,46 +89,4 @@ export class ConfigManager {
|
|
|
191
89
|
}
|
|
192
90
|
return await this.kubernetesManager.testClusterAccess();
|
|
193
91
|
}
|
|
194
|
-
/**
|
|
195
|
-
* Discover service URLs from ark-api service discovery
|
|
196
|
-
*/
|
|
197
|
-
async discoverServicesFromApi() {
|
|
198
|
-
try {
|
|
199
|
-
const apiBaseUrl = await this.getApiBaseUrl();
|
|
200
|
-
const arkClient = new ArkClient(apiBaseUrl);
|
|
201
|
-
const config = await this.loadConfig();
|
|
202
|
-
const namespace = config.kubeNamespace || config.defaultNamespace || 'default';
|
|
203
|
-
debug('service discovery: querying ark-api at %s (namespace: %s)', apiBaseUrl, namespace);
|
|
204
|
-
const services = await arkClient.getArkServices(namespace);
|
|
205
|
-
const serviceUrls = {};
|
|
206
|
-
// Dynamically map all discovered services with HTTP routes
|
|
207
|
-
for (const service of services) {
|
|
208
|
-
if (service.httproutes && service.httproutes.length > 0) {
|
|
209
|
-
const serviceName = service.release_name || service.name;
|
|
210
|
-
const serviceUrl = service.httproutes[0].url; // Use first route URL
|
|
211
|
-
serviceUrls[serviceName] = serviceUrl;
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
const discoveredServices = Object.entries(serviceUrls).map(([key, url]) => `${key}: ${url}`);
|
|
215
|
-
debug('service discovery: found %d services - %s', discoveredServices.length, discoveredServices.join(', ') || 'none');
|
|
216
|
-
return serviceUrls;
|
|
217
|
-
}
|
|
218
|
-
catch (error) {
|
|
219
|
-
debug('service discovery failed: %s', error instanceof Error ? error.message : error);
|
|
220
|
-
// Return empty object if discovery fails - will fall back to config/defaults
|
|
221
|
-
return {};
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
async getServiceUrls() {
|
|
225
|
-
// Try localhost-gateway detection (works for everyone with standard setup)
|
|
226
|
-
if (await this.isLocalhostGatewayRunning()) {
|
|
227
|
-
const gatewayUrls = this.getLocalhostGatewayUrls();
|
|
228
|
-
debug('localhost-gateway detected, using: %o', gatewayUrls);
|
|
229
|
-
return gatewayUrls;
|
|
230
|
-
}
|
|
231
|
-
// Try to discover services from ark-api (requires kubeconfig)
|
|
232
|
-
const discoveredUrls = await this.discoverServicesFromApi();
|
|
233
|
-
debug('discovered services: %o', discoveredUrls);
|
|
234
|
-
return discoveredUrls;
|
|
235
|
-
}
|
|
236
92
|
}
|