@kikimoradev/kcloud 1.2.0 → 1.4.0

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 (2) hide show
  1. package/dist/main.js +352 -127
  2. package/package.json +1 -1
package/dist/main.js CHANGED
@@ -844,7 +844,6 @@ const errorCodes = Object.keys({
844
844
  "backup.unavailable": "rule_violation",
845
845
  "backup.corrupt": "rule_violation",
846
846
  "backup.undecryptable": "rule_violation",
847
- "volume.invalid_size": "rule_violation",
848
847
  "notification.not_found": "not_found",
849
848
  "logo.not_found": "not_found",
850
849
  "logo.unreadable": "rule_violation",
@@ -1101,7 +1100,8 @@ agentEventSchema.options.map((option) => option.shape.event.value);
1101
1100
  crashLooping: "platform.crashLooping",
1102
1101
  provisioned: "platform.provisioned",
1103
1102
  sandboxStarted: "platform.sandboxStarted",
1104
- sandboxStopped: "platform.sandboxStopped"
1103
+ sandboxStopped: "platform.sandboxStopped",
1104
+ restartedByNode: "platform.restartedByNode"
1105
1105
  }).rawLog;
1106
1106
  const agentStateSchema = z.enum([
1107
1107
  "created",
@@ -1198,41 +1198,11 @@ const degradedAgentSchema = z.object({
1198
1198
  z.object({
1199
1199
  status: z.enum(runtimeHealthStates),
1200
1200
  database: z.object({ ok: z.boolean() }),
1201
- engine: z.enum(runtimeEngineKinds),
1202
- docker: z.object({
1203
- state: z.enum(dockerStates),
1204
- lastError: z.string().nullable(),
1205
- checkedAt: z.iso.datetime().nullable()
1206
- }),
1207
- storage: z.object({
1208
- data: storageStateSchema,
1209
- engine: storageStateSchema
1210
- }),
1211
1201
  partitions: z.object({
1212
1202
  defaultRows: z.int().nonnegative(),
1213
1203
  ensuredThrough: z.string().nullable(),
1214
1204
  lastMaintenanceAt: z.iso.datetime().nullable()
1215
1205
  }),
1216
- reconcile: z.object({
1217
- lastTickAt: z.iso.datetime().nullable(),
1218
- lastTickError: z.string().nullable(),
1219
- agentsQueued: z.int().nonnegative(),
1220
- agentsActive: z.int().nonnegative()
1221
- }),
1222
- ingest: z.object({
1223
- readers: z.int().nonnegative(),
1224
- oldestCursorLagSeconds: z.number().nonnegative().nullable()
1225
- }),
1226
- sandbox: z.object({
1227
- enabled: z.boolean(),
1228
- image: z.object({
1229
- tag: z.string(),
1230
- digest: z.string()
1231
- }),
1232
- agentsWithSandbox: z.int().nonnegative(),
1233
- sidecarsRunning: z.int().nonnegative(),
1234
- volumeBytes: z.int().nonnegative().nullable()
1235
- }),
1236
1206
  metrics: runtimeMetricsSchema,
1237
1207
  deadLetters: z.array(deadLetterQueueSchema),
1238
1208
  degradedAgents: z.array(degradedAgentSchema)
@@ -1641,8 +1611,10 @@ const admissionHoldSchema = z.object({
1641
1611
  const nodeStateSchema = z.enum([
1642
1612
  "pending",
1643
1613
  "online",
1644
- "offline"
1614
+ "offline",
1615
+ "outdated"
1645
1616
  ]);
1617
+ const minimumNodeVersion = "1.3.0";
1646
1618
  const nodeProviders = ["self_hosted", "digitalocean"];
1647
1619
  const nodeProviderSchema = z.enum(nodeProviders);
1648
1620
  const nodeNameSchema = z.string().trim().regex(slugPattern);
@@ -1706,7 +1678,8 @@ const poolUsageSchema = z.object({
1706
1678
  memoryBytes: z.int().nonnegative(),
1707
1679
  memoryMaxBytes: z.int().nonnegative().nullable(),
1708
1680
  cpus: z.number().nonnegative().nullable(),
1709
- memoryKills: z.int().nonnegative()
1681
+ memoryKills: z.int().nonnegative(),
1682
+ processors: z.int().positive().nullable().optional()
1710
1683
  }).meta({ id: "PoolUsage" });
1711
1684
  const nodePoolSchema = z.object({
1712
1685
  slots: z.int().nonnegative(),
@@ -1715,8 +1688,26 @@ const nodePoolSchema = z.object({
1715
1688
  holders: z.array(sessionHolderSchema).nullable(),
1716
1689
  usage: poolUsageSchema.nullable()
1717
1690
  }).meta({ id: "NodePool" });
1691
+ const sessionRefusalsSchema = z.object({
1692
+ count: z.int().positive(),
1693
+ lastAt: z.iso.datetime(),
1694
+ pool: sessionRoleSchema,
1695
+ reason: z.string()
1696
+ }).meta({ id: "SessionRefusals" });
1697
+ const poolLayoutSchema = z.object({
1698
+ cores: z.array(z.int().positive()).min(1),
1699
+ cpus: z.object({
1700
+ monitor: z.number().positive(),
1701
+ executor: z.number().positive()
1702
+ }).nullable(),
1703
+ topologyError: z.string().nullable(),
1704
+ clamped: z.boolean()
1705
+ }).meta({ id: "PoolLayout" });
1718
1706
  const nodePoolsSchema = z.object({
1719
1707
  enforcement: poolEnforcementSchema,
1708
+ agentsSlice: poolEnforcementSchema.nullable(),
1709
+ refused: sessionRefusalsSchema.nullable(),
1710
+ layout: poolLayoutSchema.nullable(),
1720
1711
  monitor: nodePoolSchema,
1721
1712
  executor: nodePoolSchema
1722
1713
  }).meta({ id: "NodePools" });
@@ -1822,12 +1813,7 @@ const nodeAgentSchema = z.object({
1822
1813
  const nodeRuntimeSchema = z.object({
1823
1814
  reportedAt: z.iso.datetime(),
1824
1815
  docker: z.object({
1825
- state: z.enum([
1826
- "ok",
1827
- "unavailable",
1828
- "disabled",
1829
- "adopting"
1830
- ]),
1816
+ state: z.enum(dockerStates),
1831
1817
  lastError: z.string().nullable()
1832
1818
  }),
1833
1819
  storage: z.object({
@@ -1840,7 +1826,20 @@ const nodeRuntimeSchema = z.object({
1840
1826
  agentsQueued: z.int().nonnegative(),
1841
1827
  agentsActive: z.int().nonnegative()
1842
1828
  }),
1843
- ingest: z.object({ readers: z.int().nonnegative() }),
1829
+ ingest: z.object({
1830
+ readers: z.int().nonnegative(),
1831
+ oldestCursorLagSeconds: z.number().nonnegative().nullable()
1832
+ }),
1833
+ sandbox: z.object({
1834
+ enabled: z.boolean(),
1835
+ image: z.object({
1836
+ tag: z.string(),
1837
+ digest: z.string()
1838
+ }),
1839
+ agentsWithSandbox: z.int().nonnegative(),
1840
+ sidecarsRunning: z.int().nonnegative(),
1841
+ volumeBytes: z.int().nonnegative().nullable()
1842
+ }),
1844
1843
  sessions: z.object({
1845
1844
  monitor: z.object({
1846
1845
  effective: z.int().nonnegative(),
@@ -1859,6 +1858,28 @@ nodeSchema.extend({
1859
1858
  update: nodeUpdateSchema.nullable()
1860
1859
  }).meta({ id: "NodeDetail" });
1861
1860
  z.object({ items: z.array(nodeSchema) }).meta({ id: "NodeList" });
1861
+ const adminNodeSessionsSchema = z.object({
1862
+ effective: z.int().nonnegative(),
1863
+ inUse: z.int().nonnegative()
1864
+ });
1865
+ const nodeHealthSchema = z.object({
1866
+ docker: z.enum(dockerStates),
1867
+ storage: z.enum(storageStates),
1868
+ reconcileError: z.string().nullable()
1869
+ }).meta({ id: "NodeHealth" });
1870
+ const adminNodeSchema = nodeSchema.extend({
1871
+ organization: z.object({
1872
+ id: idSchema,
1873
+ name: z.string().nullable()
1874
+ }),
1875
+ update: nodeUpdateSchema.nullable(),
1876
+ sessions: z.object({
1877
+ monitor: adminNodeSessionsSchema,
1878
+ executor: adminNodeSessionsSchema
1879
+ }).nullable(),
1880
+ health: nodeHealthSchema.nullable()
1881
+ }).meta({ id: "AdminNode" });
1882
+ z.object({ items: z.array(adminNodeSchema) }).meta({ id: "AdminNodeList" });
1862
1883
  z.object({ nodeId: idSchema });
1863
1884
  z.object({
1864
1885
  organizationId: idSchema,
@@ -1932,6 +1953,30 @@ const splitFits = (split, host) => {
1932
1953
  const overflow = splitOverflowOf(split, host);
1933
1954
  return overflow.cpus <= 0 && overflow.memoryMb <= 0;
1934
1955
  };
1956
+ const poolProcessorsOf = (cpus, hostCpus) => Math.max(1, Math.min(Math.round(cpus), Math.round(hostCpus)));
1957
+ const layoutAppliesSplit = (layout, split) => layout?.cpus != null && split !== null && layout.cpus.monitor === split.monitor.cpus && layout.cpus.executor === split.executor.cpus;
1958
+ const splitCoresOf = (split, cores) => {
1959
+ const processors = cores.reduce((total, size) => total + size, 0);
1960
+ const sizeAt = (index) => cores[Math.min(index, cores.length - 1)] ?? 1;
1961
+ const coresFrom = (first, cpus) => {
1962
+ const wanted = poolProcessorsOf(cpus, processors);
1963
+ let taken = sizeAt(first);
1964
+ let count = 1;
1965
+ while (taken + sizeAt(first + count) <= wanted) {
1966
+ taken += sizeAt(first + count);
1967
+ count += 1;
1968
+ }
1969
+ return count;
1970
+ };
1971
+ const executor = coresFrom(0, split.executor.cpus);
1972
+ const monitor = coresFrom(executor, split.monitor.cpus);
1973
+ return {
1974
+ cores: cores.length,
1975
+ monitor,
1976
+ executor,
1977
+ fits: executor + monitor < cores.length
1978
+ };
1979
+ };
1935
1980
  //#endregion
1936
1981
  //#region ../../packages/contracts/src/appearance.ts
1937
1982
  const colorSchema = z.string().regex(hexColorPattern);
@@ -2571,9 +2616,6 @@ const containerHealthSchema = z.enum([
2571
2616
  ]);
2572
2617
  const maxAgentDescriptionLength = 2e3;
2573
2618
  const maxAgentPromptLength = 64e3;
2574
- const minAgentCpus = .25;
2575
- const maxAgentMemoryMb = 16384;
2576
- const defaultAgentMemoryMb = 4096;
2577
2619
  const workspaceDirectorySchema = z.string().regex(/^[a-z0-9](?:[a-z0-9._-]{0,62}[a-z0-9])?$/);
2578
2620
  const timeZones = new Set(Intl.supportedValuesOf("timeZone"));
2579
2621
  const timezoneSchema = z.string().trim().refine((value) => timeZones.has(value), { message: "expected an IANA time zone identifier" });
@@ -2584,11 +2626,10 @@ const agentDiskGbSchema = z.int().min(1).max(100);
2584
2626
  const agentMachineSchema = z.object({
2585
2627
  docker: z.boolean(),
2586
2628
  browser: z.boolean(),
2587
- cpus: z.number().min(minAgentCpus).max(8),
2588
- memoryMb: z.int().min(512).max(maxAgentMemoryMb),
2589
2629
  diskGb: agentDiskGbSchema
2590
- }).meta({ id: "AgentMachine" });
2630
+ });
2591
2631
  const agentMachineInputSchema = agentMachineSchema.extend({ diskGb: agentDiskGbSchema.optional() });
2632
+ const agentMachineDtoSchema = agentMachineSchema.meta({ id: "AgentMachine" });
2592
2633
  const agentPromptsSchema = z.object({
2593
2634
  monitor: agentPromptSchema,
2594
2635
  executor: agentPromptSchema
@@ -2674,7 +2715,7 @@ agentListItemSchema.extend({
2674
2715
  version: z.int().nonnegative(),
2675
2716
  description: z.string(),
2676
2717
  timezone: z.string(),
2677
- machine: agentMachineSchema,
2718
+ machine: agentMachineDtoSchema,
2678
2719
  settings: kikimoraSettingsSchema,
2679
2720
  effectiveSettings: workerSettingsSchema,
2680
2721
  prompts: agentPromptsSchema,
@@ -2816,13 +2857,20 @@ z.object({
2816
2857
  nodeId: idSchema
2817
2858
  }).meta({ id: "RestoreAgentBody" });
2818
2859
  z.object({ agentId: idSchema }).meta({ id: "RestoredAgent" });
2860
+ const agentBackupContentDigestSchema = z.string().regex(/^[0-9a-f]{64}$/u);
2819
2861
  z.object({
2820
2862
  agentId: idSchema,
2821
2863
  trigger: z.enum(agentBackupTriggers),
2822
2864
  kikimoraVersion: z.string().min(1).max(64),
2823
- entries: z.coerce.number().int().nonnegative()
2865
+ entries: z.coerce.number().int().nonnegative(),
2866
+ contentDigest: agentBackupContentDigestSchema.optional()
2824
2867
  });
2825
2868
  z.object({ backupId: idSchema });
2869
+ z.object({
2870
+ agentId: idSchema,
2871
+ contentDigest: agentBackupContentDigestSchema
2872
+ });
2873
+ z.object({ backupId: idSchema.nullable() });
2826
2874
  [
2827
2875
  "application/vnd.oci.image.index.v1+json",
2828
2876
  "application/vnd.oci.image.manifest.v1+json",
@@ -2830,21 +2878,12 @@ z.object({ backupId: idSchema });
2830
2878
  "application/vnd.docker.distribution.manifest.v2+json"
2831
2879
  ].join(", ");
2832
2880
  const pinnedPlatformImagePattern = /^[a-z0-9][a-z0-9.-]*(?::[0-9]{1,5})?(?:\/[a-z0-9][a-z0-9._-]*)+@sha256:[0-9a-f]{64}$/;
2833
- //#endregion
2834
- //#region ../../packages/contracts/src/infrastructure.ts
2835
- const volumeRoles = ["data", "agents"];
2836
- const volumeRoleSchema = z.enum(volumeRoles);
2881
+ const volumeRoleSchema = z.enum(["data", "agents"]);
2837
2882
  const filesystemNameSchema = z.enum([
2838
2883
  "root",
2839
2884
  "data",
2840
2885
  "agents"
2841
2886
  ]);
2842
- z.enum([
2843
- "digitalOcean",
2844
- "spaces",
2845
- "ghcr",
2846
- "backup"
2847
- ]);
2848
2887
  const backupKindSchema = z.enum(["scheduled", "manual"]);
2849
2888
  const backupStatusSchema = z.enum([
2850
2889
  "running",
@@ -2888,65 +2927,42 @@ const sha256HexPattern = /^[0-9a-f]{64}$/;
2888
2927
  const platformSecretPattern = /^[\x21-\x7e]+$/;
2889
2928
  const instantSchema$1 = z.iso.datetime();
2890
2929
  const byteCountSchema$1 = z.int().nonnegative();
2891
- const platformSecretSchema = z.string().trim().min(1).max(maxPlatformSecretLength).regex(platformSecretPattern, { message: "expected printable ASCII characters without spaces" });
2930
+ z.string().trim().min(1).max(maxPlatformSecretLength).regex(platformSecretPattern, { message: "expected printable ASCII characters without spaces" });
2892
2931
  const secretPresenceSchema = z.object({ present: z.boolean() }).meta({ id: "SecretPresence" });
2893
2932
  const regionSchema = z.string().regex(regionSlugPattern);
2933
+ const bucketNameSchema = z.string().regex(bucketNamePattern);
2894
2934
  const platformVolumeSchema = z.object({
2895
2935
  role: volumeRoleSchema,
2896
2936
  id: z.guid(),
2897
2937
  name: z.string().regex(volumeNamePattern)
2898
2938
  }).meta({ id: "PlatformVolume" });
2899
- const oneVolumePerRole = (volumes) => volumes.length === volumeRoles.length && volumeRoles.every((role) => volumes.some((volume) => volume.role === role));
2900
- const platformVolumesSchema = z.array(platformVolumeSchema).refine(oneVolumePerRole, { message: `expected exactly one volume for each role: ${volumeRoles.join(", ")}` });
2939
+ const countOfRole = (volumes, role) => volumes.filter((volume) => volume.role === role).length;
2940
+ const oneDataVolumeAndAtMostOneAgentsVolume = (volumes) => countOfRole(volumes, "data") === 1 && countOfRole(volumes, "agents") <= 1;
2941
+ const platformVolumesSchema = z.array(platformVolumeSchema).refine(oneDataVolumeAndAtMostOneAgentsVolume, { message: "expected one data volume and at most one agents volume" });
2901
2942
  const isOrigin = (value) => {
2902
2943
  if (!URL.canParse(value)) return false;
2903
2944
  const url = new URL(value);
2904
2945
  return url.pathname === "/" && url.search === "" && url.hash === "" && url.username === "" && url.password === "";
2905
2946
  };
2906
2947
  const spacesEndpointSchema = z.url({ protocol: /^https?$/ }).max(2e3).refine(isOrigin, { message: "expected an origin without a path, a query, a fragment or credentials" });
2907
- const digitalOceanShape = {
2908
- dropletId: z.int().positive(),
2909
- region: regionSchema,
2910
- reservedIp: z.ipv4(),
2911
- volumes: platformVolumesSchema
2912
- };
2913
- const spacesShape = {
2914
- endpoint: spacesEndpointSchema,
2915
- region: regionSchema,
2916
- bucket: z.string().regex(bucketNamePattern)
2917
- };
2918
2948
  const backupSettingsSchema = z.object({ retentionDays: z.int().min(1).max(maxBackupRetentionDays) }).meta({ id: "BackupSettings" });
2919
2949
  z.object({
2920
- version: z.int().nonnegative(),
2921
2950
  instance: z.string(),
2922
2951
  domain: z.string(),
2923
2952
  digitalOcean: z.object({
2924
- ...digitalOceanShape,
2925
- token: secretPresenceSchema
2953
+ dropletId: z.int().positive(),
2954
+ region: regionSchema,
2955
+ reservedIp: z.ipv4(),
2956
+ volumes: platformVolumesSchema
2926
2957
  }).nullable(),
2927
2958
  spaces: z.object({
2928
- ...spacesShape,
2929
- accessKey: secretPresenceSchema,
2930
- secretKey: secretPresenceSchema
2959
+ endpoint: spacesEndpointSchema,
2960
+ region: regionSchema,
2961
+ bucket: bucketNameSchema
2931
2962
  }).nullable(),
2932
2963
  ghcrToken: secretPresenceSchema,
2933
- backup: backupSettingsSchema,
2934
- updatedAt: instantSchema$1
2964
+ backup: backupSettingsSchema
2935
2965
  }).meta({ id: "PlatformSettings" });
2936
- z.object({
2937
- version: z.int().nonnegative(),
2938
- digitalOcean: z.object({
2939
- ...digitalOceanShape,
2940
- token: platformSecretSchema.nullable()
2941
- }).nullable(),
2942
- spaces: z.object({
2943
- ...spacesShape,
2944
- accessKey: platformSecretSchema.nullable(),
2945
- secretKey: platformSecretSchema.nullable()
2946
- }).nullable(),
2947
- ghcrToken: platformSecretSchema.nullable(),
2948
- backup: backupSettingsSchema.nullable()
2949
- }).meta({ id: "UpdatePlatformSettingsBody" });
2950
2966
  const storageReadingSchema = z.discriminatedUnion("measured", [z.object({
2951
2967
  measured: z.literal(true),
2952
2968
  path: z.string(),
@@ -3010,8 +3026,6 @@ z.object({
3010
3026
  dockerRootDir: z.string().nullable()
3011
3027
  })
3012
3028
  }).meta({ id: "PlatformNode" });
3013
- z.object({ role: volumeRoleSchema });
3014
- z.object({ sizeGb: z.int().positive() }).meta({ id: "ResizeVolumeBody" });
3015
3029
  const requesterSchema = z.object({
3016
3030
  userId: idSchema,
3017
3031
  label: z.string().nullable()
@@ -3137,7 +3151,6 @@ const masterKeyVersionSchema = z.object({
3137
3151
  secrets: z.object({
3138
3152
  credentials: secretCountSchema,
3139
3153
  toolSecrets: secretCountSchema,
3140
- platformSecrets: secretCountSchema,
3141
3154
  providerAccounts: secretCountSchema.default(0)
3142
3155
  }),
3143
3156
  backups: secretCountSchema,
@@ -3148,12 +3161,15 @@ z.object({
3148
3161
  versions: z.array(masterKeyVersionSchema),
3149
3162
  rotationPending: z.boolean()
3150
3163
  }).meta({ id: "MasterKey" });
3151
- const metricsRangeSchema = z.enum([
3164
+ //#endregion
3165
+ //#region ../../packages/contracts/src/metrics.ts
3166
+ const metricsRanges = [
3152
3167
  "1h",
3153
3168
  "24h",
3154
3169
  "7d",
3155
3170
  "90d"
3156
- ]);
3171
+ ];
3172
+ const metricsRangeSchema = z.enum(metricsRanges);
3157
3173
  const agentMetricsRanges = [
3158
3174
  "1h",
3159
3175
  "24h",
@@ -3207,18 +3223,13 @@ const agentUsageSchema = z.object({
3207
3223
  blockReadBytes: byteCountSchema,
3208
3224
  blockWriteBytes: byteCountSchema
3209
3225
  }).meta({ id: "AgentUsage" });
3210
- const nodeHostSeriesSchema = z.object({
3211
- nodeId: idSchema,
3212
- nodeName: z.string(),
3213
- points: z.array(hostPointSchema)
3214
- }).meta({ id: "NodeHostSeries" });
3215
3226
  z.object({
3216
3227
  resolution: metricsResolutionSchema,
3217
3228
  from: instantSchema,
3218
3229
  to: instantSchema,
3219
- nodes: z.array(nodeHostSeriesSchema),
3230
+ points: z.array(hostPointSchema),
3220
3231
  agents: z.array(agentUsageSchema)
3221
- }).meta({ id: "HostMetrics" });
3232
+ }).meta({ id: "NodeMetrics" });
3222
3233
  z.object({
3223
3234
  resolution: metricsResolutionSchema,
3224
3235
  from: instantSchema,
@@ -3255,7 +3266,8 @@ const notificationTargetTypeSchema = z.enum([
3255
3266
  "backup",
3256
3267
  "queue",
3257
3268
  "volume",
3258
- "platform"
3269
+ "platform",
3270
+ "node"
3259
3271
  ]);
3260
3272
  const hostOperationOutcomes = [
3261
3273
  "succeeded",
@@ -4160,6 +4172,13 @@ const nodeDetailOf = async (api, { organizationId, nodeId }) => dataOf("describe
4160
4172
  organizationId,
4161
4173
  nodeId
4162
4174
  } } }), nodeHints);
4175
+ const nodeMetricsOf = async (api, { organizationId, nodeId }, range) => dataOf("show what the node used", await api.GET("/organizations/{organizationId}/nodes/{nodeId}/metrics", { params: {
4176
+ path: {
4177
+ organizationId,
4178
+ nodeId
4179
+ },
4180
+ query: { range }
4181
+ } }), nodeHints);
4163
4182
  const createdNode = async (api, organizationId, body) => {
4164
4183
  validated(createNodeBodySchema, body, "a node");
4165
4184
  return providerDataOf("create the node", await api.POST("/organizations/{organizationId}/nodes", {
@@ -4271,7 +4290,8 @@ const agentHints = {
4271
4290
  "agent.busy": "The agent is working on another action; try again in a moment.",
4272
4291
  "concurrency.conflict": "Someone changed the agent meanwhile; read it again and retry.",
4273
4292
  "agent.node_unsuitable": "The node lacks a capability the agent requires; kcloud nodes show <node> lists what it offers.",
4274
- "node.offline": "The node of the agent is not connected; kcloud nodes show <node> tells when it was last seen."
4293
+ "node.offline": "The node of the agent is not connected; kcloud nodes show <node> tells when it was last seen.",
4294
+ "node.outdated": "The node of the agent runs an older release; kcloud nodes show <node> names the release it runs and its update."
4275
4295
  };
4276
4296
  const agentFrom = async (run, reference, project) => {
4277
4297
  if (reference === void 0 || reference === "") throw new UsageError("This command needs an agent.", "Name it as <project>/<agent>, or pass --project.");
@@ -4495,6 +4515,23 @@ const enforcementText = ({ state, reason }) => {
4495
4515
  case "unsupported": return `The node holds no pool to a limit: ${reason === null ? "it cannot" : unsupportedReasons[reason] ?? reason}.`;
4496
4516
  }
4497
4517
  };
4518
+ const agentsSliceReasons = {
4519
+ cgroup_driver: "Docker on the host does not use the systemd cgroup driver",
4520
+ cgroup_driver_unread: "the node has not read the cgroup driver of Docker yet",
4521
+ host_unread: "the node has not read its host yet",
4522
+ configure_failed: "systemd did not take the limits",
4523
+ pending: "the node has not applied the limit yet",
4524
+ platform: "the node runs no pool service"
4525
+ };
4526
+ const agentsSliceText = ({ state, reason }) => {
4527
+ switch (state) {
4528
+ case "enforced": return "The node holds its agents and their sessions together to the host minus its reserve.";
4529
+ case "unsplit": return "The node has no split, so its agents outside their sessions have no limit.";
4530
+ case "unsupported": return `The node holds its agents outside their sessions to no limit: ${reason === null ? "it cannot" : agentsSliceReasons[reason] ?? reason}.`;
4531
+ }
4532
+ };
4533
+ const refusedText = ({ count, pool, lastAt, reason }) => `The session starter refused ${count === 1 ? "1 session" : `${count} sessions`} of the ${pool} pool in the last hour, the last at ${lastAt}: ${reason}`;
4534
+ const layoutTexts = (layout, split) => [...layout.topologyError === null ? [] : [`The node did not read its processor cores, so each logical processor counts as a core and a pool can share a physical core: ${layout.topologyError}`], ...layout.clamped && layoutAppliesSplit(layout, split) ? ["The split does not fit the cores of the node, so its pools run on fewer cores than the split asks for; kcloud nodes split <node> shows the cores each pool takes."] : []];
4498
4535
  const memoryOf$1 = (pool) => {
4499
4536
  if (pool.usage === null) return "-";
4500
4537
  const { memoryBytes, memoryMaxBytes } = pool.usage;
@@ -4549,10 +4586,13 @@ const queueOf = (pools) => {
4549
4586
  { header: "WORK" }
4550
4587
  ], waiting);
4551
4588
  };
4552
- const poolsText = (pools) => {
4589
+ const poolsText = (pools, split) => {
4553
4590
  const holders = holdersOf(pools);
4554
4591
  return [
4555
4592
  enforcementText(pools.enforcement),
4593
+ ...pools.agentsSlice == null ? [] : [agentsSliceText(pools.agentsSlice)],
4594
+ ...pools.refused == null ? [] : [refusedText(pools.refused)],
4595
+ ...pools.layout == null ? [] : layoutTexts(pools.layout, split),
4556
4596
  renderTable([
4557
4597
  { header: "POOL" },
4558
4598
  { header: "SLOTS IN USE" },
@@ -4603,8 +4643,36 @@ const agentPlanOf = (agent) => ({
4603
4643
  prompts: agent.prompts,
4604
4644
  settings: agent.settings,
4605
4645
  timezone: agent.timezone,
4606
- machine: agent.machine
4646
+ machine: {
4647
+ docker: agent.machine.docker,
4648
+ browser: agent.machine.browser,
4649
+ diskGb: agent.machine.diskGb
4650
+ }
4607
4651
  });
4652
+ const machineLimitKeys = /* @__PURE__ */ new Set(["cpus", "memoryMb"]);
4653
+ const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
4654
+ const warnIgnored = ({ context }, keys) => {
4655
+ const named = keys.map((key) => `machine.${key}`).join(" and ");
4656
+ context.terminal.error(context.terminal.errorStyles.warning(`warning ${named} ${keys.length === 1 ? "is" : "are"} ignored: an agent has no limits of its own, and the pools of its node limit its sessions.`));
4657
+ };
4658
+ const machineWithoutLimits = (run, machine) => {
4659
+ if (!isRecord(machine)) return machine;
4660
+ const ignored = Object.keys(machine).filter((key) => machineLimitKeys.has(key));
4661
+ if (ignored.length === 0) return machine;
4662
+ warnIgnored(run, ignored);
4663
+ return Object.fromEntries(Object.entries(machine).filter(([key]) => !machineLimitKeys.has(key)));
4664
+ };
4665
+ const agentFileWithoutLimits = (run, document) => isRecord(document) && "machine" in document ? {
4666
+ ...document,
4667
+ machine: machineWithoutLimits(run, document.machine)
4668
+ } : document;
4669
+ const agentChangeWithoutLimits = (run, document) => isRecord(document) && isRecord(document.sections) && "machine" in document.sections ? {
4670
+ ...document,
4671
+ sections: {
4672
+ ...document.sections,
4673
+ machine: machineWithoutLimits(run, document.sections.machine)
4674
+ }
4675
+ } : document;
4608
4676
  //#endregion
4609
4677
  //#region src/commands/agents/config.ts
4610
4678
  const agentsOf = async (scope, project) => {
@@ -4715,7 +4783,7 @@ const configCommands = (adaptersOf) => [
4715
4783
  const agentId = await agentFrom(run, invocation.positionals[0], invocation.values.project);
4716
4784
  const file = invocation.values.file;
4717
4785
  if (file === void 0) throw new UsageError("agents set needs --file <path>.", "The file holds the sections to change; kcloud agents show <agent> --json shows the current ones.");
4718
- const agent = await changedAgent(run.api, agentId, await documentOf(run, file));
4786
+ const agent = await changedAgent(run.api, agentId, agentChangeWithoutLimits(run, await documentOf(run, file)));
4719
4787
  return printed(run, agent, (changed) => changed.restartRequired ? `Changed ${changed.name}; it needs kcloud agents update to take the change.` : `Changed ${changed.name}.`);
4720
4788
  }
4721
4789
  }),
@@ -4781,7 +4849,7 @@ const detailsOf = (agent) => [
4781
4849
  ["node", agent.node === null ? "none" : `${agent.node.name} (${agent.node.state})`],
4782
4850
  ["requires", agent.requiredCapabilities.length === 0 ? "nothing" : agent.requiredCapabilities.join(", ")],
4783
4851
  ["timezone", agent.timezone],
4784
- ["machine", `${agent.machine.cpus} vCPU, ${agent.machine.memoryMb} MB`],
4852
+ ["disk", `${String(agent.machine.diskGb)} GB`],
4785
4853
  ["docker", agent.machine.docker ? "yes" : "no"],
4786
4854
  ["browser", agent.machine.browser ? "yes" : "no"],
4787
4855
  ["container", agent.runtime.containerId ?? "none"],
@@ -4976,15 +5044,13 @@ const askedPlan = async (run, projectId, values, required) => {
4976
5044
  timezone: values.timezone ?? localTimezone(),
4977
5045
  machine: {
4978
5046
  docker: values.docker,
4979
- browser: values.browser,
4980
- cpus: 2,
4981
- memoryMb: defaultAgentMemoryMb
5047
+ browser: values.browser
4982
5048
  }
4983
5049
  };
4984
5050
  };
4985
5051
  const isObject = (document) => typeof document === "object" && document !== null && !Array.isArray(document);
4986
5052
  const fileOf = async (run, projectId, { file, node }, required) => {
4987
- const document = await documentOf(run, file);
5053
+ const document = agentFileWithoutLimits(run, await documentOf(run, file));
4988
5054
  const placed = !isObject(document) || node === void 0 && "nodeId" in document ? document : {
4989
5055
  ...document,
4990
5056
  nodeId: await placedNodeId(run, {
@@ -6689,6 +6755,115 @@ const agentTools = [
6689
6755
  run: async ({ action, now, ...reference }, run) => actionOn(run, await agentIdOf(run, reference), action, action === "stop" && now)
6690
6756
  })
6691
6757
  ];
6758
+ const percentOf = (ratio) => ratio === null ? "-" : `${(ratio * 100).toFixed(1)}%`;
6759
+ const disksOf = (point) => [
6760
+ point.diskRootUsedRatio,
6761
+ point.diskDataUsedRatio,
6762
+ point.diskAgentsUsedRatio
6763
+ ].map(percentOf).join(" / ");
6764
+ const gpuMemoryOf = ({ gpuMemoryUsedBytesAvg: used, gpuMemoryTotalBytes: total }) => used === null || total === null ? "-" : `${bytesOf(used)} of ${bytesOf(total)}`;
6765
+ const gpuOf = (point) => `${percentOf(point.gpuUtilRatioAvg)}, ${gpuMemoryOf(point)}`;
6766
+ const readsAGpu = (points) => points.some((point) => point.gpuUtilRatioAvg !== null || point.gpuMemoryTotalBytes !== null);
6767
+ const hostText$1 = (points) => {
6768
+ const gpu = readsAGpu(points);
6769
+ return renderTable([
6770
+ { header: "AT" },
6771
+ {
6772
+ header: "CPU",
6773
+ align: "right"
6774
+ },
6775
+ {
6776
+ header: "MEMORY",
6777
+ align: "right"
6778
+ },
6779
+ {
6780
+ header: "LOAD",
6781
+ align: "right"
6782
+ },
6783
+ {
6784
+ header: "DISKS ROOT / DATA / AGENTS",
6785
+ align: "right"
6786
+ },
6787
+ ...gpu ? [{
6788
+ header: "GPU",
6789
+ align: "right"
6790
+ }] : []
6791
+ ], points.map((point) => [
6792
+ point.ts,
6793
+ percentOf(point.cpuRatioAvg),
6794
+ `${bytesOf(point.memoryUsedBytesAvg)} of ${bytesOf(point.memoryTotalBytes)}`,
6795
+ point.load1Avg === null ? "-" : point.load1Avg.toFixed(2),
6796
+ disksOf(point),
6797
+ ...gpu ? [gpuOf(point)] : []
6798
+ ]));
6799
+ };
6800
+ const byFootprint = (left, right) => right.memoryBytesAvg - left.memoryBytesAvg || left.agentName.localeCompare(right.agentName);
6801
+ const agentsText = (agents) => renderTable([
6802
+ { header: "PROJECT" },
6803
+ { header: "AGENT" },
6804
+ {
6805
+ header: "CPU (CORES)",
6806
+ align: "right"
6807
+ },
6808
+ {
6809
+ header: "MEMORY AVG",
6810
+ align: "right"
6811
+ },
6812
+ {
6813
+ header: "MEMORY MAX",
6814
+ align: "right"
6815
+ },
6816
+ {
6817
+ header: "NETWORK IN / OUT",
6818
+ align: "right"
6819
+ },
6820
+ {
6821
+ header: "DISK READ / WRITE",
6822
+ align: "right"
6823
+ }
6824
+ ], agents.toSorted(byFootprint).map((usage) => [
6825
+ usage.projectName,
6826
+ usage.agentName,
6827
+ usage.cpuRatioAvg === null ? "-" : usage.cpuRatioAvg.toFixed(2),
6828
+ bytesOf(usage.memoryBytesAvg),
6829
+ bytesOf(usage.memoryBytesMax),
6830
+ `${bytesOf(usage.networkRxBytes)} / ${bytesOf(usage.networkTxBytes)}`,
6831
+ `${bytesOf(usage.blockReadBytes)} / ${bytesOf(usage.blockWriteBytes)}`
6832
+ ]));
6833
+ const nodeMetricsText = (metrics) => [
6834
+ `${metrics.from} to ${metrics.to}, one point per ${metrics.resolution}.`,
6835
+ "",
6836
+ metrics.points.length === 0 ? "The node sent no sample in that range." : hostText$1(metrics.points),
6837
+ "",
6838
+ metrics.agents.length === 0 ? "No agent you can see ran on the node in that range." : agentsText(metrics.agents)
6839
+ ].join("\n");
6840
+ const rangeOf = (given) => {
6841
+ if (given === void 0) return "1h";
6842
+ const range = metricsRanges.find((candidate) => candidate === given);
6843
+ if (range === void 0) throw new UsageError(`--range ${given} is not a range of metrics.`, `The ranges are ${metricsRanges.join(", ")}.`);
6844
+ return range;
6845
+ };
6846
+ const createNodeMetricsCommand = (adaptersOf) => defineCommand({
6847
+ name: "metrics",
6848
+ summary: "Show what the host of a node and the agents on it used over a range.",
6849
+ usage: "nodes metrics <node> [--range <range>]",
6850
+ optionsUsage: [
6851
+ ...nodeUsage,
6852
+ ["--range <range>", `how far back: ${metricsRanges.join(", ")} (default: 1h)`],
6853
+ ["", "the agents are those of the projects you belong to, or all for an admin"]
6854
+ ],
6855
+ options: { range: { type: "string" } },
6856
+ positionals: {
6857
+ min: 1,
6858
+ max: 1
6859
+ },
6860
+ run: async (invocation) => {
6861
+ const range = rangeOf(invocation.values.range);
6862
+ const run = await runOf(adaptersOf, invocation);
6863
+ const reference = await resolveNode(run, invocation.positionals[0] ?? "");
6864
+ return printed(run, await nodeMetricsOf(run.api, reference, range), nodeMetricsText);
6865
+ }
6866
+ });
6692
6867
  //#endregion
6693
6868
  //#region src/commands/nodes/split.ts
6694
6869
  const splitFields = [
@@ -6744,6 +6919,8 @@ const changeOfParts = ({ reserve, monitor, executor }) => {
6744
6919
  const cpusText = (cpus) => String(Math.round(cpus * 100) / 100);
6745
6920
  const hostText = (host) => `${cpusText(host.cpus)} CPU and ${hostMemoryMbOf(host)} MB`;
6746
6921
  const excessText = ({ cpus, memoryMb }) => [...cpus > 0 ? [`${cpusText(cpus)} CPU`] : [], ...memoryMb > 0 ? [`${memoryMb} MB`] : []].join(" and ");
6922
+ const coresText = (count) => count === 1 ? "1 core" : `${String(count)} cores`;
6923
+ const coresOfNode = (node) => node.runtime?.pools.layout?.cores ?? null;
6747
6924
  const fittingSplit = (node, change, nameOf) => {
6748
6925
  const merged = {
6749
6926
  ...node.split === null ? {} : valuesOf(node.split),
@@ -6757,9 +6934,25 @@ const fittingSplit = (node, change, nameOf) => {
6757
6934
  validated(nodeSplitSchema, split, "a split of a node");
6758
6935
  if (node.host === null) throw new CliError(`${node.name} has not reported its CPUs and memory yet, so it cannot be split.`, "The node reports them once its service runs; kcloud nodes show <node> shows its state.");
6759
6936
  if (!splitFits(split, node.host)) throw new CliError(`The split asks for ${excessText(splitOverflowOf(split, node.host))} more than ${node.name} has.`, `${node.name} has ${hostText(node.host)}; shrink a pool or the reserve.`);
6937
+ const cores = coresOfNode(node);
6938
+ const fit = cores === null ? null : splitCoresOf(split, cores);
6939
+ if (fit !== null && !fit.fits) throw new CliError(`The pools take whole cores of ${node.name}: the executor pool would get ${coresText(fit.executor)} and the monitor pool ${coresText(fit.monitor)} of ${String(fit.cores)}, and the reserve keeps at least 1.`, "Shrink a pool, so the reserve keeps the core of processor 0.");
6760
6940
  return split;
6761
6941
  };
6942
+ const processorCountsOf = (node) => {
6943
+ const pools = node.runtime?.pools;
6944
+ if (pools === void 0 || !layoutAppliesSplit(pools.layout, node.split)) return null;
6945
+ const monitor = pools.monitor.usage?.processors ?? null;
6946
+ const executor = pools.executor.usage?.processors ?? null;
6947
+ return monitor === null && executor === null ? null : {
6948
+ monitor,
6949
+ executor
6950
+ };
6951
+ };
6952
+ const processorsText = (count) => count === 1 ? "1 processor" : `${String(count)} processors`;
6953
+ const poolCpusText = (cpus, processors) => processors === null ? cpusText(cpus) : `${cpusText(cpus)} (${processorsText(processors)})`;
6762
6954
  const splitViewOf = (node) => {
6955
+ const cores = coresOfNode(node);
6763
6956
  const overflow = node.split === null || node.host === null ? null : splitOverflowOf(node.split, node.host);
6764
6957
  return {
6765
6958
  id: node.id,
@@ -6770,6 +6963,8 @@ const splitViewOf = (node) => {
6770
6963
  memoryMb: hostMemoryMbOf(node.host)
6771
6964
  },
6772
6965
  split: node.split,
6966
+ processors: processorCountsOf(node),
6967
+ cores: cores === null || node.split === null ? null : splitCoresOf(node.split, cores),
6773
6968
  free: overflow === null ? null : {
6774
6969
  cpus: -overflow.cpus,
6775
6970
  memoryMb: -overflow.memoryMb
@@ -6779,10 +6974,12 @@ const splitViewOf = (node) => {
6779
6974
  const resourceCells = ({ cpus, memoryMb }) => [cpusText(cpus), `${memoryMb} MB`];
6780
6975
  const oneSlotOf = ({ cpus, memoryMb, slots }) => `${cpusText(cpus / slots)} CPU, ${Math.floor(memoryMb / slots)} MB`;
6781
6976
  const hostLineOf = (view) => view.host === null ? `${view.name} has not reported its CPUs and memory yet.` : `The host has ${cpusText(view.host.cpus)} CPU and ${view.host.memoryMb} MB.`;
6977
+ const coresLineOf = ({ cores, executor, monitor, fits }) => fits ? `The pools take whole cores: the executor pool ${coresText(executor)} and the monitor pool ${coresText(monitor)}; the reserve keeps ${coresText(cores - executor - monitor)} of ${String(cores)}.` : `The executor pool would take ${coresText(executor)} and the monitor pool ${coresText(monitor)} of ${String(cores)}, so the node runs them on fewer cores and keeps 1 for the reserve.`;
6782
6978
  const splitTableOf = (view, split) => {
6783
6979
  const poolRow = (role) => [
6784
6980
  role,
6785
- ...resourceCells(split[role]),
6981
+ poolCpusText(split[role].cpus, view.processors?.[role] ?? null),
6982
+ `${String(split[role].memoryMb)} MB`,
6786
6983
  String(split[role].slots),
6787
6984
  oneSlotOf(split[role])
6788
6985
  ];
@@ -6830,7 +7027,8 @@ const splitTableOf = (view, split) => {
6830
7027
  { header: "ONE SLOT" }
6831
7028
  ], rows),
6832
7029
  ...view.host === null ? ["", hostLineOf(view)] : [],
6833
- ...over === "" ? [] : ["", `The split asks for ${over} more than the host has.`]
7030
+ ...over === "" ? [] : ["", `The split asks for ${over} more than the host has.`],
7031
+ ...view.cores === null ? [] : ["", coresLineOf(view.cores)]
6834
7032
  ].join("\n");
6835
7033
  };
6836
7034
  const splitText = (view) => view.split === null ? [
@@ -6838,10 +7036,14 @@ const splitText = (view) => view.split === null ? [
6838
7036
  hostLineOf(view),
6839
7037
  `kcloud nodes split set ${view.name} with every part splits it.`
6840
7038
  ].join("\n") : splitTableOf(view, view.split);
6841
- const splitSummaryOf = (split) => {
7039
+ const splitSummaryOf = (split, processors = null) => {
6842
7040
  if (split === null) return "none, slots counted from the host without hard limits";
6843
7041
  const { reserve, monitor, executor } = split;
6844
- const pool = (role, { cpus, memoryMb, slots }) => `${role} ${cpusText(cpus)} CPU, ${memoryMb} MB, ${slots} slots`;
7042
+ const pool = (role, { cpus, memoryMb, slots }) => {
7043
+ const count = processors?.[role] ?? null;
7044
+ const onProcessors = count === null ? "" : ` (${processorsText(count)})`;
7045
+ return `${role} ${cpusText(cpus)} CPU${onProcessors}, ${memoryMb} MB, ${slots} slots`;
7046
+ };
6845
7047
  return [
6846
7048
  `reserve ${cpusText(reserve.cpus)} CPU, ${reserve.memoryMb} MB`,
6847
7049
  pool("monitor", monitor),
@@ -7023,6 +7225,18 @@ const nodeTools = [
7023
7225
  input: z.object({ node: nodeField }),
7024
7226
  run: async ({ node }, run) => nodeDetailOf(run.api, await resolveNode(run, node))
7025
7227
  }),
7228
+ defineTool({
7229
+ name: "node_metrics",
7230
+ title: "Show what the host of a node and its agents used",
7231
+ description: "Show the CPU, memory, load, disks and GPU of the host of a node over a range, and what each agent on it used over the whole range: the agents of the projects the caller belongs to, or every agent for an admin of the organization.",
7232
+ operations: ["getNodeMetrics"],
7233
+ safety: "read",
7234
+ input: z.object({
7235
+ node: nodeField,
7236
+ range: z.enum(metricsRanges).default("1h")
7237
+ }),
7238
+ run: async ({ node, range }, run) => nodeMetricsOf(run.api, await resolveNode(run, node), range)
7239
+ }),
7026
7240
  defineTool({
7027
7241
  name: "node_split_set",
7028
7242
  title: "Split a node into pools",
@@ -9663,6 +9877,7 @@ const capabilitiesOf = (node) => {
9663
9877
  const { sandbox, uidIsolation } = node.capabilities;
9664
9878
  return `sandbox ${sandbox ? "yes" : "no"}, uid isolation ${uidIsolation ? "yes" : "no"}`;
9665
9879
  };
9880
+ const stateOf = (node) => node.state === "outdated" ? `outdated: runs ${node.version ?? "-"}, needs an update to ${minimumNodeVersion} or later` : node.state;
9666
9881
  const registrationOf = (node) => node.registrationExpiresAt === null ? "-" : `a token waits until ${node.registrationExpiresAt}`;
9667
9882
  const storageOf = ({ state, freeBytes, totalBytes, path }) => `${state}, ${gibibytes(freeBytes)} free of ${gibibytes(totalBytes)} at ${path}`;
9668
9883
  const shapeText = ({ size, volumeGb }) => `${size} with a volume of ${volumeGb} GB`;
@@ -9684,14 +9899,23 @@ const operationRowsOf = (operation) => {
9684
9899
  ...error === null ? [] : [["error", error]]
9685
9900
  ];
9686
9901
  };
9687
- const runtimeRowsOf = (runtime) => [
9902
+ const lagOf = ({ oldestCursorLagSeconds }) => oldestCursorLagSeconds === null ? "-" : `${oldestCursorLagSeconds} s`;
9903
+ const sandboxRowsOf = ({ sandbox }) => sandbox.enabled ? [
9904
+ ["sandboxes", `enabled, image ${sandbox.image.tag}`],
9905
+ ["agents with docker", String(sandbox.agentsWithSandbox)],
9906
+ ["sidecars running", String(sandbox.sidecarsRunning)],
9907
+ ["sandbox volumes", sandbox.volumeBytes === null ? "not measured yet" : bytesOf(sandbox.volumeBytes)]
9908
+ ] : [["sandboxes", "disabled, SANDBOX_ENABLED is off on the node"]];
9909
+ const runtimeRowsOf = (runtime, engine) => [
9688
9910
  ["reported", runtime.reportedAt],
9689
9911
  ["docker", runtime.docker.lastError === null ? runtime.docker.state : `${runtime.docker.state}: ${runtime.docker.lastError}`],
9690
9912
  ["data storage", storageOf(runtime.storage.data)],
9691
9913
  ["engine storage", storageOf(runtime.storage.engine)],
9692
9914
  ["last reconcile", runtime.reconcile.lastTickError === null ? runtime.reconcile.lastTickAt ?? "never" : `${runtime.reconcile.lastTickAt ?? "never"}: ${runtime.reconcile.lastTickError}`],
9693
9915
  ["agents", `${runtime.reconcile.agentsActive} active, ${runtime.reconcile.agentsQueued} queued`],
9694
- ["log readers", String(runtime.ingest.readers)]
9916
+ ["log readers", String(runtime.ingest.readers)],
9917
+ ["oldest cursor lag", lagOf(runtime.ingest)],
9918
+ ...engine === "native" ? [] : sandboxRowsOf(runtime)
9695
9919
  ];
9696
9920
  const sessionsText = (count, noun) => `${String(count)} ${noun}${count === 1 ? "" : "s"}`;
9697
9921
  const heldPoolsOf = ({ held = [] }) => held;
@@ -9714,7 +9938,7 @@ const lastUpdateOf = ({ update }) => {
9714
9938
  const nodeDetailsText = (node) => [
9715
9939
  renderTable([{ header: "FIELD" }, { header: "VALUE" }], [
9716
9940
  ["name", node.name],
9717
- ["state", node.state],
9941
+ ["state", stateOf(node)],
9718
9942
  ["provider", node.provider],
9719
9943
  ...machineRowsOf(node.machine),
9720
9944
  ...operationRowsOf(node.operation),
@@ -9725,7 +9949,7 @@ const nodeDetailsText = (node) => [
9725
9949
  ["data root", node.host?.dataRoot ?? "-"],
9726
9950
  ["capabilities", capabilitiesOf(node)],
9727
9951
  ["offers", node.offered.length === 0 ? "-" : node.offered.join(", ")],
9728
- ["split", splitSummaryOf(node.split)],
9952
+ ["split", splitSummaryOf(node.split, processorCountsOf(node))],
9729
9953
  ["registration", registrationOf(node)],
9730
9954
  ["connected", node.connectedAt ?? "never"],
9731
9955
  ["last seen", node.lastSeenAt ?? "never"],
@@ -9751,9 +9975,9 @@ const nodeDetailsText = (node) => [
9751
9975
  ])),
9752
9976
  "",
9753
9977
  ...node.runtime === null ? ["The node has reported no runtime yet."] : [
9754
- renderTable([{ header: "RUNTIME" }, { header: "VALUE" }], runtimeRowsOf(node.runtime)),
9978
+ renderTable([{ header: "RUNTIME" }, { header: "VALUE" }], runtimeRowsOf(node.runtime, node.engine)),
9755
9979
  "",
9756
- poolsText(node.runtime.pools)
9980
+ poolsText(node.runtime.pools, node.split)
9757
9981
  ]
9758
9982
  ].join("\n");
9759
9983
  const installCommandsOf = (origin) => [
@@ -9880,6 +10104,7 @@ const createNodesCommand = (adaptersOf) => defineGroup({
9880
10104
  return printed(run, await nodeDetailOf(run.api, reference), nodeDetailsText);
9881
10105
  }
9882
10106
  }),
10107
+ createNodeMetricsCommand(adaptersOf),
9883
10108
  createSplitCommand(adaptersOf),
9884
10109
  defineCommand({
9885
10110
  name: "create",
package/package.json CHANGED
@@ -15,7 +15,7 @@
15
15
  "publishConfig": {
16
16
  "access": "public"
17
17
  },
18
- "version": "1.2.0",
18
+ "version": "1.4.0",
19
19
  "dependencies": {
20
20
  "@clack/prompts": "1.8.1",
21
21
  "@modelcontextprotocol/server": "2.1.0",