@calltelemetry/ct-lab-mcp 0.4.2 → 0.4.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/README.md +16 -0
  2. package/dist/bin/ct-lab-mcp.js +118 -3
  3. package/dist/src/crd/reconciler.d.ts +41 -0
  4. package/dist/src/crd/reconciler.d.ts.map +1 -0
  5. package/dist/src/crd/reconciler.js +302 -0
  6. package/dist/src/crd/reconciler.js.map +1 -0
  7. package/dist/src/crd/types.d.ts +101 -0
  8. package/dist/src/crd/types.d.ts.map +1 -0
  9. package/dist/src/crd/types.js +7 -0
  10. package/dist/src/crd/types.js.map +1 -0
  11. package/dist/src/hypervisors/esxi/client.d.ts.map +1 -1
  12. package/dist/src/hypervisors/esxi/client.js +6 -7
  13. package/dist/src/hypervisors/esxi/client.js.map +1 -1
  14. package/dist/src/hypervisors/proxmox/client.d.ts +2 -0
  15. package/dist/src/hypervisors/proxmox/client.d.ts.map +1 -1
  16. package/dist/src/hypervisors/proxmox/client.js +90 -14
  17. package/dist/src/hypervisors/proxmox/client.js.map +1 -1
  18. package/dist/src/proxmox/mock.d.ts.map +1 -1
  19. package/dist/src/proxmox/mock.js +46 -0
  20. package/dist/src/proxmox/mock.js.map +1 -1
  21. package/dist/src/server.js +2 -2
  22. package/dist/src/server.js.map +1 -1
  23. package/dist/src/slot/manager.d.ts.map +1 -1
  24. package/dist/src/slot/manager.js +16 -13
  25. package/dist/src/slot/manager.js.map +1 -1
  26. package/dist/src/slot/purge-controller.d.ts +63 -0
  27. package/dist/src/slot/purge-controller.d.ts.map +1 -0
  28. package/dist/src/slot/purge-controller.js +119 -0
  29. package/dist/src/slot/purge-controller.js.map +1 -0
  30. package/dist/src/slot/types.d.ts +6 -0
  31. package/dist/src/slot/types.d.ts.map +1 -1
  32. package/dist/src/slot/types.js +11 -0
  33. package/dist/src/slot/types.js.map +1 -1
  34. package/dist/src/tools/index.d.ts +1 -0
  35. package/dist/src/tools/index.d.ts.map +1 -1
  36. package/dist/src/tools/index.js +26 -1
  37. package/dist/src/tools/index.js.map +1 -1
  38. package/dist/src/tools/list-hosts.d.ts +2 -0
  39. package/dist/src/tools/list-hosts.d.ts.map +1 -1
  40. package/dist/src/tools/list-hosts.js.map +1 -1
  41. package/dist/src/tools/manifest-tools.d.ts +38 -0
  42. package/dist/src/tools/manifest-tools.d.ts.map +1 -0
  43. package/dist/src/tools/manifest-tools.js +148 -0
  44. package/dist/src/tools/manifest-tools.js.map +1 -0
  45. package/dist/src/tools/provision-tools.d.ts +28 -8
  46. package/dist/src/tools/provision-tools.d.ts.map +1 -1
  47. package/dist/src/tools/provision-tools.js +153 -25
  48. package/dist/src/tools/provision-tools.js.map +1 -1
  49. package/dist/src/tools/slot-tools.d.ts.map +1 -1
  50. package/dist/src/tools/slot-tools.js +9 -3
  51. package/dist/src/tools/slot-tools.js.map +1 -1
  52. package/dist/src/tools/vm-cleanup.d.ts +79 -0
  53. package/dist/src/tools/vm-cleanup.d.ts.map +1 -0
  54. package/dist/src/tools/vm-cleanup.js +102 -0
  55. package/dist/src/tools/vm-cleanup.js.map +1 -0
  56. package/package.json +3 -1
@@ -59,6 +59,8 @@ export declare const ProvisionVmInputShape: {
59
59
  tags: z.ZodOptional<z.ZodUnion<[z.ZodArray<z.ZodString, "many">, z.ZodString]>>;
60
60
  requester: z.ZodOptional<z.ZodString>;
61
61
  fullClone: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
62
+ waitForReady: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
63
+ readyTimeoutSeconds: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
62
64
  description: z.ZodOptional<z.ZodString>;
63
65
  };
64
66
  export declare const ProvisionVmInputSchema: z.ZodEffects<z.ZodEffects<z.ZodObject<{
@@ -113,6 +115,8 @@ export declare const ProvisionVmInputSchema: z.ZodEffects<z.ZodEffects<z.ZodObje
113
115
  tags: z.ZodOptional<z.ZodUnion<[z.ZodArray<z.ZodString, "many">, z.ZodString]>>;
114
116
  requester: z.ZodOptional<z.ZodString>;
115
117
  fullClone: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
118
+ waitForReady: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
119
+ readyTimeoutSeconds: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
116
120
  description: z.ZodOptional<z.ZodString>;
117
121
  }, "strip", z.ZodTypeAny, {
118
122
  hypervisorType: "proxmox" | "esxi" | "any";
@@ -121,6 +125,8 @@ export declare const ProvisionVmInputSchema: z.ZodEffects<z.ZodEffects<z.ZodObje
121
125
  recoverIncomplete: boolean;
122
126
  startOnDeploy: boolean;
123
127
  fullClone: boolean;
128
+ waitForReady: boolean;
129
+ readyTimeoutSeconds: number;
124
130
  storage?: string | undefined;
125
131
  name?: string | undefined;
126
132
  cores?: number | undefined;
@@ -146,9 +152,9 @@ export declare const ProvisionVmInputSchema: z.ZodEffects<z.ZodEffects<z.ZodObje
146
152
  confirmAssetId?: string | undefined;
147
153
  targetHost?: string | undefined;
148
154
  hostAssetId?: string | undefined;
155
+ outputFile?: string | undefined;
149
156
  templateAssetId?: string | undefined;
150
157
  diskGb?: number | undefined;
151
- outputFile?: string | undefined;
152
158
  workspaceRoot?: string | undefined;
153
159
  nativeCliDir?: string | undefined;
154
160
  importStorage?: string | undefined;
@@ -195,10 +201,10 @@ export declare const ProvisionVmInputSchema: z.ZodEffects<z.ZodEffects<z.ZodObje
195
201
  confirmAssetId?: string | undefined;
196
202
  targetHost?: string | undefined;
197
203
  hostAssetId?: string | undefined;
204
+ outputFile?: string | undefined;
198
205
  templateAssetId?: string | undefined;
199
206
  source?: "template" | "alma97-cloud" | "alma97-iso" | undefined;
200
207
  diskGb?: number | undefined;
201
- outputFile?: string | undefined;
202
208
  workspaceRoot?: string | undefined;
203
209
  nativeCliDir?: string | undefined;
204
210
  importStorage?: string | undefined;
@@ -220,6 +226,8 @@ export declare const ProvisionVmInputSchema: z.ZodEffects<z.ZodEffects<z.ZodObje
220
226
  networkBridge?: string | undefined;
221
227
  startOnDeploy?: boolean | undefined;
222
228
  fullClone?: boolean | undefined;
229
+ waitForReady?: boolean | undefined;
230
+ readyTimeoutSeconds?: number | undefined;
223
231
  }>, {
224
232
  hypervisorType: "proxmox" | "esxi" | "any";
225
233
  apply: boolean;
@@ -227,6 +235,8 @@ export declare const ProvisionVmInputSchema: z.ZodEffects<z.ZodEffects<z.ZodObje
227
235
  recoverIncomplete: boolean;
228
236
  startOnDeploy: boolean;
229
237
  fullClone: boolean;
238
+ waitForReady: boolean;
239
+ readyTimeoutSeconds: number;
230
240
  storage?: string | undefined;
231
241
  name?: string | undefined;
232
242
  cores?: number | undefined;
@@ -252,9 +262,9 @@ export declare const ProvisionVmInputSchema: z.ZodEffects<z.ZodEffects<z.ZodObje
252
262
  confirmAssetId?: string | undefined;
253
263
  targetHost?: string | undefined;
254
264
  hostAssetId?: string | undefined;
265
+ outputFile?: string | undefined;
255
266
  templateAssetId?: string | undefined;
256
267
  diskGb?: number | undefined;
257
- outputFile?: string | undefined;
258
268
  workspaceRoot?: string | undefined;
259
269
  nativeCliDir?: string | undefined;
260
270
  importStorage?: string | undefined;
@@ -301,10 +311,10 @@ export declare const ProvisionVmInputSchema: z.ZodEffects<z.ZodEffects<z.ZodObje
301
311
  confirmAssetId?: string | undefined;
302
312
  targetHost?: string | undefined;
303
313
  hostAssetId?: string | undefined;
314
+ outputFile?: string | undefined;
304
315
  templateAssetId?: string | undefined;
305
316
  source?: "template" | "alma97-cloud" | "alma97-iso" | undefined;
306
317
  diskGb?: number | undefined;
307
- outputFile?: string | undefined;
308
318
  workspaceRoot?: string | undefined;
309
319
  nativeCliDir?: string | undefined;
310
320
  importStorage?: string | undefined;
@@ -326,6 +336,8 @@ export declare const ProvisionVmInputSchema: z.ZodEffects<z.ZodEffects<z.ZodObje
326
336
  networkBridge?: string | undefined;
327
337
  startOnDeploy?: boolean | undefined;
328
338
  fullClone?: boolean | undefined;
339
+ waitForReady?: boolean | undefined;
340
+ readyTimeoutSeconds?: number | undefined;
329
341
  }>, {
330
342
  hypervisorType: "proxmox" | "esxi" | "any";
331
343
  apply: boolean;
@@ -333,6 +345,8 @@ export declare const ProvisionVmInputSchema: z.ZodEffects<z.ZodEffects<z.ZodObje
333
345
  recoverIncomplete: boolean;
334
346
  startOnDeploy: boolean;
335
347
  fullClone: boolean;
348
+ waitForReady: boolean;
349
+ readyTimeoutSeconds: number;
336
350
  storage?: string | undefined;
337
351
  name?: string | undefined;
338
352
  cores?: number | undefined;
@@ -358,9 +372,9 @@ export declare const ProvisionVmInputSchema: z.ZodEffects<z.ZodEffects<z.ZodObje
358
372
  confirmAssetId?: string | undefined;
359
373
  targetHost?: string | undefined;
360
374
  hostAssetId?: string | undefined;
375
+ outputFile?: string | undefined;
361
376
  templateAssetId?: string | undefined;
362
377
  diskGb?: number | undefined;
363
- outputFile?: string | undefined;
364
378
  workspaceRoot?: string | undefined;
365
379
  nativeCliDir?: string | undefined;
366
380
  importStorage?: string | undefined;
@@ -407,10 +421,10 @@ export declare const ProvisionVmInputSchema: z.ZodEffects<z.ZodEffects<z.ZodObje
407
421
  confirmAssetId?: string | undefined;
408
422
  targetHost?: string | undefined;
409
423
  hostAssetId?: string | undefined;
424
+ outputFile?: string | undefined;
410
425
  templateAssetId?: string | undefined;
411
426
  source?: "template" | "alma97-cloud" | "alma97-iso" | undefined;
412
427
  diskGb?: number | undefined;
413
- outputFile?: string | undefined;
414
428
  workspaceRoot?: string | undefined;
415
429
  nativeCliDir?: string | undefined;
416
430
  importStorage?: string | undefined;
@@ -432,6 +446,8 @@ export declare const ProvisionVmInputSchema: z.ZodEffects<z.ZodEffects<z.ZodObje
432
446
  networkBridge?: string | undefined;
433
447
  startOnDeploy?: boolean | undefined;
434
448
  fullClone?: boolean | undefined;
449
+ waitForReady?: boolean | undefined;
450
+ readyTimeoutSeconds?: number | undefined;
435
451
  }>;
436
452
  export type ProvisionVmInput = z.infer<typeof ProvisionVmInputSchema>;
437
453
  export type ProvisionVmRawInput = z.input<typeof ProvisionVmInputSchema>;
@@ -487,6 +503,7 @@ export declare const AutoProvisionVmInputShape: {
487
503
  ipAddress: z.ZodOptional<z.ZodString>;
488
504
  networkBridge: z.ZodOptional<z.ZodString>;
489
505
  storage: z.ZodOptional<z.ZodString>;
506
+ fullClone: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
490
507
  waitForReady: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
491
508
  readyTimeoutSeconds: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
492
509
  };
@@ -541,6 +558,7 @@ export declare const AutoProvisionVmInputSchema: z.ZodObject<{
541
558
  ipAddress: z.ZodOptional<z.ZodString>;
542
559
  networkBridge: z.ZodOptional<z.ZodString>;
543
560
  storage: z.ZodOptional<z.ZodString>;
561
+ fullClone: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
544
562
  waitForReady: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
545
563
  readyTimeoutSeconds: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
546
564
  }, "strip", z.ZodTypeAny, {
@@ -549,6 +567,7 @@ export declare const AutoProvisionVmInputSchema: z.ZodObject<{
549
567
  apply: boolean;
550
568
  source: "template" | "alma97-cloud" | "alma97-iso";
551
569
  recoverIncomplete: boolean;
570
+ fullClone: boolean;
552
571
  waitForReady: boolean;
553
572
  readyTimeoutSeconds: number;
554
573
  storage?: string | undefined;
@@ -577,9 +596,9 @@ export declare const AutoProvisionVmInputSchema: z.ZodObject<{
577
596
  confirmAssetId?: string | undefined;
578
597
  targetHost?: string | undefined;
579
598
  hostAssetId?: string | undefined;
599
+ outputFile?: string | undefined;
580
600
  templateAssetId?: string | undefined;
581
601
  diskGb?: number | undefined;
582
- outputFile?: string | undefined;
583
602
  workspaceRoot?: string | undefined;
584
603
  nativeCliDir?: string | undefined;
585
604
  importStorage?: string | undefined;
@@ -626,10 +645,10 @@ export declare const AutoProvisionVmInputSchema: z.ZodObject<{
626
645
  confirmAssetId?: string | undefined;
627
646
  targetHost?: string | undefined;
628
647
  hostAssetId?: string | undefined;
648
+ outputFile?: string | undefined;
629
649
  templateAssetId?: string | undefined;
630
650
  source?: "template" | "alma97-cloud" | "alma97-iso" | undefined;
631
651
  diskGb?: number | undefined;
632
- outputFile?: string | undefined;
633
652
  workspaceRoot?: string | undefined;
634
653
  nativeCliDir?: string | undefined;
635
654
  importStorage?: string | undefined;
@@ -647,6 +666,7 @@ export declare const AutoProvisionVmInputSchema: z.ZodObject<{
647
666
  newVmName?: string | undefined;
648
667
  newVmId?: string | number | undefined;
649
668
  networkBridge?: string | undefined;
669
+ fullClone?: boolean | undefined;
650
670
  waitForReady?: boolean | undefined;
651
671
  readyTimeoutSeconds?: number | undefined;
652
672
  }>;
@@ -1 +1 @@
1
- {"version":3,"file":"provision-tools.d.ts","sourceRoot":"","sources":["../../../src/tools/provision-tools.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,cAAc,EAA2C,MAAM,iBAAiB,CAAC;AAM1F,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAa9C,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkNjC,CAAC;AAEF,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAShC,CAAC;AAEJ,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AACtE,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAEzE,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,mBAAmB,EAC5B,OAAO,EAAE,WAAW,GACnB,OAAO,CAAC,cAAc,CAAC,CA6QzB;AAMD,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA0IrC,CAAC;AAEF,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAAsC,CAAC;AAC9E,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAC9E,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAEjF,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,uBAAuB,EAChC,OAAO,EAAE,WAAW,GACnB,OAAO,CAAC,cAAc,CAAC,CA+YzB"}
1
+ {"version":3,"file":"provision-tools.d.ts","sourceRoot":"","sources":["../../../src/tools/provision-tools.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,cAAc,EAA2C,MAAM,iBAAiB,CAAC;AAM1F,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAc9C,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+NjC,CAAC;AAEF,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAShC,CAAC;AAEJ,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AACtE,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAEzE,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,mBAAmB,EAC5B,OAAO,EAAE,WAAW,GACnB,OAAO,CAAC,cAAc,CAAC,CA8UzB;AAMD,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+IrC,CAAC;AAEF,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAAsC,CAAC;AAC9E,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAC9E,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAEjF,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,uBAAuB,EAChC,OAAO,EAAE,WAAW,GACnB,OAAO,CAAC,cAAc,CAAC,CA6czB"}
@@ -7,7 +7,7 @@
7
7
  import { z } from "zod";
8
8
  import { ProxmoxErrorCode, createToolErrorResult } from "../types/mcp.js";
9
9
  import { formatProvisionResult, formatAutoProvisionResult, } from "../utils/formatters.js";
10
- import { CapacityExceededError, GlobalCapacityExceededError, DatastoreCapacityExceededError, } from "../slot/types.js";
10
+ import { CapacityExceededError, GlobalCapacityExceededError, DatastoreCapacityExceededError, HypervisorSlotUnsupportedError, } from "../slot/types.js";
11
11
  import { ProtectedVmViolationError } from "../guardrails/types.js";
12
12
  import { applianceSshExecutor } from "../appliance/executor.js";
13
13
  import { MockProxmoxClient } from "../proxmox/mock.js";
@@ -216,8 +216,21 @@ export const ProvisionVmInputShape = {
216
216
  fullClone: z
217
217
  .boolean()
218
218
  .optional()
219
- .default(true)
220
- .describe("Whether to perform a full disk clone (default: true)."),
219
+ .default(false)
220
+ .describe("Whether to perform a full disk clone (default: false for fast linked cloning)."),
221
+ waitForReady: z
222
+ .boolean()
223
+ .optional()
224
+ .default(false)
225
+ .describe("Whether to poll port 2222 SSH readiness after provisioning (default: false)."),
226
+ readyTimeoutSeconds: z
227
+ .number()
228
+ .int()
229
+ .min(5)
230
+ .max(600)
231
+ .optional()
232
+ .default(180)
233
+ .describe("Readiness polling timeout in seconds (default: 180)."),
221
234
  description: z
222
235
  .string()
223
236
  .optional()
@@ -230,6 +243,8 @@ export const ProvisionVmInputSchema = z
230
243
  export async function handleProvisionVm(rawArgs, context) {
231
244
  let autoClaimId;
232
245
  let slotClaim;
246
+ let createdVmId;
247
+ let createdHost;
233
248
  try {
234
249
  const parseResult = ProvisionVmInputSchema.safeParse(rawArgs);
235
250
  if (!parseResult.success) {
@@ -344,6 +359,8 @@ export async function handleProvisionVm(rawArgs, context) {
344
359
  const linearIssue = args.linearIssue;
345
360
  const requester = args.requester;
346
361
  const startOnDeploy = args.start !== undefined ? args.start : args.startOnDeploy;
362
+ const waitForReady = args.waitForReady ?? false;
363
+ const readyTimeoutSeconds = args.readyTimeoutSeconds ?? 180;
347
364
  let parsedTags = [];
348
365
  if (args.tags) {
349
366
  if (Array.isArray(args.tags)) {
@@ -362,14 +379,14 @@ export async function handleProvisionVm(rawArgs, context) {
362
379
  }
363
380
  }
364
381
  let claimId = args.slotClaimId;
365
- if (!claimId) {
382
+ if (!claimId && (!effectiveTargetHost || (!effectiveTargetHost.startsWith("esxi") && args.hypervisorType !== "esxi"))) {
366
383
  const claimResult = await context.slotManager.claimSlot({
367
384
  tag: linearIssue || newName,
368
385
  requester: requester || newName,
369
386
  linearIssue: linearIssue || (newName.startsWith("CTUAT-") ? newName : undefined),
370
387
  targetHost: effectiveTargetHost,
371
388
  hypervisorType: args.hypervisorType,
372
- durationMinutes: 60,
389
+ durationMinutes: 120,
373
390
  purpose: `Disposable test VM: ${newName}`,
374
391
  });
375
392
  autoClaimId = claimResult.claimId;
@@ -384,7 +401,7 @@ export async function handleProvisionVm(rawArgs, context) {
384
401
  newVmId: newVmId,
385
402
  targetHost: effectiveTargetHost || (slotClaim ? String(slotClaim.hostId) : undefined),
386
403
  hypervisorType: hypervisorPref,
387
- fullClone: args.fullClone,
404
+ fullClone: args.fullClone ?? false,
388
405
  description: args.description || `Call Telemetry test VM (${linearIssue || "disposable"})`,
389
406
  memoryBytes: args.memoryBytes || (args.memoryMb ? args.memoryMb * 1024 * 1024 : 8192 * 1024 * 1024),
390
407
  cpuCores: args.cpuCores ?? args.cores ?? 2,
@@ -413,7 +430,7 @@ export async function handleProvisionVm(rawArgs, context) {
413
430
  const sourceVmidNum = typeof source === "number" ? source : parseInt(String(source), 10);
414
431
  const targetVmid = newVmidNum > 0 ? newVmidNum : 105;
415
432
  const cloneTask = await client.cloneVm(sourceVmidNum, targetVmid, newName, {
416
- full: cloneParams.fullClone,
433
+ full: cloneParams.fullClone ?? false,
417
434
  description: cloneParams.description,
418
435
  });
419
436
  if (startOnDeploy) {
@@ -447,6 +464,8 @@ export async function handleProvisionVm(rawArgs, context) {
447
464
  else {
448
465
  throw new Error("No hypervisor manager available to provision VM.");
449
466
  }
467
+ createdVmId = provisionResult.vmId;
468
+ createdHost = provisionResult.hostName;
450
469
  // 3. Attach VM ID/Name to the claimed slot
451
470
  if (claimId) {
452
471
  try {
@@ -456,6 +475,39 @@ export async function handleProvisionVm(rawArgs, context) {
456
475
  console.error("[provision_vm] Warning attaching VM to slot lease:", attachErr);
457
476
  }
458
477
  }
478
+ // 4. Optional port 2222 SSH readiness polling
479
+ if (waitForReady) {
480
+ const assignedIp = args.ipAddress ||
481
+ (provisionResult.ipAddresses && provisionResult.ipAddresses.length > 0
482
+ ? provisionResult.ipAddresses[0]
483
+ : `192.168.124.${100 + (Number(provisionResult.vmId) % 100 || 5)}`);
484
+ const executor = context.applianceExecutor || applianceSshExecutor;
485
+ const pollStart = Date.now();
486
+ const timeoutMs = readyTimeoutSeconds * 1000;
487
+ let isReady = false;
488
+ while (Date.now() - pollStart < timeoutMs) {
489
+ try {
490
+ const connected = await executor.testConnection(assignedIp, 2222, 2000);
491
+ if (connected) {
492
+ isReady = true;
493
+ break;
494
+ }
495
+ }
496
+ catch {
497
+ // continue polling
498
+ }
499
+ const elapsed = Date.now() - pollStart;
500
+ if (elapsed + 1000 < timeoutMs) {
501
+ await new Promise((r) => setTimeout(r, Math.min(1000, timeoutMs - elapsed)));
502
+ }
503
+ else {
504
+ break;
505
+ }
506
+ }
507
+ if (!isReady) {
508
+ throw new Error(`VM readiness timed out after ${readyTimeoutSeconds}s on ${assignedIp}:2222`);
509
+ }
510
+ }
459
511
  const formatted = formatProvisionResult(provisionResult, slotClaim);
460
512
  return {
461
513
  content: [
@@ -477,6 +529,28 @@ export async function handleProvisionVm(rawArgs, context) {
477
529
  // ignore rollback error
478
530
  }
479
531
  }
532
+ // Atomic Rollback: Destroy created VM if created before failure
533
+ if (createdVmId && createdHost && context.hypervisorManager) {
534
+ try {
535
+ await context.hypervisorManager.vmAction({
536
+ targetHost: createdHost,
537
+ vmIdOrName: String(createdVmId),
538
+ action: "destroy",
539
+ force: true,
540
+ });
541
+ }
542
+ catch {
543
+ // ignore rollback error
544
+ }
545
+ }
546
+ else if (createdVmId && createdHost && context.proxmoxManager) {
547
+ try {
548
+ await context.proxmoxManager.getClient(createdHost).destroyVm(Number(createdVmId), { purge: true });
549
+ }
550
+ catch {
551
+ // ignore rollback error
552
+ }
553
+ }
480
554
  if (error instanceof ProtectedVmViolationError) {
481
555
  return createToolErrorResult(ProxmoxErrorCode.PROTECTED_VM_ACTION_DENIED, error.message, {
482
556
  vmid: error.vmid,
@@ -557,11 +631,11 @@ export const AutoProvisionVmInputShape = {
557
631
  targetVersion: z
558
632
  .string()
559
633
  .optional()
560
- .describe("Call Telemetry release version to deploy/pin (e.g. '0.8.7-stable', '0.8.8')."),
634
+ .describe("Call Telemetry release version to deploy/pin (e.g. '0.8.7-rc', '0.8.7-stable', '0.8.8'). Defaults to '0.8.7-rc'."),
561
635
  version: z
562
636
  .string()
563
637
  .optional()
564
- .describe("Alias for targetVersion."),
638
+ .describe("Alias for targetVersion (e.g. '0.8.7-rc')."),
565
639
  workloadType: z
566
640
  .enum(["uat", "perf", "soak", "dev", "regression", "reproduction", "demo"])
567
641
  .optional()
@@ -592,8 +666,8 @@ export const AutoProvisionVmInputShape = {
592
666
  .min(1)
593
667
  .max(1440)
594
668
  .optional()
595
- .default(60)
596
- .describe("Slot lease TTL in minutes (default: 60)."),
669
+ .default(120)
670
+ .describe("Slot lease TTL in minutes (default: 120)."),
597
671
  durationMinutes: z
598
672
  .number()
599
673
  .int()
@@ -620,6 +694,11 @@ export const AutoProvisionVmInputShape = {
620
694
  .string()
621
695
  .optional()
622
696
  .describe("Target storage pool / datastore name."),
697
+ fullClone: z
698
+ .boolean()
699
+ .optional()
700
+ .default(false)
701
+ .describe("Whether to perform a full disk clone (default: false for fast linked cloning)."),
623
702
  waitForReady: z
624
703
  .boolean()
625
704
  .optional()
@@ -755,7 +834,7 @@ export async function handleAutoProvisionVm(rawArgs, context) {
755
834
  const linearIssue = args.linearIssue;
756
835
  const label = args.label;
757
836
  const branch = args.branch;
758
- const durationMinutes = args.durationMinutes ?? args.ttlMinutes ?? 60;
837
+ const durationMinutes = args.durationMinutes ?? args.ttlMinutes ?? 120;
759
838
  const vmName = args.name ||
760
839
  args.newVmName ||
761
840
  `ct-appliance-${workloadType}-${Date.now().toString(36)}`;
@@ -789,7 +868,7 @@ export async function handleAutoProvisionVm(rawArgs, context) {
789
868
  const hostId = claimedSlot.hostId;
790
869
  createdHost = hostId;
791
870
  const hypType = hostId.startsWith("esxi") ? "esxi" : "proxmox";
792
- // 2. Resolve Base Template
871
+ // 2. Resolve Base Template (prioritize AlmaLinux 9.7 base template: VMID 9000 / CT-ALMALINUX9-TEMPLATE / /almalinux.*9.*7/i)
793
872
  let template = args.template ?? args.sourceVmIdOrName;
794
873
  if (!template && context.hypervisorManager) {
795
874
  try {
@@ -797,7 +876,16 @@ export async function handleAutoProvisionVm(rawArgs, context) {
797
876
  includeTemplates: true,
798
877
  host: hostId,
799
878
  });
800
- const templateVm = vms.find((v) => v.isTemplate) ||
879
+ const templateVm = vms.find((v) => v.isTemplate &&
880
+ (v.id === "9000" ||
881
+ v.name.toUpperCase() === "CT-ALMALINUX9-TEMPLATE" ||
882
+ v.name.toUpperCase() === "CT-ALMALINUX9-MINI-TEMPLATE" ||
883
+ /almalinux.*9.*7/i.test(v.name) ||
884
+ (v.tags && v.tags.some((t) => /almalinux.*9.*7/i.test(t))))) ||
885
+ vms.find((v) => /almalinux.*9.*7/i.test(v.name) ||
886
+ v.name.toUpperCase().includes("ALMALINUX9") ||
887
+ v.id === "9000") ||
888
+ vms.find((v) => v.isTemplate) ||
801
889
  vms.find((v) => v.name.toLowerCase().includes("template") ||
802
890
  v.name.toLowerCase().includes("tmpl") ||
803
891
  (v.tags && v.tags.some((t) => t.toLowerCase().includes("template"))));
@@ -814,10 +902,10 @@ export async function handleAutoProvisionVm(rawArgs, context) {
814
902
  template = "CT-087-STABLE-TEMPLATE";
815
903
  }
816
904
  else if (hostId === "proxmox-mini") {
817
- template = "200";
905
+ template = "9000";
818
906
  }
819
907
  else {
820
- template = "102";
908
+ template = "9000";
821
909
  }
822
910
  }
823
911
  // 3. Clone VM from Template
@@ -826,7 +914,7 @@ export async function handleAutoProvisionVm(rawArgs, context) {
826
914
  newVmName: vmName,
827
915
  targetHost: hostId,
828
916
  hypervisorType: hypType,
829
- fullClone: true,
917
+ fullClone: args.fullClone ?? false,
830
918
  description: `Auto-provisioned ${workloadType} VM (${linearIssue || label || "disposable"})`,
831
919
  memoryBytes: 8192 * 1024 * 1024,
832
920
  cpuCores: 2,
@@ -850,7 +938,7 @@ export async function handleAutoProvisionVm(rawArgs, context) {
850
938
  const clientIsMock = client instanceof MockProxmoxClient;
851
939
  const targetVmid = 500;
852
940
  const sourceNum = typeof template === "number" ? template : parseInt(String(template), 10) || 9000;
853
- const cloneTask = await client.cloneVm(sourceNum, targetVmid, vmName, { full: true });
941
+ const cloneTask = await client.cloneVm(sourceNum, targetVmid, vmName, { full: args.fullClone ?? false });
854
942
  await client.startVm(targetVmid).catch(() => { });
855
943
  provisionResult = {
856
944
  vmId: String(targetVmid),
@@ -916,23 +1004,49 @@ export async function handleAutoProvisionVm(rawArgs, context) {
916
1004
  throw new Error(`VM readiness timed out after ${readyTimeoutSeconds}s on ${assignedIp}:2222 (healthStatus unverified).`);
917
1005
  }
918
1006
  }
919
- // 7. Deploy/Pin Target Release Version if Requested
1007
+ // 7. Deploy/Pin Target Release Version (defaults to 0.8.7-rc if omitted)
1008
+ const versionToDeploy = targetVersion || "0.8.7-rc";
920
1009
  let deployedVersion;
921
- if (targetVersion) {
1010
+ if (versionToDeploy) {
922
1011
  const executor = context.applianceExecutor || applianceSshExecutor;
923
1012
  const verRes = await executor.execute({
924
1013
  targetIp: assignedIp,
925
1014
  port: 2222,
926
1015
  commandType: "set_version",
927
- args: { version: targetVersion },
1016
+ args: { version: versionToDeploy },
928
1017
  timeoutMs: 60000,
929
1018
  });
930
1019
  if (verRes.exitCode !== 0) {
931
- throw new Error(`Failed to deploy requested version '${targetVersion}' on ${assignedIp}: ${verRes.stderr || verRes.stdout}`);
1020
+ throw new Error(`Failed to deploy requested version '${versionToDeploy}' on ${assignedIp}: ${verRes.stderr || verRes.stdout}`);
932
1021
  }
933
- deployedVersion = targetVersion;
1022
+ deployedVersion = versionToDeploy;
934
1023
  }
935
- // 8. Return Verified Endpoints & Summary
1024
+ // 8. Chained Health Assertions & Database Migration Verification
1025
+ if (waitForReady) {
1026
+ const executor = context.applianceExecutor || applianceSshExecutor;
1027
+ // Assert health (/healthz 200 OK, Caddy SSL, containers healthy)
1028
+ const healthRes = await executor.execute({
1029
+ targetIp: assignedIp,
1030
+ port: 2222,
1031
+ commandType: "assert_health",
1032
+ timeoutMs: 60000,
1033
+ });
1034
+ if (healthRes.exitCode !== 0) {
1035
+ throw new Error(`Appliance health assertion failed on ${assignedIp}: ${healthRes.stderr || healthRes.stdout}`);
1036
+ }
1037
+ // Verify database migrations
1038
+ const migRes = await executor.execute({
1039
+ targetIp: assignedIp,
1040
+ port: 2222,
1041
+ commandType: "migrate_status",
1042
+ timeoutMs: 60000,
1043
+ });
1044
+ if (migRes.exitCode !== 0) {
1045
+ throw new Error(`Appliance database migration check failed on ${assignedIp}: ${migRes.stderr || migRes.stdout}`);
1046
+ }
1047
+ healthStatus = "healthy";
1048
+ }
1049
+ // 9. Return Verified Endpoints & Summary
936
1050
  const autoResult = {
937
1051
  success: true,
938
1052
  slotId: claimedSlot.slotId,
@@ -948,7 +1062,7 @@ export async function handleAutoProvisionVm(rawArgs, context) {
948
1062
  user: "admin",
949
1063
  },
950
1064
  apiEndpoint: `https://${assignedIp}:443`,
951
- targetVersion,
1065
+ targetVersion: targetVersion || versionToDeploy,
952
1066
  deployedVersion,
953
1067
  workloadType,
954
1068
  label,
@@ -996,6 +1110,20 @@ export async function handleAutoProvisionVm(rawArgs, context) {
996
1110
  // ignore VM teardown error
997
1111
  }
998
1112
  }
1113
+ else if (createdVmId && createdHost && context.proxmoxManager) {
1114
+ try {
1115
+ await context.proxmoxManager.getClient(createdHost).destroyVm(Number(createdVmId), { purge: true });
1116
+ }
1117
+ catch {
1118
+ // ignore VM teardown error
1119
+ }
1120
+ }
1121
+ if (error instanceof HypervisorSlotUnsupportedError) {
1122
+ return createToolErrorResult(ProxmoxErrorCode.OPERATION_NOT_SUPPORTED, error.message, {
1123
+ host: error.hostId,
1124
+ hypervisorType: error.hypervisorType,
1125
+ });
1126
+ }
999
1127
  if (error instanceof ProtectedVmViolationError) {
1000
1128
  return createToolErrorResult(ProxmoxErrorCode.PROTECTED_VM_ACTION_DENIED, error.message, {
1001
1129
  vmid: error.vmid,