@agents-at-scale/ark 0.1.60 → 0.1.62
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 +3 -2
- package/dist/arkServices.js +21 -5
- package/dist/commands/install/index.js +27 -8
- package/dist/commands/status/index.js +20 -7
- package/dist/lib/readinessChecks.d.ts +10 -0
- package/dist/lib/readinessChecks.js +88 -0
- package/dist/types/arkService.d.ts +1 -0
- package/package.json +4 -3
- package/templates/tool/uv.lock +6 -6
package/dist/arkServices.d.ts
CHANGED
|
@@ -13,6 +13,7 @@ export declare const arkDependencies: DependencyCollection;
|
|
|
13
13
|
*/
|
|
14
14
|
export declare const arkServices: ServiceCollection;
|
|
15
15
|
/**
|
|
16
|
-
* Get services that can be installed via Helm charts (only enabled services)
|
|
16
|
+
* Get services that can be installed via Helm charts (only enabled services).
|
|
17
|
+
* When a backend is specified, services with a non-matching requiresBackend are excluded.
|
|
17
18
|
*/
|
|
18
|
-
export declare function getInstallableServices(): ServiceCollection;
|
|
19
|
+
export declare function getInstallableServices(backend?: 'etcd' | 'postgresql'): ServiceCollection;
|
package/dist/arkServices.js
CHANGED
|
@@ -74,6 +74,19 @@ const defaultArkServices = {
|
|
|
74
74
|
k8sDeploymentName: 'ark-controller',
|
|
75
75
|
k8sDevDeploymentName: 'ark-controller-devspace',
|
|
76
76
|
},
|
|
77
|
+
'ark-apiserver': {
|
|
78
|
+
name: 'ark-apiserver',
|
|
79
|
+
helmReleaseName: 'ark-apiserver',
|
|
80
|
+
description: 'Aggregated API server serving ark.mckinsey.com APIs from PostgreSQL',
|
|
81
|
+
enabled: true,
|
|
82
|
+
mandatory: true,
|
|
83
|
+
category: 'core',
|
|
84
|
+
namespace: 'ark-system',
|
|
85
|
+
chartPath: `${REGISTRY_BASE}/ark-apiserver:${CHART_VERSION}`,
|
|
86
|
+
installArgs: ['--create-namespace'],
|
|
87
|
+
k8sDeploymentName: 'ark-apiserver',
|
|
88
|
+
requiresBackend: 'postgresql',
|
|
89
|
+
},
|
|
77
90
|
'ark-completions': {
|
|
78
91
|
name: 'ark-completions',
|
|
79
92
|
helmReleaseName: 'ark-completions',
|
|
@@ -208,14 +221,17 @@ function applyConfigOverrides(defaults) {
|
|
|
208
221
|
*/
|
|
209
222
|
export const arkServices = applyConfigOverrides(defaultArkServices);
|
|
210
223
|
/**
|
|
211
|
-
* Get services that can be installed via Helm charts (only enabled services)
|
|
224
|
+
* Get services that can be installed via Helm charts (only enabled services).
|
|
225
|
+
* When a backend is specified, services with a non-matching requiresBackend are excluded.
|
|
212
226
|
*/
|
|
213
|
-
export function getInstallableServices() {
|
|
227
|
+
export function getInstallableServices(backend = 'etcd') {
|
|
214
228
|
const installable = {};
|
|
215
229
|
for (const [key, service] of Object.entries(arkServices)) {
|
|
216
|
-
if (service.enabled
|
|
217
|
-
|
|
218
|
-
|
|
230
|
+
if (!service.enabled || !service.chartPath)
|
|
231
|
+
continue;
|
|
232
|
+
if (service.requiresBackend && service.requiresBackend !== backend)
|
|
233
|
+
continue;
|
|
234
|
+
installable[key] = service;
|
|
219
235
|
}
|
|
220
236
|
return installable;
|
|
221
237
|
}
|
|
@@ -10,6 +10,7 @@ import { printNextSteps } from '../../lib/nextSteps.js';
|
|
|
10
10
|
import ora from 'ora';
|
|
11
11
|
import { waitForServicesReady, } from '../../lib/waitForReady.js';
|
|
12
12
|
import { parseTimeoutToSeconds } from '../../lib/timeout.js';
|
|
13
|
+
import { detectStorageBackend } from '../../lib/readinessChecks.js';
|
|
13
14
|
function isValidVersion(version) {
|
|
14
15
|
return /^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.-]+)?(\+[a-zA-Z0-9.-]+)?$/.test(version);
|
|
15
16
|
}
|
|
@@ -75,7 +76,7 @@ async function checkAndCleanFailedRelease(releaseName, namespace, verbose = fals
|
|
|
75
76
|
// Ignore errors - prerequisite may not exist
|
|
76
77
|
}
|
|
77
78
|
}
|
|
78
|
-
async function installService(service, verbose = false, arkVersionOverride, marketplaceVersionOverride) {
|
|
79
|
+
async function installService(service, verbose = false, arkVersionOverride, marketplaceVersionOverride, backend) {
|
|
79
80
|
await uninstallPrerequisites(service, verbose);
|
|
80
81
|
await checkAndCleanFailedRelease(service.helmReleaseName, service.namespace, verbose);
|
|
81
82
|
let chartPath = service.chartPath;
|
|
@@ -140,6 +141,7 @@ export async function installArk(config, serviceNames = [], options = {}) {
|
|
|
140
141
|
// Show cluster info
|
|
141
142
|
output.success(`connected to cluster: ${chalk.bold(clusterInfo.context)}`);
|
|
142
143
|
console.log(); // Add blank line after cluster info
|
|
144
|
+
const backend = await detectStorageBackend();
|
|
143
145
|
// If specific services are requested, install only those services
|
|
144
146
|
if (serviceNames.length > 0) {
|
|
145
147
|
for (const serviceName of serviceNames) {
|
|
@@ -185,7 +187,7 @@ export async function installArk(config, serviceNames = [], options = {}) {
|
|
|
185
187
|
continue;
|
|
186
188
|
}
|
|
187
189
|
// Core ARK service
|
|
188
|
-
const services = getInstallableServices();
|
|
190
|
+
const services = getInstallableServices(backend);
|
|
189
191
|
const service = Object.values(services).find((s) => s.name === serviceName);
|
|
190
192
|
if (!service) {
|
|
191
193
|
output.error(`service '${serviceName}' not found`);
|
|
@@ -210,11 +212,19 @@ export async function installArk(config, serviceNames = [], options = {}) {
|
|
|
210
212
|
}
|
|
211
213
|
// If not using -y flag, show checklist interface
|
|
212
214
|
if (!options.yes) {
|
|
215
|
+
const backendMatch = (s) => !s.requiresBackend || s.requiresBackend === backend;
|
|
213
216
|
const coreServices = Object.values(arkServices)
|
|
214
|
-
.filter((s) => s.category === 'core')
|
|
215
|
-
.sort((a, b) =>
|
|
217
|
+
.filter((s) => s.category === 'core' && backendMatch(s))
|
|
218
|
+
.sort((a, b) => {
|
|
219
|
+
// Ensure ark-controller is always first
|
|
220
|
+
if (a.name === 'ark-controller')
|
|
221
|
+
return -1;
|
|
222
|
+
if (b.name === 'ark-controller')
|
|
223
|
+
return 1;
|
|
224
|
+
return a.name.localeCompare(b.name);
|
|
225
|
+
});
|
|
216
226
|
const otherServices = Object.values(arkServices)
|
|
217
|
-
.filter((s) => s.category === 'service')
|
|
227
|
+
.filter((s) => s.category === 'service' && backendMatch(s))
|
|
218
228
|
.sort((a, b) => a.name.localeCompare(b.name));
|
|
219
229
|
const mandatoryServiceNames = [...coreServices, ...otherServices]
|
|
220
230
|
.filter((s) => s.mandatory)
|
|
@@ -361,8 +371,16 @@ export async function installArk(config, serviceNames = [], options = {}) {
|
|
|
361
371
|
}
|
|
362
372
|
}
|
|
363
373
|
// Install all services
|
|
364
|
-
const services = getInstallableServices();
|
|
365
|
-
|
|
374
|
+
const services = getInstallableServices(backend);
|
|
375
|
+
const sortedServices = Object.values(services).sort((a, b) => {
|
|
376
|
+
// Ensure ark-controller is always first
|
|
377
|
+
if (a.name === 'ark-controller')
|
|
378
|
+
return -1;
|
|
379
|
+
if (b.name === 'ark-controller')
|
|
380
|
+
return 1;
|
|
381
|
+
return a.name.localeCompare(b.name);
|
|
382
|
+
});
|
|
383
|
+
for (const service of sortedServices) {
|
|
366
384
|
output.info(`installing ${service.name}...`);
|
|
367
385
|
try {
|
|
368
386
|
await installService(service, options.verbose, options.arkVersion, options.marketplaceVersion);
|
|
@@ -388,7 +406,8 @@ export async function installArk(config, serviceNames = [], options = {}) {
|
|
|
388
406
|
const servicesToWait = Object.values(arkServices).filter((s) => s.enabled &&
|
|
389
407
|
s.category === 'core' &&
|
|
390
408
|
s.k8sDeploymentName &&
|
|
391
|
-
s.namespace
|
|
409
|
+
s.namespace &&
|
|
410
|
+
(!s.requiresBackend || s.requiresBackend === backend));
|
|
392
411
|
const spinner = ora(`Waiting for Ark to be ready (timeout: ${timeoutSeconds}s)...`).start();
|
|
393
412
|
const statusMap = new Map();
|
|
394
413
|
servicesToWait.forEach((s) => statusMap.set(s.name, false));
|
|
@@ -5,6 +5,7 @@ import { StatusChecker } from '../../components/statusChecker.js';
|
|
|
5
5
|
import { StatusFormatter, } from '../../ui/statusFormatter.js';
|
|
6
6
|
import { fetchVersionInfo } from '../../lib/versions.js';
|
|
7
7
|
import { waitForServicesReady, } from '../../lib/waitForReady.js';
|
|
8
|
+
import { runReadinessChecks, detectStorageBackend, } from '../../lib/readinessChecks.js';
|
|
8
9
|
import { arkServices } from '../../arkServices.js';
|
|
9
10
|
import output from '../../lib/output.js';
|
|
10
11
|
import { parseTimeoutToSeconds } from '../../lib/timeout.js';
|
|
@@ -270,13 +271,15 @@ export async function checkStatus(serviceNames, options) {
|
|
|
270
271
|
StatusFormatter.printSections(sections);
|
|
271
272
|
if (options?.waitForReady) {
|
|
272
273
|
const timeoutSeconds = parseTimeoutToSeconds(options.waitForReady);
|
|
274
|
+
const backend = await detectStorageBackend();
|
|
273
275
|
let servicesToWait = [];
|
|
274
276
|
if (serviceNames && serviceNames.length > 0) {
|
|
275
277
|
servicesToWait = serviceNames
|
|
276
278
|
.map((name) => Object.values(arkServices).find((s) => s.name === name))
|
|
277
279
|
.filter((s) => s !== undefined &&
|
|
278
280
|
s.k8sDeploymentName !== undefined &&
|
|
279
|
-
s.namespace !== undefined
|
|
281
|
+
s.namespace !== undefined &&
|
|
282
|
+
(!s.requiresBackend || s.requiresBackend === backend));
|
|
280
283
|
if (servicesToWait.length === 0) {
|
|
281
284
|
output.error(`No valid services found matching: ${serviceNames.join(', ')}`);
|
|
282
285
|
process.exit(1);
|
|
@@ -286,7 +289,8 @@ export async function checkStatus(serviceNames, options) {
|
|
|
286
289
|
servicesToWait = Object.values(arkServices).filter((s) => s.enabled &&
|
|
287
290
|
s.category === 'core' &&
|
|
288
291
|
s.k8sDeploymentName &&
|
|
289
|
-
s.namespace
|
|
292
|
+
s.namespace &&
|
|
293
|
+
(!s.requiresBackend || s.requiresBackend === backend));
|
|
290
294
|
}
|
|
291
295
|
console.log();
|
|
292
296
|
const waitSpinner = ora(`Waiting for services to be ready (timeout: ${timeoutSeconds}s)...`).start();
|
|
@@ -305,14 +309,23 @@ export async function checkStatus(serviceNames, options) {
|
|
|
305
309
|
});
|
|
306
310
|
waitSpinner.text = `Waiting for services to be ready (${elapsed}/${timeoutSeconds}s)...\n${lines.join('\n')}`;
|
|
307
311
|
});
|
|
308
|
-
if (result) {
|
|
309
|
-
waitSpinner.succeed('All services are ready');
|
|
310
|
-
process.exit(0);
|
|
311
|
-
}
|
|
312
|
-
else {
|
|
312
|
+
if (!result) {
|
|
313
313
|
waitSpinner.fail(`Services did not become ready within ${timeoutSeconds} seconds`);
|
|
314
314
|
process.exit(1);
|
|
315
315
|
}
|
|
316
|
+
waitSpinner.succeed('All services are ready');
|
|
317
|
+
const elapsedSeconds = Math.floor((Date.now() - startTime) / 1000);
|
|
318
|
+
const remainingSeconds = Math.max(1, timeoutSeconds - elapsedSeconds);
|
|
319
|
+
const deepResults = await runReadinessChecks(remainingSeconds, (r) => {
|
|
320
|
+
const icon = r.passed ? chalk.green('✓') : chalk.red('✗');
|
|
321
|
+
const dur = `${(r.durationMs / 1000).toFixed(1)}s`;
|
|
322
|
+
const suffix = r.message ? ` — ${r.message}` : '';
|
|
323
|
+
console.log(` ${icon} ${r.name} (${dur})${suffix}`);
|
|
324
|
+
});
|
|
325
|
+
if (deepResults.some((r) => !r.passed)) {
|
|
326
|
+
process.exit(1);
|
|
327
|
+
}
|
|
328
|
+
process.exit(0);
|
|
316
329
|
}
|
|
317
330
|
process.exit(0);
|
|
318
331
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type StorageBackend = 'etcd' | 'postgresql';
|
|
2
|
+
export interface ReadinessCheckResult {
|
|
3
|
+
name: string;
|
|
4
|
+
passed: boolean;
|
|
5
|
+
durationMs: number;
|
|
6
|
+
message?: string;
|
|
7
|
+
}
|
|
8
|
+
export type ReadinessProgress = (result: ReadinessCheckResult) => void;
|
|
9
|
+
export declare function detectStorageBackend(): Promise<StorageBackend>;
|
|
10
|
+
export declare function runReadinessChecks(timeoutSeconds: number, onProgress?: ReadinessProgress): Promise<ReadinessCheckResult[]>;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { execa } from 'execa';
|
|
2
|
+
const API_GROUP_POLL_INTERVAL_MS = 10000;
|
|
3
|
+
function sleep(ms) {
|
|
4
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
5
|
+
}
|
|
6
|
+
async function runKubectl(args, timeoutMs) {
|
|
7
|
+
const result = await execa('kubectl', args, {
|
|
8
|
+
timeout: timeoutMs,
|
|
9
|
+
reject: false,
|
|
10
|
+
});
|
|
11
|
+
return {
|
|
12
|
+
exitCode: result.exitCode ?? 1,
|
|
13
|
+
stdout: result.stdout ?? '',
|
|
14
|
+
stderr: result.stderr ?? '',
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export async function detectStorageBackend() {
|
|
18
|
+
const { exitCode } = await runKubectl(['get', 'crd', 'agents.ark.mckinsey.com'], 10000);
|
|
19
|
+
return exitCode === 0 ? 'etcd' : 'postgresql';
|
|
20
|
+
}
|
|
21
|
+
async function waitForApiServices(timeoutSeconds) {
|
|
22
|
+
const start = Date.now();
|
|
23
|
+
const primary = await runKubectl([
|
|
24
|
+
'wait',
|
|
25
|
+
'--for=condition=Available',
|
|
26
|
+
'apiservice',
|
|
27
|
+
'v1alpha1.ark.mckinsey.com',
|
|
28
|
+
`--timeout=${timeoutSeconds}s`,
|
|
29
|
+
], timeoutSeconds * 1000 + 5000);
|
|
30
|
+
await runKubectl([
|
|
31
|
+
'wait',
|
|
32
|
+
'--for=condition=Available',
|
|
33
|
+
'apiservice',
|
|
34
|
+
'v1prealpha1.ark.mckinsey.com',
|
|
35
|
+
'--timeout=30s',
|
|
36
|
+
], 35000);
|
|
37
|
+
return {
|
|
38
|
+
name: 'APIServices available',
|
|
39
|
+
passed: primary.exitCode === 0,
|
|
40
|
+
durationMs: Date.now() - start,
|
|
41
|
+
message: primary.exitCode === 0
|
|
42
|
+
? undefined
|
|
43
|
+
: (primary.stderr || primary.stdout).trim(),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
async function waitForApiGroup(timeoutSeconds) {
|
|
47
|
+
const start = Date.now();
|
|
48
|
+
const deadline = start + timeoutSeconds * 1000;
|
|
49
|
+
while (Date.now() < deadline) {
|
|
50
|
+
const { stdout, exitCode } = await runKubectl(['api-resources', '--api-group=ark.mckinsey.com', '-o', 'name'], 10000);
|
|
51
|
+
if (exitCode === 0 && /agents\./.test(stdout)) {
|
|
52
|
+
return {
|
|
53
|
+
name: 'API group registered',
|
|
54
|
+
passed: true,
|
|
55
|
+
durationMs: Date.now() - start,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
await sleep(API_GROUP_POLL_INTERVAL_MS);
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
name: 'API group registered',
|
|
62
|
+
passed: false,
|
|
63
|
+
durationMs: Date.now() - start,
|
|
64
|
+
message: 'timed out waiting for ark.mckinsey.com API group',
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
export async function runReadinessChecks(timeoutSeconds, onProgress) {
|
|
68
|
+
const backend = await detectStorageBackend();
|
|
69
|
+
if (backend === 'etcd') {
|
|
70
|
+
return [];
|
|
71
|
+
}
|
|
72
|
+
const overallStart = Date.now();
|
|
73
|
+
const remaining = () => Math.max(1, timeoutSeconds - Math.floor((Date.now() - overallStart) / 1000));
|
|
74
|
+
const checks = [
|
|
75
|
+
() => waitForApiServices(Math.min(remaining(), 120)),
|
|
76
|
+
() => waitForApiGroup(Math.min(remaining(), 300)),
|
|
77
|
+
];
|
|
78
|
+
const results = [];
|
|
79
|
+
for (const check of checks) {
|
|
80
|
+
const result = await check();
|
|
81
|
+
results.push(result);
|
|
82
|
+
onProgress?.(result);
|
|
83
|
+
if (!result.passed) {
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return results;
|
|
88
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agents-at-scale/ark",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.62",
|
|
4
4
|
"description": "Ark CLI - Interactive terminal interface for ARK agents",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"dependencies": {
|
|
45
45
|
"@kubernetes/client-node": "^1.3.0",
|
|
46
46
|
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
47
|
-
"axios": "^1.
|
|
47
|
+
"axios": "^1.16.0",
|
|
48
48
|
"chalk": "^4.1.2",
|
|
49
49
|
"commander": "^12.1.0",
|
|
50
50
|
"debug": "^4.4.1",
|
|
@@ -91,6 +91,7 @@
|
|
|
91
91
|
"@hono/node-server": "^1.19.13",
|
|
92
92
|
"flatted": "^3.4.2",
|
|
93
93
|
"tar": "^7.5.11",
|
|
94
|
-
"express-rate-limit": "^8.3.0"
|
|
94
|
+
"express-rate-limit": "^8.3.0",
|
|
95
|
+
"fast-uri": "^3.1.1"
|
|
95
96
|
}
|
|
96
97
|
}
|
package/templates/tool/uv.lock
CHANGED
|
@@ -763,11 +763,11 @@ wheels = [
|
|
|
763
763
|
|
|
764
764
|
[[package]]
|
|
765
765
|
name = "python-dotenv"
|
|
766
|
-
version = "1.
|
|
766
|
+
version = "1.2.2"
|
|
767
767
|
source = { registry = "https://pypi.org/simple" }
|
|
768
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
768
|
+
sdist = { url = "https://files.pythonhosted.org/packages/82/ed/0301aeeac3e5353ef3d94b6ec08bbcabd04a72018415dcb29e588514bba8/python_dotenv-1.2.2.tar.gz", hash = "sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3", size = 50135, upload-time = "2026-03-01T16:00:26.196Z" }
|
|
769
769
|
wheels = [
|
|
770
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
770
|
+
{ url = "https://files.pythonhosted.org/packages/0b/d7/1959b9648791274998a9c3526f6d0ec8fd2233e4d4acce81bbae76b44b2a/python_dotenv-1.2.2-py3-none-any.whl", hash = "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a", size = 22101, upload-time = "2026-03-01T16:00:25.09Z" },
|
|
771
771
|
]
|
|
772
772
|
|
|
773
773
|
[[package]]
|
|
@@ -1007,15 +1007,15 @@ wheels = [
|
|
|
1007
1007
|
|
|
1008
1008
|
[[package]]
|
|
1009
1009
|
name = "starlette"
|
|
1010
|
-
version = "0.
|
|
1010
|
+
version = "0.49.1"
|
|
1011
1011
|
source = { registry = "https://pypi.org/simple" }
|
|
1012
1012
|
dependencies = [
|
|
1013
1013
|
{ name = "anyio" },
|
|
1014
1014
|
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
|
|
1015
1015
|
]
|
|
1016
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
1016
|
+
sdist = { url = "https://files.pythonhosted.org/packages/1b/3f/507c21db33b66fb027a332f2cb3abbbe924cc3a79ced12f01ed8645955c9/starlette-0.49.1.tar.gz", hash = "sha256:481a43b71e24ed8c43b11ea02f5353d77840e01480881b8cb5a26b8cae64a8cb", size = 2654703, upload-time = "2025-10-28T17:34:10.928Z" }
|
|
1017
1017
|
wheels = [
|
|
1018
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1018
|
+
{ url = "https://files.pythonhosted.org/packages/51/da/545b75d420bb23b5d494b0517757b351963e974e79933f01e05c929f20a6/starlette-0.49.1-py3-none-any.whl", hash = "sha256:d92ce9f07e4a3caa3ac13a79523bd18e3bc0042bb8ff2d759a8e7dd0e1859875", size = 74175, upload-time = "2025-10-28T17:34:09.13Z" },
|
|
1019
1019
|
]
|
|
1020
1020
|
|
|
1021
1021
|
[[package]]
|